Files
harbor-net/test/server/prometheus-metrics.test.js
T
dokril ca53b671ee
Build and Deploy Gateway / build-and-push (push) Successful in 14s
Build and Deploy Gateway / deploy (push) Successful in 13s
Add domain traffic metrics and Grafana dashboard
2026-08-08 01:57:00 +03:00

92 lines
3.5 KiB
JavaScript

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/,
);
});