Files
qinglong/packages/ql3-local-sqlite/test/runLostRetryRepository.test.cjs

150 lines
4.0 KiB
JavaScript

'use strict';
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 {
migrateLocalSqlitePath,
openLocalSqliteRuntimeDatabase,
} = require('../dist');
async function fixture(t) {
const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'ql3-lost-retry-'));
const databasePath = path.join(directory, 'qinglong3.sqlite');
await migrateLocalSqlitePath({ databasePath, profile: 'edge' });
const runtime = await openLocalSqliteRuntimeDatabase({
databasePath,
profile: 'edge',
});
t.after(async () => {
await runtime.close();
fs.rmSync(directory, { recursive: true, force: true });
});
return runtime;
}
async function insertLostRun(runtime, suffix, retryPolicy) {
const runId = `run-lost-${suffix}`;
const attemptId = `attempt-lost-${suffix}`;
await runtime.runRepository.transaction(async (transaction) => {
await transaction.insertRun({
id: runId,
projectId: 'default',
taskId: `task-${suffix}`,
taskRevision: 'revision-1',
triggerType: 'manual',
executionOrigin: 'manual',
executionOwner: 'runtime',
status: 'lost',
version: 1,
eventSequence: 1,
priority: 0,
createdAtMs: 100,
queuedAtMs: 110,
startedAtMs: 120,
errorCode: 'LOCAL_RECOVERY_EXECUTION_NOT_RUNNING',
errorSummary: 'lost',
});
await transaction.insertAttempt({
id: attemptId,
runId,
attempt: 1,
status: 'lost',
executorType: 'local_process',
callbackSequence: 0,
createdAtMs: 110,
startedAtMs: 120,
finishedAtMs: 130,
errorCode: 'LOCAL_RECOVERY_EXECUTION_NOT_RUNNING',
errorSummary: 'lost',
});
if (retryPolicy) {
await transaction.insertRetryPolicy({
runId,
maxAttempts: 3,
retryOnLost: true,
safety: 'idempotent',
backoffBaseMs: 0,
backoffMaxMs: 0,
version: 0,
createdAtMs: 100,
updatedAtMs: 100,
});
}
});
return { runId, attemptId };
}
test('atomically schedules and requeues one safe Local lost Run exactly once', async (t) => {
const runtime = await fixture(t);
const ids = await insertLostRun(runtime, 'safe', true);
assert.deepEqual(await runtime.runLostRetry.reconcilePage({ limit: 1 }), {
scanned: 1,
scheduled: 1,
requeued: 0,
failed: 0,
raced: 0,
hasMore: false,
});
assert.equal(
(await runtime.runRepository.findRunById(ids.runId)).status,
'retry_wait',
);
assert.deepEqual(await runtime.runLostRetry.reconcilePage({ limit: 1 }), {
scanned: 1,
scheduled: 0,
requeued: 1,
failed: 0,
raced: 0,
hasMore: false,
});
const run = await runtime.runRepository.findRunById(ids.runId);
const attempt = await runtime.runRepository.findLatestAttemptByRunId(
ids.runId,
);
assert.equal(run.status, 'queued');
assert.equal(run.version, 4);
assert.equal(attempt.attempt, 2);
assert.equal(attempt.status, 'claimed');
assert.equal(attempt.executorType, 'local_process');
assert.deepEqual(
(await runtime.runRepository.listEvents(ids.runId)).map(
(event) => event.type,
),
['run.retry_wait', 'run.queued', 'attempt.claimed'],
);
assert.equal(
(await runtime.runLostRetry.reconcilePage({ limit: 1 })).scanned,
0,
);
});
test('fails closed without an admitted retry policy and preserves a bounded page', async (t) => {
const runtime = await fixture(t);
const first = await insertLostRun(runtime, 'disabled-a', false);
await insertLostRun(runtime, 'disabled-b', false);
const result = await runtime.runLostRetry.reconcilePage({ limit: 1 });
assert.deepEqual(result, {
scanned: 1,
scheduled: 0,
requeued: 0,
failed: 1,
raced: 0,
hasMore: true,
});
assert.equal(
(await runtime.runRepository.findRunById(first.runId)).errorCode,
'RUN_LOST_RETRY_DISABLED',
);
await assert.rejects(
runtime.runLostRetry.reconcilePage({ limit: 65 }),
/page size/,
);
});