diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/XXMessengerClient.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/XXMessengerClient.xcscheme new file mode 100644 index 0000000000000000000000000000000000000000..665c4644aed93c75db826b6b8969769c2c203fe3 --- /dev/null +++ b/.swiftpm/xcode/xcshareddata/xcschemes/XXMessengerClient.xcscheme @@ -0,0 +1,78 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Scheme + LastUpgradeVersion = "1340" + version = "1.3"> + <BuildAction + parallelizeBuildables = "YES" + buildImplicitDependencies = "YES"> + <BuildActionEntries> + <BuildActionEntry + buildForTesting = "YES" + buildForRunning = "YES" + buildForProfiling = "YES" + buildForArchiving = "YES" + buildForAnalyzing = "YES"> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "XXMessengerClient" + BuildableName = "XXMessengerClient" + BlueprintName = "XXMessengerClient" + 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 = "XXMessengerClientTests" + BuildableName = "XXMessengerClientTests" + BlueprintName = "XXMessengerClientTests" + 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 = "XXMessengerClient" + BuildableName = "XXMessengerClient" + BlueprintName = "XXMessengerClient" + ReferencedContainer = "container:"> + </BuildableReference> + </MacroExpansion> + </ProfileAction> + <AnalyzeAction + buildConfiguration = "Debug"> + </AnalyzeAction> + <ArchiveAction + buildConfiguration = "Release" + revealArchiveInOrganizer = "YES"> + </ArchiveAction> +</Scheme> diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/elixxir-dapps-sdk-swift.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/elixxir-dapps-sdk-swift.xcscheme index c0fcfabe31600c05dbbb6e33fcd972945a0d23d8..223297f5afd6ce419d4485fb8500ed96aeacbe18 100644 --- a/.swiftpm/xcode/xcshareddata/xcschemes/elixxir-dapps-sdk-swift.xcscheme +++ b/.swiftpm/xcode/xcshareddata/xcschemes/elixxir-dapps-sdk-swift.xcscheme @@ -20,6 +20,20 @@ ReferencedContainer = "container:"> </BuildableReference> </BuildActionEntry> + <BuildActionEntry + buildForTesting = "YES" + buildForRunning = "YES" + buildForProfiling = "YES" + buildForArchiving = "YES" + buildForAnalyzing = "YES"> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "XXMessengerClient" + BuildableName = "XXMessengerClient" + BlueprintName = "XXMessengerClient" + ReferencedContainer = "container:"> + </BuildableReference> + </BuildActionEntry> </BuildActionEntries> </BuildAction> <TestAction @@ -39,6 +53,16 @@ ReferencedContainer = "container:"> </BuildableReference> </TestableReference> + <TestableReference + skipped = "NO"> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "XXMessengerClientTests" + BuildableName = "XXMessengerClientTests" + BlueprintName = "XXMessengerClientTests" + ReferencedContainer = "container:"> + </BuildableReference> + </TestableReference> </Testables> </TestAction> <LaunchAction diff --git a/Docs/XXClient-quick-start-guide.md b/Docs/XXClient-quick-start-guide.md new file mode 100644 index 0000000000000000000000000000000000000000..503bc2d032666b8dab1734bf074d538657e195d2 --- /dev/null +++ b/Docs/XXClient-quick-start-guide.md @@ -0,0 +1,116 @@ +# XXClient Quick Start Guide + +Add `XXClient` library as a dependency to your project using Swift Package Manager. + +## â–¶ï¸ Instantiating cMix + +You can use a convenient `CMixManager` wrapper to manage cMix stored on disk: + +```swift +let cMixManager: CMixManager = .live( + passwordStorage: .init( + save: { password in + // securely save provided password + }, + load: { + // load securely stored password + } + ) +) + +let cMix: CMix +if cMixManager.hasStorage() { + cMix = try cMixManager.load() +} else { + cMix = try cMixManager.create() +} +``` + +Check out included example iOS application for the `PasswordStorage` implementation that uses the iOS keychain. + +## â–¶ï¸ Connecting to the network + +Start network follower: + +```swift +try cMix.startNetworkFollower(timeoutMS: 10_000) +``` + +Wait until connected: + +```swift +let isNetworkHealthy = try cMix.waitForNetwork(timeoutMS: 30_000) +``` + +## â–¶ï¸ Making a new reception identity + +Use the cMix to make a new reception identity: + +```swift +let myIdentity = try cMix.makeReceptionIdentity() +``` + +## â–¶ï¸ Create new E2E + +```swift +let login: Login = .live +let e2e = try login( + cMixId: cMix.getId(), + identity: myIdentity +) +``` + +## â–¶ï¸ Connecting to remote + +Perform auth key negotiation with the given recipient to get the `Connection`: + +```swift +let connection = try cMix.connect( + withAuthentication: false, + e2eId: e2e.getId(), + recipientContact: ... +) +``` + +Pass `true` for the `withAuthentication` parameter if you want to prove id ownership to remote as well. + +## â–¶ï¸ Sending messages + +Send a message to the connection's partner: + +```swift +let sendReport = try connection.send( + messageType: 1, + payload: ... +) +``` + +Check if the round succeeded: + +```swift +try cMix.waitForRoundResult( + roundList: try sendReport.encode(), + timeoutMS: 30_000, + callback: .init { result in + switch result { + case .delivered(let roundResults): + ... + case .notDelivered(let timedOut): + ... + } + } +) +``` + +## â–¶ï¸ Receiving messages + +Use connection's message listener to receive messages from partner: + +```swift +try connection.registerListener( + messageType: 1, + listener: .init { message in + ... + } +) +``` \ No newline at end of file diff --git a/Docs/XXMessengerClient.md b/Docs/XXMessengerClient.md new file mode 100644 index 0000000000000000000000000000000000000000..7b29ad5714f6dd32a59f83b3a6673380b55a4978 --- /dev/null +++ b/Docs/XXMessengerClient.md @@ -0,0 +1,78 @@ +# XXMessengerClient + +`XXMessengerClient` is a client wrapper library for use in xx-messenger application. + +## â–¶ï¸ Instantiate messenger + +Example: + +```swift +// setup environment: +var environment: MessengerEnvironment = .live() + +// change cMix NDF environment if needed: +environment.ndfEnvironment = ... + +// use alternative user-discovery if needed: +environment.udAddress = ... +environment.udCert = ... +environment.udContact = ... + +// instantiate messenger: +let messenger: Messenger = .live(environment) +``` + +## 🚀 Start messenger + +Example: + +``` +func start(messenger: Messenger) throws { + // check if messenger is loaded: + if messenger.isLoaded() == false { + // check if messenger is created and stored on disk: + if messenger.isCreated() == false { + // create new messenger and store it on disk: + try messenger.create() + } + // load messenger stored on disk: + try messenger.load() + } + + // start messenger's network follower: + try messenger.start() + + // check if messenger is connected: + if messenger.isConnected() == false { + // start end-to-end connection: + try messenger.connect() + } + + // check if messenger is logged in with user-discovery: + if messenger.isLoggedIn() == false { + // check if messenger is registered with user-discovery: + if try messenger.isRegistered() == false { + // register new user with user-discovery: + try messenger.register(username: "new-username") + } else { + // login previously registered user with user-discovery: + try messenger.logIn() + } + } +} +``` + +## 🛠Use client components directly + +Example: + +```swift +// get cMix: +let cMix = messenger.cMix() + +// get E2E: +let e2e = messenger.e2e() + +// get UserDicovery: +let ud = messenger.ud() +``` \ No newline at end of file diff --git a/Example/Example.xcodeproj/xcshareddata/xcschemes/ExampleApp (iOS).xcscheme b/Example/Example.xcodeproj/xcshareddata/xcschemes/ExampleApp (iOS).xcscheme index 57df4eaac7e1b2bcda0ab78bc6f21b6b61a287bf..6ae359a987e276d0151fecdeff0fc8854e31bea1 100644 --- a/Example/Example.xcodeproj/xcshareddata/xcschemes/ExampleApp (iOS).xcscheme +++ b/Example/Example.xcodeproj/xcshareddata/xcschemes/ExampleApp (iOS).xcscheme @@ -26,8 +26,49 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - shouldUseLaunchSchemeArgsEnv = "YES"> + shouldUseLaunchSchemeArgsEnv = "YES" + codeCoverageEnabled = "YES"> <Testables> + <TestableReference + skipped = "NO"> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "AppFeatureTests" + BuildableName = "AppFeatureTests" + BlueprintName = "AppFeatureTests" + ReferencedContainer = "container:example-app"> + </BuildableReference> + </TestableReference> + <TestableReference + skipped = "NO"> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "ErrorFeatureTests" + BuildableName = "ErrorFeatureTests" + BlueprintName = "ErrorFeatureTests" + ReferencedContainer = "container:example-app"> + </BuildableReference> + </TestableReference> + <TestableReference + skipped = "NO"> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "LandingFeatureTests" + BuildableName = "LandingFeatureTests" + BlueprintName = "LandingFeatureTests" + ReferencedContainer = "container:example-app"> + </BuildableReference> + </TestableReference> + <TestableReference + skipped = "NO"> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "SessionFeatureTests" + BuildableName = "SessionFeatureTests" + BlueprintName = "SessionFeatureTests" + ReferencedContainer = "container:example-app"> + </BuildableReference> + </TestableReference> </Testables> </TestAction> <LaunchAction diff --git a/Package.swift b/Package.swift index dc48fcd529e629c9e08e8ca0e05e8a6e0a831355..0024dab56e3d9aa08441134301edf535ef878da4 100644 --- a/Package.swift +++ b/Package.swift @@ -20,6 +20,7 @@ let package = Package( ], products: [ .library(name: "XXClient", targets: ["XXClient"]), + .library(name: "XXMessengerClient", targets: ["XXMessengerClient"]), ], dependencies: [ .package( @@ -30,6 +31,10 @@ let package = Package( url: "https://github.com/pointfreeco/xctest-dynamic-overlay.git", .upToNextMajor(from: "0.4.0") ), + .package( + url: "https://github.com/kishikawakatsumi/KeychainAccess.git", + .upToNextMajor(from: "4.2.2") + ), ], targets: [ .target( @@ -48,6 +53,23 @@ let package = Package( ], swiftSettings: swiftSettings ), + .target( + name: "XXMessengerClient", + dependencies: [ + .target(name: "XXClient"), + .product(name: "KeychainAccess", package: "KeychainAccess"), + .product(name: "XCTestDynamicOverlay", package: "xctest-dynamic-overlay"), + ], + swiftSettings: swiftSettings + ), + .testTarget( + name: "XXMessengerClientTests", + dependencies: [ + .target(name: "XXMessengerClient"), + .product(name: "CustomDump", package: "swift-custom-dump"), + ], + swiftSettings: swiftSettings + ), .binaryTarget( name: "Bindings", path: "Frameworks/Bindings.xcframework" diff --git a/README.md b/README.md index 5c0f3d59e1b01968e89e695ea0ce43bf99248313..a311b5801b5a2b9d5cf53877047b28e862348a85 100644 --- a/README.md +++ b/README.md @@ -3,132 +3,14 @@   -## 📱 Demo - -Refer to this [demo](https://git.xx.network/elixxir/shielded-help-demo/elixxir-dapp-demo) to see an example of how to build an app with the SDK to send `E2E` messages and send `RestLike` messsage. - -Also you can checkout included example iOS application. - ## 📖 Documentation -You can find full documentation with step by step guide [here](https://xxdk-dev.xx.network/mobile%20docs/ios-sdk) - -## 🚀 Quick Start - -Add `XXClient` library as a dependency to your project using Swift Package Manager. - -### â–¶ï¸ Instantiating cMix - -You can use a convenient `CMixManager` wrapper to manage cMix stored on disk: - -```swift -let cMixManager: CMixManager = .live( - passwordStorage: .init( - save: { password in - // securely save provided password - }, - load: { - // load securely stored password - } - ) -) - -let cMix: CMix -if cMixManager.hasStorage() { - cMix = try cMixManager.load() -} else { - cMix = try cMixManager.create() -} -``` - -Check out included example iOS application for the `PasswordStorage` implementation that uses the iOS keychain. - -### â–¶ï¸ Connecting to the network - -Start network follower: - -```swift -try cMix.startNetworkFollower(timeoutMS: 10_000) -``` - -Wait until connected: - -```swift -let isNetworkHealthy = try cMix.waitForNetwork(timeoutMS: 30_000) -``` - -### â–¶ï¸ Making a new reception identity - -Use the cMix to make a new reception identity: +- [XXClient Quick Start Guide](Docs/XXClient-quick-start-guide.md) +- [XXMessengerClient](Docs/XXMessengerClient.md) -```swift -let myIdentity = try cMix.makeReceptionIdentity() -``` - -### â–¶ï¸ Create new E2E - -```swift -let login: Login = .live -let e2e = try login( - cMixId: cMix.getId(), - identity: myIdentity -) -``` - -### â–¶ï¸ Connecting to remote - -Perform auth key negotiation with the given recipient to get the `Connection`: - -```swift -let connection = try cMix.connect( - withAuthentication: false, - e2eId: e2e.getId(), - recipientContact: ... -) -``` - -Pass `true` for the `withAuthentication` parameter if you want to prove id ownership to remote as well. - -### â–¶ï¸ Sending messages - -Send a message to the connection's partner: - -```swift -let sendReport = try connection.send( - messageType: 1, - payload: ... -) -``` - -Check if the round succeeded: - -```swift -try cMix.waitForRoundResult( - roundList: try sendReport.encode(), - timeoutMS: 30_000, - callback: .init { result in - switch result { - case .delivered(let roundResults): - ... - case .notDelivered(let timedOut): - ... - } - } -) -``` - -### â–¶ï¸ Receiving messages - -Use connection's message listener to receive messages from partner: +## 📱 Demo -```swift -try connection.registerListener( - messageType: 1, - listener: .init { message in - ... - } -) -``` +Checkout included example iOS application. ## 🛠Development @@ -139,7 +21,8 @@ Open `ElixxirDAppsSDK.xcworkspace` in Xcode (≥13.4). ``` ElixxirDAppsSDK [Xcode Workspace] ├─ elixxir-dapps-sdk-swift [Swift Package] - | └─ XXClient [Library] + | ├─ XXClient [Library] + | └─ XXMessengerClient [Library] └─ Example [Xcode Project] ├─ ExampleApp (iOS) [iOS App Target] ├─ example-app [Swift Package] @@ -157,7 +40,7 @@ ElixxirDAppsSDK [Xcode Workspace] - Use `example-app` scheme to build and test the example app package with all contained libraries. - Use `ExampleAppIcon` scheme with macOS target to build and preview the example app icon. - Use `example-app-icon-export` scheme with macOS target to build and update the example app icon. -- Use other schemes, like `AppFeature`, for building and testing individual libraries in isolation. +- Use other schemes, like `XXClient`, for building and testing individual libraries in isolation. ## 📄 License diff --git a/Sources/XXClient/CMixManager/CMixManager.swift b/Sources/XXClient/CMixManager/CMixManager.swift index 1e1b38e59f92927b6c34fe97ea8f9a9246ff82cc..a0df95ffe66de0998f46141b68c202b2ff3bcd28 100644 --- a/Sources/XXClient/CMixManager/CMixManager.swift +++ b/Sources/XXClient/CMixManager/CMixManager.swift @@ -16,7 +16,7 @@ extension CMixManager { .appendingPathComponent("xx.network.client") .path, fileManager: FileManager = .default, - environment: Environment = .mainnet, + ndfEnvironment: NDFEnvironment = .mainnet, downloadNDF: DownloadAndVerifySignedNdf = .live, generateSecret: GenerateSecret = .live, passwordStorage: PasswordStorage, @@ -31,7 +31,7 @@ extension CMixManager { fileManager: fileManager ), create: .live( - environment: environment, + ndfEnvironment: ndfEnvironment, downloadNDF: downloadNDF, generateSecret: generateSecret, passwordStorage: passwordStorage, @@ -42,7 +42,7 @@ extension CMixManager { loadCMix: loadCMix ), restore: .live( - environment: environment, + ndfEnvironment: ndfEnvironment, downloadNDF: downloadNDF, generateSecret: generateSecret, passwordStorage: passwordStorage, diff --git a/Sources/XXClient/CMixManager/Functors/CMixManagerCreate.swift b/Sources/XXClient/CMixManager/Functors/CMixManagerCreate.swift index a0d543f8fa8c87f621b3a636dacd65b454f15f0b..0dc4946729ae76e3d265ef80aee3ce8b16e8df60 100644 --- a/Sources/XXClient/CMixManager/Functors/CMixManagerCreate.swift +++ b/Sources/XXClient/CMixManager/Functors/CMixManagerCreate.swift @@ -11,7 +11,7 @@ public struct CMixManagerCreate { extension CMixManagerCreate { public static func live( - environment: Environment, + ndfEnvironment: NDFEnvironment, downloadNDF: DownloadAndVerifySignedNdf, generateSecret: GenerateSecret, passwordStorage: PasswordStorage, @@ -22,7 +22,7 @@ extension CMixManagerCreate { loadCMix: LoadCMix ) -> CMixManagerCreate { CMixManagerCreate { - let ndfData = try downloadNDF(environment) + let ndfData = try downloadNDF(ndfEnvironment) let password = generateSecret() try passwordStorage.save(password) try? fileManager.removeItem(atPath: directoryPath) diff --git a/Sources/XXClient/CMixManager/Functors/CMixManagerRestore.swift b/Sources/XXClient/CMixManager/Functors/CMixManagerRestore.swift index 466684b8c0c901b3613511ccfdd8307eb8e40677..de8b6e1ac156009d7178f2c5a4ed4d4412f39d36 100644 --- a/Sources/XXClient/CMixManager/Functors/CMixManagerRestore.swift +++ b/Sources/XXClient/CMixManager/Functors/CMixManagerRestore.swift @@ -14,7 +14,7 @@ public struct CMixManagerRestore { extension CMixManagerRestore { public static func live( - environment: Environment, + ndfEnvironment: NDFEnvironment, downloadNDF: DownloadAndVerifySignedNdf, generateSecret: GenerateSecret, passwordStorage: PasswordStorage, @@ -23,7 +23,7 @@ extension CMixManagerRestore { newCMixFromBackup: NewCMixFromBackup ) -> CMixManagerRestore { CMixManagerRestore { backup, passphrase in - let ndfData = try downloadNDF(environment) + let ndfData = try downloadNDF(ndfEnvironment) let password = generateSecret() try passwordStorage.save(password) try? fileManager.removeItem(atPath: directoryPath) diff --git a/Sources/XXClient/Functors/DownloadAndVerifySignedNdf.swift b/Sources/XXClient/Functors/DownloadAndVerifySignedNdf.swift index 2c6ba075d0a6cbea1bf7482372fc46773e8f4e44..c5395780914e3befe6e9f1786a4f81921f863a3a 100644 --- a/Sources/XXClient/Functors/DownloadAndVerifySignedNdf.swift +++ b/Sources/XXClient/Functors/DownloadAndVerifySignedNdf.swift @@ -2,9 +2,9 @@ import Bindings import XCTestDynamicOverlay public struct DownloadAndVerifySignedNdf { - public var run: (Environment) throws -> Data + public var run: (NDFEnvironment) throws -> Data - public func callAsFunction(_ env: Environment) throws -> Data { + public func callAsFunction(_ env: NDFEnvironment) throws -> Data { try run(env) } } diff --git a/Sources/XXClient/Functors/NewOrLoadUd.swift b/Sources/XXClient/Functors/NewOrLoadUd.swift index 3b1599751d57555cbf2b419e37e1d368834c1884..cdb7f011b17e67a90dcb56d1c02f53f46fc5c354 100644 --- a/Sources/XXClient/Functors/NewOrLoadUd.swift +++ b/Sources/XXClient/Functors/NewOrLoadUd.swift @@ -2,10 +2,9 @@ import Bindings import XCTestDynamicOverlay public struct NewOrLoadUd { - public struct Params { + public struct Params: Equatable { public init( e2eId: Int, - follower: UdNetworkStatus, username: String?, registrationValidationSignature: Data?, cert: Data, @@ -13,7 +12,6 @@ public struct NewOrLoadUd { address: String ) { self.e2eId = e2eId - self.follower = follower self.username = username self.registrationValidationSignature = registrationValidationSignature self.cert = cert @@ -22,7 +20,6 @@ public struct NewOrLoadUd { } public var e2eId: Int - public var follower: UdNetworkStatus public var username: String? public var registrationValidationSignature: Data? public var cert: Data @@ -30,19 +27,22 @@ public struct NewOrLoadUd { public var address: String } - public var run: (Params) throws -> UserDiscovery + public var run: (Params, UdNetworkStatus) throws -> UserDiscovery - public func callAsFunction(_ params: Params) throws -> UserDiscovery { - try run(params) + public func callAsFunction( + params: Params, + follower: UdNetworkStatus + ) throws -> UserDiscovery { + try run(params, follower) } } extension NewOrLoadUd { - public static let live = NewOrLoadUd { params in + public static let live = NewOrLoadUd { params, follower in var error: NSError? let bindingsUD = BindingsNewOrLoadUd( params.e2eId, - params.follower.makeBindingsUdNetworkStatus(), + follower.makeBindingsUdNetworkStatus(), params.username, params.registrationValidationSignature, params.cert, diff --git a/Sources/XXClient/Models/Environment.swift b/Sources/XXClient/Models/NDFEnvironment.swift similarity index 90% rename from Sources/XXClient/Models/Environment.swift rename to Sources/XXClient/Models/NDFEnvironment.swift index 92df7275de565f0953458ed05f6d32f2e04c9031..701a144c7ce8360fdb296e15b1725f13c1d42dd8 100644 --- a/Sources/XXClient/Models/Environment.swift +++ b/Sources/XXClient/Models/NDFEnvironment.swift @@ -1,6 +1,6 @@ import Foundation -public struct Environment: Equatable { +public struct NDFEnvironment: Equatable { public init(url: URL, cert: String) { self.url = url self.cert = cert @@ -10,8 +10,8 @@ public struct Environment: Equatable { public var cert: String } -extension Environment { - public static let mainnet = Environment( +extension NDFEnvironment { + public static let mainnet = NDFEnvironment( url: URL(string: "https://elixxir-bins.s3.us-west-1.amazonaws.com/ndf/mainnet.json")!, cert: """ -----BEGIN CERTIFICATE----- @@ -50,3 +50,10 @@ extension Environment { """ ) } + +extension NDFEnvironment { + public static let unimplemented = NDFEnvironment( + url: URL(fileURLWithPath: "unimplemented"), + cert: "unimplemented" + ) +} diff --git a/Sources/XXMessengerClient/Messenger/Functors/MessengerConnect.swift b/Sources/XXMessengerClient/Messenger/Functors/MessengerConnect.swift new file mode 100644 index 0000000000000000000000000000000000000000..8cebb73a648773e63137a86a4481142fe2f5b173 --- /dev/null +++ b/Sources/XXMessengerClient/Messenger/Functors/MessengerConnect.swift @@ -0,0 +1,35 @@ +import XXClient +import XCTestDynamicOverlay + +public struct MessengerConnect { + public enum Error: Swift.Error, Equatable { + case notLoaded + } + + public var run: () throws -> Void + + public func callAsFunction() throws { + try run() + } +} + +extension MessengerConnect { + public static func live(_ env: MessengerEnvironment) -> MessengerConnect { + MessengerConnect { + guard let cMix = env.cMix() else { + throw Error.notLoaded + } + env.e2e.set(try env.login( + cMixId: cMix.getId(), + identity: try cMix.makeLegacyReceptionIdentity(), + e2eParamsJSON: env.getE2EParams() + )) + } + } +} + +extension MessengerConnect { + public static let unimplemented = MessengerConnect( + run: XCTUnimplemented() + ) +} diff --git a/Sources/XXMessengerClient/Messenger/Functors/MessengerCreate.swift b/Sources/XXMessengerClient/Messenger/Functors/MessengerCreate.swift new file mode 100644 index 0000000000000000000000000000000000000000..b36b55b447956d139f227887a154bec71d109312 --- /dev/null +++ b/Sources/XXMessengerClient/Messenger/Functors/MessengerCreate.swift @@ -0,0 +1,35 @@ +import XXClient +import XCTestDynamicOverlay + +public struct MessengerCreate { + public var run: () throws -> Void + + public func callAsFunction() throws { + try run() + } +} + +extension MessengerCreate { + public static func live(_ env: MessengerEnvironment) -> MessengerCreate { + MessengerCreate { + let ndfData = try env.downloadNDF(env.ndfEnvironment) + let password = env.generateSecret() + try env.passwordStorage.save(password) + let storageDir = env.storageDir + try env.fileManager.removeDirectory(storageDir) + try env.fileManager.createDirectory(storageDir) + try env.newCMix( + ndfJSON: String(data: ndfData, encoding: .utf8)!, + storageDir: storageDir, + password: password, + registrationCode: nil + ) + } + } +} + +extension MessengerCreate { + public static let unimplemented = MessengerCreate( + run: XCTUnimplemented() + ) +} diff --git a/Sources/XXMessengerClient/Messenger/Functors/MessengerIsConnected.swift b/Sources/XXMessengerClient/Messenger/Functors/MessengerIsConnected.swift new file mode 100644 index 0000000000000000000000000000000000000000..c30437faeb6b4cbc8b0a66df811bc55bd5618e28 --- /dev/null +++ b/Sources/XXMessengerClient/Messenger/Functors/MessengerIsConnected.swift @@ -0,0 +1,25 @@ +import XXClient +import XCTestDynamicOverlay + +public struct MessengerIsConnected { + public var run: () -> Bool + + public func callAsFunction() -> Bool { + run() + } +} + +extension MessengerIsConnected { + public static func live(_ env: MessengerEnvironment) -> MessengerIsConnected { + MessengerIsConnected { + env.e2e() != nil + } + } +} + +extension MessengerIsConnected { + public static let unimplemented = MessengerIsConnected( + run: XCTUnimplemented() + ) +} + diff --git a/Sources/XXMessengerClient/Messenger/Functors/MessengerIsCreated.swift b/Sources/XXMessengerClient/Messenger/Functors/MessengerIsCreated.swift new file mode 100644 index 0000000000000000000000000000000000000000..c848518c2483e49acbdbfe3d6d0b9e0c7fc2ce32 --- /dev/null +++ b/Sources/XXMessengerClient/Messenger/Functors/MessengerIsCreated.swift @@ -0,0 +1,24 @@ +import XXClient +import XCTestDynamicOverlay + +public struct MessengerIsCreated { + public var run: () -> Bool + + public func callAsFunction() -> Bool { + run() + } +} + +extension MessengerIsCreated { + public static func live(_ env: MessengerEnvironment) -> MessengerIsCreated { + MessengerIsCreated { + env.fileManager.isDirectoryEmpty(env.storageDir) == false + } + } +} + +extension MessengerIsCreated { + public static let unimplemented = MessengerIsCreated( + run: XCTUnimplemented() + ) +} diff --git a/Sources/XXMessengerClient/Messenger/Functors/MessengerIsLoaded.swift b/Sources/XXMessengerClient/Messenger/Functors/MessengerIsLoaded.swift new file mode 100644 index 0000000000000000000000000000000000000000..dc0b42165efe5b39071674a5019a7e11860d53f5 --- /dev/null +++ b/Sources/XXMessengerClient/Messenger/Functors/MessengerIsLoaded.swift @@ -0,0 +1,24 @@ +import XXClient +import XCTestDynamicOverlay + +public struct MessengerIsLoaded { + public var run: () -> Bool + + public func callAsFunction() -> Bool { + run() + } +} + +extension MessengerIsLoaded { + public static func live(_ env: MessengerEnvironment) -> MessengerIsLoaded { + MessengerIsLoaded { + env.cMix() != nil + } + } +} + +extension MessengerIsLoaded { + public static let unimplemented = MessengerIsLoaded( + run: XCTUnimplemented() + ) +} diff --git a/Sources/XXMessengerClient/Messenger/Functors/MessengerIsLoggedIn.swift b/Sources/XXMessengerClient/Messenger/Functors/MessengerIsLoggedIn.swift new file mode 100644 index 0000000000000000000000000000000000000000..7c54c5785fc472c4815f3a91f626575881cf2dcd --- /dev/null +++ b/Sources/XXMessengerClient/Messenger/Functors/MessengerIsLoggedIn.swift @@ -0,0 +1,24 @@ +import XXClient +import XCTestDynamicOverlay + +public struct MessengerIsLoggedIn { + public var run: () -> Bool + + public func callAsFunction() -> Bool { + run() + } +} + +extension MessengerIsLoggedIn { + public static func live(_ env: MessengerEnvironment) -> MessengerIsLoggedIn { + MessengerIsLoggedIn { + env.ud() != nil + } + } +} + +extension MessengerIsLoggedIn { + public static let unimplemented = MessengerIsLoggedIn( + run: XCTUnimplemented() + ) +} diff --git a/Sources/XXMessengerClient/Messenger/Functors/MessengerIsRegistered.swift b/Sources/XXMessengerClient/Messenger/Functors/MessengerIsRegistered.swift new file mode 100644 index 0000000000000000000000000000000000000000..bf3cafa541fa67847f749325419c683e9edd34c6 --- /dev/null +++ b/Sources/XXMessengerClient/Messenger/Functors/MessengerIsRegistered.swift @@ -0,0 +1,31 @@ +import XXClient +import XCTestDynamicOverlay + +public struct MessengerIsRegistered { + public enum Error: Swift.Error, Equatable { + case notConnected + } + + public var run: () throws -> Bool + + public func callAsFunction() throws -> Bool { + try run() + } +} + +extension MessengerIsRegistered { + public static func live(_ env: MessengerEnvironment) -> MessengerIsRegistered { + MessengerIsRegistered { + guard let e2e = env.e2e() else { + throw Error.notConnected + } + return try env.isRegisteredWithUD(e2eId: e2e.getId()) + } + } +} + +extension MessengerIsRegistered { + public static let unimplemented = MessengerIsRegistered( + run: XCTUnimplemented() + ) +} diff --git a/Sources/XXMessengerClient/Messenger/Functors/MessengerLoad.swift b/Sources/XXMessengerClient/Messenger/Functors/MessengerLoad.swift new file mode 100644 index 0000000000000000000000000000000000000000..29f281688d0cb6e4611cbf50a5ee2f7844df07f1 --- /dev/null +++ b/Sources/XXMessengerClient/Messenger/Functors/MessengerLoad.swift @@ -0,0 +1,28 @@ +import XXClient +import XCTestDynamicOverlay + +public struct MessengerLoad { + public var run: () throws -> Void + + public func callAsFunction() throws { + try run() + } +} + +extension MessengerLoad { + public static func live(_ env: MessengerEnvironment) -> MessengerLoad { + MessengerLoad { + env.cMix.set(try env.loadCMix( + storageDir: env.storageDir, + password: try env.passwordStorage.load(), + cMixParamsJSON: env.getCMixParams() + )) + } + } +} + +extension MessengerLoad { + public static let unimplemented = MessengerLoad( + run: XCTUnimplemented() + ) +} diff --git a/Sources/XXMessengerClient/Messenger/Functors/MessengerLogIn.swift b/Sources/XXMessengerClient/Messenger/Functors/MessengerLogIn.swift new file mode 100644 index 0000000000000000000000000000000000000000..d0f86d8bcfa8ea7f7c0251d573a5dc591b59ddc0 --- /dev/null +++ b/Sources/XXMessengerClient/Messenger/Functors/MessengerLogIn.swift @@ -0,0 +1,47 @@ +import XXClient +import XCTestDynamicOverlay + +public struct MessengerLogIn { + public enum Error: Swift.Error, Equatable { + case notLoaded + case notConnected + } + + public var run: () throws -> Void + + public func callAsFunction() throws { + try run() + } +} + +extension MessengerLogIn { + public static func live(_ env: MessengerEnvironment) -> MessengerLogIn { + MessengerLogIn { + guard let cMix = env.cMix() else { + throw Error.notLoaded + } + guard let e2e = env.e2e() else { + throw Error.notConnected + } + env.ud.set(try env.newOrLoadUd( + params: .init( + e2eId: e2e.getId(), + username: nil, + registrationValidationSignature: nil, + cert: env.udCert ?? e2e.getUdCertFromNdf(), + contactFile: env.udContact ?? (try e2e.getUdContactFromNdf()), + address: env.udAddress ?? e2e.getUdAddressFromNdf() + ), + follower: .init { + cMix.networkFollowerStatus().rawValue + } + )) + } + } +} + +extension MessengerLogIn { + public static let unimplemented = MessengerLogIn( + run: XCTUnimplemented() + ) +} diff --git a/Sources/XXMessengerClient/Messenger/Functors/MessengerRegister.swift b/Sources/XXMessengerClient/Messenger/Functors/MessengerRegister.swift new file mode 100644 index 0000000000000000000000000000000000000000..6f4296318c4eccf771ccb505cb584db458b74f8b --- /dev/null +++ b/Sources/XXMessengerClient/Messenger/Functors/MessengerRegister.swift @@ -0,0 +1,49 @@ +import XXClient +import XCTestDynamicOverlay + +public struct MessengerRegister { + public enum Error: Swift.Error, Equatable { + case notLoaded + case notConnected + } + + public var run: (String) throws -> Void + + public func callAsFunction( + username: String + ) throws { + try run(username) + } +} + +extension MessengerRegister { + public static func live(_ env: MessengerEnvironment) -> MessengerRegister { + MessengerRegister { username in + guard let cMix = env.cMix() else { + throw Error.notLoaded + } + guard let e2e = env.e2e() else { + throw Error.notConnected + } + env.ud.set(try env.newOrLoadUd( + params: .init( + e2eId: e2e.getId(), + username: username, + registrationValidationSignature: cMix.getReceptionRegistrationValidationSignature(), + cert: env.udCert ?? e2e.getUdCertFromNdf(), + contactFile: env.udContact ?? (try e2e.getUdContactFromNdf()), + address: env.udAddress ?? e2e.getUdAddressFromNdf() + ), + follower: .init { + cMix.networkFollowerStatus().rawValue + } + )) + } + } +} + +extension MessengerRegister { + public static let unimplemented = MessengerRegister( + run: XCTUnimplemented() + ) +} diff --git a/Sources/XXMessengerClient/Messenger/Functors/MessengerStart.swift b/Sources/XXMessengerClient/Messenger/Functors/MessengerStart.swift new file mode 100644 index 0000000000000000000000000000000000000000..cc3363ec543ee2c3ed6c2414ac79d1ceaa0ccc33 --- /dev/null +++ b/Sources/XXMessengerClient/Messenger/Functors/MessengerStart.swift @@ -0,0 +1,36 @@ +import XXClient +import XCTestDynamicOverlay + +public struct MessengerStart { + public enum Error: Swift.Error { + case notLoaded + } + + public var run: (Int) throws -> Void + + public func callAsFunction( + timeoutMS: Int = 30_000 + ) throws { + try run(timeoutMS) + } +} + +extension MessengerStart { + public static func live(_ env: MessengerEnvironment) -> MessengerStart { + MessengerStart { timeoutMS in + guard let cMix = env.cMix() else { + throw Error.notLoaded + } + guard cMix.networkFollowerStatus() != .running else { + return + } + try cMix.startNetworkFollower(timeoutMS: timeoutMS) + } + } +} + +extension MessengerStart { + public static let unimplemented = MessengerStart( + run: XCTUnimplemented("\(Self.self)") + ) +} diff --git a/Sources/XXMessengerClient/Messenger/Functors/MessengerWaitForNetwork.swift b/Sources/XXMessengerClient/Messenger/Functors/MessengerWaitForNetwork.swift new file mode 100644 index 0000000000000000000000000000000000000000..8fe8cf3e4750f1d3bff8a7191574a143639466c7 --- /dev/null +++ b/Sources/XXMessengerClient/Messenger/Functors/MessengerWaitForNetwork.swift @@ -0,0 +1,36 @@ +import XXClient +import XCTestDynamicOverlay + +public struct MessengerWaitForNetwork { + public enum Error: Swift.Error { + case notLoaded + case timeout + } + + public var run: (Int) throws -> Void + + public func callAsFunction( + timeoutMS: Int = 30_000 + ) throws { + try run(timeoutMS) + } +} + +extension MessengerWaitForNetwork { + public static func live(_ env: MessengerEnvironment) -> MessengerWaitForNetwork { + MessengerWaitForNetwork { timeoutMS in + guard let cMix = env.cMix() else { + throw Error.notLoaded + } + guard cMix.waitForNetwork(timeoutMS: timeoutMS) else { + throw Error.timeout + } + } + } +} + +extension MessengerWaitForNetwork { + public static let unimplemented = MessengerWaitForNetwork( + run: XCTUnimplemented("\(Self.self)") + ) +} diff --git a/Sources/XXMessengerClient/Messenger/Functors/MessengerWaitForNodes.swift b/Sources/XXMessengerClient/Messenger/Functors/MessengerWaitForNodes.swift new file mode 100644 index 0000000000000000000000000000000000000000..b87acc2f13caf51d2998d3a864a6484f897dc528 --- /dev/null +++ b/Sources/XXMessengerClient/Messenger/Functors/MessengerWaitForNodes.swift @@ -0,0 +1,53 @@ +import XXClient +import XCTestDynamicOverlay + +public struct MessengerWaitForNodes { + public typealias Progress = (NodeRegistrationReport) -> Void + + public enum Error: Swift.Error { + case notLoaded + case timeout + } + + public var run: (Double, Int, Int, @escaping Progress) throws -> Void + + public func callAsFunction( + targetRatio: Double = 0.8, + sleepMS: Int = 1_000, + retries: Int = 10, + onProgress: @escaping Progress = { _ in } + ) throws { + try run(targetRatio, sleepMS, retries, onProgress) + } +} + +extension MessengerWaitForNodes { + public static func live(_ env: MessengerEnvironment) -> MessengerWaitForNodes { + MessengerWaitForNodes { targetRatio, sleepMS, retries, onProgress in + guard let cMix = env.cMix() else { + throw Error.notLoaded + } + + var report = try cMix.getNodeRegistrationStatus() + var retries = retries + onProgress(report) + + while report.ratio < targetRatio && retries > 0 { + env.sleep(sleepMS) + report = try cMix.getNodeRegistrationStatus() + retries -= 1 + onProgress(report) + } + + if report.ratio < targetRatio { + throw Error.timeout + } + } + } +} + +extension MessengerWaitForNodes { + public static let unimplemented = MessengerWaitForNodes( + run: XCTUnimplemented("\(Self.self)") + ) +} diff --git a/Sources/XXMessengerClient/Messenger/Messenger.swift b/Sources/XXMessengerClient/Messenger/Messenger.swift new file mode 100644 index 0000000000000000000000000000000000000000..30d63efdfac2716a0d3efc7f0a614eafeaf2e852 --- /dev/null +++ b/Sources/XXMessengerClient/Messenger/Messenger.swift @@ -0,0 +1,64 @@ +import XXClient + +public struct Messenger { + public var cMix: Stored<CMix?> + public var e2e: Stored<E2E?> + public var ud: Stored<UserDiscovery?> + public var isCreated: MessengerIsCreated + public var create: MessengerCreate + public var isLoaded: MessengerIsLoaded + public var load: MessengerLoad + public var start: MessengerStart + public var isConnected: MessengerIsConnected + public var connect: MessengerConnect + public var isRegistered: MessengerIsRegistered + public var register: MessengerRegister + public var isLoggedIn: MessengerIsLoggedIn + public var logIn: MessengerLogIn + public var waitForNetwork: MessengerWaitForNetwork + public var waitForNodes: MessengerWaitForNodes +} + +extension Messenger { + public static func live(_ env: MessengerEnvironment) -> Messenger { + Messenger( + cMix: env.cMix, + e2e: env.e2e, + ud: env.ud, + isCreated: .live(env), + create: .live(env), + isLoaded: .live(env), + load: .live(env), + start: .live(env), + isConnected: .live(env), + connect: .live(env), + isRegistered: .live(env), + register: .live(env), + isLoggedIn: .live(env), + logIn: .live(env), + waitForNetwork: .live(env), + waitForNodes: .live(env) + ) + } +} + +extension Messenger { + public static let unimplemented = Messenger( + cMix: .unimplemented(), + e2e: .unimplemented(), + ud: .unimplemented(), + isCreated: .unimplemented, + create: .unimplemented, + isLoaded: .unimplemented, + load: .unimplemented, + start: .unimplemented, + isConnected: .unimplemented, + connect: .unimplemented, + isRegistered: .unimplemented, + register: .unimplemented, + isLoggedIn: .unimplemented, + logIn: .unimplemented, + waitForNetwork: .unimplemented, + waitForNodes: .unimplemented + ) +} diff --git a/Sources/XXMessengerClient/Messenger/MessengerEnvironment.swift b/Sources/XXMessengerClient/Messenger/MessengerEnvironment.swift new file mode 100644 index 0000000000000000000000000000000000000000..0918142a1f92dd6bbc5392bb562113464f349b20 --- /dev/null +++ b/Sources/XXMessengerClient/Messenger/MessengerEnvironment.swift @@ -0,0 +1,84 @@ +import Foundation +import XXClient +import XCTestDynamicOverlay + +public struct MessengerEnvironment { + public var cMix: Stored<CMix?> + public var downloadNDF: DownloadAndVerifySignedNdf + public var e2e: Stored<E2E?> + public var fileManager: MessengerFileManager + public var generateSecret: GenerateSecret + public var getCMixParams: GetCMixParams + public var getE2EParams: GetE2EParams + public var isRegisteredWithUD: IsRegisteredWithUD + public var loadCMix: LoadCMix + public var login: Login + public var ndfEnvironment: NDFEnvironment + public var newCMix: NewCMix + public var newOrLoadUd: NewOrLoadUd + public var passwordStorage: PasswordStorage + public var sleep: (Int) -> Void + public var storageDir: String + public var ud: Stored<UserDiscovery?> + public var udAddress: String? + public var udCert: Data? + public var udContact: Data? +} + +extension MessengerEnvironment { + public static let defaultStorageDir = FileManager.default + .urls(for: .applicationSupportDirectory, in: .userDomainMask) + .first! + .appendingPathComponent("xx.network.client") + .path + + public static func live() -> MessengerEnvironment { + MessengerEnvironment( + cMix: .inMemory(), + downloadNDF: .live, + e2e: .inMemory(), + fileManager: .live(), + generateSecret: .live, + getCMixParams: .liveDefault, + getE2EParams: .liveDefault, + isRegisteredWithUD: .live, + loadCMix: .live, + login: .live, + ndfEnvironment: .mainnet, + newCMix: .live, + newOrLoadUd: .live, + passwordStorage: .keychain, + sleep: { Foundation.sleep(UInt32($0)) }, + storageDir: MessengerEnvironment.defaultStorageDir, + ud: .inMemory(), + udAddress: nil, + udCert: nil, + udContact: nil + ) + } +} + +extension MessengerEnvironment { + public static let unimplemented = MessengerEnvironment( + cMix: .unimplemented(), + downloadNDF: .unimplemented, + e2e: .unimplemented(), + fileManager: .unimplemented, + generateSecret: .unimplemented, + getCMixParams: .unimplemented, + getE2EParams: .unimplemented, + isRegisteredWithUD: .unimplemented, + loadCMix: .unimplemented, + login: .unimplemented, + ndfEnvironment: .unimplemented, + newCMix: .unimplemented, + newOrLoadUd: .unimplemented, + passwordStorage: .unimplemented, + sleep: XCTUnimplemented("\(Self.self).sleep"), + storageDir: "unimplemented", + ud: .unimplemented(), + udAddress: nil, + udCert: nil, + udContact: nil + ) +} diff --git a/Sources/XXMessengerClient/Utils/MessengerFileManager.swift b/Sources/XXMessengerClient/Utils/MessengerFileManager.swift new file mode 100644 index 0000000000000000000000000000000000000000..4ff49c5fe155e612852860d1d7276124729017d3 --- /dev/null +++ b/Sources/XXMessengerClient/Utils/MessengerFileManager.swift @@ -0,0 +1,40 @@ +import Foundation +import XCTestDynamicOverlay + +public struct MessengerFileManager { + public var isDirectoryEmpty: (String) -> Bool + public var removeDirectory: (String) throws -> Void + public var createDirectory: (String) throws -> Void +} + +extension MessengerFileManager { + public static func live( + fileManager: FileManager = .default + ) -> MessengerFileManager { + MessengerFileManager( + isDirectoryEmpty: { path in + let contents = try? fileManager.contentsOfDirectory(atPath: path) + return contents?.isEmpty ?? true + }, + removeDirectory: { path in + if fileManager.fileExists(atPath: path) { + try fileManager.removeItem(atPath: path) + } + }, + createDirectory: { path in + try fileManager.createDirectory( + atPath: path, + withIntermediateDirectories: true + ) + } + ) + } +} + +extension MessengerFileManager { + public static let unimplemented = MessengerFileManager( + isDirectoryEmpty: XCTUnimplemented("\(Self.self).isDirectoryEmpty"), + removeDirectory: XCTUnimplemented("\(Self.self).removeDirectory"), + createDirectory: XCTUnimplemented("\(Self.self).createDirectory") + ) +} diff --git a/Sources/XXMessengerClient/Utils/PasswordStorage+Keychain.swift b/Sources/XXMessengerClient/Utils/PasswordStorage+Keychain.swift new file mode 100644 index 0000000000000000000000000000000000000000..0a062cc628cf037491a37ba8639e1f2529f209e8 --- /dev/null +++ b/Sources/XXMessengerClient/Utils/PasswordStorage+Keychain.swift @@ -0,0 +1,21 @@ +import KeychainAccess +import XXClient + +extension PasswordStorage { + public static let keychain: PasswordStorage = { + let keychain = KeychainAccess.Keychain( + service: "xx.network.client.messenger" + ) + return PasswordStorage( + save: { password in + keychain[data: "password"] = password + }, + load: { + guard let password = keychain[data: "password"] else { + throw MissingPasswordError() + } + return password + } + ) + }() +} diff --git a/Sources/XXMessengerClient/Utils/Stored.swift b/Sources/XXMessengerClient/Utils/Stored.swift new file mode 100644 index 0000000000000000000000000000000000000000..56b5e97ef9db3fcc3d2ab7b8452b8fbb434acce6 --- /dev/null +++ b/Sources/XXMessengerClient/Utils/Stored.swift @@ -0,0 +1,41 @@ +import XCTestDynamicOverlay + +public struct Stored<Value> { + public var get: () -> Value + public var set: (Value) -> Void + + public func callAsFunction() -> Value { + get() + } +} + +extension Stored { + public static func inMemory(_ value: Value) -> Stored<Value> { + let memory = Memory(value) + return Stored( + get: { memory.value }, + set: { memory.value = $0 } + ) + } + + public static func inMemory<V>() -> Stored<Optional<V>> where Value == Optional<V> { + inMemory(nil) + } +} + +private final class Memory<Value> { + init(_ value: Value) { + self.value = value + } + + var value: Value +} + +extension Stored { + public static func unimplemented() -> Stored<Value> { + Stored<Value>( + get: XCTUnimplemented("\(Self.self).get"), + set: XCTUnimplemented("\(Self.self).set") + ) + } +} diff --git a/Tests/XXMessengerClientTests/Messenger/Functors/MessengerConnectTests.swift b/Tests/XXMessengerClientTests/Messenger/Functors/MessengerConnectTests.swift new file mode 100644 index 0000000000000000000000000000000000000000..fb780353631b32f3f4bcf686bf60f3c3e38703c3 --- /dev/null +++ b/Tests/XXMessengerClientTests/Messenger/Functors/MessengerConnectTests.swift @@ -0,0 +1,118 @@ +import CustomDump +import XXClient +import XCTest +@testable import XXMessengerClient + +final class MessengerConnectTests: XCTestCase { + func testConnect() throws { + struct DidLogIn: Equatable { + var ephemeral: Bool + var cMixId: Int + var authCallbacksProvided: Bool + var identity: ReceptionIdentity + var e2eParamsJSON: Data + } + + var didLogIn: [DidLogIn] = [] + var didSetE2E: [E2E?] = [] + + let cMixId = 1234 + let receptionId = ReceptionIdentity.stub + let e2eParams = "e2e-params".data(using: .utf8)! + + var env: MessengerEnvironment = .unimplemented + env.cMix.get = { + var cMix: CMix = .unimplemented + cMix.getId.run = { cMixId } + cMix.makeLegacyReceptionIdentity.run = { receptionId } + return cMix + } + env.e2e.set = { didSetE2E.append($0) } + env.getE2EParams.run = { e2eParams } + env.login.run = { ephemeral, cMixId, authCallbacks, identity, e2eParamsJSON in + didLogIn.append(.init( + ephemeral: ephemeral, + cMixId: cMixId, + authCallbacksProvided: authCallbacks != nil, + identity: identity, + e2eParamsJSON: e2eParamsJSON + )) + return .unimplemented + } + let connect: MessengerConnect = .live(env) + + try connect() + + XCTAssertNoDifference(didLogIn, [ + DidLogIn( + ephemeral: false, + cMixId: 1234, + authCallbacksProvided: false, + identity: .stub, + e2eParamsJSON: e2eParams + ) + ]) + XCTAssertEqual(didSetE2E.compactMap { $0 }.count, 1) + } + + func testConnectWithoutCMix() { + var env: MessengerEnvironment = .unimplemented + env.cMix.get = { nil } + let connect: MessengerConnect = .live(env) + + XCTAssertThrowsError(try connect()) { error in + XCTAssertEqual( + error as? MessengerConnect.Error, + MessengerConnect.Error.notLoaded + ) + } + } + + func testMakeLegacyReceptionIdentityFailure() { + struct Error: Swift.Error, Equatable {} + let error = Error() + + var env: MessengerEnvironment = .unimplemented + env.cMix.get = { + var cMix: CMix = .unimplemented + cMix.getId.run = { 1234 } + cMix.makeLegacyReceptionIdentity.run = { throw error } + return cMix + } + let connect: MessengerConnect = .live(env) + + XCTAssertThrowsError(try connect()) { err in + XCTAssertEqual(err as? Error, error) + } + } + + func testLoginFailure() { + struct Error: Swift.Error, Equatable {} + let error = Error() + + var env: MessengerEnvironment = .unimplemented + env.cMix.get = { + var cMix: CMix = .unimplemented + cMix.getId.run = { 1234 } + cMix.makeLegacyReceptionIdentity.run = { .stub } + return cMix + } + env.getE2EParams.run = { "e2e-params".data(using: .utf8)! } + env.login.run = { _, _, _, _, _ in throw error } + let connect: MessengerConnect = .live(env) + + XCTAssertThrowsError(try connect()) { err in + XCTAssertEqual(err as? Error, error) + } + } +} + +private extension ReceptionIdentity { + static let stub = ReceptionIdentity( + id: "id".data(using: .utf8)!, + rsaPrivatePem: "rsaPrivatePem".data(using: .utf8)!, + salt: "salt".data(using: .utf8)!, + dhKeyPrivate: "dhKeyPrivate".data(using: .utf8)!, + e2eGrp: "e2eGrp".data(using: .utf8)! + ) +} diff --git a/Tests/XXMessengerClientTests/Messenger/Functors/MessengerCreateTests.swift b/Tests/XXMessengerClientTests/Messenger/Functors/MessengerCreateTests.swift new file mode 100644 index 0000000000000000000000000000000000000000..2180dda3db578b74a892b2166e66a9beb20357dd --- /dev/null +++ b/Tests/XXMessengerClientTests/Messenger/Functors/MessengerCreateTests.swift @@ -0,0 +1,157 @@ +import CustomDump +import XCTest +import XXClient +@testable import XXMessengerClient + +final class MessengerCreateTests: XCTestCase { + func testCreate() throws { + struct DidNewCMix: Equatable { + var ndfJSON: String + var storageDir: String + var password: Data + var registrationCode: String? + } + + var didDownloadNDF: [NDFEnvironment] = [] + var didGenerateSecret: [Int] = [] + var didSavePassword: [Data] = [] + var didRemoveDirectory: [String] = [] + var didCreateDirectory: [String] = [] + var didNewCMix: [DidNewCMix] = [] + + let ndf = "ndf".data(using: .utf8)! + let password = "password".data(using: .utf8)! + let storageDir = "storage-dir" + + var env: MessengerEnvironment = .unimplemented + env.ndfEnvironment = .unimplemented + env.downloadNDF.run = { ndfEnvironment in + didDownloadNDF.append(ndfEnvironment) + return ndf + } + env.generateSecret.run = { numBytes in + didGenerateSecret.append(numBytes) + return password + } + env.passwordStorage.save = { password in + didSavePassword.append(password) + } + env.storageDir = storageDir + env.fileManager.removeDirectory = { path in + didRemoveDirectory.append(path) + } + env.fileManager.createDirectory = { path in + didCreateDirectory.append(path) + } + env.newCMix.run = { ndfJSON, storageDir, password, registrationCode in + didNewCMix.append(.init( + ndfJSON: ndfJSON, + storageDir: storageDir, + password: password, + registrationCode: registrationCode + )) + } + let create: MessengerCreate = .live(env) + + try create() + + XCTAssertNoDifference(didDownloadNDF, [.unimplemented]) + XCTAssertNoDifference(didGenerateSecret, [32]) + XCTAssertNoDifference(didSavePassword, [password]) + XCTAssertNoDifference(didRemoveDirectory, [storageDir]) + XCTAssertNoDifference(didCreateDirectory, [storageDir]) + XCTAssertNoDifference(didNewCMix, [.init( + ndfJSON: String(data: ndf, encoding: .utf8)!, + storageDir: storageDir, + password: password, + registrationCode: nil + )]) + } + + func testDownloadNDFFailure() { + struct Error: Swift.Error, Equatable {} + let error = Error() + + var env: MessengerEnvironment = .unimplemented + env.ndfEnvironment = .unimplemented + env.downloadNDF.run = { _ in throw error } + let create: MessengerCreate = .live(env) + + XCTAssertThrowsError(try create()) { err in + XCTAssertEqual(err as? Error, error) + } + } + + func testSavePasswordFailure() { + struct Error: Swift.Error, Equatable {} + let error = Error() + + var env: MessengerEnvironment = .unimplemented + env.ndfEnvironment = .unimplemented + env.downloadNDF.run = { _ in "ndf".data(using: .utf8)! } + env.generateSecret.run = { _ in "password".data(using: .utf8)! } + env.passwordStorage.save = { _ in throw error } + let create: MessengerCreate = .live(env) + + XCTAssertThrowsError(try create()) { err in + XCTAssertEqual(err as? Error, error) + } + } + + func testRemoveDirectoryFailure() { + struct Error: Swift.Error, Equatable {} + let error = Error() + + var env: MessengerEnvironment = .unimplemented + env.ndfEnvironment = .unimplemented + env.downloadNDF.run = { _ in "ndf".data(using: .utf8)! } + env.generateSecret.run = { _ in "password".data(using: .utf8)! } + env.passwordStorage.save = { _ in } + env.storageDir = "storage-dir" + env.fileManager.removeDirectory = { _ in throw error } + let create: MessengerCreate = .live(env) + + XCTAssertThrowsError(try create()) { err in + XCTAssertEqual(err as? Error, error) + } + } + + func testCreateDirectoryFailure() { + struct Error: Swift.Error, Equatable {} + let error = Error() + + var env: MessengerEnvironment = .unimplemented + env.ndfEnvironment = .unimplemented + env.downloadNDF.run = { _ in "ndf".data(using: .utf8)! } + env.generateSecret.run = { _ in "password".data(using: .utf8)! } + env.passwordStorage.save = { _ in } + env.storageDir = "storage-dir" + env.fileManager.removeDirectory = { _ in } + env.fileManager.createDirectory = { _ in throw error } + let create: MessengerCreate = .live(env) + + XCTAssertThrowsError(try create()) { err in + XCTAssertEqual(err as? Error, error) + } + } + + func testNewCMixFailure() { + struct Error: Swift.Error, Equatable {} + let error = Error() + + var env: MessengerEnvironment = .unimplemented + env.ndfEnvironment = .unimplemented + env.downloadNDF.run = { _ in "ndf".data(using: .utf8)! } + env.generateSecret.run = { _ in "password".data(using: .utf8)! } + env.passwordStorage.save = { _ in } + env.storageDir = "storage-dir" + env.fileManager.removeDirectory = { _ in } + env.fileManager.createDirectory = { _ in } + env.newCMix.run = { _, _, _, _ in throw error } + let create: MessengerCreate = .live(env) + + XCTAssertThrowsError(try create()) { err in + XCTAssertEqual(err as? Error, error) + } + } +} diff --git a/Tests/XXMessengerClientTests/Messenger/Functors/MessengerIsConnectedTests.swift b/Tests/XXMessengerClientTests/Messenger/Functors/MessengerIsConnectedTests.swift new file mode 100644 index 0000000000000000000000000000000000000000..c7c5de8bc78a79257ce08725a32524a5ce78da50 --- /dev/null +++ b/Tests/XXMessengerClientTests/Messenger/Functors/MessengerIsConnectedTests.swift @@ -0,0 +1,20 @@ +import XCTest +@testable import XXMessengerClient + +final class MessengerIsConnectedTests: XCTestCase { + func testWithE2E() { + var env: MessengerEnvironment = .unimplemented + env.e2e.get = { .unimplemented } + let isConnected: MessengerIsConnected = .live(env) + + XCTAssertTrue(isConnected()) + } + + func testWithoutE2E() { + var env: MessengerEnvironment = .unimplemented + env.e2e.get = { nil } + let isConnected: MessengerIsConnected = .live(env) + + XCTAssertFalse(isConnected()) + } +} diff --git a/Tests/XXMessengerClientTests/Messenger/Functors/MessengerIsCreatedTests.swift b/Tests/XXMessengerClientTests/Messenger/Functors/MessengerIsCreatedTests.swift new file mode 100644 index 0000000000000000000000000000000000000000..246be9d3be6c04c58748ce024149da79fa8b63f7 --- /dev/null +++ b/Tests/XXMessengerClientTests/Messenger/Functors/MessengerIsCreatedTests.swift @@ -0,0 +1,37 @@ +import CustomDump +import XCTest +@testable import XXMessengerClient + +final class MessengerIsCreatedTests: XCTestCase { + func testStorageDirNotEmpty() { + var didIsDirectoryEmpty: [String] = [] + let storageDir = "storage-dir" + + var env: MessengerEnvironment = .unimplemented + env.storageDir = storageDir + env.fileManager.isDirectoryEmpty = { path in + didIsDirectoryEmpty.append(path) + return false + } + let isCreated: MessengerIsCreated = .live(env) + + XCTAssertTrue(isCreated()) + XCTAssertNoDifference(didIsDirectoryEmpty, [storageDir]) + } + + func testStorageDirEmpty() { + var didIsDirectoryEmpty: [String] = [] + let storageDir = "storage-dir" + + var env: MessengerEnvironment = .unimplemented + env.storageDir = storageDir + env.fileManager.isDirectoryEmpty = { path in + didIsDirectoryEmpty.append(path) + return true + } + let isCreated: MessengerIsCreated = .live(env) + + XCTAssertFalse(isCreated()) + XCTAssertNoDifference(didIsDirectoryEmpty, [storageDir]) + } +} diff --git a/Tests/XXMessengerClientTests/Messenger/Functors/MessengerIsLoadedTests.swift b/Tests/XXMessengerClientTests/Messenger/Functors/MessengerIsLoadedTests.swift new file mode 100644 index 0000000000000000000000000000000000000000..6f0591e264cbac1a12cf707b462b0be12d65aa74 --- /dev/null +++ b/Tests/XXMessengerClientTests/Messenger/Functors/MessengerIsLoadedTests.swift @@ -0,0 +1,20 @@ +import XCTest +@testable import XXMessengerClient + +final class MessengerIsLoadedTests: XCTestCase { + func testWithCMix() { + var env: MessengerEnvironment = .unimplemented + env.cMix.get = { .unimplemented } + let isLoaded: MessengerIsLoaded = .live(env) + + XCTAssertTrue(isLoaded()) + } + + func testWithoutCMix() { + var env: MessengerEnvironment = .unimplemented + env.cMix.get = { nil } + let isLoaded: MessengerIsLoaded = .live(env) + + XCTAssertFalse(isLoaded()) + } +} diff --git a/Tests/XXMessengerClientTests/Messenger/Functors/MessengerIsLoggedInTests.swift b/Tests/XXMessengerClientTests/Messenger/Functors/MessengerIsLoggedInTests.swift new file mode 100644 index 0000000000000000000000000000000000000000..c6ea87e64f2c300f9ab7090b8b56fc7e730ee765 --- /dev/null +++ b/Tests/XXMessengerClientTests/Messenger/Functors/MessengerIsLoggedInTests.swift @@ -0,0 +1,20 @@ +import XCTest +@testable import XXMessengerClient + +final class MessengerIsLoggedInTests: XCTestCase { + func testWithUD() { + var env: MessengerEnvironment = .unimplemented + env.ud.get = { .unimplemented } + let isLoggedIn: MessengerIsLoggedIn = .live(env) + + XCTAssertTrue(isLoggedIn()) + } + + func testWithoutUD() { + var env: MessengerEnvironment = .unimplemented + env.ud.get = { nil } + let isLoggedIn: MessengerIsLoggedIn = .live(env) + + XCTAssertFalse(isLoggedIn()) + } +} diff --git a/Tests/XXMessengerClientTests/Messenger/Functors/MessengerIsRegisteredTests.swift b/Tests/XXMessengerClientTests/Messenger/Functors/MessengerIsRegisteredTests.swift new file mode 100644 index 0000000000000000000000000000000000000000..b6dca3e9e4a09b5cc78b84b3d9a56ea12b032e3a --- /dev/null +++ b/Tests/XXMessengerClientTests/Messenger/Functors/MessengerIsRegisteredTests.swift @@ -0,0 +1,68 @@ +import XCTest +import XXClient +@testable import XXMessengerClient + +final class MessengerIsRegisteredTests: XCTestCase { + func testRegistered() throws { + var didIsRegisteredWithUD: [Int] = [] + + var env: MessengerEnvironment = .unimplemented + env.e2e.get = { + var e2e: E2E = .unimplemented + e2e.getId.run = { 1234 } + return e2e + } + env.isRegisteredWithUD.run = { e2eId in + didIsRegisteredWithUD.append(e2eId) + return true + } + let isRegistered: MessengerIsRegistered = .live(env) + + XCTAssertTrue(try isRegistered()) + XCTAssertEqual(didIsRegisteredWithUD, [1234]) + } + + func testNotRegistered() throws { + var env: MessengerEnvironment = .unimplemented + env.e2e.get = { + var e2e: E2E = .unimplemented + e2e.getId.run = { 1234 } + return e2e + } + env.isRegisteredWithUD.run = { _ in false } + let isRegistered: MessengerIsRegistered = .live(env) + + XCTAssertFalse(try isRegistered()) + } + + func testWithoutE2E() { + var env: MessengerEnvironment = .unimplemented + env.e2e.get = { nil } + let isRegistered: MessengerIsRegistered = .live(env) + + XCTAssertThrowsError(try isRegistered()) { err in + XCTAssertEqual( + err as? MessengerIsRegistered.Error, + MessengerIsRegistered.Error.notConnected + ) + } + } + + func testIsRegisteredWithUDFailure() { + struct Error: Swift.Error, Equatable {} + let error = Error() + + var env: MessengerEnvironment = .unimplemented + env.e2e.get = { + var e2e: E2E = .unimplemented + e2e.getId.run = { 1234 } + return e2e + } + env.isRegisteredWithUD.run = { _ in throw error } + let isRegistered: MessengerIsRegistered = .live(env) + + XCTAssertThrowsError(try isRegistered()) { err in + XCTAssertEqual(err as? Error, error) + } + } +} diff --git a/Tests/XXMessengerClientTests/Messenger/Functors/MessengerLoadTests.swift b/Tests/XXMessengerClientTests/Messenger/Functors/MessengerLoadTests.swift new file mode 100644 index 0000000000000000000000000000000000000000..5eb90e3ae5379959c4891a5c0b36032827b8eae7 --- /dev/null +++ b/Tests/XXMessengerClientTests/Messenger/Functors/MessengerLoadTests.swift @@ -0,0 +1,76 @@ +import CustomDump +import XCTest +import XXClient +@testable import XXMessengerClient + +final class MessengerLoadTests: XCTestCase { + func testLoad() throws { + struct DidLoadCMix: Equatable { + var storageDir: String + var password: Data + var cMixParamsJSON: Data + } + var didLoadCMix: [DidLoadCMix] = [] + var didSetCMix: [CMix?] = [] + + let storageDir = "test-storage-dir" + let password = "password".data(using: .utf8)! + let cMixParams = "cmix-params".data(using: .utf8)! + + var env: MessengerEnvironment = .unimplemented + env.cMix.set = { didSetCMix.append($0) } + env.storageDir = storageDir + env.passwordStorage.load = { password } + env.getCMixParams.run = { cMixParams } + env.loadCMix.run = { storageDir, password, cMixParamsJSON in + didLoadCMix.append(.init( + storageDir: storageDir, + password: password, + cMixParamsJSON: cMixParamsJSON + )) + return .unimplemented + } + let load: MessengerLoad = .live(env) + + try load() + + XCTAssertNoDifference(didLoadCMix, [ + DidLoadCMix( + storageDir: storageDir, + password: password, + cMixParamsJSON: cMixParams + ) + ]) + XCTAssertEqual(didSetCMix.compactMap{ $0 }.count, 1) + } + + func testMissingPassword() { + var env: MessengerEnvironment = .unimplemented + env.storageDir = "storage-dir" + env.passwordStorage.load = { throw PasswordStorage.MissingPasswordError() } + let load: MessengerLoad = .live(env) + + XCTAssertThrowsError(try load()) { err in + XCTAssertEqual( + err as? PasswordStorage.MissingPasswordError, + PasswordStorage.MissingPasswordError() + ) + } + } + + func testLoadCMixFailure() { + struct Error: Swift.Error, Equatable {} + let error = Error() + + var env: MessengerEnvironment = .unimplemented + env.storageDir = "storage-dir" + env.passwordStorage.load = { "password".data(using: .utf8)! } + env.getCMixParams.run = { "cmix-params".data(using: .utf8)! } + env.loadCMix.run = { _, _, _ in throw error } + let load: MessengerLoad = .live(env) + + XCTAssertThrowsError(try load()) { err in + XCTAssertEqual(err as? Error, error) + } + } +} diff --git a/Tests/XXMessengerClientTests/Messenger/Functors/MessengerLogInTests.swift b/Tests/XXMessengerClientTests/Messenger/Functors/MessengerLogInTests.swift new file mode 100644 index 0000000000000000000000000000000000000000..83dd2cd02fc79fcb5d460d244c025d05590a2233 --- /dev/null +++ b/Tests/XXMessengerClientTests/Messenger/Functors/MessengerLogInTests.swift @@ -0,0 +1,180 @@ +import CustomDump +import XCTest +import XXClient +@testable import XXMessengerClient + +final class MessengerLogInTests: XCTestCase { + func testLogin() throws { + var didNewOrLoadUDWithParams: [NewOrLoadUd.Params] = [] + var didNewOrLoadUDWithFollower: [UdNetworkStatus] = [] + var didSetUD: [UserDiscovery?] = [] + + let e2eId = 1234 + let networkFollowerStatus: NetworkFollowerStatus = .stopped + let udCertFromNDF = "ndf-ud-cert".data(using: .utf8)! + let udContactFromNDF = "ndf-ud-contact".data(using: .utf8)! + let udAddressFromNDF = "ndf-ud-address" + + var env: MessengerEnvironment = .unimplemented + env.cMix.get = { + var cMix: CMix = .unimplemented + cMix.networkFollowerStatus.run = { networkFollowerStatus } + return cMix + } + env.e2e.get = { + var e2e: E2E = .unimplemented + e2e.getId.run = { e2eId } + e2e.getUdCertFromNdf.run = { udCertFromNDF } + e2e.getUdContactFromNdf.run = { udContactFromNDF } + e2e.getUdAddressFromNdf.run = { udAddressFromNDF } + return e2e + } + env.ud.set = { didSetUD.append($0) } + env.udCert = nil + env.udContact = nil + env.udAddress = nil + env.newOrLoadUd.run = { params, follower in + didNewOrLoadUDWithParams.append(params) + didNewOrLoadUDWithFollower.append(follower) + return .unimplemented + } + let logIn: MessengerLogIn = .live(env) + try logIn() + + XCTAssertNoDifference(didNewOrLoadUDWithParams, [.init( + e2eId: e2eId, + username: nil, + registrationValidationSignature: nil, + cert: udCertFromNDF, + contactFile: udContactFromNDF, + address: udAddressFromNDF + )]) + XCTAssertEqual(didNewOrLoadUDWithFollower.count, 1) + XCTAssertEqual( + didNewOrLoadUDWithFollower.first?.handle(), + networkFollowerStatus.rawValue + ) + XCTAssertEqual(didSetUD.compactMap { $0 }.count, 1) + } + + func testLoginWithAlternativeUD() throws { + var didNewOrLoadUDWithParams: [NewOrLoadUd.Params] = [] + var didSetUD: [UserDiscovery?] = [] + + let e2eId = 1234 + let altUdCert = "alt-ud-cert".data(using: .utf8)! + let altUdContact = "alt-ud-contact".data(using: .utf8)! + let altUdAddress = "alt-ud-address" + + var env: MessengerEnvironment = .unimplemented + env.cMix.get = { + var cMix: CMix = .unimplemented + cMix.networkFollowerStatus.run = { .running } + return cMix + } + env.e2e.get = { + var e2e: E2E = .unimplemented + e2e.getId.run = { e2eId } + return e2e + } + env.ud.set = { didSetUD.append($0) } + env.udCert = altUdCert + env.udContact = altUdContact + env.udAddress = altUdAddress + env.newOrLoadUd.run = { params, _ in + didNewOrLoadUDWithParams.append(params) + return .unimplemented + } + let logIn: MessengerLogIn = .live(env) + try logIn() + + XCTAssertNoDifference(didNewOrLoadUDWithParams, [.init( + e2eId: e2eId, + username: nil, + registrationValidationSignature: nil, + cert: altUdCert, + contactFile: altUdContact, + address: altUdAddress + )]) + XCTAssertEqual(didSetUD.compactMap { $0 }.count, 1) + } + + func testLoginWithoutCMix() { + var env: MessengerEnvironment = .unimplemented + env.cMix.get = { nil } + let logIn: MessengerLogIn = .live(env) + + XCTAssertThrowsError(try logIn()) { error in + XCTAssertEqual( + error as? MessengerLogIn.Error, + MessengerLogIn.Error.notLoaded + ) + } + } + + func testLoginWithoutE2E() { + var env: MessengerEnvironment = .unimplemented + env.cMix.get = { .unimplemented } + env.e2e.get = { nil } + let logIn: MessengerLogIn = .live(env) + + XCTAssertThrowsError(try logIn()) { error in + XCTAssertEqual( + error as? MessengerLogIn.Error, + MessengerLogIn.Error.notConnected + ) + } + } + + func testGetUdContactFromNdfFailure() { + struct Error: Swift.Error, Equatable {} + let error = Error() + + var env: MessengerEnvironment = .unimplemented + env.cMix.get = { + var cMix: CMix = .unimplemented + cMix.networkFollowerStatus.run = { .running } + return cMix + } + env.e2e.get = { + var e2e: E2E = .unimplemented + e2e.getId.run = { 1234 } + e2e.getUdCertFromNdf.run = { "ndf-ud-cert".data(using: .utf8)! } + e2e.getUdContactFromNdf.run = { throw error } + return e2e + } + env.udCert = nil + env.udContact = nil + let logIn: MessengerLogIn = .live(env) + + XCTAssertThrowsError(try logIn()) { err in + XCTAssertEqual(err as? Error, error) + } + } + + func testNewOrLoadUdFailure() { + struct Error: Swift.Error, Equatable {} + let error = Error() + + var env: MessengerEnvironment = .unimplemented + env.cMix.get = { + var cMix: CMix = .unimplemented + cMix.networkFollowerStatus.run = { .running } + return cMix + } + env.e2e.get = { + var e2e: E2E = .unimplemented + e2e.getId.run = { 1234 } + return e2e + } + env.udCert = "ud-cert".data(using: .utf8)! + env.udContact = "ud-contact".data(using: .utf8)! + env.udAddress = "ud-address" + env.newOrLoadUd.run = { _, _ in throw error } + let logIn: MessengerLogIn = .live(env) + + XCTAssertThrowsError(try logIn()) { err in + XCTAssertEqual(err as? Error, error) + } + } +} diff --git a/Tests/XXMessengerClientTests/Messenger/Functors/MessengerRegisterTests.swift b/Tests/XXMessengerClientTests/Messenger/Functors/MessengerRegisterTests.swift new file mode 100644 index 0000000000000000000000000000000000000000..ef96b25197a388e7b258a341638e053cb63ff3ad --- /dev/null +++ b/Tests/XXMessengerClientTests/Messenger/Functors/MessengerRegisterTests.swift @@ -0,0 +1,196 @@ +import CustomDump +import XCTest +import XXClient +@testable import XXMessengerClient + +final class MessengerRegisterTests: XCTestCase { + func testRegister() throws { + var didNewOrLoadUDWithParams: [NewOrLoadUd.Params] = [] + var didNewOrLoadUDWithFollower: [UdNetworkStatus] = [] + var didSetUD: [UserDiscovery?] = [] + + let e2eId = 1234 + let networkFollowerStatus: NetworkFollowerStatus = .stopped + let registrationSignature = "registration-signature".data(using: .utf8)! + let udCertFromNDF = "ndf-ud-cert".data(using: .utf8)! + let udContactFromNDF = "ndf-ud-contact".data(using: .utf8)! + let udAddressFromNDF = "ndf-ud-address" + let username = "new-user-name" + + var env: MessengerEnvironment = .unimplemented + env.cMix.get = { + var cMix: CMix = .unimplemented + cMix.networkFollowerStatus.run = { networkFollowerStatus } + cMix.getReceptionRegistrationValidationSignature.run = { + registrationSignature + } + return cMix + } + env.e2e.get = { + var e2e: E2E = .unimplemented + e2e.getId.run = { e2eId } + e2e.getUdCertFromNdf.run = { udCertFromNDF } + e2e.getUdContactFromNdf.run = { udContactFromNDF } + e2e.getUdAddressFromNdf.run = { udAddressFromNDF } + return e2e + } + env.ud.set = { didSetUD.append($0) } + env.udCert = nil + env.udContact = nil + env.udAddress = nil + env.newOrLoadUd.run = { params, follower in + didNewOrLoadUDWithParams.append(params) + didNewOrLoadUDWithFollower.append(follower) + return .unimplemented + } + let register: MessengerRegister = .live(env) + try register(username: username) + + XCTAssertNoDifference(didNewOrLoadUDWithParams, [.init( + e2eId: e2eId, + username: username, + registrationValidationSignature: registrationSignature, + cert: udCertFromNDF, + contactFile: udContactFromNDF, + address: udAddressFromNDF + )]) + XCTAssertEqual(didNewOrLoadUDWithFollower.count, 1) + XCTAssertEqual( + didNewOrLoadUDWithFollower.first?.handle(), + networkFollowerStatus.rawValue + ) + XCTAssertEqual(didSetUD.compactMap { $0 }.count, 1) + } + + func testRegisterWithAlternativeUD() throws { + var didNewOrLoadUDWithParams: [NewOrLoadUd.Params] = [] + var didSetUD: [UserDiscovery?] = [] + + let e2eId = 1234 + let registrationSignature = "registration-signature".data(using: .utf8)! + let altUdCert = "alt-ud-cert".data(using: .utf8)! + let altUdContact = "alt-ud-contact".data(using: .utf8)! + let altUdAddress = "alt-ud-address" + let username = "new-user-name" + + var env: MessengerEnvironment = .unimplemented + env.cMix.get = { + var cMix: CMix = .unimplemented + cMix.networkFollowerStatus.run = { .running } + cMix.getReceptionRegistrationValidationSignature.run = { + registrationSignature + } + return cMix + } + env.e2e.get = { + var e2e: E2E = .unimplemented + e2e.getId.run = { e2eId } + return e2e + } + env.ud.set = { didSetUD.append($0) } + env.udCert = altUdCert + env.udContact = altUdContact + env.udAddress = altUdAddress + env.newOrLoadUd.run = { params, _ in + didNewOrLoadUDWithParams.append(params) + return .unimplemented + } + let register: MessengerRegister = .live(env) + try register(username: username) + + XCTAssertNoDifference(didNewOrLoadUDWithParams, [.init( + e2eId: e2eId, + username: username, + registrationValidationSignature: registrationSignature, + cert: altUdCert, + contactFile: altUdContact, + address: altUdAddress + )]) + XCTAssertEqual(didSetUD.compactMap { $0 }.count, 1) + } + + func testRegisterWithoutCMix() { + var env: MessengerEnvironment = .unimplemented + env.cMix.get = { nil } + let register: MessengerRegister = .live(env) + + XCTAssertThrowsError(try register(username: "new-user-name")) { error in + XCTAssertEqual( + error as? MessengerRegister.Error, + MessengerRegister.Error.notLoaded + ) + } + } + + func testRegisterWithoutE2E() { + var env: MessengerEnvironment = .unimplemented + env.cMix.get = { .unimplemented } + env.e2e.get = { nil } + let register: MessengerRegister = .live(env) + + XCTAssertThrowsError(try register(username: "new-user-name")) { error in + XCTAssertEqual( + error as? MessengerRegister.Error, + MessengerRegister.Error.notConnected + ) + } + } + + func testGetUdContactFromNdfFailure() { + struct Error: Swift.Error, Equatable {} + let error = Error() + + var env: MessengerEnvironment = .unimplemented + env.cMix.get = { + var cMix: CMix = .unimplemented + cMix.networkFollowerStatus.run = { .running } + cMix.getReceptionRegistrationValidationSignature.run = { + "registration-signature".data(using: .utf8)! + } + return cMix + } + env.e2e.get = { + var e2e: E2E = .unimplemented + e2e.getId.run = { 1234 } + e2e.getUdCertFromNdf.run = { "ndf-ud-cert".data(using: .utf8)! } + e2e.getUdContactFromNdf.run = { throw error } + return e2e + } + env.udCert = nil + env.udContact = nil + let register: MessengerRegister = .live(env) + + XCTAssertThrowsError(try register(username: "new-user-name")) { err in + XCTAssertEqual(err as? Error, error) + } + } + + func testNewOrLoadUdFailure() { + struct Error: Swift.Error, Equatable {} + let error = Error() + + var env: MessengerEnvironment = .unimplemented + env.cMix.get = { + var cMix: CMix = .unimplemented + cMix.networkFollowerStatus.run = { .running } + cMix.getReceptionRegistrationValidationSignature.run = { + "registration-signature".data(using: .utf8)! + } + return cMix + } + env.e2e.get = { + var e2e: E2E = .unimplemented + e2e.getId.run = { 1234 } + return e2e + } + env.udCert = "ud-cert".data(using: .utf8)! + env.udContact = "ud-contact".data(using: .utf8)! + env.udAddress = "ud-address" + env.newOrLoadUd.run = { _, _ in throw error } + let register: MessengerRegister = .live(env) + + XCTAssertThrowsError(try register(username: "new-user-name")) { err in + XCTAssertEqual(err as? Error, error) + } + } +} diff --git a/Tests/XXMessengerClientTests/Messenger/Functors/MessengerStartTests.swift b/Tests/XXMessengerClientTests/Messenger/Functors/MessengerStartTests.swift new file mode 100644 index 0000000000000000000000000000000000000000..7b3e33af570bd8a522d26091ef551be5fec7b674 --- /dev/null +++ b/Tests/XXMessengerClientTests/Messenger/Functors/MessengerStartTests.swift @@ -0,0 +1,68 @@ +import CustomDump +import XCTest +import XXClient +@testable import XXMessengerClient + +final class MessengerStartTests: XCTestCase { + func testStart() throws { + var didStartNetworkFollower: [Int] = [] + + var env: MessengerEnvironment = .unimplemented + env.cMix.get = { + var cMix: CMix = .unimplemented + cMix.networkFollowerStatus.run = { .stopped } + cMix.startNetworkFollower.run = { timeoutMS in + didStartNetworkFollower.append(timeoutMS) + } + return cMix + } + let start: MessengerStart = .live(env) + + try start(timeoutMS: 123) + + XCTAssertNoDifference(didStartNetworkFollower, [123]) + } + + func testStartWhenNotLoaded() { + var env: MessengerEnvironment = .unimplemented + env.cMix.get = { nil } + let start: MessengerStart = .live(env) + + XCTAssertThrowsError(try start()) { error in + XCTAssertEqual( + error as? MessengerStart.Error, + MessengerStart.Error.notLoaded + ) + } + } + + func testStartWhenRunning() throws { + var env: MessengerEnvironment = .unimplemented + env.cMix.get = { + var cMix: CMix = .unimplemented + cMix.networkFollowerStatus.run = { .running } + return cMix + } + let start: MessengerStart = .live(env) + + try start() + } + + func testStartNetworkFollowerFailure() { + struct Error: Swift.Error, Equatable {} + let error = Error() + + var env: MessengerEnvironment = .unimplemented + env.cMix.get = { + var cMix: CMix = .unimplemented + cMix.networkFollowerStatus.run = { .stopped } + cMix.startNetworkFollower.run = { _ in throw error } + return cMix + } + let start: MessengerStart = .live(env) + + XCTAssertThrowsError(try start()) { err in + XCTAssertEqual(err as? Error, error) + } + } +} diff --git a/Tests/XXMessengerClientTests/Messenger/Functors/MessengerWaitForNetworkTests.swift b/Tests/XXMessengerClientTests/Messenger/Functors/MessengerWaitForNetworkTests.swift new file mode 100644 index 0000000000000000000000000000000000000000..1dbae73528c82a00c4270bf99c759356c7488171 --- /dev/null +++ b/Tests/XXMessengerClientTests/Messenger/Functors/MessengerWaitForNetworkTests.swift @@ -0,0 +1,55 @@ +import CustomDump +import XCTest +import XXClient +@testable import XXMessengerClient + +final class MessengerWaitForNetworkTests: XCTestCase { + func testWaitSuccess() throws { + var didWaitForNetwork: [Int] = [] + + var env: MessengerEnvironment = .unimplemented + env.cMix.get = { + var cMix: CMix = .unimplemented + cMix.waitForNetwork.run = { timeoutMS in + didWaitForNetwork.append(timeoutMS) + return true + } + return cMix + } + let waitForNetwork: MessengerWaitForNetwork = .live(env) + + try waitForNetwork(timeoutMS: 123) + + XCTAssertNoDifference(didWaitForNetwork, [123]) + } + + func testWaitWhenNotLoaded() { + var env: MessengerEnvironment = .unimplemented + env.cMix.get = { nil } + let waitForNetwork: MessengerWaitForNetwork = .live(env) + + XCTAssertThrowsError(try waitForNetwork()) { error in + XCTAssertEqual( + error as? MessengerWaitForNetwork.Error, + MessengerWaitForNetwork.Error.notLoaded + ) + } + } + + func testWaitTimeout() { + var env: MessengerEnvironment = .unimplemented + env.cMix.get = { + var cMix: CMix = .unimplemented + cMix.waitForNetwork.run = { _ in false } + return cMix + } + let waitForNetwork: MessengerWaitForNetwork = .live(env) + + XCTAssertThrowsError(try waitForNetwork()) { error in + XCTAssertEqual( + error as? MessengerWaitForNetwork.Error, + MessengerWaitForNetwork.Error.timeout + ) + } + } +} diff --git a/Tests/XXMessengerClientTests/Messenger/Functors/MessengerWaitForNodesTests.swift b/Tests/XXMessengerClientTests/Messenger/Functors/MessengerWaitForNodesTests.swift new file mode 100644 index 0000000000000000000000000000000000000000..bc23afb53c604fd0b737bba5db544edfebdffe68 --- /dev/null +++ b/Tests/XXMessengerClientTests/Messenger/Functors/MessengerWaitForNodesTests.swift @@ -0,0 +1,119 @@ +import CustomDump +import XCTest +import XXClient +@testable import XXMessengerClient + +final class MessengerWaitForNodesTests: XCTestCase { + func testWaitWhenNotLoaded() { + var env: MessengerEnvironment = .unimplemented + env.cMix.get = { nil } + let waitForNodes: MessengerWaitForNodes = .live(env) + + XCTAssertThrowsError(try waitForNodes()) { error in + XCTAssertEqual( + error as? MessengerWaitForNodes.Error, + MessengerWaitForNodes.Error.notLoaded + ) + } + } + + func testWaitWhenHasTargetRatio() throws { + var didProgress: [NodeRegistrationReport] = [] + + var env: MessengerEnvironment = .unimplemented + env.cMix.get = { + var cMix: CMix = .unimplemented + cMix.getNodeRegistrationStatus.run = { + NodeRegistrationReport(registered: 8, total: 10) + } + return cMix + } + let waitForNodes: MessengerWaitForNodes = .live(env) + + try waitForNodes( + targetRatio: 0.7, + sleepMS: 123, + retries: 3, + onProgress: { didProgress.append($0) } + ) + + XCTAssertNoDifference(didProgress, [ + NodeRegistrationReport(registered: 8, total: 10) + ]) + } + + func testWaitForTargetRatio() throws { + var didSleep: [Int] = [] + var didProgress: [NodeRegistrationReport] = [] + + var reports: [NodeRegistrationReport] = [ + .init(registered: 0, total: 10), + .init(registered: 3, total: 10), + .init(registered: 8, total: 10), + ] + + var env: MessengerEnvironment = .unimplemented + env.cMix.get = { + var cMix: CMix = .unimplemented + cMix.getNodeRegistrationStatus.run = { reports.removeFirst() } + return cMix + } + env.sleep = { didSleep.append($0) } + let waitForNodes: MessengerWaitForNodes = .live(env) + + try waitForNodes( + targetRatio: 0.7, + sleepMS: 123, + retries: 3, + onProgress: { didProgress.append($0) } + ) + + XCTAssertNoDifference(didSleep, [123, 123]) + XCTAssertNoDifference(didProgress, [ + NodeRegistrationReport(registered: 0, total: 10), + NodeRegistrationReport(registered: 3, total: 10), + NodeRegistrationReport(registered: 8, total: 10), + ]) + } + + func testWaitTimeout() { + var didSleep: [Int] = [] + var didProgress: [NodeRegistrationReport] = [] + + var reports: [NodeRegistrationReport] = [ + .init(registered: 0, total: 10), + .init(registered: 3, total: 10), + .init(registered: 5, total: 10), + .init(registered: 6, total: 10), + ] + + var env: MessengerEnvironment = .unimplemented + env.cMix.get = { + var cMix: CMix = .unimplemented + cMix.getNodeRegistrationStatus.run = { reports.removeFirst() } + return cMix + } + env.sleep = { didSleep.append($0) } + let waitForNodes: MessengerWaitForNodes = .live(env) + + XCTAssertThrowsError(try waitForNodes( + targetRatio: 0.7, + sleepMS: 123, + retries: 3, + onProgress: { didProgress.append($0) } + )) { error in + XCTAssertEqual( + error as? MessengerWaitForNodes.Error, + MessengerWaitForNodes.Error.timeout + ) + } + + XCTAssertNoDifference(didSleep, [123, 123, 123]) + XCTAssertNoDifference(didProgress, [ + NodeRegistrationReport(registered: 0, total: 10), + NodeRegistrationReport(registered: 3, total: 10), + NodeRegistrationReport(registered: 5, total: 10), + NodeRegistrationReport(registered: 6, total: 10), + ]) + } +} diff --git a/Tests/XXMessengerClientTests/Messenger/Utils/StoredTests.swift b/Tests/XXMessengerClientTests/Messenger/Utils/StoredTests.swift new file mode 100644 index 0000000000000000000000000000000000000000..154f61967627500def1526f2944aeb074792c748 --- /dev/null +++ b/Tests/XXMessengerClientTests/Messenger/Utils/StoredTests.swift @@ -0,0 +1,18 @@ +import XCTest +@testable import XXMessengerClient + +final class StoredTests: XCTestCase { + func testInMemory() throws { + let stored: Stored<String?> = .inMemory() + + XCTAssertNil(stored()) + + stored.set("test") + + XCTAssertEqual(stored(), "test") + + stored.set(nil) + + XCTAssertNil(stored()) + } +}