Files
fusion/packages/engine/src/__tests__/verification-utils.test.ts
Fusion 9d785a7ed6 feat(FN-3816): add merger self-healing for missing workspace and environmen
Implements merger self-healing with a missing workspace entry detector, bootstrap recovery path, and environment fault retry handling, plus supporting tests for merge error recovery and verification utilities, alongside documentation updates.

Fusion-Task-Id: FN-3816
2026-05-10 23:36:10 -07:00

110 lines
3.7 KiB
TypeScript

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 { detectMissingWorkspaceEntry, 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();
});
});
describe("detectMissingWorkspaceEntry", () => {
it("matches @fusion package names from stderr", () => {
expect(
detectMissingWorkspaceEntry('Error: Failed to resolve entry for package "@fusion/dashboard" from vite'),
).toEqual({ packageName: "@fusion/dashboard" });
});
it("matches @fusion-plugin-examples package names from stderr", () => {
expect(
detectMissingWorkspaceEntry('Failed to resolve entry for package "@fusion-plugin-examples/hermes-runtime"'),
).toEqual({ packageName: "@fusion-plugin-examples/hermes-runtime" });
});
it("returns null for unrelated stderr", () => {
expect(detectMissingWorkspaceEntry("Some different failure output")).toBeNull();
});
it("returns null for truncated messages without closing quote", () => {
expect(detectMissingWorkspaceEntry('Failed to resolve entry for package "@fusion/da')).toBeNull();
});
it("finds matches in stdout when stderr does not contain one", () => {
expect(
detectMissingWorkspaceEntry("no error in stderr", 'Failed to resolve entry for package "@fusion/core" in stdout'),
).toEqual({ packageName: "@fusion/core" });
});
});