Issue #220 - Dependency for the SDK database files/file management (#240)

unit tests implemented

naming enhanced
This commit is contained in:
Lukas Korba 2022-04-07 16:13:40 +02:00 committed by GitHub
parent b665a18fe2
commit 8136ec3040
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 353 additions and 10 deletions

View File

@ -87,6 +87,8 @@
66A0807B271993C500118B79 /* OnboardingProgressIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66A0807A271993C500118B79 /* OnboardingProgressIndicator.swift */; };
66D50668271D9B6100E51F0D /* NavigationButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66D50667271D9B6100E51F0D /* NavigationButtonStyle.swift */; };
66DC733F271D88CC0053CBB6 /* StandardButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66DC733E271D88CC0053CBB6 /* StandardButtonStyle.swift */; };
9E02B56A27FED43E005B809B /* WrappedFileManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E02B56927FED43E005B809B /* WrappedFileManager.swift */; };
9E02B56C27FED475005B809B /* DatabaseFilesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E02B56B27FED475005B809B /* DatabaseFilesTests.swift */; };
9E2AC0FF27D8EC120042AA47 /* MnemonicSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 9E2AC0FE27D8EC120042AA47 /* MnemonicSwift */; };
9E2AC10127D8EF0B0042AA47 /* MnemonicSeedPhraseProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E2AC10027D8EF0B0042AA47 /* MnemonicSeedPhraseProvider.swift */; };
9E2AC10327DA28200042AA47 /* WalletStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E2AC10227DA28200042AA47 /* WalletStorage.swift */; };
@ -99,6 +101,7 @@
9E69A24D27FB002800A55317 /* Welcome.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E69A24C27FB002800A55317 /* Welcome.swift */; };
9E80B47227E4B34B008FF493 /* UserPreferencesStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E80B47127E4B34B008FF493 /* UserPreferencesStorage.swift */; };
9EBEF87A27CE369800B4F343 /* RecoveryPhraseTestPreambleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EBEF87927CE369800B4F343 /* RecoveryPhraseTestPreambleView.swift */; };
9ECAE56827FC713C0089A0EF /* DatabaseFiles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9ECAE56727FC713C0089A0EF /* DatabaseFiles.swift */; };
9EF8135C27ECC25E0075AF48 /* WalletStorageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EF8135A27ECC25E0075AF48 /* WalletStorageTests.swift */; };
9EF8135D27ECC25E0075AF48 /* UserPreferencesStorageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EF8135B27ECC25E0075AF48 /* UserPreferencesStorageTests.swift */; };
9EF8136027F043CC0075AF48 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EF8135F27F043CC0075AF48 /* AppDelegate.swift */; };
@ -237,6 +240,8 @@
66A0807A271993C500118B79 /* OnboardingProgressIndicator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingProgressIndicator.swift; sourceTree = "<group>"; };
66D50667271D9B6100E51F0D /* NavigationButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationButtonStyle.swift; sourceTree = "<group>"; };
66DC733E271D88CC0053CBB6 /* StandardButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StandardButtonStyle.swift; sourceTree = "<group>"; };
9E02B56927FED43E005B809B /* WrappedFileManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WrappedFileManager.swift; sourceTree = "<group>"; };
9E02B56B27FED475005B809B /* DatabaseFilesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatabaseFilesTests.swift; sourceTree = "<group>"; };
9E2AC10027D8EF0B0042AA47 /* MnemonicSeedPhraseProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MnemonicSeedPhraseProvider.swift; sourceTree = "<group>"; };
9E2AC10227DA28200042AA47 /* WalletStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletStorage.swift; sourceTree = "<group>"; };
9E2AC10527DA34610042AA47 /* RecoveryPhraseStorageTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecoveryPhraseStorageTests.swift; sourceTree = "<group>"; };
@ -249,6 +254,7 @@
9E69A24C27FB002800A55317 /* Welcome.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Welcome.swift; sourceTree = "<group>"; };
9E80B47127E4B34B008FF493 /* UserPreferencesStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserPreferencesStorage.swift; sourceTree = "<group>"; };
9EBEF87927CE369800B4F343 /* RecoveryPhraseTestPreambleView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecoveryPhraseTestPreambleView.swift; sourceTree = "<group>"; };
9ECAE56727FC713C0089A0EF /* DatabaseFiles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatabaseFiles.swift; sourceTree = "<group>"; };
9EF8135A27ECC25E0075AF48 /* WalletStorageTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WalletStorageTests.swift; sourceTree = "<group>"; };
9EF8135B27ECC25E0075AF48 /* UserPreferencesStorageTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserPreferencesStorageTests.swift; sourceTree = "<group>"; };
9EF8135F27F043CC0075AF48 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
@ -413,6 +419,7 @@
children = (
0D4E7A0826B364170058B01E /* SecantApp.swift */,
0D4E7A0A26B364170058B01E /* ContentView.swift */,
9E02B56827FED42D005B809B /* Wrappers */,
0DACFA7D27208CC80039EEA5 /* Util */,
6654C73B2715A3F000901167 /* Features */,
660558F4270C85F7009D6954 /* Generated */,
@ -542,6 +549,7 @@
9E80B47127E4B34B008FF493 /* UserPreferencesStorage.swift */,
9EF8139B27F47AED0075AF48 /* InitializationState.swift */,
9EF8139027F191BF0075AF48 /* WalletStorageInteractor.swift */,
9ECAE56727FC713C0089A0EF /* DatabaseFiles.swift */,
);
path = Util;
sourceTree = "<group>";
@ -719,6 +727,14 @@
path = CircularFrame;
sourceTree = "<group>";
};
9E02B56827FED42D005B809B /* Wrappers */ = {
isa = PBXGroup;
children = (
9E02B56927FED43E005B809B /* WrappedFileManager.swift */,
);
path = Wrappers;
sourceTree = "<group>";
};
9E2DF99727CF704D00649636 /* ImportWallet */ = {
isa = PBXGroup;
children = (
@ -758,6 +774,7 @@
children = (
9EF8135A27ECC25E0075AF48 /* WalletStorageTests.swift */,
9EF8135B27ECC25E0075AF48 /* UserPreferencesStorageTests.swift */,
9E02B56B27FED475005B809B /* DatabaseFilesTests.swift */,
);
path = Util;
sourceTree = "<group>";
@ -1132,6 +1149,7 @@
9EF8136027F043CC0075AF48 /* AppDelegate.swift in Sources */,
9E80B47227E4B34B008FF493 /* UserPreferencesStorage.swift in Sources */,
F96B41E8273B501F0021B49A /* TransactionDetailView.swift in Sources */,
9E02B56A27FED43E005B809B /* WrappedFileManager.swift in Sources */,
663FABA2271D876C00E495F8 /* SecondaryButton.swift in Sources */,
0DC487C32772574C00BE6A63 /* ValidationSucceededView.swift in Sources */,
2EB1C5E827D77F6100BC43D7 /* TextFieldStore.swift in Sources */,
@ -1188,6 +1206,7 @@
F9C165C02740403600592F76 /* ApproveView.swift in Sources */,
0DF2DC5427235E3E00FA31E2 /* View+InnerShadow.swift in Sources */,
9E2AC10327DA28200042AA47 /* WalletStorage.swift in Sources */,
9ECAE56827FC713C0089A0EF /* DatabaseFiles.swift in Sources */,
F9971A6B27680E1000A2DB75 /* WalletInfo.swift in Sources */,
0D185819272723FF0046B928 /* ColoredChip.swift in Sources */,
2EA11F5D27467F7700709571 /* OnboardingContentView.swift in Sources */,
@ -1230,6 +1249,7 @@
0D4E7A1B26B364180058B01E /* secantTests.swift in Sources */,
0DFE93E6272CB6F7000FCCA5 /* RecoveryPhraseValidationTests.swift in Sources */,
9EF8135C27ECC25E0075AF48 /* WalletStorageTests.swift in Sources */,
9E02B56C27FED475005B809B /* DatabaseFilesTests.swift in Sources */,
9EF8135D27ECC25E0075AF48 /* UserPreferencesStorageTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;

View File

@ -34,6 +34,7 @@ enum AppAction: Equatable {
}
struct AppEnvironment {
let databaseFiles: DatabaseFilesInteractor
let scheduler: AnySchedulerOf<DispatchQueue>
let mnemonicSeedPhraseProvider: MnemonicSeedPhraseProvider
let walletStorage: WalletStorageInteractor
@ -41,15 +42,17 @@ struct AppEnvironment {
extension AppEnvironment {
static let live = AppEnvironment(
databaseFiles: .live(),
scheduler: DispatchQueue.main.eraseToAnyScheduler(),
mnemonicSeedPhraseProvider: .live,
walletStorage: .live(walletStorage: WalletStorage())
walletStorage: .live()
)
static let mock = AppEnvironment(
databaseFiles: .live(),
scheduler: DispatchQueue.main.eraseToAnyScheduler(),
mnemonicSeedPhraseProvider: .mock,
walletStorage: .live(walletStorage: WalletStorage())
walletStorage: .live()
)
}
@ -96,15 +99,9 @@ extension AppReducer {
/// Checking presense of stored wallet in the keychain and presense of database files in documents directory.
case .checkWalletInitialization:
// TODO: Create a dependency to handle database files for the SDK, issue #220 (https://github.com/zcash/secant-ios-wallet/issues/220)
let fileManager = FileManager()
do {
// TODO: use database URL from the same issue #220
let documentsURL = try fileManager.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
let dataDatabaseURL = documentsURL.appendingPathComponent("ZcashSDK.defaultDataDbName", isDirectory: false)
let attributes = try fileManager.attributesOfItem(atPath: dataDatabaseURL.path)
let databaseFilesPresent = attributes.isEmpty
// TODO: replace the hardcoded network with the environmental value, issue 239 (https://github.com/zcash/secant-ios-wallet/issues/239)
let databaseFilesPresent = try environment.databaseFiles.areDbFilesPresentFor("mainnet")
let keysPresent = try environment.walletStorage.areKeysPresent()
switch (keysPresent, databaseFilesPresent) {

View File

@ -0,0 +1,98 @@
//
// DatabaseFiles.swift
// secant-testnet
//
// Created by Lukáš Korba on 05.04.2022.
//
import Foundation
struct DatabaseFiles {
enum DatabaseFilesError: Error {
case getDocumentsURL
case getDataURL
case nukeFiles
case filesPresentCheck
}
private let fileManager: WrappedFileManager
init(fileManager: WrappedFileManager) {
self.fileManager = fileManager
}
func documentsDirectory() throws -> URL {
do {
return try fileManager.url(.documentDirectory, .userDomainMask, nil, true)
} catch {
throw DatabaseFilesError.getDocumentsURL
}
}
func dataDbURL(for network: String) throws -> URL {
do {
return try documentsDirectory().appendingPathComponent("zcash.\(network).data.db", isDirectory: false)
} catch {
throw DatabaseFilesError.getDataURL
}
}
func areDbFilesPresent(for network: String) throws -> Bool {
do {
let dataDatabaseURL = try dataDbURL(for: network)
return fileManager.fileExists(dataDatabaseURL.path)
} catch {
throw DatabaseFilesError.filesPresentCheck
}
}
func nukeDbFiles(for network: String) throws {
do {
let dataDatabaseURL = try dataDbURL(for: network)
try fileManager.removeItem(dataDatabaseURL)
} catch {
throw DatabaseFilesError.nukeFiles
}
}
}
struct DatabaseFilesInteractor {
let documentsDirectory: () throws -> URL
let dataDbURLFor: (String) throws -> URL
let areDbFilesPresentFor: (String) throws -> Bool
let nukeDbFilesFor: (String) throws -> Void
}
extension DatabaseFilesInteractor {
static func live(databaseFiles: DatabaseFiles = DatabaseFiles(fileManager: .live)) -> Self {
Self(
documentsDirectory: {
try databaseFiles.documentsDirectory()
},
dataDbURLFor: { network in
try databaseFiles.dataDbURL(for: network)
},
areDbFilesPresentFor: { network in
try databaseFiles.areDbFilesPresent(for: network)
},
nukeDbFilesFor: { network in
try databaseFiles.nukeDbFiles(for: network)
}
)
}
static var throwing = DatabaseFilesInteractor(
documentsDirectory: {
throw DatabaseFiles.DatabaseFilesError.getDocumentsURL
},
dataDbURLFor: { _ in
throw DatabaseFiles.DatabaseFilesError.getDataURL
},
areDbFilesPresentFor: { _ in
throw DatabaseFiles.DatabaseFilesError.filesPresentCheck
},
nukeDbFilesFor: { _ in
throw DatabaseFiles.DatabaseFilesError.nukeFiles
}
)
}

View File

@ -0,0 +1,28 @@
//
// WrappedFileManager.swift
// secant-testnet
//
// Created by Lukáš Korba on 07.04.2022.
//
import Foundation
struct WrappedFileManager {
let url: (FileManager.SearchPathDirectory, FileManager.SearchPathDomainMask, URL?, Bool) throws -> URL
let fileExists: (String) -> Bool
let removeItem: (URL) throws -> Void
}
extension WrappedFileManager {
static let live = WrappedFileManager(
url: { searchPathDirectory, searchPathDomainMask, appropriateForURL, shouldCreate in
try FileManager.default.url(for: searchPathDirectory, in: searchPathDomainMask, appropriateFor: appropriateForURL, create: shouldCreate)
},
fileExists: { path in
FileManager.default.fileExists(atPath: path)
},
removeItem: { url in
try FileManager.default.removeItem(at: url)
}
)
}

View File

@ -0,0 +1,200 @@
//
// DatabaseFilesTests.swift
// secantTests
//
// Created by Lukáš Korba on 07.04.2022.
//
import XCTest
@testable import secant_testnet
extension String: Error {}
extension DatabaseFiles.DatabaseFilesError {
var debugValue: String {
switch self {
case .getDocumentsURL: return "getDocumentsURL"
case .getDataURL: return "getDataURL"
case .nukeFiles: return "nukeFiles"
case .filesPresentCheck: return "filesPresentCheck"
}
}
}
class DatabaseFilesTests: XCTestCase {
func testFailingDocumentsDirectory() throws {
let mockedFileManager = WrappedFileManager(
url: { _, _, _, _ in throw "some error" },
fileExists: { _ in return true },
removeItem: { _ in }
)
let dfInteractor = DatabaseFilesInteractor.live(databaseFiles: DatabaseFiles(fileManager: mockedFileManager))
do {
_ = try dfInteractor.documentsDirectory()
XCTFail("DatabaseFiles: `testFailingDocumentsDirectory` expected to fail but passed with no error.")
} catch {
guard let error = error as? DatabaseFiles.DatabaseFilesError else {
XCTFail("DatabaseFiles: the error is expected to be DatabaseFilesError but it's \(error).")
return
}
XCTAssertEqual(
error.debugValue,
DatabaseFiles.DatabaseFilesError.getDocumentsURL.debugValue,
"DatabaseFiles: error must be .getDocumentsURL but it's \(error)."
)
}
}
func testFailingDataDbURL() throws {
let mockedFileManager = WrappedFileManager(
url: { _, _, _, _ in throw "some error" },
fileExists: { _ in return true },
removeItem: { _ in }
)
let dfInteractor = DatabaseFilesInteractor.live(databaseFiles: DatabaseFiles(fileManager: mockedFileManager))
do {
_ = try dfInteractor.dataDbURLFor("")
XCTFail("DatabaseFiles: `testFailingDataDbURL` expected to fail but passed with no error.")
} catch {
guard let error = error as? DatabaseFiles.DatabaseFilesError else {
XCTFail("DatabaseFiles: the error is expected to be DatabaseFilesError but it's \(error).")
return
}
XCTAssertEqual(
error.debugValue,
DatabaseFiles.DatabaseFilesError.getDataURL.debugValue,
"DatabaseFiles: error must be .getDataURL but it's \(error)."
)
}
}
func testDatabaseFilesPresent() throws {
let mockedFileManager = WrappedFileManager(
url: { _, _, _, _ in URL(fileURLWithPath: "") },
fileExists: { _ in return true },
removeItem: { _ in }
)
let dfInteractor = DatabaseFilesInteractor.live(databaseFiles: DatabaseFiles(fileManager: mockedFileManager))
do {
let areFilesPresent = try dfInteractor.areDbFilesPresentFor("")
XCTAssertTrue(areFilesPresent, "DatabaseFiles: `testDatabaseFilesPresent` is expected to be true but it's \(areFilesPresent)")
} catch {
XCTFail("DatabaseFiles: `testDatabaseFilesPresent` expected to fail but passed with no error.")
}
}
func testDatabaseFilesNotPresent() throws {
let mockedFileManager = WrappedFileManager(
url: { _, _, _, _ in URL(fileURLWithPath: "") },
fileExists: { _ in return false },
removeItem: { _ in }
)
let dfInteractor = DatabaseFilesInteractor.live(databaseFiles: DatabaseFiles(fileManager: mockedFileManager))
do {
let areFilesPresent = try dfInteractor.areDbFilesPresentFor("")
XCTAssertFalse(areFilesPresent, "DatabaseFiles: `testDatabaseFilesNotPresent` is expected to be false but it's \(areFilesPresent)")
} catch {
XCTFail("DatabaseFiles: `testDatabaseFilesPresent` expected to fail but passed with no error.")
}
}
func testDatabaseFilesPresentFailure() throws {
let mockedFileManager = WrappedFileManager(
url: { _, _, _, _ in throw "some error" },
fileExists: { _ in return true },
removeItem: { _ in }
)
let dfInteractor = DatabaseFilesInteractor.live(databaseFiles: DatabaseFiles(fileManager: mockedFileManager))
do {
_ = try dfInteractor.areDbFilesPresentFor("")
XCTFail("DatabaseFiles: `testDatabaseFilesPresentFailure` expected to fail but passed with no error.")
} catch {
guard let error = error as? DatabaseFiles.DatabaseFilesError else {
XCTFail("DatabaseFiles: the error is expected to be DatabaseFilesError but it's \(error).")
return
}
XCTAssertEqual(
error.debugValue,
DatabaseFiles.DatabaseFilesError.filesPresentCheck.debugValue,
"DatabaseFiles: error must be .filesPresentCheck but it's \(error)."
)
}
}
func testNukeFiles_RemoveFileFailure() throws {
let mockedFileManager = WrappedFileManager(
url: { _, _, _, _ in URL(fileURLWithPath: "") },
fileExists: { _ in return true },
removeItem: { _ in throw "some error" }
)
let dfInteractor = DatabaseFilesInteractor.live(databaseFiles: DatabaseFiles(fileManager: mockedFileManager))
do {
_ = try dfInteractor.nukeDbFilesFor("")
XCTFail("DatabaseFiles: `testNukeFiles_RemoveFileFailure` expected to fail but passed with no error.")
} catch {
guard let error = error as? DatabaseFiles.DatabaseFilesError else {
XCTFail("DatabaseFiles: the error is expected to be DatabaseFilesError but it's \(error).")
return
}
XCTAssertEqual(
error.debugValue,
DatabaseFiles.DatabaseFilesError.nukeFiles.debugValue,
"DatabaseFiles: error must be .nukeFiles but it's \(error)."
)
}
}
func testNukeFiles_URLFailure() throws {
let mockedFileManager = WrappedFileManager(
url: { _, _, _, _ in throw "some error" },
fileExists: { _ in return true },
removeItem: { _ in }
)
let dfInteractor = DatabaseFilesInteractor.live(databaseFiles: DatabaseFiles(fileManager: mockedFileManager))
do {
_ = try dfInteractor.nukeDbFilesFor("")
XCTFail("DatabaseFiles: `testNukeFiles_URLFailure` expected to fail but passed with no error.")
} catch {
guard let error = error as? DatabaseFiles.DatabaseFilesError else {
XCTFail("DatabaseFiles: the error is expected to be DatabaseFilesError but it's \(error).")
return
}
XCTAssertEqual(
error.debugValue,
DatabaseFiles.DatabaseFilesError.nukeFiles.debugValue,
"DatabaseFiles: error must be .nukeFiles but it's \(error)."
)
}
}
}