From 2b0c8fff00eb02c06564582f99aa316be8fb7691 Mon Sep 17 00:00:00 2001
From: Dariusz Rybicki <dariusz@elixxir.io>
Date: Tue, 4 Oct 2022 23:22:33 +0200
Subject: [PATCH] Implement resetting authenticated channel

---
 Examples/xx-messenger/Package.swift           |  2 +
 .../AppFeature/AppEnvironment+Live.swift      |  6 +-
 .../ResetAuthFeature/ResetAuthFeature.swift   | 75 +++++++++++++++++--
 .../ResetAuthFeature/ResetAuthView.swift      | 45 ++++++++++-
 .../ResetAuthFeatureTests.swift               | 68 ++++++++++++++++-
 5 files changed, 185 insertions(+), 11 deletions(-)

diff --git a/Examples/xx-messenger/Package.swift b/Examples/xx-messenger/Package.swift
index f7b1252e..8e7e2984 100644
--- a/Examples/xx-messenger/Package.swift
+++ b/Examples/xx-messenger/Package.swift
@@ -316,8 +316,10 @@ let package = Package(
     .target(
       name: "ResetAuthFeature",
       dependencies: [
+        .target(name: "AppCore"),
         .product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
         .product(name: "XXClient", package: "elixxir-dapps-sdk-swift"),
+        .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 0e77635b..fef073d1 100644
--- a/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift
+++ b/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift
@@ -79,7 +79,11 @@ extension AppEnvironment {
         )
       },
       resetAuth: {
-        ResetAuthEnvironment()
+        ResetAuthEnvironment(
+          messenger: messenger,
+          mainQueue: mainQueue,
+          bgQueue: bgQueue
+        )
       },
       chat: {
         ChatEnvironment(
diff --git a/Examples/xx-messenger/Sources/ResetAuthFeature/ResetAuthFeature.swift b/Examples/xx-messenger/Sources/ResetAuthFeature/ResetAuthFeature.swift
index b1408662..d4acb740 100644
--- a/Examples/xx-messenger/Sources/ResetAuthFeature/ResetAuthFeature.swift
+++ b/Examples/xx-messenger/Sources/ResetAuthFeature/ResetAuthFeature.swift
@@ -1,27 +1,90 @@
 import ComposableArchitecture
+import Foundation
 import XCTestDynamicOverlay
 import XXClient
+import XXMessengerClient
 
 public struct ResetAuthState: Equatable {
   public init(
-    partner: Contact
+    partner: Contact,
+    isResetting: Bool = false,
+    failure: String? = nil,
+    didReset: Bool = false
   ) {
     self.partner = partner
+    self.isResetting = isResetting
+    self.failure = failure
+    self.didReset = didReset
   }
 
-  var partner: Contact
+  public var partner: Contact
+  public var isResetting: Bool
+  public var failure: String?
+  public var didReset: Bool
 }
 
-public enum ResetAuthAction: Equatable {}
+public enum ResetAuthAction: Equatable {
+  case resetTapped
+  case didReset
+  case didFail(String)
+}
 
 public struct ResetAuthEnvironment {
-  public init() {}
+  public init(
+    messenger: Messenger,
+    mainQueue: AnySchedulerOf<DispatchQueue>,
+    bgQueue: AnySchedulerOf<DispatchQueue>
+  ) {
+    self.messenger = messenger
+    self.mainQueue = mainQueue
+    self.bgQueue = bgQueue
+  }
+
+  public var messenger: Messenger
+  public var mainQueue: AnySchedulerOf<DispatchQueue>
+  public var bgQueue: AnySchedulerOf<DispatchQueue>
 }
 
 #if DEBUG
 extension ResetAuthEnvironment {
-  public static let unimplemented = ResetAuthEnvironment()
+  public static let unimplemented = ResetAuthEnvironment(
+    messenger: .unimplemented,
+    mainQueue: .unimplemented,
+    bgQueue: .unimplemented
+  )
 }
 #endif
 
-public let resetAuthReducer = Reducer<ResetAuthState, ResetAuthAction, ResetAuthEnvironment>.empty
+public let resetAuthReducer = Reducer<ResetAuthState, ResetAuthAction, ResetAuthEnvironment>
+{ state, action, env in
+  switch action {
+  case .resetTapped:
+    state.isResetting = true
+    state.didReset = false
+    state.failure = nil
+    return Effect.result { [state] in
+      do {
+        let e2e = try env.messenger.e2e.tryGet()
+        _ = try e2e.resetAuthenticatedChannel(partner: state.partner)
+        return .success(.didReset)
+      } catch {
+        return .success(.didFail(error.localizedDescription))
+      }
+    }
+    .subscribe(on: env.bgQueue)
+    .receive(on: env.mainQueue)
+    .eraseToEffect()
+
+  case .didReset:
+    state.isResetting = false
+    state.didReset = true
+    state.failure = nil
+    return .none
+
+  case .didFail(let failure):
+    state.isResetting = false
+    state.didReset = false
+    state.failure = failure
+    return .none
+  }
+}
diff --git a/Examples/xx-messenger/Sources/ResetAuthFeature/ResetAuthView.swift b/Examples/xx-messenger/Sources/ResetAuthFeature/ResetAuthView.swift
index 76677a1c..7b384efb 100644
--- a/Examples/xx-messenger/Sources/ResetAuthFeature/ResetAuthView.swift
+++ b/Examples/xx-messenger/Sources/ResetAuthFeature/ResetAuthView.swift
@@ -1,3 +1,4 @@
+import AppCore
 import ComposableArchitecture
 import SwiftUI
 
@@ -9,13 +10,53 @@ public struct ResetAuthView: View {
   let store: Store<ResetAuthState, ResetAuthAction>
 
   struct ViewState: Equatable {
-    init(state: ResetAuthState) {}
+    init(state: ResetAuthState) {
+      contactID = try? state.partner.getId()
+      isResetting = state.isResetting
+      failure = state.failure
+      didReset = state.didReset
+    }
+
+    var contactID: Data?
+    var isResetting: Bool
+    var failure: String?
+    var didReset: Bool
   }
 
   public var body: some View {
     WithViewStore(store, observe: ViewState.init) { viewStore in
       Form {
-        Text("Unimplemented")
+        Section {
+          Text(viewStore.contactID?.hexString() ?? "")
+            .font(.footnote.monospaced())
+            .textSelection(.enabled)
+        } header: {
+          Label("ID", systemImage: "number")
+        }
+
+        Button {
+          viewStore.send(.resetTapped)
+        } label: {
+          HStack {
+            Text("Reset authenticated channel")
+            Spacer()
+            if viewStore.isResetting {
+              ProgressView()
+            } else if viewStore.didReset {
+              Image(systemName: "checkmark")
+                .foregroundColor(.green)
+            }
+          }
+        }
+        .disabled(viewStore.isResetting)
+
+        if let failure = viewStore.failure {
+          Section {
+            Text(failure)
+          } header: {
+            Text("Error")
+          }
+        }
       }
       .navigationTitle("Reset auth")
     }
diff --git a/Examples/xx-messenger/Tests/ResetAuthFeatureTests/ResetAuthFeatureTests.swift b/Examples/xx-messenger/Tests/ResetAuthFeatureTests/ResetAuthFeatureTests.swift
index 3721b662..46b5dc9e 100644
--- a/Examples/xx-messenger/Tests/ResetAuthFeatureTests/ResetAuthFeatureTests.swift
+++ b/Examples/xx-messenger/Tests/ResetAuthFeatureTests/ResetAuthFeatureTests.swift
@@ -1,8 +1,72 @@
+import ComposableArchitecture
+import CustomDump
 import XCTest
+import XXClient
 @testable import ResetAuthFeature
 
 final class ResetAuthFeatureTests: XCTestCase {
-  func testExample() {
-    XCTAssert(true)
+  func testReset() {
+    let partnerData = "contact-data".data(using: .utf8)!
+    let partner = Contact.unimplemented(partnerData)
+
+    var didResetAuthChannel: [Contact] = []
+
+    let store = TestStore(
+      initialState: ResetAuthState(
+        partner: partner
+      ),
+      reducer: resetAuthReducer,
+      environment: .unimplemented
+    )
+    store.environment.mainQueue = .immediate
+    store.environment.bgQueue = .immediate
+    store.environment.messenger.e2e.get = {
+      var e2e: E2E = .unimplemented
+      e2e.resetAuthenticatedChannel.run = { contact in
+        didResetAuthChannel.append(contact)
+        return 0
+      }
+      return e2e
+    }
+
+    store.send(.resetTapped) {
+      $0.isResetting = true
+    }
+
+    XCTAssertNoDifference(didResetAuthChannel, [partner])
+
+    store.receive(.didReset) {
+      $0.isResetting = false
+      $0.didReset = true
+    }
+  }
+
+  func testResetFailure() {
+    struct Failure: Error {}
+    let failure = Failure()
+
+    let store = TestStore(
+      initialState: ResetAuthState(
+        partner: .unimplemented(Data())
+      ),
+      reducer: resetAuthReducer,
+      environment: .unimplemented
+    )
+    store.environment.mainQueue = .immediate
+    store.environment.bgQueue = .immediate
+    store.environment.messenger.e2e.get = {
+      var e2e: E2E = .unimplemented
+      e2e.resetAuthenticatedChannel.run = { _ in throw failure }
+      return e2e
+    }
+
+    store.send(.resetTapped) {
+      $0.isResetting = true
+    }
+
+    store.receive(.didFail(failure.localizedDescription)) {
+      $0.isResetting = false
+      $0.failure = failure.localizedDescription
+    }
   }
 }
-- 
GitLab