Improve message requests, add megaphone.

This commit is contained in:
Alex Hart
2020-02-19 18:08:34 -04:00
committed by Greyson Parrelli
parent dc689d325b
commit 9e5f64c431
83 changed files with 2406 additions and 735 deletions

View File

@@ -0,0 +1,102 @@
package org.thoughtcrime.securesms.jobmanager.migrations;
import org.junit.Test;
import org.thoughtcrime.securesms.database.MmsSmsDatabase;
import org.thoughtcrime.securesms.jobmanager.Data;
import org.thoughtcrime.securesms.jobmanager.JobMigration;
import org.thoughtcrime.securesms.jobs.SendReadReceiptJob;
import org.thoughtcrime.securesms.recipients.RecipientId;
import java.util.ArrayList;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.anyLong;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class SendReadReceiptsJobMigrationTest {
private final MmsSmsDatabase mockDatabase = mock(MmsSmsDatabase.class);
private final SendReadReceiptsJobMigration testSubject = new SendReadReceiptsJobMigration(mockDatabase);
@Test
public void givenSendReadReceiptJobDataWithoutThreadIdAndThreadIdFound_whenIMigrate_thenIInsertThreadId() {
// GIVEN
SendReadReceiptJob job = new SendReadReceiptJob(1, RecipientId.from(2), new ArrayList<>());
JobMigration.JobData jobData = new JobMigration.JobData(job.getFactoryKey(),
"asdf",
new Data.Builder()
.putString("recipient", RecipientId.from(2).serialize())
.putLongArray("message_ids", new long[]{1, 2, 3, 4, 5})
.putLong("timestamp", 292837649).build());
when(mockDatabase.getThreadForMessageId(anyLong())).thenReturn(1234L);
// WHEN
JobMigration.JobData result = testSubject.migrate(jobData);
// THEN
assertEquals(1234L, result.getData().getLong("thread"));
assertEquals(RecipientId.from(2).serialize(), result.getData().getString("recipient"));
assertTrue(result.getData().hasLongArray("message_ids"));
assertTrue(result.getData().hasLong("timestamp"));
}
@Test
public void givenSendReadReceiptJobDataWithoutThreadIdAndThreadIdNotFound_whenIMigrate_thenIGetAFailingJob() {
// GIVEN
SendReadReceiptJob job = new SendReadReceiptJob(1, RecipientId.from(2), new ArrayList<>());
JobMigration.JobData jobData = new JobMigration.JobData(job.getFactoryKey(),
"asdf",
new Data.Builder()
.putString("recipient", RecipientId.from(2).serialize())
.putLongArray("message_ids", new long[]{})
.putLong("timestamp", 292837649).build());
when(mockDatabase.getThreadForMessageId(anyLong())).thenReturn(-1L);
// WHEN
JobMigration.JobData result = testSubject.migrate(jobData);
// THEN
assertEquals("FailingJob", result.getFactoryKey());
}
@Test
public void givenSendReadReceiptJobDataWithThreadId_whenIMigrate_thenIDoNotReplace() {
// GIVEN
SendReadReceiptJob job = new SendReadReceiptJob(1, RecipientId.from(2), new ArrayList<>());
JobMigration.JobData jobData = new JobMigration.JobData(job.getFactoryKey(), "asdf", job.serialize());
// WHEN
JobMigration.JobData result = testSubject.migrate(jobData);
// THEN
assertEquals(jobData, result);
}
@Test
public void givenSomeOtherJobDataWithThreadId_whenIMigrate_thenIDoNotReplace() {
// GIVEN
JobMigration.JobData jobData = new JobMigration.JobData("SomeOtherJob", "asdf", new Data.Builder().putLong("thread", 1).build());
// WHEN
JobMigration.JobData result = testSubject.migrate(jobData);
// THEN
assertEquals(jobData, result);
}
@Test
public void givenSomeOtherJobDataWithoutThreadId_whenIMigrate_thenIDoNotReplace() {
// GIVEN
JobMigration.JobData jobData = new JobMigration.JobData("SomeOtherJob", "asdf", new Data.Builder().build());
// WHEN
JobMigration.JobData result = testSubject.migrate(jobData);
// THEN
assertEquals(jobData, result);
}
}

