/** * Regression coverage for issue #1519 — image-generation calls were timing * out at a hardcoded 120s with no CLI override. The fix raised the default * and exposed a per-invocation override via `--api-timeout`. * * These tests pin the contract that the constant exports at the new value * and that the `timeoutMs` param is honored by the AbortController path. */ import { describe, test, expect, beforeEach, afterEach } from "bun:test"; import fs from "fs"; import os from "os"; import path from "path"; import { generateVariant } from "../src/variants"; import { DEFAULT_IMAGE_GEN_TIMEOUT_MS } from "../src/constants"; describe("DEFAULT_IMAGE_GEN_TIMEOUT_MS", () => { test("default is 300_000ms (5min) — see issue #1519", () => { expect(DEFAULT_IMAGE_GEN_TIMEOUT_MS).toBe(300_000); }); }); describe("generateVariant timeoutMs override", () => { let tmpDir: string; let outputPath: string; beforeEach(() => { tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "api-timeout-")); outputPath = path.join(tmpDir, "variant.png"); }); afterEach(() => { fs.rmSync(tmpDir, { recursive: true, force: true }); }); test("aborts after explicit timeoutMs when fetch never resolves", async () => { // Stub fetch that waits for the signal to abort, then throws AbortError. const fetchFn = (async (_input: any, init?: any) => { const signal = init?.signal as AbortSignal | undefined; await new Promise((_resolve, reject) => { if (!signal) return; signal.addEventListener("abort", () => { const err: any = new Error("aborted"); err.name = "AbortError"; reject(err); }); }); return new Response("never reached"); }) as typeof globalThis.fetch; const t0 = Date.now(); const result = await generateVariant( "fake-key", "prompt", outputPath, "1024x1024", "high", fetchFn, 200, ); const elapsed = Date.now() - t0; expect(result.success).toBe(false); expect(result.error).toBe("Timeout (200ms)"); // Was aborted by the 200ms timer, not by exponential-backoff retry chain expect(elapsed).toBeLessThan(2_000); }); test("default timeoutMs is the shared constant when omitted", async () => { // 1x1 transparent PNG, base64 const TINY_PNG_BASE64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkAAIAAAoAAv/lxKUAAAAASUVORK5CYII="; const fetchFn = (async () => new Response( JSON.stringify({ output: [{ type: "image_generation_call", result: TINY_PNG_BASE64 }], }), { status: 200, headers: { "Content-Type": "application/json" } }, )) as typeof globalThis.fetch; // Should succeed using the default timeout — fetch resolves instantly here, // so the timeoutMs value only matters for the AbortController setup not firing. const result = await generateVariant( "fake-key", "prompt", outputPath, "1024x1024", "high", fetchFn, ); expect(result.success).toBe(true); }); });