import assert from 'node:assert/strict'; import fs from 'node:fs'; import path from 'node:path'; import test from 'node:test'; import { fileURLToPath } from 'node:url'; import { formatByteString, formatLastSeen, positiveByteDelta, sortDevicesByTraffic, } from '../../src/web/utils/format.js'; const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..'); const overview = fs.readFileSync(path.join(root, 'src/web/components/ClientOverviewPage.jsx'), 'utf8'); const panel = fs.readFileSync(path.join(root, 'src/web/components/DevicesPanel.jsx'), 'utf8'); const api = fs.readFileSync(path.join(root, 'src/web/api.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'); test('Gateway device inventory uses the existing accessible responsive drawer', () => { assert.match(overview, /isGateway && latestDevice\[key\] !== device\[key\]\)[\s\S]*api\.devices\.update\(device\.id, patch, latest\.revision\)/); assert.match(panel, /deviceNodes\.current\.get\(id\)\?\.animate/); assert.match(panel, /prefers-reduced-motion: reduce/); assert.match(api, /refresh: \(\) => request\('\/api\/devices\/refresh', \{ method: 'POST' \}\)/); assert.match(api, /setPolicy: \(id, mode, expectedRevision\) => request\(`\/api\/devices\/\$\{id\}\/policy`/); assert.match(server, /requestUrl\.pathname === '\/api\/devices\/refresh'[\s\S]*deviceInventory\.refresh\(\)/); assert.match(server, /\/api\\\/devices\\\/\(dev_\[a-f0-9\]\{16\}\)\\\/policy\$[\s\S]*deviceInventory\.setPolicy/); assert.match(panel, /Изменить название<\/Tooltip>/); assert.match(panel, /copyText\(device\.ip\)/); assert.match(panel, /client-device-name-primary[\s\S]*client-device-name-ip[\s\S]*client-device-name-feedback/); assert.match(panel, /client-device-name-feedback[\s\S]*'copied!'/); assert.match(panel, /COPY_FEEDBACK_MS = 5_000/); assert.match(panel, /client-device-name-feedback[\s\S]*client-device-edit-wrap[\s\S]*client-device-last-seen/); assert.doesNotMatch(panel, /client-device-title/); assert.match(panel, /online \? 'В сети' : /); assert.match(panel, /aria-label=\{`\$\{online \? 'В сети' : 'Не в сети'\}\. Последний контакт: \$\{seen\.tooltip\}`\}/); assert.doesNotMatch(panel, /client-device-status|STATUS_LABELS/); assert.doesNotMatch(panel, /client-text-morph-goo/); assert.doesNotMatch(panel, /client-device-addresses|client-device-manufacturer|client-device-meta/); assert.doesNotMatch(panel, /device\.interface/); assert.doesNotMatch(panel, /device\.mac/); assert.doesNotMatch(panel, /client-device-identity|Пояснение идентификации устройства|ⓘ/); assert.match(panel, /device\.confidence === 'ambiguous'/); assert.match(panel, /sortDevicesByTraffic\(snapshot\?\.devices, sortDirection\)/); assert.match(panel, /Трафик временно не обновляется/); assert.match(panel, /Учитывается только трафик, который прошёл через Harbor/); assert.match(panel, /Gateway<\/b>Прокси<\/b>Всего<\/b>\{totalTraffic\}<\/strong>/); assert.match(panel, /positiveByteDelta\(previous\.gateway, gateway\)[\s\S]*positiveByteDelta\(previous\.proxy, proxy\)/); assert.match(panel, /setTimeout\(\(\) => setTrafficDeltas\(\{\}\), TRAFFIC_DELTA_MS\)/); assert.doesNotMatch(panel, /client-device-traffic client-tooltip-anchor|\{trafficLabel\}<\/Tooltip>/); assert.match(panel, /source\?\.traffic\?\.proxy\?\.error/); assert.match(panel, /client-device-pin-wrap[\s\S]*client-device-main[\s\S]*client-device-traffic[\s\S]*client-device-policy-wrap/); assert.doesNotMatch(panel, /client-device-details/); assert.match(panel, /api\.devices\.setPolicy\(device\.id, mode, snapshot\.revision\)/); assert.match(panel, /requestError\.code !== 'STATE_CONFLICT'[\s\S]*latestDevice\.desiredPolicy !== device\.desiredPolicy[\s\S]*api\.devices\.setPolicy\(device\.id, mode, latest\.revision\)/); assert.match(panel, /className=\{`client-device-policy is-\$\{displayPolicy\}/); assert.match(panel, /displayPolicy === 'direct' \? Источник<\/dt>/); assert.match(panel, /aria-pressed=\{device\.pinned\}/); assert.doesNotMatch(panel, /Закрепите устройство, чтобы изменить маршрут|Сначала верните маршрут через Gateway/); assert.match(panel, /maxLength="64"[\s\S]*autoFocus/); assert.match(styles, /\.client-devices \{\s*width: min\(580px, 100vw\)/); assert.match(styles, /\.client-device \{[\s\S]*grid-template-columns: 34px minmax\(0, 1fr\) 112px 34px;[\s\S]*padding: 10px 8px/); assert.match(styles, /\.client-device-main \{[\s\S]*display: flex;[\s\S]*align-items: center/); assert.match(styles, /\.client-device-traffic-sources \{[\s\S]*display: grid/); assert.match(styles, /\.client-device-traffic-sources > span \{[\s\S]*grid-template-columns: 46px minmax\(0, 1fr\)/); assert.match(styles, /\.client-device-policy \{[\s\S]*width: 34px;[\s\S]*border-radius: 50%/); assert.match(styles, /\.client-device-name \{[\s\S]*font: 700 15px\/1\.2/); assert.match(styles, /\.client-device-last-seen \{[\s\S]*font-size: 10px/); assert.match(styles, /\.client-device-traffic-sources strong \{[\s\S]*font-size: 10px/); assert.match(styles, /\.client-device-name > span \{[^}]*opacity: 0/); assert.match(styles, /\.client-device-name > \.client-device-name-primary \{[^}]*opacity: 1/); assert.match(styles, /\.client-device-name > span \{[^}]*transition: opacity 360ms[^}]*transform 480ms/); assert.match(styles, /\.client-device-name:hover \.client-device-name-primary[\s\S]*translateY\(-0\.18em\)/); assert.match(styles, /\.client-device-name:hover \.client-device-name-ip[\s\S]*opacity: 1[\s\S]*translateY\(0\) scale\(1\)/); assert.match(styles, /\.client-device-name\.is-copied \.client-device-name-feedback[\s\S]*opacity: 1/); assert.match(styles, /\.client-device-name-feedback \{[^}]*font-size: 9px/); assert.match(styles, /\.client-device-main \{[^}]*height: 34px[\s\S]*align-items: center/); assert.match(styles, /\.client-device-last-seen \{[^}]*height: 32px[\s\S]*align-items: center/); assert.match(styles, /\.client-device-traffic-value\.has-delta > \.is-total[\s\S]*translateY\(-0\.18em\)/); assert.match(styles, /\.client-device-last-seen\.is-online \{[\s\S]*color: var\(--client-accent\)/); assert.match(styles, /\.client-text-morph-value \{[\s\S]*transition: opacity 360ms[\s\S]*filter 480ms/); assert.match(styles, /\.client-device-last-seen:hover \.client-text-morph-value\.is-relative[\s\S]*opacity: 1/); assert.match(styles, /\.client-device-pin-wrap\.client-tooltip-anchor:hover > \.client-tooltip[\s\S]*translate\(0, 0\)/); assert.match(styles, /\.client-device-pin:hover:not\(:disabled\) svg[\s\S]*translateY\(-2px\) rotate\(-12deg\)/); assert.match(styles, /\.client-device-pin\[aria-pressed="true"\] svg/); assert.match(styles, /\.client-device-edit:hover svg[\s\S]*translate\(1px, -1px\) rotate\(-4deg\)/); assert.match(styles, /\.client-devices-sort:hover \.client-devices-sort-icon[\s\S]*rotate\(360deg\)/); assert.match(styles, /\.client-devices-refresh-ring circle[\s\S]*client-devices-refresh-progress 15s/); assert.match(styles, /@media \(prefers-reduced-motion: reduce\)[\s\S]*\.client-device-policy svg[\s\S]*\.client-device-name > span[\s\S]*\.client-device-traffic-value > span[\s\S]*\.client-text-morph-value/); assert.match(styles, /\.client-drawer \{[\s\S]*z-index: 50;[\s\S]*box-shadow:/); assert.match(styles, /\.harbor-versions \{[\s\S]*z-index: 40/); }); test('device last-seen copy is compact with precise accessible and relative forms', () => { const lastSeen = new Date(2026, 7, 7, 13, 15); const now = new Date(2026, 7, 7, 13, 27); assert.deepEqual( formatLastSeen(lastSeen.toISOString(), now), { label: 'Сегодня, 13:15', relative: '12 минут назад', tooltip: '7 августа 2026 г. в 13:15 (12 минут назад)', }, ); }); test('device traffic formatting and sorting preserve uint64 precision and canonical ties', () => { assert.equal(formatByteString('9007199254740993'), '8,0 ПБ'); assert.equal(formatByteString('1536'), '1,5 КБ'); assert.equal(formatByteString('invalid'), '0 Б'); assert.equal(positiveByteDelta('1048576', '3145728'), '2,0 МБ'); assert.equal(positiveByteDelta('3145728', '3145728'), ''); assert.equal(positiveByteDelta('3145728', '1048576'), ''); const devices = [ { id: 'a', uploadBytes: '9007199254740993', downloadBytes: '0', proxyUploadBytes: '0' }, { id: 'b', uploadBytes: '9007199254740992', downloadBytes: '2' }, { id: 'd', uploadBytes: '15', downloadBytes: '5', proxyUploadBytes: '100' }, { id: 'c', uploadBytes: '10', downloadBytes: '10', proxyDownloadBytes: '100' }, { id: 'e', pinned: true, uploadBytes: '0', downloadBytes: '0' }, ]; assert.deepEqual(sortDevicesByTraffic(devices, 'desc').map(({ id }) => id), ['e', 'b', 'a', 'c', 'd']); assert.deepEqual(sortDevicesByTraffic(devices, 'asc').map(({ id }) => id), ['e', 'c', 'd', 'a', 'b']); });