flow-project-parity-integration.test.mjs
140 lines 4.3 KB
Raw
sha256:8915fe406161f95c1681f9469375e7bae5b28c884f00bedbdef65e4b0cd0738d docs(flow): commit FLOW-V0-SPEC.md hygiene for 7A-INT merge Human 13 hours ago
1 /**
2 * Tier 2 — INTEGRATION: CLI = MCP = Hub handler parity for flow project.
3 *
4 * @see docs/FLOW-PROJECTION-GENERATOR-CONTRACT-7A-11.md §9
5 */
6 import { describe, it, beforeEach, afterEach } from 'node:test';
7 import assert from 'node:assert/strict';
8 import fs from 'node:fs';
9 import path from 'node:path';
10 import { fileURLToPath } from 'node:url';
11 import {
12 handleFlowProjectRequest,
13 stripFlowProjectGeneratedAt,
14 serializeFlowPayload,
15 } from '../lib/flow/flow-handlers.mjs';
16 import { seedStarterFlows } from '../lib/flow/flow-store.mjs';
17 import { getRepoRoot } from '../lib/repo-root.mjs';
18
19 const __dirname = path.dirname(fileURLToPath(import.meta.url));
20 const tmpRoot = path.join(__dirname, 'fixtures', 'tmp-flow-project-parity');
21 const starterDir = path.join(getRepoRoot(), 'flows/starter');
22
23 function hubProject(input) {
24 return handleFlowProjectRequest({
25 ...input,
26 role: 'admin',
27 generatedAt: '2026-06-20T00:00:00Z',
28 });
29 }
30
31 function cliProject(input) {
32 return handleFlowProjectRequest({
33 ...input,
34 cliScopes: ['personal', 'project'],
35 generatedAt: '2026-06-20T00:00:00Z',
36 });
37 }
38
39 function mcpProject(input) {
40 return handleFlowProjectRequest({
41 ...input,
42 cliScopes: ['personal', 'project'],
43 generatedAt: '2026-06-20T00:00:00Z',
44 });
45 }
46
47 describe('Flow project — triple-surface parity', () => {
48 const dataDir = path.join(tmpRoot, 'data');
49 const vaultId = 'default';
50
51 beforeEach(() => {
52 fs.rmSync(tmpRoot, { recursive: true, force: true });
53 fs.mkdirSync(dataDir, { recursive: true });
54 seedStarterFlows(dataDir, vaultId, { starterDir });
55 });
56
57 afterEach(() => {
58 fs.rmSync(tmpRoot, { recursive: true, force: true });
59 });
60
61 it('Hub, CLI, and MCP payloads are deep-equal excluding generated_at', () => {
62 const base = {
63 dataDir,
64 vaultId,
65 flowId: 'flow_overseer_handover',
66 harness: 'cli_runbook',
67 };
68 const hub = hubProject(base);
69 const cli = cliProject(base);
70 const mcp = mcpProject(base);
71 assert.equal(hub.ok, true);
72 assert.equal(cli.ok, true);
73 assert.equal(mcp.ok, true);
74 assert.deepEqual(stripFlowProjectGeneratedAt(hub.payload), stripFlowProjectGeneratedAt(cli.payload));
75 assert.deepEqual(stripFlowProjectGeneratedAt(cli.payload), stripFlowProjectGeneratedAt(mcp.payload));
76 assert.equal(
77 serializeFlowPayload(stripFlowProjectGeneratedAt(hub.payload)),
78 serializeFlowPayload(stripFlowProjectGeneratedAt(mcp.payload)),
79 );
80 });
81
82 it('pinned version resolves identically across surfaces', () => {
83 const base = {
84 dataDir,
85 vaultId,
86 flowId: 'flow_capture_to_note',
87 harness: 'cursor_rule',
88 version: '0.1.0',
89 };
90 const hub = hubProject(base);
91 const cli = cliProject(base);
92 assert.equal(hub.ok, true);
93 assert.deepEqual(stripFlowProjectGeneratedAt(hub.payload), stripFlowProjectGeneratedAt(cli.payload));
94 assert.equal(hub.payload.projection.flow_version, '0.1.0');
95 });
96
97 it('FLOW_HARNESS_UNSUPPORTED is identical for agent_bundle and cursor_skill', () => {
98 for (const harness of ['agent_bundle', 'cursor_skill', 'mcp_prompt']) {
99 const hub = hubProject({
100 dataDir,
101 vaultId,
102 flowId: 'flow_overseer_handover',
103 harness,
104 });
105 const cli = cliProject({
106 dataDir,
107 vaultId,
108 flowId: 'flow_overseer_handover',
109 harness,
110 });
111 assert.equal(hub.ok, false);
112 assert.equal(cli.ok, false);
113 assert.equal(hub.code, 'FLOW_HARNESS_UNSUPPORTED');
114 assert.equal(cli.code, hub.code);
115 assert.equal(hub.status, 400);
116 }
117 });
118
119 it('scope filter applied identically — personal caller cannot project project flow', () => {
120 const denied = handleFlowProjectRequest({
121 dataDir,
122 vaultId,
123 flowId: 'flow_overseer_handover',
124 harness: 'cli_runbook',
125 visibleScopes: new Set(['personal']),
126 generatedAt: '2026-06-20T00:00:00Z',
127 });
128 assert.equal(denied.ok, false);
129 assert.equal(denied.code, 'unknown_flow');
130 assert.equal(denied.status, 404);
131 });
132 });
133
134 describe('Flow project routes — hub wiring contract', () => {
135 it('registers GET /api/v1/flows/:id/projection with auth middleware', () => {
136 const src = fs.readFileSync(path.join(getRepoRoot(), 'hub/server.mjs'), 'utf8');
137 assert.match(src, /\/api\/v1\/flows\/:id\/projection/);
138 assert.match(src, /handleFlowProjectRequest/);
139 });
140 });
File History 1 commit
sha256:8915fe406161f95c1681f9469375e7bae5b28c884f00bedbdef65e4b0cd0738d docs(flow): commit FLOW-V0-SPEC.md hygiene for 7A-INT merge Human 13 hours ago