mirror of
https://github.com/Permissionless-Software-Foundation/psf-bch-api-base.git
synced 2026-09-21 16:52:00 -07:00
feat(slp): Ported SLP endpoints from bch-api
This commit is contained in:
@@ -12,6 +12,7 @@ import DSProofRouter from '../../../src/controllers/rest-api/full-node/dsproof/r
|
||||
import MiningRouter from '../../../src/controllers/rest-api/full-node/mining/router.js'
|
||||
import RawTransactionsRouter from '../../../src/controllers/rest-api/full-node/rawtransactions/router.js'
|
||||
import FulcrumRouter from '../../../src/controllers/rest-api/fulcrum/router.js'
|
||||
import SlpRouter from '../../../src/controllers/rest-api/slp/router.js'
|
||||
|
||||
describe('#controllers/rest-api/index.js', () => {
|
||||
let sandbox
|
||||
@@ -84,6 +85,17 @@ describe('#controllers/rest-api/index.js', () => {
|
||||
getRawTransactions: () => {},
|
||||
sendRawTransaction: () => {},
|
||||
sendRawTransactions: () => {}
|
||||
},
|
||||
slp: {
|
||||
getStatus: () => {},
|
||||
getAddress: () => {},
|
||||
getTxid: () => {},
|
||||
getTokenStats: () => {},
|
||||
getTokenData: () => {},
|
||||
getTokenData2: () => {},
|
||||
getMutableCid: () => {},
|
||||
decodeOpReturn: () => {},
|
||||
getCIDData: () => {}
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -116,6 +128,7 @@ describe('#controllers/rest-api/index.js', () => {
|
||||
const fulcrumAttachStub = sandbox.stub(FulcrumRouter.prototype, 'attach')
|
||||
const miningAttachStub = sandbox.stub(MiningRouter.prototype, 'attach')
|
||||
const rawtransactionsAttachStub = sandbox.stub(RawTransactionsRouter.prototype, 'attach')
|
||||
const slpAttachStub = sandbox.stub(SlpRouter.prototype, 'attach')
|
||||
const restControllers = new RESTControllers({
|
||||
adapters: mockAdapters,
|
||||
useCases: mockUseCases
|
||||
@@ -136,6 +149,8 @@ describe('#controllers/rest-api/index.js', () => {
|
||||
assert.equal(miningAttachStub.getCall(0).args[0], app)
|
||||
assert.isTrue(rawtransactionsAttachStub.calledOnce)
|
||||
assert.equal(rawtransactionsAttachStub.getCall(0).args[0], app)
|
||||
assert.isTrue(slpAttachStub.calledOnce)
|
||||
assert.equal(slpAttachStub.getCall(0).args[0], app)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -0,0 +1,369 @@
|
||||
/*
|
||||
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: {} }),
|
||||
getTokenData: sandbox.stub().resolves({ genesisData: {}, immutableData: '', mutableData: '' }),
|
||||
getTokenData2: sandbox.stub().resolves({ tokenIcon: 'test-icon.png' })
|
||||
})
|
||||
|
||||
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' })
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getTokenData2()', () => {
|
||||
it('should return expanded token data on success', async () => {
|
||||
const req = createMockRequest({
|
||||
body: { tokenId: 'a'.repeat(64) }
|
||||
})
|
||||
const res = createMockResponse()
|
||||
|
||||
await uut.getTokenData2(req, res)
|
||||
|
||||
assert.equal(res.statusValue, 200)
|
||||
assert.deepEqual(res.jsonData, { tokenIcon: 'test-icon.png' })
|
||||
assert.isTrue(mockUseCases.slp.getTokenData2.calledOnce)
|
||||
})
|
||||
|
||||
it('should pass updateCache flag', async () => {
|
||||
const req = createMockRequest({
|
||||
body: { tokenId: 'a'.repeat(64), updateCache: true }
|
||||
})
|
||||
const res = createMockResponse()
|
||||
|
||||
await uut.getTokenData2(req, res)
|
||||
|
||||
assert.isTrue(mockUseCases.slp.getTokenData2.calledWith({
|
||||
tokenId: 'a'.repeat(64),
|
||||
updateCache: true
|
||||
}))
|
||||
})
|
||||
|
||||
it('should return error if tokenId is empty', async () => {
|
||||
const req = createMockRequest({
|
||||
body: { tokenId: '' }
|
||||
})
|
||||
const res = createMockResponse()
|
||||
|
||||
await uut.getTokenData2(req, res)
|
||||
|
||||
assert.equal(res.statusValue, 400)
|
||||
assert.property(res.jsonData, 'error')
|
||||
})
|
||||
|
||||
it('should handle errors via handleError', async () => {
|
||||
const error = new Error('Token icon not found')
|
||||
error.status = 404
|
||||
mockUseCases.slp.getTokenData2.rejects(error)
|
||||
const req = createMockRequest({
|
||||
body: { tokenId: 'a'.repeat(64) }
|
||||
})
|
||||
const res = createMockResponse()
|
||||
|
||||
await uut.getTokenData2(req, res)
|
||||
|
||||
assert.equal(res.statusValue, 404)
|
||||
assert.deepEqual(res.jsonData, { error: 'Token icon not found' })
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,311 @@
|
||||
/*
|
||||
Unit tests for SlpUseCases.
|
||||
*/
|
||||
|
||||
import { assert } from 'chai'
|
||||
import sinon from 'sinon'
|
||||
import BCHJS from '@psf/bch-js'
|
||||
|
||||
import SlpUseCases from '../../../src/use-cases/slp-use-cases.js'
|
||||
|
||||
describe('#slp-use-cases.js', () => {
|
||||
let sandbox
|
||||
let mockAdapters
|
||||
let mockConfig
|
||||
let uut
|
||||
let mockBchjs
|
||||
let mockWallet
|
||||
let mockSlpTokenMedia
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.createSandbox()
|
||||
mockConfig = {
|
||||
restURL: 'http://localhost:3000/v5/',
|
||||
ipfsGateway: 'p2wdb-gateway-678.fullstack.cash'
|
||||
}
|
||||
|
||||
mockAdapters = {
|
||||
slpIndexer: {
|
||||
get: sandbox.stub().resolves({}),
|
||||
post: sandbox.stub().resolves({})
|
||||
}
|
||||
}
|
||||
|
||||
// Create mock BCHJS
|
||||
mockBchjs = new BCHJS()
|
||||
mockBchjs.Electrumx = {
|
||||
txData: sandbox.stub().resolves({
|
||||
details: {
|
||||
vout: [
|
||||
{
|
||||
scriptPubKey: {
|
||||
hex: '6a0c48656c6c6f20576f726c6421'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
}
|
||||
mockBchjs.Script = {
|
||||
toASM: sandbox.stub().returns('OP_RETURN 48656c6c6f20576f726c6421')
|
||||
}
|
||||
|
||||
// Create mock wallet
|
||||
mockWallet = {
|
||||
walletInfoPromise: Promise.resolve(),
|
||||
getTransactions: sandbox.stub().resolves([]),
|
||||
getTxData: sandbox.stub().resolves([{
|
||||
vin: [{
|
||||
address: 'bitcoincash:test123'
|
||||
}]
|
||||
}])
|
||||
}
|
||||
|
||||
// Create mock SlpTokenMedia
|
||||
mockSlpTokenMedia = {
|
||||
getIcon: sandbox.stub().resolves({ tokenIcon: 'test-icon.png' })
|
||||
}
|
||||
|
||||
// Mock the imports
|
||||
uut = new SlpUseCases({
|
||||
adapters: mockAdapters,
|
||||
bchjs: mockBchjs,
|
||||
config: mockConfig
|
||||
})
|
||||
|
||||
// Replace the wallet initialization with our mocks
|
||||
uut.wallet = mockWallet
|
||||
uut.slpTokenMedia = mockSlpTokenMedia
|
||||
uut.walletInitialized = true
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
sandbox.restore()
|
||||
})
|
||||
|
||||
describe('#constructor()', () => {
|
||||
it('should require adapters', () => {
|
||||
assert.throws(() => {
|
||||
// eslint-disable-next-line no-new
|
||||
new SlpUseCases()
|
||||
}, /Adapters instance required/)
|
||||
})
|
||||
|
||||
it('should require slpIndexer adapter', () => {
|
||||
assert.throws(() => {
|
||||
// eslint-disable-next-line no-new
|
||||
new SlpUseCases({ adapters: {} })
|
||||
}, /SLP Indexer adapter required/)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getStatus()', () => {
|
||||
it('should call slpIndexer adapter get method', async () => {
|
||||
mockAdapters.slpIndexer.get.resolves({ status: 'ok' })
|
||||
|
||||
const result = await uut.getStatus()
|
||||
|
||||
assert.isTrue(mockAdapters.slpIndexer.get.calledOnceWith('slp/status/'))
|
||||
assert.deepEqual(result, { status: 'ok' })
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getAddress()', () => {
|
||||
it('should call slpIndexer adapter post method', async () => {
|
||||
const address = 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf'
|
||||
mockAdapters.slpIndexer.post.resolves({ balance: 1000 })
|
||||
|
||||
const result = await uut.getAddress({ address })
|
||||
|
||||
assert.isTrue(
|
||||
mockAdapters.slpIndexer.post.calledOnceWith('slp/address/', { address })
|
||||
)
|
||||
assert.deepEqual(result, { balance: 1000 })
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getTxid()', () => {
|
||||
it('should call slpIndexer adapter post method', async () => {
|
||||
const txid = 'a'.repeat(64)
|
||||
mockAdapters.slpIndexer.post.resolves({ txid })
|
||||
|
||||
const result = await uut.getTxid({ txid })
|
||||
|
||||
assert.isTrue(
|
||||
mockAdapters.slpIndexer.post.calledOnceWith('slp/tx/', { txid })
|
||||
)
|
||||
assert.deepEqual(result, { txid })
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getTokenStats()', () => {
|
||||
it('should call slpIndexer adapter post method', async () => {
|
||||
const tokenId = 'a'.repeat(64)
|
||||
const withTxHistory = false
|
||||
mockAdapters.slpIndexer.post.resolves({ tokenData: {} })
|
||||
|
||||
const result = await uut.getTokenStats({ tokenId, withTxHistory })
|
||||
|
||||
assert.isTrue(
|
||||
mockAdapters.slpIndexer.post.calledOnceWith('slp/token/', { tokenId, withTxHistory })
|
||||
)
|
||||
assert.deepEqual(result, { tokenData: {} })
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getTokenData()', () => {
|
||||
it('should get token data with mutable and immutable data', async () => {
|
||||
const tokenId = 'a'.repeat(64)
|
||||
const tokenStats = {
|
||||
tokenData: {
|
||||
documentUri: 'ipfs://test123',
|
||||
documentHash: 'b'.repeat(64)
|
||||
}
|
||||
}
|
||||
mockAdapters.slpIndexer.post.resolves(tokenStats)
|
||||
|
||||
// Mock decodeOpReturn to return JSON with mda
|
||||
sandbox.stub(uut, 'decodeOpReturn').resolves(JSON.stringify({ mda: 'bitcoincash:test123' }))
|
||||
sandbox.stub(uut, 'getMutableCid').resolves('mutable-cid-123')
|
||||
|
||||
const result = await uut.getTokenData({ tokenId })
|
||||
|
||||
assert.property(result, 'genesisData')
|
||||
assert.property(result, 'immutableData')
|
||||
assert.property(result, 'mutableData')
|
||||
})
|
||||
|
||||
it('should handle errors when getting mutable data', async () => {
|
||||
const tokenId = 'a'.repeat(64)
|
||||
const tokenStats = {
|
||||
tokenData: {
|
||||
documentUri: 'ipfs://test123',
|
||||
documentHash: 'b'.repeat(64)
|
||||
}
|
||||
}
|
||||
mockAdapters.slpIndexer.post.resolves(tokenStats)
|
||||
|
||||
sandbox.stub(uut, 'getMutableCid').rejects(new Error('Test error'))
|
||||
|
||||
const result = await uut.getTokenData({ tokenId })
|
||||
|
||||
assert.property(result, 'genesisData')
|
||||
assert.property(result, 'immutableData')
|
||||
assert.equal(result.mutableData, '')
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getTokenData2()', () => {
|
||||
it('should call slpTokenMedia getIcon method', async () => {
|
||||
const tokenId = 'a'.repeat(64)
|
||||
const updateCache = false
|
||||
mockSlpTokenMedia.getIcon.resolves({ tokenIcon: 'test-icon.png' })
|
||||
|
||||
const result = await uut.getTokenData2({ tokenId, updateCache })
|
||||
|
||||
assert.isTrue(
|
||||
mockSlpTokenMedia.getIcon.calledOnceWith({ tokenId, updateCache })
|
||||
)
|
||||
assert.deepEqual(result, { tokenIcon: 'test-icon.png' })
|
||||
})
|
||||
})
|
||||
|
||||
describe('#decodeOpReturn()', () => {
|
||||
it('should decode OP_RETURN data from transaction', async () => {
|
||||
const txid = 'a'.repeat(64)
|
||||
const mockTxData = {
|
||||
details: {
|
||||
vout: [
|
||||
{
|
||||
scriptPubKey: {
|
||||
hex: '6a0c48656c6c6f20576f726c6421'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
mockBchjs.Electrumx.txData.resolves(mockTxData)
|
||||
mockBchjs.Script.toASM.returns('OP_RETURN 48656c6c6f20576f726c6421')
|
||||
|
||||
const result = await uut.decodeOpReturn({ txid })
|
||||
|
||||
assert.isTrue(mockBchjs.Electrumx.txData.calledOnceWith(txid))
|
||||
assert.isString(result)
|
||||
})
|
||||
|
||||
it('should throw error if txid is not a string', async () => {
|
||||
try {
|
||||
await uut.decodeOpReturn({ txid: null })
|
||||
assert.fail('Should have thrown an error')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'txid must be a string')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getCIDData()', () => {
|
||||
it('should fetch IPFS data from CID', async () => {
|
||||
const cid = 'ipfs://test123'
|
||||
const mockData = { name: 'Test Token' }
|
||||
|
||||
// Mock axios
|
||||
const axios = await import('axios')
|
||||
sandbox.stub(axios.default, 'get').resolves({ data: mockData })
|
||||
|
||||
const result = await uut.getCIDData({ cid })
|
||||
|
||||
assert.deepEqual(result, mockData)
|
||||
})
|
||||
|
||||
it('should throw error if cid is not a string', async () => {
|
||||
try {
|
||||
await uut.getCIDData({ cid: null })
|
||||
assert.fail('Should have thrown an error')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'cid must be a string')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getMutableCid()', () => {
|
||||
it('should extract mutable CID from token stats', async () => {
|
||||
const tokenStats = {
|
||||
documentHash: 'a'.repeat(64)
|
||||
}
|
||||
|
||||
const mockOpReturn = JSON.stringify({ mda: 'bitcoincash:test123' })
|
||||
sandbox.stub(uut, 'decodeOpReturn').resolves(mockOpReturn)
|
||||
|
||||
mockWallet.getTransactions.resolves([
|
||||
{
|
||||
tx_hash: 'b'.repeat(64),
|
||||
height: 100
|
||||
}
|
||||
])
|
||||
|
||||
mockWallet.getTxData.resolves([{
|
||||
vin: [{
|
||||
address: 'bitcoincash:test123'
|
||||
}]
|
||||
}])
|
||||
|
||||
// Mock decodeOpReturn for the transaction
|
||||
uut.decodeOpReturn.onSecondCall().resolves(JSON.stringify({ cid: 'ipfs://mutable-cid-123', ts: 1234567890 }))
|
||||
|
||||
const result = await uut.getMutableCid({ tokenStats })
|
||||
|
||||
assert.isString(result)
|
||||
})
|
||||
|
||||
it('should return false if no documentHash in tokenStats', async () => {
|
||||
const tokenStats = {}
|
||||
|
||||
try {
|
||||
await uut.getMutableCid({ tokenStats })
|
||||
assert.fail('Should have thrown an error')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'No documentHash property found')
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user