// SPDX-License-Identifier: LicenseRef-ConusAI-Template-1.0 // Run the real dev entry point with synthetic build/database/platform dependencies. import { type ChildProcess, spawn } from "node:child_process" import { chmodSync, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, symlinkSync, writeFileSync, } from "node:fs" import { tmpdir } from "node:os" import { join } from "node:path" import { fileURLToPath } from "node:url" import { afterEach, expect, it } from "vitest" const source = readFileSync(fileURLToPath(new URL("../scripts/dev.mjs", import.meta.url)), "utf8") let directory: string let parent: ChildProcess | undefined const owned = new Set() const alive = (pid: number) => { try { process.kill(pid, 0) return true } catch { return false } } const delay = () => new Promise((resolve) => setTimeout(resolve, 20)) async function until(check: () => boolean, milliseconds = 5_000) { const deadline = Date.now() + milliseconds while (!check()) { if (Date.now() > deadline) throw new Error("synthetic dev process did not reach the expected state") await delay() } } afterEach(async () => { parent?.kill("SIGKILL") for (const pid of owned) { try { process.kill(pid, "SIGKILL") } catch { // Only synthetic task-owned children; a completed child may already be gone. } } owned.clear() if (directory) rmSync(directory, { recursive: true, force: true }) }) function boot( stage: "migrate" | "seed", outcome: "wait" | "fail" | "signal" | "missing" | "stubborn" | "orphan" = "wait", probeError?: "EPERM" | "EIO", ) { directory = mkdtempSync(join(tmpdir(), "app-dev-shutdown-")) for (const folder of ["scripts", "bin", "apps/worker/dist"]) mkdirSync(join(directory, folder), { recursive: true }) // Only substitute the external platform dependency; all process management is unchanged. writeFileSync( join(directory, "scripts/dev.mjs"), source.replace('"@conusai-kit/testing/fake-platform"', '"./platform.mjs"'), ) writeFileSync( join(directory, "scripts/platform.mjs"), ` import { writeFileSync } from "node:fs" export const fakeNativeServices = () => ({ deployment: {} }) export const validReply = () => ({}) export const startFakePlatform = async () => ({ url: "synthetic", ai: { respond() {} }, async close() { writeFileSync(${JSON.stringify(join(directory, "closed"))}, "closed") } }) `, ) const pnpm = join(directory, "bin/pnpm") writeFileSync(pnpm, `#!${process.execPath}\nprocess.exit(0)\n`) chmodSync(pnpm, 0o755) if (outcome !== "missing") symlinkSync(process.execPath, join(directory, "bin/node")) const pidFile = join(directory, "child.pid") const grandchildFile = join(directory, "grandchild.pid") const onTerm = outcome === "stubborn" ? "() => {}" : `() => setTimeout(() => { require("node:fs").writeFileSync(${JSON.stringify(join(directory, "child-ended"))}, "ended") process.exit(0) }, 30)` const grandchild = ` ${outcome === "stubborn" || outcome === "orphan" ? 'process.on("SIGTERM", () => {});' : ""} require("node:fs").writeFileSync(${JSON.stringify(grandchildFile)}, String(process.pid)) setInterval(() => {}, 1000) ` const waiting = ` const { writeFileSync } = require("node:fs") const { spawn } = require("node:child_process") process.on("SIGTERM", ${onTerm}) writeFileSync(${JSON.stringify(pidFile)}, String(process.pid)) spawn(process.execPath, ["-e", ${JSON.stringify(grandchild)}], { stdio: "inherit" }) setInterval(() => {}, 1000) ` const body = outcome === "fail" ? "process.exit(7)" : outcome === "signal" ? 'process.kill(process.pid, "SIGTERM")' : waiting writeFileSync(join(directory, "apps/worker/dist/migrate.js"), stage === "migrate" ? body : "process.exit(0)") writeFileSync( join(directory, "apps/worker/dist/seed-main.js"), `require("node:fs").writeFileSync(${JSON.stringify(join(directory, "seed-started"))}, "started");\n${body}`, ) const probeLog = join(directory, "probes") const preload = join(directory, "probe.mjs") if (probeError) { // Inject only an existence-probe error for a group the actual entry point has signalled. // Real spawning, TERM delivery, child termination and the production wait bounds remain intact. writeFileSync( preload, ` import { appendFileSync } from "node:fs" const kill = process.kill.bind(process) let group let denied = 0 process.kill = (pid, signal) => { if (pid === group && signal === 0 && denied < 3) { denied += 1 appendFileSync(${JSON.stringify(probeLog)}, ${JSON.stringify(`${probeError}\n`)}) throw Object.assign(new Error("synthetic existence probe failure"), { code: ${JSON.stringify(probeError)} }) } const result = kill(pid, signal) if (pid < 0 && signal === "SIGTERM") group = pid return result } `, ) } const args = [...(probeError ? ["--import", preload] : []), join(directory, "scripts/dev.mjs")] const child = spawn(process.execPath, args, { env: { ...process.env, PATH: join(directory, "bin"), POSTGRES_URL: "synthetic", POSTGRES_READER_URL: "synthetic", POSTGRES_MIGRATE_URL: "synthetic", }, stdio: ["ignore", "ignore", "pipe"], }) let stderr = "" child.stderr?.on("data", (chunk) => { stderr += String(chunk) }) parent = child const exited = new Promise((resolve, reject) => { child.once("exit", resolve) child.once("error", reject) }) return { child, exited, pidFile, grandchildFile, probeLog, stderr: () => stderr } } it.each([ ["migrate", "SIGTERM"], ["seed", "SIGTERM"], ["migrate", "SIGINT"], ["seed", "SIGINT"], ] as const)("%s cancellation by %s stops its child and descendants before further startup", async (stage, signal) => { const { child, exited, pidFile, grandchildFile } = boot(stage) await until(() => existsSync(pidFile) && existsSync(grandchildFile)) const pids = [pidFile, grandchildFile].map((file) => Number(readFileSync(file, "utf8"))) for (const pid of pids) owned.add(pid) child.kill(signal) expect(await exited).toBe(0) expect(existsSync(join(directory, "child-ended"))).toBe(true) await until(() => pids.every((pid) => !alive(pid))) expect(existsSync(join(directory, "closed"))).toBe(true) if (stage === "migrate") expect(existsSync(join(directory, "seed-started"))).toBe(false) }) it.each(["fail", "signal", "missing"] as const)( "preserves failure handling when the startup child has outcome %s", async (outcome) => { const { exited } = boot("migrate", outcome) expect(await exited).toBe(outcome === "fail" ? 7 : 1) expect(existsSync(join(directory, "seed-started"))).toBe(false) }, ) // Use the actual production grace (35s), not a shortened test-only shutdown policy. it.each(["stubborn", "orphan"] as const)( "escalates cancellation when the startup process is %s", async (outcome) => { const { child, exited, pidFile, grandchildFile } = boot("migrate", outcome) await until(() => existsSync(pidFile) && existsSync(grandchildFile)) const pids = [pidFile, grandchildFile].map((file) => Number(readFileSync(file, "utf8"))) for (const pid of pids) owned.add(pid) const sentinel = spawn(process.execPath, ["-e", "setInterval(() => {}, 1000)"], { detached: true, stdio: "ignore" }) if (sentinel.pid === undefined) throw new Error("synthetic unrelated process did not start") owned.add(sentinel.pid) const began = performance.now() child.kill("SIGTERM") await until(() => child.exitCode !== null || child.signalCode !== null, 42_000) expect(await exited).toBe(0) expect(pids.every((pid) => !alive(pid))).toBe(true) expect(performance.now() - began).toBeGreaterThanOrEqual(35_000) expect(alive(sentinel.pid)).toBe(true) expect(existsSync(join(directory, "seed-started"))).toBe(false) expect(existsSync(join(directory, "closed"))).toBe(true) }, 50_000, ) it.each(["EPERM", "EIO"] as const)("handles existence-probe %s without treating it as absence", async (code) => { const { child, exited, pidFile, grandchildFile, probeLog, stderr } = boot("migrate", "wait", code) await until(() => existsSync(pidFile) && existsSync(grandchildFile)) for (const file of [pidFile, grandchildFile]) owned.add(Number(readFileSync(file, "utf8"))) child.kill("SIGTERM") expect(await exited, stderr()).toBe(code === "EPERM" ? 0 : 1) expect(existsSync(probeLog)).toBe(true) if (code === "EPERM") { // EPERM must keep the group pending, not let cleanup declare it absent immediately. expect(readFileSync(probeLog, "utf8").trim().split("\n")).toEqual(["EPERM", "EPERM", "EPERM"]) expect(existsSync(join(directory, "closed"))).toBe(true) } expect(existsSync(join(directory, "seed-started"))).toBe(false) })