mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-20 16:07:11 +08:00
feat(ql3): establish 3.0 incubation baseline
This commit is contained in:
@@ -0,0 +1,819 @@
|
||||
const assert = require('node:assert/strict');
|
||||
const { test } = require('node:test');
|
||||
|
||||
const {
|
||||
createInitialPluginPackageAutomationPublication,
|
||||
} = require('@qinglong/runtime-core/plugin-package-automation-publication');
|
||||
const {
|
||||
createPluginPackageWorkflowAdmissionBundle,
|
||||
} = require('@qinglong/runtime-core/plugin-package-workflow-execution-plan');
|
||||
const {
|
||||
pluginPackageTaskReconciliationFixture,
|
||||
} = require('../../../test/contracts/pluginPackageTaskReconciliationRepositoryContract.cjs');
|
||||
const {
|
||||
createClusterPluginPackageWorkflowAdministrationCapability,
|
||||
} = require('@qinglong/cluster-control/workflow-administration');
|
||||
const {
|
||||
createClusterControlPluginPackageWorkflowRoutes,
|
||||
} = require('@qinglong/cluster-control/workflow-routes');
|
||||
|
||||
const IDS = Object.freeze({
|
||||
planId: '123e4567-e89b-42d3-a456-426614174000',
|
||||
runId: '123e4567-e89b-42d3-a456-426614174001',
|
||||
collect: '123e4567-e89b-42d3-a456-426614174002',
|
||||
summarize: '123e4567-e89b-42d3-a456-426614174003',
|
||||
});
|
||||
|
||||
function productFixture() {
|
||||
const value = pluginPackageTaskReconciliationFixture('cluster-product', {
|
||||
workflows: [
|
||||
{
|
||||
schema: 'qinglong/plugin-package-workflow-resource@v1',
|
||||
id: 'daily',
|
||||
name: 'Daily workflow',
|
||||
enabled: true,
|
||||
steps: [
|
||||
{ id: 'collect', task: 'alpha', needs: [] },
|
||||
{ id: 'summarize', task: 'beta', needs: ['collect'] },
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
return {
|
||||
...value,
|
||||
publication: createInitialPluginPackageAutomationPublication(
|
||||
value.revision,
|
||||
value.registry,
|
||||
2_000,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
function principal(now = 3_000) {
|
||||
return Object.freeze({
|
||||
subject: { type: 'api_app', id: 'workflow-operator' },
|
||||
authenticationId: 'api_credential:workflow-product:1',
|
||||
authenticatedAtMs: now - 1,
|
||||
expiresAtMs: now + 60_000,
|
||||
assurance: 'service',
|
||||
});
|
||||
}
|
||||
|
||||
test('derives the cluster Workflow plan from current server publications and exactly replays it', async () => {
|
||||
const value = productFixture();
|
||||
let storedPlan = null;
|
||||
let cancellationCommand;
|
||||
let inspectionCommand;
|
||||
let runListCommand;
|
||||
let stepListCommand;
|
||||
let runEventListCommand;
|
||||
const admissions = [];
|
||||
const capability = createClusterPluginPackageWorkflowAdministrationCapability(
|
||||
{
|
||||
async findCurrent(projectId, packageName) {
|
||||
assert.equal(projectId, value.projectId);
|
||||
assert.equal(packageName, value.packageName);
|
||||
return value.publication;
|
||||
},
|
||||
},
|
||||
{
|
||||
async find(generationDigest) {
|
||||
assert.equal(
|
||||
generationDigest,
|
||||
value.revision.generation.generationDigest,
|
||||
);
|
||||
return value.revision;
|
||||
},
|
||||
},
|
||||
{
|
||||
async findPlanByPlanId(planId) {
|
||||
assert.equal(planId, IDS.planId);
|
||||
return storedPlan;
|
||||
},
|
||||
async admitAuthorized(admission) {
|
||||
admissions.push(admission);
|
||||
const bundle = createPluginPackageWorkflowAdmissionBundle(
|
||||
admission.plan,
|
||||
);
|
||||
const status = storedPlan ? 'existing' : 'created';
|
||||
storedPlan = admission.plan;
|
||||
return { status, receipt: bundle.receipt };
|
||||
},
|
||||
},
|
||||
{
|
||||
async inspectRunAuthorized(command) {
|
||||
inspectionCommand = command;
|
||||
return {
|
||||
schema: 'qinglong/plugin-package-workflow-run-inspection@v1',
|
||||
found: false,
|
||||
projectId: command.projectId,
|
||||
packageName: command.packageName,
|
||||
workflowId: command.workflowId,
|
||||
runId: command.runId,
|
||||
run: null,
|
||||
stepCount: null,
|
||||
stepStatusCounts: null,
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
async listRunsAuthorized(command) {
|
||||
runListCommand = command;
|
||||
return {
|
||||
schema: 'qinglong/plugin-package-workflow-run-list@v1',
|
||||
projectId: command.projectId,
|
||||
packageName: command.packageName,
|
||||
workflowId: command.workflowId,
|
||||
after: command.after,
|
||||
runs: [],
|
||||
truncated: false,
|
||||
next: null,
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
async listStepRunsAuthorized(command) {
|
||||
stepListCommand = command;
|
||||
return {
|
||||
schema: 'qinglong/plugin-package-workflow-step-run-list@v1',
|
||||
found: false,
|
||||
projectId: command.projectId,
|
||||
packageName: command.packageName,
|
||||
workflowId: command.workflowId,
|
||||
runId: command.runId,
|
||||
stepRuns: [],
|
||||
truncated: false,
|
||||
next: null,
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
async listRunEventsAuthorized(command) {
|
||||
runEventListCommand = command;
|
||||
return {
|
||||
schema: 'qinglong/plugin-package-workflow-run-event-list@v1',
|
||||
found: false,
|
||||
projectId: command.projectId,
|
||||
packageName: command.packageName,
|
||||
workflowId: command.workflowId,
|
||||
runId: command.runId,
|
||||
afterSequence: command.afterSequence,
|
||||
headSequence: null,
|
||||
events: [],
|
||||
truncated: false,
|
||||
nextAfterSequence: null,
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
async requestUserCancellation(command) {
|
||||
cancellationCommand = command;
|
||||
return {
|
||||
status: 'accepted',
|
||||
projectId: command.projectId,
|
||||
runId: command.runId,
|
||||
runStatus: 'running',
|
||||
runVersion: 4,
|
||||
eventSequence: 4,
|
||||
cancelRequestedAtMs: 3_100,
|
||||
cancelReason: 'user',
|
||||
};
|
||||
},
|
||||
},
|
||||
value.registry,
|
||||
);
|
||||
|
||||
const listed = await capability.inspect(value.projectId, value.packageName);
|
||||
assert.equal(listed.found, true);
|
||||
assert.deepEqual(listed.workflows[0].steps, [
|
||||
{ id: 'collect', task: 'alpha', needs: [] },
|
||||
{ id: 'summarize', task: 'beta', needs: ['collect'] },
|
||||
]);
|
||||
|
||||
const command = {
|
||||
projectId: value.projectId,
|
||||
packageName: value.packageName,
|
||||
workflowId: 'daily',
|
||||
planId: IDS.planId,
|
||||
runId: IDS.runId,
|
||||
stepRunIds: { collect: IDS.collect, summarize: IDS.summarize },
|
||||
principal: principal(),
|
||||
policyFence: { projectVersion: 3, bindingVersion: 7 },
|
||||
plannedAtMs: 3_000,
|
||||
};
|
||||
const created = await capability.start(command);
|
||||
const replay = await capability.start({ ...command, plannedAtMs: 9_000 });
|
||||
assert.equal(created.status, 'created');
|
||||
assert.equal(replay.status, 'existing');
|
||||
assert.equal(replay.plan.planDigest, created.plan.planDigest);
|
||||
assert.equal(
|
||||
created.plan.target.publicationDigest,
|
||||
value.publication.publicationDigest,
|
||||
);
|
||||
assert.equal(admissions[0].audit.eventId, IDS.planId);
|
||||
assert.equal(admissions[0].audit.requestId, IDS.planId);
|
||||
assert.equal(
|
||||
admissions[0].audit.authenticationId,
|
||||
principal().authenticationId,
|
||||
);
|
||||
assert.equal(admissions[1].audit.occurredAtMs, created.plan.plannedAtMs);
|
||||
|
||||
assert.equal(
|
||||
(
|
||||
await capability.cancel({
|
||||
projectId: value.projectId,
|
||||
packageName: value.packageName,
|
||||
workflowId: 'daily',
|
||||
runId: IDS.runId,
|
||||
mutationId: 'workflow-cancel-mutation-1',
|
||||
eventId: '018f0000-0000-7000-8000-000000000091',
|
||||
principal: principal(),
|
||||
policyFence: { projectVersion: 3, bindingVersion: 7 },
|
||||
})
|
||||
).status,
|
||||
'accepted',
|
||||
);
|
||||
assert.deepEqual(cancellationCommand.workflowTarget, {
|
||||
packageName: value.packageName,
|
||||
workflowId: 'daily',
|
||||
});
|
||||
const inspected = await capability.inspectRun({
|
||||
projectId: value.projectId,
|
||||
packageName: value.packageName,
|
||||
workflowId: 'daily',
|
||||
runId: IDS.runId,
|
||||
requestId: 'workflow-run-read-request-1',
|
||||
auditEventId: '123e4567-e89b-42d3-a456-426614174093',
|
||||
principal: principal(),
|
||||
policyFence: { projectVersion: 3, bindingVersion: 7 },
|
||||
observedAtMs: 3_200,
|
||||
});
|
||||
assert.equal(inspected.found, false);
|
||||
assert.equal(inspectionCommand.audit.operationId, 'workflow.run.read');
|
||||
assert.equal(
|
||||
inspectionCommand.audit.requestId,
|
||||
'workflow-run-read-request-1',
|
||||
);
|
||||
assert.deepEqual(inspectionCommand.fence, {
|
||||
projectVersion: 3,
|
||||
bindingVersion: 7,
|
||||
});
|
||||
const runPage = await capability.listRuns({
|
||||
projectId: value.projectId,
|
||||
packageName: value.packageName,
|
||||
workflowId: 'daily',
|
||||
limit: 16,
|
||||
after: { admittedAtMs: 3_100, runId: IDS.runId },
|
||||
requestId: 'workflow-run-list-request-1',
|
||||
auditEventId: '123e4567-e89b-42d3-a456-426614174096',
|
||||
principal: principal(),
|
||||
policyFence: { projectVersion: 3, bindingVersion: 7 },
|
||||
observedAtMs: 3_250,
|
||||
});
|
||||
assert.deepEqual(runPage.runs, []);
|
||||
assert.equal(runListCommand.audit.operationId, 'workflow.run.list');
|
||||
assert.equal(runListCommand.limit, 16);
|
||||
assert.deepEqual(runListCommand.after, {
|
||||
admittedAtMs: 3_100,
|
||||
runId: IDS.runId,
|
||||
});
|
||||
const stepPage = await capability.listStepRuns({
|
||||
projectId: value.projectId,
|
||||
packageName: value.packageName,
|
||||
workflowId: 'daily',
|
||||
runId: IDS.runId,
|
||||
limit: 16,
|
||||
after: { stepKey: 'collect', id: IDS.collect },
|
||||
requestId: 'workflow-step-list-request-1',
|
||||
auditEventId: '123e4567-e89b-42d3-a456-426614174094',
|
||||
principal: principal(),
|
||||
policyFence: { projectVersion: 3, bindingVersion: 7 },
|
||||
observedAtMs: 3_300,
|
||||
});
|
||||
assert.equal(stepPage.found, false);
|
||||
assert.equal(stepListCommand.audit.operationId, 'workflow.step.list');
|
||||
assert.equal(stepListCommand.limit, 16);
|
||||
assert.deepEqual(stepListCommand.after, {
|
||||
stepKey: 'collect',
|
||||
id: IDS.collect,
|
||||
});
|
||||
const eventPage = await capability.listRunEvents({
|
||||
projectId: value.projectId,
|
||||
packageName: value.packageName,
|
||||
workflowId: 'daily',
|
||||
runId: IDS.runId,
|
||||
limit: 16,
|
||||
afterSequence: 2,
|
||||
requestId: 'workflow-event-list-request-1',
|
||||
auditEventId: '123e4567-e89b-42d3-a456-426614174095',
|
||||
principal: principal(),
|
||||
policyFence: { projectVersion: 3, bindingVersion: 7 },
|
||||
observedAtMs: 3_400,
|
||||
});
|
||||
assert.equal(eventPage.found, false);
|
||||
assert.equal(runEventListCommand.audit.operationId, 'workflow.event.list');
|
||||
assert.equal(runEventListCommand.limit, 16);
|
||||
assert.equal(runEventListCommand.afterSequence, 2);
|
||||
});
|
||||
|
||||
test('publishes strict authenticated list and content-free start routes', async () => {
|
||||
let startCommand;
|
||||
let cancellationError;
|
||||
const routes = createClusterControlPluginPackageWorkflowRoutes(
|
||||
{
|
||||
async inspect() {
|
||||
return { found: false, publicationState: null, workflows: [] };
|
||||
},
|
||||
async inspectRun(command) {
|
||||
return {
|
||||
schema: 'qinglong/plugin-package-workflow-run-inspection@v1',
|
||||
found: true,
|
||||
projectId: command.projectId,
|
||||
packageName: command.packageName,
|
||||
workflowId: command.workflowId,
|
||||
runId: command.runId,
|
||||
run: {
|
||||
status: 'running',
|
||||
version: 4,
|
||||
eventSequence: 4,
|
||||
createdAtMs: 3_000,
|
||||
queuedAtMs: 3_010,
|
||||
startedAtMs: 3_020,
|
||||
finishedAtMs: null,
|
||||
cancelRequestedAtMs: null,
|
||||
cancelReason: null,
|
||||
},
|
||||
stepCount: 1,
|
||||
stepStatusCounts: {
|
||||
pending: 0,
|
||||
ready: 0,
|
||||
waiting_approval: 0,
|
||||
running: 1,
|
||||
lost: 0,
|
||||
succeeded: 0,
|
||||
failed: 0,
|
||||
skipped: 0,
|
||||
cancelled: 0,
|
||||
timed_out: 0,
|
||||
},
|
||||
};
|
||||
},
|
||||
async listRuns(command) {
|
||||
return {
|
||||
schema: 'qinglong/plugin-package-workflow-run-list@v1',
|
||||
projectId: command.projectId,
|
||||
packageName: command.packageName,
|
||||
workflowId: command.workflowId,
|
||||
after: command.after,
|
||||
runs: [
|
||||
{
|
||||
runId: IDS.runId,
|
||||
status: 'running',
|
||||
version: 4,
|
||||
eventSequence: 4,
|
||||
stepCount: 2,
|
||||
admittedAtMs: 3_000,
|
||||
queuedAtMs: 3_010,
|
||||
startedAtMs: 3_020,
|
||||
finishedAtMs: null,
|
||||
cancelRequestedAtMs: null,
|
||||
cancelReason: null,
|
||||
},
|
||||
],
|
||||
truncated: true,
|
||||
next: { admittedAtMs: 3_000, runId: IDS.runId },
|
||||
};
|
||||
},
|
||||
async listStepRuns(command) {
|
||||
return {
|
||||
schema: 'qinglong/plugin-package-workflow-step-run-list@v1',
|
||||
found: true,
|
||||
projectId: command.projectId,
|
||||
packageName: command.packageName,
|
||||
workflowId: command.workflowId,
|
||||
runId: command.runId,
|
||||
stepRuns: [
|
||||
{
|
||||
id: IDS.collect,
|
||||
parentStepRunId: null,
|
||||
stepKey: 'collect',
|
||||
kind: 'task',
|
||||
required: true,
|
||||
status: 'running',
|
||||
version: 2,
|
||||
attemptCount: 1,
|
||||
readyAtMs: 3_010,
|
||||
startedAtMs: 3_020,
|
||||
finishedAtMs: null,
|
||||
resultCode: null,
|
||||
createdAtMs: 3_000,
|
||||
updatedAtMs: 3_020,
|
||||
},
|
||||
],
|
||||
truncated: true,
|
||||
next: { stepKey: 'collect', id: IDS.collect },
|
||||
};
|
||||
},
|
||||
async listRunEvents(command) {
|
||||
return {
|
||||
schema: 'qinglong/plugin-package-workflow-run-event-list@v1',
|
||||
found: true,
|
||||
projectId: command.projectId,
|
||||
packageName: command.packageName,
|
||||
workflowId: command.workflowId,
|
||||
runId: command.runId,
|
||||
afterSequence: command.afterSequence,
|
||||
headSequence: 4,
|
||||
events: [
|
||||
{
|
||||
id: '123e4567-e89b-42d3-a456-426614174004',
|
||||
sequence: 3,
|
||||
type: 'workflow.task_attempt.running',
|
||||
stepRunId: IDS.collect,
|
||||
createdAtMs: 3_020,
|
||||
},
|
||||
],
|
||||
truncated: true,
|
||||
nextAfterSequence: 3,
|
||||
};
|
||||
},
|
||||
async start(command) {
|
||||
startCommand = command;
|
||||
return {
|
||||
status: 'created',
|
||||
plan: { planId: command.planId, runId: command.runId },
|
||||
receipt: { receiptDigest: 'a'.repeat(64) },
|
||||
};
|
||||
},
|
||||
async cancel(command) {
|
||||
if (cancellationError) {
|
||||
throw cancellationError;
|
||||
}
|
||||
return {
|
||||
status: 'accepted',
|
||||
projectId: command.projectId,
|
||||
runId: command.runId,
|
||||
runStatus: 'running',
|
||||
runVersion: 4,
|
||||
eventSequence: 4,
|
||||
cancelRequestedAtMs: 3_100,
|
||||
cancelReason: 'user',
|
||||
};
|
||||
},
|
||||
},
|
||||
() => 3_000,
|
||||
() => '123e4567-e89b-42d3-a456-426614174092',
|
||||
);
|
||||
const start = routes.find(
|
||||
({ operationId }) => operationId === 'workflow.start',
|
||||
);
|
||||
assert.ok(start);
|
||||
const authorized = {
|
||||
request: {
|
||||
requestId: 'transport-request-1',
|
||||
body: {
|
||||
schema: 'qinglong/cluster-plugin-package-workflow-start-request@v1',
|
||||
planId: IDS.planId,
|
||||
runId: IDS.runId,
|
||||
stepRunIds: { collect: IDS.collect },
|
||||
},
|
||||
signal: new AbortController().signal,
|
||||
},
|
||||
principal: principal(),
|
||||
operationId: 'workflow.start',
|
||||
permission: 'run.start',
|
||||
projectId: 'project-1',
|
||||
policyFence: { projectVersion: 3, bindingVersion: 7 },
|
||||
};
|
||||
const result = await start.handle(authorized, {
|
||||
packageName: 'example',
|
||||
workflowId: 'daily',
|
||||
});
|
||||
assert.equal(result.statusCode, 201);
|
||||
assert.deepEqual(Object.keys(result.body).sort(), [
|
||||
'planId',
|
||||
'receiptDigest',
|
||||
'replayed',
|
||||
'runId',
|
||||
'schema',
|
||||
'status',
|
||||
]);
|
||||
assert.equal(startCommand.principal.subject.id, 'workflow-operator');
|
||||
assert.equal(startCommand.plannedAtMs, 3_000);
|
||||
|
||||
const inspectRun = routes.find(
|
||||
({ operationId }) => operationId === 'workflow.run.read',
|
||||
);
|
||||
assert.ok(inspectRun);
|
||||
assert.equal(inspectRun.permission, 'run.read');
|
||||
const inspected = await inspectRun.handle(
|
||||
{
|
||||
...authorized,
|
||||
operationId: 'workflow.run.read',
|
||||
permission: 'run.read',
|
||||
request: { ...authorized.request, body: null },
|
||||
},
|
||||
{
|
||||
packageName: 'example',
|
||||
workflowId: 'daily',
|
||||
runId: IDS.runId,
|
||||
},
|
||||
);
|
||||
assert.equal(inspected.statusCode, 200);
|
||||
assert.equal(
|
||||
inspected.body.schema,
|
||||
'qinglong/plugin-package-workflow-run-inspection@v1',
|
||||
);
|
||||
assert.deepEqual(Object.keys(inspected.body.run).sort(), [
|
||||
'cancelReason',
|
||||
'cancelRequestedAtMs',
|
||||
'createdAtMs',
|
||||
'eventSequence',
|
||||
'finishedAtMs',
|
||||
'queuedAtMs',
|
||||
'startedAtMs',
|
||||
'status',
|
||||
'version',
|
||||
]);
|
||||
|
||||
const listRuns = routes.find(
|
||||
({ operationId }) => operationId === 'workflow.run.list',
|
||||
);
|
||||
assert.ok(listRuns);
|
||||
assert.equal(listRuns.permission, 'run.read');
|
||||
assert.deepEqual(listRuns.allowedQuery, [
|
||||
'after_admitted_at_ms',
|
||||
'after_run_id',
|
||||
'limit',
|
||||
]);
|
||||
const listedRuns = await listRuns.handle(
|
||||
{
|
||||
...authorized,
|
||||
operationId: 'workflow.run.list',
|
||||
permission: 'run.read',
|
||||
request: {
|
||||
...authorized.request,
|
||||
body: null,
|
||||
query: {
|
||||
limit: ['1'],
|
||||
after_admitted_at_ms: ['3100'],
|
||||
after_run_id: [IDS.runId],
|
||||
},
|
||||
},
|
||||
},
|
||||
{ packageName: 'example', workflowId: 'daily' },
|
||||
);
|
||||
assert.equal(listedRuns.statusCode, 200);
|
||||
assert.equal(
|
||||
listedRuns.body.schema,
|
||||
'qinglong/plugin-package-workflow-run-list@v1',
|
||||
);
|
||||
assert.deepEqual(Object.keys(listedRuns.body.runs[0]).sort(), [
|
||||
'admittedAtMs',
|
||||
'cancelReason',
|
||||
'cancelRequestedAtMs',
|
||||
'eventSequence',
|
||||
'finishedAtMs',
|
||||
'queuedAtMs',
|
||||
'runId',
|
||||
'startedAtMs',
|
||||
'status',
|
||||
'stepCount',
|
||||
'version',
|
||||
]);
|
||||
const invalidRunCursor = await listRuns.handle(
|
||||
{
|
||||
...authorized,
|
||||
request: {
|
||||
...authorized.request,
|
||||
body: null,
|
||||
query: { after_admitted_at_ms: ['3100'] },
|
||||
},
|
||||
},
|
||||
{ packageName: 'example', workflowId: 'daily' },
|
||||
);
|
||||
assert.equal(invalidRunCursor.statusCode, 400);
|
||||
|
||||
const listSteps = routes.find(
|
||||
({ operationId }) => operationId === 'workflow.step.list',
|
||||
);
|
||||
assert.ok(listSteps);
|
||||
assert.equal(listSteps.permission, 'run.read');
|
||||
assert.deepEqual(listSteps.allowedQuery, [
|
||||
'after_step_key',
|
||||
'after_step_run_id',
|
||||
'limit',
|
||||
]);
|
||||
const listedSteps = await listSteps.handle(
|
||||
{
|
||||
...authorized,
|
||||
operationId: 'workflow.step.list',
|
||||
permission: 'run.read',
|
||||
request: {
|
||||
...authorized.request,
|
||||
body: null,
|
||||
query: {
|
||||
limit: ['1'],
|
||||
after_step_key: ['collect'],
|
||||
after_step_run_id: [IDS.collect],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
packageName: 'example',
|
||||
workflowId: 'daily',
|
||||
runId: IDS.runId,
|
||||
},
|
||||
);
|
||||
assert.equal(listedSteps.statusCode, 200);
|
||||
assert.equal(
|
||||
listedSteps.body.schema,
|
||||
'qinglong/plugin-package-workflow-step-run-list@v1',
|
||||
);
|
||||
assert.deepEqual(Object.keys(listedSteps.body.stepRuns[0]).sort(), [
|
||||
'attemptCount',
|
||||
'createdAtMs',
|
||||
'finishedAtMs',
|
||||
'id',
|
||||
'kind',
|
||||
'parentStepRunId',
|
||||
'readyAtMs',
|
||||
'required',
|
||||
'resultCode',
|
||||
'startedAtMs',
|
||||
'status',
|
||||
'stepKey',
|
||||
'updatedAtMs',
|
||||
'version',
|
||||
]);
|
||||
const invalidStepCursor = await listSteps.handle(
|
||||
{
|
||||
...authorized,
|
||||
request: {
|
||||
...authorized.request,
|
||||
body: null,
|
||||
query: { after_step_key: ['collect'] },
|
||||
},
|
||||
},
|
||||
{
|
||||
packageName: 'example',
|
||||
workflowId: 'daily',
|
||||
runId: IDS.runId,
|
||||
},
|
||||
);
|
||||
assert.equal(invalidStepCursor.statusCode, 400);
|
||||
|
||||
const listEvents = routes.find(
|
||||
({ operationId }) => operationId === 'workflow.event.list',
|
||||
);
|
||||
assert.ok(listEvents);
|
||||
assert.equal(listEvents.permission, 'run.read');
|
||||
assert.deepEqual(listEvents.allowedQuery, ['after_sequence', 'limit']);
|
||||
const listedEvents = await listEvents.handle(
|
||||
{
|
||||
...authorized,
|
||||
operationId: 'workflow.event.list',
|
||||
permission: 'run.read',
|
||||
request: {
|
||||
...authorized.request,
|
||||
body: null,
|
||||
query: { limit: ['1'], after_sequence: ['2'] },
|
||||
},
|
||||
},
|
||||
{
|
||||
packageName: 'example',
|
||||
workflowId: 'daily',
|
||||
runId: IDS.runId,
|
||||
},
|
||||
);
|
||||
assert.equal(listedEvents.statusCode, 200);
|
||||
assert.equal(
|
||||
listedEvents.body.schema,
|
||||
'qinglong/plugin-package-workflow-run-event-list@v1',
|
||||
);
|
||||
assert.deepEqual(Object.keys(listedEvents.body.events[0]).sort(), [
|
||||
'createdAtMs',
|
||||
'id',
|
||||
'sequence',
|
||||
'stepRunId',
|
||||
'type',
|
||||
]);
|
||||
const invalidEventCursor = await listEvents.handle(
|
||||
{
|
||||
...authorized,
|
||||
request: {
|
||||
...authorized.request,
|
||||
body: null,
|
||||
query: { after_sequence: ['02'] },
|
||||
},
|
||||
},
|
||||
{
|
||||
packageName: 'example',
|
||||
workflowId: 'daily',
|
||||
runId: IDS.runId,
|
||||
},
|
||||
);
|
||||
assert.equal(invalidEventCursor.statusCode, 400);
|
||||
|
||||
const invalid = await start.handle(
|
||||
{
|
||||
...authorized,
|
||||
request: {
|
||||
...authorized.request,
|
||||
body: { ...authorized.request.body, extra: true },
|
||||
},
|
||||
},
|
||||
{ packageName: 'example', workflowId: 'daily' },
|
||||
);
|
||||
assert.equal(invalid.statusCode, 400);
|
||||
|
||||
const cancellation = routes.find(
|
||||
({ operationId }) => operationId === 'workflow.cancel',
|
||||
);
|
||||
assert.ok(cancellation);
|
||||
const cancelled = await cancellation.handle(
|
||||
{
|
||||
...authorized,
|
||||
operationId: 'workflow.cancel',
|
||||
permission: 'run.stop',
|
||||
request: {
|
||||
...authorized.request,
|
||||
body: {
|
||||
schema: 'qinglong/run-cancellation@v1',
|
||||
mutationId: 'workflow-cancel-mutation-1',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
packageName: 'example',
|
||||
workflowId: 'daily',
|
||||
runId: IDS.runId,
|
||||
},
|
||||
);
|
||||
assert.equal(cancelled.statusCode, 202);
|
||||
assert.equal(cancelled.body.status, 'accepted');
|
||||
assert.deepEqual(
|
||||
Object.keys(cancelled.body).sort(),
|
||||
[
|
||||
'cancelReason',
|
||||
'cancelRequestedAtMs',
|
||||
'eventSequence',
|
||||
'projectId',
|
||||
'runId',
|
||||
'runStatus',
|
||||
'runVersion',
|
||||
'schema',
|
||||
'status',
|
||||
].sort(),
|
||||
);
|
||||
const invalidCancellation = await cancellation.handle(
|
||||
{
|
||||
...authorized,
|
||||
operationId: 'workflow.cancel',
|
||||
permission: 'run.stop',
|
||||
request: {
|
||||
...authorized.request,
|
||||
body: {
|
||||
schema: 'qinglong/run-cancellation@v1',
|
||||
mutationId: 'workflow-cancel-mutation-1',
|
||||
reason: 'shutdown',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
packageName: 'example',
|
||||
workflowId: 'daily',
|
||||
runId: IDS.runId,
|
||||
},
|
||||
);
|
||||
assert.equal(invalidCancellation.statusCode, 400);
|
||||
|
||||
cancellationError = Object.assign(new Error('private adapter detail'), {
|
||||
code: 'CLUSTER_RUN_CANCELLATION_FENCE_REJECTED',
|
||||
reason: 'private_adapter_detail',
|
||||
});
|
||||
const closedCancellationFailure = await cancellation.handle(
|
||||
{
|
||||
...authorized,
|
||||
operationId: 'workflow.cancel',
|
||||
permission: 'run.stop',
|
||||
request: {
|
||||
...authorized.request,
|
||||
body: {
|
||||
schema: 'qinglong/run-cancellation@v1',
|
||||
mutationId: 'workflow-cancel-mutation-2',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
packageName: 'example',
|
||||
workflowId: 'daily',
|
||||
runId: IDS.runId,
|
||||
},
|
||||
);
|
||||
assert.equal(closedCancellationFailure.statusCode, 409);
|
||||
assert.deepEqual(closedCancellationFailure.body, {
|
||||
code: 'workflow_cancellation_fence_rejected',
|
||||
reason: 'state_mismatch',
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user