Files
qinglong/packages/ql3-local-api/test/runCancellationRoute.test.cjs

138 lines
3.8 KiB
JavaScript

const assert = require('node:assert/strict');
const { test } = require('node:test');
const {
RUN_CANCELLATION_SCHEMA,
RunCancellationFenceRejectedError,
RunCancellationNotFoundError,
RunCancellationUnavailableError,
} = require('@qinglong/runtime-core/run-cancellation');
const {
createLocalApiRunCancellationRoute,
} = require('../dist/run/runCancellationRoute.js');
const PRINCIPAL = Object.freeze({
subject: Object.freeze({ type: 'user', id: 'user-1' }),
authenticationId: 'credential:user-1',
authenticatedAtMs: 9_000,
expiresAtMs: 11_000,
assurance: 'single_factor',
});
const FENCE = Object.freeze({ projectVersion: 2, bindingVersion: 3 });
function accepted(overrides = {}) {
return {
status: 'accepted',
projectId: 'project-1',
runId: 'run-1',
runStatus: 'running',
runVersion: 5,
eventSequence: 7,
cancelRequestedAtMs: 10_000,
cancelReason: 'user',
...overrides,
};
}
function request(overrides = {}) {
return {
projectId: 'project-1',
runId: 'run-1',
body: { schema: RUN_CANCELLATION_SCHEMA, mutationId: 'mutation-1' },
principal: PRINCIPAL,
policyFence: FENCE,
...overrides,
};
}
test('publishes one profile-neutral durable cancellation command', async () => {
let observed;
const route = createLocalApiRunCancellationRoute(
{
async requestUserCancellation(command) {
observed = command;
return accepted();
},
},
() => '018f0000-0000-7000-8000-000000000001',
);
assert.deepEqual(await route.handle(request()), {
statusCode: 202,
body: { schema: RUN_CANCELLATION_SCHEMA, ...accepted() },
});
assert.deepEqual(observed, {
projectId: 'project-1',
runId: 'run-1',
mutationId: 'mutation-1',
eventId: '018f0000-0000-7000-8000-000000000001',
subject: PRINCIPAL.subject,
policyFence: FENCE,
});
});
test('rejects malformed bodies and incomplete authorization fences', async () => {
let calls = 0;
const route = createLocalApiRunCancellationRoute(
{
async requestUserCancellation() {
calls += 1;
return accepted();
},
},
() => '018f0000-0000-7000-8000-000000000001',
);
assert.deepEqual(await route.handle(request({ body: { mutationId: 'x' } })), {
statusCode: 400,
body: {
code: 'invalid_run_cancellation_request',
schema: RUN_CANCELLATION_SCHEMA,
},
});
assert.deepEqual(await route.handle(request({ policyFence: null })), {
statusCode: 503,
body: { code: 'run_cancellation_unavailable' },
});
assert.equal(calls, 0);
});
test('maps replay, terminal, missing, fence and unavailable outcomes', async () => {
for (const outcome of [
accepted({ status: 'already_requested' }),
{
status: 'already_terminal',
projectId: 'project-1',
runId: 'run-1',
runStatus: 'succeeded',
runVersion: 6,
eventSequence: 8,
},
]) {
const route = createLocalApiRunCancellationRoute(
{ async requestUserCancellation() { return outcome; } },
() => '018f0000-0000-7000-8000-000000000001',
);
assert.equal((await route.handle(request())).statusCode, 200);
}
for (const [error, statusCode, code] of [
[new RunCancellationNotFoundError(), 404, 'run_not_found'],
[
new RunCancellationFenceRejectedError('authorization_changed'),
409,
'run_cancellation_fence_rejected',
],
[
new RunCancellationUnavailableError(),
503,
'run_cancellation_unavailable',
],
]) {
const route = createLocalApiRunCancellationRoute(
{ async requestUserCancellation() { throw error; } },
() => '018f0000-0000-7000-8000-000000000001',
);
const response = await route.handle(request());
assert.equal(response.statusCode, statusCode);
assert.equal(response.body.code, code);
}
});