diff --git a/Examples/xx-messenger/.swiftpm/xcode/xcshareddata/xcschemes/CheckContactAuthFeature.xcscheme b/Examples/xx-messenger/.swiftpm/xcode/xcshareddata/xcschemes/CheckContactAuthFeature.xcscheme new file mode 100644 index 0000000000000000000000000000000000000000..17099adba157e5f3e5b943744dbe3cdf5b17706d --- /dev/null +++ b/Examples/xx-messenger/.swiftpm/xcode/xcshareddata/xcschemes/CheckContactAuthFeature.xcscheme @@ -0,0 +1,78 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Scheme + LastUpgradeVersion = "1400" + version = "1.3"> + <BuildAction + parallelizeBuildables = "YES" + buildImplicitDependencies = "YES"> + <BuildActionEntries> + <BuildActionEntry + buildForTesting = "YES" + buildForRunning = "YES" + buildForProfiling = "YES" + buildForArchiving = "YES" + buildForAnalyzing = "YES"> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "CheckContactAuthFeature" + BuildableName = "CheckContactAuthFeature" + BlueprintName = "CheckContactAuthFeature" + ReferencedContainer = "container:"> + </BuildableReference> + </BuildActionEntry> + </BuildActionEntries> + </BuildAction> + <TestAction + buildConfiguration = "Debug" + selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" + selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + shouldUseLaunchSchemeArgsEnv = "YES" + codeCoverageEnabled = "YES"> + <Testables> + <TestableReference + skipped = "NO"> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "CheckContactAuthFeatureTests" + BuildableName = "CheckContactAuthFeatureTests" + BlueprintName = "CheckContactAuthFeatureTests" + ReferencedContainer = "container:"> + </BuildableReference> + </TestableReference> + </Testables> + </TestAction> + <LaunchAction + buildConfiguration = "Debug" + selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" + selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + launchStyle = "0" + useCustomWorkingDirectory = "NO" + ignoresPersistentStateOnLaunch = "NO" + debugDocumentVersioning = "YES" + debugServiceExtension = "internal" + allowLocationSimulation = "YES"> + </LaunchAction> + <ProfileAction + buildConfiguration = "Release" + shouldUseLaunchSchemeArgsEnv = "YES" + savedToolIdentifier = "" + useCustomWorkingDirectory = "NO" + debugDocumentVersioning = "YES"> + <MacroExpansion> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "CheckContactAuthFeature" + BuildableName = "CheckContactAuthFeature" + BlueprintName = "CheckContactAuthFeature" + ReferencedContainer = "container:"> + </BuildableReference> + </MacroExpansion> + </ProfileAction> + <AnalyzeAction + buildConfiguration = "Debug"> + </AnalyzeAction> + <ArchiveAction + buildConfiguration = "Release" + revealArchiveInOrganizer = "YES"> + </ArchiveAction> +</Scheme> diff --git a/Examples/xx-messenger/.swiftpm/xcode/xcshareddata/xcschemes/ConfirmRequestFeature.xcscheme b/Examples/xx-messenger/.swiftpm/xcode/xcshareddata/xcschemes/ConfirmRequestFeature.xcscheme new file mode 100644 index 0000000000000000000000000000000000000000..a3026c70355eab27e54fd62ebe8af6b508a45acd --- /dev/null +++ b/Examples/xx-messenger/.swiftpm/xcode/xcshareddata/xcschemes/ConfirmRequestFeature.xcscheme @@ -0,0 +1,78 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Scheme + LastUpgradeVersion = "1400" + version = "1.3"> + <BuildAction + parallelizeBuildables = "YES" + buildImplicitDependencies = "YES"> + <BuildActionEntries> + <BuildActionEntry + buildForTesting = "YES" + buildForRunning = "YES" + buildForProfiling = "YES" + buildForArchiving = "YES" + buildForAnalyzing = "YES"> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "ConfirmRequestFeature" + BuildableName = "ConfirmRequestFeature" + BlueprintName = "ConfirmRequestFeature" + ReferencedContainer = "container:"> + </BuildableReference> + </BuildActionEntry> + </BuildActionEntries> + </BuildAction> + <TestAction + buildConfiguration = "Debug" + selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" + selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + shouldUseLaunchSchemeArgsEnv = "YES" + codeCoverageEnabled = "YES"> + <Testables> + <TestableReference + skipped = "NO"> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "ConfirmRequestFeatureTests" + BuildableName = "ConfirmRequestFeatureTests" + BlueprintName = "ConfirmRequestFeatureTests" + ReferencedContainer = "container:"> + </BuildableReference> + </TestableReference> + </Testables> + </TestAction> + <LaunchAction + buildConfiguration = "Debug" + selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" + selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + launchStyle = "0" + useCustomWorkingDirectory = "NO" + ignoresPersistentStateOnLaunch = "NO" + debugDocumentVersioning = "YES" + debugServiceExtension = "internal" + allowLocationSimulation = "YES"> + </LaunchAction> + <ProfileAction + buildConfiguration = "Release" + shouldUseLaunchSchemeArgsEnv = "YES" + savedToolIdentifier = "" + useCustomWorkingDirectory = "NO" + debugDocumentVersioning = "YES"> + <MacroExpansion> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "ConfirmRequestFeature" + BuildableName = "ConfirmRequestFeature" + BlueprintName = "ConfirmRequestFeature" + ReferencedContainer = "container:"> + </BuildableReference> + </MacroExpansion> + </ProfileAction> + <AnalyzeAction + buildConfiguration = "Debug"> + </AnalyzeAction> + <ArchiveAction + buildConfiguration = "Release" + revealArchiveInOrganizer = "YES"> + </ArchiveAction> +</Scheme> diff --git a/Examples/xx-messenger/.swiftpm/xcode/xcshareddata/xcschemes/VerifyContactFeature.xcscheme b/Examples/xx-messenger/.swiftpm/xcode/xcshareddata/xcschemes/VerifyContactFeature.xcscheme new file mode 100644 index 0000000000000000000000000000000000000000..54cf617e7cd04c69782ad98a9873054c02982ec1 --- /dev/null +++ b/Examples/xx-messenger/.swiftpm/xcode/xcshareddata/xcschemes/VerifyContactFeature.xcscheme @@ -0,0 +1,78 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Scheme + LastUpgradeVersion = "1400" + version = "1.3"> + <BuildAction + parallelizeBuildables = "YES" + buildImplicitDependencies = "YES"> + <BuildActionEntries> + <BuildActionEntry + buildForTesting = "YES" + buildForRunning = "YES" + buildForProfiling = "YES" + buildForArchiving = "YES" + buildForAnalyzing = "YES"> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "VerifyContactFeature" + BuildableName = "VerifyContactFeature" + BlueprintName = "VerifyContactFeature" + ReferencedContainer = "container:"> + </BuildableReference> + </BuildActionEntry> + </BuildActionEntries> + </BuildAction> + <TestAction + buildConfiguration = "Debug" + selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" + selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + shouldUseLaunchSchemeArgsEnv = "YES" + codeCoverageEnabled = "YES"> + <Testables> + <TestableReference + skipped = "NO"> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "VerifyContactFeatureTests" + BuildableName = "VerifyContactFeatureTests" + BlueprintName = "VerifyContactFeatureTests" + ReferencedContainer = "container:"> + </BuildableReference> + </TestableReference> + </Testables> + </TestAction> + <LaunchAction + buildConfiguration = "Debug" + selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" + selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + launchStyle = "0" + useCustomWorkingDirectory = "NO" + ignoresPersistentStateOnLaunch = "NO" + debugDocumentVersioning = "YES" + debugServiceExtension = "internal" + allowLocationSimulation = "YES"> + </LaunchAction> + <ProfileAction + buildConfiguration = "Release" + shouldUseLaunchSchemeArgsEnv = "YES" + savedToolIdentifier = "" + useCustomWorkingDirectory = "NO" + debugDocumentVersioning = "YES"> + <MacroExpansion> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "VerifyContactFeature" + BuildableName = "VerifyContactFeature" + BlueprintName = "VerifyContactFeature" + ReferencedContainer = "container:"> + </BuildableReference> + </MacroExpansion> + </ProfileAction> + <AnalyzeAction + buildConfiguration = "Debug"> + </AnalyzeAction> + <ArchiveAction + buildConfiguration = "Release" + revealArchiveInOrganizer = "YES"> + </ArchiveAction> +</Scheme> diff --git a/Examples/xx-messenger/Package.swift b/Examples/xx-messenger/Package.swift index 57081f3ef7c5ae6abcf0cb130aa994d8a6460abc..5e9a48c35a590e8cb106ef2a0d8510f37779b977 100644 --- a/Examples/xx-messenger/Package.swift +++ b/Examples/xx-messenger/Package.swift @@ -20,6 +20,8 @@ let package = Package( products: [ .library(name: "AppCore", targets: ["AppCore"]), .library(name: "AppFeature", targets: ["AppFeature"]), + .library(name: "CheckContactAuthFeature", targets: ["CheckContactAuthFeature"]), + .library(name: "ConfirmRequestFeature", targets: ["ConfirmRequestFeature"]), .library(name: "ContactFeature", targets: ["ContactFeature"]), .library(name: "ContactsFeature", targets: ["ContactsFeature"]), .library(name: "HomeFeature", targets: ["HomeFeature"]), @@ -27,6 +29,7 @@ let package = Package( .library(name: "RestoreFeature", targets: ["RestoreFeature"]), .library(name: "SendRequestFeature", targets: ["SendRequestFeature"]), .library(name: "UserSearchFeature", targets: ["UserSearchFeature"]), + .library(name: "VerifyContactFeature", targets: ["VerifyContactFeature"]), .library(name: "WelcomeFeature", targets: ["WelcomeFeature"]), ], dependencies: [ @@ -73,6 +76,8 @@ let package = Package( name: "AppFeature", dependencies: [ .target(name: "AppCore"), + .target(name: "CheckContactAuthFeature"), + .target(name: "ConfirmRequestFeature"), .target(name: "ContactFeature"), .target(name: "ContactsFeature"), .target(name: "HomeFeature"), @@ -80,6 +85,7 @@ let package = Package( .target(name: "RestoreFeature"), .target(name: "SendRequestFeature"), .target(name: "UserSearchFeature"), + .target(name: "VerifyContactFeature"), .target(name: "WelcomeFeature"), .product(name: "ComposableArchitecture", package: "swift-composable-architecture"), .product(name: "ComposablePresentation", package: "swift-composable-presentation"), @@ -95,11 +101,46 @@ let package = Package( ], swiftSettings: swiftSettings ), + .target( + name: "CheckContactAuthFeature", + 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"), + .product(name: "XXModels", package: "client-ios-db"), + ] + ), + .testTarget( + name: "CheckContactAuthFeatureTests", + dependencies: [ + .target(name: "CheckContactAuthFeature"), + ] + ), + .target( + name: "ConfirmRequestFeature", + 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"), + .product(name: "XXModels", package: "client-ios-db"), + ] + ), + .testTarget( + name: "ConfirmRequestFeatureTests", + dependencies: [ + .target(name: "ConfirmRequestFeature"), + ] + ), .target( name: "ContactFeature", dependencies: [ .target(name: "AppCore"), + .target(name: "CheckContactAuthFeature"), + .target(name: "ConfirmRequestFeature"), .target(name: "SendRequestFeature"), + .target(name: "VerifyContactFeature"), .product(name: "ComposableArchitecture", package: "swift-composable-architecture"), .product(name: "ComposablePresentation", package: "swift-composable-presentation"), .product(name: "XXMessengerClient", package: "elixxir-dapps-sdk-swift"), @@ -223,6 +264,22 @@ let package = Package( ], swiftSettings: swiftSettings ), + .target( + name: "VerifyContactFeature", + 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"), + .product(name: "XXModels", package: "client-ios-db"), + ] + ), + .testTarget( + name: "VerifyContactFeatureTests", + dependencies: [ + .target(name: "VerifyContactFeature"), + ] + ), .target( name: "WelcomeFeature", dependencies: [ diff --git a/Examples/xx-messenger/Project/XXMessenger.xcodeproj/xcshareddata/xcschemes/XXMessenger.xcscheme b/Examples/xx-messenger/Project/XXMessenger.xcodeproj/xcshareddata/xcschemes/XXMessenger.xcscheme index 9d31f6f70a3f7dd3e87b943466febea60557f6af..0e5e54ad091bf310eb399f185b4c8680fed98e07 100644 --- a/Examples/xx-messenger/Project/XXMessenger.xcodeproj/xcshareddata/xcschemes/XXMessenger.xcscheme +++ b/Examples/xx-messenger/Project/XXMessenger.xcodeproj/xcshareddata/xcschemes/XXMessenger.xcscheme @@ -49,6 +49,26 @@ ReferencedContainer = "container:.."> </BuildableReference> </TestableReference> + <TestableReference + skipped = "NO"> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "CheckContactAuthFeatureTests" + BuildableName = "CheckContactAuthFeatureTests" + BlueprintName = "CheckContactAuthFeatureTests" + ReferencedContainer = "container:.."> + </BuildableReference> + </TestableReference> + <TestableReference + skipped = "NO"> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "ConfirmRequestFeatureTests" + BuildableName = "ConfirmRequestFeatureTests" + BlueprintName = "ConfirmRequestFeatureTests" + ReferencedContainer = "container:.."> + </BuildableReference> + </TestableReference> <TestableReference skipped = "NO"> <BuildableReference @@ -119,6 +139,16 @@ ReferencedContainer = "container:.."> </BuildableReference> </TestableReference> + <TestableReference + skipped = "NO"> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "VerifyContactFeatureTests" + BuildableName = "VerifyContactFeatureTests" + BlueprintName = "VerifyContactFeatureTests" + ReferencedContainer = "container:.."> + </BuildableReference> + </TestableReference> <TestableReference skipped = "NO"> <BuildableReference diff --git a/Examples/xx-messenger/Sources/AppCore/AuthCallbackHandler/AuthCallbackHandlerRequest.swift b/Examples/xx-messenger/Sources/AppCore/AuthCallbackHandler/AuthCallbackHandlerRequest.swift index d8ac3d18a496b64427b293fcc476c341d93353cb..6d1943e923732bc6dbdafe053deeb962191b7d13 100644 --- a/Examples/xx-messenger/Sources/AppCore/AuthCallbackHandler/AuthCallbackHandlerRequest.swift +++ b/Examples/xx-messenger/Sources/AppCore/AuthCallbackHandler/AuthCallbackHandlerRequest.swift @@ -15,7 +15,6 @@ public struct AuthCallbackHandlerRequest { extension AuthCallbackHandlerRequest { public static func live( db: DBManagerGetDB, - messenger: Messenger, now: @escaping () -> Date ) -> AuthCallbackHandlerRequest { AuthCallbackHandlerRequest { xxContact in @@ -28,21 +27,9 @@ extension AuthCallbackHandlerRequest { 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.authStatus = .stranger dbContact.createdAt = now() 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) - } 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 5267fcc3f1a42445702386278b8fbeeb600785ae..7adbcae89b9a90d56d2297c29f63ba4be3700367 100644 --- a/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift +++ b/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift @@ -1,4 +1,6 @@ import AppCore +import CheckContactAuthFeature +import ConfirmRequestFeature import ContactFeature import ContactsFeature import Foundation @@ -7,6 +9,7 @@ import RegisterFeature import RestoreFeature import SendRequestFeature import UserSearchFeature +import VerifyContactFeature import WelcomeFeature import XXMessengerClient import XXModels @@ -18,11 +21,7 @@ extension AppEnvironment { let messenger = Messenger.live(messengerEnv) let authHandler = AuthCallbackHandler.live( messenger: messenger, - handleRequest: .live( - db: dbManager.getDB, - messenger: messenger, - now: Date.init - ), + handleRequest: .live(db: dbManager.getDB, now: Date.init), handleConfirm: .live(db: dbManager.getDB), handleReset: .live(db: dbManager.getDB) ) @@ -41,6 +40,30 @@ extension AppEnvironment { mainQueue: mainQueue, bgQueue: bgQueue ) + }, + verifyContact: { + VerifyContactEnvironment( + messenger: messenger, + db: dbManager.getDB, + mainQueue: mainQueue, + bgQueue: bgQueue + ) + }, + confirmRequest: { + ConfirmRequestEnvironment( + messenger: messenger, + db: dbManager.getDB, + mainQueue: mainQueue, + bgQueue: bgQueue + ) + }, + checkAuth: { + CheckContactAuthEnvironment( + messenger: messenger, + db: dbManager.getDB, + mainQueue: mainQueue, + bgQueue: bgQueue + ) } ) diff --git a/Examples/xx-messenger/Sources/CheckContactAuthFeature/CheckContactAuthFeature.swift b/Examples/xx-messenger/Sources/CheckContactAuthFeature/CheckContactAuthFeature.swift new file mode 100644 index 0000000000000000000000000000000000000000..1f768be8d1cba4f20c1f2db65253788358a66023 --- /dev/null +++ b/Examples/xx-messenger/Sources/CheckContactAuthFeature/CheckContactAuthFeature.swift @@ -0,0 +1,94 @@ +import AppCore +import ComposableArchitecture +import Foundation +import XCTestDynamicOverlay +import XXClient +import XXMessengerClient +import XXModels + +public struct CheckContactAuthState: Equatable { + public enum Result: Equatable { + case success(Bool) + case failure(String) + } + + public init( + contact: XXClient.Contact, + isChecking: Bool = false, + result: Result? = nil + ) { + self.contact = contact + self.isChecking = isChecking + self.result = result + } + + public var contact: XXClient.Contact + public var isChecking: Bool + public var result: Result? +} + +public enum CheckContactAuthAction: Equatable { + case checkTapped + case didCheck(CheckContactAuthState.Result) +} + +public struct CheckContactAuthEnvironment { + public init( + messenger: Messenger, + db: DBManagerGetDB, + mainQueue: AnySchedulerOf<DispatchQueue>, + bgQueue: AnySchedulerOf<DispatchQueue> + ) { + self.messenger = messenger + self.db = db + self.mainQueue = mainQueue + self.bgQueue = bgQueue + } + + public var messenger: Messenger + public var db: DBManagerGetDB + public var mainQueue: AnySchedulerOf<DispatchQueue> + public var bgQueue: AnySchedulerOf<DispatchQueue> +} + +#if DEBUG +extension CheckContactAuthEnvironment { + public static let unimplemented = CheckContactAuthEnvironment( + messenger: .unimplemented, + db: .unimplemented, + mainQueue: .unimplemented, + bgQueue: .unimplemented + ) +} +#endif + +public let checkContactAuthReducer = Reducer<CheckContactAuthState, CheckContactAuthAction, CheckContactAuthEnvironment> +{ state, action, env in + switch action { + case .checkTapped: + state.isChecking = true + state.result = nil + return Effect.result { [state] in + do { + let e2e = try env.messenger.e2e.tryGet() + let contactId = try state.contact.getId() + let result = try e2e.hasAuthenticatedChannel(partnerId: contactId) + try env.db().bulkUpdateContacts.callAsFunction( + .init(id: [contactId]), + .init(authStatus: result ? .friend : .stranger) + ) + return .success(.didCheck(.success(result))) + } catch { + return .success(.didCheck(.failure(error.localizedDescription))) + } + } + .subscribe(on: env.bgQueue) + .receive(on: env.mainQueue) + .eraseToEffect() + + case .didCheck(let result): + state.isChecking = false + state.result = result + return .none + } +} diff --git a/Examples/xx-messenger/Sources/CheckContactAuthFeature/CheckContactAuthView.swift b/Examples/xx-messenger/Sources/CheckContactAuthFeature/CheckContactAuthView.swift new file mode 100644 index 0000000000000000000000000000000000000000..dd2e7894e256c67f4a1ddcf421877dcbfbac63cc --- /dev/null +++ b/Examples/xx-messenger/Sources/CheckContactAuthFeature/CheckContactAuthView.swift @@ -0,0 +1,101 @@ +import ComposableArchitecture +import SwiftUI +import XXClient + +public struct CheckContactAuthView: View { + public init(store: Store<CheckContactAuthState, CheckContactAuthAction>) { + self.store = store + } + + let store: Store<CheckContactAuthState, CheckContactAuthAction> + + struct ViewState: Equatable { + var username: String? + var email: String? + var phone: String? + var isChecking: Bool + var result: CheckContactAuthState.Result? + + init(state: CheckContactAuthState) { + username = try? state.contact.getFact(.username)?.value + email = try? state.contact.getFact(.email)?.value + phone = try? state.contact.getFact(.phone)?.value + isChecking = state.isChecking + result = state.result + } + } + + public var body: some View { + WithViewStore(store.scope(state: ViewState.init)) { viewStore in + Form { + Section { + Label(viewStore.username ?? "", systemImage: "person") + Label(viewStore.email ?? "", systemImage: "envelope") + Label(viewStore.phone ?? "", systemImage: "phone") + } header: { + Text("Facts") + } + + Section { + Button { + viewStore.send(.checkTapped) + } label: { + HStack { + Text("Check") + Spacer() + if viewStore.isChecking { + ProgressView() + } else { + Image(systemName: "play") + } + } + } + .disabled(viewStore.isChecking) + } + + if let result = viewStore.result { + Section { + HStack { + switch result { + case .success(true): + Text("Authorized") + Spacer() + Image(systemName: "person.fill.checkmark") + + case .success(false): + Text("Not authorized") + Spacer() + Image(systemName: "person.fill.xmark") + + case .failure(_): + Text("Checking status failed") + Spacer() + Image(systemName: "xmark") + } + } + if case .failure(let failure) = result { + Text(failure) + } + } header: { + Text("Result") + } + } + } + .navigationTitle("Check connection") + } + } +} + +#if DEBUG +public struct CheckContactAuthView_Previews: PreviewProvider { + public static var previews: some View { + CheckContactAuthView(store: Store( + initialState: CheckContactAuthState( + contact: .unimplemented("contact-data".data(using: .utf8)!) + ), + reducer: .empty, + environment: () + )) + } +} +#endif diff --git a/Examples/xx-messenger/Sources/ConfirmRequestFeature/ConfirmRequestFeature.swift b/Examples/xx-messenger/Sources/ConfirmRequestFeature/ConfirmRequestFeature.swift new file mode 100644 index 0000000000000000000000000000000000000000..7cc40da0b7e268ce6ca78bb1b57b07e00784b148 --- /dev/null +++ b/Examples/xx-messenger/Sources/ConfirmRequestFeature/ConfirmRequestFeature.swift @@ -0,0 +1,98 @@ +import AppCore +import ComposableArchitecture +import Foundation +import XCTestDynamicOverlay +import XXClient +import XXMessengerClient +import XXModels + +public struct ConfirmRequestState: Equatable { + public enum Result: Equatable { + case success + case failure(String) + } + + public init( + contact: XXClient.Contact, + isConfirming: Bool = false, + result: Result? = nil + ) { + self.contact = contact + self.isConfirming = isConfirming + self.result = result + } + + public var contact: XXClient.Contact + public var isConfirming: Bool + public var result: Result? +} + +public enum ConfirmRequestAction: Equatable { + case confirmTapped + case didConfirm(ConfirmRequestState.Result) +} + +public struct ConfirmRequestEnvironment { + public init( + messenger: Messenger, + db: DBManagerGetDB, + mainQueue: AnySchedulerOf<DispatchQueue>, + bgQueue: AnySchedulerOf<DispatchQueue> + ) { + self.messenger = messenger + self.db = db + self.mainQueue = mainQueue + self.bgQueue = bgQueue + } + + public var messenger: Messenger + public var db: DBManagerGetDB + public var mainQueue: AnySchedulerOf<DispatchQueue> + public var bgQueue: AnySchedulerOf<DispatchQueue> +} + +#if DEBUG +extension ConfirmRequestEnvironment { + public static let unimplemented = ConfirmRequestEnvironment( + messenger: .unimplemented, + db: .unimplemented, + mainQueue: .unimplemented, + bgQueue: .unimplemented + ) +} +#endif + +public let confirmRequestReducer = Reducer<ConfirmRequestState, ConfirmRequestAction, ConfirmRequestEnvironment> +{ state, action, env in + switch action { + case .confirmTapped: + state.isConfirming = true + state.result = nil + return Effect.result { [state] in + func updateStatus(_ status: XXModels.Contact.AuthStatus) throws { + try env.db().bulkUpdateContacts.callAsFunction( + .init(id: [try state.contact.getId()]), + .init(authStatus: status) + ) + } + do { + try updateStatus(.confirming) + let e2e = try env.messenger.e2e.tryGet() + _ = try e2e.confirmReceivedRequest(partner: state.contact) + try updateStatus(.friend) + return .success(.didConfirm(.success)) + } catch { + try? updateStatus(.confirmationFailed) + return .success(.didConfirm(.failure(error.localizedDescription))) + } + } + .subscribe(on: env.bgQueue) + .receive(on: env.mainQueue) + .eraseToEffect() + + case .didConfirm(let result): + state.isConfirming = false + state.result = result + return .none + } +} diff --git a/Examples/xx-messenger/Sources/ConfirmRequestFeature/ConfirmRequestView.swift b/Examples/xx-messenger/Sources/ConfirmRequestFeature/ConfirmRequestView.swift new file mode 100644 index 0000000000000000000000000000000000000000..90ebc70e4490d51792db636a47d2e02b318f5820 --- /dev/null +++ b/Examples/xx-messenger/Sources/ConfirmRequestFeature/ConfirmRequestView.swift @@ -0,0 +1,95 @@ +import ComposableArchitecture +import SwiftUI + +public struct ConfirmRequestView: View { + public init(store: Store<ConfirmRequestState, ConfirmRequestAction>) { + self.store = store + } + + let store: Store<ConfirmRequestState, ConfirmRequestAction> + + struct ViewState: Equatable { + var username: String? + var email: String? + var phone: String? + var isConfirming: Bool + var result: ConfirmRequestState.Result? + + init(state: ConfirmRequestState) { + username = try? state.contact.getFact(.username)?.value + email = try? state.contact.getFact(.email)?.value + phone = try? state.contact.getFact(.phone)?.value + isConfirming = state.isConfirming + result = state.result + } + } + + public var body: some View { + WithViewStore(store, observe: ViewState.init) { viewStore in + Form { + Section { + Label(viewStore.username ?? "", systemImage: "person") + Label(viewStore.email ?? "", systemImage: "envelope") + Label(viewStore.phone ?? "", systemImage: "phone") + } header: { + Text("Facts") + } + + Section { + Button { + viewStore.send(.confirmTapped) + } label: { + HStack { + Text("Confirm") + Spacer() + if viewStore.isConfirming { + ProgressView() + } else { + Image(systemName: "checkmark") + } + } + } + .disabled(viewStore.isConfirming) + } + + if let result = viewStore.result { + Section { + HStack { + switch result { + case .success: + Text("Request confirmed") + Spacer() + Image(systemName: "person.fill.checkmark") + + case .failure(_): + Text("Confirming request failed") + Spacer() + Image(systemName: "xmark") + } + } + if case .failure(let failure) = result { + Text(failure) + } + } header: { + Text("Result") + } + } + } + .navigationTitle("Confirm request") + } + } +} + +#if DEBUG +public struct ConfirmRequestView_Previews: PreviewProvider { + public static var previews: some View { + ConfirmRequestView(store: Store( + initialState: ConfirmRequestState( + contact: .unimplemented("contact-data".data(using: .utf8)!) + ), + reducer: .empty, + environment: () + )) + } +} +#endif diff --git a/Examples/xx-messenger/Sources/ContactFeature/ContactFeature.swift b/Examples/xx-messenger/Sources/ContactFeature/ContactFeature.swift index 9f6ef294f5a8b35bdc7e7d0300ce05fd84ae1823..545cb8fb520e9f1a99e691855c78dd1b166e98a0 100644 --- a/Examples/xx-messenger/Sources/ContactFeature/ContactFeature.swift +++ b/Examples/xx-messenger/Sources/ContactFeature/ContactFeature.swift @@ -1,8 +1,11 @@ import AppCore +import CheckContactAuthFeature import ComposableArchitecture import ComposablePresentation +import ConfirmRequestFeature import Foundation import SendRequestFeature +import VerifyContactFeature import XCTestDynamicOverlay import XXClient import XXMessengerClient @@ -16,7 +19,10 @@ public struct ContactState: Equatable { importUsername: Bool = true, importEmail: Bool = true, importPhone: Bool = true, - sendRequest: SendRequestState? = nil + sendRequest: SendRequestState? = nil, + verifyContact: VerifyContactState? = nil, + confirmRequest: ConfirmRequestState? = nil, + checkAuth: CheckContactAuthState? = nil ) { self.id = id self.dbContact = dbContact @@ -25,6 +31,9 @@ public struct ContactState: Equatable { self.importEmail = importEmail self.importPhone = importPhone self.sendRequest = sendRequest + self.verifyContact = verifyContact + self.confirmRequest = confirmRequest + self.checkAuth = checkAuth } public var id: Data @@ -34,6 +43,9 @@ public struct ContactState: Equatable { @BindableState public var importEmail: Bool @BindableState public var importPhone: Bool public var sendRequest: SendRequestState? + public var verifyContact: VerifyContactState? + public var confirmRequest: ConfirmRequestState? + public var checkAuth: CheckContactAuthState? } public enum ContactAction: Equatable, BindableAction { @@ -43,6 +55,15 @@ public enum ContactAction: Equatable, BindableAction { case sendRequestTapped case sendRequestDismissed case sendRequest(SendRequestAction) + case verifyContactTapped + case verifyContactDismissed + case verifyContact(VerifyContactAction) + case checkAuthTapped + case checkAuthDismissed + case checkAuth(CheckContactAuthAction) + case confirmRequestTapped + case confirmRequestDismissed + case confirmRequest(ConfirmRequestAction) case binding(BindingAction<ContactState>) } @@ -52,13 +73,19 @@ public struct ContactEnvironment { db: DBManagerGetDB, mainQueue: AnySchedulerOf<DispatchQueue>, bgQueue: AnySchedulerOf<DispatchQueue>, - sendRequest: @escaping () -> SendRequestEnvironment + sendRequest: @escaping () -> SendRequestEnvironment, + verifyContact: @escaping () -> VerifyContactEnvironment, + confirmRequest: @escaping () -> ConfirmRequestEnvironment, + checkAuth: @escaping () -> CheckContactAuthEnvironment ) { self.messenger = messenger self.db = db self.mainQueue = mainQueue self.bgQueue = bgQueue self.sendRequest = sendRequest + self.verifyContact = verifyContact + self.confirmRequest = confirmRequest + self.checkAuth = checkAuth } public var messenger: Messenger @@ -66,6 +93,9 @@ public struct ContactEnvironment { public var mainQueue: AnySchedulerOf<DispatchQueue> public var bgQueue: AnySchedulerOf<DispatchQueue> public var sendRequest: () -> SendRequestEnvironment + public var verifyContact: () -> VerifyContactEnvironment + public var confirmRequest: () -> ConfirmRequestEnvironment + public var checkAuth: () -> CheckContactAuthEnvironment } #if DEBUG @@ -75,7 +105,10 @@ extension ContactEnvironment { db: .unimplemented, mainQueue: .unimplemented, bgQueue: .unimplemented, - sendRequest: { .unimplemented } + sendRequest: { .unimplemented }, + verifyContact: { .unimplemented }, + confirmRequest: { .unimplemented }, + checkAuth: { .unimplemented } ) } #endif @@ -135,10 +168,43 @@ public let contactReducer = Reducer<ContactState, ContactAction, ContactEnvironm state.sendRequest = nil return .none - case .sendRequest(_): + case .verifyContactTapped: + if let marshaled = state.dbContact?.marshaled { + state.verifyContact = VerifyContactState( + contact: .live(marshaled) + ) + } + return .none + + case .verifyContactDismissed: + state.verifyContact = nil + return .none + + case .checkAuthTapped: + if let marshaled = state.dbContact?.marshaled { + state.checkAuth = CheckContactAuthState( + contact: .live(marshaled) + ) + } + return .none + + case .checkAuthDismissed: + state.checkAuth = nil + return .none + + case .confirmRequestTapped: + if let marshaled = state.dbContact?.marshaled { + state.confirmRequest = ConfirmRequestState( + contact: .live(marshaled) + ) + } return .none - case .binding(_): + case .confirmRequestDismissed: + state.confirmRequest = nil + return .none + + case .binding(_), .sendRequest(_), .verifyContact(_), .confirmRequest(_), .checkAuth(_): return .none } } @@ -150,3 +216,24 @@ public let contactReducer = Reducer<ContactState, ContactAction, ContactEnvironm action: /ContactAction.sendRequest, environment: { $0.sendRequest() } ) +.presenting( + verifyContactReducer, + state: .keyPath(\.verifyContact), + id: .notNil(), + action: /ContactAction.verifyContact, + environment: { $0.verifyContact() } +) +.presenting( + confirmRequestReducer, + state: .keyPath(\.confirmRequest), + id: .notNil(), + action: /ContactAction.confirmRequest, + environment: { $0.confirmRequest() } +) +.presenting( + checkContactAuthReducer, + state: .keyPath(\.checkAuth), + id: .notNil(), + action: /ContactAction.checkAuth, + environment: { $0.checkAuth() } +) diff --git a/Examples/xx-messenger/Sources/ContactFeature/ContactView.swift b/Examples/xx-messenger/Sources/ContactFeature/ContactView.swift index 1cd8fa2eda4f9058cd8ed333f15e15f7588cd2dc..08ae7eb8863b95bc9e5b3d0ef8e7f529b2f8b39e 100644 --- a/Examples/xx-messenger/Sources/ContactFeature/ContactView.swift +++ b/Examples/xx-messenger/Sources/ContactFeature/ContactView.swift @@ -1,8 +1,11 @@ import AppCore +import CheckContactAuthFeature import ComposableArchitecture import ComposablePresentation +import ConfirmRequestFeature import SendRequestFeature import SwiftUI +import VerifyContactFeature import XXClient import XXModels @@ -114,6 +117,33 @@ public struct ContactView: View { Image(systemName: "chevron.forward") } } + Button { + viewStore.send(.verifyContactTapped) + } label: { + HStack { + Text("Verify contact") + Spacer() + Image(systemName: "chevron.forward") + } + } + Button { + viewStore.send(.confirmRequestTapped) + } label: { + HStack { + Text("Confirm request") + Spacer() + Image(systemName: "chevron.forward") + } + } + Button { + viewStore.send(.checkAuthTapped) + } label: { + HStack { + Text("Check authorization") + Spacer() + Image(systemName: "chevron.forward") + } + } } header: { Text("Auth") } @@ -131,6 +161,30 @@ public struct ContactView: View { onDeactivate: { viewStore.send(.sendRequestDismissed) }, destination: SendRequestView.init(store:) )) + .background(NavigationLinkWithStore( + store.scope( + state: \.verifyContact, + action: ContactAction.verifyContact + ), + onDeactivate: { viewStore.send(.verifyContactDismissed) }, + destination: VerifyContactView.init(store:) + )) + .background(NavigationLinkWithStore( + store.scope( + state: \.confirmRequest, + action: ContactAction.confirmRequest + ), + onDeactivate: { viewStore.send(.confirmRequestDismissed) }, + destination: ConfirmRequestView.init(store:) + )) + .background(NavigationLinkWithStore( + store.scope( + state: \.checkAuth, + action: ContactAction.checkAuth + ), + onDeactivate: { viewStore.send(.checkAuthDismissed) }, + destination: CheckContactAuthView.init(store:) + )) } } } diff --git a/Examples/xx-messenger/Sources/VerifyContactFeature/VerifyContactFeature.swift b/Examples/xx-messenger/Sources/VerifyContactFeature/VerifyContactFeature.swift new file mode 100644 index 0000000000000000000000000000000000000000..1663d155f59e9c8d7eb4b542de4255021620be4c --- /dev/null +++ b/Examples/xx-messenger/Sources/VerifyContactFeature/VerifyContactFeature.swift @@ -0,0 +1,97 @@ +import AppCore +import ComposableArchitecture +import Foundation +import XCTestDynamicOverlay +import XXClient +import XXMessengerClient +import XXModels + +public struct VerifyContactState: Equatable { + public enum Result: Equatable { + case success(Bool) + case failure(String) + } + + public init( + contact: XXClient.Contact, + isVerifying: Bool = false, + result: Result? = nil + ) { + self.contact = contact + self.isVerifying = isVerifying + self.result = result + } + + public var contact: XXClient.Contact + public var isVerifying: Bool + public var result: Result? +} + +public enum VerifyContactAction: Equatable { + case verifyTapped + case didVerify(VerifyContactState.Result) +} + +public struct VerifyContactEnvironment { + public init( + messenger: Messenger, + db: DBManagerGetDB, + mainQueue: AnySchedulerOf<DispatchQueue>, + bgQueue: AnySchedulerOf<DispatchQueue> + ) { + self.messenger = messenger + self.db = db + self.mainQueue = mainQueue + self.bgQueue = bgQueue + } + + public var messenger: Messenger + public var db: DBManagerGetDB + public var mainQueue: AnySchedulerOf<DispatchQueue> + public var bgQueue: AnySchedulerOf<DispatchQueue> +} + +#if DEBUG +extension VerifyContactEnvironment { + public static let unimplemented = VerifyContactEnvironment( + messenger: .unimplemented, + db: .unimplemented, + mainQueue: .unimplemented, + bgQueue: .unimplemented + ) +} +#endif + +public let verifyContactReducer = Reducer<VerifyContactState, VerifyContactAction, VerifyContactEnvironment> +{ state, action, env in + switch action { + case .verifyTapped: + state.isVerifying = true + state.result = nil + return Effect.result { [state] in + func updateStatus(_ status: XXModels.Contact.AuthStatus) throws { + try env.db().bulkUpdateContacts.callAsFunction( + .init(id: [try state.contact.getId()]), + .init(authStatus: status) + ) + } + do { + try updateStatus(.verificationInProgress) + let result = try env.messenger.verifyContact(state.contact) + try updateStatus(result ? .verified : .verificationFailed) + return .success(.didVerify(.success(result))) + } catch { + try? updateStatus(.verificationFailed) + return .success(.didVerify(.failure(error.localizedDescription))) + } + } + .subscribe(on: env.bgQueue) + .receive(on: env.mainQueue) + .eraseToEffect() + + case .didVerify(let result): + state.isVerifying = false + state.result = result + return .none + } +} diff --git a/Examples/xx-messenger/Sources/VerifyContactFeature/VerifyContactView.swift b/Examples/xx-messenger/Sources/VerifyContactFeature/VerifyContactView.swift new file mode 100644 index 0000000000000000000000000000000000000000..32b81ab789a5408cb46f34574cca6bf7e12144c0 --- /dev/null +++ b/Examples/xx-messenger/Sources/VerifyContactFeature/VerifyContactView.swift @@ -0,0 +1,100 @@ +import ComposableArchitecture +import SwiftUI + +public struct VerifyContactView: View { + public init(store: Store<VerifyContactState, VerifyContactAction>) { + self.store = store + } + + let store: Store<VerifyContactState, VerifyContactAction> + + struct ViewState: Equatable { + var username: String? + var email: String? + var phone: String? + var isVerifying: Bool + var result: VerifyContactState.Result? + + init(state: VerifyContactState) { + username = try? state.contact.getFact(.username)?.value + email = try? state.contact.getFact(.email)?.value + phone = try? state.contact.getFact(.phone)?.value + isVerifying = state.isVerifying + result = state.result + } + } + + public var body: some View { + WithViewStore(store, observe: ViewState.init) { viewStore in + Form { + Section { + Label(viewStore.username ?? "", systemImage: "person") + Label(viewStore.email ?? "", systemImage: "envelope") + Label(viewStore.phone ?? "", systemImage: "phone") + } header: { + Text("Facts") + } + + Section { + Button { + viewStore.send(.verifyTapped) + } label: { + HStack { + Text("Verify") + Spacer() + if viewStore.isVerifying { + ProgressView() + } else { + Image(systemName: "play") + } + } + } + .disabled(viewStore.isVerifying) + } + + if let result = viewStore.result { + Section { + HStack { + switch result { + case .success(true): + Text("Contact verified") + Spacer() + Image(systemName: "person.fill.checkmark") + + case .success(false): + Text("Contact not verified") + Spacer() + Image(systemName: "person.fill.xmark") + + case .failure(_): + Text("Verification failed") + Spacer() + Image(systemName: "xmark") + } + } + if case .failure(let failure) = result { + Text(failure) + } + } header: { + Text("Result") + } + } + } + .navigationTitle("Verify Contact") + } + } +} + +#if DEBUG +public struct VerifyContactView_Previews: PreviewProvider { + public static var previews: some View { + VerifyContactView(store: Store( + initialState: VerifyContactState( + contact: .unimplemented("contact-data".data(using: .utf8)!) + ), + reducer: .empty, + environment: () + )) + } +} +#endif diff --git a/Examples/xx-messenger/Tests/AppCoreTests/AuthCallbackHandler/AuthCallbackHandlerRequestTests.swift b/Examples/xx-messenger/Tests/AppCoreTests/AuthCallbackHandler/AuthCallbackHandlerRequestTests.swift index 65786884bfbe0821343509f20bd1f0942998768d..3a7cb6fbf2d475eadfdc10aa854938fad247167b 100644 --- a/Examples/xx-messenger/Tests/AppCoreTests/AuthCallbackHandler/AuthCallbackHandlerRequestTests.swift +++ b/Examples/xx-messenger/Tests/AppCoreTests/AuthCallbackHandler/AuthCallbackHandlerRequestTests.swift @@ -10,17 +10,8 @@ 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.waitForNetwork.run = { _ in } - messenger.waitForNodes.run = { _, _, _, _ in } - messenger.verifyContact.run = { contact in - didVerifyContact.append(contact) - return true - } - let request = AuthCallbackHandlerRequest.live( db: .init { var db: Database = .failing @@ -34,7 +25,6 @@ final class AuthCallbackHandlerRequestTests: XCTestCase { } return db }, - messenger: messenger, now: { now } ) var xxContact = XXClient.Contact.unimplemented("contact".data(using: .utf8)!) @@ -50,26 +40,15 @@ 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 - ), - .init( - id: "id".data(using: .utf8)!, - marshaled: "contact".data(using: .utf8)!, - username: "username", - email: "email", - phone: "phone", - authStatus: .verified, - createdAt: now - ) - ]) + XCTAssertNoDifference(didSaveContact, [.init( + id: "id".data(using: .utf8)!, + marshaled: "contact".data(using: .utf8)!, + username: "username", + email: "email", + phone: "phone", + authStatus: .stranger, + createdAt: now + )]) } func testRequestWhenContactInDatabase() throws { @@ -79,7 +58,6 @@ 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)!) @@ -87,96 +65,4 @@ final class AuthCallbackHandlerRequestTests: XCTestCase { try request(contact) } - - func testRequestFromNewContactVerificationFalse() throws { - let now = Date() - 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( - 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.waitForNetwork.run = { _ in } - messenger.waitForNodes.run = { _, _, _, _ in } - 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 - ) - ]) - } } diff --git a/Examples/xx-messenger/Tests/CheckContactAuthFeatureTests/CheckContactAuthFeatureTests.swift b/Examples/xx-messenger/Tests/CheckContactAuthFeatureTests/CheckContactAuthFeatureTests.swift new file mode 100644 index 0000000000000000000000000000000000000000..95f5a80773838e9b72d4f8c6e380e0f7ce8799fd --- /dev/null +++ b/Examples/xx-messenger/Tests/CheckContactAuthFeatureTests/CheckContactAuthFeatureTests.swift @@ -0,0 +1,147 @@ +import ComposableArchitecture +import CustomDump +import XCTest +import XXClient +import XXModels +@testable import CheckContactAuthFeature + +final class CheckContactAuthFeatureTests: XCTestCase { + func testCheck() { + var contact = XXClient.Contact.unimplemented("contact-data".data(using: .utf8)!) + let contactId = "contact-id".data(using: .utf8)! + contact.getIdFromContact.run = { _ in contactId } + + let store = TestStore( + initialState: CheckContactAuthState( + contact: contact + ), + reducer: checkContactAuthReducer, + environment: .unimplemented + ) + + var didCheckPartnerId: [Data] = [] + var didBulkUpdateContactsWithQuery: [XXModels.Contact.Query] = [] + var didBulkUpdateContactsWithAssignments: [XXModels.Contact.Assignments] = [] + + store.environment.mainQueue = .immediate + store.environment.bgQueue = .immediate + store.environment.messenger.e2e.get = { + var e2e: E2E = .unimplemented + e2e.hasAuthenticatedChannel.run = { partnerId in + didCheckPartnerId.append(partnerId) + return true + } + return e2e + } + store.environment.db.run = { + var db: Database = .failing + db.bulkUpdateContacts.run = { query, assignments in + didBulkUpdateContactsWithQuery.append(query) + didBulkUpdateContactsWithAssignments.append(assignments) + return 0 + } + return db + } + + store.send(.checkTapped) { + $0.isChecking = true + $0.result = nil + } + + XCTAssertNoDifference(didCheckPartnerId, [contactId]) + XCTAssertNoDifference(didBulkUpdateContactsWithQuery, [.init(id: [contactId])]) + XCTAssertNoDifference(didBulkUpdateContactsWithAssignments, [.init(authStatus: .friend)]) + + store.receive(.didCheck(.success(true))) { + $0.isChecking = false + $0.result = .success(true) + } + } + + func testCheckNoConnection() { + var contact = XXClient.Contact.unimplemented("contact-data".data(using: .utf8)!) + let contactId = "contact-id".data(using: .utf8)! + contact.getIdFromContact.run = { _ in contactId } + + let store = TestStore( + initialState: CheckContactAuthState( + contact: contact + ), + reducer: checkContactAuthReducer, + environment: .unimplemented + ) + + var didCheckPartnerId: [Data] = [] + var didBulkUpdateContactsWithQuery: [XXModels.Contact.Query] = [] + var didBulkUpdateContactsWithAssignments: [XXModels.Contact.Assignments] = [] + + store.environment.mainQueue = .immediate + store.environment.bgQueue = .immediate + store.environment.messenger.e2e.get = { + var e2e: E2E = .unimplemented + e2e.hasAuthenticatedChannel.run = { partnerId in + didCheckPartnerId.append(partnerId) + return false + } + return e2e + } + store.environment.db.run = { + var db: Database = .failing + db.bulkUpdateContacts.run = { query, assignments in + didBulkUpdateContactsWithQuery.append(query) + didBulkUpdateContactsWithAssignments.append(assignments) + return 0 + } + return db + } + + store.send(.checkTapped) { + $0.isChecking = true + $0.result = nil + } + + XCTAssertNoDifference(didCheckPartnerId, [contactId]) + XCTAssertNoDifference(didBulkUpdateContactsWithQuery, [.init(id: [contactId])]) + XCTAssertNoDifference(didBulkUpdateContactsWithAssignments, [.init(authStatus: .stranger)]) + + store.receive(.didCheck(.success(false))) { + $0.isChecking = false + $0.result = .success(false) + } + } + + func testCheckFailure() { + var contact = XXClient.Contact.unimplemented("contact-data".data(using: .utf8)!) + let contactId = "contact-id".data(using: .utf8)! + contact.getIdFromContact.run = { _ in contactId } + + let store = TestStore( + initialState: CheckContactAuthState( + contact: contact + ), + reducer: checkContactAuthReducer, + environment: .unimplemented + ) + + struct Failure: Error {} + let error = Failure() + + store.environment.mainQueue = .immediate + store.environment.bgQueue = .immediate + store.environment.messenger.e2e.get = { + var e2e: E2E = .unimplemented + e2e.hasAuthenticatedChannel.run = { _ in throw error } + return e2e + } + + store.send(.checkTapped) { + $0.isChecking = true + $0.result = nil + } + + store.receive(.didCheck(.failure(error.localizedDescription))) { + $0.isChecking = false + $0.result = .failure(error.localizedDescription) + } + } +} diff --git a/Examples/xx-messenger/Tests/ConfirmRequestFeatureTests/ConfirmRequestFeatureTests.swift b/Examples/xx-messenger/Tests/ConfirmRequestFeatureTests/ConfirmRequestFeatureTests.swift new file mode 100644 index 0000000000000000000000000000000000000000..8dea06e119b266534d4b5c6919023ce69789a762 --- /dev/null +++ b/Examples/xx-messenger/Tests/ConfirmRequestFeatureTests/ConfirmRequestFeatureTests.swift @@ -0,0 +1,122 @@ +import ComposableArchitecture +import CustomDump +import XCTest +import XXClient +import XXModels +@testable import ConfirmRequestFeature + +final class ConfirmRequestFeatureTests: XCTestCase { + func testConfirm() { + var contact = XXClient.Contact.unimplemented("contact-data".data(using: .utf8)!) + let contactId = "contact-id".data(using: .utf8)! + contact.getIdFromContact.run = { _ in contactId } + + let store = TestStore( + initialState: ConfirmRequestState( + contact: contact + ), + reducer: confirmRequestReducer, + environment: .unimplemented + ) + + var didConfirmRequestFromContact: [XXClient.Contact] = [] + var didBulkUpdateContactsWithQuery: [XXModels.Contact.Query] = [] + var didBulkUpdateContactsWithAssignments: [XXModels.Contact.Assignments] = [] + + store.environment.mainQueue = .immediate + store.environment.bgQueue = .immediate + store.environment.messenger.e2e.get = { + var e2e: E2E = .unimplemented + e2e.confirmReceivedRequest.run = { contact in + didConfirmRequestFromContact.append(contact) + return 0 + } + return e2e + } + store.environment.db.run = { + var db: Database = .failing + db.bulkUpdateContacts.run = { query, assignments in + didBulkUpdateContactsWithQuery.append(query) + didBulkUpdateContactsWithAssignments.append(assignments) + return 0 + } + return db + } + + store.send(.confirmTapped) { + $0.isConfirming = true + $0.result = nil + } + + XCTAssertNoDifference(didConfirmRequestFromContact, [contact]) + XCTAssertNoDifference(didBulkUpdateContactsWithQuery, [ + .init(id: [contactId]), + .init(id: [contactId]), + ]) + XCTAssertNoDifference(didBulkUpdateContactsWithAssignments, [ + .init(authStatus: .confirming), + .init(authStatus: .friend), + ]) + + store.receive(.didConfirm(.success)) { + $0.isConfirming = false + $0.result = .success + } + } + + func testConfirmFailure() { + var contact = XXClient.Contact.unimplemented("contact-data".data(using: .utf8)!) + let contactId = "contact-id".data(using: .utf8)! + contact.getIdFromContact.run = { _ in contactId } + + let store = TestStore( + initialState: ConfirmRequestState( + contact: contact + ), + reducer: confirmRequestReducer, + environment: .unimplemented + ) + + struct Failure: Error {} + let error = Failure() + + var didBulkUpdateContactsWithQuery: [XXModels.Contact.Query] = [] + var didBulkUpdateContactsWithAssignments: [XXModels.Contact.Assignments] = [] + + store.environment.mainQueue = .immediate + store.environment.bgQueue = .immediate + store.environment.messenger.e2e.get = { + var e2e: E2E = .unimplemented + e2e.confirmReceivedRequest.run = { _ in throw error } + return e2e + } + store.environment.db.run = { + var db: Database = .failing + db.bulkUpdateContacts.run = { query, assignments in + didBulkUpdateContactsWithQuery.append(query) + didBulkUpdateContactsWithAssignments.append(assignments) + return 0 + } + return db + } + + store.send(.confirmTapped) { + $0.isConfirming = true + $0.result = nil + } + + XCTAssertNoDifference(didBulkUpdateContactsWithQuery, [ + .init(id: [contactId]), + .init(id: [contactId]), + ]) + XCTAssertNoDifference(didBulkUpdateContactsWithAssignments, [ + .init(authStatus: .confirming), + .init(authStatus: .confirmationFailed), + ]) + + store.receive(.didConfirm(.failure(error.localizedDescription))) { + $0.isConfirming = false + $0.result = .failure(error.localizedDescription) + } + } +} diff --git a/Examples/xx-messenger/Tests/ContactFeatureTests/ContactFeatureTests.swift b/Examples/xx-messenger/Tests/ContactFeatureTests/ContactFeatureTests.swift index 247abb49e226acd5ea720d8a7cd8265f6ca197ef..afc146b1981cf07e5c0105da1c6022f51c56aef4 100644 --- a/Examples/xx-messenger/Tests/ContactFeatureTests/ContactFeatureTests.swift +++ b/Examples/xx-messenger/Tests/ContactFeatureTests/ContactFeatureTests.swift @@ -1,7 +1,10 @@ +import CheckContactAuthFeature import Combine import ComposableArchitecture +import ConfirmRequestFeature import CustomDump import SendRequestFeature +import VerifyContactFeature import XCTest import XXClient import XXModels @@ -163,4 +166,118 @@ final class ContactFeatureTests: XCTestCase { $0.sendRequest = nil } } + + func testVerifyContactTapped() { + let contactData = "contact-data".data(using: .utf8)! + let store = TestStore( + initialState: ContactState( + id: Data(), + dbContact: XXModels.Contact( + id: Data(), + marshaled: contactData + ) + ), + reducer: contactReducer, + environment: .unimplemented + ) + + store.send(.verifyContactTapped) { + $0.verifyContact = VerifyContactState( + contact: .unimplemented(contactData) + ) + } + } + + func testVerifyContactDismissed() { + let store = TestStore( + initialState: ContactState( + id: "contact-id".data(using: .utf8)!, + verifyContact: VerifyContactState( + contact: .unimplemented("contact-data".data(using: .utf8)!) + ) + ), + reducer: contactReducer, + environment: .unimplemented + ) + + store.send(.verifyContactDismissed) { + $0.verifyContact = nil + } + } + + func testCheckAuthTapped() { + let contactData = "contact-data".data(using: .utf8)! + let store = TestStore( + initialState: ContactState( + id: Data(), + dbContact: XXModels.Contact( + id: Data(), + marshaled: contactData + ) + ), + reducer: contactReducer, + environment: .unimplemented + ) + + store.send(.checkAuthTapped) { + $0.checkAuth = CheckContactAuthState( + contact: .unimplemented(contactData) + ) + } + } + + func testCheckAuthDismissed() { + let store = TestStore( + initialState: ContactState( + id: "contact-id".data(using: .utf8)!, + checkAuth: CheckContactAuthState( + contact: .unimplemented("contact-data".data(using: .utf8)!) + ) + ), + reducer: contactReducer, + environment: .unimplemented + ) + + store.send(.checkAuthDismissed) { + $0.checkAuth = nil + } + } + + func testConfirmRequestTapped() { + let contactData = "contact-data".data(using: .utf8)! + let store = TestStore( + initialState: ContactState( + id: Data(), + dbContact: XXModels.Contact( + id: Data(), + marshaled: contactData + ) + ), + reducer: contactReducer, + environment: .unimplemented + ) + + store.send(.confirmRequestTapped) { + $0.confirmRequest = ConfirmRequestState( + contact: .unimplemented(contactData) + ) + } + } + + func testConfirmRequestDismissed() { + let store = TestStore( + initialState: ContactState( + id: "contact-id".data(using: .utf8)!, + confirmRequest: ConfirmRequestState( + contact: .unimplemented("contact-data".data(using: .utf8)!) + ) + ), + reducer: contactReducer, + environment: .unimplemented + ) + + store.send(.confirmRequestDismissed) { + $0.confirmRequest = nil + } + } } diff --git a/Examples/xx-messenger/Tests/VerifyContactFeatureTests/VerifyContactFeatureTests.swift b/Examples/xx-messenger/Tests/VerifyContactFeatureTests/VerifyContactFeatureTests.swift new file mode 100644 index 0000000000000000000000000000000000000000..97f8b428e53ffc63ae123bf9f8510594cc0f5daf --- /dev/null +++ b/Examples/xx-messenger/Tests/VerifyContactFeatureTests/VerifyContactFeatureTests.swift @@ -0,0 +1,168 @@ +import ComposableArchitecture +import CustomDump +import XCTest +import XXClient +import XXModels +@testable import VerifyContactFeature + +final class VerifyContactFeatureTests: XCTestCase { + func testVerify() { + var contact = XXClient.Contact.unimplemented("contact-data".data(using: .utf8)!) + let contactId = "contact-id".data(using: .utf8)! + contact.getIdFromContact.run = { _ in contactId } + + let store = TestStore( + initialState: VerifyContactState( + contact: contact + ), + reducer: verifyContactReducer, + environment: .unimplemented + ) + + var didVerifyContact: [XXClient.Contact] = [] + var didBulkUpdateContactsWithQuery: [XXModels.Contact.Query] = [] + var didBulkUpdateContactsWithAssignments: [XXModels.Contact.Assignments] = [] + + store.environment.mainQueue = .immediate + store.environment.bgQueue = .immediate + store.environment.messenger.verifyContact.run = { contact in + didVerifyContact.append(contact) + return true + } + store.environment.db.run = { + var db: Database = .failing + db.bulkUpdateContacts.run = { query, assignments in + didBulkUpdateContactsWithQuery.append(query) + didBulkUpdateContactsWithAssignments.append(assignments) + return 0 + } + return db + } + + store.send(.verifyTapped) { + $0.isVerifying = true + $0.result = nil + } + + XCTAssertNoDifference(didVerifyContact, [contact]) + XCTAssertNoDifference(didBulkUpdateContactsWithQuery, [ + .init(id: [contactId]), + .init(id: [contactId]), + ]) + XCTAssertNoDifference(didBulkUpdateContactsWithAssignments, [ + .init(authStatus: .verificationInProgress), + .init(authStatus: .verified) + ]) + + store.receive(.didVerify(.success(true))) { + $0.isVerifying = false + $0.result = .success(true) + } + } + + func testVerifyNotVerified() { + var contact = XXClient.Contact.unimplemented("contact-data".data(using: .utf8)!) + let contactId = "contact-id".data(using: .utf8)! + contact.getIdFromContact.run = { _ in contactId } + + let store = TestStore( + initialState: VerifyContactState( + contact: contact + ), + reducer: verifyContactReducer, + environment: .unimplemented + ) + + var didVerifyContact: [XXClient.Contact] = [] + var didBulkUpdateContactsWithQuery: [XXModels.Contact.Query] = [] + var didBulkUpdateContactsWithAssignments: [XXModels.Contact.Assignments] = [] + + store.environment.mainQueue = .immediate + store.environment.bgQueue = .immediate + store.environment.messenger.verifyContact.run = { contact in + didVerifyContact.append(contact) + return false + } + store.environment.db.run = { + var db: Database = .failing + db.bulkUpdateContacts.run = { query, assignments in + didBulkUpdateContactsWithQuery.append(query) + didBulkUpdateContactsWithAssignments.append(assignments) + return 0 + } + return db + } + + store.send(.verifyTapped) { + $0.isVerifying = true + $0.result = nil + } + + XCTAssertNoDifference(didVerifyContact, [contact]) + XCTAssertNoDifference(didBulkUpdateContactsWithQuery, [ + .init(id: [contactId]), + .init(id: [contactId]), + ]) + XCTAssertNoDifference(didBulkUpdateContactsWithAssignments, [ + .init(authStatus: .verificationInProgress), + .init(authStatus: .verificationFailed), + ]) + + store.receive(.didVerify(.success(false))) { + $0.isVerifying = false + $0.result = .success(false) + } + } + + func testVerifyFailure() { + var contact = XXClient.Contact.unimplemented("contact-data".data(using: .utf8)!) + let contactId = "contact-id".data(using: .utf8)! + contact.getIdFromContact.run = { _ in contactId } + + let store = TestStore( + initialState: VerifyContactState( + contact: contact + ), + reducer: verifyContactReducer, + environment: .unimplemented + ) + + struct Failure: Error {} + let error = Failure() + + var didBulkUpdateContactsWithQuery: [XXModels.Contact.Query] = [] + var didBulkUpdateContactsWithAssignments: [XXModels.Contact.Assignments] = [] + + store.environment.mainQueue = .immediate + store.environment.bgQueue = .immediate + store.environment.messenger.verifyContact.run = { _ in throw error } + store.environment.db.run = { + var db: Database = .failing + db.bulkUpdateContacts.run = { query, assignments in + didBulkUpdateContactsWithQuery.append(query) + didBulkUpdateContactsWithAssignments.append(assignments) + return 0 + } + return db + } + + store.send(.verifyTapped) { + $0.isVerifying = true + $0.result = nil + } + + XCTAssertNoDifference(didBulkUpdateContactsWithQuery, [ + .init(id: [contactId]), + .init(id: [contactId]), + ]) + XCTAssertNoDifference(didBulkUpdateContactsWithAssignments, [ + .init(authStatus: .verificationInProgress), + .init(authStatus: .verificationFailed), + ]) + + store.receive(.didVerify(.failure(error.localizedDescription))) { + $0.isVerifying = false + $0.result = .failure(error.localizedDescription) + } + } +}