SysDeck/klanker-gate/apps/gateway/routes/catalog_test.ts

168 lines
5.3 KiB
TypeScript
Executable File

import { assert, assertEquals } from "@std/assert";
import { Router } from "../../../packages/core/src/mod.ts";
import { registerCatalogRoutes } from "./catalog.ts";
import { type AppContext, NullToolExecutor, VERSION } from "../context.ts";
import { ProviderManager } from "../../../packages/providers/src/mod.ts";
import { Metrics } from "../../../packages/telemetry/src/metrics.ts";
import { LogBus } from "../../../packages/telemetry/src/logbus.ts";
import { VirtualKeyManager } from "../../../packages/governance/src/virtual_keys.ts";
import { UsageTracker } from "../../../packages/telemetry/src/usagestore.ts";
import { MCPRegistry } from "../../../packages/mcp/src/registry.ts";
import { PluginManager } from "../../../packages/plugins/src/lifecycle.ts";
// The catalog route is not yet wired into main.ts (the orchestrator applies the
// registration lines), so these tests register it on a bare Router directly.
const base = "http://gateway.test";
function usageRecord(provider: string, model: string, cost: number) {
return {
ts: new Date().toISOString(),
provider,
model,
promptTokens: 10,
completionTokens: 20,
totalTokens: 30,
costMicroUsd: cost,
durationMs: 5,
status: 200,
stream: false,
cacheHit: false,
};
}
function baseContext(
providers: ProviderManager,
usage: UsageTracker,
): AppContext {
return {
providers,
metrics: new Metrics(),
logBus: new LogBus(),
usage,
virtualKeys: new VirtualKeyManager(),
mcp: new MCPRegistry(),
plugins: new PluginManager(),
toolExecutor: new NullToolExecutor(),
version: VERSION,
};
}
function makeContext(): AppContext {
const providers = new ProviderManager([
{
id: "openai",
type: "openai",
apiKey: "k",
enabled: true,
models: ["gpt-4o", "gpt-4o-mini"],
priority: 0,
},
{
id: "local",
type: "lmstudio",
baseUrl: "http://localhost:1234/v1",
enabled: true,
models: ["gpt-4o", "local-llama"], // gpt-4o shared with openai
priority: 0,
},
]);
const usage = new UsageTracker();
usage.record(usageRecord("openai", "gpt-4o", 2_000_000));
usage.record(usageRecord("openai", "gpt-4o-mini", 500_000));
usage.record(usageRecord("local", "local-llama", 1_000_000));
return baseContext(providers, usage);
}
function catalogRouter(ctx: AppContext): Router {
const router = new Router();
registerCatalogRoutes(router, ctx);
return router;
}
Deno.test("GET /api/catalog joins providers with 24h analytics", async () => {
const router = catalogRouter(makeContext());
const res = await router.handle(new Request(`${base}/api/catalog`));
assertEquals(res.status, 200);
const body = await res.json();
const byId = new Map(body.providers.map((p: { id: string }) => [p.id, p]));
const openai = byId.get("openai") as {
type: string;
custom: boolean;
models: string[];
traffic24h: number;
cost24h: number;
};
assertEquals(openai.type, "openai");
assertEquals(openai.custom, false);
assertEquals(openai.models, ["gpt-4o", "gpt-4o-mini"]);
assertEquals(openai.traffic24h, 2);
assertEquals(openai.cost24h, 2.5); // (2_000_000 + 500_000) micro-USD
const local = byId.get("local") as { custom: boolean; traffic24h: number };
assertEquals(local.custom, true); // lmstudio is a generic custom endpoint
assertEquals(local.traffic24h, 1);
// Totals: 2 providers, 3 distinct models (gpt-4o shared), 3 requests, 3.5c.
assertEquals(body.totals.providers, 2);
assertEquals(body.totals.models, 3);
assertEquals(body.totals.requests24h, 3);
assertEquals(body.totals.cost24h, 3.5);
});
Deno.test("GET /api/catalog omits disabled providers", async () => {
const providers = new ProviderManager([
{
id: "openai",
type: "openai",
apiKey: "k",
enabled: true,
models: ["gpt-4o", "gpt-4o-mini"],
priority: 0,
},
{
id: "retired",
type: "openai",
apiKey: "k",
enabled: false,
// gpt-4o-mini shared with openai; retired-only exposes the exclusion.
models: ["gpt-4o-mini", "retired-only"],
priority: 0,
},
]);
const router = catalogRouter(baseContext(providers, new UsageTracker()));
const res = await router.handle(new Request(`${base}/api/catalog`));
assertEquals(res.status, 200);
const body = await res.json();
const ids = body.providers.map((p: { id: string }) => p.id);
assert(ids.includes("openai"));
assert(!ids.includes("retired")); // disabled provider absent from listing
// Totals derive from the enabled provider only: 1 provider, 2 distinct
// models (the disabled account's retired-only is excluded).
assertEquals(body.totals.providers, 1);
assertEquals(body.totals.models, 2);
});
Deno.test("GET /api/catalog reports zeros when no analytics exist", async () => {
const providers = new ProviderManager([
{
id: "openai",
type: "openai",
apiKey: "k",
enabled: true,
models: ["gpt-4o"],
priority: 0,
},
]);
const router = catalogRouter(baseContext(providers, new UsageTracker()));
const res = await router.handle(new Request(`${base}/api/catalog`));
const body = await res.json();
assertEquals(body.providers[0].traffic24h, 0);
assertEquals(body.providers[0].cost24h, 0);
assertEquals(body.totals.requests24h, 0);
assert(Array.isArray(body.providers));
});