mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-21 01:32:44 +08:00
246 lines
6.8 KiB
JavaScript
246 lines
6.8 KiB
JavaScript
const assert = require('node:assert/strict');
|
|
const { spawnSync } = require('node:child_process');
|
|
const { DatabaseSync } = require('node:sqlite');
|
|
const path = require('node:path');
|
|
const { test } = require('node:test');
|
|
|
|
const {
|
|
RunCancellationNotFoundError,
|
|
} = require('@qinglong/runtime-core/run-cancellation');
|
|
const {
|
|
openLocalSqliteRuntimeDatabase,
|
|
} = require('@qinglong/local-sqlite/runtime');
|
|
const {
|
|
runLocalRunStopCommandFile,
|
|
} = require('../dist/run-management/runStopCommand.js');
|
|
const {
|
|
auditRows,
|
|
localManagementFixture,
|
|
writeCommand,
|
|
} = require('./localManagementFixture.cjs');
|
|
|
|
function uuid(value) {
|
|
return `019f9300-0000-4000-8000-${value.toString(16).padStart(12, '0')}`;
|
|
}
|
|
|
|
async function createRunningSource(value, suffix = 1) {
|
|
const runtime = await openLocalSqliteRuntimeDatabase({
|
|
databasePath: value.databasePath,
|
|
profile: 'edge',
|
|
});
|
|
try {
|
|
const task = (
|
|
await runtime.taskDefinitions.appendTaskDefinitionRevision({
|
|
projectId: 'default',
|
|
taskId: `stop-product-task-${suffix}`,
|
|
expectedRevision: null,
|
|
mutationId: uuid(10 + suffix),
|
|
name: 'Stop product task',
|
|
kind: 'command',
|
|
spec: {
|
|
schema: 'qinglong/command@v1',
|
|
config: {
|
|
command: {
|
|
kind: 'argv',
|
|
file: '/bin/echo',
|
|
args: ['stop-product'],
|
|
},
|
|
},
|
|
},
|
|
labels: {},
|
|
enabled: true,
|
|
occurredAtMs: value.now,
|
|
})
|
|
).definition;
|
|
return await (
|
|
await runtime.taskStartRepository()
|
|
).startTask({
|
|
projectId: 'default',
|
|
taskId: task.taskId,
|
|
mutationId: uuid(20 + suffix),
|
|
expectedRevision: task.revision,
|
|
expectedContentDigest: task.contentDigest,
|
|
runId: uuid(30 + suffix),
|
|
attemptId: uuid(40 + suffix),
|
|
createdEventId: uuid(50 + suffix),
|
|
queuedEventId: uuid(60 + suffix),
|
|
subject: { type: 'user', id: 'automation-user' },
|
|
policyFence: { projectVersion: 1, bindingVersion: 1 },
|
|
});
|
|
} finally {
|
|
await runtime.close();
|
|
}
|
|
}
|
|
|
|
function request(value, runId, overrides = {}) {
|
|
return {
|
|
projectId: 'default',
|
|
runId,
|
|
mutationId: uuid(100),
|
|
requestId: 'local-run-stop-product-1',
|
|
auditEventId: uuid(101),
|
|
failureAuditEventId: uuid(102),
|
|
occurredAtMs: value.now,
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
test('strongly stops one Local Run and replays through the unified CLI', async (t) => {
|
|
const value = await localManagementFixture(t);
|
|
const source = await createRunningSource(value);
|
|
const commandFile = writeCommand(
|
|
value,
|
|
'run.stop',
|
|
request(value, source.runId),
|
|
'run-stop',
|
|
);
|
|
|
|
const accepted = await runLocalRunStopCommandFile(commandFile);
|
|
assert.equal(accepted.schemaVersion, 1);
|
|
assert.equal(accepted.operation, 'run.stop');
|
|
assert.equal(accepted.stop.status, 'accepted');
|
|
assert.equal(accepted.stop.runId, source.runId);
|
|
assert.equal(accepted.stop.cancelReason, 'user');
|
|
|
|
const cli = path.join(
|
|
__dirname,
|
|
'..',
|
|
'dist',
|
|
'run-management',
|
|
'runManagementCli.js',
|
|
);
|
|
const replay = spawnSync(
|
|
process.execPath,
|
|
[cli, 'stop', '--command-file', commandFile],
|
|
{ encoding: 'utf8' },
|
|
);
|
|
assert.equal(replay.status, 0, replay.stderr);
|
|
assert.equal(JSON.parse(replay.stdout).stop.status, 'already_requested');
|
|
|
|
assert.deepEqual(
|
|
auditRows(value.databasePath)
|
|
.filter(({ operationId }) => operationId === 'run.stop')
|
|
.map((row) => ({ ...row })),
|
|
[
|
|
{
|
|
eventId: uuid(101),
|
|
operationId: 'run.stop',
|
|
outcome: 'allowed',
|
|
reasonsJson: '["role_grant","strong_authentication"]',
|
|
},
|
|
],
|
|
);
|
|
const database = new DatabaseSync(value.databasePath, { readOnly: true });
|
|
try {
|
|
assert.deepEqual(
|
|
{
|
|
...database
|
|
.prepare(
|
|
`SELECT "cancel_reason" AS "cancelReason",
|
|
(SELECT count(*) FROM "RunEvents"
|
|
WHERE "run_id" = ? AND "type" = 'run.cancel_requested')
|
|
AS "eventCount"
|
|
FROM "Runs" WHERE "id" = ?`,
|
|
)
|
|
.get(source.runId, source.runId),
|
|
},
|
|
{ cancelReason: 'user', eventCount: 1 },
|
|
);
|
|
} finally {
|
|
database.close();
|
|
}
|
|
});
|
|
|
|
test('audits a missing stop target without creating Run state', async (t) => {
|
|
const value = await localManagementFixture(t);
|
|
const commandFile = writeCommand(
|
|
value,
|
|
'run.stop',
|
|
request(value, 'missing-run', {
|
|
mutationId: uuid(110),
|
|
requestId: 'local-run-stop-missing',
|
|
auditEventId: uuid(111),
|
|
failureAuditEventId: uuid(112),
|
|
}),
|
|
'run-stop-missing',
|
|
);
|
|
await assert.rejects(
|
|
runLocalRunStopCommandFile(commandFile),
|
|
RunCancellationNotFoundError,
|
|
);
|
|
assert.deepEqual(
|
|
auditRows(value.databasePath)
|
|
.filter(({ operationId }) => operationId === 'run.stop')
|
|
.map((row) => ({ ...row })),
|
|
[
|
|
{
|
|
eventId: uuid(112),
|
|
operationId: 'run.stop',
|
|
outcome: 'denied',
|
|
reasonsJson: '["run_not_found"]',
|
|
},
|
|
],
|
|
);
|
|
});
|
|
|
|
test('denies a Viewer and preserves the running Run', async (t) => {
|
|
const value = await localManagementFixture(t);
|
|
const source = await createRunningSource(value, 2);
|
|
const policyDatabase = new DatabaseSync(value.databasePath);
|
|
try {
|
|
policyDatabase
|
|
.prepare(
|
|
`INSERT INTO "QingLong3ProjectRoleBindings" (
|
|
"project_id", "subject_type", "subject_id", "version", "state",
|
|
"role", "mutation_id", "changed_by_type", "changed_by_id",
|
|
"created_at_ms"
|
|
) VALUES ('default', 'user', 'automation-user', 2, 'active', 'viewer',
|
|
'run-stop-viewer-binding', 'user', 'automation-user', ?)`,
|
|
)
|
|
.run(value.now + 1);
|
|
} finally {
|
|
policyDatabase.close();
|
|
}
|
|
const commandFile = writeCommand(
|
|
value,
|
|
'run.stop',
|
|
request(value, source.runId, {
|
|
mutationId: uuid(120),
|
|
requestId: 'local-run-stop-viewer',
|
|
auditEventId: uuid(121),
|
|
failureAuditEventId: uuid(122),
|
|
}),
|
|
'run-stop-viewer',
|
|
);
|
|
await assert.rejects(
|
|
runLocalRunStopCommandFile(commandFile),
|
|
(error) => error?.code === 'LOCAL_RUN_STOP_COMMAND_AUTHORIZATION_REJECTED',
|
|
);
|
|
assert.deepEqual(
|
|
auditRows(value.databasePath)
|
|
.filter(({ operationId }) => operationId === 'run.stop')
|
|
.map((row) => ({ ...row })),
|
|
[
|
|
{
|
|
eventId: uuid(122),
|
|
operationId: 'run.stop',
|
|
outcome: 'denied',
|
|
reasonsJson: '["run_stop_fence_rejected"]',
|
|
},
|
|
],
|
|
);
|
|
const database = new DatabaseSync(value.databasePath, { readOnly: true });
|
|
try {
|
|
assert.equal(
|
|
database
|
|
.prepare(
|
|
`SELECT "cancel_requested_at_ms" AS value FROM "Runs" WHERE "id" = ?`,
|
|
)
|
|
.get(source.runId).value,
|
|
null,
|
|
);
|
|
} finally {
|
|
database.close();
|
|
}
|
|
});
|