@bsv/overlay-express
Version:
BSV Blockchain Overlay Express
256 lines (234 loc) • 8.35 kB
text/typescript
import { describe, it, expect, jest } from '@jest/globals'
import { Transaction } from '@bsv/sdk'
import { OverlayMonitor, analyzeOverlayAnchorTip, analyzeOverlayLookupResponse } from '../OverlayMonitor.js'
describe('OverlayMonitor', () => {
it('summarizes an empty lookup response', () => {
const result = analyzeOverlayLookupResponse({
target: 'local-overlay',
url: 'https://overlay.example/lookup',
probe: 'empty',
service: 'ls_empty',
status: 200,
ok: true,
responseBody: { outputs: [] },
responseBytes: 14,
durationMs: 5
})
expect(result.outputCount).toBe(0)
expect(result.analyzedOutputCount).toBe(0)
expect(result.warnings).toHaveLength(0)
})
it('reports missing subject proofs in returned BEEF', () => {
const tx = new Transaction(1, [], [], 0)
const result = analyzeOverlayLookupResponse({
target: 'local-overlay',
url: 'https://overlay.example/lookup',
probe: 'proof-shape',
service: 'ls_ship',
status: 200,
ok: true,
responseBody: {
outputs: [
{
outputIndex: 0,
beef: tx.toBEEF()
}
]
},
responseBytes: 512,
durationMs: 5
})
expect(result.outputs[0].txid).toBe(tx.id('hex'))
expect(result.outputs[0].subjectHasProof).toBe(false)
expect(result.outputs[0].txCount).toBe(1)
expect(result.warnings.map(warning => warning.code)).toContain('subject-proof-missing')
})
it('runs configured probes through Overlay Express lookup endpoints', async () => {
const tx = new Transaction(1, [], [], 0)
const responseBody = JSON.stringify({
outputs: [
{
outputIndex: 0,
beef: tx.toBEEF()
}
]
})
const fetchImpl = jest.fn<typeof fetch>(async () => new Response(responseBody, { status: 200 }))
const monitor = new OverlayMonitor({
targets: [
{
name: 'local-overlay',
baseUrl: 'https://overlay.example',
probes: [
{
name: 'ship',
service: 'ls_ship',
query: { topics: ['tm_example'] }
}
]
}
],
fetchImpl
})
const report = await monitor.runOnce()
expect(fetchImpl).toHaveBeenCalledWith('https://overlay.example/lookup', expect.objectContaining({
method: 'POST',
body: JSON.stringify({ service: 'ls_ship', query: { topics: ['tm_example'] } })
}))
expect(report.summary.targetCount).toBe(1)
expect(report.summary.probeCount).toBe(1)
expect(report.summary.maintenanceActionCount).toBe(0)
expect(report.summary.outputsMissingSubjectProof).toBe(1)
})
it('reports stale and mismatched BASM anchor tips', () => {
const result = analyzeOverlayAnchorTip({
target: 'local-overlay',
url: 'https://overlay.example/requestTopicAnchorTip',
probe: 'anchors',
topic: 'tm_example',
status: 200,
ok: true,
responseBody: {
blockHeight: 850000,
tac: '11'.repeat(32),
expiredUnprovenCount: 2
},
responseBytes: 128,
durationMs: 5,
expectedTac: '22'.repeat(32),
currentHeight: 850020,
thresholds: {
anchorTipLagBlocks: 6,
expiredUnprovenCount: 0
}
})
expect(result.warnings.map(warning => warning.code)).toEqual([
'anchor-tip-mismatch',
'anchor-stale-height',
'expired-unproven-state'
])
})
it('runs configured anchor probes through Overlay Express BASM endpoints', async () => {
const responseBody = JSON.stringify({
topic: 'tm_example',
blockHeight: 850000,
tac: '11'.repeat(32)
})
const fetchImpl = jest.fn<typeof fetch>(async () => new Response(responseBody, { status: 200 }))
const monitor = new OverlayMonitor({
targets: [
{
name: 'local-overlay',
baseUrl: 'https://overlay.example',
probes: [],
anchorProbes: [
{
name: 'topic-tip',
topic: 'tm_example',
expectedTac: '11'.repeat(32)
}
]
}
],
fetchImpl
})
const report = await monitor.runOnce()
expect(fetchImpl).toHaveBeenCalledWith('https://overlay.example/requestTopicAnchorTip', expect.objectContaining({
method: 'POST',
headers: expect.objectContaining({ 'x-bsv-topic': 'tm_example' })
}))
expect(report.summary.anchorProbeCount).toBe(1)
expect(report.anchorResults[0].warnings).toHaveLength(0)
})
it('runs configured admin maintenance actions', async () => {
const fetchImpl = jest.fn<typeof fetch>(async (url) => {
if (String(url).endsWith('/admin/maintainUnproven')) {
return new Response(JSON.stringify({ status: 'success', data: { evictedTransactions: 1 } }), { status: 200 })
}
if (String(url).endsWith('/admin/startBASMSync')) {
return new Response(JSON.stringify({ status: 'success', data: { synced: true } }), { status: 200 })
}
return new Response(JSON.stringify({ status: 'success' }), { status: 200 })
})
const monitor = new OverlayMonitor({
targets: [
{
name: 'maintained-overlay',
baseUrl: 'https://overlay.example',
probes: [],
maintenance: {
adminToken: 'admin-secret',
startBASMSync: true,
maintainUnproven: {
topics: ['tm_a', 'tm_b'],
thresholdBlocks: 12
}
}
}
],
fetchImpl
})
const report = await monitor.runOnce()
expect(fetchImpl).toHaveBeenCalledWith('https://overlay.example/admin/startBASMSync', expect.objectContaining({
method: 'POST',
headers: expect.objectContaining({ Authorization: 'Bearer admin-secret' }),
body: JSON.stringify({})
}))
expect(fetchImpl).toHaveBeenCalledWith('https://overlay.example/admin/maintainUnproven', expect.objectContaining({
method: 'POST',
headers: expect.objectContaining({ Authorization: 'Bearer admin-secret' }),
body: JSON.stringify({ topic: 'tm_a', thresholdBlocks: 12 })
}))
expect(fetchImpl).toHaveBeenCalledWith('https://overlay.example/admin/maintainUnproven', expect.objectContaining({
method: 'POST',
body: JSON.stringify({ topic: 'tm_b', thresholdBlocks: 12 })
}))
expect(report.maintenanceResults).toHaveLength(3)
expect(report.summary.maintenanceActionCount).toBe(3)
expect(report.summary.failedMaintenanceActionCount).toBe(0)
})
it('reports failed admin maintenance separately from probe failures', async () => {
const fetchImpl = jest.fn<typeof fetch>(async () => new Response(JSON.stringify({ message: 'Forbidden' }), { status: 403 }))
const monitor = new OverlayMonitor({
targets: [
{
name: 'maintained-overlay',
baseUrl: 'https://overlay.example',
probes: [],
maintenance: {
maintainUnproven: true
}
}
],
fetchImpl
})
const report = await monitor.runOnce()
expect(report.summary.failedProbeCount).toBe(0)
expect(report.summary.failedMaintenanceActionCount).toBe(1)
expect(report.maintenanceResults[0].error).toBe('Forbidden')
})
it('aborts a probe that exceeds the configured timeout', async () => {
// Never resolves on its own; only settles when the abort signal fires.
const fetchImpl = jest.fn<typeof fetch>(async (_url, init) => await new Promise<Response>((_resolve, reject) => {
init?.signal?.addEventListener('abort', () => reject(new Error('aborted')))
}))
const monitor = new OverlayMonitor({
targets: [
{
name: 'slow-overlay',
baseUrl: 'https://overlay.example',
probes: [{ name: 'slow', service: 'ls_slow', query: {} }]
}
],
fetchImpl,
timeoutMs: 10
})
const report = await monitor.runOnce()
expect(fetchImpl).toHaveBeenCalledWith('https://overlay.example/lookup', expect.objectContaining({
signal: expect.anything()
}))
expect(report.results[0].ok).toBe(false)
expect(report.results[0].error).toContain('timed out')
expect(report.summary.failedProbeCount).toBe(1)
})
})