Files
coreci-chat/packages/byom/tests/validator.test.ts
T
CIAgent 3b8345dfe0 feat(P3): Wave C BYOM — endpoint registry, validate-on-save, routing shim
REQ-006: configure BYOM endpoint (URL in DB, key in secret manager)
REQ-007: validate-on-save test inference call (OpenAI-compatible)
REQ-008: route all inference to BYOM (G-001: test-inference proxy endpoint)
REQ-009: reject when unconfigured/unreachable (ByomUnconfiguredError/ByomUnreachableError)

---ci---
phase: 3
milestone: v0.1
status: execute
---/ci---
2026-08-25 01:54:20 +00:00

122 lines
4.0 KiB
TypeScript

/**
* validator.test.ts — validateEndpoint (REQ-007).
*
* Mocks the global `fetch` to assert:
* - 200 with a valid chat-completion body → { ok: true }
* - 401 → { ok: false, error: "auth_failed" }
* - fetch throws (network/timeout) → { ok: false, error: "connection_failed" }
*/
import { describe, it, expect, vi, afterEach } from "vitest";
import { validateEndpoint } from "../src/validator.js";
const URL = "https://byom.example.com";
const KEY = "sk-test-key";
function jsonResponse(body: unknown, status = 200): Response {
return new Response(JSON.stringify(body), {
status,
headers: { "Content-Type": "application/json" },
});
}
afterEach(() => {
vi.restoreAllMocks();
});
describe("validateEndpoint (REQ-007)", () => {
it("returns ok:true on a 200 with a valid chat completion body", async () => {
vi.stubGlobal(
"fetch",
vi.fn().mockResolvedValue(
jsonResponse({
id: "chatcmpl-1",
choices: [{ index: 0, message: { role: "assistant", content: "pong" }, finish_reason: "stop" }],
usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 },
}),
),
);
const res = await validateEndpoint(URL, KEY);
expect(res.ok).toBe(true);
expect(res.error).toBeUndefined();
// Verify it POSTed to /v1/chat/completions with the bearer key.
expect(fetch).toHaveBeenCalledTimes(1);
const [calledUrl, init] = (fetch as unknown as ReturnType<typeof vi.fn>).mock.calls[0] as [
string,
RequestInit,
];
expect(calledUrl).toBe(`${URL}/v1/chat/completions`);
expect(init.method).toBe("POST");
expect((init.headers as Record<string, string>).Authorization).toBe(`Bearer ${KEY}`);
});
it("returns auth_failed on 401", async () => {
vi.stubGlobal(
"fetch",
vi.fn().mockResolvedValue(new Response("unauthorized", { status: 401 })),
);
const res = await validateEndpoint(URL, KEY);
expect(res.ok).toBe(false);
expect(res.error).toBe("auth_failed");
expect(res.detail).toBeTruthy();
});
it("returns auth_failed on 403", async () => {
vi.stubGlobal(
"fetch",
vi.fn().mockResolvedValue(new Response("forbidden", { status: 403 })),
);
const res = await validateEndpoint(URL, KEY);
expect(res.ok).toBe(false);
expect(res.error).toBe("auth_failed");
});
it("returns connection_failed when fetch throws", async () => {
vi.stubGlobal("fetch", vi.fn().mockRejectedValue(new Error("ENOTFOUND")));
const res = await validateEndpoint(URL, KEY);
expect(res.ok).toBe(false);
expect(res.error).toBe("connection_failed");
expect(res.detail).toContain("ENOTFOUND");
});
it("returns connection_failed on 5xx", async () => {
vi.stubGlobal(
"fetch",
vi.fn().mockResolvedValue(new Response("bad gateway", { status: 502 })),
);
const res = await validateEndpoint(URL, KEY);
expect(res.ok).toBe(false);
expect(res.error).toBe("connection_failed");
});
it("returns invalid_response on 200 with a non-chat-completion body", async () => {
vi.stubGlobal(
"fetch",
vi.fn().mockResolvedValue(jsonResponse({ hello: "world" })),
);
const res = await validateEndpoint(URL, KEY);
expect(res.ok).toBe(false);
expect(res.error).toBe("invalid_response");
});
it("returns invalid_response on 200 with non-JSON body", async () => {
vi.stubGlobal(
"fetch",
vi.fn().mockResolvedValue(new Response("not json", { status: 200 })),
);
const res = await validateEndpoint(URL, KEY);
expect(res.ok).toBe(false);
expect(res.error).toBe("invalid_response");
});
it("tolerates a configured URL that already ends in /v1", async () => {
vi.stubGlobal(
"fetch",
vi.fn().mockResolvedValue(jsonResponse({ choices: [] })),
);
await validateEndpoint(`${URL}/v1`, KEY);
const [calledUrl] = (fetch as unknown as ReturnType<typeof vi.fn>).mock.calls[0] as [string];
expect(calledUrl).toBe(`${URL}/v1/chat/completions`);
});
});