fix(FN-1210): stabilize workflow-step refine route and tests
- Add an injectable createKbAgent override for the workflow-step refine route to avoid brittle dynamic import behavior in tests - Update refine logic to use injected agent factory when provided and fall back to @fusion/engine import otherwise - Expand route tests with a successful AI refinement path using a mocked session text stream - Reset refine agent injection between tests and tighten fallback assertions when AI creation fails
This commit is contained in:
@@ -14,7 +14,7 @@ import { githubRateLimiter } from "./github-poll.js";
|
||||
import type { TaskStore, TaskAttachment } from "@fusion/core";
|
||||
import type { TaskDetail } from "@fusion/core";
|
||||
import type { AuthStorageLike, ModelRegistryLike } from "./routes.js";
|
||||
import { __resetBatchImportRateLimiter } from "./routes.js";
|
||||
import { __resetBatchImportRateLimiter, __setCreateKbAgentForRefine } from "./routes.js";
|
||||
import { __resetPlanningState, __setCreateKbAgent, planningStreamManager } from "./planning.js";
|
||||
import { __resetSubtaskBreakdownState, subtaskStreamManager } from "./subtask-breakdown.js";
|
||||
import * as terminalServiceModule from "./terminal-service.js";
|
||||
@@ -8053,6 +8053,10 @@ describe("POST /workflow-steps/:id/refine", () => {
|
||||
store = createMockStore();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
__setCreateKbAgentForRefine(undefined);
|
||||
});
|
||||
|
||||
function buildApp() {
|
||||
const app = express();
|
||||
app.use(express.json());
|
||||
@@ -8097,6 +8101,47 @@ describe("POST /workflow-steps/:id/refine", () => {
|
||||
expect(res.body.error).toContain("Cannot refine prompt for script-mode");
|
||||
});
|
||||
|
||||
it("returns AI-refined prompt when engine is available", async () => {
|
||||
const ws = { id: "WS-001", name: "Docs", description: "Check docs", mode: "prompt", prompt: "", enabled: true, createdAt: "2026-01-01", updatedAt: "2026-01-01" };
|
||||
(store.getWorkflowStep as ReturnType<typeof vi.fn>).mockResolvedValueOnce(ws);
|
||||
(store.getSettings as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
|
||||
planningProvider: "anthropic",
|
||||
planningModelId: "claude-sonnet-4-5",
|
||||
});
|
||||
|
||||
const updatedWs = { ...ws, prompt: "Refined prompt from AI" };
|
||||
(store.updateWorkflowStep as ReturnType<typeof vi.fn>).mockResolvedValueOnce(updatedWs);
|
||||
|
||||
let onText: ((delta: string) => void) | undefined;
|
||||
const session = {
|
||||
on: vi.fn((event: string, cb: (delta: string) => void) => {
|
||||
if (event === "text") {
|
||||
onText = cb;
|
||||
}
|
||||
}),
|
||||
prompt: vi.fn(async () => {
|
||||
onText?.("Refined ");
|
||||
onText?.("prompt from AI");
|
||||
}),
|
||||
dispose: vi.fn(),
|
||||
};
|
||||
|
||||
const createKbAgentMock = vi.fn(async () => ({ session }));
|
||||
__setCreateKbAgentForRefine(createKbAgentMock);
|
||||
|
||||
const res = await REQUEST(buildApp(), "POST", "/api/workflow-steps/WS-001/refine", JSON.stringify({}), {
|
||||
"Content-Type": "application/json",
|
||||
});
|
||||
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.body.prompt).toBe("Refined prompt from AI");
|
||||
expect(res.body.workflowStep.prompt).toBe("Refined prompt from AI");
|
||||
expect(createKbAgentMock).toHaveBeenCalledTimes(1);
|
||||
expect(session.prompt).toHaveBeenCalledWith(expect.stringContaining("Name: Docs"));
|
||||
expect(session.dispose).toHaveBeenCalledTimes(1);
|
||||
expect(store.updateWorkflowStep).toHaveBeenCalledWith("WS-001", { prompt: "Refined prompt from AI" });
|
||||
});
|
||||
|
||||
it("falls back to description when AI is unavailable", async () => {
|
||||
const ws = { id: "WS-001", name: "Docs", description: "Check docs", mode: "prompt", prompt: "", enabled: true, createdAt: "2026-01-01", updatedAt: "2026-01-01" };
|
||||
(store.getWorkflowStep as ReturnType<typeof vi.fn>).mockResolvedValueOnce(ws);
|
||||
@@ -8104,16 +8149,19 @@ describe("POST /workflow-steps/:id/refine", () => {
|
||||
const updatedWs = { ...ws, prompt: "Check docs" };
|
||||
(store.updateWorkflowStep as ReturnType<typeof vi.fn>).mockResolvedValueOnce(updatedWs);
|
||||
|
||||
__setCreateKbAgentForRefine(async () => {
|
||||
throw new Error("AI unavailable");
|
||||
});
|
||||
|
||||
const res = await REQUEST(buildApp(), "POST", "/api/workflow-steps/WS-001/refine", JSON.stringify({}), {
|
||||
"Content-Type": "application/json",
|
||||
});
|
||||
|
||||
// AI import will fail in test env, falling back to description
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.body.prompt).toBeDefined();
|
||||
expect(res.body.workflowStep).toBeDefined();
|
||||
expect(store.updateWorkflowStep).toHaveBeenCalled();
|
||||
}, 30000);
|
||||
expect(res.body.prompt).toBe("Check docs");
|
||||
expect(res.body.workflowStep.prompt).toBe("Check docs");
|
||||
expect(store.updateWorkflowStep).toHaveBeenCalledWith("WS-001", { prompt: "Check docs" });
|
||||
});
|
||||
});
|
||||
|
||||
// ── Workflow Step Template Tests ──────────────────────────────────────────
|
||||
|
||||
@@ -83,6 +83,15 @@ const upload = multer({
|
||||
limits: { fileSize: 5 * 1024 * 1024 }, // 5MB
|
||||
});
|
||||
|
||||
// Dynamic import fallback for @fusion/engine with injectable override for tests.
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
let createKbAgentForRefine: any;
|
||||
|
||||
/** @internal Inject a mock createKbAgent function for workflow-step refine route tests. */
|
||||
export function __setCreateKbAgentForRefine(mock: typeof createKbAgentForRefine): void {
|
||||
createKbAgentForRefine = mock;
|
||||
}
|
||||
|
||||
function validateOptionalModelField(value: unknown, name: string): string | undefined {
|
||||
if (value === undefined || value === null) return undefined;
|
||||
if (typeof value !== "string") {
|
||||
@@ -6902,9 +6911,14 @@ export function createApiRoutes(store: TaskStore, options?: ServerOptions): Rout
|
||||
// Use AI to refine the description into a detailed agent prompt
|
||||
let refinedPrompt: string;
|
||||
try {
|
||||
// Dynamic import to avoid resolution issues in tests
|
||||
const engineModule = "@fusion/engine";
|
||||
const { createKbAgent } = await import(/* @vite-ignore */ engineModule);
|
||||
let createKbAgent = createKbAgentForRefine;
|
||||
if (!createKbAgent) {
|
||||
// Dynamic import to avoid resolution issues in tests
|
||||
const engineModule = "@fusion/engine";
|
||||
const engine = await import(/* @vite-ignore */ engineModule);
|
||||
createKbAgent = engine.createKbAgent;
|
||||
}
|
||||
|
||||
const settings = await scopedStore.getSettings();
|
||||
|
||||
const systemPrompt = `You are an expert at creating detailed agent prompts for workflow steps.
|
||||
|
||||
Reference in New Issue
Block a user