// // Copyright (c) 2020 Open Whisper Systems. All rights reserved. // import Foundation import XCTest @testable import SignalServiceKit class UUIDBackfillTaskTest: SSKBaseTestSwift { private var dut: UUIDBackfillTask! = nil private var persistence: MockPersistence! = nil private var network: MockNetwork! = nil override func setUp() { super.setUp() // The shared signal service address cache will persist UUIDs as soon as they're created // This messes with some of the verification checks performed in this test suite SSKEnvironment.shared.signalServiceAddressCache.testing_disableCache = true persistence = MockPersistence() network = MockNetwork() dut = UUIDBackfillTask(persistence: persistence, network: network) dut.testing_shortBackoffInterval = true } override func tearDown() { super.tearDown() SSKEnvironment.shared.signalServiceAddressCache.testing_disableCache = false } // MARK: - Tests func testNoLegacyRecipients() { // Setup let didComplete = expectation(description: "Task Completed") configureMocks(expectingRegistrationFor: [], expectingUnregistrationFor: []) // Test dut.perform { didComplete.fulfill() } // Verify waitForExpectations(timeout: 3) persistence.verifySuccess() network.verify(requestCount: 0) } func testOneLegacyRecipient_Found() { // Setup let didComplete = expectation(description: "Task Completed") configureMocks(expectingRegistrationFor: ["+1234567890"], expectingUnregistrationFor: []) // Test dut.perform { didComplete.fulfill() } // Verify waitForExpectations(timeout: 3) persistence.verifySuccess() network.verify(requestCount: 1) } func testOneLegacyRecipient_NotFound() { // Setup let didComplete = expectation(description: "Task Completed") configureMocks(expectingRegistrationFor: [], expectingUnregistrationFor: ["+1234567890"]) // Test dut.perform { didComplete.fulfill() } // Verify waitForExpectations(timeout: 3) persistence.verifySuccess() network.verify(requestCount: 1) } func testManyLegacyRecipients_AllFound() { // Setup let didComplete = expectation(description: "Task Completed") let registrations = (1234567890..<1234567890+1000).map { "+\($0)" } configureMocks(expectingRegistrationFor: registrations, expectingUnregistrationFor: []) // Test dut.perform { didComplete.fulfill() } // Verify waitForExpectations(timeout: 3) persistence.verifySuccess() network.verify(requestCount: 1) } func testManyLegacyRecipients_SomeFound() { // Setup let didComplete = expectation(description: "Task Completed") let registrations = (1234567890..<1234567890+500).map { "+\($0)" } let unregistrations = (2234567890..<2234567890+500).map { "+\($0)" } configureMocks(expectingRegistrationFor: registrations, expectingUnregistrationFor: unregistrations) // Test dut.perform { didComplete.fulfill() } // Verify waitForExpectations(timeout: 3) persistence.verifySuccess() network.verify(requestCount: 1) } func testManyLegacyRecipients_NoneFound() { // Setup let didComplete = expectation(description: "Task Completed") let unregistrations = (1234567890..<1234567890+1000).map { "+\($0)" } configureMocks(expectingRegistrationFor: [], expectingUnregistrationFor: unregistrations) // Test dut.perform { didComplete.fulfill() } // Verify waitForExpectations(timeout: 3) persistence.verifySuccess() network.verify(requestCount: 1) } func testNetworkFailure() { // Setup let didComplete = expectation(description: "Task Completed") configureMocks(expectingRegistrationFor: ["+1234567890"], expectingUnregistrationFor: [], forcedFailures: [ NSError(domain: TSNetworkManagerErrorDomain, code: 0, userInfo: nil) ]) // Test dut.perform { didComplete.fulfill() } // Verify waitForExpectations(timeout: 3) persistence.verifySuccess() network.verify(requestCount: 2) } func testRepeatedNetworkFailures() { // Setup let didComplete = expectation(description: "Task Completed") configureMocks(expectingRegistrationFor: ["+1234567890"], expectingUnregistrationFor: [], forcedFailures: [ NSError(domain: TSNetworkManagerErrorDomain, code: 0, userInfo: nil), NSError(domain: TSNetworkManagerErrorDomain, code: 0, userInfo: nil), NSError(domain: TSNetworkManagerErrorDomain, code: 0, userInfo: nil), NSError(domain: TSNetworkManagerErrorDomain, code: 0, userInfo: nil), NSError(domain: TSNetworkManagerErrorDomain, code: 0, userInfo: nil), NSError(domain: TSNetworkManagerErrorDomain, code: 0, userInfo: nil), NSError(domain: TSNetworkManagerErrorDomain, code: 0, userInfo: nil) ]) // Test dut.perform { didComplete.fulfill() } // Verify waitForExpectations(timeout: 10) persistence.verifySuccess() network.verify(requestCount: 8) } func testUnknownFailures() { // Setup let didComplete = expectation(description: "Task Completed") configureMocks(expectingRegistrationFor: ["+1234567890"], expectingUnregistrationFor: [], forcedFailures: [ NSError(domain: "TestDomain", code: 1, userInfo: nil) ]) // Test dut.perform { didComplete.fulfill() } // Verify waitForExpectations(timeout: 3) persistence.verifySuccess() network.verify(requestCount: 2) } func testRepeatedUnknownFailures() { // Setup let didComplete = expectation(description: "Task Completed") configureMocks(expectingRegistrationFor: ["+1234567890"], expectingUnregistrationFor: [], forcedFailures: [ NSError(domain: "TestDomain", code: 1, userInfo: nil), NSError(domain: "TestDomain", code: 2, userInfo: nil), NSError(domain: "TestDomain", code: 3, userInfo: nil), NSError(domain: "TestDomain", code: 4, userInfo: nil), NSError(domain: "TestDomain", code: 5, userInfo: nil), NSError(domain: "TestDomain", code: 6, userInfo: nil), NSError(domain: "TestDomain", code: 7, userInfo: nil), NSError(domain: "TestDomain", code: 8, userInfo: nil), NSError(domain: "TestDomain", code: 9, userInfo: nil) ]) // Test dut.perform { didComplete.fulfill() } // Verify waitForExpectations(timeout: 10) persistence.verifyFailure() network.verify(requestCount: 5) } func testMixedFailures() { // Setup let didComplete = expectation(description: "Task Completed") configureMocks(expectingRegistrationFor: ["+1234567890"], expectingUnregistrationFor: [], forcedFailures: [ NSError(domain: "TestDomain", code: 1, userInfo: nil), NSError(domain: "TestDomain", code: 2, userInfo: nil), NSError(domain: TSNetworkManagerErrorDomain, code: 0, userInfo: nil), NSError(domain: TSNetworkManagerErrorDomain, code: 0, userInfo: nil), NSError(domain: TSNetworkManagerErrorDomain, code: 0, userInfo: nil), NSError(domain: TSNetworkManagerErrorDomain, code: 0, userInfo: nil), NSError(domain: TSNetworkManagerErrorDomain, code: 0, userInfo: nil) ]) // Test dut.perform { didComplete.fulfill() } // Verify waitForExpectations(timeout: 10) persistence.verifySuccess() network.verify(requestCount: 8) } } extension UUIDBackfillTaskTest { func configureMocks(expectingRegistrationFor registeredNumbers: [String], expectingUnregistrationFor unregisteredNumbers: [String], forcedFailures: [Error] = []) { // Verify no duplicate entries XCTAssertEqual(registeredNumbers.count + unregisteredNumbers.count, Set(registeredNumbers + unregisteredNumbers).count, "Invalid test configuration") let initialRecipients = (registeredNumbers + unregisteredNumbers).map { SignalRecipient(address: SignalServiceAddress(uuid: nil, phoneNumber: $0)) } let expectedRegistration = registeredNumbers.map { SignalServiceAddress(uuid: UUID(), phoneNumber: $0) } let expectedUnregistration = unregisteredNumbers.map { SignalServiceAddress(uuid: nil, phoneNumber: $0) } let finalCDSResult = expectedRegistration.map { CDSRegisteredContact(signalUuid: $0.uuid!, e164PhoneNumber: $0.phoneNumber!) } persistence.unknownRecipients = Set(initialRecipients) persistence.expectedRegistration = Set(expectedRegistration) persistence.expectedUnregistration = Set(expectedUnregistration) network.scheduledErrors = forcedFailures network.finalResult = Set(finalCDSResult) } func testBackoffInterval() { // Setup dut.testing_shortBackoffInterval = false // reset to normal backoff behavior // Test + Verify: first attempt has no delay dut.testing_attemptCount = 0 XCTAssertEqual(dut.testing_backoffInterval, .seconds(0)) // Test + Verify: next few attempts are briefly delayed (1..<4).forEach { dut.testing_attemptCount = $0 XCTAssertLessThan(dut.testing_backoffInterval, .seconds(1)) } // Test + Verify: later attempts are greatly delayed (10..<13).forEach { dut.testing_attemptCount = $0 XCTAssertGreaterThan(dut.testing_backoffInterval, .seconds(60)) } // Test + Verify: delays cap out at 15 minutes [20, 30, 50, 100, 500, 1000].forEach { dut.testing_attemptCount = $0 XCTAssertEqual(dut.testing_backoffInterval, .seconds(15 * 60)) } // Verify } } extension UUIDBackfillTaskTest { class MockPersistence: UUIDBackfillTask.PersistenceProvider { var unknownRecipients: Set = Set() var expectedRegistration: Set = Set() var expectedUnregistration: Set = Set() var registered: Set = Set() var unregistered: Set = Set() var fetchInvocations = 0 var registerInvocations = 0 override func fetchRegisteredRecipientsWithoutUUID() -> [SignalRecipient] { fetchInvocations += 1 return Array(unknownRecipients) } override func updateSignalRecipients(registering addressesToRegister: [SignalServiceAddress], unregistering addressesToUnregister: [SignalServiceAddress]) { registerInvocations += 1 unknownRecipients = unknownRecipients.filter { unknown in let wasRegistered = addressesToRegister .contains(where: { $0.phoneNumber == unknown.recipientPhoneNumber }) let wasUnregistered = addressesToUnregister .contains(where: { $0.phoneNumber == unknown.recipientPhoneNumber }) return (!wasRegistered && !wasUnregistered) } registered.formUnion(addressesToRegister) unregistered.formUnion(addressesToUnregister) } func verifySuccess() { XCTAssertEqual(registered, expectedRegistration) XCTAssertEqual(unregistered, expectedUnregistration) XCTAssertTrue(unknownRecipients.isEmpty) XCTAssertEqual(fetchInvocations, 1) if expectedRegistration.isEmpty && expectedUnregistration.isEmpty { XCTAssertEqual(registerInvocations, 0) } else { XCTAssertEqual(registerInvocations, 1) } } func verifyFailure() { XCTAssertEqual(registered.count, 0) XCTAssertEqual(unregistered.count, 0) XCTAssertEqual(fetchInvocations, 1) XCTAssertEqual(registerInvocations, 0) } } class MockNetwork: UUIDBackfillTask.NetworkProvider { var requestCount = 0 var scheduledErrors: [Error] = [] var finalResult: Set = Set() override func fetchServiceAddress(for phoneNumbers: [String], completion: @escaping (Set, Error?) -> Void) { requestCount += 1 if let error = scheduledErrors.first { completion(Set(), error) scheduledErrors.remove(at: 0) } else { let milliseconds = Int.random(in: 0...300) DispatchQueue.global().asyncAfter(deadline: .now() + .milliseconds(milliseconds)) { completion(self.finalResult, nil) } } } func verify(requestCount expected: Int) { XCTAssertEqual(requestCount, expected) } } } extension DispatchTimeInterval: Comparable { private var normalizedNanoseconds: Int64 { switch self { case let .seconds(val): return Int64(val) * Int64(NSEC_PER_SEC) case let .milliseconds(val): return Int64(val) * Int64(NSEC_PER_MSEC) case let .microseconds(val): return Int64(val) * Int64(NSEC_PER_USEC) case let .nanoseconds(val): return Int64(val) case .never: return Int64.max default: assertionFailure("welp~") return 0 } } public static func < (lhs: DispatchTimeInterval, rhs: DispatchTimeInterval) -> Bool { return lhs.normalizedNanoseconds < rhs.normalizedNanoseconds } }