Files
harbor-net/test/web/diagnostics-feature-contract.test.js
dokril 76a99f098a
Build and Deploy Gateway / build-and-push (push) Successful in 31s
Build and Deploy Gateway / deploy (push) Successful in 13s
Add DNS diagnostics across dataplane and client
2026-09-01 04:16:15 +03:00

142 lines
9.4 KiB
JavaScript

import assert from 'node:assert/strict';
import fs from 'node:fs';
import path from 'node:path';
import test from 'node:test';
import { parseConnectivityResult } from '../../.test-dist/src/web/features/diagnostics/connectivityResult.js';
const root = path.resolve(import.meta.dirname, '../..');
const page = fs.readFileSync(path.join(root, 'src/web/components/ClientOverviewPage.tsx'), 'utf8');
const feature = fs.readFileSync(path.join(root, 'src/web/features/diagnostics/DiagnosticsFeature.tsx'), 'utf8');
const panel = fs.readFileSync(path.join(root, 'src/web/features/diagnostics/ConnectivityDiagnosticsPanel.tsx'), 'utf8');
const model = fs.readFileSync(path.join(root, 'src/web/features/diagnostics/connectivityResult.ts'), 'utf8');
const customServiceAction = fs.readFileSync(path.join(root, 'src/web/features/diagnostics/customServiceAction.ts'), 'utf8');
const boundary = fs.readFileSync(path.join(root, 'src/web/features/diagnostics/index.ts'), 'utf8');
const dns = fs.readFileSync(path.join(root, 'src/web/features/diagnostics/DnsDiagnosticsSection.tsx'), 'utf8');
const ip = { source: 'cloudflare', address: '198.51.100.10', extra: true };
const site = { id: 'google', status: 'available', httpStatus: 204, latencyMs: 120 };
const pathResult = {
available: true,
internetAvailable: true,
ipv4: { addresses: ['198.51.100.10'], sources: [ip] },
ipv6: null,
ipv6Source: null,
network: {
address: '198.51.100.10',
asn: 'AS12389',
provider: 'Rostelecom',
city: 'Moscow',
country: 'RU',
},
sites: [site],
};
const valid = {
checkedAt: '2026-08-08T12:00:00.000Z',
direct: pathResult,
vpn: { ...pathResult, server: { id: 'server-1', label: 'Server 1' } },
extra: { retained: true },
};
test('diagnostics feature is the sole owner while the conditional panel keeps reset semantics', () => {
assert.match(boundary, /ConnectivityDiagnosticsPanel[\s\S]*DiagnosticsToggle,[\s\S]*useDiagnosticsFeature/);
assert.equal(fs.existsSync(path.join(root, 'src/web/components/ConnectivityDiagnosticsPanel.jsx')), false);
assert.equal((page.match(/useDiagnosticsFeature\(\)/g) || []).length, 1);
assert.equal((page.match(/<DiagnosticsToggle/g) || []).length, 1);
assert.equal((page.match(/<ConnectivityDiagnosticsPanel/g) || []).length, 1);
assert.match(page, /const diagnosticsAvailable = hasSubscription/);
assert.match(page, /\{diagnosticsAvailable && <ConnectivityDiagnosticsPanel[\s\S]*feature=\{diagnosticsFeature\}/);
assert.match(page, /if \(!diagnosticsAvailable\) diagnosticsFeature\.close\(\)/);
assert.doesNotMatch(page, /diagnosticsOpen|setDiagnosticsOpen|diagnosticsPanelRef|diagnosticsToggleRef|diagnosticsCloseRef|client-diagnostics-toggle/);
assert.doesNotMatch(feature, /setResult|customServices|localStorage|runConnectivityDiagnostics|activeTarget|removingServiceId/);
assert.match(feature, /closeRef\.current\?\.focus\(\)[\s\S]*panelRef\.current\?\.contains[\s\S]*toggleRef\.current\?\.focus\(\)/);
assert.doesNotMatch([feature, panel].join('\n'), /from ['"][^'"]*\/api\/|ConnectionPanel|SubscriptionFeature|RoutingFeature|DevicesFeature/);
});
test('unknown target and legacy-full results pass one identity-preserving parser before use', () => {
assert.equal(parseConnectivityResult(valid), valid);
const legacyFull = {
...valid,
direct: {
...valid.direct,
ipv4: { ...valid.direct.ipv4, sources: [ip, { source: 'ipify', address: null }] },
sites: [site, { id: 'youtube', status: 'responded', httpStatus: 403, latencyMs: null }],
},
};
assert.equal(parseConnectivityResult(legacyFull), legacyFull);
for (const invalid of [
null,
{},
{ ...valid, direct: undefined },
{ ...valid, direct: { ...valid.direct, available: 'yes' } },
{ ...valid, direct: { ...valid.direct, ipv4: { addresses: [], sources: [{}] } } },
{ ...valid, direct: { ...valid.direct, ipv6: 6 } },
{ ...valid, direct: { ...valid.direct, network: { ...valid.direct.network, asn: 12389 } } },
{ ...valid, direct: { ...valid.direct, sites: [{ ...site, id: '' }] } },
{ ...valid, direct: { ...valid.direct, sites: [{ ...site, status: 'blocked' }] } },
{ ...valid, direct: { ...valid.direct, sites: [{ ...site, latencyMs: -1 }] } },
{ ...valid, vpn: { ...valid.vpn, server: undefined } },
{ ...valid, vpn: { ...valid.vpn, server: { id: 1, label: 'Server' } } },
]) assert.throws(() => parseConnectivityResult(invalid), TypeError);
assert.match(panel, /parseConnectivityResult\(await runConnectivityDiagnostics\(target\)\)/);
assert.match(panel, /const legacyFullResult = partial\.direct\.ipv4\.sources\.length > 1 \|\| partial\.direct\.sites\.length > 1/);
assert.match(panel, /next = legacyFullResult \? partial : mergeResult\(next, partial\)/);
assert.doesNotMatch(model, /\sas\s(?:ConnectivityResult|Record<string, unknown>)/);
});
test('serial probes stay panel-owned while custom service validation is shared by both editors', () => {
assert.match(panel, /const targets = onlyTarget \? \[onlyTarget\] : \[[\s\S]*CONNECTIVITY_NETWORK_SOURCE\.id[\s\S]*CONNECTIVITY_IP_SOURCES\.map[\s\S]*sites\.map/);
assert.match(panel, /for \(const target of targets\) \{[\s\S]*setActiveTarget\(target\)[\s\S]*await runConnectivityDiagnostics\(target\)[\s\S]*if \(legacyFullResult\) break/);
assert.match(panel, /CUSTOM_SERVICES_KEY = 'harbor-diagnostic-services'/);
assert.match(panel, /HIDDEN_SERVICES_KEY = 'harbor-hidden-diagnostic-services'/);
assert.match(panel, /if \(settings\.configured\)[\s\S]*clearLegacyServices\(\)/);
assert.match(panel, /updateSettings\(\{[\s\S]*customServices:[\s\S]*hiddenServiceIds/);
assert.doesNotMatch(panel, /localStorage\.setItem/);
assert.doesNotMatch(panel, /useState\(read(?:Custom|Hidden)Services\)/);
assert.match(panel, /slice\(0, MAX_CUSTOM_DIAGNOSTIC_SERVICES\)/);
assert.match(panel, /saveCustomDiagnosticService/);
assert.match(customServiceAction, /parsed\.protocol !== 'https:'/);
assert.match(panel, /document\.startViewTransition\(update\)/);
assert.match(panel, /retryable: Boolean\(Reflect\.get\(value, 'retryable'\)\)/);
assert.match(panel, /requestDetails\(error\)[\s\S]*requestError\.retryable/);
});
test('each visible row reuses the targeted probe without adding another result owner', () => {
assert.match(panel, /function RowRefresh\([\s\S]*aria-label=\{`Проверить: \$\{label\}`\}[\s\S]*aria-busy=\{running\}[\s\S]*disabled=\{disabled\}/);
assert.match(panel, /<Tooltip>Проверить только эту строку<\/Tooltip>/);
assert.match(panel, /async function run\(onlyTarget\?: string\)[\s\S]*const targets = onlyTarget \? \[onlyTarget\] :/);
assert.match(panel, /onRun=\{\(\) => run\(CONNECTIVITY_NETWORK_SOURCE\.id\)\}/);
assert.match(panel, /onRun=\{\(\) => run\(target\)\}/);
assert.match(panel, /onRun=\{\(\) => run\(`site:\$\{site\.id\}`\)\}/);
assert.match(panel, /retryTargetRef\.current = onlyTarget[\s\S]*run\(retryTargetRef\.current\)/);
assert.match(panel, /scope="row" aria-label=\{CONNECTIVITY_NETWORK_SOURCE\.label\}/);
assert.match(panel, /role="rowheader" aria-label=\{site\.label\}/);
assert.equal((panel.match(/className="client-diagnostics-row-name"/g) || []).length, 2);
assert.match(panel, /client-diagnostics-service-name client-diagnostics-row-name/);
});
test('network identity is one stable compact row for Direct and VPN', () => {
assert.match(panel, /data-diagnostic-target=\{CONNECTIVITY_NETWORK_SOURCE\.id\}[\s\S]*<NetworkCell[\s\S]*route="Напрямую, сеть"[\s\S]*<NetworkCell[\s\S]*route="VPN, сеть"/);
assert.match(panel, /path\?\.network\?\.asn[\s\S]*path\?\.network\?\.provider[\s\S]*path\?\.network\?\.city[\s\S]*path\?\.network\?\.country/);
assert.match(panel, /<Status value=\{status\} route=\{route\} \/><br \/>[\s\S]*client-diagnostics-status is-muted[\s\S]*&nbsp;/);
assert.match(panel, /if \(!value\) return <Status value=\{\['is-muted', '—'\]\}[\s\S]*if \(!value\?\.address\) return <Status value=\{\['is-error', 'Нет ответа'\]\}/);
assert.match(panel, /else if \(path\.network == null\) status = \['is-muted', '—'\][\s\S]*if \(!status && !identity && !location\) status = \['is-error', 'Нет ответа'\]/);
assert.doesNotMatch(panel, /traceroute|tracepath/);
});
test('DNS diagnostics reuse the drawer owner, canonical settings and independent row refresh', () => {
assert.match(panel, /<DnsDiagnosticsSection[\s\S]*settings=\{settings\}[\s\S]*updateSettings=\{updateSettings\}/);
assert.match(panel, /<DnsDiagnosticsSection[\s\S]*loadCatalog=\{loadDnsDiagnosticsCatalog\}[\s\S]*runDiagnostics=\{runDnsDiagnostics\}/);
assert.match(dns, /<select[\s\S]*id="client-dns-domain"[\s\S]*value=\{activeDomainId\}/);
assert.match(dns, /setCache\(\(current\)[\s\S]*mergeRow\(previous\[row\.resolver\.id\], row\)/);
assert.match(dns, /function preserveAddresses[\s\S]*ipv4: previous\.ipv4, ipv6: previous\.ipv6/);
assert.match(dns, /aria-busy=\{running\}/);
assert.match(dns, /<Refresh label=\{`Проверить: \$\{item\.label\}`\}[\s\S]*onClick=\{\(\) => void run\(item\.id\)\}/);
assert.match(dns, /updateSettings\([\s\S]*customDnsDomains/);
assert.match(dns, /updateSettings\([\s\S]*customDnsResolvers/);
assert.match(dns, /settings\.customDnsDomains\.length >= MAX_CUSTOM_DNS_DOMAINS/);
assert.match(dns, /settings\.customDnsResolvers\.length >= MAX_CUSTOM_DNS_RESOLVERS/);
assert.match(dns, /<b>A<\/b>[\s\S]*<b>AAAA<\/b>/);
assert.match(dns, /Разные ответы могут быть нормой для CDN/);
});