Persist configurable connectivity diagnostics
This commit is contained in:
@@ -59,6 +59,7 @@ test('connectivity diagnostics endpoint is available in Connect and Gateway thro
|
||||
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\)/);
|
||||
@@ -69,6 +70,12 @@ test('connectivity diagnostics endpoint is available in Connect and Gateway thro
|
||||
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',
|
||||
@@ -90,9 +97,12 @@ test('connectivity use case captures applied server before probes and preserves
|
||||
assessment: { summary: 'available' },
|
||||
};
|
||||
const useCase = createConnectivityDiagnosticsUseCase({
|
||||
readState: () => {
|
||||
events.push('state');
|
||||
return state;
|
||||
state: {
|
||||
read: () => {
|
||||
events.push('state');
|
||||
return state;
|
||||
},
|
||||
update: () => { throw new Error('unexpected update'); },
|
||||
},
|
||||
runDiagnostics: async (services, target) => {
|
||||
events.push(['probe', services, target]);
|
||||
@@ -100,12 +110,12 @@ test('connectivity use case captures applied server before probes and preserves
|
||||
return sourceResult;
|
||||
},
|
||||
});
|
||||
const resultPromise = useCase.run({ raw: true }, 42);
|
||||
const resultPromise = useCase.run(42);
|
||||
state.profiles[0].servers[0].label = 'Changed during probe';
|
||||
releaseProbe();
|
||||
const result = await resultPromise;
|
||||
|
||||
assert.deepEqual(events, ['state', ['probe', { raw: true }, 42]]);
|
||||
assert.deepEqual(events, ['state', ['probe', state.diagnostics.customServices, 42]]);
|
||||
assert.deepEqual(result, {
|
||||
...sourceResult,
|
||||
vpn: {
|
||||
@@ -119,52 +129,98 @@ test('connectivity use case captures applied server before probes and preserves
|
||||
test('connectivity use case keeps applied priority, selected fallback and error identity', async () => {
|
||||
const result = { vpn: { available: false }, marker: true };
|
||||
const selected = createConnectivityDiagnosticsUseCase({
|
||||
readState: () => ({
|
||||
desiredProfileId: 'primary',
|
||||
appliedServerId: '',
|
||||
profiles: [{
|
||||
id: 'primary',
|
||||
desiredServerId: 'selected',
|
||||
servers: [{ id: 'selected', label: 'Selected' }],
|
||||
}],
|
||||
}),
|
||||
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, null)).vpn.server, {
|
||||
assert.deepEqual((await selected.run(null)).vpn.server, {
|
||||
id: 'selected',
|
||||
label: 'Selected',
|
||||
});
|
||||
|
||||
const missingApplied = createConnectivityDiagnosticsUseCase({
|
||||
readState: () => ({
|
||||
desiredProfileId: 'primary',
|
||||
appliedProfileId: 'primary',
|
||||
appliedServerId: 'missing',
|
||||
profiles: [{
|
||||
id: 'primary',
|
||||
desiredServerId: 'selected',
|
||||
servers: [{ id: 'selected', label: 'Selected' }],
|
||||
}],
|
||||
}),
|
||||
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);
|
||||
assert.equal((await missingApplied.run(null)).vpn.server, null);
|
||||
|
||||
const stateError = new Error('state failed');
|
||||
let probes = 0;
|
||||
const brokenState = createConnectivityDiagnosticsUseCase({
|
||||
readState: () => { throw stateError; },
|
||||
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);
|
||||
await assert.rejects(brokenState.run(null), (error) => error === stateError);
|
||||
assert.equal(probes, 0);
|
||||
|
||||
const probeError = new Error('probe failed');
|
||||
const brokenProbe = createConnectivityDiagnosticsUseCase({
|
||||
readState: () => ({ servers: [] }),
|
||||
state: {
|
||||
read: () => ({ profiles: [] }),
|
||||
update: () => { throw new Error('unexpected update'); },
|
||||
},
|
||||
runDiagnostics: async () => { throw probeError; },
|
||||
});
|
||||
await assert.rejects(brokenProbe.run([], null), (error) => error === 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: [] },
|
||||
};
|
||||
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'],
|
||||
});
|
||||
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() {
|
||||
@@ -189,34 +245,46 @@ test('connectivity route preserves exact URL, defaults and raw response', async
|
||||
calls.push(args);
|
||||
return { checkedAt: 'now', vpn: { server: null } };
|
||||
},
|
||||
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.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), [null, 17]);
|
||||
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);
|
||||
|
||||
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, 2);
|
||||
assert.equal(bodyReads, 3);
|
||||
});
|
||||
|
||||
test('a targeted IP row uses three samples and keeps the majority address', async () => {
|
||||
|
||||
Reference in New Issue
Block a user