Files
fusion/packages/engine/src/__tests__/triage-soft-delete-write-abort.test.ts
gsxdsm 87da1cb757 test(dashboard): align github-tracking-documentation phrases with current docs
The doc was rewritten to cover "every task-creation path" (with an
explicit enumeration of surfaces) instead of the older "task creation
flows (including ...)" wording, and uses "best-effort and non-blocking"
instead of "Creation is best-effort and non-blocking". Update the
documentation contract test to match the current phrasing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Fusion-Task-Id: FN-5208
2026-05-19 19:23:47 -07:00

151 lines
4.6 KiB
TypeScript

import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import type { Settings, Task, TaskDetail, TaskStore } from "@fusion/core";
import { TaskDeletedError } from "@fusion/core";
const {
mockCreateResolvedAgentSession,
mockPromptWithFallback,
mockDescribeModel,
} = vi.hoisted(() => ({
mockCreateResolvedAgentSession: vi.fn(),
mockPromptWithFallback: vi.fn(),
mockDescribeModel: vi.fn().mockReturnValue("mock-model"),
}));
vi.mock("../agent-session-helpers.js", () => ({
createResolvedAgentSession: mockCreateResolvedAgentSession,
extractRuntimeHint: vi.fn(),
resolvePlanningSessionModel: vi.fn().mockReturnValue({ provider: "mock", modelId: "mock-model" }),
}));
vi.mock("../pi.js", () => ({
describeModel: mockDescribeModel,
promptWithFallback: mockPromptWithFallback,
}));
vi.mock("../reviewer.js", () => ({
reviewStep: vi.fn(),
}));
vi.mock("@fusion/core", async (importOriginal) => {
const { createEngineCoreMock } = await import("../test/mockCore.js");
return createEngineCoreMock(() => importOriginal<typeof import("@fusion/core")>(), {
resolveAgentPrompt: vi.fn().mockReturnValue(null),
});
});
import { planLog } from "../logger.js";
import { TriageProcessor } from "../triage.js";
const mockTaskDetail: TaskDetail = {
id: "FN-5208",
description: "soft delete race",
column: "triage",
dependencies: [],
steps: [],
currentStep: 0,
log: [],
createdAt: "2026-01-01T00:00:00.000Z",
updatedAt: "2026-01-01T00:00:00.000Z",
prompt: "# FN-5208\n",
attachments: [],
comments: [],
};
function createTask(overrides: Partial<Task> = {}): Task {
return {
id: "FN-5208",
description: "soft delete race",
column: "triage",
dependencies: [],
steps: [],
currentStep: 0,
log: [],
createdAt: "2026-01-01T00:00:00.000Z",
updatedAt: "2026-01-01T00:00:00.000Z",
...overrides,
};
}
function createMockStore(overrides: Partial<TaskStore> = {}): TaskStore {
return {
on: vi.fn(),
getTask: vi.fn().mockResolvedValue({ ...mockTaskDetail }),
getSettings: vi.fn().mockResolvedValue({
maxConcurrent: 2,
maxWorktrees: 4,
pollIntervalMs: 10000,
groupOverlappingFiles: false,
autoMerge: true,
} as Settings),
listTasks: vi.fn().mockResolvedValue([]),
updateTask: vi.fn().mockResolvedValue(undefined),
logEntry: vi.fn().mockResolvedValue(undefined),
appendAgentLog: vi.fn().mockResolvedValue(undefined),
parseDependenciesFromPrompt: vi.fn().mockResolvedValue([]),
parseStepsFromPrompt: vi.fn().mockResolvedValue([]),
parseFileScopeFromPrompt: vi.fn().mockResolvedValue([]),
...overrides,
} as unknown as TaskStore;
}
describe("triage soft-delete write abort", () => {
beforeEach(() => {
vi.clearAllMocks();
mockPromptWithFallback.mockResolvedValue(undefined);
});
afterEach(() => {
vi.clearAllMocks();
});
it("aborts specifyTask cleanly when a store write hits TaskDeletedError", async () => {
const deletedAt = "2026-05-19T12:00:00.000Z";
const dispose = vi.fn();
const onSpecifyError = vi.fn();
const logSpy = vi.spyOn(planLog, "log");
mockCreateResolvedAgentSession.mockResolvedValue({
session: {
state: {},
sessionManager: {},
dispose,
navigateTree: vi.fn(),
},
});
const store = createMockStore({
updateTask: vi.fn().mockRejectedValueOnce(new TaskDeletedError("FN-5208", deletedAt)),
});
const processor = new TriageProcessor(store, "/tmp/root", { onSpecifyError });
await expect(processor.specifyTask(createTask())).resolves.toBeUndefined();
expect(store.updateTask).toHaveBeenCalledTimes(1);
expect(dispose).not.toHaveBeenCalled();
expect(onSpecifyError).not.toHaveBeenCalled();
expect(logSpy).toHaveBeenCalledWith("[triage] FN-5208: skipping spec write — task soft-deleted");
expect(mockPromptWithFallback).not.toHaveBeenCalled();
expect((processor as any).activeSessions.size).toBe(0);
});
it("keeps normal specifyTask runs progressing through planning setup", async () => {
const dispose = vi.fn();
mockCreateResolvedAgentSession.mockResolvedValue({
session: {
state: {},
sessionManager: {},
dispose,
navigateTree: vi.fn(),
},
});
const store = createMockStore();
const processor = new TriageProcessor(store, "/tmp/root");
await expect(processor.specifyTask(createTask())).resolves.toBeUndefined();
expect(store.updateTask).toHaveBeenCalledWith("FN-5208", { status: "planning" });
expect(mockPromptWithFallback).toHaveBeenCalled();
expect(dispose).toHaveBeenCalledTimes(1);
});
});