Skip to content
Snippets Groups Projects
Commit bebb525c authored by Dariusz Rybicki's avatar Dariusz Rybicki
Browse files

Merge branch 'fix/example-save-facts-when-restored' into 'development'

Backup restoration fixes

See merge request elixxir/elixxir-dapps-sdk-swift!105
parents 394c8fc6 58f851c4
No related branches found
No related tags found
2 merge requests!105Backup restoration fixes,!102Release 1.0.0
...@@ -274,8 +274,10 @@ let package = Package( ...@@ -274,8 +274,10 @@ let package = Package(
.target( .target(
name: "RestoreFeature", name: "RestoreFeature",
dependencies: [ dependencies: [
.target(name: "AppCore"),
.product(name: "ComposableArchitecture", package: "swift-composable-architecture"), .product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
.product(name: "XXMessengerClient", package: "elixxir-dapps-sdk-swift"), .product(name: "XXMessengerClient", package: "elixxir-dapps-sdk-swift"),
.product(name: "XXModels", package: "client-ios-db"),
], ],
swiftSettings: swiftSettings swiftSettings: swiftSettings
), ),
......
...@@ -103,7 +103,9 @@ extension AppEnvironment { ...@@ -103,7 +103,9 @@ extension AppEnvironment {
restore: { restore: {
RestoreEnvironment( RestoreEnvironment(
messenger: messenger, messenger: messenger,
db: dbManager.getDB,
loadData: .live, loadData: .live,
now: Date.init,
mainQueue: mainQueue, mainQueue: mainQueue,
bgQueue: bgQueue bgQueue: bgQueue
) )
......
import AppCore
import Combine import Combine
import ComposableArchitecture import ComposableArchitecture
import Foundation import Foundation
import XCTestDynamicOverlay import XCTestDynamicOverlay
import XXMessengerClient import XXMessengerClient
import XXModels
public struct RestoreState: Equatable { public struct RestoreState: Equatable {
public enum Field: String, Hashable { public enum Field: String, Hashable {
...@@ -58,18 +60,24 @@ public enum RestoreAction: Equatable, BindableAction { ...@@ -58,18 +60,24 @@ public enum RestoreAction: Equatable, BindableAction {
public struct RestoreEnvironment { public struct RestoreEnvironment {
public init( public init(
messenger: Messenger, messenger: Messenger,
db: DBManagerGetDB,
loadData: URLDataLoader, loadData: URLDataLoader,
now: @escaping () -> Date,
mainQueue: AnySchedulerOf<DispatchQueue>, mainQueue: AnySchedulerOf<DispatchQueue>,
bgQueue: AnySchedulerOf<DispatchQueue> bgQueue: AnySchedulerOf<DispatchQueue>
) { ) {
self.messenger = messenger self.messenger = messenger
self.db = db
self.loadData = loadData self.loadData = loadData
self.now = now
self.mainQueue = mainQueue self.mainQueue = mainQueue
self.bgQueue = bgQueue self.bgQueue = bgQueue
} }
public var messenger: Messenger public var messenger: Messenger
public var db: DBManagerGetDB
public var loadData: URLDataLoader public var loadData: URLDataLoader
public var now: () -> Date
public var mainQueue: AnySchedulerOf<DispatchQueue> public var mainQueue: AnySchedulerOf<DispatchQueue>
public var bgQueue: AnySchedulerOf<DispatchQueue> public var bgQueue: AnySchedulerOf<DispatchQueue>
} }
...@@ -77,7 +85,9 @@ public struct RestoreEnvironment { ...@@ -77,7 +85,9 @@ public struct RestoreEnvironment {
extension RestoreEnvironment { extension RestoreEnvironment {
public static let unimplemented = RestoreEnvironment( public static let unimplemented = RestoreEnvironment(
messenger: .unimplemented, messenger: .unimplemented,
db: .unimplemented,
loadData: .unimplemented, loadData: .unimplemented,
now: XCTUnimplemented("\(Self.self).now"),
mainQueue: .unimplemented, mainQueue: .unimplemented,
bgQueue: .unimplemented bgQueue: .unimplemented
) )
...@@ -118,12 +128,20 @@ public let restoreReducer = Reducer<RestoreState, RestoreAction, RestoreEnvironm ...@@ -118,12 +128,20 @@ public let restoreReducer = Reducer<RestoreState, RestoreAction, RestoreEnvironm
state.restoreFailure = nil state.restoreFailure = nil
return Effect.result { return Effect.result {
do { do {
_ = try env.messenger.restoreBackup( let result = try env.messenger.restoreBackup(
backupData: backupData, backupData: backupData,
backupPassphrase: backupPassphrase 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) return .success(.finished)
} catch { } catch {
try? env.messenger.destroy()
return .success(.failed(error as NSError)) return .success(.failed(error as NSError))
} }
} }
......
import CustomDump
import ComposableArchitecture import ComposableArchitecture
import CustomDump
import XCTest import XCTest
@testable import RestoreFeature import XXClient
import XXMessengerClient import XXMessengerClient
import XXModels
@testable import RestoreFeature
final class RestoreFeatureTests: XCTestCase { final class RestoreFeatureTests: XCTestCase {
func testFileImport() { func testFileImport() {
...@@ -79,15 +81,18 @@ final class RestoreFeatureTests: XCTestCase { ...@@ -79,15 +81,18 @@ final class RestoreFeatureTests: XCTestCase {
let backupPassphrase = "backup-passphrase" let backupPassphrase = "backup-passphrase"
let restoreResult = MessengerRestoreBackup.Result( let restoreResult = MessengerRestoreBackup.Result(
restoredParams: BackupParams.init( restoredParams: BackupParams.init(
username: "", username: "restored-username",
email: nil, email: "restored-email",
phone: nil phone: "restored-phone"
), ),
restoredContacts: [] restoredContacts: []
) )
let now = Date()
let contactId = "contact-id".data(using: .utf8)!
var didRestoreWithData: [Data] = [] var didRestoreWithData: [Data] = []
var didRestoreWithPassphrase: [String] = [] var didRestoreWithPassphrase: [String] = []
var didSaveContact: [XXModels.Contact] = []
let store = TestStore( let store = TestStore(
initialState: RestoreState( initialState: RestoreState(
...@@ -99,11 +104,29 @@ final class RestoreFeatureTests: XCTestCase { ...@@ -99,11 +104,29 @@ final class RestoreFeatureTests: XCTestCase {
store.environment.bgQueue = .immediate store.environment.bgQueue = .immediate
store.environment.mainQueue = .immediate store.environment.mainQueue = .immediate
store.environment.now = { now }
store.environment.messenger.restoreBackup.run = { data, passphrase in store.environment.messenger.restoreBackup.run = { data, passphrase in
didRestoreWithData.append(data) didRestoreWithData.append(data)
didRestoreWithPassphrase.append(passphrase) didRestoreWithPassphrase.append(passphrase)
return restoreResult 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)) { store.send(.set(\.$passphrase, backupPassphrase)) {
$0.passphrase = backupPassphrase $0.passphrase = backupPassphrase
...@@ -115,6 +138,13 @@ final class RestoreFeatureTests: XCTestCase { ...@@ -115,6 +138,13 @@ final class RestoreFeatureTests: XCTestCase {
XCTAssertNoDifference(didRestoreWithData, [backupData]) XCTAssertNoDifference(didRestoreWithData, [backupData])
XCTAssertNoDifference(didRestoreWithPassphrase, [backupPassphrase]) 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) { store.receive(.finished) {
$0.isRestoring = false $0.isRestoring = false
...@@ -137,6 +167,8 @@ final class RestoreFeatureTests: XCTestCase { ...@@ -137,6 +167,8 @@ final class RestoreFeatureTests: XCTestCase {
struct Failure: Error {} struct Failure: Error {}
let failure = Failure() let failure = Failure()
var didDestroyMessenger = 0
let store = TestStore( let store = TestStore(
initialState: RestoreState( initialState: RestoreState(
file: .init(name: "name", data: "data".data(using: .utf8)!) file: .init(name: "name", data: "data".data(using: .utf8)!)
...@@ -148,11 +180,14 @@ final class RestoreFeatureTests: XCTestCase { ...@@ -148,11 +180,14 @@ final class RestoreFeatureTests: XCTestCase {
store.environment.bgQueue = .immediate store.environment.bgQueue = .immediate
store.environment.mainQueue = .immediate store.environment.mainQueue = .immediate
store.environment.messenger.restoreBackup.run = { _, _ in throw failure } store.environment.messenger.restoreBackup.run = { _, _ in throw failure }
store.environment.messenger.destroy.run = { didDestroyMessenger += 1 }
store.send(.restoreTapped) { store.send(.restoreTapped) {
$0.isRestoring = true $0.isRestoring = true
} }
XCTAssertEqual(didDestroyMessenger, 1)
store.receive(.failed(failure as NSError)) { store.receive(.failed(failure as NSError)) {
$0.isRestoring = false $0.isRestoring = false
$0.restoreFailure = failure.localizedDescription $0.restoreFailure = failure.localizedDescription
......
...@@ -2,13 +2,9 @@ ...@@ -2,13 +2,9 @@
import PackageDescription import PackageDescription
let swiftSettings: [SwiftSetting] = [ let swiftSettings: [SwiftSetting] = [
.unsafeFlags( //.unsafeFlags(["-Xfrontend", "-warn-concurrency"], .when(configuration: .debug)),
[ //.unsafeFlags(["-Xfrontend", "-debug-time-function-bodies"], .when(configuration: .debug)),
"-Xfrontend", "-debug-time-function-bodies", //.unsafeFlags(["-Xfrontend", "-debug-time-expression-type-checking"], .when(configuration: .debug)),
"-Xfrontend", "-debug-time-expression-type-checking",
],
.when(configuration: .debug)
),
] ]
let package = Package( let package = Package(
......
...@@ -30,58 +30,53 @@ extension MessengerRestoreBackup { ...@@ -30,58 +30,53 @@ extension MessengerRestoreBackup {
public static func live(_ env: MessengerEnvironment) -> MessengerRestoreBackup { public static func live(_ env: MessengerEnvironment) -> MessengerRestoreBackup {
MessengerRestoreBackup { backupData, backupPassphrase in MessengerRestoreBackup { backupData, backupPassphrase in
let storageDir = env.storageDir let storageDir = env.storageDir
do { let ndfData = try env.downloadNDF(env.ndfEnvironment)
let ndfData = try env.downloadNDF(env.ndfEnvironment) let password = env.generateSecret()
let password = env.generateSecret() try env.passwordStorage.save(password)
try env.passwordStorage.save(password) try env.fileManager.removeDirectory(storageDir)
try env.fileManager.removeDirectory(storageDir) try env.fileManager.createDirectory(storageDir)
try env.fileManager.createDirectory(storageDir) let report = try env.newCMixFromBackup(
let report = try env.newCMixFromBackup( ndfJSON: String(data: ndfData, encoding: .utf8)!,
ndfJSON: String(data: ndfData, encoding: .utf8)!, storageDir: storageDir,
storageDir: storageDir, backupPassphrase: backupPassphrase,
backupPassphrase: backupPassphrase, sessionPassword: password,
sessionPassword: password, backupFileContents: backupData
backupFileContents: backupData )
) let cMix = try env.loadCMix(
let cMix = try env.loadCMix( storageDir: storageDir,
storageDir: storageDir, password: password,
password: password, cMixParamsJSON: env.getCMixParams()
cMixParamsJSON: env.getCMixParams() )
) env.cMix.set(cMix)
try cMix.startNetworkFollower(timeoutMS: 30_000) try cMix.startNetworkFollower(timeoutMS: 30_000)
let e2e = try env.login( let e2e = try env.login(
cMixId: cMix.getId(), cMixId: cMix.getId(),
authCallbacks: env.authCallbacks.registered(), authCallbacks: env.authCallbacks.registered(),
identity: try cMix.makeReceptionIdentity(legacy: true), identity: try cMix.makeReceptionIdentity(legacy: true),
e2eParamsJSON: env.getE2EParams() e2eParamsJSON: env.getE2EParams()
) )
let decoder = JSONDecoder() env.e2e.set(e2e)
let paramsData = report.params.data(using: .utf8)! env.isListeningForMessages.set(false)
let params = try decoder.decode(BackupParams.self, from: paramsData) let decoder = JSONDecoder()
let ud = try env.newUdManagerFromBackup( let paramsData = report.params.data(using: .utf8)!
params: NewUdManagerFromBackup.Params( let params = try decoder.decode(BackupParams.self, from: paramsData)
e2eId: e2e.getId(), let ud = try env.newUdManagerFromBackup(
username: Fact(type: .username, value: params.username), params: NewUdManagerFromBackup.Params(
email: params.email.map { Fact(type: .email, value: $0) }, e2eId: e2e.getId(),
phone: params.phone.map { Fact(type: .phone, value: $0) }, username: Fact(type: .username, value: params.username),
cert: env.udCert ?? e2e.getUdCertFromNdf(), email: params.email.map { Fact(type: .email, value: $0) },
contact: env.udContact ?? (try e2e.getUdContactFromNdf()), phone: params.phone.map { Fact(type: .phone, value: $0) },
address: env.udAddress ?? e2e.getUdAddressFromNdf() cert: env.udCert ?? e2e.getUdCertFromNdf(),
), contact: env.udContact ?? (try e2e.getUdContactFromNdf()),
follower: UdNetworkStatus { cMix.networkFollowerStatus() } address: env.udAddress ?? e2e.getUdAddressFromNdf()
) ),
env.cMix.set(cMix) follower: UdNetworkStatus { cMix.networkFollowerStatus() }
env.e2e.set(e2e) )
env.ud.set(ud) env.ud.set(ud)
env.isListeningForMessages.set(false) return Result(
return Result( restoredParams: params,
restoredParams: params, restoredContacts: report.restoredContacts
restoredContacts: report.restoredContacts )
)
} catch {
try? env.fileManager.removeDirectory(storageDir)
throw error
}
} }
} }
} }
......
...@@ -136,6 +136,7 @@ final class MessengerRestoreBackupTests: XCTestCase { ...@@ -136,6 +136,7 @@ final class MessengerRestoreBackupTests: XCTestCase {
password: password, password: password,
cMixParams: cMixParams cMixParams: cMixParams
), ),
.didSetCMix,
.cMixDidStartNetworkFollower( .cMixDidStartNetworkFollower(
timeoutMS: 30_000 timeoutMS: 30_000
), ),
...@@ -148,6 +149,10 @@ final class MessengerRestoreBackupTests: XCTestCase { ...@@ -148,6 +149,10 @@ final class MessengerRestoreBackupTests: XCTestCase {
identity: receptionIdentity, identity: receptionIdentity,
e2eParamsJSON: e2eParams e2eParamsJSON: e2eParams
), ),
.didSetE2E,
.didSetIsListeningForMessages(
isListening: false
),
.didNewUdManagerFromBackup(params: .init( .didNewUdManagerFromBackup(params: .init(
e2eId: e2eId, e2eId: e2eId,
username: Fact(type: .username, value: backupParams.username), username: Fact(type: .username, value: backupParams.username),
...@@ -157,12 +162,7 @@ final class MessengerRestoreBackupTests: XCTestCase { ...@@ -157,12 +162,7 @@ final class MessengerRestoreBackupTests: XCTestCase {
contact: udContactFromNdf, contact: udContactFromNdf,
address: udAddressFromNdf address: udAddressFromNdf
)), )),
.didSetCMix,
.didSetE2E,
.didSetUD, .didSetUD,
.didSetIsListeningForMessages(
isListening: false
),
]) ])
XCTAssertNoDifference(result, MessengerRestoreBackup.Result( XCTAssertNoDifference(result, MessengerRestoreBackup.Result(
...@@ -175,19 +175,13 @@ final class MessengerRestoreBackupTests: XCTestCase { ...@@ -175,19 +175,13 @@ final class MessengerRestoreBackupTests: XCTestCase {
struct Failure: Error, Equatable {} struct Failure: Error, Equatable {}
let failure = Failure() let failure = Failure()
var actions: [CaughtAction] = []
var env: MessengerEnvironment = .unimplemented var env: MessengerEnvironment = .unimplemented
env.downloadNDF.run = { _ in throw failure } env.downloadNDF.run = { _ in throw failure }
env.fileManager.removeDirectory = { actions.append(.didRemoveDirectory(path: $0)) }
let restore: MessengerRestoreBackup = .live(env) let restore: MessengerRestoreBackup = .live(env)
XCTAssertThrowsError(try restore(backupData: Data(), backupPassphrase: "")) { error in XCTAssertThrowsError(try restore(backupData: Data(), backupPassphrase: "")) { error in
XCTAssertNoDifference(error as? Failure, failure) XCTAssertNoDifference(error as? Failure, failure)
} }
XCTAssertNoDifference(actions, [
.didRemoveDirectory(path: env.storageDir)
])
} }
} }
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment