diff --git a/Docs/XXMessengerClient.md b/Docs/XXMessengerClient.md index 05afa4aaa33cb74d114d0548faf8c157e00678ce..971aefd97c91f7b210ea1bdeb456a257cb89ae71 100644 --- a/Docs/XXMessengerClient.md +++ b/Docs/XXMessengerClient.md @@ -4,7 +4,7 @@ ## â–¶ï¸ Instantiate messenger -Example: +### Example ```swift // setup environment: @@ -24,7 +24,7 @@ let messenger: Messenger = .live(environment) ## 🚀 Start messenger -Example: +### Example ```swift // allow cancellation of callbacks: @@ -84,7 +84,7 @@ func start(messenger: Messenger) throws { ## 🛠Use client components directly -Example: +### Example ```swift // get cMix: @@ -95,4 +95,60 @@ let e2e = messenger.e2e() // get UserDicovery: let ud = messenger.ud() -``` \ No newline at end of file + +// get Backup: +let backup = messenger.backup() +``` + +## 💾 Backup + +### Make backup + +```swift +// start receiving backup data before starting or resuming backup: +let cancellable = messenger.registerBackupCallback(.init { data in + // handle backup data, save on disk, upload to cloud, etc. +}) + +// check if backup is already running: +if messenger.isBackupRunning() == false { + do { + // try to resume previous backup: + try messenger.resumeBackup() + } catch { + // try to start a new backup: + let params: BackupParams = ... + try messenger.startBackup( + password: "backup-passphrase", + params: params + ) + } +} + +// update params in the backup: +let params: BackupParams = ... +try messenger.backupParams(params) + +// stop the backup: +try messenger.stopBackup() + +// optionally stop receiving backup data +cancellable.cancel() +``` + +When starting a new backup you must provide `BackupParams` to prevent creating backups that does not contain it. + +The registered backup callback can be reused later when a new backup is started. There is no need to cancel it and register a new callback in such a case. + +### Restore from backup + +```swift +let result = try messenger.restoreBackup( + backupData: ..., + backupPassphrase: "backup-passphrase" +) + +// handle restoration result +``` + +If no error was thrown during restoration, the `Messenger` is already loaded, started, connected, and logged in. \ No newline at end of file diff --git a/Sources/XXMessengerClient/Messenger/Functions/MessengerBackupParams.swift b/Sources/XXMessengerClient/Messenger/Functions/MessengerBackupParams.swift new file mode 100644 index 0000000000000000000000000000000000000000..f5ac71f04d82b27fd3585acfbfe89b4372034ac2 --- /dev/null +++ b/Sources/XXMessengerClient/Messenger/Functions/MessengerBackupParams.swift @@ -0,0 +1,33 @@ +import Bindings +import XCTestDynamicOverlay + +public struct MessengerBackupParams { + public enum Error: Swift.Error, Equatable { + case notRunning + } + + public var run: (BackupParams) throws -> Void + + public func callAsFunction(_ params: BackupParams) throws { + try run(params) + } +} + +extension MessengerBackupParams { + public static func live(_ env: MessengerEnvironment) -> MessengerBackupParams { + MessengerBackupParams { params in + guard let backup = env.backup(), backup.isRunning() else { + throw Error.notRunning + } + let paramsData = try params.encode() + let paramsString = String(data: paramsData, encoding: .utf8)! + backup.addJSON(paramsString) + } + } +} + +extension MessengerBackupParams { + public static let unimplemented = MessengerBackupParams( + run: XCTUnimplemented("\(Self.self)") + ) +} diff --git a/Sources/XXMessengerClient/Messenger/Functions/MessengerIsBackupRunning.swift b/Sources/XXMessengerClient/Messenger/Functions/MessengerIsBackupRunning.swift new file mode 100644 index 0000000000000000000000000000000000000000..08453e23cb8eddf2190ac49b5870489980b88765 --- /dev/null +++ b/Sources/XXMessengerClient/Messenger/Functions/MessengerIsBackupRunning.swift @@ -0,0 +1,24 @@ +import XCTestDynamicOverlay +import XXClient + +public struct MessengerIsBackupRunning { + public var run: () -> Bool + + public func callAsFunction() -> Bool { + run() + } +} + +extension MessengerIsBackupRunning { + public static func live(_ env: MessengerEnvironment) -> MessengerIsBackupRunning { + MessengerIsBackupRunning { + env.backup()?.isRunning() == true + } + } +} + +extension MessengerIsBackupRunning { + public static let unimplemented = MessengerIsBackupRunning( + run: XCTUnimplemented("\(Self.self)") + ) +} diff --git a/Sources/XXMessengerClient/Messenger/Functions/MessengerRegisterBackupCallback.swift b/Sources/XXMessengerClient/Messenger/Functions/MessengerRegisterBackupCallback.swift new file mode 100644 index 0000000000000000000000000000000000000000..f7c9a251072b04f28f7c12f527e42be7174dce15 --- /dev/null +++ b/Sources/XXMessengerClient/Messenger/Functions/MessengerRegisterBackupCallback.swift @@ -0,0 +1,24 @@ +import XCTestDynamicOverlay +import XXClient + +public struct MessengerRegisterBackupCallback { + public var run: (UpdateBackupFunc) -> Cancellable + + public func callAsFunction(_ callback: UpdateBackupFunc) -> Cancellable { + run(callback) + } +} + +extension MessengerRegisterBackupCallback { + public static func live(_ env: MessengerEnvironment) -> MessengerRegisterBackupCallback { + MessengerRegisterBackupCallback { callback in + env.backupCallbacks.register(callback) + } + } +} + +extension MessengerRegisterBackupCallback { + public static let unimplemented = MessengerRegisterBackupCallback( + run: XCTUnimplemented("\(Self.self)", placeholder: Cancellable {}) + ) +} diff --git a/Sources/XXMessengerClient/Messenger/Functions/MessengerRestoreBackup.swift b/Sources/XXMessengerClient/Messenger/Functions/MessengerRestoreBackup.swift index 66f50dd550930409dd164315095b7517fd34acca..b196faca4b578bf3ea9bc534073be015b24dc816 100644 --- a/Sources/XXMessengerClient/Messenger/Functions/MessengerRestoreBackup.swift +++ b/Sources/XXMessengerClient/Messenger/Functions/MessengerRestoreBackup.swift @@ -42,9 +42,8 @@ extension MessengerRestoreBackup { sessionPassword: password, backupFileContents: backupData ) - let decoder = JSONDecoder() let paramsData = report.params.data(using: .utf8)! - let params = try decoder.decode(BackupParams.self, from: paramsData) + let params = try BackupParams.decode(paramsData) let cMix = try env.loadCMix( storageDir: storageDir, password: password, diff --git a/Sources/XXMessengerClient/Messenger/Functions/MessengerResumeBackup.swift b/Sources/XXMessengerClient/Messenger/Functions/MessengerResumeBackup.swift new file mode 100644 index 0000000000000000000000000000000000000000..9c4d62a09fe35d22c4afc7c1d6c7fed50d5b1951 --- /dev/null +++ b/Sources/XXMessengerClient/Messenger/Functions/MessengerResumeBackup.swift @@ -0,0 +1,44 @@ +import Bindings +import XCTestDynamicOverlay + +public struct MessengerResumeBackup { + public enum Error: Swift.Error, Equatable { + case isRunning + case notConnected + case notLoggedIn + } + + public var run: () throws -> Void + + public func callAsFunction() throws { + try run() + } +} + +extension MessengerResumeBackup { + public static func live(_ env: MessengerEnvironment) -> MessengerResumeBackup { + MessengerResumeBackup { + guard env.backup()?.isRunning() != true else { + throw Error.isRunning + } + guard let e2e = env.e2e() else { + throw Error.notConnected + } + guard let ud = env.ud() else { + throw Error.notLoggedIn + } + let backup = try env.resumeBackup( + e2eId: e2e.getId(), + udId: ud.getId(), + callback: env.backupCallbacks.registered() + ) + env.backup.set(backup) + } + } +} + +extension MessengerResumeBackup { + public static let unimplemented = MessengerResumeBackup( + run: XCTUnimplemented("\(Self.self)") + ) +} diff --git a/Sources/XXMessengerClient/Messenger/Functions/MessengerStartBackup.swift b/Sources/XXMessengerClient/Messenger/Functions/MessengerStartBackup.swift new file mode 100644 index 0000000000000000000000000000000000000000..254bc6b9268f03c9c01d6ed8086d2c1e55c1fd89 --- /dev/null +++ b/Sources/XXMessengerClient/Messenger/Functions/MessengerStartBackup.swift @@ -0,0 +1,62 @@ +import XCTestDynamicOverlay +import XXClient + +public struct MessengerStartBackup { + public enum Error: Swift.Error, Equatable { + case isRunning + case notConnected + case notLoggedIn + } + + public var run: (String, BackupParams) throws -> Void + + public func callAsFunction( + password: String, + params: BackupParams + ) throws { + try run(password, params) + } +} + +extension MessengerStartBackup { + public static func live(_ env: MessengerEnvironment) -> MessengerStartBackup { + MessengerStartBackup { password, params in + guard env.backup()?.isRunning() != true else { + throw Error.isRunning + } + guard let e2e = env.e2e() else { + throw Error.notConnected + } + guard let ud = env.ud() else { + throw Error.notLoggedIn + } + let paramsData = try params.encode() + let paramsString = String(data: paramsData, encoding: .utf8)! + var didAddParams = false + func addParams() { + guard let backup = env.backup() else { return } + backup.addJSON(paramsString) + didAddParams = true + } + let backup = try env.initializeBackup( + e2eId: e2e.getId(), + udId: ud.getId(), + password: password, + callback: .init { data in + if !didAddParams { + addParams() + } else { + env.backupCallbacks.registered().handle(data) + } + } + ) + env.backup.set(backup) + } + } +} + +extension MessengerStartBackup { + public static let unimplemented = MessengerStartBackup( + run: XCTUnimplemented("\(Self.self)") + ) +} diff --git a/Sources/XXMessengerClient/Messenger/Functions/MessengerStopBackup.swift b/Sources/XXMessengerClient/Messenger/Functions/MessengerStopBackup.swift new file mode 100644 index 0000000000000000000000000000000000000000..f5471166652f0d03488f70c74bf3a21aed8df6f8 --- /dev/null +++ b/Sources/XXMessengerClient/Messenger/Functions/MessengerStopBackup.swift @@ -0,0 +1,26 @@ +import Bindings +import XCTestDynamicOverlay + +public struct MessengerStopBackup { + public var run: () throws -> Void + + public func callAsFunction() throws { + try run() + } +} + +extension MessengerStopBackup { + public static func live(_ env: MessengerEnvironment) -> MessengerStopBackup { + MessengerStopBackup { + guard let backup = env.backup() else { return } + try backup.stop() + env.backup.set(nil) + } + } +} + +extension MessengerStopBackup { + public static let unimplemented = MessengerStopBackup( + run: XCTUnimplemented("\(Self.self)") + ) +} diff --git a/Sources/XXMessengerClient/Messenger/Messenger.swift b/Sources/XXMessengerClient/Messenger/Messenger.swift index ea85852c6752e30af60987264851d57af910e811..ce598c5451db8694e727055bba3ae087f08ae3fc 100644 --- a/Sources/XXMessengerClient/Messenger/Messenger.swift +++ b/Sources/XXMessengerClient/Messenger/Messenger.swift @@ -4,6 +4,7 @@ public struct Messenger { public var cMix: Stored<CMix?> public var e2e: Stored<E2E?> public var ud: Stored<UserDiscovery?> + public var backup: Stored<Backup?> public var isCreated: MessengerIsCreated public var create: MessengerCreate public var restoreBackup: MessengerRestoreBackup @@ -29,6 +30,12 @@ public struct Messenger { public var registerForNotifications: MessengerRegisterForNotifications public var verifyContact: MessengerVerifyContact public var sendMessage: MessengerSendMessage + public var registerBackupCallback: MessengerRegisterBackupCallback + public var isBackupRunning: MessengerIsBackupRunning + public var startBackup: MessengerStartBackup + public var resumeBackup: MessengerResumeBackup + public var backupParams: MessengerBackupParams + public var stopBackup: MessengerStopBackup } extension Messenger { @@ -37,6 +44,7 @@ extension Messenger { cMix: env.cMix, e2e: env.e2e, ud: env.ud, + backup: env.backup, isCreated: .live(env), create: .live(env), restoreBackup: .live(env), @@ -61,7 +69,13 @@ extension Messenger { lookupContacts: .live(env), registerForNotifications: .live(env), verifyContact: .live(env), - sendMessage: .live(env) + sendMessage: .live(env), + registerBackupCallback: .live(env), + isBackupRunning: .live(env), + startBackup: .live(env), + resumeBackup: .live(env), + backupParams: .live(env), + stopBackup: .live(env) ) } } @@ -71,6 +85,7 @@ extension Messenger { cMix: .unimplemented(), e2e: .unimplemented(), ud: .unimplemented(), + backup: .unimplemented(), isCreated: .unimplemented, create: .unimplemented, restoreBackup: .unimplemented, @@ -95,6 +110,12 @@ extension Messenger { lookupContacts: .unimplemented, registerForNotifications: .unimplemented, verifyContact: .unimplemented, - sendMessage: .unimplemented + sendMessage: .unimplemented, + registerBackupCallback: .unimplemented, + isBackupRunning: .unimplemented, + startBackup: .unimplemented, + resumeBackup: .unimplemented, + backupParams: .unimplemented, + stopBackup: .unimplemented ) } diff --git a/Sources/XXMessengerClient/Messenger/MessengerEnvironment.swift b/Sources/XXMessengerClient/Messenger/MessengerEnvironment.swift index 641a8062d3f9ae136192af38b2814fe14cafccb9..f019df1618b0985f70936fdeb97e4558060a46eb 100644 --- a/Sources/XXMessengerClient/Messenger/MessengerEnvironment.swift +++ b/Sources/XXMessengerClient/Messenger/MessengerEnvironment.swift @@ -4,6 +4,8 @@ import XCTestDynamicOverlay public struct MessengerEnvironment { public var authCallbacks: AuthCallbacksRegistry + public var backup: Stored<Backup?> + public var backupCallbacks: BackupCallbacksRegistry public var cMix: Stored<CMix?> public var downloadNDF: DownloadAndVerifySignedNdf public var e2e: Stored<E2E?> @@ -12,6 +14,7 @@ public struct MessengerEnvironment { public var getCMixParams: GetCMixParams public var getE2EParams: GetE2EParams public var getSingleUseParams: GetSingleUseParams + public var initializeBackup: InitializeBackup public var isListeningForMessages: Stored<Bool> public var isRegisteredWithUD: IsRegisteredWithUD public var loadCMix: LoadCMix @@ -26,6 +29,7 @@ public struct MessengerEnvironment { public var newUdManagerFromBackup: NewUdManagerFromBackup public var passwordStorage: PasswordStorage public var registerForNotifications: RegisterForNotifications + public var resumeBackup: ResumeBackup public var searchUD: SearchUD public var sleep: (TimeInterval) -> Void public var storageDir: String @@ -45,6 +49,8 @@ extension MessengerEnvironment { public static func live() -> MessengerEnvironment { MessengerEnvironment( authCallbacks: .live(), + backup: .inMemory(), + backupCallbacks: .live(), cMix: .inMemory(), downloadNDF: .live, e2e: .inMemory(), @@ -53,6 +59,7 @@ extension MessengerEnvironment { getCMixParams: .liveDefault, getE2EParams: .liveDefault, getSingleUseParams: .liveDefault, + initializeBackup: .live, isListeningForMessages: .inMemory(false), isRegisteredWithUD: .live, loadCMix: .live, @@ -67,6 +74,7 @@ extension MessengerEnvironment { newUdManagerFromBackup: .live, passwordStorage: .keychain, registerForNotifications: .live, + resumeBackup: .live, searchUD: .live, sleep: { Thread.sleep(forTimeInterval: $0) }, storageDir: MessengerEnvironment.defaultStorageDir, @@ -81,6 +89,8 @@ extension MessengerEnvironment { extension MessengerEnvironment { public static let unimplemented = MessengerEnvironment( authCallbacks: .unimplemented, + backup: .unimplemented(), + backupCallbacks: .unimplemented, cMix: .unimplemented(), downloadNDF: .unimplemented, e2e: .unimplemented(), @@ -89,6 +99,7 @@ extension MessengerEnvironment { getCMixParams: .unimplemented, getE2EParams: .unimplemented, getSingleUseParams: .unimplemented, + initializeBackup: .unimplemented, isListeningForMessages: .unimplemented(placeholder: false), isRegisteredWithUD: .unimplemented, loadCMix: .unimplemented, @@ -103,6 +114,7 @@ extension MessengerEnvironment { newUdManagerFromBackup: .unimplemented, passwordStorage: .unimplemented, registerForNotifications: .unimplemented, + resumeBackup: .unimplemented, searchUD: .unimplemented, sleep: XCTUnimplemented("\(Self.self).sleep"), storageDir: "unimplemented", diff --git a/Sources/XXMessengerClient/Utils/BackupCallbackRegistry.swift b/Sources/XXMessengerClient/Utils/BackupCallbackRegistry.swift new file mode 100644 index 0000000000000000000000000000000000000000..7c7fc9e8ae08a983f72a4240728610bab152f965 --- /dev/null +++ b/Sources/XXMessengerClient/Utils/BackupCallbackRegistry.swift @@ -0,0 +1,36 @@ +import Foundation +import XCTestDynamicOverlay +import XXClient + +public struct BackupCallbacksRegistry { + public var register: (UpdateBackupFunc) -> Cancellable + public var registered: () -> UpdateBackupFunc +} + +extension BackupCallbacksRegistry { + public static func live() -> BackupCallbacksRegistry { + class Registry { + var callbacks: [UUID: UpdateBackupFunc] = [:] + } + let registry = Registry() + return BackupCallbacksRegistry( + register: { callback in + let id = UUID() + registry.callbacks[id] = callback + return Cancellable { registry.callbacks[id] = nil } + }, + registered: { + UpdateBackupFunc { data in + registry.callbacks.values.forEach { $0.handle(data) } + } + } + ) + } +} + +extension BackupCallbacksRegistry { + public static let unimplemented = BackupCallbacksRegistry( + register: XCTUnimplemented("\(Self.self).register", placeholder: Cancellable {}), + registered: XCTUnimplemented("\(Self.self).registered", placeholder: UpdateBackupFunc { _ in }) + ) +} diff --git a/Sources/XXMessengerClient/Utils/BackupParams.swift b/Sources/XXMessengerClient/Utils/BackupParams.swift index 02fdc595461cbcc14e617560b6f7b91b6b398fc2..c3bd8fc0f8e566348bb1d731bf8606c2f8bea5ce 100644 --- a/Sources/XXMessengerClient/Utils/BackupParams.swift +++ b/Sources/XXMessengerClient/Utils/BackupParams.swift @@ -1,6 +1,6 @@ import Foundation -public struct BackupParams: Equatable, Codable { +public struct BackupParams: Equatable { public init( username: String, email: String?, @@ -15,3 +15,13 @@ public struct BackupParams: Equatable, Codable { public var email: String? public var phone: String? } + +extension BackupParams: Codable { + public static func decode(_ data: Data) throws -> Self { + try JSONDecoder().decode(Self.self, from: data) + } + + public func encode() throws -> Data { + try JSONEncoder().encode(self) + } +} diff --git a/Tests/XXMessengerClientTests/Messenger/Functions/MessengerBackupParamsTests.swift b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerBackupParamsTests.swift new file mode 100644 index 0000000000000000000000000000000000000000..e0af7129f1f82f9c5fbce7b67d2ea4c3ff05060e --- /dev/null +++ b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerBackupParamsTests.swift @@ -0,0 +1,62 @@ +import CustomDump +import XCTest +import XXClient +@testable import XXMessengerClient + +final class MessengerBackupParamsTests: XCTestCase { + func testBackupParams() throws { + var didAddJSON: [String] = [] + + var env: MessengerEnvironment = .unimplemented + env.backup.get = { + var backup: Backup = .unimplemented + backup.isRunning.run = { true } + backup.addJSON.run = { didAddJSON.append($0) } + return backup + } + let backup: MessengerBackupParams = .live(env) + let params = BackupParams( + username: "test-username", + email: "test-email", + phone: "test-phone" + ) + + try backup(params) + + XCTAssertNoDifference(didAddJSON, [ + String(data: try params.encode(), encoding: .utf8)! + ]) + } + + func testBackupParamsWhenNoBackup() { + var env: MessengerEnvironment = .unimplemented + env.backup.get = { nil } + let backup: MessengerBackupParams = .live(env) + let params = BackupParams(username: "test", email: nil, phone: nil) + + XCTAssertThrowsError(try backup(params)) { error in + XCTAssertNoDifference( + error as NSError, + MessengerBackupParams.Error.notRunning as NSError + ) + } + } + + func testBackupParamsWhenBackupNotRunning() { + var env: MessengerEnvironment = .unimplemented + env.backup.get = { + var backup: Backup = .unimplemented + backup.isRunning.run = { false } + return backup + } + let backup: MessengerBackupParams = .live(env) + let params = BackupParams(username: "test", email: nil, phone: nil) + + XCTAssertThrowsError(try backup(params)) { error in + XCTAssertNoDifference( + error as NSError, + MessengerBackupParams.Error.notRunning as NSError + ) + } + } +} diff --git a/Tests/XXMessengerClientTests/Messenger/Functions/MessengerIsBackupRunningTests.swift b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerIsBackupRunningTests.swift new file mode 100644 index 0000000000000000000000000000000000000000..7076aa648b4d287ece157cf56ae868000fdfe6ed --- /dev/null +++ b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerIsBackupRunningTests.swift @@ -0,0 +1,37 @@ +import XCTest +import XXClient +@testable import XXMessengerClient + +final class MessengerIsBackupRunningTests: XCTestCase { + func testWithoutBackup() { + var env: MessengerEnvironment = .unimplemented + env.backup.get = { nil } + let isRunning: MessengerIsBackupRunning = .live(env) + + XCTAssertFalse(isRunning()) + } + + func testWithBackupRunning() { + var env: MessengerEnvironment = .unimplemented + env.backup.get = { + var backup: Backup = .unimplemented + backup.isRunning.run = { true } + return backup + } + let isRunning: MessengerIsBackupRunning = .live(env) + + XCTAssertTrue(isRunning()) + } + + func testWithBackupNotRunning() { + var env: MessengerEnvironment = .unimplemented + env.backup.get = { + var backup: Backup = .unimplemented + backup.isRunning.run = { false } + return backup + } + let isRunning: MessengerIsBackupRunning = .live(env) + + XCTAssertFalse(isRunning()) + } +} diff --git a/Tests/XXMessengerClientTests/Messenger/Functions/MessengerRegisterBackupCallbackTests.swift b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerRegisterBackupCallbackTests.swift new file mode 100644 index 0000000000000000000000000000000000000000..44e7e21c2bbac9badafd94370a595fea209da2f9 --- /dev/null +++ b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerRegisterBackupCallbackTests.swift @@ -0,0 +1,34 @@ +import CustomDump +import XCTest +import XXClient +@testable import XXMessengerClient + +final class MessengerRegisterBackupCallbackTests: XCTestCase { + func testRegisterBackupCallback() { + var registeredCallbacks: [UpdateBackupFunc] = [] + var didHandleData: [Data] = [] + var didCancelRegisteredCallback = 0 + + var env: MessengerEnvironment = .unimplemented + env.backupCallbacks.register = { callback in + registeredCallbacks.append(callback) + return Cancellable { didCancelRegisteredCallback += 1 } + } + let registerBackupCallback: MessengerRegisterBackupCallback = .live(env) + let cancellable = registerBackupCallback(UpdateBackupFunc { data in + didHandleData.append(data) + }) + + XCTAssertEqual(registeredCallbacks.count, 1) + + registeredCallbacks.forEach { callback in + callback.handle("test".data(using: .utf8)!) + } + + XCTAssertNoDifference(didHandleData, ["test".data(using: .utf8)!]) + + cancellable.cancel() + + XCTAssertEqual(didCancelRegisteredCallback, 1) + } +} diff --git a/Tests/XXMessengerClientTests/Messenger/Functions/MessengerResumeBackupTests.swift b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerResumeBackupTests.swift new file mode 100644 index 0000000000000000000000000000000000000000..7979ffbd7da561d8b3405e701d2bb84718e39ea1 --- /dev/null +++ b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerResumeBackupTests.swift @@ -0,0 +1,126 @@ +import CustomDump +import XCTest +import XXClient +@testable import XXMessengerClient + +final class MessengerResumeBackupTests: XCTestCase { + func testResume() throws { + struct ResumeBackupParams: Equatable { + var e2eId: Int + var udId: Int + } + var didResumeBackup: [ResumeBackupParams] = [] + var backupCallbacks: [UpdateBackupFunc] = [] + var didHandleCallback: [Data] = [] + var didSetBackup: [Backup?] = [] + + let e2eId = 123 + let udId = 321 + let data = "test-data".data(using: .utf8)! + + var env: MessengerEnvironment = .unimplemented + env.backup.get = { nil } + env.backup.set = { didSetBackup.append($0) } + env.e2e.get = { + var e2e: E2E = .unimplemented + e2e.getId.run = { e2eId } + return e2e + } + env.ud.get = { + var ud: UserDiscovery = .unimplemented + ud.getId.run = { udId } + return ud + } + env.backupCallbacks.registered = { + UpdateBackupFunc { didHandleCallback.append($0) } + } + env.resumeBackup.run = { e2eId, udId, callback in + didResumeBackup.append(.init(e2eId: e2eId, udId: udId)) + backupCallbacks.append(callback) + return .unimplemented + } + let resume: MessengerResumeBackup = .live(env) + + try resume() + + XCTAssertNoDifference(didResumeBackup, [ + .init(e2eId: e2eId, udId: udId) + ]) + XCTAssertNoDifference(didSetBackup.map { $0 != nil }, [true]) + + backupCallbacks.forEach { $0.handle(data) } + + XCTAssertNoDifference(didHandleCallback, [data]) + } + + func testResumeWhenRunning() { + var env: MessengerEnvironment = .unimplemented + env.backup.get = { + var backup: Backup = .unimplemented + backup.isRunning.run = { true } + return backup + } + let resume: MessengerResumeBackup = .live(env) + + XCTAssertThrowsError(try resume()) { error in + XCTAssertNoDifference( + error as NSError, + MessengerResumeBackup.Error.isRunning as NSError + ) + } + } + + func testResumeWhenNotConnected() { + var env: MessengerEnvironment = .unimplemented + env.backup.get = { nil } + env.e2e.get = { nil } + let resume: MessengerResumeBackup = .live(env) + + XCTAssertThrowsError(try resume()) { error in + XCTAssertNoDifference( + error as NSError, + MessengerResumeBackup.Error.notConnected as NSError + ) + } + } + + func testResumeWhenNotLoggedIn() { + var env: MessengerEnvironment = .unimplemented + env.backup.get = { nil } + env.e2e.get = { .unimplemented } + env.ud.get = { nil } + let resume: MessengerResumeBackup = .live(env) + + XCTAssertThrowsError(try resume()) { error in + XCTAssertNoDifference( + error as NSError, + MessengerResumeBackup.Error.notLoggedIn as NSError + ) + } + } + + func testResumeFailure() { + struct Failure: Error, Equatable {} + let failure = Failure() + + var env: MessengerEnvironment = .unimplemented + env.backup.get = { nil } + env.e2e.get = { + var e2e: E2E = .unimplemented + e2e.getId.run = { 123 } + return e2e + } + env.ud.get = { + var ud: UserDiscovery = .unimplemented + ud.getId.run = { 321 } + return ud + } + env.backupCallbacks.registered = { UpdateBackupFunc { _ in } } + env.resumeBackup.run = { _, _ , _ in throw failure } + let resume: MessengerResumeBackup = .live(env) + + XCTAssertThrowsError(try resume()) { error in + XCTAssertNoDifference(error as NSError, failure as NSError) + } + } +} diff --git a/Tests/XXMessengerClientTests/Messenger/Functions/MessengerStartBackupTests.swift b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerStartBackupTests.swift new file mode 100644 index 0000000000000000000000000000000000000000..b92bd1ee38948737453e973a8e4b4cd874797b84 --- /dev/null +++ b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerStartBackupTests.swift @@ -0,0 +1,147 @@ +import CustomDump +import XCTest +import XXClient +@testable import XXMessengerClient + +final class MessengerStartBackupTests: XCTestCase { + func testStart() throws { + struct InitBackupParams: Equatable { + var e2eId: Int + var udId: Int + var password: String + } + var didInitializeBackup: [InitBackupParams] = [] + var backupCallbacks: [UpdateBackupFunc] = [] + var didHandleCallback: [Data] = [] + var didSetBackup: [Backup?] = [] + var didAddJSON: [String] = [] + + let password = "test-password" + let e2eId = 123 + let udId = 321 + let dataWithoutParams = "backup-without-params".data(using: .utf8)! + let dataWithParams = "backup-with-params".data(using: .utf8)! + + var env: MessengerEnvironment = .unimplemented + env.backup.get = { didSetBackup.last as? Backup } + env.backup.set = { didSetBackup.append($0) } + env.e2e.get = { + var e2e: E2E = .unimplemented + e2e.getId.run = { e2eId } + return e2e + } + env.ud.get = { + var ud: UserDiscovery = .unimplemented + ud.getId.run = { udId } + return ud + } + env.backupCallbacks.registered = { + UpdateBackupFunc { didHandleCallback.append($0) } + } + env.initializeBackup.run = { e2eId, udId, password, callback in + didInitializeBackup.append(.init(e2eId: e2eId, udId: udId, password: password)) + backupCallbacks.append(callback) + var backup: Backup = .unimplemented + backup.addJSON.run = { string in + didAddJSON.append(string) + } + return backup + } + let start: MessengerStartBackup = .live(env) + + try start(password: password, params: .stub) + + XCTAssertNoDifference(didInitializeBackup, [ + .init(e2eId: e2eId, udId: udId, password: password) + ]) + XCTAssertNoDifference(didSetBackup.map { $0 != nil }, [true]) + + backupCallbacks.forEach { $0.handle(dataWithoutParams) } + + XCTAssertNoDifference( + didHandleCallback.map(StringData.init), + [].map(StringData.init) + ) + XCTAssertNoDifference(didAddJSON, [ + String(data: try BackupParams.stub.encode(), encoding: .utf8)! + ]) + + backupCallbacks.forEach { $0.handle(dataWithParams) } + + XCTAssertNoDifference( + didHandleCallback.map(StringData.init), + [dataWithParams].map(StringData.init) + ) + } + + func testStartWhenRunning() { + var env: MessengerEnvironment = .unimplemented + env.backup.get = { + var backup: Backup = .unimplemented + backup.isRunning.run = { true } + return backup + } + let start: MessengerStartBackup = .live(env) + + XCTAssertThrowsError(try start(password: "", params: .stub)) { error in + XCTAssertNoDifference( + error as NSError, + MessengerStartBackup.Error.isRunning as NSError + ) + } + } + + func testStartWhenNotConnected() { + var env: MessengerEnvironment = .unimplemented + env.backup.get = { nil } + env.e2e.get = { nil } + let start: MessengerStartBackup = .live(env) + + XCTAssertThrowsError(try start(password: "", params: .stub)) { error in + XCTAssertNoDifference( + error as NSError, + MessengerStartBackup.Error.notConnected as NSError + ) + } + } + + func testStartWhenNotLoggedIn() { + var env: MessengerEnvironment = .unimplemented + env.backup.get = { nil } + env.e2e.get = { .unimplemented } + env.ud.get = { nil } + let start: MessengerStartBackup = .live(env) + + XCTAssertThrowsError(try start(password: "", params: .stub)) { error in + XCTAssertNoDifference( + error as NSError, + MessengerStartBackup.Error.notLoggedIn as NSError + ) + } + } + + func testStartFailure() { + struct Failure: Error, Equatable {} + let failure = Failure() + + var env: MessengerEnvironment = .unimplemented + env.backup.get = { nil } + env.e2e.get = { + var e2e: E2E = .unimplemented + e2e.getId.run = { 123 } + return e2e + } + env.ud.get = { + var ud: UserDiscovery = .unimplemented + ud.getId.run = { 321 } + return ud + } + env.backupCallbacks.registered = { UpdateBackupFunc { _ in } } + env.initializeBackup.run = { _, _, _, _ in throw failure } + let start: MessengerStartBackup = .live(env) + + XCTAssertThrowsError(try start(password: "", params: .stub)) { error in + XCTAssertNoDifference(error as NSError, failure as NSError) + } + } +} diff --git a/Tests/XXMessengerClientTests/Messenger/Functions/MessengerStopBackupTests.swift b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerStopBackupTests.swift new file mode 100644 index 0000000000000000000000000000000000000000..306b6c2d1d6b6507cd0bd83071d54ad99fd2e8f7 --- /dev/null +++ b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerStopBackupTests.swift @@ -0,0 +1,53 @@ +import CustomDump +import XCTest +import XXClient +@testable import XXMessengerClient + +final class MessengerStopBackupTests: XCTestCase { + func testStop() throws { + var didStopBackup = 0 + var didSetBackup: [Backup?] = [] + + var env: MessengerEnvironment = .unimplemented + env.backup.get = { + var backup: Backup = .unimplemented + backup.stop.run = { didStopBackup += 1 } + return backup + } + env.backup.set = { backup in + didSetBackup.append(backup) + } + let stop: MessengerStopBackup = .live(env) + + try stop() + + XCTAssertEqual(didStopBackup, 1) + XCTAssertEqual(didSetBackup.count, 1) + XCTAssertNil(didSetBackup.first as? Backup) + } + + func testStopFailure() { + struct Failure: Error, Equatable {} + let failure = Failure() + + var env: MessengerEnvironment = .unimplemented + env.backup.get = { + var backup: Backup = .unimplemented + backup.stop.run = { throw failure } + return backup + } + let stop: MessengerStopBackup = .live(env) + + XCTAssertThrowsError(try stop()) { error in + XCTAssertNoDifference(error as NSError, failure as NSError) + } + } + + func testStopWithoutBackup() throws { + var env: MessengerEnvironment = .unimplemented + env.backup.get = { nil } + let stop: MessengerStopBackup = .live(env) + + try stop() + } +} diff --git a/Tests/XXMessengerClientTests/TestHelpers/StringData.swift b/Tests/XXMessengerClientTests/TestHelpers/StringData.swift new file mode 100644 index 0000000000000000000000000000000000000000..6987a4ea385989f6823e701b0e00002b036f0f00 --- /dev/null +++ b/Tests/XXMessengerClientTests/TestHelpers/StringData.swift @@ -0,0 +1,14 @@ +import CustomDump +import Foundation + +struct StringData: Equatable, CustomDumpStringConvertible { + var data: Data + + var customDumpDescription: String { + if let string = String(data: data, encoding: .utf8) { + return #"Data(string: "\#(string)", encoding: .utf8)"# + } else { + return data.customDumpDescription + } + } +} diff --git a/Tests/XXMessengerClientTests/TestHelpers/Message+stubs.swift b/Tests/XXMessengerClientTests/TestHelpers/TestDoubles.swift similarity index 74% rename from Tests/XXMessengerClientTests/TestHelpers/Message+stubs.swift rename to Tests/XXMessengerClientTests/TestHelpers/TestDoubles.swift index e10bea4a6298544c2e4c4d01ba267a00d2dcc167..4d57e29363d3f22bdbb836622d7bfa92b014b683 100644 --- a/Tests/XXMessengerClientTests/TestHelpers/Message+stubs.swift +++ b/Tests/XXMessengerClientTests/TestHelpers/TestDoubles.swift @@ -1,4 +1,5 @@ import XXClient +import XXMessengerClient extension Message { static func stub(_ stubId: Int) -> Message { @@ -16,3 +17,11 @@ extension Message { ) } } + +extension BackupParams { + static let stub = BackupParams( + username: "stub-username", + email: "stub-email", + phone: "stub-phone" + ) +} diff --git a/Tests/XXMessengerClientTests/Utils/BackupCallbackRegistryTests.swift b/Tests/XXMessengerClientTests/Utils/BackupCallbackRegistryTests.swift new file mode 100644 index 0000000000000000000000000000000000000000..d3cba50cb81fe1efb2188985fed7ef9a8470290d --- /dev/null +++ b/Tests/XXMessengerClientTests/Utils/BackupCallbackRegistryTests.swift @@ -0,0 +1,43 @@ +import CustomDump +import XCTest +import XXClient +@testable import XXMessengerClient + +final class BackupCallbackRegistryTests: XCTestCase { + func testRegistry() { + var firstCallbackDidHandle: [Data] = [] + var secondCallbackDidHandle: [Data] = [] + + let firstCallback = UpdateBackupFunc { data in + firstCallbackDidHandle.append(data) + } + let secondCallback = UpdateBackupFunc { data in + secondCallbackDidHandle.append(data) + } + let callbackRegistry: BackupCallbacksRegistry = .live() + let registeredCallbacks = callbackRegistry.registered() + let firstCallbackCancellable = callbackRegistry.register(firstCallback) + let secondCallbackCancellable = callbackRegistry.register(secondCallback) + + let firstData = "1".data(using: .utf8)! + registeredCallbacks.handle(firstData) + + XCTAssertNoDifference(firstCallbackDidHandle, [firstData]) + XCTAssertNoDifference(secondCallbackDidHandle, [firstData]) + + firstCallbackCancellable.cancel() + let secondData = "2".data(using: .utf8)! + registeredCallbacks.handle(secondData) + + XCTAssertNoDifference(firstCallbackDidHandle, [firstData]) + XCTAssertNoDifference(secondCallbackDidHandle, [firstData, secondData]) + + secondCallbackCancellable.cancel() + + let thirdData = "3".data(using: .utf8)! + registeredCallbacks.handle(thirdData) + + XCTAssertNoDifference(firstCallbackDidHandle, [firstData]) + XCTAssertNoDifference(secondCallbackDidHandle, [firstData, secondData]) + } +}