import assert from 'node:assert/strict'; import { readFileSync } from 'node:fs'; import test from 'node:test'; import { renderPrometheusMetrics, sendPrometheusMetrics, } from '../../dist/server/prometheusMetrics.js'; import { createPrometheusMetricsRoute } from '../../dist/server/http/routes/prometheusMetricsRoute.js'; const observedAt = '2026-08-08T10:00:00.000Z'; const snapshot = { traffic: { gatewayBytes: '9007199254740993', proxyBytes: '3000', gatewayObservedAt: observedAt, proxyObservedAt: observedAt, }, devices: [{ id: 'dev_0011223344556677', alias: 'ТВ "Зал"\\основной\nэкран', hostname: 'tv.local', ip: '192.168.50.7', mac: '00:11:22:33:44:55', uploadBytes: '9007199254740993', downloadBytes: '100', trafficObservedAt: observedAt, proxyUploadBytes: '0', proxyDownloadBytes: '0', proxyTrafficObservedAt: null, }], domainTraffic: { observedAt, overflowConnections: '2', attributionEvents: { unresolved_host: '3', unknown_device: '4', unsupported_source: '5', }, series: [{ deviceId: 'dev_0011223344556677', domain: 'chatgpt.com', service: 'OpenAI / ChatGPT', source: 'proxy', uploadBytes: '12', downloadBytes: '345', }], }, }; test('Prometheus exposition keeps exact counters, stable identity and escaped names', () => { const output = renderPrometheusMetrics(snapshot); assert.match(output, /# TYPE harbor_traffic_bytes_total counter/); assert.match(output, /harbor_traffic_bytes_total\{source="gateway"\} 9007199254740993/); assert.match(output, /harbor_device_info\{device_id="dev_0011223344556677",name="ТВ \\"Зал\\"\\\\основной\\nэкран",ip="192\.168\.50\.7"\} 1/); assert.match(output, /harbor_device_traffic_bytes_total\{device_id="dev_0011223344556677",source="gateway",direction="upload"\} 9007199254740993/); assert.doesNotMatch(output, /harbor_device_traffic_bytes_total\{[^\n]*name=/); assert.doesNotMatch(output, /harbor_device_traffic_bytes_total\{[^\n]*source="proxy"/); assert.doesNotMatch(output, /00:11:22:33:44:55/); assert.match(output, /harbor_device_traffic_last_observed_timestamp_seconds\{device_id="dev_0011223344556677",source="gateway"\} 1786183200/); assert.match(output, /harbor_device_domain_traffic_bytes_total\{device_id="dev_0011223344556677",domain="chatgpt\.com",service="OpenAI \/ ChatGPT",source="proxy",direction="download"\} 345/); assert.match(output, /harbor_domain_traffic_last_observed_timestamp_seconds 1786183200/); assert.match(output, /harbor_domain_traffic_overflow_connections_total 2/); assert.match(output, /# TYPE harbor_domain_traffic_attribution_events_total counter/); assert.match(output, /harbor_domain_traffic_attribution_events_total\{outcome="unresolved_host"\} 3/); assert.match(output, /harbor_domain_traffic_attribution_events_total\{outcome="unknown_device"\} 4/); assert.match(output, /harbor_domain_traffic_attribution_events_total\{outcome="unsupported_source"\} 5/); assert.doesNotMatch(output, /harbor_device_domain_traffic_bytes_total\{[^\n]*name=/); assert.equal(output.endsWith('\n'), true); }); test('Prometheus response uses the negotiated legacy text contract without mutating the snapshot', () => { const before = structuredClone(snapshot); const response = { writeHead(status, headers) { this.status = status; this.headers = headers; }, end(body) { this.body = body; }, }; sendPrometheusMetrics(response, snapshot); assert.equal(response.status, 200); assert.equal(response.headers['content-type'], 'text/plain; version=0.0.4; charset=utf-8'); assert.match(response.body, /harbor_device_info/); assert.deepEqual(snapshot, before); }); test('invalid canonical counters fail the scrape instead of publishing corrupt values', () => { const invalid = { traffic: { gatewayBytes: 'broken', proxyBytes: '0' }, devices: [] }; assert.throws( () => renderPrometheusMetrics(invalid), /Invalid Prometheus counter/, ); assert.throws( () => sendPrometheusMetrics({ writeHead() { throw new Error('headers sent'); } }, invalid), /Invalid Prometheus counter/, ); }); function routeResponse() { return { writeHead(status, headers) { this.status = status; this.headers = headers; }, end(body) { this.body = body; }, }; } test('Prometheus route reads one current snapshot and preserves the exact text contract', async () => { let reads = 0; let refreshes = 0; const route = createPrometheusMetricsRoute({ deviceInventory: { metricsSnapshot: () => { reads += 1; return snapshot; }, refresh: () => { refreshes += 1; }, }, }); const res = routeResponse(); assert.equal(await route.handle({ method: 'GET', url: '/metrics?source=prometheus' }, res), true); assert.equal(reads, 1); assert.equal(refreshes, 0); assert.equal(res.status, 200); assert.equal(res.headers['content-type'], 'text/plain; version=0.0.4; charset=utf-8'); assert.equal(res.body, renderPrometheusMetrics(snapshot)); assert.equal(res.body.endsWith('\n'), true); }); test('Prometheus route preserves path, method and nullable inventory gating', async () => { let reads = 0; const route = createPrometheusMetricsRoute({ deviceInventory: { metricsSnapshot: () => { reads += 1; return snapshot; } }, }); for (const url of ['/metrics/', '/api/metrics', '/']) { assert.equal(await route.handle({ method: 'GET', url }, routeResponse()), false); } await assert.rejects( route.handle({ method: 'POST', url: '/metrics' }, routeResponse()), (error) => error.code === 'ENDPOINT_NOT_FOUND', ); assert.equal(reads, 0); const clientRoute = createPrometheusMetricsRoute({ deviceInventory: null }); await assert.rejects( clientRoute.handle({ method: 'GET', url: '/metrics' }, routeResponse()), (error) => error.code === 'ENDPOINT_NOT_FOUND', ); }); test('Prometheus route propagates snapshot and renderer failures unchanged', async () => { const snapshotError = new Error('snapshot failed'); const snapshotRoute = createPrometheusMetricsRoute({ deviceInventory: { metricsSnapshot: () => { throw snapshotError; } }, }); await assert.rejects( snapshotRoute.handle({ method: 'GET', url: '/metrics' }, routeResponse()), (error) => error === snapshotError, ); const rendererRoute = createPrometheusMetricsRoute({ deviceInventory: { metricsSnapshot: () => ({ traffic: { gatewayBytes: 'broken', proxyBytes: '0' } }), }, }); await assert.rejects( rendererRoute.handle({ method: 'GET', url: '/metrics' }, routeResponse()), /Invalid Prometheus counter/, ); }); test('Prometheus route is the sole HTTP owner before API and static fallback', () => { const index = readFileSync(new URL('../../src/server/index.ts', import.meta.url), 'utf8'); const route = readFileSync( new URL('../../src/server/http/routes/prometheusMetricsRoute.ts', import.meta.url), 'utf8', ); assert.match(index, /createPrometheusMetricsRoute\(\{ deviceInventory \}\)/); const delegation = index.indexOf('prometheusMetricsRoute.handle(req, res)'); const apiDispatch = index.indexOf("requestUrl.pathname.startsWith('/api/')"); const staticFallback = index.indexOf(': serveStatic(req, res)'); assert.ok(delegation >= 0 && delegation < apiDispatch && apiDispatch < staticFallback); assert.doesNotMatch(index, /['"]\/metrics['"]/); assert.doesNotMatch(index, /metricsSnapshot\(\)|sendPrometheusMetrics/); assert.match(route, /pathname !== '\/metrics'/); assert.match(route, /deviceInventory\.metricsSnapshot\(\)/); assert.doesNotMatch(route, /\.refresh\(/); });