Files
OmniRoute/tests/unit/pollinations-image-anon-fallback-8085.test.ts
Diego Rodrigues de Sa e Souza e6087c62b2 fix(sse): anonymous fingerprint fallback for keyless Pollinations image gen (#8085) (#8157)
Pollinations image requests with no configured apiKey/accessToken (the
common free case) were sent with no Authorization header AND no
fingerprint headers, so Pollinations' own upstream legitimately
rejected them with a real 401 even for a valid OmniRoute key. The
chat path already has an anonymous fingerprint-pool fallback
(PollinationsExecutor.execute()'s isAnonymous branch); the image
path never reused it.

Adds open-sse/handlers/imageGeneration/pollinationsAnonAuth.ts,
mirroring the chat executor's anonymous session-pool fallback for
handleOpenAIImageGeneration, and fixes the pre-existing bug where
Authorization was set to the literal string "Bearer undefined"
when no token was configured (now correctly gated by if (token)).

Regression test: tests/unit/pollinations-image-anon-fallback-8085.test.ts
2026-07-22 11:28:12 -03:00

65 lines
2.6 KiB
TypeScript

import test from "node:test";
import assert from "node:assert/strict";
import { mkdtempSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
// Isolated DATA_DIR so this test never touches the real ~/.omniroute DB
// (handleImageGeneration's call-log path opens the shared DB singleton).
process.env.DATA_DIR = mkdtempSync(join(tmpdir(), "omniroute-images-anon-8085-"));
// #8085 — Pollinations image-generation requests with NO configured API key
// (the common free/keyless case) must reuse the same anonymous fingerprint
// session-pool fallback that PollinationsExecutor.execute() already applies
// to the chat path (open-sse/executors/pollinations.ts:52-71). Without it,
// the outbound request to gen.pollinations.ai carries no Authorization AND
// no browser fingerprint, so Pollinations legitimately rejects it with a
// real upstream 401 — even though the caller supplied a perfectly valid
// OmniRoute API key.
const { handleImageGeneration } = await import("../../open-sse/handlers/imageGeneration.ts");
test("#8085 keyless Pollinations image request includes anonymous fingerprint headers (User-Agent) instead of going out bare", async () => {
const originalFetch = globalThis.fetch;
let captured;
globalThis.fetch = async (url, options = {}) => {
captured = {
url: String(url),
headers: options.headers || {},
};
return new Response(
JSON.stringify({ created: 123, data: [{ url: "https://cdn.example.com/image.png" }] }),
{ status: 200, headers: { "content-type": "application/json" } }
);
};
try {
const result = await handleImageGeneration({
body: {
model: "pollinations/flux",
prompt: "a cat",
},
// No apiKey/accessToken — the common keyless/free Pollinations case.
credentials: {},
log: null,
});
assert.equal(result.success, true);
assert.ok(captured, "fetch should have been called");
assert.equal(captured.url, "https://gen.pollinations.ai/v1/images/generations");
// No key was supplied, so no Authorization header is expected — but the
// anonymous fallback must inject fingerprint headers (mirroring the chat
// executor's isAnonymous branch) so the upstream doesn't see a bare,
// headerless request and reject it with a real 401.
assert.equal(captured.headers.Authorization, undefined);
assert.ok(
captured.headers["User-Agent"],
"expected anonymous session-pool fingerprint headers (User-Agent) on the outbound Pollinations image request"
);
} finally {
globalThis.fetch = originalFetch;
}
});