Files
OmniRoute/tests/unit/image-generation-route.test.ts
Diego Rodrigues de Sa e Souza 0cd388efb8 Release v3.7.4 (#1730)
* chore(release): v3.7.4 — version bump, openapi and changelog sync

* fix: preserve previous_response_id and conversation_id fields on empty input array (#1729)

* fix: bypass UI validation block for optional API keys and fix string fallback typing (#1721)

* fix(proxy): disable HTTP keep-alive and pipelining in Undici proxy dispatcher to prevent socket hang up

* feat(proxy): implement bulk proxy import via pipe-delimited parser with update-or-create logic

* docs: update changelog for v3.7.4 fixes and proxy features

* test: update responses store expectations for empty input arrays

* feat(pwa): add fullscreen installable PWA with manifest, service worker, and cross-platform app icons. (#1728)

Integrated into release/v3.7.4

* Fix image provider validation and Stability image requests (#1726)

Integrated into release/v3.7.4

* docs: add PR 1726 and PR 1728 to v3.7.4 changelog

* fix(security): replace insecure Math.random with crypto.getRandomValues for fallback UUID generation

* fix(migrations): intercept 007 migration to use IF NOT EXISTS logic on fresh installs

Fixes #1733

* test: fix typescript compilation errors in unit tests

* fix(db): reconcile legacy reasoning cache migration

* chore(release): bump to v3.7.4 — changelog, docs, version sync

* fix(cc-compatible): preserve Claude Code system skeleton (#1740)

Integrated into release/v3.7.4

* docs(changelog): update for PR #1740 merge

* docs(changelog): include workflow updates

* fix(db): reconcile legacy reasoning cache migration (#1734)

Integrated into release/v3.7.4

* Add endpoint tunnel visibility settings (#1743)

Integrated into release/v3.7.4

* Normalize max reasoning effort for Codex routing (#1744)

Integrated into release/v3.7.4

* Fix Claude Code gateway config helper (#1745)

Integrated into release/v3.7.4

* Refresh CLI fingerprint provider profiles (#1746)

Integrated into release/v3.7.4

* Integrated into release/v3.7.4 (PR #1742)

* docs(changelog): update for PRs 1742-1746

---------

Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
Co-authored-by: Yash Ghule <y.ghule77@gmail.com>
Co-authored-by: backryun <bakryun0718@proton.me>
Co-authored-by: dhaern <manker_lol@hotmail.com>
Co-authored-by: Randi <55005611+rdself@users.noreply.github.com>
Co-authored-by: Duncan L <leungd@gmail.com>
2026-04-28 20:46:25 -03:00

141 lines
4.9 KiB
TypeScript

import test from "node:test";
import assert from "node:assert/strict";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-image-route-"));
process.env.DATA_DIR = TEST_DATA_DIR;
process.env.API_KEY_SECRET = process.env.API_KEY_SECRET || "image-route-test-api-key-secret";
const core = await import("../../src/lib/db/core.ts");
const providersDb = await import("../../src/lib/db/providers.ts");
const apiKeysDb = await import("../../src/lib/db/apiKeys.ts");
const imageRoute = await import("../../src/app/api/v1/images/generations/route.ts");
const imageEditRoute = await import("../../src/app/api/v1/images/edits/route.ts");
const originalFetch = globalThis.fetch;
async function resetStorage() {
globalThis.fetch = originalFetch;
apiKeysDb.resetApiKeyState();
core.resetDbInstance();
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
fs.mkdirSync(TEST_DATA_DIR, { recursive: true });
}
async function seedConnection(provider: string, overrides: { apiKey?: string | null } = {}) {
return providersDb.createProviderConnection({
provider,
authType: "apikey",
name: `${provider}-${Math.random().toString(16).slice(2, 8)}`,
apiKey: overrides.apiKey ?? "test-key",
isActive: true,
testStatus: "active",
providerSpecificData: {},
});
}
test.beforeEach(async () => {
await resetStorage();
});
test.after(() => {
globalThis.fetch = originalFetch;
apiKeysDb.resetApiKeyState();
core.resetDbInstance();
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
});
test("v1 image models GET exposes image-only modalities for image-only models", async () => {
const response = await imageRoute.GET();
const body = (await response.json()) as any;
const byId = new Map(body.data.map((item: { id: string }) => [item.id, item]));
assert.equal(response.status, 200);
assert.deepEqual((byId.get("topaz/topaz-enhance") as any).input_modalities, ["image"]);
assert.deepEqual((byId.get("stability-ai/remove-background") as any).input_modalities, ["image"]);
assert.deepEqual((byId.get("stability-ai/fast") as any).input_modalities, ["image"]);
});
test("v1 image generation POST accepts promptless requests for image-only models", async () => {
await seedConnection("topaz", { apiKey: "topaz-key" });
globalThis.fetch = async (url, options = {}) => {
const stringUrl = String(url);
if (stringUrl === "https://example.com/topaz-input.png") {
return new Response(new Uint8Array([1, 2, 3]), {
status: 200,
headers: { "content-type": "image/png" },
});
}
if (stringUrl === "https://api.topazlabs.com/image/v1/enhance") {
const formData = options.body as FormData;
assert.ok(formData.get("image") instanceof File);
return new Response(new Uint8Array([7, 7, 7]), {
status: 200,
headers: { "content-type": "image/jpeg" },
});
}
throw new Error(`Unexpected URL: ${stringUrl}`);
};
const response = await imageRoute.POST(
new Request("http://localhost/api/v1/images/generations", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
model: "topaz/topaz-enhance",
image_url: "https://example.com/topaz-input.png",
size: "2048x2048",
response_format: "b64_json",
}),
})
);
const body = (await response.json()) as any;
assert.equal(response.status, 200);
assert.equal(body.data[0].b64_json, "BwcH");
});
test("v1 image generation POST still requires prompts for text-input models", async () => {
const response = await imageRoute.POST(
new Request("http://localhost/api/v1/images/generations", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
model: "openai/gpt-image-2",
image_url: "https://example.com/source.png",
}),
})
);
const body = (await response.json()) as any;
assert.equal(response.status, 400);
assert.match(body.error.message, /Prompt is required for image model: openai\/gpt-image-2/);
});
test("v1 image edit POST enforces disabled API key policy", async () => {
const createdKey = await apiKeysDb.createApiKey("Disabled image edit key", "machine-image-edit");
await apiKeysDb.updateApiKeyPermissions(createdKey.id, { isActive: false });
const formData = new FormData();
formData.set("prompt", "make the background lighter");
formData.set("model", "cgpt-web/gpt-5.3-instant");
formData.set("image", new File([new Uint8Array([1, 2, 3])], "source.png", { type: "image/png" }));
const response = await imageEditRoute.POST(
new Request("http://localhost/api/v1/images/edits", {
method: "POST",
headers: { Authorization: `Bearer ${createdKey.key}` },
body: formData,
})
);
const body = (await response.json()) as any;
assert.equal(response.status, 403);
assert.match(body.error.message, /disabled/);
});