diff --git a/Examples/xx-messenger/Package.swift b/Examples/xx-messenger/Package.swift index 4cf167f95221db4aa4230d79f1cba10952f7a19f..b310549f615363a0f38348b5c79f2d158d00c0e2 100644 --- a/Examples/xx-messenger/Package.swift +++ b/Examples/xx-messenger/Package.swift @@ -274,8 +274,10 @@ let package = Package( .target( name: "RestoreFeature", dependencies: [ + .target(name: "AppCore"), .product(name: "ComposableArchitecture", package: "swift-composable-architecture"), .product(name: "XXMessengerClient", package: "elixxir-dapps-sdk-swift"), + .product(name: "XXModels", package: "client-ios-db"), ], swiftSettings: swiftSettings ), diff --git a/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift b/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift index c79d5b935e7f12255b852278256742608695734e..24e5bd8910a267354a1a608579e5ddb31d1f4cc0 100644 --- a/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift +++ b/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift @@ -103,7 +103,9 @@ extension AppEnvironment { restore: { RestoreEnvironment( messenger: messenger, + db: dbManager.getDB, loadData: .live, + now: Date.init, mainQueue: mainQueue, bgQueue: bgQueue ) diff --git a/Examples/xx-messenger/Sources/RestoreFeature/RestoreFeature.swift b/Examples/xx-messenger/Sources/RestoreFeature/RestoreFeature.swift index 5339c89bbd3dba39edaf2cc8f0181e673b396f4b..d1872840470448de3dccfb2e9af2b2f4ad135324 100644 --- a/Examples/xx-messenger/Sources/RestoreFeature/RestoreFeature.swift +++ b/Examples/xx-messenger/Sources/RestoreFeature/RestoreFeature.swift @@ -1,8 +1,10 @@ +import AppCore import Combine import ComposableArchitecture import Foundation import XCTestDynamicOverlay import XXMessengerClient +import XXModels public struct RestoreState: Equatable { public enum Field: String, Hashable { @@ -58,18 +60,24 @@ public enum RestoreAction: Equatable, BindableAction { public struct RestoreEnvironment { public init( messenger: Messenger, + db: DBManagerGetDB, loadData: URLDataLoader, + now: @escaping () -> Date, mainQueue: AnySchedulerOf<DispatchQueue>, bgQueue: AnySchedulerOf<DispatchQueue> ) { self.messenger = messenger + self.db = db self.loadData = loadData + self.now = now self.mainQueue = mainQueue self.bgQueue = bgQueue } public var messenger: Messenger + public var db: DBManagerGetDB public var loadData: URLDataLoader + public var now: () -> Date public var mainQueue: AnySchedulerOf<DispatchQueue> public var bgQueue: AnySchedulerOf<DispatchQueue> } @@ -77,7 +85,9 @@ public struct RestoreEnvironment { extension RestoreEnvironment { public static let unimplemented = RestoreEnvironment( messenger: .unimplemented, + db: .unimplemented, loadData: .unimplemented, + now: XCTUnimplemented("\(Self.self).now"), mainQueue: .unimplemented, bgQueue: .unimplemented ) @@ -118,12 +128,20 @@ public let restoreReducer = Reducer<RestoreState, RestoreAction, RestoreEnvironm state.restoreFailure = nil return Effect.result { do { - _ = try env.messenger.restoreBackup( + let result = try env.messenger.restoreBackup( backupData: backupData, backupPassphrase: backupPassphrase ) + try env.db().saveContact(Contact( + id: try env.messenger.e2e.tryGet().getContact().getId(), + username: result.restoredParams.username, + email: result.restoredParams.email, + phone: result.restoredParams.phone, + createdAt: env.now() + )) return .success(.finished) } catch { + try? env.messenger.destroy() return .success(.failed(error as NSError)) } } diff --git a/Examples/xx-messenger/Tests/RestoreFeatureTests/RestoreFeatureTests.swift b/Examples/xx-messenger/Tests/RestoreFeatureTests/RestoreFeatureTests.swift index 9a3cde3227357eb11d878c8c8f7fd7cc76f00fb5..4e55f78bf14a04fe0aea7885f2f86d3783e6b04c 100644 --- a/Examples/xx-messenger/Tests/RestoreFeatureTests/RestoreFeatureTests.swift +++ b/Examples/xx-messenger/Tests/RestoreFeatureTests/RestoreFeatureTests.swift @@ -1,8 +1,10 @@ -import CustomDump import ComposableArchitecture +import CustomDump import XCTest -@testable import RestoreFeature +import XXClient import XXMessengerClient +import XXModels +@testable import RestoreFeature final class RestoreFeatureTests: XCTestCase { func testFileImport() { @@ -79,15 +81,18 @@ final class RestoreFeatureTests: XCTestCase { let backupPassphrase = "backup-passphrase" let restoreResult = MessengerRestoreBackup.Result( restoredParams: BackupParams.init( - username: "", - email: nil, - phone: nil + username: "restored-username", + email: "restored-email", + phone: "restored-phone" ), restoredContacts: [] ) + let now = Date() + let contactId = "contact-id".data(using: .utf8)! var didRestoreWithData: [Data] = [] var didRestoreWithPassphrase: [String] = [] + var didSaveContact: [XXModels.Contact] = [] let store = TestStore( initialState: RestoreState( @@ -99,11 +104,29 @@ final class RestoreFeatureTests: XCTestCase { store.environment.bgQueue = .immediate store.environment.mainQueue = .immediate + store.environment.now = { now } store.environment.messenger.restoreBackup.run = { data, passphrase in didRestoreWithData.append(data) didRestoreWithPassphrase.append(passphrase) return restoreResult } + store.environment.messenger.e2e.get = { + var e2e: E2E = .unimplemented + e2e.getContact.run = { + var contact: XXClient.Contact = .unimplemented(Data()) + contact.getIdFromContact.run = { _ in contactId } + return contact + } + return e2e + } + store.environment.db.run = { + var db: Database = .unimplemented + db.saveContact.run = { contact in + didSaveContact.append(contact) + return contact + } + return db + } store.send(.set(\.$passphrase, backupPassphrase)) { $0.passphrase = backupPassphrase @@ -115,6 +138,13 @@ final class RestoreFeatureTests: XCTestCase { XCTAssertNoDifference(didRestoreWithData, [backupData]) XCTAssertNoDifference(didRestoreWithPassphrase, [backupPassphrase]) + XCTAssertNoDifference(didSaveContact, [Contact( + id: contactId, + username: restoreResult.restoredParams.username, + email: restoreResult.restoredParams.email, + phone: restoreResult.restoredParams.phone, + createdAt: now + )]) store.receive(.finished) { $0.isRestoring = false @@ -137,6 +167,8 @@ final class RestoreFeatureTests: XCTestCase { struct Failure: Error {} let failure = Failure() + var didDestroyMessenger = 0 + let store = TestStore( initialState: RestoreState( file: .init(name: "name", data: "data".data(using: .utf8)!) @@ -148,11 +180,14 @@ final class RestoreFeatureTests: XCTestCase { store.environment.bgQueue = .immediate store.environment.mainQueue = .immediate store.environment.messenger.restoreBackup.run = { _, _ in throw failure } + store.environment.messenger.destroy.run = { didDestroyMessenger += 1 } store.send(.restoreTapped) { $0.isRestoring = true } + XCTAssertEqual(didDestroyMessenger, 1) + store.receive(.failed(failure as NSError)) { $0.isRestoring = false $0.restoreFailure = failure.localizedDescription diff --git a/Package.swift b/Package.swift index 6e22e8e11f75b1e92b19dece4325e6f3b9edbb16..9136971583d840fd35a2993ec5be731e8ac774c4 100644 --- a/Package.swift +++ b/Package.swift @@ -2,13 +2,9 @@ import PackageDescription let swiftSettings: [SwiftSetting] = [ - .unsafeFlags( - [ - "-Xfrontend", "-debug-time-function-bodies", - "-Xfrontend", "-debug-time-expression-type-checking", - ], - .when(configuration: .debug) - ), + //.unsafeFlags(["-Xfrontend", "-warn-concurrency"], .when(configuration: .debug)), + //.unsafeFlags(["-Xfrontend", "-debug-time-function-bodies"], .when(configuration: .debug)), + //.unsafeFlags(["-Xfrontend", "-debug-time-expression-type-checking"], .when(configuration: .debug)), ] let package = Package( diff --git a/Sources/XXMessengerClient/Messenger/Functions/MessengerRestoreBackup.swift b/Sources/XXMessengerClient/Messenger/Functions/MessengerRestoreBackup.swift index 9f0d591b44e0820c6f2f6af6611055c72f74be06..e32e6324cb1eeebc7dce54b8cc3769d830f4d43e 100644 --- a/Sources/XXMessengerClient/Messenger/Functions/MessengerRestoreBackup.swift +++ b/Sources/XXMessengerClient/Messenger/Functions/MessengerRestoreBackup.swift @@ -30,58 +30,53 @@ extension MessengerRestoreBackup { public static func live(_ env: MessengerEnvironment) -> MessengerRestoreBackup { MessengerRestoreBackup { backupData, backupPassphrase in let storageDir = env.storageDir - do { - let ndfData = try env.downloadNDF(env.ndfEnvironment) - let password = env.generateSecret() - try env.passwordStorage.save(password) - try env.fileManager.removeDirectory(storageDir) - try env.fileManager.createDirectory(storageDir) - let report = try env.newCMixFromBackup( - ndfJSON: String(data: ndfData, encoding: .utf8)!, - storageDir: storageDir, - backupPassphrase: backupPassphrase, - sessionPassword: password, - backupFileContents: backupData - ) - let cMix = try env.loadCMix( - storageDir: storageDir, - password: password, - cMixParamsJSON: env.getCMixParams() - ) - try cMix.startNetworkFollower(timeoutMS: 30_000) - let e2e = try env.login( - cMixId: cMix.getId(), - authCallbacks: env.authCallbacks.registered(), - identity: try cMix.makeReceptionIdentity(legacy: true), - e2eParamsJSON: env.getE2EParams() - ) - let decoder = JSONDecoder() - let paramsData = report.params.data(using: .utf8)! - let params = try decoder.decode(BackupParams.self, from: paramsData) - let ud = try env.newUdManagerFromBackup( - params: NewUdManagerFromBackup.Params( - e2eId: e2e.getId(), - username: Fact(type: .username, value: params.username), - email: params.email.map { Fact(type: .email, value: $0) }, - phone: params.phone.map { Fact(type: .phone, value: $0) }, - cert: env.udCert ?? e2e.getUdCertFromNdf(), - contact: env.udContact ?? (try e2e.getUdContactFromNdf()), - address: env.udAddress ?? e2e.getUdAddressFromNdf() - ), - follower: UdNetworkStatus { cMix.networkFollowerStatus() } - ) - env.cMix.set(cMix) - env.e2e.set(e2e) - env.ud.set(ud) - env.isListeningForMessages.set(false) - return Result( - restoredParams: params, - restoredContacts: report.restoredContacts - ) - } catch { - try? env.fileManager.removeDirectory(storageDir) - throw error - } + let ndfData = try env.downloadNDF(env.ndfEnvironment) + let password = env.generateSecret() + try env.passwordStorage.save(password) + try env.fileManager.removeDirectory(storageDir) + try env.fileManager.createDirectory(storageDir) + let report = try env.newCMixFromBackup( + ndfJSON: String(data: ndfData, encoding: .utf8)!, + storageDir: storageDir, + backupPassphrase: backupPassphrase, + sessionPassword: password, + backupFileContents: backupData + ) + let cMix = try env.loadCMix( + storageDir: storageDir, + password: password, + cMixParamsJSON: env.getCMixParams() + ) + env.cMix.set(cMix) + try cMix.startNetworkFollower(timeoutMS: 30_000) + let e2e = try env.login( + cMixId: cMix.getId(), + authCallbacks: env.authCallbacks.registered(), + identity: try cMix.makeReceptionIdentity(legacy: true), + e2eParamsJSON: env.getE2EParams() + ) + env.e2e.set(e2e) + env.isListeningForMessages.set(false) + let decoder = JSONDecoder() + let paramsData = report.params.data(using: .utf8)! + let params = try decoder.decode(BackupParams.self, from: paramsData) + let ud = try env.newUdManagerFromBackup( + params: NewUdManagerFromBackup.Params( + e2eId: e2e.getId(), + username: Fact(type: .username, value: params.username), + email: params.email.map { Fact(type: .email, value: $0) }, + phone: params.phone.map { Fact(type: .phone, value: $0) }, + cert: env.udCert ?? e2e.getUdCertFromNdf(), + contact: env.udContact ?? (try e2e.getUdContactFromNdf()), + address: env.udAddress ?? e2e.getUdAddressFromNdf() + ), + follower: UdNetworkStatus { cMix.networkFollowerStatus() } + ) + env.ud.set(ud) + return Result( + restoredParams: params, + restoredContacts: report.restoredContacts + ) } } } diff --git a/Tests/XXMessengerClientTests/Messenger/Functions/MessengerRestoreBackupTests.swift b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerRestoreBackupTests.swift index fb8fe7da315df608763b572a273ec67ef2c8366c..3e2dc1f2b8ea333fd1155a06b2010d7f3faeee13 100644 --- a/Tests/XXMessengerClientTests/Messenger/Functions/MessengerRestoreBackupTests.swift +++ b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerRestoreBackupTests.swift @@ -136,6 +136,7 @@ final class MessengerRestoreBackupTests: XCTestCase { password: password, cMixParams: cMixParams ), + .didSetCMix, .cMixDidStartNetworkFollower( timeoutMS: 30_000 ), @@ -148,6 +149,10 @@ final class MessengerRestoreBackupTests: XCTestCase { identity: receptionIdentity, e2eParamsJSON: e2eParams ), + .didSetE2E, + .didSetIsListeningForMessages( + isListening: false + ), .didNewUdManagerFromBackup(params: .init( e2eId: e2eId, username: Fact(type: .username, value: backupParams.username), @@ -157,12 +162,7 @@ final class MessengerRestoreBackupTests: XCTestCase { contact: udContactFromNdf, address: udAddressFromNdf )), - .didSetCMix, - .didSetE2E, .didSetUD, - .didSetIsListeningForMessages( - isListening: false - ), ]) XCTAssertNoDifference(result, MessengerRestoreBackup.Result( @@ -175,19 +175,13 @@ final class MessengerRestoreBackupTests: XCTestCase { struct Failure: Error, Equatable {} let failure = Failure() - var actions: [CaughtAction] = [] - var env: MessengerEnvironment = .unimplemented env.downloadNDF.run = { _ in throw failure } - env.fileManager.removeDirectory = { actions.append(.didRemoveDirectory(path: $0)) } let restore: MessengerRestoreBackup = .live(env) XCTAssertThrowsError(try restore(backupData: Data(), backupPassphrase: "")) { error in XCTAssertNoDifference(error as? Failure, failure) } - XCTAssertNoDifference(actions, [ - .didRemoveDirectory(path: env.storageDir) - ]) } }