SysDeck/klanker-gate/tests/browser/gateway.spec.ts

78 lines
3.0 KiB
TypeScript
Executable File

import { expect, test } from "@playwright/test";
// End-to-end browser flows against the deployed gateway + control UI.
//
// These run against a REAL gateway backed by a persistent PostgreSQL volume, so
// they cannot assume a clean database. Cleaning up only at the end of each test
// is not enough: one failed or interrupted run leaves `e2e-openai` /`e2e-mcp`
// behind, and every later run then fails on the duplicate. Fixtures are
// therefore removed BEFORE each test as well, ignoring "not found".
const FIXTURES = [
"/api/providers/e2e-openai",
"/api/mcp/clients/e2e-mcp",
];
test.beforeEach(async ({ request }) => {
for (const path of FIXTURES) {
// A 404 is the expected result on a clean database.
await request.delete(path).catch(() => {});
}
});
test("control plane shell loads with all sections", async ({ page }) => {
await page.goto("/");
await expect(page.getByRole("heading", { name: "Klanker Gateway Manager" }))
.toBeVisible();
for (const tab of ["Providers", "Status", "Logs", "Extensions"]) {
await expect(page.getByRole("button", { name: tab })).toBeVisible();
}
});
test("provider add/delete round-trip never renders the secret", async ({ page }) => {
const secret = "sk-e2e-secret-do-not-render-1234567890";
await page.goto("/");
await page.getByLabel("ID").fill("e2e-openai");
await page.getByLabel("API key").fill(secret);
await page.getByRole("button", { name: "Add provider" }).click();
const row = page.getByRole("row", { name: /e2e-openai/ });
await expect(row).toBeVisible();
// Presence is shown as a status badge, never as the value. An enabled
// account holding credentials reads "online"; without them it reads
// "no key". Either way the secret itself must not be in the document.
await expect(row.getByText(/online|no key/)).toBeVisible();
// The key must never appear anywhere in the served document.
expect(await page.content()).not.toContain(secret);
await row.getByRole("button", { name: "Delete" }).click();
await expect(row).toBeHidden();
});
test("MCP server registration defaults to the SSE transport", async ({ page }) => {
await page.goto("/");
await page.getByRole("button", { name: "Extensions" }).click();
await page.getByLabel("ID").fill("e2e-mcp");
await page.getByLabel("URL").fill("http://127.0.0.1:59999/rpc");
// Transport selector left at its default — asserting D11 end to end.
await page.getByRole("button", { name: "Add MCP server" }).click();
const row = page.getByRole("row", { name: /e2e-mcp/ });
await expect(row).toBeVisible();
await expect(row.getByText("http-sse")).toBeVisible();
await row.getByRole("button", { name: "Remove" }).click();
await expect(row).toBeHidden();
});
test("status tab reports live gateway health", async ({ page }) => {
await page.goto("/");
await page.getByRole("button", { name: "Status" }).click();
await expect(page.getByText("Gateway health")).toBeVisible();
await expect(page.getByText("ok", { exact: true })).toBeVisible();
await expect(page.getByText(/gateway v\d+\.\d+\.\d+/)).toBeVisible();
});