mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-21 18:08:20 +08:00
feat(ql3): establish 3.0 incubation baseline
This commit is contained in:
@@ -0,0 +1,264 @@
|
||||
'use strict';
|
||||
|
||||
const assert = require('node:assert/strict');
|
||||
const { test } = require('node:test');
|
||||
const {
|
||||
ClusterRunCancellationFenceRejectedError,
|
||||
ClusterRunCancellationNotFoundError,
|
||||
} = require('@qinglong/runtime-core/cluster-run-cancellation');
|
||||
const {
|
||||
PostgresClusterRunCancellationRepository,
|
||||
} = require('../dist/entrypoints/runtime');
|
||||
|
||||
function command(overrides = {}) {
|
||||
return {
|
||||
projectId: 'project-1',
|
||||
runId: 'run-1',
|
||||
mutationId: 'mutation-1',
|
||||
eventId: '018f0000-0000-7000-8000-000000000001',
|
||||
subject: { type: 'user', id: 'user-1' },
|
||||
policyFence: { projectVersion: 2, bindingVersion: 3 },
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function run(overrides = {}) {
|
||||
return {
|
||||
projectId: 'project-1',
|
||||
runStatus: 'running',
|
||||
runVersion: 4,
|
||||
eventSequence: 6,
|
||||
cancelRequestedAtMs: null,
|
||||
cancelReason: null,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function fixture(options = {}) {
|
||||
const calls = [];
|
||||
const client = {
|
||||
async query(sql, params = []) {
|
||||
const normalized = sql.replace(/\s+/g, ' ').trim();
|
||||
calls.push({ sql: normalized, params });
|
||||
if (
|
||||
normalized.startsWith('BEGIN') || normalized === 'COMMIT' ||
|
||||
normalized === 'ROLLBACK' || normalized.startsWith('SELECT set_config')
|
||||
) return { rows: [], rowCount: 0 };
|
||||
if (normalized.includes('FROM "ql3"."projects"')) {
|
||||
return {
|
||||
rows: options.projectRows ?? [{
|
||||
projectStatus: 'active',
|
||||
projectVersion: 2,
|
||||
}],
|
||||
rowCount: 1,
|
||||
};
|
||||
}
|
||||
if (normalized.includes('FROM "ql3"."project_role_bindings"')) {
|
||||
return {
|
||||
rows: options.bindingRows ?? [{
|
||||
bindingVersion: 3,
|
||||
bindingState: 'active',
|
||||
bindingRole: 'operator',
|
||||
}],
|
||||
rowCount: 1,
|
||||
};
|
||||
}
|
||||
if (
|
||||
normalized.includes('FROM "ql3"."runs"') &&
|
||||
normalized.startsWith('SELECT')
|
||||
) {
|
||||
return {
|
||||
rows: options.runRows ?? [run()],
|
||||
rowCount: options.runRows?.length ?? 1,
|
||||
};
|
||||
}
|
||||
if (
|
||||
normalized.includes(
|
||||
'FROM "ql3"."plugin_package_workflow_admissions"',
|
||||
)
|
||||
) {
|
||||
const rows = options.workflowAdmissionRows ?? [
|
||||
{
|
||||
projectId: 'project-1',
|
||||
packageName: 'example',
|
||||
workflowId: 'daily',
|
||||
},
|
||||
];
|
||||
return { rows, rowCount: rows.length };
|
||||
}
|
||||
if (normalized.includes('statement_timestamp()')) {
|
||||
return { rows: [{ nowMs: options.nowMs ?? 1_000 }], rowCount: 1 };
|
||||
}
|
||||
if (normalized.startsWith('UPDATE "ql3"."runs"')) {
|
||||
return {
|
||||
rows: options.updatedRows ?? [run({
|
||||
runVersion: 5,
|
||||
eventSequence: 7,
|
||||
cancelRequestedAtMs: options.nowMs ?? 1_000,
|
||||
cancelReason: 'user',
|
||||
})],
|
||||
rowCount: options.updatedRows?.length ?? 1,
|
||||
};
|
||||
}
|
||||
if (normalized.startsWith('INSERT INTO "ql3"."run_events"')) {
|
||||
return { rows: [], rowCount: 1 };
|
||||
}
|
||||
throw new Error(`Unexpected SQL: ${normalized}`);
|
||||
},
|
||||
release() { calls.push({ sql: 'RELEASE', params: [] }); },
|
||||
};
|
||||
return {
|
||||
repository: new PostgresClusterRunCancellationRepository({
|
||||
async connect() { return client; },
|
||||
}),
|
||||
calls,
|
||||
};
|
||||
}
|
||||
|
||||
test('revalidates policy authority and commits one database-timed intent', async () => {
|
||||
const { repository, calls } = fixture();
|
||||
assert.deepEqual(await repository.requestUserCancellation(command()), {
|
||||
status: 'accepted',
|
||||
projectId: 'project-1',
|
||||
runId: 'run-1',
|
||||
runStatus: 'running',
|
||||
runVersion: 5,
|
||||
eventSequence: 7,
|
||||
cancelRequestedAtMs: 1_000,
|
||||
cancelReason: 'user',
|
||||
});
|
||||
const projectIndex = calls.findIndex(({ sql }) =>
|
||||
sql.includes('FROM "ql3"."projects"'));
|
||||
const bindingIndex = calls.findIndex(({ sql }) =>
|
||||
sql.includes('FROM "ql3"."project_role_bindings"'));
|
||||
const runIndex = calls.findIndex(({ sql }) =>
|
||||
sql.includes('FROM "ql3"."runs"'));
|
||||
assert.ok(projectIndex < bindingIndex && bindingIndex < runIndex);
|
||||
const update = calls.find(({ sql }) =>
|
||||
sql.startsWith('UPDATE "ql3"."runs"'));
|
||||
assert.deepEqual(update.params, ['run-1', 1_000, 5, 7, 4]);
|
||||
const event = calls.find(({ sql }) =>
|
||||
sql.startsWith('INSERT INTO "ql3"."run_events"'));
|
||||
assert.equal(event.params[0], command().eventId);
|
||||
assert.equal(event.params[3], 'user-cancel:mutation-1');
|
||||
assert.equal(event.params[4], 'user');
|
||||
assert.equal(JSON.parse(event.params[6]).reason, 'user');
|
||||
assert.equal(calls.some(({ sql }) => sql === 'COMMIT'), true);
|
||||
});
|
||||
|
||||
test('returns existing intent and terminal state without adding an event', async () => {
|
||||
const existing = fixture({
|
||||
runRows: [run({ cancelRequestedAtMs: 900, cancelReason: 'timeout' })],
|
||||
});
|
||||
assert.equal(
|
||||
(await existing.repository.requestUserCancellation(command())).status,
|
||||
'already_requested',
|
||||
);
|
||||
assert.equal(existing.calls.some(({ sql }) =>
|
||||
sql.startsWith('INSERT INTO "ql3"."run_events"')), false);
|
||||
|
||||
const terminal = fixture({
|
||||
runRows: [run({ runStatus: 'succeeded', runVersion: 5 })],
|
||||
});
|
||||
assert.equal(
|
||||
(await terminal.repository.requestUserCancellation(command())).status,
|
||||
'already_terminal',
|
||||
);
|
||||
assert.equal(terminal.calls.some(({ sql }) =>
|
||||
sql.startsWith('UPDATE "ql3"."runs"')), false);
|
||||
});
|
||||
|
||||
test('accepts cancellation for a lost Run that still owns retry authority', async () => {
|
||||
const { repository } = fixture({
|
||||
runRows: [run({ runStatus: 'lost' })],
|
||||
updatedRows: [run({
|
||||
runStatus: 'lost',
|
||||
runVersion: 5,
|
||||
eventSequence: 7,
|
||||
cancelRequestedAtMs: 1_000,
|
||||
cancelReason: 'user',
|
||||
})],
|
||||
});
|
||||
assert.equal(
|
||||
(await repository.requestUserCancellation(command())).status,
|
||||
'accepted',
|
||||
);
|
||||
});
|
||||
|
||||
test('rejects a revoked policy fence before locking the Run', async () => {
|
||||
const { repository, calls } = fixture({
|
||||
bindingRows: [{
|
||||
bindingVersion: 4,
|
||||
bindingState: 'revoked',
|
||||
bindingRole: null,
|
||||
}],
|
||||
});
|
||||
await assert.rejects(
|
||||
repository.requestUserCancellation(command()),
|
||||
(error) =>
|
||||
error instanceof ClusterRunCancellationFenceRejectedError &&
|
||||
error.reason === 'authorization_changed',
|
||||
);
|
||||
assert.equal(calls.some(({ sql }) => sql.includes('FROM "ql3"."runs"')), false);
|
||||
assert.equal(calls.some(({ sql }) => sql === 'ROLLBACK'), true);
|
||||
});
|
||||
|
||||
test('masks cross-Project and missing Runs', async () => {
|
||||
for (const runRows of [[], [run({ projectId: 'project-other' })]]) {
|
||||
const { repository } = fixture({ runRows });
|
||||
await assert.rejects(
|
||||
repository.requestUserCancellation(command()),
|
||||
ClusterRunCancellationNotFoundError,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test('binds Workflow cancellation to the immutable admission target', async () => {
|
||||
const targeted = command({
|
||||
workflowTarget: { packageName: 'example', workflowId: 'daily' },
|
||||
});
|
||||
const accepted = fixture();
|
||||
assert.equal(
|
||||
(await accepted.repository.requestUserCancellation(targeted)).status,
|
||||
'accepted',
|
||||
);
|
||||
const admission = accepted.calls.find(({ sql }) =>
|
||||
sql.includes('FROM "ql3"."plugin_package_workflow_admissions"'),
|
||||
);
|
||||
assert.deepEqual(admission.params, ['run-1']);
|
||||
assert.equal(
|
||||
admission.sql.includes('FOR SHARE'),
|
||||
false,
|
||||
'immutable admission lookup must not require UPDATE authority',
|
||||
);
|
||||
|
||||
for (const workflowAdmissionRows of [
|
||||
[],
|
||||
[
|
||||
{
|
||||
projectId: 'project-1',
|
||||
packageName: 'other',
|
||||
workflowId: 'daily',
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
projectId: 'project-1',
|
||||
packageName: 'example',
|
||||
workflowId: 'other',
|
||||
},
|
||||
],
|
||||
]) {
|
||||
const rejected = fixture({ workflowAdmissionRows });
|
||||
await assert.rejects(
|
||||
rejected.repository.requestUserCancellation(targeted),
|
||||
ClusterRunCancellationNotFoundError,
|
||||
);
|
||||
assert.equal(
|
||||
rejected.calls.some(({ sql }) =>
|
||||
sql.startsWith('UPDATE "ql3"."runs"'),
|
||||
),
|
||||
false,
|
||||
);
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user