Files
fusion/packages/engine/src/__tests__/executor-task-done-summary.test.ts
gsxdsm 74b4de3d19 FN-8883: migrate executor tests to StepSessionExecutor seam
Route executor lifecycle tests through graph-owned StepSessionExecutor fixtures.

- Add reusable workflow routing, worktree refresh, and implementation-session test helpers
- Update executor prompt, step-session, pause, and completion assertions for graph-owned sessions
- Document the graph executor fixture seams for focused verification

Files changed:
 docs/testing.md                                    |   3 +
 .../__tests__/ephemeral-task-create-gate.test.ts   |  29 +-
 .../engine/src/__tests__/executor-prompt.test.ts   | 417 ++++++++++++---------
 .../src/__tests__/executor-review-verdicts.test.ts |   2 +-
 .../executor-step-numbering-zero-based.test.ts     |  23 +-
 .../src/__tests__/executor-step-session.test.ts    | 152 ++++++--
 .../executor-task-done-summary.test.ts             |  14 +-
 .../engine/src/__tests__/executor-test-helpers.ts  |  65 +++-
 8 files changed, 468 insertions(+), 237 deletions(-)

Fusion-Task-Id: FN-8883

Fusion-Task-Lineage: 7ebbee52-fe43-4e18-b0e9-2253911a0acb

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-08-09 04:59:15 -07:00

168 lines
5.9 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from "vitest";
import "./executor-test-helpers.js";
import { TaskExecutor } from "../executor.js";
import {
captureNamedTool,
createMockStore,
createWorkflowRoutingAgentStore,
mockedCreateFnAgent,
mockedExistsSync,
resetExecutorMocks,
} from "./executor-test-helpers.js";
/*
FNXC:EngineTests 2026-08-09-11:30:
The graph resolves an executor principal before reaching tool or step-numbering behavior. Route
these focused fixtures through the shared durable agent so their assertions reach the owned seam.
*/
function createRoutingExecutor(store: any) {
return new TaskExecutor(store, "/tmp/test", {
agentStore: createWorkflowRoutingAgentStore(store).agentStore,
});
}
function createBaseTask() {
return {
id: "FN-001",
title: "Test",
description: "Test task",
column: "in-progress",
/*
FNXC:EngineTests 2026-07-19-16:40 (U10b):
Summary replace-vs-append is keyed on whether any WORKFLOW STEP has produced a result, so
the test owns that variable via `workflowStepResults`. Under graph ownership the optional
pre-merge review nodes would run and record results of their own, making "no workflow steps
have run yet" unreachable; declaring no pre-merge gates keeps the fixture in control of the
only input the branch reads.
*/
enabledWorkflowSteps: [],
dependencies: [],
steps: [{ name: "Step 1", status: "in-progress" as const }],
currentStep: 0,
log: [],
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
};
}
async function setupTaskDoneTool(currentTaskOverrides: Record<string, unknown> = {}) {
const store = createMockStore();
let capturedTool: any = null;
let currentTask: any = {
...createBaseTask(),
...currentTaskOverrides,
};
store.getTask.mockImplementation(async () => ({
...currentTask,
steps: currentTask.steps.map((step: any) => ({ ...step })),
workflowStepResults: currentTask.workflowStepResults?.map((result: any) => ({ ...result })),
}));
mockedCreateFnAgent.mockImplementation(async ({ customTools }: any) => {
capturedTool = captureNamedTool(customTools, "fn_task_done", capturedTool);
return {
session: {
prompt: vi.fn().mockResolvedValue(undefined),
dispose: vi.fn(),
},
} as any;
});
const executor = createRoutingExecutor(store);
await executor.execute(createBaseTask() as any);
return {
store,
capturedTool,
setCurrentTask(nextTask: Record<string, unknown>) {
currentTask = { ...currentTask, ...nextTask };
},
};
}
function getSummaryUpdateCalls(store: ReturnType<typeof createMockStore>) {
return store.updateTask.mock.calls.filter((call: any[]) => Object.hasOwn(call[1] ?? {}, "summary"));
}
describe("TaskExecutor fn_task_done summary persistence", () => {
beforeEach(() => {
resetExecutorMocks();
mockedExistsSync.mockReturnValue(true);
});
it("replaces the summary on the first completion when no prior summary or workflow results exist", async () => {
const { store, capturedTool } = await setupTaskDoneTool();
await capturedTool.execute("tool-1", { summary: "Initial summary" });
expect(getSummaryUpdateCalls(store)).toEqual([["FN-001", { summary: "Initial summary" }]]);
});
it("appends rerun summaries when a prior summary exists and workflow steps have already run", async () => {
const { store, capturedTool, setCurrentTask } = await setupTaskDoneTool({
summary: "Original completion summary",
workflowStepResults: [{ stepName: "FrontendUX", status: "revision-requested" }],
});
setCurrentTask({
summary: "Original completion summary",
workflowStepResults: [{ stepName: "FrontendUX", status: "revision-requested" }],
});
await capturedTool.execute("tool-1", { summary: "Addressed workflow feedback" });
const summaryUpdateCalls = getSummaryUpdateCalls(store);
expect(summaryUpdateCalls).toHaveLength(1);
expect(summaryUpdateCalls[0][1].summary).toContain("Original completion summary");
expect(summaryUpdateCalls[0][1].summary).toContain("---\nRerun after workflow step revision:\nAddressed workflow feedback");
expect(
store.logEntry.mock.calls.some(
([id, action]: [string, string]) =>
id === "FN-001" && action === "fn_task_done summary appended to existing summary (workflow-step rerun)",
),
).toBe(true);
});
it("falls back to replace mode when a prior summary exists but no workflow steps have run yet", async () => {
const { store, capturedTool } = await setupTaskDoneTool({
summary: "Original completion summary",
workflowStepResults: [],
});
await capturedTool.execute("tool-1", { summary: "Replacement summary" });
expect(getSummaryUpdateCalls(store)).toEqual([["FN-001", { summary: "Replacement summary" }]]);
});
it("does not rewrite the summary when fn_task_done receives an empty or missing summary", async () => {
const { store, capturedTool } = await setupTaskDoneTool({
summary: "Original completion summary",
workflowStepResults: [{ stepName: "FrontendUX", status: "passed" }],
});
await capturedTool.execute("tool-1", {});
await capturedTool.execute("tool-2", { summary: " " });
expect(getSummaryUpdateCalls(store)).toHaveLength(0);
});
it("avoids duplicate appends when the rerun summary is already the existing suffix", async () => {
const existingSummary = [
"Original completion summary",
"",
"---",
"Rerun after workflow step revision:",
"Addressed workflow feedback",
].join("\n");
const { store, capturedTool } = await setupTaskDoneTool({
summary: existingSummary,
workflowStepResults: [{ stepName: "FrontendUX", status: "revision-requested" }],
});
await capturedTool.execute("tool-1", { summary: "Addressed workflow feedback" });
expect(getSummaryUpdateCalls(store)).toHaveLength(0);
});
});