SysDeck/klanker-gate/tests/integration/runtime_api_test.ts

252 lines
8.5 KiB
TypeScript
Executable File

// GET /api/runtime - the surface the Status page reads for worker topology,
// saturation, and limit state.
//
// The properties that matter are the ones a Status tile would otherwise state
// wrongly: that connection URLs never leave the process with credentials in
// them, that the worker count reflects what the PLATFORM can do rather than
// what the env var asks for, and that the concurrency gauge does not leak.
import { assert, assertEquals } from "@std/assert";
import { Router } from "../../packages/core/src/mod.ts";
import {
redactPgTarget,
registerRuntimeRoutes,
type RuntimeView,
} from "../../apps/gateway/routes/runtime.ts";
import { createContext } from "../../apps/gateway/context.ts";
import { metricsMiddleware } from "../../apps/gateway/routes/governance.ts";
function handler() {
const router = new Router();
const ctx = createContext();
registerRuntimeRoutes(router, ctx);
return { router, ctx };
}
async function fetchRuntime(router: Router): Promise<RuntimeView> {
const response = await router.handle(
new Request("http://gw.test/api/runtime"),
);
assertEquals(response.status, 200);
return await response.json() as RuntimeView;
}
Deno.test("runtime: reports worker topology for the current platform", async () => {
const { router } = handler();
const view = await fetchRuntime(router);
assertEquals(view.workers.platform, Deno.build.os);
// Windows has no SO_REUSEPORT, so the effective count is pinned at 1 no
// matter what FROSTY_WORKERS says. Reporting the requested number there
// would tell an operator four processes are serving when one is.
if (!view.workers.reusePortSupported) {
assertEquals(view.workers.effective, 1);
}
assert(view.workers.effective >= 1);
assert(
view.workers.reason.length > 0,
"reason is rendered verbatim in the UI",
);
});
Deno.test("runtime: FROSTY_WORKERS is honored only where the platform allows", async () => {
const previous = Deno.env.get("FROSTY_WORKERS");
Deno.env.set("FROSTY_WORKERS", "4");
try {
const { router } = handler();
const view = await fetchRuntime(router);
assertEquals(view.workers.configured, 4);
assertEquals(
view.workers.effective,
view.workers.reusePortSupported ? 4 : 1,
);
// The connection estimate must follow the EFFECTIVE count: sizing a
// database against processes that are not running is how max_connections
// gets set wrong.
assertEquals(
view.postgres.estimatedFleetConnections,
view.workers.effective * view.postgres.poolSize + view.workers.effective,
);
} finally {
if (previous === undefined) {
Deno.env.delete("FROSTY_WORKERS");
} else {
Deno.env.set("FROSTY_WORKERS", previous);
}
}
});
Deno.test("runtime: a garbage FROSTY_WORKERS falls back to 1", async () => {
const previous = Deno.env.get("FROSTY_WORKERS");
Deno.env.set("FROSTY_WORKERS", "not-a-number");
try {
const view = await fetchRuntime(handler().router);
assertEquals(view.workers.configured, 1);
assertEquals(view.workers.effective, 1);
} finally {
if (previous === undefined) {
Deno.env.delete("FROSTY_WORKERS");
} else {
Deno.env.set("FROSTY_WORKERS", previous);
}
}
});
Deno.test("runtime: NEVER echoes database credentials", async () => {
const previous = Deno.env.get("FROSTY_PG_URL");
Deno.env.set(
"FROSTY_PG_URL",
"postgres://frosty:sup3r-s3cret@db.internal:5432/frosty",
);
try {
const view = await fetchRuntime(handler().router);
// The whole point of the redaction: this endpoint is admin-gated but its
// response ends up in browser memory, screenshots, and bug reports.
assert(
!view.postgres.target.includes("sup3r-s3cret"),
"password must never reach the client",
);
assert(
!view.postgres.target.includes("frosty:"),
"user must not be echoed",
);
assertEquals(view.postgres.target, "db.internal:5432/frosty");
} finally {
if (previous === undefined) {
Deno.env.delete("FROSTY_PG_URL");
} else {
Deno.env.set("FROSTY_PG_URL", previous);
}
}
});
Deno.test("redactPgTarget: handles absent and unparseable input", () => {
assertEquals(redactPgTarget(undefined), "not configured");
assertEquals(redactPgTarget(""), "not configured");
// An unparseable value must not be echoed back - it could be a paste
// accident that still contains a password in an unexpected shape.
assertEquals(redactPgTarget("://:@@nonsense"), "(unparseable)");
assertEquals(
redactPgTarget("postgres://u:p@host:6432/appdb?sslmode=require"),
"host:6432/appdb",
);
});
Deno.test("runtime: rate-limit state counts only keys that declare a limit", async () => {
const { router, ctx } = handler();
ctx.virtualKeys.upsert({
id: "k-limited",
name: "limited",
enabled: true,
usedRequests: 0,
usedCostMicroUsd: 0,
token: "vk-runtime-limited",
rateLimit: { maxRequests: 60, windowMs: 60_000 },
});
ctx.virtualKeys.upsert({
id: "k-open",
name: "open",
enabled: true,
usedRequests: 0,
usedCostMicroUsd: 0,
token: "vk-runtime-open",
});
const view = await fetchRuntime(router);
assertEquals(view.rateLimit.totalKeys, 2);
assertEquals(view.rateLimit.keysWithLimits, 1);
assertEquals(view.rateLimit.enforced, true);
assertEquals(view.rateLimit.windows, [
{ keyId: "k-limited", maxRequests: 60, windowMs: 60_000 },
]);
// Per-process is a correctness caveat, not a cosmetic label: the UI relies
// on it to warn that N workers multiply a per-window limit by N.
assertEquals(view.rateLimit.scope, "per-process");
assertEquals(view.concurrency.scope, "per-process");
});
// Gauge mechanics are unit-tested in packages/telemetry/src/concurrency_test.ts.
// What belongs HERE is that the middleware actually holds the gauge across a
// streamed response - the wiring, not the counter.
Deno.test("runtime: metricsMiddleware counts a stream for its whole life", async () => {
const ctx = createContext();
const gauge = ctx.concurrency!;
let release!: () => void;
const gate = new Promise<void>((r) => (release = r));
const handler = metricsMiddleware(ctx);
const response = await handler(
new Request("http://gw.test/v1/chat/completions", { method: "POST" }),
() =>
Promise.resolve(
new Response(
new ReadableStream<Uint8Array>({
async start(controller) {
controller.enqueue(new TextEncoder().encode("data: a\n\n"));
await gate;
controller.close();
},
}),
),
),
);
const reader = response.body!.getReader();
await reader.read();
// The handler has long since returned; only the body is still open.
assertEquals(gauge.dispatching(), 0, "handler finished");
assertEquals(gauge.active(), 1, "connection still counted mid-stream");
release();
await reader.read();
assertEquals(gauge.active(), 0, "closed when the stream ended");
assertEquals(gauge.snapshot().completed, 1);
});
Deno.test("runtime: a client abort mid-stream releases the connection", async () => {
const ctx = createContext();
const gauge = ctx.concurrency!;
const handler = metricsMiddleware(ctx);
const response = await handler(
new Request("http://gw.test/v1/chat/completions", { method: "POST" }),
() =>
Promise.resolve(
new Response(
new ReadableStream<Uint8Array>({
start(controller) {
controller.enqueue(new TextEncoder().encode("data: a\n\n"));
},
}),
),
),
);
const reader = response.body!.getReader();
await reader.read();
assertEquals(gauge.active(), 1);
await reader.cancel("client hung up");
// An abandoned SSE connection is the common case in production; leaking it
// would make the gateway read as permanently saturated.
assertEquals(gauge.active(), 0);
});
Deno.test("runtime: a throwing handler releases the connection", async () => {
const ctx = createContext();
const gauge = ctx.concurrency!;
const handler = metricsMiddleware(ctx);
await handler(
new Request("http://gw.test/v1/chat/completions"),
() => Promise.reject(new Error("boom")),
).catch(() => {});
assertEquals(gauge.active(), 0);
assertEquals(gauge.dispatching(), 0);
});
Deno.test("runtime: the reported snapshot reflects live gauge state", async () => {
const { router, ctx } = handler();
const held = ctx.concurrency!.open();
const view = await fetchRuntime(router);
assert(view.concurrency.active >= 1, "an open connection must be visible");
assertEquals(view.concurrency.scope, "per-process");
held.close();
});