2025-11-15 17:03:02 -08:00
|
|
|
/*
|
|
|
|
|
Unit tests for SlpRESTController.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
import { assert } from 'chai'
|
|
|
|
|
import sinon from 'sinon'
|
|
|
|
|
|
|
|
|
|
import SlpRESTController from '../../../src/controllers/rest-api/slp/controller.js'
|
|
|
|
|
import {
|
|
|
|
|
createMockRequest,
|
|
|
|
|
createMockResponse
|
|
|
|
|
} from '../mocks/controller-mocks.js'
|
|
|
|
|
|
|
|
|
|
// Valid mainnet cash address for testing
|
|
|
|
|
const VALID_MAINNET_ADDRESS = 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf'
|
|
|
|
|
|
|
|
|
|
describe('#slp-controller.js', () => {
|
|
|
|
|
let sandbox
|
|
|
|
|
let mockUseCases
|
|
|
|
|
let mockAdapters
|
|
|
|
|
let uut
|
|
|
|
|
|
|
|
|
|
const createSlpUseCaseStubs = () => ({
|
|
|
|
|
getStatus: sandbox.stub().resolves({ status: 'ok' }),
|
|
|
|
|
getAddress: sandbox.stub().resolves({ balance: 1000 }),
|
|
|
|
|
getTxid: sandbox.stub().resolves({ txid: 'abc' }),
|
|
|
|
|
getTokenStats: sandbox.stub().resolves({ tokenData: {} }),
|
2025-11-16 07:38:35 -08:00
|
|
|
getTokenData: sandbox.stub().resolves({ genesisData: {}, immutableData: '', mutableData: '' })
|
2025-11-15 17:03:02 -08:00
|
|
|
})
|
|
|
|
|
|
|
|
|
|
beforeEach(() => {
|
|
|
|
|
sandbox = sinon.createSandbox()
|
|
|
|
|
mockAdapters = {}
|
|
|
|
|
mockUseCases = {
|
|
|
|
|
slp: createSlpUseCaseStubs()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uut = new SlpRESTController({
|
|
|
|
|
adapters: mockAdapters,
|
|
|
|
|
useCases: mockUseCases
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
afterEach(() => {
|
|
|
|
|
sandbox.restore()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
describe('#constructor()', () => {
|
|
|
|
|
it('should require adapters', () => {
|
|
|
|
|
assert.throws(() => {
|
|
|
|
|
// eslint-disable-next-line no-new
|
|
|
|
|
new SlpRESTController({ useCases: mockUseCases })
|
|
|
|
|
}, /Adapters library required/)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should require slp use cases', () => {
|
|
|
|
|
assert.throws(() => {
|
|
|
|
|
// eslint-disable-next-line no-new
|
|
|
|
|
new SlpRESTController({ adapters: mockAdapters, useCases: {} })
|
|
|
|
|
}, /SLP use cases required/)
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
describe('#root()', () => {
|
|
|
|
|
it('should return service status', async () => {
|
|
|
|
|
const req = createMockRequest()
|
|
|
|
|
const res = createMockResponse()
|
|
|
|
|
|
|
|
|
|
await uut.root(req, res)
|
|
|
|
|
|
|
|
|
|
assert.equal(res.statusValue, 200)
|
|
|
|
|
assert.deepEqual(res.jsonData, { status: 'psf-slp-indexer' })
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
describe('#getStatus()', () => {
|
|
|
|
|
it('should return status on success', async () => {
|
|
|
|
|
const req = createMockRequest()
|
|
|
|
|
const res = createMockResponse()
|
|
|
|
|
|
|
|
|
|
await uut.getStatus(req, res)
|
|
|
|
|
|
|
|
|
|
assert.equal(res.statusValue, 200)
|
|
|
|
|
assert.deepEqual(res.jsonData, { status: 'ok' })
|
|
|
|
|
assert.isTrue(mockUseCases.slp.getStatus.calledOnce)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should handle errors via handleError', async () => {
|
|
|
|
|
const error = new Error('failure')
|
|
|
|
|
error.status = 503
|
|
|
|
|
mockUseCases.slp.getStatus.rejects(error)
|
|
|
|
|
const req = createMockRequest()
|
|
|
|
|
const res = createMockResponse()
|
|
|
|
|
|
|
|
|
|
await uut.getStatus(req, res)
|
|
|
|
|
|
|
|
|
|
assert.equal(res.statusValue, 503)
|
|
|
|
|
assert.deepEqual(res.jsonData, { error: 'failure' })
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
describe('#getAddress()', () => {
|
|
|
|
|
it('should return address balance on success', async () => {
|
|
|
|
|
const req = createMockRequest({
|
|
|
|
|
body: { address: VALID_MAINNET_ADDRESS }
|
|
|
|
|
})
|
|
|
|
|
const res = createMockResponse()
|
|
|
|
|
|
|
|
|
|
await uut.getAddress(req, res)
|
|
|
|
|
|
|
|
|
|
assert.equal(res.statusValue, 200)
|
|
|
|
|
assert.deepEqual(res.jsonData, { balance: 1000 })
|
|
|
|
|
assert.isTrue(mockUseCases.slp.getAddress.calledOnce)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should return error if address is empty', async () => {
|
|
|
|
|
const req = createMockRequest({
|
|
|
|
|
body: { address: '' }
|
|
|
|
|
})
|
|
|
|
|
const res = createMockResponse()
|
|
|
|
|
|
|
|
|
|
await uut.getAddress(req, res)
|
|
|
|
|
|
|
|
|
|
assert.equal(res.statusValue, 400)
|
|
|
|
|
assert.property(res.jsonData, 'error')
|
|
|
|
|
assert.include(res.jsonData.error, 'can not be empty')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should return error if address is missing', async () => {
|
|
|
|
|
const req = createMockRequest({
|
|
|
|
|
body: {}
|
|
|
|
|
})
|
|
|
|
|
const res = createMockResponse()
|
|
|
|
|
|
|
|
|
|
await uut.getAddress(req, res)
|
|
|
|
|
|
|
|
|
|
assert.equal(res.statusValue, 400)
|
|
|
|
|
assert.property(res.jsonData, 'error')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should handle errors via handleError', async () => {
|
|
|
|
|
const error = new Error('Invalid address')
|
|
|
|
|
error.status = 400
|
|
|
|
|
mockUseCases.slp.getAddress.rejects(error)
|
|
|
|
|
const req = createMockRequest({
|
|
|
|
|
body: { address: VALID_MAINNET_ADDRESS }
|
|
|
|
|
})
|
|
|
|
|
const res = createMockResponse()
|
|
|
|
|
|
|
|
|
|
await uut.getAddress(req, res)
|
|
|
|
|
|
|
|
|
|
assert.equal(res.statusValue, 400)
|
|
|
|
|
assert.deepEqual(res.jsonData, { error: 'Invalid address' })
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
describe('#getTxid()', () => {
|
|
|
|
|
it('should return transaction data on success', async () => {
|
|
|
|
|
const req = createMockRequest({
|
|
|
|
|
body: { txid: 'a'.repeat(64) }
|
|
|
|
|
})
|
|
|
|
|
const res = createMockResponse()
|
|
|
|
|
|
|
|
|
|
await uut.getTxid(req, res)
|
|
|
|
|
|
|
|
|
|
assert.equal(res.statusValue, 200)
|
|
|
|
|
assert.deepEqual(res.jsonData, { txid: 'abc' })
|
|
|
|
|
assert.isTrue(mockUseCases.slp.getTxid.calledOnce)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should return error if txid is empty', async () => {
|
|
|
|
|
const req = createMockRequest({
|
|
|
|
|
body: { txid: '' }
|
|
|
|
|
})
|
|
|
|
|
const res = createMockResponse()
|
|
|
|
|
|
|
|
|
|
await uut.getTxid(req, res)
|
|
|
|
|
|
|
|
|
|
assert.equal(res.statusValue, 400)
|
|
|
|
|
assert.property(res.jsonData, 'error')
|
|
|
|
|
assert.include(res.jsonData.error, 'can not be empty')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should return error if txid is not 64 characters', async () => {
|
|
|
|
|
const req = createMockRequest({
|
|
|
|
|
body: { txid: 'abc' }
|
|
|
|
|
})
|
|
|
|
|
const res = createMockResponse()
|
|
|
|
|
|
|
|
|
|
await uut.getTxid(req, res)
|
|
|
|
|
|
|
|
|
|
assert.equal(res.statusValue, 400)
|
|
|
|
|
assert.property(res.jsonData, 'error')
|
|
|
|
|
assert.include(res.jsonData.error, 'not a txid')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should handle errors via handleError', async () => {
|
|
|
|
|
const error = new Error('Transaction not found')
|
|
|
|
|
error.status = 404
|
|
|
|
|
mockUseCases.slp.getTxid.rejects(error)
|
|
|
|
|
const req = createMockRequest({
|
|
|
|
|
body: { txid: 'a'.repeat(64) }
|
|
|
|
|
})
|
|
|
|
|
const res = createMockResponse()
|
|
|
|
|
|
|
|
|
|
await uut.getTxid(req, res)
|
|
|
|
|
|
|
|
|
|
assert.equal(res.statusValue, 404)
|
|
|
|
|
assert.deepEqual(res.jsonData, { error: 'Transaction not found' })
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
describe('#getTokenStats()', () => {
|
|
|
|
|
it('should return token stats on success', async () => {
|
|
|
|
|
const req = createMockRequest({
|
|
|
|
|
body: { tokenId: 'a'.repeat(64) }
|
|
|
|
|
})
|
|
|
|
|
const res = createMockResponse()
|
|
|
|
|
|
|
|
|
|
await uut.getTokenStats(req, res)
|
|
|
|
|
|
|
|
|
|
assert.equal(res.statusValue, 200)
|
|
|
|
|
assert.deepEqual(res.jsonData, { tokenData: {} })
|
|
|
|
|
assert.isTrue(mockUseCases.slp.getTokenStats.calledOnce)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should pass withTxHistory flag', async () => {
|
|
|
|
|
const req = createMockRequest({
|
|
|
|
|
body: { tokenId: 'a'.repeat(64), withTxHistory: true }
|
|
|
|
|
})
|
|
|
|
|
const res = createMockResponse()
|
|
|
|
|
|
|
|
|
|
await uut.getTokenStats(req, res)
|
|
|
|
|
|
|
|
|
|
assert.isTrue(mockUseCases.slp.getTokenStats.calledWith({
|
|
|
|
|
tokenId: 'a'.repeat(64),
|
|
|
|
|
withTxHistory: true
|
|
|
|
|
}))
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should return error if tokenId is empty', async () => {
|
|
|
|
|
const req = createMockRequest({
|
|
|
|
|
body: { tokenId: '' }
|
|
|
|
|
})
|
|
|
|
|
const res = createMockResponse()
|
|
|
|
|
|
|
|
|
|
await uut.getTokenStats(req, res)
|
|
|
|
|
|
|
|
|
|
assert.equal(res.statusValue, 400)
|
|
|
|
|
assert.property(res.jsonData, 'error')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should handle errors via handleError', async () => {
|
|
|
|
|
const error = new Error('Token not found')
|
|
|
|
|
error.status = 404
|
|
|
|
|
mockUseCases.slp.getTokenStats.rejects(error)
|
|
|
|
|
const req = createMockRequest({
|
|
|
|
|
body: { tokenId: 'a'.repeat(64) }
|
|
|
|
|
})
|
|
|
|
|
const res = createMockResponse()
|
|
|
|
|
|
|
|
|
|
await uut.getTokenStats(req, res)
|
|
|
|
|
|
|
|
|
|
assert.equal(res.statusValue, 404)
|
|
|
|
|
assert.deepEqual(res.jsonData, { error: 'Token not found' })
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
describe('#getTokenData()', () => {
|
|
|
|
|
it('should return token data on success', async () => {
|
|
|
|
|
const req = createMockRequest({
|
|
|
|
|
body: { tokenId: 'a'.repeat(64) }
|
|
|
|
|
})
|
|
|
|
|
const res = createMockResponse()
|
|
|
|
|
|
|
|
|
|
await uut.getTokenData(req, res)
|
|
|
|
|
|
|
|
|
|
assert.equal(res.statusValue, 200)
|
|
|
|
|
assert.property(res.jsonData, 'genesisData')
|
|
|
|
|
assert.property(res.jsonData, 'immutableData')
|
|
|
|
|
assert.property(res.jsonData, 'mutableData')
|
|
|
|
|
assert.isTrue(mockUseCases.slp.getTokenData.calledOnce)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should return error if tokenId is empty', async () => {
|
|
|
|
|
const req = createMockRequest({
|
|
|
|
|
body: { tokenId: '' }
|
|
|
|
|
})
|
|
|
|
|
const res = createMockResponse()
|
|
|
|
|
|
|
|
|
|
await uut.getTokenData(req, res)
|
|
|
|
|
|
|
|
|
|
assert.equal(res.statusValue, 400)
|
|
|
|
|
assert.property(res.jsonData, 'error')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should handle errors via handleError', async () => {
|
|
|
|
|
const error = new Error('Token data not found')
|
|
|
|
|
error.status = 404
|
|
|
|
|
mockUseCases.slp.getTokenData.rejects(error)
|
|
|
|
|
const req = createMockRequest({
|
|
|
|
|
body: { tokenId: 'a'.repeat(64) }
|
|
|
|
|
})
|
|
|
|
|
const res = createMockResponse()
|
|
|
|
|
|
|
|
|
|
await uut.getTokenData(req, res)
|
|
|
|
|
|
|
|
|
|
assert.equal(res.statusValue, 404)
|
|
|
|
|
assert.deepEqual(res.jsonData, { error: 'Token data not found' })
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
})
|