Add targeted connectivity diagnostics with sampled results
Build and Deploy Gateway / build-and-push (push) Successful in 15s
Build and Deploy Gateway / deploy (push) Successful in 13s

This commit is contained in:
2026-08-07 22:11:04 +03:00
parent b2a2ed7104
commit e9433e754f
12 changed files with 359 additions and 114 deletions
@@ -55,6 +55,53 @@ test('connectivity diagnostics force separate direct and VPN paths', async () =>
test('connectivity diagnostics endpoint is available in Connect and Gateway', () => {
assert.match(server, /const localConnectivityDiagnostics = !remoteDataplane/);
assert.doesNotMatch(server, /settings\.appMode !== 'gateway'[\s\S]{0,120}ENDPOINT_NOT_FOUND/);
assert.match(server, /runConnectivityDiagnostics\(services, target\)/);
});
test('a targeted IP row uses three samples and keeps the majority address', async () => {
const attempts = { direct: 0, vpn: 0 };
const execute = async (args) => {
const route = args.includes('--proxy') ? 'vpn' : 'direct';
attempts[route] += 1;
if (route === 'vpn' && attempts.vpn === 1) {
return response('', { exitcode: 28, http_code: 0, errormsg: 'timeout' });
}
const address = route === 'vpn' ? '203.0.113.20' : '198.51.100.10';
return response(`{"ipv4":"${address}"}`, {
time_starttransfer: route === 'vpn' ? attempts.vpn * 0.1 : attempts.direct * 0.1,
});
};
const result = await createConnectivityDiagnosticsService({ proxyPort: 18080, execute })
.run({ vpnAvailable: true, target: 'ip:yandex-internet' });
assert.equal(result.direct.ipv4.sources[0].attempts, 3);
assert.equal(result.vpn.ipv4.sources[0].attempts, 3);
assert.equal(result.direct.ipv4.sources[0].address, '198.51.100.10');
assert.equal(result.vpn.ipv4.sources[0].address, '203.0.113.20');
assert.equal(result.direct.ipv4.sources[0].latencyMs, 200);
assert.equal(result.vpn.ipv4.sources[0].latencyMs, 250);
});
test('a targeted service row averages three measurements and ignores one transient failure', async () => {
const attempts = { direct: 0, vpn: 0 };
const execute = async (args) => {
const route = args.includes('--proxy') ? 'vpn' : 'direct';
attempts[route] += 1;
if (route === 'direct' && attempts.direct === 1) {
return response('', { exitcode: 28, http_code: 0, errormsg: 'timeout' });
}
return response('', {
time_starttransfer: route === 'direct' ? attempts.direct * 0.1 : attempts.vpn * 0.2,
});
};
const result = await createConnectivityDiagnosticsService({ proxyPort: 18080, execute })
.run({ vpnAvailable: true, target: 'site:yandex' });
assert.deepEqual(attempts, { direct: 3, vpn: 3 });
assert.equal(result.direct.sites[0].status, 'available');
assert.equal(result.direct.sites[0].attempts, 3);
assert.equal(result.direct.sites[0].latencyMs, 250);
assert.equal(result.vpn.sites[0].latencyMs, 400);
});
test('connectivity diagnostics reports a likely direct restriction without claiming its owner', async () => {
@@ -160,3 +207,30 @@ test('connectivity diagnostics pins public custom services and rejects private d
assert.equal(result.direct.sites.find((site) => site.id === 'custom-private').stage, 'validation');
assert.equal(result.assessment.comparisons.some((item) => item.id === 'custom-public'), true);
});
test('a targeted custom row validates and samples only that service', async () => {
const lookups = [];
const calls = [];
const result = await createConnectivityDiagnosticsService({
proxyPort: 18080,
execute: async (args) => {
calls.push(args.at(-1));
return response();
},
lookup: async (hostname) => {
lookups.push(hostname);
return [{ address: '93.184.216.34', family: 4 }];
},
}).run({
vpnAvailable: false,
services: [
{ id: 'custom-first', url: 'https://first.example/' },
{ id: 'custom-second', url: 'https://second.example/' },
],
target: 'site:custom-second',
});
assert.deepEqual(lookups, ['second.example']);
assert.deepEqual(calls, ['https://second.example/', 'https://second.example/', 'https://second.example/']);
assert.equal(result.direct.sites[0].latencyMs, 120);
});
+6 -2
View File
@@ -26,7 +26,10 @@ test('control uses the dataplane socket protocol', async () => {
assert.equal(traffic.running, true);
await client.observeDevicePolicy();
await client.applyDevicePolicies([{ id: 'dev_0011223344556677' }]);
await client.runConnectivityDiagnostics([{ id: 'custom-test', url: 'https://example.com' }]);
await client.runConnectivityDiagnostics(
[{ id: 'custom-test', url: 'https://example.com' }],
'site:custom-test',
);
assert.equal(client.running, true);
await client.restart();
assert.equal((await client.stop()).running, false);
@@ -44,8 +47,9 @@ test('control uses the dataplane socket protocol', async () => {
assert.deepEqual(requests[5].body, { devices: [{ id: 'dev_0011223344556677' }] });
assert.deepEqual(requests[6].body, {
services: [{ id: 'custom-test', url: 'https://example.com' }],
target: 'site:custom-test',
});
assert.equal(requests[6].timeoutMs, 15_000);
assert.equal(requests[6].timeoutMs, 25_000);
});
test('connectivity diagnostics expose a retryable domain error', async () => {
+10 -4
View File
@@ -36,10 +36,11 @@ test('page reload plays one stable startup sequence', () => {
assert.match(component, /const \[showIntro, setShowIntro\] = useState\(true\)/);
assert.match(component, /\$\{showIntro \? ' is-intro' : ''\}/);
assert.doesNotMatch(component, /showIntro && !hasSubscription/);
assert.match(styles, /\.client-shell\.is-intro \.harbor-brand-content \{[\s\S]*harbor-startup-brand 1200ms/);
assert.match(styles, /@keyframes harbor-startup-brand[\s\S]*translateY\(53px\)/);
assert.match(styles, /\.client-shell\.is-intro \.client-panel,[\s\S]*\.client-secondary-menu,[\s\S]*\.harbor-versions \{[\s\S]*harbor-startup-content 680ms 520ms/);
assert.match(styles, /@keyframes harbor-startup-content[\s\S]*opacity: 0;[\s\S]*filter: blur\(10px\)/);
assert.match(styles, /\.client-shell\.is-intro \.harbor-brand-content \{[\s\S]*harbor-startup-brand 760ms/);
assert.match(styles, /@keyframes harbor-startup-brand[\s\S]*opacity: 0\.35;[\s\S]*filter: blur\(5px\)/);
assert.doesNotMatch(/@keyframes harbor-startup-brand \{([\s\S]*?)\n\}/.exec(styles)?.[1] || '', /translate|scale/);
assert.match(styles, /\.client-shell\.is-intro \.client-panel,[\s\S]*\.client-secondary-menu,[\s\S]*\.harbor-versions \{[\s\S]*harbor-startup-content 720ms 120ms/);
assert.match(styles, /@keyframes harbor-startup-content[\s\S]*opacity: 0;[\s\S]*filter: blur\(6px\)/);
assert.match(styles, /@media \(prefers-reduced-motion: reduce\)[\s\S]*\.client-shell\.is-intro \.client-panel,[\s\S]*animation: none/);
});
@@ -136,6 +137,11 @@ test('connectivity diagnostics render stable compact tables before the first run
assert.doesNotMatch(diagnostics, /PathDetails|client-diagnostics-details|Технические детали/);
assert.doesNotMatch(rule('.client-diagnostics-feedback'), /min-height:/);
assert.match(rule('.client-diagnostics-table'), /table-layout:\s*fixed/);
assert.match(diagnostics, /for \(const target of targets\)/);
assert.match(diagnostics, /api\.diagnostics\.connectivity\(customServices, target\)/);
assert.match(diagnostics, /const target = `ip:\$\{source\.id\}`;[\s\S]*activeTarget === target/);
assert.match(diagnostics, /activeTarget === `site:\$\{site\.id\}`/);
assert.match(styles, /\.client-diagnostics-table tbody tr\.is-running \{[\s\S]*client-diagnostics-row-pulse/);
});
test('duration and Gateway access keep stable geometry without tabs', () => {