evOo-local/packages/adapters/src/openai-compatible-url.test.ts
cynarAI 6c7eed7580 Initial clean evOo-local from f00577b
Fresh history cut from cynarAI/evoo antigravity-handoff/project-timeline
(Voice First + project timeline). Brand docs/titles as evOo; keep @rakazo/*
packages as tech debt. Excludes node_modules, caches, secrets, voice blobs.
2026-09-20 17:15:16 +02:00

170 lines
7.0 KiB
TypeScript

import { afterEach, describe, expect, it } from "vitest";
import {
assertAllowedOpenAiCompatibleRequestUrl,
assertAllowedOpenAiCompatibleUrl,
assertHttpsForKeyedOpenAiCompatibleUrl,
normalizeOpenAiCompatibleBaseUrl,
openAiCompatAllowPublicHosts,
} from "./openai-compatible-url.js";
const savedAllowPublic = process.env.RAKAZO_OPENAI_COMPAT_ALLOW_PUBLIC;
afterEach(() => {
if (savedAllowPublic === undefined) delete process.env.RAKAZO_OPENAI_COMPAT_ALLOW_PUBLIC;
else process.env.RAKAZO_OPENAI_COMPAT_ALLOW_PUBLIC = savedAllowPublic;
});
describe("openai-compatible URL policy", () => {
it("normalizes missing /v1 suffix without doubling it", () => {
expect(normalizeOpenAiCompatibleBaseUrl("http://127.0.0.1:8000")).toBe(
"http://127.0.0.1:8000/v1",
);
expect(normalizeOpenAiCompatibleBaseUrl("http://127.0.0.1:8000/v1")).toBe(
"http://127.0.0.1:8000/v1",
);
expect(normalizeOpenAiCompatibleBaseUrl("http://127.0.0.1:8000/v1/")).toBe(
"http://127.0.0.1:8000/v1",
);
});
it("treats any /vN versioned API root as complete (no /v1 append)", () => {
expect(normalizeOpenAiCompatibleBaseUrl("http://127.0.0.1:8000/v4")).toBe(
"http://127.0.0.1:8000/v4",
);
expect(normalizeOpenAiCompatibleBaseUrl("http://127.0.0.1:8000/v2")).toBe(
"http://127.0.0.1:8000/v2",
);
expect(normalizeOpenAiCompatibleBaseUrl("http://127.0.0.1:8000/v3")).toBe(
"http://127.0.0.1:8000/v3",
);
expect(normalizeOpenAiCompatibleBaseUrl("http://127.0.0.1:8000/v10")).toBe(
"http://127.0.0.1:8000/v10",
);
expect(normalizeOpenAiCompatibleBaseUrl("https://open.bigmodel.cn/api/paas/v4")).toBe(
"https://open.bigmodel.cn/api/paas/v4",
);
expect(normalizeOpenAiCompatibleBaseUrl("https://open.bigmodel.cn/api/paas/v4/")).toBe(
"https://open.bigmodel.cn/api/paas/v4",
);
expect(normalizeOpenAiCompatibleBaseUrl("http://127.0.0.1:8000/api")).toBe(
"http://127.0.0.1:8000/api/v1",
);
});
it("allows public bigmodel host when ALLOW_PUBLIC=1 without rewriting /v4", () => {
process.env.RAKAZO_OPENAI_COMPAT_ALLOW_PUBLIC = "1";
expect(assertAllowedOpenAiCompatibleUrl("https://open.bigmodel.cn/api/paas/v4").href).toBe(
"https://open.bigmodel.cn/api/paas/v4",
);
});
it("validates request URLs without changing their path", () => {
expect(
assertAllowedOpenAiCompatibleRequestUrl("http://127.0.0.1:8000/v1/chat/completions").href,
).toBe("http://127.0.0.1:8000/v1/chat/completions");
});
it("rejects non-http(s) and credential-bearing URLs", () => {
expect(() => normalizeOpenAiCompatibleBaseUrl("file:///tmp/sock")).toThrow(/http or https/);
expect(() => normalizeOpenAiCompatibleBaseUrl("http://user:pass@127.0.0.1:8000/v1")).toThrow(
/credentials/,
);
});
it("allows loopback and RFC1918 hosts by default", () => {
expect(assertAllowedOpenAiCompatibleUrl("http://127.0.0.1:8000/v1").href).toBe(
"http://127.0.0.1:8000/v1",
);
expect(assertAllowedOpenAiCompatibleUrl("http://localhost:11434/v1").href).toBe(
"http://localhost:11434/v1",
);
expect(assertAllowedOpenAiCompatibleUrl("http://192.168.1.20:8080/v1").href).toBe(
"http://192.168.1.20:8080/v1",
);
expect(assertAllowedOpenAiCompatibleUrl("http://host.docker.internal:8000/v1").href).toBe(
"http://host.docker.internal:8000/v1",
);
expect(() => assertAllowedOpenAiCompatibleUrl("http://ollama.local:11434/v1")).toThrow(
/Public model endpoints are blocked/,
);
});
it("blocks metadata and link-local endpoints", () => {
expect(() =>
assertAllowedOpenAiCompatibleUrl("http://169.254.169.254/latest/meta-data/"),
).toThrow(/blocked metadata or link-local host/);
expect(() => assertAllowedOpenAiCompatibleUrl("http://169.254.1.1/v1")).toThrow(
/blocked metadata or link-local host/,
);
expect(() => assertAllowedOpenAiCompatibleUrl("http://[fe80::1]/v1")).toThrow(
/blocked metadata or link-local host/,
);
expect(() => assertAllowedOpenAiCompatibleUrl("http://[::ffff:169.254.1.1]/v1")).toThrow(
/blocked metadata or link-local host/,
);
expect(() => assertAllowedOpenAiCompatibleUrl("http://[::169.254.1.1]/v1")).toThrow(
/blocked metadata or link-local host/,
);
expect(() => assertAllowedOpenAiCompatibleUrl("http://metadata.google.internal/")).toThrow(
/blocked metadata or link-local host/,
);
expect(() =>
assertAllowedOpenAiCompatibleUrl("http://100.100.100.200/latest/meta-data/"),
).toThrow(/blocked metadata or link-local host/);
expect(() =>
assertAllowedOpenAiCompatibleUrl("http://[::ffff:100.100.100.200]/latest/meta-data/"),
).toThrow(/blocked metadata or link-local host/);
expect(() =>
assertAllowedOpenAiCompatibleUrl("http://[fd00:ec2::254]/latest/meta-data/"),
).toThrow(/blocked metadata or link-local host/);
expect(() =>
assertAllowedOpenAiCompatibleUrl(
"http://[fd00:0ec2:0000:0000:0000:0000:0000:0254]/latest/meta-data/",
),
).toThrow(/blocked metadata or link-local host/);
});
it("rejects public hosts unless explicitly allowed", () => {
delete process.env.RAKAZO_OPENAI_COMPAT_ALLOW_PUBLIC;
expect(openAiCompatAllowPublicHosts()).toBe(false);
expect(() => assertAllowedOpenAiCompatibleUrl("https://api.example.com/v1")).toThrow(
/Public model endpoints are blocked.*private reverse proxy.*RFC1918/s,
);
process.env.RAKAZO_OPENAI_COMPAT_ALLOW_PUBLIC = "1";
expect(assertAllowedOpenAiCompatibleUrl("https://api.example.com/v1").href).toBe(
"https://api.example.com/v1",
);
});
});
describe("assertHttpsForKeyedOpenAiCompatibleUrl", () => {
it("rejects public http when an API key is set", () => {
process.env.RAKAZO_OPENAI_COMPAT_ALLOW_PUBLIC = "1";
const url = assertAllowedOpenAiCompatibleUrl("http://api.example.com/v1");
expect(() => assertHttpsForKeyedOpenAiCompatibleUrl(url, "secret-key")).toThrow(
/must use HTTPS/,
);
});
it("allows public https when an API key is set", () => {
process.env.RAKAZO_OPENAI_COMPAT_ALLOW_PUBLIC = "1";
const url = assertAllowedOpenAiCompatibleUrl("https://api.example.com/v1");
expect(() => assertHttpsForKeyedOpenAiCompatibleUrl(url, "secret-key")).not.toThrow();
});
it("allows private http when an API key is set", () => {
const url = assertAllowedOpenAiCompatibleUrl("http://127.0.0.1:8000/v1");
expect(() => assertHttpsForKeyedOpenAiCompatibleUrl(url, "local-secret")).not.toThrow();
const lan = assertAllowedOpenAiCompatibleUrl("http://192.168.1.20:8080/v1");
expect(() => assertHttpsForKeyedOpenAiCompatibleUrl(lan, "local-secret")).not.toThrow();
});
it("skips the HTTPS check when no API key is set", () => {
process.env.RAKAZO_OPENAI_COMPAT_ALLOW_PUBLIC = "1";
const url = assertAllowedOpenAiCompatibleUrl("http://api.example.com/v1");
expect(() => assertHttpsForKeyedOpenAiCompatibleUrl(url, undefined)).not.toThrow();
expect(() => assertHttpsForKeyedOpenAiCompatibleUrl(url, " ")).not.toThrow();
});
});