From c2aceb2bd1ffa5f0290a1efe329b767c195127df Mon Sep 17 00:00:00 2001 From: Jameson Williams Date: Fri, 6 Dec 2024 23:45:13 -0600 Subject: [PATCH] Convert more tests to kotlin. Resolves #13829 --- .../webrtc/CallParticipantListUpdateTest.java | 184 ---------- .../webrtc/CallParticipantListUpdateTest.kt | 186 +++++++++++ ...rchivedConversationListDataSourceTest.java | 314 ------------------ ...narchivedConversationListDataSourceTest.kt | 305 +++++++++++++++++ .../jobs/JobManagerFactoriesTest.java | 35 -- .../securesms/jobs/JobManagerFactoriesTest.kt | 29 ++ .../recipients/RecipientIdCacheTest.java | 275 --------------- .../recipients/RecipientIdCacheTest.kt | 264 +++++++++++++++ .../storage/StorageSyncHelperTest.java | 182 ---------- .../storage/StorageSyncHelperTest.kt | 150 +++++++++ .../testutil/SecureRandomTestUtil.java | 48 --- .../testutil/SecureRandomTestUtil.kt | 31 ++ 12 files changed, 965 insertions(+), 1038 deletions(-) delete mode 100644 app/src/test/java/org/thoughtcrime/securesms/components/webrtc/CallParticipantListUpdateTest.java create mode 100644 app/src/test/java/org/thoughtcrime/securesms/components/webrtc/CallParticipantListUpdateTest.kt delete mode 100644 app/src/test/java/org/thoughtcrime/securesms/conversationlist/UnarchivedConversationListDataSourceTest.java create mode 100644 app/src/test/java/org/thoughtcrime/securesms/conversationlist/UnarchivedConversationListDataSourceTest.kt delete mode 100644 app/src/test/java/org/thoughtcrime/securesms/jobs/JobManagerFactoriesTest.java create mode 100644 app/src/test/java/org/thoughtcrime/securesms/jobs/JobManagerFactoriesTest.kt delete mode 100644 app/src/test/java/org/thoughtcrime/securesms/recipients/RecipientIdCacheTest.java create mode 100644 app/src/test/java/org/thoughtcrime/securesms/recipients/RecipientIdCacheTest.kt delete mode 100644 app/src/test/java/org/thoughtcrime/securesms/storage/StorageSyncHelperTest.java create mode 100644 app/src/test/java/org/thoughtcrime/securesms/storage/StorageSyncHelperTest.kt delete mode 100644 app/src/test/java/org/thoughtcrime/securesms/testutil/SecureRandomTestUtil.java create mode 100644 app/src/test/java/org/thoughtcrime/securesms/testutil/SecureRandomTestUtil.kt diff --git a/app/src/test/java/org/thoughtcrime/securesms/components/webrtc/CallParticipantListUpdateTest.java b/app/src/test/java/org/thoughtcrime/securesms/components/webrtc/CallParticipantListUpdateTest.java deleted file mode 100644 index 15b86ae3d3..0000000000 --- a/app/src/test/java/org/thoughtcrime/securesms/components/webrtc/CallParticipantListUpdateTest.java +++ /dev/null @@ -1,184 +0,0 @@ -package org.thoughtcrime.securesms.components.webrtc; - -import androidx.annotation.NonNull; - -import com.annimon.stream.Stream; - -import org.hamcrest.Matchers; -import org.junit.Test; -import org.thoughtcrime.securesms.events.CallParticipant; -import org.thoughtcrime.securesms.events.CallParticipantId; -import org.thoughtcrime.securesms.recipients.Recipient; -import org.thoughtcrime.securesms.recipients.RecipientCreator; -import org.thoughtcrime.securesms.recipients.RecipientId; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.mock; - -public class CallParticipantListUpdateTest { - - @Test - public void givenEmptySets_thenExpectNoChanges() { - // GIVEN - Set added = Collections.emptySet(); - Set removed = Collections.emptySet(); - CallParticipantListUpdate update = new CallParticipantListUpdate(added, removed); - - // THEN - assertTrue(update.hasNoChanges()); - assertFalse(update.hasSingleChange()); - } - - @Test - public void givenOneEmptySet_thenExpectMultipleChanges() { - // GIVEN - Set added = new HashSet<>(Arrays.asList(createWrappers(1, 2, 3))); - Set removed = Collections.emptySet(); - CallParticipantListUpdate update = new CallParticipantListUpdate(added, removed); - - // THEN - assertFalse(update.hasNoChanges()); - assertFalse(update.hasSingleChange()); - } - - @Test - public void givenNoEmptySets_thenExpectMultipleChanges() { - // GIVEN - Set added = new HashSet<>(Arrays.asList(createWrappers(1, 2, 3))); - Set removed = new HashSet<>(Arrays.asList(createWrappers(4, 5, 6))); - CallParticipantListUpdate update = new CallParticipantListUpdate(added, removed); - - // THEN - assertFalse(update.hasNoChanges()); - assertFalse(update.hasSingleChange()); - } - - @Test - public void givenOneSetWithSingleItemAndAnEmptySet_thenExpectSingleChange() { - // GIVEN - Set added = new HashSet<>(Arrays.asList(createWrappers(1))); - Set removed = Collections.emptySet(); - CallParticipantListUpdate update = new CallParticipantListUpdate(added, removed); - - // THEN - assertFalse(update.hasNoChanges()); - assertTrue(update.hasSingleChange()); - } - - @Test - public void whenFirstListIsAdded_thenIExpectAnUpdateWithAllItemsFromListAdded() { - // GIVEN - List newList = createParticipants(1, 2, 3, 4, 5); - - // WHEN - CallParticipantListUpdate update = CallParticipantListUpdate.computeDeltaUpdate(Collections.emptyList(), newList); - - // THEN - assertFalse(update.hasNoChanges()); - assertTrue(update.getRemoved().isEmpty()); - assertThat(update.getAdded(), Matchers.containsInAnyOrder(createWrappers(1, 2, 3, 4, 5))); - } - - @Test - public void whenSameListIsAddedTwiceInARowWithinTimeout_thenIExpectAnEmptyUpdate() { - // GIVEN - List newList = createParticipants(1, 2, 3, 4, 5); - - // WHEN - CallParticipantListUpdate update = CallParticipantListUpdate.computeDeltaUpdate(newList, newList); - - // THEN - assertTrue(update.hasNoChanges()); - } - - @Test - public void whenPlaceholdersAreUsed_thenIExpectAnEmptyUpdate() { - // GIVEN - List newList = createPlaceholderParticipants(1, 2, 3, 4, 5); - - // WHEN - CallParticipantListUpdate update = CallParticipantListUpdate.computeDeltaUpdate(Collections.emptyList(), newList); - - // THEN - assertTrue(update.hasNoChanges()); - } - - @Test - public void whenNewListIsAdded_thenIExpectAReducedUpdate() { - // GIVEN - List list1 = createParticipants(1, 2, 3, 4, 5); - List list2 = createParticipants(2, 3, 4, 5, 6); - - // WHEN - CallParticipantListUpdate update = CallParticipantListUpdate.computeDeltaUpdate(list1, list2); - - // THEN - assertFalse(update.hasNoChanges()); - assertThat(update.getAdded(), Matchers.containsInAnyOrder(createWrappers(6))); - assertThat(update.getRemoved(), Matchers.containsInAnyOrder(createWrappers(1))); - } - - @Test - public void whenRecipientExistsMultipleTimes_thenIExpectOneInstancePrimaryAndOthersSecondary() { - // GIVEN - List list = createParticipants(new long[]{1, 1, 1}, new long[]{1, 2, 3}); - - // WHEN - CallParticipantListUpdate update = CallParticipantListUpdate.computeDeltaUpdate(Collections.emptyList(), list); - - // THEN - List isPrimaryList = Stream.of(update.getAdded()).map(wrapper -> wrapper.getCallParticipant().isPrimary()).toList(); - assertThat(isPrimaryList, Matchers.containsInAnyOrder(true, false, false)); - } - - static CallParticipantListUpdate.Wrapper[] createWrappers(long ... recipientIds) { - CallParticipantListUpdate.Wrapper[] ids = new CallParticipantListUpdate.Wrapper[recipientIds.length]; - Set primaries = new HashSet<>(); - - for (int i = 0; i < recipientIds.length; i++) { - CallParticipant participant = createParticipant(recipientIds[i], recipientIds[i], primaries.contains(recipientIds[i]) ? CallParticipant.DeviceOrdinal.SECONDARY : CallParticipant.DeviceOrdinal.PRIMARY); - - ids[i] = CallParticipantListUpdate.createWrapper(participant); - } - - return ids; - } - - private static List createPlaceholderParticipants(long ... recipientIds) { - long[] deMuxIds = new long[recipientIds.length]; - Arrays.fill(deMuxIds, -1); - return createParticipants(recipientIds, deMuxIds); - } - - private static List createParticipants(long ... recipientIds) { - return createParticipants(recipientIds, recipientIds); - } - - private static List createParticipants(long[] recipientIds, long[] placeholderIds) { - List participants = new ArrayList<>(recipientIds.length); - Set primaries = new HashSet<>(); - - for (int i = 0; i < recipientIds.length; i++) { - participants.add(createParticipant(recipientIds[i], placeholderIds[i], primaries.contains(recipientIds[i]) ? CallParticipant.DeviceOrdinal.SECONDARY : CallParticipant.DeviceOrdinal.PRIMARY)); - primaries.add(recipientIds[i]); - } - - return participants; - } - - private static CallParticipant createParticipant(long recipientId, long deMuxId, @NonNull CallParticipant.DeviceOrdinal deviceOrdinal) { - Recipient recipient = RecipientCreator.forId(RecipientId.from(recipientId), true); - - return CallParticipant.createRemote(new CallParticipantId(deMuxId, recipient.getId()), recipient, null, new BroadcastVideoSink(), false, false, false, CallParticipant.HAND_LOWERED, -1, false, 0, false, deviceOrdinal); - } - -} \ No newline at end of file diff --git a/app/src/test/java/org/thoughtcrime/securesms/components/webrtc/CallParticipantListUpdateTest.kt b/app/src/test/java/org/thoughtcrime/securesms/components/webrtc/CallParticipantListUpdateTest.kt new file mode 100644 index 0000000000..e5d8a758d7 --- /dev/null +++ b/app/src/test/java/org/thoughtcrime/securesms/components/webrtc/CallParticipantListUpdateTest.kt @@ -0,0 +1,186 @@ +package org.thoughtcrime.securesms.components.webrtc + +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.Matchers.containsInAnyOrder +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Test +import org.thoughtcrime.securesms.events.CallParticipant +import org.thoughtcrime.securesms.events.CallParticipant.Companion.createRemote +import org.thoughtcrime.securesms.events.CallParticipant.DeviceOrdinal +import org.thoughtcrime.securesms.events.CallParticipantId +import org.thoughtcrime.securesms.recipients.RecipientCreator.forId +import org.thoughtcrime.securesms.recipients.RecipientId + +class CallParticipantListUpdateTest { + @Test + fun givenEmptySets_thenExpectNoChanges() { + // GIVEN + val added = emptySet() + val removed = emptySet() + val update = CallParticipantListUpdate(added, removed) + + // THEN + assertTrue(update.hasNoChanges()) + assertFalse(update.hasSingleChange()) + } + + @Test + fun givenOneEmptySet_thenExpectMultipleChanges() { + // GIVEN + val added = createWrappers(1, 2, 3).toSet() + val removed = emptySet() + val update = CallParticipantListUpdate(added, removed) + + // THEN + assertFalse(update.hasNoChanges()) + assertFalse(update.hasSingleChange()) + } + + @Test + fun givenNoEmptySets_thenExpectMultipleChanges() { + // GIVEN + val added = createWrappers(1, 2, 3).toSet() + val removed = createWrappers(4, 5, 6).toSet() + val update = CallParticipantListUpdate(added, removed) + + // THEN + assertFalse(update.hasNoChanges()) + assertFalse(update.hasSingleChange()) + } + + @Test + fun givenOneSetWithSingleItemAndAnEmptySet_thenExpectSingleChange() { + // GIVEN + val added = createWrappers(1).toSet() + val removed = emptySet() + val update = CallParticipantListUpdate(added, removed) + + // THEN + assertFalse(update.hasNoChanges()) + assertTrue(update.hasSingleChange()) + } + + @Test + fun whenFirstListIsAdded_thenIExpectAnUpdateWithAllItemsFromListAdded() { + // GIVEN + val newList = createParticipants(1, 2, 3, 4, 5) + + // WHEN + val update = CallParticipantListUpdate.computeDeltaUpdate(emptyList(), newList) + + // THEN + assertFalse(update.hasNoChanges()) + assertTrue(update.removed.isEmpty()) + assertThat(update.added, containsInAnyOrder(*createWrappers(1, 2, 3, 4, 5))) + } + + @Test + fun whenSameListIsAddedTwiceInARowWithinTimeout_thenIExpectAnEmptyUpdate() { + // GIVEN + val newList = createParticipants(1, 2, 3, 4, 5) + + // WHEN + val update = CallParticipantListUpdate.computeDeltaUpdate(newList, newList) + + // THEN + assertTrue(update.hasNoChanges()) + } + + @Test + fun whenPlaceholdersAreUsed_thenIExpectAnEmptyUpdate() { + // GIVEN + val newList = createPlaceholderParticipants(1, 2, 3, 4, 5) + + // WHEN + val update = CallParticipantListUpdate.computeDeltaUpdate(emptyList(), newList) + + // THEN + assertTrue(update.hasNoChanges()) + } + + @Test + fun whenNewListIsAdded_thenIExpectAReducedUpdate() { + // GIVEN + val list1 = createParticipants(1, 2, 3, 4, 5) + val list2 = createParticipants(2, 3, 4, 5, 6) + + // WHEN + val update = CallParticipantListUpdate.computeDeltaUpdate(list1, list2) + + // THEN + assertFalse(update.hasNoChanges()) + assertThat(update.added, containsInAnyOrder(*createWrappers(6))) + assertThat(update.removed, containsInAnyOrder(*createWrappers(1))) + } + + @Test + fun whenRecipientExistsMultipleTimes_thenIExpectOneInstancePrimaryAndOthersSecondary() { + // GIVEN + val list = createParticipants(longArrayOf(1, 1, 1), longArrayOf(1, 2, 3)) + + // WHEN + val update = CallParticipantListUpdate.computeDeltaUpdate(emptyList(), list) + + // THEN + val isPrimaryList = update.added.map { it.callParticipant.isPrimary }.toList() + assertThat(isPrimaryList, containsInAnyOrder(true, false, false)) + } + + companion object { + internal fun createWrappers(vararg recipientIds: Long): Array { + val ids = arrayOfNulls(recipientIds.size) + + for (i in recipientIds.indices) { + val participant = createParticipant(recipientIds[i], recipientIds[i], DeviceOrdinal.PRIMARY) + + ids[i] = CallParticipantListUpdate.createWrapper(participant) + } + + return ids + } + + private fun createPlaceholderParticipants( + @Suppress("SameParameterValue") vararg recipientIds: Long + ): List { + val deMuxIds = LongArray(recipientIds.size) { -1 } + return createParticipants(recipientIds, deMuxIds) + } + + private fun createParticipants(vararg recipientIds: Long): List { + return createParticipants(recipientIds, recipientIds) + } + + private fun createParticipants(recipientIds: LongArray, placeholderIds: LongArray): List { + val participants = mutableListOf() + val primaries = mutableSetOf() + + for (i in recipientIds.indices) { + participants.add(createParticipant(recipientIds[i], placeholderIds[i], if (primaries.contains(recipientIds[i])) DeviceOrdinal.SECONDARY else DeviceOrdinal.PRIMARY)) + primaries.add(recipientIds[i]) + } + + return participants + } + + private fun createParticipant(recipientId: Long, deMuxId: Long, deviceOrdinal: DeviceOrdinal): CallParticipant { + val recipient = forId(RecipientId.from(recipientId), true) + + return createRemote( + callParticipantId = CallParticipantId(deMuxId, recipient.id), + recipient = recipient, + identityKey = null, + renderer = BroadcastVideoSink(), + isForwardingVideo = false, + audioEnabled = false, + videoEnabled = false, + handRaisedTimestamp = CallParticipant.HAND_LOWERED, + lastSpoke = -1, + mediaKeysReceived = false, + addedToCallTime = 0, + isScreenSharing = false, + deviceOrdinal = deviceOrdinal + ) + } + } +} diff --git a/app/src/test/java/org/thoughtcrime/securesms/conversationlist/UnarchivedConversationListDataSourceTest.java b/app/src/test/java/org/thoughtcrime/securesms/conversationlist/UnarchivedConversationListDataSourceTest.java deleted file mode 100644 index f84d4144e5..0000000000 --- a/app/src/test/java/org/thoughtcrime/securesms/conversationlist/UnarchivedConversationListDataSourceTest.java +++ /dev/null @@ -1,314 +0,0 @@ -package org.thoughtcrime.securesms.conversationlist; - -import android.app.Application; -import android.database.Cursor; - -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockedStatic; -import org.mockito.junit.MockitoJUnit; -import org.mockito.junit.MockitoRule; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; -import org.thoughtcrime.securesms.components.settings.app.chats.folders.ChatFolderRecord; -import org.thoughtcrime.securesms.conversationlist.model.ConversationFilter; -import org.thoughtcrime.securesms.conversationlist.model.ConversationReader; -import org.thoughtcrime.securesms.database.DatabaseObserver; -import org.thoughtcrime.securesms.database.SignalDatabase; -import org.thoughtcrime.securesms.database.ThreadTable; -import org.thoughtcrime.securesms.dependencies.AppDependencies; -import org.thoughtcrime.securesms.util.RemoteConfig; - -import java.util.ArrayList; -import java.util.HashSet; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyLong; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -@RunWith(RobolectricTestRunner.class) -@Config(manifest = Config.NONE, application = Application.class) -public class UnarchivedConversationListDataSourceTest { - - @Rule - public MockitoRule rule = MockitoJUnit.rule(); - - @Mock - private MockedStatic applicationDependenciesMockedStatic; - - @Mock - private MockedStatic signalDatabaseMockedStatic; - - @Mock - private MockedStatic remoteConfigMockedStatic; - - private ConversationListDataSource.UnarchivedConversationListDataSource testSubject; - - private ChatFolderRecord allChatsFolder; - - private ThreadTable threadTable; - - @Before - public void setUp() { - threadTable = mock(ThreadTable.class); - - when(SignalDatabase.threads()).thenReturn(threadTable); - when(AppDependencies.getDatabaseObserver()).thenReturn(mock(DatabaseObserver.class)); - when(RemoteConfig.getInlinePinnedChats()).thenReturn(true); - - allChatsFolder = setupAllChatsFolder(); - testSubject = new ConversationListDataSource.UnarchivedConversationListDataSource(allChatsFolder, ConversationFilter.OFF, false); - } - - - @Test - public void givenNoConversations_whenIGetTotalCount_thenIExpectZero() { - // WHEN - int result = testSubject.getTotalCount(); - - // THEN - assertEquals(0, result); - assertFalse(testSubject.hasConversationFilterFooter()); - assertFalse(testSubject.hasArchivedFooter()); - } - - @Test - public void givenArchivedConversations_whenIGetTotalCount_thenIExpectOne() { - // GIVEN - when(threadTable.getArchivedConversationListCount(ConversationFilter.OFF)).thenReturn(12); - - // WHEN - int result = testSubject.getTotalCount(); - - // THEN - assertEquals(1, result); - assertFalse(testSubject.hasConversationFilterFooter()); - assertTrue(testSubject.hasArchivedFooter()); - } - - @Test - public void givenSinglePinnedAndArchivedConversations_whenIGetTotalCount_thenIExpectTwo() { - // GIVEN - when(threadTable.getPinnedConversationListCount(ConversationFilter.OFF, allChatsFolder)).thenReturn(1); - when(threadTable.getUnarchivedConversationListCount(ConversationFilter.OFF, allChatsFolder)).thenReturn(1); - when(threadTable.getArchivedConversationListCount(ConversationFilter.OFF)).thenReturn(12); - - // WHEN - int result = testSubject.getTotalCount(); - - // THEN - assertEquals(2, result); - assertFalse(testSubject.hasConversationFilterFooter()); - assertTrue(testSubject.hasArchivedFooter()); - } - - @Test - public void givenSingleUnpinnedAndArchivedConversations_whenIGetTotalCount_thenIExpectTwo() { - // GIVEN - when(threadTable.getUnarchivedConversationListCount(ConversationFilter.OFF, allChatsFolder)).thenReturn(1); - when(threadTable.getArchivedConversationListCount(ConversationFilter.OFF)).thenReturn(12); - - // WHEN - int result = testSubject.getTotalCount(); - - // THEN - assertEquals(2, result); - assertFalse(testSubject.hasConversationFilterFooter()); - assertTrue(testSubject.hasArchivedFooter()); - } - - @Test - public void givenSinglePinnedAndSingleUnpinned_whenIGetTotalCount_thenIExpectTwo() { - // GIVEN - when(threadTable.getPinnedConversationListCount(ConversationFilter.OFF, allChatsFolder)).thenReturn(1); - when(threadTable.getUnarchivedConversationListCount(ConversationFilter.OFF, allChatsFolder)).thenReturn(2); - - // WHEN - int result = testSubject.getTotalCount(); - - // THEN - assertEquals(2, result); - assertFalse(testSubject.hasConversationFilterFooter()); - assertFalse(testSubject.hasArchivedFooter()); - } - - @Test - public void givenNoConversations_whenIGetCursor_thenIExpectAnEmptyCursor() { - // GIVEN - setupThreadDatabaseCursors(0, 0); - - // WHEN - Cursor cursor = testSubject.getCursor(0, 100); - - // THEN - verify(threadTable).getUnarchivedConversationList(ConversationFilter.OFF, true, 0, 100, allChatsFolder); - verify(threadTable).getUnarchivedConversationList(ConversationFilter.OFF, false, 0, 100, allChatsFolder); - assertEquals(0, cursor.getCount()); - } - - @Test - public void givenArchivedConversations_whenIGetCursor_thenIExpectOne() { - // GIVEN - setupThreadDatabaseCursors(0, 0); - when(threadTable.getArchivedConversationListCount(ConversationFilter.OFF)).thenReturn(12); - testSubject.getTotalCount(); - - // WHEN - Cursor cursor = testSubject.getCursor(0, 100); - - // THEN - verify(threadTable).getUnarchivedConversationList(ConversationFilter.OFF, true, 0, 100, allChatsFolder); - verify(threadTable).getUnarchivedConversationList(ConversationFilter.OFF, false, 0, 100, allChatsFolder); - assertEquals(1, cursor.getCount()); - } - - @Test - public void givenSinglePinnedAndArchivedConversations_whenIGetCursor_thenIExpectTwo() { - // GIVEN - setupThreadDatabaseCursors(1, 0); - when(threadTable.getPinnedConversationListCount(ConversationFilter.OFF, allChatsFolder)).thenReturn(1); - when(threadTable.getUnarchivedConversationListCount(ConversationFilter.OFF, allChatsFolder)).thenReturn(1); - when(threadTable.getArchivedConversationListCount(ConversationFilter.OFF)).thenReturn(12); - testSubject.getTotalCount(); - - // WHEN - Cursor cursor = testSubject.getCursor(0, 100); - - // THEN - verify(threadTable).getUnarchivedConversationList(ConversationFilter.OFF, true, 0, 100, allChatsFolder); - verify(threadTable).getUnarchivedConversationList(ConversationFilter.OFF, false, 0, 99, allChatsFolder); - assertEquals(2, cursor.getCount()); - } - - @Test - public void givenSingleUnpinnedAndArchivedConversations_whenIGetCursor_thenIExpectTwo() { - // GIVEN - setupThreadDatabaseCursors(0, 1); - when(threadTable.getUnarchivedConversationListCount(ConversationFilter.OFF, allChatsFolder)).thenReturn(1); - when(threadTable.getArchivedConversationListCount(ConversationFilter.OFF)).thenReturn(12); - testSubject.getTotalCount(); - - // WHEN - Cursor cursor = testSubject.getCursor(0, 100); - - // THEN - verify(threadTable).getUnarchivedConversationList(ConversationFilter.OFF, true, 0, 100, allChatsFolder); - verify(threadTable).getUnarchivedConversationList(ConversationFilter.OFF, false, 0, 100, allChatsFolder); - assertEquals(2, cursor.getCount()); - } - - @Test - public void givenSinglePinnedAndSingleUnpinned_whenIGetCursor_thenIExpectTwo() { - // GIVEN - setupThreadDatabaseCursors(1, 1); - when(threadTable.getPinnedConversationListCount(ConversationFilter.OFF, allChatsFolder)).thenReturn(1); - when(threadTable.getUnarchivedConversationListCount(ConversationFilter.OFF, allChatsFolder)).thenReturn(2); - testSubject.getTotalCount(); - - // WHEN - Cursor cursor = testSubject.getCursor(0, 100); - - // THEN - verify(threadTable).getUnarchivedConversationList(ConversationFilter.OFF, true, 0, 100, allChatsFolder); - verify(threadTable).getUnarchivedConversationList(ConversationFilter.OFF, false, 0, 99, allChatsFolder); - assertEquals(2, cursor.getCount()); - } - - @Test - public void givenLoadingSecondPage_whenIGetCursor_thenIExpectProperOffsetAndCursorCount() { - // GIVEN - setupThreadDatabaseCursors(0, 100); - when(threadTable.getPinnedConversationListCount(ConversationFilter.OFF, allChatsFolder)).thenReturn(4); - when(threadTable.getUnarchivedConversationListCount(ConversationFilter.OFF, allChatsFolder)).thenReturn(104); - testSubject.getTotalCount(); - - // WHEN - Cursor cursor = testSubject.getCursor(50, 100); - - // THEN - verify(threadTable).getUnarchivedConversationList(ConversationFilter.OFF, true, 50, 100, allChatsFolder); - verify(threadTable).getUnarchivedConversationList(ConversationFilter.OFF, false, 46, 100, allChatsFolder); - assertEquals(100, cursor.getCount()); - } - - @Test - public void givenHasArchivedAndLoadingLastPage_whenIGetCursor_thenIExpectProperOffsetAndCursorCount() { - // GIVEN - setupThreadDatabaseCursors(0, 99); - when(threadTable.getPinnedConversationListCount(ConversationFilter.OFF, allChatsFolder)).thenReturn(4); - when(threadTable.getUnarchivedConversationListCount(ConversationFilter.OFF, allChatsFolder)).thenReturn(103); - when(threadTable.getArchivedConversationListCount(ConversationFilter.OFF)).thenReturn(12); - testSubject.getTotalCount(); - - // WHEN - Cursor cursor = testSubject.getCursor(50, 100); - - // THEN - verify(threadTable).getUnarchivedConversationList(ConversationFilter.OFF, true, 50, 100, allChatsFolder); - verify(threadTable).getUnarchivedConversationList(ConversationFilter.OFF, false, 46, 100, allChatsFolder); - assertEquals(100, cursor.getCount()); - - cursor.moveToLast(); - assertEquals(0, cursor.getColumnIndex(ConversationReader.HEADER_COLUMN[0])); - } - - @Test - public void givenHasNoArchivedAndIsFiltered_whenIGetCursor_thenIExpectConversationFilterFooter() { - // GIVEN - ConversationListDataSource.UnarchivedConversationListDataSource testSubject = new ConversationListDataSource.UnarchivedConversationListDataSource(allChatsFolder, ConversationFilter.UNREAD, false); - setupThreadDatabaseCursors(0, 3); - when(threadTable.getPinnedConversationListCount(ConversationFilter.UNREAD, allChatsFolder)).thenReturn(0); - when(threadTable.getUnarchivedConversationListCount(ConversationFilter.UNREAD, allChatsFolder)).thenReturn(3); - when(threadTable.getArchivedConversationListCount(ConversationFilter.UNREAD)).thenReturn(0); - testSubject.getTotalCount(); - - // WHEN - Cursor cursor = testSubject.getCursor(0, 5); - - // THEN - assertEquals(4, cursor.getCount()); - assertTrue(testSubject.hasConversationFilterFooter()); - - cursor.moveToLast(); - assertEquals(0, cursor.getColumnIndex(ConversationReader.HEADER_COLUMN[0])); - } - - - private void setupThreadDatabaseCursors(int pinned, int unpinned) { - Cursor pinnedCursor = mock(Cursor.class); - when(pinnedCursor.getCount()).thenReturn(pinned); - - Cursor unpinnedCursor = mock(Cursor.class); - when(unpinnedCursor.getCount()).thenReturn(unpinned); - - when(threadTable.getUnarchivedConversationList(any(), eq(true), anyLong(), anyLong(), any())).thenReturn(pinnedCursor); - when(threadTable.getUnarchivedConversationList(any(), eq(false), anyLong(), anyLong(), any())).thenReturn(unpinnedCursor); - } - - private ChatFolderRecord setupAllChatsFolder() { - return new ChatFolderRecord( - 1, - "", - -1, - new ArrayList<>(), - new ArrayList<>(), - new HashSet<>(), - new HashSet<>(), - false, - false, - false, - false, - false, - ChatFolderRecord.FolderType.ALL, - 0 - ); - } -} \ No newline at end of file diff --git a/app/src/test/java/org/thoughtcrime/securesms/conversationlist/UnarchivedConversationListDataSourceTest.kt b/app/src/test/java/org/thoughtcrime/securesms/conversationlist/UnarchivedConversationListDataSourceTest.kt new file mode 100644 index 0000000000..cc679e46ad --- /dev/null +++ b/app/src/test/java/org/thoughtcrime/securesms/conversationlist/UnarchivedConversationListDataSourceTest.kt @@ -0,0 +1,305 @@ +package org.thoughtcrime.securesms.conversationlist + +import android.app.Application +import android.database.Cursor +import io.mockk.every +import io.mockk.just +import io.mockk.mockk +import io.mockk.mockkObject +import io.mockk.mockkStatic +import io.mockk.runs +import io.mockk.unmockkAll +import io.mockk.verify +import org.junit.After +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.annotation.Config +import org.thoughtcrime.securesms.components.settings.app.chats.folders.ChatFolderRecord +import org.thoughtcrime.securesms.conversationlist.ConversationListDataSource.UnarchivedConversationListDataSource +import org.thoughtcrime.securesms.conversationlist.model.ConversationFilter +import org.thoughtcrime.securesms.conversationlist.model.ConversationReader +import org.thoughtcrime.securesms.database.SignalDatabase +import org.thoughtcrime.securesms.database.ThreadTable +import org.thoughtcrime.securesms.testutil.MockAppDependenciesRule +import org.thoughtcrime.securesms.util.RemoteConfig + +@RunWith(RobolectricTestRunner::class) +@Config(manifest = Config.NONE, application = Application::class) +class UnarchivedConversationListDataSourceTest { + private lateinit var testSubject: UnarchivedConversationListDataSource + private lateinit var allChatsFolder: ChatFolderRecord + private lateinit var threadTable: ThreadTable + + @get:Rule + val appDependencies = MockAppDependenciesRule() + + @Before + fun setUp() { + threadTable = mockk(relaxed = true) + + mockkStatic(RemoteConfig::class) + every { RemoteConfig.init() } just runs + every { RemoteConfig.inlinePinnedChats } returns true + + mockkObject(SignalDatabase) + every { SignalDatabase.threads } returns threadTable + + allChatsFolder = setupAllChatsFolder() + testSubject = UnarchivedConversationListDataSource(allChatsFolder, ConversationFilter.OFF, false) + } + + @After + fun cleanup() { + unmockkAll() + } + + @Test + fun givenNoConversations_whenIGetTotalCount_thenIExpectZero() { + // WHEN + val result = testSubject.totalCount + + // THEN + assertEquals(0, result) + assertFalse(testSubject.hasConversationFilterFooter()) + assertFalse(testSubject.hasArchivedFooter()) + } + + @Test + fun givenArchivedConversations_whenIGetTotalCount_thenIExpectOne() { + // GIVEN + every { threadTable.getArchivedConversationListCount(ConversationFilter.OFF) } returns 12 + + // WHEN + val result = testSubject.totalCount + + // THEN + assertEquals(1, result) + assertFalse(testSubject.hasConversationFilterFooter()) + assertTrue(testSubject.hasArchivedFooter()) + } + + @Test + fun givenSinglePinnedAndArchivedConversations_whenIGetTotalCount_thenIExpectTwo() { + // GIVEN + every { threadTable.getPinnedConversationListCount(ConversationFilter.OFF, allChatsFolder) } returns 1 + every { threadTable.getUnarchivedConversationListCount(ConversationFilter.OFF, allChatsFolder) } returns 1 + every { threadTable.getArchivedConversationListCount(ConversationFilter.OFF) } returns 12 + + // WHEN + val result = testSubject.totalCount + + // THEN + assertEquals(2, result) + assertFalse(testSubject.hasConversationFilterFooter()) + assertTrue(testSubject.hasArchivedFooter()) + } + + @Test + fun givenSingleUnpinnedAndArchivedConversations_whenIGetTotalCount_thenIExpectTwo() { + // GIVEN + every { threadTable.getUnarchivedConversationListCount(ConversationFilter.OFF, allChatsFolder) } returns 1 + every { threadTable.getArchivedConversationListCount(ConversationFilter.OFF) } returns 12 + + // WHEN + val result = testSubject.totalCount + + // THEN + assertEquals(2, result) + assertFalse(testSubject.hasConversationFilterFooter()) + assertTrue(testSubject.hasArchivedFooter()) + } + + @Test + fun givenSinglePinnedAndSingleUnpinned_whenIGetTotalCount_thenIExpectTwo() { + // GIVEN + every { threadTable.getPinnedConversationListCount(ConversationFilter.OFF, allChatsFolder) } returns 1 + every { threadTable.getUnarchivedConversationListCount(ConversationFilter.OFF, allChatsFolder) } returns 2 + + // WHEN + val result = testSubject.totalCount + + // THEN + assertEquals(2, result) + assertFalse(testSubject.hasConversationFilterFooter()) + assertFalse(testSubject.hasArchivedFooter()) + } + + @Test + fun givenNoConversations_whenIGetCursor_thenIExpectAnEmptyCursor() { + // GIVEN + setupThreadDatabaseCursors(0, 0) + + // WHEN + val cursor = testSubject.getCursor(0, 100) + + // THEN + verify { threadTable.getUnarchivedConversationList(ConversationFilter.OFF, true, 0, 100, allChatsFolder) } + verify { threadTable.getUnarchivedConversationList(ConversationFilter.OFF, false, 0, 100, allChatsFolder) } + assertEquals(0, cursor.count) + } + + @Test + fun givenArchivedConversations_whenIGetCursor_thenIExpectOne() { + // GIVEN + setupThreadDatabaseCursors(0, 0) + every { threadTable.getArchivedConversationListCount(ConversationFilter.OFF) } returns 12 + testSubject.totalCount + + // WHEN + val cursor = testSubject.getCursor(0, 100) + + // THEN + verify { threadTable.getUnarchivedConversationList(ConversationFilter.OFF, true, 0, 100, allChatsFolder) } + verify { threadTable.getUnarchivedConversationList(ConversationFilter.OFF, false, 0, 100, allChatsFolder) } + assertEquals(1, cursor.count) + } + + @Test + fun givenSinglePinnedAndArchivedConversations_whenIGetCursor_thenIExpectTwo() { + // GIVEN + setupThreadDatabaseCursors(1, 0) + every { threadTable.getPinnedConversationListCount(ConversationFilter.OFF, allChatsFolder) } returns 1 + every { threadTable.getUnarchivedConversationListCount(ConversationFilter.OFF, allChatsFolder) } returns 1 + every { threadTable.getArchivedConversationListCount(ConversationFilter.OFF) } returns 12 + testSubject.totalCount + + // WHEN + val cursor = testSubject.getCursor(0, 100) + + // THEN + verify { threadTable.getUnarchivedConversationList(ConversationFilter.OFF, true, 0, 100, allChatsFolder) } + verify { threadTable.getUnarchivedConversationList(ConversationFilter.OFF, false, 0, 99, allChatsFolder) } + assertEquals(2, cursor.count) + } + + @Test + fun givenSingleUnpinnedAndArchivedConversations_whenIGetCursor_thenIExpectTwo() { + // GIVEN + setupThreadDatabaseCursors(0, 1) + every { threadTable.getUnarchivedConversationListCount(ConversationFilter.OFF, allChatsFolder) } returns 1 + every { threadTable.getArchivedConversationListCount(ConversationFilter.OFF) } returns 12 + testSubject.totalCount + + // WHEN + val cursor = testSubject.getCursor(0, 100) + + // THEN + verify { threadTable.getUnarchivedConversationList(ConversationFilter.OFF, true, 0, 100, allChatsFolder) } + verify { threadTable.getUnarchivedConversationList(ConversationFilter.OFF, false, 0, 100, allChatsFolder) } + assertEquals(2, cursor.count) + } + + @Test + fun givenSinglePinnedAndSingleUnpinned_whenIGetCursor_thenIExpectTwo() { + // GIVEN + setupThreadDatabaseCursors(1, 1) + every { threadTable.getPinnedConversationListCount(ConversationFilter.OFF, allChatsFolder) } returns 1 + every { threadTable.getUnarchivedConversationListCount(ConversationFilter.OFF, allChatsFolder) } returns 2 + testSubject.totalCount + + // WHEN + val cursor = testSubject.getCursor(0, 100) + + // THEN + verify { threadTable.getUnarchivedConversationList(ConversationFilter.OFF, true, 0, 100, allChatsFolder) } + verify { threadTable.getUnarchivedConversationList(ConversationFilter.OFF, false, 0, 99, allChatsFolder) } + assertEquals(2, cursor.count) + } + + @Test + fun givenLoadingSecondPage_whenIGetCursor_thenIExpectProperOffsetAndCursorCount() { + // GIVEN + setupThreadDatabaseCursors(0, 100) + every { threadTable.getPinnedConversationListCount(ConversationFilter.OFF, allChatsFolder) } returns 4 + every { threadTable.getUnarchivedConversationListCount(ConversationFilter.OFF, allChatsFolder) } returns 104 + testSubject.totalCount + + // WHEN + val cursor = testSubject.getCursor(50, 100) + + // THEN + verify { threadTable.getUnarchivedConversationList(ConversationFilter.OFF, true, 50, 100, allChatsFolder) } + verify { threadTable.getUnarchivedConversationList(ConversationFilter.OFF, false, 46, 100, allChatsFolder) } + assertEquals(100, cursor.count) + } + + @Test + fun givenHasArchivedAndLoadingLastPage_whenIGetCursor_thenIExpectProperOffsetAndCursorCount() { + // GIVEN + setupThreadDatabaseCursors(0, 99) + every { threadTable.getPinnedConversationListCount(ConversationFilter.OFF, allChatsFolder) } returns 4 + every { threadTable.getUnarchivedConversationListCount(ConversationFilter.OFF, allChatsFolder) } returns 103 + every { threadTable.getArchivedConversationListCount(ConversationFilter.OFF) } returns 12 + testSubject.totalCount + + // WHEN + val cursor = testSubject.getCursor(50, 100) + + // THEN + verify { threadTable.getUnarchivedConversationList(ConversationFilter.OFF, true, 50, 100, allChatsFolder) } + verify { threadTable.getUnarchivedConversationList(ConversationFilter.OFF, false, 46, 100, allChatsFolder) } + assertEquals(100, cursor.count) + + cursor.moveToLast() + assertEquals(0, cursor.getColumnIndex(ConversationReader.HEADER_COLUMN[0])) + } + + @Test + fun givenHasNoArchivedAndIsFiltered_whenIGetCursor_thenIExpectConversationFilterFooter() { + // GIVEN + val testSubject = UnarchivedConversationListDataSource(allChatsFolder, ConversationFilter.UNREAD, false) + setupThreadDatabaseCursors(0, 3) + every { threadTable.getPinnedConversationListCount(ConversationFilter.UNREAD, allChatsFolder) } returns 0 + every { threadTable.getUnarchivedConversationListCount(ConversationFilter.UNREAD, allChatsFolder) } returns 3 + every { threadTable.getArchivedConversationListCount(ConversationFilter.UNREAD) } returns 0 + testSubject.totalCount + + // WHEN + val cursor = testSubject.getCursor(0, 5) + + // THEN + assertEquals(4, cursor.count) + assertTrue(testSubject.hasConversationFilterFooter()) + + cursor.moveToLast() + assertEquals(0, cursor.getColumnIndex(ConversationReader.HEADER_COLUMN[0])) + } + + private fun setupThreadDatabaseCursors(pinned: Int, unpinned: Int) { + every { + threadTable.getUnarchivedConversationList(any(), true, any(), any(), any()) + } returns mockk(relaxed = true) { + every { count } returns pinned + } + every { + threadTable.getUnarchivedConversationList(any(), false, any(), any(), any()) + } returns mockk(relaxed = true) { + every { count } returns unpinned + } + } + + private fun setupAllChatsFolder(): ChatFolderRecord { + return ChatFolderRecord( + id = 1, + name = "", + position = -1, + includedChats = emptyList(), + excludedChats = emptyList(), + includedRecipients = emptySet(), + excludedRecipients = emptySet(), + showUnread = false, + showMutedChats = false, + showIndividualChats = false, + showGroupChats = false, + isMuted = false, + folderType = ChatFolderRecord.FolderType.ALL, + unreadCount = 0 + ) + } +} diff --git a/app/src/test/java/org/thoughtcrime/securesms/jobs/JobManagerFactoriesTest.java b/app/src/test/java/org/thoughtcrime/securesms/jobs/JobManagerFactoriesTest.java deleted file mode 100644 index c6864d9341..0000000000 --- a/app/src/test/java/org/thoughtcrime/securesms/jobs/JobManagerFactoriesTest.java +++ /dev/null @@ -1,35 +0,0 @@ -package org.thoughtcrime.securesms.jobs; - -import android.app.Application; - -import org.junit.Test; -import org.thoughtcrime.securesms.jobmanager.Job; - -import java.util.Map; - -import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.mock; - -public final class JobManagerFactoriesTest { - - @Test - public void PushContentReceiveJob_is_retired() { - Map factories = JobManagerFactories.getJobFactories(mock(Application.class)); - - assertTrue(factories.get("PushContentReceiveJob") instanceof FailingJob.Factory); - } - - @Test - public void AttachmentUploadJob_is_retired() { - Map factories = JobManagerFactories.getJobFactories(mock(Application.class)); - - assertTrue(factories.get("AttachmentUploadJob") instanceof FailingJob.Factory); - } - - @Test - public void MmsSendJob_is_retired() { - Map factories = JobManagerFactories.getJobFactories(mock(Application.class)); - - assertTrue(factories.get("MmsSendJob") instanceof FailingJob.Factory); - } -} diff --git a/app/src/test/java/org/thoughtcrime/securesms/jobs/JobManagerFactoriesTest.kt b/app/src/test/java/org/thoughtcrime/securesms/jobs/JobManagerFactoriesTest.kt new file mode 100644 index 0000000000..8bd1bd8bfb --- /dev/null +++ b/app/src/test/java/org/thoughtcrime/securesms/jobs/JobManagerFactoriesTest.kt @@ -0,0 +1,29 @@ +package org.thoughtcrime.securesms.jobs + +import android.app.Application +import io.mockk.mockk +import org.junit.Assert.assertTrue +import org.junit.Test + +class JobManagerFactoriesTest { + @Test + fun test_PushContentReceiveJob_is_retired() { + val factories = JobManagerFactories.getJobFactories(mockk()) + + assertTrue(factories["PushContentReceiveJob"] is FailingJob.Factory) + } + + @Test + fun test_AttachmentUploadJob_is_retired() { + val factories = JobManagerFactories.getJobFactories(mockk()) + + assertTrue(factories["AttachmentUploadJob"] is FailingJob.Factory) + } + + @Test + fun test_MmsSendJob_is_retired() { + val factories = JobManagerFactories.getJobFactories(mockk()) + + assertTrue(factories["MmsSendJob"] is FailingJob.Factory) + } +} diff --git a/app/src/test/java/org/thoughtcrime/securesms/recipients/RecipientIdCacheTest.java b/app/src/test/java/org/thoughtcrime/securesms/recipients/RecipientIdCacheTest.java deleted file mode 100644 index 57479aa402..0000000000 --- a/app/src/test/java/org/thoughtcrime/securesms/recipients/RecipientIdCacheTest.java +++ /dev/null @@ -1,275 +0,0 @@ -package org.thoughtcrime.securesms.recipients; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import org.junit.Before; -import org.junit.Test; -import org.signal.core.util.logging.Log; -import org.thoughtcrime.securesms.testutil.LogRecorder; -import org.whispersystems.signalservice.api.push.ServiceId.ACI; -import org.whispersystems.signalservice.api.push.ServiceId; - -import java.util.Optional; -import java.util.UUID; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -public final class RecipientIdCacheTest { - - private static final int TEST_CACHE_LIMIT = 5; - - private RecipientIdCache recipientIdCache; - private LogRecorder logRecorder; - - @Before - public void setup() { - recipientIdCache = new RecipientIdCache(TEST_CACHE_LIMIT); - logRecorder = new LogRecorder(); - Log.initialize(logRecorder); - } - - @Test - public void empty_access_by_nulls() { - RecipientId recipientId = recipientIdCache.get(null, null); - - assertNull(recipientId); - } - - @Test - public void empty_access_by_uuid() { - RecipientId recipientId = recipientIdCache.get(ACI.from(UUID.randomUUID()), null); - - assertNull(recipientId); - } - - @Test - public void empty_access_by_e164() { - RecipientId recipientId = recipientIdCache.get(null, "+155512345"); - - assertNull(recipientId); - } - - @Test - public void cache_hit_by_uuid() { - RecipientId recipientId1 = recipientId(); - ServiceId sid1 = ACI.from(UUID.randomUUID()); - - recipientIdCache.put(recipient(recipientId1, sid1, null)); - - RecipientId recipientId = recipientIdCache.get(sid1, null); - - assertEquals(recipientId1, recipientId); - } - - @Test - public void cache_miss_by_uuid() { - RecipientId recipientId1 = recipientId(); - ServiceId sid1 = ACI.from(UUID.randomUUID()); - ServiceId sid2 = ACI.from(UUID.randomUUID()); - - recipientIdCache.put(recipient(recipientId1, sid1, null)); - - RecipientId recipientId = recipientIdCache.get(sid2, null); - - assertNull(recipientId); - } - - @Test - public void cache_hit_by_uuid_e164_not_supplied_on_get() { - RecipientId recipientId1 = recipientId(); - ServiceId sid1 = ACI.from(UUID.randomUUID()); - - recipientIdCache.put(recipient(recipientId1, sid1, "+15551234567")); - - RecipientId recipientId = recipientIdCache.get(sid1, null); - - assertEquals(recipientId1, recipientId); - } - - @Test - public void cache_miss_by_uuid_e164_not_supplied_on_put() { - RecipientId recipientId1 = recipientId(); - ServiceId sid1 = ACI.from(UUID.randomUUID()); - - recipientIdCache.put(recipient(recipientId1, sid1, null)); - - RecipientId recipientId = recipientIdCache.get(sid1, "+15551234567"); - - assertNull(recipientId); - } - - @Test - public void cache_hit_by_e164() { - RecipientId recipientId1 = recipientId(); - String e164 = "+1555123456"; - - recipientIdCache.put(recipient(recipientId1, null, e164)); - - RecipientId recipientId = recipientIdCache.get(null, e164); - - assertEquals(recipientId1, recipientId); - } - - @Test - public void cache_miss_by_e164() { - RecipientId recipientId1 = recipientId(); - String e164a = "+1555123456"; - String e164b = "+1555123457"; - - recipientIdCache.put(recipient(recipientId1, null, e164a)); - - RecipientId recipientId = recipientIdCache.get(null, e164b); - - assertNull(recipientId); - } - - @Test - public void cache_hit_by_e164_uuid_not_supplied_on_get() { - RecipientId recipientId1 = recipientId(); - ServiceId sid1 = ACI.from(UUID.randomUUID()); - - recipientIdCache.put(recipient(recipientId1, sid1, "+15551234567")); - - RecipientId recipientId = recipientIdCache.get(null, "+15551234567"); - - assertEquals(recipientId1, recipientId); - } - - @Test - public void cache_miss_by_e164_uuid_not_supplied_on_put() { - RecipientId recipientId1 = recipientId(); - ServiceId sid1 = ACI.from(UUID.randomUUID()); - String e164 = "+1555123456"; - - recipientIdCache.put(recipient(recipientId1, null, e164)); - - RecipientId recipientId = recipientIdCache.get(sid1, e164); - - assertNull(recipientId); - } - - @Test - public void cache_hit_by_both() { - RecipientId recipientId1 = recipientId(); - ServiceId sid1 = ACI.from(UUID.randomUUID()); - String e164 = "+1555123456"; - - recipientIdCache.put(recipient(recipientId1, sid1, e164)); - - RecipientId recipientId = recipientIdCache.get(sid1, e164); - - assertEquals(recipientId1, recipientId); - } - - @Test - public void full_recipient_id_learned_by_two_puts() { - RecipientId recipientId1 = recipientId(); - ServiceId sid1 = ACI.from(UUID.randomUUID()); - String e164 = "+1555123456"; - - recipientIdCache.put(recipient(recipientId1, sid1, null)); - recipientIdCache.put(recipient(recipientId1, null, e164)); - - RecipientId recipientId = recipientIdCache.get(sid1, e164); - - assertEquals(recipientId1, recipientId); - } - - @Test - public void if_cache_state_disagrees_returns_null() { - RecipientId recipientId1 = recipientId(); - RecipientId recipientId2 = recipientId(); - ServiceId sid = ACI.from(UUID.randomUUID()); - String e164 = "+1555123456"; - - recipientIdCache.put(recipient(recipientId1, null, e164)); - recipientIdCache.put(recipient(recipientId2, sid, null)); - - RecipientId recipientId = recipientIdCache.get(sid, e164); - - assertNull(recipientId); - - assertEquals(1, logRecorder.getWarnings().size()); - assertEquals("Seen invalid RecipientIdCacheState", logRecorder.getWarnings().get(0).getMessage()); - } - - @Test - public void after_invalid_cache_hit_entries_are_cleared_up() { - RecipientId recipientId1 = recipientId(); - RecipientId recipientId2 = recipientId(); - ServiceId sid = ACI.from(UUID.randomUUID()); - String e164 = "+1555123456"; - - recipientIdCache.put(recipient(recipientId1, null, e164)); - recipientIdCache.put(recipient(recipientId2, sid, null)); - - recipientIdCache.get(sid, e164); - - assertNull(recipientIdCache.get(sid, null)); - assertNull(recipientIdCache.get(null, e164)); - } - - @Test - public void multiple_entries() { - RecipientId recipientId1 = recipientId(); - RecipientId recipientId2 = recipientId(); - ServiceId sid1 = ACI.from(UUID.randomUUID()); - ServiceId sid2 = ACI.from(UUID.randomUUID()); - - recipientIdCache.put(recipient(recipientId1, sid1, null)); - recipientIdCache.put(recipient(recipientId2, sid2, null)); - - assertEquals(recipientId1, recipientIdCache.get(sid1, null)); - assertEquals(recipientId2, recipientIdCache.get(sid2, null)); - } - - @Test - public void drops_oldest_when_reaches_cache_limit() { - RecipientId recipientId1 = recipientId(); - ServiceId sid1 = ACI.from(UUID.randomUUID()); - - recipientIdCache.put(recipient(recipientId1, sid1, null)); - - for (int i = 0; i < TEST_CACHE_LIMIT; i++) { - recipientIdCache.put(recipient(recipientId(), ACI.from(UUID.randomUUID()), null)); - } - - assertNull(recipientIdCache.get(sid1, null)); - } - - @Test - public void remains_in_cache_when_used_before_reaching_cache_limit() { - RecipientId recipientId1 = recipientId(); - ServiceId sid1 = ACI.from(UUID.randomUUID()); - - recipientIdCache.put(recipient(recipientId1, sid1, null)); - - for (int i = 0; i < TEST_CACHE_LIMIT - 1; i++) { - recipientIdCache.put(recipient(recipientId(), ACI.from(UUID.randomUUID()), null)); - } - - assertEquals(recipientId1, recipientIdCache.get(sid1, null)); - - recipientIdCache.put(recipient(recipientId(), ACI.from(UUID.randomUUID()), null)); - - assertEquals(recipientId1, recipientIdCache.get(sid1, null)); - } - - private static @NonNull RecipientId recipientId() { - return mock(RecipientId.class); - } - - private static @NonNull Recipient recipient(RecipientId recipientId, @Nullable ServiceId serviceId, @Nullable String e164) { - Recipient mock = mock(Recipient.class); - - when(mock.getId()).thenReturn(recipientId); - when(mock.getServiceId()).thenReturn(Optional.ofNullable(serviceId)); - when(mock.getE164()).thenReturn(Optional.ofNullable(e164)); - - return mock; - } -} diff --git a/app/src/test/java/org/thoughtcrime/securesms/recipients/RecipientIdCacheTest.kt b/app/src/test/java/org/thoughtcrime/securesms/recipients/RecipientIdCacheTest.kt new file mode 100644 index 0000000000..e7d67c07a3 --- /dev/null +++ b/app/src/test/java/org/thoughtcrime/securesms/recipients/RecipientIdCacheTest.kt @@ -0,0 +1,264 @@ +package org.thoughtcrime.securesms.recipients + +import io.mockk.every +import io.mockk.mockk +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNull +import org.junit.Test +import org.signal.core.util.logging.Log +import org.thoughtcrime.securesms.testutil.LogRecorder +import org.whispersystems.signalservice.api.push.ServiceId +import java.util.Optional +import java.util.UUID + +class RecipientIdCacheTest { + private val recipientIdCache = RecipientIdCache(TEST_CACHE_LIMIT) + private val logRecorder = LogRecorder().apply { Log.initialize(this) } + + @Test + fun empty_access_by_nulls() { + val recipientId = recipientIdCache[null, null] + + assertNull(recipientId) + } + + @Test + fun empty_access_by_uuid() { + val recipientId = recipientIdCache[randomAci(), null] + + assertNull(recipientId) + } + + @Test + fun empty_access_by_e164() { + val recipientId = recipientIdCache[null, "+155512345"] + + assertNull(recipientId) + } + + @Test + fun cache_hit_by_uuid() { + val recipientId1 = recipientId() + val sid1 = randomAci() + + recipientIdCache.put(recipient(recipientId1, sid1, null)) + + val recipientId = recipientIdCache[sid1, null] + + assertEquals(recipientId1, recipientId) + } + + @Test + fun cache_miss_by_uuid() { + val recipientId1 = recipientId() + val sid1 = randomAci() + val sid2 = randomAci() + + recipientIdCache.put(recipient(recipientId1, sid1, null)) + + val recipientId = recipientIdCache[sid2, null] + + assertNull(recipientId) + } + + @Test + fun cache_hit_by_uuid_e164_not_supplied_on_get() { + val recipientId1 = recipientId() + val sid1 = randomAci() + + recipientIdCache.put(recipient(recipientId1, sid1, "+15551234567")) + + val recipientId = recipientIdCache[sid1, null] + + assertEquals(recipientId1, recipientId) + } + + @Test + fun cache_miss_by_uuid_e164_not_supplied_on_put() { + val recipientId1 = recipientId() + val sid1 = randomAci() + + recipientIdCache.put(recipient(recipientId1, sid1, null)) + + val recipientId = recipientIdCache[sid1, "+15551234567"] + + assertNull(recipientId) + } + + @Test + fun cache_hit_by_e164() { + val recipientId1 = recipientId() + val e164 = "+1555123456" + + recipientIdCache.put(recipient(recipientId1, null, e164)) + + val recipientId = recipientIdCache[null, e164] + + assertEquals(recipientId1, recipientId) + } + + @Test + fun cache_miss_by_e164() { + val recipientId1 = recipientId() + val e164a = "+1555123456" + val e164b = "+1555123457" + + recipientIdCache.put(recipient(recipientId1, null, e164a)) + + val recipientId = recipientIdCache[null, e164b] + + assertNull(recipientId) + } + + @Test + fun cache_hit_by_e164_uuid_not_supplied_on_get() { + val recipientId1 = recipientId() + val sid1 = randomAci() + + recipientIdCache.put(recipient(recipientId1, sid1, "+15551234567")) + + val recipientId = recipientIdCache[null, "+15551234567"] + + assertEquals(recipientId1, recipientId) + } + + @Test + fun cache_miss_by_e164_uuid_not_supplied_on_put() { + val recipientId1 = recipientId() + val sid1 = randomAci() + val e164 = "+1555123456" + + recipientIdCache.put(recipient(recipientId1, null, e164)) + + val recipientId = recipientIdCache[sid1, e164] + + assertNull(recipientId) + } + + @Test + fun cache_hit_by_both() { + val recipientId1 = recipientId() + val sid1 = randomAci() + val e164 = "+1555123456" + + recipientIdCache.put(recipient(recipientId1, sid1, e164)) + + val recipientId = recipientIdCache[sid1, e164] + + assertEquals(recipientId1, recipientId) + } + + @Test + fun full_recipient_id_learned_by_two_puts() { + val recipientId1 = recipientId() + val sid1 = randomAci() + val e164 = "+1555123456" + + recipientIdCache.put(recipient(recipientId1, sid1, null)) + recipientIdCache.put(recipient(recipientId1, null, e164)) + + val recipientId = recipientIdCache[sid1, e164] + + assertEquals(recipientId1, recipientId) + } + + @Test + fun if_cache_state_disagrees_returns_null() { + val recipientId1 = recipientId() + val recipientId2 = recipientId() + val sid = randomAci() + val e164 = "+1555123456" + + recipientIdCache.put(recipient(recipientId1, null, e164)) + recipientIdCache.put(recipient(recipientId2, sid, null)) + + val recipientId = recipientIdCache[sid, e164] + + assertNull(recipientId) + + assertEquals(1, logRecorder.warnings.size) + assertEquals("Seen invalid RecipientIdCacheState", logRecorder.warnings.single().message) + } + + @Test + fun after_invalid_cache_hit_entries_are_cleared_up() { + val recipientId1 = recipientId() + val recipientId2 = recipientId() + val sid = randomAci() + val e164 = "+1555123456" + + recipientIdCache.put(recipient(recipientId1, null, e164)) + recipientIdCache.put(recipient(recipientId2, sid, null)) + + recipientIdCache[sid, e164] + + assertNull(recipientIdCache[sid, null]) + assertNull(recipientIdCache[null, e164]) + } + + @Test + fun multiple_entries() { + val recipientId1 = recipientId() + val recipientId2 = recipientId() + val sid1 = randomAci() + val sid2 = randomAci() + + recipientIdCache.put(recipient(recipientId1, sid1, null)) + recipientIdCache.put(recipient(recipientId2, sid2, null)) + + assertEquals(recipientId1, recipientIdCache[sid1, null]) + assertEquals(recipientId2, recipientIdCache[sid2, null]) + } + + @Test + fun drops_oldest_when_reaches_cache_limit() { + val recipientId1 = recipientId() + val sid1 = randomAci() + + recipientIdCache.put(recipient(recipientId1, sid1, null)) + + for (i in 0 until TEST_CACHE_LIMIT) { + recipientIdCache.put(recipient(recipientId(), randomAci(), null)) + } + + assertNull(recipientIdCache[sid1, null]) + } + + @Test + fun remains_in_cache_when_used_before_reaching_cache_limit() { + val recipientId1 = recipientId() + val sid1 = randomAci() + + recipientIdCache.put(recipient(recipientId1, sid1, null)) + + for (i in 0 until TEST_CACHE_LIMIT - 1) { + recipientIdCache.put(recipient(recipientId(), randomAci(), null)) + } + + assertEquals(recipientId1, recipientIdCache[sid1, null]) + + recipientIdCache.put(recipient(recipientId(), randomAci(), null)) + + assertEquals(recipientId1, recipientIdCache[sid1, null]) + } + + companion object { + private const val TEST_CACHE_LIMIT = 5 + + private fun recipientId(): RecipientId { + return mockk() + } + + private fun randomAci(): ServiceId { + return ServiceId.ACI.from(UUID.randomUUID()) + } + + private fun recipient(recipientId: RecipientId, serviceId: ServiceId?, e164: String?): Recipient { + return mockk { + every { this@mockk.id } returns recipientId + every { this@mockk.serviceId } returns Optional.ofNullable(serviceId) + every { this@mockk.e164 } returns Optional.ofNullable(e164) + } + } + } +} diff --git a/app/src/test/java/org/thoughtcrime/securesms/storage/StorageSyncHelperTest.java b/app/src/test/java/org/thoughtcrime/securesms/storage/StorageSyncHelperTest.java deleted file mode 100644 index ef570317d0..0000000000 --- a/app/src/test/java/org/thoughtcrime/securesms/storage/StorageSyncHelperTest.java +++ /dev/null @@ -1,182 +0,0 @@ -package org.thoughtcrime.securesms.storage; - -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.signal.core.util.logging.Log; -import org.thoughtcrime.securesms.recipients.Recipient; -import org.thoughtcrime.securesms.storage.StorageSyncHelper.IdDifferenceResult; -import org.thoughtcrime.securesms.util.RemoteConfig; -import org.whispersystems.signalservice.api.push.ServiceId.ACI; -import org.whispersystems.signalservice.api.storage.SignalContactRecord; -import org.whispersystems.signalservice.api.storage.SignalRecord; -import org.whispersystems.signalservice.api.storage.StorageId; -import org.whispersystems.signalservice.internal.storage.protos.ContactRecord; - -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; - -import okio.ByteString; - -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotEquals; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; -import static org.thoughtcrime.securesms.testutil.TestHelpers.assertContentsEqual; -import static org.thoughtcrime.securesms.testutil.TestHelpers.byteArray; -import static org.thoughtcrime.securesms.testutil.TestHelpers.byteListOf; - -public final class StorageSyncHelperTest { - - private static final ACI ACI_A = ACI.parseOrThrow("ebef429e-695e-4f51-bcc4-526a60ac68c7"); - private static final ACI ACI_SELF = ACI.parseOrThrow("1b2a2ca5-fc9e-4656-8c9f-22cc349ed3af"); - - private static final String E164_A = "+16108675309"; - private static final String E164_SELF = "+16105555555"; - - private static final Recipient SELF = mock(Recipient.class); - static { - when(SELF.getServiceId()).thenReturn(Optional.of(ACI_SELF)); - when(SELF.getE164()).thenReturn(Optional.of(E164_SELF)); - when(SELF.resolve()).thenReturn(SELF); - } - - @Rule - public MockitoRule rule = MockitoJUnit.rule(); - - @Mock - private MockedStatic recipientMockedStatic; - - @Mock - private MockedStatic remoteConfigMockedStatic; - - @Before - public void setup() { - recipientMockedStatic.when(Recipient::self).thenReturn(SELF); - Log.initialize(new Log.Logger[0]); - StorageSyncHelper.setTestKeyGenerator(null); - } - - @Test - public void findIdDifference_allOverlap() { - IdDifferenceResult result = StorageSyncHelper.findIdDifference(keyListOf(1, 2, 3), keyListOf(1, 2, 3)); - assertTrue(result.localOnlyIds.isEmpty()); - assertTrue(result.remoteOnlyIds.isEmpty()); - assertFalse(result.getHasTypeMismatches()); - } - - @Test - public void findIdDifference_noOverlap() { - IdDifferenceResult result = StorageSyncHelper.findIdDifference(keyListOf(1, 2, 3), keyListOf(4, 5, 6)); - assertContentsEqual(keyListOf(1, 2, 3), result.remoteOnlyIds); - assertContentsEqual(keyListOf(4, 5, 6), result.localOnlyIds); - assertFalse(result.getHasTypeMismatches()); - } - - @Test - public void findIdDifference_someOverlap() { - IdDifferenceResult result = StorageSyncHelper.findIdDifference(keyListOf(1, 2, 3), keyListOf(2, 3, 4)); - assertContentsEqual(keyListOf(1), result.remoteOnlyIds); - assertContentsEqual(keyListOf(4), result.localOnlyIds); - assertFalse(result.getHasTypeMismatches()); - } - - @Test - public void findIdDifference_typeMismatch_allOverlap() { - IdDifferenceResult result = StorageSyncHelper.findIdDifference(keyListOf(new HashMap() {{ - put(100, 1); - put(200, 2); - }}), - keyListOf(new HashMap() {{ - put(100, 1); - put(200, 1); - }})); - - assertTrue(result.localOnlyIds.isEmpty()); - assertTrue(result.remoteOnlyIds.isEmpty()); - assertTrue(result.getHasTypeMismatches()); - } - - @Test - public void findIdDifference_typeMismatch_someOverlap() { - IdDifferenceResult result = StorageSyncHelper.findIdDifference(keyListOf(new HashMap() {{ - put(100, 1); - put(200, 2); - put(300, 1); - }}), - keyListOf(new HashMap() {{ - put(100, 1); - put(200, 1); - put(400, 1); - }})); - - assertContentsEqual(Arrays.asList(StorageId.forType(byteArray(300), 1)), result.remoteOnlyIds); - assertContentsEqual(Arrays.asList(StorageId.forType(byteArray(400), 1)), result.localOnlyIds); - assertTrue(result.getHasTypeMismatches()); - } - - @Test - public void ContactUpdate_equals_sameProfileKeys() { - byte[] profileKey = new byte[32]; - byte[] profileKeyCopy = profileKey.clone(); - - ContactRecord contactA = contactBuilder(ACI_A, E164_A, "a").profileKey(ByteString.of(profileKey)).build(); - ContactRecord contactB = contactBuilder(ACI_A, E164_A, "a").profileKey(ByteString.of(profileKeyCopy)).build(); - - SignalContactRecord signalContactA = new SignalContactRecord(StorageId.forContact(byteArray(1)), contactA); - SignalContactRecord signalContactB = new SignalContactRecord(StorageId.forContact(byteArray(1)), contactB); - - assertEquals(signalContactA, signalContactB); - assertEquals(signalContactA.hashCode(), signalContactB.hashCode()); - - assertFalse(StorageSyncHelper.profileKeyChanged(update(signalContactA, signalContactB))); - } - - @Test - public void ContactUpdate_equals_differentProfileKeys() { - byte[] profileKey = new byte[32]; - byte[] profileKeyCopy = profileKey.clone(); - profileKeyCopy[0] = 1; - - ContactRecord contactA = contactBuilder(ACI_A, E164_A, "a").profileKey(ByteString.of(profileKey)).build(); - ContactRecord contactB = contactBuilder(ACI_A, E164_A, "a").profileKey(ByteString.of(profileKeyCopy)).build(); - - SignalContactRecord signalContactA = new SignalContactRecord(StorageId.forContact(byteArray(1)), contactA); - SignalContactRecord signalContactB = new SignalContactRecord(StorageId.forContact(byteArray(1)), contactB); - - assertNotEquals(signalContactA, signalContactB); - assertNotEquals(signalContactA.hashCode(), signalContactB.hashCode()); - - assertTrue(StorageSyncHelper.profileKeyChanged(update(signalContactA, signalContactB))); - } - - private static ContactRecord.Builder contactBuilder(ACI aci, String e164, String profileName) { - return new ContactRecord.Builder() - .aci(aci.toString()) - .e164(e164) - .givenName(profileName); - } - - private static > StorageRecordUpdate update(E oldRecord, E newRecord) { - return new StorageRecordUpdate<>(oldRecord, newRecord); - } - - private static List keyListOf(int... vals) { - return Stream.of(byteListOf(vals)).map(b -> StorageId.forType(b, 1)).toList(); - } - - private static List keyListOf(Map vals) { - return Stream.of(vals).map(e -> StorageId.forType(byteArray(e.getKey()), e.getValue())).toList(); - } -} diff --git a/app/src/test/java/org/thoughtcrime/securesms/storage/StorageSyncHelperTest.kt b/app/src/test/java/org/thoughtcrime/securesms/storage/StorageSyncHelperTest.kt new file mode 100644 index 0000000000..ac8d56faf3 --- /dev/null +++ b/app/src/test/java/org/thoughtcrime/securesms/storage/StorageSyncHelperTest.kt @@ -0,0 +1,150 @@ +package org.thoughtcrime.securesms.storage + +import okio.ByteString +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertNotEquals +import org.junit.Assert.assertTrue +import org.junit.Test +import org.thoughtcrime.securesms.storage.StorageSyncHelper.findIdDifference +import org.thoughtcrime.securesms.storage.StorageSyncHelper.profileKeyChanged +import org.thoughtcrime.securesms.testutil.TestHelpers +import org.whispersystems.signalservice.api.push.ServiceId.ACI +import org.whispersystems.signalservice.api.push.ServiceId.ACI.Companion.parseOrThrow +import org.whispersystems.signalservice.api.storage.SignalContactRecord +import org.whispersystems.signalservice.api.storage.SignalRecord +import org.whispersystems.signalservice.api.storage.StorageId +import org.whispersystems.signalservice.internal.storage.protos.ContactRecord + +class StorageSyncHelperTest { + @Test + fun findIdDifference_allOverlap() { + val result = findIdDifference(keyListOf(1, 2, 3), keyListOf(1, 2, 3)) + assertTrue(result.localOnlyIds.isEmpty()) + assertTrue(result.remoteOnlyIds.isEmpty()) + assertFalse(result.hasTypeMismatches) + } + + @Test + fun findIdDifference_noOverlap() { + val result = findIdDifference(keyListOf(1, 2, 3), keyListOf(4, 5, 6)) + TestHelpers.assertContentsEqual(keyListOf(1, 2, 3), result.remoteOnlyIds) + TestHelpers.assertContentsEqual(keyListOf(4, 5, 6), result.localOnlyIds) + assertFalse(result.hasTypeMismatches) + } + + @Test + fun findIdDifference_someOverlap() { + val result = findIdDifference(keyListOf(1, 2, 3), keyListOf(2, 3, 4)) + TestHelpers.assertContentsEqual(keyListOf(1), result.remoteOnlyIds) + TestHelpers.assertContentsEqual(keyListOf(4), result.localOnlyIds) + assertFalse(result.hasTypeMismatches) + } + + @Test + fun findIdDifference_typeMismatch_allOverlap() { + val result = findIdDifference( + keyListOf( + mapOf( + 100 to 1, + 200 to 2 + ) + ), + keyListOf( + mapOf( + 100 to 1, + 200 to 1 + ) + ) + ) + + assertTrue(result.localOnlyIds.isEmpty()) + assertTrue(result.remoteOnlyIds.isEmpty()) + assertTrue(result.hasTypeMismatches) + } + + @Test + fun findIdDifference_typeMismatch_someOverlap() { + val result = findIdDifference( + keyListOf( + mapOf( + 100 to 1, + 200 to 2, + 300 to 1 + ) + ), + keyListOf( + mapOf( + 100 to 1, + 200 to 1, + 400 to 1 + ) + ) + ) + + TestHelpers.assertContentsEqual(listOf(StorageId.forType(TestHelpers.byteArray(300), 1)), result.remoteOnlyIds) + TestHelpers.assertContentsEqual(listOf(StorageId.forType(TestHelpers.byteArray(400), 1)), result.localOnlyIds) + assertTrue(result.hasTypeMismatches) + } + + @Test + fun test_ContactUpdate_equals_sameProfileKeys() { + val profileKey = ByteArray(32) + val profileKeyCopy = profileKey.clone() + + val contactA = contactBuilder(ACI_A, E164_A, "a").profileKey(ByteString.of(*profileKey)).build() + val contactB = contactBuilder(ACI_A, E164_A, "a").profileKey(ByteString.of(*profileKeyCopy)).build() + + val signalContactA = SignalContactRecord(StorageId.forContact(TestHelpers.byteArray(1)), contactA) + val signalContactB = SignalContactRecord(StorageId.forContact(TestHelpers.byteArray(1)), contactB) + + assertEquals(signalContactA, signalContactB) + assertEquals(signalContactA.hashCode(), signalContactB.hashCode()) + + assertFalse(profileKeyChanged(update(signalContactA, signalContactB))) + } + + @Test + fun test_ContactUpdate_equals_differentProfileKeys() { + val profileKey = ByteArray(32) + val profileKeyCopy = profileKey.clone() + profileKeyCopy[0] = 1 + + val contactA = contactBuilder(ACI_A, E164_A, "a").profileKey(ByteString.of(*profileKey)).build() + val contactB = contactBuilder(ACI_A, E164_A, "a").profileKey(ByteString.of(*profileKeyCopy)).build() + + val signalContactA = SignalContactRecord(StorageId.forContact(TestHelpers.byteArray(1)), contactA) + val signalContactB = SignalContactRecord(StorageId.forContact(TestHelpers.byteArray(1)), contactB) + + assertNotEquals(signalContactA, signalContactB) + assertNotEquals(signalContactA.hashCode(), signalContactB.hashCode()) + + assertTrue(profileKeyChanged(update(signalContactA, signalContactB))) + } + + companion object { + private val ACI_A = parseOrThrow("ebef429e-695e-4f51-bcc4-526a60ac68c7") + + private const val E164_A = "+16108675309" + + @Suppress("SameParameterValue") + private fun contactBuilder(aci: ACI, e164: String, profileName: String): ContactRecord.Builder { + return ContactRecord.Builder() + .aci(aci.toString()) + .e164(e164) + .givenName(profileName) + } + + private fun > update(oldRecord: E, newRecord: E): StorageRecordUpdate { + return StorageRecordUpdate(oldRecord, newRecord) + } + + private fun keyListOf(vararg vals: Int): List { + return TestHelpers.byteListOf(*vals).map { StorageId.forType(it, 1) }.toList() + } + + private fun keyListOf(vals: Map): List { + return vals.map { StorageId.forType(TestHelpers.byteArray(it.key), it.value) }.toList() + } + } +} diff --git a/app/src/test/java/org/thoughtcrime/securesms/testutil/SecureRandomTestUtil.java b/app/src/test/java/org/thoughtcrime/securesms/testutil/SecureRandomTestUtil.java deleted file mode 100644 index 3cb7526e27..0000000000 --- a/app/src/test/java/org/thoughtcrime/securesms/testutil/SecureRandomTestUtil.java +++ /dev/null @@ -1,48 +0,0 @@ -package org.thoughtcrime.securesms.testutil; - -import org.mockito.ArgumentCaptor; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; - -import java.security.SecureRandom; - -import static org.junit.Assert.assertEquals; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.mock; - -public final class SecureRandomTestUtil { - - private SecureRandomTestUtil() { - } - - /** - * Creates a {@link SecureRandom} that returns exactly the {@param returnValue} the first time - * its {@link SecureRandom#nextBytes(byte[])}} method is called. - *

