import { describe, expect, it } from "@suaveplan/testing/runner"; import { SERVER_KEYS_CODES, ServerKeysError, } from "../error-codes/error-codes.js"; import { runFunction } from "./run-function.js"; interface CapturedLogger { info: (m: string) => void; error: (m: string) => void; out: string[]; } function makeLogger(): CapturedLogger { const out: string[] = []; return { out, info: (m: string) => out.push(`I:${m}`), error: (m: string) => out.push(`E:${m}`), }; } function neverExit(exitCodes: number[]) { return (code: number) => { exitCodes.push(code); }; } describe("runFunction", () => { it("returns the body's value on success and increments OTel without printing an error", { timeout: 5000, }, async () => { const logger = makeLogger(); const exitCodes: number[] = []; const result = await runFunction("ok-cmd", async () => "done", { logger, exit: neverExit(exitCodes), }); expect(result).toBe("done"); expect(exitCodes).toHaveLength(0); expect(logger.out.filter((line) => line.startsWith("E:"))).toHaveLength( 0 ); }); it("wraps a ServerKeysError, exits 1, prints structured stderr, then re-throws", { timeout: 5000, }, async () => { const logger = makeLogger(); const exitCodes: number[] = []; await expect( runFunction( "fail-cmd", async () => { throw new ServerKeysError( SERVER_KEYS_CODES.VAULT_NOT_FOUND, "missing" ); }, { logger, exit: neverExit(exitCodes) } ) ).rejects.toBeInstanceOf(ServerKeysError); expect(exitCodes).toEqual([1]); expect(logger.out[0]).toBe( "E:[fail-cmd] SERVER_KEYS.VAULT_NOT_FOUND: missing" ); }); it("normalises a non-ServerKeysError Error throw into SERVER_KEYS.UNKNOWN_COMMAND", { timeout: 5000, }, async () => { const logger = makeLogger(); const exitCodes: number[] = []; await expect( runFunction( "unk-cmd", async () => { throw new Error("boom"); }, { logger, exit: neverExit(exitCodes) } ) ).rejects.toMatchObject({ code: SERVER_KEYS_CODES.UNKNOWN_COMMAND, }); expect(exitCodes).toEqual([1]); expect(logger.out[0]).toContain( "SERVER_KEYS.UNKNOWN_COMMAND: unk-cmd failed: boom" ); }); it("wraps non-Error throws with String() in the message", { timeout: 5000, }, async () => { const logger = makeLogger(); const exitCodes: number[] = []; await expect( runFunction( "str-cmd", async () => { const notErr: unknown = "string-throw"; throw notErr; }, { logger, exit: neverExit(exitCodes) } ) ).rejects.toMatchObject({ code: SERVER_KEYS_CODES.UNKNOWN_COMMAND, }); expect(logger.out[0]).toContain("string-throw"); }); it("short-circuits with SERVER_KEYS.ABORTED when signal already aborted", { timeout: 5000, }, async () => { const logger = makeLogger(); const exitCodes: number[] = []; const ac = new AbortController(); ac.abort(); let bodyRan = false; await expect( runFunction( "abort-cmd", async () => { bodyRan = true; }, { logger, exit: neverExit(exitCodes), signal: ac.signal, } ) ).rejects.toMatchObject({ code: SERVER_KEYS_CODES.ABORTED }); expect(bodyRan).toBe(false); expect(exitCodes).toEqual([1]); expect(logger.out[0]).toBe( "E:[abort-cmd] aborted: SERVER_KEYS.ABORTED" ); }); it("threads the AbortSignal into the body when not pre-aborted", { timeout: 5000, }, async () => { const logger = makeLogger(); const exitCodes: number[] = []; const ac = new AbortController(); const received = await runFunction( "thread-cmd", async (signal) => signal, { logger, exit: neverExit(exitCodes), signal: ac.signal } ); expect(received).toBe(ac.signal); }); it("default consoleLogger writes the success banner via process.stdout", { timeout: 5000, }, async () => { const stdoutWrites: string[] = []; const origStdoutWrite = process.stdout.write.bind(process.stdout); try { (process.stdout as { write: unknown }).write = ( chunk: string | Uint8Array ): boolean => { stdoutWrites.push( typeof chunk === "string" ? chunk : String(chunk) ); return true; }; const result = await runFunction("default-cmd", async () => 42); expect(result).toBe(42); } finally { (process.stdout as { write: unknown }).write = origStdoutWrite; } expect(stdoutWrites.join("")).toContain("[default-cmd] ok"); }); it("default consoleLogger writes via process.stderr and default exit calls process.exit on the failure path", { timeout: 5000, }, async () => { const stderrWrites: string[] = []; const stdoutWrites: string[] = []; const exitCalls: number[] = []; const origStderrWrite = process.stderr.write.bind(process.stderr); const origStdoutWrite = process.stdout.write.bind(process.stdout); const origExit = process.exit.bind(process); try { (process.stderr as { write: unknown }).write = ( chunk: string | Uint8Array ): boolean => { stderrWrites.push( typeof chunk === "string" ? chunk : String(chunk) ); return true; }; (process.stdout as { write: unknown }).write = ( chunk: string | Uint8Array ): boolean => { stdoutWrites.push( typeof chunk === "string" ? chunk : String(chunk) ); return true; }; (process as { exit: unknown }).exit = ( code?: number | string | null ): never => { exitCalls.push(typeof code === "number" ? code : -1); // Don't actually exit — let the wrapper re-throw. return undefined as never; }; await expect( runFunction("defaults-fail-cmd", async () => { throw new ServerKeysError( SERVER_KEYS_CODES.VAULT_NOT_FOUND, "no defaults" ); }) ).rejects.toBeInstanceOf(ServerKeysError); } finally { (process.stderr as { write: unknown }).write = origStderrWrite; (process.stdout as { write: unknown }).write = origStdoutWrite; (process as { exit: unknown }).exit = origExit; } expect(exitCalls).toEqual([1]); expect(stderrWrites.join("")).toContain( "[defaults-fail-cmd] SERVER_KEYS.VAULT_NOT_FOUND: no defaults" ); }); it("default consoleLogger writes via process.stderr on the pre-aborted path", { timeout: 5000, }, async () => { const stderrWrites: string[] = []; const exitCalls: number[] = []; const origStderrWrite = process.stderr.write.bind(process.stderr); const origExit = process.exit.bind(process); const ac = new AbortController(); ac.abort(); try { (process.stderr as { write: unknown }).write = ( chunk: string | Uint8Array ): boolean => { stderrWrites.push( typeof chunk === "string" ? chunk : String(chunk) ); return true; }; (process as { exit: unknown }).exit = ( code?: number | string | null ): never => { exitCalls.push(typeof code === "number" ? code : -1); return undefined as never; }; await expect( runFunction("defaults-abort-cmd", async () => {}, { signal: ac.signal, }) ).rejects.toMatchObject({ code: SERVER_KEYS_CODES.ABORTED }); } finally { (process.stderr as { write: unknown }).write = origStderrWrite; (process as { exit: unknown }).exit = origExit; } expect(exitCalls).toEqual([1]); expect(stderrWrites.join("")).toContain( "[defaults-abort-cmd] aborted: SERVER_KEYS.ABORTED" ); }); });