diff --git a/Sources/XXMessengerClient/Messenger/Functions/MessengerVerifyContact.swift b/Sources/XXMessengerClient/Messenger/Functions/MessengerVerifyContact.swift new file mode 100644 index 0000000000000000000000000000000000000000..7851dd1f122204b83c9fc45c282c9ff1e4a71c37 --- /dev/null +++ b/Sources/XXMessengerClient/Messenger/Functions/MessengerVerifyContact.swift @@ -0,0 +1,78 @@ +import Foundation +import XCTestDynamicOverlay +import XXClient + +public struct MessengerVerifyContact { + public enum Error: Swift.Error, Equatable { + case notConnected + case notLoggedIn + } + + public var run: (Contact) throws -> Bool + + public func callAsFunction(_ contact: Contact) throws -> Bool { + try run(contact) + } +} + +extension MessengerVerifyContact { + public static func live(_ env: MessengerEnvironment) -> MessengerVerifyContact { + MessengerVerifyContact { contact in + guard let e2e = env.e2e() else { + throw Error.notConnected + } + guard let ud = env.ud() else { + throw Error.notLoggedIn + } + let facts = try contact.getFacts() + let verifiedContact: Contact? + if facts.isEmpty { + var lookupResult: Result<Contact, NSError>! + let semaphore = DispatchSemaphore(value: 0) + _ = try env.lookupUD( + e2eId: e2e.getId(), + udContact: try ud.getContact(), + lookupId: try contact.getId(), + singleRequestParamsJSON: env.getSingleUseParams(), + callback: .init { result in + lookupResult = result + semaphore.signal() + } + ) + semaphore.wait() + verifiedContact = try lookupResult.get() + } else { + var searchResult: Result<[Contact], NSError>! + let semaphore = DispatchSemaphore(value: 0) + _ = try env.searchUD( + e2eId: e2e.getId(), + udContact: try ud.getContact(), + facts: facts, + singleRequestParamsJSON: env.getSingleUseParams(), + callback: .init { result in + searchResult = result + semaphore.signal() + } + ) + semaphore.wait() + verifiedContact = try searchResult.get().first + } + + guard let verifiedContact = verifiedContact else { + return false + } + + return try e2e.verifyOwnership( + received: contact, + verified: verifiedContact, + e2eId: e2e.getId() + ) + } + } +} + +extension MessengerVerifyContact { + public static let unimplemented = MessengerVerifyContact( + run: XCTUnimplemented("\(Self.self)") + ) +} diff --git a/Sources/XXMessengerClient/Messenger/Messenger.swift b/Sources/XXMessengerClient/Messenger/Messenger.swift index cc48651c730cd7cce79aa7fda28265d4fa6ea5a1..e2d31f69350c83022d18d8b5dd3698eddff59255 100644 --- a/Sources/XXMessengerClient/Messenger/Messenger.swift +++ b/Sources/XXMessengerClient/Messenger/Messenger.swift @@ -21,6 +21,7 @@ public struct Messenger { public var destroy: MessengerDestroy public var searchUsers: MessengerSearchUsers public var registerForNotifications: MessengerRegisterForNotifications + public var verifyContact: MessengerVerifyContact } extension Messenger { @@ -45,7 +46,8 @@ extension Messenger { waitForNodes: .live(env), destroy: .live(env), searchUsers: .live(env), - registerForNotifications: .live(env) + registerForNotifications: .live(env), + verifyContact: .live(env) ) } } @@ -71,6 +73,7 @@ extension Messenger { waitForNodes: .unimplemented, destroy: .unimplemented, searchUsers: .unimplemented, - registerForNotifications: .unimplemented + registerForNotifications: .unimplemented, + verifyContact: .unimplemented ) } diff --git a/Sources/XXMessengerClient/Messenger/MessengerEnvironment.swift b/Sources/XXMessengerClient/Messenger/MessengerEnvironment.swift index 85e53d0b99599b70136dfb1a5c30144e4564613c..9c4837748fa35628fdfd87bdbcc0e746d745f77e 100644 --- a/Sources/XXMessengerClient/Messenger/MessengerEnvironment.swift +++ b/Sources/XXMessengerClient/Messenger/MessengerEnvironment.swift @@ -15,6 +15,7 @@ public struct MessengerEnvironment { public var isRegisteredWithUD: IsRegisteredWithUD public var loadCMix: LoadCMix public var login: Login + public var lookupUD: LookupUD public var ndfEnvironment: NDFEnvironment public var newCMix: NewCMix public var newOrLoadUd: NewOrLoadUd @@ -50,6 +51,7 @@ extension MessengerEnvironment { isRegisteredWithUD: .live, loadCMix: .live, login: .live, + lookupUD: .live, ndfEnvironment: .mainnet, newCMix: .live, newOrLoadUd: .live, @@ -80,6 +82,7 @@ extension MessengerEnvironment { isRegisteredWithUD: .unimplemented, loadCMix: .unimplemented, login: .unimplemented, + lookupUD: .unimplemented, ndfEnvironment: .unimplemented, newCMix: .unimplemented, newOrLoadUd: .unimplemented, diff --git a/Tests/XXMessengerClientTests/Messenger/Functions/MessengerVerifyContactTests.swift b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerVerifyContactTests.swift new file mode 100644 index 0000000000000000000000000000000000000000..52fed6cb730831a1cdaf6ad41d924533355e643c --- /dev/null +++ b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerVerifyContactTests.swift @@ -0,0 +1,349 @@ +import CustomDump +import XCTest +import XXClient +@testable import XXMessengerClient + +final class MessengerVerifyContactTests: XCTestCase { + func testVerifyWhenNotConnected() { + var env: MessengerEnvironment = .unimplemented + env.e2e.get = { nil } + let verify: MessengerVerifyContact = .live(env) + let contact = Contact.unimplemented("data".data(using: .utf8)!) + + XCTAssertThrowsError(try verify(contact)) { error in + XCTAssertNoDifference(error as? MessengerVerifyContact.Error, .notConnected) + } + } + + func testVerifyWhenNotLoggedIn() { + var env: MessengerEnvironment = .unimplemented + env.e2e.get = { .unimplemented } + env.ud.get = { nil } + let verify: MessengerVerifyContact = .live(env) + let contact = Contact.unimplemented("data".data(using: .utf8)!) + + XCTAssertThrowsError(try verify(contact)) { error in + XCTAssertNoDifference(error as? MessengerVerifyContact.Error, .notLoggedIn) + } + } + + func testVerifyContactWithoutFacts() throws { + struct LookupUDParams: Equatable { + var e2eId: Int + var udContact: Contact + var lookupId: Data + var singleRequestParamsJSON: Data + } + struct VerifyOwnershipParams: Equatable { + var received: Contact + var verified: Contact + var e2eId: Int + } + var didLookupUDWithParams: [LookupUDParams] = [] + var didVerifyOwnershipWithParams: [VerifyOwnershipParams] = [] + + var contact = Contact.unimplemented("contact-data".data(using: .utf8)!) + contact.getIdFromContact.run = { _ in "contact-id".data(using: .utf8)! } + contact.getFactsFromContact.run = { _ in [] } + + let lookedUpContact = Contact.unimplemented("looked-up-contact-data".data(using: .utf8)!) + + var env: MessengerEnvironment = .unimplemented + env.e2e.get = { + var e2e: E2E = .unimplemented + e2e.getId.run = { 123 } + e2e.verifyOwnership.run = { received, verified, e2eId in + didVerifyOwnershipWithParams.append(.init( + received: received, + verified: verified, + e2eId: e2eId + )) + return true + } + return e2e + } + env.ud.get = { + var ud: UserDiscovery = .unimplemented + ud.getContact.run = { .unimplemented("ud-contact-data".data(using: .utf8)!) } + return ud + } + env.getSingleUseParams.run = { "single-use-params".data(using: .utf8)! } + env.lookupUD.run = { e2eId, udContact, lookupId, singleRequestParamsJSON, callback in + didLookupUDWithParams.append(.init( + e2eId: e2eId, + udContact: udContact, + lookupId: lookupId, + singleRequestParamsJSON: singleRequestParamsJSON + )) + callback.handle(.success(lookedUpContact)) + return SingleUseSendReport(rounds: [], roundURL: "", ephId: 0, receptionId: Data()) + } + let verify: MessengerVerifyContact = .live(env) + + let result = try verify(contact) + + XCTAssertNoDifference(didLookupUDWithParams, [.init( + e2eId: 123, + udContact: .unimplemented("ud-contact-data".data(using: .utf8)!), + lookupId: "contact-id".data(using: .utf8)!, + singleRequestParamsJSON: "single-use-params".data(using: .utf8)! + )]) + + XCTAssertNoDifference(didVerifyOwnershipWithParams, [.init( + received: contact, + verified: lookedUpContact, + e2eId: 123 + )]) + + XCTAssertTrue(result) + } + + func testVerifyContactWithoutFactsLookupFailure() { + var contact = Contact.unimplemented("contact-data".data(using: .utf8)!) + contact.getFactsFromContact.run = { _ in [] } + contact.getIdFromContact.run = { _ in "contact-id".data(using: .utf8)! } + + let lookupFailure = NSError(domain: "test", code: 111) + + var env: MessengerEnvironment = .unimplemented + env.e2e.get = { + var e2e: E2E = .unimplemented + e2e.getId.run = { 123 } + return e2e + } + env.ud.get = { + var ud: UserDiscovery = .unimplemented + ud.getContact.run = { .unimplemented("ud-contact-data".data(using: .utf8)!) } + return ud + } + env.getSingleUseParams.run = { "single-use-params".data(using: .utf8)! } + env.lookupUD.run = { _, _, _, _, callback in + callback.handle(.failure(lookupFailure)) + return SingleUseSendReport(rounds: [], roundURL: "", ephId: 0, receptionId: Data()) + } + let verify: MessengerVerifyContact = .live(env) + + XCTAssertThrowsError(try verify(contact)) { error in + XCTAssertNoDifference(error as NSError, lookupFailure) + } + } + + func testVerifyContactWithFacts() throws { + struct SearchUDParams: Equatable { + var e2eId: Int + var udContact: Contact + var facts: [Fact] + var singleRequestParamsJSON: Data + } + struct VerifyOwnershipParams: Equatable { + var received: Contact + var verified: Contact + var e2eId: Int + } + var didSearchUDWithParams: [SearchUDParams] = [] + var didVerifyOwnershipWithParams: [VerifyOwnershipParams] = [] + + var contact = Contact.unimplemented("contact-data".data(using: .utf8)!) + contact.getIdFromContact.run = { _ in "contact-id".data(using: .utf8)! } + let contactFacts = [ + Fact(type: .username, value: "contact-username"), + Fact(type: .email, value: "contact-email"), + Fact(type: .phone, value: "contact-phone"), + ] + contact.getFactsFromContact.run = { _ in contactFacts } + + let foundContact = Contact.unimplemented("found-contact-data".data(using: .utf8)!) + + var env: MessengerEnvironment = .unimplemented + env.e2e.get = { + var e2e: E2E = .unimplemented + e2e.getId.run = { 123 } + e2e.verifyOwnership.run = { received, verified, e2eId in + didVerifyOwnershipWithParams.append(.init( + received: received, + verified: verified, + e2eId: e2eId + )) + return true + } + return e2e + } + env.ud.get = { + var ud: UserDiscovery = .unimplemented + ud.getContact.run = { .unimplemented("ud-contact-data".data(using: .utf8)!) } + return ud + } + env.getSingleUseParams.run = { "single-use-params".data(using: .utf8)! } + env.searchUD.run = { e2eId, udContact, facts, singleRequestParamsJSON, callback in + didSearchUDWithParams.append(.init( + e2eId: e2eId, + udContact: udContact, + facts: facts, + singleRequestParamsJSON: singleRequestParamsJSON + )) + callback.handle(.success([foundContact])) + return SingleUseSendReport(rounds: [], roundURL: "", ephId: 0, receptionId: Data()) + } + + let verify: MessengerVerifyContact = .live(env) + + let result = try verify(contact) + + XCTAssertNoDifference(didSearchUDWithParams, [.init( + e2eId: 123, + udContact: .unimplemented("ud-contact-data".data(using: .utf8)!), + facts: contactFacts, + singleRequestParamsJSON: "single-use-params".data(using: .utf8)! + )]) + XCTAssertNoDifference(didVerifyOwnershipWithParams, [.init( + received: contact, + verified: foundContact, + e2eId: 123 + )]) + + XCTAssertTrue(result) + } + + func testVerifyContactWithFactsEmptySearchResults() throws { + var contact = Contact.unimplemented("contact-data".data(using: .utf8)!) + contact.getIdFromContact.run = { _ in "contact-id".data(using: .utf8)! } + let contactFacts = [ + Fact(type: .username, value: "contact-username"), + Fact(type: .email, value: "contact-email"), + Fact(type: .phone, value: "contact-phone"), + ] + contact.getFactsFromContact.run = { _ in contactFacts } + + var env: MessengerEnvironment = .unimplemented + env.e2e.get = { + var e2e: E2E = .unimplemented + e2e.getId.run = { 123 } + return e2e + } + env.ud.get = { + var ud: UserDiscovery = .unimplemented + ud.getContact.run = { .unimplemented("ud-contact-data".data(using: .utf8)!) } + return ud + } + env.getSingleUseParams.run = { "single-use-params".data(using: .utf8)! } + env.searchUD.run = { e2eId, udContact, facts, singleRequestParamsJSON, callback in + callback.handle(.success([])) + return SingleUseSendReport(rounds: [], roundURL: "", ephId: 0, receptionId: Data()) + } + + let verify: MessengerVerifyContact = .live(env) + + let result = try verify(contact) + + XCTAssertFalse(result) + } + + func testVerifyContactWithFactsSearchFailure() { + var contact = Contact.unimplemented("contact-data".data(using: .utf8)!) + contact.getIdFromContact.run = { _ in "contact-id".data(using: .utf8)! } + let contactFacts = [ + Fact(type: .username, value: "contact-username"), + Fact(type: .email, value: "contact-email"), + Fact(type: .phone, value: "contact-phone"), + ] + contact.getFactsFromContact.run = { _ in contactFacts } + + let searchFailure = NSError(domain: "test", code: 111) + + var env: MessengerEnvironment = .unimplemented + env.e2e.get = { + var e2e: E2E = .unimplemented + e2e.getId.run = { 123 } + return e2e + } + env.ud.get = { + var ud: UserDiscovery = .unimplemented + ud.getContact.run = { .unimplemented("ud-contact-data".data(using: .utf8)!) } + return ud + } + env.getSingleUseParams.run = { "single-use-params".data(using: .utf8)! } + env.searchUD.run = { _, _, _, _, callback in + callback.handle(.failure(searchFailure)) + return SingleUseSendReport(rounds: [], roundURL: "", ephId: 0, receptionId: Data()) + } + + let verify: MessengerVerifyContact = .live(env) + + XCTAssertThrowsError(try verify(contact)) { error in + XCTAssertNoDifference(error as NSError, searchFailure) + } + } + func testVerifyContactWithFactsVerifyOwnershipReturnsFalse() throws { + var contact = Contact.unimplemented("contact-data".data(using: .utf8)!) + contact.getIdFromContact.run = { _ in "contact-id".data(using: .utf8)! } + let contactFacts = [ + Fact(type: .username, value: "contact-username"), + Fact(type: .email, value: "contact-email"), + Fact(type: .phone, value: "contact-phone"), + ] + contact.getFactsFromContact.run = { _ in contactFacts } + + var env: MessengerEnvironment = .unimplemented + env.e2e.get = { + var e2e: E2E = .unimplemented + e2e.getId.run = { 123 } + e2e.verifyOwnership.run = { _, _, _ in false } + return e2e + } + env.ud.get = { + var ud: UserDiscovery = .unimplemented + ud.getContact.run = { .unimplemented("ud-contact-data".data(using: .utf8)!) } + return ud + } + env.getSingleUseParams.run = { "single-use-params".data(using: .utf8)! } + env.searchUD.run = { _, _, _, _, callback in + callback.handle(.success([.unimplemented("found-contact-data".data(using: .utf8)!)])) + return SingleUseSendReport(rounds: [], roundURL: "", ephId: 0, receptionId: Data()) + } + + let verify: MessengerVerifyContact = .live(env) + + let result = try verify(contact) + + XCTAssertFalse(result) + } + + func testVerifyContactWithFactsVerifyOwnershipFailure() { + var contact = Contact.unimplemented("contact-data".data(using: .utf8)!) + contact.getIdFromContact.run = { _ in "contact-id".data(using: .utf8)! } + let contactFacts = [ + Fact(type: .username, value: "contact-username"), + Fact(type: .email, value: "contact-email"), + Fact(type: .phone, value: "contact-phone"), + ] + contact.getFactsFromContact.run = { _ in contactFacts } + + let verifyOwnershipFailure = NSError(domain: "test", code: 111) + + var env: MessengerEnvironment = .unimplemented + env.e2e.get = { + var e2e: E2E = .unimplemented + e2e.getId.run = { 123 } + e2e.verifyOwnership.run = { _, _, _ in + throw verifyOwnershipFailure + } + return e2e + } + env.ud.get = { + var ud: UserDiscovery = .unimplemented + ud.getContact.run = { .unimplemented("ud-contact-data".data(using: .utf8)!) } + return ud + } + env.getSingleUseParams.run = { "single-use-params".data(using: .utf8)! } + env.searchUD.run = { _, _, _, _, callback in + callback.handle(.success([.unimplemented("found-contact-data".data(using: .utf8)!)])) + return SingleUseSendReport(rounds: [], roundURL: "", ephId: 0, receptionId: Data()) + } + + let verify: MessengerVerifyContact = .live(env) + + XCTAssertThrowsError(try verify(contact)) { error in + XCTAssertNoDifference(error as NSError, verifyOwnershipFailure) + } + } +}