psf-bch-api 1.2.0 → 1.3.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 (45) hide show
  1. package/.env-local +9 -0
  2. package/bin/server.js +2 -1
  3. package/package.json +4 -1
  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 +21 -24
  9. package/src/controllers/rest-api/fulcrum/controller.js +563 -0
  10. package/src/controllers/rest-api/fulcrum/router.js +64 -0
  11. package/src/controllers/rest-api/full-node/blockchain/controller.js +4 -4
  12. package/src/controllers/rest-api/full-node/mining/controller.js +99 -0
  13. package/src/controllers/rest-api/full-node/mining/router.js +52 -0
  14. package/src/controllers/rest-api/full-node/rawtransactions/controller.js +333 -0
  15. package/src/controllers/rest-api/full-node/rawtransactions/router.js +58 -0
  16. package/src/controllers/rest-api/index.js +19 -3
  17. package/src/controllers/rest-api/slp/controller.js +218 -0
  18. package/src/controllers/rest-api/slp/router.js +55 -0
  19. package/src/controllers/timer-controller.js +1 -1
  20. package/src/use-cases/fulcrum-use-cases.js +155 -0
  21. package/src/use-cases/full-node-mining-use-cases.js +28 -0
  22. package/src/use-cases/full-node-rawtransactions-use-cases.js +121 -0
  23. package/src/use-cases/index.js +8 -0
  24. package/src/use-cases/slp-use-cases.js +321 -0
  25. package/test/unit/controllers/blockchain-controller-unit.js +2 -3
  26. package/test/unit/controllers/fulcrum-controller-unit.js +481 -0
  27. package/test/unit/controllers/mining-controller-unit.js +139 -0
  28. package/test/unit/controllers/rawtransactions-controller-unit.js +388 -0
  29. package/test/unit/controllers/rest-api-index-unit.js +59 -3
  30. package/test/unit/controllers/slp-controller-unit.js +312 -0
  31. package/test/unit/use-cases/fulcrum-use-cases-unit.js +297 -0
  32. package/test/unit/use-cases/full-node-mining-use-cases-unit.js +84 -0
  33. package/test/unit/use-cases/full-node-rawtransactions-use-cases-unit.js +267 -0
  34. package/test/unit/use-cases/slp-use-cases-unit.js +296 -0
  35. package/src/entities/event.js +0 -71
  36. package/test/integration/api/event-integration.js +0 -250
  37. package/test/integration/api/req-integration.js +0 -173
  38. package/test/integration/api/subscription-integration.js +0 -198
  39. package/test/integration/use-cases/manage-subscription-integration.js +0 -163
  40. package/test/integration/use-cases/publish-event-integration.js +0 -104
  41. package/test/integration/use-cases/query-events-integration.js +0 -95
  42. package/test/unit/entities/event-unit.js +0 -139
  43. /package/src/controllers/rest-api/full-node/blockchain/{index.js → router.js} +0 -0
  44. /package/src/controllers/rest-api/full-node/control/{index.js → router.js} +0 -0
  45. /package/src/controllers/rest-api/full-node/dsproof/{index.js → router.js} +0 -0
