import assert from 'node:assert/strict'; import test from 'node:test'; import { renderPrometheusMetrics, sendPrometheusMetrics, } from '../../src/server/prometheusMetrics.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', 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.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/, ); });