Skip to content

Commit b4facf9

Browse files
authored
[trello.com/c/Htm0a2Qp]: DashWalletService tests (#671)
* [trello.com/c/Htm0a2Qp]: DashWalletService tests * [trello.com/c/Htm0a2Qp]: use files instead of multiline string for mocking json responses
1 parent 6eeb371 commit b4facf9

20 files changed

+761
-32
lines changed

Adamant.xcodeproj/project.pbxproj

Lines changed: 60 additions & 0 deletions
Large diffs are not rendered by default.

Adamant/App/DI/AppAssembly.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,12 @@ struct AppAssembly: MainThreadAssembly {
202202
))
203203
}.inObjectScope(.container)
204204

205+
206+
// MARK: DashLastTransactionStorage
207+
container.register(DashLastTransactionStorageProtocol.self) { r in
208+
DashLastTransactionStorage(securedStore: r.resolve(SecuredStore.self)!)
209+
}.inObjectScope(.container)
210+
205211
// MARK: LskNodeApiService
206212
container.register(KlyNodeApiService.self) { r in
207213
KlyNodeApiService(api: .init(

Adamant/Modules/Wallets/Dash/DashApiService.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ final class DashApiCore: BlockchainHealthCheckableService, Sendable {
6464
}
6565
}
6666

67-
final class DashApiService: ApiServiceProtocol {
67+
final class DashApiService: DashApiServiceProtocol {
6868
let api: BlockchainHealthCheckWrapper<DashApiCore>
6969

7070
@MainActor
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
//
2+
// DashApiServiceProtocol.swift
3+
// Adamant
4+
//
5+
// Created by Christian Benua on 23.01.2025.
6+
// Copyright © 2025 Adamant. All rights reserved.
7+
//
8+
9+
import CommonKit
10+
import Foundation
11+
12+
protocol DashApiServiceProtocol: ApiServiceProtocol {
13+
func request<Output>(
14+
waitsForConnectivity: Bool,
15+
_ request: @Sendable @escaping (APICoreProtocol, NodeOrigin) async -> ApiServiceResult<Output>
16+
) async -> WalletServiceResult<Output>
17+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
//
2+
// DashLastTransactionStorage.swift
3+
// Adamant
4+
//
5+
// Created by Christian Benua on 22.01.2025.
6+
// Copyright © 2025 Adamant. All rights reserved.
7+
//
8+
9+
import CommonKit
10+
import Foundation
11+
12+
final class DashLastTransactionStorage: DashLastTransactionStorageProtocol {
13+
14+
private let securedStore: SecuredStore
15+
16+
init(securedStore: SecuredStore) {
17+
self.securedStore = securedStore
18+
}
19+
20+
func getLastTransactionId() -> String? {
21+
guard
22+
let hash: String = self.securedStore.get(Constants.transactionIdKey),
23+
let timestampString: String = self.securedStore.get(Constants.transactionTimeKey),
24+
let timestamp = Double(string: timestampString)
25+
else { return nil }
26+
27+
let date = Date(timeIntervalSince1970: TimeInterval(timestamp))
28+
let timeAgo = -1 * date.timeIntervalSinceNow
29+
30+
if timeAgo > Constants.tenMinutes { // 10m waiting for transaction complete
31+
self.securedStore.remove(Constants.transactionTimeKey)
32+
self.securedStore.remove(Constants.transactionIdKey)
33+
return nil
34+
} else {
35+
return hash
36+
}
37+
}
38+
39+
func setLastTransactionId(_ id: String?) {
40+
if let value = id {
41+
let timestamp = Date().timeIntervalSince1970
42+
self.securedStore.set("\(timestamp)", for: Constants.transactionTimeKey)
43+
self.securedStore.set(value, for: Constants.transactionIdKey)
44+
} else {
45+
self.securedStore.remove(Constants.transactionTimeKey)
46+
self.securedStore.remove(Constants.transactionIdKey)
47+
}
48+
}
49+
}
50+
51+
private enum Constants {
52+
static let transactionTimeKey = "lastDashTransactionTime"
53+
static let transactionIdKey = "lastDashTransactionId"
54+
55+
static let tenMinutes: TimeInterval = 10 * 60
56+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
//
2+
// DashLastTransactionStorageProtocol.swift
3+
// Adamant
4+
//
5+
// Created by Christian Benua on 22.01.2025.
6+
// Copyright © 2025 Adamant. All rights reserved.
7+
//
8+
9+
protocol DashLastTransactionStorageProtocol: AnyObject {
10+
func getLastTransactionId() -> String?
11+
func setLastTransactionId(_ id: String?)
12+
}

Adamant/Modules/Wallets/Dash/DashWalletService+Send.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,12 +71,13 @@ extension DashWalletService: WalletServiceTwoStepSend {
7171
}
7272

7373
// MARK: 4. Create local transaction
74-
let transaction = BitcoinKit.Transaction.createNewTransaction(
74+
let transaction = transactionFactory.createTransaction(
7575
toAddress: toAddress,
7676
amount: rawAmount,
7777
fee: fee,
7878
changeAddress: wallet.addressEntity,
7979
utxos: utxos,
80+
lockTime: 0,
8081
keys: [key]
8182
)
8283
return transaction

Adamant/Modules/Wallets/Dash/DashWalletService.swift

Lines changed: 16 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -65,9 +65,10 @@ final class DashWalletService: WalletCoreProtocol, @unchecked Sendable {
6565

6666
// MARK: - Dependencies
6767
var apiService: AdamantApiServiceProtocol!
68-
var dashApiService: DashApiService!
68+
var dashApiService: DashApiServiceProtocol!
6969
var accountService: AccountService!
70-
var securedStore: SecuredStore!
70+
var lastTransactionStorage: DashLastTransactionStorageProtocol!
71+
var transactionFactory: BitcoinKitTransactionFactoryProtocol!
7172
var dialogService: DialogService!
7273
var addressConverter: AddressConverter!
7374
var coreDataStack: CoreDataStack!
@@ -90,32 +91,10 @@ final class DashWalletService: WalletCoreProtocol, @unchecked Sendable {
9091

9192
var lastTransactionId: String? {
9293
get {
93-
guard
94-
let hash: String = self.securedStore.get("lastDashTransactionId"),
95-
let timestampString: String = self.securedStore.get("lastDashTransactionTime"),
96-
let timestamp = Double(string: timestampString)
97-
else { return nil }
98-
99-
let date = Date(timeIntervalSince1970: TimeInterval(timestamp))
100-
let timeAgo = -1 * date.timeIntervalSinceNow
101-
102-
if timeAgo > 10 * 60 { // 10m waiting for transaction complete
103-
self.securedStore.remove("lastDashTransactionTime")
104-
self.securedStore.remove("lastDashTransactionId")
105-
return nil
106-
} else {
107-
return hash
108-
}
94+
lastTransactionStorage.getLastTransactionId()
10995
}
11096
set {
111-
if let value = newValue {
112-
let timestamp = Date().timeIntervalSince1970
113-
self.securedStore.set("\(timestamp)", for: "lastDashTransactionTime")
114-
self.securedStore.set(value, for: "lastDashTransactionId")
115-
} else {
116-
self.securedStore.remove("lastDashTransactionTime")
117-
self.securedStore.remove("lastDashTransactionId")
118-
}
97+
lastTransactionStorage.setLastTransactionId(newValue)
11998
}
12099
}
121100

@@ -420,7 +399,8 @@ extension DashWalletService: SwinjectDependentService {
420399
func injectDependencies(from container: Container) {
421400
accountService = container.resolve(AccountService.self)
422401
apiService = container.resolve(AdamantApiServiceProtocol.self)
423-
securedStore = container.resolve(SecuredStore.self)
402+
lastTransactionStorage = container.resolve(DashLastTransactionStorageProtocol.self)
403+
transactionFactory = container.resolve(BitcoinKitTransactionFactoryProtocol.self)
424404
dialogService = container.resolve(DialogService.self)
425405
addressConverter = container.resolve(AddressConverterFactory.self)?
426406
.make(network: network)
@@ -618,6 +598,15 @@ extension DashWalletService {
618598
}
619599
}
620600

601+
#if DEBUG
602+
extension DashWalletService {
603+
@available(*, deprecated, message: "For testing purposes only")
604+
func setWalletForTests(_ wallet: DashWallet?) {
605+
self.dashWallet = wallet
606+
}
607+
}
608+
#endif
609+
621610
// MARK: - PrivateKey generator
622611
extension DashWalletService: PrivateKeyGenerator {
623612
var rowTitle: String {
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
//
2+
// Data+Extensions.swift
3+
// Adamant
4+
//
5+
// Created by Christian Benua on 03.02.2025.
6+
// Copyright © 2025 Adamant. All rights reserved.
7+
//
8+
9+
import Foundation
10+
11+
private final class BundleTag {}
12+
13+
extension Data {
14+
static func readResource(name: String, withExtension extensionName: String?) -> Data? {
15+
Bundle(for: BundleTag.self)
16+
.url(forResource: name, withExtension: extensionName)
17+
.flatMap { try? Data(contentsOf: $0) }
18+
}
19+
}
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
//
2+
// DashWalletServiceIntegrationTests.swift
3+
// Adamant
4+
//
5+
// Created by Christian Benua on 24.01.2025.
6+
// Copyright © 2025 Adamant. All rights reserved.
7+
//
8+
9+
import XCTest
10+
@testable import Adamant
11+
import Swinject
12+
import BitcoinKit
13+
import CommonKit
14+
15+
final class DashWalletServiceIntegrationTests: XCTestCase {
16+
17+
private var apiCoreMock: APICoreProtocolMock!
18+
private var lastTransactionStorageMock: DashLastTransactionStorageProtocolMock!
19+
private var dashApiServiceProtocolMock: DashApiServiceProtocolMock!
20+
private var sut: DashWalletService!
21+
22+
override func setUp() {
23+
super.setUp()
24+
apiCoreMock = APICoreProtocolMock()
25+
dashApiServiceProtocolMock = DashApiServiceProtocolMock()
26+
dashApiServiceProtocolMock.api = DashApiCore(apiCore: apiCoreMock)
27+
lastTransactionStorageMock = DashLastTransactionStorageProtocolMock()
28+
29+
sut = DashWalletService()
30+
sut.lastTransactionStorage = lastTransactionStorageMock
31+
sut.addressConverter = AddressConverterFactory().make(network: DashMainnet())
32+
sut.dashApiService = dashApiServiceProtocolMock
33+
sut.transactionFactory = BitcoinKitTransactionFactory()
34+
}
35+
36+
override func tearDown() {
37+
apiCoreMock = nil
38+
dashApiServiceProtocolMock = nil
39+
lastTransactionStorageMock = nil
40+
sut = nil
41+
super.tearDown()
42+
}
43+
44+
func test_createAndSendTransaction_createsValidTxIdAndHash() async throws {
45+
// given
46+
sut.setWalletForTests(try makeWallet())
47+
let data = Constants.unspentTranscationsData
48+
await apiCoreMock.isolated { mock in
49+
mock.stubbedSendRequestBasicGenericResult = APIResponseModel(result: .success(data), data: data, code: 200)
50+
}
51+
52+
// when 1
53+
let result = await Result(catchingAsync: {
54+
try await self.sut.createTransaction(
55+
recipient: Constants.recipient,
56+
amount: 0.01,
57+
fee: 0.0000001,
58+
comment: nil
59+
)
60+
})
61+
62+
// then 1
63+
let transaction = try XCTUnwrap(result.value)
64+
XCTAssertEqual(transaction.serialized().hex, Constants.expectedTransactionHex)
65+
XCTAssertEqual(transaction.txID, Constants.expectedTransactionID)
66+
67+
// given 2
68+
let txData = Constants.sendTransactionResponseData
69+
await apiCoreMock.isolated { mock in
70+
mock.stubbedSendRequestBasicGenericResult = APIResponseModel(result: .success(txData), data: txData, code: 200)
71+
}
72+
73+
// when 2
74+
let result2 = await Result {
75+
try await self.sut.sendTransaction(transaction)
76+
}
77+
// then 2
78+
XCTAssertNil(result2.error)
79+
await apiCoreMock.isolated { mock in
80+
XCTAssertEqual(mock.invokedSendRequestBasicGenericCount, 2)
81+
}
82+
}
83+
}
84+
85+
// MARK: Private
86+
87+
private extension DashWalletServiceIntegrationTests {
88+
func makeWallet() throws -> DashWallet {
89+
let privateKeyData = Constants.passphrase
90+
.data(using: .utf8)!
91+
.sha256()
92+
let privateKey = PrivateKey(
93+
data: privateKeyData,
94+
network: DashMainnet(),
95+
isPublicKeyCompressed: true
96+
)
97+
return try DashWallet(
98+
unicId: "DASHDASH",
99+
privateKey: privateKey,
100+
addressConverter: AddressConverterFactory().make(network: DashMainnet())
101+
)
102+
}
103+
}
104+
105+
private enum Constants {
106+
107+
static let passphrase = "village lunch say patrol glow first hurt shiver name method dolphin dead"
108+
109+
static let expectedTransactionID = "d4cf3fde45d0e7ba855db9621bdc6da091856011d86f199bebd1937f7b63020a"
110+
111+
static let recipient = "Xp6kFbogHMD4QRBDLQdqRp5zUgzmfj1KPn"
112+
113+
static let expectedTransactionHex = "0100000001721f0c2437124acb8a20fea5af60908057b086118b240119adcb39c890b4c3a2010000006a473044022002e19bc62748ca3f34a6e5f1aeab31bb3dc43997792d9551e9b8c51a094abbae02200e3c9bde7c60326ef6984045a81304a9915e8fbce96de01813fe89886ef26453012102cd3dcbdfc1b77e54b3a8f273310806ab56b0c2463c2f1677c7694a89a713e0d0ffffffff0240420f00000000001976a914931ef5cbdad28723ba9596de5da1145ae969a71888acb695a905000000001976a91457f6f900ac7a7e3ccab712326cd7b85638fc15a888ac00000000"
114+
115+
static let unspentTranscationsData = Data.readResource(
116+
name: "dash_unspent_transaction_intergration_test",
117+
withExtension: "json"
118+
)!
119+
120+
static let sendTransactionResponseData = Data.readResource(
121+
name: "dash_send_transation_response",
122+
withExtension: "json"
123+
)!
124+
}

0 commit comments

Comments
 (0)