ZcashLightClientKit/Tests/OfflineTests/SynchronizerOfflineTests.swift

438 lines
15 KiB
Swift

//
// SynchronizerOfflineTests.swift
//
//
// Created by Michal Fousek on 23.03.2023.
//
import Combine
import Foundation
@testable import TestUtils
import XCTest
@testable import ZcashLightClientKit
class SynchronizerOfflineTests: ZcashTestCase {
let data = TestsData(networkType: .testnet)
var network: ZcashNetwork!
var cancellables: [AnyCancellable] = []
override func setUp() async throws {
try await super.setUp()
network = ZcashNetworkBuilder.network(for: .testnet)
cancellables = []
}
override func tearDown() async throws {
try await super.tearDown()
network = nil
cancellables = []
}
func testCallPrepareWithAlreadyUsedAliasThrowsError() async throws {
let firstTestCoordinator = try await TestCoordinator(
alias: .custom("alias"),
container: mockContainer,
walletBirthday: 10,
network: network,
callPrepareInConstructor: false
)
let secondTestCoordinator = try await TestCoordinator(
alias: .custom("alias"),
container: mockContainer,
walletBirthday: 10,
network: network,
callPrepareInConstructor: false
)
do {
_ = try await firstTestCoordinator.prepare(seed: Environment.seedBytes)
} catch {
XCTFail("Unpected fail. Prepare should succeed. \(error)")
}
do {
_ = try await secondTestCoordinator.prepare(seed: Environment.seedBytes)
XCTFail("Prepare should fail.")
} catch { }
}
func testWhenSynchronizerIsDeallocatedAliasIsntUsedAnymore() async throws {
var testCoordinator: TestCoordinator! = try await TestCoordinator(
alias: .default,
container: mockContainer,
walletBirthday: 10,
network: network,
callPrepareInConstructor: false
)
do {
_ = try await testCoordinator.prepare(seed: Environment.seedBytes)
} catch {
XCTFail("Unpected fail. Prepare should succeed. \(error)")
}
testCoordinator = try await TestCoordinator(
alias: .default,
container: mockContainer,
walletBirthday: 10,
network: network,
callPrepareInConstructor: false
)
do {
_ = try await testCoordinator.prepare(seed: Environment.seedBytes)
} catch {
XCTFail("Unpected fail. Prepare should succeed. \(error)")
}
}
func testCallWipeWithAlreadyUsedAliasThrowsError() async throws {
let firstTestCoordinator = try await TestCoordinator(
alias: .default,
container: mockContainer,
walletBirthday: 10,
network: network,
callPrepareInConstructor: false
)
let secondTestCoordinator = try await TestCoordinator(
alias: .default,
container: mockContainer,
walletBirthday: 10,
network: network,
callPrepareInConstructor: false
)
let firstWipeExpectation = XCTestExpectation(description: "First wipe expectation")
firstTestCoordinator.synchronizer.wipe()
.sink(
receiveCompletion: { result in
switch result {
case .finished:
firstWipeExpectation.fulfill()
case let .failure(error):
XCTFail("Unexpected error when calling wipe \(error)")
}
},
receiveValue: { _ in }
)
.store(in: &cancellables)
await fulfillment(of: [firstWipeExpectation], timeout: 1)
let secondWipeExpectation = XCTestExpectation(description: "Second wipe expectation")
secondTestCoordinator.synchronizer.wipe()
.sink(
receiveCompletion: { result in
switch result {
case .finished:
XCTFail("Second wipe should fail with error.")
case let .failure(error):
if let error = error as? ZcashError, case .initializerAliasAlreadyInUse = error {
secondWipeExpectation.fulfill()
} else {
XCTFail("Wipe failed with unexpected error: \(error)")
}
}
},
receiveValue: { _ in }
)
.store(in: &cancellables)
await fulfillment(of: [secondWipeExpectation], timeout: 1)
}
func testPrepareCanBeCalledAfterWipeWithSameInstanceOfSDKSynchronizer() async throws {
let testCoordinator = try await TestCoordinator(
alias: .default,
container: mockContainer,
walletBirthday: 10,
network: network,
callPrepareInConstructor: false
)
let expectation = XCTestExpectation(description: "Wipe expectation")
testCoordinator.synchronizer.wipe()
.sink(
receiveCompletion: { result in
switch result {
case .finished:
expectation.fulfill()
case let .failure(error):
XCTFail("Unexpected error when calling wipe \(error)")
}
},
receiveValue: { _ in }
)
.store(in: &cancellables)
await fulfillment(of: [expectation], timeout: 1)
do {
_ = try await testCoordinator.prepare(seed: Environment.seedBytes)
} catch {
XCTFail("Prepare after wipe should succeed.")
}
}
func testSendToAddressCalledWithoutPrepareThrowsError() async throws {
let testCoordinator = try await TestCoordinator(
alias: .default,
container: mockContainer,
walletBirthday: 10,
network: network,
callPrepareInConstructor: false
)
do {
_ = try await testCoordinator.synchronizer.sendToAddress(
spendingKey: testCoordinator.spendingKey,
zatoshi: Zatoshi(1),
toAddress: .transparent(data.transparentAddress),
memo: nil
)
XCTFail("Send to address should fail.")
} catch {
if let error = error as? ZcashError, case .synchronizerNotPrepared = error {
} else {
XCTFail("Send to address failed with unexpected error: \(error)")
}
}
}
func testShieldFundsCalledWithoutPrepareThrowsError() async throws {
let testCoordinator = try await TestCoordinator(
alias: .default,
container: mockContainer,
walletBirthday: 10,
network: network,
callPrepareInConstructor: false
)
do {
_ = try await testCoordinator.synchronizer.shieldFunds(
spendingKey: testCoordinator.spendingKey,
memo: Memo(string: "memo"),
shieldingThreshold: Zatoshi(1)
)
XCTFail("Shield funds should fail.")
} catch {
if let error = error as? ZcashError, case .synchronizerNotPrepared = error {
} else {
XCTFail("Shield funds failed with unexpected error: \(error)")
}
}
}
func testRefreshUTXOCalledWithoutPrepareThrowsError() async throws {
let testCoordinator = try await TestCoordinator(
alias: .default,
container: mockContainer,
walletBirthday: 10,
network: network,
callPrepareInConstructor: false
)
do {
_ = try await testCoordinator.synchronizer.refreshUTXOs(address: data.transparentAddress, from: 1)
XCTFail("Shield funds should fail.")
} catch {
if let error = error as? ZcashError, case .synchronizerNotPrepared = error {
} else {
XCTFail("Shield funds failed with unexpected error: \(error)")
}
}
}
func testRewindCalledWithoutPrepareThrowsError() async throws {
let testCoordinator = try await TestCoordinator(
alias: .default,
container: mockContainer,
walletBirthday: 10,
network: network,
callPrepareInConstructor: false
)
let expectation = XCTestExpectation()
testCoordinator.synchronizer.rewind(.quick)
.sink(
receiveCompletion: { result in
switch result {
case .finished:
XCTFail("Rewind should fail with error.")
case let .failure(error):
if let error = error as? ZcashError, case .synchronizerNotPrepared = error {
expectation.fulfill()
} else {
XCTFail("Rewind failed with unexpected error: \(error)")
}
}
},
receiveValue: { _ in }
)
.store(in: &cancellables)
await fulfillment(of: [expectation], timeout: 1)
}
func testURLsParsingFailsInInitializerPrepareThenThrowsError() async throws {
let validFileURL = URL(fileURLWithPath: "/some/valid/path/to.file")
let validDirectoryURL = URL(fileURLWithPath: "/some/valid/path/to/directory")
let invalidPathURL = URL(string: "https://whatever")!
let initializer = Initializer(
cacheDbURL: nil,
fsBlockDbRoot: validDirectoryURL,
dataDbURL: invalidPathURL,
endpoint: LightWalletEndpointBuilder.default,
network: ZcashNetworkBuilder.network(for: .testnet),
spendParamsURL: validFileURL,
outputParamsURL: validFileURL,
saplingParamsSourceURL: .default,
alias: .default,
loggingPolicy: .default(.debug)
)
XCTAssertNotNil(initializer.urlsParsingError)
let synchronizer = SDKSynchronizer(initializer: initializer)
do {
let derivationTool = DerivationTool(networkType: network.networkType)
let spendingKey = try derivationTool.deriveUnifiedSpendingKey(
seed: Environment.seedBytes,
accountIndex: 0
)
let viewingKey = try derivationTool.deriveUnifiedFullViewingKey(from: spendingKey)
_ = try await synchronizer.prepare(with: Environment.seedBytes, viewingKeys: [viewingKey], walletBirthday: 123000)
XCTFail("Failure of prepare is expected.")
} catch {
if let error = error as? ZcashError, case let .initializerCantUpdateURLWithAlias(failedURL) = error {
XCTAssertEqual(failedURL, invalidPathURL)
} else {
XCTFail("Failed with unexpected error: \(error)")
}
}
}
func testURLsParsingFailsInInitializerWipeThenThrowsError() async throws {
let validFileURL = URL(fileURLWithPath: "/some/valid/path/to.file")
let validDirectoryURL = URL(fileURLWithPath: "/some/valid/path/to/directory")
let invalidPathURL = URL(string: "https://whatever")!
let initializer = Initializer(
cacheDbURL: nil,
fsBlockDbRoot: validDirectoryURL,
dataDbURL: invalidPathURL,
endpoint: LightWalletEndpointBuilder.default,
network: ZcashNetworkBuilder.network(for: .testnet),
spendParamsURL: validFileURL,
outputParamsURL: validFileURL,
saplingParamsSourceURL: .default,
alias: .default,
loggingPolicy: .default(.debug)
)
XCTAssertNotNil(initializer.urlsParsingError)
let synchronizer = SDKSynchronizer(initializer: initializer)
let expectation = XCTestExpectation()
synchronizer.wipe()
.sink(
receiveCompletion: { result in
switch result {
case .finished:
XCTFail("Failure of wipe is expected.")
case let .failure(error):
if let error = error as? ZcashError, case let .initializerCantUpdateURLWithAlias(failedURL) = error {
XCTAssertEqual(failedURL, invalidPathURL)
expectation.fulfill()
} else {
XCTFail("Failed with unexpected error: \(error)")
}
}
},
receiveValue: { _ in }
)
.store(in: &cancellables)
await fulfillment(of: [expectation], timeout: 1)
}
func testIsNewSessionOnUnpreparedToValidTransition() {
XCTAssertTrue(SessionTicker.live.isNewSyncSession(.unprepared, .syncing(.nullProgress)))
}
func testIsNotNewSessionOnUnpreparedToStateThatWontSync() {
XCTAssertFalse(SessionTicker.live.isNewSyncSession(.unprepared, .disconnected))
XCTAssertFalse(SessionTicker.live.isNewSyncSession(.unprepared, .unprepared))
}
func testIsNotNewSessionOnUnpreparedToInvalidOrUnexpectedTransitions() {
XCTAssertFalse(SessionTicker.live.isNewSyncSession(.unprepared, .synced))
XCTAssertFalse(SessionTicker.live.isNewSyncSession(.unprepared, .fetching))
XCTAssertFalse(SessionTicker.live.isNewSyncSession(.unprepared, .enhancing(.zero)))
}
func testIsNotNewSyncSessionOnSameSession() {
XCTAssertFalse(
SessionTicker.live.isNewSyncSession(
.syncing(
BlockProgress(startHeight: 1, targetHeight: 10, progressHeight: 3)
),
.syncing(
BlockProgress(startHeight: 1, targetHeight: 10, progressHeight: 4)
)
)
)
}
func testIsNewSyncSessionWhenStartingFromSynced() {
XCTAssertTrue(
SessionTicker.live.isNewSyncSession(
.synced,
.syncing(
BlockProgress(startHeight: 1, targetHeight: 10, progressHeight: 4)
)
)
)
}
func testIsNewSyncSessionWhenStartingFromDisconnected() {
XCTAssertTrue(
SessionTicker.live.isNewSyncSession(
.disconnected,
.syncing(
BlockProgress(startHeight: 1, targetHeight: 10, progressHeight: 4)
)
)
)
}
func testIsNewSyncSessionWhenStartingFromStopped() {
XCTAssertTrue(
SessionTicker.live.isNewSyncSession(
.stopped,
.syncing(
BlockProgress(startHeight: 1, targetHeight: 10, progressHeight: 4)
)
)
)
}
func testSyncStatusesDontDifferWhenOuterStatusIsTheSame() {
XCTAssertFalse(SyncStatus.disconnected.isDifferent(from: .disconnected))
XCTAssertFalse(SyncStatus.fetching.isDifferent(from: .fetching))
XCTAssertFalse(SyncStatus.stopped.isDifferent(from: .stopped))
XCTAssertFalse(SyncStatus.synced.isDifferent(from: .synced))
XCTAssertFalse(SyncStatus.syncing(.nullProgress).isDifferent(from: .syncing(.nullProgress)))
XCTAssertFalse(SyncStatus.unprepared.isDifferent(from: .unprepared))
}
}