Add targeted connectivity diagnostics with sampled results
This commit is contained in:
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
Reference in New Issue
Block a user