fix(FN-1473): recover no-progress task_done failures

This commit is contained in:
gsxdsm
2026-04-12 12:13:06 -07:00
parent f915764e80
commit b2e25c08b2
8 changed files with 395 additions and 4 deletions

View File

@@ -0,0 +1,117 @@
import { describe, it, expect, vi, beforeEach, afterEach, beforeAll, afterAll } from "vitest";
import { EventEmitter } from "node:events";
import { execSync } from "node:child_process";
import { mkdirSync, writeFileSync, rmSync } from "node:fs";
import { join } from "node:path";
import { tmpdir } from "node:os";
import type { Task } from "@fusion/core";
import { createApiRoutes } from "../routes.js";
class MockStore extends EventEmitter {
private tasks = new Map<string, Task>();
getRootDir(): string { return process.cwd(); }
async getTask(id: string): Promise<Task> {
const task = this.tasks.get(id);
if (!task) throw Object.assign(new Error("Task not found"), { code: "ENOENT" });
return task;
}
addTask(task: Task): void { this.tasks.set(task.id, task); }
getMissionStore() { return new EventEmitter(); }
async listTasks(): Promise<Task[]> { return []; }
}
function createTask(overrides: Partial<Task> = {}): Task {
return {
id: "FN-675", title: "Test task", description: "Test description",
column: "in-progress", dependencies: [], steps: [], currentStep: 0, log: [],
createdAt: "2026-04-01T00:00:00.000Z", updatedAt: "2026-04-01T00:00:00.000Z",
columnMovedAt: "2026-04-01T00:00:00.000Z", worktree: "/tmp/fn-675",
...overrides,
};
}
describe("Session files endpoint", () => {
let testWorktree: string;
let firstCommit: string;
beforeAll(() => {
// Create a test git repository
testWorktree = join(tmpdir(), `fn-test-${Date.now()}`);
mkdirSync(testWorktree, { recursive: true });
// Initialize git repo
execSync("git init", { cwd: testWorktree });
execSync("git config user.email test@test.com", { cwd: testWorktree });
execSync("git config user.name Test", { cwd: testWorktree });
// Create a commit
writeFileSync(join(testWorktree, "test.txt"), "initial content");
execSync("git add .", { cwd: testWorktree });
execSync("git commit -m 'initial'", { cwd: testWorktree });
// Get the first commit SHA
firstCommit = execSync("git rev-parse HEAD", { cwd: testWorktree }).toString().trim();
// Create a second commit with changes
writeFileSync(join(testWorktree, "changed.txt"), "new content");
execSync("git add .", { cwd: testWorktree });
execSync("git commit -m 'second commit'", { cwd: testWorktree });
console.log("Test worktree:", testWorktree);
console.log("First commit:", firstCommit);
});
afterAll(() => {
// Clean up
try {
rmSync(testWorktree, { recursive: true, force: true });
} catch (e) {
// Ignore cleanup errors
}
});
beforeEach(() => {
vi.useFakeTimers({ toFake: ["Date", "setTimeout", "clearTimeout", "setInterval", "clearInterval"] });
vi.setSystemTime(new Date("2026-04-01T12:00:00.000Z"));
});
afterEach(() => {
vi.useRealTimers();
});
it("endpoint should return files from git using real baseCommitSha", async () => {
const store = new MockStore();
store.addTask(createTask({
id: "FN-TEST", title: "Test", description: "Test",
column: "in-progress", dependencies: [], steps: [], currentStep: 0, log: [],
createdAt: "2026-04-01T00:00:00.000Z", updatedAt: "2026-04-01T00:00:00.000Z",
columnMovedAt: "2026-04-01T00:00:00.000Z", worktree: testWorktree, baseCommitSha: firstCommit,
}));
const router = createApiRoutes(store);
const layer = (router as any).stack.find(
(c: any) => c.route?.path === "/tasks/:id/session-files" && c.route?.methods?.get,
);
const handler = layer.route.stack[layer.route.stack.length - 1].handle;
const res: any = {
statusCode: 200,
body: undefined,
status(code: number) { this.statusCode = code; return this; },
json(payload: any) { this.body = payload; return this; }
};
await handler({ params: { id: "FN-TEST" } }, res);
console.log("Response body:", res.body);
expect(res.statusCode).toBe(200);
expect(Array.isArray(res.body)).toBe(true);
// Should have changed.txt as a changed file
expect(res.body).toContain("changed.txt");
});
});