Files
fusion/packages/engine/src/__tests__/builtin-coding-browser-verification-group.test.ts
gsxdsm cb57093d03 refactor: domain folder layout (types, API, core, engine) (#2398)
## Summary

Wave 17 organizes Fusion into **domain folders** (stacks on #2397).

### Layout
- **core/types/** — board, task, agents, settings, merge, workflow,
mesh, …
- **core/src/** — agents, ai, async-stores, workflows, tasks, config,
db, …
- **dashboard/app/api/** — client, tasks, agents, git, missions,
planning, …
- **engine/src/** — agents, auth, execution, merge, missions, overseer,
worktree, …

Root keepers retained for large entrypoints (`store.ts`, `executor.ts`,
`merger.ts`, …).

Public barrels (`@fusion/core`, `@fusion/engine`, `app/api.ts` → legacy)
stay stable.

## Test plan
- [x] `@fusion/core` typecheck
- [x] `@fusion/engine` typecheck (pre-existing playwright-core noise
only)
- [ ] CI merge gate

**Stack:** #2394 → #2397 → **this PR**
2026-08-03 00:20:53 -07:00

103 lines
4.9 KiB
TypeScript

import { describe, expect, it, vi } from "vitest";
import { BUILTIN_CODING_WORKFLOW_IR } from "@fusion/core";
import type { TaskDetail } from "@fusion/core";
import { WorkflowGraphExecutor, type WorkflowNodeHandler } from "../workflows/workflow-graph-executor.js";
/*
FNXC:WorkflowOptionalGroup 2026-06-21-15:10:
Built-in-level execution coverage for U6: the coding workflow now expresses the
pre-merge browser-verification step as an `optional-group` (default OFF). This is
the dead-toggle / two-task divergence guard at the BUILT-IN level (not just the
generic construct): two coding tasks identical except `enabledWorkflowSteps` must
diverge — the one including the group id runs the browser-verification prompt node
pre-merge; the sibling runs NONE and still reaches review. Real executor runs (not
traversal-only) so a mock-masked dead path cannot pass.
The inner template node id is `browser-verification-step` (distinct from the group
id `browser-verification` per the U1 template-node-id collision rule), and its
materialized visited id is `browser-verification::browser-verification-step`.
*/
const settingsOn = () => ({ experimentalFeatures: { workflowGraphExecutor: true } });
const GROUP_ID = "browser-verification";
const INNER_STEP_VISITED_ID = "browser-verification::browser-verification-step";
function codingTask(enabledWorkflowSteps?: string[]): TaskDetail {
return {
id: "FN-CODING",
...(enabledWorkflowSteps ? { enabledWorkflowSteps } : {}),
} as unknown as TaskDetail;
}
/** Count how many times the inner browser-verification prompt node ran. A prompt
* handler keyed on the inner template node id; everything else succeeds. */
function makeExecutor(onInnerStep: () => void) {
const prompt = vi.fn<WorkflowNodeHandler>(async (node) => {
if (node.id === "browser-verification-step") {
expect(node.config?.requiresBrowser).toBe(true);
onInnerStep();
}
return { outcome: "success" };
});
return new WorkflowGraphExecutor({ handlers: { prompt } });
}
describe("builtin coding browser-verification optional-group (U6)", () => {
it("two-task divergence: the enabled task runs browser-verification pre-merge; the disabled task does not", async () => {
// Enabled.
let enabledRuns = 0;
const enabledResult = await makeExecutor(() => {
enabledRuns++;
}).run(codingTask([GROUP_ID]), settingsOn(), BUILTIN_CODING_WORKFLOW_IR);
// Disabled (no enabledWorkflowSteps).
let disabledRuns = 0;
const disabledResult = await makeExecutor(() => {
disabledRuns++;
}).run(codingTask(), settingsOn(), BUILTIN_CODING_WORKFLOW_IR);
// The browser-verification step ran exactly once when enabled, carrying the
// browser capability flag; the disabled task never materializes the inner step.
expect(enabledRuns).toBe(1);
expect(disabledRuns).toBe(0);
// Enabled: the inner template node is visited pre-merge (before review).
expect(enabledResult.visitedNodeIds).toContain(INNER_STEP_VISITED_ID);
const innerIdx = enabledResult.visitedNodeIds.indexOf(INNER_STEP_VISITED_ID);
const reviewIdxEnabled = enabledResult.visitedNodeIds.indexOf("review");
const executeIdxEnabled = enabledResult.visitedNodeIds.indexOf("execute");
expect(executeIdxEnabled).toBeLessThan(innerIdx);
expect(innerIdx).toBeLessThan(reviewIdxEnabled);
// Disabled: the group node is traversed (bypassed) but its body never runs;
// both tasks reach the same downstream review node.
expect(disabledResult.visitedNodeIds).toContain(GROUP_ID);
expect(disabledResult.visitedNodeIds).not.toContain(INNER_STEP_VISITED_ID);
expect(disabledResult.visitedNodeIds).toContain("review");
expect(enabledResult.visitedNodeIds).toContain("review");
});
it("a browser-verification failure surfaces as the group's outcome and routes its failure edge to end", async () => {
// The inner step fails → the group's failure edge (browser-verification → end)
// fires, so review is never reached.
const prompt = vi.fn<WorkflowNodeHandler>(async (node) => {
if (node.id === "browser-verification-step") return { outcome: "failure", value: "verify-failed" };
return { outcome: "success" };
});
const executor = new WorkflowGraphExecutor({ handlers: { prompt } });
const result = await executor.run(codingTask([GROUP_ID]), settingsOn(), BUILTIN_CODING_WORKFLOW_IR);
expect(result.context[`node:${GROUP_ID}:outcome`]).toBe("failure");
expect(result.visitedNodeIds).toContain(INNER_STEP_VISITED_ID);
// The group's only two outgoing edges are `success → review` and
// `failure → end`; the inner-step failure routes the failure edge, so review
// is skipped. (`end` is a terminal node the executor does not record in
// visitedNodeIds, so the routing is asserted via the group's failure outcome
// above + review being unreachable here.)
expect(result.visitedNodeIds).not.toContain("review");
});
});