/** Disposable inline fixtures only: never require a built payload or workflow artifacts. */ import assert from "node:assert/strict"; import { spawnSync } from "node:child_process"; import * as fs from "node:fs"; import * as os from "node:os"; import * as path from "node:path"; import { describe, it } from "node:test"; import { fileURLToPath } from "node:url"; import { resolveSmokeOptions, validateSmokeOptions } from "../scripts/smoke-server.mjs"; const script = fileURLToPath(new URL("../scripts/smoke-server.mjs", import.meta.url)); const checkout = fileURLToPath(new URL("..", import.meta.url)); const run = (args: string[]) => spawnSync(process.execPath, [script, ...args], { encoding: "utf8", timeout: 10_000 }); function withFixture(test: (root: string) => void) { const root = fs.mkdtempSync(path.join(os.tmpdir(), "lh-server-options-test-")); try { test(root); } finally { fs.rmSync(root, { recursive: true, force: true }); } } function fails(args: string[], message: RegExp) { const result = run(args); assert.equal(result.error, undefined); assert.equal(result.status, 1, result.stdout + result.stderr); assert.match(result.stderr, message); assert.doesNotMatch(result.stdout, /boot ok|smoke passed|import ok/); } describe("standalone server smoke options", () => { it("preserves the checkout staging default and host runtime without booting on import", () => { const options = resolveSmokeOptions(); assert.equal(options.standalone, path.join(checkout, ".next", "standalone")); assert.equal(options.server, path.join(options.standalone, "server.js")); assert.equal(options.runtime, process.execPath); }); it("resolves explicit relative paths against the caller and accepts an explicit root", () => { const relative = resolveSmokeOptions(["--standalone", "payload", "--runtime", "bin/runtime"]); assert.equal(relative.standalone, path.resolve("payload")); assert.equal(relative.runtime, path.resolve("bin/runtime")); withFixture((root) => { fs.writeFileSync(path.join(root, "server.js"), "// fixture; never booted"); const options = resolveSmokeOptions(["--runtime", process.execPath, "--standalone", root]); assert.equal(options.server, path.join(root, "server.js")); assert.doesNotThrow(() => validateSmokeOptions(options)); }); }); for (const option of ["--standalone", "--runtime"]) { for (const suffix of [[], [""], ["--electron"]]) { it(`rejects missing or malformed ${option} value ${JSON.stringify(suffix)}`, () => { fails([option, ...suffix], new RegExp(`${option} requires a path`)); }); } } it("rejects unknown flags and conflicting runtime selectors", () => { fails(["--unknown"], /Unknown option/); fails(["--electron", "--runtime", process.execPath], /Use either --electron or --runtime/); }); it("retains the legacy Electron runtime selector", () => { assert.match(resolveSmokeOptions(["--electron"]).runtime, /electron/); }); it("fails early for nonexistent and non-directory roots", () => { withFixture((root) => { fails(["--standalone", path.join(root, "absent")], /Standalone directory not found/); const file = path.join(root, "file"); fs.writeFileSync(file, "fixture"); fails(["--standalone", file], /Standalone directory not found/); }); }); it("fails early for missing or non-file server entries", () => { withFixture((root) => { fails(["--standalone", root], /Standalone server not found/); fs.mkdirSync(path.join(root, "server.js")); fails(["--standalone", root], /Standalone server not found/); }); }); it("fails early for missing, non-file and non-executable runtimes", () => { withFixture((root) => { fs.writeFileSync(path.join(root, "server.js"), "// fixture"); fails(["--standalone", root, "--runtime", path.join(root, "missing")], /Runtime not found/); fails(["--standalone", root, "--runtime", root], /Runtime not found/); const runtime = path.join(root, "runtime"); fs.writeFileSync(runtime, "fixture", { mode: 0o600 }); fails(["--standalone", root, "--runtime", runtime], /EACCES/); }); }); for (const relative of ["auth.json", ".env", ".env.local", ".env.production", ".env.production.local", ".learnharness", "data"]) { it(`rejects leaked credential/data path ${relative} before dependency probes`, () => { withFixture((root) => { fs.writeFileSync(path.join(root, "server.js"), "// fixture"); fs.writeFileSync(path.join(root, relative), "not-a-real-credential"); fails(["--standalone", root], /Credential\/data path leaked into standalone/); }); }); } });