Files
qinglong/packages/ql3-local-sqlite/test/startupRecovery.test.cjs
T

134 lines
3.6 KiB
JavaScript

const assert = require('node:assert/strict');
const fs = require('node:fs');
const os = require('node:os');
const path = require('node:path');
const { test } = require('node:test');
const {
MAX_LOCAL_RUN_STARTUP_RECOVERY_CANDIDATES,
migrateLocalSqlitePath,
openLocalSqliteRuntimeDatabase,
} = require('../dist');
async function database(t) {
const directory = fs.mkdtempSync(
path.join(os.tmpdir(), 'ql3-local-startup-recovery-'),
);
t.after(() => fs.rmSync(directory, { recursive: true, force: true }));
const databasePath = path.join(directory, 'qinglong3.sqlite');
await migrateLocalSqlitePath({ databasePath, profile: 'edge' });
const opened = await openLocalSqliteRuntimeDatabase({
databasePath,
profile: 'edge',
});
t.after(() => opened.close());
return opened;
}
function run(id, status, executionOwner = 'runtime') {
return {
id,
projectId: 'default',
taskId: `task-${id}`,
taskRevision: `revision-${id}`,
triggerType: 'manual',
executionOrigin: 'manual',
executionOwner,
status,
version: 0,
eventSequence: 0,
priority: 0,
createdAtMs: 1,
};
}
function attempt(id, runId, status = 'running') {
return {
id,
runId,
attempt: 1,
status,
executorType: 'local_process',
callbackSequence: 0,
createdAtMs: 1,
};
}
test('finds only runtime-owned dispatching or running Runs', async (t) => {
const opened = await database(t);
await opened.runRepository.transaction(async (transaction) => {
await transaction.insertRun(run('run-1', 'running'));
await transaction.insertAttempt(attempt('attempt-1', 'run-1'));
await transaction.insertRun(run('run-2', 'dispatching'));
await transaction.insertRun(run('run-3', 'succeeded'));
await transaction.insertRun(run('run-4', 'running', 'legacy'));
});
const page = await opened.startupRecovery.inspectCandidates();
assert.deepEqual(page, {
candidates: [
{
runId: 'run-1',
runStatus: 'running',
activeAttemptCount: 1,
},
{
runId: 'run-2',
runStatus: 'dispatching',
activeAttemptCount: 0,
},
],
truncated: false,
});
});
test('returns a bounded deterministic page and explicit truncation', async (t) => {
const opened = await database(t);
await opened.runRepository.transaction(async (transaction) => {
await transaction.insertRun(run('run-3', 'running'));
await transaction.insertRun(run('run-1', 'running'));
await transaction.insertRun(run('run-2', 'running'));
});
assert.deepEqual(
await opened.startupRecovery.inspectCandidates({ limit: 2 }),
{
candidates: [
{
runId: 'run-1',
runStatus: 'running',
activeAttemptCount: 0,
},
{
runId: 'run-2',
runStatus: 'running',
activeAttemptCount: 0,
},
],
truncated: true,
},
);
});
test('rejects invalid limits and shares repository close fencing', async (t) => {
const opened = await database(t);
await assert.rejects(
opened.startupRecovery.inspectCandidates({ limit: 0 }),
/limit must be between 1/,
);
await assert.rejects(
opened.startupRecovery.inspectCandidates({
limit: MAX_LOCAL_RUN_STARTUP_RECOVERY_CANDIDATES + 1,
}),
/limit must be between 1/,
);
await opened.close();
await assert.rejects(
opened.startupRecovery.inspectCandidates(),
(error) =>
error &&
error.name === 'RunRepositoryOperationError' &&
error.cause instanceof Error &&
error.cause.message === 'Local SQLite Run repository is closed',
);
});