import test from 'brittle'
import {
buildAutoHealPayload,
buildHealthDetailPayload,
buildMetricsHistoryPayload,
buildOperatorTelemetryRoutePayload,
buildStorageTopPayload,
MAX_OPERATOR_STORAGE_TOP_ENTRIES,
MAX_OPERATOR_AUTO_HEAL_DRIVES,
MAX_OPERATOR_HEALTH_ACTIONS,
MAX_METRICS_HISTORY_SNAPSHOTS,
resolveOperatorTelemetryRoute
} from 'p2p-hiverelay/core/relay-node/api-operator-telemetry.js'
test('api operator telemetry: route helper maps exact telemetry routes', (t) => {
t.alike(resolveOperatorTelemetryRoute('GET', '/api/health-detail'), {
kind: 'health-detail',
authMessage: 'Unauthorized — API key required for /api/health-detail'
})
t.alike(resolveOperatorTelemetryRoute('GET', '/api/storage/top'), {
kind: 'storage-top',
authMessage: 'Unauthorized — storage top requires API key or localhost'
})
t.alike(resolveOperatorTelemetryRoute('GET', '/api/auto-heal'), {
kind: 'auto-heal',
authMessage: 'Unauthorized — API key required for /api/auto-heal'
})
t.alike(resolveOperatorTelemetryRoute('GET', '/api/history'), {
kind: 'history',
authMessage: 'Unauthorized — API key required for /api/history'
})
t.is(resolveOperatorTelemetryRoute('POST', '/api/health-detail'), null)
t.is(resolveOperatorTelemetryRoute('GET', '/api/history/extra'), null)
t.is(resolveOperatorTelemetryRoute('GET', '/api/storage'), null)
t.is(resolveOperatorTelemetryRoute('GET', '/api/auto-heal/drives'), null)
})
test('api operator telemetry: route payload builder centralizes telemetry responses and query clamps', (t) => {
const storageTopCalls = []
const node = {
getHealthStatus () {
return {
healthy: true,
checks: {
memory: { ok: true, heapPct: 20, rssMB: 100 }
}
}
},
selfHeal: {
getActions () {
return []
}
},
storageAccounting: {
getSummary () {
return { totalBytes: 100, measuredEntries: 1 }
},
getTop (n) {
storageTopCalls.push(n)
return [{ appKey: 'a'.repeat(64), bytes: 50, measuredAt: 123 }]
}
},
autoHeal: {
snapshot () {
return {
enabled: true,
running: true,
tracked: 1,
drives: [{ appKey: 'b'.repeat(64), replicas: 2 }]
}
}
},
metrics: {
snapshots: [{ timestamp: Date.now(), connections: 2 }]
}
}
const health = buildOperatorTelemetryRoutePayload({
route: { kind: 'health-detail' },
node,
url: new URL('http://relay.test/api/health-detail')
})
const storage = buildOperatorTelemetryRoutePayload({
route: { kind: 'storage-top' },
node,
url: new URL('http://relay.test/api/storage/top?n=999999')
})
const autoHeal = buildOperatorTelemetryRoutePayload({
route: { kind: 'auto-heal' },
node,
url: new URL('http://relay.test/api/auto-heal')
})
const history = buildOperatorTelemetryRoutePayload({
route: { kind: 'history' },
node,
url: new URL('http://relay.test/api/history?minutes=bad')
})
const unknown = buildOperatorTelemetryRoutePayload({
route: { kind: 'unknown' },
node,
url: new URL('http://relay.test/api/unknown')
})
t.is(health.status, 200)
t.is(health.payload.healthy, true)
t.alike(storageTopCalls, [MAX_OPERATOR_STORAGE_TOP_ENTRIES])
t.is(storage.payload.entries.length, 1)
t.is(autoHeal.payload.enabled, true)
t.is(history.status, 200)
t.is(history.payload.length, 1)
t.is(unknown.status, 404)
t.is(unknown.payload.error, 'unknown operator telemetry route')
})
test('api operator telemetry: health detail caps and sanitizes health and self-heal actions', (t) => {
const out = buildHealthDetailPayload({
node: {
getHealthStatus () {
return {
healthy: true,
lastCheck: 1234,
consecutiveFailures: 2,
unsafe: 'should-not-leak',
checks: {
memory: {
ok: false,
critical: true,
heapPct: 99.5,
rssMB: 700,
secret: 'should-not-leak'
},
connections: {
ok: true,
staleCount: 1,
totalConns: 3,
suggestion: 'DHT re-announce'
},
disk: {
ok: false,
usedPct: 91.2,
freeGB: 1.5,
totalGB: 20,
error: 'disk almost full',
mountPath: '/should/not/leak'
},
unknown: { ok: false, secret: 'should-not-leak' }
}
}
},
selfHeal: {
getActions () {
return [
{ type: 'bad\ncontrol', timestamp: 1, secret: 'should-not-leak' },
...Array.from({ length: MAX_OPERATOR_HEALTH_ACTIONS + 1 }, (_, i) => ({
type: 'repair',
check: 'memory',
appKey: 'a'.repeat(64),
timestamp: 1000 + i,
reason: 'bounded action',
error: i === MAX_OPERATOR_HEALTH_ACTIONS ? 'last error' : null,
destroyed: i,
details: {
ok: false,
heapPct: 99,
secret: 'should-not-leak'
},
secret: 'should-not-leak'
}))
]
}
}
}
})
t.is(out.status, 200)
t.alike(out.payload.checks.memory, {
ok: false,
critical: true,
heapPct: 99.5,
rssMB: 700
})
t.alike(out.payload.checks.connections, {
ok: true,
staleCount: 1,
totalConns: 3,
suggestion: 'DHT re-announce'
})
t.alike(out.payload.checks.disk, {
ok: false,
usedPct: 91.2,
freeGB: 1.5,
totalGB: 20,
error: 'disk almost full'
})
t.is(out.payload.actions.length, MAX_OPERATOR_HEALTH_ACTIONS, 'action history is capped')
t.is(out.payload.actions[0].timestamp, 1001, 'cap keeps newest self-heal actions')
t.is(out.payload.actions[out.payload.actions.length - 1].error, 'last error')
t.absent(JSON.stringify(out.payload).includes('should-not-leak'), 'raw health/action fields are dropped')
t.alike(buildHealthDetailPayload({ node: {} }).payload, { actions: [] })
})
test('api operator telemetry: storage top sanitizes summary and measured rows', (t) => {
const calls = []
const out = buildStorageTopPayload({
n: 7,
storageAccounting: {
getSummary () {
return { totalBytes: 1234, measuredEntries: 2, privatePath: '/should/not/leak' }
},
getTop (n) {
calls.push(n)
return [
{ appKey: 'b'.repeat(64), bytes: 512, measuredAt: 1000, privatePath: '/should/not/leak' },
{ appKey: 'not-hex', bytes: 1 },
{ appKey: 'c'.repeat(64), bytes: -1 }
]
}
}
})
t.is(out.status, 200)
t.alike(calls, [7])
t.alike(out.payload, {
totalBytes: 1234,
measuredEntries: 2,
entries: [{ appKey: 'b'.repeat(64), bytes: 512, measuredAt: 1000 }]
})
t.absent(JSON.stringify(out.payload).includes('should-not-leak'), 'raw storage fields are dropped')
t.alike(buildStorageTopPayload().payload, { entries: [] })
})
test('api operator telemetry: auto-heal payload distinguishes disabled and sanitizes running state', (t) => {
t.alike(buildAutoHealPayload().payload, {
enabled: false,
reason: 'AutoHeal not enabled in config'
})
const out = buildAutoHealPayload({
autoHeal: {
snapshot () {
return {
enabled: true,
running: true,
tickMs: 5000,
thresholds: {
minReplicas: 7,
minRegions: 4,
minOperators: 5,
replicaBuffer: 2,
secret: 'should-not-leak'
},
tracked: MAX_OPERATOR_AUTO_HEAL_DRIVES + 3,
below: 1,
verifyProofs: true,
proofCacheSize: 9,
privateCache: 'should-not-leak',
drives: [
{ appKey: 'bad-key', replicas: 1, secret: 'should-not-leak' },
...Array.from({ length: MAX_OPERATOR_AUTO_HEAL_DRIVES + 2 }, (_, i) => ({
appKey: (i % 2 === 0 ? 'c' : 'd').repeat(64),
replicas: i,
regions: ['eu', 'us', 'bad\nregion'],
operators: ['alice', 'bob', 'bad\noperator'],
meetsThreshold: i > 0,
haveLocally: i === 1,
backoff: { failures: i, retryInMs: 1000 + i, reason: 'should-not-leak' },
raw: [{ secret: 'should-not-leak' }]
}))
]
}
}
}
})
t.is(out.status, 200)
t.is(out.payload.enabled, true)
t.is(out.payload.running, true)
t.alike(out.payload.thresholds, {
minReplicas: 7,
minRegions: 4,
minOperators: 5,
replicaBuffer: 2
})
t.is(out.payload.drives.length, MAX_OPERATOR_AUTO_HEAL_DRIVES, 'auto-heal drives are capped')
t.alike(out.payload.drives[0], {
appKey: 'c'.repeat(64),
replicas: 0,
regions: ['eu', 'us'],
operators: ['alice', 'bob'],
meetsThreshold: false,
haveLocally: false,
backoff: { failures: 0, retryInMs: 1000 }
})
t.absent(JSON.stringify(out.payload).includes('should-not-leak'), 'raw auto-heal fields are dropped')
})
test('api operator telemetry: metrics history is retention-bounded, capped, and sanitized', (t) => {
const now = 1_000_000
const out = buildMetricsHistoryPayload({
now,
minutes: 10,
metrics: {
snapshots: [
{
timestamp: now - 5 * 60_000,
running: true,
mode: 'public',
seededApps: 7,
connections: 4,
marker: 'recent',
credential: 'should-not-leak',
holesail: { connectionKey: 'should-not-leak' },
registry: { key: 'should-not-leak' },
seeder: {
coresSeeded: 1,
totalBytesStored: 2,
totalBytesServed: 3,
capacityUsedPct: 4.5,
secret: 'should-not-leak'
},
served: {
totalBytesServed: 5,
totalBlocksServed: 6,
trackedCores: 2
},
relay: {
activeCircuits: 1,
totalCircuitsServed: 2,
totalBytesRelayed: 3,
capacityUsedPct: 50,
peersWithCircuits: 1
},
appRegistry: {
entries: 2,
anchored: 1,
unanchored: 1,
cores: 4
},
storage: {
totalBytes: 9,
measuredEntries: 2,
fullSweeps: 1,
lastFullSweepAt: now - 1000
},
disk: {
totalBytes: 100,
usedBytes: 40,
availableBytes: 60,
usedPct: 40,
checkedAt: now - 500,
status: 'warn',
mountPath: '/should/not/leak',
error: 'should-not-leak'
}
},
{ timestamp: now - 20 * 60_000, marker: 'old' },
{ timestamp: 'not-a-number', marker: 'malformed' },
null
]
}
})
t.is(out.status, 200)
t.alike(out.payload, [{
timestamp: now - 5 * 60_000,
running: true,
mode: 'public',
seededApps: 7,
connections: 4,
relay: {
activeCircuits: 1,
totalCircuitsServed: 2,
totalBytesRelayed: 3,
capacityUsedPct: 50,
peersWithCircuits: 1
},
seeder: {
coresSeeded: 1,
totalBytesStored: 2,
totalBytesServed: 3,
capacityUsedPct: 4.5
},
served: {
totalBytesServed: 5,
totalBlocksServed: 6,
trackedCores: 2
},
storage: {
totalBytes: 9,
measuredEntries: 2,
fullSweeps: 1,
lastFullSweepAt: now - 1000
},
appRegistry: {
entries: 2,
anchored: 1,
unanchored: 1,
cores: 4
},
disk: {
totalBytes: 100,
usedBytes: 40,
availableBytes: 60,
usedPct: 40,
checkedAt: now - 500,
status: 'warn'
}
}])
t.absent(JSON.stringify(out.payload).includes('should-not-leak'), 'raw internal fields are dropped')
const missing = buildMetricsHistoryPayload({ metrics: null })
t.is(missing.status, 503)
t.alike(missing.payload, { error: 'Metrics not enabled' })
const malformedStore = buildMetricsHistoryPayload({
now,
minutes: 10,
metrics: { snapshots: null }
})
t.is(malformedStore.status, 200)
t.alike(malformedStore.payload, [])
const capped = buildMetricsHistoryPayload({
now,
minutes: 10,
metrics: {
snapshots: Array.from({ length: MAX_METRICS_HISTORY_SNAPSHOTS + 5 }, (_, i) => ({
timestamp: now - (MAX_METRICS_HISTORY_SNAPSHOTS + 5 - i),
connections: i,
marker: `row-${i}`
}))
}
})
t.is(capped.status, 200)
t.is(capped.payload.length, MAX_METRICS_HISTORY_SNAPSHOTS, 'history response is capped')
t.is(capped.payload[0].connections, 5, 'cap keeps newest rows in chronological order')
t.absent(JSON.stringify(capped.payload).includes('row-'), 'cap output keeps the shaped metric surface')
})