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, stabilizeDevicesByTraffic, trafficAxisMid, trafficBytesPerSecond, trafficScaleRatio, } from '../../.test-dist/src/web/utils/format.js'; import { readStyleSource } from './style-source.js'; const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..'); const overview = fs.readFileSync(path.join(root, 'src/web/components/ClientOverviewPage.tsx'), 'utf8'); const connection = fs.readFileSync(path.join(root, 'src/web/features/connection/ConnectionPanel.tsx'), 'utf8'); const subscription = fs.readFileSync(path.join(root, 'src/web/features/subscription/SubscriptionFeature.tsx'), 'utf8'); const feature = fs.readFileSync(path.join(root, 'src/web/features/devices/DevicesFeature.tsx'), 'utf8'); const panel = fs.readFileSync(path.join(root, 'src/web/features/devices/DevicesPanel.tsx'), 'utf8'); const chart = fs.readFileSync(path.join(root, 'src/web/features/devices/TrafficChart.tsx'), 'utf8'); const api = fs.readFileSync(path.join(root, 'src/web/api/harborClient.ts'), 'utf8'); const server = fs.readFileSync(path.join(root, 'src/server/index.ts'), 'utf8'); const deviceRoute = fs.readFileSync(path.join(root, 'src/server/http/routes/deviceInventoryRoute.ts'), 'utf8'); const styles = readStyleSource(root); test('Gateway device inventory uses the existing accessible responsive drawer', () => { assert.match(overview, /isGateway && !current \|\| next\.revision > current\.revision/); assert.match(overview, / load\(true\),/); assert.match(feature, /requestDeviceUpdate\(device\.id, patch, snapshot\.revision\)/); assert.match(feature, /requestError\(caught\)\.code !== 'STATE_CONFLICT'[\s\S]*listDevices\(\)[\s\S]*Object\.keys\(patch\)\.some\(\(key\) => latestDevice\[key\] !== device\[key\]\)[\s\S]*requestDeviceUpdate\(device\.id, patch, latest\.revision\)/); assert.match(panel, /deviceNodes\.current\.get\(id\)\?\.animate/); assert.match(panel, /movementAnimations\.current\.get\(id\)\?\.cancel\(\)/); assert.match(panel, /previousPositions\.current\.size > 0/); assert.match(panel, /previousScrollTop\.current - currentScrollTop/); assert.match(feature, /next\.revision > current\.revision/); assert.doesNotMatch(panel, /revision >= current\.revision/); assert.match(panel, /prefers-reduced-motion: reduce/); assert.match(api, /refresh: \(\) => request\('\/api\/devices\/refresh', \{ method: 'POST' \}\)/); assert.match(api, /setPolicy:[\s\S]*`\/api\/devices\/\$\{id\}\/policy`/); assert.match(server, /createDeviceInventoryRoute\(\{/); assert.match(deviceRoute, /pathname === '\/api\/devices\/refresh'[\s\S]*deviceInventory\.refresh\(\)/); assert.match(deviceRoute, /DEVICE_POLICY_PATH[\s\S]*deviceInventory\.setPolicy/); assert.match(panel, /Изменить название<\/Tooltip>/); assert.match(panel, /copyText\(device\.ip\)/); assert.match(panel, /const hasName = Boolean\(device\.alias \|\| device\.hostname\)/); assert.match(panel, /client-device-alias-trigger[\s\S]*client-device-name-separator[\s\S]*client-device-ip/); assert.match(panel, /onClick=\{\(\) => startEditing\(device\)\}/); assert.match(panel, /\{!hasName && !editing && /); assert.match(panel, /client-device-name-heading\$\{hasName \? '' : ' is-address-only'\}\$\{editing \? ' is-editing' : ''\}/); assert.match(panel, /className="client-device-alias-input"[\s\S]*onBlur=\{\(\) => saveAlias\(device\)\}[\s\S]*event\.key === 'Enter'[\s\S]*event\.currentTarget\.blur\(\)/); assert.match(panel, /aliasBaseline\.current = \{ id: device\.id, value \}/); assert.match(panel, /style=\{\{ '--alias-width': `\$\{Math\.max\(1, alias\.length\)\}ch` \} as CSSProperties\}/); assert.doesNotMatch(panel, /aliasWidth|getBoundingClientRect\(\)\.width/); assert.match(panel, /nextAlias === aliasBaseline\.current\.value\.trim\(\)[\s\S]*setEditingId\(\(current\) => current === device\.id \? '' : current\)/); assert.doesNotMatch(panel, /client-device-alias"|Сохранить название|Отменить изменение/); assert.match(panel, /pencilAnimationId === device\.id \? ' is-writing'/); assert.match(panel, /onAnimationEnd=\{\(\) => setPencilAnimationId/); assert.match(panel, /COPY_FEEDBACK_MS = 800/); assert.match(panel, /IP \$\{device\.ip\} скопирован/); assert.doesNotMatch(panel, /client-device-name-feedback|client-device-name-primary/); 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, /stabilizeDevicesByTraffic\(snapshot\?\.devices, sortDirection, previousIds\)/); assert.match(panel, /Трафик временно не обновляется/); assert.match(panel, /Учитывается только трафик, который прошёл через Harbor/); assert.match(panel, /client-device-traffic-total[\s\S]*Всего<\/b>Gateway<\/b> 0n/); assert.match(panel, /client-device-traffic-breakdown[\s\S]*\{hasProxyTraffic && Прокси<\/b> 100 - ratio \* \(100 - TRAFFIC_CHART_HEADROOM\)/); assert.match(chart, /gatewayY: trafficChartY\(trafficScaleRatio\(gateway, max, scale\)\)[\s\S]*proxyY: trafficChartY\(trafficScaleRatio\(proxy, max, scale\)\)/); assert.match(chart, /client-device-traffic-grid[\s\S]*y1=\{TRAFFIC_CHART_HEADROOM\}[\s\S]*y1=\{\(100 \+ TRAFFIC_CHART_HEADROOM\) \/ 2\}/); assert.match(chart, /client-device-traffic-cursor[\s\S]*y1=\{TRAFFIC_CHART_HEADROOM\} y2="100"/); assert.doesNotMatch(panel, /key=\{latest\}/); assert.match(chart, /createPortal\([\s\S]*client-device-traffic-point-tooltip[\s\S]*document\.body/); assert.match(chart, /onPointerMove=\{trackPointer\}/); assert.match(chart, /pinned && max > 0n && 0n && Proxy \{formatByteString\(hovered\.proxy\)\}<\/span>\}/); assert.match(chart, /