View File

@@ -0,0 +1,101 @@
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.Test;
import org.junit.runner.RunWith;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.thoughtcrime.securesms.database.MessagingDatabase;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.jobmanager.Data;
import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.jobmanager.JobManager;
import org.thoughtcrime.securesms.jobs.MultiDeviceReadUpdateJob;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.libsignal.util.Pair;
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.Matchers.any;
import static org.powermock.api.mockito.PowerMockito.doAnswer;
import static org.powermock.api.mockito.PowerMockito.mock;
import static org.powermock.api.mockito.PowerMockito.mockStatic;
import static org.powermock.api.mockito.PowerMockito.when;
@RunWith(PowerMockRunner.class)
@PrepareForTest(ApplicationDependencies.class)
public class MarkReadReceiverTest {
private final Context mockContext = mock(Context.class);
private final JobManager mockJobManager = mock(JobManager.class);
private final List<Job> jobs = new LinkedList<>();
@Before
public void setUp() {
mockStatic(ApplicationDependencies.class);
when(ApplicationDependencies.getJobManager()).thenReturn(mockJobManager);
doAnswer((Answer<Void>) invocation -> {
jobs.add((Job) invocation.getArguments()[0]);
return null;
}).when(mockJobManager).add(any());
}
@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<MessagingDatabase.MarkedMessageInfo> infoList = Stream.of(threads)
.flatMap(threadId -> Stream.of(recipients)
.map(recipientId -> createMarkedMessageInfo(threadId, recipientId)))
.toList();
List<MessagingDatabase.MarkedMessageInfo> duplicatedList = Util.concatenatedList(infoList, infoList);
// WHEN
MarkReadReceiver.process(mockContext, 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;
}
Data data = 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 MessagingDatabase.MarkedMessageInfo createMarkedMessageInfo(long threadId, @NonNull RecipientId recipientId) {
return new MessagingDatabase.MarkedMessageInfo(threadId,
new MessagingDatabase.SyncMessageId(recipientId, 0),
new MessagingDatabase.ExpirationInfo(0, 0, 0, false));
}
}

View File

