diff --git a/app/src/test/java/org/thoughtcrime/securesms/groups/v2/ProfileKeySetTest.java b/app/src/test/java/org/thoughtcrime/securesms/groups/v2/ProfileKeySetTest.java deleted file mode 100644 index 9d21f525977..00000000000 --- a/app/src/test/java/org/thoughtcrime/securesms/groups/v2/ProfileKeySetTest.java +++ /dev/null @@ -1,205 +0,0 @@ -package org.thoughtcrime.securesms.groups.v2; - -import org.junit.Test; -import org.signal.core.util.logging.Log; -import org.signal.libsignal.zkgroup.profiles.ProfileKey; -import org.thoughtcrime.securesms.crypto.ProfileKeyUtil; -import org.thoughtcrime.securesms.testutil.LogRecorder; -import org.whispersystems.signalservice.api.push.ServiceId.ACI; - -import java.util.Collections; -import java.util.UUID; - -import static org.hamcrest.CoreMatchers.is; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; -import static org.thoughtcrime.securesms.groups.v2.ChangeBuilder.changeBy; -import static org.thoughtcrime.securesms.groups.v2.ChangeBuilder.changeByUnknown; -import static org.thoughtcrime.securesms.testutil.LogRecorder.hasMessages; - -public final class ProfileKeySetTest { - - @Test - public void empty_change() { - ACI editor = ACI.from(UUID.randomUUID()); - ProfileKeySet profileKeySet = new ProfileKeySet(); - - profileKeySet.addKeysFromGroupChange(changeBy(editor).build()); - - assertTrue(profileKeySet.getProfileKeys().isEmpty()); - assertTrue(profileKeySet.getAuthoritativeProfileKeys().isEmpty()); - } - - @Test - public void new_member_is_not_authoritative() { - ACI editor = ACI.from(UUID.randomUUID()); - ACI newMember = ACI.from(UUID.randomUUID()); - ProfileKey profileKey = ProfileKeyUtil.createNew(); - ProfileKeySet profileKeySet = new ProfileKeySet(); - - profileKeySet.addKeysFromGroupChange(changeBy(editor).addMember(newMember, profileKey).build()); - - assertTrue(profileKeySet.getAuthoritativeProfileKeys().isEmpty()); - assertThat(profileKeySet.getProfileKeys(), is(Collections.singletonMap(newMember, profileKey))); - } - - @Test - public void new_member_by_self_is_authoritative() { - ACI newMember = ACI.from(UUID.randomUUID()); - ProfileKey profileKey = ProfileKeyUtil.createNew(); - ProfileKeySet profileKeySet = new ProfileKeySet(); - - profileKeySet.addKeysFromGroupChange(changeBy(newMember).addMember(newMember, profileKey).build()); - - assertTrue(profileKeySet.getProfileKeys().isEmpty()); - assertThat(profileKeySet.getAuthoritativeProfileKeys(), is(Collections.singletonMap(newMember, profileKey))); - } - - @Test - public void new_member_by_self_promote_is_authoritative() { - ACI newMember = ACI.from(UUID.randomUUID()); - ProfileKey profileKey = ProfileKeyUtil.createNew(); - ProfileKeySet profileKeySet = new ProfileKeySet(); - - profileKeySet.addKeysFromGroupChange(changeBy(newMember).promote(newMember, profileKey).build()); - - assertTrue(profileKeySet.getProfileKeys().isEmpty()); - assertThat(profileKeySet.getAuthoritativeProfileKeys(), is(Collections.singletonMap(newMember, profileKey))); - } - - @Test - public void new_member_by_promote_by_other_editor_is_not_authoritative() { - ACI editor = ACI.from(UUID.randomUUID()); - ACI newMember = ACI.from(UUID.randomUUID()); - ProfileKey profileKey = ProfileKeyUtil.createNew(); - ProfileKeySet profileKeySet = new ProfileKeySet(); - - profileKeySet.addKeysFromGroupChange(changeBy(editor).promote(newMember, profileKey).build()); - - assertTrue(profileKeySet.getAuthoritativeProfileKeys().isEmpty()); - assertThat(profileKeySet.getProfileKeys(), is(Collections.singletonMap(newMember, profileKey))); - } - - @Test - public void new_member_by_promote_by_unknown_editor_is_not_authoritative() { - ACI newMember = ACI.from(UUID.randomUUID()); - ProfileKey profileKey = ProfileKeyUtil.createNew(); - ProfileKeySet profileKeySet = new ProfileKeySet(); - - profileKeySet.addKeysFromGroupChange(changeByUnknown().promote(newMember, profileKey).build()); - - assertTrue(profileKeySet.getAuthoritativeProfileKeys().isEmpty()); - assertThat(profileKeySet.getProfileKeys(), is(Collections.singletonMap(newMember, profileKey))); - } - - @Test - public void profile_key_update_by_self_is_authoritative() { - ACI member = ACI.from(UUID.randomUUID()); - ProfileKey profileKey = ProfileKeyUtil.createNew(); - ProfileKeySet profileKeySet = new ProfileKeySet(); - - profileKeySet.addKeysFromGroupChange(changeBy(member).profileKeyUpdate(member, profileKey).build()); - - assertTrue(profileKeySet.getProfileKeys().isEmpty()); - assertThat(profileKeySet.getAuthoritativeProfileKeys(), is(Collections.singletonMap(member, profileKey))); - } - - @Test - public void profile_key_update_by_another_is_not_authoritative() { - ACI editor = ACI.from(UUID.randomUUID()); - ACI member = ACI.from(UUID.randomUUID()); - ProfileKey profileKey = ProfileKeyUtil.createNew(); - ProfileKeySet profileKeySet = new ProfileKeySet(); - - profileKeySet.addKeysFromGroupChange(changeBy(editor).profileKeyUpdate(member, profileKey).build()); - - assertTrue(profileKeySet.getAuthoritativeProfileKeys().isEmpty()); - assertThat(profileKeySet.getProfileKeys(), is(Collections.singletonMap(member, profileKey))); - } - - @Test - public void multiple_updates_overwrite() { - ACI editor = ACI.from(UUID.randomUUID()); - ACI member = ACI.from(UUID.randomUUID()); - ProfileKey profileKey1 = ProfileKeyUtil.createNew(); - ProfileKey profileKey2 = ProfileKeyUtil.createNew(); - ProfileKeySet profileKeySet = new ProfileKeySet(); - - profileKeySet.addKeysFromGroupChange(changeBy(editor).profileKeyUpdate(member, profileKey1).build()); - profileKeySet.addKeysFromGroupChange(changeBy(editor).profileKeyUpdate(member, profileKey2).build()); - - assertTrue(profileKeySet.getAuthoritativeProfileKeys().isEmpty()); - assertThat(profileKeySet.getProfileKeys(), is(Collections.singletonMap(member, profileKey2))); - } - - @Test - public void authoritative_takes_priority_when_seen_first() { - ACI editor = ACI.from(UUID.randomUUID()); - ACI member = ACI.from(UUID.randomUUID()); - ProfileKey profileKey1 = ProfileKeyUtil.createNew(); - ProfileKey profileKey2 = ProfileKeyUtil.createNew(); - ProfileKeySet profileKeySet = new ProfileKeySet(); - - profileKeySet.addKeysFromGroupChange(changeBy(member).profileKeyUpdate(member, profileKey1).build()); - profileKeySet.addKeysFromGroupChange(changeBy(editor).profileKeyUpdate(member, profileKey2).build()); - - assertTrue(profileKeySet.getProfileKeys().isEmpty()); - assertThat(profileKeySet.getAuthoritativeProfileKeys(), is(Collections.singletonMap(member, profileKey1))); - } - - @Test - public void authoritative_takes_priority_when_seen_second() { - ACI editor = ACI.from(UUID.randomUUID()); - ACI member = ACI.from(UUID.randomUUID()); - ProfileKey profileKey1 = ProfileKeyUtil.createNew(); - ProfileKey profileKey2 = ProfileKeyUtil.createNew(); - ProfileKeySet profileKeySet = new ProfileKeySet(); - - profileKeySet.addKeysFromGroupChange(changeBy(editor).profileKeyUpdate(member, profileKey1).build()); - profileKeySet.addKeysFromGroupChange(changeBy(member).profileKeyUpdate(member, profileKey2).build()); - - assertTrue(profileKeySet.getProfileKeys().isEmpty()); - assertThat(profileKeySet.getAuthoritativeProfileKeys(), is(Collections.singletonMap(member, profileKey2))); - } - - @Test - public void bad_profile_key() { - LogRecorder logRecorder = new LogRecorder(); - ACI editor = ACI.from(UUID.randomUUID()); - ACI member = ACI.from(UUID.randomUUID()); - byte[] badProfileKey = new byte[10]; - ProfileKeySet profileKeySet = new ProfileKeySet(); - - Log.initialize(logRecorder); - profileKeySet.addKeysFromGroupChange(changeBy(editor).profileKeyUpdate(member, badProfileKey).build()); - - assertTrue(profileKeySet.getProfileKeys().isEmpty()); - assertTrue(profileKeySet.getAuthoritativeProfileKeys().isEmpty()); - assertThat(logRecorder.getWarnings(), hasMessages("Bad profile key in group")); - } - - @Test - public void new_requesting_member_if_editor_is_authoritative() { - ACI editor = ACI.from(UUID.randomUUID()); - ProfileKey profileKey = ProfileKeyUtil.createNew(); - ProfileKeySet profileKeySet = new ProfileKeySet(); - - profileKeySet.addKeysFromGroupChange(changeBy(editor).requestJoin(profileKey).build()); - - assertThat(profileKeySet.getAuthoritativeProfileKeys(), is(Collections.singletonMap(editor, profileKey))); - assertTrue(profileKeySet.getProfileKeys().isEmpty()); - } - - @Test - public void new_requesting_member_if_not_editor_is_not_authoritative() { - ACI editor = ACI.from(UUID.randomUUID()); - ACI requesting = ACI.from(UUID.randomUUID()); - ProfileKey profileKey = ProfileKeyUtil.createNew(); - ProfileKeySet profileKeySet = new ProfileKeySet(); - - profileKeySet.addKeysFromGroupChange(changeBy(editor).requestJoin(requesting, profileKey).build()); - - assertTrue(profileKeySet.getAuthoritativeProfileKeys().isEmpty()); - assertThat(profileKeySet.getProfileKeys(), is(Collections.singletonMap(requesting, profileKey))); - } -} diff --git a/app/src/test/java/org/thoughtcrime/securesms/groups/v2/ProfileKeySetTest.kt b/app/src/test/java/org/thoughtcrime/securesms/groups/v2/ProfileKeySetTest.kt new file mode 100644 index 00000000000..b41d19d8f7d --- /dev/null +++ b/app/src/test/java/org/thoughtcrime/securesms/groups/v2/ProfileKeySetTest.kt @@ -0,0 +1,205 @@ +package org.thoughtcrime.securesms.groups.v2 + +import assertk.assertThat +import assertk.assertions.containsOnly +import assertk.assertions.isEmpty +import org.junit.Test +import org.signal.core.util.logging.Log.initialize +import org.thoughtcrime.securesms.crypto.ProfileKeyUtil +import org.thoughtcrime.securesms.testutil.LogRecorder +import org.whispersystems.signalservice.api.push.ServiceId +import java.util.UUID + +class ProfileKeySetTest { + @Test + fun empty_change() { + val editor = randomACI() + val profileKeySet = ProfileKeySet() + + profileKeySet.addKeysFromGroupChange(ChangeBuilder.changeBy(editor).build()) + + assertThat(profileKeySet.profileKeys).isEmpty() + assertThat(profileKeySet.authoritativeProfileKeys).isEmpty() + } + + @Test + fun new_member_is_not_authoritative() { + val editor = randomACI() + val newMember = randomACI() + val profileKey = ProfileKeyUtil.createNew() + val profileKeySet = ProfileKeySet() + + profileKeySet.addKeysFromGroupChange(ChangeBuilder.changeBy(editor).addMember(newMember, profileKey).build()) + + assertThat(profileKeySet.authoritativeProfileKeys).isEmpty() + assertThat(profileKeySet.profileKeys).containsOnly(newMember to profileKey) + } + + @Test + fun new_member_by_self_is_authoritative() { + val newMember = randomACI() + val profileKey = ProfileKeyUtil.createNew() + val profileKeySet = ProfileKeySet() + + profileKeySet.addKeysFromGroupChange(ChangeBuilder.changeBy(newMember).addMember(newMember, profileKey).build()) + + assertThat(profileKeySet.profileKeys).isEmpty() + assertThat(profileKeySet.authoritativeProfileKeys).containsOnly(newMember to profileKey) + } + + @Test + fun new_member_by_self_promote_is_authoritative() { + val newMember = randomACI() + val profileKey = ProfileKeyUtil.createNew() + val profileKeySet = ProfileKeySet() + + profileKeySet.addKeysFromGroupChange(ChangeBuilder.changeBy(newMember).promote(newMember, profileKey).build()) + + assertThat(profileKeySet.profileKeys).isEmpty() + assertThat(profileKeySet.authoritativeProfileKeys).containsOnly(newMember to profileKey) + } + + @Test + fun new_member_by_promote_by_other_editor_is_not_authoritative() { + val editor = randomACI() + val newMember = randomACI() + val profileKey = ProfileKeyUtil.createNew() + val profileKeySet = ProfileKeySet() + + profileKeySet.addKeysFromGroupChange(ChangeBuilder.changeBy(editor).promote(newMember, profileKey).build()) + + assertThat(profileKeySet.authoritativeProfileKeys).isEmpty() + assertThat(profileKeySet.profileKeys).containsOnly(newMember to profileKey) + } + + @Test + fun new_member_by_promote_by_unknown_editor_is_not_authoritative() { + val newMember = randomACI() + val profileKey = ProfileKeyUtil.createNew() + val profileKeySet = ProfileKeySet() + + profileKeySet.addKeysFromGroupChange(ChangeBuilder.changeByUnknown().promote(newMember, profileKey).build()) + + assertThat(profileKeySet.authoritativeProfileKeys).isEmpty() + assertThat(profileKeySet.profileKeys).containsOnly(newMember to profileKey) + } + + @Test + fun profile_key_update_by_self_is_authoritative() { + val member = randomACI() + val profileKey = ProfileKeyUtil.createNew() + val profileKeySet = ProfileKeySet() + + profileKeySet.addKeysFromGroupChange(ChangeBuilder.changeBy(member).profileKeyUpdate(member, profileKey).build()) + + assertThat(profileKeySet.profileKeys).isEmpty() + assertThat(profileKeySet.authoritativeProfileKeys).containsOnly(member to profileKey) + } + + @Test + fun profile_key_update_by_another_is_not_authoritative() { + val editor = randomACI() + val member = randomACI() + val profileKey = ProfileKeyUtil.createNew() + val profileKeySet = ProfileKeySet() + + profileKeySet.addKeysFromGroupChange(ChangeBuilder.changeBy(editor).profileKeyUpdate(member, profileKey).build()) + + assertThat(profileKeySet.authoritativeProfileKeys).isEmpty() + assertThat(profileKeySet.profileKeys).containsOnly(member to profileKey) + } + + @Test + fun multiple_updates_overwrite() { + val editor = randomACI() + val member = randomACI() + val profileKey1 = ProfileKeyUtil.createNew() + val profileKey2 = ProfileKeyUtil.createNew() + val profileKeySet = ProfileKeySet() + + profileKeySet.addKeysFromGroupChange(ChangeBuilder.changeBy(editor).profileKeyUpdate(member, profileKey1).build()) + profileKeySet.addKeysFromGroupChange(ChangeBuilder.changeBy(editor).profileKeyUpdate(member, profileKey2).build()) + + assertThat(profileKeySet.authoritativeProfileKeys).isEmpty() + assertThat(profileKeySet.profileKeys).containsOnly(member to profileKey2) + } + + @Test + fun authoritative_takes_priority_when_seen_first() { + val editor = randomACI() + val member = randomACI() + val profileKey1 = ProfileKeyUtil.createNew() + val profileKey2 = ProfileKeyUtil.createNew() + val profileKeySet = ProfileKeySet() + + profileKeySet.addKeysFromGroupChange(ChangeBuilder.changeBy(member).profileKeyUpdate(member, profileKey1).build()) + profileKeySet.addKeysFromGroupChange(ChangeBuilder.changeBy(editor).profileKeyUpdate(member, profileKey2).build()) + + assertThat(profileKeySet.profileKeys).isEmpty() + assertThat(profileKeySet.authoritativeProfileKeys).containsOnly(member to profileKey1) + } + + @Test + fun authoritative_takes_priority_when_seen_second() { + val editor = randomACI() + val member = randomACI() + val profileKey1 = ProfileKeyUtil.createNew() + val profileKey2 = ProfileKeyUtil.createNew() + val profileKeySet = ProfileKeySet() + + profileKeySet.addKeysFromGroupChange(ChangeBuilder.changeBy(editor).profileKeyUpdate(member, profileKey1).build()) + profileKeySet.addKeysFromGroupChange(ChangeBuilder.changeBy(member).profileKeyUpdate(member, profileKey2).build()) + + assertThat(profileKeySet.profileKeys).isEmpty() + assertThat(profileKeySet.authoritativeProfileKeys).containsOnly(member to profileKey2) + } + + @Test + fun bad_profile_key() { + val logRecorder = LogRecorder() + val editor = randomACI() + val member = randomACI() + val badProfileKey = ByteArray(10) + val profileKeySet = ProfileKeySet() + + initialize(logRecorder) + profileKeySet.addKeysFromGroupChange(ChangeBuilder.changeBy(editor).profileKeyUpdate(member, badProfileKey).build()) + + assertThat(profileKeySet.profileKeys).isEmpty() + assertThat(profileKeySet.authoritativeProfileKeys).isEmpty() + assertThat(logRecorder.warnings) + .transform { lines -> + lines.map { line -> + line.message + } + } + .containsOnly("Bad profile key in group") + } + + @Test + fun new_requesting_member_if_editor_is_authoritative() { + val editor = randomACI() + val profileKey = ProfileKeyUtil.createNew() + val profileKeySet = ProfileKeySet() + + profileKeySet.addKeysFromGroupChange(ChangeBuilder.changeBy(editor).requestJoin(profileKey).build()) + + assertThat(profileKeySet.authoritativeProfileKeys).containsOnly(editor to profileKey) + assertThat(profileKeySet.profileKeys).isEmpty() + } + + @Test + fun new_requesting_member_if_not_editor_is_not_authoritative() { + val editor = randomACI() + val requesting = randomACI() + val profileKey = ProfileKeyUtil.createNew() + val profileKeySet = ProfileKeySet() + + profileKeySet.addKeysFromGroupChange(ChangeBuilder.changeBy(editor).requestJoin(requesting, profileKey).build()) + + assertThat(profileKeySet.authoritativeProfileKeys).isEmpty() + assertThat(profileKeySet.profileKeys).containsOnly(requesting to profileKey) + } + + private fun randomACI(): ServiceId.ACI = ServiceId.ACI.from(UUID.randomUUID()) +} diff --git a/app/src/test/java/org/thoughtcrime/securesms/testutil/LogRecorder.java b/app/src/test/java/org/thoughtcrime/securesms/testutil/LogRecorder.java index 4860be1b1e6..b800384f930 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/testutil/LogRecorder.java +++ b/app/src/test/java/org/thoughtcrime/securesms/testutil/LogRecorder.java @@ -1,8 +1,5 @@ package org.thoughtcrime.securesms.testutil; -import org.hamcrest.BaseMatcher; -import org.hamcrest.Description; -import org.hamcrest.Matcher; import org.signal.core.util.logging.Log; import java.util.ArrayList; @@ -93,46 +90,4 @@ public Throwable getThrowable() { return throwable; } } - - @SafeVarargs - public static Matcher hasMessages(T... messages) { - return new BaseMatcher() { - - @Override - public void describeTo(Description description) { - description.appendValueList("[", ", ", "]", messages); - } - - @Override - public void describeMismatch(Object item, Description description) { - @SuppressWarnings("unchecked") - List list = (List) item; - ArrayList messages = new ArrayList<>(list.size()); - - for (Entry e : list) { - messages.add(e.message); - } - - description.appendText("was ").appendValueList("[", ", ", "]", messages); - } - - @Override - public boolean matches(Object item) { - @SuppressWarnings("unchecked") - List list = (List) item; - - if (list.size() != messages.length) { - return false; - } - - for (int i = 0; i < messages.length; i++) { - if (!list.get(i).message.equals(messages[i])) { - return false; - } - } - - return true; - } - }; - } }