import { test, expect, afterEach } from "bun:test"; import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { loadAppConfig, resolveProfile, loadEnv, renderStyles, validateAppConfig, explainAppConfig, } from "../src/index.ts"; afterEach(() => { delete process.env.WRNEXUS_PROFILE; }); test("observability config requires bounded sampling and a valid OTLP endpoint", () => { expect(validateAppConfig({ observability: { sampleRate: Number.NaN } })).toContainEqual( expect.objectContaining({ path: "observability.sampleRate", severity: "error" }), ); expect( validateAppConfig({ observability: { exporter: "otlp", endpoint: "collector:4318" } }), ).toContainEqual(expect.objectContaining({ path: "observability.endpoint", severity: "error" })); expect( validateAppConfig({ observability: { exporter: "otlp", endpoint: "https://collector.example/v1/traces", serviceName: "api", }, }), ).toEqual([]); }); test("resolveProfile: explicit > WRNEXUS_PROFILE > mode default", () => { delete process.env.WRNEXUS_PROFILE; expect(resolveProfile({ mode: "development" })).toBe("development"); expect(resolveProfile({ mode: "production" })).toBe("production"); expect(resolveProfile({ explicit: "uat", mode: "production" })).toBe("uat"); process.env.WRNEXUS_PROFILE = "test"; expect(resolveProfile({ mode: "production" })).toBe("test"); expect(resolveProfile({ explicit: "uat" })).toBe("uat"); // explicit still wins }); test("loadAppConfig deep-merges the active profile and strips `profiles`", async () => { const dir = mkdtempSync(join(tmpdir(), "wrn-cfg-")); writeFileSync( join(dir, "wrnexus.config.mjs"), `export default { port: 3000, db: { driver: "sqlite", url: "file:./dev.db" }, seo: { title: "Base", siteName: "App" }, profiles: { prod: { db: { driver: "postgres", url: "postgres://prod" }, port: 8080 }, uat: { seo: { title: "UAT" } }, }, };`, ); const dev = await loadAppConfig(dir, "development"); expect(dev.db).toEqual({ driver: "sqlite", url: "file:./dev.db" }); expect((dev as { profiles?: unknown }).profiles).toBeUndefined(); const prod = await loadAppConfig(dir, "prod"); expect(prod.db).toEqual({ driver: "postgres", url: "postgres://prod" }); expect(prod.port).toBe(8080); expect(prod.seo).toEqual({ title: "Base", siteName: "App" }); // untouched const uat = await loadAppConfig(dir, "uat"); expect(uat.seo).toEqual({ title: "UAT", siteName: "App" }); // deep-merged expect(uat.db!.driver).toBe("sqlite"); }); test("config layers compose recursively with deterministic application precedence", async () => { const root = mkdtempSync(join(tmpdir(), "wrnexus-layers-")); try { mkdirSync(join(root, "layers", "base"), { recursive: true }); mkdirSync(join(root, "layers", "company"), { recursive: true }); writeFileSync( join(root, "layers", "base", "wrnexus.layer.mjs"), `export default { port: 1000, head: [""], seo: { siteName: "Foundation", title: "Base" }, profiles: { production: { port: 7000 } } }`, ); writeFileSync( join(root, "layers", "company", "wrnexus.layer.mjs"), `export default { extends: ["../base"], head: "", seo: { title: "Company" } }`, ); writeFileSync( join(root, "wrnexus.config.mjs"), `export default { extends: ["./layers/company"], port: 3000, head: [""], seo: { title: "App" }, profiles: { production: { port: 8000 } } }`, ); const config = await loadAppConfig(root, "production"); expect(config.port).toBe(8000); expect((await loadAppConfig(root, "development")).port).toBe(3000); expect(config.seo).toEqual({ siteName: "Foundation", title: "App" }); expect(config.head).toEqual([ "", "", "", ]); expect(config.extends).toBeUndefined(); const explained = await explainAppConfig(root, "production"); expect(explained.sources.filter((source) => source.includes("wrnexus.layer")).length).toBe(2); } finally { rmSync(root, { recursive: true, force: true }); } }); test("config layers resolve packages and reject dependency cycles", async () => { const root = mkdtempSync(join(tmpdir(), "wrnexus-package-layer-")); try { const pkg = join(root, "node_modules", "company-layer"); mkdirSync(pkg, { recursive: true }); writeFileSync(join(root, "package.json"), JSON.stringify({ name: "app" })); writeFileSync( join(pkg, "package.json"), JSON.stringify({ name: "company-layer", wrnexus: { layer: "./foundation.mjs" }, }), ); writeFileSync(join(pkg, "foundation.mjs"), `export default { port: 4400 }`); writeFileSync(join(root, "wrnexus.config.mjs"), `export default { extends: "company-layer" }`); expect((await loadAppConfig(root)).port).toBe(4400); const cycle = join(root, "cycle"); mkdirSync(cycle); writeFileSync(join(cycle, "a.mjs"), `export default { extends: "./b.mjs" }`); writeFileSync(join(cycle, "b.mjs"), `export default { extends: "./a.mjs" }`); writeFileSync(join(cycle, "wrnexus.config.mjs"), `export default { extends: "./a.mjs" }`); await expect(loadAppConfig(cycle)).rejects.toThrow("WRN-CONFIG-LAYER-CYCLE"); } finally { rmSync(root, { recursive: true, force: true }); } }); test("loadEnv layers .env files by precedence; real env always wins", () => { const dir = mkdtempSync(join(tmpdir(), "wrn-env-")); writeFileSync(join(dir, ".env"), 'BASE=1\nSHARED=base\n# a comment\nQUOTED="hi there"\n'); writeFileSync(join(dir, ".env.prod"), "SHARED=prod\nPROD_ONLY=yes\nREALVAR=fromfile\n"); writeFileSync(join(dir, ".env.prod.local"), "SHARED=local\n"); process.env.REALVAR = "real"; // simulate a real environment variable try { const loaded = loadEnv(dir, "prod"); expect(loaded.BASE).toBe("1"); expect(process.env.BASE).toBe("1"); expect(process.env.QUOTED).toBe("hi there"); // quotes stripped expect(process.env.SHARED).toBe("local"); // .env.prod.local beats .env.prod beats .env expect(process.env.PROD_ONLY).toBe("yes"); expect(process.env.REALVAR).toBe("real"); // real env NOT overridden by a file } finally { for (const k of ["BASE", "SHARED", "PROD_ONLY", "QUOTED", "REALVAR"]) delete process.env[k]; } }); test("loadAppConfig loads env before evaluating the config module", async () => { const dir = mkdtempSync(join(tmpdir(), "wrn-config-env-")); writeFileSync(join(dir, ".env"), "DATABASE_URL=postgres://pms-from-env\n"); writeFileSync( join(dir, "wrnexus.config.mjs"), `export default { db: { driver: "postgres", url: process.env.DATABASE_URL }, };`, ); try { delete process.env.DATABASE_URL; const config = await loadAppConfig(dir, "development"); expect(config.db?.url).toBe("postgres://pms-from-env"); } finally { delete process.env.DATABASE_URL; rmSync(dir, { recursive: true, force: true }); } }); test("Google Fonts CSP supports direct fonts and service-worker fetches", async () => { const dir = mkdtempSync(join(tmpdir(), "wrnexus-font-csp-")); try { writeFileSync( join(dir, "wrnexus.config.ts"), `export default { fonts: { google: [{ family: "Inter" }] }, security: { contentSecurityPolicy: { directives: { "connect-src": ["'self'", "https://api.example.com"] } } } };\n`, ); const config = await loadAppConfig(dir, "production"); const directives = config.security?.contentSecurityPolicy === false ? undefined : config.security?.contentSecurityPolicy?.directives; expect(directives?.["style-src"]).toContain("https://fonts.googleapis.com"); expect(directives?.["font-src"]).toContain("https://fonts.gstatic.com"); expect(directives?.["connect-src"]).toEqual([ "'self'", "https://api.example.com", "https://fonts.gstatic.com", ]); } finally { rmSync(dir, { recursive: true, force: true }); } }); test("package-aware styles add relative imports and Tailwind scan sources", async () => { const root = mkdtempSync(join(tmpdir(), "wrnexus-styles-")); try { const appDir = join(root, "app"); const entry = join(appDir, "styles", "global.css"); const packageSource = join(root, "packages", "system", "components"); const packageCss = join(root, "packages", "system", "system.css"); mkdirSync(join(appDir, "styles"), { recursive: true }); mkdirSync(packageSource, { recursive: true }); writeFileSync(entry, '@import "tailwindcss";\n'); writeFileSync(packageCss, ".system{}\n"); let wrapper = ""; const css = await renderStyles( { entryPath: entry, appDir, appRoot: root, mode: "development", sources: [packageSource], entries: [packageCss], }, { process: async ({ entryPath, originalEntryPath, sources }) => { expect(originalEntryPath).toBe(entry); expect(sources).toEqual([packageSource]); wrapper = readFileSync(entryPath!, "utf8"); return ".compiled{}"; }, }, ); expect(css).toBe(".compiled{}"); expect(wrapper).toContain('@import "../../app/styles/global.css";'); expect(wrapper).toContain('@source "../../packages/system/components";'); expect(wrapper).not.toContain(root.replace(/\\/g, "/")); } finally { rmSync(root, { recursive: true, force: true }); } }); test("production style processors fail closed by default", async () => { const root = mkdtempSync(join(tmpdir(), "wrnexus-styles-")); try { const entry = join(root, "global.css"); writeFileSync(entry, "body{}\n"); await expect( renderStyles( { entryPath: entry, appDir: root, appRoot: root, mode: "production" }, { process: () => { throw new Error("processor unavailable"); }, }, ), ).rejects.toThrow("Stylesheet processing failed"); } finally { rmSync(root, { recursive: true, force: true }); } }); test("package CSS entries build without an application stylesheet", async () => { const root = mkdtempSync(join(tmpdir(), "wrnexus-package-styles-")); try { const packageCss = join(root, "packages", "system", "system.css"); mkdirSync(join(root, "packages", "system"), { recursive: true }); writeFileSync(packageCss, ".system{}\n"); let wrapper = ""; const css = await renderStyles( { entryPath: null, appDir: join(root, "app"), appRoot: root, mode: "development", entries: [packageCss], }, { process: async ({ entryPath, originalEntryPath }) => { expect(originalEntryPath).toBeNull(); wrapper = readFileSync(entryPath!, "utf8"); return ".package-compiled{}"; }, }, ); expect(css).toBe(".package-compiled{}"); expect(wrapper).toContain('@import "../../packages/system/system.css";'); } finally { rmSync(root, { recursive: true, force: true }); } });