@@ -0,0 +1,253 @@
package org.thoughtcrime.securesms.recipients;
import android.content.Context;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MmsSmsDatabase;
import org.thoughtcrime.securesms.database.RecipientDatabase;
import org.thoughtcrime.securesms.database.ThreadDatabase;
import org.thoughtcrime.securesms.util.FeatureFlags;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyLong;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.powermock.api.mockito.PowerMockito.mockStatic;
import static org.powermock.api.mockito.PowerMockito.when;
@RunWith(PowerMockRunner.class)
@PrepareForTest({DatabaseFactory.class, FeatureFlags.class})
public class RecipientUtilTest {
private Context context = mock(Context.class);
private Recipient recipient = mock(Recipient.class);
private ThreadDatabase mockThreadDatabase = mock(ThreadDatabase.class);
private MmsSmsDatabase mockMmsSmsDatabase = mock(MmsSmsDatabase.class);
private RecipientDatabase mockRecipientDatabase = mock(RecipientDatabase.class);
@Before
public void setUp() {
mockStatic(DatabaseFactory.class);
when(DatabaseFactory.getThreadDatabase(any())).thenReturn(mockThreadDatabase);
when(DatabaseFactory.getMmsSmsDatabase(any())).thenReturn(mockMmsSmsDatabase);
when(DatabaseFactory.getRecipientDatabase(any())).thenReturn(mockRecipientDatabase);
mockStatic(FeatureFlags.class);
when(FeatureFlags.messageRequests()).thenReturn(true);
when(recipient.getId()).thenReturn(RecipientId.from(5));
when(recipient.resolve()).thenReturn(recipient);
}
@Test
public void givenMessageRequestsFlagDisabled_whenIsThreadMessageRequestAccepted_thenIExpectTrue() {
// GIVEN
when(FeatureFlags.messageRequests()).thenReturn(false);
// WHEN
boolean result = RecipientUtil.isThreadMessageRequestAccepted(context, 1);
// THEN
assertTrue(result);
}
@Test
public void givenThreadIsNegativeOne_whenIsThreadMessageRequestAccepted_thenIExpectTrue() {
// WHEN
boolean result = RecipientUtil.isThreadMessageRequestAccepted(context, -1L);
// THEN
assertTrue(result);
}
@Test
public void givenRecipientIsNullForThreadId_whenIsThreadMessageRequestAccepted_thenIExpectTrue() {
// WHEN
boolean result = RecipientUtil.isThreadMessageRequestAccepted(context, 1L);
// THEN
assertTrue(result);
}
@Test
public void givenIHaveSentASecureMessageInThisThread_whenIsThreadMessageRequestAccepted_thenIExpectTrue() {
// GIVEN
when(mockThreadDatabase.getRecipientForThreadId(anyLong())).thenReturn(recipient);
when(mockMmsSmsDatabase.getOutgoingSecureConversationCount(1L)).thenReturn(5);
// WHEN
boolean result = RecipientUtil.isThreadMessageRequestAccepted(context, 1L);
// THEN
assertTrue(result);
}
@Test
public void givenIHaveNotSentASecureMessageInThisThreadAndIAmProfileSharing_whenIsThreadMessageRequestAccepted_thenIExpectTrue() {
// GIVEN
when(recipient.isProfileSharing()).thenReturn(true);
when(mockThreadDatabase.getRecipientForThreadId(anyLong())).thenReturn(recipient);
when(mockMmsSmsDatabase.getOutgoingSecureConversationCount(1L)).thenReturn(0);
// WHEN
boolean result = RecipientUtil.isThreadMessageRequestAccepted(context, 1L);
// THEN
assertTrue(result);
}
@Test
public void givenIHaveNotSentASecureMessageInThisThreadAndRecipientIsSystemContact_whenIsThreadMessageRequestAccepted_thenIExpectTrue() {
// GIVEN
when(recipient.isSystemContact()).thenReturn(true);
when(mockThreadDatabase.getRecipientForThreadId(anyLong())).thenReturn(recipient);
when(mockMmsSmsDatabase.getOutgoingSecureConversationCount(1L)).thenReturn(0);
// WHEN
boolean result = RecipientUtil.isThreadMessageRequestAccepted(context, 1L);
// THEN
assertTrue(result);
}
@Test
public void givenIHaveReceivedASecureMessageIHaveNotSentASecureMessageAndRecipientIsNotSystemContactAndNotProfileSharing_whenIsThreadMessageRequestAccepted_thenIExpectFalse() {
// GIVEN
when(mockThreadDatabase.getRecipientForThreadId(anyLong())).thenReturn(recipient);
when(mockMmsSmsDatabase.getOutgoingSecureConversationCount(1L)).thenReturn(0);
when(mockMmsSmsDatabase.getSecureConversationCount(1L)).thenReturn(5);
// WHEN
boolean result = RecipientUtil.isThreadMessageRequestAccepted(context, 1L);
// THEN
assertFalse(result);
}
@Test
public void givenIHaveNotReceivedASecureMessageIHaveNotSentASecureMessageAndRecipientIsNotSystemContactAndNotProfileSharing_whenIsThreadMessageRequestAccepted_thenIExpectTrue() {
// GIVEN
when(mockThreadDatabase.getRecipientForThreadId(anyLong())).thenReturn(recipient);
when(mockMmsSmsDatabase.getOutgoingSecureConversationCount(1L)).thenReturn(0);
when(mockMmsSmsDatabase.getSecureConversationCount(1L)).thenReturn(0);
// WHEN
boolean result = RecipientUtil.isThreadMessageRequestAccepted(context, 1L);
// THEN
assertTrue(result);
}
@Test
public void givenRecipientIsNull_whenIsRecipientMessageRequestAccepted_thenIExpectTrue() {
// WHEN
boolean result = RecipientUtil.isRecipientMessageRequestAccepted(context, null);
// THEN
assertTrue(result);
}
@Test
public void givenMessageRequestsFlagIsOff_whenIsRecipientMessageRequestAccepted_thenIExpectTrue() {
// GIVEN
when(FeatureFlags.messageRequests()).thenReturn(false);
// WHEN
boolean result = RecipientUtil.isRecipientMessageRequestAccepted(context, recipient);
// THEN
assertTrue(result);
}
@Test
public void givenNonZeroOutgoingSecureMessageCount_whenIsRecipientMessageRequestAccepted_thenIExpectTrue() {
// GIVEN
when(mockMmsSmsDatabase.getOutgoingSecureConversationCount(anyLong())).thenReturn(1);
// WHEN
boolean result = RecipientUtil.isRecipientMessageRequestAccepted(context, recipient);
// THEN
assertTrue(result);
}
@Test
public void givenIAmProfileSharing_whenIsRecipientMessageRequestAccepted_thenIExpectTrue() {
// GIVEN
when(recipient.isProfileSharing()).thenReturn(true);
// WHEN
boolean result = RecipientUtil.isRecipientMessageRequestAccepted(context, recipient);
// THEN
assertTrue(result);
}
@Test
public void givenRecipientIsASystemContact_whenIsRecipientMessageRequestAccepted_thenIExpectTrue() {
// GIVEN
when(recipient.isSystemContact()).thenReturn(true);
// WHEN
boolean result = RecipientUtil.isRecipientMessageRequestAccepted(context, recipient);
// THEN
assertTrue(result);
}
@Test
public void givenNoSecureMessagesSentSomeSecureMessagesReceivedNotSharingAndNotSystemContact_whenIsRecipientMessageRequestAccepted_thenIExpectFalse() {
// GIVEN
when(mockMmsSmsDatabase.getSecureConversationCount(anyLong())).thenReturn(5);
// WHEN
boolean result = RecipientUtil.isRecipientMessageRequestAccepted(context, recipient);
// THEN
assertFalse(result);
}
@Test
public void givenNoSecureMessagesSentNoSecureMessagesReceivedNotSharingAndNotSystemContact_whenIsRecipientMessageRequestAccepted_thenIExpectTrue() {
// GIVEN
when(mockMmsSmsDatabase.getSecureConversationCount(anyLong())).thenReturn(0);
// WHEN
boolean result = RecipientUtil.isRecipientMessageRequestAccepted(context, recipient);
// THEN
assertTrue(result);
}
@Test
public void givenNoSecureMessagesSent_whenIShareProfileIfFirstSecureMessage_thenIShareProfile() {
// GIVEN
when(mockMmsSmsDatabase.getOutgoingSecureConversationCount(anyLong())).thenReturn(0);
// WHEN
RecipientUtil.shareProfileIfFirstSecureMessage(context, recipient);
// THEN
verify(mockRecipientDatabase).setProfileSharing(recipient.getId(), true);
}
@Test
public void givenSecureMessagesSent_whenIShareProfileIfFirstSecureMessage_thenIShareProfile() {
// GIVEN
when(mockMmsSmsDatabase.getOutgoingSecureConversationCount(anyLong())).thenReturn(5);
// WHEN
RecipientUtil.shareProfileIfFirstSecureMessage(context, recipient);
// THEN
verify(mockRecipientDatabase, never()).setProfileSharing(recipient.getId(), true);
}
}