From 24beaf32de227c526b81378282cae3eedc0bdd8a Mon Sep 17 00:00:00 2001
From: Dariusz Rybicki <dariusz@elixxir.io>
Date: Thu, 8 Sep 2022 14:49:03 +0200
Subject: [PATCH 1/8] Add MessengerRegisterAuthCallbacks unimplemented
 placeholder

---
 .../Messenger/Functions/MessengerRegisterAuthCallbacks.swift    | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Sources/XXMessengerClient/Messenger/Functions/MessengerRegisterAuthCallbacks.swift b/Sources/XXMessengerClient/Messenger/Functions/MessengerRegisterAuthCallbacks.swift
index ad184957..3a8b0a5b 100644
--- a/Sources/XXMessengerClient/Messenger/Functions/MessengerRegisterAuthCallbacks.swift
+++ b/Sources/XXMessengerClient/Messenger/Functions/MessengerRegisterAuthCallbacks.swift
@@ -19,6 +19,6 @@ extension MessengerRegisterAuthCallbacks {
 
 extension MessengerRegisterAuthCallbacks {
   public static let unimplemented = MessengerRegisterAuthCallbacks(
-    run: XCTUnimplemented("\(Self.self)")
+    run: XCTUnimplemented("\(Self.self)", placeholder: Cancellable {})
   )
 }
-- 
GitLab


From db42327efcf81e520fa5946dac4e8a56958b6bdf Mon Sep 17 00:00:00 2001
From: Dariusz Rybicki <dariusz@elixxir.io>
Date: Thu, 8 Sep 2022 15:17:36 +0200
Subject: [PATCH 2/8] Handle auth requests in HomeFeature

---
 .../AppFeature/AppEnvironment+Live.swift      |   1 +
 .../Sources/HomeFeature/HomeFeature.swift     |  68 +++++++++
 .../HomeFeatureTests/HomeFeatureTests.swift   | 133 ++++++++++++++++++
 3 files changed, 202 insertions(+)

diff --git a/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift b/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift
index 61bfba1f..4092af82 100644
--- a/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift
+++ b/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift
@@ -55,6 +55,7 @@ extension AppEnvironment {
           dbManager: dbManager,
           mainQueue: mainQueue,
           bgQueue: bgQueue,
+          now: Date.init,
           register: {
             RegisterEnvironment(
               messenger: messenger,
diff --git a/Examples/xx-messenger/Sources/HomeFeature/HomeFeature.swift b/Examples/xx-messenger/Sources/HomeFeature/HomeFeature.swift
index faa5757c..985ef329 100644
--- a/Examples/xx-messenger/Sources/HomeFeature/HomeFeature.swift
+++ b/Examples/xx-messenger/Sources/HomeFeature/HomeFeature.swift
@@ -6,8 +6,10 @@ import ContactsFeature
 import Foundation
 import RegisterFeature
 import UserSearchFeature
+import XCTestDynamicOverlay
 import XXClient
 import XXMessengerClient
+import XXModels
 
 public struct HomeState: Equatable {
   public init(
@@ -47,6 +49,12 @@ public enum HomeAction: Equatable {
     case failure(NSError)
   }
 
+  public enum AuthCallbacks: Equatable {
+    case register
+    case unregister
+    case handle(XXClient.AuthCallbacks.Callback)
+  }
+
   public enum NetworkMonitor: Equatable {
     case start
     case stop
@@ -62,6 +70,7 @@ public enum HomeAction: Equatable {
   }
 
   case messenger(Messenger)
+  case authCallbacks(AuthCallbacks)
   case networkMonitor(NetworkMonitor)
   case deleteAccount(DeleteAccount)
   case didDismissAlert
@@ -81,6 +90,7 @@ public struct HomeEnvironment {
     dbManager: DBManager,
     mainQueue: AnySchedulerOf<DispatchQueue>,
     bgQueue: AnySchedulerOf<DispatchQueue>,
+    now: @escaping () -> Date,
     register: @escaping () -> RegisterEnvironment,
     contacts: @escaping () -> ContactsEnvironment,
     userSearch: @escaping () -> UserSearchEnvironment
@@ -89,6 +99,7 @@ public struct HomeEnvironment {
     self.dbManager = dbManager
     self.mainQueue = mainQueue
     self.bgQueue = bgQueue
+    self.now = now
     self.register = register
     self.contacts = contacts
     self.userSearch = userSearch
@@ -98,6 +109,7 @@ public struct HomeEnvironment {
   public var dbManager: DBManager
   public var mainQueue: AnySchedulerOf<DispatchQueue>
   public var bgQueue: AnySchedulerOf<DispatchQueue>
+  public var now: () -> Date
   public var register: () -> RegisterEnvironment
   public var contacts: () -> ContactsEnvironment
   public var userSearch: () -> UserSearchEnvironment
@@ -109,6 +121,7 @@ extension HomeEnvironment {
     dbManager: .unimplemented,
     mainQueue: .unimplemented,
     bgQueue: .unimplemented,
+    now: XCTUnimplemented("\(Self.self).now", placeholder: Date()),
     register: { .unimplemented },
     contacts: { .unimplemented },
     userSearch: { .unimplemented }
@@ -119,10 +132,12 @@ public let homeReducer = Reducer<HomeState, HomeAction, HomeEnvironment>
 { state, action, env in
   enum NetworkHealthEffectId {}
   enum NetworkNodesEffectId {}
+  enum AuthCallbacksEffectId {}
 
   switch action {
   case .messenger(.start):
     return .merge(
+      Effect(value: .authCallbacks(.register)),
       Effect(value: .networkMonitor(.stop)),
       Effect.result {
         do {
@@ -160,6 +175,59 @@ public let homeReducer = Reducer<HomeState, HomeAction, HomeEnvironment>
     state.failure = error.localizedDescription
     return .none
 
+  case .authCallbacks(.register):
+    return Effect.run { subscriber in
+      let handler = AuthCallbacks { callback in
+        subscriber.send(.authCallbacks(.handle(callback)))
+      }
+      let cancellable = env.messenger.registerAuthCallbacks(handler)
+      return AnyCancellable { cancellable.cancel() }
+    }
+    .subscribe(on: env.bgQueue)
+    .receive(on: env.mainQueue)
+    .eraseToEffect()
+    .cancellable(id: AuthCallbacksEffectId.self, cancelInFlight: true)
+
+  case .authCallbacks(.unregister):
+    return .cancel(id: AuthCallbacksEffectId.self)
+
+  case .authCallbacks(.handle(.request(let contact, _, _, _))):
+    return .fireAndForget {
+      let db = try env.dbManager.getDB()
+      let contactId = try contact.getId()
+      guard try db.fetchContacts(.init(id: [contactId])).isEmpty else {
+        return
+      }
+      var dbContact = XXModels.Contact(id: contactId)
+      dbContact.marshaled = contact.data
+      dbContact.username = try contact.getFact(.username)?.value
+      dbContact.email = try contact.getFact(.email)?.value
+      dbContact.phone = try contact.getFact(.phone)?.value
+      dbContact.authStatus = .verificationInProgress
+      dbContact.createdAt = env.now()
+      dbContact = try db.saveContact(dbContact)
+    }
+    .subscribe(on: env.bgQueue)
+    .receive(on: env.mainQueue)
+    .eraseToEffect()
+
+  case .authCallbacks(.handle(.confirm(let contact, _, _, _))):
+    return .fireAndForget {
+      let db = try env.dbManager.getDB()
+      let contactId = try contact.getId()
+      guard var dbContact = try db.fetchContacts(.init(id: [contactId])).first else {
+        return
+      }
+      dbContact.authStatus = .friend
+      dbContact = try db.saveContact(dbContact)
+    }
+    .subscribe(on: env.bgQueue)
+    .receive(on: env.mainQueue)
+    .eraseToEffect()
+
+  case .authCallbacks(.handle(.reset(let contact, _, _, _))):
+    return .none
+
   case .networkMonitor(.start):
     return .merge(
       Effect.run { subscriber in
diff --git a/Examples/xx-messenger/Tests/HomeFeatureTests/HomeFeatureTests.swift b/Examples/xx-messenger/Tests/HomeFeatureTests/HomeFeatureTests.swift
index 89aef4c9..40cd5a2a 100644
--- a/Examples/xx-messenger/Tests/HomeFeatureTests/HomeFeatureTests.swift
+++ b/Examples/xx-messenger/Tests/HomeFeatureTests/HomeFeatureTests.swift
@@ -21,6 +21,7 @@ final class HomeFeatureTests: XCTestCase {
 
     store.environment.bgQueue = .immediate
     store.environment.mainQueue = .immediate
+    store.environment.messenger.registerAuthCallbacks.run = { _ in Cancellable {} }
     store.environment.messenger.start.run = { messengerDidStartWithTimeout.append($0) }
     store.environment.messenger.isConnected.run = { false }
     store.environment.messenger.connect.run = { messengerDidConnect += 1 }
@@ -32,10 +33,13 @@ final class HomeFeatureTests: XCTestCase {
     XCTAssertNoDifference(messengerDidStartWithTimeout, [30_000])
     XCTAssertNoDifference(messengerDidConnect, 1)
 
+    store.receive(.authCallbacks(.register))
     store.receive(.networkMonitor(.stop))
     store.receive(.messenger(.didStartUnregistered)) {
       $0.register = RegisterState()
     }
+
+    store.send(.authCallbacks(.unregister))
   }
 
   func testMessengerStartRegistered() {
@@ -51,6 +55,7 @@ final class HomeFeatureTests: XCTestCase {
 
     store.environment.bgQueue = .immediate
     store.environment.mainQueue = .immediate
+    store.environment.messenger.registerAuthCallbacks.run = { _ in Cancellable {} }
     store.environment.messenger.start.run = { messengerDidStartWithTimeout.append($0) }
     store.environment.messenger.isConnected.run = { false }
     store.environment.messenger.connect.run = { messengerDidConnect += 1 }
@@ -73,11 +78,14 @@ final class HomeFeatureTests: XCTestCase {
     XCTAssertNoDifference(messengerDidConnect, 1)
     XCTAssertNoDifference(messengerDidLogIn, 1)
 
+    store.receive(.authCallbacks(.register))
     store.receive(.networkMonitor(.stop))
     store.receive(.messenger(.didStartRegistered))
     store.receive(.networkMonitor(.start))
 
     store.send(.networkMonitor(.stop))
+
+    store.send(.authCallbacks(.unregister))
   }
 
   func testRegisterFinished() {
@@ -94,6 +102,7 @@ final class HomeFeatureTests: XCTestCase {
 
     store.environment.bgQueue = .immediate
     store.environment.mainQueue = .immediate
+    store.environment.messenger.registerAuthCallbacks.run = { _ in Cancellable {} }
     store.environment.messenger.start.run = { messengerDidStartWithTimeout.append($0) }
     store.environment.messenger.isConnected.run = { true }
     store.environment.messenger.isLoggedIn.run = { false }
@@ -118,11 +127,14 @@ final class HomeFeatureTests: XCTestCase {
     XCTAssertNoDifference(messengerDidStartWithTimeout, [30_000])
     XCTAssertNoDifference(messengerDidLogIn, 1)
 
+    store.receive(.authCallbacks(.register))
     store.receive(.networkMonitor(.stop))
     store.receive(.messenger(.didStartRegistered))
     store.receive(.networkMonitor(.start))
 
     store.send(.networkMonitor(.stop))
+
+    store.send(.authCallbacks(.unregister))
   }
 
   func testMessengerStartFailure() {
@@ -137,14 +149,18 @@ final class HomeFeatureTests: XCTestCase {
 
     store.environment.bgQueue = .immediate
     store.environment.mainQueue = .immediate
+    store.environment.messenger.registerAuthCallbacks.run = { _ in Cancellable {} }
     store.environment.messenger.start.run = { _ in throw error }
 
     store.send(.messenger(.start))
 
+    store.receive(.authCallbacks(.register))
     store.receive(.networkMonitor(.stop))
     store.receive(.messenger(.failure(error as NSError))) {
       $0.failure = error.localizedDescription
     }
+
+    store.send(.authCallbacks(.unregister))
   }
 
   func testMessengerStartConnectFailure() {
@@ -159,16 +175,20 @@ final class HomeFeatureTests: XCTestCase {
 
     store.environment.bgQueue = .immediate
     store.environment.mainQueue = .immediate
+    store.environment.messenger.registerAuthCallbacks.run = { _ in Cancellable {} }
     store.environment.messenger.start.run = { _ in }
     store.environment.messenger.isConnected.run = { false }
     store.environment.messenger.connect.run = { throw error }
 
     store.send(.messenger(.start))
 
+    store.receive(.authCallbacks(.register))
     store.receive(.networkMonitor(.stop))
     store.receive(.messenger(.failure(error as NSError))) {
       $0.failure = error.localizedDescription
     }
+
+    store.send(.authCallbacks(.unregister))
   }
 
   func testMessengerStartIsRegisteredFailure() {
@@ -183,6 +203,7 @@ final class HomeFeatureTests: XCTestCase {
 
     store.environment.bgQueue = .immediate
     store.environment.mainQueue = .immediate
+    store.environment.messenger.registerAuthCallbacks.run = { _ in Cancellable {} }
     store.environment.messenger.start.run = { _ in }
     store.environment.messenger.isConnected.run = { true }
     store.environment.messenger.isLoggedIn.run = { false }
@@ -190,10 +211,13 @@ final class HomeFeatureTests: XCTestCase {
 
     store.send(.messenger(.start))
 
+    store.receive(.authCallbacks(.register))
     store.receive(.networkMonitor(.stop))
     store.receive(.messenger(.failure(error as NSError))) {
       $0.failure = error.localizedDescription
     }
+
+    store.send(.authCallbacks(.unregister))
   }
 
   func testMessengerStartLogInFailure() {
@@ -208,6 +232,7 @@ final class HomeFeatureTests: XCTestCase {
 
     store.environment.bgQueue = .immediate
     store.environment.mainQueue = .immediate
+    store.environment.messenger.registerAuthCallbacks.run = { _ in Cancellable {} }
     store.environment.messenger.start.run = { _ in }
     store.environment.messenger.isConnected.run = { true }
     store.environment.messenger.isLoggedIn.run = { false }
@@ -216,10 +241,13 @@ final class HomeFeatureTests: XCTestCase {
 
     store.send(.messenger(.start))
 
+    store.receive(.authCallbacks(.register))
     store.receive(.networkMonitor(.stop))
     store.receive(.messenger(.failure(error as NSError))) {
       $0.failure = error.localizedDescription
     }
+
+    store.send(.authCallbacks(.unregister))
   }
 
   func testNetworkMonitorStart() {
@@ -491,4 +519,109 @@ final class HomeFeatureTests: XCTestCase {
       $0.contacts = nil
     }
   }
+
+  func testAuthCallbacks() {
+    let store = TestStore(
+      initialState: HomeState(),
+      reducer: homeReducer,
+      environment: .unimplemented
+    )
+
+    let now = Date()
+    var didRegisterCallbacks: [AuthCallbacks] = []
+    var didCancelAuthCallbacks = 0
+    var dbContact: XXModels.Contact?
+    var dbDidSaveContact: [XXModels.Contact] = []
+
+    store.environment.mainQueue = .immediate
+    store.environment.bgQueue = .immediate
+    store.environment.now = { now }
+    store.environment.messenger.registerAuthCallbacks.run = { callbacks in
+      didRegisterCallbacks.append(callbacks)
+      return Cancellable { didCancelAuthCallbacks += 1 }
+    }
+    store.environment.dbManager.getDB.run = {
+      var db: Database = .failing
+      db.fetchContacts.run = { _ in [dbContact].compactMap { $0 } }
+      db.saveContact.run = { contact in
+        dbDidSaveContact.append(contact)
+        return contact
+      }
+      return db
+    }
+
+    store.send(.authCallbacks(.register))
+
+    XCTAssertNoDifference(didRegisterCallbacks.count, 1)
+
+    var contact = XXClient.Contact.unimplemented("data".data(using: .utf8)!)
+    contact.getIdFromContact.run = { _ in "id".data(using: .utf8)! }
+    contact.getFactsFromContact.run = { _ in
+      [
+        Fact(type: .username, value: "username"),
+        Fact(type: .email, value: "email"),
+        Fact(type: .phone, value: "phone"),
+      ]
+    }
+
+    dbContact = nil
+    dbDidSaveContact = []
+    didRegisterCallbacks.first?.handle(
+      .request(contact: contact, receptionId: Data(), ephemeralId: 0, roundId: 0)
+    )
+
+    store.receive(.authCallbacks(.handle(
+      .request(contact: contact, receptionId: Data(), ephemeralId: 0, roundId: 0)
+    )))
+    XCTAssertNoDifference(dbDidSaveContact, [.init(
+      id: "id".data(using: .utf8)!,
+      marshaled: "data".data(using: .utf8)!,
+      username: "username",
+      email: "email",
+      phone: "phone",
+      authStatus: .verificationInProgress,
+      createdAt: now
+    )])
+
+    dbContact = .init(id: "id".data(using: .utf8)!)
+    dbDidSaveContact = []
+    didRegisterCallbacks.first?.handle(
+      .request(contact: contact, receptionId: Data(), ephemeralId: 0, roundId: 0)
+    )
+
+    store.receive(.authCallbacks(.handle(
+      .request(contact: contact, receptionId: Data(), ephemeralId: 0, roundId: 0)
+    )))
+    XCTAssertNoDifference(dbDidSaveContact, [])
+
+    dbContact = .init(id: "id".data(using: .utf8)!)
+    dbDidSaveContact = []
+    didRegisterCallbacks.first?.handle(
+      .confirm(contact: contact, receptionId: Data(), ephemeralId: 0, roundId: 0)
+    )
+
+    store.receive(.authCallbacks(.handle(
+      .confirm(contact: contact, receptionId: Data(), ephemeralId: 0, roundId: 0)
+    )))
+    XCTAssertNoDifference(dbDidSaveContact, [.init(
+      id: "id".data(using: .utf8)!,
+      authStatus: .friend,
+      createdAt: dbContact!.createdAt
+    )])
+
+    dbContact = nil
+    dbDidSaveContact = []
+    didRegisterCallbacks.first?.handle(
+      .reset(contact: contact, receptionId: Data(), ephemeralId: 0, roundId: 0)
+    )
+
+    store.receive(.authCallbacks(.handle(
+      .reset(contact: contact, receptionId: Data(), ephemeralId: 0, roundId: 0)
+    )))
+    XCTAssertNoDifference(dbDidSaveContact, [])
+
+    store.send(.authCallbacks(.unregister))
+
+    XCTAssertNoDifference(didCancelAuthCallbacks, 1)
+  }
 }
-- 
GitLab


From 1b94869c21e941e57ba634fdfb715535e9ac6644 Mon Sep 17 00:00:00 2001
From: Dariusz Rybicki <dariusz@elixxir.io>
Date: Fri, 9 Sep 2022 12:35:29 +0200
Subject: [PATCH 3/8] Add AuthCallbackHandler

---
 Examples/xx-messenger/Package.swift           |   1 +
 .../AuthCallbackHandler.swift                 |  49 ++++++++
 .../AuthCallbackHandlerConfirm.swift          |  31 +++++
 .../AuthCallbackHandlerRequest.swift          |  40 ++++++
 .../AuthCallbackHandlerReset.swift            |  31 +++++
 .../AuthCallbackHandlerConfirmTests.swift     |  54 +++++++++
 .../AuthCallbackHandlerRequestTests.swift     |  67 ++++++++++
 .../AuthCallbackHandlerResetTests.swift       |  54 +++++++++
 .../AuthCallbackHandlerTests.swift            | 114 ++++++++++++++++++
 9 files changed, 441 insertions(+)
 create mode 100644 Examples/xx-messenger/Sources/AppCore/AuthCallbackHandler/AuthCallbackHandler.swift
 create mode 100644 Examples/xx-messenger/Sources/AppCore/AuthCallbackHandler/AuthCallbackHandlerConfirm.swift
 create mode 100644 Examples/xx-messenger/Sources/AppCore/AuthCallbackHandler/AuthCallbackHandlerRequest.swift
 create mode 100644 Examples/xx-messenger/Sources/AppCore/AuthCallbackHandler/AuthCallbackHandlerReset.swift
 create mode 100644 Examples/xx-messenger/Tests/AppCoreTests/AuthCallbackHandler/AuthCallbackHandlerConfirmTests.swift
 create mode 100644 Examples/xx-messenger/Tests/AppCoreTests/AuthCallbackHandler/AuthCallbackHandlerRequestTests.swift
 create mode 100644 Examples/xx-messenger/Tests/AppCoreTests/AuthCallbackHandler/AuthCallbackHandlerResetTests.swift
 create mode 100644 Examples/xx-messenger/Tests/AppCoreTests/AuthCallbackHandler/AuthCallbackHandlerTests.swift

diff --git a/Examples/xx-messenger/Package.swift b/Examples/xx-messenger/Package.swift
index 70066bd2..57081f3e 100644
--- a/Examples/xx-messenger/Package.swift
+++ b/Examples/xx-messenger/Package.swift
@@ -57,6 +57,7 @@ let package = Package(
         .product(name: "XCTestDynamicOverlay", package: "xctest-dynamic-overlay"),
         .product(name: "XXClient", package: "elixxir-dapps-sdk-swift"),
         .product(name: "XXDatabase", package: "client-ios-db"),
+        .product(name: "XXMessengerClient", package: "elixxir-dapps-sdk-swift"),
         .product(name: "XXModels", package: "client-ios-db"),
       ],
       swiftSettings: swiftSettings
diff --git a/Examples/xx-messenger/Sources/AppCore/AuthCallbackHandler/AuthCallbackHandler.swift b/Examples/xx-messenger/Sources/AppCore/AuthCallbackHandler/AuthCallbackHandler.swift
new file mode 100644
index 00000000..c6e03cbe
--- /dev/null
+++ b/Examples/xx-messenger/Sources/AppCore/AuthCallbackHandler/AuthCallbackHandler.swift
@@ -0,0 +1,49 @@
+import Foundation
+import XCTestDynamicOverlay
+import XXClient
+import XXMessengerClient
+import XXModels
+
+public struct AuthCallbackHandler {
+  public typealias OnError = (Error) -> Void
+
+  public var run: (@escaping OnError) -> Cancellable
+
+  public func callAsFunction(onError: @escaping OnError) -> Cancellable {
+    run(onError)
+  }
+}
+
+extension AuthCallbackHandler {
+  public static func live(
+    messenger: Messenger,
+    handleRequest: AuthCallbackHandlerRequest,
+    handleConfirm: AuthCallbackHandlerConfirm,
+    handleReset: AuthCallbackHandlerReset
+  ) -> AuthCallbackHandler {
+    AuthCallbackHandler { onError in
+      messenger.registerAuthCallbacks(.init { callback in
+        do {
+          switch callback {
+          case .request(let contact, _, _, _):
+            try handleRequest(contact)
+
+          case .confirm(let contact, _, _, _):
+            try handleConfirm(contact)
+
+          case .reset(let contact, _, _, _):
+            try handleReset(contact)
+          }
+        } catch {
+          onError(error)
+        }
+      })
+    }
+  }
+}
+
+extension AuthCallbackHandler {
+  public static let unimplemented = AuthCallbackHandler(
+    run: XCTUnimplemented("\(Self.self)", placeholder: Cancellable {})
+  )
+}
diff --git a/Examples/xx-messenger/Sources/AppCore/AuthCallbackHandler/AuthCallbackHandlerConfirm.swift b/Examples/xx-messenger/Sources/AppCore/AuthCallbackHandler/AuthCallbackHandlerConfirm.swift
new file mode 100644
index 00000000..2aa6787f
--- /dev/null
+++ b/Examples/xx-messenger/Sources/AppCore/AuthCallbackHandler/AuthCallbackHandlerConfirm.swift
@@ -0,0 +1,31 @@
+import Foundation
+import XCTestDynamicOverlay
+import XXClient
+import XXModels
+
+public struct AuthCallbackHandlerConfirm {
+  public var run: (XXClient.Contact) throws -> Void
+
+  public func callAsFunction(_ contact: XXClient.Contact) throws {
+    try run(contact)
+  }
+}
+
+extension AuthCallbackHandlerConfirm {
+  public static func live(db: DBManagerGetDB) -> AuthCallbackHandlerConfirm {
+    AuthCallbackHandlerConfirm { xxContact in
+      let id = try xxContact.getId()
+      guard var dbContact = try db().fetchContacts(.init(id: [id])).first else {
+        return
+      }
+      dbContact.authStatus = .friend
+      dbContact = try db().saveContact(dbContact)
+    }
+  }
+}
+
+extension AuthCallbackHandlerConfirm {
+  public static let unimplemented = AuthCallbackHandlerConfirm(
+    run: XCTUnimplemented("\(Self.self)")
+  )
+}
diff --git a/Examples/xx-messenger/Sources/AppCore/AuthCallbackHandler/AuthCallbackHandlerRequest.swift b/Examples/xx-messenger/Sources/AppCore/AuthCallbackHandler/AuthCallbackHandlerRequest.swift
new file mode 100644
index 00000000..1ec23923
--- /dev/null
+++ b/Examples/xx-messenger/Sources/AppCore/AuthCallbackHandler/AuthCallbackHandlerRequest.swift
@@ -0,0 +1,40 @@
+import Foundation
+import XCTestDynamicOverlay
+import XXClient
+import XXModels
+
+public struct AuthCallbackHandlerRequest {
+  public var run: (XXClient.Contact) throws -> Void
+
+  public func callAsFunction(_ contact: XXClient.Contact) throws {
+    try run(contact)
+  }
+}
+
+extension AuthCallbackHandlerRequest {
+  public static func live(
+    db: DBManagerGetDB,
+    now: @escaping () -> Date
+  ) -> AuthCallbackHandlerRequest {
+    AuthCallbackHandlerRequest { xxContact in
+      let id = try xxContact.getId()
+      guard try db().fetchContacts(.init(id: [id])).isEmpty else {
+        return
+      }
+      var dbContact = XXModels.Contact(id: id)
+      dbContact.marshaled = xxContact.data
+      dbContact.username = try xxContact.getFact(.username)?.value
+      dbContact.email = try xxContact.getFact(.email)?.value
+      dbContact.phone = try xxContact.getFact(.phone)?.value
+      dbContact.authStatus = .verificationInProgress
+      dbContact.createdAt = now()
+      dbContact = try db().saveContact(dbContact)
+    }
+  }
+}
+
+extension AuthCallbackHandlerRequest {
+  public static let unimplemented = AuthCallbackHandlerRequest(
+    run: XCTUnimplemented("\(Self.self)")
+  )
+}
diff --git a/Examples/xx-messenger/Sources/AppCore/AuthCallbackHandler/AuthCallbackHandlerReset.swift b/Examples/xx-messenger/Sources/AppCore/AuthCallbackHandler/AuthCallbackHandlerReset.swift
new file mode 100644
index 00000000..729fe13e
--- /dev/null
+++ b/Examples/xx-messenger/Sources/AppCore/AuthCallbackHandler/AuthCallbackHandlerReset.swift
@@ -0,0 +1,31 @@
+import Foundation
+import XCTestDynamicOverlay
+import XXClient
+import XXModels
+
+public struct AuthCallbackHandlerReset {
+  public var run: (XXClient.Contact) throws -> Void
+
+  public func callAsFunction(_ contact: XXClient.Contact) throws {
+    try run(contact)
+  }
+}
+
+extension AuthCallbackHandlerReset {
+  public static func live(db: DBManagerGetDB) -> AuthCallbackHandlerReset {
+    AuthCallbackHandlerReset { xxContact in
+      let id = try xxContact.getId()
+      guard var dbContact = try db().fetchContacts(.init(id: [id])).first else {
+        return
+      }
+      dbContact.authStatus = .stranger
+      dbContact = try db().saveContact(dbContact)
+    }
+  }
+}
+
+extension AuthCallbackHandlerReset {
+  public static let unimplemented = AuthCallbackHandlerReset(
+    run: XCTUnimplemented("\(Self.self)")
+  )
+}
diff --git a/Examples/xx-messenger/Tests/AppCoreTests/AuthCallbackHandler/AuthCallbackHandlerConfirmTests.swift b/Examples/xx-messenger/Tests/AppCoreTests/AuthCallbackHandler/AuthCallbackHandlerConfirmTests.swift
new file mode 100644
index 00000000..1a1b5f19
--- /dev/null
+++ b/Examples/xx-messenger/Tests/AppCoreTests/AuthCallbackHandler/AuthCallbackHandlerConfirmTests.swift
@@ -0,0 +1,54 @@
+import CustomDump
+import XCTest
+import XXModels
+import XXClient
+@testable import AppCore
+
+final class AuthCallbackHandlerConfirmTests: XCTestCase {
+  func testConfirm() throws {
+    var didFetchContacts: [XXModels.Contact.Query] = []
+    var didSaveContact: [XXModels.Contact] = []
+
+    let dbContact = XXModels.Contact(
+      id: "id".data(using: .utf8)!,
+      authStatus: .requested
+    )
+    let confirm = AuthCallbackHandlerConfirm.live(
+      db: .init {
+        var db: Database = .failing
+        db.fetchContacts.run = { query in
+          didFetchContacts.append(query)
+          return [dbContact]
+        }
+        db.saveContact.run = { contact in
+          didSaveContact.append(contact)
+          return contact
+        }
+        return db
+      }
+    )
+    var xxContact = XXClient.Contact.unimplemented("contact".data(using: .utf8)!)
+    xxContact.getIdFromContact.run = { _ in "id".data(using: .utf8)! }
+
+    try confirm(xxContact)
+
+    XCTAssertNoDifference(didFetchContacts, [.init(id: ["id".data(using: .utf8)!])])
+    var expectedSavedContact = dbContact
+    expectedSavedContact.authStatus = .friend
+    XCTAssertNoDifference(didSaveContact, [expectedSavedContact])
+  }
+
+  func testConfirmWhenContactNotInDatabase() throws {
+    let confirm = AuthCallbackHandlerConfirm.live(
+      db: .init {
+        var db: Database = .failing
+        db.fetchContacts.run = { _ in [] }
+        return db
+      }
+    )
+    var contact = XXClient.Contact.unimplemented("contact".data(using: .utf8)!)
+    contact.getIdFromContact.run = { _ in "id".data(using: .utf8)! }
+
+    try confirm(contact)
+  }
+}
diff --git a/Examples/xx-messenger/Tests/AppCoreTests/AuthCallbackHandler/AuthCallbackHandlerRequestTests.swift b/Examples/xx-messenger/Tests/AppCoreTests/AuthCallbackHandler/AuthCallbackHandlerRequestTests.swift
new file mode 100644
index 00000000..fb92a5b8
--- /dev/null
+++ b/Examples/xx-messenger/Tests/AppCoreTests/AuthCallbackHandler/AuthCallbackHandlerRequestTests.swift
@@ -0,0 +1,67 @@
+import CustomDump
+import XCTest
+import XXModels
+import XXClient
+import XCTestDynamicOverlay
+@testable import AppCore
+
+final class AuthCallbackHandlerRequestTests: XCTestCase {
+  func testRequestFromNewContact() throws {
+    let now = Date()
+    var didFetchContacts: [XXModels.Contact.Query] = []
+    var didSaveContact: [XXModels.Contact] = []
+
+    let request = AuthCallbackHandlerRequest.live(
+      db: .init {
+        var db: Database = .failing
+        db.fetchContacts.run = { query in
+          didFetchContacts.append(query)
+          return []
+        }
+        db.saveContact.run = { contact in
+          didSaveContact.append(contact)
+          return contact
+        }
+        return db
+      },
+      now: { now }
+    )
+    var xxContact = XXClient.Contact.unimplemented("contact".data(using: .utf8)!)
+    xxContact.getIdFromContact.run = { _ in "id".data(using: .utf8)! }
+    xxContact.getFactsFromContact.run = { _ in
+      [
+        Fact(type: .username, value: "username"),
+        Fact(type: .email, value: "email"),
+        Fact(type: .phone, value: "phone"),
+      ]
+    }
+
+    try request(xxContact)
+
+    XCTAssertNoDifference(didFetchContacts, [.init(id: ["id".data(using: .utf8)!])])
+    XCTAssertNoDifference(didSaveContact, [.init(
+      id: "id".data(using: .utf8)!,
+      marshaled: "contact".data(using: .utf8)!,
+      username: "username",
+      email: "email",
+      phone: "phone",
+      authStatus: .verificationInProgress,
+      createdAt: now
+    )])
+  }
+
+  func testRequestWhenContactInDatabase() throws {
+    let request = AuthCallbackHandlerRequest.live(
+      db: .init {
+        var db: Database = .failing
+        db.fetchContacts.run = { _ in [.init(id: "id".data(using: .utf8)!)] }
+        return db
+      },
+      now: XCTUnimplemented("now", placeholder: Date())
+    )
+    var contact = XXClient.Contact.unimplemented("contact".data(using: .utf8)!)
+    contact.getIdFromContact.run = { _ in "id".data(using: .utf8)! }
+
+    try request(contact)
+  }
+}
diff --git a/Examples/xx-messenger/Tests/AppCoreTests/AuthCallbackHandler/AuthCallbackHandlerResetTests.swift b/Examples/xx-messenger/Tests/AppCoreTests/AuthCallbackHandler/AuthCallbackHandlerResetTests.swift
new file mode 100644
index 00000000..9a4407bf
--- /dev/null
+++ b/Examples/xx-messenger/Tests/AppCoreTests/AuthCallbackHandler/AuthCallbackHandlerResetTests.swift
@@ -0,0 +1,54 @@
+import CustomDump
+import XCTest
+import XXModels
+import XXClient
+@testable import AppCore
+
+final class AuthCallbackHandlerResetTests: XCTestCase {
+  func testReset() throws {
+    var didFetchContacts: [XXModels.Contact.Query] = []
+    var didSaveContact: [XXModels.Contact] = []
+
+    let dbContact = XXModels.Contact(
+      id: "id".data(using: .utf8)!,
+      authStatus: .friend
+    )
+    let reset = AuthCallbackHandlerReset.live(
+      db: .init {
+        var db: Database = .failing
+        db.fetchContacts.run = { query in
+          didFetchContacts.append(query)
+          return [dbContact]
+        }
+        db.saveContact.run = { contact in
+          didSaveContact.append(contact)
+          return contact
+        }
+        return db
+      }
+    )
+    var xxContact = XXClient.Contact.unimplemented("contact".data(using: .utf8)!)
+    xxContact.getIdFromContact.run = { _ in "id".data(using: .utf8)! }
+
+    try reset(xxContact)
+
+    XCTAssertNoDifference(didFetchContacts, [.init(id: ["id".data(using: .utf8)!])])
+    var expectedSavedContact = dbContact
+    expectedSavedContact.authStatus = .stranger
+    XCTAssertNoDifference(didSaveContact, [expectedSavedContact])
+  }
+
+  func testResetWhenContactNotInDatabase() throws {
+    let reset = AuthCallbackHandlerReset.live(
+      db: .init {
+        var db: Database = .failing
+        db.fetchContacts.run = { _ in [] }
+        return db
+      }
+    )
+    var contact = XXClient.Contact.unimplemented("contact".data(using: .utf8)!)
+    contact.getIdFromContact.run = { _ in "id".data(using: .utf8)! }
+
+    try reset(contact)
+  }
+}
diff --git a/Examples/xx-messenger/Tests/AppCoreTests/AuthCallbackHandler/AuthCallbackHandlerTests.swift b/Examples/xx-messenger/Tests/AppCoreTests/AuthCallbackHandler/AuthCallbackHandlerTests.swift
new file mode 100644
index 00000000..2eddbbb9
--- /dev/null
+++ b/Examples/xx-messenger/Tests/AppCoreTests/AuthCallbackHandler/AuthCallbackHandlerTests.swift
@@ -0,0 +1,114 @@
+import CustomDump
+import XCTest
+import XXClient
+import XXMessengerClient
+@testable import AppCore
+
+final class AuthCallbackHandlerTests: XCTestCase {
+  func testCallbackHandling() throws {
+    struct TestState: Equatable {
+      var didRegisterAuthCallbacks = 0
+      var didCancelAuthCallbacks = 0
+      var didHandleRequest: [Contact] = []
+      var didHandleConfirm: [Contact] = []
+      var didHandleReset: [Contact] = []
+    }
+    var registeredAuthCallbacks: [AuthCallbacks] = []
+    var state = TestState()
+    var expectedState = state
+
+    var messenger: Messenger = .unimplemented
+    messenger.registerAuthCallbacks.run = { callbacks in
+      state.didRegisterAuthCallbacks += 1
+      registeredAuthCallbacks.append(callbacks)
+      return Cancellable { state.didCancelAuthCallbacks += 1 }
+    }
+
+    let handle = AuthCallbackHandler.live(
+      messenger: messenger,
+      handleRequest: .init { state.didHandleRequest.append($0) },
+      handleConfirm: .init { state.didHandleConfirm.append($0) },
+      handleReset: .init { state.didHandleReset.append($0) }
+    )
+
+    var cancellable: Any? = handle(onError: { error in
+      XCTFail("Unexpected error: \(error)")
+    })
+
+    expectedState.didRegisterAuthCallbacks += 1
+    XCTAssertNoDifference(state, expectedState)
+
+    let contact1 = XXClient.Contact.unimplemented("1".data(using: .utf8)!)
+    registeredAuthCallbacks.first?.handle(
+      .request(contact: contact1, receptionId: Data(), ephemeralId: 0, roundId: 0)
+    )
+
+    expectedState.didHandleRequest.append(contact1)
+    XCTAssertNoDifference(state, expectedState)
+
+    let contact2 = XXClient.Contact.unimplemented("2".data(using: .utf8)!)
+    registeredAuthCallbacks.first?.handle(
+      .confirm(contact: contact2, receptionId: Data(), ephemeralId: 0, roundId: 0)
+    )
+
+    expectedState.didHandleConfirm.append(contact2)
+    XCTAssertNoDifference(state, expectedState)
+
+    let contact3 = XXClient.Contact.unimplemented("3".data(using: .utf8)!)
+    registeredAuthCallbacks.first?.handle(
+      .reset(contact: contact3, receptionId: Data(), ephemeralId: 0, roundId: 0)
+    )
+
+    expectedState.didHandleReset.append(contact3)
+    XCTAssertNoDifference(state, expectedState)
+
+    cancellable = nil
+
+    expectedState.didCancelAuthCallbacks += 1
+    XCTAssertNoDifference(state, expectedState)
+
+    _ = cancellable
+  }
+
+  func testCallbackHandlingFailure() {
+    enum Failure: Error, Equatable {
+      case request
+      case confirm
+      case reset
+    }
+    var registeredAuthCallbacks: [AuthCallbacks] = []
+    var errors: [Error] = []
+
+    var messenger: Messenger = .unimplemented
+    messenger.registerAuthCallbacks.run = { callbacks in
+      registeredAuthCallbacks.append(callbacks)
+      return Cancellable {}
+    }
+
+    let handle = AuthCallbackHandler.live(
+      messenger: messenger,
+      handleRequest: .init { _ in throw Failure.request },
+      handleConfirm: .init { _ in throw Failure.confirm },
+      handleReset: .init { _ in throw Failure.reset }
+    )
+
+    let cancellable = handle(onError: { errors.append($0) })
+
+    registeredAuthCallbacks.first?.handle(
+      .request(contact: .unimplemented(Data()), receptionId: Data(), ephemeralId: 0, roundId: 0)
+    )
+    registeredAuthCallbacks.first?.handle(
+      .confirm(contact: .unimplemented(Data()), receptionId: Data(), ephemeralId: 0, roundId: 0)
+    )
+    registeredAuthCallbacks.first?.handle(
+      .reset(contact: .unimplemented(Data()), receptionId: Data(), ephemeralId: 0, roundId: 0)
+    )
+
+    XCTAssertNoDifference(
+      errors.map { $0 as? Failure },
+      [.request, .confirm, .reset]
+    )
+
+    _ = cancellable
+  }
+}
-- 
GitLab


From 0f8d52eb0a2443bebf8ea059217be3f861ccd594 Mon Sep 17 00:00:00 2001
From: Dariusz Rybicki <dariusz@elixxir.io>
Date: Fri, 9 Sep 2022 13:00:20 +0200
Subject: [PATCH 4/8] Use AuthCallbackHandler in HomeFeature

---
 .../AppFeature/AppEnvironment+Live.swift      |   8 +-
 .../Sources/HomeFeature/HomeFeature.swift     |  74 +++------
 .../Sources/HomeFeature/HomeView.swift        |  23 ++-
 .../HomeFeatureTests/HomeFeatureTests.swift   | 149 +++++-------------
 4 files changed, 91 insertions(+), 163 deletions(-)

diff --git a/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift b/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift
index 4092af82..c45c93ef 100644
--- a/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift
+++ b/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift
@@ -16,6 +16,12 @@ extension AppEnvironment {
     let dbManager = DBManager.live()
     let messengerEnv = MessengerEnvironment.live()
     let messenger = Messenger.live(messengerEnv)
+    let authHandler = AuthCallbackHandler.live(
+      messenger: messenger,
+      handleRequest: .live(db: dbManager.getDB, now: Date.init),
+      handleConfirm: .live(db: dbManager.getDB),
+      handleReset: .live(db: dbManager.getDB)
+    )
     let mainQueue = DispatchQueue.main.eraseToAnyScheduler()
     let bgQueue = DispatchQueue.global(qos: .background).eraseToAnyScheduler()
 
@@ -53,9 +59,9 @@ extension AppEnvironment {
         HomeEnvironment(
           messenger: messenger,
           dbManager: dbManager,
+          authHandler: authHandler,
           mainQueue: mainQueue,
           bgQueue: bgQueue,
-          now: Date.init,
           register: {
             RegisterEnvironment(
               messenger: messenger,
diff --git a/Examples/xx-messenger/Sources/HomeFeature/HomeFeature.swift b/Examples/xx-messenger/Sources/HomeFeature/HomeFeature.swift
index 985ef329..e28008f3 100644
--- a/Examples/xx-messenger/Sources/HomeFeature/HomeFeature.swift
+++ b/Examples/xx-messenger/Sources/HomeFeature/HomeFeature.swift
@@ -14,6 +14,7 @@ import XXModels
 public struct HomeState: Equatable {
   public init(
     failure: String? = nil,
+    authFailure: String? = nil,
     isNetworkHealthy: Bool? = nil,
     networkNodesReport: NodeRegistrationReport? = nil,
     isDeletingAccount: Bool = false,
@@ -23,6 +24,7 @@ public struct HomeState: Equatable {
     userSearch: UserSearchState? = nil
   ) {
     self.failure = failure
+    self.authFailure = authFailure
     self.isNetworkHealthy = isNetworkHealthy
     self.isDeletingAccount = isDeletingAccount
     self.alert = alert
@@ -32,6 +34,7 @@ public struct HomeState: Equatable {
   }
 
   public var failure: String?
+  public var authFailure: String?
   public var isNetworkHealthy: Bool?
   public var networkNodesReport: NodeRegistrationReport?
   public var isDeletingAccount: Bool
@@ -49,10 +52,11 @@ public enum HomeAction: Equatable {
     case failure(NSError)
   }
 
-  public enum AuthCallbacks: Equatable {
-    case register
-    case unregister
-    case handle(XXClient.AuthCallbacks.Callback)
+  public enum AuthHandler: Equatable {
+    case start
+    case stop
+    case failure(NSError)
+    case failureDismissed
   }
 
   public enum NetworkMonitor: Equatable {
@@ -70,7 +74,7 @@ public enum HomeAction: Equatable {
   }
 
   case messenger(Messenger)
-  case authCallbacks(AuthCallbacks)
+  case authHandler(AuthHandler)
   case networkMonitor(NetworkMonitor)
   case deleteAccount(DeleteAccount)
   case didDismissAlert
@@ -88,18 +92,18 @@ public struct HomeEnvironment {
   public init(
     messenger: Messenger,
     dbManager: DBManager,
+    authHandler: AuthCallbackHandler,
     mainQueue: AnySchedulerOf<DispatchQueue>,
     bgQueue: AnySchedulerOf<DispatchQueue>,
-    now: @escaping () -> Date,
     register: @escaping () -> RegisterEnvironment,
     contacts: @escaping () -> ContactsEnvironment,
     userSearch: @escaping () -> UserSearchEnvironment
   ) {
     self.messenger = messenger
     self.dbManager = dbManager
+    self.authHandler = authHandler
     self.mainQueue = mainQueue
     self.bgQueue = bgQueue
-    self.now = now
     self.register = register
     self.contacts = contacts
     self.userSearch = userSearch
@@ -107,9 +111,9 @@ public struct HomeEnvironment {
 
   public var messenger: Messenger
   public var dbManager: DBManager
+  public var authHandler: AuthCallbackHandler
   public var mainQueue: AnySchedulerOf<DispatchQueue>
   public var bgQueue: AnySchedulerOf<DispatchQueue>
-  public var now: () -> Date
   public var register: () -> RegisterEnvironment
   public var contacts: () -> ContactsEnvironment
   public var userSearch: () -> UserSearchEnvironment
@@ -119,9 +123,9 @@ extension HomeEnvironment {
   public static let unimplemented = HomeEnvironment(
     messenger: .unimplemented,
     dbManager: .unimplemented,
+    authHandler: .unimplemented,
     mainQueue: .unimplemented,
     bgQueue: .unimplemented,
-    now: XCTUnimplemented("\(Self.self).now", placeholder: Date()),
     register: { .unimplemented },
     contacts: { .unimplemented },
     userSearch: { .unimplemented }
@@ -137,7 +141,7 @@ public let homeReducer = Reducer<HomeState, HomeAction, HomeEnvironment>
   switch action {
   case .messenger(.start):
     return .merge(
-      Effect(value: .authCallbacks(.register)),
+      Effect(value: .authHandler(.start)),
       Effect(value: .networkMonitor(.stop)),
       Effect.result {
         do {
@@ -175,12 +179,11 @@ public let homeReducer = Reducer<HomeState, HomeAction, HomeEnvironment>
     state.failure = error.localizedDescription
     return .none
 
-  case .authCallbacks(.register):
+  case .authHandler(.start):
     return Effect.run { subscriber in
-      let handler = AuthCallbacks { callback in
-        subscriber.send(.authCallbacks(.handle(callback)))
-      }
-      let cancellable = env.messenger.registerAuthCallbacks(handler)
+      let cancellable = env.authHandler(onError: { error in
+        subscriber.send(.authHandler(.failure(error as NSError)))
+      })
       return AnyCancellable { cancellable.cancel() }
     }
     .subscribe(on: env.bgQueue)
@@ -188,44 +191,15 @@ public let homeReducer = Reducer<HomeState, HomeAction, HomeEnvironment>
     .eraseToEffect()
     .cancellable(id: AuthCallbacksEffectId.self, cancelInFlight: true)
 
-  case .authCallbacks(.unregister):
+  case .authHandler(.stop):
     return .cancel(id: AuthCallbacksEffectId.self)
 
-  case .authCallbacks(.handle(.request(let contact, _, _, _))):
-    return .fireAndForget {
-      let db = try env.dbManager.getDB()
-      let contactId = try contact.getId()
-      guard try db.fetchContacts(.init(id: [contactId])).isEmpty else {
-        return
-      }
-      var dbContact = XXModels.Contact(id: contactId)
-      dbContact.marshaled = contact.data
-      dbContact.username = try contact.getFact(.username)?.value
-      dbContact.email = try contact.getFact(.email)?.value
-      dbContact.phone = try contact.getFact(.phone)?.value
-      dbContact.authStatus = .verificationInProgress
-      dbContact.createdAt = env.now()
-      dbContact = try db.saveContact(dbContact)
-    }
-    .subscribe(on: env.bgQueue)
-    .receive(on: env.mainQueue)
-    .eraseToEffect()
-
-  case .authCallbacks(.handle(.confirm(let contact, _, _, _))):
-    return .fireAndForget {
-      let db = try env.dbManager.getDB()
-      let contactId = try contact.getId()
-      guard var dbContact = try db.fetchContacts(.init(id: [contactId])).first else {
-        return
-      }
-      dbContact.authStatus = .friend
-      dbContact = try db.saveContact(dbContact)
-    }
-    .subscribe(on: env.bgQueue)
-    .receive(on: env.mainQueue)
-    .eraseToEffect()
+  case .authHandler(.failure(let error)):
+    state.authFailure = error.localizedDescription
+    return .none
 
-  case .authCallbacks(.handle(.reset(let contact, _, _, _))):
+  case .authHandler(.failureDismissed):
+    state.authFailure = nil
     return .none
 
   case .networkMonitor(.start):
diff --git a/Examples/xx-messenger/Sources/HomeFeature/HomeView.swift b/Examples/xx-messenger/Sources/HomeFeature/HomeView.swift
index f1abfb2b..f6a96489 100644
--- a/Examples/xx-messenger/Sources/HomeFeature/HomeView.swift
+++ b/Examples/xx-messenger/Sources/HomeFeature/HomeView.swift
@@ -15,12 +15,14 @@ public struct HomeView: View {
 
   struct ViewState: Equatable {
     var failure: String?
+    var authFailure: String?
     var isNetworkHealthy: Bool?
     var networkNodesReport: NodeRegistrationReport?
     var isDeletingAccount: Bool
 
     init(state: HomeState) {
       failure = state.failure
+      authFailure = state.authFailure
       isNetworkHealthy = state.isNetworkHealthy
       isDeletingAccount = state.isDeletingAccount
       networkNodesReport = state.networkNodesReport
@@ -31,21 +33,32 @@ public struct HomeView: View {
     WithViewStore(store, observe: ViewState.init) { viewStore in
       NavigationView {
         Form {
-          Section {
-            if let failure = viewStore.failure {
+          if let failure = viewStore.failure {
+            Section {
               Text(failure)
               Button {
                 viewStore.send(.messenger(.start))
               } label: {
                 Text("Retry")
               }
-            }
-          } header: {
-            if viewStore.failure != nil {
+            } header: {
               Text("Error")
             }
           }
 
+          if let authFailure = viewStore.authFailure {
+            Section {
+              Text(authFailure)
+              Button {
+                viewStore.send(.authHandler(.failureDismissed))
+              } label: {
+                Text("Dismiss")
+              }
+            } header: {
+              Text("Auth Error")
+            }
+          }
+
           Section {
             HStack {
               Text("Health")
diff --git a/Examples/xx-messenger/Tests/HomeFeatureTests/HomeFeatureTests.swift b/Examples/xx-messenger/Tests/HomeFeatureTests/HomeFeatureTests.swift
index 40cd5a2a..f3e5bcf8 100644
--- a/Examples/xx-messenger/Tests/HomeFeatureTests/HomeFeatureTests.swift
+++ b/Examples/xx-messenger/Tests/HomeFeatureTests/HomeFeatureTests.swift
@@ -1,3 +1,4 @@
+import AppCore
 import ComposableArchitecture
 import ContactsFeature
 import RegisterFeature
@@ -21,7 +22,7 @@ final class HomeFeatureTests: XCTestCase {
 
     store.environment.bgQueue = .immediate
     store.environment.mainQueue = .immediate
-    store.environment.messenger.registerAuthCallbacks.run = { _ in Cancellable {} }
+    store.environment.authHandler.run = { _ in Cancellable {} }
     store.environment.messenger.start.run = { messengerDidStartWithTimeout.append($0) }
     store.environment.messenger.isConnected.run = { false }
     store.environment.messenger.connect.run = { messengerDidConnect += 1 }
@@ -33,13 +34,13 @@ final class HomeFeatureTests: XCTestCase {
     XCTAssertNoDifference(messengerDidStartWithTimeout, [30_000])
     XCTAssertNoDifference(messengerDidConnect, 1)
 
-    store.receive(.authCallbacks(.register))
+    store.receive(.authHandler(.start))
     store.receive(.networkMonitor(.stop))
     store.receive(.messenger(.didStartUnregistered)) {
       $0.register = RegisterState()
     }
 
-    store.send(.authCallbacks(.unregister))
+    store.send(.authHandler(.stop))
   }
 
   func testMessengerStartRegistered() {
@@ -55,7 +56,7 @@ final class HomeFeatureTests: XCTestCase {
 
     store.environment.bgQueue = .immediate
     store.environment.mainQueue = .immediate
-    store.environment.messenger.registerAuthCallbacks.run = { _ in Cancellable {} }
+    store.environment.authHandler.run = { _ in Cancellable {} }
     store.environment.messenger.start.run = { messengerDidStartWithTimeout.append($0) }
     store.environment.messenger.isConnected.run = { false }
     store.environment.messenger.connect.run = { messengerDidConnect += 1 }
@@ -78,14 +79,13 @@ final class HomeFeatureTests: XCTestCase {
     XCTAssertNoDifference(messengerDidConnect, 1)
     XCTAssertNoDifference(messengerDidLogIn, 1)
 
-    store.receive(.authCallbacks(.register))
+    store.receive(.authHandler(.start))
     store.receive(.networkMonitor(.stop))
     store.receive(.messenger(.didStartRegistered))
     store.receive(.networkMonitor(.start))
 
     store.send(.networkMonitor(.stop))
-
-    store.send(.authCallbacks(.unregister))
+    store.send(.authHandler(.stop))
   }
 
   func testRegisterFinished() {
@@ -102,7 +102,7 @@ final class HomeFeatureTests: XCTestCase {
 
     store.environment.bgQueue = .immediate
     store.environment.mainQueue = .immediate
-    store.environment.messenger.registerAuthCallbacks.run = { _ in Cancellable {} }
+    store.environment.authHandler.run = { _ in Cancellable {} }
     store.environment.messenger.start.run = { messengerDidStartWithTimeout.append($0) }
     store.environment.messenger.isConnected.run = { true }
     store.environment.messenger.isLoggedIn.run = { false }
@@ -127,14 +127,13 @@ final class HomeFeatureTests: XCTestCase {
     XCTAssertNoDifference(messengerDidStartWithTimeout, [30_000])
     XCTAssertNoDifference(messengerDidLogIn, 1)
 
-    store.receive(.authCallbacks(.register))
+    store.receive(.authHandler(.start))
     store.receive(.networkMonitor(.stop))
     store.receive(.messenger(.didStartRegistered))
     store.receive(.networkMonitor(.start))
 
     store.send(.networkMonitor(.stop))
-
-    store.send(.authCallbacks(.unregister))
+    store.send(.authHandler(.stop))
   }
 
   func testMessengerStartFailure() {
@@ -149,18 +148,18 @@ final class HomeFeatureTests: XCTestCase {
 
     store.environment.bgQueue = .immediate
     store.environment.mainQueue = .immediate
-    store.environment.messenger.registerAuthCallbacks.run = { _ in Cancellable {} }
+    store.environment.authHandler.run = { _ in Cancellable {} }
     store.environment.messenger.start.run = { _ in throw error }
 
     store.send(.messenger(.start))
 
-    store.receive(.authCallbacks(.register))
+    store.receive(.authHandler(.start))
     store.receive(.networkMonitor(.stop))
     store.receive(.messenger(.failure(error as NSError))) {
       $0.failure = error.localizedDescription
     }
 
-    store.send(.authCallbacks(.unregister))
+    store.send(.authHandler(.stop))
   }
 
   func testMessengerStartConnectFailure() {
@@ -175,20 +174,20 @@ final class HomeFeatureTests: XCTestCase {
 
     store.environment.bgQueue = .immediate
     store.environment.mainQueue = .immediate
-    store.environment.messenger.registerAuthCallbacks.run = { _ in Cancellable {} }
+    store.environment.authHandler.run = { _ in Cancellable {} }
     store.environment.messenger.start.run = { _ in }
     store.environment.messenger.isConnected.run = { false }
     store.environment.messenger.connect.run = { throw error }
 
     store.send(.messenger(.start))
 
-    store.receive(.authCallbacks(.register))
+    store.receive(.authHandler(.start))
     store.receive(.networkMonitor(.stop))
     store.receive(.messenger(.failure(error as NSError))) {
       $0.failure = error.localizedDescription
     }
 
-    store.send(.authCallbacks(.unregister))
+    store.send(.authHandler(.stop))
   }
 
   func testMessengerStartIsRegisteredFailure() {
@@ -203,7 +202,7 @@ final class HomeFeatureTests: XCTestCase {
 
     store.environment.bgQueue = .immediate
     store.environment.mainQueue = .immediate
-    store.environment.messenger.registerAuthCallbacks.run = { _ in Cancellable {} }
+    store.environment.authHandler.run = { _ in Cancellable {} }
     store.environment.messenger.start.run = { _ in }
     store.environment.messenger.isConnected.run = { true }
     store.environment.messenger.isLoggedIn.run = { false }
@@ -211,13 +210,13 @@ final class HomeFeatureTests: XCTestCase {
 
     store.send(.messenger(.start))
 
-    store.receive(.authCallbacks(.register))
+    store.receive(.authHandler(.start))
     store.receive(.networkMonitor(.stop))
     store.receive(.messenger(.failure(error as NSError))) {
       $0.failure = error.localizedDescription
     }
 
-    store.send(.authCallbacks(.unregister))
+    store.send(.authHandler(.stop))
   }
 
   func testMessengerStartLogInFailure() {
@@ -232,7 +231,7 @@ final class HomeFeatureTests: XCTestCase {
 
     store.environment.bgQueue = .immediate
     store.environment.mainQueue = .immediate
-    store.environment.messenger.registerAuthCallbacks.run = { _ in Cancellable {} }
+    store.environment.authHandler.run = { _ in Cancellable {} }
     store.environment.messenger.start.run = { _ in }
     store.environment.messenger.isConnected.run = { true }
     store.environment.messenger.isLoggedIn.run = { false }
@@ -241,13 +240,13 @@ final class HomeFeatureTests: XCTestCase {
 
     store.send(.messenger(.start))
 
-    store.receive(.authCallbacks(.register))
+    store.receive(.authHandler(.start))
     store.receive(.networkMonitor(.stop))
     store.receive(.messenger(.failure(error as NSError))) {
       $0.failure = error.localizedDescription
     }
 
-    store.send(.authCallbacks(.unregister))
+    store.send(.authHandler(.stop))
   }
 
   func testNetworkMonitorStart() {
@@ -527,101 +526,37 @@ final class HomeFeatureTests: XCTestCase {
       environment: .unimplemented
     )
 
-    let now = Date()
-    var didRegisterCallbacks: [AuthCallbacks] = []
-    var didCancelAuthCallbacks = 0
-    var dbContact: XXModels.Contact?
-    var dbDidSaveContact: [XXModels.Contact] = []
+    var didRunAuthHandler = 0
+    var didCancelAuthHandler = 0
+    var authHandlerOnError: [AuthCallbackHandler.OnError] = []
 
     store.environment.mainQueue = .immediate
     store.environment.bgQueue = .immediate
-    store.environment.now = { now }
-    store.environment.messenger.registerAuthCallbacks.run = { callbacks in
-      didRegisterCallbacks.append(callbacks)
-      return Cancellable { didCancelAuthCallbacks += 1 }
-    }
-    store.environment.dbManager.getDB.run = {
-      var db: Database = .failing
-      db.fetchContacts.run = { _ in [dbContact].compactMap { $0 } }
-      db.saveContact.run = { contact in
-        dbDidSaveContact.append(contact)
-        return contact
-      }
-      return db
-    }
-
-    store.send(.authCallbacks(.register))
-
-    XCTAssertNoDifference(didRegisterCallbacks.count, 1)
-
-    var contact = XXClient.Contact.unimplemented("data".data(using: .utf8)!)
-    contact.getIdFromContact.run = { _ in "id".data(using: .utf8)! }
-    contact.getFactsFromContact.run = { _ in
-      [
-        Fact(type: .username, value: "username"),
-        Fact(type: .email, value: "email"),
-        Fact(type: .phone, value: "phone"),
-      ]
+    store.environment.authHandler.run = { onError in
+      didRunAuthHandler += 1
+      authHandlerOnError.append(onError)
+      return Cancellable { didCancelAuthHandler += 1 }
     }
 
-    dbContact = nil
-    dbDidSaveContact = []
-    didRegisterCallbacks.first?.handle(
-      .request(contact: contact, receptionId: Data(), ephemeralId: 0, roundId: 0)
-    )
+    store.send(.authHandler(.start))
 
-    store.receive(.authCallbacks(.handle(
-      .request(contact: contact, receptionId: Data(), ephemeralId: 0, roundId: 0)
-    )))
-    XCTAssertNoDifference(dbDidSaveContact, [.init(
-      id: "id".data(using: .utf8)!,
-      marshaled: "data".data(using: .utf8)!,
-      username: "username",
-      email: "email",
-      phone: "phone",
-      authStatus: .verificationInProgress,
-      createdAt: now
-    )])
-
-    dbContact = .init(id: "id".data(using: .utf8)!)
-    dbDidSaveContact = []
-    didRegisterCallbacks.first?.handle(
-      .request(contact: contact, receptionId: Data(), ephemeralId: 0, roundId: 0)
-    )
+    XCTAssertNoDifference(didRunAuthHandler, 1)
 
-    store.receive(.authCallbacks(.handle(
-      .request(contact: contact, receptionId: Data(), ephemeralId: 0, roundId: 0)
-    )))
-    XCTAssertNoDifference(dbDidSaveContact, [])
+    struct AuthHandlerError: Error { var id: Int }
+    authHandlerOnError.first?(AuthHandlerError(id: 1))
 
-    dbContact = .init(id: "id".data(using: .utf8)!)
-    dbDidSaveContact = []
-    didRegisterCallbacks.first?.handle(
-      .confirm(contact: contact, receptionId: Data(), ephemeralId: 0, roundId: 0)
-    )
+    store.receive(.authHandler(.failure(AuthHandlerError(id: 1) as NSError))) {
+      $0.authFailure = AuthHandlerError(id: 1).localizedDescription
+    }
 
-    store.receive(.authCallbacks(.handle(
-      .confirm(contact: contact, receptionId: Data(), ephemeralId: 0, roundId: 0)
-    )))
-    XCTAssertNoDifference(dbDidSaveContact, [.init(
-      id: "id".data(using: .utf8)!,
-      authStatus: .friend,
-      createdAt: dbContact!.createdAt
-    )])
-
-    dbContact = nil
-    dbDidSaveContact = []
-    didRegisterCallbacks.first?.handle(
-      .reset(contact: contact, receptionId: Data(), ephemeralId: 0, roundId: 0)
-    )
+    store.send(.authHandler(.failureDismissed)) {
+      $0.authFailure = nil
+    }
 
-    store.receive(.authCallbacks(.handle(
-      .reset(contact: contact, receptionId: Data(), ephemeralId: 0, roundId: 0)
-    )))
-    XCTAssertNoDifference(dbDidSaveContact, [])
+    store.send(.authHandler(.stop))
 
-    store.send(.authCallbacks(.unregister))
+    XCTAssertNoDifference(didCancelAuthHandler, 1)
 
-    XCTAssertNoDifference(didCancelAuthCallbacks, 1)
+    authHandlerOnError.first?(AuthHandlerError(id: 2))
   }
 }
-- 
GitLab


From d0006cd916be314faae0381719a6fefa050b1144 Mon Sep 17 00:00:00 2001
From: Dariusz Rybicki <dariusz@elixxir.io>
Date: Fri, 9 Sep 2022 13:27:01 +0200
Subject: [PATCH 5/8] Add MessengerVerifyContact function

---
 .../Functions/MessengerVerifyContact.swift    | 25 +++++++++++++++++++
 .../Messenger/Messenger.swift                 |  7 ++++--
 .../MessengerVerifyContactTests.swift         | 16 ++++++++++++
 3 files changed, 46 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..3d3d40e3
--- /dev/null
+++ b/Sources/XXMessengerClient/Messenger/Functions/MessengerVerifyContact.swift
@@ -0,0 +1,25 @@
+import XCTestDynamicOverlay
+import XXClient
+
+public struct MessengerVerifyContact {
+  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
+      // TODO:
+      return false
+    }
+  }
+}
+
+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/Tests/XXMessengerClientTests/Messenger/Functions/MessengerVerifyContactTests.swift b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerVerifyContactTests.swift
new file mode 100644
index 00000000..255a7220
--- /dev/null
+++ b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerVerifyContactTests.swift
@@ -0,0 +1,16 @@
+import CustomDump
+import XCTest
+import XXClient
+@testable import XXMessengerClient
+
+final class MessengerVerifyContactTests: XCTestCase {
+  func testVerify() throws {
+    var env: MessengerEnvironment = .unimplemented
+    let verify: MessengerVerifyContact = .live(env)
+    let contact = Contact.unimplemented("data".data(using: .utf8)!)
+
+    let result = try verify(contact)
+
+    XCTAssertNoDifference(result, false)
+  }
+}
-- 
GitLab


From 7906115403fee75347f973e5b0f26452b033e052 Mon Sep 17 00:00:00 2001
From: Dariusz Rybicki <dariusz@elixxir.io>
Date: Fri, 9 Sep 2022 13:37:52 +0200
Subject: [PATCH 6/8] Implement verification in AuthCallbackHandlerRequest

---
 .../AuthCallbackHandlerRequest.swift          |  12 ++
 .../AppFeature/AppEnvironment+Live.swift      |   6 +-
 .../AuthCallbackHandlerRequestTests.swift     | 131 ++++++++++++++++--
 3 files changed, 137 insertions(+), 12 deletions(-)

diff --git a/Examples/xx-messenger/Sources/AppCore/AuthCallbackHandler/AuthCallbackHandlerRequest.swift b/Examples/xx-messenger/Sources/AppCore/AuthCallbackHandler/AuthCallbackHandlerRequest.swift
index 1ec23923..e8f14806 100644
--- a/Examples/xx-messenger/Sources/AppCore/AuthCallbackHandler/AuthCallbackHandlerRequest.swift
+++ b/Examples/xx-messenger/Sources/AppCore/AuthCallbackHandler/AuthCallbackHandlerRequest.swift
@@ -1,6 +1,7 @@
 import Foundation
 import XCTestDynamicOverlay
 import XXClient
+import XXMessengerClient
 import XXModels
 
 public struct AuthCallbackHandlerRequest {
@@ -14,6 +15,7 @@ public struct AuthCallbackHandlerRequest {
 extension AuthCallbackHandlerRequest {
   public static func live(
     db: DBManagerGetDB,
+    messenger: Messenger,
     now: @escaping () -> Date
   ) -> AuthCallbackHandlerRequest {
     AuthCallbackHandlerRequest { xxContact in
@@ -29,6 +31,16 @@ extension AuthCallbackHandlerRequest {
       dbContact.authStatus = .verificationInProgress
       dbContact.createdAt = now()
       dbContact = try db().saveContact(dbContact)
+
+      do {
+        let verified = try messenger.verifyContact(xxContact)
+        dbContact.authStatus = verified ? .verified : .verificationFailed
+        dbContact = try db().saveContact(dbContact)
+      } catch {
+        dbContact.authStatus = .verificationFailed
+        dbContact = try db().saveContact(dbContact)
+        throw error
+      }
     }
   }
 }
diff --git a/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift b/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift
index c45c93ef..5267fcc3 100644
--- a/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift
+++ b/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift
@@ -18,7 +18,11 @@ extension AppEnvironment {
     let messenger = Messenger.live(messengerEnv)
     let authHandler = AuthCallbackHandler.live(
       messenger: messenger,
-      handleRequest: .live(db: dbManager.getDB, now: Date.init),
+      handleRequest: .live(
+        db: dbManager.getDB,
+        messenger: messenger,
+        now: Date.init
+      ),
       handleConfirm: .live(db: dbManager.getDB),
       handleReset: .live(db: dbManager.getDB)
     )
diff --git a/Examples/xx-messenger/Tests/AppCoreTests/AuthCallbackHandler/AuthCallbackHandlerRequestTests.swift b/Examples/xx-messenger/Tests/AppCoreTests/AuthCallbackHandler/AuthCallbackHandlerRequestTests.swift
index fb92a5b8..07359d1f 100644
--- a/Examples/xx-messenger/Tests/AppCoreTests/AuthCallbackHandler/AuthCallbackHandlerRequestTests.swift
+++ b/Examples/xx-messenger/Tests/AppCoreTests/AuthCallbackHandler/AuthCallbackHandlerRequestTests.swift
@@ -1,16 +1,24 @@
 import CustomDump
 import XCTest
-import XXModels
-import XXClient
 import XCTestDynamicOverlay
+import XXClient
+import XXMessengerClient
+import XXModels
 @testable import AppCore
 
 final class AuthCallbackHandlerRequestTests: XCTestCase {
   func testRequestFromNewContact() throws {
     let now = Date()
     var didFetchContacts: [XXModels.Contact.Query] = []
+    var didVerifyContact: [XXClient.Contact] = []
     var didSaveContact: [XXModels.Contact] = []
 
+    var messenger: Messenger = .unimplemented
+    messenger.verifyContact.run = { contact in
+      didVerifyContact.append(contact)
+      return true
+    }
+
     let request = AuthCallbackHandlerRequest.live(
       db: .init {
         var db: Database = .failing
@@ -24,6 +32,7 @@ final class AuthCallbackHandlerRequestTests: XCTestCase {
         }
         return db
       },
+      messenger: messenger,
       now: { now }
     )
     var xxContact = XXClient.Contact.unimplemented("contact".data(using: .utf8)!)
@@ -39,15 +48,26 @@ final class AuthCallbackHandlerRequestTests: XCTestCase {
     try request(xxContact)
 
     XCTAssertNoDifference(didFetchContacts, [.init(id: ["id".data(using: .utf8)!])])
-    XCTAssertNoDifference(didSaveContact, [.init(
-      id: "id".data(using: .utf8)!,
-      marshaled: "contact".data(using: .utf8)!,
-      username: "username",
-      email: "email",
-      phone: "phone",
-      authStatus: .verificationInProgress,
-      createdAt: now
-    )])
+    XCTAssertNoDifference(didSaveContact, [
+      .init(
+        id: "id".data(using: .utf8)!,
+        marshaled: "contact".data(using: .utf8)!,
+        username: "username",
+        email: "email",
+        phone: "phone",
+        authStatus: .verificationInProgress,
+        createdAt: now
+      ),
+      .init(
+        id: "id".data(using: .utf8)!,
+        marshaled: "contact".data(using: .utf8)!,
+        username: "username",
+        email: "email",
+        phone: "phone",
+        authStatus: .verified,
+        createdAt: now
+      )
+    ])
   }
 
   func testRequestWhenContactInDatabase() throws {
@@ -57,6 +77,7 @@ final class AuthCallbackHandlerRequestTests: XCTestCase {
         db.fetchContacts.run = { _ in [.init(id: "id".data(using: .utf8)!)] }
         return db
       },
+      messenger: .unimplemented,
       now: XCTUnimplemented("now", placeholder: Date())
     )
     var contact = XXClient.Contact.unimplemented("contact".data(using: .utf8)!)
@@ -64,4 +85,92 @@ final class AuthCallbackHandlerRequestTests: XCTestCase {
 
     try request(contact)
   }
+
+  func testRequestFromNewContactVerificationFalse() throws {
+    let now = Date()
+    var didSaveContact: [XXModels.Contact] = []
+
+    var messenger: Messenger = .unimplemented
+    messenger.verifyContact.run = { _ in false }
+
+    let request = AuthCallbackHandlerRequest.live(
+      db: .init {
+        var db: Database = .failing
+        db.fetchContacts.run = { query in return [] }
+        db.saveContact.run = { contact in
+          didSaveContact.append(contact)
+          return contact
+        }
+        return db
+      },
+      messenger: messenger,
+      now: { now }
+    )
+    var xxContact = XXClient.Contact.unimplemented("contact".data(using: .utf8)!)
+    xxContact.getIdFromContact.run = { _ in "id".data(using: .utf8)! }
+    xxContact.getFactsFromContact.run = { _ in [] }
+
+    try request(xxContact)
+
+    XCTAssertNoDifference(didSaveContact, [
+      .init(
+        id: "id".data(using: .utf8)!,
+        marshaled: "contact".data(using: .utf8)!,
+        authStatus: .verificationInProgress,
+        createdAt: now
+      ),
+      .init(
+        id: "id".data(using: .utf8)!,
+        marshaled: "contact".data(using: .utf8)!,
+        authStatus: .verificationFailed,
+        createdAt: now
+      )
+    ])
+  }
+
+  func testRequestFromNewContactVerificationFailure() throws {
+    struct Failure: Error, Equatable {}
+    let failure = Failure()
+    let now = Date()
+    var didSaveContact: [XXModels.Contact] = []
+
+    var messenger: Messenger = .unimplemented
+    messenger.verifyContact.run = { _ in throw failure }
+
+    let request = AuthCallbackHandlerRequest.live(
+      db: .init {
+        var db: Database = .failing
+        db.fetchContacts.run = { query in return [] }
+        db.saveContact.run = { contact in
+          didSaveContact.append(contact)
+          return contact
+        }
+        return db
+      },
+      messenger: messenger,
+      now: { now }
+    )
+    var xxContact = XXClient.Contact.unimplemented("contact".data(using: .utf8)!)
+    xxContact.getIdFromContact.run = { _ in "id".data(using: .utf8)! }
+    xxContact.getFactsFromContact.run = { _ in [] }
+
+    XCTAssertThrowsError(try request(xxContact)) { error in
+      XCTAssertNoDifference(error as? Failure, failure)
+    }
+
+    XCTAssertNoDifference(didSaveContact, [
+      .init(
+        id: "id".data(using: .utf8)!,
+        marshaled: "contact".data(using: .utf8)!,
+        authStatus: .verificationInProgress,
+        createdAt: now
+      ),
+      .init(
+        id: "id".data(using: .utf8)!,
+        marshaled: "contact".data(using: .utf8)!,
+        authStatus: .verificationFailed,
+        createdAt: now
+      )
+    ])
+  }
 }
-- 
GitLab


From 81c28379194c8db1e8a110537edfe9fcadcc0140 Mon Sep 17 00:00:00 2001
From: Dariusz Rybicki <dariusz@elixxir.io>
Date: Fri, 9 Sep 2022 14:07:31 +0200
Subject: [PATCH 7/8] Wait for network and nodes before verifying

---
 .../AuthCallbackHandler/AuthCallbackHandlerRequest.swift    | 2 ++
 .../AuthCallbackHandlerRequestTests.swift                   | 6 ++++++
 2 files changed, 8 insertions(+)

diff --git a/Examples/xx-messenger/Sources/AppCore/AuthCallbackHandler/AuthCallbackHandlerRequest.swift b/Examples/xx-messenger/Sources/AppCore/AuthCallbackHandler/AuthCallbackHandlerRequest.swift
index e8f14806..d8ac3d18 100644
--- a/Examples/xx-messenger/Sources/AppCore/AuthCallbackHandler/AuthCallbackHandlerRequest.swift
+++ b/Examples/xx-messenger/Sources/AppCore/AuthCallbackHandler/AuthCallbackHandlerRequest.swift
@@ -33,6 +33,8 @@ extension AuthCallbackHandlerRequest {
       dbContact = try db().saveContact(dbContact)
 
       do {
+        try messenger.waitForNetwork()
+        try messenger.waitForNodes()
         let verified = try messenger.verifyContact(xxContact)
         dbContact.authStatus = verified ? .verified : .verificationFailed
         dbContact = try db().saveContact(dbContact)
diff --git a/Examples/xx-messenger/Tests/AppCoreTests/AuthCallbackHandler/AuthCallbackHandlerRequestTests.swift b/Examples/xx-messenger/Tests/AppCoreTests/AuthCallbackHandler/AuthCallbackHandlerRequestTests.swift
index 07359d1f..65786884 100644
--- a/Examples/xx-messenger/Tests/AppCoreTests/AuthCallbackHandler/AuthCallbackHandlerRequestTests.swift
+++ b/Examples/xx-messenger/Tests/AppCoreTests/AuthCallbackHandler/AuthCallbackHandlerRequestTests.swift
@@ -14,6 +14,8 @@ final class AuthCallbackHandlerRequestTests: XCTestCase {
     var didSaveContact: [XXModels.Contact] = []
 
     var messenger: Messenger = .unimplemented
+    messenger.waitForNetwork.run = { _ in }
+    messenger.waitForNodes.run = { _, _, _, _ in }
     messenger.verifyContact.run = { contact in
       didVerifyContact.append(contact)
       return true
@@ -91,6 +93,8 @@ final class AuthCallbackHandlerRequestTests: XCTestCase {
     var didSaveContact: [XXModels.Contact] = []
 
     var messenger: Messenger = .unimplemented
+    messenger.waitForNetwork.run = { _ in }
+    messenger.waitForNodes.run = { _, _, _, _ in }
     messenger.verifyContact.run = { _ in false }
 
     let request = AuthCallbackHandlerRequest.live(
@@ -135,6 +139,8 @@ final class AuthCallbackHandlerRequestTests: XCTestCase {
     var didSaveContact: [XXModels.Contact] = []
 
     var messenger: Messenger = .unimplemented
+    messenger.waitForNetwork.run = { _ in }
+    messenger.waitForNodes.run = { _, _, _, _ in }
     messenger.verifyContact.run = { _ in throw failure }
 
     let request = AuthCallbackHandlerRequest.live(
-- 
GitLab


From 7fdbd1c6248f635ae42837c58737a4f73d28a713 Mon Sep 17 00:00:00 2001
From: Dariusz Rybicki <dariusz@elixxir.io>
Date: Fri, 9 Sep 2022 15:18:32 +0200
Subject: [PATCH 8/8] WIP

---
 .../Functions/MessengerVerifyContact.swift    | 56 ++++++++++++++++++-
 .../Messenger/MessengerEnvironment.swift      |  3 +
 2 files changed, 57 insertions(+), 2 deletions(-)

diff --git a/Sources/XXMessengerClient/Messenger/Functions/MessengerVerifyContact.swift b/Sources/XXMessengerClient/Messenger/Functions/MessengerVerifyContact.swift
index 3d3d40e3..a7eb985d 100644
--- a/Sources/XXMessengerClient/Messenger/Functions/MessengerVerifyContact.swift
+++ b/Sources/XXMessengerClient/Messenger/Functions/MessengerVerifyContact.swift
@@ -1,7 +1,13 @@
+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 {
@@ -12,8 +18,54 @@ public struct MessengerVerifyContact {
 extension MessengerVerifyContact {
   public static func live(_ env: MessengerEnvironment) -> MessengerVerifyContact {
     MessengerVerifyContact { contact in
-      // TODO:
-      return false
+      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(),
+          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()
+      )
     }
   }
 }
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,
-- 
GitLab