Files
qinglong/packages/ql3-runtime-core/test/pluginPackageWorkflowAdministration.test.cjs

680 lines
20 KiB
JavaScript

const assert = require('node:assert/strict');
const { test } = require('node:test');
const {
InvalidPluginPackageWorkflowAdministrationMutationError,
PLUGIN_PACKAGE_WORKFLOW_RUN_EVENT_LIST_SCHEMA,
PLUGIN_PACKAGE_WORKFLOW_RUN_INSPECTION_SCHEMA,
PLUGIN_PACKAGE_WORKFLOW_RUN_LIST_SCHEMA,
PLUGIN_PACKAGE_WORKFLOW_STEP_RUN_LIST_SCHEMA,
normalizeAuthorizedPluginPackageWorkflowAdmission,
normalizeAuthorizedPluginPackageWorkflowCancellation,
normalizeAuthorizedPluginPackageWorkflowRunEventList,
normalizeAuthorizedPluginPackageWorkflowRunInspection,
normalizeAuthorizedPluginPackageWorkflowRunList,
normalizeAuthorizedPluginPackageWorkflowStepRunList,
normalizePluginPackageWorkflowCancellationResult,
normalizePluginPackageWorkflowRunEventListResult,
normalizePluginPackageWorkflowRunInspectionResult,
normalizePluginPackageWorkflowRunListResult,
normalizePluginPackageWorkflowStepRunListResult,
} = require('@qinglong/runtime-core/plugin-package-workflow-administration');
const {
createInitialPluginPackageAutomationPublication,
} = require('../dist/plugin-package/pluginPackageAutomationPublication');
const {
createPluginPackageWorkflowExecutionPlan,
} = require('@qinglong/runtime-core/plugin-package-workflow-execution-plan');
const {
pluginPackageTaskReconciliationFixture,
} = require('../../../test/contracts/pluginPackageTaskReconciliationRepositoryContract.cjs');
test('binds Workflow administration to one exact allowed audit and explicit subpath', () => {
const value = pluginPackageTaskReconciliationFixture(
'workflow-administration',
{
workflows: [
{
schema: 'qinglong/plugin-package-workflow-resource@v1',
id: 'daily',
name: 'Daily workflow',
enabled: true,
steps: [{ id: 'collect', task: 'alpha', needs: [] }],
},
],
},
);
const plan = createPluginPackageWorkflowExecutionPlan({
planId: 'workflow-administration-plan',
runId: 'workflow-administration-run',
workflowId: 'daily',
stepRunIds: { collect: 'workflow-administration-step' },
publication: createInitialPluginPackageAutomationPublication(
value.revision,
value.registry,
2_000,
),
revision: value.revision,
taskSpecSemanticRegistry: value.registry,
plannedAtMs: 3_000,
});
const admission = {
plan,
actor: { type: 'user', id: 'workflow-owner' },
fence: { projectVersion: 2, bindingVersion: 3 },
audit: {
eventId: '00000000-0000-4000-8000-000000000001',
requestId: 'workflow-administration-request',
operationId: 'workflow.start',
projectId: plan.target.projectId,
subject: { type: 'user', id: 'workflow-owner' },
authenticationId: 'workflow-administration-authentication',
outcome: 'allowed',
reasons: ['permission_granted'],
fence: { projectVersion: 2, bindingVersion: 3 },
occurredAtMs: plan.plannedAtMs,
},
};
assert.deepEqual(
normalizeAuthorizedPluginPackageWorkflowAdmission(admission),
admission,
);
assert.throws(
() =>
normalizeAuthorizedPluginPackageWorkflowAdmission({
...admission,
audit: { ...admission.audit, operationId: 'workflow.read' },
}),
InvalidPluginPackageWorkflowAdministrationMutationError,
);
assert.throws(
() =>
normalizeAuthorizedPluginPackageWorkflowAdmission({
...admission,
fence: { ...admission.fence, bindingVersion: 4 },
}),
InvalidPluginPackageWorkflowAdministrationMutationError,
);
const subpath = require('@qinglong/runtime-core/plugin-package-workflow-administration');
const root = require('../dist');
assert.equal(
subpath.normalizeAuthorizedPluginPackageWorkflowAdmission,
normalizeAuthorizedPluginPackageWorkflowAdmission,
);
assert.equal(
root.normalizeAuthorizedPluginPackageWorkflowAdmission,
undefined,
);
});
test('binds Workflow cancellation to run.stop audit identity and low-sensitive result', () => {
const cancellation = {
projectId: 'project-1',
packageName: 'example-package',
runId: '95000000-0000-4000-8000-000000000001',
mutationId: '9a000000-0000-4000-8000-000000000001',
runEventId: '9b000000-0000-4000-8000-000000000001',
actor: { type: 'user', id: 'workflow-owner' },
fence: { projectVersion: 2, bindingVersion: 3 },
audit: {
eventId: '9c000000-0000-4000-8000-000000000001',
requestId: 'workflow-cancel-1',
operationId: 'workflow.cancel',
projectId: 'project-1',
subject: { type: 'user', id: 'workflow-owner' },
authenticationId: 'workflow-administration-authentication',
outcome: 'allowed',
reasons: ['permission_granted'],
fence: { projectVersion: 2, bindingVersion: 3 },
occurredAtMs: 4_000,
},
};
assert.deepEqual(
normalizeAuthorizedPluginPackageWorkflowCancellation(cancellation),
cancellation,
);
assert.throws(
() =>
normalizeAuthorizedPluginPackageWorkflowCancellation({
...cancellation,
audit: { ...cancellation.audit, operationId: 'workflow.start' },
}),
InvalidPluginPackageWorkflowAdministrationMutationError,
);
assert.deepEqual(
normalizePluginPackageWorkflowCancellationResult({
status: 'accepted',
projectId: cancellation.projectId,
packageName: cancellation.packageName,
workflowId: 'daily',
runId: cancellation.runId,
runStatus: 'running',
runVersion: 5,
eventSequence: 5,
cancelRequestedAtMs: 4_000,
cancelReason: 'user',
}),
{
status: 'accepted',
projectId: cancellation.projectId,
packageName: cancellation.packageName,
workflowId: 'daily',
runId: cancellation.runId,
runStatus: 'running',
runVersion: 5,
eventSequence: 5,
cancelRequestedAtMs: 4_000,
cancelReason: 'user',
},
);
});
test('binds Workflow Run inspection to an exact allowed audit fence and target', () => {
const inspection = {
projectId: 'project-1',
packageName: 'example-package',
workflowId: 'daily',
runId: '95000000-0000-4000-8000-000000000002',
actor: { type: 'user', id: 'workflow-owner' },
fence: { projectVersion: 2, bindingVersion: 3 },
audit: {
eventId: '9c000000-0000-4000-8000-000000000002',
requestId: 'workflow-inspect-1',
operationId: 'workflow.run.read',
projectId: 'project-1',
subject: { type: 'user', id: 'workflow-owner' },
authenticationId: 'workflow-administration-authentication',
outcome: 'allowed',
reasons: ['permission_granted'],
fence: { projectVersion: 2, bindingVersion: 3 },
occurredAtMs: 4_000,
},
};
assert.deepEqual(
normalizeAuthorizedPluginPackageWorkflowRunInspection(inspection),
inspection,
);
for (const invalid of [
{
...inspection,
audit: { ...inspection.audit, operationId: 'workflow.read' },
},
{
...inspection,
audit: { ...inspection.audit, projectId: 'project-2' },
},
{
...inspection,
fence: { ...inspection.fence, bindingVersion: 4 },
},
{ ...inspection, workflowId: 'Daily' },
{ ...inspection, unexpected: true },
]) {
assert.throws(
() => normalizeAuthorizedPluginPackageWorkflowRunInspection(invalid),
InvalidPluginPackageWorkflowAdministrationMutationError,
);
}
});
test('normalizes a low-sensitive Workflow Run inspection projection and missing target', () => {
const target = {
schema: PLUGIN_PACKAGE_WORKFLOW_RUN_INSPECTION_SCHEMA,
projectId: 'project-1',
packageName: 'example-package',
workflowId: 'daily',
runId: '95000000-0000-4000-8000-000000000002',
};
const stepStatusCounts = {
pending: 0,
ready: 1,
waiting_approval: 0,
running: 1,
lost: 0,
succeeded: 1,
failed: 0,
skipped: 0,
cancelled: 0,
timed_out: 0,
};
const found = {
...target,
found: true,
run: {
status: 'running',
version: 4,
eventSequence: 3,
createdAtMs: 1_000,
queuedAtMs: 1_100,
startedAtMs: 1_200,
finishedAtMs: null,
cancelRequestedAtMs: 1_300,
cancelReason: 'user',
},
stepCount: 3,
stepStatusCounts,
};
const normalized = normalizePluginPackageWorkflowRunInspectionResult(found);
assert.deepEqual(normalized, found);
assert.ok(Object.isFrozen(normalized));
assert.ok(Object.isFrozen(normalized.run));
assert.ok(Object.isFrozen(normalized.stepStatusCounts));
assert.deepEqual(
normalizePluginPackageWorkflowRunInspectionResult({
...target,
found: false,
run: null,
stepCount: null,
stepStatusCounts: null,
}),
{
...target,
found: false,
run: null,
stepCount: null,
stepStatusCounts: null,
},
);
for (const invalid of [
{ ...found, unexpected: true },
{ ...found, stepCount: 4 },
{
...found,
run: { ...found.run, cancelRequestedAtMs: null },
},
{
...found,
stepStatusCounts: { ...stepStatusCounts, unknown: 0 },
},
{
...target,
found: false,
run: found.run,
stepCount: null,
stepStatusCounts: null,
},
]) {
assert.throws(
() => normalizePluginPackageWorkflowRunInspectionResult(invalid),
InvalidPluginPackageWorkflowAdministrationMutationError,
);
}
});
test('binds a bounded Workflow Run list to run.read authority and a newest-first cursor', () => {
const query = {
projectId: 'project-1',
packageName: 'example-package',
workflowId: 'daily',
limit: 32,
after: {
admittedAtMs: 2_000,
runId: '95000000-0000-4000-8000-000000000012',
},
actor: { type: 'user', id: 'workflow-owner' },
fence: { projectVersion: 2, bindingVersion: 3 },
audit: {
eventId: '9c000000-0000-4000-8000-000000000012',
requestId: 'workflow-run-list-1',
operationId: 'workflow.run.list',
projectId: 'project-1',
subject: { type: 'user', id: 'workflow-owner' },
authenticationId: 'workflow-administration-authentication',
outcome: 'allowed',
reasons: ['permission_granted'],
fence: { projectVersion: 2, bindingVersion: 3 },
occurredAtMs: 4_000,
},
};
assert.deepEqual(normalizeAuthorizedPluginPackageWorkflowRunList(query), query);
for (const invalid of [
{ ...query, limit: 65 },
{ ...query, after: { admittedAtMs: -1, runId: query.after.runId } },
{ ...query, audit: { ...query.audit, operationId: 'workflow.run.read' } },
{ ...query, unexpected: true },
]) {
assert.throws(
() => normalizeAuthorizedPluginPackageWorkflowRunList(invalid),
InvalidPluginPackageWorkflowAdministrationMutationError,
);
}
});
test('normalizes only a low-sensitive newest-first Workflow Run page', () => {
const target = {
schema: PLUGIN_PACKAGE_WORKFLOW_RUN_LIST_SCHEMA,
projectId: 'project-1',
packageName: 'example-package',
workflowId: 'daily',
after: null,
};
const runs = [
{
runId: '95000000-0000-4000-8000-000000000012',
status: 'running',
version: 2,
eventSequence: 1,
stepCount: 2,
admittedAtMs: 2_000,
queuedAtMs: 2_001,
startedAtMs: 2_002,
finishedAtMs: null,
cancelRequestedAtMs: null,
cancelReason: null,
},
{
runId: '95000000-0000-4000-8000-000000000011',
status: 'queued',
version: 1,
eventSequence: 0,
stepCount: 1,
admittedAtMs: 2_000,
queuedAtMs: 2_000,
startedAtMs: null,
finishedAtMs: null,
cancelRequestedAtMs: null,
cancelReason: null,
},
];
const page = {
...target,
runs,
truncated: true,
next: { admittedAtMs: 2_000, runId: runs[1].runId },
};
const normalized = normalizePluginPackageWorkflowRunListResult(page);
assert.deepEqual(normalized, page);
assert.ok(Object.isFrozen(normalized));
assert.ok(Object.isFrozen(normalized.runs));
assert.ok(Object.isFrozen(normalized.runs[0]));
assert.deepEqual(
normalizePluginPackageWorkflowRunListResult({
...target,
runs: [],
truncated: false,
next: null,
}),
{ ...target, runs: [], truncated: false, next: null },
);
for (const invalid of [
{ ...page, runs: [...runs].reverse() },
{ ...page, next: { admittedAtMs: 2_000, runId: runs[0].runId } },
{
...page,
runs: [{ ...runs[0], planDigest: 'private' }],
truncated: false,
next: null,
},
{ ...page, unexpected: true },
]) {
assert.throws(
() => normalizePluginPackageWorkflowRunListResult(invalid),
InvalidPluginPackageWorkflowAdministrationMutationError,
);
}
});
test('binds a bounded Workflow StepRun list to run.read authority and a keyset cursor', () => {
const query = {
projectId: 'project-1',
packageName: 'example-package',
workflowId: 'daily',
runId: '95000000-0000-4000-8000-000000000003',
limit: 32,
after: {
stepKey: 'collect',
id: '95000000-0000-4000-8000-000000000004',
},
actor: { type: 'user', id: 'workflow-owner' },
fence: { projectVersion: 2, bindingVersion: 3 },
audit: {
eventId: '9c000000-0000-4000-8000-000000000003',
requestId: 'workflow-step-list-1',
operationId: 'workflow.step.list',
projectId: 'project-1',
subject: { type: 'user', id: 'workflow-owner' },
authenticationId: 'workflow-administration-authentication',
outcome: 'allowed',
reasons: ['permission_granted'],
fence: { projectVersion: 2, bindingVersion: 3 },
occurredAtMs: 4_000,
},
};
assert.deepEqual(
normalizeAuthorizedPluginPackageWorkflowStepRunList(query),
query,
);
for (const invalid of [
{ ...query, limit: 65 },
{ ...query, after: { stepKey: 'collect' } },
{
...query,
audit: { ...query.audit, operationId: 'workflow.run.read' },
},
{ ...query, unexpected: true },
]) {
assert.throws(
() => normalizeAuthorizedPluginPackageWorkflowStepRunList(invalid),
InvalidPluginPackageWorkflowAdministrationMutationError,
);
}
});
test('normalizes only the low-sensitive ordered Workflow StepRun page', () => {
const target = {
schema: PLUGIN_PACKAGE_WORKFLOW_STEP_RUN_LIST_SCHEMA,
projectId: 'project-1',
packageName: 'example-package',
workflowId: 'daily',
runId: '95000000-0000-4000-8000-000000000003',
};
const stepRuns = [
{
id: '95000000-0000-4000-8000-000000000004',
parentStepRunId: null,
stepKey: 'collect',
kind: 'task',
required: true,
status: 'ready',
version: 1,
attemptCount: 0,
readyAtMs: 1_100,
startedAtMs: null,
finishedAtMs: null,
resultCode: null,
createdAtMs: 1_000,
updatedAtMs: 1_100,
},
{
id: '95000000-0000-4000-8000-000000000005',
parentStepRunId: null,
stepKey: 'summarize',
kind: 'task',
required: true,
status: 'running',
version: 2,
attemptCount: 1,
readyAtMs: 1_100,
startedAtMs: 1_200,
finishedAtMs: null,
resultCode: null,
createdAtMs: 1_000,
updatedAtMs: 1_200,
},
];
const found = {
...target,
found: true,
stepRuns,
truncated: true,
next: { stepKey: 'summarize', id: stepRuns[1].id },
};
const normalized = normalizePluginPackageWorkflowStepRunListResult(found);
assert.deepEqual(normalized, found);
assert.ok(Object.isFrozen(normalized));
assert.ok(Object.isFrozen(normalized.stepRuns));
assert.ok(Object.isFrozen(normalized.stepRuns[0]));
assert.ok(Object.isFrozen(normalized.next));
assert.deepEqual(
normalizePluginPackageWorkflowStepRunListResult({
...target,
found: false,
stepRuns: [],
truncated: false,
next: null,
}),
{
...target,
found: false,
stepRuns: [],
truncated: false,
next: null,
},
);
for (const invalid of [
{ ...found, next: { stepKey: 'collect', id: stepRuns[0].id } },
{ ...found, stepRuns: [...stepRuns].reverse() },
{
...found,
stepRuns: [{ ...stepRuns[0], inputRef: 'artifact:private' }],
truncated: false,
next: null,
},
{ ...found, unexpected: true },
]) {
assert.throws(
() => normalizePluginPackageWorkflowStepRunListResult(invalid),
InvalidPluginPackageWorkflowAdministrationMutationError,
);
}
});
test('binds a bounded Workflow RunEvent list to run.read and a sequence cursor', () => {
const query = {
projectId: 'project-1',
packageName: 'example-package',
workflowId: 'daily',
runId: '95000000-0000-4000-8000-000000000006',
limit: 32,
afterSequence: 4,
actor: { type: 'user', id: 'workflow-owner' },
fence: { projectVersion: 2, bindingVersion: 3 },
audit: {
eventId: '9c000000-0000-4000-8000-000000000004',
requestId: 'workflow-event-list-1',
operationId: 'workflow.event.list',
projectId: 'project-1',
subject: { type: 'user', id: 'workflow-owner' },
authenticationId: 'workflow-administration-authentication',
outcome: 'allowed',
reasons: ['permission_granted'],
fence: { projectVersion: 2, bindingVersion: 3 },
occurredAtMs: 4_000,
},
};
assert.deepEqual(
normalizeAuthorizedPluginPackageWorkflowRunEventList(query),
query,
);
for (const invalid of [
{ ...query, limit: 65 },
{ ...query, afterSequence: -1 },
{
...query,
audit: { ...query.audit, operationId: 'workflow.run.read' },
},
{ ...query, unexpected: true },
]) {
assert.throws(
() => normalizeAuthorizedPluginPackageWorkflowRunEventList(invalid),
InvalidPluginPackageWorkflowAdministrationMutationError,
);
}
});
test('normalizes only a contiguous content-free Workflow RunEvent page', () => {
const target = {
schema: PLUGIN_PACKAGE_WORKFLOW_RUN_EVENT_LIST_SCHEMA,
projectId: 'project-1',
packageName: 'example-package',
workflowId: 'daily',
runId: '95000000-0000-4000-8000-000000000006',
afterSequence: 1,
};
const events = [
{
id: '95000000-0000-4000-8000-000000000007',
sequence: 2,
type: 'workflow.task_attempt_admitted',
stepRunId: '95000000-0000-4000-8000-000000000004',
createdAtMs: 1_100,
},
{
id: '95000000-0000-4000-8000-000000000008',
sequence: 3,
type: 'workflow.task_attempt.running',
stepRunId: '95000000-0000-4000-8000-000000000004',
createdAtMs: 1_200,
},
];
const found = {
...target,
found: true,
headSequence: 4,
events,
truncated: true,
nextAfterSequence: 3,
};
const normalized = normalizePluginPackageWorkflowRunEventListResult(found);
assert.deepEqual(normalized, found);
assert.ok(Object.isFrozen(normalized));
assert.ok(Object.isFrozen(normalized.events));
assert.ok(Object.isFrozen(normalized.events[0]));
assert.deepEqual(
normalizePluginPackageWorkflowRunEventListResult({
...target,
found: false,
headSequence: null,
events: [],
truncated: false,
nextAfterSequence: null,
}),
{
...target,
found: false,
headSequence: null,
events: [],
truncated: false,
nextAfterSequence: null,
},
);
for (const invalid of [
{ ...found, nextAfterSequence: 2 },
{ ...found, events: [{ ...events[0], sequence: 3 }, events[1]] },
{
...found,
events: [{ ...events[0], payload: { secret: 'private' } }],
headSequence: 2,
truncated: false,
nextAfterSequence: null,
},
{
...found,
headSequence: 3,
truncated: false,
nextAfterSequence: null,
events: [events[0]],
},
]) {
assert.throws(
() => normalizePluginPackageWorkflowRunEventListResult(invalid),
InvalidPluginPackageWorkflowAdministrationMutationError,
);
}
});