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