Files
qinglong/packages/ql3-cluster-control/test/routeRegistry.test.cjs
T

185 lines
5.4 KiB
JavaScript

const assert = require('node:assert/strict');
const { test } = require('node:test');
const {
CLUSTER_CONTROL_ROUTE_REGISTRY_LIMITS,
ClusterControlRouteRegistryConfigurationError,
ClusterControlRouteResolutionError,
createClusterControlRouteRegistry,
isClusterControlRouteRegistry,
} = require('@qinglong/cluster-control/routes');
function metadata(overrides = {}) {
return Object.freeze({
requestId: 'request-1',
method: 'POST',
path: '/api/v3/projects/prj_default/runs',
query: Object.freeze({}),
headers: Object.freeze({}),
signal: new AbortController().signal,
...overrides,
});
}
function definition(overrides = {}) {
return {
method: 'POST',
path: '/api/v3/projects/{projectId}/runs',
operationId: 'run.create',
permission: 'run.start',
projectParameter: 'projectId',
handle(request, parameters) {
return {
statusCode: 202,
body: {
projectId: request.projectId,
parameter: parameters.projectId,
},
};
},
...overrides,
};
}
test('compiles one immutable reviewed route and owns its Project scope', async () => {
const source = definition({ allowedQuery: ['dry_run'] });
const registry = createClusterControlRouteRegistry([source]);
source.operationId = 'forged.operation';
assert.equal(isClusterControlRouteRegistry(registry), true);
assert.equal(isClusterControlRouteRegistry({ ...registry }), false);
assert.equal(Object.isFrozen(registry), true);
assert.equal(registry.contractVersion, 1);
assert.equal(registry.size, 1);
const route = registry.resolve(
metadata({ query: Object.freeze({ dry_run: Object.freeze(['true']) }) }),
);
assert.equal(route.operationId, 'run.create');
assert.equal(route.permission, 'run.start');
assert.equal(route.projectId, 'prj_default');
assert.equal(Object.isFrozen(route), true);
assert.deepEqual(
await route.handle({
request: { ...metadata(), body: { taskId: 'task-1' } },
principal: {
subject: { type: 'user', id: 'usr_primary' },
authenticationId: 'session:1',
authenticatedAtMs: 1,
expiresAtMs: 2,
assurance: 'multi_factor',
},
operationId: route.operationId,
permission: route.permission,
projectId: route.projectId,
policyFence: { projectVersion: 1, bindingVersion: 1 },
}),
{
statusCode: 202,
body: { projectId: 'prj_default', parameter: 'prj_default' },
},
);
});
test('rejects widened, ambiguous and unbounded route definitions at startup', () => {
const invalidSets = [
[definition({ extra: true })],
[definition({ path: '/api/v3/projects/{projectId}/' })],
[definition({ path: '/api/v3/projects/%7BprojectId%7D/runs' })],
[definition({ projectParameter: 'missing' })],
[definition(), definition({ operationId: 'run.create' })],
[
definition(),
definition({
path: '/api/v3/projects/fixed/runs',
operationId: 'run.create.fixed',
}),
],
];
for (const definitions of invalidSets) {
assert.throws(
() => createClusterControlRouteRegistry(definitions),
ClusterControlRouteRegistryConfigurationError,
);
}
const tooMany = Array.from(
{ length: CLUSTER_CONTROL_ROUTE_REGISTRY_LIMITS.maxRoutes + 1 },
(_, index) =>
definition({
path: `/api/v3/routes/route-${index}`,
operationId: `route.operation:${index}`,
projectParameter: null,
}),
);
assert.throws(
() => createClusterControlRouteRegistry(tooMany),
ClusterControlRouteRegistryConfigurationError,
);
});
test('rejects non-canonical paths and unknown query before returning a route', () => {
const registry = createClusterControlRouteRegistry([
definition({ allowedQuery: ['dry_run'] }),
]);
for (const path of [
'/api/v3/projects/prj_default/runs/',
'/api/v3/projects//runs',
'/api/v3/projects/%2e%2e/runs',
'/api/v3/projects/prj_default\\runs',
]) {
assert.throws(
() => registry.resolve(metadata({ path })),
(error) =>
error instanceof ClusterControlRouteResolutionError &&
error.statusCode === 400 &&
error.code === 'invalid_route_path',
);
}
assert.throws(
() =>
registry.resolve(
metadata({ query: Object.freeze({ debug: Object.freeze(['1']) }) }),
),
(error) =>
error instanceof ClusterControlRouteResolutionError &&
error.code === 'invalid_route_query',
);
assert.equal(
registry.resolve(metadata({ method: 'GET' })),
null,
'method is part of the reviewed route identity',
);
assert.equal(
registry.resolve(metadata({ path: '/api/v3/projects/prj_default/tasks' })),
null,
);
});
test('bounds repeated query values and rejects control characters', () => {
const registry = createClusterControlRouteRegistry([
definition({ allowedQuery: ['cursor'] }),
]);
for (const values of [
[],
Array.from(
{
length:
CLUSTER_CONTROL_ROUTE_REGISTRY_LIMITS.maxQueryValuesPerParameter + 1,
},
() => 'value',
),
['unsafe\u0000value'],
['x'.repeat(CLUSTER_CONTROL_ROUTE_REGISTRY_LIMITS.maxQueryValueBytes + 1)],
]) {
assert.throws(
() =>
registry.resolve(
metadata({ query: Object.freeze({ cursor: Object.freeze(values) }) }),
),
(error) =>
error instanceof ClusterControlRouteResolutionError &&
error.code === 'invalid_route_query',
);
}
});