From 83576e091da961bc32cebd7790c93d06008db3e2 Mon Sep 17 00:00:00 2001
From: Dariusz Rybicki <dariusz@elixxir.io>
Date: Fri, 9 Sep 2022 15:55:04 +0200
Subject: [PATCH] Add MessengerVerifyContact function

---
 .../Functions/MessengerVerifyContact.swift    |  78 ++++
 .../Messenger/Messenger.swift                 |   7 +-
 .../Messenger/MessengerEnvironment.swift      |   3 +
 .../MessengerVerifyContactTests.swift         | 349 ++++++++++++++++++
 4 files changed, 435 insertions(+), 2 deletions(-)
 create mode 100644 Sources/XXMessengerClient/Messenger/Functions/MessengerVerifyContact.swift
 create mode 100644 Tests/XXMessengerClientTests/Messenger/Functions/MessengerVerifyContactTests.swift

diff --git a/Sources/XXMessengerClient/Messenger/Functions/MessengerVerifyContact.swift b/Sources/XXMessengerClient/Messenger/Functions/MessengerVerifyContact.swift
new file mode 100644
index 00000000..7851dd1f
--- /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 cc48651c..e2d31f69 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 85e53d0b..9c483774 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 00000000..52fed6cb
--- /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)
+    }
+  }
+}
-- 
GitLab