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

151 lines
4.1 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 {
createLocalExecutionContextRecipe,
createLocalTaskExecutionRevision,
} = require('@qinglong/runtime-core/local-dispatch');
const {
RunRepositoryConstraintError,
} = require('@qinglong/runtime-core/run-repository');
const {
migrateLocalSqlitePath,
openLocalSqliteRuntimeDatabase,
} = require('../dist');
async function fixture(t) {
const directory = fs.mkdtempSync(
path.join(os.tmpdir(), 'ql3-local-dispatch-store-'),
);
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;
}
function run(id, priority, queuedAtMs, cancelled = false) {
return {
id: `run-${id}`,
projectId: 'default',
taskId: 'task-1',
taskRevision: 'revision-1',
triggerType: 'manual',
executionOrigin: 'manual',
executionOwner: 'runtime',
status: 'queued',
version: 0,
eventSequence: 0,
priority,
createdAtMs: 1,
queuedAtMs,
...(cancelled ? { cancelRequestedAtMs: 2, cancelReason: 'user' } : {}),
};
}
function attempt(id, createdAtMs) {
return {
id: `attempt-${id}`,
runId: `run-${id}`,
attempt: 1,
status: 'claimed',
executorType: 'local_process',
callbackSequence: 0,
createdAtMs,
};
}
test('append-only definitions replay by content and reject identity conflicts', async (t) => {
const runtime = await fixture(t);
const recipe = createLocalExecutionContextRecipe({
environment: [{ name: 'VALUE', kind: 'public', value: 'one' }],
createdAtMs: 1,
});
assert.equal(
await runtime.localDispatch.appendLocalExecutionContextRecipe(recipe),
'inserted',
);
assert.equal(
await runtime.localDispatch.appendLocalExecutionContextRecipe({
...recipe,
createdAtMs: 2,
}),
'existing',
);
const revision = createLocalTaskExecutionRevision({
projectId: 'default',
taskId: 'task-1',
taskRevision: 'revision-1',
executorType: 'local_process',
command: { kind: 'argv', file: '/bin/echo', args: ['one'] },
contextRef: recipe.contextRef,
createdAtMs: 1,
});
assert.equal(
await runtime.localDispatch.appendLocalTaskExecutionRevision(revision),
'inserted',
);
assert.equal(
await runtime.localDispatch.appendLocalTaskExecutionRevision({
...revision,
createdAtMs: 2,
}),
'existing',
);
await assert.rejects(
runtime.localDispatch.appendLocalTaskExecutionRevision(
createLocalTaskExecutionRevision({
...revision,
command: { kind: 'argv', file: '/bin/echo', args: ['different'] },
}),
),
RunRepositoryConstraintError,
);
});
test('candidate pages are bounded, ordered and exclude cancellation intent', async (t) => {
const runtime = await fixture(t);
await runtime.runRepository.transaction(async (transaction) => {
for (const value of [
['a', 10, 2, 2, false],
['b', 10, 1, 3, false],
['c', 5, 1, 1, false],
['cancelled', 100, 1, 1, true],
]) {
await transaction.insertRun(run(value[0], value[1], value[2], value[4]));
await transaction.insertAttempt(attempt(value[0], value[3]));
}
});
const first = await runtime.localDispatch.listLocalDispatchCandidates({
limit: 2,
});
assert.deepEqual(
first.candidates.map(({ runId }) => runId),
['run-b', 'run-a'],
);
assert.equal(first.truncated, true);
const last = first.candidates.at(-1);
const second = await runtime.localDispatch.listLocalDispatchCandidates({
limit: 2,
after: {
priority: last.priority,
queuedAtMs: last.queuedAtMs,
attemptCreatedAtMs: last.attemptCreatedAtMs,
attemptId: last.attemptId,
},
});
assert.deepEqual(
second.candidates.map(({ runId }) => runId),
['run-c'],
);
assert.equal(second.truncated, false);
});