import assert from 'node:assert/strict'; import fs from 'node:fs'; import path from 'node:path'; import test from 'node:test'; import { createConnectivityDiagnosticsService, CURL_META_MARKER, } from '../../dist/server/services/connectivityDiagnosticsService.js'; import { createConnectivityDiagnosticsUseCase } from '../../dist/server/features/diagnostics/index.js'; import { createConnectivityDiagnosticsRoute } from '../../dist/server/http/routes/connectivityDiagnosticsRoute.js'; const server = fs.readFileSync(path.resolve(import.meta.dirname, '../../src/server/index.ts'), 'utf8'); function response(body = '', overrides = {}) { return { exitCode: 0, error: '', stderr: '', stdout: `${body}${CURL_META_MARKER}${JSON.stringify({ exitcode: 0, http_code: 204, time_starttransfer: 0.12, time_total: 0.15, ...overrides, })}`, }; } test('connectivity diagnostics force separate direct and VPN paths', async () => { const calls = []; const execute = async (args) => { calls.push(args); const vpn = args.includes('--proxy'); const url = args.at(-1); if (url.includes('cloudflare')) return response(`ip=${vpn ? '203.0.113.20' : '198.51.100.10'}\n`); if (url.includes('api6')) return response(vpn ? '2001:db8::20' : '2001:db8::10'); if (url.includes('ipify')) return response(vpn ? '203.0.113.20' : '198.51.100.10'); return response(); }; const service = createConnectivityDiagnosticsService({ proxyPort: 18080, execute, now: () => '2026-08-07T12:00:00.000Z', }); const result = await service.run({ vpnAvailable: true }); assert.deepEqual(result.direct.ipv4.addresses, ['198.51.100.10']); assert.deepEqual(result.vpn.ipv4.addresses, ['203.0.113.20']); assert.equal(result.direct.ipv6, '2001:db8::10'); assert.equal(result.vpn.ipv6, '2001:db8::20'); assert.equal(result.direct.network.error, 'invalid network response'); assert.equal(result.assessment.summary, 'available'); assert.ok(calls.some((args) => args.includes('--noproxy') && args.includes('*'))); assert.ok(calls.some((args) => args.includes('--proxy') && args.includes('http://127.0.0.1:18080'))); }); test('connectivity diagnostics endpoint is available in Connect and Gateway through one owner', () => { 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\)/); assert.match(server, /sendState: \(res\) => stateRoute\.send\(res\)/); assert.match(server, /createConnectivityDiagnosticsUseCase\(\{/); assert.match(server, /createConnectivityDiagnosticsRoute\(\{/); assert.match(server, /connectivityDiagnosticsRoute\.handle\(req, res\)/); assert.doesNotMatch(server, /['"]\/api\/diagnostics\/connectivity['"]/); assert.doesNotMatch(server, /appliedServerId\s*\|\|\s*state\.selectedServerId/); }); test('connectivity use case captures applied server before probes and preserves result fields', async () => { const events = []; let state = { revision: 3, diagnostics: { configured: true, customServices: [{ id: 'custom-saved', label: 'Saved', url: 'https://example.com/' }], hiddenServiceIds: [], }, desiredProfileId: 'primary', appliedProfileId: 'primary', appliedServerId: 'applied', profiles: [{ id: 'primary', desiredServerId: 'selected', servers: [ { id: 'applied', label: 'Applied before probe' }, { id: 'selected', label: 'Selected' }, ], }], }; let releaseProbe; const probe = new Promise((resolve) => { releaseProbe = resolve; }); const sourceResult = { checkedAt: 'now', direct: { available: true }, vpn: { available: true, server: { id: 'stale', label: 'Stale' }, detail: 7 }, assessment: { summary: 'available' }, }; const useCase = createConnectivityDiagnosticsUseCase({ state: { read: () => { events.push('state'); return state; }, update: () => { throw new Error('unexpected update'); }, }, runDiagnostics: async (services, target) => { events.push(['probe', services, target]); await probe; return sourceResult; }, }); const resultPromise = useCase.run(42); state.profiles[0].servers[0].label = 'Changed during probe'; releaseProbe(); const result = await resultPromise; assert.deepEqual(events, ['state', ['probe', state.diagnostics.customServices, 42]]); assert.deepEqual(result, { ...sourceResult, vpn: { ...sourceResult.vpn, server: { id: 'applied', label: 'Applied before probe' }, }, }); assert.deepEqual(sourceResult.vpn.server, { id: 'stale', label: 'Stale' }); }); test('connectivity use case keeps applied priority, selected fallback and error identity', async () => { const result = { vpn: { available: false }, marker: true }; const selected = createConnectivityDiagnosticsUseCase({ state: { read: () => ({ desiredProfileId: 'primary', appliedServerId: '', profiles: [{ id: 'primary', desiredServerId: 'selected', servers: [{ id: 'selected', label: 'Selected' }], }], }), update: () => { throw new Error('unexpected update'); }, }, runDiagnostics: async () => result, }); assert.deepEqual((await selected.run(null)).vpn.server, { id: 'selected', label: 'Selected', }); const missingApplied = createConnectivityDiagnosticsUseCase({ state: { read: () => ({ desiredProfileId: 'primary', appliedProfileId: 'primary', appliedServerId: 'missing', profiles: [{ id: 'primary', desiredServerId: 'selected', servers: [{ id: 'selected', label: 'Selected' }], }], }), update: () => { throw new Error('unexpected update'); }, }, runDiagnostics: async () => result, }); assert.equal((await missingApplied.run(null)).vpn.server, null); const stateError = new Error('state failed'); let probes = 0; const brokenState = createConnectivityDiagnosticsUseCase({ state: { read: () => { throw stateError; }, update: () => { throw new Error('unexpected update'); }, }, runDiagnostics: async () => { probes += 1; return result; }, }); await assert.rejects(brokenState.run(null), (error) => error === stateError); assert.equal(probes, 0); const probeError = new Error('probe failed'); const brokenProbe = createConnectivityDiagnosticsUseCase({ state: { read: () => ({ profiles: [] }), update: () => { throw new Error('unexpected update'); }, }, runDiagnostics: async () => { throw probeError; }, }); await assert.rejects(brokenProbe.run(null), (error) => error === probeError); }); test('diagnostics settings validate and replace one canonical revision', () => { let state = { revision: 7, diagnostics: { configured: false, customServices: [], hiddenServiceIds: [], customDnsResolvers: [{ id: 'custom-dns-office', label: 'Office', kind: 'dns', endpoint: '192.168.1.1', }], customDnsDomains: [{ id: 'custom-domain-office', label: 'Office', hostname: 'office.example.com' }], }, }; const useCase = createConnectivityDiagnosticsUseCase({ state: { read: () => state, update: (mutator) => { state = { ...mutator(state), revision: state.revision + 1 }; return state; }, }, runDiagnostics: async () => ({ vpn: {} }), }); useCase.updateSettings({ customServices: [{ id: 'custom-status', label: 'Status', url: 'https://example.com/status' }], hiddenServiceIds: ['speedtest'], }, 7); assert.deepEqual(state.diagnostics, { configured: true, customServices: [{ id: 'custom-status', label: 'Status', url: 'https://example.com/status' }], hiddenServiceIds: ['speedtest'], customDnsResolvers: [{ id: 'custom-dns-office', label: 'Office', kind: 'dns', endpoint: '192.168.1.1', }], customDnsDomains: [{ id: 'custom-domain-office', label: 'Office', hostname: 'office.example.com' }], }); assert.equal(state.revision, 8); assert.throws(() => useCase.updateSettings({ customServices: [], hiddenServiceIds: [] }, 7), { code: 'STATE_CONFLICT', }); assert.throws(() => useCase.updateSettings({ customServices: [{ id: 'bad', label: 'Router', url: 'http://router.local/' }], hiddenServiceIds: [], }, 8), { code: 'REQUEST_INVALID' }); }); function routeResponse() { return { writeHead(status, headers) { this.status = status; this.headers = headers; }, end(payload) { this.payload = JSON.parse(payload); }, }; } test('connectivity route preserves exact URL, defaults and raw response', async () => { const calls = []; let body = {}; let bodyReads = 0; const route = createConnectivityDiagnosticsRoute({ diagnostics: { run: async (...args) => { calls.push(args); return { checkedAt: 'now', vpn: { server: null } }; }, dnsCatalog: async () => ({ resolvers: [{ id: 'google-dns' }], domains: [] }), runDns: async (...args) => { calls.push(['dns', ...args]); return { checkedAt: 'dns-now', results: [] }; }, updateSettings: (...args) => calls.push(['settings', ...args]), }, readBody: async () => { bodyReads += 1; return body; }, sendState: async (response) => { response.writeHead(200, { 'content-type': 'application/json; charset=utf-8' }); response.end(JSON.stringify({ success: true, state: {} })); }, }); const res = routeResponse(); assert.equal(await route.handle({ method: 'POST', url: '/api/diagnostics/connectivity', }, res), true); assert.deepEqual(calls, [[null]]); assert.equal(res.status, 200); assert.equal(res.headers['content-type'], 'application/json; charset=utf-8'); assert.deepEqual(res.payload, { checkedAt: 'now', vpn: { server: null } }); body = { services: null, target: 17 }; await route.handle({ method: 'POST', url: '/api/diagnostics/connectivity' }, routeResponse()); assert.deepEqual(calls.at(-1), [17]); body = { settings: { customServices: [], hiddenServiceIds: [] }, expectedRevision: 4 }; const settingsResponse = routeResponse(); assert.equal(await route.handle({ method: 'PUT', url: '/api/diagnostics/settings' }, settingsResponse), true); assert.deepEqual(calls.at(-1), ['settings', body.settings, 4]); assert.equal(settingsResponse.payload.success, true); const catalogResponse = routeResponse(); assert.equal(await route.handle({ method: 'GET', url: '/api/diagnostics/dns' }, catalogResponse), true); assert.deepEqual(catalogResponse.payload, { resolvers: [{ id: 'google-dns' }], domains: [] }); body = { domainId: 'youtube', resolverId: 'google-dns' }; const dnsResponse = routeResponse(); assert.equal(await route.handle({ method: 'POST', url: '/api/diagnostics/dns' }, dnsResponse), true); assert.deepEqual(calls.at(-1), ['dns', 'youtube', 'google-dns']); assert.deepEqual(dnsResponse.payload, { checkedAt: 'dns-now', results: [] }); for (const [method, url] of [ ['GET', '/api/diagnostics/connectivity'], ['POST', '/api/diagnostics/connectivity?target=all'], ['POST', '/api/diagnostics/settings'], ['POST', '/api/diagnostics/other'], ]) { assert.equal(await route.handle({ method, url }, routeResponse()), false); } assert.equal(bodyReads, 4); }); 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 network row reports provider, ASN and location through both forced paths', async () => { const attempts = { direct: 0, vpn: 0 }; const calls = []; const execute = async (args) => { calls.push(args); const route = args.includes('--proxy') ? 'vpn' : 'direct'; attempts[route] += 1; if (route === 'direct' && attempts.direct === 1) return response('{invalid'); return response(JSON.stringify(route === 'vpn' ? { success: true, ip: '2a12:bec4:1bb0:10bb::2', city: 'Amsterdam', country_code: 'NL', connection: { asn: 9009, isp: 'M247 Europe' }, } : { success: true, ip: '198.51.100.10', city: 'Moscow', country_code: 'RU', connection: { asn: 12389, isp: 'Rostelecom' }, })); }; const result = await createConnectivityDiagnosticsService({ proxyPort: 18080, execute }) .run({ vpnAvailable: true, target: 'network' }); assert.deepEqual(attempts, { direct: 3, vpn: 3 }); assert.deepEqual(result.direct.network, { address: '198.51.100.10', asn: 'AS12389', provider: 'Rostelecom', city: 'Moscow', country: 'RU', attempts: 3, error: null, }); assert.equal(result.vpn.network.address, '2a12:bec4:1bb0:10bb::2'); assert.equal(result.vpn.network.asn, 'AS9009'); assert.equal(result.vpn.network.provider, 'M247 Europe'); assert.ok(calls.every((args) => args.at(-1) === 'https://ipwho.is/' && args.includes('--ipv4'))); assert.ok(calls.some((args) => args.includes('--noproxy') && args.includes('*'))); assert.ok(calls.some((args) => args.includes('--proxy') && args.includes('http://127.0.0.1:18080'))); }); 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 () => { const attempts = new Map(); const execute = async (args) => { const vpn = args.includes('--proxy'); const url = args.at(-1); if (url.includes('cloudflare')) return response(`ip=${vpn ? '203.0.113.20' : '198.51.100.10'}\n`); if (url.includes('api6')) return response('', { exitcode: 6, http_code: 0, errormsg: 'resolve failed' }); if (url.includes('ipify')) return response(vpn ? '203.0.113.20' : '198.51.100.10'); const key = `${vpn}:${url}`; attempts.set(key, (attempts.get(key) || 0) + 1); if (!vpn && url.includes('youtube')) { return response('', { exitcode: 28, http_code: 0, errormsg: 'timeout' }); } return response(); }; const result = await createConnectivityDiagnosticsService({ proxyPort: 18080, execute }) .run({ vpnAvailable: true }); assert.equal(result.assessment.summary, 'likely-direct-restriction'); assert.equal( result.assessment.comparisons.find((item) => item.id === 'youtube').assessment, 'likely-direct-restriction', ); assert.equal(attempts.get('false:https://www.youtube.com/generate_204'), 2); }); test('connectivity diagnostics skips VPN probes when sing-box is off', async () => { const calls = []; const execute = async (args) => { calls.push(args); const url = args.at(-1); if (url.includes('cloudflare')) return response('ip=198.51.100.10\n'); if (url.includes('api6')) return response('', { exitcode: 6, http_code: 0, errormsg: 'resolve failed' }); if (url.includes('ipify')) return response('198.51.100.10'); return response(); }; const result = await createConnectivityDiagnosticsService({ proxyPort: 18080, execute }) .run({ vpnAvailable: false }); assert.equal(result.vpn.available, false); assert.equal(result.assessment.summary, 'vpn-off'); assert.equal(calls.some((args) => args.includes('--proxy')), false); }); test('connectivity diagnostics keeps every IP source in a partial snapshot', async () => { const execute = async (args) => { const url = args.at(-1); if (url.includes('cloudflare')) { return response('', { exitcode: 28, http_code: 0, errormsg: 'timeout' }); } if (url.includes('api6')) return response('', { exitcode: 6, http_code: 0, errormsg: 'resolve failed' }); if (url.includes('ipify')) return response('198.51.100.10'); if (url.includes('amazonaws')) return response('198.51.100.10'); return response(); }; const result = await createConnectivityDiagnosticsService({ proxyPort: 18080, execute }) .run({ vpnAvailable: false }); assert.equal(result.direct.internetAvailable, true); assert.deepEqual(result.direct.ipv4.addresses, ['198.51.100.10']); assert.deepEqual(result.direct.ipv4.sources.map((source) => source.source), [ 'cloudflare', 'ipify', 'aws', 'icanhazip', 'ifconfig-me', 'yandex-internet', ]); }); test('connectivity diagnostics pins public custom services and rejects private destinations', async () => { const calls = []; const execute = async (args) => { calls.push(args); const url = args.at(-1); if (url.includes('cloudflare')) return response('ip=198.51.100.10\n'); if (url.includes('api6')) return response('', { exitcode: 6, http_code: 0, errormsg: 'resolve failed' }); if (url.includes('ipify')) return response('198.51.100.10'); return response(); }; const lookup = async (hostname) => hostname === 'router.local' ? [{ address: '192.168.50.1', family: 4 }] : [{ address: '93.184.216.34', family: 4 }]; const result = await createConnectivityDiagnosticsService({ proxyPort: 18080, execute, lookup }) .run({ vpnAvailable: false, services: [ { id: 'custom-public', label: 'Example', url: 'https://example.com/status' }, { id: 'custom-private', label: 'Router', url: 'https://router.local/' }, ], }); const publicCall = calls.find((args) => args.at(-1) === 'https://example.com/status'); assert.ok(publicCall); assert.equal(publicCall.includes('--location'), false); assert.deepEqual(publicCall.slice(publicCall.indexOf('--resolve'), publicCall.indexOf('--resolve') + 2), [ '--resolve', 'example.com:443:93.184.216.34', ]); assert.equal(calls.some((args) => args.at(-1) === 'https://router.local/'), false); 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); }); test('failover VPN checks apply their per-service timeout to curl', async () => { const calls = []; await createConnectivityDiagnosticsService({ proxyPort: 18080, execute: async (args) => { calls.push(args); return response(); }, }).runVpn({ target: 'site:youtube', timeoutMs: 9_000 }); assert.equal(calls.length, 3); for (const args of calls) { assert.equal(args[args.indexOf('--max-time') + 1], '9'); assert.equal(args[args.indexOf('--connect-timeout') + 1], '3'); assert.ok(args.includes('http://127.0.0.1:18080')); } });