Signal-Android/app/src/androidTest/java/org/thoughtcrime/securesms/database/RecipientTableTest_getAndPo...

763 wiersze
26 KiB
Kotlin
Czysty Zwykły widok Historia

package org.thoughtcrime.securesms.database
2022-11-16 01:55:31 +00:00
import androidx.core.content.contentValuesOf
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.hamcrest.MatcherAssert
import org.hamcrest.Matchers
import org.junit.Assert
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.signal.core.util.CursorUtil
2022-11-16 01:55:31 +00:00
import org.signal.core.util.SqlUtil
import org.signal.core.util.select
import org.signal.libsignal.protocol.IdentityKey
import org.signal.libsignal.protocol.SignalProtocolAddress
import org.signal.libsignal.protocol.state.SessionRecord
import org.thoughtcrime.securesms.conversation.colors.AvatarColor
import org.thoughtcrime.securesms.database.model.DistributionListId
import org.thoughtcrime.securesms.database.model.DistributionListRecord
import org.thoughtcrime.securesms.database.model.Mention
import org.thoughtcrime.securesms.database.model.MessageId
import org.thoughtcrime.securesms.database.model.MessageRecord
import org.thoughtcrime.securesms.database.model.ReactionRecord
2022-11-16 01:55:31 +00:00
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.groups.GroupId
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.mms.IncomingMediaMessage
import org.thoughtcrime.securesms.notifications.profiles.NotificationProfile
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
2022-11-16 01:55:31 +00:00
import org.thoughtcrime.securesms.sms.IncomingEncryptedMessage
import org.thoughtcrime.securesms.sms.IncomingTextMessage
2021-10-28 19:39:36 +00:00
import org.whispersystems.signalservice.api.push.ACI
import org.whispersystems.signalservice.api.push.PNI
2022-11-16 01:55:31 +00:00
import org.whispersystems.signalservice.api.push.ServiceId
import java.util.Optional
import java.util.UUID
@RunWith(AndroidJUnit4::class)
class RecipientTableTest_getAndPossiblyMerge {
@Before
fun setup() {
2022-11-16 01:55:31 +00:00
SignalStore.account().setE164(E164_SELF)
SignalStore.account().setAci(ACI_SELF)
SignalStore.account().setPni(PNI_SELF)
}
@Test
2022-11-16 01:55:31 +00:00
fun allSimpleTests() {
test("no match, e164-only") {
process(E164_A, null, null)
expect(E164_A, null, null)
}
2022-11-16 01:55:31 +00:00
test("no match, e164 and pni") {
process(E164_A, PNI_A, null)
expect(E164_A, PNI_A, null)
}
2022-11-16 01:55:31 +00:00
test("no match, aci-only") {
process(null, null, ACI_A)
expect(null, null, ACI_A)
}
2022-11-16 01:55:31 +00:00
test("no match, e164 and aci") {
process(E164_A, null, ACI_A)
expect(E164_A, null, ACI_A)
}
2022-11-16 01:55:31 +00:00
test("no match, no data", exception = java.lang.IllegalArgumentException::class.java) {
process(null, null, null)
}
2022-11-16 01:55:31 +00:00
test("no match, all fields") {
process(E164_A, PNI_A, ACI_A)
expect(E164_A, PNI_A, ACI_A)
}
2022-11-16 01:55:31 +00:00
test("full match") {
given(E164_A, PNI_A, ACI_A)
process(E164_A, PNI_A, ACI_A)
expect(E164_A, PNI_A, ACI_A)
}
2022-11-16 01:55:31 +00:00
test("e164 matches, all fields provided") {
given(E164_A, null, null)
process(E164_A, PNI_A, ACI_A)
expect(E164_A, PNI_A, ACI_A)
}
2022-11-16 01:55:31 +00:00
test("e164 matches, e164 and aci provided") {
given(E164_A, null, null)
process(E164_A, null, ACI_A)
expect(E164_A, null, ACI_A)
}
2022-11-16 01:55:31 +00:00
test("e164 matches, all provided, different aci") {
given(E164_A, null, ACI_B)
2022-11-16 01:55:31 +00:00
process(E164_A, PNI_A, ACI_A)
2022-11-16 01:55:31 +00:00
expect(null, null, ACI_B)
expect(E164_A, PNI_A, ACI_A)
}
2022-11-16 01:55:31 +00:00
test("e164 matches, e164 and aci provided, different aci") {
given(E164_A, null, ACI_A)
2022-11-16 01:55:31 +00:00
process(E164_A, null, ACI_B)
2022-11-16 01:55:31 +00:00
expect(null, null, ACI_A)
expect(E164_A, null, ACI_B)
}
2022-11-16 01:55:31 +00:00
test("e164 and pni matches, all provided, new aci") {
given(E164_A, PNI_A, null)
process(E164_A, PNI_A, ACI_A)
expect(E164_A, PNI_A, ACI_A)
}
2022-11-16 01:55:31 +00:00
test("e164 and aci matches, all provided, new pni") {
given(E164_A, null, ACI_A)
process(E164_A, PNI_A, ACI_A)
expect(E164_A, PNI_A, ACI_A)
}
2022-11-16 01:55:31 +00:00
test("pni matches, all provided, new e164 and aci") {
given(null, PNI_A, null)
process(E164_A, PNI_A, ACI_A)
expect(E164_A, PNI_A, ACI_A)
}
2022-11-16 01:55:31 +00:00
test("pni and aci matches, all provided, new e164") {
given(null, PNI_A, ACI_A)
process(E164_A, PNI_A, ACI_A)
expect(E164_A, PNI_A, ACI_A)
}
2022-11-16 01:55:31 +00:00
test("e164 and aci matches, e164 and aci provided, nothing new") {
given(E164_A, null, ACI_A)
process(E164_A, null, ACI_A)
expect(E164_A, null, ACI_A)
}
2022-11-16 01:55:31 +00:00
test("aci matches, all provided, new e164 and pni") {
given(null, null, ACI_A)
process(E164_A, PNI_A, ACI_A)
expect(E164_A, PNI_A, ACI_A)
}
2022-11-16 01:55:31 +00:00
test("aci matches, e164 and aci provided") {
given(null, null, ACI_A)
process(E164_A, null, ACI_A)
expect(E164_A, null, ACI_A)
}
2022-11-16 01:55:31 +00:00
test("aci matches, local user, changeSelf=false") {
given(E164_SELF, PNI_SELF, ACI_SELF)
2022-11-16 01:55:31 +00:00
process(E164_SELF, null, ACI_B)
2022-11-16 01:55:31 +00:00
expect(E164_SELF, PNI_SELF, ACI_SELF)
expect(null, null, ACI_B)
}
2022-11-16 01:55:31 +00:00
test("e164 matches, e164 and pni provided, pni changes, no pni session") {
given(E164_A, PNI_B, null)
process(E164_A, PNI_A, null)
expect(E164_A, PNI_A, null)
}
2022-11-16 01:55:31 +00:00
test("e164 and pni matches, all provided, no existing session") {
given(E164_A, PNI_A, null)
process(E164_A, PNI_A, ACI_A)
expect(E164_A, PNI_A, ACI_A)
}
2022-11-16 01:55:31 +00:00
test("pni matches, all provided, no existing session") {
given(null, PNI_A, null)
process(E164_A, PNI_A, ACI_A)
expect(E164_A, PNI_A, ACI_A)
}
2022-11-16 01:55:31 +00:00
// This test, I could go either way. We decide to change the E164 on the existing row rather than create a new one.
// But it's an "unstable E164->PNI mapping" case, which we don't expect, so as long as there's a user-visible impact that should be fine.
test("pni matches, no existing pni session, changes number") {
given(E164_B, PNI_A, null)
process(E164_A, PNI_A, ACI_A)
expect(E164_A, PNI_A, ACI_A)
2022-11-16 01:55:31 +00:00
expectChangeNumberEvent()
}
2022-11-16 01:55:31 +00:00
// This test, I could go either way. We decide to change the E164 on the existing row rather than create a new one.
// But it's an "unstable E164->PNI mapping" case, which we don't expect, so as long as there's a user-visible impact that should be fine.
test("pni and aci matches, change number") {
given(E164_B, PNI_A, ACI_A)
process(E164_A, PNI_A, ACI_A)
expect(E164_A, PNI_A, ACI_A)
2022-11-16 01:55:31 +00:00
expectChangeNumberEvent()
}
2022-11-16 01:55:31 +00:00
test("aci matches, all provided, change number") {
given(E164_B, null, ACI_A)
process(E164_A, PNI_A, ACI_A)
expect(E164_A, PNI_A, ACI_A)
2022-11-16 01:55:31 +00:00
expectChangeNumberEvent()
}
2022-01-12 18:08:24 +00:00
2022-11-16 01:55:31 +00:00
test("aci matches, e164 and aci provided, change number") {
given(E164_B, null, ACI_A)
process(E164_A, null, ACI_A)
expect(E164_A, null, ACI_A)
2022-01-12 18:08:24 +00:00
2022-11-16 01:55:31 +00:00
expectChangeNumberEvent()
}
2022-01-12 18:08:24 +00:00
2022-11-16 01:55:31 +00:00
test("steal, e164+pni & e164+pni, no aci provided, no sessions") {
given(E164_A, PNI_B, null)
given(E164_B, PNI_A, null)
2022-01-12 18:08:24 +00:00
2022-11-16 01:55:31 +00:00
process(E164_A, PNI_A, null)
2022-01-12 18:08:24 +00:00
2022-11-16 01:55:31 +00:00
expect(E164_A, PNI_A, null)
expect(E164_B, null, null)
}
2022-01-12 18:08:24 +00:00
2022-11-16 01:55:31 +00:00
test("steal, e164+pni & aci, e164 record has separate e164") {
given(E164_B, PNI_A, null)
given(null, null, ACI_A)
2022-11-16 01:55:31 +00:00
process(E164_A, PNI_A, ACI_A)
2022-11-16 01:55:31 +00:00
expect(E164_B, null, null)
expect(E164_A, PNI_A, ACI_A)
}
2022-11-16 01:55:31 +00:00
test("steal, e164+aci & e164+aci, change number") {
given(E164_B, null, ACI_A)
given(E164_A, null, ACI_B)
2022-11-16 01:55:31 +00:00
process(E164_A, null, ACI_A)
2022-01-12 18:08:24 +00:00
2022-11-16 01:55:31 +00:00
expect(E164_A, null, ACI_A)
expect(null, null, ACI_B)
2022-11-16 01:55:31 +00:00
expectChangeNumberEvent()
}
2022-11-16 01:55:31 +00:00
test("merge, e164 & pni & aci, all provided") {
given(E164_A, null, null)
given(null, PNI_A, null)
given(null, null, ACI_A)
2022-11-16 01:55:31 +00:00
process(E164_A, PNI_A, ACI_A)
2022-11-16 01:55:31 +00:00
expectDeleted()
expectDeleted()
expect(E164_A, PNI_A, ACI_A)
}
2022-11-16 01:55:31 +00:00
test("merge, e164 & pni, no aci provided") {
given(E164_A, null, null)
given(null, PNI_A, null)
2022-11-16 01:55:31 +00:00
process(E164_A, PNI_A, null)
expect(E164_A, PNI_A, null)
expectDeleted()
}
2022-11-16 01:55:31 +00:00
test("merge, e164 & pni, aci provided but no aci record") {
given(E164_A, null, null)
given(null, PNI_A, null)
2022-11-16 01:55:31 +00:00
process(E164_A, PNI_A, ACI_A)
2022-11-16 01:55:31 +00:00
expect(E164_A, PNI_A, ACI_A)
expectDeleted()
}
2022-11-16 01:55:31 +00:00
test("merge, e164 & pni+e164, no aci provided") {
given(E164_A, null, null)
given(E164_B, PNI_A, null)
2022-11-16 01:55:31 +00:00
process(E164_A, PNI_A, null)
expect(E164_A, PNI_A, null)
expect(E164_B, null, null)
2022-01-12 18:08:24 +00:00
}
2022-11-16 01:55:31 +00:00
test("merge, e164+pni & pni, no aci provided") {
given(E164_A, PNI_B, null)
given(null, PNI_A, null)
2022-01-12 18:08:24 +00:00
2022-11-16 01:55:31 +00:00
process(E164_A, PNI_A, null)
2022-01-12 18:08:24 +00:00
2022-11-16 01:55:31 +00:00
expect(E164_A, PNI_A, null)
expectDeleted()
}
2022-01-12 18:08:24 +00:00
2022-11-16 01:55:31 +00:00
test("merge, e164+pni & aci") {
given(E164_A, PNI_A, null)
given(null, null, ACI_A)
process(E164_A, PNI_A, ACI_A)
expectDeleted()
expect(E164_A, PNI_A, ACI_A)
2022-01-12 18:08:24 +00:00
}
2022-11-16 01:55:31 +00:00
test("merge, e164+pni & e164+pni+aci, change number") {
given(E164_A, PNI_A, null)
given(E164_B, PNI_B, ACI_A)
2022-01-12 18:08:24 +00:00
2022-11-16 01:55:31 +00:00
process(E164_A, PNI_A, ACI_A)
2022-01-12 18:08:24 +00:00
2022-11-16 01:55:31 +00:00
expectDeleted()
expect(E164_A, PNI_A, ACI_A)
2022-01-12 18:08:24 +00:00
2022-11-16 01:55:31 +00:00
expectChangeNumberEvent()
}
2022-01-12 18:08:24 +00:00
2022-11-16 01:55:31 +00:00
test("merge, e164+pni & e164+aci, change number") {
given(E164_A, PNI_A, null)
given(E164_B, null, ACI_A)
2022-01-12 18:08:24 +00:00
2022-11-16 01:55:31 +00:00
process(E164_A, PNI_A, ACI_A)
expectDeleted()
expect(E164_A, PNI_A, ACI_A)
expectChangeNumberEvent()
}
test("merge, e164 & aci") {
given(E164_A, null, null)
given(null, null, ACI_A)
process(E164_A, null, ACI_A)
expectDeleted()
expect(E164_A, null, ACI_A)
}
test("merge, e164 & e164+aci, change number") {
given(E164_A, null, null)
given(E164_B, null, ACI_A)
process(E164_A, null, ACI_A)
expectDeleted()
expect(E164_A, null, ACI_A)
expectChangeNumberEvent()
}
test("local user, local e164 and aci provided, changeSelf=false, leave e164 alone") {
given(E164_SELF, null, ACI_SELF)
given(null, null, ACI_A)
process(E164_SELF, null, ACI_A)
expect(E164_SELF, null, ACI_SELF)
expect(null, null, ACI_A)
}
test("local user, e164 and aci provided, changeSelf=false, leave e164 alone") {
given(E164_SELF, null, ACI_SELF)
process(E164_A, null, ACI_SELF)
expect(E164_SELF, null, ACI_SELF)
}
2022-01-12 18:08:24 +00:00
2022-11-16 01:55:31 +00:00
test("local user, e164 and aci provided, changeSelf=true, change e164") {
given(E164_SELF, null, ACI_SELF)
process(E164_A, null, ACI_SELF, changeSelf = true)
expect(E164_A, null, ACI_SELF)
}
2022-01-12 18:08:24 +00:00
}
2022-11-16 01:55:31 +00:00
/**
* Somewhat exhaustive test of verifying all the data that gets merged.
*/
@Test
fun getAndPossiblyMerge_merge_general() {
// Setup
2022-11-16 01:55:31 +00:00
val recipientIdAci: RecipientId = SignalDatabase.recipients.getOrInsertFromServiceId(ACI_A)
val recipientIdE164: RecipientId = SignalDatabase.recipients.getOrInsertFromE164(E164_A)
val recipientIdAciB: RecipientId = SignalDatabase.recipients.getOrInsertFromServiceId(ACI_B)
2022-11-16 01:55:31 +00:00
val smsId1: Long = SignalDatabase.sms.insertMessageInbox(smsMessage(sender = recipientIdAci, time = 0, body = "0")).get().messageId
val smsId2: Long = SignalDatabase.sms.insertMessageInbox(smsMessage(sender = recipientIdE164, time = 1, body = "1")).get().messageId
val smsId3: Long = SignalDatabase.sms.insertMessageInbox(smsMessage(sender = recipientIdAci, time = 2, body = "2")).get().messageId
2022-11-16 01:55:31 +00:00
val mmsId1: Long = SignalDatabase.mms.insertSecureDecryptedMessageInbox(mmsMessage(sender = recipientIdAci, time = 3, body = "3"), -1).get().messageId
val mmsId2: Long = SignalDatabase.mms.insertSecureDecryptedMessageInbox(mmsMessage(sender = recipientIdE164, time = 4, body = "4"), -1).get().messageId
val mmsId3: Long = SignalDatabase.mms.insertSecureDecryptedMessageInbox(mmsMessage(sender = recipientIdAci, time = 5, body = "5"), -1).get().messageId
2022-11-16 01:55:31 +00:00
val threadIdAci: Long = SignalDatabase.threads.getThreadIdFor(recipientIdAci)!!
val threadIdE164: Long = SignalDatabase.threads.getThreadIdFor(recipientIdE164)!!
Assert.assertNotEquals(threadIdAci, threadIdE164)
2022-11-16 01:55:31 +00:00
SignalDatabase.mentions.insert(threadIdAci, mmsId1, listOf(Mention(recipientIdE164, 0, 1)))
SignalDatabase.mentions.insert(threadIdE164, mmsId2, listOf(Mention(recipientIdAci, 0, 1)))
2022-11-16 01:55:31 +00:00
SignalDatabase.groupReceipts.insert(listOf(recipientIdAci, recipientIdE164), mmsId1, 0, 3)
val identityKeyAci: IdentityKey = identityKey(1)
val identityKeyE164: IdentityKey = identityKey(2)
SignalDatabase.identities.saveIdentity(ACI_A.toString(), recipientIdAci, identityKeyAci, IdentityTable.VerifiedStatus.VERIFIED, false, 0, false)
SignalDatabase.identities.saveIdentity(E164_A, recipientIdE164, identityKeyE164, IdentityTable.VerifiedStatus.VERIFIED, false, 0, false)
2022-11-16 01:55:31 +00:00
SignalDatabase.sessions.store(ACI_SELF, SignalProtocolAddress(ACI_A.toString(), 1), SessionRecord())
2022-11-16 01:55:31 +00:00
SignalDatabase.reactions.addReaction(MessageId(smsId1, false), ReactionRecord("a", recipientIdAci, 1, 1))
SignalDatabase.reactions.addReaction(MessageId(mmsId1, true), ReactionRecord("b", recipientIdE164, 1, 1))
val profile1: NotificationProfile = notificationProfile(name = "Test")
val profile2: NotificationProfile = notificationProfile(name = "Test2")
2022-11-16 01:55:31 +00:00
SignalDatabase.notificationProfiles.addAllowedRecipient(profileId = profile1.id, recipientId = recipientIdAci)
SignalDatabase.notificationProfiles.addAllowedRecipient(profileId = profile1.id, recipientId = recipientIdE164)
SignalDatabase.notificationProfiles.addAllowedRecipient(profileId = profile2.id, recipientId = recipientIdE164)
SignalDatabase.notificationProfiles.addAllowedRecipient(profileId = profile2.id, recipientId = recipientIdAciB)
2022-11-16 01:55:31 +00:00
val distributionListId: DistributionListId = SignalDatabase.distributionLists.createList("testlist", listOf(recipientIdE164, recipientIdAciB))!!
// Merge
2022-11-16 01:55:31 +00:00
val retrievedId: RecipientId = SignalDatabase.recipients.getAndPossiblyMerge(ACI_A, E164_A, true)
val retrievedThreadId: Long = SignalDatabase.threads.getThreadIdFor(retrievedId)!!
assertEquals(recipientIdAci, retrievedId)
// Recipient validation
val retrievedRecipient = Recipient.resolved(retrievedId)
assertEquals(ACI_A, retrievedRecipient.requireServiceId())
assertEquals(E164_A, retrievedRecipient.requireE164())
val existingE164Recipient = Recipient.resolved(recipientIdE164)
assertEquals(retrievedId, existingE164Recipient.id)
// Thread validation
assertEquals(threadIdAci, retrievedThreadId)
2022-11-16 01:55:31 +00:00
Assert.assertNull(SignalDatabase.threads.getThreadIdFor(recipientIdE164))
Assert.assertNull(SignalDatabase.threads.getThreadRecord(threadIdE164))
// SMS validation
2022-11-16 01:55:31 +00:00
val sms1: MessageRecord = SignalDatabase.sms.getMessageRecord(smsId1)!!
val sms2: MessageRecord = SignalDatabase.sms.getMessageRecord(smsId2)!!
val sms3: MessageRecord = SignalDatabase.sms.getMessageRecord(smsId3)!!
assertEquals(retrievedId, sms1.recipient.id)
assertEquals(retrievedId, sms2.recipient.id)
assertEquals(retrievedId, sms3.recipient.id)
assertEquals(retrievedThreadId, sms1.threadId)
assertEquals(retrievedThreadId, sms2.threadId)
assertEquals(retrievedThreadId, sms3.threadId)
// MMS validation
2022-11-16 01:55:31 +00:00
val mms1: MessageRecord = SignalDatabase.mms.getMessageRecord(mmsId1)!!
val mms2: MessageRecord = SignalDatabase.mms.getMessageRecord(mmsId2)!!
val mms3: MessageRecord = SignalDatabase.mms.getMessageRecord(mmsId3)!!
assertEquals(retrievedId, mms1.recipient.id)
assertEquals(retrievedId, mms2.recipient.id)
assertEquals(retrievedId, mms3.recipient.id)
assertEquals(retrievedThreadId, mms1.threadId)
assertEquals(retrievedThreadId, mms2.threadId)
assertEquals(retrievedThreadId, mms3.threadId)
// Mention validation
val mention1: MentionModel = getMention(mmsId1)
assertEquals(retrievedId, mention1.recipientId)
assertEquals(retrievedThreadId, mention1.threadId)
val mention2: MentionModel = getMention(mmsId2)
assertEquals(retrievedId, mention2.recipientId)
assertEquals(retrievedThreadId, mention2.threadId)
// Group receipt validation
val groupReceipts: List<GroupReceiptTable.GroupReceiptInfo> = SignalDatabase.groupReceipts.getGroupReceiptInfo(mmsId1)
assertEquals(retrievedId, groupReceipts[0].recipientId)
assertEquals(retrievedId, groupReceipts[1].recipientId)
// Identity validation
2022-11-16 01:55:31 +00:00
assertEquals(identityKeyAci, SignalDatabase.identities.getIdentityStoreRecord(ACI_A.toString())!!.identityKey)
Assert.assertNull(SignalDatabase.identities.getIdentityStoreRecord(E164_A))
// Session validation
2022-11-16 01:55:31 +00:00
Assert.assertNotNull(SignalDatabase.sessions.load(ACI_SELF, SignalProtocolAddress(ACI_A.toString(), 1)))
// Reaction validation
2022-11-16 01:55:31 +00:00
val reactionsSms: List<ReactionRecord> = SignalDatabase.reactions.getReactions(MessageId(smsId1, false))
val reactionsMms: List<ReactionRecord> = SignalDatabase.reactions.getReactions(MessageId(mmsId1, true))
assertEquals(1, reactionsSms.size)
assertEquals(ReactionRecord("a", recipientIdAci, 1, 1), reactionsSms[0])
assertEquals(1, reactionsMms.size)
assertEquals(ReactionRecord("b", recipientIdAci, 1, 1), reactionsMms[0])
// Notification Profile validation
2022-11-16 01:55:31 +00:00
val updatedProfile1: NotificationProfile = SignalDatabase.notificationProfiles.getProfile(profile1.id)!!
val updatedProfile2: NotificationProfile = SignalDatabase.notificationProfiles.getProfile(profile2.id)!!
MatcherAssert.assertThat("Notification Profile 1 should now only contain ACI $recipientIdAci", updatedProfile1.allowedMembers, Matchers.containsInAnyOrder(recipientIdAci))
MatcherAssert.assertThat("Notification Profile 2 should now contain ACI A ($recipientIdAci) and ACI B ($recipientIdAciB)", updatedProfile2.allowedMembers, Matchers.containsInAnyOrder(recipientIdAci, recipientIdAciB))
// Distribution List validation
2022-11-16 01:55:31 +00:00
val updatedList: DistributionListRecord = SignalDatabase.distributionLists.getList(distributionListId)!!
MatcherAssert.assertThat("Distribution list should have updated $recipientIdE164 to $recipientIdAci", updatedList.members, Matchers.containsInAnyOrder(recipientIdAci, recipientIdAciB))
}
private fun smsMessage(sender: RecipientId, time: Long = 0, body: String = "", groupId: Optional<GroupId> = Optional.empty()): IncomingTextMessage {
return IncomingTextMessage(sender, 1, time, time, time, body, groupId, 0, true, null)
}
private fun mmsMessage(sender: RecipientId, time: Long = 0, body: String = "", groupId: Optional<GroupId> = Optional.empty()): IncomingMediaMessage {
2022-11-02 00:14:17 +00:00
return IncomingMediaMessage(sender, groupId, body, time, time, time, emptyList(), 0, 0, false, false, true, Optional.empty(), false, false)
}
private fun identityKey(value: Byte): IdentityKey {
val bytes = ByteArray(33)
bytes[0] = 0x05
bytes[1] = value
return IdentityKey(bytes)
}
private fun notificationProfile(name: String): NotificationProfile {
2022-11-16 01:55:31 +00:00
return (SignalDatabase.notificationProfiles.createProfile(name = name, emoji = "", color = AvatarColor.A210, System.currentTimeMillis()) as NotificationProfileDatabase.NotificationProfileChangeResult.Success).notificationProfile
}
private fun getMention(messageId: Long): MentionModel {
SignalDatabase.rawDatabase.rawQuery("SELECT * FROM ${MentionTable.TABLE_NAME} WHERE ${MentionTable.MESSAGE_ID} = $messageId").use { cursor ->
cursor.moveToFirst()
return MentionModel(
recipientId = RecipientId.from(CursorUtil.requireLong(cursor, MentionTable.RECIPIENT_ID)),
threadId = CursorUtil.requireLong(cursor, MentionTable.THREAD_ID)
)
}
}
/** The normal mention model doesn't have a threadId, so we need to do it ourselves for the test */
data class MentionModel(
val recipientId: RecipientId,
val threadId: Long
)
2022-11-16 01:55:31 +00:00
/**
* Baby DSL for making tests readable.
*/
private fun test(name: String, init: TestCase.() -> Unit): TestCase {
// Weird issue with generics wouldn't let me make the exception an arg with default value -- had to do an actual overload
return test(name, null as Class<Throwable>?, init)
}
/**
* Baby DSL for making tests readable.
*/
private fun <E> test(name: String, exception: Class<E>?, init: TestCase.() -> Unit): TestCase where E : Throwable {
val test = TestCase()
try {
test.init()
if (exception != null) {
throw java.lang.AssertionError("Expected $exception, but none was thrown.")
}
if (!test.changeNumberExpected) {
test.expectNoChangeNumberEvent()
}
} catch (e: Throwable) {
if (e.javaClass != exception) {
val error = java.lang.AssertionError("[$name] ${e.message}")
error.stackTrace = e.stackTrace
throw error
}
}
return test
}
private inner class TestCase {
private val generatedIds: LinkedHashSet<RecipientId> = LinkedHashSet()
private var expectCount = 0
private lateinit var outputRecipientId: RecipientId
var changeNumberExpected = false
init {
// Need to delete these first to prevent foreign key crash
SignalDatabase.rawDatabase.execSQL("DELETE FROM distribution_list")
SignalDatabase.rawDatabase.execSQL("DELETE FROM distribution_list_member")
SqlUtil.getAllTables(SignalDatabase.rawDatabase)
.filterNot { it.contains("sqlite") || it.contains("fts") || it.startsWith("emoji_search_") } // If we delete these we'll corrupt the DB
.sorted()
.forEach { table ->
SignalDatabase.rawDatabase.execSQL("DELETE FROM $table")
}
ApplicationDependencies.getRecipientCache().clear()
RecipientId.clearCache()
}
fun given(
e164: String?,
pni: PNI?,
aci: ACI?,
createThread: Boolean = true,
sms: List<String> = emptyList(),
mms: List<String> = emptyList()
) {
val id = insert(e164, pni, aci)
generatedIds += id
if (createThread) {
// Create a thread and throw a dummy message in it so it doesn't get automatically deleted
SignalDatabase.threads.getOrCreateThreadIdFor(Recipient.resolved(id))
SignalDatabase.sms.insertMessageInbox(IncomingEncryptedMessage(IncomingTextMessage(id, 1, 0, 0, 0, "", Optional.empty(), 0, false, ""), ""))
}
}
fun process(e164: String?, pni: PNI?, aci: ACI?, changeSelf: Boolean = false) {
outputRecipientId = SignalDatabase.recipients.getAndPossiblyMerge(serviceId = aci ?: pni, pni = pni, e164 = e164, pniVerified = false, changeSelf = changeSelf)
generatedIds += outputRecipientId
}
fun expect(e164: String?, pni: PNI?, aci: ACI?) {
expect(generatedIds.elementAt(expectCount++), e164, pni, aci)
}
fun expect(id: RecipientId, e164: String?, pni: PNI?, aci: ACI?) {
val recipient = Recipient.resolved(id)
val expected = RecipientTuple(
e164 = e164,
pni = pni,
serviceId = aci ?: pni
)
val actual = RecipientTuple(
e164 = recipient.e164.orElse(null),
pni = recipient.pni.orElse(null),
serviceId = recipient.serviceId.orElse(null)
)
assertEquals(expected, actual)
}
fun expectDeleted() {
expectDeleted(generatedIds.elementAt(expectCount++))
}
fun expectDeleted(id: RecipientId) {
SignalDatabase.rawDatabase
.select("1")
.from(RecipientTable.TABLE_NAME)
.where("${RecipientTable.ID} = ?", id)
2022-11-16 01:55:31 +00:00
.run()
.use { !it.moveToFirst() }
}
fun expectChangeNumberEvent() {
assertEquals(1, SignalDatabase.sms.getChangeNumberMessageCount(outputRecipientId))
changeNumberExpected = true
}
fun expectNoChangeNumberEvent() {
assertEquals(0, SignalDatabase.sms.getChangeNumberMessageCount(outputRecipientId))
changeNumberExpected = false
}
private fun insert(e164: String?, pni: PNI?, aci: ACI?): RecipientId {
val serviceIdString: String? = (aci ?: pni)?.toString()
val pniString: String? = pni?.toString()
val id: Long = SignalDatabase.rawDatabase.insert(
RecipientTable.TABLE_NAME,
2022-11-16 01:55:31 +00:00
null,
contentValuesOf(
RecipientTable.PHONE to e164,
RecipientTable.SERVICE_ID to serviceIdString,
RecipientTable.PNI_COLUMN to pniString,
RecipientTable.REGISTERED to RecipientTable.RegisteredState.REGISTERED.id
2022-11-16 01:55:31 +00:00
)
)
assertTrue("Failed to insert! E164: $e164, ServiceId: $serviceIdString, PNI: $pniString", id > 0)
return RecipientId.from(id)
}
}
data class RecipientTuple(
val e164: String?,
val pni: PNI?,
val serviceId: ServiceId?
) {
/**
* The intent here is to give nice diffs with the name of the constants rather than the values.
*/
override fun toString(): String {
return "(${e164.e164String()}, ${pni.pniString()}, ${serviceId.serviceIdString()})"
}
private fun String?.e164String(): String {
return this?.let {
when (it) {
E164_A -> "E164_A"
E164_B -> "E164_B"
else -> it
}
} ?: "null"
}
private fun PNI?.pniString(): String {
return this?.let {
when (it) {
PNI_A -> "PNI_A"
PNI_B -> "PNI_B"
PNI_SELF -> "PNI_SELF"
else -> it.toString()
}
} ?: "null"
}
private fun ServiceId?.serviceIdString(): String {
return this?.let {
when (it) {
PNI_A -> "PNI_A"
PNI_B -> "PNI_B"
PNI_SELF -> "PNI_SELF"
ACI_A -> "ACI_A"
ACI_B -> "ACI_B"
ACI_SELF -> "ACI_SELF"
else -> it.toString()
}
} ?: "null"
}
}
companion object {
2022-11-16 01:55:31 +00:00
val ACI_A = ACI.from(UUID.fromString("aaaa0000-5a76-47fa-a98a-7e72c948a82e"))
val ACI_B = ACI.from(UUID.fromString("bbbb0000-0b60-4a68-9cd9-ed2f8453f9ed"))
val ACI_SELF = ACI.from(UUID.fromString("77770000-b477-4f35-a824-d92987a63641"))
2022-11-16 01:55:31 +00:00
val PNI_A = PNI.from(UUID.fromString("aaaa1111-c960-4f6c-8385-671ad2ffb999"))
val PNI_B = PNI.from(UUID.fromString("bbbb1111-cd55-40bf-adda-c35a85375533"))
val PNI_SELF = PNI.from(UUID.fromString("77771111-b014-41fb-bf73-05cb2ec52910"))
2022-11-16 01:55:31 +00:00
const val E164_A = "+12222222222"
const val E164_B = "+13333333333"
const val E164_SELF = "+10000000000"
}
}