Add import/export test for initially account data.
This commit is contained in:
parent
78d30fc479
commit
58846bbf42
2 changed files with 218 additions and 4 deletions
|
@ -0,0 +1,205 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2024 Signal Messenger, LLC
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.thoughtcrime.securesms.backup.v2
|
||||||
|
|
||||||
|
import okio.ByteString.Companion.toByteString
|
||||||
|
import org.junit.Assert
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import org.signal.libsignal.zkgroup.profiles.ProfileKey
|
||||||
|
import org.thoughtcrime.securesms.backup.v2.proto.AccountData
|
||||||
|
import org.thoughtcrime.securesms.backup.v2.proto.BackupInfo
|
||||||
|
import org.thoughtcrime.securesms.backup.v2.proto.Call
|
||||||
|
import org.thoughtcrime.securesms.backup.v2.proto.Chat
|
||||||
|
import org.thoughtcrime.securesms.backup.v2.proto.ChatItem
|
||||||
|
import org.thoughtcrime.securesms.backup.v2.proto.Frame
|
||||||
|
import org.thoughtcrime.securesms.backup.v2.proto.Recipient
|
||||||
|
import org.thoughtcrime.securesms.backup.v2.proto.Self
|
||||||
|
import org.thoughtcrime.securesms.backup.v2.proto.StickerPack
|
||||||
|
import org.thoughtcrime.securesms.backup.v2.stream.EncryptedBackupReader
|
||||||
|
import org.thoughtcrime.securesms.backup.v2.stream.EncryptedBackupWriter
|
||||||
|
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||||
|
import org.whispersystems.signalservice.api.push.ServiceId
|
||||||
|
import org.whispersystems.signalservice.api.subscriptions.SubscriberId
|
||||||
|
import java.io.ByteArrayInputStream
|
||||||
|
import java.io.ByteArrayOutputStream
|
||||||
|
import java.util.ArrayList
|
||||||
|
import java.util.UUID
|
||||||
|
import kotlin.random.Random
|
||||||
|
|
||||||
|
class ImportExportTest {
|
||||||
|
companion object {
|
||||||
|
val SELF_ACI = ServiceId.ACI.from(UUID.fromString("77770000-b477-4f35-a824-d92987a63641"))
|
||||||
|
val SELF_PNI = ServiceId.PNI.from(UUID.fromString("77771111-b014-41fb-bf73-05cb2ec52910"))
|
||||||
|
const val SELF_E164 = "+10000000000"
|
||||||
|
val SELF_PROFILE_KEY = ProfileKey(Random.nextBytes(32))
|
||||||
|
|
||||||
|
val defaultBackupInfo = BackupInfo(version = 1L, backupTimeMs = 123456L)
|
||||||
|
val selfRecipient = Recipient(id = 1, self = Self())
|
||||||
|
val standardAccountData = AccountData(
|
||||||
|
profileKey = SELF_PROFILE_KEY.serialize().toByteString(),
|
||||||
|
username = "testusername",
|
||||||
|
usernameLink = null,
|
||||||
|
givenName = "Peter",
|
||||||
|
familyName = "Parker",
|
||||||
|
avatarUrlPath = "https://example.com/",
|
||||||
|
subscriberId = SubscriberId.generate().bytes.toByteString(),
|
||||||
|
subscriberCurrencyCode = "USD",
|
||||||
|
subscriptionManuallyCancelled = true,
|
||||||
|
accountSettings = AccountData.AccountSettings(
|
||||||
|
readReceipts = true,
|
||||||
|
sealedSenderIndicators = true,
|
||||||
|
typingIndicators = true,
|
||||||
|
linkPreviews = true,
|
||||||
|
notDiscoverableByPhoneNumber = true,
|
||||||
|
preferContactAvatars = true,
|
||||||
|
universalExpireTimer = 42,
|
||||||
|
displayBadgesOnProfile = true,
|
||||||
|
keepMutedChatsArchived = true,
|
||||||
|
hasSetMyStoriesPrivacy = true,
|
||||||
|
hasViewedOnboardingStory = true,
|
||||||
|
storiesDisabled = true,
|
||||||
|
storyViewReceiptsEnabled = true,
|
||||||
|
hasSeenGroupStoryEducationSheet = true,
|
||||||
|
hasCompletedUsernameOnboarding = true,
|
||||||
|
phoneNumberSharingMode = AccountData.PhoneNumberSharingMode.EVERYBODY,
|
||||||
|
preferredReactionEmoji = listOf("a", "b", "c")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setup() {
|
||||||
|
SignalStore.account().setE164(SELF_E164)
|
||||||
|
SignalStore.account().setAci(SELF_ACI)
|
||||||
|
SignalStore.account().setPni(SELF_PNI)
|
||||||
|
SignalStore.account().generateAciIdentityKeyIfNecessary()
|
||||||
|
SignalStore.account().generatePniIdentityKeyIfNecessary()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun accountAndSelf() {
|
||||||
|
importExport(
|
||||||
|
defaultBackupInfo,
|
||||||
|
standardAccountData,
|
||||||
|
selfRecipient
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun importExport(vararg objects: Any) {
|
||||||
|
val outputStream = ByteArrayOutputStream()
|
||||||
|
val writer = EncryptedBackupWriter(
|
||||||
|
key = SignalStore.svr().getOrCreateMasterKey().deriveBackupKey(),
|
||||||
|
aci = SignalStore.account().aci!!,
|
||||||
|
outputStream = outputStream,
|
||||||
|
append = { mac -> outputStream.write(mac) }
|
||||||
|
)
|
||||||
|
|
||||||
|
writer.use {
|
||||||
|
for (obj in objects) {
|
||||||
|
when (obj) {
|
||||||
|
is BackupInfo -> writer.write(obj)
|
||||||
|
is AccountData -> writer.write(Frame(account = obj))
|
||||||
|
is Recipient -> writer.write(Frame(recipient = obj))
|
||||||
|
is Chat -> writer.write(Frame(chat = obj))
|
||||||
|
is ChatItem -> writer.write(Frame(chatItem = obj))
|
||||||
|
is Call -> writer.write(Frame(call = obj))
|
||||||
|
is StickerPack -> writer.write(Frame(stickerPack = obj))
|
||||||
|
else -> Assert.fail("invalid object $obj")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val importData = outputStream.toByteArray()
|
||||||
|
BackupRepository.import(length = importData.size.toLong(), inputStreamFactory = { ByteArrayInputStream(importData) }, selfData = BackupRepository.SelfData(SELF_ACI, SELF_PNI, SELF_E164, SELF_PROFILE_KEY))
|
||||||
|
|
||||||
|
val export = BackupRepository.export()
|
||||||
|
compare(importData, export)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun compare(import: ByteArray, export: ByteArray) {
|
||||||
|
val selfData = BackupRepository.SelfData(SELF_ACI, SELF_PNI, SELF_E164, SELF_PROFILE_KEY)
|
||||||
|
val framesImported = readAllFrames(import, selfData)
|
||||||
|
val framesExported = readAllFrames(export, selfData)
|
||||||
|
|
||||||
|
compareFrameList(framesImported, framesExported)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun compareFrameList(framesImported: List<Frame>, framesExported: List<Frame>) {
|
||||||
|
val accountExported = ArrayList<AccountData>()
|
||||||
|
val accountImported = ArrayList<AccountData>()
|
||||||
|
val recipientsImported = ArrayList<Recipient>()
|
||||||
|
val recipientsExported = ArrayList<Recipient>()
|
||||||
|
val chatsImported = ArrayList<Chat>()
|
||||||
|
val chatsExported = ArrayList<Chat>()
|
||||||
|
val chatItemsImported = ArrayList<ChatItem>()
|
||||||
|
val chatItemsExported = ArrayList<ChatItem>()
|
||||||
|
val callsImported = ArrayList<Call>()
|
||||||
|
val callsExported = ArrayList<Call>()
|
||||||
|
val stickersImported = ArrayList<StickerPack>()
|
||||||
|
val stickersExported = ArrayList<StickerPack>()
|
||||||
|
|
||||||
|
for (f in framesImported) {
|
||||||
|
when {
|
||||||
|
f.account != null -> accountExported.add(f.account!!)
|
||||||
|
f.recipient != null -> recipientsImported.add(f.recipient!!)
|
||||||
|
f.chat != null -> chatsImported.add(f.chat!!)
|
||||||
|
f.chatItem != null -> chatItemsImported.add(f.chatItem!!)
|
||||||
|
f.call != null -> callsImported.add(f.call!!)
|
||||||
|
f.stickerPack != null -> stickersImported.add(f.stickerPack!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (f in framesExported) {
|
||||||
|
when {
|
||||||
|
f.account != null -> accountImported.add(f.account!!)
|
||||||
|
f.recipient != null -> recipientsExported.add(f.recipient!!)
|
||||||
|
f.chat != null -> chatsExported.add(f.chat!!)
|
||||||
|
f.chatItem != null -> chatItemsExported.add(f.chatItem!!)
|
||||||
|
f.call != null -> callsExported.add(f.call!!)
|
||||||
|
f.stickerPack != null -> stickersExported.add(f.stickerPack!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
prettyAssertEquals(accountImported, accountExported)
|
||||||
|
prettyAssertEquals(recipientsImported, recipientsExported) { it.id }
|
||||||
|
prettyAssertEquals(chatsImported, chatsExported) { it.id }
|
||||||
|
prettyAssertEquals(chatItemsImported, chatItemsExported)
|
||||||
|
prettyAssertEquals(callsImported, callsExported) { it.callId }
|
||||||
|
prettyAssertEquals(stickersImported, stickersExported) { it.packId }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun <T> prettyAssertEquals(import: List<T>, export: List<T>) {
|
||||||
|
Assert.assertEquals(import.size, export.size)
|
||||||
|
import.zip(export).forEach { (a1, a2) ->
|
||||||
|
if (a1 != a2) {
|
||||||
|
Assert.fail("Items do not match: \n $a1 \n $a2")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun <T, R : Comparable<R>> prettyAssertEquals(import: List<T>, export: List<T>, selector: (T) -> R?) {
|
||||||
|
Assert.assertEquals(import.size, export.size)
|
||||||
|
val sortedImport = import.sortedBy(selector)
|
||||||
|
val sortedExport = export.sortedBy(selector)
|
||||||
|
|
||||||
|
prettyAssertEquals(sortedImport, sortedExport)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun readAllFrames(import: ByteArray, selfData: BackupRepository.SelfData): List<Frame> {
|
||||||
|
val inputFactory = { ByteArrayInputStream(import) }
|
||||||
|
val frameReader = EncryptedBackupReader(
|
||||||
|
key = SignalStore.svr().getOrCreateMasterKey().deriveBackupKey(),
|
||||||
|
aci = selfData.aci,
|
||||||
|
streamLength = import.size.toLong(),
|
||||||
|
dataStream = inputFactory
|
||||||
|
)
|
||||||
|
val frames = ArrayList<Frame>()
|
||||||
|
while (frameReader.hasNext()) {
|
||||||
|
frames.add(frameReader.next())
|
||||||
|
}
|
||||||
|
|
||||||
|
return frames
|
||||||
|
}
|
||||||
|
}
|
|
@ -28,6 +28,7 @@ import org.whispersystems.signalservice.api.push.UsernameLinkComponents
|
||||||
import org.whispersystems.signalservice.api.storage.StorageRecordProtoUtil.defaultAccountRecord
|
import org.whispersystems.signalservice.api.storage.StorageRecordProtoUtil.defaultAccountRecord
|
||||||
import org.whispersystems.signalservice.api.subscriptions.SubscriberId
|
import org.whispersystems.signalservice.api.subscriptions.SubscriberId
|
||||||
import org.whispersystems.signalservice.api.util.UuidUtil
|
import org.whispersystems.signalservice.api.util.UuidUtil
|
||||||
|
import kotlin.jvm.optionals.getOrNull
|
||||||
|
|
||||||
object AccountDataProcessor {
|
object AccountDataProcessor {
|
||||||
|
|
||||||
|
@ -47,12 +48,11 @@ object AccountDataProcessor {
|
||||||
familyName = self.profileName.familyName,
|
familyName = self.profileName.familyName,
|
||||||
avatarUrlPath = self.profileAvatar ?: "",
|
avatarUrlPath = self.profileAvatar ?: "",
|
||||||
subscriptionManuallyCancelled = SignalStore.donationsValues().isUserManuallyCancelled(),
|
subscriptionManuallyCancelled = SignalStore.donationsValues().isUserManuallyCancelled(),
|
||||||
username = SignalStore.account().username,
|
username = self.username.getOrNull(),
|
||||||
subscriberId = subscriber?.subscriberId?.bytes?.toByteString() ?: defaultAccountRecord.subscriberId,
|
subscriberId = subscriber?.subscriberId?.bytes?.toByteString() ?: defaultAccountRecord.subscriberId,
|
||||||
subscriberCurrencyCode = subscriber?.currencyCode ?: defaultAccountRecord.subscriberCurrencyCode,
|
subscriberCurrencyCode = subscriber?.currencyCode ?: defaultAccountRecord.subscriberCurrencyCode,
|
||||||
accountSettings = AccountData.AccountSettings(
|
accountSettings = AccountData.AccountSettings(
|
||||||
storyViewReceiptsEnabled = SignalStore.storyValues().viewedReceiptsEnabled,
|
storyViewReceiptsEnabled = SignalStore.storyValues().viewedReceiptsEnabled,
|
||||||
noteToSelfMarkedUnread = record != null && record.syncExtras.isForcedUnread,
|
|
||||||
typingIndicators = TextSecurePreferences.isTypingIndicatorsEnabled(context),
|
typingIndicators = TextSecurePreferences.isTypingIndicatorsEnabled(context),
|
||||||
readReceipts = TextSecurePreferences.isReadReceiptsEnabled(context),
|
readReceipts = TextSecurePreferences.isReadReceiptsEnabled(context),
|
||||||
sealedSenderIndicators = TextSecurePreferences.isShowUnidentifiedDeliveryIndicatorsEnabled(context),
|
sealedSenderIndicators = TextSecurePreferences.isShowUnidentifiedDeliveryIndicatorsEnabled(context),
|
||||||
|
@ -61,13 +61,14 @@ object AccountDataProcessor {
|
||||||
phoneNumberSharingMode = SignalStore.phoneNumberPrivacy().phoneNumberSharingMode.toBackupPhoneNumberSharingMode(),
|
phoneNumberSharingMode = SignalStore.phoneNumberPrivacy().phoneNumberSharingMode.toBackupPhoneNumberSharingMode(),
|
||||||
preferContactAvatars = SignalStore.settings().isPreferSystemContactPhotos,
|
preferContactAvatars = SignalStore.settings().isPreferSystemContactPhotos,
|
||||||
universalExpireTimer = SignalStore.settings().universalExpireTimer,
|
universalExpireTimer = SignalStore.settings().universalExpireTimer,
|
||||||
preferredReactionEmoji = SignalStore.emojiValues().reactions,
|
preferredReactionEmoji = SignalStore.emojiValues().rawReactions,
|
||||||
storiesDisabled = SignalStore.storyValues().isFeatureDisabled,
|
storiesDisabled = SignalStore.storyValues().isFeatureDisabled,
|
||||||
hasViewedOnboardingStory = SignalStore.storyValues().userHasViewedOnboardingStory,
|
hasViewedOnboardingStory = SignalStore.storyValues().userHasViewedOnboardingStory,
|
||||||
hasSetMyStoriesPrivacy = SignalStore.storyValues().userHasBeenNotifiedAboutStories,
|
hasSetMyStoriesPrivacy = SignalStore.storyValues().userHasBeenNotifiedAboutStories,
|
||||||
keepMutedChatsArchived = SignalStore.settings().shouldKeepMutedChatsArchived(),
|
keepMutedChatsArchived = SignalStore.settings().shouldKeepMutedChatsArchived(),
|
||||||
displayBadgesOnProfile = SignalStore.donationsValues().getDisplayBadgesOnProfile(),
|
displayBadgesOnProfile = SignalStore.donationsValues().getDisplayBadgesOnProfile(),
|
||||||
hasSeenGroupStoryEducationSheet = SignalStore.storyValues().userHasSeenGroupStoryEducationSheet
|
hasSeenGroupStoryEducationSheet = SignalStore.storyValues().userHasSeenGroupStoryEducationSheet,
|
||||||
|
hasCompletedUsernameOnboarding = SignalStore.uiHints().hasCompletedUsernameOnboarding()
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -122,6 +123,14 @@ object AccountDataProcessor {
|
||||||
)
|
)
|
||||||
SignalStore.misc().usernameQrCodeColorScheme = accountData.usernameLink.color.toLocalUsernameColor()
|
SignalStore.misc().usernameQrCodeColorScheme = accountData.usernameLink.color.toLocalUsernameColor()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (settings.preferredReactionEmoji.isNotEmpty()) {
|
||||||
|
SignalStore.emojiValues().reactions = settings.preferredReactionEmoji
|
||||||
|
}
|
||||||
|
|
||||||
|
if (settings.hasCompletedUsernameOnboarding) {
|
||||||
|
SignalStore.uiHints().setHasCompletedUsernameOnboarding(true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SignalDatabase.runPostSuccessfulTransaction { ProfileUtil.handleSelfProfileKeyChange() }
|
SignalDatabase.runPostSuccessfulTransaction { ProfileUtil.handleSelfProfileKeyChange() }
|
||||||
|
|
Loading…
Add table
Reference in a new issue