psf-bch-api 1.2.0 → 7.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/.env-local +28 -0
  2. package/bin/server.js +61 -9
  3. package/package.json +6 -2
  4. package/src/adapters/fulcrum-api.js +124 -0
  5. package/src/adapters/full-node-rpc.js +2 -6
  6. package/src/adapters/index.js +4 -0
  7. package/src/adapters/slp-indexer-api.js +124 -0
  8. package/src/config/env/common.js +29 -25
  9. package/src/config/x402.js +7 -0
  10. package/src/controllers/rest-api/fulcrum/controller.js +563 -0
  11. package/src/controllers/rest-api/fulcrum/router.js +64 -0
  12. package/src/controllers/rest-api/full-node/blockchain/controller.js +4 -4
  13. package/src/controllers/rest-api/full-node/mining/controller.js +99 -0
  14. package/src/controllers/rest-api/full-node/mining/router.js +52 -0
  15. package/src/controllers/rest-api/full-node/rawtransactions/controller.js +333 -0
  16. package/src/controllers/rest-api/full-node/rawtransactions/router.js +58 -0
  17. package/src/controllers/rest-api/index.js +23 -3
  18. package/src/controllers/rest-api/price/controller.js +96 -0
  19. package/src/controllers/rest-api/price/router.js +52 -0
  20. package/src/controllers/rest-api/slp/controller.js +218 -0
  21. package/src/controllers/rest-api/slp/router.js +55 -0
  22. package/src/controllers/timer-controller.js +1 -1
  23. package/src/middleware/basic-auth.js +61 -0
  24. package/src/use-cases/fulcrum-use-cases.js +155 -0
  25. package/src/use-cases/full-node-mining-use-cases.js +28 -0
  26. package/src/use-cases/full-node-rawtransactions-use-cases.js +121 -0
  27. package/src/use-cases/index.js +10 -0
  28. package/src/use-cases/price-use-cases.js +83 -0
  29. package/src/use-cases/slp-use-cases.js +321 -0
  30. package/test/unit/controllers/blockchain-controller-unit.js +2 -3
  31. package/test/unit/controllers/fulcrum-controller-unit.js +481 -0
  32. package/test/unit/controllers/mining-controller-unit.js +139 -0
  33. package/test/unit/controllers/price-controller-unit.js +116 -0
  34. package/test/unit/controllers/rawtransactions-controller-unit.js +388 -0
  35. package/test/unit/controllers/rest-api-index-unit.js +67 -3
  36. package/test/unit/controllers/slp-controller-unit.js +312 -0
  37. package/test/unit/use-cases/fulcrum-use-cases-unit.js +297 -0
  38. package/test/unit/use-cases/full-node-mining-use-cases-unit.js +84 -0
  39. package/test/unit/use-cases/full-node-rawtransactions-use-cases-unit.js +267 -0
  40. package/test/unit/use-cases/price-use-cases-unit.js +103 -0
  41. package/test/unit/use-cases/slp-use-cases-unit.js +296 -0
  42. package/src/entities/event.js +0 -71
  43. package/test/integration/api/event-integration.js +0 -250
  44. package/test/integration/api/req-integration.js +0 -173
  45. package/test/integration/api/subscription-integration.js +0 -198
  46. package/test/integration/use-cases/manage-subscription-integration.js +0 -163
  47. package/test/integration/use-cases/publish-event-integration.js +0 -104
  48. package/test/integration/use-cases/query-events-integration.js +0 -95
  49. package/test/unit/entities/event-unit.js +0 -139
  50. /package/{index.js → psf-bch-api.js} +0 -0
  51. /package/src/controllers/rest-api/full-node/blockchain/{index.js → router.js} +0 -0
  52. /package/src/controllers/rest-api/full-node/control/{index.js → router.js} +0 -0
  53. /package/src/controllers/rest-api/full-node/dsproof/{index.js → router.js} +0 -0
