import ComposableArchitecture import XCTest import XXClient import XXMessengerClient import XXModels @testable import BackupFeature final class BackupFeatureTests: XCTestCase { func testTask() { var isBackupRunning: [Bool] = [false] var observers: [UUID: BackupStorage.Observer] = [:] let store = TestStore( initialState: BackupState(), reducer: backupReducer, environment: .unimplemented ) store.environment.mainQueue = .immediate store.environment.bgQueue = .immediate store.environment.messenger.isBackupRunning.run = { isBackupRunning.removeFirst() } store.environment.backupStorage.observe = { let id = UUID() observers[id] = $0 return Cancellable { observers[id] = nil } } store.send(.task) XCTAssertNoDifference(observers.count, 1) let backup = BackupStorage.Backup( date: .init(timeIntervalSince1970: 1), data: "backup".data(using: .utf8)! ) observers.values.forEach { $0(backup) } store.receive(.backupUpdated(backup)) { $0.backup = backup } observers.values.forEach { $0(nil) } store.receive(.backupUpdated(nil)) { $0.backup = nil } store.send(.cancelTask) XCTAssertNoDifference(observers.count, 0) } func testStartBackup() { var actions: [Action]! var isBackupRunning: [Bool] = [true] let contactID = "contact-id".data(using: .utf8)! let dbContact = XXModels.Contact( id: contactID, username: "db-contact-username" ) let passphrase = "backup-password" let store = TestStore( initialState: BackupState(), reducer: backupReducer, environment: .unimplemented ) store.environment.mainQueue = .immediate store.environment.bgQueue = .immediate store.environment.messenger.startBackup.run = { passphrase, params in actions.append(.didStartBackup(passphrase: passphrase, params: params)) } store.environment.messenger.isBackupRunning.run = { isBackupRunning.removeFirst() } 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.fetchContacts.run = { _ in return [dbContact] } return db } actions = [] store.send(.set(\.$passphrase, passphrase)) { $0.passphrase = passphrase } XCTAssertNoDifference(actions, []) actions = [] store.send(.startTapped) { $0.isStarting = true } XCTAssertNoDifference(actions, [ .didStartBackup( passphrase: passphrase, params: .init(username: dbContact.username!) ) ]) store.receive(.didStart(failure: nil)) { $0.isRunning = true $0.isStarting = false $0.passphrase = "" } } func testStartBackupWithoutDbContact() { var isBackupRunning: [Bool] = [false] let contactID = "contact-id".data(using: .utf8)! let store = TestStore( initialState: BackupState( passphrase: "1234" ), reducer: backupReducer, environment: .unimplemented ) store.environment.mainQueue = .immediate store.environment.bgQueue = .immediate store.environment.messenger.isBackupRunning.run = { isBackupRunning.removeFirst() } 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.fetchContacts.run = { _ in [] } return db } store.send(.startTapped) { $0.isStarting = true } let failure = BackupState.Error.dbContactNotFound store.receive(.didStart(failure: failure as NSError)) { $0.isRunning = false $0.isStarting = false $0.alert = .error(failure) } } func testStartBackupWithoutDbContactUsername() { var isBackupRunning: [Bool] = [false] let contactID = "contact-id".data(using: .utf8)! let dbContact = XXModels.Contact( id: contactID, username: nil ) let store = TestStore( initialState: BackupState( passphrase: "1234" ), reducer: backupReducer, environment: .unimplemented ) store.environment.mainQueue = .immediate store.environment.bgQueue = .immediate store.environment.messenger.isBackupRunning.run = { isBackupRunning.removeFirst() } 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.fetchContacts.run = { _ in [dbContact] } return db } store.send(.startTapped) { $0.isStarting = true } let failure = BackupState.Error.dbContactUsernameMissing store.receive(.didStart(failure: failure as NSError)) { $0.isRunning = false $0.isStarting = false $0.alert = .error(failure) } } func testStartBackupFailure() { struct Failure: Error {} let failure = Failure() var isBackupRunning: [Bool] = [false] let contactID = "contact-id".data(using: .utf8)! let dbContact = XXModels.Contact( id: contactID, username: "db-contact-username" ) let store = TestStore( initialState: BackupState( passphrase: "1234" ), reducer: backupReducer, environment: .unimplemented ) store.environment.mainQueue = .immediate store.environment.bgQueue = .immediate store.environment.messenger.startBackup.run = { _, _ in throw failure } store.environment.messenger.isBackupRunning.run = { isBackupRunning.removeFirst() } 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.fetchContacts.run = { _ in return [dbContact] } return db } store.send(.startTapped) { $0.isStarting = true } store.receive(.didStart(failure: failure as NSError)) { $0.isRunning = false $0.isStarting = false $0.alert = .error(failure) } } func testResumeBackup() { var actions: [Action]! var isBackupRunning: [Bool] = [true] let store = TestStore( initialState: BackupState(), reducer: backupReducer, environment: .unimplemented ) store.environment.mainQueue = .immediate store.environment.bgQueue = .immediate store.environment.messenger.resumeBackup.run = { actions.append(.didResumeBackup) } store.environment.messenger.isBackupRunning.run = { isBackupRunning.removeFirst() } actions = [] store.send(.resumeTapped) { $0.isResuming = true } XCTAssertNoDifference(actions, [.didResumeBackup]) actions = [] store.receive(.didResume(failure: nil)) { $0.isRunning = true $0.isResuming = false } XCTAssertNoDifference(actions, []) } func testResumeBackupFailure() { struct Failure: Error {} let failure = Failure() var isBackupRunning: [Bool] = [false] let store = TestStore( initialState: BackupState(), reducer: backupReducer, environment: .unimplemented ) store.environment.mainQueue = .immediate store.environment.bgQueue = .immediate store.environment.messenger.resumeBackup.run = { throw failure } store.environment.messenger.isBackupRunning.run = { isBackupRunning.removeFirst() } store.send(.resumeTapped) { $0.isResuming = true } store.receive(.didResume(failure: failure as NSError)) { $0.isRunning = false $0.isResuming = false $0.alert = .error(failure) } } func testStopBackup() { var actions: [Action]! var isBackupRunning: [Bool] = [false] let store = TestStore( initialState: BackupState(), reducer: backupReducer, environment: .unimplemented ) store.environment.mainQueue = .immediate store.environment.bgQueue = .immediate store.environment.messenger.stopBackup.run = { actions.append(.didStopBackup) } store.environment.messenger.isBackupRunning.run = { isBackupRunning.removeFirst() } store.environment.backupStorage.remove = { actions.append(.didRemoveBackup) } actions = [] store.send(.stopTapped) { $0.isStopping = true } XCTAssertNoDifference(actions, [ .didStopBackup, .didRemoveBackup, ]) actions = [] store.receive(.didStop(failure: nil)) { $0.isRunning = false $0.isStopping = false } XCTAssertNoDifference(actions, []) } func testStopBackupFailure() { struct Failure: Error {} let failure = Failure() var isBackupRunning: [Bool] = [true] let store = TestStore( initialState: BackupState(), reducer: backupReducer, environment: .unimplemented ) store.environment.mainQueue = .immediate store.environment.bgQueue = .immediate store.environment.messenger.stopBackup.run = { throw failure } store.environment.messenger.isBackupRunning.run = { isBackupRunning.removeFirst() } store.send(.stopTapped) { $0.isStopping = true } store.receive(.didStop(failure: failure as NSError)) { $0.isRunning = true $0.isStopping = false $0.alert = .error(failure) } } func testAlertDismissed() { let store = TestStore( initialState: BackupState( alert: .error(NSError(domain: "test", code: 0)) ), reducer: backupReducer, environment: .unimplemented ) store.send(.alertDismissed) { $0.alert = nil } } func testExportBackup() { let backupData = "backup-data".data(using: .utf8)! let store = TestStore( initialState: BackupState( backup: .init( date: Date(), data: backupData ) ), reducer: backupReducer, environment: .unimplemented ) store.send(.exportTapped) { $0.isExporting = true $0.exportData = backupData } store.send(.didExport(failure: nil)) { $0.isExporting = false $0.exportData = nil } store.send(.exportTapped) { $0.isExporting = true $0.exportData = backupData } let failure = NSError(domain: "test", code: 0) store.send(.didExport(failure: failure)) { $0.isExporting = false $0.exportData = nil $0.alert = .error(failure) } } } private enum Action: Equatable { case didRegisterObserver case didStartBackup(passphrase: String, params: BackupParams) case didResumeBackup case didStopBackup case didRemoveBackup case didFetchContacts(XXModels.Contact.Query) }