// Smoke test for scripts/local-setup.sh — verifies idempotency and that // the .env upserts preserve unrelated keys. Uses a stubbed `mkcert` on // the runner so no real CA is touched. import { test } from "node:test"; import assert from "node:assert/strict"; import { mkdtempSync, writeFileSync, readFileSync, copyFileSync, mkdirSync, existsSync, chmodSync, cpSync, rmSync } from "node:fs"; import { spawnSync } from "node:child_process"; import { tmpdir } from "node:os"; import path from "node:path"; import { fileURLToPath } from "node:url"; // Resolve the real local-setup.sh from this test file's location so the // test works regardless of pnpm/node cwd. const __dirname = path.dirname(fileURLToPath(import.meta.url)); const LOCAL_SETUP_SH = path.resolve(__dirname, "..", "local-setup.sh"); function runLocalSetup(projectDir, extraEnv = {}) { const stubDir = path.join(projectDir, ".stub-bin"); mkdirSync(stubDir, { recursive: true }); // mkcert stub: writes minimal PEM-looking files so the script's // existence check is satisfied. Also implements `-CAROOT`. const mkcertStub = `#!/usr/bin/env bash set -e if [ "$1" = "-CAROOT" ]; then echo "/tmp/fake-caroot" exit 0 fi cert=""; key="" positional=() while [ $# -gt 0 ]; do case "$1" in -cert-file) cert="$2"; shift 2;; -key-file) key="$2"; shift 2;; *) positional+=("$1"); shift;; esac done san="" for s in "\${positional[@]}"; do if [[ "$s" =~ ^[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+$ ]]; then if [ -z "$san" ]; then san="IP:$s"; else san="$san,IP:$s"; fi else if [ -z "$san" ]; then san="DNS:$s"; else san="$san,DNS:$s"; fi fi done openssl req -x509 -newkey rsa:2048 -nodes -days 1 \\ -subj "/CN=local-test" \\ -addext "subjectAltName=$san" \\ -keyout "$key" -out "$cert" >/dev/null 2>&1 `; const stubPath = path.join(stubDir, "mkcert"); writeFileSync(stubPath, mkcertStub); chmodSync(stubPath, 0o755); return spawnSync("bash", [path.join(projectDir, "scripts", "local-setup.sh")], { cwd: projectDir, env: { ...process.env, PATH: `${stubDir}:${process.env.PATH}`, LOCAL_SETUP_DRY_RUN: "1", LOCAL_SETUP_NONINTERACTIVE: "1", OSTYPE: "linux-gnu", ...extraEnv, }, encoding: "utf8", }); } function makeFakeProject() { const dir = mkdtempSync(path.join(tmpdir(), "tx-localsetup-")); mkdirSync(path.join(dir, "scripts"), { recursive: true }); // Copy the script under test verbatim. copyFileSync( LOCAL_SETUP_SH, path.join(dir, "scripts", "local-setup.sh"), ); chmodSync(path.join(dir, "scripts", "local-setup.sh"), 0o755); // Minimal .env.example so bootstrap works. writeFileSync( path.join(dir, ".env.example"), [ "SESSION_SECRET=change-me", "POSTGRES_PASSWORD=change-me", "SOME_USER_KEY=preserve-me", "LOCAL_DOMAIN=tx.local", "LOCAL_IP=127.0.0.1", "BASE_URL=https://tx.local", "PUBLIC_BASE_URL=https://tx.local", "ALLOWED_ORIGINS=https://tx.local", "HTTPS_MODE=local", "", ].join("\n"), ); return dir; } test("first run bootstraps .env and writes a cert", () => { const dir = makeFakeProject(); try { const r = runLocalSetup(dir); assert.equal(r.status, 0, r.stderr || r.stdout); const env = readFileSync(path.join(dir, ".env"), "utf8"); assert.match(env, /^LOCAL_DOMAIN=tx\.local$/m); assert.match(env, /^BASE_URL=https:\/\/tx\.local$/m); assert.match(env, /^PUBLIC_BASE_URL=https:\/\/tx\.local$/m); assert.match(env, /^SOME_USER_KEY=preserve-me$/m); assert.ok(existsSync(path.join(dir, "certs", "local-cert.pem"))); assert.ok(existsSync(path.join(dir, "certs", "local-key.pem"))); } finally { rmSync(dir, { recursive: true, force: true }); } }); test("second run is a no-op for cert generation and preserves user edits", () => { const dir = makeFakeProject(); try { const first = runLocalSetup(dir); assert.equal(first.status, 0, first.stderr || first.stdout); // Operator edits an unrelated key. let env = readFileSync(path.join(dir, ".env"), "utf8"); env = env.replace(/^SOME_USER_KEY=.*$/m, "SOME_USER_KEY=user-edited"); writeFileSync(path.join(dir, ".env"), env); // Capture cert mtime to verify second run didn't rewrite it. const certPath = path.join(dir, "certs", "local-cert.pem"); const before = readFileSync(certPath); const second = runLocalSetup(dir); assert.equal(second.status, 0, second.stderr || second.stdout); const after = readFileSync(certPath); assert.deepEqual(before, after, "cert was rewritten on idempotent re-run"); const env2 = readFileSync(path.join(dir, ".env"), "utf8"); assert.match(env2, /^SOME_USER_KEY=user-edited$/m); } finally { rmSync(dir, { recursive: true, force: true }); } });