fix: green full-suite after product and CI PG drift (#2285)
## Summary Main Full Suite was red again after release/desktop workflow drift, engine mock gaps, mission landed-SHA gating, and compound-engineering PG admin auth on GHA (`USER=runner`). ## Fixes | Area | Failure | Fix | |------|---------|-----| | desktop `release-workflow` | expected old `find artifacts -type f` | assert pruned collect + `release-files/*` | | `step-session-executor` | missing `resolveExecutorFallbackThinkingLevel` | mock export | | tool-availability tests | empty tools (cascade from above) | fixed by mock | | `skill-resolver` | TDZ on `mockFiles` during import | `vi.hoisted` filesystem state | | `merge-error-recovery` | enqueue no-op when not started | set `started=true` | | mission behavioral posture | `blocked` (no landed SHA / git probe) | `mergeDetails.commitSha` + staleness stub | | GraphTaskNode | missing `useOptionalToast` | mock both toast exports | | CE `pipeline-store.pg` | psql as `runner` | admin via `FUSION_PG_TEST_URL_BASE` | ## Test plan - [x] step-session-executor, skill-resolver, merge-error-recovery, mission-validator-behavioral-posture (203) - [x] release-workflow (10) - [x] `pnpm --filter @fusion/engine test:core` (294) - [ ] Full Suite (non-blocking) after merge <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **Bug Fixes** - Improved desktop release artifact collection by pruning nested `runtime` and `migrations` directories and consistently staging release uploads via a dedicated `release-files` mapping. - **Tests** - Enhanced engine merge error-recovery coverage and mission validator behavioral posture setup. - Improved test reliability by synchronizing mocked filesystem state, executor fallbacks, and toast hook variants. - Updated Postgres test harness/admin commands to use a configurable base URL; refined related Windows changeset description. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -2,6 +2,6 @@
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
summary: Elevated Windows no longer creates a local 'fusion-pg' account to boot embedded PostgreSQL; leftover accounts are removed.
|
||||
summary: Elevated Windows boots embedded PostgreSQL without a local fusion-pg account; leftovers are cleaned up.
|
||||
category: fix
|
||||
dev: "Replaces the Start-Process -Credential non-admin-user launcher with pg_ctl's built-in restricted-token re-exec (embedded-windows-elevated.ts). Removes user creation, icacls grants, and the cmd/PowerShell wrapper — also eliminating the 'directory name is invalid' launch failure and the EBUSY on wrapper-held postgres.log. The elevated path now best-effort deletes a legacy fusion-pg account on start."
|
||||
|
||||
@@ -124,7 +124,13 @@ describe("desktop release workflow wiring", () => {
|
||||
expect(release).toContain(
|
||||
"needs: [build-binaries, build-desktop-windows, build-desktop-macos, build-desktop-linux, build-android]",
|
||||
);
|
||||
expect(release).toContain('find artifacts -type f \\(');
|
||||
/*
|
||||
FNXC:DesktopTests 2026-07-18-04:35:
|
||||
Collect now prunes nested runtime/migrations trees before matching desktop
|
||||
artifact globs, and copies into release-files/ for softprops/action-gh-release.
|
||||
*/
|
||||
expect(release).toContain('find artifacts \\( -path "*/runtime/*" -o -path "*/migrations/*" \\) -prune -o -type f \\(');
|
||||
expect(release).toContain('mkdir release-files');
|
||||
expect(release).toContain('-name "*.exe"');
|
||||
expect(release).toContain('-name "*.exe.sha256"');
|
||||
expect(release).toContain('-name "*.blockmap"');
|
||||
@@ -134,6 +140,7 @@ describe("desktop release workflow wiring", () => {
|
||||
expect(release).toContain('-name "*.deb"');
|
||||
expect(release).toContain('-name "*.tar.gz"');
|
||||
expect(release).toContain('-name "latest*.yml"');
|
||||
expect(release).toContain("files: release-files/*");
|
||||
});
|
||||
|
||||
it("wires test-release collect job to wait for all desktop build jobs", async () => {
|
||||
|
||||
@@ -359,8 +359,16 @@ describe("ProjectEngine merge error recovery", () => {
|
||||
const privateEngine = engine as unknown as {
|
||||
internalEnqueueMerge: (taskId: string) => void;
|
||||
mergeRunning: boolean;
|
||||
started: boolean;
|
||||
};
|
||||
|
||||
/*
|
||||
FNXC:EngineTests 2026-07-18-04:40:
|
||||
internalEnqueueMerge no-ops when started=false (post-start gate). Mark the
|
||||
engine started so the null-store drain path runs and the unexpected-failure
|
||||
catch can log.
|
||||
*/
|
||||
privateEngine.started = true;
|
||||
privateEngine.internalEnqueueMerge(TASK_ID);
|
||||
await Promise.resolve();
|
||||
await Promise.resolve();
|
||||
|
||||
@@ -258,6 +258,34 @@ describe("Validator behavioral posture (U2 + U3)", () => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
/*
|
||||
FNXC:EngineTests 2026-07-18-04:40:
|
||||
Behavioral validation now requires a proven landed merge SHA
|
||||
(task.mergeDetails.commitSha) and a non-stale inspection root before FAIL can
|
||||
mint Fix Features. Stub the workspace-staleness probe so unit tests do not
|
||||
depend on a real git repo under rootDir=/tmp.
|
||||
*/
|
||||
function proveLandedInspection() {
|
||||
vi.spyOn(MissionExecutionLoop.prototype as any, "isValidationWorkspaceStale").mockImplementation(
|
||||
async (landedSha: string | undefined) => {
|
||||
if (!landedSha) {
|
||||
return { workspaceStale: false, inspectionUnavailableReason: "landed merge SHA is unavailable" };
|
||||
}
|
||||
return { workspaceStale: false };
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
function landedTask(id: string, title: string, extra: Record<string, unknown> = {}) {
|
||||
return {
|
||||
id,
|
||||
title,
|
||||
log: [],
|
||||
mergeDetails: { commitSha: "sha123" },
|
||||
...extra,
|
||||
};
|
||||
}
|
||||
|
||||
function judgePass(assertionIds: string[]) {
|
||||
mockSessionHolder.session.state.messages = [
|
||||
{
|
||||
@@ -272,10 +300,11 @@ describe("Validator behavioral posture (U2 + U3)", () => {
|
||||
}
|
||||
|
||||
it("AE2: behavioral assertion the judge calls pass → fails with no verification capability", async () => {
|
||||
proveLandedInspection();
|
||||
const feature = createMockFeature({ loopState: "implementing", taskId: "FN-B", status: "in-progress" });
|
||||
missionStore._setFeature(feature);
|
||||
missionStore._setAssertions("F-001", [assertionRow({ id: "CA-1", type: "behavioral" })]);
|
||||
taskStore._setTask({ id: "FN-B", title: "behavioral", log: [] });
|
||||
taskStore._setTask(landedTask("FN-B", "behavioral"));
|
||||
judgePass(["CA-1"]);
|
||||
|
||||
loop = new MissionExecutionLoop({ taskStore: taskStore as any, missionStore: missionStore as any, rootDir: "/tmp" });
|
||||
@@ -324,11 +353,12 @@ describe("Validator behavioral posture (U2 + U3)", () => {
|
||||
});
|
||||
|
||||
it("behavioral assertion confirmed by an injected verification capability → passes", async () => {
|
||||
proveLandedInspection();
|
||||
const verify = vi.fn(async (req): Promise<VerificationOutcome> => ({ verdict: "pass", assertionId: req.assertionId, reason: "confirmed" }));
|
||||
const feature = createMockFeature({ loopState: "implementing", taskId: "FN-BV", status: "in-progress" });
|
||||
missionStore._setFeature(feature);
|
||||
missionStore._setAssertions("F-001", [assertionRow({ id: "CA-1", type: "behavioral" })]);
|
||||
taskStore._setTask({ id: "FN-BV", title: "behavioral verified", integrationSha: "sha123", log: [] });
|
||||
taskStore._setTask(landedTask("FN-BV", "behavioral verified"));
|
||||
judgePass(["CA-1"]);
|
||||
|
||||
loop = new MissionExecutionLoop({ taskStore: taskStore as any, missionStore: missionStore as any, rootDir: "/tmp", verificationCapability: { verifyBehavioralAssertion: verify } });
|
||||
@@ -342,11 +372,12 @@ describe("Validator behavioral posture (U2 + U3)", () => {
|
||||
});
|
||||
|
||||
it("behavioral assertion verification inconclusive → blocked, NO fix feature", async () => {
|
||||
proveLandedInspection();
|
||||
const verify = vi.fn(async (req): Promise<VerificationOutcome> => ({ verdict: "inconclusive", assertionId: req.assertionId, reason: "no isolating sandbox backend" }));
|
||||
const feature = createMockFeature({ loopState: "implementing", taskId: "FN-INC", status: "in-progress" });
|
||||
missionStore._setFeature(feature);
|
||||
missionStore._setAssertions("F-001", [assertionRow({ id: "CA-1", type: "behavioral" })]);
|
||||
taskStore._setTask({ id: "FN-INC", title: "inconclusive", integrationSha: "sha123", log: [] });
|
||||
taskStore._setTask(landedTask("FN-INC", "inconclusive"));
|
||||
judgePass(["CA-1"]);
|
||||
|
||||
loop = new MissionExecutionLoop({ taskStore: taskStore as any, missionStore: missionStore as any, rootDir: "/tmp", verificationCapability: { verifyBehavioralAssertion: verify } });
|
||||
@@ -359,6 +390,7 @@ describe("Validator behavioral posture (U2 + U3)", () => {
|
||||
});
|
||||
|
||||
it("mixed set: static passes via judge, behavioral confirmed via verification → overall pass", async () => {
|
||||
proveLandedInspection();
|
||||
const verify = vi.fn(async (req): Promise<VerificationOutcome> => ({ verdict: "pass", assertionId: req.assertionId, reason: "confirmed" }));
|
||||
const feature = createMockFeature({ loopState: "implementing", taskId: "FN-MIX", status: "in-progress" });
|
||||
missionStore._setFeature(feature);
|
||||
@@ -366,7 +398,7 @@ describe("Validator behavioral posture (U2 + U3)", () => {
|
||||
assertionRow({ id: "CA-static", type: "static" }),
|
||||
assertionRow({ id: "CA-behav", type: "behavioral" }),
|
||||
]);
|
||||
taskStore._setTask({ id: "FN-MIX", title: "mixed", integrationSha: "sha123", log: [] });
|
||||
taskStore._setTask(landedTask("FN-MIX", "mixed"));
|
||||
judgePass(["CA-static", "CA-behav"]);
|
||||
|
||||
loop = new MissionExecutionLoop({ taskStore: taskStore as any, missionStore: missionStore as any, rootDir: "/tmp", verificationCapability: { verifyBehavioralAssertion: verify } });
|
||||
@@ -381,6 +413,7 @@ describe("Validator behavioral posture (U2 + U3)", () => {
|
||||
});
|
||||
|
||||
it("mixed set: behavioral observed wrong → overall fail even though static passes", async () => {
|
||||
proveLandedInspection();
|
||||
const verify = vi.fn(async (req): Promise<VerificationOutcome> => ({ verdict: "fail", assertionId: req.assertionId, reason: "defect still reproduces" }));
|
||||
const feature = createMockFeature({ loopState: "implementing", taskId: "FN-MIX2", status: "in-progress" });
|
||||
missionStore._setFeature(feature);
|
||||
@@ -388,7 +421,7 @@ describe("Validator behavioral posture (U2 + U3)", () => {
|
||||
assertionRow({ id: "CA-static", type: "static" }),
|
||||
assertionRow({ id: "CA-behav", type: "behavioral" }),
|
||||
]);
|
||||
taskStore._setTask({ id: "FN-MIX2", title: "mixed fail", integrationSha: "sha123", log: [] });
|
||||
taskStore._setTask(landedTask("FN-MIX2", "mixed fail"));
|
||||
judgePass(["CA-static", "CA-behav"]);
|
||||
|
||||
loop = new MissionExecutionLoop({ taskStore: taskStore as any, missionStore: missionStore as any, rootDir: "/tmp", verificationCapability: { verifyBehavioralAssertion: verify } });
|
||||
@@ -401,6 +434,7 @@ describe("Validator behavioral posture (U2 + U3)", () => {
|
||||
});
|
||||
|
||||
it("U6/R6: failed verification passes the observed-vs-expected reason to the Fix Feature", async () => {
|
||||
proveLandedInspection();
|
||||
const verify = vi.fn(async (req): Promise<VerificationOutcome> => ({
|
||||
verdict: "fail",
|
||||
assertionId: req.assertionId,
|
||||
@@ -410,7 +444,7 @@ describe("Validator behavioral posture (U2 + U3)", () => {
|
||||
const feature = createMockFeature({ loopState: "implementing", taskId: "FN-R6", status: "in-progress" });
|
||||
missionStore._setFeature(feature);
|
||||
missionStore._setAssertions("F-001", [assertionRow({ id: "CA-1", type: "behavioral" })]);
|
||||
taskStore._setTask({ id: "FN-R6", title: "reason", integrationSha: "sha123", log: [] });
|
||||
taskStore._setTask(landedTask("FN-R6", "reason"));
|
||||
judgePass(["CA-1"]);
|
||||
|
||||
loop = new MissionExecutionLoop({ taskStore: taskStore as any, missionStore: missionStore as any, rootDir: "/tmp", verificationCapability: { verifyBehavioralAssertion: verify } });
|
||||
@@ -427,11 +461,12 @@ describe("Validator behavioral posture (U2 + U3)", () => {
|
||||
});
|
||||
|
||||
it("U6/R16: a verification FAILURE emits a persisted mission event with outcome=fail", async () => {
|
||||
proveLandedInspection();
|
||||
const verify = vi.fn(async (req): Promise<VerificationOutcome> => ({ verdict: "fail", assertionId: req.assertionId, reason: "defect still reproduces" }));
|
||||
const feature = createMockFeature({ loopState: "implementing", taskId: "FN-EVT-F", status: "in-progress" });
|
||||
missionStore._setFeature(feature);
|
||||
missionStore._setAssertions("F-001", [assertionRow({ id: "CA-1", type: "behavioral" })]);
|
||||
taskStore._setTask({ id: "FN-EVT-F", title: "evt fail", integrationSha: "sha123", log: [] });
|
||||
taskStore._setTask(landedTask("FN-EVT-F", "evt fail"));
|
||||
judgePass(["CA-1"]);
|
||||
|
||||
loop = new MissionExecutionLoop({ taskStore: taskStore as any, missionStore: missionStore as any, rootDir: "/tmp", verificationCapability: { verifyBehavioralAssertion: verify } });
|
||||
@@ -447,11 +482,12 @@ describe("Validator behavioral posture (U2 + U3)", () => {
|
||||
});
|
||||
|
||||
it("U6/R16+R21: an INCONCLUSIVE verdict emits a distinguishable infra-failure event and no Fix Feature", async () => {
|
||||
proveLandedInspection();
|
||||
const verify = vi.fn(async (req): Promise<VerificationOutcome> => ({ verdict: "inconclusive", assertionId: req.assertionId, reason: "no isolating sandbox backend" }));
|
||||
const feature = createMockFeature({ loopState: "implementing", taskId: "FN-EVT-INC", status: "in-progress" });
|
||||
missionStore._setFeature(feature);
|
||||
missionStore._setAssertions("F-001", [assertionRow({ id: "CA-1", type: "behavioral" })]);
|
||||
taskStore._setTask({ id: "FN-EVT-INC", title: "evt inconclusive", integrationSha: "sha123", log: [] });
|
||||
taskStore._setTask(landedTask("FN-EVT-INC", "evt inconclusive"));
|
||||
judgePass(["CA-1"]);
|
||||
|
||||
loop = new MissionExecutionLoop({ taskStore: taskStore as any, missionStore: missionStore as any, rootDir: "/tmp", verificationCapability: { verifyBehavioralAssertion: verify } });
|
||||
@@ -478,11 +514,12 @@ describe("Validator behavioral posture (U2 + U3)", () => {
|
||||
});
|
||||
|
||||
it("U6/R16: a swallowed Fix-Feature triage error is durably recorded, not silent", async () => {
|
||||
proveLandedInspection();
|
||||
const verify = vi.fn(async (req): Promise<VerificationOutcome> => ({ verdict: "fail", assertionId: req.assertionId, reason: "defect still reproduces" }));
|
||||
const feature = createMockFeature({ loopState: "implementing", taskId: "FN-TRIAGE", status: "in-progress" });
|
||||
missionStore._setFeature(feature);
|
||||
missionStore._setAssertions("F-001", [assertionRow({ id: "CA-1", type: "behavioral" })]);
|
||||
taskStore._setTask({ id: "FN-TRIAGE", title: "triage fail", integrationSha: "sha123", log: [] });
|
||||
taskStore._setTask(landedTask("FN-TRIAGE", "triage fail"));
|
||||
judgePass(["CA-1"]);
|
||||
// Make triage throw so the swallow path is exercised.
|
||||
missionStore.triageFeature = vi.fn(async () => { throw new Error("triage boom"); }) as any;
|
||||
|
||||
@@ -4,12 +4,21 @@
|
||||
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const { mockPiLog } = vi.hoisted(() => ({
|
||||
/*
|
||||
FNXC:EngineTests 2026-07-18-04:35:
|
||||
Hoist mock filesystem state used inside vi.mock factories. Without vi.hoisted,
|
||||
existsSync runs during module import (schema-applier path resolution) before
|
||||
const mockFiles initializes and throws TDZ "Cannot access before initialization".
|
||||
*/
|
||||
const { mockPiLog, mockFiles, mockDirs, mockDirCounter } = vi.hoisted(() => ({
|
||||
mockPiLog: {
|
||||
log: vi.fn(),
|
||||
warn: vi.fn(),
|
||||
error: vi.fn(),
|
||||
},
|
||||
mockFiles: new Map<string, string>(),
|
||||
mockDirs: new Set<string>(),
|
||||
mockDirCounter: { value: 0 },
|
||||
}));
|
||||
|
||||
vi.mock("../logger.js", () => ({
|
||||
@@ -25,18 +34,13 @@ import {
|
||||
|
||||
// ── Mock Setup ───────────────────────────────────────────────────────────────
|
||||
|
||||
// In-memory file system for tests - using a proxy to intercept fs calls
|
||||
const mockFiles = new Map<string, string>();
|
||||
const mockDirs = new Set<string>();
|
||||
let mockDirCounter = 0;
|
||||
|
||||
vi.mock("node:fs", async () => {
|
||||
const actual = await vi.importActual<typeof import("node:fs")>("node:fs");
|
||||
return {
|
||||
...actual,
|
||||
existsSync: (path: unknown) => mockFiles.has(String(path)) || mockDirs.has(String(path)),
|
||||
readFileSync: (path: unknown) => mockFiles.get(String(path)) ?? "{}",
|
||||
mkdtempSync: () => `/tmp/skill-resolver-mock-${++mockDirCounter}`,
|
||||
mkdtempSync: () => `/tmp/skill-resolver-mock-${++mockDirCounter.value}`,
|
||||
writeFileSync: (path: unknown, content: unknown) => mockFiles.set(String(path), String(content)),
|
||||
rmSync: (path: unknown) => {
|
||||
const pathStr = String(path);
|
||||
@@ -50,7 +54,7 @@ vi.mock("node:fs", async () => {
|
||||
// ── Test Helpers ─────────────────────────────────────────────────────────────
|
||||
|
||||
function createMockProjectDir(settings: Record<string, unknown> | null): string {
|
||||
const dir = `/tmp/skill-resolver-mock-${++mockDirCounter}`;
|
||||
const dir = `/tmp/skill-resolver-mock-${++mockDirCounter.value}`;
|
||||
if (settings !== null) {
|
||||
mockDirs.add(`${dir}/.fusion`);
|
||||
mockFiles.set(`${dir}/.fusion/settings.json`, JSON.stringify(settings));
|
||||
@@ -64,18 +68,18 @@ describe("resolveProjectRoot", () => {
|
||||
beforeEach(() => {
|
||||
mockFiles.clear();
|
||||
mockDirs.clear();
|
||||
mockDirCounter = 0;
|
||||
mockDirCounter.value = 0;
|
||||
});
|
||||
|
||||
it("returns cwd directly when cwd contains .fusion", () => {
|
||||
const dir = `/tmp/skill-resolver-mock-${++mockDirCounter}`;
|
||||
const dir = `/tmp/skill-resolver-mock-${++mockDirCounter.value}`;
|
||||
mockDirs.add(`${dir}/.fusion`);
|
||||
|
||||
expect(resolveProjectRoot(dir)).toBe(dir);
|
||||
});
|
||||
|
||||
it("prefers parent repo root for worktree paths when both parent and worktree have .fusion", () => {
|
||||
const projectDir = `/tmp/skill-resolver-mock-${++mockDirCounter}`;
|
||||
const projectDir = `/tmp/skill-resolver-mock-${++mockDirCounter.value}`;
|
||||
const worktreeDir = `${projectDir}/.worktrees/swift-falcon`;
|
||||
mockDirs.add(`${projectDir}/.fusion`);
|
||||
mockDirs.add(`${worktreeDir}/.fusion`);
|
||||
@@ -84,7 +88,7 @@ describe("resolveProjectRoot", () => {
|
||||
});
|
||||
|
||||
it("falls back to legacy walk when parent repo .fusion is missing", () => {
|
||||
const projectDir = `/tmp/skill-resolver-mock-${++mockDirCounter}`;
|
||||
const projectDir = `/tmp/skill-resolver-mock-${++mockDirCounter.value}`;
|
||||
const worktreeDir = `${projectDir}/.worktrees/swift-falcon`;
|
||||
mockDirs.add(`${worktreeDir}/.fusion`);
|
||||
|
||||
@@ -92,7 +96,7 @@ describe("resolveProjectRoot", () => {
|
||||
});
|
||||
|
||||
it("walks up from deeply nested path", () => {
|
||||
const projectDir = `/tmp/skill-resolver-mock-${++mockDirCounter}`;
|
||||
const projectDir = `/tmp/skill-resolver-mock-${++mockDirCounter.value}`;
|
||||
const nestedDir = `${projectDir}/.worktrees/task-branch/src/components`;
|
||||
mockDirs.add(`${projectDir}/.fusion`);
|
||||
|
||||
@@ -100,14 +104,14 @@ describe("resolveProjectRoot", () => {
|
||||
});
|
||||
|
||||
it("returns cwd when no .fusion directory found anywhere", () => {
|
||||
const dir = `/tmp/skill-resolver-mock-${++mockDirCounter}`;
|
||||
const dir = `/tmp/skill-resolver-mock-${++mockDirCounter.value}`;
|
||||
|
||||
// No .fusion set up anywhere
|
||||
expect(resolveProjectRoot(dir)).toBe(dir);
|
||||
});
|
||||
|
||||
it("returns cwd when .fusion is in a sibling directory (not ancestor)", () => {
|
||||
const parentDir = `/tmp/skill-resolver-mock-${++mockDirCounter}`;
|
||||
const parentDir = `/tmp/skill-resolver-mock-${++mockDirCounter.value}`;
|
||||
const dir = `${parentDir}/my-project`;
|
||||
const siblingDir = `${parentDir}/other-project`;
|
||||
mockDirs.add(`${siblingDir}/.fusion`);
|
||||
@@ -121,7 +125,7 @@ describe("resolveSessionSkills", () => {
|
||||
beforeEach(() => {
|
||||
mockFiles.clear();
|
||||
mockDirs.clear();
|
||||
mockDirCounter = 0;
|
||||
mockDirCounter.value = 0;
|
||||
});
|
||||
|
||||
describe("returns filterActive: false when no patterns and no requested names", () => {
|
||||
@@ -385,7 +389,7 @@ describe("resolveSessionSkills", () => {
|
||||
});
|
||||
|
||||
it("resolves project root from worktree path", () => {
|
||||
const projectDir = `/tmp/skill-resolver-mock-${++mockDirCounter}`;
|
||||
const projectDir = `/tmp/skill-resolver-mock-${++mockDirCounter.value}`;
|
||||
const worktreeDir = `${projectDir}/.worktrees/branch-name`;
|
||||
|
||||
// Set up project root with .fusion directory and settings
|
||||
@@ -405,7 +409,7 @@ describe("resolveSessionSkills", () => {
|
||||
});
|
||||
|
||||
it("resolves project root from deeply nested worktree subdirectory", () => {
|
||||
const projectDir = `/tmp/skill-resolver-mock-${++mockDirCounter}`;
|
||||
const projectDir = `/tmp/skill-resolver-mock-${++mockDirCounter.value}`;
|
||||
const worktreeSubdir = `${projectDir}/.worktrees/task-branch/src/components`;
|
||||
|
||||
mockDirs.add(`${projectDir}/.fusion`);
|
||||
@@ -1058,7 +1062,7 @@ describe("createSkillsOverrideFromSelection", () => {
|
||||
describe("end-to-end flow with project settings + agent metadata + discovered skills", () => {
|
||||
beforeEach(() => {
|
||||
mockFiles.clear();
|
||||
mockDirCounter = 0;
|
||||
mockDirCounter.value = 0;
|
||||
});
|
||||
|
||||
it("full end-to-end flow: settings patterns + agent skills produce matching override", () => {
|
||||
|
||||
@@ -978,6 +978,13 @@ vi.mock("../agent-session-helpers.js", async () => {
|
||||
// defined", failing every step-execution test. Neutral undefined return —
|
||||
// no test asserts on thinking level here.
|
||||
resolveExecutorThinkingLevel: vi.fn(() => undefined),
|
||||
/*
|
||||
FNXC:EngineTestDrift 2026-07-18-04:35:
|
||||
FN-7794 / fallback-swap path imports resolveExecutorFallbackThinkingLevel
|
||||
unconditionally. Without it, executeAll fails before customTools are captured
|
||||
and tool-availability tests see an empty tool list.
|
||||
*/
|
||||
resolveExecutorFallbackThinkingLevel: vi.fn(() => undefined),
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@@ -27,11 +27,16 @@ let setupPromise: Promise<void> | null = null;
|
||||
// FNXC:PgTestAuthFix 2026-07-14-07:40:
|
||||
// The inline admin used process.env.USER for the psql -U flag, which is 'runner' on
|
||||
// GitHub Actions (not 'postgres'). Use the PG_TEST_URL_BASE connection string instead.
|
||||
function admin(statement: string): void { execSync(`psql "${process.env.FUSION_PG_TEST_URL_BASE ?? "postgresql://localhost:5432"}/postgres" -v ON_ERROR_STOP=1 -c "${statement}"`, { stdio: "pipe" }); }
|
||||
// FNXC:PgTestAuthFix 2026-07-18-04:45: default credentials match full-suite.yml service container.
|
||||
const PG_TEST_URL_BASE =
|
||||
process.env.FUSION_PG_TEST_URL_BASE ?? "postgresql://postgres:postgres@localhost:5432";
|
||||
function admin(statement: string): void {
|
||||
execSync(`psql "${PG_TEST_URL_BASE}/postgres" -v ON_ERROR_STOP=1 -c "${statement}"`, { stdio: "pipe" });
|
||||
}
|
||||
async function setupPostgres(): Promise<void> {
|
||||
if (connections) return;
|
||||
admin(`DROP DATABASE IF EXISTS ${dbName}`); admin(`CREATE DATABASE ${dbName}`);
|
||||
const url = `${process.env.FUSION_PG_TEST_URL_BASE ?? "postgresql://localhost:5432"}/${dbName}`;
|
||||
const url = `${PG_TEST_URL_BASE}/${dbName}`;
|
||||
const backend: ResolvedBackend = { mode: "external", runtimeUrl: url, migrationUrl: url, migrationUrlOverridden: false };
|
||||
const schema = await createConnectionSetFromUrl(backend, { poolMax: 1, connectTimeoutSeconds: 5 });
|
||||
await applySchemaBaseline(schema.migration); await schema.close();
|
||||
|
||||
@@ -37,15 +37,20 @@ import {
|
||||
pgDescribe,
|
||||
} from "@fusion/test-utils/pg-test-harness";
|
||||
|
||||
/*
|
||||
FNXC:PgTestAuthFix 2026-07-18-04:45:
|
||||
Do not use process.env.USER for psql -U: on GitHub Actions USER is "runner" and
|
||||
the service container only has POSTGRES_USER=postgres. Always admin via
|
||||
FUSION_PG_TEST_URL_BASE (CI sets postgresql://postgres:postgres@localhost:5432).
|
||||
*/
|
||||
const PG_TEST_URL_BASE =
|
||||
process.env.FUSION_PG_TEST_URL_BASE ?? "postgresql://localhost:5432";
|
||||
const PG_USER = process.env.USER ?? "postgres";
|
||||
process.env.FUSION_PG_TEST_URL_BASE ?? "postgresql://postgres:postgres@localhost:5432";
|
||||
|
||||
function adminExec(statement: string): void {
|
||||
// Single short psql DDL call (CREATE/DROP DATABASE can't run in a tx). This
|
||||
// is the same acceptable execSync use as core's data-layer.test.ts.
|
||||
execSync(
|
||||
`psql -h localhost -p 5432 -U ${PG_USER} -d postgres -v ON_ERROR_STOP=1 -c "${statement.replace(/"/g, '\\"')}"`,
|
||||
`psql "${PG_TEST_URL_BASE}/postgres" -v ON_ERROR_STOP=1 -c "${statement.replace(/"/g, '\\"')}"`,
|
||||
{ stdio: "pipe", env: process.env },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7,8 +7,14 @@ import { GraphTaskNode } from "../GraphTaskNode";
|
||||
FNXC:DependencyGraphTests 2026-07-08-13:10:
|
||||
GraphTaskNode renders the REAL TaskCard; TaskCard's RuntimeFallbackBadge calls the dashboard's useToast() hook, and this file has no ToastProvider. Mock useToast (same as the dashboard's own TaskCard.test.tsx) to avoid "useToast must be used within ToastProvider".
|
||||
*/
|
||||
/*
|
||||
FNXC:DependencyGraphTests 2026-07-18-04:35:
|
||||
RuntimeFallbackBadge uses useOptionalToast (soft-fail path). Mock both exports so
|
||||
GraphTaskNode suites stay provider-free.
|
||||
*/
|
||||
vi.mock("@fusion/dashboard/app/hooks/useToast", () => ({
|
||||
useToast: () => ({ addToast: vi.fn(), removeToast: vi.fn(), toasts: [] }),
|
||||
useOptionalToast: () => ({ addToast: vi.fn(), removeToast: vi.fn(), toasts: [] }),
|
||||
}));
|
||||
|
||||
function task(id = "FN-1"): Task {
|
||||
|
||||
@@ -7,8 +7,14 @@ import { GraphTaskNode } from "../GraphTaskNode";
|
||||
FNXC:DependencyGraphTests 2026-07-08-13:10:
|
||||
GraphTaskNode renders the REAL TaskCard (to verify prop pass-through), and TaskCard now renders RuntimeFallbackBadge which calls the dashboard's useToast() hook. This file has no ToastProvider, so mock useToast the same way the dashboard's own TaskCard.test.tsx does to avoid "useToast must be used within ToastProvider".
|
||||
*/
|
||||
/*
|
||||
FNXC:DependencyGraphTests 2026-07-18-04:35:
|
||||
RuntimeFallbackBadge uses useOptionalToast (soft-fail path). Mock both exports so
|
||||
GraphTaskNode suites stay provider-free.
|
||||
*/
|
||||
vi.mock("@fusion/dashboard/app/hooks/useToast", () => ({
|
||||
useToast: () => ({ addToast: vi.fn(), removeToast: vi.fn(), toasts: [] }),
|
||||
useOptionalToast: () => ({ addToast: vi.fn(), removeToast: vi.fn(), toasts: [] }),
|
||||
}));
|
||||
|
||||
function createTask(overrides: Partial<Task> = {}): Task {
|
||||
|
||||
Reference in New Issue
Block a user