Files
qinglong/packages/ql3-runtime-core/test/boundedRunStepListProjection.test.cjs
T

283 lines
6.8 KiB
JavaScript

const assert = require('node:assert/strict');
const { spawnSync } = require('node:child_process');
const path = require('node:path');
const { test } = require('node:test');
const {
BoundedRunStepListProjectionUnavailableError,
DEFAULT_BOUNDED_RUN_STEP_LIST_LIMIT,
InvalidBoundedRunStepListProjectionError,
MAX_BOUNDED_RUN_STEP_LIST_LIMIT,
executeBoundedRunStepListProjection,
} = require('../dist/run/projection/boundedRunStepListProjection.js');
const { createStepRunRecord } = require('../dist/run/stepRun.js');
function run(projectId = 'prj_default') {
return Object.freeze({ id: 'run-1', projectId });
}
function step(id, stepKey, overrides = {}) {
return createStepRunRecord({
id,
runId: 'run-1',
parentStepRunId: 'step-parent',
stepKey,
kind: 'tool',
definitionRef: 'tool:private.internal@1.0.0',
definitionDigest: 'a'.repeat(64),
required: true,
initialStatus: 'ready',
inputRef: 'artifact:private-input',
mutationId: `create-${id}`,
createdAtMs: 1_000,
...overrides,
});
}
test('projects one bounded low-sensitive Step page and stable cursor', async () => {
const calls = [];
const first = step('step-1', 'build');
const second = step('step-2', 'deploy');
const result = await executeBoundedRunStepListProjection(
{
async findRunById(runId) {
calls.push(['run', runId]);
return run();
},
},
{
async listByRun(query) {
calls.push(['steps', query]);
return {
stepRuns: [first, second],
truncated: true,
next: { stepKey: second.stepKey, id: second.id },
};
},
},
'prj_default',
'run-1',
{ after: { stepKey: 'admit', stepRunId: 'step-0' }, limit: 2 },
);
assert.deepEqual(calls, [
['run', 'run-1'],
[
'steps',
{
runId: 'run-1',
limit: 2,
after: { stepKey: 'admit', id: 'step-0' },
},
],
]);
assert.deepEqual(result, {
found: true,
steps: [
{
id: 'step-1',
parentStepRunId: 'step-parent',
stepKey: 'build',
kind: 'tool',
required: true,
status: 'ready',
version: 1,
attemptCount: 0,
readyAtMs: 1_000,
startedAtMs: null,
finishedAtMs: null,
resultCode: null,
createdAtMs: 1_000,
updatedAtMs: 1_000,
},
{
id: 'step-2',
parentStepRunId: 'step-parent',
stepKey: 'deploy',
kind: 'tool',
required: true,
status: 'ready',
version: 1,
attemptCount: 0,
readyAtMs: 1_000,
startedAtMs: null,
finishedAtMs: null,
resultCode: null,
createdAtMs: 1_000,
updatedAtMs: 1_000,
},
],
hasMore: true,
next: { stepKey: 'deploy', stepRunId: 'step-2' },
});
const serialized = JSON.stringify(result);
for (const hidden of [
'definition',
'private-input',
'approvalRequestId',
'errorSummary',
'stepRunDigest',
]) {
assert.equal(serialized.includes(hidden), false);
}
});
test('uses default and maximum bounds and preserves an empty page', async () => {
const calls = [];
const stepRuns = {
async listByRun(query) {
calls.push(query);
return { stepRuns: [], truncated: false };
},
};
const runs = {
async findRunById() {
return run();
},
};
assert.deepEqual(
await executeBoundedRunStepListProjection(
runs,
stepRuns,
'prj_default',
'run-1',
{},
),
{ found: true, steps: [], hasMore: false, next: null },
);
await executeBoundedRunStepListProjection(
runs,
stepRuns,
'prj_default',
'run-1',
{ limit: MAX_BOUNDED_RUN_STEP_LIST_LIMIT },
);
assert.deepEqual(calls, [
{ runId: 'run-1', limit: DEFAULT_BOUNDED_RUN_STEP_LIST_LIMIT },
{ runId: 'run-1', limit: MAX_BOUNDED_RUN_STEP_LIST_LIMIT },
]);
});
test('masks absence and Project mismatch without reading StepRuns', async () => {
for (const value of [null, run('prj_other')]) {
let reads = 0;
const result = await executeBoundedRunStepListProjection(
{
async findRunById() {
return value;
},
},
{
async listByRun() {
reads += 1;
return { stepRuns: [], truncated: false };
},
},
'prj_default',
'run-1',
{},
);
assert.deepEqual(result, {
found: false,
steps: [],
hasMore: false,
next: null,
});
assert.equal(reads, 0);
}
});
test('fails closed on invalid input, repository failure and corrupt pages', async () => {
const runs = {
async findRunById() {
return run();
},
};
const empty = {
async listByRun() {
return { stepRuns: [], truncated: false };
},
};
for (const input of [
null,
{ limit: 0 },
{ limit: 65 },
{ after: { stepKey: 'build' } },
{ after: { stepKey: 'build', stepRunId: 'step-1', extra: true } },
{ extra: true },
]) {
await assert.rejects(
executeBoundedRunStepListProjection(
runs,
empty,
'prj_default',
'run-1',
input,
),
InvalidBoundedRunStepListProjectionError,
);
}
const duplicateStepKey = [step('step-1', 'build'), step('step-2', 'build')];
for (const page of [
{ stepRuns: duplicateStepKey, truncated: false },
{
stepRuns: [step('step-1', 'build')],
truncated: true,
next: { stepKey: 'other', id: 'step-1' },
},
{
stepRuns: [{ ...step('step-1', 'build'), status: 'succeeded' }],
truncated: false,
},
]) {
await assert.rejects(
executeBoundedRunStepListProjection(
runs,
{
async listByRun() {
return page;
},
},
'prj_default',
'run-1',
{ limit: 2 },
),
BoundedRunStepListProjectionUnavailableError,
);
}
await assert.rejects(
executeBoundedRunStepListProjection(
{
async findRunById() {
throw new Error('offline');
},
},
empty,
'prj_default',
'run-1',
{},
),
BoundedRunStepListProjectionUnavailableError,
);
});
test('leaf import does not load Tool Registry or SemVer', () => {
const entry = path.resolve(
__dirname,
'../dist/run/projection/boundedRunStepListProjection.js',
);
const result = spawnSync(
process.execPath,
[
'-e',
`require(${JSON.stringify(entry)});
const loaded = Object.keys(require.cache);
if (loaded.some((value) => /node_modules[\\/]semver(?:[\\/]|$)/.test(value))) process.exit(2);
if (loaded.some((value) => /tool-execution[\\/]tool-registry/.test(value))) process.exit(3);
process.stdout.write(String(loaded.length));`,
],
{ encoding: 'utf8' },
);
assert.equal(result.status, 0, result.stderr);
assert.ok(Number(result.stdout) <= 5);
});