Fix resend after safety number change in groups or distribution lists.

This commit is contained in:
Cody Henthorne 2022-04-05 11:56:13 -04:00
parent 2253e25ae1
commit c56ef33833
6 changed files with 123 additions and 59 deletions

View file

@ -32,7 +32,9 @@ import org.whispersystems.signalservice.api.SignalSessionLock;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
final class SafetyNumberChangeRepository {
@ -151,8 +153,9 @@ final class SafetyNumberChangeRepository {
@WorkerThread
private void processOutgoingMessageRecord(@NonNull List<ChangedRecipient> changedRecipients, @NonNull MessageRecord messageRecord) {
Log.d(TAG, "processOutgoingMessageRecord");
MessageDatabase smsDatabase = SignalDatabase.sms();
MessageDatabase mmsDatabase = SignalDatabase.mms();
MessageDatabase smsDatabase = SignalDatabase.sms();
MessageDatabase mmsDatabase = SignalDatabase.mms();
Set<RecipientId> resendIds = new HashSet<>();
for (ChangedRecipient changedRecipient : changedRecipients) {
RecipientId id = changedRecipient.getRecipient().getId();
@ -161,8 +164,8 @@ final class SafetyNumberChangeRepository {
if (messageRecord.isMms()) {
mmsDatabase.removeMismatchedIdentity(messageRecord.getId(), id, identityKey);
if (messageRecord.getRecipient().isPushGroup()) {
MessageSender.resendGroupMessage(context, messageRecord, id);
if (messageRecord.getRecipient().isDistributionList() || messageRecord.getRecipient().isPushGroup()) {
resendIds.add(id);
} else {
MessageSender.resend(context, messageRecord);
}
@ -172,6 +175,14 @@ final class SafetyNumberChangeRepository {
MessageSender.resend(context, messageRecord);
}
}
if (Util.hasItems(resendIds)) {
if (messageRecord.getRecipient().isPushGroup()) {
MessageSender.resendGroupMessage(context, messageRecord, resendIds);
} else {
MessageSender.resendDistributionList(context, messageRecord, resendIds);
}
}
}
static final class SafetyNumberChangeState {

View file

@ -30,6 +30,7 @@ import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.stories.Stories;
import org.thoughtcrime.securesms.transport.RetryLaterException;
import org.thoughtcrime.securesms.transport.UndeliverableMessageException;
import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
import org.whispersystems.signalservice.api.messages.SendMessageResult;
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
@ -37,10 +38,13 @@ import org.whispersystems.signalservice.api.messages.SignalServiceStoryMessage;
import org.whispersystems.signalservice.api.push.exceptions.ServerRejectedException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
* A job that lets us send a message to a distribution list. Currently the only supported message type is a story.
@ -51,31 +55,36 @@ public final class PushDistributionListSendJob extends PushSendJob {
private static final String TAG = Log.tag(PushDistributionListSendJob.class);
private static final String KEY_MESSAGE_ID = "message_id";
private static final String KEY_MESSAGE_ID = "message_id";
private static final String KEY_FILTERED_RECIPIENT_IDS = "filtered_recipient_ids";
private final long messageId;
private final long messageId;
private final Set<RecipientId> filterRecipientIds;
public PushDistributionListSendJob(long messageId, @NonNull RecipientId destination, boolean hasMedia) {
public PushDistributionListSendJob(long messageId, @NonNull RecipientId destination, boolean hasMedia, @NonNull Set<RecipientId> filterRecipientIds) {
this(new Parameters.Builder()
.setQueue(destination.toQueueKey(hasMedia))
.addConstraint(NetworkConstraint.KEY)
.setLifespan(TimeUnit.DAYS.toMillis(1))
.setMaxAttempts(Parameters.UNLIMITED)
.build(),
messageId);
messageId,
filterRecipientIds
);
}
private PushDistributionListSendJob(@NonNull Parameters parameters, long messageId) {
private PushDistributionListSendJob(@NonNull Parameters parameters, long messageId, @NonNull Set<RecipientId> filterRecipientIds) {
super(parameters);
this.messageId = messageId;
this.messageId = messageId;
this.filterRecipientIds = filterRecipientIds;
}
@WorkerThread
public static void enqueue(@NonNull Context context,
@NonNull JobManager jobManager,
long messageId,
@NonNull RecipientId destination)
@NonNull RecipientId destination,
@NonNull Set<RecipientId> filterRecipientIds)
{
try {
Recipient listRecipient = Recipient.resolved(destination);
@ -92,7 +101,7 @@ public final class PushDistributionListSendJob extends PushSendJob {
Set<String> attachmentUploadIds = enqueueCompressingAndUploadAttachmentsChains(jobManager, message);
jobManager.add(new PushDistributionListSendJob(messageId, destination, !attachmentUploadIds.isEmpty()), attachmentUploadIds, attachmentUploadIds.isEmpty() ? null : destination.toQueueKey());
jobManager.add(new PushDistributionListSendJob(messageId, destination, !attachmentUploadIds.isEmpty(), filterRecipientIds), attachmentUploadIds, attachmentUploadIds.isEmpty() ? null : destination.toQueueKey());
} catch (NoSuchMessageException | MmsException e) {
Log.w(TAG, "Failed to enqueue message.", e);
SignalDatabase.mms().markAsSentFailed(messageId);
@ -102,7 +111,9 @@ public final class PushDistributionListSendJob extends PushSendJob {
@Override
public @NonNull Data serialize() {
return new Data.Builder().putLong(KEY_MESSAGE_ID, messageId).build();
return new Data.Builder().putLong(KEY_MESSAGE_ID, messageId)
.putString(KEY_FILTERED_RECIPIENT_IDS, RecipientId.toSerializedList(filterRecipientIds))
.build();
}
@Override
@ -142,15 +153,22 @@ public final class PushDistributionListSendJob extends PushSendJob {
try {
log(TAG, String.valueOf(message.getSentTimeMillis()), "Sending message: " + messageId + ", Recipient: " + message.getRecipient().getId() + ", Attachments: " + buildAttachmentString(message.getAttachments()));
List<Recipient> target;
List<Recipient> targets;
if (!existingNetworkFailures.isEmpty()) target = Stream.of(existingNetworkFailures).map(nf -> nf.getRecipientId(context)).distinct().map(Recipient::resolved).toList();
else target = Stream.of(Stories.getRecipientsToSendTo(messageId, message.getSentTimeMillis(), message.getStoryType().isStoryWithReplies())).distinctBy(Recipient::getId).toList();
if (Util.hasItems(filterRecipientIds)) {
targets = new ArrayList<>(filterRecipientIds.size() + existingNetworkFailures.size());
targets.addAll(filterRecipientIds.stream().map(Recipient::resolved).collect(Collectors.toList()));
targets.addAll(existingNetworkFailures.stream().map(nf -> nf.getRecipientId(context)).distinct().map(Recipient::resolved).collect(Collectors.toList()));
} else if (!existingNetworkFailures.isEmpty()) {
targets = Stream.of(existingNetworkFailures).map(nf -> nf.getRecipientId(context)).distinct().map(Recipient::resolved).toList();
} else {
targets = Stream.of(Stories.getRecipientsToSendTo(messageId, message.getSentTimeMillis(), message.getStoryType().isStoryWithReplies())).distinctBy(Recipient::getId).toList();
}
List<SendMessageResult> results = deliver(message, target);
List<SendMessageResult> results = deliver(message, targets);
Log.i(TAG, JobLogger.format(this, "Finished send."));
PushGroupSendJob.processGroupMessageResults(context, messageId, -1, null, message, results, target, Collections.emptyList(), existingNetworkFailures, existingIdentityMismatches);
PushGroupSendJob.processGroupMessageResults(context, messageId, -1, null, message, results, targets, Collections.emptyList(), existingNetworkFailures, existingIdentityMismatches);
} catch (UntrustedIdentityException | UndeliverableMessageException e) {
warn(TAG, String.valueOf(message.getSentTimeMillis()), e);
database.markAsSentFailed(messageId);
@ -191,7 +209,8 @@ public final class PushDistributionListSendJob extends PushSendJob {
public static class Factory implements Job.Factory<PushDistributionListSendJob> {
@Override
public @NonNull PushDistributionListSendJob create(@NonNull Parameters parameters, @NonNull Data data) {
return new PushDistributionListSendJob(parameters, data.getLong(KEY_MESSAGE_ID));
Set<RecipientId> recipientIds = new HashSet<>(RecipientId.fromSerializedList(data.getStringOrDefault(KEY_FILTERED_RECIPIENT_IDS, "")));
return new PushDistributionListSendJob(parameters, data.getLong(KEY_MESSAGE_ID), recipientIds);
}
}
}

View file

@ -47,6 +47,7 @@ import org.thoughtcrime.securesms.util.GroupUtil;
import org.thoughtcrime.securesms.util.RecipientAccessList;
import org.signal.core.util.SetUtil;
import org.thoughtcrime.securesms.util.SignalLocalMetrics;
import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.signalservice.api.crypto.ContentHint;
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
import org.whispersystems.signalservice.api.messages.SendMessageResult;
@ -63,6 +64,8 @@ import org.whispersystems.signalservice.internal.push.SignalServiceProtos.GroupC
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
@ -75,28 +78,28 @@ public final class PushGroupSendJob extends PushSendJob {
private static final String TAG = Log.tag(PushGroupSendJob.class);
private static final String KEY_MESSAGE_ID = "message_id";
private static final String KEY_FILTER_RECIPIENT = "filter_recipient";
private static final String KEY_MESSAGE_ID = "message_id";
private static final String KEY_FILTER_RECIPIENTS = "filter_recipient";
private final long messageId;
private final RecipientId filterRecipient;
private final long messageId;
private final Set<RecipientId> filterRecipients;
public PushGroupSendJob(long messageId, @NonNull RecipientId destination, @Nullable RecipientId filterRecipient, boolean hasMedia) {
public PushGroupSendJob(long messageId, @NonNull RecipientId destination, @NonNull Set<RecipientId> filterRecipients, boolean hasMedia) {
this(new Job.Parameters.Builder()
.setQueue(destination.toQueueKey(hasMedia))
.addConstraint(NetworkConstraint.KEY)
.setLifespan(TimeUnit.DAYS.toMillis(1))
.setMaxAttempts(Parameters.UNLIMITED)
.build(),
messageId, filterRecipient);
messageId, filterRecipients);
}
private PushGroupSendJob(@NonNull Job.Parameters parameters, long messageId, @Nullable RecipientId filterRecipient) {
private PushGroupSendJob(@NonNull Job.Parameters parameters, long messageId, @NonNull Set<RecipientId> filterRecipients) {
super(parameters);
this.messageId = messageId;
this.filterRecipient = filterRecipient;
this.messageId = messageId;
this.filterRecipients = filterRecipients;
}
@WorkerThread
@ -104,7 +107,7 @@ public final class PushGroupSendJob extends PushSendJob {
@NonNull JobManager jobManager,
long messageId,
@NonNull RecipientId destination,
@Nullable RecipientId filterAddress)
@NonNull Set<RecipientId> filterAddresses)
{
try {
Recipient group = Recipient.resolved(destination);
@ -120,7 +123,7 @@ public final class PushGroupSendJob extends PushSendJob {
throw new MmsException("Inactive group!");
}
jobManager.add(new PushGroupSendJob(messageId, destination, filterAddress, !attachmentUploadIds.isEmpty()), attachmentUploadIds, attachmentUploadIds.isEmpty() ? null : destination.toQueueKey());
jobManager.add(new PushGroupSendJob(messageId, destination, filterAddresses, !attachmentUploadIds.isEmpty()), attachmentUploadIds, attachmentUploadIds.isEmpty() ? null : destination.toQueueKey());
} catch (NoSuchMessageException | MmsException e) {
Log.w(TAG, "Failed to enqueue message.", e);
@ -132,7 +135,7 @@ public final class PushGroupSendJob extends PushSendJob {
@Override
public @NonNull Data serialize() {
return new Data.Builder().putLong(KEY_MESSAGE_ID, messageId)
.putString(KEY_FILTER_RECIPIENT, filterRecipient != null ? filterRecipient.serialize() : null)
.putString(KEY_FILTER_RECIPIENTS, RecipientId.toSerializedList(filterRecipients))
.build();
}
@ -189,8 +192,10 @@ public final class PushGroupSendJob extends PushSendJob {
List<Recipient> target;
List<RecipientId> skipped = new ArrayList<>();
if (filterRecipient != null) {
target = Collections.singletonList(Recipient.resolved(filterRecipient));
if (Util.hasItems(filterRecipients)) {
target = new ArrayList<>(filterRecipients.size() + existingNetworkFailures.size());
target.addAll(Stream.of(filterRecipients).map(Recipient::resolved).toList());
target.addAll(Stream.of(existingNetworkFailures).map(nf -> nf.getRecipientId(context)).distinct().map(Recipient::resolved).toList());
} else if (!existingNetworkFailures.isEmpty()) {
target = Stream.of(existingNetworkFailures).map(nf -> nf.getRecipientId(context)).distinct().map(Recipient::resolved).toList();
} else {
@ -230,9 +235,9 @@ public final class PushGroupSendJob extends PushSendJob {
try {
rotateSenderCertificateIfNecessary();
GroupId.Push groupId = groupRecipient.requireGroupId().requirePush();
Optional<byte[]> profileKey = getProfileKey(groupRecipient);
Optional<SignalServiceDataMessage.Sticker> sticker = getStickerFor(message);
GroupId.Push groupId = groupRecipient.requireGroupId().requirePush();
Optional<byte[]> profileKey = getProfileKey(groupRecipient);
Optional<SignalServiceDataMessage.Sticker> sticker = getStickerFor(message);
List<SharedContact> sharedContacts = getSharedContactsFor(message);
List<SignalServicePreview> previews = getPreviewsFor(message);
List<SignalServiceDataMessage.Mention> mentions = getMentionsFor(message.getMentions());
@ -407,7 +412,7 @@ public final class PushGroupSendJob extends PushSendJob {
handleProofRequiredException(context, proofRequired, groupRecipient, threadId, messageId, true);
}
if (existingNetworkFailures.isEmpty() && networkFailures.isEmpty() && identityMismatches.isEmpty() && existingIdentityMismatches.isEmpty()) {
if (existingNetworkFailures.isEmpty() && existingIdentityMismatches.isEmpty()) {
database.markAsSent(messageId, true);
markAttachmentsUploaded(messageId, message);
@ -429,12 +434,12 @@ public final class PushGroupSendJob extends PushSendJob {
if (message.getStoryType().isStory()) {
ApplicationDependencies.getExpireStoriesManager().scheduleIfNecessary();
}
} else if (!identityMismatches.isEmpty()) {
Log.w(TAG, "Failing because there were " + identityMismatches.size() + " identity mismatches.");
} else if (!existingIdentityMismatches.isEmpty()) {
Log.w(TAG, "Failing because there were " + existingIdentityMismatches.size() + " identity mismatches.");
database.markAsSentFailed(messageId);
notifyMediaMessageDeliveryFailed(context, messageId);
Set<RecipientId> mismatchRecipientIds = Stream.of(identityMismatches)
Set<RecipientId> mismatchRecipientIds = Stream.of(existingIdentityMismatches)
.map(mismatch -> mismatch.getRecipientId(context))
.collect(Collectors.toSet());
@ -489,10 +494,10 @@ public final class PushGroupSendJob extends PushSendJob {
public static class Factory implements Job.Factory<PushGroupSendJob> {
@Override
public @NonNull PushGroupSendJob create(@NonNull Parameters parameters, @NonNull org.thoughtcrime.securesms.jobmanager.Data data) {
String raw = data.getString(KEY_FILTER_RECIPIENT);
RecipientId filter = raw != null ? RecipientId.from(raw) : null;
String raw = data.getStringOrDefault(KEY_FILTER_RECIPIENTS, "");
Set<RecipientId> filters = raw != null ? new HashSet<>(RecipientId.fromSerializedList(raw)) : Collections.emptySet();
return new PushGroupSendJob(parameters, data.getLong(KEY_MESSAGE_ID), filter);
return new PushGroupSendJob(parameters, data.getLong(KEY_MESSAGE_ID), filters);
}
}
}

View file

@ -44,8 +44,10 @@ import org.thoughtcrime.securesms.database.SmsDatabase;
import org.thoughtcrime.securesms.database.ThreadDatabase;
import org.thoughtcrime.securesms.database.model.MessageId;
import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.database.model.MmsMessageRecord;
import org.thoughtcrime.securesms.database.model.ReactionRecord;
import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
import org.thoughtcrime.securesms.database.model.StoryType;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.jobmanager.JobManager;
@ -86,6 +88,7 @@ import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.TimeUnit;
public class MessageSender {
@ -327,9 +330,9 @@ public class MessageSender {
if (isLocalSelfSend(context, recipient, false)) {
sendLocalMediaSelf(context, messageId);
} else if (recipient.isPushGroup()) {
jobManager.add(new PushGroupSendJob(messageId, recipient.getId(), null, true), messageDependsOnIds, recipient.getId().toQueueKey());
jobManager.add(new PushGroupSendJob(messageId, recipient.getId(), Collections.emptySet(), true), messageDependsOnIds, recipient.getId().toQueueKey());
} else if (recipient.isDistributionList()) {
jobManager.add(new PushDistributionListSendJob(messageId, recipient.getId(), true), messageDependsOnIds, recipient.getId().toQueueKey());
jobManager.add(new PushDistributionListSendJob(messageId, recipient.getId(), true, Collections.emptySet()), messageDependsOnIds, recipient.getId().toQueueKey());
} else {
jobManager.add(new PushMediaSendJob(messageId, recipient, true), messageDependsOnIds, recipient.getId().toQueueKey());
}
@ -403,9 +406,17 @@ public class MessageSender {
}
}
public static void resendGroupMessage(Context context, MessageRecord messageRecord, RecipientId filterRecipientId) {
public static void resendGroupMessage(@NonNull Context context, @NonNull MessageRecord messageRecord, @NonNull Set<RecipientId> filterRecipientIds) {
if (!messageRecord.isMms()) throw new AssertionError("Not Group");
sendGroupPush(context, messageRecord.getRecipient(), messageRecord.getId(), filterRecipientId, Collections.emptyList());
sendGroupPush(context, messageRecord.getRecipient(), messageRecord.getId(), filterRecipientIds, Collections.emptyList());
onMessageSent();
}
public static void resendDistributionList(@NonNull Context context, @NonNull MessageRecord messageRecord, @NonNull Set<RecipientId> filterRecipientIds) {
if (!messageRecord.isMms() && !((MmsMessageRecord) messageRecord).getStoryType().isStory()) {
throw new AssertionError("Not a story");
}
sendDistributionList(context, messageRecord.getRecipient(), messageRecord.getId(), filterRecipientIds, Collections.emptyList());
onMessageSent();
}
@ -447,9 +458,9 @@ public class MessageSender {
if (isLocalSelfSend(context, recipient, forceSms)) {
sendLocalMediaSelf(context, messageId);
} else if (recipient.isPushGroup()) {
sendGroupPush(context, recipient, messageId, null, uploadJobIds);
sendGroupPush(context, recipient, messageId, Collections.emptySet(), uploadJobIds);
} else if (recipient.isDistributionList()) {
sendDistributionList(context, recipient, messageId, uploadJobIds);
sendDistributionList(context, recipient, messageId, Collections.emptySet(), uploadJobIds);
} else if (!forceSms && isPushMediaSend(context, recipient)) {
sendMediaPush(context, recipient, messageId, uploadJobIds);
} else {
@ -485,25 +496,25 @@ public class MessageSender {
}
}
private static void sendGroupPush(Context context, Recipient recipient, long messageId, RecipientId filterRecipientId, @NonNull Collection<String> uploadJobIds) {
private static void sendGroupPush(@NonNull Context context, @NonNull Recipient recipient, long messageId, @NonNull Set<RecipientId> filterRecipientIds, @NonNull Collection<String> uploadJobIds) {
JobManager jobManager = ApplicationDependencies.getJobManager();
if (uploadJobIds.size() > 0) {
Job groupSend = new PushGroupSendJob(messageId, recipient.getId(), filterRecipientId, !uploadJobIds.isEmpty());
Job groupSend = new PushGroupSendJob(messageId, recipient.getId(), filterRecipientIds, !uploadJobIds.isEmpty());
jobManager.add(groupSend, uploadJobIds, uploadJobIds.isEmpty() ? null : recipient.getId().toQueueKey());
} else {
PushGroupSendJob.enqueue(context, jobManager, messageId, recipient.getId(), filterRecipientId);
PushGroupSendJob.enqueue(context, jobManager, messageId, recipient.getId(), filterRecipientIds);
}
}
private static void sendDistributionList(Context context, Recipient recipient, long messageId, @NonNull Collection<String> uploadJobIds) {
private static void sendDistributionList(@NonNull Context context, @NonNull Recipient recipient, long messageId, @NonNull Set<RecipientId> filterRecipientIds, @NonNull Collection<String> uploadJobIds) {
JobManager jobManager = ApplicationDependencies.getJobManager();
if (uploadJobIds.size() > 0) {
Job groupSend = new PushDistributionListSendJob(messageId, recipient.getId(), !uploadJobIds.isEmpty());
Job groupSend = new PushDistributionListSendJob(messageId, recipient.getId(), !uploadJobIds.isEmpty(), filterRecipientIds);
jobManager.add(groupSend, uploadJobIds, uploadJobIds.isEmpty() ? null : recipient.getId().toQueueKey());
} else {
PushDistributionListSendJob.enqueue(context, jobManager, messageId, recipient.getId());
PushDistributionListSendJob.enqueue(context, jobManager, messageId, recipient.getId(), filterRecipientIds);
}
}

View file

@ -14,6 +14,7 @@ import org.thoughtcrime.securesms.components.settings.DSLSettingsText
import org.thoughtcrime.securesms.components.settings.configure
import org.thoughtcrime.securesms.conversation.mutiselect.forward.MultiselectForwardFragment
import org.thoughtcrime.securesms.conversation.mutiselect.forward.MultiselectForwardFragmentArgs
import org.thoughtcrime.securesms.conversation.ui.error.SafetyNumberChangeDialog
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.stories.dialogs.StoryContextMenu
@ -73,8 +74,12 @@ class MyStoriesFragment : DSLSettingsFragment(
distributionStory = conversationMessage,
onClick = { it, preview ->
if (it.distributionStory.messageRecord.isOutgoing && it.distributionStory.messageRecord.isFailed) {
lifecycleDisposable += viewModel.resend(it.distributionStory.messageRecord).subscribe()
Toast.makeText(requireContext(), R.string.message_recipients_list_item__resend, Toast.LENGTH_SHORT).show()
if (it.distributionStory.messageRecord.isIdentityMismatchFailure) {
SafetyNumberChangeDialog.show(requireContext(), childFragmentManager, it.distributionStory.messageRecord)
} else {
lifecycleDisposable += viewModel.resend(it.distributionStory.messageRecord).subscribe()
Toast.makeText(requireContext(), R.string.message_recipients_list_item__resend, Toast.LENGTH_SHORT).show()
}
} else {
val recipientId = if (it.distributionStory.messageRecord.recipient.isGroup) {
it.distributionStory.messageRecord.recipient.id

View file

@ -1,11 +1,17 @@
package org.thoughtcrime.securesms.recipients;
import org.hamcrest.Matcher;
import org.hamcrest.MatcherAssert;
import org.junit.Test;
import java.util.List;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.hasSize;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
@ -43,6 +49,13 @@ public final class RecipientIdSerializationTest {
assertThat(RecipientId.fromSerializedList("123,456"), is(asList(RecipientId.from(123), RecipientId.from(456))));
}
@Test
public void fromSerializedList_recipient_serialize() {
List<RecipientId> recipientIds = RecipientId.fromSerializedList(RecipientId.from(123).serialize());
assertThat(recipientIds, hasSize(1));
assertThat(recipientIds, contains(RecipientId.from(123)));
}
@Test
public void serializedListContains_empty_list_does_not_contain_item() {
assertFalse(RecipientId.serializedListContains("", RecipientId.from(456)));