import test from 'brittle'
import { AVAILABLE_MODES } from '../../packages/core/core/relay-node/api-mode-transport.js'
import {
MAX_MANAGEMENT_SERVICE_ERROR_BYTES,
MAX_MANAGEMENT_SERVICE_METHODS,
MAX_MANAGEMENT_SERVICE_SNAPSHOT_SERVICES,
MAX_MANAGEMENT_SERVICE_STATS_ARRAY,
MAX_MANAGEMENT_SERVICE_STATS_KEYS,
MAX_MANAGEMENT_SERVICE_STATS_STRING_BYTES,
buildManagementSnapshotRoutePayload,
buildDeviceStatusPayload,
buildManagementConfigPayload,
buildManagementServicesPayload,
buildModeCatalogPayload,
buildPairingStatusPayload,
buildServiceRegistrySnapshot,
buildTransportStatusPayload,
resolveManagementSnapshotRoute
} from '../../packages/core/core/relay-node/api-management-snapshots.js'
test('api management snapshots: route helper maps exact GET snapshot reads', (t) => {
t.alike(resolveManagementSnapshotRoute('GET', '/api/manage/config'), { kind: 'config' })
t.alike(resolveManagementSnapshotRoute('GET', '/api/manage/ai/models'), { kind: 'ai-models' })
t.alike(resolveManagementSnapshotRoute('GET', '/api/manage/services/available'), { kind: 'service-config' })
t.alike(resolveManagementSnapshotRoute('GET', '/api/manage/services'), { kind: 'services' })
t.alike(resolveManagementSnapshotRoute('GET', '/api/manage/transports'), { kind: 'transports' })
t.alike(resolveManagementSnapshotRoute('GET', '/api/manage/devices'), { kind: 'devices' })
t.alike(resolveManagementSnapshotRoute('GET', '/api/manage/pairing'), { kind: 'pairing' })
t.alike(resolveManagementSnapshotRoute('GET', '/api/manage/modes'), { kind: 'modes' })
t.is(resolveManagementSnapshotRoute('POST', '/api/manage/config'), null)
t.is(resolveManagementSnapshotRoute('GET', '/api/manage/service'), null)
t.is(resolveManagementSnapshotRoute('GET', '/api/manage/services/available/extra'), null)
t.is(resolveManagementSnapshotRoute('GET', '/api/manage'), null)
})
test('api management snapshots: service registry payload tolerates missing registry', (t) => {
t.alike(buildServiceRegistrySnapshot(null), { services: [], count: 0 })
t.alike(buildServiceRegistrySnapshot({ services: null }), { services: [], count: 0 })
})
test('api management snapshots: config payload uses safe config fields only', (t) => {
const payload = buildManagementConfigPayload({
_operatingMode: 'gateway',
config: {
name: 'relay',
storage: '/tmp/hiverelay',
apiKey: 'do-not-leak',
connectionKey: 'do-not-leak-either',
plugins: ['poker'],
pairing: { enabled: true, token: 'hidden' },
subsidy: { enabled: true, payoutDestination: 'addr' }
}
})
t.is(payload.mode, 'gateway')
t.is(payload.config.name, 'relay')
t.is(payload.config.mode, 'gateway')
t.alike(payload.config.plugins, ['poker'])
t.absent(Object.prototype.hasOwnProperty.call(payload.config, 'apiKey'))
t.absent(Object.prototype.hasOwnProperty.call(payload.config, 'connectionKey'))
})
test('api management snapshots: route payload builder centralizes GET snapshot responses', async (t) => {
const node = {
_operatingMode: 'private',
mode: 'private',
config: {
name: 'relay',
enableServices: true,
plugins: ['ai'],
transports: { websocket: true },
wsPort: 9999
},
serviceRegistry: {
services: new Map([
['ai', { status: 'running', capabilities: ['list-models'] }]
])
},
accessControl: {
isPairing: true,
_pairingState: {
token: 'do-not-leak',
expiresAt: 12345
}
},
listDevices () {
return [{ pubkey: 'a'.repeat(64), name: 'phone' }]
}
}
const aiProvider = {
ok: true,
provider: {
async 'list-models' () {
return [{ modelId: 'local-model', hasEndpoint: true }]
}
}
}
const config = await buildManagementSnapshotRoutePayload({ route: { kind: 'config' }, node })
const aiModels = await buildManagementSnapshotRoutePayload({ route: { kind: 'ai-models' }, node, aiModelProvider: aiProvider })
const serviceConfig = await buildManagementSnapshotRoutePayload({ route: { kind: 'service-config' }, node })
const services = await buildManagementSnapshotRoutePayload({ route: { kind: 'services' }, node })
const transports = await buildManagementSnapshotRoutePayload({ route: { kind: 'transports' }, node })
const devices = await buildManagementSnapshotRoutePayload({ route: { kind: 'devices' }, node })
const pairing = await buildManagementSnapshotRoutePayload({ route: { kind: 'pairing' }, node })
const modes = await buildManagementSnapshotRoutePayload({ route: { kind: 'modes' }, node })
const missingAI = await buildManagementSnapshotRoutePayload({
route: { kind: 'ai-models' },
node,
aiModelProvider: { ok: false, status: 503, error: 'AI service is not running' }
})
const unknown = await buildManagementSnapshotRoutePayload({ route: { kind: 'unknown' }, node })
t.is(config.status, undefined)
t.is(config.payload.mode, 'private')
t.is(aiModels.payload.count, 1)
t.is(aiModels.payload.models[0].modelId, 'local-model')
t.alike(serviceConfig.payload.plugins, ['ai'])
t.is(services.payload.count, 1)
t.is(transports.payload.websocket.port, 9999)
t.is(devices.payload.count, 1)
t.is(pairing.payload.pairing.expiresAt, 12345)
t.is(modes.payload.current, 'private')
t.is(missingAI.status, 503)
t.is(missingAI.payload.error, 'AI service is not running')
t.is(unknown.status, 404)
t.is(unknown.payload.error, 'unknown management snapshot route')
t.absent(JSON.stringify(pairing.payload).includes('do-not-leak'))
})
test('api management snapshots: services payload preserves compatibility capabilities', (t) => {
const payload = buildManagementServicesPayload({
services: new Map([
['storage', {
status: 'running',
capabilities: ['put', 'get'],
provider: { stats () { return { ok: true } } }
}]
])
})
t.is(payload.enabled, true)
t.is(payload.statsVerified, false)
t.is(payload.count, 1)
t.alike(payload.services[0].methods, ['put', 'get'])
t.alike(payload.services[0].capabilities, ['put', 'get'])
})
test('api management snapshots: service entries prefer capabilities and isolate stats failures', (t) => {
const registry = {
services: new Map([
['ai', {
version: '1.0.0',
description: 'models',
status: 'running',
capabilities: ['infer'],
provider: {
methods: { ignored () {} },
stats () { throw new Error('stats failed') }
},
restartCount: 2,
lastStartedAt: 'start',
lastStoppedAt: 'stop',
lastError: 'previous error'
}],
['poker', {
running: true,
provider: {
methods: {
listTables () {},
joinTable () {}
},
stats () {
return { tables: 3 }
}
}
}]
])
}
const payload = buildServiceRegistrySnapshot(registry)
t.is(payload.count, 2)
t.is(payload.total, 2)
t.absent(payload.truncated)
t.alike(payload.services[0], {
name: 'ai',
version: '1.0.0',
description: 'models',
status: 'running',
running: true,
methods: ['infer'],
stats: null,
restartCount: 2,
lastStartedAt: 'start',
lastStoppedAt: 'stop',
lastError: 'previous error'
})
t.alike(payload.services[1], {
name: 'poker',
version: null,
description: '',
status: 'running',
running: true,
methods: ['listTables', 'joinTable'],
stats: { tables: 3 },
restartCount: 0,
lastStartedAt: null,
lastStoppedAt: null,
lastError: null
})
})
test('api management snapshots: service entries sanitize provider stats and noisy metadata', (t) => {
const registry = {
services: new Map([
['bad\nname', { version: '1.0.0' }],
['storage', {
version: '1.0.0',
description: 'x'.repeat(200),
status: 'running',
capabilities: [
'put',
'put',
'bad\nmethod',
...Array.from({ length: MAX_MANAGEMENT_SERVICE_METHODS + 8 }, (_, i) => 'method-' + i)
],
stats: {
requests: 12,
nested: {
ok: true,
apiKey: 'do-not-leak',
token: 'also-hidden'
},
labels: [
'ok',
'bad\nlabel',
...Array.from({ length: MAX_MANAGEMENT_SERVICE_STATS_ARRAY + 4 }, (_, i) => 'item-' + i)
],
text: 'z'.repeat(MAX_MANAGEMENT_SERVICE_STATS_STRING_BYTES + 64),
nope: Infinity
},
restartCount: 1.9,
lastStartedAt: 'start',
lastStoppedAt: 'bad\nstop',
lastError: 'e'.repeat(MAX_MANAGEMENT_SERVICE_ERROR_BYTES + 64)
}]
])
}
for (let i = 0; i < MAX_MANAGEMENT_SERVICE_STATS_KEYS + 10; i++) {
registry.services.get('storage').stats['metric-' + i] = i
}
for (let i = 0; i < MAX_MANAGEMENT_SERVICE_SNAPSHOT_SERVICES + 4; i++) {
registry.services.set('extra-' + i, { version: '1.0.0' })
}
const payload = buildServiceRegistrySnapshot(registry)
t.is(payload.count, MAX_MANAGEMENT_SERVICE_SNAPSHOT_SERVICES)
t.is(payload.total, MAX_MANAGEMENT_SERVICE_SNAPSHOT_SERVICES + 6)
t.ok(payload.truncated)
const badName = payload.services[0]
t.is(badName.name, 'unknown')
const service = payload.services[1]
t.is(service.methods.length, MAX_MANAGEMENT_SERVICE_METHODS)
t.is(service.methods[0], 'put')
t.absent(service.methods.includes('bad\nmethod'))
t.is(service.restartCount, 1)
t.is(service.lastStoppedAt, null)
t.is(Buffer.byteLength(service.lastError, 'utf8'), MAX_MANAGEMENT_SERVICE_ERROR_BYTES)
t.is(Buffer.byteLength(service.stats.text, 'utf8'), MAX_MANAGEMENT_SERVICE_STATS_STRING_BYTES)
t.is(service.stats.requests, 12)
t.is(service.stats.nested.ok, true)
t.absent(Object.prototype.hasOwnProperty.call(service.stats.nested, 'apiKey'))
t.absent(Object.prototype.hasOwnProperty.call(service.stats.nested, 'token'))
t.absent(Object.prototype.hasOwnProperty.call(service.stats, 'nope'))
t.is(service.stats.labels.length, MAX_MANAGEMENT_SERVICE_STATS_ARRAY)
t.absent(JSON.stringify(payload).includes('do-not-leak'))
t.absent(JSON.stringify(payload).includes('also-hidden'))
})
test('api management snapshots: transport payload reports optional runtime transports', (t) => {
const payload = buildTransportStatusPayload({
config: { transports: { websocket: true }, wsPort: 9999 },
holesailTransport: { connectionKey: 'hole-key', running: true },
torTransport: { onionAddress: 'relay.onion', running: false }
})
t.alike(payload, {
udp: true,
holesail: {
enabled: true,
connectionKey: 'hole-key',
running: true
},
tor: {
enabled: true,
onionAddress: 'relay.onion',
running: false
},
websocket: {
enabled: true,
port: 9999
}
})
t.alike(buildTransportStatusPayload({ config: {} }).websocket, {
enabled: false,
port: 8765
})
})
test('api management snapshots: device and pairing status avoid private state leakage', (t) => {
t.alike(buildDeviceStatusPayload({ mode: 'public' }), {
enabled: false,
mode: 'public',
devices: []
})
t.alike(buildPairingStatusPayload({ mode: 'public' }), {
enabled: false,
mode: 'public',
pairing: null
})
const device = { pubkey: 'A'.repeat(64), name: 'phone', pairedAt: 1, lastSeen: 2, token: 'do-not-leak-device-token' }
const node = {
mode: 'private',
accessControl: {
isPairing: true,
_pairingState: {
token: 'do-not-leak',
relayPubkey: 'b'.repeat(64),
expiresAt: 12345
}
},
listDevices () {
return [
device,
{ pubkey: 'not-a-key', name: 'ignored', secret: 'hidden' },
{ pubkey: 'b'.repeat(64), name: 'bad\nname', pairedAt: -1, lastSeen: Infinity }
]
}
}
t.alike(buildDeviceStatusPayload(node), {
enabled: true,
mode: 'private',
count: 2,
total: 3,
truncated: true,
devices: [
{ pubkey: 'a'.repeat(64), name: 'phone', pairedAt: 1, lastSeen: 2 },
{ pubkey: 'b'.repeat(64), name: 'manual', pairedAt: null, lastSeen: null }
]
})
t.absent(JSON.stringify(buildDeviceStatusPayload(node)).includes('do-not-leak-device-token'))
const pairing = buildPairingStatusPayload(node)
t.alike(pairing, {
enabled: true,
mode: 'private',
pairing: {
active: true,
expiresAt: 12345
}
})
t.absent(Object.prototype.hasOwnProperty.call(pairing.pairing, 'token'))
t.absent(JSON.stringify(pairing).includes('do-not-leak'))
})
test('api management snapshots: mode catalog stays aligned with switchable modes', (t) => {
const payload = buildModeCatalogPayload('private')
t.is(payload.current, 'private')
t.alike(payload.available.map(mode => mode.id), AVAILABLE_MODES)
t.ok(payload.available.every(mode => mode.name && mode.description))
})