Write previous group state to the database for advanced change messages.

master
Alan Evans 2020-10-06 11:21:56 -03:00 committed by GitHub
parent b40fd7b243
commit 38fa58c0a3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 198 additions and 40 deletions

View File

@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.database.model;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
import com.google.protobuf.ByteString;
@ -81,7 +82,11 @@ final class GroupsV2UpdateMessageProducer {
}
}
List<UpdateDescription> describeChanges(@NonNull DecryptedGroupChange change) {
List<UpdateDescription> describeChanges(@Nullable DecryptedGroup previousGroupState, @NonNull DecryptedGroupChange change) {
if (DecryptedGroup.getDefaultInstance().equals(previousGroupState)) {
previousGroupState = null;
}
List<UpdateDescription> updates = new LinkedList<>();
if (change.getEditor().isEmpty() || UuidUtil.UNKNOWN_UUID.equals(UuidUtil.fromByteString(change.getEditor()))) {
@ -96,7 +101,7 @@ final class GroupsV2UpdateMessageProducer {
describeUnknownEditorNewTimer(change, updates);
describeUnknownEditorNewAttributeAccess(change, updates);
describeUnknownEditorNewMembershipAccess(change, updates);
describeUnknownEditorNewGroupInviteLinkAccess(change, updates);
describeUnknownEditorNewGroupInviteLinkAccess(previousGroupState, change, updates);
describeRequestingMembers(change, updates);
describeUnknownEditorRequestingMembersApprovals(change, updates);
describeUnknownEditorRequestingMembersDeletes(change, updates);
@ -119,7 +124,7 @@ final class GroupsV2UpdateMessageProducer {
describeNewTimer(change, updates);
describeNewAttributeAccess(change, updates);
describeNewMembershipAccess(change, updates);
describeNewGroupInviteLinkAccess(change, updates);
describeNewGroupInviteLinkAccess(previousGroupState, change, updates);
describeRequestingMembers(change, updates);
describeRequestingMembersApprovals(change, updates);
describeRequestingMembersDeletes(change, updates);
@ -509,7 +514,16 @@ final class GroupsV2UpdateMessageProducer {
}
}
private void describeNewGroupInviteLinkAccess(@NonNull DecryptedGroupChange change, @NonNull List<UpdateDescription> updates) {
private void describeNewGroupInviteLinkAccess(@Nullable DecryptedGroup previousGroupState,
@NonNull DecryptedGroupChange change,
@NonNull List<UpdateDescription> updates)
{
AccessControl.AccessRequired previousAccessControl = null;
if (previousGroupState != null) {
previousAccessControl = previousGroupState.getAccessControl().getAddFromInviteLink();
}
boolean editorIsYou = change.getEditor().equals(selfUuidBytes);
boolean groupLinkEnabled = false;
@ -517,17 +531,33 @@ final class GroupsV2UpdateMessageProducer {
case ANY:
groupLinkEnabled = true;
if (editorIsYou) {
updates.add(updateDescription(context.getString(R.string.MessageRecord_you_turned_on_the_group_link_with_admin_approval_off)));
if (previousAccessControl == AccessControl.AccessRequired.ADMINISTRATOR) {
updates.add(updateDescription(context.getString(R.string.MessageRecord_you_turned_off_admin_approval_for_the_group_link)));
} else {
updates.add(updateDescription(context.getString(R.string.MessageRecord_you_turned_on_the_group_link_with_admin_approval_off)));
}
} else {
updates.add(updateDescription(change.getEditor(), editor -> context.getString(R.string.MessageRecord_s_turned_on_the_group_link_with_admin_approval_off, editor)));
if (previousAccessControl == AccessControl.AccessRequired.ADMINISTRATOR) {
updates.add(updateDescription(change.getEditor(), editor -> context.getString(R.string.MessageRecord_s_turned_off_admin_approval_for_the_group_link, editor)));
} else {
updates.add(updateDescription(change.getEditor(), editor -> context.getString(R.string.MessageRecord_s_turned_on_the_group_link_with_admin_approval_off, editor)));
}
}
break;
case ADMINISTRATOR:
groupLinkEnabled = true;
if (editorIsYou) {
updates.add(updateDescription(context.getString(R.string.MessageRecord_you_turned_on_the_group_link_with_admin_approval_on)));
if (previousAccessControl == AccessControl.AccessRequired.ANY) {
updates.add(updateDescription(context.getString(R.string.MessageRecord_you_turned_on_admin_approval_for_the_group_link)));
} else {
updates.add(updateDescription(context.getString(R.string.MessageRecord_you_turned_on_the_group_link_with_admin_approval_on)));
}
} else {
updates.add(updateDescription(change.getEditor(), editor -> context.getString(R.string.MessageRecord_s_turned_on_the_group_link_with_admin_approval_on, editor)));
if (previousAccessControl == AccessControl.AccessRequired.ANY) {
updates.add(updateDescription(change.getEditor(), editor -> context.getString(R.string.MessageRecord_s_turned_on_admin_approval_for_the_group_link, editor)));
} else {
updates.add(updateDescription(change.getEditor(), editor -> context.getString(R.string.MessageRecord_s_turned_on_the_group_link_with_admin_approval_on, editor)));
}
}
break;
case UNSATISFIABLE:
@ -548,13 +578,30 @@ final class GroupsV2UpdateMessageProducer {
}
}
private void describeUnknownEditorNewGroupInviteLinkAccess(@NonNull DecryptedGroupChange change, @NonNull List<UpdateDescription> updates) {
private void describeUnknownEditorNewGroupInviteLinkAccess(@Nullable DecryptedGroup previousGroupState,
@NonNull DecryptedGroupChange change,
@NonNull List<UpdateDescription> updates)
{
AccessControl.AccessRequired previousAccessControl = null;
if (previousGroupState != null) {
previousAccessControl = previousGroupState.getAccessControl().getAddFromInviteLink();
}
switch (change.getNewInviteLinkAccess()) {
case ANY:
updates.add(updateDescription(context.getString(R.string.MessageRecord_the_group_link_has_been_turned_on_with_admin_approval_off)));
if (previousAccessControl == AccessControl.AccessRequired.ADMINISTRATOR) {
updates.add(updateDescription(context.getString(R.string.MessageRecord_the_admin_approval_for_the_group_link_has_been_turned_off)));
} else {
updates.add(updateDescription(context.getString(R.string.MessageRecord_the_group_link_has_been_turned_on_with_admin_approval_off)));
}
break;
case ADMINISTRATOR:
updates.add(updateDescription(context.getString(R.string.MessageRecord_the_group_link_has_been_turned_on_with_admin_approval_on)));
if (previousAccessControl == AccessControl.AccessRequired.ANY) {
updates.add(updateDescription(context.getString(R.string.MessageRecord_the_admin_approval_for_the_group_link_has_been_turned_on)));
} else {
updates.add(updateDescription(context.getString(R.string.MessageRecord_the_group_link_has_been_turned_on_with_admin_approval_on)));
}
break;
case UNSATISFIABLE:
updates.add(updateDescription(context.getString(R.string.MessageRecord_the_group_link_has_been_turned_off)));

View File

@ -177,7 +177,7 @@ public abstract class MessageRecord extends DisplayRecord {
GroupsV2UpdateMessageProducer updateMessageProducer = new GroupsV2UpdateMessageProducer(context, descriptionStrategy, Recipient.self().getUuid().get());
if (decryptedGroupV2Context.hasChange() && decryptedGroupV2Context.getGroupState().getRevision() != 0) {
return UpdateDescription.concatWithNewLines(updateMessageProducer.describeChanges(decryptedGroupV2Context.getChange()));
return UpdateDescription.concatWithNewLines(updateMessageProducer.describeChanges(decryptedGroupV2Context.getPreviousGroupState(), decryptedGroupV2Context.getChange()));
} else {
return updateMessageProducer.describeNewGroup(decryptedGroupV2Context.getGroupState(), decryptedGroupV2Context.getChange());
}

View File

@ -193,7 +193,7 @@ final class GroupManagerV2 {
.setEditor(UuidUtil.toByteString(selfUuid))
.build();
RecipientAndThread recipientAndThread = sendGroupUpdate(masterKey, decryptedGroup, groupChange, null);
RecipientAndThread recipientAndThread = sendGroupUpdate(masterKey, new GroupMutation(null, groupChange, decryptedGroup), null);
return new GroupManager.GroupActionResult(recipientAndThread.groupRecipient,
recipientAndThread.threadId,
@ -505,16 +505,18 @@ final class GroupManagerV2 {
private GroupManager.GroupActionResult commitChange(@NonNull GroupChange.Actions.Builder change)
throws GroupNotAMemberException, GroupChangeFailedException, IOException, GroupInsufficientRightsException
{
final GroupDatabase.GroupRecord groupRecord = groupDatabase.requireGroup(groupId);
final GroupDatabase.V2GroupProperties v2GroupProperties = groupRecord.requireV2GroupProperties();
final int nextRevision = v2GroupProperties.getGroupRevision() + 1;
final GroupChange.Actions changeActions = change.setRevision(nextRevision).build();
final GroupDatabase.GroupRecord groupRecord = groupDatabase.requireGroup(groupId);
final GroupDatabase.V2GroupProperties v2GroupProperties = groupRecord.requireV2GroupProperties();
final int nextRevision = v2GroupProperties.getGroupRevision() + 1;
final GroupChange.Actions changeActions = change.setRevision(nextRevision).build();
final DecryptedGroupChange decryptedChange;
final DecryptedGroup decryptedGroupState;
final DecryptedGroup previousGroupState;
try {
previousGroupState = v2GroupProperties.getDecryptedGroup();
decryptedChange = groupOperations.decryptChange(changeActions, selfUuid);
decryptedGroupState = DecryptedGroupUtil.apply(v2GroupProperties.getDecryptedGroup(), decryptedChange);
decryptedGroupState = DecryptedGroupUtil.apply(previousGroupState, decryptedChange);
} catch (VerificationFailedException | InvalidGroupStateException | NotAbleToApplyGroupV2ChangeException e) {
Log.w(TAG, e);
throw new IOException(e);
@ -523,7 +525,8 @@ final class GroupManagerV2 {
GroupChange signedGroupChange = commitToServer(changeActions);
groupDatabase.update(groupId, decryptedGroupState);
RecipientAndThread recipientAndThread = sendGroupUpdate(groupMasterKey, decryptedGroupState, decryptedChange, signedGroupChange);
GroupMutation groupMutation = new GroupMutation(previousGroupState, decryptedChange, decryptedGroupState);
RecipientAndThread recipientAndThread = sendGroupUpdate(groupMasterKey, groupMutation, signedGroupChange);
int newMembersCount = decryptedChange.getNewMembersCount();
List<RecipientId> newPendingMembers = getPendingMemberRecipientIds(decryptedChange.getNewPendingMembersList());
@ -681,7 +684,7 @@ final class GroupManagerV2 {
} else if (requestToJoin) {
Log.i(TAG, "Requested to join, cannot send update");
RecipientAndThread recipientAndThread = sendGroupUpdate(groupMasterKey, decryptedGroup, decryptedChange, signedGroupChange);
RecipientAndThread recipientAndThread = sendGroupUpdate(groupMasterKey, new GroupMutation(null, decryptedChange, decryptedGroup), signedGroupChange);
return new GroupManager.GroupActionResult(groupRecipient,
recipientAndThread.threadId,
@ -706,7 +709,7 @@ final class GroupManagerV2 {
System.currentTimeMillis(),
decryptedChange);
RecipientAndThread recipientAndThread = sendGroupUpdate(groupMasterKey, decryptedGroup, decryptedChange, signedGroupChange);
RecipientAndThread recipientAndThread = sendGroupUpdate(groupMasterKey, new GroupMutation(null, decryptedChange, decryptedGroup), signedGroupChange);
return new GroupManager.GroupActionResult(groupRecipient,
recipientAndThread.threadId,
@ -905,7 +908,7 @@ final class GroupManagerV2 {
groupDatabase.update(groupId, resetRevision(newGroup, decryptedGroup.getRevision()));
sendGroupUpdate(groupMasterKey, decryptedGroup, decryptedChange, signedGroupChange);
sendGroupUpdate(groupMasterKey, new GroupMutation(decryptedGroup, decryptedChange, newGroup), signedGroupChange);
} catch (VerificationFailedException | InvalidGroupStateException | NotAbleToApplyGroupV2ChangeException e) {
throw new GroupChangeFailedException(e);
}
@ -959,13 +962,12 @@ final class GroupManagerV2 {
}
private @NonNull RecipientAndThread sendGroupUpdate(@NonNull GroupMasterKey masterKey,
@NonNull DecryptedGroup decryptedGroup,
@Nullable DecryptedGroupChange plainGroupChange,
@NonNull GroupMutation groupMutation,
@Nullable GroupChange signedGroupChange)
{
GroupId.V2 groupId = GroupId.v2(masterKey);
Recipient groupRecipient = Recipient.externalGroup(context, groupId);
DecryptedGroupV2Context decryptedGroupV2Context = GroupProtoUtil.createDecryptedGroupV2Context(masterKey, decryptedGroup, plainGroupChange, signedGroupChange);
DecryptedGroupV2Context decryptedGroupV2Context = GroupProtoUtil.createDecryptedGroupV2Context(masterKey, groupMutation, signedGroupChange);
OutgoingGroupUpdateMessage outgoingMessage = new OutgoingGroupUpdateMessage(groupRecipient,
decryptedGroupV2Context,
null,
@ -977,8 +979,11 @@ final class GroupManagerV2 {
Collections.emptyList(),
Collections.emptyList());
DecryptedGroupChange plainGroupChange = groupMutation.getGroupChange();
if (plainGroupChange != null && DecryptedGroupUtil.changeIsEmptyExceptForProfileKeyChanges(plainGroupChange)) {
ApplicationDependencies.getJobManager().add(PushGroupSilentUpdateSendJob.create(context, groupId, decryptedGroup, outgoingMessage));
ApplicationDependencies.getJobManager().add(PushGroupSilentUpdateSendJob.create(context, groupId, groupMutation.getNewGroupState(), outgoingMessage));
return new RecipientAndThread(groupRecipient, -1);
} else {
long threadId = MessageSender.send(context, outgoingMessage, -1, false, null);

View File

@ -0,0 +1,31 @@
package org.thoughtcrime.securesms.groups;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.signal.storageservice.protos.groups.local.DecryptedGroup;
import org.signal.storageservice.protos.groups.local.DecryptedGroupChange;
public final class GroupMutation {
@Nullable private final DecryptedGroup previousGroupState;
@Nullable private final DecryptedGroupChange groupChange;
@NonNull private final DecryptedGroup newGroupState;
public GroupMutation(@Nullable DecryptedGroup previousGroupState, @Nullable DecryptedGroupChange groupChange, @NonNull DecryptedGroup newGroupState) {
this.previousGroupState = previousGroupState;
this.groupChange = groupChange;
this.newGroupState = newGroupState;
}
public @Nullable DecryptedGroup getPreviousGroupState() {
return previousGroupState;
}
public @Nullable DecryptedGroupChange getGroupChange() {
return groupChange;
}
public @NonNull DecryptedGroup getNewGroupState() {
return newGroupState;
}
}

View File

@ -49,12 +49,13 @@ public final class GroupProtoUtil {
}
public static DecryptedGroupV2Context createDecryptedGroupV2Context(@NonNull GroupMasterKey masterKey,
@NonNull DecryptedGroup decryptedGroup,
@Nullable DecryptedGroupChange plainGroupChange,
@NonNull GroupMutation groupMutation,
@Nullable GroupChange signedServerChange)
{
int revision = plainGroupChange != null ? plainGroupChange.getRevision() : decryptedGroup.getRevision();
SignalServiceProtos.GroupContextV2.Builder contextBuilder = SignalServiceProtos.GroupContextV2.newBuilder()
DecryptedGroupChange plainGroupChange = groupMutation.getGroupChange();
DecryptedGroup decryptedGroup = groupMutation.getNewGroupState();
int revision = plainGroupChange != null ? plainGroupChange.getRevision() : decryptedGroup.getRevision();
SignalServiceProtos.GroupContextV2.Builder contextBuilder = SignalServiceProtos.GroupContextV2.newBuilder()
.setMasterKey(ByteString.copyFrom(masterKey.serialize()))
.setRevision(revision);
@ -66,6 +67,10 @@ public final class GroupProtoUtil {
.setContext(contextBuilder.build())
.setGroupState(decryptedGroup);
if (groupMutation.getPreviousGroupState() != null) {
builder.setPreviousGroupState(groupMutation.getPreviousGroupState());
}
if (plainGroupChange != null) {
builder.setChange(plainGroupChange);
}

View File

@ -23,6 +23,7 @@ import org.thoughtcrime.securesms.database.RecipientDatabase;
import org.thoughtcrime.securesms.database.model.databaseprotos.DecryptedGroupV2Context;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.groups.GroupId;
import org.thoughtcrime.securesms.groups.GroupMutation;
import org.thoughtcrime.securesms.groups.GroupNotAMemberException;
import org.thoughtcrime.securesms.groups.GroupProtoUtil;
import org.thoughtcrime.securesms.groups.GroupsV2Authorization;
@ -226,9 +227,9 @@ public final class GroupsV2StateProcessor {
determineProfileSharing(inputGroupState, newLocalState);
if (localState != null && localState.getRevision() == GroupsV2StateProcessor.RESTORE_PLACEHOLDER_REVISION) {
Log.i(TAG, "Inserting single update message for restore placeholder");
insertUpdateMessages(timestamp, Collections.singleton(new LocalGroupLogEntry(newLocalState, null)));
insertUpdateMessages(timestamp, null, Collections.singleton(new LocalGroupLogEntry(newLocalState, null)));
} else {
insertUpdateMessages(timestamp, advanceGroupStateResult.getProcessedLogEntries());
insertUpdateMessages(timestamp, localState, advanceGroupStateResult.getProcessedLogEntries());
}
persistLearnedProfileKeys(inputGroupState);
@ -260,7 +261,7 @@ public final class GroupsV2StateProcessor {
.addDeleteMembers(UuidUtil.toByteString(selfUuid))
.build();
DecryptedGroupV2Context decryptedGroupV2Context = GroupProtoUtil.createDecryptedGroupV2Context(masterKey, simulatedGroupState, simulatedGroupChange, null);
DecryptedGroupV2Context decryptedGroupV2Context = GroupProtoUtil.createDecryptedGroupV2Context(masterKey, new GroupMutation(decryptedGroup, simulatedGroupChange, simulatedGroupState), null);
OutgoingGroupUpdateMessage leaveMessage = new OutgoingGroupUpdateMessage(groupRecipient,
decryptedGroupV2Context,
null,
@ -362,13 +363,17 @@ public final class GroupsV2StateProcessor {
}
}
private void insertUpdateMessages(long timestamp, Collection<LocalGroupLogEntry> processedLogEntries) {
private void insertUpdateMessages(long timestamp,
@Nullable DecryptedGroup previousGroupState,
Collection<LocalGroupLogEntry> processedLogEntries)
{
for (LocalGroupLogEntry entry : processedLogEntries) {
if (entry.getChange() != null && DecryptedGroupUtil.changeIsEmptyExceptForProfileKeyChanges(entry.getChange()) && !DecryptedGroupUtil.changeIsEmpty(entry.getChange())) {
Log.d(TAG, "Skipping profile key changes only update message");
} else {
storeMessage(GroupProtoUtil.createDecryptedGroupV2Context(masterKey, entry.getGroup(), entry.getChange(), null), timestamp);
storeMessage(GroupProtoUtil.createDecryptedGroupV2Context(masterKey, new GroupMutation(previousGroupState, entry.getChange(), entry.getGroup()), null), timestamp);
}
previousGroupState = entry.getGroup();
}
}

View File

@ -28,9 +28,10 @@ import "SignalService.proto";
import "DecryptedGroups.proto";
message DecryptedGroupV2Context {
signalservice.GroupContextV2 context = 1;
DecryptedGroupChange change = 2;
DecryptedGroup groupState = 3;
signalservice.GroupContextV2 context = 1;
DecryptedGroupChange change = 2;
DecryptedGroup groupState = 3;
DecryptedGroup previousGroupState = 4;
}
message TemporalAuthCredentialResponse {

View File

@ -1013,6 +1013,12 @@
<string name="MessageRecord_the_group_link_has_been_turned_on_with_admin_approval_off">The group link has been turned on with admin approval off.</string>
<string name="MessageRecord_the_group_link_has_been_turned_on_with_admin_approval_on">The group link has been turned on with admin approval on.</string>
<string name="MessageRecord_the_group_link_has_been_turned_off">The group link has been turned off.</string>
<string name="MessageRecord_you_turned_off_admin_approval_for_the_group_link">You turned off admin approval for the group link.</string>
<string name="MessageRecord_s_turned_off_admin_approval_for_the_group_link">%1$s turned off admin approval for the group link.</string>
<string name="MessageRecord_the_admin_approval_for_the_group_link_has_been_turned_off">The admin approval for the group link has been turned off.</string>
<string name="MessageRecord_you_turned_on_admin_approval_for_the_group_link">You turned on admin approval for the group link.</string>
<string name="MessageRecord_s_turned_on_admin_approval_for_the_group_link">%1$s turned on admin approval for the group link.</string>
<string name="MessageRecord_the_admin_approval_for_the_group_link_has_been_turned_on">The admin approval for the group link has been turned on.</string>
<!-- GV2 group link reset -->
<string name="MessageRecord_you_reset_the_group_link">You reset the group link.</string>

View File

@ -3,6 +3,7 @@ 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;
@ -35,6 +36,7 @@ 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;
@ -921,6 +923,56 @@ public final class GroupsV2UpdateMessageProducerTest {
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<String> strings = describeChange(previousGroupState, change);
assertEquals(1, strings.size());
return strings.get(0);
}
// Group link reset
@Test
@ -1271,8 +1323,14 @@ public final class GroupsV2UpdateMessageProducerTest {
}
private @NonNull List<String> describeChange(@NonNull DecryptedGroupChange change) {
return describeChange(null, change);
}
private @NonNull List<String> describeChange(@Nullable DecryptedGroup previousGroupState,
@NonNull DecryptedGroupChange change)
{
MainThreadUtil.setMainThread(false);
return Stream.of(producer.describeChanges(change))
return Stream.of(producer.describeChanges(previousGroupState, change))
.map(UpdateDescription::getString)
.toList();
}
@ -1291,7 +1349,7 @@ public final class GroupsV2UpdateMessageProducerTest {
}
private void assertSingleChangeMentioning(DecryptedGroupChange change, List<UUID> expectedMentions) {
List<UpdateDescription> changes = producer.describeChanges(change);
List<UpdateDescription> changes = producer.describeChanges(null, change);
assertThat(changes.size(), is(1));