151 lines
11 KiB
Swift
151 lines
11 KiB
Swift
//
|
|
// Copyright 2023 Signal Messenger, LLC
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
//
|
|
|
|
import LibSignalClient
|
|
import XCTest
|
|
|
|
@testable import SignalServiceKit
|
|
|
|
class ProfileManagerTest: XCTestCase {
|
|
func testNormalizeRecipientInProfileWhitelist() {
|
|
let aci = Aci.constantForTesting("00000000-0000-4000-8000-000000000aaa")
|
|
let phoneNumber = E164("+16505550100")!
|
|
let pni = Pni.constantForTesting("PNI:00000000-0000-4000-8000-000000000bbb")
|
|
|
|
let serviceIdStore = KeyValueStore(collection: "serviceId")
|
|
let phoneNumberStore = KeyValueStore(collection: "phoneNumber")
|
|
|
|
let db = InMemoryDB()
|
|
|
|
func normalizeRecipient(_ recipient: SignalRecipient, tx: DBWriteTransaction) {
|
|
OWSProfileManager.swift_normalizeRecipientInProfileWhitelist(
|
|
recipient,
|
|
serviceIdStore: serviceIdStore,
|
|
phoneNumberStore: phoneNumberStore,
|
|
tx: tx
|
|
)
|
|
}
|
|
|
|
// Don't add any values unless one is already present.
|
|
db.write { tx in
|
|
normalizeRecipient(SignalRecipient(aci: aci, pni: pni, phoneNumber: phoneNumber), tx: tx)
|
|
XCTAssertFalse(serviceIdStore.hasValue(aci.serviceIdUppercaseString, transaction: tx))
|
|
XCTAssertFalse(phoneNumberStore.hasValue(phoneNumber.stringValue, transaction: tx))
|
|
XCTAssertFalse(serviceIdStore.hasValue(pni.serviceIdUppercaseString, transaction: tx))
|
|
}
|
|
|
|
// Move the PNI identifier to the phone number.
|
|
db.write { tx in
|
|
serviceIdStore.setBool(true, key: pni.serviceIdUppercaseString, transaction: tx)
|
|
normalizeRecipient(SignalRecipient(aci: nil, pni: pni, phoneNumber: phoneNumber), tx: tx)
|
|
XCTAssertFalse(serviceIdStore.hasValue(aci.serviceIdUppercaseString, transaction: tx))
|
|
XCTAssertTrue(phoneNumberStore.hasValue(phoneNumber.stringValue, transaction: tx))
|
|
XCTAssertFalse(serviceIdStore.hasValue(pni.serviceIdUppercaseString, transaction: tx))
|
|
}
|
|
|
|
// Clear lower priority identifiers when multiple are present.
|
|
db.write { tx in
|
|
serviceIdStore.setBool(true, key: aci.serviceIdUppercaseString, transaction: tx)
|
|
normalizeRecipient(SignalRecipient(aci: aci, pni: pni, phoneNumber: phoneNumber), tx: tx)
|
|
XCTAssertTrue(serviceIdStore.hasValue(aci.serviceIdUppercaseString, transaction: tx))
|
|
XCTAssertFalse(phoneNumberStore.hasValue(phoneNumber.stringValue, transaction: tx))
|
|
XCTAssertFalse(serviceIdStore.hasValue(pni.serviceIdUppercaseString, transaction: tx))
|
|
}
|
|
|
|
// Keep the highest priority identifier if it's already present.
|
|
db.write { tx in
|
|
normalizeRecipient(SignalRecipient(aci: aci, pni: pni, phoneNumber: phoneNumber), tx: tx)
|
|
XCTAssertTrue(serviceIdStore.hasValue(aci.serviceIdUppercaseString, transaction: tx))
|
|
XCTAssertFalse(phoneNumberStore.hasValue(phoneNumber.stringValue, transaction: tx))
|
|
XCTAssertFalse(serviceIdStore.hasValue(pni.serviceIdUppercaseString, transaction: tx))
|
|
}
|
|
}
|
|
|
|
func testEncodeDecodeProfileChanges() throws {
|
|
let testCases: [(PendingProfileUpdate, String)] = [
|
|
(
|
|
PendingProfileUpdate(
|
|
profileGivenName: .setTo(.init(truncating: "Alice")!),
|
|
profileFamilyName: .setTo(.init(truncating: "Johnson")!),
|
|
profileBio: .setTo("A short bio."),
|
|
profileBioEmoji: .setTo("💙"),
|
|
profileAvatarData: .setTo(Data(1...3)),
|
|
visibleBadgeIds: .setTo(["BOOST"]),
|
|
userProfileWriter: .registration
|
|
),
|
|
"YnBsaXN0MDDUAQIDBAUGBwpYJHZlcnNpb25ZJGFyY2hpdmVyVCR0b3BYJG9iamVjdHMSAAGGoF8QD05TS2V5ZWRBcmNoaXZlctEICVRyb290gAGvEA8LDCEiIyQlJicsLTM3ODxVJG51bGzaDQ4PEBESExQVFhcYGRobHB0eHyBfEA9wcm9maWxlQmlvRW1vamlacHJvZmlsZUJpb1YkY2xhc3NSaWRfEBh1bnNhdmVkUm90YXRlZFByb2ZpbGVLZXlfEBF1c2VyUHJvZmlsZVdyaXRlcl8QD3Zpc2libGVCYWRnZUlkc18QEXByb2ZpbGVBdmF0YXJEYXRhXxARcHJvZmlsZUZhbWlseU5hbWVfEBBwcm9maWxlR2l2ZW5OYW1lgAaABYAOgAKACxAEgAiAB4AEgANfECQwMDAwMDAwMC0wMDAwLTQwMDAtQTAwMC0wMDAwMDAwMDAwMDBVQWxpY2VXSm9obnNvblxBIHNob3J0IGJpby5i2D3cmUMBAgPSKA8pK1pOUy5vYmplY3RzoSqACYAKVUJPT1NU0i4vMDFaJGNsYXNzbmFtZVgkY2xhc3Nlc1dOU0FycmF5ojAyWE5TT2JqZWN00jQPNTZXa2V5RGF0YYAMgA1PECABAgMEBQYHCAkKCwwNDg8QERITFBUWFxgZGhscHR4fINIuLzk6XE9XU0FFUzI1NktleaI7MlxPV1NBRVMyNTZLZXnSLi89Pl8QJFNpZ25hbE1lc3NhZ2luZy5QZW5kaW5nUHJvZmlsZVVwZGF0ZaI/Ml8QJFNpZ25hbE1lc3NhZ2luZy5QZW5kaW5nUHJvZmlsZVVwZGF0ZQAIABEAGgAkACkAMgA3AEkATABRAFMAZQBrAIAAkgCdAKQApwDCANYA6AD8ARABIwElAScBKQErAS0BLwExATMBNQE3AV4BZAFsAXkBfgGCAYcBkgGUAZYBmAGeAaMBrgG3Ab8BwgHLAdAB2AHaAdwB/wIEAhECFAIhAiYCTQJQAAAAAAAAAgEAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAnc="
|
|
),
|
|
(
|
|
PendingProfileUpdate(
|
|
profileGivenName: .setTo(.init(truncating: "Alice")!),
|
|
profileFamilyName: .setTo(.init(truncating: "Johnson")!),
|
|
profileBio: .setTo("A short bio."),
|
|
profileBioEmoji: .setTo("💙"),
|
|
profileAvatarData: .setTo(Data(1...3)),
|
|
visibleBadgeIds: .setTo(["BOOST"]),
|
|
userProfileWriter: .registration
|
|
),
|
|
"YnBsaXN0MDDUAQIDBAUGBwpYJHZlcnNpb25ZJGFyY2hpdmVyVCR0b3BYJG9iamVjdHMSAAGGoF8QD05TS2V5ZWRBcmNoaXZlctEICVRyb290gAGsCwwfICEiIyQlKisxVSRudWxs2Q0ODxAREhMUFRYXGBkaGxwdHl8QD3Byb2ZpbGVCaW9FbW9qaVpwcm9maWxlQmlvViRjbGFzc1JpZF8QEXVzZXJQcm9maWxlV3JpdGVyXxAPdmlzaWJsZUJhZGdlSWRzXxARcHJvZmlsZUF2YXRhckRhdGFfEBFwcm9maWxlRmFtaWx5TmFtZV8QEHByb2ZpbGVHaXZlbk5hbWWABoAFgAuAAhAEgAiAB4AEgANfECQwMDAwMDAwMC0wMDAwLTQwMDAtQTAwMC0wMDAwMDAwMDAwMDBVQWxpY2VXSm9obnNvblxBIHNob3J0IGJpby5i2D3cmUMBAgPSJg8nKVpOUy5vYmplY3RzoSiACYAKVUJPT1NU0iwtLi9aJGNsYXNzbmFtZVgkY2xhc3Nlc1dOU0FycmF5oi4wWE5TT2JqZWN00iwtMjNfECRTaWduYWxNZXNzYWdpbmcuUGVuZGluZ1Byb2ZpbGVVcGRhdGWiNDBfECRTaWduYWxNZXNzYWdpbmcuUGVuZGluZ1Byb2ZpbGVVcGRhdGUACAARABoAJAApADIANwBJAEwAUQBTAGAAZgB5AIsAlgCdAKAAtADGANoA7gEBAQMBBQEHAQkBCwENAQ8BEQETAToBQAFIAVUBWgFeAWMBbgFwAXIBdAF6AX8BigGTAZsBngGnAawB0wHWAAAAAAAAAgEAAAAAAAAANQAAAAAAAAAAAAAAAAAAAf0="
|
|
),
|
|
(
|
|
PendingProfileUpdate(
|
|
profileGivenName: .noChange,
|
|
profileFamilyName: .noChange,
|
|
profileBio: .noChange,
|
|
profileBioEmoji: .noChange,
|
|
profileAvatarData: .noChange,
|
|
visibleBadgeIds: .noChange,
|
|
userProfileWriter: .localUser
|
|
),
|
|
"YnBsaXN0MDDUAQIDBAUGBwpYJHZlcnNpb25ZJGFyY2hpdmVyVCR0b3BYJG9iamVjdHMSAAGGoF8QD05TS2V5ZWRBcmNoaXZlctEICVRyb290gAGkCwwdHlUkbnVsbNgNDg8QERITFBUWFxcXFxsXViRjbGFzc1JpZF8QFnByb2ZpbGVCaW9FbW9qaUNoYW5nZWRfEBhwcm9maWxlRmFtaWx5TmFtZUNoYW5nZWRfEBFwcm9maWxlQmlvQ2hhbmdlZF8QF3Byb2ZpbGVHaXZlbk5hbWVDaGFuZ2VkXxARdXNlclByb2ZpbGVXcml0ZXJfEBhwcm9maWxlQXZhdGFyRGF0YUNoYW5nZWSAA4ACCAgICBAACF8QJDAwMDAwMDAwLTAwMDAtNDAwMC1BMDAwLTAwMDAwMDAwMDAwMNIfICEiWiRjbGFzc25hbWVYJGNsYXNzZXNfECRTaWduYWxNZXNzYWdpbmcuUGVuZGluZ1Byb2ZpbGVVcGRhdGWiIyRfECRTaWduYWxNZXNzYWdpbmcuUGVuZGluZ1Byb2ZpbGVVcGRhdGVYTlNPYmplY3QACAARABoAJAApADIANwBJAEwAUQBTAFgAXgBvAHYAeQCSAK0AwQDbAO8BCgEMAQ4BDwEQAREBEgEUARUBPAFBAUwBVQF8AX8BpgAAAAAAAAIBAAAAAAAAACUAAAAAAAAAAAAAAAAAAAGv"
|
|
),
|
|
(
|
|
PendingProfileUpdate(
|
|
profileGivenName: .noChange,
|
|
profileFamilyName: .noChange,
|
|
profileBio: .noChange,
|
|
profileBioEmoji: .noChange,
|
|
profileAvatarData: .noChangeButMustReupload,
|
|
visibleBadgeIds: .noChange,
|
|
userProfileWriter: .localUser
|
|
),
|
|
"YnBsaXN0MDDUAQIDBAUGBwpYJHZlcnNpb25ZJGFyY2hpdmVyVCR0b3BYJG9iamVjdHMSAAGGoF8QD05TS2V5ZWRBcmNoaXZlctEICVRyb290gAGkCwwfIFUkbnVsbNkNDg8QERITFBUWFxgYGBgYHR5WJGNsYXNzUmlkXxAYcHJvZmlsZUF2YXRhckRhdGFDaGFuZ2VkXxAYcHJvZmlsZUZhbWlseU5hbWVDaGFuZ2VkXxARcHJvZmlsZUJpb0NoYW5nZWRfEBZwcm9maWxlQmlvRW1vamlDaGFuZ2VkXxAXcHJvZmlsZUdpdmVuTmFtZUNoYW5nZWRfEBF1c2VyUHJvZmlsZVdyaXRlcl8QHXByb2ZpbGVBdmF0YXJEYXRhTXVzdFJldXBsb2FkgAOAAggICAgIEAAJXxAkMDAwMDAwMDAtMDAwMC00MDAwLUEwMDAtMDAwMDAwMDAwMDAw0iEiIyRaJGNsYXNzbmFtZVgkY2xhc3Nlc18QJVNpZ25hbFNlcnZpY2VLaXQuUGVuZGluZ1Byb2ZpbGVVcGRhdGWiJSZfECVTaWduYWxTZXJ2aWNlS2l0LlBlbmRpbmdQcm9maWxlVXBkYXRlWE5TT2JqZWN0AAgAEQAaACQAKQAyADcASQBMAFEAUwBYAF4AcQB4AHsAlgCxAMUA3gD4AQwBLAEuATABMQEyATMBNAE1ATcBOAFfAWQBbwF4AaABowHLAAAAAAAAAgEAAAAAAAAAJwAAAAAAAAAAAAAAAAAAAdQ="
|
|
),
|
|
]
|
|
let expectedId = UUID(uuidString: "00000000-0000-4000-A000-000000000000")!
|
|
for (expectedValue, encodedValue) in testCases {
|
|
// Test that it can be encoded/decoded.
|
|
let decodedEncodedValue = try Self.deserialize(data: Self.serialize(expectedValue))
|
|
XCTAssertEqual(decodedEncodedValue.id, expectedValue.id)
|
|
XCTAssertEqual(decodedEncodedValue.profileGivenName, expectedValue.profileGivenName)
|
|
XCTAssertEqual(decodedEncodedValue.profileFamilyName, expectedValue.profileFamilyName)
|
|
XCTAssertEqual(decodedEncodedValue.profileBio, expectedValue.profileBio)
|
|
XCTAssertEqual(decodedEncodedValue.profileBioEmoji, expectedValue.profileBioEmoji)
|
|
XCTAssertEqual(decodedEncodedValue.profileAvatarData, expectedValue.profileAvatarData)
|
|
XCTAssertEqual(decodedEncodedValue.visibleBadgeIds, expectedValue.visibleBadgeIds)
|
|
XCTAssertEqual(decodedEncodedValue.userProfileWriter, expectedValue.userProfileWriter)
|
|
|
|
// Test a stable decoding.
|
|
let decodedValue = try Self.deserialize(data: XCTUnwrap(Data(base64Encoded: encodedValue)))
|
|
XCTAssertEqual(decodedValue.id, expectedId)
|
|
XCTAssertEqual(decodedValue.profileGivenName, expectedValue.profileGivenName)
|
|
XCTAssertEqual(decodedValue.profileFamilyName, expectedValue.profileFamilyName)
|
|
XCTAssertEqual(decodedValue.profileBio, expectedValue.profileBio)
|
|
XCTAssertEqual(decodedValue.profileBioEmoji, expectedValue.profileBioEmoji)
|
|
XCTAssertEqual(decodedValue.profileAvatarData, expectedValue.profileAvatarData)
|
|
XCTAssertEqual(decodedValue.visibleBadgeIds, expectedValue.visibleBadgeIds)
|
|
XCTAssertEqual(decodedValue.userProfileWriter, expectedValue.userProfileWriter)
|
|
}
|
|
}
|
|
|
|
private static func serialize(_ pendingProfileUpdate: PendingProfileUpdate) throws -> Data {
|
|
try NSKeyedArchiver.archivedData(withRootObject: pendingProfileUpdate, requiringSecureCoding: false)
|
|
}
|
|
|
|
private static func deserialize(data: Data) throws -> PendingProfileUpdate {
|
|
NSKeyedUnarchiver.setClass(PendingProfileUpdate.self, forClassName: "SignalMessaging.PendingProfileUpdate")
|
|
return try XCTUnwrap(NSKeyedUnarchiver.unarchivedObject(ofClass: PendingProfileUpdate.self, from: data))
|
|
}
|
|
}
|