parent
574d6c51ab
commit
c2aceb2bd1
12 changed files with 965 additions and 1038 deletions
|
@ -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<CallParticipantListUpdate.Wrapper> added = Collections.emptySet();
|
||||
Set<CallParticipantListUpdate.Wrapper> removed = Collections.emptySet();
|
||||
CallParticipantListUpdate update = new CallParticipantListUpdate(added, removed);
|
||||
|
||||
// THEN
|
||||
assertTrue(update.hasNoChanges());
|
||||
assertFalse(update.hasSingleChange());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenOneEmptySet_thenExpectMultipleChanges() {
|
||||
// GIVEN
|
||||
Set<CallParticipantListUpdate.Wrapper> added = new HashSet<>(Arrays.asList(createWrappers(1, 2, 3)));
|
||||
Set<CallParticipantListUpdate.Wrapper> removed = Collections.emptySet();
|
||||
CallParticipantListUpdate update = new CallParticipantListUpdate(added, removed);
|
||||
|
||||
// THEN
|
||||
assertFalse(update.hasNoChanges());
|
||||
assertFalse(update.hasSingleChange());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenNoEmptySets_thenExpectMultipleChanges() {
|
||||
// GIVEN
|
||||
Set<CallParticipantListUpdate.Wrapper> added = new HashSet<>(Arrays.asList(createWrappers(1, 2, 3)));
|
||||
Set<CallParticipantListUpdate.Wrapper> 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<CallParticipantListUpdate.Wrapper> added = new HashSet<>(Arrays.asList(createWrappers(1)));
|
||||
Set<CallParticipantListUpdate.Wrapper> removed = Collections.emptySet();
|
||||
CallParticipantListUpdate update = new CallParticipantListUpdate(added, removed);
|
||||
|
||||
// THEN
|
||||
assertFalse(update.hasNoChanges());
|
||||
assertTrue(update.hasSingleChange());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void whenFirstListIsAdded_thenIExpectAnUpdateWithAllItemsFromListAdded() {
|
||||
// GIVEN
|
||||
List<CallParticipant> 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<CallParticipant> 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<CallParticipant> 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<CallParticipant> list1 = createParticipants(1, 2, 3, 4, 5);
|
||||
List<CallParticipant> 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<CallParticipant> list = createParticipants(new long[]{1, 1, 1}, new long[]{1, 2, 3});
|
||||
|
||||
// WHEN
|
||||
CallParticipantListUpdate update = CallParticipantListUpdate.computeDeltaUpdate(Collections.emptyList(), list);
|
||||
|
||||
// THEN
|
||||
List<Boolean> 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<Long> 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<CallParticipant> createPlaceholderParticipants(long ... recipientIds) {
|
||||
long[] deMuxIds = new long[recipientIds.length];
|
||||
Arrays.fill(deMuxIds, -1);
|
||||
return createParticipants(recipientIds, deMuxIds);
|
||||
}
|
||||
|
||||
private static List<CallParticipant> createParticipants(long ... recipientIds) {
|
||||
return createParticipants(recipientIds, recipientIds);
|
||||
}
|
||||
|
||||
private static List<CallParticipant> createParticipants(long[] recipientIds, long[] placeholderIds) {
|
||||
List<CallParticipant> participants = new ArrayList<>(recipientIds.length);
|
||||
Set<Long> 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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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<CallParticipantListUpdate.Wrapper>()
|
||||
val removed = emptySet<CallParticipantListUpdate.Wrapper>()
|
||||
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<CallParticipantListUpdate.Wrapper>()
|
||||
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<CallParticipantListUpdate.Wrapper>()
|
||||
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<CallParticipantListUpdate.Wrapper?> {
|
||||
val ids = arrayOfNulls<CallParticipantListUpdate.Wrapper>(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<CallParticipant> {
|
||||
val deMuxIds = LongArray(recipientIds.size) { -1 }
|
||||
return createParticipants(recipientIds, deMuxIds)
|
||||
}
|
||||
|
||||
private fun createParticipants(vararg recipientIds: Long): List<CallParticipant> {
|
||||
return createParticipants(recipientIds, recipientIds)
|
||||
}
|
||||
|
||||
private fun createParticipants(recipientIds: LongArray, placeholderIds: LongArray): List<CallParticipant> {
|
||||
val participants = mutableListOf<CallParticipant>()
|
||||
val primaries = mutableSetOf<Long>()
|
||||
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<AppDependencies> applicationDependenciesMockedStatic;
|
||||
|
||||
@Mock
|
||||
private MockedStatic<SignalDatabase> signalDatabaseMockedStatic;
|
||||
|
||||
@Mock
|
||||
private MockedStatic<RemoteConfig> 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
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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<ThreadTable>(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<Cursor>(relaxed = true) {
|
||||
every { count } returns pinned
|
||||
}
|
||||
every {
|
||||
threadTable.getUnarchivedConversationList(any(), false, any(), any(), any())
|
||||
} returns mockk<Cursor>(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
|
||||
)
|
||||
}
|
||||
}
|
|
@ -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<String, Job.Factory> factories = JobManagerFactories.getJobFactories(mock(Application.class));
|
||||
|
||||
assertTrue(factories.get("PushContentReceiveJob") instanceof FailingJob.Factory);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void AttachmentUploadJob_is_retired() {
|
||||
Map<String, Job.Factory> factories = JobManagerFactories.getJobFactories(mock(Application.class));
|
||||
|
||||
assertTrue(factories.get("AttachmentUploadJob") instanceof FailingJob.Factory);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void MmsSendJob_is_retired() {
|
||||
Map<String, Job.Factory> factories = JobManagerFactories.getJobFactories(mock(Application.class));
|
||||
|
||||
assertTrue(factories.get("MmsSendJob") instanceof FailingJob.Factory);
|
||||
}
|
||||
}
|
|
@ -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<Application>())
|
||||
|
||||
assertTrue(factories["PushContentReceiveJob"] is FailingJob.Factory)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_AttachmentUploadJob_is_retired() {
|
||||
val factories = JobManagerFactories.getJobFactories(mockk<Application>())
|
||||
|
||||
assertTrue(factories["AttachmentUploadJob"] is FailingJob.Factory)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_MmsSendJob_is_retired() {
|
||||
val factories = JobManagerFactories.getJobFactories(mockk<Application>())
|
||||
|
||||
assertTrue(factories["MmsSendJob"] is FailingJob.Factory)
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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<RecipientId>()
|
||||
}
|
||||
|
||||
private fun randomAci(): ServiceId {
|
||||
return ServiceId.ACI.from(UUID.randomUUID())
|
||||
}
|
||||
|
||||
private fun recipient(recipientId: RecipientId, serviceId: ServiceId?, e164: String?): Recipient {
|
||||
return mockk<Recipient> {
|
||||
every { this@mockk.id } returns recipientId
|
||||
every { this@mockk.serviceId } returns Optional.ofNullable(serviceId)
|
||||
every { this@mockk.e164 } returns Optional.ofNullable(e164)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<Recipient> recipientMockedStatic;
|
||||
|
||||
@Mock
|
||||
private MockedStatic<RemoteConfig> 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<Integer, Integer>() {{
|
||||
put(100, 1);
|
||||
put(200, 2);
|
||||
}}),
|
||||
keyListOf(new HashMap<Integer, Integer>() {{
|
||||
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<Integer, Integer>() {{
|
||||
put(100, 1);
|
||||
put(200, 2);
|
||||
put(300, 1);
|
||||
}}),
|
||||
keyListOf(new HashMap<Integer, Integer>() {{
|
||||
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 <E extends SignalRecord<?>> StorageRecordUpdate<E> update(E oldRecord, E newRecord) {
|
||||
return new StorageRecordUpdate<>(oldRecord, newRecord);
|
||||
}
|
||||
|
||||
private static List<StorageId> keyListOf(int... vals) {
|
||||
return Stream.of(byteListOf(vals)).map(b -> StorageId.forType(b, 1)).toList();
|
||||
}
|
||||
|
||||
private static List<StorageId> keyListOf(Map<Integer, Integer> vals) {
|
||||
return Stream.of(vals).map(e -> StorageId.forType(byteArray(e.getKey()), e.getValue())).toList();
|
||||
}
|
||||
}
|
|
@ -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 <E : SignalRecord<*>> update(oldRecord: E, newRecord: E): StorageRecordUpdate<E> {
|
||||
return StorageRecordUpdate(oldRecord, newRecord)
|
||||
}
|
||||
|
||||
private fun keyListOf(vararg vals: Int): List<StorageId> {
|
||||
return TestHelpers.byteListOf(*vals).map { StorageId.forType(it, 1) }.toList()
|
||||
}
|
||||
|
||||
private fun keyListOf(vals: Map<Int, Int>): List<StorageId> {
|
||||
return vals.map { StorageId.forType(TestHelpers.byteArray(it.key), it.value) }.toList()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
* <p>
|
||||
* 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<byte[]> argument = ArgumentCaptor.forClass(byte[].class);
|
||||
|
||||
doAnswer(new Answer<Void>() {
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -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<SecureRandom> {
|
||||
var count = 0
|
||||
val slot = slot<ByteArray>()
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue