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.
- package/.env-local +28 -0
- package/bin/server.js +61 -9
- package/package.json +6 -2
- package/src/adapters/fulcrum-api.js +124 -0
- package/src/adapters/full-node-rpc.js +2 -6
- package/src/adapters/index.js +4 -0
- package/src/adapters/slp-indexer-api.js +124 -0
- package/src/config/env/common.js +29 -25
- package/src/config/x402.js +7 -0
- package/src/controllers/rest-api/fulcrum/controller.js +563 -0
- package/src/controllers/rest-api/fulcrum/router.js +64 -0
- package/src/controllers/rest-api/full-node/blockchain/controller.js +4 -4
- package/src/controllers/rest-api/full-node/mining/controller.js +99 -0
- package/src/controllers/rest-api/full-node/mining/router.js +52 -0
- package/src/controllers/rest-api/full-node/rawtransactions/controller.js +333 -0
- package/src/controllers/rest-api/full-node/rawtransactions/router.js +58 -0
- package/src/controllers/rest-api/index.js +23 -3
- package/src/controllers/rest-api/price/controller.js +96 -0
- package/src/controllers/rest-api/price/router.js +52 -0
- package/src/controllers/rest-api/slp/controller.js +218 -0
- package/src/controllers/rest-api/slp/router.js +55 -0
- package/src/controllers/timer-controller.js +1 -1
- package/src/middleware/basic-auth.js +61 -0
- package/src/use-cases/fulcrum-use-cases.js +155 -0
- package/src/use-cases/full-node-mining-use-cases.js +28 -0
- package/src/use-cases/full-node-rawtransactions-use-cases.js +121 -0
- package/src/use-cases/index.js +10 -0
- package/src/use-cases/price-use-cases.js +83 -0
- package/src/use-cases/slp-use-cases.js +321 -0
- package/test/unit/controllers/blockchain-controller-unit.js +2 -3
- package/test/unit/controllers/fulcrum-controller-unit.js +481 -0
- package/test/unit/controllers/mining-controller-unit.js +139 -0
- package/test/unit/controllers/price-controller-unit.js +116 -0
- package/test/unit/controllers/rawtransactions-controller-unit.js +388 -0
- package/test/unit/controllers/rest-api-index-unit.js +67 -3
- package/test/unit/controllers/slp-controller-unit.js +312 -0
- package/test/unit/use-cases/fulcrum-use-cases-unit.js +297 -0
- package/test/unit/use-cases/full-node-mining-use-cases-unit.js +84 -0
- package/test/unit/use-cases/full-node-rawtransactions-use-cases-unit.js +267 -0
- package/test/unit/use-cases/price-use-cases-unit.js +103 -0
- package/test/unit/use-cases/slp-use-cases-unit.js +296 -0
- package/src/entities/event.js +0 -71
- package/test/integration/api/event-integration.js +0 -250
- package/test/integration/api/req-integration.js +0 -173
- package/test/integration/api/subscription-integration.js +0 -198
- package/test/integration/use-cases/manage-subscription-integration.js +0 -163
- package/test/integration/use-cases/publish-event-integration.js +0 -104
- package/test/integration/use-cases/query-events-integration.js +0 -95
- package/test/unit/entities/event-unit.js +0 -139
- /package/{index.js → psf-bch-api.js} +0 -0
- /package/src/controllers/rest-api/full-node/blockchain/{index.js → router.js} +0 -0
- /package/src/controllers/rest-api/full-node/control/{index.js → router.js} +0 -0
- /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
|
+
})
|