- Added a new Archify skill, enabling users to create polished architecture, workflow, sequence, data-flow, and lifecycle diagrams. - Implemented comprehensive functionality including rendering, validation, and delivery of diagrams in various formats. - Integrated a user-friendly command-line interface for generating and previewing diagrams. - Developed supporting files including package.json, LICENSE, and SKILL.md for documentation and licensing. - Added unit tests to ensure reliability and functionality of the new skill. These changes enhance the application by providing a structured approach to visualizing system architecture and workflows, improving user experience and data representation.
132 lines
4.5 KiB
JavaScript
132 lines
4.5 KiB
JavaScript
import { test } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
|
|
import { compileWorkflow } from '../renderers/workflow/workflow-compiler.mjs';
|
|
|
|
function document(semanticChecks) {
|
|
return {
|
|
schema_version: 2,
|
|
diagram_type: 'workflow',
|
|
meta: {
|
|
title: 'Workflow semantic contract fixture',
|
|
legend: { mode: 'hidden' },
|
|
},
|
|
lanes: [
|
|
{ id: 'main', label: 'Main' },
|
|
{ id: 'recovery', label: 'Recovery' },
|
|
],
|
|
nodes: [
|
|
{ id: 'input', lane: 'main', col: 0, type: 'external', label: 'Input' },
|
|
{ id: 'process', lane: 'main', col: 2, type: 'backend', label: 'Process' },
|
|
{ id: 'result', lane: 'main', col: 4, type: 'frontend', label: 'Result' },
|
|
{ id: 'ledger', lane: 'recovery', col: 4, type: 'database', label: 'Ledger' },
|
|
{ id: 'resume', lane: 'recovery', col: 1, type: 'backend', label: 'Resume' },
|
|
],
|
|
edges: [
|
|
{ id: 'start', from: 'input', to: 'process' },
|
|
{ id: 'finish', from: 'process', to: 'result' },
|
|
{ id: 'record', from: 'process', to: 'ledger' },
|
|
],
|
|
...(semanticChecks ? { semanticChecks } : {}),
|
|
};
|
|
}
|
|
|
|
test('semanticChecks rejects an undeclared root instead of accepting an orphan recovery node', () => {
|
|
const workflow = document({
|
|
allowedRoots: ['input'],
|
|
});
|
|
|
|
const result = compileWorkflow({ workflow, qualityProfile: 'standard' });
|
|
|
|
assert.equal(result.ok, false);
|
|
assert.ok(
|
|
result.diagnostics.some((diagnostic) => (
|
|
diagnostic.code === 'workflow/unexpected-root'
|
|
&& diagnostic.subject?.node === 'resume'
|
|
)),
|
|
JSON.stringify(result.diagnostics, null, 2),
|
|
);
|
|
});
|
|
|
|
test('semanticChecks rejects a missing required edge with its exact endpoints', () => {
|
|
const workflow = document({
|
|
requiredEdges: [{ from: 'ledger', to: 'resume' }],
|
|
});
|
|
|
|
const result = compileWorkflow({ workflow, qualityProfile: 'standard' });
|
|
|
|
assert.equal(result.ok, false);
|
|
assert.ok(
|
|
result.diagnostics.some((diagnostic) => (
|
|
diagnostic.code === 'workflow/required-edge'
|
|
&& diagnostic.subject?.from === 'ledger'
|
|
&& diagnostic.subject?.to === 'resume'
|
|
)),
|
|
JSON.stringify(result.diagnostics, null, 2),
|
|
);
|
|
});
|
|
|
|
test('semanticChecks accepts required reachability through intermediate nodes without changing SVG bytes', () => {
|
|
const baseline = document();
|
|
const constrained = document({
|
|
allowedRoots: ['input', 'resume'],
|
|
allowedTerminals: ['result', 'ledger', 'resume'],
|
|
requiredEdges: [{ from: 'process', to: 'ledger' }],
|
|
requiredPaths: [{ from: 'input', to: 'result' }],
|
|
});
|
|
|
|
const baselineResult = compileWorkflow({ workflow: baseline, qualityProfile: 'standard' });
|
|
const constrainedResult = compileWorkflow({ workflow: constrained, qualityProfile: 'standard' });
|
|
|
|
assert.equal(baselineResult.ok, true, JSON.stringify(baselineResult.diagnostics, null, 2));
|
|
assert.equal(constrainedResult.ok, true, JSON.stringify(constrainedResult.diagnostics, null, 2));
|
|
assert.equal(constrainedResult.svg, baselineResult.svg);
|
|
assert.deepEqual(constrainedResult.receipt, baselineResult.receipt);
|
|
});
|
|
|
|
test('semanticChecks rejects a required path that is not reachable in authored direction', () => {
|
|
const workflow = document({
|
|
requiredPaths: [{ from: 'ledger', to: 'resume' }],
|
|
});
|
|
|
|
const result = compileWorkflow({ workflow, qualityProfile: 'standard' });
|
|
|
|
assert.equal(result.ok, false);
|
|
assert.ok(
|
|
result.diagnostics.some((diagnostic) => diagnostic.code === 'workflow/required-path'),
|
|
JSON.stringify(result.diagnostics, null, 2),
|
|
);
|
|
});
|
|
|
|
test('semanticChecks rejects an undeclared terminal', () => {
|
|
const workflow = document({
|
|
allowedTerminals: ['result', 'ledger'],
|
|
});
|
|
|
|
const result = compileWorkflow({ workflow, qualityProfile: 'standard' });
|
|
|
|
assert.equal(result.ok, false);
|
|
assert.ok(
|
|
result.diagnostics.some((diagnostic) => (
|
|
diagnostic.code === 'workflow/unexpected-terminal'
|
|
&& diagnostic.subject?.node === 'resume'
|
|
)),
|
|
JSON.stringify(result.diagnostics, null, 2),
|
|
);
|
|
});
|
|
|
|
test('semanticChecks reports unknown contract node ids before graph analysis', () => {
|
|
const workflow = document({
|
|
requiredEdges: [{ from: 'missing', to: 'resume' }],
|
|
});
|
|
|
|
const result = compileWorkflow({ workflow, qualityProfile: 'standard' });
|
|
|
|
assert.equal(result.ok, false);
|
|
assert.deepEqual(
|
|
result.diagnostics.map(({ code }) => code),
|
|
['workflow/semantic-node-reference'],
|
|
);
|
|
assert.equal(result.diagnostics[0].subject.path, '/semanticChecks/requiredEdges/0/from');
|
|
});
|