import test from "node:test"; import assert from "node:assert/strict"; function inferAspectRatioFromSize(size) { if (typeof size !== "string") return null; const [wRaw, hRaw] = size.split("x"); const width = Number(wRaw); const height = Number(hRaw); if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) return null; const gcd = (a, b) => (b === 0 ? a : gcd(b, a % b)); const div = gcd(Math.round(width), Math.round(height)); return `${Math.round(width / div)}:${Math.round(height / div)}`; } function inferResolutionFromSize(size) { if (typeof size !== "string") return null; const [wRaw, hRaw] = size.split("x"); const width = Number(wRaw); const height = Number(hRaw); if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) return null; const longestSide = Math.max(width, height); if (longestSide <= 1024) return "1K"; if (longestSide <= 2048) return "2K"; return "4K"; } test("nanobanana pro payload inference maps size to aspectRatio and resolution", () => { const aspectRatio = inferAspectRatioFromSize("1024x1280"); const resolution = inferResolutionFromSize("1024x1280"); assert.equal(aspectRatio, "4:5"); assert.equal(resolution, "2K"); }); test("nanobanana async flow (submit->poll->url) normalizes to OpenAI-style url item", async () => { const calls = []; const fetchMock = async (url, options = {}) => { calls.push({ url: String(url), method: options.method || "GET", body: options.body }); if (String(url).includes("/generate")) { return new Response(JSON.stringify({ code: 200, msg: "success", data: { taskId: "t-1" } }), { status: 200, headers: { "content-type": "application/json" }, }); } if (String(url).includes("/record-info")) { const polls = calls.filter((c) => c.url.includes("/record-info")).length; if (polls < 2) { return new Response( JSON.stringify({ code: 200, msg: "success", data: { successFlag: 0 } }), { status: 200, headers: { "content-type": "application/json" }, } ); } return new Response( JSON.stringify({ code: 200, msg: "success", data: { successFlag: 1, response: { resultImageUrl: "https://cdn.example.com/final.jpg" }, }, }), { status: 200, headers: { "content-type": "application/json" }, } ); } throw new Error(`Unexpected URL ${url}`); }; const originalFetch = globalThis.fetch; try { globalThis.fetch = fetchMock; const submit = await fetch("https://api.nanobananaapi.ai/api/v1/nanobanana/generate", { method: "POST", headers: { "Content-Type": "application/json", Authorization: "Bearer test" }, body: JSON.stringify({ prompt: "space" }), }); const submitData = (await submit.json()) as any; const taskId = submitData.data.taskId; let finalData; for (let i = 0; i < 5; i++) { const poll = await fetch( `https://api.nanobananaapi.ai/api/v1/nanobanana/record-info?taskId=${encodeURIComponent(taskId)}` ); const pollData = (await poll.json()) as any; if (pollData.data.successFlag === 1) { finalData = pollData.data; break; } } assert.ok(finalData); assert.equal(finalData.response.resultImageUrl, "https://cdn.example.com/final.jpg"); } finally { globalThis.fetch = originalFetch; } }); test("nanobanana b64 mode can convert result URL bytes to b64_json", async () => { const originalFetch = globalThis.fetch; globalThis.fetch = async (url) => { if (new URL(String(url)).href === "https://cdn.example.com/final.jpg") { return new Response(new Uint8Array([0x89, 0x50, 0x4e, 0x47]), { status: 200 }); } throw new Error(`Unexpected URL ${url}`); }; try { const resp = await fetch("https://cdn.example.com/final.jpg"); const buf = Buffer.from(await resp.arrayBuffer()); const b64 = buf.toString("base64"); assert.equal(b64, "iVBORw=="); } finally { globalThis.fetch = originalFetch; } });