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,311 @@
|
||||
const assert = require('node:assert/strict');
|
||||
const { test } = require('node:test');
|
||||
|
||||
const {
|
||||
createInitialPluginPackageAutomationPublication,
|
||||
createPluginPackageAutomationLifecyclePublication,
|
||||
} = require('../dist/plugin-package/pluginPackageAutomationPublication');
|
||||
const {
|
||||
createPluginPackageWorkflowAdmissionBundle,
|
||||
createPluginPackageWorkflowExecutionPlan,
|
||||
InvalidPluginPackageWorkflowAdmissionReceiptError,
|
||||
InvalidPluginPackageWorkflowExecutionPlanError,
|
||||
normalizePluginPackageWorkflowAdmissionReceipt,
|
||||
normalizePluginPackageWorkflowExecutionPlan,
|
||||
PluginPackageWorkflowExecutionPlanConflictError,
|
||||
pluginPackageWorkflowAdmissionReceiptDigest,
|
||||
pluginPackageWorkflowExecutionPlanDigest,
|
||||
} = require('@qinglong/runtime-core/plugin-package-workflow-execution-plan');
|
||||
const {
|
||||
pluginPackageTaskReconciliationFixture,
|
||||
} = require('../../../test/contracts/pluginPackageTaskReconciliationRepositoryContract.cjs');
|
||||
|
||||
function fixture(namespace = 'workflow-execution-plan') {
|
||||
const value = pluginPackageTaskReconciliationFixture(namespace, {
|
||||
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 planInput(value, overrides = {}) {
|
||||
return {
|
||||
planId: 'workflow-plan-001',
|
||||
runId: 'workflow-run-001',
|
||||
workflowId: 'daily',
|
||||
stepRunIds: {
|
||||
collect: 'step-run-collect-001',
|
||||
summarize: 'step-run-summarize-001',
|
||||
},
|
||||
publication: value.publication,
|
||||
revision: value.revision,
|
||||
taskSpecSemanticRegistry: value.registry,
|
||||
plannedAtMs: 3_000,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
test('binds one active publication and exact materialized Tasks into a canonical DAG plan', () => {
|
||||
const value = fixture();
|
||||
const plan = createPluginPackageWorkflowExecutionPlan(planInput(value));
|
||||
|
||||
assert.equal(
|
||||
plan.target.publicationDigest,
|
||||
value.publication.publicationDigest,
|
||||
);
|
||||
assert.equal(
|
||||
plan.target.materializedRevisionDigest,
|
||||
value.revision.revisionDigest,
|
||||
);
|
||||
assert.equal(plan.target.workflowId, 'daily');
|
||||
assert.match(plan.target.workflowDefinitionDigest, /^[0-9a-f]{64}$/);
|
||||
assert.deepEqual(
|
||||
plan.steps.map(
|
||||
({ stepKey, taskId, needs, initialStatus, taskDefinitionRef }) => ({
|
||||
stepKey,
|
||||
taskId,
|
||||
needs,
|
||||
initialStatus,
|
||||
taskDefinitionRef,
|
||||
}),
|
||||
),
|
||||
[
|
||||
{
|
||||
stepKey: 'collect',
|
||||
taskId: 'alpha',
|
||||
needs: [],
|
||||
initialStatus: 'ready',
|
||||
taskDefinitionRef: `plugin-package:${value.revision.revisionDigest}:task:alpha`,
|
||||
},
|
||||
{
|
||||
stepKey: 'summarize',
|
||||
taskId: 'beta',
|
||||
needs: ['collect'],
|
||||
initialStatus: 'pending',
|
||||
taskDefinitionRef: `plugin-package:${value.revision.revisionDigest}:task:beta`,
|
||||
},
|
||||
],
|
||||
);
|
||||
assert.equal(
|
||||
plan.steps.every(({ required }) => required),
|
||||
true,
|
||||
);
|
||||
assert.equal(plan.planDigest, pluginPackageWorkflowExecutionPlanDigest(plan));
|
||||
assert.deepEqual(normalizePluginPackageWorkflowExecutionPlan(plan), plan);
|
||||
assert.deepEqual(
|
||||
normalizePluginPackageWorkflowExecutionPlan(
|
||||
JSON.parse(JSON.stringify(plan)),
|
||||
),
|
||||
plan,
|
||||
);
|
||||
assert.equal(
|
||||
createPluginPackageWorkflowExecutionPlan(planInput(value)).planDigest,
|
||||
plan.planDigest,
|
||||
);
|
||||
});
|
||||
|
||||
test('rejects withdrawn, drifted and incomplete Workflow execution inputs', () => {
|
||||
const value = fixture('workflow-execution-plan-conflict');
|
||||
const withdrawn = createPluginPackageAutomationLifecyclePublication({
|
||||
previous: value.publication,
|
||||
state: 'withdrawn',
|
||||
lifecycleEventDigest: 'a'.repeat(64),
|
||||
publishedAtMs: 2_001,
|
||||
});
|
||||
assert.throws(
|
||||
() =>
|
||||
createPluginPackageWorkflowExecutionPlan(
|
||||
planInput(value, { publication: withdrawn }),
|
||||
),
|
||||
PluginPackageWorkflowExecutionPlanConflictError,
|
||||
);
|
||||
|
||||
const replacement = fixture('workflow-execution-plan-replacement');
|
||||
assert.throws(
|
||||
() =>
|
||||
createPluginPackageWorkflowExecutionPlan(
|
||||
planInput(value, { revision: replacement.revision }),
|
||||
),
|
||||
PluginPackageWorkflowExecutionPlanConflictError,
|
||||
);
|
||||
assert.throws(
|
||||
() =>
|
||||
createPluginPackageWorkflowExecutionPlan(
|
||||
planInput(value, {
|
||||
stepRunIds: { collect: 'step-run-collect-001' },
|
||||
}),
|
||||
),
|
||||
InvalidPluginPackageWorkflowExecutionPlanError,
|
||||
);
|
||||
assert.throws(
|
||||
() =>
|
||||
createPluginPackageWorkflowExecutionPlan(
|
||||
planInput(value, {
|
||||
stepRunIds: {
|
||||
collect: 'step-run-shared',
|
||||
summarize: 'step-run-shared',
|
||||
},
|
||||
}),
|
||||
),
|
||||
InvalidPluginPackageWorkflowExecutionPlanError,
|
||||
);
|
||||
});
|
||||
|
||||
test('fails closed when a durable plan digest or generation-bound Task reference drifts', () => {
|
||||
const value = fixture('workflow-execution-plan-normalization');
|
||||
const plan = createPluginPackageWorkflowExecutionPlan(planInput(value));
|
||||
assert.throws(
|
||||
() =>
|
||||
normalizePluginPackageWorkflowExecutionPlan({
|
||||
...plan,
|
||||
planDigest: 'f'.repeat(64),
|
||||
}),
|
||||
InvalidPluginPackageWorkflowExecutionPlanError,
|
||||
);
|
||||
assert.throws(
|
||||
() =>
|
||||
normalizePluginPackageWorkflowExecutionPlan({
|
||||
...plan,
|
||||
steps: plan.steps.map((step) =>
|
||||
step.stepKey === 'collect'
|
||||
? {
|
||||
...step,
|
||||
taskDefinitionRef:
|
||||
'plugin-package:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff:task:alpha',
|
||||
}
|
||||
: step,
|
||||
),
|
||||
}),
|
||||
InvalidPluginPackageWorkflowExecutionPlanError,
|
||||
);
|
||||
});
|
||||
|
||||
test('derives a deterministic atomic admission bundle and durable receipt', () => {
|
||||
const value = fixture('workflow-admission-bundle');
|
||||
const plan = createPluginPackageWorkflowExecutionPlan(planInput(value));
|
||||
const bundle = createPluginPackageWorkflowAdmissionBundle(plan);
|
||||
|
||||
assert.deepEqual(bundle.plan, plan);
|
||||
assert.equal(bundle.run.id, plan.runId);
|
||||
assert.equal(bundle.run.status, 'running');
|
||||
assert.equal(bundle.run.version, 3);
|
||||
assert.equal(bundle.run.eventSequence, 3);
|
||||
assert.equal(bundle.admissionEvent.sequence, 1);
|
||||
assert.equal(bundle.admissionEvent.type, 'workflow.admitted');
|
||||
assert.equal(bundle.admissionEvent.id.length <= 36, true);
|
||||
assert.deepEqual(
|
||||
bundle.stepMutations.map((mutation) => ({
|
||||
stepKey: mutation.stepRun.stepKey,
|
||||
status: mutation.stepRun.status,
|
||||
expectedRunVersion: mutation.expectedRunVersion,
|
||||
expectedRunEventSequence: mutation.expectedRunEventSequence,
|
||||
eventSequence: mutation.event.sequence,
|
||||
})),
|
||||
[
|
||||
{
|
||||
stepKey: 'collect',
|
||||
status: 'ready',
|
||||
expectedRunVersion: 1,
|
||||
expectedRunEventSequence: 1,
|
||||
eventSequence: 2,
|
||||
},
|
||||
{
|
||||
stepKey: 'summarize',
|
||||
status: 'pending',
|
||||
expectedRunVersion: 2,
|
||||
expectedRunEventSequence: 2,
|
||||
eventSequence: 3,
|
||||
},
|
||||
],
|
||||
);
|
||||
assert.equal(bundle.receipt.planDigest, plan.planDigest);
|
||||
assert.equal(bundle.receipt.finalRunVersion, 3);
|
||||
assert.equal(bundle.receipt.finalRunEventSequence, 3);
|
||||
assert.equal(
|
||||
bundle.receipt.receiptDigest,
|
||||
pluginPackageWorkflowAdmissionReceiptDigest(bundle.receipt),
|
||||
);
|
||||
assert.equal(
|
||||
bundle.stepMutations.every(
|
||||
({ event }) => event.id.length <= 36 && event.dedupeKey.length <= 36,
|
||||
),
|
||||
true,
|
||||
);
|
||||
assert.deepEqual(
|
||||
normalizePluginPackageWorkflowAdmissionReceipt(
|
||||
JSON.parse(JSON.stringify(bundle.receipt)),
|
||||
),
|
||||
bundle.receipt,
|
||||
);
|
||||
assert.deepEqual(createPluginPackageWorkflowAdmissionBundle(plan), bundle);
|
||||
});
|
||||
|
||||
test('rejects tampered Workflow admission receipts and counters', () => {
|
||||
const value = fixture('workflow-admission-receipt-invalid');
|
||||
const bundle = createPluginPackageWorkflowAdmissionBundle(
|
||||
createPluginPackageWorkflowExecutionPlan(planInput(value)),
|
||||
);
|
||||
|
||||
assert.throws(
|
||||
() =>
|
||||
normalizePluginPackageWorkflowAdmissionReceipt({
|
||||
...bundle.receipt,
|
||||
receiptDigest: 'f'.repeat(64),
|
||||
}),
|
||||
InvalidPluginPackageWorkflowAdmissionReceiptError,
|
||||
);
|
||||
assert.throws(
|
||||
() =>
|
||||
normalizePluginPackageWorkflowAdmissionReceipt({
|
||||
...bundle.receipt,
|
||||
finalRunVersion: 2,
|
||||
}),
|
||||
InvalidPluginPackageWorkflowAdmissionReceiptError,
|
||||
);
|
||||
});
|
||||
|
||||
test('keeps Run and RunEvent identities portable across SQLite and PostgreSQL', () => {
|
||||
const value = fixture('workflow-portable-identity');
|
||||
assert.throws(
|
||||
() =>
|
||||
createPluginPackageWorkflowExecutionPlan(
|
||||
planInput(value, { runId: `r${'a'.repeat(36)}` }),
|
||||
),
|
||||
InvalidPluginPackageWorkflowExecutionPlanError,
|
||||
);
|
||||
});
|
||||
|
||||
test('publishes the pure planner only through its explicit runtime-core subpath', () => {
|
||||
const subpath = require('@qinglong/runtime-core/plugin-package-workflow-execution-plan');
|
||||
const root = require('../dist');
|
||||
assert.equal(
|
||||
subpath.createPluginPackageWorkflowExecutionPlan,
|
||||
createPluginPackageWorkflowExecutionPlan,
|
||||
);
|
||||
assert.equal(root.createPluginPackageWorkflowExecutionPlan, undefined);
|
||||
});
|
||||
Reference in New Issue
Block a user