- * Any attempt to call with the incorrect length, or a second time will fail. - */ - public static SecureRandom mockRandom(byte[] returnValue) { - SecureRandom mock = mock(SecureRandom.class); - ArgumentCaptor argument = ArgumentCaptor.forClass(byte[].class); - - doAnswer(new Answer() { - - private int count; - - @Override - public Void answer(InvocationOnMock invocation) { - assertEquals("SecureRandom Mock: nextBytes only expected to be called once", 1, ++count); - - byte[] output = argument.getValue(); - - assertEquals("SecureRandom Mock: nextBytes byte[] length requested does not match byte[] setup", returnValue.length, output.length); - - System.arraycopy(returnValue, 0, output, 0, returnValue.length); - - return null; - } - }).when(mock).nextBytes(argument.capture()); - - return mock; - } -} diff --git a/app/src/test/java/org/thoughtcrime/securesms/testutil/SecureRandomTestUtil.kt b/app/src/test/java/org/thoughtcrime/securesms/testutil/SecureRandomTestUtil.kt new file mode 100644 index 0000000000..5d296771ea --- /dev/null +++ b/app/src/test/java/org/thoughtcrime/securesms/testutil/SecureRandomTestUtil.kt @@ -0,0 +1,31 @@ +package org.thoughtcrime.securesms.testutil + +import io.mockk.every +import io.mockk.mockk +import io.mockk.slot +import org.junit.Assert.assertEquals +import java.security.SecureRandom + +object SecureRandomTestUtil { + /** + * Creates a [SecureRandom] that returns exactly the {@param returnValue} the first time + * its [SecureRandom.nextBytes]} method is called. + * + * Any attempt to call with the incorrect length, or a second time will fail. + */ + @JvmStatic + fun mockRandom(returnValue: ByteArray): SecureRandom { + return mockk { + var count = 0 + val slot = slot() + every { + nextBytes(capture(slot)) + } answers { + assertEquals("SecureRandom Mock: nextBytes only expected to be called once", 1, ++count) + val output = slot.captured + assertEquals("SecureRandom Mock: nextBytes byte[] length requested does not match byte[] setup", returnValue.size, output.size) + System.arraycopy(returnValue, 0, output, 0, returnValue.size) + } + } + } +}