fix: repair pre-existing engine test failures from missing pi-ai mock

Add @mariozechner/pi-ai mock to restart.integration.test.ts and
project-runtime.test.ts to fix module linking errors. Fix call count
assertions in executor and restart tests to account for the
retry-without-task_done behavior.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
gsxdsm
2026-04-04 13:59:08 -07:00
parent dd0b53c358
commit aac5297154
3 changed files with 40 additions and 13 deletions

View File

@@ -1,4 +1,18 @@
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
vi.mock("@mariozechner/pi-ai", () => ({
Type: {
Object: (props: Record<string, unknown>) => ({ type: "object", properties: props }),
String: (opts?: unknown) => ({ type: "string", ...((opts as object) ?? {}) }),
Number: (opts?: unknown) => ({ type: "number", ...((opts as object) ?? {}) }),
Boolean: (opts?: unknown) => ({ type: "boolean", ...((opts as object) ?? {}) }),
Optional: (schema: unknown) => schema,
Array: (schema: unknown, opts?: unknown) => ({ type: "array", items: schema, ...((opts as object) ?? {}) }),
Union: (schemas: unknown[], opts?: unknown) => ({ anyOf: schemas, ...((opts as object) ?? {}) }),
Literal: (value: unknown) => ({ const: value }),
},
}));
import { EventEmitter } from "node:events";
import type { Task, TaskStore, CentralCore } from "@fusion/core";
import type { Scheduler } from "../scheduler.js";

View File

@@ -2118,7 +2118,8 @@ describe("buildExecutionPrompt", () => {
updatedAt: new Date().toISOString(),
});
expect(mockPrompt).toHaveBeenCalledOnce();
// Called twice: initial execution + retry when agent finishes without task_done
expect(mockPrompt).toHaveBeenCalledTimes(2);
const agentPrompt = mockPrompt.mock.calls[0][0];
expect(agentPrompt).toContain("## Project Commands");
expect(agentPrompt).toContain("- **Test:** `npm test`");
@@ -2395,8 +2396,8 @@ describe("TaskExecutor pause behavior", () => {
// Wait for async execution to start
await new Promise((r) => setTimeout(r, 30));
// Agent should have been created to resume the task
expect(mockedCreateHaiAgent).toHaveBeenCalledTimes(1);
// Agent created twice: initial resume + retry when agent finishes without task_done
expect(mockedCreateHaiAgent).toHaveBeenCalledTimes(2);
expect(store.logEntry).toHaveBeenCalledWith("FN-001", "Resuming execution after unpause");
});
@@ -2434,8 +2435,8 @@ describe("TaskExecutor pause behavior", () => {
updatedAt: new Date().toISOString(),
});
// Only one agent should have been created (no duplicate from the unpause event)
expect(mockedCreateHaiAgent).toHaveBeenCalledTimes(1);
// Two agent creations (initial + retry without task_done), but no duplicate from the unpause event
expect(mockedCreateHaiAgent).toHaveBeenCalledTimes(2);
});
it("does not resume unpaused task that is not in-progress", async () => {
@@ -2503,8 +2504,8 @@ describe("TaskExecutor pause behavior", () => {
await executePromise;
// Only one agent session created — the unpause during active session was a no-op
expect(mockedCreateHaiAgent).toHaveBeenCalledTimes(1);
// Two agent sessions (initial + retry without task_done) — the unpause during active session was a no-op
expect(mockedCreateHaiAgent).toHaveBeenCalledTimes(2);
});
it("uses SessionManager.create for fresh execution and persists sessionFile", async () => {
@@ -2836,9 +2837,9 @@ describe("TaskExecutor enginePaused soft pause (no agent termination)", () => {
createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(),
});
// dispose should only be called once in the finally block (normal cleanup),
// dispose called twice: initial session + retry session (both cleaned up normally),
// NOT by an engine pause listener
expect(disposeFn).toHaveBeenCalledTimes(1);
expect(disposeFn).toHaveBeenCalledTimes(2);
// Task should complete normally and move to in-review, not todo
expect(store.moveTask).toHaveBeenCalledWith("FN-001", "in-review");
expect(store.moveTask).not.toHaveBeenCalledWith("FN-001", "todo");

View File

@@ -38,6 +38,18 @@ vi.mock("node:fs", () => ({
vi.mock("node:fs/promises", () => ({
readFile: vi.fn().mockResolvedValue("# Task prompt content"),
}));
vi.mock("@mariozechner/pi-ai", () => ({
Type: {
Object: (props: Record<string, unknown>) => ({ type: "object", properties: props }),
String: (opts?: unknown) => ({ type: "string", ...((opts as object) ?? {}) }),
Number: (opts?: unknown) => ({ type: "number", ...((opts as object) ?? {}) }),
Boolean: (opts?: unknown) => ({ type: "boolean", ...((opts as object) ?? {}) }),
Optional: (schema: unknown) => schema,
Array: (schema: unknown, opts?: unknown) => ({ type: "array", items: schema, ...((opts as object) ?? {}) }),
Union: (schemas: unknown[], opts?: unknown) => ({ anyOf: schemas, ...((opts as object) ?? {}) }),
Literal: (value: unknown) => ({ const: value }),
},
}));
vi.mock("@mariozechner/pi-coding-agent", () => {
const mockSessionManager = {};
return {
@@ -182,8 +194,8 @@ describe("In-progress task resume after restart", () => {
// Wait for async execute calls to complete
await new Promise((r) => setTimeout(r, 50));
// createKbAgent should have been called once per in-progress task
expect(mockedCreateHaiAgent).toHaveBeenCalledTimes(2);
// createKbAgent called twice per task (initial + retry when agent finishes without task_done)
expect(mockedCreateHaiAgent).toHaveBeenCalledTimes(4);
});
it("resumed task reuses existing worktree — no git worktree add called", async () => {
@@ -633,8 +645,8 @@ describe("Crash scenario edge cases", () => {
await executor.resumeOrphaned();
await new Promise((r) => setTimeout(r, 50));
// Agent should have been created again for the re-resume
expect(mockedCreateHaiAgent).toHaveBeenCalledTimes(1);
// Agent should have been created again for the re-resume (twice: initial + retry without task_done)
expect(mockedCreateHaiAgent).toHaveBeenCalledTimes(2);
});
it("engine killed during merge — git reset --merge cleanup, task stays in-review", async () => {