package org.thoughtcrime.securesms.database.model; import android.app.Application; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.test.core.app.ApplicationProvider; import com.annimon.stream.Stream; import com.google.common.collect.ImmutableMap; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.powermock.core.classloader.annotations.PowerMockIgnore; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.rule.PowerMockRule; import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; import org.signal.storageservice.protos.groups.AccessControl; import org.signal.storageservice.protos.groups.local.DecryptedGroup; import org.signal.storageservice.protos.groups.local.DecryptedGroupChange; import org.signal.storageservice.protos.groups.local.DecryptedMember; import org.signal.storageservice.protos.groups.local.DecryptedPendingMember; import org.thoughtcrime.securesms.testutil.MainThreadUtil; import org.thoughtcrime.securesms.util.Util; import org.whispersystems.signalservice.api.util.UuidUtil; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.UUID; import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; 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.util.StringUtil.isolateBidi; @RunWith(RobolectricTestRunner.class) @Config(manifest = Config.NONE, application = Application.class) @PowerMockIgnore({ "org.mockito.*", "org.robolectric.*", "android.*", "androidx.*" }) @PrepareForTest(Util.class) public final class GroupsV2UpdateMessageProducerTest { private UUID you; private UUID alice; private UUID bob; private GroupsV2UpdateMessageProducer producer; @Rule public PowerMockRule powerMockRule = new PowerMockRule(); @Before public void setup() { you = UUID.randomUUID(); alice = UUID.randomUUID(); bob = UUID.randomUUID(); GroupsV2UpdateMessageProducer.DescribeMemberStrategy describeMember = createDescriber(ImmutableMap.of(alice, "Alice", bob, "Bob")); producer = new GroupsV2UpdateMessageProducer(ApplicationProvider.getApplicationContext(), describeMember, you); } @Test public void empty_change() { DecryptedGroupChange change = changeBy(alice) .build(); assertThat(describeChange(change), is(singletonList("Alice updated the group."))); } @Test public void empty_change_by_you() { DecryptedGroupChange change = changeBy(you) .build(); assertThat(describeChange(change), is(singletonList("You updated the group."))); } @Test public void empty_change_by_unknown() { DecryptedGroupChange change = changeByUnknown() .build(); assertThat(describeChange(change), is(singletonList("The group was updated."))); } // Member additions @Test public void member_added_member() { DecryptedGroupChange change = changeBy(alice) .addMember(bob) .build(); assertThat(describeChange(change), is(singletonList("Alice added Bob."))); } @Test public void member_added_member_mentions_both() { DecryptedGroupChange change = changeBy(alice) .addMember(bob) .build(); assertSingleChangeMentioning(change, Arrays.asList(alice, bob)); } @Test public void you_added_member() { DecryptedGroupChange change = changeBy(you) .addMember(bob) .build(); assertThat(describeChange(change), is(singletonList("You added Bob."))); } @Test public void you_added_member_mentions_just_member() { DecryptedGroupChange change = changeBy(you) .addMember(bob) .build(); assertSingleChangeMentioning(change, singletonList(bob)); } @Test public void member_added_you() { DecryptedGroupChange change = changeBy(alice) .addMember(you) .build(); assertThat(describeChange(change), is(singletonList("Alice added you to the group."))); } @Test public void you_added_you() { DecryptedGroupChange change = changeBy(you) .addMember(you) .build(); assertThat(describeChange(change), is(singletonList("You joined the group via the group link."))); } @Test public void member_added_themselves() { DecryptedGroupChange change = changeBy(bob) .addMember(bob) .build(); assertThat(describeChange(change), is(singletonList("Bob joined the group via the group link."))); } @Test public void member_added_themselves_mentions_just_member() { DecryptedGroupChange change = changeBy(bob) .addMember(bob) .build(); assertSingleChangeMentioning(change, singletonList(bob)); } @Test public void unknown_added_you() { DecryptedGroupChange change = changeByUnknown() .addMember(you) .build(); assertThat(describeChange(change), is(singletonList("You joined the group."))); } @Test public void unknown_added_member() { DecryptedGroupChange change = changeByUnknown() .addMember(bob) .build(); assertThat(describeChange(change), is(singletonList("Bob joined the group."))); } @Test public void member_added_you_and_another_where_you_are_not_first() { DecryptedGroupChange change = changeBy(bob) .addMember(alice) .addMember(you) .build(); assertThat(describeChange(change), is(Arrays.asList("Bob added you to the group.", "Bob added Alice."))); } @Test public void unknown_member_added_you_and_another_where_you_are_not_first() { DecryptedGroupChange change = changeByUnknown() .addMember(alice) .addMember(you) .build(); assertThat(describeChange(change), is(Arrays.asList("You joined the group.", "Alice joined the group."))); } @Test public void you_added_you_and_another_where_you_are_not_first() { DecryptedGroupChange change = changeBy(you) .addMember(alice) .addMember(you) .build(); assertThat(describeChange(change), is(Arrays.asList("You joined the group via the group link.", "You added Alice."))); } // Member removals @Test public void member_removed_member() { DecryptedGroupChange change = changeBy(alice) .deleteMember(bob) .build(); assertThat(describeChange(change), is(singletonList("Alice removed Bob."))); } @Test public void you_removed_member() { DecryptedGroupChange change = changeBy(you) .deleteMember(bob) .build(); assertThat(describeChange(change), is(singletonList("You removed Bob."))); } @Test public void member_removed_you() { DecryptedGroupChange change = changeBy(alice) .deleteMember(you) .build(); assertThat(describeChange(change), is(singletonList("Alice removed you from the group."))); } @Test public void you_removed_you() { DecryptedGroupChange change = changeBy(you) .deleteMember(you) .build(); assertThat(describeChange(change), is(singletonList("You left the group."))); } @Test public void member_removed_themselves() { DecryptedGroupChange change = changeBy(bob) .deleteMember(bob) .build(); assertThat(describeChange(change), is(singletonList("Bob left the group."))); } @Test public void unknown_removed_member() { DecryptedGroupChange change = changeByUnknown() .deleteMember(alice) .build(); assertThat(describeChange(change), is(singletonList("Alice is no longer in the group."))); } @Test public void unknown_removed_you() { DecryptedGroupChange change = changeByUnknown() .deleteMember(you) .build(); assertThat(describeChange(change), is(singletonList("You are no longer in the group."))); } // Member role modifications @Test public void you_make_member_admin() { DecryptedGroupChange change = changeBy(you) .promoteToAdmin(alice) .build(); assertThat(describeChange(change), is(singletonList("You made Alice an admin."))); } @Test public void member_makes_member_admin() { DecryptedGroupChange change = changeBy(bob) .promoteToAdmin(alice) .build(); assertThat(describeChange(change), is(singletonList("Bob made Alice an admin."))); } @Test public void member_makes_you_admin() { DecryptedGroupChange change = changeBy(alice) .promoteToAdmin(you) .build(); assertThat(describeChange(change), is(singletonList("Alice made you an admin."))); } @Test public void you_revoked_member_admin() { DecryptedGroupChange change = changeBy(you) .demoteToMember(bob) .build(); assertThat(describeChange(change), is(singletonList("You revoked admin privileges from Bob."))); } @Test public void member_revokes_member_admin() { DecryptedGroupChange change = changeBy(bob) .demoteToMember(alice) .build(); assertThat(describeChange(change), is(singletonList("Bob revoked admin privileges from Alice."))); } @Test public void member_revokes_your_admin() { DecryptedGroupChange change = changeBy(alice) .demoteToMember(you) .build(); assertThat(describeChange(change), is(singletonList("Alice revoked your admin privileges."))); } @Test public void unknown_makes_member_admin() { DecryptedGroupChange change = changeByUnknown() .promoteToAdmin(alice) .build(); assertThat(describeChange(change), is(singletonList("Alice is now an admin."))); } @Test public void unknown_makes_you_admin() { DecryptedGroupChange change = changeByUnknown() .promoteToAdmin(you) .build(); assertThat(describeChange(change), is(singletonList("You are now an admin."))); } @Test public void unknown_revokes_member_admin() { DecryptedGroupChange change = changeByUnknown() .demoteToMember(alice) .build(); assertThat(describeChange(change), is(singletonList("Alice is no longer an admin."))); } @Test public void unknown_revokes_your_admin() { DecryptedGroupChange change = changeByUnknown() .demoteToMember(you) .build(); assertThat(describeChange(change), is(singletonList("You are no longer an admin."))); } // Member invitation @Test public void you_invited_member() { DecryptedGroupChange change = changeBy(you) .invite(alice) .build(); assertThat(describeChange(change), is(singletonList("You invited Alice to the group."))); } @Test public void member_invited_you() { DecryptedGroupChange change = changeBy(alice) .invite(you) .build(); assertThat(describeChange(change), is(singletonList("Alice invited you to the group."))); } @Test public void member_invited_1_person() { DecryptedGroupChange change = changeBy(alice) .invite(bob) .build(); assertThat(describeChange(change), is(singletonList("Alice invited 1 person to the group."))); } @Test public void member_invited_2_persons() { DecryptedGroupChange change = changeBy(alice) .invite(bob) .invite(UUID.randomUUID()) .build(); assertThat(describeChange(change), is(singletonList("Alice invited 2 people to the group."))); } @Test public void member_invited_3_persons_and_you() { DecryptedGroupChange change = changeBy(bob) .invite(alice) .invite(you) .invite(UUID.randomUUID()) .invite(UUID.randomUUID()) .build(); assertThat(describeChange(change), is(Arrays.asList("Bob invited you to the group.", "Bob invited 3 people to the group."))); } @Test public void unknown_editor_but_known_invitee_invited_you() { DecryptedGroupChange change = changeByUnknown() .inviteBy(you, alice) .build(); assertThat(describeChange(change), is(singletonList("Alice invited you to the group."))); } @Test public void unknown_editor_and_unknown_inviter_invited_you() { DecryptedGroupChange change = changeByUnknown() .invite(you) .build(); assertThat(describeChange(change), is(singletonList("You were invited to the group."))); } @Test public void unknown_invited_1_person() { DecryptedGroupChange change = changeByUnknown() .invite(alice) .build(); assertThat(describeChange(change), is(singletonList("1 person was invited to the group."))); } @Test public void unknown_invited_2_persons() { DecryptedGroupChange change = changeByUnknown() .invite(alice) .invite(bob) .build(); assertThat(describeChange(change), is(singletonList("2 people were invited to the group."))); } @Test public void unknown_invited_3_persons_and_you() { DecryptedGroupChange change = changeByUnknown() .invite(alice) .invite(you) .invite(UUID.randomUUID()) .invite(UUID.randomUUID()) .build(); assertThat(describeChange(change), is(Arrays.asList("You were invited to the group.", "3 people were invited to the group."))); } @Test public void unknown_editor_invited_3_persons_and_you_inviter_known() { DecryptedGroupChange change = changeByUnknown() .invite(alice) .inviteBy(you, bob) .invite(UUID.randomUUID()) .invite(UUID.randomUUID()) .build(); assertThat(describeChange(change), is(Arrays.asList("Bob invited you to the group.", "3 people were invited to the group."))); } @Test public void member_invited_3_persons_and_you_and_added_another_where_you_were_not_first() { DecryptedGroupChange change = changeBy(bob) .addMember(alice) .invite(you) .invite(UUID.randomUUID()) .invite(UUID.randomUUID()) .build(); assertThat(describeChange(change), is(Arrays.asList("Bob invited you to the group.", "Bob added Alice.", "Bob invited 2 people to the group."))); } @Test public void unknown_editor_but_known_invitee_invited_you_and_added_another_where_you_were_not_first() { DecryptedGroupChange change = changeByUnknown() .addMember(bob) .inviteBy(you, alice) .build(); assertThat(describeChange(change), is(Arrays.asList("Alice invited you to the group.", "Bob joined the group."))); } @Test public void unknown_editor_and_unknown_inviter_invited_you_and_added_another_where_you_were_not_first() { DecryptedGroupChange change = changeByUnknown() .addMember(alice) .invite(you) .build(); assertThat(describeChange(change), is(Arrays.asList("You were invited to the group.", "Alice joined the group."))); } // Member invitation revocation @Test public void member_uninvited_1_person() { DecryptedGroupChange change = changeBy(alice) .uninvite(bob) .build(); assertThat(describeChange(change), is(singletonList("Alice revoked an invitation to the group."))); } @Test public void member_uninvited_2_people() { DecryptedGroupChange change = changeBy(alice) .uninvite(bob) .uninvite(UUID.randomUUID()) .build(); assertThat(describeChange(change), is(singletonList("Alice revoked 2 invitations to the group."))); } @Test public void you_uninvited_1_person() { DecryptedGroupChange change = changeBy(you) .uninvite(bob) .build(); assertThat(describeChange(change), is(singletonList("You revoked an invitation to the group."))); } @Test public void you_uninvited_2_people() { DecryptedGroupChange change = changeBy(you) .uninvite(bob) .uninvite(UUID.randomUUID()) .build(); assertThat(describeChange(change), is(singletonList("You revoked 2 invitations to the group."))); } @Test public void pending_member_declines_invite() { DecryptedGroupChange change = changeBy(bob) .uninvite(bob) .build(); assertThat(describeChange(change), is(singletonList("Someone declined an invitation to the group."))); } @Test public void you_decline_invite() { DecryptedGroupChange change = changeBy(you) .uninvite(you) .build(); assertThat(describeChange(change), is(singletonList("You declined the invitation to the group."))); } @Test public void unknown_revokes_your_invite() { DecryptedGroupChange change = changeByUnknown() .uninvite(you) .build(); assertThat(describeChange(change), is(singletonList("An admin revoked your invitation to the group."))); } @Test public void unknown_revokes_1_invite() { DecryptedGroupChange change = changeByUnknown() .uninvite(bob) .build(); assertThat(describeChange(change), is(singletonList("An invitation to the group was revoked."))); } @Test public void unknown_revokes_2_invites() { DecryptedGroupChange change = changeByUnknown() .uninvite(bob) .uninvite(UUID.randomUUID()) .build(); assertThat(describeChange(change), is(singletonList("2 invitations to the group were revoked."))); } @Test public void unknown_revokes_yours_and_three_other_invites() { DecryptedGroupChange change = changeByUnknown() .uninvite(bob) .uninvite(you) .uninvite(UUID.randomUUID()) .uninvite(UUID.randomUUID()) .build(); assertThat(describeChange(change), is(Arrays.asList("An admin revoked your invitation to the group.", "3 invitations to the group were revoked."))); } @Test public void your_invite_was_revoked_by_known_member() { DecryptedGroupChange change = changeBy(bob) .uninvite(you) .build(); assertThat(describeChange(change), is(singletonList("Bob revoked your invitation to the group."))); } // Promote pending members @Test public void member_accepts_invite() { DecryptedGroupChange change = changeBy(bob) .promote(bob) .build(); assertThat(describeChange(change), is(singletonList("Bob accepted an invitation to the group."))); } @Test public void you_accept_invite() { DecryptedGroupChange change = changeBy(you) .promote(you) .build(); assertThat(describeChange(change), is(singletonList("You accepted the invitation to the group."))); } @Test public void member_promotes_pending_member() { DecryptedGroupChange change = changeBy(bob) .promote(alice) .build(); assertThat(describeChange(change), is(singletonList("Bob added invited member Alice."))); } @Test public void you_promote_pending_member() { DecryptedGroupChange change = changeBy(you) .promote(bob) .build(); assertThat(describeChange(change), is(singletonList("You added invited member Bob."))); } @Test public void member_promotes_you() { DecryptedGroupChange change = changeBy(bob) .promote(you) .build(); assertThat(describeChange(change), is(singletonList("Bob added you to the group."))); } @Test public void unknown_added_by_invite() { DecryptedGroupChange change = changeByUnknown() .promote(you) .build(); assertThat(describeChange(change), is(singletonList("You joined the group."))); } @Test public void unknown_promotes_pending_member() { DecryptedGroupChange change = changeByUnknown() .promote(alice) .build(); assertThat(describeChange(change), is(singletonList("Alice joined the group."))); } // Title change @Test public void member_changes_title() { DecryptedGroupChange change = changeBy(alice) .title("New title") .build(); assertThat(describeChange(change), is(singletonList("Alice changed the group name to \"" + isolateBidi("New title") + "\"."))); } @Test public void you_change_title() { DecryptedGroupChange change = changeBy(you) .title("Title 2") .build(); assertThat(describeChange(change), is(singletonList("You changed the group name to \"" + isolateBidi("Title 2") + "\"."))); } @Test public void unknown_changed_title() { DecryptedGroupChange change = changeByUnknown() .title("Title 3") .build(); assertThat(describeChange(change), is(singletonList("The group name has changed to \"" + isolateBidi("Title 3") + "\"."))); } // Avatar change @Test public void member_changes_avatar() { DecryptedGroupChange change = changeBy(alice) .avatar("Avatar1") .build(); assertThat(describeChange(change), is(singletonList("Alice changed the group avatar."))); } @Test public void you_change_avatar() { DecryptedGroupChange change = changeBy(you) .avatar("Avatar2") .build(); assertThat(describeChange(change), is(singletonList("You changed the group avatar."))); } @Test public void unknown_changed_avatar() { DecryptedGroupChange change = changeByUnknown() .avatar("Avatar3") .build(); assertThat(describeChange(change), is(singletonList("The group avatar has been changed."))); } // Timer change @Test public void member_changes_timer() { DecryptedGroupChange change = changeBy(bob) .timer(10) .build(); assertThat(describeChange(change), is(singletonList("Bob set the disappearing message timer to 10 seconds."))); } @Test public void you_change_timer() { DecryptedGroupChange change = changeBy(you) .timer(60) .build(); assertThat(describeChange(change), is(singletonList("You set the disappearing message timer to 1 minute."))); } @Test public void unknown_change_timer() { DecryptedGroupChange change = changeByUnknown() .timer(120) .build(); assertThat(describeChange(change), is(singletonList("The disappearing message timer has been set to 2 minutes."))); } @Test public void unknown_change_timer_mentions_no_one() { DecryptedGroupChange change = changeByUnknown() .timer(120) .build(); assertSingleChangeMentioning(change, emptyList()); } // Attribute access change @Test public void member_changes_attribute_access() { DecryptedGroupChange change = changeBy(bob) .attributeAccess(AccessControl.AccessRequired.MEMBER) .build(); assertThat(describeChange(change), is(singletonList("Bob changed who can edit group info to \"All members\"."))); } @Test public void you_changed_attribute_access() { DecryptedGroupChange change = changeBy(you) .attributeAccess(AccessControl.AccessRequired.ADMINISTRATOR) .build(); assertThat(describeChange(change), is(singletonList("You changed who can edit group info to \"Only admins\"."))); } @Test public void unknown_changed_attribute_access() { DecryptedGroupChange change = changeByUnknown() .attributeAccess(AccessControl.AccessRequired.ADMINISTRATOR) .build(); assertThat(describeChange(change), is(singletonList("Who can edit group info has been changed to \"Only admins\"."))); } // Membership access change @Test public void member_changes_membership_access() { DecryptedGroupChange change = changeBy(alice) .membershipAccess(AccessControl.AccessRequired.ADMINISTRATOR) .build(); assertThat(describeChange(change), is(singletonList("Alice changed who can edit group membership to \"Only admins\"."))); } @Test public void you_changed_membership_access() { DecryptedGroupChange change = changeBy(you) .membershipAccess(AccessControl.AccessRequired.MEMBER) .build(); assertThat(describeChange(change), is(singletonList("You changed who can edit group membership to \"All members\"."))); } @Test public void unknown_changed_membership_access() { DecryptedGroupChange change = changeByUnknown() .membershipAccess(AccessControl.AccessRequired.ADMINISTRATOR) .build(); assertThat(describeChange(change), is(singletonList("Who can edit group membership has been changed to \"Only admins\"."))); } // Group link access change @Test public void you_changed_group_link_access_to_any() { DecryptedGroupChange change = changeBy(you) .inviteLinkAccess(AccessControl.AccessRequired.ANY) .build(); assertThat(describeChange(change), is(singletonList("You turned on the group link with admin approval off."))); } @Test public void you_changed_group_link_access_to_administrator_approval() { DecryptedGroupChange change = changeBy(you) .inviteLinkAccess(AccessControl.AccessRequired.ADMINISTRATOR) .build(); assertThat(describeChange(change), is(singletonList("You turned on the group link with admin approval on."))); } @Test public void you_turned_off_group_link_access() { DecryptedGroupChange change = changeBy(you) .inviteLinkAccess(AccessControl.AccessRequired.UNSATISFIABLE) .build(); assertThat(describeChange(change), is(singletonList("You turned off the group link."))); } @Test public void member_changed_group_link_access_to_any() { DecryptedGroupChange change = changeBy(alice) .inviteLinkAccess(AccessControl.AccessRequired.ANY) .build(); assertThat(describeChange(change), is(singletonList("Alice turned on the group link with admin approval off."))); } @Test public void member_changed_group_link_access_to_administrator_approval() { DecryptedGroupChange change = changeBy(bob) .inviteLinkAccess(AccessControl.AccessRequired.ADMINISTRATOR) .build(); assertThat(describeChange(change), is(singletonList("Bob turned on the group link with admin approval on."))); } @Test public void member_turned_off_group_link_access() { DecryptedGroupChange change = changeBy(alice) .inviteLinkAccess(AccessControl.AccessRequired.UNSATISFIABLE) .build(); assertThat(describeChange(change), is(singletonList("Alice turned off the group link."))); } @Test public void unknown_changed_group_link_access_to_any() { DecryptedGroupChange change = changeByUnknown() .inviteLinkAccess(AccessControl.AccessRequired.ANY) .build(); assertThat(describeChange(change), is(singletonList("The group link has been turned on with admin approval off."))); } @Test public void unknown_changed_group_link_access_to_administrator_approval() { DecryptedGroupChange change = changeByUnknown() .inviteLinkAccess(AccessControl.AccessRequired.ADMINISTRATOR) .build(); assertThat(describeChange(change), is(singletonList("The group link has been turned on with admin approval on."))); } @Test public void unknown_turned_off_group_link_access() { DecryptedGroupChange change = changeByUnknown() .inviteLinkAccess(AccessControl.AccessRequired.UNSATISFIABLE) .build(); assertThat(describeChange(change), is(singletonList("The group link has been turned off."))); } // Group link with known previous group state @Test public void group_link_access_from_unknown_to_administrator() { assertEquals("You turned on the group link with admin approval on.", describeGroupLinkChange(you, AccessControl.AccessRequired.UNKNOWN, AccessControl.AccessRequired.ADMINISTRATOR)); assertEquals("Alice turned on the group link with admin approval on.", describeGroupLinkChange(alice, AccessControl.AccessRequired.UNKNOWN, AccessControl.AccessRequired.ADMINISTRATOR)); assertEquals("The group link has been turned on with admin approval on.", describeGroupLinkChange(null, AccessControl.AccessRequired.UNKNOWN, AccessControl.AccessRequired.ADMINISTRATOR)); } @Test public void group_link_access_from_administrator_to_unsatisfiable() { assertEquals("You turned off the group link.", describeGroupLinkChange(you, AccessControl.AccessRequired.ADMINISTRATOR, AccessControl.AccessRequired.UNSATISFIABLE)); assertEquals("Bob turned off the group link.", describeGroupLinkChange(bob, AccessControl.AccessRequired.ADMINISTRATOR, AccessControl.AccessRequired.UNSATISFIABLE)); assertEquals("The group link has been turned off.", describeGroupLinkChange(null, AccessControl.AccessRequired.ADMINISTRATOR, AccessControl.AccessRequired.UNSATISFIABLE)); } @Test public void group_link_access_from_unsatisfiable_to_administrator() { assertEquals("You turned on the group link with admin approval on.", describeGroupLinkChange(you, AccessControl.AccessRequired.UNSATISFIABLE, AccessControl.AccessRequired.ADMINISTRATOR)); assertEquals("Alice turned on the group link with admin approval on.", describeGroupLinkChange(alice, AccessControl.AccessRequired.UNSATISFIABLE, AccessControl.AccessRequired.ADMINISTRATOR)); assertEquals("The group link has been turned on with admin approval on.", describeGroupLinkChange(null, AccessControl.AccessRequired.UNSATISFIABLE, AccessControl.AccessRequired.ADMINISTRATOR)); } @Test public void group_link_access_from_administrator_to_any() { assertEquals("You turned off admin approval for the group link.", describeGroupLinkChange(you, AccessControl.AccessRequired.ADMINISTRATOR, AccessControl.AccessRequired.ANY)); assertEquals("Bob turned off admin approval for the group link.", describeGroupLinkChange(bob, AccessControl.AccessRequired.ADMINISTRATOR, AccessControl.AccessRequired.ANY)); assertEquals("The admin approval for the group link has been turned off.", describeGroupLinkChange(null, AccessControl.AccessRequired.ADMINISTRATOR, AccessControl.AccessRequired.ANY)); } @Test public void group_link_access_from_any_to_administrator() { assertEquals("You turned on admin approval for the group link.", describeGroupLinkChange(you, AccessControl.AccessRequired.ANY, AccessControl.AccessRequired.ADMINISTRATOR)); assertEquals("Bob turned on admin approval for the group link.", describeGroupLinkChange(bob, AccessControl.AccessRequired.ANY, AccessControl.AccessRequired.ADMINISTRATOR)); assertEquals("The admin approval for the group link has been turned on.", describeGroupLinkChange(null, AccessControl.AccessRequired.ANY, AccessControl.AccessRequired.ADMINISTRATOR)); } private String describeGroupLinkChange(@Nullable UUID editor, @NonNull AccessControl.AccessRequired fromAccess, AccessControl.AccessRequired toAccess){ DecryptedGroup previousGroupState = DecryptedGroup.newBuilder() .setAccessControl(AccessControl.newBuilder() .setAddFromInviteLink(fromAccess)) .build(); DecryptedGroupChange change = (editor != null ? changeBy(editor) : changeByUnknown()).inviteLinkAccess(toAccess) .build(); List strings = describeChange(previousGroupState, change); assertEquals(1, strings.size()); return strings.get(0); } // Group link reset @Test public void you_reset_group_link() { DecryptedGroupChange change = changeBy(you) .resetGroupLink() .build(); assertThat(describeChange(change), is(singletonList("You reset the group link."))); } @Test public void member_reset_group_link() { DecryptedGroupChange change = changeBy(alice) .resetGroupLink() .build(); assertThat(describeChange(change), is(singletonList("Alice reset the group link."))); } @Test public void unknown_reset_group_link() { DecryptedGroupChange change = changeByUnknown() .resetGroupLink() .build(); assertThat(describeChange(change), is(singletonList("The group link has been reset."))); } /** * When the group link is turned on and reset in the same change, assume this is the first time * the link password it being set and do not show reset message. */ @Test public void member_changed_group_link_access_to_on_and_reset() { DecryptedGroupChange change = changeBy(alice) .inviteLinkAccess(AccessControl.AccessRequired.ANY) .resetGroupLink() .build(); assertThat(describeChange(change), is(singletonList("Alice turned on the group link with admin approval off."))); } /** * When the group link is turned on and reset in the same change, assume this is the first time * the link password it being set and do not show reset message. */ @Test public void you_changed_group_link_access_to_on_and_reset() { DecryptedGroupChange change = changeBy(you) .inviteLinkAccess(AccessControl.AccessRequired.ADMINISTRATOR) .resetGroupLink() .build(); assertThat(describeChange(change), is(singletonList("You turned on the group link with admin approval on."))); } @Test public void you_changed_group_link_access_to_off_and_reset() { DecryptedGroupChange change = changeBy(you) .inviteLinkAccess(AccessControl.AccessRequired.UNSATISFIABLE) .resetGroupLink() .build(); assertThat(describeChange(change), is(Arrays.asList("You turned off the group link.", "You reset the group link."))); } // Group link request @Test public void you_requested_to_join_the_group() { DecryptedGroupChange change = changeBy(you) .requestJoin() .build(); assertThat(describeChange(change), is(singletonList("You sent a request to join the group."))); } @Test public void member_requested_to_join_the_group() { DecryptedGroupChange change = changeBy(bob) .requestJoin() .build(); assertThat(describeChange(change), is(singletonList("Bob requested to join via the group link."))); } @Test public void unknown_requested_to_join_the_group() { DecryptedGroupChange change = changeByUnknown() .requestJoin(alice) .build(); assertThat(describeChange(change), is(singletonList("Alice requested to join via the group link."))); } @Test public void member_approved_your_join_request() { DecryptedGroupChange change = changeBy(bob) .approveRequest(you) .build(); assertThat(describeChange(change), is(singletonList("Bob approved your request to join the group."))); } @Test public void member_approved_another_join_request() { DecryptedGroupChange change = changeBy(alice) .approveRequest(bob) .build(); assertThat(describeChange(change), is(singletonList("Alice approved a request to join the group from Bob."))); } @Test public void you_approved_another_join_request() { DecryptedGroupChange change = changeBy(you) .approveRequest(alice) .build(); assertThat(describeChange(change), is(singletonList("You approved a request to join the group from Alice."))); } @Test public void unknown_approved_your_join_request() { DecryptedGroupChange change = changeByUnknown() .approveRequest(you) .build(); assertThat(describeChange(change), is(singletonList("Your request to join the group has been approved."))); } @Test public void unknown_approved_another_join_request() { DecryptedGroupChange change = changeByUnknown() .approveRequest(bob) .build(); assertThat(describeChange(change), is(singletonList("A request to join the group from Bob has been approved."))); } @Test public void member_denied_another_join_request() { DecryptedGroupChange change = changeBy(alice) .denyRequest(bob) .build(); assertThat(describeChange(change), is(singletonList("Alice denied a request to join the group from Bob."))); } @Test public void member_denied_your_join_request() { DecryptedGroupChange change = changeBy(alice) .denyRequest(you) .build(); assertThat(describeChange(change), is(singletonList("Your request to join the group has been denied by an admin."))); } @Test public void you_cancelled_your_join_request() { DecryptedGroupChange change = changeBy(you) .denyRequest(you) .build(); assertThat(describeChange(change), is(singletonList("You canceled your request to join the group."))); } @Test public void member_cancelled_their_join_request() { DecryptedGroupChange change = changeBy(alice) .denyRequest(alice) .build(); assertThat(describeChange(change), is(singletonList("Alice canceled their request to join the group."))); } @Test public void unknown_denied_your_join_request() { DecryptedGroupChange change = changeByUnknown() .denyRequest(you) .build(); assertThat(describeChange(change), is(singletonList("Your request to join the group has been denied by an admin."))); } @Test public void unknown_denied_another_join_request() { DecryptedGroupChange change = changeByUnknown() .denyRequest(bob) .build(); assertThat(describeChange(change), is(singletonList("A request to join the group from Bob has been denied."))); } // Multiple changes @Test public void multiple_changes() { DecryptedGroupChange change = changeBy(alice) .addMember(bob) .membershipAccess(AccessControl.AccessRequired.MEMBER) .title("Title") .addMember(you) .timer(300) .build(); assertThat(describeChange(change), is(Arrays.asList( "Alice added you to the group.", "Alice added Bob.", "Alice changed the group name to \"" + isolateBidi("Title") + "\".", "Alice set the disappearing message timer to 5 minutes.", "Alice changed who can edit group membership to \"All members\"."))); } @Test public void multiple_changes_leave_and_promote() { DecryptedGroupChange change = changeBy(alice) .deleteMember(alice) .promoteToAdmin(bob) .build(); assertThat(describeChange(change), is(Arrays.asList( "Alice made Bob an admin.", "Alice left the group."))); } @Test public void multiple_changes_leave_and_promote_by_unknown() { DecryptedGroupChange change = changeByUnknown() .deleteMember(alice) .promoteToAdmin(bob) .build(); assertThat(describeChange(change), is(Arrays.asList( "Bob is now an admin.", "Alice is no longer in the group."))); } @Test public void multiple_changes_by_unknown() { DecryptedGroupChange change = changeByUnknown() .addMember(bob) .membershipAccess(AccessControl.AccessRequired.MEMBER) .title("Title 2") .avatar("Avatar 1") .timer(600) .build(); assertThat(describeChange(change), is(Arrays.asList( "Bob joined the group.", "The group name has changed to \"" + isolateBidi("Title 2") + "\".", "The group avatar has been changed.", "The disappearing message timer has been set to 10 minutes.", "Who can edit group membership has been changed to \"All members\"."))); } @Test public void multiple_changes_join_and_leave_by_unknown() { DecryptedGroupChange change = changeByUnknown() .addMember(alice) .promoteToAdmin(alice) .deleteMember(alice) .title("Updated title") .build(); assertThat(describeChange(change), is(Arrays.asList( "Alice joined the group.", "Alice is now an admin.", "The group name has changed to \"" + isolateBidi("Updated title") + "\".", "Alice is no longer in the group."))); } // Group state without a change record @Test public void you_created_a_group_change_not_found() { DecryptedGroup group = newGroupBy(you, 0) .build(); assertThat(describeNewGroup(group), is("You joined the group.")); } @Test public void you_created_a_group() { DecryptedGroup group = newGroupBy(you, 0) .build(); DecryptedGroupChange change = changeBy(you) .addMember(alice) .addMember(you) .addMember(bob) .title("New title") .build(); assertThat(describeNewGroup(group, change), is("You created the group.")); } @Test public void alice_created_a_group_change_not_found() { DecryptedGroup group = newGroupBy(alice, 0) .member(you) .build(); assertThat(describeNewGroup(group), is("You joined the group.")); } @Test public void alice_created_a_group() { DecryptedGroup group = newGroupBy(alice, 0) .member(you) .build(); DecryptedGroupChange change = changeBy(alice) .addMember(you) .addMember(alice) .addMember(bob) .title("New title") .build(); assertThat(describeNewGroup(group, change), is("Alice added you to the group.")); } @Test public void alice_created_a_group_above_zero() { DecryptedGroup group = newGroupBy(alice, 1) .member(you) .build(); assertThat(describeNewGroup(group), is("You joined the group.")); } @Test public void you_were_invited_to_a_group() { DecryptedGroup group = newGroupBy(alice, 0) .invite(bob, you) .build(); assertThat(describeNewGroup(group), is("Bob invited you to the group.")); } @Test public void describe_a_group_you_are_not_in() { DecryptedGroup group = newGroupBy(alice, 1) .build(); assertThat(describeNewGroup(group), is("Group updated.")); } private @NonNull List describeChange(@NonNull DecryptedGroupChange change) { return describeChange(null, change); } private @NonNull List describeChange(@Nullable DecryptedGroup previousGroupState, @NonNull DecryptedGroupChange change) { MainThreadUtil.setMainThread(false); return Stream.of(producer.describeChanges(previousGroupState, change)) .map(UpdateDescription::getString) .toList(); } private @NonNull String describeNewGroup(@NonNull DecryptedGroup group) { return describeNewGroup(group, DecryptedGroupChange.getDefaultInstance()); } private @NonNull String describeNewGroup(@NonNull DecryptedGroup group, @NonNull DecryptedGroupChange groupChange) { MainThreadUtil.setMainThread(false); return producer.describeNewGroup(group, groupChange).getString(); } private static GroupStateBuilder newGroupBy(UUID foundingMember, int revision) { return new GroupStateBuilder(foundingMember, revision); } private void assertSingleChangeMentioning(DecryptedGroupChange change, List expectedMentions) { List changes = producer.describeChanges(null, change); assertThat(changes.size(), is(1)); UpdateDescription description = changes.get(0); assertThat(description.getMentioned(), is(expectedMentions)); if (expectedMentions.isEmpty()) { assertTrue(description.isStringStatic()); } else { assertFalse(description.isStringStatic()); } } private static class GroupStateBuilder { private final DecryptedGroup.Builder builder; GroupStateBuilder(@NonNull UUID foundingMember, int revision) { builder = DecryptedGroup.newBuilder() .setRevision(revision) .addMembers(DecryptedMember.newBuilder() .setUuid(UuidUtil.toByteString(foundingMember))); } GroupStateBuilder invite(@NonNull UUID inviter, @NonNull UUID invitee) { builder.addPendingMembers(DecryptedPendingMember.newBuilder() .setUuid(UuidUtil.toByteString(invitee)) .setAddedByUuid(UuidUtil.toByteString(inviter))); return this; } GroupStateBuilder member(@NonNull UUID member) { builder.addMembers(DecryptedMember.newBuilder() .setUuid(UuidUtil.toByteString(member))); return this; } public DecryptedGroup build() { return builder.build(); } } private static @NonNull GroupsV2UpdateMessageProducer.DescribeMemberStrategy createDescriber(@NonNull Map map) { return uuid -> { String name = map.get(uuid); assertNotNull(name); return name; }; } }