FN-8225: migrate goal-tool tests to shared PostgreSQL harness
Migrate goal-tool extension tests to the injected shared PostgreSQL harness. - Replace temporary embedded PostgreSQL roots with shared harness lifecycle hooks - Reuse harness API registration and tool helpers across goal retrieval and audit suites - Preserve retrieval audit assertions while avoiding concurrent database startup locks Files changed: .../__tests__/extension-goal-tools-audit.test.ts | 72 ++++++------- .../cli/src/__tests__/extension-goal-tools.test.ts | 113 ++++++++------------- 2 files changed, 76 insertions(+), 109 deletions(-) Fusion-Task-Id: FN-8225 Fusion-Task-Lineage: 424c1bb2-5cb7-4e81-b3fd-8f6a6f29b433 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
@@ -1,55 +1,49 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { mkdtemp, mkdir, rm } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import { tmpdir } from "node:os";
|
||||
/**
|
||||
* FNXC:PostgresCutover 2026-07-17-00:00:
|
||||
* FN-8225 moves this audit suite from a per-test temporary-root `getStore(cwd)`
|
||||
* path, which booted embedded PostgreSQL, to the shared injected PostgreSQL
|
||||
* harness. This prevents FN-8222's concurrent postmaster.pid locks, leaked
|
||||
* initdb processes, and five-second timeouts while retaining prototype audit spies.
|
||||
*/
|
||||
|
||||
import { afterAll, afterEach, beforeAll, beforeEach, expect, it, vi } from "vitest";
|
||||
import { TaskStore, collectCitedGoalIdsFromAudit } from "@fusion/core";
|
||||
import kbExtension, { closeCachedStores } from "../extension.js";
|
||||
import { GOAL_RETRIEVAL_INVOKED } from "@fusion/engine";
|
||||
import {
|
||||
createMockApi,
|
||||
createPgExtensionHarness,
|
||||
pgDescribe,
|
||||
registerExtension,
|
||||
requireTool,
|
||||
} from "./pg-extension-harness.js";
|
||||
|
||||
interface RegisteredTool {
|
||||
name: string;
|
||||
execute: (toolCallId: string, params: any, signal: AbortSignal | undefined, onUpdate: ((update: any) => void) | undefined, ctx: any) => Promise<any>;
|
||||
}
|
||||
|
||||
function createMockAPI() {
|
||||
const tools = new Map<string, RegisteredTool>();
|
||||
return {
|
||||
registerTool(def: RegisteredTool) { tools.set(def.name, def); },
|
||||
registerCommand() {},
|
||||
registerShortcut() {},
|
||||
registerFlag() {},
|
||||
on() {},
|
||||
tools,
|
||||
} as any;
|
||||
}
|
||||
|
||||
describe("extension goal tools retrieval audit", () => {
|
||||
let tmpDir: string;
|
||||
|
||||
beforeEach(async () => {
|
||||
tmpDir = await mkdtemp(join(tmpdir(), "kb-goal-audit-"));
|
||||
await mkdir(join(tmpDir, ".fusion"), { recursive: true });
|
||||
});
|
||||
pgDescribe("extension goal tools retrieval audit", () => {
|
||||
const h = createPgExtensionHarness("fn-goal-tools-audit");
|
||||
|
||||
beforeAll(h.beforeAll);
|
||||
beforeEach(h.beforeEach);
|
||||
afterEach(async () => {
|
||||
await closeCachedStores();
|
||||
await rm(tmpDir, { recursive: true, force: true });
|
||||
vi.restoreAllMocks();
|
||||
try {
|
||||
await h.afterEach();
|
||||
} finally {
|
||||
vi.restoreAllMocks();
|
||||
}
|
||||
});
|
||||
afterAll(h.afterAll);
|
||||
|
||||
it("emits retrieval audit for fn_goal_list and fn_goal_show branches", async () => {
|
||||
const recordSpy = vi.spyOn(TaskStore.prototype, "recordRunAuditEvent");
|
||||
const api = createMockAPI();
|
||||
kbExtension(api);
|
||||
const api = createMockApi();
|
||||
registerExtension(api);
|
||||
|
||||
const createTool = api.tools.get("fn_goal_create");
|
||||
const listTool = api.tools.get("fn_goal_list");
|
||||
const showTool = api.tools.get("fn_goal_show");
|
||||
const ctx = { cwd: tmpDir, runId: "run-1", agentId: "agent-1", taskId: "FN-1" };
|
||||
const createTool = requireTool(api, "fn_goal_create");
|
||||
const listTool = requireTool(api, "fn_goal_list");
|
||||
const showTool = requireTool(api, "fn_goal_show");
|
||||
const ctx = { cwd: h.rootDir(), runId: "run-1", agentId: "agent-1", taskId: "FN-1" };
|
||||
|
||||
await createTool.execute("c1", { title: "Goal one" }, undefined, undefined, ctx);
|
||||
const listResult = await listTool.execute("l1", { status: "active" }, undefined, undefined, ctx);
|
||||
const goalId = listResult.details.goals[0].id as string;
|
||||
const goalId = (listResult.details!.goals as Array<{ id: string }>)[0].id;
|
||||
|
||||
await showTool.execute("s1", { id: goalId }, undefined, undefined, ctx);
|
||||
await showTool.execute("s2", { id: "G-404" }, undefined, undefined, ctx);
|
||||
|
||||
@@ -1,42 +1,18 @@
|
||||
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||
import { mkdtemp, mkdir, rm } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import { tmpdir } from "node:os";
|
||||
import kbExtension, { closeCachedStores } from "../extension.js";
|
||||
/**
|
||||
* FNXC:PostgresCutover 2026-07-17-00:00:
|
||||
* FN-8225 moves these goal-tool tests from a per-test temporary-root
|
||||
* `getStore(cwd)` path, which booted embedded PostgreSQL, to the shared
|
||||
* injected PostgreSQL harness. This prevents FN-8222's concurrent
|
||||
* postmaster.pid locks, leaked initdb processes, and five-second timeouts.
|
||||
*/
|
||||
|
||||
interface RegisteredTool {
|
||||
name: string;
|
||||
description: string;
|
||||
parameters?: {
|
||||
type: string;
|
||||
properties?: Record<string, unknown>;
|
||||
required?: string[];
|
||||
};
|
||||
execute: (
|
||||
toolCallId: string,
|
||||
params: any,
|
||||
signal: AbortSignal | undefined,
|
||||
onUpdate: ((update: any) => void) | undefined,
|
||||
ctx: any,
|
||||
) => Promise<any>;
|
||||
}
|
||||
|
||||
function createMockAPI() {
|
||||
const tools = new Map<string, RegisteredTool>();
|
||||
|
||||
const api = {
|
||||
registerTool(def: RegisteredTool) {
|
||||
tools.set(def.name, def);
|
||||
},
|
||||
registerCommand() {},
|
||||
registerShortcut() {},
|
||||
registerFlag() {},
|
||||
on() {},
|
||||
tools,
|
||||
};
|
||||
|
||||
return api as any;
|
||||
}
|
||||
import { afterAll, afterEach, beforeAll, beforeEach, expect, it } from "vitest";
|
||||
import {
|
||||
createMockApi,
|
||||
createPgExtensionHarness,
|
||||
pgDescribe,
|
||||
registerExtension,
|
||||
} from "./pg-extension-harness.js";
|
||||
|
||||
function makeCtx(cwd: string) {
|
||||
return { cwd } as any;
|
||||
@@ -47,21 +23,18 @@ function textOf(result: { content: Array<{ type: string; text?: string }> }): st
|
||||
return first && first.type === "text" ? (first.text ?? "") : "";
|
||||
}
|
||||
|
||||
describe("extension goal retrieval tools", () => {
|
||||
let tmpDir: string;
|
||||
let api: ReturnType<typeof createMockAPI>;
|
||||
pgDescribe("extension goal retrieval tools", () => {
|
||||
const h = createPgExtensionHarness("fn-goal-tools");
|
||||
let api: ReturnType<typeof createMockApi>;
|
||||
|
||||
beforeAll(h.beforeAll);
|
||||
beforeEach(async () => {
|
||||
tmpDir = await mkdtemp(join(tmpdir(), "kb-goal-tools-"));
|
||||
await mkdir(join(tmpDir, ".fusion"), { recursive: true });
|
||||
api = createMockAPI();
|
||||
kbExtension(api);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closeCachedStores();
|
||||
await rm(tmpDir, { recursive: true, force: true });
|
||||
await h.beforeEach();
|
||||
api = createMockApi();
|
||||
registerExtension(api);
|
||||
});
|
||||
afterEach(h.afterEach);
|
||||
afterAll(h.afterAll);
|
||||
|
||||
it("registers fn_goal_show with expected schema", () => {
|
||||
const tool = api.tools.get("fn_goal_show");
|
||||
@@ -92,11 +65,11 @@ describe("extension goal retrieval tools", () => {
|
||||
{ title: "Improve reliability", description: "Reduce flaky test retries" },
|
||||
undefined,
|
||||
undefined,
|
||||
makeCtx(tmpDir),
|
||||
makeCtx(h.rootDir()),
|
||||
);
|
||||
|
||||
const goalId = created.details.goalId as string;
|
||||
const result = await tool!.execute("goal-show-1", { id: goalId }, undefined, undefined, makeCtx(tmpDir));
|
||||
const result = await tool!.execute("goal-show-1", { id: goalId }, undefined, undefined, makeCtx(h.rootDir()));
|
||||
|
||||
expect(result.isError).toBeUndefined();
|
||||
expect(result.details.goal).toMatchObject({
|
||||
@@ -118,7 +91,7 @@ describe("extension goal retrieval tools", () => {
|
||||
const tool = api.tools.get("fn_goal_show");
|
||||
expect(tool).toBeDefined();
|
||||
|
||||
const result = await tool!.execute("goal-show-404", { id: "G-404" }, undefined, undefined, makeCtx(tmpDir));
|
||||
const result = await tool!.execute("goal-show-404", { id: "G-404" }, undefined, undefined, makeCtx(h.rootDir()));
|
||||
|
||||
expect(result.isError).toBe(true);
|
||||
expect(textOf(result)).toBe("Goal G-404 not found");
|
||||
@@ -131,7 +104,7 @@ describe("extension goal retrieval tools", () => {
|
||||
expect(createTool).toBeDefined();
|
||||
expect(listTool).toBeDefined();
|
||||
|
||||
const emptyResult = await listTool!.execute("goal-list-empty", {}, undefined, undefined, makeCtx(tmpDir));
|
||||
const emptyResult = await listTool!.execute("goal-list-empty", {}, undefined, undefined, makeCtx(h.rootDir()));
|
||||
expect(textOf(emptyResult)).toBe(["Goals (0) [filter: active]", "Active: 0/5", "", "No goals found."].join("\n"));
|
||||
expect(emptyResult.details).toEqual({ goals: [], activeCount: 0, softWarning: false, hardLimit: 5 });
|
||||
|
||||
@@ -140,10 +113,10 @@ describe("extension goal retrieval tools", () => {
|
||||
{ title: "Ship slice 2" },
|
||||
undefined,
|
||||
undefined,
|
||||
makeCtx(tmpDir),
|
||||
makeCtx(h.rootDir()),
|
||||
);
|
||||
const goalId = created.details.goalId as string;
|
||||
const result = await listTool!.execute("goal-list-single", { status: "active" }, undefined, undefined, makeCtx(tmpDir));
|
||||
const result = await listTool!.execute("goal-list-single", { status: "active" }, undefined, undefined, makeCtx(h.rootDir()));
|
||||
|
||||
expect(textOf(result)).toBe([
|
||||
"Goals (1) [filter: active]",
|
||||
@@ -170,11 +143,11 @@ describe("extension goal retrieval tools", () => {
|
||||
},
|
||||
undefined,
|
||||
undefined,
|
||||
makeCtx(tmpDir),
|
||||
makeCtx(h.rootDir()),
|
||||
);
|
||||
|
||||
const goalId = created.details.goalId as string;
|
||||
const listResult = await listTool!.execute("goal-list-long", {}, undefined, undefined, makeCtx(tmpDir));
|
||||
const listResult = await listTool!.execute("goal-list-long", {}, undefined, undefined, makeCtx(h.rootDir()));
|
||||
const listText = textOf(listResult);
|
||||
|
||||
expect(listText).toContain(`- ${goalId} [active] Cite goals by ID — First line with extra spaces`);
|
||||
@@ -189,7 +162,7 @@ describe("extension goal retrieval tools", () => {
|
||||
},
|
||||
]);
|
||||
|
||||
const showResult = await showTool!.execute("goal-show-long", { id: goalId }, undefined, undefined, makeCtx(tmpDir));
|
||||
const showResult = await showTool!.execute("goal-show-long", { id: goalId }, undefined, undefined, makeCtx(h.rootDir()));
|
||||
expect(textOf(showResult)).toContain("Description: First line with extra spaces");
|
||||
expect(textOf(showResult)).toContain("Second line must never appear in fn_goal_list.");
|
||||
expect(showResult.details.goal.description).toContain("Second line must never appear in fn_goal_list.");
|
||||
@@ -203,14 +176,14 @@ describe("extension goal retrieval tools", () => {
|
||||
expect(archiveTool).toBeDefined();
|
||||
expect(listTool).toBeDefined();
|
||||
|
||||
const archivedGoal = await createTool!.execute("goal-create-4", { title: "Archive me", description: "one line" }, undefined, undefined, makeCtx(tmpDir));
|
||||
await archiveTool!.execute("goal-archive-1", { id: archivedGoal.details.goalId }, undefined, undefined, makeCtx(tmpDir));
|
||||
await createTool!.execute("goal-create-5", { title: "One" }, undefined, undefined, makeCtx(tmpDir));
|
||||
await createTool!.execute("goal-create-6", { title: "Two" }, undefined, undefined, makeCtx(tmpDir));
|
||||
await createTool!.execute("goal-create-7", { title: "Three" }, undefined, undefined, makeCtx(tmpDir));
|
||||
const archivedGoal = await createTool!.execute("goal-create-4", { title: "Archive me", description: "one line" }, undefined, undefined, makeCtx(h.rootDir()));
|
||||
await archiveTool!.execute("goal-archive-1", { id: archivedGoal.details.goalId }, undefined, undefined, makeCtx(h.rootDir()));
|
||||
await createTool!.execute("goal-create-5", { title: "One" }, undefined, undefined, makeCtx(h.rootDir()));
|
||||
await createTool!.execute("goal-create-6", { title: "Two" }, undefined, undefined, makeCtx(h.rootDir()));
|
||||
await createTool!.execute("goal-create-7", { title: "Three" }, undefined, undefined, makeCtx(h.rootDir()));
|
||||
|
||||
const archivedResult = await listTool!.execute("goal-list-archived", { status: "archived" }, undefined, undefined, makeCtx(tmpDir));
|
||||
const allResult = await listTool!.execute("goal-list-all", { status: "all" }, undefined, undefined, makeCtx(tmpDir));
|
||||
const archivedResult = await listTool!.execute("goal-list-archived", { status: "archived" }, undefined, undefined, makeCtx(h.rootDir()));
|
||||
const allResult = await listTool!.execute("goal-list-all", { status: "all" }, undefined, undefined, makeCtx(h.rootDir()));
|
||||
|
||||
expect(textOf(archivedResult)).toBe([
|
||||
"Goals (1) [filter: archived]",
|
||||
@@ -232,14 +205,14 @@ describe("extension goal retrieval tools", () => {
|
||||
expect(listTool).toBeDefined();
|
||||
expect(showTool).toBeDefined();
|
||||
|
||||
await createTool!.execute("goal-create-8", { title: "Ship slice 2" }, undefined, undefined, makeCtx(tmpDir));
|
||||
await createTool!.execute("goal-create-8", { title: "Ship slice 2" }, undefined, undefined, makeCtx(h.rootDir()));
|
||||
|
||||
const listResult = await listTool!.execute("goal-list-1", { status: "active" }, undefined, undefined, makeCtx(tmpDir));
|
||||
const listResult = await listTool!.execute("goal-list-1", { status: "active" }, undefined, undefined, makeCtx(h.rootDir()));
|
||||
expect(Array.isArray(listResult.details.goals)).toBe(true);
|
||||
expect(listResult.details.goals.length).toBeGreaterThan(0);
|
||||
|
||||
const listedGoal = listResult.details.goals[0] as { id: string };
|
||||
const showResult = await showTool!.execute("goal-show-2", { id: listedGoal.id }, undefined, undefined, makeCtx(tmpDir));
|
||||
const showResult = await showTool!.execute("goal-show-2", { id: listedGoal.id }, undefined, undefined, makeCtx(h.rootDir()));
|
||||
|
||||
expect(showResult.details.goal.id).toBe(listedGoal.id);
|
||||
expect(showResult.details.goal).toMatchObject({
|
||||
|
||||
Reference in New Issue
Block a user