import assert from 'node:assert/strict'; import fs from 'node:fs'; import path from 'node:path'; import test from 'node:test'; import { assertLiveTrafficSnapshot } from '../../.test-dist/src/shared/liveTraffic.js'; import { groupTrafficConnections, reconcileTrafficGroups, sortTrafficGroups, summarizeTrafficOrigins, trafficConnectionMatchesFilters, trafficGroupMatches, } from '../../.test-dist/src/web/features/traffic/trafficRows.js'; const root = path.resolve(import.meta.dirname, '../..'); const source = (file) => fs.readFileSync(path.join(root, file), 'utf8'); const app = source('src/web/App.tsx'); const api = source('src/web/api/harborClient.ts'); const page = source('src/web/components/ClientOverviewPage.tsx'); const feature = source('src/web/features/traffic/TrafficFeature.tsx'); const rowModel = source('src/web/features/traffic/trafficRows.ts'); const boundary = source('src/web/features/traffic/index.ts'); const styles = source('src/web/styles/features/traffic.css'); const primitives = source('src/web/styles/primitives.css'); const validSnapshot = { apiVersion: 1, epoch: 'epoch-1', sequence: 1, observedAt: '2026-08-31T10:00:00.000Z', capabilities: { lifecycle: true, deviceAttribution: false, applicationAttribution: false }, source: { transport: 'native', state: 'live', completeness: 'lifecycle', singBoxVersion: '1.14.0-rc.5', singBoxApiVersion: 1, error: null, unattributedUploadBytes: '0', unattributedDownloadBytes: '0', }, summary: { active: 1, recent: 0, visible: 1, recognized: 1, unresolved: 0, unresolvedOrigin: 0, truncated: false, }, connections: [{ id: 'connection-1', startedAt: '2026-08-31T09:59:59.000Z', closedAt: null, inbound: { tag: 'mixed-in', type: 'mixed' }, network: 'tcp', protocol: 'tls', source: { ip: '127.0.0.1', port: 53000 }, destination: { domain: 'example.com', ip: '203.0.113.1', port: 443, provenance: 'sing-box' }, origin: { kind: 'this-mac', id: null, label: 'Этот Mac', provenance: 'client-runtime' }, route: { kind: 'vpn', scope: 'local-sing-box', outbound: 'proxy', outboundType: 'selector', chain: ['proxy'], rule: 'default', }, traffic: { uploadBytes: '10', downloadBytes: '20', uploadBytesPerSecond: '1', downloadBytesPerSecond: '2', }, }], }; function trafficConnection(id, overrides = {}) { const base = validSnapshot.connections[0]; return { ...base, ...overrides, id, inbound: { ...base.inbound, ...overrides.inbound }, source: { ...base.source, ...overrides.source }, destination: { ...base.destination, ...overrides.destination }, origin: { ...base.origin, ...overrides.origin }, route: { ...base.route, ...overrides.route }, traffic: { ...base.traffic, ...overrides.traffic }, }; } test('live traffic runtime boundary accepts the DTO and rejects inconsistent or malformed snapshots', () => { assert.equal(assertLiveTrafficSnapshot(validSnapshot), validSnapshot); assert.throws(() => assertLiveTrafficSnapshot({ ...validSnapshot, summary: { ...validSnapshot.summary, visible: 0 }, }), /Inconsistent traffic summary/); assert.throws(() => assertLiveTrafficSnapshot({ ...validSnapshot, connections: [{ ...validSnapshot.connections[0], route: { ...validSnapshot.connections[0].route, outbound: 42 }, }], }), /Invalid traffic connection/); assert.throws(() => assertLiveTrafficSnapshot({ ...validSnapshot, summary: { ...validSnapshot.summary, active: 0, recent: 1, recognized: 0 }, connections: [{ ...validSnapshot.connections[0], closedAt: '2026-08-31T10:00:00.000Z', }], }), /Invalid closed traffic rate/); assert.throws(() => assertLiveTrafficSnapshot({ ...validSnapshot, summary: { ...validSnapshot.summary, active: 2, visible: 2, recognized: 2 }, connections: [validSnapshot.connections[0], validSnapshot.connections[0]], }), /Invalid traffic connection identity/); assert.throws(() => assertLiveTrafficSnapshot({ ...validSnapshot, connections: [{ ...validSnapshot.connections[0], closedAt: '2026-08-31 10:00:00Z' }], }), /Invalid traffic connection identity/); assert.throws(() => assertLiveTrafficSnapshot({ ...validSnapshot, connections: [{ ...validSnapshot.connections[0], traffic: { ...validSnapshot.connections[0].traffic, uploadBytes: 10 }, }], }), /Invalid traffic byte value/); }); test('Mac and Gateway traffic drawers use one feature boundary and the cached read-only endpoint', () => { assert.match(boundary, /TrafficPanel,[\s\S]*TrafficToggle,[\s\S]*useTrafficFeature/); assert.match(api, /traffic: \{[\s\S]*live: \(\) => request\('\/api\/traffic\/live'\)/); assert.match(app, /loadLiveTraffic: api\.traffic\.live/); assert.match(page, /useTrafficFeature\(\{[\s\S]*enabled: true,[\s\S]*isGateway,[\s\S]*loadLiveTraffic: actions\.loadLiveTraffic/); assert.match(feature, /interface TrafficFeatureOptions \{[\s\S]*isGateway: boolean/); assert.match(feature, /return \{[\s\S]*isGateway,[\s\S]*isOpen/); const rail = page.slice(page.indexOf('')); const devices = rail.indexOf('= 0 && traffic > devices && diagnostics > traffic); assert.equal((rail.match(/}/); assert.doesNotMatch(page, /!isGateway && hasSubscription && { assert.match(feature, /const POLL_MS = 1_000/); assert.match(feature, /if \(!enabled \|\| !isOpen \|\| paused \|\| view !== 'live'\) return undefined/); assert.match(feature, /assertLiveTrafficSnapshot\(await loadLiveTraffic\(\)\)/); assert.match(feature, /setSnapshot\(next\)[\s\S]*setRequestState\('ready'\)/); assert.match(feature, /catch \{[\s\S]*setRequestState\('error'\)/); assert.match(feature, /timer = setTimeout\(poll, POLL_MS\)/); assert.match(feature, /cancelled = true[\s\S]*clearTimeout\(timer\)/); assert.match(feature, /aria-pressed=\{feature\.paused\}[\s\S]*Продолжить[\s\S]*Пауза/); assert.doesNotMatch(feature, /setInterval|WebSocket|EventSource/); }); test('traffic drawer exposes the requested truthful states and accessible controls', () => { for (const copy of [ 'VPN остановлен', 'Активных соединений пока нет', 'Инспектор трафика временно недоступен', 'Эта версия sing-box не поддерживает инспектор трафика', 'Инспектор трафика выключен в настройках Harbor Connect.', 'Инспектор трафика выключен в настройках Harbor Gateway.', 'Показан последний полученный снимок', 'Только трафик через Harbor Connect. Приложения macOS недоступны внутри Docker.', 'Только соединения, прошедшие через sing-box Gateway. Трафик, обходящий sing-box напрямую, здесь не виден.', ]) assert.match(feature, new RegExp(copy.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))); assert.match(feature, /feature\.view === 'live' \? `\$\{snapshot\?\.summary\.active \|\| 0\} АКТИВНЫХ` : 'ИСТОРИЯ'/); assert.match(feature, /feature\.isGateway[\s\S]*Инспектор трафика выключен в настройках Harbor Gateway\.[\s\S]*Инспектор трафика выключен в настройках Harbor Connect\./); assert.match(feature, /feature\.isGateway[\s\S]*Только соединения, прошедшие через sing-box Gateway\. Трафик, обходящий sing-box напрямую, здесь не виден\.[\s\S]*Только трафик через Harbor Connect\. Приложения macOS недоступны внутри Docker\./); assert.match(feature, /aria-label="Найти устройство"/); assert.match(feature, /aria-label="Найти сайт, IP или сервис"/); assert.match(feature, /role="group" aria-label="Фильтр по маршруту"/); assert.match(feature, /role="group" aria-label="Фильтр по качеству распознавания"/); assert.match(feature, /aria-pressed=\{routeFilter === value\}/); assert.match(feature, /aria-pressed=\{qualityFilter === value\}/); assert.match(feature, /const \[expandedId, setExpandedId\] = useState\(''\)/); assert.match(feature, /aria-expanded=\{expanded\}[\s\S]*aria-controls=\{detailsId\}/); assert.match(feature, /Источник[\s\S]*Назначение[\s\S]*Правило[\s\S]*Цепочка/); assert.doesNotMatch(feature, /closeConnection|reroute|sessionStorage/); assert.match(feature, /TrafficHistoryPanel/); }); test('traffic retention and grouping use canonical server settings and the frozen server observation clock', () => { assert.doesNotMatch(feature, /localStorage|sessionStorage/); assert.match(feature, /TRAFFIC_RETENTION_OPTIONS\.map/); assert.match(feature, /feature\.updateSettings\(settings\)/); assert.match(api, /updateSettings:[\s\S]*\/api\/traffic\/settings[\s\S]*expectedRevision/); assert.match(app, /onUpdateTrafficSettings=[\s\S]*api\.traffic\.updateSettings/); assert.match(feature, /Завершённые/); assert.match(feature, /aria-label="Время показа завершённых соединений"/); assert.match(feature, /snapshot\?\.observedAt \? Date\.parse\(snapshot\.observedAt\) : Number\.NaN/); assert.match(feature, /connection\.closedAt === null \|\| !Number\.isFinite\(snapshotTime\)/); assert.match(feature, /snapshotTime - Date\.parse\(connection\.closedAt\) < retentionSeconds \* 1_000/); assert.match(feature, /groupTrafficConnections\(selectedConnections, grouping\)/); assert.match(feature, /trafficGroupMatches\(group, query, 'all', 'all'\)/); assert.match(feature, /group\.connections\.length === 1[\s\S]*`Завершено · \$\{group\.protocol\}`/); assert.match(feature, /Соединения сгруппированы по назначению, протоколу и маршруту\./); }); test('traffic groups combine compatible UUIDs with exact byte sums and whole-group search', () => { const groups = groupTrafficConnections([ trafficConnection('active', { destination: { domain: 'yandex.ru', ip: '203.0.113.1' }, traffic: { uploadBytes: '10', downloadBytes: '20', uploadBytesPerSecond: '1', downloadBytesPerSecond: '2', }, }), trafficConnection('closed-a', { closedAt: '2026-08-31T09:59:59.500Z', destination: { domain: 'yandex.ru', ip: '203.0.113.2' }, traffic: { uploadBytes: '30', downloadBytes: '40', uploadBytesPerSecond: '0', downloadBytesPerSecond: '0', }, }), trafficConnection('closed-b', { closedAt: '2026-08-31T09:59:59.700Z', destination: { domain: 'yandex.ru', ip: '203.0.113.2' }, traffic: { uploadBytes: '9007199254740993', downloadBytes: '9007199254740995', uploadBytesPerSecond: '0', downloadBytesPerSecond: '0', }, }), ]); assert.equal(groups.length, 1); assert.equal(groups[0].label, 'yandex.ru'); assert.equal(groups[0].activeCount, 1); assert.equal(groups[0].recentCount, 2); assert.deepEqual(groups[0].connections.map(({ id }) => id), ['active', 'closed-a', 'closed-b']); assert.deepEqual(groups[0].destinationIps, ['203.0.113.1', '203.0.113.2']); assert.deepEqual(groups[0].traffic, { uploadBytes: '9007199254741033', downloadBytes: '9007199254741055', uploadBytesPerSecond: '1', downloadBytesPerSecond: '2', }); assert.equal(trafficGroupMatches(groups[0], '203.0.113.2', 'all', 'all'), true); assert.equal(trafficGroupMatches(groups[0], 'missing.test', 'all', 'all'), false); assert.equal(trafficGroupMatches(groups[0], '', 'vpn', 'recognized'), true); assert.equal(trafficGroupMatches(groups[0], '', 'direct', 'all'), false); }); test('traffic groups sort by bounded frequency or latest start without changing the groups', () => { const groups = groupTrafficConnections([ trafficConnection('latest', { startedAt: '2026-08-31T09:59:00.000Z', destination: { domain: 'latest.test' }, }), ...['a', 'b', 'c'].map((id, index) => trafficConnection(`popular-${id}`, { startedAt: `2026-08-31T09:0${index}:00.000Z`, destination: { domain: 'popular.test' }, })), ...['a', 'b'].map((id) => trafficConnection(`frequent-recent-${id}`, { startedAt: '2026-08-31T09:59:00.000Z', destination: { domain: 'frequent-recent.test' }, })), ]); const original = groups.map(({ label }) => label); assert.deepEqual(sortTrafficGroups(groups, 'popular').map(({ label }) => label), [ 'popular.test', 'frequent-recent.test', 'latest.test', ]); assert.deepEqual(sortTrafficGroups(groups, 'recent').map(({ label }) => label), [ 'frequent-recent.test', 'latest.test', 'popular.test', ]); assert.deepEqual(groups.map(({ label }) => label), original); assert.match(feature, /const \{ grouping, sort: sortMode, retentionSeconds \} = feature\.settings/); assert.match(feature, /role="group" aria-label="Сортировка соединений"/); assert.match(feature, /\['popular', 'Популярные'\][\s\S]*\['recent', 'Последние'\]/); assert.match(feature, /aria-pressed=\{sortMode === value\}/); }); test('site grouping combines devices with exact per-device totals while device grouping keeps them separate', () => { const first = trafficConnection('phone', { origin: { kind: 'device', id: 'dev_0000000000000001', label: 'Телефон', provenance: 'source-ip' }, source: { ip: '192.168.50.10' }, destination: { domain: 'youtube.com' }, traffic: { uploadBytes: '10', downloadBytes: '20' }, }); const second = trafficConnection('tv', { origin: { kind: 'device', id: 'dev_0000000000000002', label: 'ТВ', provenance: 'source-ip' }, source: { ip: '192.168.50.11' }, destination: { domain: 'youtube.com' }, traffic: { uploadBytes: '30', downloadBytes: '40' }, }); assert.equal(groupTrafficConnections([first, second], 'device').length, 2); const [site] = groupTrafficConnections([first, second], 'site'); assert.equal(site.connections.length, 2); assert.equal(site.origins.length, 2); assert.deepEqual(site.traffic, { uploadBytes: '40', downloadBytes: '60', uploadBytesPerSecond: '2', downloadBytesPerSecond: '4', }); assert.deepEqual(site.origins.map(({ label, connections, traffic }) => ({ label, connections, traffic })), [ { label: 'ТВ', connections: 1, traffic: { uploadBytes: '30', downloadBytes: '40' } }, { label: 'Телефон', connections: 1, traffic: { uploadBytes: '10', downloadBytes: '20' } }, ]); const ranked = summarizeTrafficOrigins([first, first, second]); assert.deepEqual(ranked.map(({ label, connections }) => [label, connections]), [['Телефон', 2], ['ТВ', 1]]); assert.equal(trafficConnectionMatchesFilters(first, 'vpn', 'recognized'), true); assert.equal(trafficConnectionMatchesFilters(first, 'direct', 'all'), false); }); test('traffic grouping keeps incompatible and unknown destinations separate', () => { const base = trafficConnection('base', { destination: { domain: 'example.com', ip: '203.0.113.1' } }); const same = trafficConnection('same', { destination: { domain: 'Example.COM', ip: '203.0.113.2' } }); assert.equal(groupTrafficConnections([base, same]).length, 1); const variants = [ ['domain', { destination: { domain: 'www.example.com', ip: '203.0.113.1' } }], ['port', { destination: { domain: 'example.com', ip: '203.0.113.1', port: 80 } }], ['network', { network: 'udp' }], ['protocol', { protocol: 'http' }], ['origin', { origin: { kind: 'device', id: 'device-1', label: 'iPhone', provenance: 'source-ip' } }], ['route kind', { route: { kind: 'other' } }], ['route outbound', { route: { outbound: 'other-proxy' } }], ['route outbound type', { route: { outboundType: 'vless' } }], ['route chain', { route: { chain: ['proxy', 'hop'] } }], ['route rule', { route: { rule: 'other-rule' } }], ]; for (const [name, overrides] of variants) { assert.equal(groupTrafficConnections([base, trafficConnection(String(name), overrides)]).length, 2, name); } assert.equal(groupTrafficConnections([ trafficConnection('ip-a', { destination: { domain: null, ip: '203.0.113.1' } }), trafficConnection('ip-b', { destination: { domain: null, ip: '203.0.113.2' } }), ]).length, 2); assert.equal(groupTrafficConnections([ trafficConnection('unknown-a', { destination: { domain: null, ip: null, provenance: 'unknown' } }), trafficConnection('unknown-b', { destination: { domain: null, ip: null, provenance: 'unknown' } }), ]).length, 2); const deviceOrigin = { kind: 'device', id: 'device-1', label: 'iPhone', provenance: 'source-ip' }; const [deviceGroup] = groupTrafficConnections([ trafficConnection('device', { origin: deviceOrigin }), ]); assert.deepEqual(deviceGroup.origin, deviceOrigin); assert.match(feature, /const source = onlyConnection[\s\S]*group\.origin\.label[\s\S]*onlyConnection\.source\.ip[\s\S]*group\.connections\.length/); }); test('traffic groups stay mounted and inert through exit while the same group cancels removal', () => { const group = (id, count = 1) => ({ id, connections: Array.from({ length: count }) }); let rows = reconcileTrafficGroups([], [group('a'), group('b'), group('c')], false); rows = reconcileTrafficGroups(rows, [group('b', 2)], false); assert.deepEqual(rows.map((row) => [row.group.id, row.group.connections.length, row.exiting]), [ ['a', 1, true], ['b', 2, false], ['c', 1, true], ]); rows = reconcileTrafficGroups(rows, [group('a'), group('b', 3), group('c')], false); assert.deepEqual(rows.map((row) => [row.group.id, row.group.connections.length, row.exiting]), [ ['a', 1, false], ['b', 3, false], ['c', 1, false], ]); assert.deepEqual(reconcileTrafficGroups(rows, [], true), []); assert.match(rowModel, /const desiredIds = new Set\(desired\.map\(\(group\) => group\.id\)\)/); assert.match(feature, /groupTrafficConnections\(selectedConnections, grouping\)/); assert.match(feature, /trafficGroupMatches\(group, query, 'all', 'all'\)/); assert.match(feature, /reconcileTrafficGroups\(current, groups, immediate\)/); assert.match(feature, /setExpandedId\(\(current\) => desiredIds\.has\(current\) \? current : ''\)/); assert.match(feature, /inert=\{exiting \|\| undefined\}/); assert.match(feature, /event\.target === event\.currentTarget[\s\S]*event\.animationName === 'client-traffic-connection-out'/); assert.match(feature, /row\.group\.id !== id \|\| !row\.exiting/); assert.match(feature, /matchMedia\('\(prefers-reduced-motion: reduce\)'\)[\s\S]*media\.addEventListener\('change', update\)[\s\S]*media\.removeEventListener\('change', update\)/); assert.match(feature, /const immediate = reducedMotion[\s\S]*\['disabled', 'incompatible', 'stopped'\]\.includes/); assert.match(feature, /aria-label="Группы активных и недавно завершённых соединений"/); assert.match(feature, /group\.connections\.length > 1[\s\S]*×\$\{group\.connections\.length\}/); assert.match(feature, /group\.activeCount > 0 && /); assert.match(feature, /displayedGroups\.map\(\(row\) => Активных распознано<\/b>/); assert.match(feature, /Активных требует внимания<\/b>/); }); test('traffic styling preserves the shared drawer geometry and minimal motion', () => { assert.match(styles, /\.client-traffic-toggle svg \{[\s\S]*width: 24px;[\s\S]*height: 24px/); assert.match(primitives, /\.client-drawer \{[\s\S]*width: min\(580px, 100vw\)/); assert.match(styles, /@media \(max-width: 768px\) \{[\s\S]*\.client-traffic \{[\s\S]*width: 100vw/); assert.doesNotMatch(styles, /overflow-y:\s*(?:auto|scroll)/); assert.match(styles, /\.client-traffic-meta button \{[\s\S]*width: 96px/); assert.match(styles, /\.client-traffic-details \{[\s\S]*animation: client-traffic-details-in 180ms/); assert.match(styles, /\.client-traffic-connection \{[\s\S]*animation: client-traffic-connection-in 420ms/); assert.match(styles, /\.client-traffic-connection\.is-exiting \{[\s\S]*animation: client-traffic-connection-out 240ms/); const detailMotion = /@keyframes client-traffic-details-in \{([\s\S]*?)\n\}/.exec(styles)?.[1] || ''; assert.match(detailMotion, /opacity:/); assert.match(detailMotion, /translateY/); assert.doesNotMatch(detailMotion, /height|width|margin|padding|scale|filter/); for (const name of ['client-traffic-connection-in', 'client-traffic-connection-out']) { const rowMotion = new RegExp(`@keyframes ${name} \\{([\\s\\S]*?)\\n\\}`).exec(styles)?.[1] || ''; assert.match(rowMotion, /opacity:/); assert.match(rowMotion, /translateY/); assert.doesNotMatch(rowMotion, /height|width|margin|padding|scale|filter/); } assert.match(styles, /@media \(prefers-reduced-motion: reduce\) \{[\s\S]*\.client-traffic-connection,[\s\S]*\.client-traffic-details,[\s\S]*\.client-traffic-device-list \{[\s\S]*animation: none/); });