@@ -0,0 +1,481 @@
1
+ /*
2
+ Unit tests for FulcrumRESTController.
3
+ */
4
+
5
+ import { assert } from 'chai'
6
+ import sinon from 'sinon'
7
+
8
+ import FulcrumRESTController from '../../../src/controllers/rest-api/fulcrum/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('#fulcrum-controller.js', () => {
18
+ let sandbox
19
+ let mockUseCases
20
+ let mockAdapters
21
+ let uut
22
+
23
+ const createFulcrumUseCaseStubs = () => ({
24
+ getBalance: sandbox.stub().resolves({ balance: 1000 }),
25
+ getBalances: sandbox.stub().resolves({ balances: [] }),
26
+ getUtxos: sandbox.stub().resolves({ utxos: [] }),
27
+ getUtxosBulk: sandbox.stub().resolves({ utxos: [] }),
28
+ getTransactionDetails: sandbox.stub().resolves({ txid: 'abc' }),
29
+ getTransactionDetailsBulk: sandbox.stub().resolves({ transactions: [] }),
30
+ broadcastTransaction: sandbox.stub().resolves({ txid: 'abc' }),
31
+ getBlockHeaders: sandbox.stub().resolves({ headers: [] }),
32
+ getBlockHeadersBulk: sandbox.stub().resolves({ headers: [] }),
33
+ getTransactions: sandbox.stub().resolves({ transactions: [] }),
34
+ getTransactionsBulk: sandbox.stub().resolves({ transactions: [] }),
35
+ getMempool: sandbox.stub().resolves({ mempool: [] }),
36
+ getMempoolBulk: sandbox.stub().resolves({ mempool: [] })
37
+ })
38
+
39
+ beforeEach(() => {
40
+ sandbox = sinon.createSandbox()
41
+ mockAdapters = {
42
+ fullNode: {
43
+ validateArraySize: sandbox.stub().returns(true)
44
+ }
45
+ }
46
+ mockUseCases = {
47
+ fulcrum: createFulcrumUseCaseStubs()
48
+ }
49
+
50
+ uut = new FulcrumRESTController({
51
+ adapters: mockAdapters,
52
+ useCases: mockUseCases
53
+ })
54
+ })
55
+
56
+ afterEach(() => {
57
+ sandbox.restore()
58
+ })
59
+
60
+ describe('#constructor()', () => {
61
+ it('should require adapters', () => {
62
+ assert.throws(() => {
63
+ // eslint-disable-next-line no-new
64
+ new FulcrumRESTController({ useCases: mockUseCases })
65
+ }, /Adapters library required/)
66
+ })
67
+
68
+ it('should require fulcrum use cases', () => {
69
+ assert.throws(() => {
70
+ // eslint-disable-next-line no-new
71
+ new FulcrumRESTController({ adapters: mockAdapters, useCases: {} })
72
+ }, /Fulcrum use cases required/)
73
+ })
74
+ })
75
+
76
+ describe('#root()', () => {
77
+ it('should return service status', async () => {
78
+ const req = createMockRequest()
79
+ const res = createMockResponse()
80
+
81
+ await uut.root(req, res)
82
+
83
+ assert.equal(res.statusValue, 200)
84
+ assert.deepEqual(res.jsonData, { status: 'fulcrum' })
85
+ })
86
+ })
87
+
88
+ describe('#getBalance()', () => {
89
+ it('should return balance on success', async () => {
90
+ const req = createMockRequest({
91
+ params: { address: VALID_MAINNET_ADDRESS }
92
+ })
93
+ const res = createMockResponse()
94
+
95
+ await uut.getBalance(req, res)
96
+
97
+ assert.equal(res.statusValue, 200)
98
+ assert.deepEqual(res.jsonData, { balance: 1000 })
99
+ assert.isTrue(mockUseCases.fulcrum.getBalance.calledOnce)
100
+ })
101
+
102
+ it('should return error if address is array', async () => {
103
+ const req = createMockRequest({
104
+ params: { address: [] }
105
+ })
106
+ const res = createMockResponse()
107
+
108
+ await uut.getBalance(req, res)
109
+
110
+ assert.equal(res.statusValue, 400)
111
+ assert.property(res.jsonData, 'error')
112
+ })
113
+
114
+ it('should handle errors via handleError', async () => {
115
+ const error = new Error('failure')
116
+ error.status = 503
117
+ mockUseCases.fulcrum.getBalance.rejects(error)
118
+ const req = createMockRequest({
119
+ params: { address: VALID_MAINNET_ADDRESS }
120
+ })
121
+ const res = createMockResponse()
122
+
123
+ await uut.getBalance(req, res)
124
+
125
+ assert.equal(res.statusValue, 503)
126
+ assert.deepEqual(res.jsonData, { error: 'failure' })
127
+ })
128
+ })
129
+
130
+ describe('#balanceBulk()', () => {
131
+ it('should return error if addresses is not array', async () => {
132
+ const req = createMockRequest({
133
+ body: { addresses: 'not-an-array' }
134
+ })
135
+ const res = createMockResponse()
136
+
137
+ await uut.balanceBulk(req, res)
138
+
139
+ assert.equal(res.statusValue, 400)
140
+ assert.property(res.jsonData, 'error')
141
+ })
142
+
143
+ it('should validate array size and call use case', async () => {
144
+ const req = createMockRequest({
145
+ body: { addresses: [VALID_MAINNET_ADDRESS] }
146
+ })
147
+ const res = createMockResponse()
148
+
149
+ await uut.balanceBulk(req, res)
150
+
151
+ assert.equal(res.statusValue, 200)
152
+ assert.isTrue(mockAdapters.fullNode.validateArraySize.calledOnce)
153
+ assert.isTrue(mockUseCases.fulcrum.getBalances.calledOnce)
154
+ })
155
+
156
+ it('should return error if array size invalid', async () => {
157
+ mockAdapters.fullNode.validateArraySize.returns(false)
158
+ const req = createMockRequest({
159
+ body: { addresses: [VALID_MAINNET_ADDRESS] }
160
+ })
161
+ const res = createMockResponse()
162
+
163
+ await uut.balanceBulk(req, res)
164
+
165
+ assert.equal(res.statusValue, 400)
166
+ assert.equal(res.jsonData.error, 'Array too large.')
167
+ })
168
+ })
169
+
170
+ describe('#getUtxos()', () => {
171
+ it('should return utxos on success', async () => {
172
+ const req = createMockRequest({
173
+ params: { address: VALID_MAINNET_ADDRESS }
174
+ })
175
+ const res = createMockResponse()
176
+
177
+ await uut.getUtxos(req, res)
178
+
179
+ assert.equal(res.statusValue, 200)
180
+ assert.deepEqual(res.jsonData, { utxos: [] })
181
+ assert.isTrue(mockUseCases.fulcrum.getUtxos.calledOnce)
182
+ })
183
+ })
184
+
185
+ describe('#utxosBulk()', () => {
186
+ it('should validate array and call use case', async () => {
187
+ const req = createMockRequest({
188
+ body: { addresses: [VALID_MAINNET_ADDRESS] }
189
+ })
190
+ const res = createMockResponse()
191
+
192
+ await uut.utxosBulk(req, res)
193
+
194
+ assert.equal(res.statusValue, 200)
195
+ assert.isTrue(mockUseCases.fulcrum.getUtxosBulk.calledOnce)
196
+ })
197
+ })
198
+
199
+ describe('#getTransactionDetails()', () => {
200
+ it('should return transaction details on success', async () => {
201
+ const txid = 'a'.repeat(64)
202
+ const req = createMockRequest({
203
+ params: { txid }
204
+ })
205
+ const res = createMockResponse()
206
+
207
+ await uut.getTransactionDetails(req, res)
208
+
209
+ assert.equal(res.statusValue, 200)
210
+ assert.deepEqual(res.jsonData, { txid: 'abc' })
211
+ assert.isTrue(mockUseCases.fulcrum.getTransactionDetails.calledOnce)
212
+ })
213
+
214
+ it('should return error if txid is not string', async () => {
215
+ const req = createMockRequest({
216
+ params: { txid: 123 }
217
+ })
218
+ const res = createMockResponse()
219
+
220
+ await uut.getTransactionDetails(req, res)
221
+
222
+ assert.equal(res.statusValue, 400)
223
+ assert.property(res.jsonData, 'error')
224
+ })
225
+ })
226
+
227
+ describe('#transactionDetailsBulk()', () => {
228
+ it('should validate array and call use case', async () => {
229
+ const req = createMockRequest({
230
+ body: { txids: ['a'.repeat(64)], verbose: true }
231
+ })
232
+ const res = createMockResponse()
233
+
234
+ await uut.transactionDetailsBulk(req, res)
235
+
236
+ assert.equal(res.statusValue, 200)
237
+ assert.isTrue(mockUseCases.fulcrum.getTransactionDetailsBulk.calledOnce)
238
+ })
239
+
240
+ it('should default verbose to true', async () => {
241
+ const req = createMockRequest({
242
+ body: { txids: ['a'.repeat(64)] }
243
+ })
244
+ const res = createMockResponse()
245
+
246
+ await uut.transactionDetailsBulk(req, res)
247
+
248
+ assert.isTrue(
249
+ mockUseCases.fulcrum.getTransactionDetailsBulk.calledWithMatch({
250
+ txids: ['a'.repeat(64)],
251
+ verbose: true
252
+ })
253
+ )
254
+ })
255
+ })
256
+
257
+ describe('#broadcastTransaction()', () => {
258
+ it('should broadcast transaction on success', async () => {
259
+ const req = createMockRequest({
260
+ body: { txHex: '010203' }
261
+ })
262
+ const res = createMockResponse()
263
+
264
+ await uut.broadcastTransaction(req, res)
265
+
266
+ assert.equal(res.statusValue, 200)
267
+ assert.deepEqual(res.jsonData, { txid: 'abc' })
268
+ assert.isTrue(mockUseCases.fulcrum.broadcastTransaction.calledOnce)
269
+ })
270
+
271
+ it('should return error if txHex is not string', async () => {
272
+ const req = createMockRequest({
273
+ body: { txHex: 123 }
274
+ })
275
+ const res = createMockResponse()
276
+
277
+ await uut.broadcastTransaction(req, res)
278
+
279
+ assert.equal(res.statusValue, 400)
280
+ assert.property(res.jsonData, 'error')
281
+ })
282
+ })
283
+
284
+ describe('#getBlockHeaders()', () => {
285
+ it('should return block headers on success', async () => {
286
+ const req = createMockRequest({
287
+ params: { height: '100' },
288
+ query: { count: '2' }
289
+ })
290
+ const res = createMockResponse()
291
+
292
+ await uut.getBlockHeaders(req, res)
293
+
294
+ assert.equal(res.statusValue, 200)
295
+ assert.isTrue(
296
+ mockUseCases.fulcrum.getBlockHeaders.calledWithMatch({
297
+ height: 100,
298
+ count: 2
299
+ })
300
+ )
301
+ })
302
+
303
+ it('should default count to 1', async () => {
304
+ const req = createMockRequest({
305
+ params: { height: '100' }
306
+ })
307
+ const res = createMockResponse()
308
+
309
+ await uut.getBlockHeaders(req, res)
310
+
311
+ assert.isTrue(
312
+ mockUseCases.fulcrum.getBlockHeaders.calledWithMatch({
313
+ height: 100,
314
+ count: 1
315
+ })
316
+ )
317
+ })
318
+
319
+ it('should return error if height is invalid', async () => {
320
+ const req = createMockRequest({
321
+ params: { height: 'invalid' }
322
+ })
323
+ const res = createMockResponse()
324
+
325
+ await uut.getBlockHeaders(req, res)
326
+
327
+ assert.equal(res.statusValue, 400)
328
+ assert.property(res.jsonData, 'error')
329
+ })
330
+ })
331
+
332
+ describe('#blockHeadersBulk()', () => {
333
+ it('should validate heights array and call use case', async () => {
334
+ const req = createMockRequest({
335
+ body: { heights: [{ height: 100, count: 2 }] }
336
+ })
337
+ const res = createMockResponse()
338
+
339
+ await uut.blockHeadersBulk(req, res)
340
+
341
+ assert.equal(res.statusValue, 200)
342
+ assert.isTrue(mockUseCases.fulcrum.getBlockHeadersBulk.calledOnce)
343
+ })
344
+
345
+ it('should return error if heights is not array', async () => {
346
+ const req = createMockRequest({
347
+ body: { heights: 'not-an-array' }
348
+ })
349
+ const res = createMockResponse()
350
+
351
+ await uut.blockHeadersBulk(req, res)
352
+
353
+ assert.equal(res.statusValue, 400)
354
+ assert.property(res.jsonData, 'error')
355
+ })
356
+
357
+ it('should validate height objects', async () => {
358
+ const req = createMockRequest({
359
+ body: { heights: [{ height: 'invalid', count: 2 }] }
360
+ })
361
+ const res = createMockResponse()
362
+
363
+ await uut.blockHeadersBulk(req, res)
364
+
365
+ assert.equal(res.statusValue, 400)
366
+ assert.property(res.jsonData, 'error')
367
+ })
368
+ })
369
+
370
+ describe('#getTransactions()', () => {
371
+ it('should return transactions on success', async () => {
372
+ const req = createMockRequest({
373
+ params: { address: VALID_MAINNET_ADDRESS }
374
+ })
375
+ const res = createMockResponse()
376
+
377
+ await uut.getTransactions(req, res)
378
+
379
+ assert.equal(res.statusValue, 200)
380
+ assert.isTrue(mockUseCases.fulcrum.getTransactions.calledOnce)
381
+ })
382
+
383
+ it('should handle allTxs from params', async () => {
384
+ const req = createMockRequest({
385
+ params: { address: VALID_MAINNET_ADDRESS, allTxs: 'true' }
386
+ })
387
+ const res = createMockResponse()
388
+
389
+ await uut.getTransactions(req, res)
390
+
391
+ assert.isTrue(
392
+ mockUseCases.fulcrum.getTransactions.calledWithMatch({
393
+ address: VALID_MAINNET_ADDRESS,
394
+ allTxs: true
395
+ })
396
+ )
397
+ })
398
+
399
+ it('should handle allTxs from query', async () => {
400
+ const req = createMockRequest({
401
+ params: { address: VALID_MAINNET_ADDRESS },
402
+ query: { allTxs: 'true' }
403
+ })
404
+ const res = createMockResponse()
405
+
406
+ await uut.getTransactions(req, res)
407
+
408
+ assert.isTrue(
409
+ mockUseCases.fulcrum.getTransactions.calledWithMatch({
410
+ allTxs: true
411
+ })
412
+ )
413
+ })
414
+ })
415
+
416
+ describe('#transactionsBulk()', () => {
417
+ it('should validate addresses and call use case', async () => {
418
+ const req = createMockRequest({
419
+ body: { addresses: [VALID_MAINNET_ADDRESS], allTxs: true }
420
+ })
421
+ const res = createMockResponse()
422
+
423
+ await uut.transactionsBulk(req, res)
424
+
425
+ assert.equal(res.statusValue, 200)
426
+ assert.isTrue(mockUseCases.fulcrum.getTransactionsBulk.calledOnce)
427
+ })
428
+ })
429
+
430
+ describe('#getMempool()', () => {
431
+ it('should return mempool on success', async () => {
432
+ const req = createMockRequest({
433
+ params: { address: VALID_MAINNET_ADDRESS }
434
+ })
435
+ const res = createMockResponse()
436
+
437
+ await uut.getMempool(req, res)
438
+
439
+ assert.equal(res.statusValue, 200)
440
+ assert.deepEqual(res.jsonData, { mempool: [] })
441
+ assert.isTrue(mockUseCases.fulcrum.getMempool.calledOnce)
442
+ })
443
+ })
444
+
445
+ describe('#mempoolBulk()', () => {
446
+ it('should validate addresses and call use case', async () => {
447
+ const req = createMockRequest({
448
+ body: { addresses: [VALID_MAINNET_ADDRESS] }
449
+ })
450
+ const res = createMockResponse()
451
+
452
+ await uut.mempoolBulk(req, res)
453
+
454
+ assert.equal(res.statusValue, 200)
455
+ assert.isTrue(mockUseCases.fulcrum.getMempoolBulk.calledOnce)
456
+ })
457
+ })
458
+
459
+ describe('#handleError()', () => {
460
+ it('should handle errors with status', async () => {
461
+ const error = new Error('test error')
462
+ error.status = 400
463
+ const res = createMockResponse()
464
+
465
+ uut.handleError(error, res)
466
+
467
+ assert.equal(res.statusValue, 400)
468
+ assert.deepEqual(res.jsonData, { error: 'test error' })
469
+ })
470
+
471
+ it('should default status to 500', async () => {
472
+ const error = new Error('test error')
473
+ const res = createMockResponse()
474
+
475
+ uut.handleError(error, res)
476
+
477
+ assert.equal(res.statusValue, 500)
478
+ assert.deepEqual(res.jsonData, { error: 'test error' })
479
+ })
480
+ })
481
+ })
@@ -0,0 +1,139 @@
1
+ /*
2
+ Unit tests for MiningRESTController.
3
+ */
4
+
5
+ import { assert } from 'chai'
6
+ import sinon from 'sinon'
7
+
8
+ import MiningRESTController from '../../../src/controllers/rest-api/full-node/mining/controller.js'
9
+ import { createMockRequest, createMockResponse } from '../mocks/controller-mocks.js'
10
+
11
+ describe('#mining-controller.js', () => {
12
+ let sandbox
13
+ let mockAdapters
14
+ let mockUseCases
15
+ let uut
16
+
17
+ beforeEach(() => {
18
+ sandbox = sinon.createSandbox()
19
+ mockAdapters = {}
20
+ mockUseCases = {
21
+ mining: {
22
+ getMiningInfo: sandbox.stub().resolves({ blocks: 100, difficulty: 1.5 }),
23
+ getNetworkHashPS: sandbox.stub().resolves(1234567890)
24
+ }
25
+ }
26
+
27
+ uut = new MiningRESTController({
28
+ adapters: mockAdapters,
29
+ useCases: mockUseCases
30
+ })
31
+ })
32
+
33
+ afterEach(() => {
34
+ sandbox.restore()
35
+ })
36
+
37
+ describe('#constructor()', () => {
38
+ it('should require adapters', () => {
39
+ assert.throws(() => {
40
+ // eslint-disable-next-line no-new
41
+ new MiningRESTController({ useCases: mockUseCases })
42
+ }, /Adapters library required/)
43
+ })
44
+
45
+ it('should require mining use cases', () => {
46
+ assert.throws(() => {
47
+ // eslint-disable-next-line no-new
48
+ new MiningRESTController({ adapters: mockAdapters, useCases: {} })
49
+ }, /Mining use cases required/)
50
+ })
51
+ })
52
+
53
+ describe('#root()', () => {
54
+ it('should return mining status', async () => {
55
+ const req = createMockRequest()
56
+ const res = createMockResponse()
57
+
58
+ await uut.root(req, res)
59
+
60
+ assert.equal(res.statusValue, 200)
61
+ assert.deepEqual(res.jsonData, { status: 'mining' })
62
+ })
63
+ })
64
+
65
+ describe('#getMiningInfo()', () => {
66
+ it('should return mining info on success', async () => {
67
+ const req = createMockRequest()
68
+ const res = createMockResponse()
69
+
70
+ await uut.getMiningInfo(req, res)
71
+
72
+ assert.equal(res.statusValue, 200)
73
+ assert.deepEqual(res.jsonData, { blocks: 100, difficulty: 1.5 })
74
+ assert.isTrue(mockUseCases.mining.getMiningInfo.calledOnce)
75
+ })
76
+
77
+ it('should handle errors via handleError', async () => {
78
+ const error = new Error('failure')
79
+ error.status = 503
80
+ mockUseCases.mining.getMiningInfo.rejects(error)
81
+ const req = createMockRequest()
82
+ const res = createMockResponse()
83
+
84
+ await uut.getMiningInfo(req, res)
85
+
86
+ assert.equal(res.statusValue, 503)
87
+ assert.deepEqual(res.jsonData, { error: 'failure' })
88
+ })
89
+ })
90
+
91
+ describe('#getNetworkHashPS()', () => {
92
+ it('should return network hash PS with default params', async () => {
93
+ const req = createMockRequest()
94
+ const res = createMockResponse()
95
+
96
+ await uut.getNetworkHashPS(req, res)
97
+
98
+ assert.equal(res.statusValue, 200)
99
+ assert.equal(res.jsonData, 1234567890)
100
+ assert.isTrue(mockUseCases.mining.getNetworkHashPS.calledOnce)
101
+ assert.deepEqual(mockUseCases.mining.getNetworkHashPS.firstCall.args[0], {
102
+ nblocks: 120,
103
+ height: -1
104
+ })
105
+ })
106
+
107
+ it('should parse query params for nblocks and height', async () => {
108
+ const req = createMockRequest({
109
+ query: {
110
+ nblocks: '240',
111
+ height: '1000'
112
+ }
113
+ })
114
+ const res = createMockResponse()
115
+
116
+ await uut.getNetworkHashPS(req, res)
117
+
118
+ assert.equal(res.statusValue, 200)
119
+ assert.isTrue(mockUseCases.mining.getNetworkHashPS.calledOnce)
120
+ assert.deepEqual(mockUseCases.mining.getNetworkHashPS.firstCall.args[0], {
121
+ nblocks: 240,
122
+ height: 1000
123
+ })
124
+ })
125
+
126
+ it('should handle errors via handleError', async () => {
127
+ const error = new Error('RPC error')
128
+ error.status = 500
129
+ mockUseCases.mining.getNetworkHashPS.rejects(error)
130
+ const req = createMockRequest()
131
+ const res = createMockResponse()
132
+
133
+ await uut.getNetworkHashPS(req, res)
134
+
135
+ assert.equal(res.statusValue, 500)
136
+ assert.deepEqual(res.jsonData, { error: 'RPC error' })
137
+ })
138
+ })
139
+ })