Files
2026-07-13 13:39:12 +08:00

315 lines
9.8 KiB
TypeScript

import test from "node:test";
import assert from "node:assert/strict";
// Ported from upstream 9router#1195: NVIDIA NIM image generation (FLUX models).
// NVIDIA already exists as a CHAT provider (integrate.api.nvidia.com,
// OpenAI-compatible) — image generation is a distinct host (ai.api.nvidia.com)
// with a native NIM body shape, so it gets its own `nvidia-nim` format/handler
// (handleNvidiaNimImageGeneration) rather than reusing the OpenAI image path.
import { handleImageGeneration } from "../../open-sse/handlers/imageGeneration.ts";
import { getImageProvider } from "../../open-sse/config/imageRegistry.ts";
import {
buildNvidiaNimRequestBody,
normalizeNvidiaNimImages,
} from "../../open-sse/handlers/imageGeneration/providers/nvidiaNim.ts";
test("nvidia is registered as an nvidia-nim image provider with the 4 FLUX models", () => {
const cfg = getImageProvider("nvidia");
assert.ok(cfg, "expected an IMAGE_PROVIDERS entry for nvidia");
assert.equal(cfg.format, "nvidia-nim");
assert.equal(cfg.baseUrl, "https://ai.api.nvidia.com/v1/genai");
assert.equal(cfg.authType, "apikey");
assert.equal(cfg.authHeader, "bearer");
const ids = cfg.models.map((m) => m.id);
assert.deepEqual(ids, [
"black-forest-labs/flux.1-dev",
"black-forest-labs/flux.1-schnell",
"black-forest-labs/flux.1-kontext-dev",
"black-forest-labs/flux.2-klein-4b",
]);
});
test("handleImageGeneration(nvidia/flux.1-schnell): URL construction + minimal body shaping", async () => {
const originalFetch = globalThis.fetch;
let capturedUrl;
let capturedOptions;
globalThis.fetch = async (url, options) => {
capturedUrl = String(url);
capturedOptions = options;
return new Response(
JSON.stringify({ artifacts: [{ base64: "base64nvidia", finishReason: "SUCCESS" }] }),
{ status: 200, headers: { "content-type": "application/json" } }
);
};
try {
const result = await handleImageGeneration({
body: {
model: "nvidia/black-forest-labs/flux.1-schnell",
prompt: "A neon city",
width: 1344,
height: 1024,
seed: 7,
steps: 4,
},
credentials: { apiKey: "nv-token" },
log: null,
});
assert.equal(result.success, true);
assert.equal(capturedUrl, "https://ai.api.nvidia.com/v1/genai/black-forest-labs/flux.1-schnell");
assert.equal(capturedOptions.method, "POST");
assert.equal(capturedOptions.headers.Authorization, "Bearer nv-token");
assert.equal(capturedOptions.headers.Accept, "application/json");
const requestBody = JSON.parse(capturedOptions.body);
assert.deepEqual(requestBody, {
prompt: "A neon city",
width: 1344,
height: 1024,
seed: 7,
steps: 4,
});
assert.equal(result.data.data[0].b64_json, "base64nvidia");
assert.equal(result.data.data[0].finish_reason, "SUCCESS");
} finally {
globalThis.fetch = originalFetch;
}
});
test("handleImageGeneration(nvidia/flux.2-klein-4b): sends edit input image as an array", async () => {
const originalFetch = globalThis.fetch;
globalThis.fetch = async () =>
new Response(JSON.stringify({ artifacts: [{ base64: "base64nvidiaedit" }] }), {
status: 200,
headers: { "content-type": "application/json" },
});
try {
const result = await handleImageGeneration({
body: {
model: "nvidia/black-forest-labs/flux.2-klein-4b",
prompt: "Make the frog wear tiny glasses",
image: "data:image/png;example_id,0",
width: 1024,
height: 1024,
seed: 0,
steps: 4,
},
credentials: { apiKey: "nv-token" },
log: null,
});
assert.equal(result.success, true);
assert.equal(result.data.data[0].b64_json, "base64nvidiaedit");
} finally {
globalThis.fetch = originalFetch;
}
});
test("buildNvidiaNimRequestBody(flux.2-klein-4b): image sent as an array, width/height passthrough", () => {
const req = buildNvidiaNimRequestBody("black-forest-labs/flux.2-klein-4b", {
prompt: "Make the frog wear tiny glasses",
image: "data:image/png;example_id,0",
width: 1024,
height: 1024,
seed: 0,
steps: 4,
});
assert.deepEqual(req, {
prompt: "Make the frog wear tiny glasses",
width: 1024,
height: 1024,
image: ["data:image/png;example_id,0"],
seed: 0,
steps: 4,
});
});
test("buildNvidiaNimRequestBody(flux.1-dev): omits input image in base mode", () => {
const req = buildNvidiaNimRequestBody("black-forest-labs/flux.1-dev", {
prompt: "A simple coffee shop interior",
mode: "base",
image: "data:image/png;example_id,0",
cfg_scale: 1.1,
width: 768,
height: 1344,
seed: 0,
steps: 50,
});
assert.deepEqual(req, {
prompt: "A simple coffee shop interior",
mode: "base",
width: 768,
height: 1344,
cfg_scale: 1.1,
seed: 0,
steps: 50,
});
});
test("buildNvidiaNimRequestBody(flux.1-dev): drops out-of-range dimensions and non-positive cfg_scale", () => {
const req = buildNvidiaNimRequestBody("black-forest-labs/flux.1-dev", {
prompt: "A simple coffee shop interior",
mode: "base",
image: "data:image/png;example_id,0",
cfg_scale: 0,
width: 1792, // out of the 768-1344 range
height: 1024,
seed: 0,
steps: 50,
});
assert.deepEqual(req, {
prompt: "A simple coffee shop interior",
mode: "base",
seed: 0,
steps: 50,
});
assert.ok(!("width" in req) && !("height" in req) && !("cfg_scale" in req));
});
test("buildNvidiaNimRequestBody(flux.1-dev): sends control image as a string for non-base modes", () => {
const req = buildNvidiaNimRequestBody("black-forest-labs/flux.1-dev", {
prompt: "A simple coffee shop interior",
mode: "depth",
image: "data:image/png;example_id,0",
cfg_scale: 3.5,
width: 1024,
height: 1024,
seed: 0,
steps: 50,
});
assert.deepEqual(req, {
prompt: "A simple coffee shop interior",
width: 1024,
height: 1024,
mode: "depth",
image: "data:image/png;example_id,0",
cfg_scale: 3.5,
seed: 0,
steps: 50,
});
});
test("buildNvidiaNimRequestBody(flux.1-kontext-dev): uses aspect_ratio instead of width/height", () => {
const req = buildNvidiaNimRequestBody("black-forest-labs/flux.1-kontext-dev", {
prompt: "Now the mouse is holding pizza instead",
image: "data:image/png;example_id,0",
aspect_ratio: "match_input_image",
width: 1024,
height: 1024,
steps: 30,
cfg_scale: 3.5,
seed: 0,
});
assert.deepEqual(req, {
prompt: "Now the mouse is holding pizza instead",
image: "data:image/png;example_id,0",
aspect_ratio: "match_input_image",
cfg_scale: 3.5,
seed: 0,
steps: 30,
});
});
test("handleImageGeneration(nvidia/flux.1-kontext-dev): requires an input image, does not fetch", async () => {
const originalFetch = globalThis.fetch;
let fetchCalled = false;
globalThis.fetch = async () => {
fetchCalled = true;
throw new Error("fetch should not be called without an input image");
};
try {
const result = await handleImageGeneration({
body: {
model: "nvidia/black-forest-labs/flux.1-kontext-dev",
prompt: "Now the mouse is holding pizza instead",
},
credentials: { apiKey: "nv-token" },
log: null,
});
assert.equal(result.success, false);
assert.equal(result.status, 400);
assert.match(result.error, /requires an input image/i);
assert.equal(fetchCalled, false);
} finally {
globalThis.fetch = originalFetch;
}
});
test("normalizeNvidiaNimImages: accepts artifacts[], images[], data[], and single-value shapes", () => {
assert.deepEqual(normalizeNvidiaNimImages({ artifacts: [{ base64: "a1" }] }).data, [
{ b64_json: "a1" },
]);
assert.deepEqual(normalizeNvidiaNimImages({ images: ["b1", "b2"] }).data, [
{ b64_json: "b1" },
{ b64_json: "b2" },
]);
assert.deepEqual(normalizeNvidiaNimImages({ data: [{ b64_json: "c1" }] }).data, [
{ b64_json: "c1" },
]);
assert.deepEqual(normalizeNvidiaNimImages({ image: "d1" }).data, [{ b64_json: "d1" }]);
assert.deepEqual(normalizeNvidiaNimImages({ result: { image: "e1" } }).data, [
{ b64_json: "e1" },
]);
// Already-OpenAI-shaped responses pass through untouched.
const passthrough = { created: 123, data: [{ b64_json: "f1" }] };
assert.deepEqual(normalizeNvidiaNimImages(passthrough), passthrough);
// Unrecognized shape -> empty data, never throws.
assert.deepEqual(normalizeNvidiaNimImages({ nonsense: true }).data, []);
});
test("handleImageGeneration(nvidia): upstream error body never leaks a stack trace", async () => {
const originalFetch = globalThis.fetch;
globalThis.fetch = async () => {
throw new Error(`boom at /home/user/secret/path/imageGeneration.ts:123:45`);
};
try {
const result = await handleImageGeneration({
body: {
model: "nvidia/black-forest-labs/flux.1-schnell",
prompt: "test",
},
credentials: { apiKey: "nv-token" },
log: null,
});
assert.equal(result.success, false);
assert.equal(result.status, 502);
assert.ok(!result.error.includes("/home/"), "error must not leak an absolute path/stack");
assert.ok(!result.error.includes(":123:45"), "error must not leak a stack trace location");
} finally {
globalThis.fetch = originalFetch;
}
});
test("handleImageGeneration(nvidia): upstream non-2xx response is surfaced with its status", async () => {
const originalFetch = globalThis.fetch;
globalThis.fetch = async () =>
new Response("rate limited", { status: 429, headers: { "content-type": "text/plain" } });
try {
const result = await handleImageGeneration({
body: {
model: "nvidia/black-forest-labs/flux.1-schnell",
prompt: "test",
},
credentials: { apiKey: "nv-token" },
log: null,
});
assert.equal(result.success, false);
assert.equal(result.status, 429);
} finally {
globalThis.fetch = originalFetch;
}
});