test(FN-4992): add changes-requested reliability interaction coverage

Fusion-Task-Id: FN-4992
Fusion-Task-Lineage: 133d5e6a-0612-4f67-9105-3b7dbabd5ec0
This commit is contained in:
Fusion (runfusion.ai)
2026-05-17 19:26:42 -07:00
committed by gsxdsm
parent 8ecba01c76
commit 0fd9d4094d

View File

@@ -0,0 +1,131 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { EventEmitter } from "node:events";
import type { Settings, Task, TaskStore } from "@fusion/core";
import * as worktreePool from "../../worktree-pool.js";
import { activeSessionRegistry } from "../../active-session-registry.js";
import { PrCommentHandler } from "../../pr-comment-handler.js";
function task(overrides: Partial<Task> = {}): Task {
return {
id: "FN-4992",
title: "t",
description: "d",
column: "in-review",
branch: "fusion/fn-4992",
worktree: "/tmp/test/.worktrees/fn-4992",
paused: false,
userPaused: false,
dependencies: [],
steps: [],
currentStep: 0,
log: [],
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
...overrides,
} as Task;
}
function store(t: Task): TaskStore & EventEmitter {
const emitter = new EventEmitter();
const settings = { globalPause: false, enginePaused: false } as Settings;
return Object.assign(emitter, {
getSettings: vi.fn(async () => settings),
getTask: vi.fn(async (id: string) => (id === t.id ? t : null)),
updateTask: vi.fn(async (_id: string, updates: Partial<Task>) => Object.assign(t, updates)),
addTaskComment: vi.fn(async () => undefined),
moveTask: vi.fn(async (_id: string, column: Task["column"]) => {
t.column = column;
return t;
}),
logEntry: vi.fn(async () => undefined),
}) as unknown as TaskStore & EventEmitter;
}
describe("reliability interaction (FN-4766): PR changes-requested re-execution", () => {
beforeEach(() => {
vi.restoreAllMocks();
activeSessionRegistry.clear();
vi.spyOn(worktreePool, "isUsableTaskWorktree").mockResolvedValue(true);
});
it.each([
{ column: "in-review" as const, shouldMove: true },
{ column: "in-progress" as const, shouldMove: false },
])("enforces in-review precondition for re-execution move: $column", async ({ column, shouldMove }) => {
const t = task({ column });
const s = store(t);
const handler = new PrCommentHandler(s as any);
await handler.handleChangesRequested(
t.id,
{ url: "https://github.com/o/r/pull/1", number: 1, status: "open", title: "pr", headBranch: "h", baseBranch: "main", commentCount: 0 },
"reviewer",
"please fix X",
);
expect((s as any).moveTask).toHaveBeenCalledTimes(shouldMove ? 1 : 0);
expect((s as any).logEntry).toHaveBeenCalledTimes(shouldMove ? 1 : 0);
});
it("preserves task branch/worktree metadata and paused state while moving engine-sourced to in-progress", async () => {
const t = task({ column: "in-review", paused: true });
const s = store(t);
const handler = new PrCommentHandler(s as any);
await handler.handleChangesRequested(
t.id,
{ url: "https://github.com/o/r/pull/2", number: 2, status: "open", title: "pr", headBranch: "h", baseBranch: "main", commentCount: 0 },
"reviewer",
"please fix Y",
);
expect((s as any).moveTask).toHaveBeenCalledWith(t.id, "in-progress");
expect((s as any).moveTask.mock.calls[0].length).toBe(2);
expect(t.branch).toBe("fusion/fn-4992");
expect(t.worktree).toBe("/tmp/test/.worktrees/fn-4992");
expect(t.paused).toBe(true);
});
it("materializes feedback in ordered write sequence comment -> review item -> move -> log", async () => {
const t = task();
const s = store(t);
const handler = new PrCommentHandler(s as any);
await handler.handleChangesRequested(
t.id,
{ url: "https://github.com/o/r/pull/3", number: 3, status: "open", title: "pr", headBranch: "h", baseBranch: "main", commentCount: 0 },
"reviewer",
"please fix Z",
);
expect((s as any).addTaskComment).toHaveBeenCalledTimes(1);
expect((s as any).updateTask).toHaveBeenCalledTimes(1);
expect((s as any).moveTask).toHaveBeenCalledTimes(1);
expect((s as any).logEntry).toHaveBeenCalledTimes(1);
const commentOrder = (s as any).addTaskComment.mock.invocationCallOrder[0];
const reviewOrder = (s as any).updateTask.mock.invocationCallOrder[0];
const moveOrder = (s as any).moveTask.mock.invocationCallOrder[0];
const logOrder = (s as any).logEntry.mock.invocationCallOrder[0];
expect(commentOrder).toBeLessThan(reviewOrder);
expect(reviewOrder).toBeLessThan(moveOrder);
expect(moveOrder).toBeLessThan(logOrder);
});
it("swallows moveTask failures as best-effort", async () => {
const t = task();
const s = store(t);
(s as any).moveTask = vi.fn().mockRejectedValueOnce(new Error("boom"));
const handler = new PrCommentHandler(s as any);
await expect(
handler.handleChangesRequested(
t.id,
{ url: "https://github.com/o/r/pull/4", number: 4, status: "open", title: "pr", headBranch: "h", baseBranch: "main", commentCount: 0 },
"reviewer",
"please fix boom",
),
).resolves.toBeUndefined();
});
});