mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-21 22:52:19 +03:00
Merged — locally validated (60/60 combined image-generation tests, typecheck:core clean, complexity/cognitive/file-size/changelog gates green, small rebaseline for the combined imageRegistry.ts growth).
298 lines
12 KiB
TypeScript
298 lines
12 KiB
TypeScript
import { test } from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import { EventEmitter } from "node:events";
|
|
import { IMAGE_PROVIDERS, parseImageModel, getImageProvider } from "../../open-sse/config/imageRegistry.ts";
|
|
import {
|
|
buildCursorAgentAuthEnv,
|
|
buildCursorAgentImagePrompt,
|
|
CURSOR_AGENT_IMAGE_FORMAT,
|
|
handleCursorAgentImageGeneration,
|
|
isRasterImageBuffer,
|
|
normalizeCursorSeatToken,
|
|
resolveCursorImageModel,
|
|
resolveCursorImageTimeoutMs,
|
|
__resetCursorAgentImageConcurrencyForTests,
|
|
} from "../../open-sse/handlers/imageGeneration/providers/cursorAgentImage.ts";
|
|
|
|
test("cursor is registered in IMAGE_PROVIDERS with cursor-agent-image format", () => {
|
|
const entry = IMAGE_PROVIDERS.cursor;
|
|
assert.ok(entry, "expected IMAGE_PROVIDERS.cursor");
|
|
assert.equal(entry.id, "cursor");
|
|
assert.equal(entry.alias, "cu");
|
|
assert.equal(entry.format, CURSOR_AGENT_IMAGE_FORMAT);
|
|
assert.equal(entry.authType, "oauth");
|
|
assert.equal(entry.authHeader, "bearer");
|
|
assert.ok(entry.models.some((m) => m.id === "auto"));
|
|
assert.deepEqual(getImageProvider("cursor"), entry);
|
|
});
|
|
|
|
test("parseImageModel resolves cursor/auto and cu/auto to the cursor image provider", () => {
|
|
assert.deepEqual(parseImageModel("cursor/auto"), { provider: "cursor", model: "auto" });
|
|
assert.deepEqual(parseImageModel("cu/auto"), { provider: "cursor", model: "auto" });
|
|
});
|
|
|
|
test("normalizeCursorSeatToken strips account:: prefix like CursorExecutor", () => {
|
|
assert.equal(normalizeCursorSeatToken("acct::tok_abc"), "tok_abc");
|
|
assert.equal(normalizeCursorSeatToken(" crsr_live "), "crsr_live");
|
|
assert.equal(normalizeCursorSeatToken("a::b::c"), "b::c");
|
|
});
|
|
|
|
test("buildCursorAgentAuthEnv maps crsr_ to CURSOR_API_KEY and JWTs to CURSOR_AUTH_TOKEN", () => {
|
|
assert.deepEqual(buildCursorAgentAuthEnv("crsr_abc"), { CURSOR_API_KEY: "crsr_abc" });
|
|
assert.deepEqual(buildCursorAgentAuthEnv("user::crsr_abc"), { CURSOR_API_KEY: "crsr_abc" });
|
|
assert.deepEqual(buildCursorAgentAuthEnv("eyJhbGciOi.jwt"), {
|
|
CURSOR_AUTH_TOKEN: "eyJhbGciOi.jwt",
|
|
});
|
|
});
|
|
|
|
test("buildCursorAgentImagePrompt locks the agent to native generateImage + exact out path", () => {
|
|
const prompt = buildCursorAgentImagePrompt("a red cube", "/tmp/out.png", "1024x1024");
|
|
assert.match(prompt, /native image-generation tool/i);
|
|
assert.match(prompt, /Do NOT write code/);
|
|
assert.match(prompt, /a red cube/);
|
|
assert.match(prompt, /1024x1024/);
|
|
assert.match(prompt, /\/tmp\/out\.png/);
|
|
assert.match(prompt, /\bDONE\b/);
|
|
});
|
|
|
|
test("isRasterImageBuffer accepts PNG and JPEG magics", () => {
|
|
const png = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00]);
|
|
const jpeg = Buffer.from([0xff, 0xd8, 0xff, 0xe0]);
|
|
assert.equal(isRasterImageBuffer(png), true);
|
|
assert.equal(isRasterImageBuffer(jpeg), true);
|
|
assert.equal(isRasterImageBuffer(Buffer.from("not-an-image")), false);
|
|
});
|
|
|
|
test("handleCursorAgentImageGeneration rejects empty prompt and missing credentials", async () => {
|
|
__resetCursorAgentImageConcurrencyForTests();
|
|
const noPrompt = await handleCursorAgentImageGeneration({
|
|
model: "auto",
|
|
provider: "cursor",
|
|
providerConfig: { baseUrl: "agent://cursor-agent" },
|
|
body: { prompt: " " },
|
|
credentials: { accessToken: "crsr_x" },
|
|
peerLocality: "loopback",
|
|
});
|
|
assert.equal(noPrompt.success, false);
|
|
assert.equal(noPrompt.status, 400);
|
|
|
|
const noCreds = await handleCursorAgentImageGeneration({
|
|
model: "auto",
|
|
provider: "cursor",
|
|
providerConfig: { baseUrl: "agent://cursor-agent" },
|
|
body: { prompt: "hi" },
|
|
credentials: {},
|
|
peerLocality: "loopback",
|
|
});
|
|
assert.equal(noCreds.success, false);
|
|
assert.equal(noCreds.status, 401);
|
|
});
|
|
|
|
test("handleCursorAgentImageGeneration returns 501 when agentBin path is missing", async () => {
|
|
__resetCursorAgentImageConcurrencyForTests();
|
|
const result = await handleCursorAgentImageGeneration({
|
|
model: "auto",
|
|
provider: "cursor",
|
|
providerConfig: { baseUrl: "agent://cursor-agent" },
|
|
body: { prompt: "a lantern" },
|
|
credentials: {
|
|
accessToken: "crsr_test",
|
|
providerSpecificData: { agentBin: "/nonexistent/cursor-agent-bin" },
|
|
},
|
|
peerLocality: "loopback",
|
|
});
|
|
assert.equal(result.success, false);
|
|
assert.equal(result.status, 501);
|
|
assert.match(String(result.error), /CURSOR_AGENT_BIN|agentBin/i);
|
|
});
|
|
|
|
// ─── Hard Rules #15 + #17: spawn-capable providers must loopback/LAN-gate ───
|
|
|
|
test("handleCursorAgentImageGeneration rejects a non-loopback/non-LAN caller BEFORE spawning", async () => {
|
|
__resetCursorAgentImageConcurrencyForTests();
|
|
let spawnCalled = false;
|
|
const spyingSpawn = (() => {
|
|
spawnCalled = true;
|
|
throw new Error("spawn must never be invoked for a remote caller");
|
|
}) as unknown as typeof import("node:child_process").spawn;
|
|
|
|
const result = await handleCursorAgentImageGeneration({
|
|
model: "auto",
|
|
provider: "cursor",
|
|
providerConfig: { baseUrl: "agent://cursor-agent" },
|
|
body: { prompt: "a lantern in fog" },
|
|
credentials: {
|
|
accessToken: "crsr_test",
|
|
providerSpecificData: { agentBin: process.execPath },
|
|
},
|
|
spawnImpl: spyingSpawn,
|
|
peerLocality: "remote",
|
|
});
|
|
|
|
assert.equal(spawnCalled, false, "spawn must not run for a rejected non-local caller");
|
|
assert.equal(result.success, false);
|
|
assert.equal(result.status, 403);
|
|
assert.match(String(result.error), /localhost|LAN/i);
|
|
});
|
|
|
|
test("handleCursorAgentImageGeneration rejects when peerLocality is missing (fail closed)", async () => {
|
|
__resetCursorAgentImageConcurrencyForTests();
|
|
const result = await handleCursorAgentImageGeneration({
|
|
model: "auto",
|
|
provider: "cursor",
|
|
providerConfig: { baseUrl: "agent://cursor-agent" },
|
|
body: { prompt: "a lantern in fog" },
|
|
credentials: { accessToken: "crsr_test" },
|
|
});
|
|
assert.equal(result.success, false);
|
|
assert.equal(result.status, 403);
|
|
});
|
|
|
|
/**
|
|
* Minimal fake `spawn` that writes a tiny PNG to the out path embedded in the
|
|
* prompt and exits 0 — exercises the success path without a real Cursor Agent.
|
|
*/
|
|
test("handleCursorAgentImageGeneration returns b64_json via injectable spawn", async () => {
|
|
__resetCursorAgentImageConcurrencyForTests();
|
|
const { writeFile, mkdir } = await import("node:fs/promises");
|
|
const path = await import("node:path");
|
|
|
|
const tinyPng = Buffer.from([
|
|
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52,
|
|
]);
|
|
|
|
const fakeSpawn = ((bin: string, args: string[]) => {
|
|
assert.ok(bin, "agent bin required");
|
|
const prompt = args[args.length - 1] || "";
|
|
const marker = "Save the resulting image to exactly this path: ";
|
|
const idx = prompt.indexOf(marker);
|
|
assert.ok(idx >= 0, "prompt must contain out path");
|
|
const after = prompt.slice(idx + marker.length);
|
|
const end = after.indexOf(". When the file exists");
|
|
assert.ok(end > 0, "prompt must end out path before DONE clause");
|
|
const outPath = after.slice(0, end);
|
|
const child = new EventEmitter() as EventEmitter & {
|
|
stdout: EventEmitter;
|
|
stderr: EventEmitter;
|
|
kill: () => void;
|
|
};
|
|
child.stdout = new EventEmitter();
|
|
child.stderr = new EventEmitter();
|
|
child.kill = () => undefined;
|
|
queueMicrotask(async () => {
|
|
await mkdir(path.dirname(outPath), { recursive: true });
|
|
await writeFile(outPath, tinyPng);
|
|
child.emit("close", 0);
|
|
});
|
|
return child;
|
|
}) as unknown as typeof import("node:child_process").spawn;
|
|
|
|
// Use an existing path so the preflight existsSync check passes; spawn is faked.
|
|
const result = await handleCursorAgentImageGeneration({
|
|
model: "auto",
|
|
provider: "cursor",
|
|
providerConfig: { baseUrl: "agent://cursor-agent" },
|
|
body: { prompt: "a lantern in fog", size: "1024x1024", n: 1 },
|
|
credentials: {
|
|
accessToken: "crsr_test",
|
|
providerSpecificData: { agentBin: process.execPath },
|
|
},
|
|
spawnImpl: fakeSpawn,
|
|
peerLocality: "loopback",
|
|
});
|
|
|
|
assert.equal(result.success, true);
|
|
assert.ok(result.data?.data?.[0]?.b64_json);
|
|
assert.equal(result.data.data[0].b64_json, tinyPng.toString("base64"));
|
|
});
|
|
|
|
test("resolveCursorImageModel allows only registry models, clamping everything else to auto", () => {
|
|
// The three ids declared in IMAGE_PROVIDERS.cursor.models pass through verbatim.
|
|
for (const m of IMAGE_PROVIDERS.cursor.models) {
|
|
assert.equal(resolveCursorImageModel(m.id), m.id);
|
|
}
|
|
// Unknown models and (crucially) flag-shaped / injection-y strings fall back to auto.
|
|
assert.equal(resolveCursorImageModel("--dangerously-allow-shell"), "auto");
|
|
assert.equal(resolveCursorImageModel("-p"), "auto");
|
|
assert.equal(resolveCursorImageModel("composer-9"), "auto");
|
|
assert.equal(resolveCursorImageModel(" composer-2 "), "composer-2"); // trimmed, still valid
|
|
assert.equal(resolveCursorImageModel(""), "auto");
|
|
assert.equal(resolveCursorImageModel(undefined), "auto");
|
|
assert.equal(resolveCursorImageModel(42), "auto");
|
|
});
|
|
|
|
/**
|
|
* End-to-end guard: an odd/flag-shaped `model` from the request must never reach
|
|
* the spawned Agent CLI argv — the handler resolves it to "auto" first.
|
|
*/
|
|
test("handleCursorAgentImageGeneration never forwards a flag-shaped model into CLI argv", async () => {
|
|
__resetCursorAgentImageConcurrencyForTests();
|
|
const { writeFile, mkdir } = await import("node:fs/promises");
|
|
const path = await import("node:path");
|
|
|
|
const tinyPng = Buffer.from([
|
|
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52,
|
|
]);
|
|
|
|
let capturedArgs: string[] = [];
|
|
const fakeSpawn = ((_bin: string, args: string[]) => {
|
|
capturedArgs = args;
|
|
const prompt = args[args.length - 1] || "";
|
|
const marker = "Save the resulting image to exactly this path: ";
|
|
const idx = prompt.indexOf(marker);
|
|
const after = prompt.slice(idx + marker.length);
|
|
const outPath = after.slice(0, after.indexOf(". When the file exists"));
|
|
const child = new EventEmitter() as EventEmitter & {
|
|
stdout: EventEmitter;
|
|
stderr: EventEmitter;
|
|
kill: () => void;
|
|
};
|
|
child.stdout = new EventEmitter();
|
|
child.stderr = new EventEmitter();
|
|
child.kill = () => undefined;
|
|
queueMicrotask(async () => {
|
|
await mkdir(path.dirname(outPath), { recursive: true });
|
|
await writeFile(outPath, tinyPng);
|
|
child.emit("close", 0);
|
|
});
|
|
return child;
|
|
}) as unknown as typeof import("node:child_process").spawn;
|
|
|
|
const result = await handleCursorAgentImageGeneration({
|
|
model: "--dangerously-allow-shell", // untrusted, flag-shaped
|
|
provider: "cursor",
|
|
providerConfig: { baseUrl: "agent://cursor-agent" },
|
|
body: { prompt: "a lantern in fog", n: 1 },
|
|
credentials: {
|
|
accessToken: "crsr_test",
|
|
providerSpecificData: { agentBin: process.execPath },
|
|
},
|
|
spawnImpl: fakeSpawn,
|
|
peerLocality: "loopback",
|
|
});
|
|
|
|
assert.equal(result.success, true);
|
|
const modelIdx = capturedArgs.indexOf("--model");
|
|
assert.ok(modelIdx >= 0, "expected --model in the CLI argv");
|
|
assert.equal(capturedArgs[modelIdx + 1], "auto", "flag-shaped model must resolve to auto");
|
|
assert.ok(
|
|
!capturedArgs.includes("--dangerously-allow-shell"),
|
|
"the raw flag-shaped model must not appear anywhere in argv"
|
|
);
|
|
});
|
|
|
|
test("resolveCursorImageTimeoutMs clamps caller timeout_ms to the 300s ceiling", () => {
|
|
const prev = process.env.CURSOR_IMG_TIMEOUT_MS;
|
|
delete process.env.CURSOR_IMG_TIMEOUT_MS; // isolate from any operator default
|
|
try {
|
|
assert.equal(resolveCursorImageTimeoutMs(5_000), 5_000); // under the cap: unchanged
|
|
assert.equal(resolveCursorImageTimeoutMs(300_000), 300_000); // exactly at the cap
|
|
assert.equal(resolveCursorImageTimeoutMs(999_999_999), 300_000); // over the cap: clamped
|
|
assert.equal(resolveCursorImageTimeoutMs(-1), 210_000); // invalid → default fallback
|
|
assert.equal(resolveCursorImageTimeoutMs(undefined), 210_000); // absent → default fallback
|
|
} finally {
|
|
if (prev === undefined) delete process.env.CURSOR_IMG_TIMEOUT_MS;
|
|
else process.env.CURSOR_IMG_TIMEOUT_MS = prev;
|
|
}
|
|
});
|