Do not enqueue no-op read receipt jobs.

This commit is contained in:
Cody Henthorne
2024-08-16 15:39:33 -04:00
committed by mtang-signal
parent cda029cd93
commit 4c9b5926b9
5 changed files with 313 additions and 362 deletions

View File

@@ -1,115 +0,0 @@
package org.thoughtcrime.securesms.notifications;
import android.content.Context;
import androidx.annotation.NonNull;
import com.annimon.stream.Stream;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.mockito.stubbing.Answer;
import org.signal.libsignal.protocol.util.Pair;
import org.thoughtcrime.securesms.database.MessageTable;
import org.thoughtcrime.securesms.database.model.MessageId;
import org.thoughtcrime.securesms.database.model.StoryType;
import org.thoughtcrime.securesms.dependencies.AppDependencies;
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.jobmanager.JobManager;
import org.thoughtcrime.securesms.jobs.MultiDeviceReadUpdateJob;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.Util;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class MarkReadReceiverTest {
@Rule
public MockitoRule rule = MockitoJUnit.rule();
@Mock
private MockedStatic<AppDependencies> applicationDependenciesMockedStatic;
@Mock
private MockedStatic<Recipient> recipientMockedStatic;
private final Context mockContext = mock(Context.class);
private final JobManager mockJobManager = mock(JobManager.class);
private final Recipient mockSelf = mock(Recipient.class);
private final List<Job> jobs = new LinkedList<>();
@Before
public void setUp() {
applicationDependenciesMockedStatic.when(AppDependencies::getJobManager).thenReturn(mockJobManager);
doAnswer((Answer<Void>) invocation -> {
jobs.add((Job) invocation.getArguments()[0]);
return null;
}).when(mockJobManager).add(any());
recipientMockedStatic.when(Recipient::self).thenReturn(mockSelf);
when(mockSelf.getId()).thenReturn(RecipientId.from(-1));
}
@Test
public void givenMultipleThreadsWithMultipleMessagesEach_whenIProcess_thenIProperlyGroupByThreadAndRecipient() {
// GIVEN
List<RecipientId> recipients = Stream.range(1L, 4L).map(RecipientId::from).toList();
List<Long> threads = Stream.range(4L, 7L).toList();
int expected = recipients.size() * threads.size() + 1;
List<MessageTable.MarkedMessageInfo> infoList = Stream.of(threads)
.flatMap(threadId -> Stream.of(recipients)
.map(recipientId -> createMarkedMessageInfo(threadId, recipientId)))
.toList();
List<MessageTable.MarkedMessageInfo> duplicatedList = Util.concatenatedList(infoList, infoList);
// WHEN
MarkReadReceiver.process(duplicatedList);
// THEN
assertEquals("Should have 10 total jobs, including MultiDeviceReadUpdateJob", expected, jobs.size());
Set<Pair<Long, String>> threadRecipientPairs = new HashSet<>();
Stream.of(jobs).forEach(job -> {
if (job instanceof MultiDeviceReadUpdateJob) {
return;
}
JsonJobData data = JsonJobData.deserialize(job.serialize());
long threadId = data.getLong("thread");
String recipientId = data.getString("recipient");
long[] messageIds = data.getLongArray("message_ids");
assertEquals("Each job should contain two messages.", 2, messageIds.length);
assertTrue("Each thread recipient pair should only exist once.", threadRecipientPairs.add(new Pair<>(threadId, recipientId)));
});
assertEquals("Should have 9 total combinations.", 9, threadRecipientPairs.size());
}
private MessageTable.MarkedMessageInfo createMarkedMessageInfo(long threadId, @NonNull RecipientId recipientId) {
return new MessageTable.MarkedMessageInfo(threadId,
new MessageTable.SyncMessageId(recipientId, 0),
new MessageId(1),
new MessageTable.ExpirationInfo(0, 0, 0, false),
StoryType.NONE);
}
}

View File

@@ -0,0 +1,104 @@
package org.thoughtcrime.securesms.notifications
import android.app.Application
import androidx.test.core.app.ApplicationProvider
import io.mockk.every
import io.mockk.mockkObject
import io.mockk.mockkStatic
import io.mockk.unmockkAll
import org.junit.After
import org.junit.Assert
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
import org.signal.libsignal.protocol.util.Pair
import org.thoughtcrime.securesms.database.MessageTable.ExpirationInfo
import org.thoughtcrime.securesms.database.MessageTable.MarkedMessageInfo
import org.thoughtcrime.securesms.database.MessageTable.SyncMessageId
import org.thoughtcrime.securesms.database.model.MessageId
import org.thoughtcrime.securesms.database.model.StoryType
import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.dependencies.MockApplicationDependencyProvider
import org.thoughtcrime.securesms.jobmanager.Job
import org.thoughtcrime.securesms.jobmanager.JobManager
import org.thoughtcrime.securesms.jobmanager.JsonJobData
import org.thoughtcrime.securesms.jobs.MultiDeviceReadUpdateJob
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.util.TextSecurePreferences
import java.util.LinkedList
@RunWith(RobolectricTestRunner::class)
@Config(manifest = Config.NONE, application = Application::class)
class MarkReadReceiverTest {
private val jobs: MutableList<Job> = LinkedList()
@Before
fun setUp() {
if (!AppDependencies.isInitialized) {
AppDependencies.init(
ApplicationProvider.getApplicationContext(),
MockApplicationDependencyProvider()
)
}
val jobManager: JobManager = AppDependencies.jobManager
every { jobManager.add(capture(jobs)) } returns Unit
mockkObject(Recipient)
every { Recipient.self() } returns Recipient()
mockkStatic(TextSecurePreferences::class)
every { TextSecurePreferences.isReadReceiptsEnabled(any()) } returns true
}
@After
fun tearDown() {
unmockkAll()
}
@Test
fun givenMultipleThreadsWithMultipleMessagesEach_whenIProcess_thenIProperlyGroupByThreadAndRecipient() {
// GIVEN
val recipients = (1L until 4L).map { id -> RecipientId.from(id) }
val threads = (4L until 7L).toList()
val expected = recipients.size * threads.size + 1
val infoList = threads.map { threadId -> recipients.map { recipientId -> createMarkedMessageInfo(threadId, recipientId) } }.flatten()
// WHEN
MarkReadReceiver.process(infoList + infoList)
// THEN
Assert.assertEquals("Should have 10 total jobs, including MultiDeviceReadUpdateJob", expected.toLong(), jobs.size.toLong())
val threadRecipientPairs: MutableSet<Pair<Long, String>> = HashSet()
jobs.forEach { job ->
if (job is MultiDeviceReadUpdateJob) {
return@forEach
}
val data = JsonJobData.deserialize(job.serialize())
val threadId = data.getLong("thread")
val recipientId = data.getString("recipient")
val messageIds = data.getLongArray("message_ids")
Assert.assertEquals("Each job should contain two messages.", 2, messageIds.size.toLong())
Assert.assertTrue("Each thread recipient pair should only exist once.", threadRecipientPairs.add(Pair(threadId, recipientId)))
}
Assert.assertEquals("Should have 9 total combinations.", 9, threadRecipientPairs.size.toLong())
}
private fun createMarkedMessageInfo(threadId: Long, recipientId: RecipientId): MarkedMessageInfo {
return MarkedMessageInfo(
threadId,
SyncMessageId(recipientId, 0),
MessageId(1),
ExpirationInfo(0, 0, 0, false),
StoryType.NONE
)
}
}