From 8014ffc3be3e89de0e0e7981a95bf1fd6c9dabb7 Mon Sep 17 00:00:00 2001
From: Dariusz Rybicki <dariusz@elixxir.io>
Date: Wed, 31 Aug 2022 15:41:41 +0100
Subject: [PATCH] Register from Home

---
 Examples/xx-messenger/Package.swift           |   3 +
 .../AppFeature/AppEnvironment+Live.swift      |   9 +-
 .../Sources/HomeFeature/HomeFeature.swift     |  74 +++++-
 .../Sources/HomeFeature/HomeView.swift        |  39 ++-
 .../HomeFeatureTests/HomeFeatureTests.swift   | 222 +++++++++++++++++-
 5 files changed, 338 insertions(+), 9 deletions(-)

diff --git a/Examples/xx-messenger/Package.swift b/Examples/xx-messenger/Package.swift
index b91c4340..4ed6cfd8 100644
--- a/Examples/xx-messenger/Package.swift
+++ b/Examples/xx-messenger/Package.swift
@@ -69,6 +69,7 @@ let package = Package(
       dependencies: [
         .target(name: "AppCore"),
         .target(name: "HomeFeature"),
+        .target(name: "RegisterFeature"),
         .target(name: "RestoreFeature"),
         .target(name: "WelcomeFeature"),
         .product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
@@ -89,7 +90,9 @@ let package = Package(
       name: "HomeFeature",
       dependencies: [
         .target(name: "AppCore"),
+        .target(name: "RegisterFeature"),
         .product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
+        .product(name: "ComposablePresentation", package: "swift-composable-presentation"),
         .product(name: "XXMessengerClient", package: "elixxir-dapps-sdk-swift"),
       ],
       swiftSettings: swiftSettings
diff --git a/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift b/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift
index eec6f199..d3cc134a 100644
--- a/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift
+++ b/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift
@@ -34,7 +34,14 @@ extension AppEnvironment {
         HomeEnvironment(
           messenger: messenger,
           mainQueue: mainQueue,
-          bgQueue: bgQueue
+          bgQueue: bgQueue,
+          register: {
+            RegisterEnvironment(
+              messenger: messenger,
+              mainQueue: mainQueue,
+              bgQueue: bgQueue
+            )
+          }
         )
       }
     )
diff --git a/Examples/xx-messenger/Sources/HomeFeature/HomeFeature.swift b/Examples/xx-messenger/Sources/HomeFeature/HomeFeature.swift
index 499bed78..29abfb6b 100644
--- a/Examples/xx-messenger/Sources/HomeFeature/HomeFeature.swift
+++ b/Examples/xx-messenger/Sources/HomeFeature/HomeFeature.swift
@@ -1,37 +1,58 @@
+import Combine
 import ComposableArchitecture
+import ComposablePresentation
 import Foundation
+import RegisterFeature
 import XXClient
 import XXMessengerClient
 
 public struct HomeState: Equatable {
-  public init() {}
+  public init(
+    username: String? = nil,
+    failure: String? = nil,
+    register: RegisterState? = nil
+  ) {
+    self.username = username
+    self.failure = failure
+    self.register = register
+  }
+
+  @BindableState public var username: String?
+  @BindableState public var failure: String?
+  @BindableState public var register: RegisterState?
 }
 
-public enum HomeAction: Equatable {
+public enum HomeAction: Equatable, BindableAction {
   case start
+  case binding(BindingAction<HomeState>)
+  case register(RegisterAction)
 }
 
 public struct HomeEnvironment {
   public init(
     messenger: Messenger,
     mainQueue: AnySchedulerOf<DispatchQueue>,
-    bgQueue: AnySchedulerOf<DispatchQueue>
+    bgQueue: AnySchedulerOf<DispatchQueue>,
+    register: @escaping () -> RegisterEnvironment
   ) {
     self.messenger = messenger
     self.mainQueue = mainQueue
     self.bgQueue = bgQueue
+    self.register = register
   }
 
   public var messenger: Messenger
   public var mainQueue: AnySchedulerOf<DispatchQueue>
   public var bgQueue: AnySchedulerOf<DispatchQueue>
+  public var register: () -> RegisterEnvironment
 }
 
 extension HomeEnvironment {
   public static let unimplemented = HomeEnvironment(
     messenger: .unimplemented,
     mainQueue: .unimplemented,
-    bgQueue: .unimplemented
+    bgQueue: .unimplemented,
+    register: { .unimplemented }
   )
 }
 
@@ -39,6 +60,51 @@ public let homeReducer = Reducer<HomeState, HomeAction, HomeEnvironment>
 { state, action, env in
   switch action {
   case .start:
+    return .run { subscriber in
+      do {
+        try env.messenger.start()
+
+        if env.messenger.isConnected() == false {
+          try env.messenger.connect()
+        }
+
+        if env.messenger.isLoggedIn() == false {
+          if try env.messenger.isRegistered() == false {
+            subscriber.send(.set(\.$register, RegisterState()))
+            subscriber.send(completion: .finished)
+            return AnyCancellable {}
+          }
+          try env.messenger.logIn()
+        }
+
+        if let contact = env.messenger.e2e()?.getContact(),
+           let facts = try? contact.getFacts(),
+           let username = facts.first(where: { $0.type == 0 })?.fact {
+          subscriber.send(.set(\.$username, username))
+        }
+      } catch {
+        subscriber.send(.set(\.$failure, error.localizedDescription))
+      }
+      subscriber.send(completion: .finished)
+      return AnyCancellable {}
+    }
+    .subscribe(on: env.bgQueue)
+    .receive(on: env.mainQueue)
+    .eraseToEffect()
+
+  case .register(.finished):
+    state.register = nil
+    return Effect(value: .start)
+
+  case .binding(_), .register(_):
     return .none
   }
 }
+.binding()
+.presenting(
+  registerReducer,
+  state: .keyPath(\.register),
+  id: .notNil(),
+  action: /HomeAction.register,
+  environment: { $0.register() }
+)
diff --git a/Examples/xx-messenger/Sources/HomeFeature/HomeView.swift b/Examples/xx-messenger/Sources/HomeFeature/HomeView.swift
index 0bda29bd..e40bb1db 100644
--- a/Examples/xx-messenger/Sources/HomeFeature/HomeView.swift
+++ b/Examples/xx-messenger/Sources/HomeFeature/HomeView.swift
@@ -1,4 +1,6 @@
 import ComposableArchitecture
+import ComposablePresentation
+import RegisterFeature
 import SwiftUI
 
 public struct HomeView: View {
@@ -9,19 +11,54 @@ public struct HomeView: View {
   let store: Store<HomeState, HomeAction>
 
   struct ViewState: Equatable {
-    init(state: HomeState) {}
+    var username: String?
+    var failure: String?
+
+    init(state: HomeState) {
+      username = state.username
+      failure = state.failure
+    }
   }
 
   public var body: some View {
     WithViewStore(store.scope(state: ViewState.init)) { viewStore in
       NavigationView {
         Form {
+          if let username = viewStore.username {
+            Section {
+              Text(username)
+            } header: {
+              Text("Username")
+            }
+          }
 
+          if let failure = viewStore.failure {
+            Section {
+              Text(failure)
+              Button {
+                viewStore.send(.start)
+              } label: {
+                Text("Retry")
+              }
+            } header: {
+              Text("Error")
+            }
+          }
         }
         .navigationTitle("Home")
       }
       .navigationViewStyle(.stack)
       .task { viewStore.send(.start) }
+      .fullScreenCover(
+        store.scope(
+          state: \.register,
+          action: HomeAction.register
+        ),
+        onDismiss: {
+          viewStore.send(.set(\.$register, nil))
+        },
+        content: RegisterView.init(store:)
+      )
     }
   }
 }
diff --git a/Examples/xx-messenger/Tests/HomeFeatureTests/HomeFeatureTests.swift b/Examples/xx-messenger/Tests/HomeFeatureTests/HomeFeatureTests.swift
index 21a25bb6..efdd867f 100644
--- a/Examples/xx-messenger/Tests/HomeFeatureTests/HomeFeatureTests.swift
+++ b/Examples/xx-messenger/Tests/HomeFeatureTests/HomeFeatureTests.swift
@@ -1,16 +1,232 @@
 import ComposableArchitecture
+import RegisterFeature
 import XCTest
+import XXClient
+import XXMessengerClient
 @testable import HomeFeature
 
-@MainActor
 final class HomeFeatureTests: XCTestCase {
-  func testStart() async throws {
+  func testStartUnregistered() {
     let store = TestStore(
       initialState: HomeState(),
       reducer: homeReducer,
       environment: .unimplemented
     )
 
-    await store.send(.start)
+    let bgQueue = DispatchQueue.test
+    let mainQueue = DispatchQueue.test
+    var messengerDidStartWithTimeout: [Int] = []
+    var messengerDidConnect = 0
+
+    store.environment.bgQueue = bgQueue.eraseToAnyScheduler()
+    store.environment.mainQueue = mainQueue.eraseToAnyScheduler()
+    store.environment.messenger.start.run = { messengerDidStartWithTimeout.append($0) }
+    store.environment.messenger.isConnected.run = { false }
+    store.environment.messenger.connect.run = { messengerDidConnect += 1 }
+    store.environment.messenger.isLoggedIn.run = { false }
+    store.environment.messenger.isRegistered.run = { false }
+
+    store.send(.start)
+
+    bgQueue.advance()
+
+    XCTAssertNoDifference(messengerDidStartWithTimeout, [30_000])
+    XCTAssertNoDifference(messengerDidConnect, 1)
+
+    mainQueue.advance()
+
+    store.receive(.set(\.$register, RegisterState())) {
+      $0.register = RegisterState()
+    }
+  }
+
+  func testStartRegistered() {
+    let store = TestStore(
+      initialState: HomeState(),
+      reducer: homeReducer,
+      environment: .unimplemented
+    )
+
+    let username = "test_username"
+    let bgQueue = DispatchQueue.test
+    let mainQueue = DispatchQueue.test
+    var messengerDidStartWithTimeout: [Int] = []
+    var messengerDidConnect = 0
+    var messengerDidLogIn = 0
+
+    store.environment.bgQueue = bgQueue.eraseToAnyScheduler()
+    store.environment.mainQueue = mainQueue.eraseToAnyScheduler()
+    store.environment.messenger.start.run = { messengerDidStartWithTimeout.append($0) }
+    store.environment.messenger.isConnected.run = { false }
+    store.environment.messenger.connect.run = { messengerDidConnect += 1 }
+    store.environment.messenger.isLoggedIn.run = { false }
+    store.environment.messenger.isRegistered.run = { true }
+    store.environment.messenger.logIn.run = { messengerDidLogIn += 1 }
+    store.environment.messenger.e2e.get = {
+      var e2e = E2E.unimplemented
+      e2e.getContact.run = {
+        var contact = Contact.unimplemented(Data())
+        contact.getFactsFromContact.run = { _ in [Fact(fact: username, type: 0)] }
+        return contact
+      }
+      return e2e
+    }
+
+    store.send(.start)
+
+    bgQueue.advance()
+
+    XCTAssertNoDifference(messengerDidStartWithTimeout, [30_000])
+    XCTAssertNoDifference(messengerDidConnect, 1)
+    XCTAssertNoDifference(messengerDidLogIn, 1)
+
+    mainQueue.advance()
+
+    store.receive(.set(\.$username, username)) {
+      $0.username = username
+    }
+  }
+
+  func testRegisterFinished() {
+    let store = TestStore(
+      initialState: HomeState(
+        register: RegisterState()
+      ),
+      reducer: homeReducer,
+      environment: .unimplemented
+    )
+
+    let username = "test_username"
+    let bgQueue = DispatchQueue.test
+    let mainQueue = DispatchQueue.test
+    var messengerDidStartWithTimeout: [Int] = []
+    var messengerDidLogIn = 0
+
+    store.environment.bgQueue = bgQueue.eraseToAnyScheduler()
+    store.environment.mainQueue = mainQueue.eraseToAnyScheduler()
+    store.environment.messenger.start.run = { messengerDidStartWithTimeout.append($0) }
+    store.environment.messenger.isConnected.run = { true }
+    store.environment.messenger.isLoggedIn.run = { false }
+    store.environment.messenger.isRegistered.run = { true }
+    store.environment.messenger.logIn.run = { messengerDidLogIn += 1 }
+    store.environment.messenger.e2e.get = {
+      var e2e = E2E.unimplemented
+      e2e.getContact.run = {
+        var contact = Contact.unimplemented(Data())
+        contact.getFactsFromContact.run = { _ in [Fact(fact: username, type: 0)] }
+        return contact
+      }
+      return e2e
+    }
+
+    store.send(.register(.finished)) {
+      $0.register = nil
+    }
+
+    store.receive(.start)
+
+    bgQueue.advance()
+
+    XCTAssertNoDifference(messengerDidStartWithTimeout, [30_000])
+    XCTAssertNoDifference(messengerDidLogIn, 1)
+
+    mainQueue.advance()
+
+    store.receive(.set(\.$username, username)) {
+      $0.username = username
+    }
+  }
+
+  func testStartMessengerStartFailure() {
+    let store = TestStore(
+      initialState: HomeState(),
+      reducer: homeReducer,
+      environment: .unimplemented
+    )
+
+    struct Failure: Error {}
+    let error = Failure()
+
+    store.environment.bgQueue = .immediate
+    store.environment.mainQueue = .immediate
+    store.environment.messenger.start.run = { _ in throw error }
+
+    store.send(.start)
+
+    store.receive(.set(\.$failure, error.localizedDescription)) {
+      $0.failure = error.localizedDescription
+    }
+  }
+
+  func testStartMessengerConnectFailure() {
+    let store = TestStore(
+      initialState: HomeState(),
+      reducer: homeReducer,
+      environment: .unimplemented
+    )
+
+    struct Failure: Error {}
+    let error = Failure()
+
+    store.environment.bgQueue = .immediate
+    store.environment.mainQueue = .immediate
+    store.environment.messenger.start.run = { _ in }
+    store.environment.messenger.isConnected.run = { false }
+    store.environment.messenger.connect.run = { throw error }
+
+    store.send(.start)
+
+    store.receive(.set(\.$failure, error.localizedDescription)) {
+      $0.failure = error.localizedDescription
+    }
+  }
+
+  func testStartMessengerIsRegisteredFailure() {
+    let store = TestStore(
+      initialState: HomeState(),
+      reducer: homeReducer,
+      environment: .unimplemented
+    )
+
+    struct Failure: Error {}
+    let error = Failure()
+
+    store.environment.bgQueue = .immediate
+    store.environment.mainQueue = .immediate
+    store.environment.messenger.start.run = { _ in }
+    store.environment.messenger.isConnected.run = { true }
+    store.environment.messenger.isLoggedIn.run = { false }
+    store.environment.messenger.isRegistered.run = { throw error }
+
+    store.send(.start)
+
+    store.receive(.set(\.$failure, error.localizedDescription)) {
+      $0.failure = error.localizedDescription
+    }
+  }
+
+  func testStartMessengerLogInFailure() {
+    let store = TestStore(
+      initialState: HomeState(),
+      reducer: homeReducer,
+      environment: .unimplemented
+    )
+
+    struct Failure: Error {}
+    let error = Failure()
+
+    store.environment.bgQueue = .immediate
+    store.environment.mainQueue = .immediate
+    store.environment.messenger.start.run = { _ in }
+    store.environment.messenger.isConnected.run = { true }
+    store.environment.messenger.isLoggedIn.run = { false }
+    store.environment.messenger.isRegistered.run = { true }
+    store.environment.messenger.logIn.run = { throw error }
+
+    store.send(.start)
+
+    store.receive(.set(\.$failure, error.localizedDescription)) {
+      $0.failure = error.localizedDescription
+    }
   }
 }
-- 
GitLab