diff --git a/Tests/OfflineTests/CompactBlockProcessorActions/DownloadActionTests.swift b/Tests/OfflineTests/CompactBlockProcessorActions/DownloadActionTests.swift new file mode 100644 index 00000000..7c7689da --- /dev/null +++ b/Tests/OfflineTests/CompactBlockProcessorActions/DownloadActionTests.swift @@ -0,0 +1,178 @@ +// +// DownloadActionTests.swift +// +// +// Created by Lukáš Korba on 21.05.2023. +// + +import XCTest +@testable import TestUtils +@testable import ZcashLightClientKit + +final class DownloadActionTests: ZcashTestCase { + var underlyingDownloadAndScanRange: CompactBlockRange? + + func testDownloadAction_NextAction() async throws { + let blockDownloaderMock = BlockDownloaderMock() + let transactionRepositoryMock = TransactionRepositoryMock() + + transactionRepositoryMock.lastScannedHeightReturnValue = 1 + blockDownloaderMock.setSyncRangeBatchSizeClosure = { _, _ in } + blockDownloaderMock.setDownloadLimitClosure = { _ in } + blockDownloaderMock.startDownloadMaxBlockBufferSizeClosure = { _ in } + blockDownloaderMock.waitUntilRequestedBlocksAreDownloadedInClosure = { _ in } + + let downloadAction = setupAction( + blockDownloaderMock, + transactionRepositoryMock + ) + + underlyingDownloadAndScanRange = CompactBlockRange(uncheckedBounds: (1000, 2000)) + + let syncContext = await setupActionContext() + + do { + let nextContext = try await downloadAction.run(with: syncContext) { _ in } + + XCTAssertTrue(transactionRepositoryMock.lastScannedHeightCalled, "transactionRepository.lastScannedHeight() is expected to be called.") + XCTAssertTrue(blockDownloaderMock.setSyncRangeBatchSizeCalled, "downloader.setSyncRange() is expected to be called.") + XCTAssertTrue(blockDownloaderMock.setDownloadLimitCalled, "downloader.setDownloadLimit() is expected to be called.") + XCTAssertTrue(blockDownloaderMock.startDownloadMaxBlockBufferSizeCalled, "downloader.startDownload() is expected to be called.") + XCTAssertTrue( + blockDownloaderMock.waitUntilRequestedBlocksAreDownloadedInCalled, + "downloader.waitUntilRequestedBlocksAreDownloaded() is expected to be called." + ) + + let nextState = await nextContext.state + XCTAssertTrue( + nextState == .validate, + "nextContext after .download is expected to be .validate but received \(nextState)" + ) + } catch { + XCTFail("testDownloadAction_NextAction is not expected to fail. \(error)") + } + } + + func testDownloadAction_NoDownloadAndScanRange() async throws { + let blockDownloaderMock = BlockDownloaderMock() + let transactionRepositoryMock = TransactionRepositoryMock() + + let downloadAction = setupAction( + blockDownloaderMock, + transactionRepositoryMock + ) + + let syncContext = await setupActionContext() + + do { + let nextContext = try await downloadAction.run(with: syncContext) { _ in } + + XCTAssertFalse(transactionRepositoryMock.lastScannedHeightCalled, "transactionRepository.lastScannedHeight() is not expected to be called.") + XCTAssertFalse(blockDownloaderMock.setSyncRangeBatchSizeCalled, "downloader.setSyncRange() is not expected to be called.") + XCTAssertFalse(blockDownloaderMock.setDownloadLimitCalled, "downloader.setDownloadLimit() is not expected to be called.") + XCTAssertFalse(blockDownloaderMock.startDownloadMaxBlockBufferSizeCalled, "downloader.startDownload() is not expected to be called.") + XCTAssertFalse( + blockDownloaderMock.waitUntilRequestedBlocksAreDownloadedInCalled, + "downloader.waitUntilRequestedBlocksAreDownloaded() is not expected to be called." + ) + + let nextState = await nextContext.state + XCTAssertTrue( + nextState == .validate, + "nextContext after .download is expected to be .validate but received \(nextState)" + ) + } catch { + XCTFail("testDownloadAction_NoDownloadAndScanRange is not expected to fail. \(error)") + } + } + + func testDownloadAction_NothingMoreToDownload() async throws { + let blockDownloaderMock = BlockDownloaderMock() + let transactionRepositoryMock = TransactionRepositoryMock() + + transactionRepositoryMock.lastScannedHeightReturnValue = 2001 + + let downloadAction = setupAction( + blockDownloaderMock, + transactionRepositoryMock + ) + + underlyingDownloadAndScanRange = CompactBlockRange(uncheckedBounds: (1000, 2000)) + + let syncContext = await setupActionContext() + + do { + let nextContext = try await downloadAction.run(with: syncContext) { _ in } + + XCTAssertTrue(transactionRepositoryMock.lastScannedHeightCalled, "transactionRepository.lastScannedHeight() is expected to be called.") + XCTAssertFalse(blockDownloaderMock.setSyncRangeBatchSizeCalled, "downloader.setSyncRange() is not expected to be called.") + XCTAssertFalse(blockDownloaderMock.setDownloadLimitCalled, "downloader.setDownloadLimit() is not expected to be called.") + XCTAssertFalse(blockDownloaderMock.startDownloadMaxBlockBufferSizeCalled, "downloader.startDownload() is not expected to be called.") + XCTAssertFalse( + blockDownloaderMock.waitUntilRequestedBlocksAreDownloadedInCalled, + "downloader.waitUntilRequestedBlocksAreDownloaded() is not expected to be called." + ) + + let nextState = await nextContext.state + XCTAssertTrue( + nextState == .validate, + "nextContext after .download is expected to be .validate but received \(nextState)" + ) + } catch { + XCTFail("testDownloadAction_NoDownloadAndScanRange is not expected to fail. \(error)") + } + } + + func testDownloadAction_DownloadStops() async throws { + let blockDownloaderMock = BlockDownloaderMock() + + blockDownloaderMock.stopDownloadClosure = { } + + let downloadAction = setupAction( + blockDownloaderMock + ) + + await downloadAction.stop() + + XCTAssertTrue(blockDownloaderMock.stopDownloadCalled, "downloader.stopDownload() is expected to be called.") + } + + private func setupActionContext() async -> ActionContext { + let syncContext: ActionContext = .init(state: .download) + + let syncRanges = SyncRanges( + latestBlockHeight: 0, + downloadedButUnscannedRange: nil, + downloadAndScanRange: underlyingDownloadAndScanRange, + enhanceRange: nil, + fetchUTXORange: nil, + latestScannedHeight: nil, + latestDownloadedBlockHeight: nil + ) + + await syncContext.update(syncRanges: syncRanges) + + return syncContext + } + + private func setupAction( + _ blockDownloaderMock: BlockDownloaderMock = BlockDownloaderMock(), + _ transactionRepositoryMock: TransactionRepositoryMock = TransactionRepositoryMock(), + _ loggerMock: LoggerMock = LoggerMock() + ) -> DownloadAction { + mockContainer.mock(type: BlockDownloader.self, isSingleton: true) { _ in blockDownloaderMock } + mockContainer.mock(type: TransactionRepository.self, isSingleton: true) { _ in transactionRepositoryMock } + mockContainer.mock(type: Logger.self, isSingleton: true) { _ in loggerMock } + + loggerMock.debugFileFunctionLineClosure = { _, _, _, _ in } + + let config: CompactBlockProcessor.Configuration = .standard( + for: ZcashNetworkBuilder.network(for: .testnet), walletBirthday: 0 + ) + + return DownloadAction( + container: mockContainer, + config: config + ) + } +} diff --git a/Tests/TestUtils/Sourcery/AutoMockable.swift b/Tests/TestUtils/Sourcery/AutoMockable.swift index e9558762..25f929df 100644 --- a/Tests/TestUtils/Sourcery/AutoMockable.swift +++ b/Tests/TestUtils/Sourcery/AutoMockable.swift @@ -12,6 +12,7 @@ @testable import ZcashLightClientKit +extension BlockDownloader { } extension BlockEnhancer { } extension BlockScanner { } extension BlockValidator { } diff --git a/Tests/TestUtils/Sourcery/GeneratedMocks/AutoMockable.generated.swift b/Tests/TestUtils/Sourcery/GeneratedMocks/AutoMockable.generated.swift index adeb9316..83476f8a 100644 --- a/Tests/TestUtils/Sourcery/GeneratedMocks/AutoMockable.generated.swift +++ b/Tests/TestUtils/Sourcery/GeneratedMocks/AutoMockable.generated.swift @@ -7,6 +7,95 @@ import Foundation // MARK: - AutoMockable protocols +class BlockDownloaderMock: BlockDownloader { + + + init( + ) { + } + + // MARK: - setDownloadLimit + + var setDownloadLimitCallsCount = 0 + var setDownloadLimitCalled: Bool { + return setDownloadLimitCallsCount > 0 + } + var setDownloadLimitReceivedLimit: BlockHeight? + var setDownloadLimitClosure: ((BlockHeight) async -> Void)? + + func setDownloadLimit(_ limit: BlockHeight) async { + setDownloadLimitCallsCount += 1 + setDownloadLimitReceivedLimit = limit + await setDownloadLimitClosure!(limit) + } + + // MARK: - setSyncRange + + var setSyncRangeBatchSizeThrowableError: Error? + var setSyncRangeBatchSizeCallsCount = 0 + var setSyncRangeBatchSizeCalled: Bool { + return setSyncRangeBatchSizeCallsCount > 0 + } + var setSyncRangeBatchSizeReceivedArguments: (range: CompactBlockRange, batchSize: Int)? + var setSyncRangeBatchSizeClosure: ((CompactBlockRange, Int) async throws -> Void)? + + func setSyncRange(_ range: CompactBlockRange, batchSize: Int) async throws { + if let error = setSyncRangeBatchSizeThrowableError { + throw error + } + setSyncRangeBatchSizeCallsCount += 1 + setSyncRangeBatchSizeReceivedArguments = (range: range, batchSize: batchSize) + try await setSyncRangeBatchSizeClosure!(range, batchSize) + } + + // MARK: - startDownload + + var startDownloadMaxBlockBufferSizeCallsCount = 0 + var startDownloadMaxBlockBufferSizeCalled: Bool { + return startDownloadMaxBlockBufferSizeCallsCount > 0 + } + var startDownloadMaxBlockBufferSizeReceivedMaxBlockBufferSize: Int? + var startDownloadMaxBlockBufferSizeClosure: ((Int) async -> Void)? + + func startDownload(maxBlockBufferSize: Int) async { + startDownloadMaxBlockBufferSizeCallsCount += 1 + startDownloadMaxBlockBufferSizeReceivedMaxBlockBufferSize = maxBlockBufferSize + await startDownloadMaxBlockBufferSizeClosure!(maxBlockBufferSize) + } + + // MARK: - stopDownload + + var stopDownloadCallsCount = 0 + var stopDownloadCalled: Bool { + return stopDownloadCallsCount > 0 + } + var stopDownloadClosure: (() async -> Void)? + + func stopDownload() async { + stopDownloadCallsCount += 1 + await stopDownloadClosure!() + } + + // MARK: - waitUntilRequestedBlocksAreDownloaded + + var waitUntilRequestedBlocksAreDownloadedInThrowableError: Error? + var waitUntilRequestedBlocksAreDownloadedInCallsCount = 0 + var waitUntilRequestedBlocksAreDownloadedInCalled: Bool { + return waitUntilRequestedBlocksAreDownloadedInCallsCount > 0 + } + var waitUntilRequestedBlocksAreDownloadedInReceivedRange: CompactBlockRange? + var waitUntilRequestedBlocksAreDownloadedInClosure: ((CompactBlockRange) async throws -> Void)? + + func waitUntilRequestedBlocksAreDownloaded(in range: CompactBlockRange) async throws { + if let error = waitUntilRequestedBlocksAreDownloadedInThrowableError { + throw error + } + waitUntilRequestedBlocksAreDownloadedInCallsCount += 1 + waitUntilRequestedBlocksAreDownloadedInReceivedRange = range + try await waitUntilRequestedBlocksAreDownloadedInClosure!(range) + } + +} class BlockEnhancerMock: BlockEnhancer {