Refactor VPN proxy client implementation
This commit is contained in:
@@ -0,0 +1,191 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import test from 'node:test';
|
||||
|
||||
import { createDeviceInventoryRoute } from '../../dist/server/http/routes/deviceInventoryRoute.js';
|
||||
|
||||
const deviceId = 'dev_0123456789abcdef';
|
||||
|
||||
function response() {
|
||||
return {
|
||||
writeHead(status, headers) {
|
||||
this.status = status;
|
||||
this.headers = headers;
|
||||
},
|
||||
end(payload) {
|
||||
this.payload = JSON.parse(payload);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function createHarness({ inventory = {}, body = {} } = {}) {
|
||||
const calls = [];
|
||||
let bodyReads = 0;
|
||||
const deviceInventory = inventory === null ? null : {
|
||||
snapshot: () => {
|
||||
calls.push(['snapshot']);
|
||||
return inventory.snapshot ?? { revision: 1, devices: [] };
|
||||
},
|
||||
refresh: async () => {
|
||||
calls.push(['refresh']);
|
||||
return inventory.refresh ?? { revision: 2, devices: [] };
|
||||
},
|
||||
update: (...args) => {
|
||||
calls.push(['update', ...args]);
|
||||
return inventory.update ?? { revision: 3 };
|
||||
},
|
||||
setPolicy: async (...args) => {
|
||||
calls.push(['setPolicy', ...args]);
|
||||
return inventory.setPolicy ?? { revision: 4 };
|
||||
},
|
||||
};
|
||||
const route = createDeviceInventoryRoute({
|
||||
deviceInventory,
|
||||
readBody: async () => {
|
||||
bodyReads += 1;
|
||||
return body;
|
||||
},
|
||||
});
|
||||
return { route, calls, bodyReads: () => bodyReads };
|
||||
}
|
||||
|
||||
test('device route forwards list and refresh query paths as raw JSON responses', async () => {
|
||||
const harness = createHarness({
|
||||
inventory: {
|
||||
snapshot: { revision: 11, devices: [{ id: deviceId }] },
|
||||
refresh: { revision: 12, devices: [] },
|
||||
},
|
||||
});
|
||||
const listResponse = response();
|
||||
assert.equal(await harness.route.handle({
|
||||
method: 'GET',
|
||||
url: '/api/devices?source=ui',
|
||||
}, listResponse), true);
|
||||
assert.equal(listResponse.status, 200);
|
||||
assert.equal(listResponse.headers['content-type'], 'application/json; charset=utf-8');
|
||||
assert.deepEqual(listResponse.payload, { revision: 11, devices: [{ id: deviceId }] });
|
||||
|
||||
const refreshResponse = response();
|
||||
assert.equal(await harness.route.handle({
|
||||
method: 'POST',
|
||||
url: '/api/devices/refresh?source=ui',
|
||||
}, refreshResponse), true);
|
||||
assert.deepEqual(refreshResponse.payload, { revision: 12, devices: [] });
|
||||
assert.deepEqual(harness.calls, [['snapshot'], ['refresh']]);
|
||||
});
|
||||
|
||||
test('device route forwards metadata patch and policy arguments without coercion', async () => {
|
||||
const patch = { expectedRevision: 7, alias: 'Desk', pinned: false, extra: 0 };
|
||||
const metadata = createHarness({ body: patch });
|
||||
const metadataResponse = response();
|
||||
assert.equal(await metadata.route.handle({
|
||||
method: 'PUT',
|
||||
url: `/api/devices/${deviceId}?source=ui`,
|
||||
}, metadataResponse), true);
|
||||
assert.deepEqual(metadata.calls, [[
|
||||
'update',
|
||||
deviceId,
|
||||
{ alias: 'Desk', pinned: false, extra: 0 },
|
||||
7,
|
||||
]]);
|
||||
assert.deepEqual(metadataResponse.payload, { revision: 3 });
|
||||
|
||||
const policy = createHarness({ body: { mode: 42, expectedRevision: '8', ignored: true } });
|
||||
const policyResponse = response();
|
||||
assert.equal(await policy.route.handle({
|
||||
method: 'PUT',
|
||||
url: `/api/devices/${deviceId}/policy?source=ui`,
|
||||
}, policyResponse), true);
|
||||
assert.deepEqual(policy.calls, [['setPolicy', deviceId, 42, '8']]);
|
||||
assert.deepEqual(policyResponse.payload, { revision: 4 });
|
||||
});
|
||||
|
||||
test('device route preserves endpoint gating and strict lowercase IDs', async () => {
|
||||
for (const [method, url] of [
|
||||
['POST', '/api/devices'],
|
||||
['GET', '/api/devices/refresh'],
|
||||
['GET', `/api/devices/${deviceId}`],
|
||||
['POST', `/api/devices/${deviceId}/policy`],
|
||||
]) {
|
||||
const harness = createHarness();
|
||||
await assert.rejects(
|
||||
harness.route.handle({ method, url }, response()),
|
||||
(error) => error.code === 'ENDPOINT_NOT_FOUND',
|
||||
);
|
||||
assert.deepEqual(harness.calls, []);
|
||||
assert.equal(harness.bodyReads(), 0);
|
||||
}
|
||||
|
||||
for (const url of [
|
||||
'/api/other',
|
||||
'/api/devices/dev_0123456789ABCDEF',
|
||||
'/api/devices/dev_short',
|
||||
]) {
|
||||
const harness = createHarness();
|
||||
assert.equal(await harness.route.handle({ method: 'PUT', url }, response()), false);
|
||||
assert.deepEqual(harness.calls, []);
|
||||
assert.equal(harness.bodyReads(), 0);
|
||||
}
|
||||
|
||||
for (const [method, url] of [
|
||||
['GET', '/api/devices'],
|
||||
['POST', '/api/devices/refresh'],
|
||||
['PUT', `/api/devices/${deviceId}`],
|
||||
['PUT', `/api/devices/${deviceId}/policy`],
|
||||
]) {
|
||||
const client = createHarness({ inventory: null });
|
||||
await assert.rejects(
|
||||
client.route.handle({ method, url }, response()),
|
||||
(error) => error.code === 'ENDPOINT_NOT_FOUND',
|
||||
);
|
||||
assert.equal(client.bodyReads(), 0);
|
||||
}
|
||||
});
|
||||
|
||||
test('device route propagates synchronous and asynchronous service errors unchanged', async () => {
|
||||
const syncError = new Error('snapshot failed');
|
||||
const syncRoute = createDeviceInventoryRoute({
|
||||
deviceInventory: {
|
||||
snapshot: () => { throw syncError; },
|
||||
refresh: async () => ({}),
|
||||
update: () => ({}),
|
||||
setPolicy: async () => ({}),
|
||||
},
|
||||
readBody: async () => ({}),
|
||||
});
|
||||
await assert.rejects(
|
||||
syncRoute.handle({ method: 'GET', url: '/api/devices' }, response()),
|
||||
(error) => error === syncError,
|
||||
);
|
||||
|
||||
const asyncError = new Error('refresh failed');
|
||||
const asyncRoute = createDeviceInventoryRoute({
|
||||
deviceInventory: {
|
||||
snapshot: () => ({}),
|
||||
refresh: async () => { throw asyncError; },
|
||||
update: () => ({}),
|
||||
setPolicy: async () => ({}),
|
||||
},
|
||||
readBody: async () => ({}),
|
||||
});
|
||||
await assert.rejects(
|
||||
asyncRoute.handle({ method: 'POST', url: '/api/devices/refresh' }, response()),
|
||||
(error) => error === asyncError,
|
||||
);
|
||||
});
|
||||
|
||||
test('device route is the only HTTP owner while lifecycle stays in composition', () => {
|
||||
const index = readFileSync(new URL('../../src/server/index.ts', import.meta.url), 'utf8');
|
||||
const route = readFileSync(
|
||||
new URL('../../src/server/http/routes/deviceInventoryRoute.ts', import.meta.url),
|
||||
'utf8',
|
||||
);
|
||||
assert.match(index, /createDeviceInventoryRoute\(\{/);
|
||||
assert.match(index, /deviceInventoryRoute\.handle\(req, res\)/);
|
||||
assert.doesNotMatch(index, /\/api\/devices/);
|
||||
assert.doesNotMatch(index, /deviceInventory\.(?:snapshot|update|setPolicy)\(/);
|
||||
assert.match(index, /deviceInventory\.reconcilePolicies\(\)/);
|
||||
assert.match(index, /deviceInventory\.refresh\(\)/);
|
||||
assert.match(route, /DEVICE_PATH/);
|
||||
assert.match(route, /DEVICE_POLICY_PATH/);
|
||||
});
|
||||
Reference in New Issue
Block a user