148 lines
5.7 KiB
TypeScript
148 lines
5.7 KiB
TypeScript
/**
|
|
* T-02 auth-hook contract tests.
|
|
*
|
|
* Covers the `createOmniRouteAuthHook(opts)` factory and its loader behaviour
|
|
* against every Auth flavor (`api`, `oauth`, null, empty key). Validates the
|
|
* multi-instance fix: provider id flows from plugin options, not a module
|
|
* constant.
|
|
*/
|
|
|
|
import test from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import { createOmniRouteAuthHook } from "../src/index.js";
|
|
|
|
test("createOmniRouteAuthHook: default providerId is 'omniroute'", () => {
|
|
const hook = createOmniRouteAuthHook();
|
|
assert.equal(hook.provider, "opencode-omniroute");
|
|
});
|
|
|
|
test("createOmniRouteAuthHook: custom providerId binds to hook.provider (multi-instance)", () => {
|
|
const hook = createOmniRouteAuthHook({ providerId: "omniroute-preprod" });
|
|
assert.equal(hook.provider, "opencode-omniroute-preprod");
|
|
});
|
|
|
|
test("createOmniRouteAuthHook: methods[0] is type 'api' with label including displayName", () => {
|
|
const hook = createOmniRouteAuthHook();
|
|
assert.equal(Array.isArray(hook.methods), true);
|
|
assert.equal(hook.methods.length, 1);
|
|
const m = hook.methods[0];
|
|
assert.equal(m.type, "api");
|
|
assert.equal(m.label, "OmniRoute API Key");
|
|
|
|
const custom = createOmniRouteAuthHook({ providerId: "omniroute-preprod" });
|
|
assert.equal(custom.methods[0].label, "OmniRoute (opencode-omniroute-preprod) API Key");
|
|
});
|
|
|
|
test("createOmniRouteAuthHook: prompts[0] uses key='apiKey' per @opencode-ai/plugin contract", () => {
|
|
// NOTE: spec referenced `name: "apiKey"`; the official
|
|
// @opencode-ai/plugin@1.15.6 prompt shape uses `key` + `message` (no
|
|
// `name`/`label`/`mask` fields). Asserting against the real type contract.
|
|
const hook = createOmniRouteAuthHook();
|
|
const m = hook.methods[0];
|
|
assert.equal(m.type, "api");
|
|
// narrow: api method may carry prompts
|
|
const prompts = "prompts" in m ? m.prompts : undefined;
|
|
assert.ok(Array.isArray(prompts) && prompts.length === 1, "expected one prompt");
|
|
const p = prompts![0];
|
|
assert.equal(p.type, "text");
|
|
assert.equal((p as { key: string }).key, "apiKey");
|
|
assert.ok(
|
|
typeof (p as { message: string }).message === "string" &&
|
|
(p as { message: string }).message.includes("omniroute"),
|
|
"prompt message should mention provider id"
|
|
);
|
|
});
|
|
|
|
test("loader: valid api auth → {apiKey} when no baseURL option (T-04: fetch omitted)", async () => {
|
|
// T-04 changed the loader return shape: without a resolvable baseURL the
|
|
// interceptor cannot gate-keep requests, so the loader falls back to
|
|
// apiKey-only and the AI-SDK uses its default fetch. See fetch-interceptor
|
|
// tests for the wired-fetch branches.
|
|
const hook = createOmniRouteAuthHook();
|
|
assert.ok(hook.loader, "loader must be defined");
|
|
const result = await hook.loader!(
|
|
async () => ({ type: "api", key: "sk-test" }) as never,
|
|
{} as never
|
|
);
|
|
assert.deepEqual(result, { apiKey: "sk-test" });
|
|
});
|
|
|
|
test("loader: valid api auth → {apiKey, baseURL, fetch} when baseURL option set (T-04)", async () => {
|
|
const hook = createOmniRouteAuthHook({ baseURL: "https://or.example.com/v1" });
|
|
const result = await hook.loader!(
|
|
async () => ({ type: "api", key: "sk-x" }) as never,
|
|
{} as never
|
|
);
|
|
assert.equal((result as { apiKey: string }).apiKey, "sk-x");
|
|
assert.equal((result as { baseURL: string }).baseURL, "https://or.example.com/v1");
|
|
assert.equal(
|
|
typeof (result as { fetch?: unknown }).fetch,
|
|
"function",
|
|
"T-04: loader must wire fetch interceptor when baseURL resolves"
|
|
);
|
|
});
|
|
|
|
test("loader: features.fetchInterceptor=false AND geminiSanitization=false → no custom fetch (flags honored)", async () => {
|
|
// Regression: both fetch-layer flags were documented + schema-validated but
|
|
// silently ignored. Disabling both must fall back to the SDK default fetch.
|
|
const hook = createOmniRouteAuthHook({
|
|
baseURL: "https://or.example.com/v1",
|
|
features: { fetchInterceptor: false, geminiSanitization: false },
|
|
});
|
|
const result = await hook.loader!(
|
|
async () => ({ type: "api", key: "sk-x" }) as never,
|
|
{} as never
|
|
);
|
|
assert.deepEqual(result, { apiKey: "sk-x", baseURL: "https://or.example.com/v1" });
|
|
assert.equal(
|
|
(result as { fetch?: unknown }).fetch,
|
|
undefined,
|
|
"both flags off must omit the custom fetch"
|
|
);
|
|
});
|
|
|
|
test("loader: features.fetchInterceptor=false but geminiSanitization=true → fetch still wired (sanitizer only)", async () => {
|
|
const hook = createOmniRouteAuthHook({
|
|
baseURL: "https://or.example.com/v1",
|
|
features: { fetchInterceptor: false, geminiSanitization: true },
|
|
});
|
|
const result = await hook.loader!(
|
|
async () => ({ type: "api", key: "sk-x" }) as never,
|
|
{} as never
|
|
);
|
|
assert.equal(
|
|
typeof (result as { fetch?: unknown }).fetch,
|
|
"function",
|
|
"geminiSanitization alone must still provide a fetch wrapper"
|
|
);
|
|
});
|
|
|
|
test("loader: null/undefined auth → {} (no creds yet, OC surfaces /connect)", async () => {
|
|
const hook = createOmniRouteAuthHook();
|
|
const r1 = await hook.loader!(async () => null as never, {} as never);
|
|
assert.deepEqual(r1, {});
|
|
const r2 = await hook.loader!(async () => undefined as never, {} as never);
|
|
assert.deepEqual(r2, {});
|
|
});
|
|
|
|
test("loader: oauth-flavored auth → {} (wrong method type, ignored)", async () => {
|
|
const hook = createOmniRouteAuthHook();
|
|
const result = await hook.loader!(
|
|
async () =>
|
|
({
|
|
type: "oauth",
|
|
refresh: "r",
|
|
access: "a",
|
|
expires: 0,
|
|
}) as never,
|
|
{} as never
|
|
);
|
|
assert.deepEqual(result, {});
|
|
});
|
|
|
|
test("loader: api auth with empty key → {} (empty creds rejected)", async () => {
|
|
const hook = createOmniRouteAuthHook();
|
|
const result = await hook.loader!(async () => ({ type: "api", key: "" }) as never, {} as never);
|
|
assert.deepEqual(result, {});
|
|
});
|