223 lines
9.4 KiB
Swift
223 lines
9.4 KiB
Swift
//
|
||
// Copyright 2024 Signal Messenger, LLC
|
||
// SPDX-License-Identifier: AGPL-3.0-only
|
||
//
|
||
|
||
import GRDB
|
||
import LibSignalClient
|
||
import XCTest
|
||
|
||
@testable import SignalServiceKit
|
||
|
||
private extension MessageBackupIntegrationTestCase {
|
||
var depBridge: DependenciesBridge { .shared }
|
||
}
|
||
|
||
final class MessageBackupAccountDataTest: MessageBackupIntegrationTestCase {
|
||
func testAccountData() async throws {
|
||
try await runTest(backupName: "account-data") { sdsTx, tx in
|
||
XCTAssertNotNil(profileManager.localProfileKey())
|
||
|
||
switch depBridge.localUsernameManager.usernameState(tx: tx) {
|
||
case .available(let username, let usernameLink):
|
||
XCTAssertEqual(username, "boba_fett.66")
|
||
XCTAssertEqual(usernameLink.handle, UUID("61C101A2-00D5-4217-89C2-0518D8497AF0"))
|
||
XCTAssertEqual(depBridge.localUsernameManager.usernameLinkQRCodeColor(tx: tx), .olive)
|
||
case .unset, .linkCorrupted, .usernameAndLinkCorrupted:
|
||
XCTFail("Unexpected username state!")
|
||
}
|
||
|
||
XCTAssertEqual(profileManager.localGivenName(), "Boba")
|
||
XCTAssertEqual(profileManager.localFamilyName(), "Fett")
|
||
XCTAssertNil(profileManager.localProfileAvatarData())
|
||
|
||
XCTAssertNotNil(subscriptionManager.getSubscriberID(transaction: sdsTx))
|
||
XCTAssertEqual(subscriptionManager.getSubscriberCurrencyCode(transaction: sdsTx), "USD")
|
||
XCTAssertTrue(subscriptionManager.userManuallyCancelledSubscription(transaction: sdsTx))
|
||
|
||
XCTAssertTrue(receiptManager.areReadReceiptsEnabled(transaction: sdsTx))
|
||
XCTAssertTrue(preferences.shouldShowUnidentifiedDeliveryIndicators(transaction: sdsTx))
|
||
XCTAssertTrue(typingIndicatorsImpl.areTypingIndicatorsEnabled())
|
||
XCTAssertFalse(SSKPreferences.areLinkPreviewsEnabled(transaction: sdsTx))
|
||
XCTAssertEqual(depBridge.phoneNumberDiscoverabilityManager.phoneNumberDiscoverability(tx: tx), .nobody)
|
||
XCTAssertTrue(SSKPreferences.preferContactAvatars(transaction: sdsTx))
|
||
let universalExpireConfig = depBridge.disappearingMessagesConfigurationStore.fetch(for: .universal, tx: tx)
|
||
XCTAssertEqual(universalExpireConfig?.isEnabled, true)
|
||
XCTAssertEqual(universalExpireConfig?.durationSeconds, 3600)
|
||
XCTAssertEqual(ReactionManager.customEmojiSet(transaction: sdsTx), ["🏎️"])
|
||
XCTAssertTrue(subscriptionManager.displayBadgesOnProfile(transaction: sdsTx))
|
||
XCTAssertTrue(SSKPreferences.shouldKeepMutedChatsArchived(transaction: sdsTx))
|
||
XCTAssertTrue(StoryManager.hasSetMyStoriesPrivacy(transaction: sdsTx))
|
||
XCTAssertTrue(systemStoryManager.isOnboardingStoryRead(transaction: sdsTx))
|
||
XCTAssertFalse(StoryManager.areStoriesEnabled(transaction: sdsTx))
|
||
XCTAssertTrue(StoryManager.areViewReceiptsEnabled(transaction: sdsTx))
|
||
XCTAssertTrue(systemStoryManager.isOnboardingOverlayViewed(transaction: sdsTx))
|
||
XCTAssertFalse(depBridge.usernameEducationManager.shouldShowUsernameEducation(tx: tx))
|
||
XCTAssertEqual(udManager.phoneNumberSharingMode(tx: tx), .nobody)
|
||
}
|
||
}
|
||
}
|
||
|
||
final class MessageBackupContactTest: MessageBackupIntegrationTestCase {
|
||
private func assert(
|
||
recipient: SignalRecipient,
|
||
aci: Aci = .fixture,
|
||
pni: Pni = .fixture,
|
||
username: String = "han_solo.44",
|
||
phoneNumber: String = "+17735550199",
|
||
isBlocked: Bool,
|
||
isHidden: Bool,
|
||
isRegistered: Bool,
|
||
unregisteredAtTimestamp: UInt64?,
|
||
isWhitelisted: Bool,
|
||
givenName: String = "Han",
|
||
familyName: String = "Solo",
|
||
isStoryHidden: Bool = true,
|
||
sdsTx: SDSAnyReadTransaction,
|
||
tx: DBReadTransaction
|
||
) {
|
||
XCTAssertEqual(recipient.aci, aci)
|
||
XCTAssertEqual(recipient.pni, pni)
|
||
XCTAssertEqual(depBridge.usernameLookupManager.fetchUsername(forAci: recipient.aci!, transaction: tx), username)
|
||
XCTAssertEqual(recipient.phoneNumber?.stringValue, phoneNumber)
|
||
XCTAssertEqual(blockingManager.isAddressBlocked(recipient.address, transaction: sdsTx), isBlocked)
|
||
XCTAssertEqual(depBridge.recipientHidingManager.isHiddenRecipient(recipient, tx: tx), isHidden)
|
||
XCTAssertEqual(recipient.isRegistered, isRegistered)
|
||
XCTAssertEqual(recipient.unregisteredAtTimestamp, unregisteredAtTimestamp)
|
||
let recipientProfile = profileManager.getUserProfile(for: recipient.address, transaction: sdsTx)
|
||
XCTAssertNotNil(recipientProfile?.profileKey)
|
||
XCTAssertEqual(profileManager.isUser(inProfileWhitelist: recipient.address, transaction: sdsTx), isWhitelisted)
|
||
XCTAssertEqual(recipientProfile?.givenName, givenName)
|
||
XCTAssertEqual(recipientProfile?.familyName, familyName)
|
||
XCTAssertEqual(StoryStoreImpl().getOrCreateStoryContextAssociatedData(for: recipient.aci!, tx: tx).isHidden, isStoryHidden)
|
||
}
|
||
|
||
func testRegisteredBlockedContact() async throws {
|
||
try await runTest(backupName: "registered-blocked-contact") { sdsTx, tx in
|
||
let allRecipients = depBridge.recipientDatabaseTable.allRecipients(tx: tx)
|
||
XCTAssertEqual(allRecipients.count, 1)
|
||
|
||
assert(
|
||
recipient: allRecipients.first!,
|
||
isBlocked: true,
|
||
isHidden: true,
|
||
isRegistered: true,
|
||
unregisteredAtTimestamp: nil,
|
||
isWhitelisted: false,
|
||
sdsTx: sdsTx,
|
||
tx: tx
|
||
)
|
||
}
|
||
}
|
||
|
||
func testUnregisteredContact() async throws {
|
||
try await runTest(backupName: "unregistered-contact") { sdsTx, tx in
|
||
let allRecipients = depBridge.recipientDatabaseTable.allRecipients(tx: tx)
|
||
XCTAssertEqual(allRecipients.count, 1)
|
||
|
||
assert(
|
||
recipient: allRecipients.first!,
|
||
isBlocked: false,
|
||
isHidden: false,
|
||
isRegistered: false,
|
||
unregisteredAtTimestamp: 1713157772000,
|
||
isWhitelisted: true,
|
||
sdsTx: sdsTx,
|
||
tx: tx
|
||
)
|
||
}
|
||
}
|
||
}
|
||
|
||
final class MessageBackupDistributionListTest: MessageBackupIntegrationTestCase {
|
||
typealias ListValidationBlock = ((TSPrivateStoryThread) -> Void)
|
||
|
||
let storyStore = StoryStoreImpl()
|
||
let threadStore = ThreadStoreImpl()
|
||
|
||
func testDistributionList() async throws {
|
||
func skipTest() throws {
|
||
/// This test is failing because the `.binproto` hardcodes a
|
||
/// timestamp, and these assertions care about the time between
|
||
/// "now" and that timestamp, which is not stable.
|
||
///
|
||
/// Disabling for now to unblock CI – we can re-enable once we have
|
||
/// a plan for timestamp-agnosticism in Backup tests.
|
||
throw XCTSkip()
|
||
}
|
||
|
||
/// Can't do a direct `throw` here, because the compiler complains that
|
||
/// the code below won't be executed. Who's smarter now, compiler??
|
||
try skipTest()
|
||
|
||
try await runTest(backupName: "story-distribution-list") { sdsTx, tx in
|
||
let deletedStories = storyStore.getAllDeletedStories(tx: tx)
|
||
XCTAssertEqual(deletedStories.count, 2)
|
||
|
||
let validationBlocks: [UUID: ListValidationBlock] = [
|
||
UUID(TSPrivateStoryThread.myStoryUniqueId): { thread in
|
||
XCTAssertTrue(thread.allowsReplies)
|
||
},
|
||
UUID(data: Data(base64Encoded: "me/ptJ9tRnyCWu/eg9uP7Q==")!)!: { thread in
|
||
XCTAssertEqual(thread.name, "Mandalorians")
|
||
XCTAssertTrue(thread.allowsReplies)
|
||
XCTAssertEqual(thread.storyViewMode, .blockList)
|
||
XCTAssertEqual(thread.addresses.count, 2)
|
||
},
|
||
UUID(data: Data(base64Encoded: "ZYoHlxwxS8aBGSQJ1tL0sA==")!)!: { thread in
|
||
XCTAssertEqual(thread.name, "Hutts")
|
||
XCTAssertFalse(thread.allowsReplies)
|
||
// check member list
|
||
XCTAssertEqual(thread.storyViewMode, .explicit)
|
||
XCTAssertEqual(thread.addresses.count, 1)
|
||
}
|
||
]
|
||
|
||
try threadStore.enumerateStoryThreads(tx: tx) { thread, stop in
|
||
do {
|
||
let validationBlock = try XCTUnwrap(validationBlocks[UUID(thread.uniqueId)])
|
||
validationBlock(thread)
|
||
} catch {
|
||
XCTFail("Missing validation block")
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// MARK: -
|
||
|
||
private extension RecipientDatabaseTable {
|
||
func allRecipients(tx: any DBReadTransaction) -> [SignalRecipient] {
|
||
var result = [SignalRecipient]()
|
||
enumerateAll(tx: tx) { result.append($0) }
|
||
return result
|
||
}
|
||
}
|
||
|
||
// MARK: _
|
||
|
||
private extension UUID {
|
||
init(_ string: String) {
|
||
self.init(uuidString: string)!
|
||
}
|
||
}
|
||
|
||
private extension Aci {
|
||
/// Corresponds to base64 data `QHaZXgUxQEKp5B5np33zWA==`.
|
||
static let fixture: Aci = Aci("4076995E-0531-4042-A9E4-1E67A77DF358")
|
||
|
||
convenience init(_ string: String) {
|
||
self.init(fromUUID: UUID(string))
|
||
}
|
||
}
|
||
|
||
private extension Pni {
|
||
/// Corresponds to base64 data `JvwCorpYSn2wgZ2iOXFXCg==`.
|
||
static let fixture: Pni = Pni("26FC02A2-BA58-4A7D-B081-9DA23971570A")
|
||
|
||
convenience init(_ string: String) {
|
||
self.init(fromUUID: UUID(string))
|
||
}
|
||
}
|