@@ -0,0 +1,312 @@
1
+ /*
2
+ Unit tests for SlpRESTController.
3
+ */
4
+
5
+ import { assert } from 'chai'
6
+ import sinon from 'sinon'
7
+
8
+ import SlpRESTController from '../../../src/controllers/rest-api/slp/controller.js'
9
+ import {
10
+ createMockRequest,
11
+ createMockResponse
12
+ } from '../mocks/controller-mocks.js'
13
+
14
+ // Valid mainnet cash address for testing
15
+ const VALID_MAINNET_ADDRESS = 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf'
16
+
17
+ describe('#slp-controller.js', () => {
18
+ let sandbox
19
+ let mockUseCases
20
+ let mockAdapters
21
+ let uut
22
+
23
+ const createSlpUseCaseStubs = () => ({
24
+ getStatus: sandbox.stub().resolves({ status: 'ok' }),
25
+ getAddress: sandbox.stub().resolves({ balance: 1000 }),
26
+ getTxid: sandbox.stub().resolves({ txid: 'abc' }),
27
+ getTokenStats: sandbox.stub().resolves({ tokenData: {} }),
28
+ getTokenData: sandbox.stub().resolves({ genesisData: {}, immutableData: '', mutableData: '' })
29
+ })
30
+
31
+ beforeEach(() => {
32
+ sandbox = sinon.createSandbox()
33
+ mockAdapters = {}
34
+ mockUseCases = {
35
+ slp: createSlpUseCaseStubs()
36
+ }
37
+
38
+ uut = new SlpRESTController({
39
+ adapters: mockAdapters,
40
+ useCases: mockUseCases
41
+ })
42
+ })
43
+
44
+ afterEach(() => {
45
+ sandbox.restore()
46
+ })
47
+
48
+ describe('#constructor()', () => {
49
+ it('should require adapters', () => {
50
+ assert.throws(() => {
51
+ // eslint-disable-next-line no-new
52
+ new SlpRESTController({ useCases: mockUseCases })
53
+ }, /Adapters library required/)
54
+ })
55
+
56
+ it('should require slp use cases', () => {
57
+ assert.throws(() => {
58
+ // eslint-disable-next-line no-new
59
+ new SlpRESTController({ adapters: mockAdapters, useCases: {} })
60
+ }, /SLP use cases required/)
61
+ })
62
+ })
63
+
64
+ describe('#root()', () => {
65
+ it('should return service status', async () => {
66
+ const req = createMockRequest()
67
+ const res = createMockResponse()
68
+
69
+ await uut.root(req, res)
70
+
71
+ assert.equal(res.statusValue, 200)
72
+ assert.deepEqual(res.jsonData, { status: 'psf-slp-indexer' })
73
+ })
74
+ })
75
+
76
+ describe('#getStatus()', () => {
77
+ it('should return status on success', async () => {
78
+ const req = createMockRequest()
79
+ const res = createMockResponse()
80
+
81
+ await uut.getStatus(req, res)
82
+
83
+ assert.equal(res.statusValue, 200)
84
+ assert.deepEqual(res.jsonData, { status: 'ok' })
85
+ assert.isTrue(mockUseCases.slp.getStatus.calledOnce)
86
+ })
87
+
88
+ it('should handle errors via handleError', async () => {
89
+ const error = new Error('failure')
90
+ error.status = 503
91
+ mockUseCases.slp.getStatus.rejects(error)
92
+ const req = createMockRequest()
93
+ const res = createMockResponse()
94
+
95
+ await uut.getStatus(req, res)
96
+
97
+ assert.equal(res.statusValue, 503)
98
+ assert.deepEqual(res.jsonData, { error: 'failure' })
99
+ })
100
+ })
101
+
102
+ describe('#getAddress()', () => {
103
+ it('should return address balance on success', async () => {
104
+ const req = createMockRequest({
105
+ body: { address: VALID_MAINNET_ADDRESS }
106
+ })
107
+ const res = createMockResponse()
108
+
109
+ await uut.getAddress(req, res)
110
+
111
+ assert.equal(res.statusValue, 200)
112
+ assert.deepEqual(res.jsonData, { balance: 1000 })
113
+ assert.isTrue(mockUseCases.slp.getAddress.calledOnce)
114
+ })
115
+
116
+ it('should return error if address is empty', async () => {
117
+ const req = createMockRequest({
118
+ body: { address: '' }
119
+ })
120
+ const res = createMockResponse()
121
+
122
+ await uut.getAddress(req, res)
123
+
124
+ assert.equal(res.statusValue, 400)
125
+ assert.property(res.jsonData, 'error')
126
+ assert.include(res.jsonData.error, 'can not be empty')
127
+ })
128
+
129
+ it('should return error if address is missing', async () => {
130
+ const req = createMockRequest({
131
+ body: {}
132
+ })
133
+ const res = createMockResponse()
134
+
135
+ await uut.getAddress(req, res)
136
+
137
+ assert.equal(res.statusValue, 400)
138
+ assert.property(res.jsonData, 'error')
139
+ })
140
+
141
+ it('should handle errors via handleError', async () => {
142
+ const error = new Error('Invalid address')
143
+ error.status = 400
144
+ mockUseCases.slp.getAddress.rejects(error)
145
+ const req = createMockRequest({
146
+ body: { address: VALID_MAINNET_ADDRESS }
147
+ })
148
+ const res = createMockResponse()
149
+
150
+ await uut.getAddress(req, res)
151
+
152
+ assert.equal(res.statusValue, 400)
153
+ assert.deepEqual(res.jsonData, { error: 'Invalid address' })
154
+ })
155
+ })
156
+
157
+ describe('#getTxid()', () => {
158
+ it('should return transaction data on success', async () => {
159
+ const req = createMockRequest({
160
+ body: { txid: 'a'.repeat(64) }
161
+ })
162
+ const res = createMockResponse()
163
+
164
+ await uut.getTxid(req, res)
165
+
166
+ assert.equal(res.statusValue, 200)
167
+ assert.deepEqual(res.jsonData, { txid: 'abc' })
168
+ assert.isTrue(mockUseCases.slp.getTxid.calledOnce)
169
+ })
170
+
171
+ it('should return error if txid is empty', async () => {
172
+ const req = createMockRequest({
173
+ body: { txid: '' }
174
+ })
175
+ const res = createMockResponse()
176
+
177
+ await uut.getTxid(req, res)
178
+
179
+ assert.equal(res.statusValue, 400)
180
+ assert.property(res.jsonData, 'error')
181
+ assert.include(res.jsonData.error, 'can not be empty')
182
+ })
183
+
184
+ it('should return error if txid is not 64 characters', async () => {
185
+ const req = createMockRequest({
186
+ body: { txid: 'abc' }
187
+ })
188
+ const res = createMockResponse()
189
+
190
+ await uut.getTxid(req, res)
191
+
192
+ assert.equal(res.statusValue, 400)
193
+ assert.property(res.jsonData, 'error')
194
+ assert.include(res.jsonData.error, 'not a txid')
195
+ })
196
+
197
+ it('should handle errors via handleError', async () => {
198
+ const error = new Error('Transaction not found')
199
+ error.status = 404
200
+ mockUseCases.slp.getTxid.rejects(error)
201
+ const req = createMockRequest({
202
+ body: { txid: 'a'.repeat(64) }
203
+ })
204
+ const res = createMockResponse()
205
+
206
+ await uut.getTxid(req, res)
207
+
208
+ assert.equal(res.statusValue, 404)
209
+ assert.deepEqual(res.jsonData, { error: 'Transaction not found' })
210
+ })
211
+ })
212
+
213
+ describe('#getTokenStats()', () => {
214
+ it('should return token stats on success', async () => {
215
+ const req = createMockRequest({
216
+ body: { tokenId: 'a'.repeat(64) }
217
+ })
218
+ const res = createMockResponse()
219
+
220
+ await uut.getTokenStats(req, res)
221
+
222
+ assert.equal(res.statusValue, 200)
223
+ assert.deepEqual(res.jsonData, { tokenData: {} })
224
+ assert.isTrue(mockUseCases.slp.getTokenStats.calledOnce)
225
+ })
226
+
227
+ it('should pass withTxHistory flag', async () => {
228
+ const req = createMockRequest({
229
+ body: { tokenId: 'a'.repeat(64), withTxHistory: true }
230
+ })
231
+ const res = createMockResponse()
232
+
233
+ await uut.getTokenStats(req, res)
234
+
235
+ assert.isTrue(mockUseCases.slp.getTokenStats.calledWith({
236
+ tokenId: 'a'.repeat(64),
237
+ withTxHistory: true
238
+ }))
239
+ })
240
+
241
+ it('should return error if tokenId is empty', async () => {
242
+ const req = createMockRequest({
243
+ body: { tokenId: '' }
244
+ })
245
+ const res = createMockResponse()
246
+
247
+ await uut.getTokenStats(req, res)
248
+
249
+ assert.equal(res.statusValue, 400)
250
+ assert.property(res.jsonData, 'error')
251
+ })
252
+
253
+ it('should handle errors via handleError', async () => {
254
+ const error = new Error('Token not found')
255
+ error.status = 404
256
+ mockUseCases.slp.getTokenStats.rejects(error)
257
+ const req = createMockRequest({
258
+ body: { tokenId: 'a'.repeat(64) }
259
+ })
260
+ const res = createMockResponse()
261
+
262
+ await uut.getTokenStats(req, res)
263
+
264
+ assert.equal(res.statusValue, 404)
265
+ assert.deepEqual(res.jsonData, { error: 'Token not found' })
266
+ })
267
+ })
268
+
269
+ describe('#getTokenData()', () => {
270
+ it('should return token data on success', async () => {
271
+ const req = createMockRequest({
272
+ body: { tokenId: 'a'.repeat(64) }
273
+ })
274
+ const res = createMockResponse()
275
+
276
+ await uut.getTokenData(req, res)
277
+
278
+ assert.equal(res.statusValue, 200)
279
+ assert.property(res.jsonData, 'genesisData')
280
+ assert.property(res.jsonData, 'immutableData')
281
+ assert.property(res.jsonData, 'mutableData')
282
+ assert.isTrue(mockUseCases.slp.getTokenData.calledOnce)
283
+ })
284
+
285
+ it('should return error if tokenId is empty', async () => {
286
+ const req = createMockRequest({
287
+ body: { tokenId: '' }
288
+ })
289
+ const res = createMockResponse()
290
+
291
+ await uut.getTokenData(req, res)
292
+
293
+ assert.equal(res.statusValue, 400)
294
+ assert.property(res.jsonData, 'error')
295
+ })
296
+
297
+ it('should handle errors via handleError', async () => {
298
+ const error = new Error('Token data not found')
299
+ error.status = 404
300
+ mockUseCases.slp.getTokenData.rejects(error)
301
+ const req = createMockRequest({
302
+ body: { tokenId: 'a'.repeat(64) }
303
+ })
304
+ const res = createMockResponse()
305
+
306
+ await uut.getTokenData(req, res)
307
+
308
+ assert.equal(res.statusValue, 404)
309
+ assert.deepEqual(res.jsonData, { error: 'Token data not found' })
310
+ })
311
+ })
312
+ })
@@ -0,0 +1,297 @@
1
+ /*
2
+ Unit tests for FulcrumUseCases.
3
+ */
4
+
5
+ import { assert } from 'chai'
6
+ import sinon from 'sinon'
7
+ import BCHJS from '@psf/bch-js'
8
+
9
+ import FulcrumUseCases from '../../../src/use-cases/fulcrum-use-cases.js'
10
+
11
+ describe('#fulcrum-use-cases.js', () => {
12
+ let sandbox
13
+ let mockAdapters
14
+ let uut
15
+ let sortAllTxsStub
16
+
17
+ beforeEach(() => {
18
+ sandbox = sinon.createSandbox()
19
+ mockAdapters = {
20
+ fulcrum: {
21
+ get: sandbox.stub().resolves({}),
22
+ post: sandbox.stub().resolves({})
23
+ }
24
+ }
25
+
26
+ // Create a mock BCHJS instance with stubbed sortAllTxs method
27
+ const mockBchjs = new BCHJS()
28
+ if (!mockBchjs.Electrumx) {
29
+ mockBchjs.Electrumx = {}
30
+ }
31
+
32
+ // Create a stub that sorts transactions
33
+ sortAllTxsStub = sandbox.stub(mockBchjs.Electrumx, 'sortAllTxs')
34
+ sortAllTxsStub.callsFake(async (txs, order) => {
35
+ const sorted = [...txs].sort((a, b) => {
36
+ if (order === 'DESCENDING') {
37
+ return (b.height || 0) - (a.height || 0)
38
+ }
39
+ return (a.height || 0) - (b.height || 0)
40
+ })
41
+ return sorted
42
+ })
43
+
44
+ // Inject the mocked bchjs instance into the use cases
45
+ uut = new FulcrumUseCases({ adapters: mockAdapters, bchjs: mockBchjs })
46
+ })
47
+
48
+ afterEach(() => {
49
+ sandbox.restore()
50
+ })
51
+
52
+ describe('#constructor()', () => {
53
+ it('should require adapters', () => {
54
+ assert.throws(() => {
55
+ // eslint-disable-next-line no-new
56
+ new FulcrumUseCases()
57
+ }, /Adapters instance required/)
58
+ })
59
+
60
+ it('should require fulcrum adapter', () => {
61
+ assert.throws(() => {
62
+ // eslint-disable-next-line no-new
63
+ new FulcrumUseCases({ adapters: {} })
64
+ }, /Fulcrum adapter required/)
65
+ })
66
+ })
67
+
68
+ describe('#getBalance()', () => {
69
+ it('should call fulcrum adapter get method', async () => {
70
+ const address = 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf'
71
+ mockAdapters.fulcrum.get.resolves({ balance: 1000 })
72
+
73
+ const result = await uut.getBalance({ address })
74
+
75
+ assert.isTrue(mockAdapters.fulcrum.get.calledOnceWith(`electrumx/balance/${address}`))
76
+ assert.deepEqual(result, { balance: 1000 })
77
+ })
78
+ })
79
+
80
+ describe('#getBalances()', () => {
81
+ it('should call fulcrum adapter post method', async () => {
82
+ const addresses = ['bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf']
83
+ mockAdapters.fulcrum.post.resolves({ balances: [] })
84
+
85
+ const result = await uut.getBalances({ addresses })
86
+
87
+ assert.isTrue(
88
+ mockAdapters.fulcrum.post.calledOnceWith('electrumx/balance/', { addresses })
89
+ )
90
+ assert.deepEqual(result, { balances: [] })
91
+ })
92
+ })
93
+
94
+ describe('#getUtxos()', () => {
95
+ it('should call fulcrum adapter get method', async () => {
96
+ const address = 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf'
97
+ mockAdapters.fulcrum.get.resolves({ utxos: [] })
98
+
99
+ const result = await uut.getUtxos({ address })
100
+
101
+ assert.isTrue(mockAdapters.fulcrum.get.calledOnceWith(`electrumx/utxos/${address}`))
102
+ assert.deepEqual(result, { utxos: [] })
103
+ })
104
+ })
105
+
106
+ describe('#getUtxosBulk()', () => {
107
+ it('should call fulcrum adapter post method', async () => {
108
+ const addresses = ['bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf']
109
+ mockAdapters.fulcrum.post.resolves({ utxos: [] })
110
+
111
+ const result = await uut.getUtxosBulk({ addresses })
112
+
113
+ assert.isTrue(
114
+ mockAdapters.fulcrum.post.calledOnceWith('electrumx/utxos/', { addresses })
115
+ )
116
+ assert.deepEqual(result, { utxos: [] })
117
+ })
118
+ })
119
+
120
+ describe('#getTransactionDetails()', () => {
121
+ it('should call fulcrum adapter get method', async () => {
122
+ const txid = 'a'.repeat(64)
123
+ mockAdapters.fulcrum.get.resolves({ txid })
124
+
125
+ const result = await uut.getTransactionDetails({ txid })
126
+
127
+ assert.isTrue(mockAdapters.fulcrum.get.calledOnceWith(`electrumx/tx/data/${txid}`))
128
+ assert.deepEqual(result, { txid })
129
+ })
130
+ })
131
+
132
+ describe('#getTransactionDetailsBulk()', () => {
133
+ it('should call fulcrum adapter post method with verbose', async () => {
134
+ const txids = ['a'.repeat(64)]
135
+ const verbose = true
136
+ mockAdapters.fulcrum.post.resolves({ transactions: [] })
137
+
138
+ const result = await uut.getTransactionDetailsBulk({ txids, verbose })
139
+
140
+ assert.isTrue(
141
+ mockAdapters.fulcrum.post.calledOnceWith('electrumx/tx/data', { txids, verbose })
142
+ )
143
+ assert.deepEqual(result, { transactions: [] })
144
+ })
145
+ })
146
+
147
+ describe('#broadcastTransaction()', () => {
148
+ it('should call fulcrum adapter post method', async () => {
149
+ const txHex = '010203'
150
+ mockAdapters.fulcrum.post.resolves({ txid: 'abc' })
151
+
152
+ const result = await uut.broadcastTransaction({ txHex })
153
+
154
+ assert.isTrue(
155
+ mockAdapters.fulcrum.post.calledOnceWith('electrumx/tx/broadcast', { txHex })
156
+ )
157
+ assert.deepEqual(result, { txid: 'abc' })
158
+ })
159
+ })
160
+
161
+ describe('#getBlockHeaders()', () => {
162
+ it('should call fulcrum adapter get method with height and count', async () => {
163
+ const height = 100
164
+ const count = 2
165
+ mockAdapters.fulcrum.get.resolves({ headers: [] })
166
+
167
+ const result = await uut.getBlockHeaders({ height, count })
168
+
169
+ assert.isTrue(
170
+ mockAdapters.fulcrum.get.calledOnceWith(`electrumx/block/headers/${height}?count=${count}`)
171
+ )
172
+ assert.deepEqual(result, { headers: [] })
173
+ })
174
+ })
175
+
176
+ describe('#getBlockHeadersBulk()', () => {
177
+ it('should call fulcrum adapter post method', async () => {
178
+ const heights = [{ height: 100, count: 2 }]
179
+ mockAdapters.fulcrum.post.resolves({ headers: [] })
180
+
181
+ const result = await uut.getBlockHeadersBulk({ heights })
182
+
183
+ assert.isTrue(
184
+ mockAdapters.fulcrum.post.calledOnceWith('electrumx/block/headers', { heights })
185
+ )
186
+ assert.deepEqual(result, { headers: [] })
187
+ })
188
+ })
189
+
190
+ describe('#getTransactions()', () => {
191
+ it('should call fulcrum adapter and sort transactions when allTxs is false', async () => {
192
+ const address = 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf'
193
+ const allTxs = false
194
+ const mockTransactions = [
195
+ { tx_hash: 'aaa', height: 100 },
196
+ { tx_hash: 'bbb', height: 200 },
197
+ { tx_hash: 'ccc', height: 150 }
198
+ ]
199
+ mockAdapters.fulcrum.get.resolves({
200
+ transactions: mockTransactions
201
+ })
202
+
203
+ const result = await uut.getTransactions({ address, allTxs })
204
+
205
+ assert.isTrue(mockAdapters.fulcrum.get.calledOnceWith(`electrumx/transactions/${address}`))
206
+ assert.property(result, 'transactions')
207
+ // Transactions should be sorted and limited to 100
208
+ if (result.transactions && result.transactions.length > 100) {
209
+ assert.isAtMost(result.transactions.length, 100)
210
+ }
211
+ })
212
+
213
+ it('should return all transactions when allTxs is true', async () => {
214
+ const address = 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf'
215
+ const allTxs = true
216
+ const mockTransactions = Array(150).fill({ tx_hash: 'aaa', height: 100 })
217
+ mockAdapters.fulcrum.get.resolves({
218
+ transactions: mockTransactions
219
+ })
220
+
221
+ const result = await uut.getTransactions({ address, allTxs })
222
+
223
+ assert.property(result, 'transactions')
224
+ // All transactions should be returned when allTxs is true
225
+ assert.equal(result.transactions.length, 150)
226
+ })
227
+ })
228
+
229
+ describe('#getTransactionsBulk()', () => {
230
+ it('should call fulcrum adapter and sort transactions for each address', async () => {
231
+ const addresses = ['bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf']
232
+ const allTxs = false
233
+ const mockResponse = {
234
+ transactions: [
235
+ {
236
+ transactions: [
237
+ { tx_hash: 'aaa', height: 100 },
238
+ { tx_hash: 'bbb', height: 200 }
239
+ ]
240
+ }
241
+ ]
242
+ }
243
+ mockAdapters.fulcrum.post.resolves(mockResponse)
244
+
245
+ const result = await uut.getTransactionsBulk({ addresses, allTxs })
246
+
247
+ assert.isTrue(
248
+ mockAdapters.fulcrum.post.calledOnceWith('electrumx/transactions/', { addresses })
249
+ )
250
+ assert.property(result, 'transactions')
251
+ })
252
+
253
+ it('should limit to 100 transactions when allTxs is false', async () => {
254
+ const addresses = ['bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf']
255
+ const allTxs = false
256
+ const mockTransactions = Array(150).fill({ tx_hash: 'aaa', height: 100 })
257
+ const mockResponse = {
258
+ transactions: [
259
+ {
260
+ transactions: mockTransactions
261
+ }
262
+ ]
263
+ }
264
+ mockAdapters.fulcrum.post.resolves(mockResponse)
265
+
266
+ const result = await uut.getTransactionsBulk({ addresses, allTxs })
267
+
268
+ assert.isAtMost(result.transactions[0].transactions.length, 100)
269
+ })
270
+ })
271
+
272
+ describe('#getMempool()', () => {
273
+ it('should call fulcrum adapter get method', async () => {
274
+ const address = 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf'
275
+ mockAdapters.fulcrum.get.resolves({ mempool: [] })
276
+
277
+ const result = await uut.getMempool({ address })
278
+
279
+ assert.isTrue(mockAdapters.fulcrum.get.calledOnceWith(`electrumx/unconfirmed/${address}`))
280
+ assert.deepEqual(result, { mempool: [] })
281
+ })
282
+ })
283
+
284
+ describe('#getMempoolBulk()', () => {
285
+ it('should call fulcrum adapter post method', async () => {
286
+ const addresses = ['bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf']
287
+ mockAdapters.fulcrum.post.resolves({ mempool: [] })
288
+
289
+ const result = await uut.getMempoolBulk({ addresses })
290
+
291
+ assert.isTrue(
292
+ mockAdapters.fulcrum.post.calledOnceWith('electrumx/unconfirmed/', { addresses })
293
+ )
294
+ assert.deepEqual(result, { mempool: [] })
295
+ })
296
+ })
297
+ })
@@ -0,0 +1,84 @@
1
+ /*
2
+ Unit tests for MiningUseCases.
3
+ */
4
+
5
+ import { assert } from 'chai'
6
+
7
+ import MiningUseCases from '../../../src/use-cases/full-node-mining-use-cases.js'
8
+
9
+ describe('#full-node-mining-use-cases.js', () => {
10
+ let mockAdapters
11
+ let uut
12
+
13
+ beforeEach(() => {
14
+ mockAdapters = {
15
+ fullNode: {
16
+ call: async () => ({})
17
+ }
18
+ }
19
+
20
+ uut = new MiningUseCases({ adapters: mockAdapters })
21
+ })
22
+
23
+ describe('#constructor()', () => {
24
+ it('should require adapters', () => {
25
+ assert.throws(() => {
26
+ // eslint-disable-next-line no-new
27
+ new MiningUseCases()
28
+ }, /Adapters instance required/)
29
+ })
30
+
31
+ it('should require full node adapter', () => {
32
+ assert.throws(() => {
33
+ // eslint-disable-next-line no-new
34
+ new MiningUseCases({ adapters: {} })
35
+ }, /Full node adapter required/)
36
+ })
37
+ })
38
+
39
+ describe('#getMiningInfo()', () => {
40
+ it('should call full node adapter with correct method', async () => {
41
+ let capturedMethod = ''
42
+ mockAdapters.fullNode.call = async method => {
43
+ capturedMethod = method
44
+ return { blocks: 100, difficulty: 1.5 }
45
+ }
46
+
47
+ const result = await uut.getMiningInfo()
48
+
49
+ assert.equal(capturedMethod, 'getmininginfo')
50
+ assert.deepEqual(result, { blocks: 100, difficulty: 1.5 })
51
+ })
52
+ })
53
+
54
+ describe('#getNetworkHashPS()', () => {
55
+ it('should call full node adapter with correct method and default params', async () => {
56
+ let capturedMethod = ''
57
+ let capturedParams = []
58
+ mockAdapters.fullNode.call = async (method, params) => {
59
+ capturedMethod = method
60
+ capturedParams = params
61
+ return 1234567890
62
+ }
63
+
64
+ const result = await uut.getNetworkHashPS({ nblocks: 120, height: -1 })
65
+
66
+ assert.equal(capturedMethod, 'getnetworkhashps')
67
+ assert.deepEqual(capturedParams, [120, -1])
68
+ assert.equal(result, 1234567890)
69
+ })
70
+
71
+ it('should call full node adapter with custom params', async () => {
72
+ let capturedParams = []
73
+ mockAdapters.fullNode.call = async (method, params) => {
74
+ capturedParams = params
75
+ return 9876543210
76
+ }
77
+
78
+ const result = await uut.getNetworkHashPS({ nblocks: 240, height: 1000 })
79
+
80
+ assert.deepEqual(capturedParams, [240, 1000])
81
+ assert.equal(result, 9876543210)
82
+ })
83
+ })
84
+ })