Merge pull request #51 from Runfusion/codex/testing-suite-quality-prd

test: harden and slim local test workflow
This commit is contained in:
gsxdsm
2026-05-07 10:18:35 -07:00
committed by GitHub
37 changed files with 2074 additions and 408 deletions

View File

@@ -0,0 +1,60 @@
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { readCustomProviders } from "../custom-providers.js";
describe("readCustomProviders", () => {
let homeDir: string;
let settingsPath: string;
beforeEach(async () => {
homeDir = await mkdtemp(join(tmpdir(), "fn-custom-providers-home-"));
settingsPath = join(homeDir, ".fusion", "settings.json");
await mkdir(join(homeDir, ".fusion"), { recursive: true });
});
afterEach(async () => {
await rm(homeDir, { recursive: true, force: true });
});
it("returns an empty list when settings are missing or malformed", async () => {
expect(readCustomProviders(homeDir)).toEqual([]);
await writeFile(settingsPath, "{ invalid json", "utf-8");
expect(readCustomProviders(homeDir)).toEqual([]);
await writeFile(
settingsPath,
JSON.stringify({ customProviders: { id: "not-an-array" } }),
"utf-8",
);
expect(readCustomProviders(homeDir)).toEqual([]);
});
it("returns custom provider arrays from user settings", async () => {
const providers = [
{
id: "local-openai",
name: "Local OpenAI",
apiType: "openai-compatible",
baseUrl: "http://localhost:11434/v1",
apiKey: "local-key",
models: [{ id: "qwen3", name: "Qwen 3" }],
},
{
id: "anthropic-proxy",
name: "Anthropic Proxy",
apiType: "anthropic-compatible",
baseUrl: "https://anthropic.example.test",
},
];
await writeFile(
settingsPath,
JSON.stringify({ customProviders: providers }),
"utf-8",
);
expect(readCustomProviders(homeDir)).toEqual(providers);
});
});

View File

@@ -0,0 +1,39 @@
import { describe, expect, it, vi } from "vitest";
import type { TaskDetail } from "@fusion/core";
import { getTaskCompletionBlockerForStore } from "../task-completion.js";
function createTask(overrides: Partial<TaskDetail> = {}): TaskDetail {
const now = new Date().toISOString();
return {
id: "FN-100",
description: "Task",
prompt: "Task prompt",
column: "in-progress",
dependencies: [],
steps: [],
currentStep: 0,
log: [],
createdAt: now,
updatedAt: now,
...overrides,
};
}
describe("getTaskCompletionBlockerForStore", () => {
it("treats dependency lookup failures as unresolved dependencies", async () => {
const getTask = vi.fn(async (taskId: string) => {
if (taskId === "FN-DONE") {
return createTask({ id: taskId, column: "done" });
}
throw new Error("database temporarily unavailable");
});
await expect(getTaskCompletionBlockerForStore(
{ getTask },
createTask({ dependencies: ["FN-DONE", "FN-MISSING"] }),
)).resolves.toBe("task has unresolved dependencies: FN-MISSING");
expect(getTask).toHaveBeenCalledWith("FN-DONE");
expect(getTask).toHaveBeenCalledWith("FN-MISSING");
});
});

View File

@@ -0,0 +1,81 @@
import { access, mkdtemp, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { setTimeout as delay } from "node:timers/promises";
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { execWithProcessGroup } from "../verification-utils.js";
const onPosix = process.platform !== "win32";
const itPosix = onPosix ? it : it.skip;
describe("execWithProcessGroup", { timeout: 10_000 }, () => {
let tempDir: string;
beforeEach(async () => {
tempDir = await mkdtemp(join(tmpdir(), "fn-verification-utils-"));
});
afterEach(async () => {
await rm(tempDir, { recursive: true, force: true });
});
it("reports buffer overflow while preserving capped stdout", async () => {
const result = await execWithProcessGroup(
`${JSON.stringify(process.execPath)} -e "process.stdout.write('x'.repeat(128))"`,
{ cwd: tempDir, timeout: 1_000, maxBuffer: 12 },
);
expect(result).toEqual({
stdout: "x".repeat(12),
stderr: "",
bufferOverflow: true,
});
});
it("rejects and kills the command when the abort signal fires", async () => {
const controller = new AbortController();
const promise = execWithProcessGroup(
`${JSON.stringify(process.execPath)} -e "setInterval(() => {}, 1000)"`,
{ cwd: tempDir, timeout: 5_000, maxBuffer: 1_024, signal: controller.signal },
);
setTimeout(() => controller.abort(), 50);
await expect(promise).rejects.toMatchObject({
code: "ABORT_ERR",
aborted: true,
killed: true,
});
});
itPosix("times out and terminates child processes in the spawned process group", async () => {
const markerPath = join(tempDir, "descendant-survived.txt");
const parentScriptPath = join(tempDir, "spawn-descendant.cjs");
await writeFile(
parentScriptPath,
`
const { spawn } = require("node:child_process");
spawn(process.execPath, [
"-e",
"setTimeout(() => require('node:fs').writeFileSync(process.env.MARKER, 'survived'), 450)",
], {
env: { ...process.env, MARKER: process.argv[2] },
stdio: "ignore",
}).unref();
setInterval(() => {}, 1000);
`,
"utf-8",
);
await expect(execWithProcessGroup(
`${JSON.stringify(process.execPath)} ${JSON.stringify(parentScriptPath)} ${JSON.stringify(markerPath)}`,
{ cwd: tempDir, timeout: 75, maxBuffer: 1_024 },
)).rejects.toMatchObject({
code: "ETIMEDOUT",
killed: true,
});
await delay(700);
await expect(access(markerPath)).rejects.toThrow();
});
});

View File

@@ -3,9 +3,9 @@ import { homedir } from "node:os";
import { join } from "node:path";
import type { CustomProvider } from "@fusion/core";
export function readCustomProviders(): CustomProvider[] {
export function readCustomProviders(homeDir = homedir()): CustomProvider[] {
try {
const settingsPath = join(homedir(), ".fusion", "settings.json");
const settingsPath = join(homeDir, ".fusion", "settings.json");
const raw = readFileSync(settingsPath, "utf-8");
const parsed = JSON.parse(raw) as { customProviders?: CustomProvider[] };
return Array.isArray(parsed.customProviders) ? parsed.customProviders : [];