@bsv/overlay
Version:
BSV Blockchain Overlay Services Engine
172 lines (141 loc) • 5.78 kB
text/typescript
import { GraphNode, OverlayGASPStorage } from '../GASP/OverlayGASPStorage'
import { Transaction, MerklePath } from '@bsv/sdk'
import { GASPNode } from '@bsv/gasp'
describe('OverlayGASPStorage', () => {
let overlayStorage: OverlayGASPStorage
let mockEngine: any
beforeEach(() => {
mockEngine = { storage: { findOutput: jest.fn(() => undefined), findUTXOsForTopic: jest.fn() }, managers: {} }
overlayStorage = new OverlayGASPStorage('test-topic', mockEngine)
})
describe('appendToGraph', () => {
it('should append a new node to an empty graph', async () => {
const mockTx = {
rawTx: '001122',
outputIndex: 0,
graphID: 'txid123.0'
}
// Use actual Transaction implementation
const parsedTx = Transaction.fromHex(mockTx.rawTx)
await overlayStorage.appendToGraph(mockTx)
expect(Object.keys(overlayStorage.temporaryGraphNodeRefs).length).toBe(1)
expect(overlayStorage.temporaryGraphNodeRefs['txid123.0'].txid).toBe(parsedTx.id('hex'))
})
it('throws error when max nodes are exceeded', async () => {
overlayStorage.maxNodesInGraph = 1
const graphNode: GraphNode = { txid: 'txid123', children: [], rawTx: '', graphID: 'txid4321.2', outputIndex: 0 }
overlayStorage.temporaryGraphNodeRefs['txid123.0'] = graphNode
const mockTx = {
rawTx: '334455',
outputIndex: 1,
graphID: 'txid234.1'
}
await expect(overlayStorage.appendToGraph(mockTx)).rejects.toThrow('The max number of nodes in transaction graph has been reached!')
})
})
describe('findKnownUTXOs', () => {
it('should return known UTXOs since a given timestamp', async () => {
const mockUTXOs = [{ txid: 'txid1', outputIndex: 0, score: 0 }, { txid: 'txid2', outputIndex: 1, score: 0 }]
mockEngine.storage.findUTXOsForTopic.mockResolvedValue(mockUTXOs)
const result = await overlayStorage.findKnownUTXOs(1234567890)
expect(result).toEqual([
{ txid: 'txid1', outputIndex: 0, score: 0 },
{ txid: 'txid2', outputIndex: 1, score: 0 }
])
expect(mockEngine.storage.findUTXOsForTopic).toHaveBeenCalledWith('test-topic', 1234567890)
})
it('should handle errors correctly', async () => {
mockEngine.storage.findUTXOsForTopic.mockRejectedValue(new Error('Database error'))
await expect(overlayStorage.findKnownUTXOs(1234567890)).rejects.toThrow('Database error')
})
})
describe('hydrateGASPNode', () => {
it('should throw an error if no output is found', async () => {
await expect(overlayStorage.hydrateGASPNode('graphID', 'txid', 0, false)).rejects.toThrow('No matching output found!')
})
// TODO: Further test coverage
})
describe('findNeededInputs', () => {
// TODO: Write more complicated test cases
it('should return inputs needed for further verification when no proof is present', async () => {
const mockTx: GASPNode = {
rawTx: '001122',
proof: undefined,
graphID: 'txid123.0',
outputIndex: 0
}
const parsedTx = {
inputs: [{ sourceTXID: 'inputTxid1', sourceOutputIndex: 0 }],
toBEEF: jest.fn(),
id: jest.fn().mockReturnValue('txid123')
}
Transaction.fromHex = jest.fn().mockReturnValue(parsedTx)
const result = await overlayStorage.findNeededInputs(mockTx)
expect(result).toEqual({
requestedInputs: { 'inputTxid1.0': { metadata: false } }
})
})
it('should return inputs needed for further verification when proof is present', async () => {
const mockTx: GASPNode = {
rawTx: '001122',
proof: 'someproof',
graphID: 'txid123.0',
outputIndex: 0
}
const parsedTx = {
inputs: [{ sourceTXID: 'neededTxid', sourceOutputIndex: 1 }],
toBEEF: jest.fn(),
id: jest.fn().mockReturnValue('txid123'),
merklePath: {}
}
Transaction.fromHex = jest.fn().mockReturnValue(parsedTx)
MerklePath.fromHex = jest.fn().mockReturnValue(parsedTx.merklePath)
mockEngine.managers['test-topic'] = {
identifyAdmissibleOutputs: jest.fn().mockResolvedValue({
outputsToAdmit: []
}),
identifyNeededInputs: jest.fn().mockResolvedValue([{ txid: 'neededTxid', outputIndex: 1 }])
}
const result = await overlayStorage.findNeededInputs(mockTx)
expect(result).toEqual({
requestedInputs: { 'neededTxid.1': { metadata: false } }
})
})
})
describe('discardGraph', () => {
it('should discard the graph and its nodes', async () => {
const graphNode1: GraphNode = {
txid: 'txid123',
graphID: 'txid123.0',
rawTx: 'rawTxData',
outputIndex: 0,
children: [],
parent: undefined
}
overlayStorage.temporaryGraphNodeRefs['txid123.0'] = graphNode1
const parentNode: GraphNode = {
txid: 'txid123',
graphID: 'txid123.0',
rawTx: 'rawTxData',
outputIndex: 0,
children: []
}
const graphNode2: GraphNode = {
txid: 'txid124',
graphID: 'txid123.0',
rawTx: 'rawTxData',
outputIndex: 1,
children: [],
parent: parentNode
}
overlayStorage.temporaryGraphNodeRefs['txid124.0'] = graphNode2
await overlayStorage.discardGraph('txid123.0')
expect(overlayStorage.temporaryGraphNodeRefs['txid123.0']).toBeUndefined()
expect(overlayStorage.temporaryGraphNodeRefs['txid124.0']).toBeUndefined()
})
})
it('should handle non-existent graphID', async () => {
await overlayStorage.discardGraph('nonexistent.0')
expect(Object.keys(overlayStorage.temporaryGraphNodeRefs).length).toBe(0)
})
})