import assert from 'node:assert/strict'; import fs from 'node:fs'; import path from 'node:path'; import test from 'node:test'; const root = path.resolve(import.meta.dirname, '../..'); const overview = fs.readFileSync(path.join(root, 'src/web/components/ClientOverviewPage.jsx'), 'utf8'); const instructions = fs.readFileSync(path.join(root, 'src/web/instructions.js'), 'utf8'); const prometheus = fs.readFileSync(path.join(root, 'src/web/prometheus.js'), 'utf8'); const server = fs.readFileSync(path.join(root, 'src/server/index.js'), 'utf8'); const styles = fs.readFileSync(path.join(root, 'src/web/styles.css'), 'utf8'); const dashboard = JSON.parse(fs.readFileSync(path.join(root, 'monitoring/grafana/harbor-gateway.json'), 'utf8')); test('Gateway info drawer contains copyable Prometheus and Grafana instructions', () => { assert.match(instructions, /\.\.\.\(isGateway \? \[\{/); assert.match(instructions, /id: 'prometheus'/); assert.match(instructions, /title: 'Prometheus и Grafana'/); assert.match(instructions, /prometheusScrapeConfig\(controlHost\)/); assert.match(instructions, /label: 'prometheus\.yml'/); assert.match(instructions, /label: 'Grafana dashboard'/); assert.match(prometheus, /monitoring\/grafana\/harbor-gateway\.json/); assert.match(prometheus, /scrape_interval: 30s[\s\S]*metrics_path: \/metrics/); assert.match(overview, /const controlHost = window\.location\.host/); assert.match(overview, /client-copy-label">Скопировать/); assert.doesNotMatch(overview, /prometheus-toggle|Prometheus<\/span><\/button>/); assert.match(styles, /\.client-instruction-copy-button \{[\s\S]*width: 104px;[\s\S]*min-width: 104px/); assert.match(styles, /@media \(prefers-reduced-motion: reduce\)[\s\S]*\.client-copy-feedback/); }); test('metrics route reads the current snapshot before static fallback', () => { const metricsRoute = server.indexOf("requestUrl.pathname === '/metrics'"); const staticFallback = server.indexOf(': serveStatic(req, res)'); assert.ok(metricsRoute >= 0 && metricsRoute < staticFallback); assert.match(server, /requestUrl\.pathname === '\/metrics'[\s\S]*deviceInventory\.snapshot\(\)/); assert.doesNotMatch(server.slice(metricsRoute, staticFallback), /deviceInventory\.refresh\(/); }); test('Grafana dashboard covers global and named per-device traffic', () => { const expressions = dashboard.panels.flatMap((panel) => panel.targets || []).map(({ expr }) => expr).filter(Boolean); const titles = dashboard.panels.map(({ title }) => title); assert.equal(dashboard.title, 'Harbor Gateway Traffic'); assert.ok(titles.includes('Всего учтено Harbor')); assert.ok(titles.includes('Общая скорость за 5 минут')); assert.ok(titles.includes('Устройства по объёму')); assert.ok(titles.includes('Скорость выбранных устройств')); assert.ok(expressions.some((expression) => expression.includes('harbor_traffic_bytes_total'))); assert.ok(expressions.some((expression) => expression.includes('harbor_device_traffic_bytes_total'))); assert.ok(expressions.some((expression) => expression.includes('group_left (name, ip)'))); assert.equal(dashboard.templating.list[0].query.query, 'label_values(harbor_device_info, name)'); });