feat(FN-2606): merge fusion/fn-2606 (auto-resolved)

- fix(FN-2606): complete Step 4 — clean up mkdtemp test directories
- test(FN-2606): complete Step 3 — add isolation guard coverage
- feat(FN-2606): complete Step 2 — add engine/dashboard HOME test isolation setup
This commit is contained in:
Fusion
2026-04-26 13:00:56 -07:00
committed by gsxdsm
parent 63914028e1
commit caa773cb95
11 changed files with 149 additions and 27 deletions

View File

@@ -1,9 +1,9 @@
// @vitest-environment node
import { mkdtemp, mkdir, writeFile } from "node:fs/promises";
import { mkdtemp, mkdir, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { dirname, join } from "node:path";
import { describe, expect, it } from "vitest";
import { afterEach, describe, expect, it } from "vitest";
import {
PRIORITY_SCRIPTS,
detectDevServerCommands,
@@ -23,8 +23,21 @@ async function writePackageJson(
}
describe("devserver-detect", () => {
const tempRoots = new Set<string>();
afterEach(async () => {
await Promise.all(Array.from(tempRoots).map((root) => rm(root, { recursive: true, force: true })));
tempRoots.clear();
});
const createTempRoot = async (prefix: string) => {
const root = await mkdtemp(join(tmpdir(), prefix));
tempRoots.add(root);
return root;
};
it("detects priority scripts from root package.json", async () => {
const root = await mkdtemp(join(tmpdir(), "devserver-detect-"));
const root = await createTempRoot("devserver-detect-");
await writePackageJson(root, {
scripts: {
dev: "vite",
@@ -46,7 +59,7 @@ describe("devserver-detect", () => {
});
it("only returns priority scripts", async () => {
const root = await mkdtemp(join(tmpdir(), "devserver-detect-"));
const root = await createTempRoot("devserver-detect-");
await writePackageJson(root, {
scripts: {
dev: "vite",
@@ -62,7 +75,7 @@ describe("devserver-detect", () => {
});
it("scans nested package.json files in apps/* and packages/*", async () => {
const root = await mkdtemp(join(tmpdir(), "devserver-detect-"));
const root = await createTempRoot("devserver-detect-");
await writePackageJson(root, { scripts: {} });
await writePackageJson(
root,
@@ -82,7 +95,7 @@ describe("devserver-detect", () => {
});
it("sorts results by PRIORITY_SCRIPTS order", async () => {
const root = await mkdtemp(join(tmpdir(), "devserver-detect-"));
const root = await createTempRoot("devserver-detect-");
expect(PRIORITY_SCRIPTS.indexOf("dev")).toBeLessThan(PRIORITY_SCRIPTS.indexOf("serve"));
await writePackageJson(root, {
scripts: {
@@ -99,7 +112,7 @@ describe("devserver-detect", () => {
});
it("returns empty array when scripts is empty", async () => {
const root = await mkdtemp(join(tmpdir(), "devserver-detect-"));
const root = await createTempRoot("devserver-detect-");
await writePackageJson(root, { scripts: {} });
const detected = await detectDevServerCommands(root);
@@ -107,14 +120,14 @@ describe("devserver-detect", () => {
});
it("returns empty array when root package.json is missing", async () => {
const root = await mkdtemp(join(tmpdir(), "devserver-detect-"));
const root = await createTempRoot("devserver-detect-");
const detected = await detectDevServerCommands(root);
expect(detected).toEqual([]);
});
it("returns empty array for malformed package.json without throwing", async () => {
const root = await mkdtemp(join(tmpdir(), "devserver-detect-"));
const root = await createTempRoot("devserver-detect-");
const filePath = join(root, "package.json");
await mkdir(dirname(filePath), { recursive: true });
await writeFile(filePath, '{ "invalid json', "utf-8");
@@ -124,7 +137,7 @@ describe("devserver-detect", () => {
});
it("deeply nested package.json (2+ levels) is NOT scanned", async () => {
const root = await mkdtemp(join(tmpdir(), "devserver-detect-"));
const root = await createTempRoot("devserver-detect-");
await writePackageJson(root, { scripts: {} });
// Create a deeply nested package.json (2 levels deep)
await writePackageJson(
@@ -144,7 +157,7 @@ describe("devserver-detect", () => {
});
it("persists and reloads devserver configs", async () => {
const root = await mkdtemp(join(tmpdir(), "devserver-config-"));
const root = await createTempRoot("devserver-config-");
const configs: DevServerConfig[] = [
{
id: createDevServerId("cfg-1"),

View File

@@ -1,16 +1,28 @@
// @vitest-environment node
import { mkdir, writeFile } from "node:fs/promises";
import { mkdtemp } from "node:fs/promises";
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { dirname, join } from "node:path";
import { describe, expect, it } from "vitest";
import { afterEach, describe, expect, it } from "vitest";
import { loadDevServerConfigs, saveDevServerConfigs } from "../devserver-persistence.js";
import { createDevServerId, type DevServerConfig } from "../devserver-types.js";
describe("devserver-persistence", () => {
const tempRoots = new Set<string>();
afterEach(async () => {
await Promise.all(Array.from(tempRoots).map((root) => rm(root, { recursive: true, force: true })));
tempRoots.clear();
});
const createTempRoot = async (prefix: string) => {
const root = await mkdtemp(join(tmpdir(), prefix));
tempRoots.add(root);
return root;
};
it("saves and reloads multiple dev server configs", async () => {
const root = await mkdtemp(join(tmpdir(), "devserver-persist-"));
const root = await createTempRoot("devserver-persist-");
const configs: DevServerConfig[] = [
{
id: createDevServerId("server-1"),
@@ -46,7 +58,7 @@ describe("devserver-persistence", () => {
});
it("preserves exact config properties on reload", async () => {
const root = await mkdtemp(join(tmpdir(), "devserver-persist-exact-"));
const root = await createTempRoot("devserver-persist-exact-");
const config: DevServerConfig = {
id: createDevServerId("exact-test"),
name: "Exact Config",
@@ -66,14 +78,14 @@ describe("devserver-persistence", () => {
});
it("returns empty array when no configs have been saved", async () => {
const root = await mkdtemp(join(tmpdir(), "devserver-persist-empty-"));
const root = await createTempRoot("devserver-persist-empty-");
const loaded = await loadDevServerConfigs(root);
expect(loaded).toEqual([]);
});
it("handles corrupted devserver.json gracefully", async () => {
const root = await mkdtemp(join(tmpdir(), "devserver-persist-corrupt-"));
const root = await createTempRoot("devserver-persist-corrupt-");
const configPath = join(root, ".fusion", "devserver.json");
await mkdir(dirname(configPath), { recursive: true });
await writeFile(configPath, '{ "invalid json', "utf-8");
@@ -83,7 +95,7 @@ describe("devserver-persistence", () => {
});
it("handles empty devserver.json", async () => {
const root = await mkdtemp(join(tmpdir(), "devserver-persist-empty-file-"));
const root = await createTempRoot("devserver-persist-empty-file-");
const configPath = join(root, ".fusion", "devserver.json");
await mkdir(dirname(configPath), { recursive: true });
await writeFile(configPath, '', "utf-8");
@@ -93,7 +105,7 @@ describe("devserver-persistence", () => {
});
it("handles devserver.json with missing configs array", async () => {
const root = await mkdtemp(join(tmpdir(), "devserver-persist-missing-"));
const root = await createTempRoot("devserver-persist-missing-");
const configPath = join(root, ".fusion", "devserver.json");
await mkdir(dirname(configPath), { recursive: true });
await writeFile(configPath, '{"other": "data"}', "utf-8");
@@ -103,7 +115,7 @@ describe("devserver-persistence", () => {
});
it("persists three or more servers correctly", async () => {
const root = await mkdtemp(join(tmpdir(), "devserver-persist-multi-"));
const root = await createTempRoot("devserver-persist-multi-");
const configs: DevServerConfig[] = [
{ id: createDevServerId("multi-1"), name: "Server 1", command: "npm run dev", cwd: root },
{ id: createDevServerId("multi-2"), name: "Server 2", command: "npm run storybook", cwd: root },
@@ -119,7 +131,7 @@ describe("devserver-persistence", () => {
});
it("filters out invalid configs during load", async () => {
const root = await mkdtemp(join(tmpdir(), "devserver-persist-filter-"));
const root = await createTempRoot("devserver-persist-filter-");
const configPath = join(root, ".fusion", "devserver.json");
await mkdir(dirname(configPath), { recursive: true });
// Write manually with an invalid config (missing required fields)
@@ -144,7 +156,7 @@ describe("devserver-persistence", () => {
});
it("overwrites existing configs when saving", async () => {
const root = await mkdtemp(join(tmpdir(), "devserver-persist-overwrite-"));
const root = await createTempRoot("devserver-persist-overwrite-");
const initialConfigs: DevServerConfig[] = [
{ id: createDevServerId("overwrite-1"), name: "Initial", command: "npm run dev", cwd: root },

View File

@@ -0,0 +1,12 @@
/**
* Global test isolation: prevents dashboard tests from writing to the real ~/.fusion/ directory.
*
* This runs in every Vitest worker before shared setup. By forcing process.env.HOME
* to a fresh temp directory, homedir()-derived paths resolve to isolated locations.
*/
import { mkdtempSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
const tempHome = mkdtempSync(join(tmpdir(), "fn-test-home-"));
process.env.HOME = tempHome;

View File

@@ -0,0 +1,23 @@
import { describe, expect, it } from "vitest";
import { tmpdir } from "node:os";
import { dirname, join, resolve } from "node:path";
import { fileURLToPath } from "node:url";
describe("test isolation guard", () => {
it("overrides HOME to a temp fn-test-home directory", () => {
const home = process.env.HOME;
expect(home).toBeDefined();
expect(home).toContain(tmpdir());
expect(home).toContain("fn-test-home-");
});
it("does not run from the real project root cwd", () => {
const thisFile = fileURLToPath(import.meta.url);
const projectRoot = resolve(dirname(thisFile), "../../../../");
const projectFusionDir = join(projectRoot, ".fusion");
expect(process.cwd()).not.toBe(projectRoot);
expect(process.cwd().startsWith(projectFusionDir)).toBe(false);
});
});

View File

@@ -25,6 +25,7 @@ export default defineConfig({
globals: true,
include: ["app/**/*.test.{ts,tsx}", "src/**/*.test.{ts,tsx}"],
setupFiles: [
"./src/__tests__/setup-test-isolation.ts",
resolve(__dirname, "../core/src/__test-utils__/vitest-setup.ts"),
"./vitest.setup.ts",
],