Publish curated ITWorx_MCP_Hub source from 6c7434368ce2a594005983c5a6601a31a639e7f1

This commit is contained in:
Public Source Publisher committed 2026-10-01 18:03:00 +00:00
1 parent 77946d62c4
commit 6d40ab975c
86 files changed
+6784 -1478

No files matched your search

+24 -4
View File
@@ -10,12 +10,32 @@ describe("management API safety contracts", () => {
await createApiClient().resource("tools", { filter: "risk:R1", limit: 25 });
expect(fetchMock).toHaveBeenCalledWith("/api/tools?q=risk%3AR1&limit=25", expect.objectContaining({ credentials: "same-origin", headers: { Accept: "application/json" } }));
});
it("maps protected client actions and never invents returned secret material", async () => {
const fetchMock = vi.fn().mockResolvedValue({ ok: true, json: async () => ({ clientId: "client-1", endpoint: "https://mcp.example.test/sse", scopes: ["tools:read"] }) });
it("projects only safe client profile fields from the generated configuration", async () => {
const fetchMock = vi.fn().mockResolvedValue({ ok: true, json: async () => ({
clientId: "client-1",
kind: "stdio-bridge",
protocolVersion: "2026-07-28",
endpoint: "https://mcp.example.test/mcp/ludarium/prod",
transport: "stdio",
scopes: ["tools:read"],
credentialReference: "authentik://mcp-client/private-reference",
environmentVariable: "ITWORX_MCP_ACCESS_TOKEN",
bridgeCommand: "itworx-mcp-bridge",
}) });
vi.stubGlobal("fetch", fetchMock);
const config = await createApiClient().generatedConfig("client-1");
expect(config).toEqual({ clientId: "client-1", endpoint: "https://mcp.example.test/sse", scopes: ["tools:read"] });
expect(JSON.stringify(config)).not.toContain("secret");
expect(config).toEqual({
clientId: "client-1",
kind: "stdio-bridge",
protocolVersion: "2026-07-28",
endpoint: "https://mcp.example.test/mcp/ludarium/prod",
transport: "stdio",
scopes: ["tools:read"],
environmentVariable: "ITWORX_MCP_ACCESS_TOKEN",
bridgeCommand: "itworx-mcp-bridge",
});
expect(config).not.toHaveProperty("credentialReference");
expect(JSON.stringify(config)).not.toContain("authentik://mcp-client/private-reference");
expect(fetchMock.mock.calls[0]?.[1]).toEqual(expect.objectContaining({ credentials: "same-origin" }));
});
it("fails closed without CSRF and sends the session-bound token on writes", async () => {
+4
View File
@@ -3,6 +3,10 @@ import { isSnapshotStale, routeTitle } from "../src/contracts";
import { formatBrusselsTime, healthLabel } from "../src/format";
describe("management shell contracts", () => {
it("labels connector detail breadcrumbs as a registered route", () => {
expect(routeTitle("/connectors/knowledge-default")).toBe("Connector detail");
expect(routeTitle("/connectors/knowledge-default/")).toBe("Connector detail");
});
it("maps required routes to stable titles", () => { expect(routeTitle("/")).toBe("Overview"); expect(routeTitle("/projects/ludarium")).toBe("Project detail"); expect(routeTitle("/audit")).toBe("Audit"); expect(routeTitle("/missing")).toBe("Not found"); });
it("marks invalid and old snapshots stale instead of implying health", () => { const snapshot = { generatedAt: "2026-08-01T10:00:00Z", staleAfterSeconds: 30 } as Parameters<typeof isSnapshotStale>[0]; expect(isSnapshotStale(snapshot, Date.parse("2026-08-01T10:00:31Z"))).toBe(true); expect(isSnapshotStale({ ...snapshot, generatedAt: "not-a-date" })).toBe(true); });
it("formats operator times in Brussels and labels state textually", () => { expect(formatBrusselsTime("2026-08-01T10:00:00Z")).toContain("12:00"); expect(formatBrusselsTime(null)).toBe("Never recorded"); expect(healthLabel("unavailable")).toBe("Unavailable"); });
+4 -2
View File
@@ -16,8 +16,10 @@ test.describe("authentication", () => {
await expect(page).toHaveURL(/\/$/);
await expect(page.getByText("Authenticated", { exact: true })).toBeVisible();
await expect(page.getByRole("heading", { name: "Governed MCP, with the current signal first." })).toBeVisible();
// Real backend data, not a placeholder: Ludarium is the seeded catalog project.
await expect(page.getByText("Ludarium", { exact: true })).toBeVisible();
// Assert a real bounded dashboard row without depending on catalog ordering.
const snapshot = await (await page.request.get("/api/dashboard")).json();
expect(snapshot.projects.length).toBeGreaterThan(0);
await expect(page.locator(`[href="/projects/${snapshot.projects[0].slug}"]`).first()).toContainText(snapshot.projects[0].name);
});
test("an authenticated user can sign out and stale management data disappears", async ({ page }) => {
+2 -1
View File
@@ -18,7 +18,8 @@ test.describe("mobile navigation drawer", () => {
await trigger.click();
await expect(page.locator(".sidebar.sidebar-open")).toHaveCount(1);
await expect(trigger).toHaveAttribute("aria-expanded", "true");
await expect(page.locator(".nav-trigger")).toHaveAttribute("aria-expanded", "true");
await expect(page.getByRole("dialog", { name: "Application navigation" })).toBeVisible();
});
test("closes via its own close button", async ({ page }) => {
+194
View File
@@ -0,0 +1,194 @@
import AxeBuilder from "@axe-core/playwright";
import { expect, loginAs, test } from "./fixtures";
const darkReview = process.env.WP400_DARK_REVIEW === "1";
test.use({ colorScheme: darkReview ? "dark" : "light" });
const viewports = [
{ width: 320, height: 568 }, { width: 360, height: 800 }, { width: 390, height: 844 },
{ width: 430, height: 932 }, { width: 768, height: 1024 }, { width: 1280, height: 800 },
{ width: 1440, height: 900 }, { width: 844, height: 390 },
];
for (const viewport of viewports) {
test(`operator layout and accessible surfaces at ${viewport.width}x${viewport.height}`, async ({ page }, info) => {
test.setTimeout(180_000);
await page.setViewportSize(viewport);
await loginAs(page, "hub-admin");
for (const path of ["/", "/projects", "/servers", "/tools", "/connectors", "/clients", "/policies", "/audit", "/operations", "/settings", "/projects/ludarium", "/connectors/knowledge-default"]) {
await page.goto(path);
await expect(page.locator(".topbar")).toBeVisible();
if (viewport.width <= 840) {
const target = await page.locator(".nav-trigger").boundingBox();
expect(target?.width).toBeGreaterThanOrEqual(44);
expect(target?.height).toBeGreaterThanOrEqual(44);
}
await expect(page.locator(".loading-state")).toHaveCount(0, { timeout: 15_000 });
const dimensions = await page.evaluate(() => ({ document: document.documentElement.scrollWidth, viewport: innerWidth }));
expect(dimensions.document, path).toBeLessThanOrEqual(dimensions.viewport + 1);
if (path === "/" && viewport.width <= 430) {
await expect(page.getByLabel("Project status records")).toBeVisible();
await expect(page.getByLabel("Project status table")).toBeHidden();
const grid = await page.locator(".primary-metrics").evaluate((element) => getComputedStyle(element).gridTemplateColumns);
expect(grid.split(" ")).toHaveLength(1);
}
if (path === "/tools" && viewport.width <= 430) await expect(page.getByRole("group", { name: "Table density" })).toBeHidden();
if (viewport.width === 390 || viewport.width === 1440) {
const result = await new AxeBuilder({ page }).analyze();
expect(result.violations.filter((violation) => ["serious", "critical"].includes(violation.impact ?? "")), path).toEqual([]);
}
if (process.env.SOL_VISUAL_REVIEW && ["/", "/tools", "/clients", "/operations", "/projects/ludarium", "/connectors/knowledge-default"].includes(path)) {
const themeSuffix = darkReview ? "-dark" : "";
await page.screenshot({ path: info.outputPath(`${path.replaceAll("/", "_") || "dashboard"}-${viewport.width}${themeSuffix}.png`), fullPage: true });
await page.screenshot({ path: info.outputPath(`${path.replaceAll("/", "_") || "dashboard"}-${viewport.width}${themeSuffix}-viewport.png`) });
}
}
});
}
test("mobile client actions require an explicit disclosure and confirmation", async ({ page }) => {
await page.setViewportSize({ width: 390, height: 844 });
await loginAs(page, "hub-admin");
await page.goto("/clients");
const name = `mobile-safety-${Date.now()}`;
await page.getByLabel("Name", { exact: true }).fill(name);
await page.getByRole("button", { name: "Create pending client" }).click();
const card = page.locator(".mobile-record").filter({ hasText: name }).first();
await expect(card.getByRole("button", { name: "Config", exact: true })).toBeVisible();
await expect(card.getByRole("button", { name: "Revoke", exact: true })).toBeHidden();
await card.locator("summary").click();
await card.getByRole("button", { name: "Revoke", exact: true }).click();
await expect(page.getByRole("dialog", { name: /Revoke/ })).toBeVisible();
await page.getByRole("button", { name: "Cancel", exact: true }).click();
});
test("desktop density persists while mobile cards have no inert density control", async ({ page }) => {
await loginAs(page, "hub-viewer");
await page.setViewportSize({ width: 1280, height: 800 });
await page.goto("/tools");
await page.getByRole("button", { name: "Compact", exact: true }).click();
await page.reload();
await expect(page.getByRole("button", { name: "Compact", exact: true })).toHaveAttribute("aria-pressed", "true");
await page.setViewportSize({ width: 430, height: 932 });
await expect(page.getByRole("group", { name: "Table density" })).toBeHidden();
});
test("phone trend keeps outcome details and operations errors never imply no alerts", async ({ page }) => {
await page.setViewportSize({ width: 390, height: 844 });
await loginAs(page, "hub-viewer");
await page.route("**/api/operations/summary", async (route) => {
const response = await route.fetch();
const summary = await response.json();
summary.trend = Array.from({ length: 24 }, (_, hour) => ({ hour: new Date(Date.UTC(2026, 9, 1, hour)).toISOString(), success: hour, denied: 1, error: 0, rate_limited: 0 }));
await route.fulfill({ json: summary });
});
await page.goto("/operations");
const mobile = page.getByRole("group", { name: "Four-hour call buckets" });
await expect(mobile).toBeVisible();
await expect(mobile.getByRole("button")).toHaveCount(6);
await mobile.getByRole("button").first().click();
await expect(page.locator(".mobile-trend .trend-detail")).toContainText("success");
await page.unroute("**/api/operations/summary");
await page.route("**/api/operations/summary", (route) => route.fulfill({ status: 503, body: "unavailable" }));
await page.reload();
await expect(page.getByText("Alerts are unknown while the summary is unavailable.")).toBeVisible();
await expect(page.getByText("No alerts in the latest summary")).toHaveCount(0);
});
test("status refresh backs off, reports disconnection and does not invoke probes", async ({ page }) => {
await loginAs(page, "hub-viewer");
await page.clock.install();
await page.goto("/");
await expect(page.getByText("Current · auto refresh")).toBeVisible();
let reads = 0;
let writes = 0;
page.on("request", (request) => { if (request.method() === "POST" && request.url().includes("/api/")) writes += 1; });
await page.route("**/api/dashboard", (route) => { reads += 1; return route.fulfill({ status: 503, body: "unavailable" }); });
await page.clock.fastForward(31_000);
await expect(page.getByText("Disconnected · retry available")).toBeVisible();
expect(reads).toBe(1);
await page.clock.fastForward(30_000);
expect(reads).toBe(1);
await page.clock.fastForward(31_000);
await expect.poll(() => reads).toBe(2);
expect(writes).toBe(0);
});
test("a hidden dashboard pauses scheduled refresh and never sends an automatic write", async ({ page }) => {
await loginAs(page, "hub-viewer");
await page.clock.install();
await page.addInitScript(() => {
Object.defineProperty(document, "hidden", {
configurable: true,
get: () => sessionStorage.getItem("e2e-document-hidden") !== "false",
});
});
let dashboardReads = 0;
let apiWrites = 0;
page.on("request", (request) => {
const url = new URL(request.url());
if (url.pathname === "/api/dashboard" && request.method() === "GET") dashboardReads += 1;
if (url.pathname.startsWith("/api/") && request.method() === "POST") apiWrites += 1;
});
await page.goto("/");
await expect(page.getByText("Auto refresh paused")).toBeVisible();
expect(dashboardReads).toBe(1); // The one initial snapshot; no timer-driven read.
await page.clock.fastForward(120_000);
expect(dashboardReads).toBe(1);
expect(apiWrites).toBe(0);
});
test("the dashboard action inbox starts with four signals and expands the rest on demand", async ({ page }) => {
await page.setViewportSize({ width: 390, height: 844 });
await loginAs(page, "hub-viewer");
await page.route("**/api/dashboard", async (route) => {
const response = await route.fetch();
const snapshot = await response.json();
snapshot.generatedAt = new Date().toISOString();
snapshot.staleAfterSeconds = 3600;
snapshot.health = { hub: "healthy", gateway: "healthy", detail: "Fixture is current." };
snapshot.connectors = Array.from({ length: 6 }, (_, index) => ({
id: `inbox-fixture-${index + 1}`,
name: `Inbox fixture ${index + 1}`,
project: "ludarium",
state: "degraded",
detail: "A controlled connector needs review.",
registrationState: "registered",
readiness: { state: "unknown", detail: "No live probe was requested.", observedAt: null, source: "not-probed" },
}));
snapshot.drift = { count: 0, projects: [], known: true, detail: "No drift." };
snapshot.backup = { state: "healthy", lastSuccessfulAt: null, detail: "Fixture backup state." };
snapshot.denials = { count: 0, window: "24h" };
await route.fulfill({ json: snapshot });
});
await page.goto("/");
const inbox = page.getByRole("region", { name: "6 actionable signals" });
await expect(inbox.locator(".exception-item")).toHaveCount(4);
await expect(inbox.getByRole("link", { name: /Inbox fixture 1/ })).toBeVisible();
await expect(inbox.getByRole("link", { name: /Inbox fixture 4/ })).toBeVisible();
await expect(inbox.getByRole("link", { name: /Inbox fixture 5/ })).toHaveCount(0);
await inbox.getByRole("button", { name: "Show 2 more signals" }).click();
await expect(inbox.locator(".exception-item")).toHaveCount(6);
await expect(inbox.getByRole("link", { name: /Inbox fixture 6/ })).toBeVisible();
await inbox.getByRole("button", { name: "Show fewer signals" }).click();
await expect(inbox.locator(".exception-item")).toHaveCount(4);
});
test("project and connector detail links resolve to registered records", async ({ page }) => {
await loginAs(page, "hub-admin");
await page.setViewportSize({ width: 1280, height: 800 });
await page.goto("/connectors/knowledge-default");
await expect(page.getByRole("heading", { name: "Knowledge Default", exact: true })).toBeVisible();
await page.goto("/projects/ludarium?tab=MCP%20Servers");
await expect(page.getByLabel("MCP servers", { exact: true }).getByRole("heading", { name: "ludarium-prod-readonly", exact: true })).toBeVisible();
await page.goto("/projects/ludarium?tab=Knowledge%20Sources");
await expect(page.getByLabel("Knowledge sources", { exact: true }).getByRole("heading", { name: "discovery://repository/ludarium", exact: true })).toBeVisible();
});
+118
View File
@@ -0,0 +1,118 @@
import { expect, loginAs, test } from "./fixtures";
test.describe("operator review regressions", () => {
test.beforeEach(async ({ page }) => { await loginAs(page, "hub-admin"); });
test("filter changes retain the URL project and environment at the API boundary", async ({ page }) => {
await page.goto("/connectors?project=ludarium&environment=prod");
await expect(page.getByLabel("Filter records")).toBeVisible();
const response = page.waitForResponse((value) => {
const url = new URL(value.url());
return url.pathname === "/api/connectors" && url.searchParams.get("q") === "knowledge-default";
});
await page.getByLabel("Filter records").fill("knowledge-default");
const actual = new URL((await response).url());
expect(actual.searchParams.get("project")).toBe("ludarium");
expect(actual.searchParams.get("environment")).toBe("prod");
await expect.poll(() => new URL(page.url()).searchParams.get("q")).toBe("knowledge-default");
expect(new URL(page.url()).searchParams.get("project")).toBe("ludarium");
expect(new URL(page.url()).searchParams.get("environment")).toBe("prod");
await expect(page.getByRole("link", { name: "Knowledge Default", exact: true })).toBeVisible();
});
test("catalog scope stays locked until an explicit apply finishes", async ({ page }) => {
let release!: () => void;
const pending = new Promise<void>((resolve) => { release = resolve; });
await page.route("**/api/catalog/preview", (route) => route.fulfill({ json: { valid: true, changes: ["Review fixture change"], errors: [] } }));
await page.route("**/api/catalog/apply", async (route) => { await pending; await route.fulfill({ json: { accepted: true, message: "Reviewed catalog apply completed" } }); });
await page.goto("/tools?project=ludarium");
const workflow = page.locator(".workflow-panel").filter({ has: page.getByRole("heading", { name: "Catalog validation and preview" }) });
await workflow.getByRole("button", { name: "Validate and preview" }).click();
await workflow.getByRole("button", { name: "Apply approved catalog" }).click();
await page.getByRole("button", { name: "Confirm apply" }).click();
await expect(workflow.getByRole("combobox", { name: "Project", exact: true })).toBeDisabled();
await expect(workflow.getByRole("button", { name: "Applying…" })).toBeDisabled();
release();
await expect(workflow.getByText("Reviewed catalog apply completed")).toBeVisible();
await expect(workflow.getByRole("combobox", { name: "Project", exact: true })).toBeEnabled();
});
test("a pending client created after SPA navigation retains a visible resume path", async ({ page }) => {
let release!: () => void;
const pending = new Promise<void>((resolve) => { release = resolve; });
let createdId = "";
await page.route("**/api/client-management", async (route) => {
if (route.request().method() !== "POST") { await route.continue(); return; }
const response = await route.fetch();
createdId = ((await response.json()) as { client: { id: string } }).client.id;
await pending;
await route.fulfill({ response });
});
await page.goto("/clients");
const name = `creation-navigation-${Date.now()}`;
await page.getByLabel("Name", { exact: true }).fill(name);
await page.getByRole("button", { name: "Create pending client" }).click();
await expect.poll(() => createdId).not.toBe("");
await page.getByRole("navigation", { name: "Primary navigation" }).getByRole("link", { name: "Operations", exact: true }).click();
await expect(page.getByRole("heading", { name: "Operations", level: 1 })).toBeVisible();
release();
const notice = page.locator(".client-created-notice");
await expect(notice).toContainText(name);
expect(new URL(page.url()).pathname).toBe("/operations");
await expect(notice.getByRole("link", { name: "Resume onboarding" })).toHaveAttribute("href", `/clients?onboard=${createdId}`);
await notice.getByRole("link", { name: "Resume onboarding" }).click();
await expect(page.getByText(/Resumable Hub identity/)).toBeVisible();
expect(new URL(page.url()).searchParams.get("onboard")).toBe(createdId);
});
test("operation-check results link to registered connector identities", async ({ page }) => {
await page.route("**/api/operations/check", (route) => route.fulfill({ json: {
generatedAt: new Date().toISOString(), healthy: true,
connectors: { "itworx-knowledge": { reachable: true, status: "healthy", detail: "Bounded contract fixture", connectorRefs: [{ id: "knowledge-default", name: "Knowledge Default" }], connectorRefsTruncated: false } },
} }));
await page.goto("/operations");
await page.getByRole("button", { name: "Run live checks" }).click();
const registration = page.locator(".connector-checks").getByRole("link", { name: "Knowledge Default", exact: true });
await expect(registration).toHaveAttribute("href", "/connectors/knowledge-default");
await registration.click();
await expect(page.getByRole("heading", { name: "Knowledge Default", exact: true })).toBeVisible();
});
test("Quick Jump sends an ordinary client to its filtered records instead of an unavailable onboarding workflow", async ({ page }) => {
await page.route("**/api/clients?*", (route) => route.fulfill({ json: {
kind: "clients", generatedAt: new Date().toISOString(),
rows: [{ id: "ordinary-fixture-client", name: "Ordinary fixture client", status: "healthy", summary: "Existing client", project: "ludarium", environment: "prod" }],
page: { total: 1, nextCursor: null },
} }));
await page.getByRole("button", { name: "Open quick jump" }).click();
const dialog = page.getByRole("dialog", { name: "Quick jump" });
await dialog.getByRole("combobox").fill("ordinary-fixture-client");
await dialog.getByRole("group", { name: "Clients", exact: true }).getByRole("option", { name: /Ordinary fixture client/ }).click();
await expect(page).toHaveURL(/\/clients\?q=ordinary-fixture-client$/);
await expect(page.locator(".desktop-data-table").getByText("Existing client", { exact: true })).toBeVisible();
const desktopRecord = page.locator(".desktop-data-table").getByRole("link", { name: "Ordinary fixture client", exact: true });
await expect(desktopRecord).toHaveAttribute("href", "/clients?q=ordinary-fixture-client");
await desktopRecord.click();
await expect(page).toHaveURL(/\/clients\?q=ordinary-fixture-client$/);
await page.setViewportSize({ width: 390, height: 844 });
const mobileRecord = page.locator(".mobile-record-list").getByRole("link", { name: "Ordinary fixture client", exact: true });
await expect(mobileRecord).toHaveAttribute("href", "/clients?q=ordinary-fixture-client");
await mobileRecord.click();
await expect(page).toHaveURL(/\/clients\?q=ordinary-fixture-client$/);
await expect(page.getByText(/Resumable Hub identity/)).toHaveCount(0);
});
test("Quick Jump retains the selected audit trace as its destination filter", async ({ page }) => {
await page.route("**/api/audit?*", (route) => route.fulfill({ json: {
kind: "audit", generatedAt: new Date().toISOString(),
rows: [{ id: "audit-fixture", name: "tool.invocation", status: "healthy", summary: "Fixture event", traceId: "review-fixture-trace", project: "ludarium" }],
page: { total: 1, nextCursor: null },
} }));
await page.getByRole("button", { name: "Open quick jump" }).click();
const dialog = page.getByRole("dialog", { name: "Quick jump" });
await dialog.getByRole("combobox").fill("review-fixture-trace");
await dialog.getByRole("group", { name: "Audit", exact: true }).getByRole("option", { name: /tool.invocation/ }).click();
await expect(page).toHaveURL(/\/audit\?q=review-fixture-trace$/);
await expect(page.getByLabel("Filter records")).toHaveValue("review-fixture-trace");
});
});
+144
View File
@@ -0,0 +1,144 @@
import { expect, loginAs, test } from "./fixtures";
test.use({ viewport: { width: 390, height: 844 } });
test.describe("mobile operator shell", () => {
test.beforeEach(async ({ page }) => {
await loginAs(page, "hub-viewer");
await page.goto("/");
});
test("navigation isolates the page, traps focus, locks scroll, and restores focus on Escape", async ({ page }, testInfo) => {
const trigger = page.getByRole("button", { name: "Open navigation" });
const main = page.locator("main");
await trigger.focus();
await trigger.press("Enter");
const drawer = page.getByRole("dialog", { name: "Application navigation" });
const close = drawer.getByRole("button", { name: "Close navigation" });
await expect(drawer).toBeVisible();
await expect(drawer).toHaveAttribute("aria-modal", "true");
await expect(close).toBeFocused();
if (process.env.SOL_VISUAL_REVIEW === "1") await page.screenshot({ path: testInfo.outputPath("mobile-drawer.png"), animations: "disabled" });
await expect.poll(() => main.evaluate((element) => (element as HTMLElement).inert)).toBe(true);
await expect.poll(() => page.evaluate(() => document.body.style.overflow)).toBe("hidden");
await drawer.getByRole("link", { name: "ITWorx MCP Hub" }).focus();
await page.keyboard.press("Shift+Tab");
await expect(drawer.getByRole("link", { name: "Platform" })).toBeFocused();
await page.keyboard.press("Tab");
await expect(drawer.getByRole("link", { name: "ITWorx MCP Hub" })).toBeFocused();
await page.keyboard.press("Escape");
await expect(drawer).toHaveCount(0);
await expect(trigger).toBeFocused();
await expect.poll(() => main.evaluate((element) => (element as HTMLElement).inert)).toBe(false);
await expect.poll(() => page.evaluate(() => document.body.style.overflow)).toBe("");
});
test("short phones can scroll the full drawer while the page stays locked", async ({ page }) => {
await page.setViewportSize({ width: 320, height: 520 });
await page.getByRole("button", { name: "Open navigation" }).click();
const drawer = page.getByRole("dialog", { name: "Application navigation" });
await expect(drawer).toBeVisible();
const metrics = await drawer.evaluate((element) => ({
height: element.getBoundingClientRect().height,
clientHeight: element.clientHeight,
scrollHeight: element.scrollHeight,
overflowY: getComputedStyle(element).overflowY,
}));
expect(metrics.height).toBeLessThanOrEqual(521);
expect(metrics.overflowY).toBe("auto");
expect(metrics.scrollHeight).toBeGreaterThan(metrics.clientHeight);
await drawer.evaluate((element) => { element.scrollTop = element.scrollHeight; });
await expect(drawer.getByRole("link", { name: "Platform" })).toBeInViewport();
});
test("Quick Jump searches grouped entities with keyboard control and never requests audit for a viewer", async ({ page }, testInfo) => {
const auditRequests: string[] = [];
page.on("request", (request) => {
if (new URL(request.url()).pathname === "/api/audit") auditRequests.push(request.url());
});
await page.getByRole("button", { name: "Open quick jump" }).click();
const dialog = page.getByRole("dialog", { name: "Quick jump" });
const input = dialog.getByRole("combobox", { name: "Find a workspace or resource" });
await expect(input).toBeFocused();
await expect(dialog).toHaveAttribute("aria-modal", "true");
await expect.poll(() => page.locator("main").evaluate((element) => Boolean(element.closest("[inert]")))).toBe(true);
await input.fill("Ludarium");
const project = dialog.getByRole("group", { name: "Projects", exact: true }).getByRole("option", { name: /Ludarium/i });
await expect(project).toBeVisible();
expect((await project.boundingBox())!.height).toBeLessThanOrEqual(120);
if (process.env.SOL_VISUAL_REVIEW === "1") await page.screenshot({ path: testInfo.outputPath("mobile-quick-jump.png"), animations: "disabled" });
await expect(dialog.getByRole("group", { name: "Projects" })).toBeVisible();
await input.press("ArrowDown");
await expect(input).toHaveAttribute("aria-activedescendant", /quick-jump-option-/);
await input.press("Enter");
await expect(page).toHaveURL(/\/projects\/ludarium$/);
expect(auditRequests).toEqual([]);
});
test("Quick Jump keeps recent navigation in memory and shows search failures", async ({ page }) => {
await page.getByRole("button", { name: "Open quick jump" }).click();
let dialog = page.getByRole("dialog", { name: "Quick jump" });
await dialog.getByRole("option", { name: "Catalog" }).click();
await expect(page).toHaveURL(/\/tools$/);
await page.getByRole("button", { name: "Open quick jump" }).click();
dialog = page.getByRole("dialog", { name: "Quick jump" });
await expect(dialog.getByRole("group", { name: "Recent destinations" }).getByRole("option", { name: "Catalog" })).toBeVisible();
await page.route("**/api/projects?*", (route) => route.fulfill({ status: 503, body: "unavailable" }));
const input = dialog.getByRole("combobox", { name: "Find a workspace or resource" });
await input.fill("Ludarium");
await expect(dialog.getByRole("alert")).toHaveText("One resource type could not be searched. Results may be incomplete.");
});
test("sign-out remains explicit and reports its busy state on a narrow screen", async ({ page }) => {
await page.route("**/auth/logout", async (route) => {
await new Promise((resolve) => setTimeout(resolve, 300));
await route.fulfill({ status: 503, body: "unavailable" });
});
const signOut = page.getByRole("button", { name: "Sign out" });
await expect(signOut).toBeVisible();
await expect(signOut).toHaveText("Sign out");
await signOut.click();
const busyButton = page.getByRole("button", { name: "Signing out…" });
await expect(busyButton).toBeDisabled();
await expect(page.getByRole("alert")).toContainText("control API is unavailable");
await expect(page.getByRole("button", { name: "Sign out" })).toBeEnabled();
});
});
test.describe("mobile workflow dialog", () => {
test.use({ viewport: { width: 390, height: 844 } });
test("large tool workflow fills the dynamic viewport and scrolls inside the dialog", async ({ page }, testInfo) => {
await loginAs(page, "hub-operator");
await page.goto("/tools");
const trigger = page.locator(".mobile-record-list").getByRole("button", { name: "Readiness" }).first();
await trigger.click();
const dialog = page.getByRole("dialog", { name: /Tool console/ });
await expect(dialog).toBeVisible();
await expect.poll(() => page.evaluate(() => document.body.style.overflow)).toBe("hidden");
await expect.poll(() => page.locator("main").evaluate((element) => Boolean(element.closest("[inert]")))).toBe(true);
const layout = await dialog.evaluate((element) => ({
height: element.getBoundingClientRect().height,
overflow: getComputedStyle(element).overflowY,
bodyOverflow: getComputedStyle(element.querySelector(".modal-body")!).overflowY,
}));
expect(layout.height).toBeGreaterThan(800);
expect(layout.overflow).toBe("hidden");
expect(layout.bodyOverflow).toBe("auto");
if (process.env.SOL_VISUAL_REVIEW === "1") {
await expect(dialog.getByRole("button", { name: "Run readiness check" })).toBeEnabled();
await page.screenshot({ path: testInfo.outputPath("mobile-tool-workflow.png"), animations: "disabled" });
}
await page.keyboard.press("Escape");
await expect(dialog).toHaveCount(0);
await expect(trigger).toBeFocused();
});
});
@@ -0,0 +1,329 @@
import { expect, loginAs, test } from "./fixtures";
function projectDetailFixture(slug: string) {
return {
slug,
name: `Project ${slug.toUpperCase()}`,
description: `Delayed route fixture for ${slug}.`,
environments: [{ name: "prod", classification: "production" }],
capabilityPacks: [],
connectorBindings: [],
mcpServers: [],
knowledgeSources: [],
virtualServerCount: 0,
clientCount: 0,
activeClientCount: 0,
};
}
function connectorDetailFixture(id: string) {
return {
id,
name: `Connector ${id.toUpperCase()}`,
connectorType: "fixture",
description: `Delayed route fixture for ${id}.`,
registration: { state: "registered" as const, detail: "Fixture registration." },
readiness: { reachable: null, status: "unknown", detail: "No probe was requested.", observedAt: null, source: "unavailable" as const },
bindings: [],
relatedTools: [],
};
}
test.describe("operator workflow details", () => {
test.beforeEach(async ({ page }) => {
await loginAs(page, "hub-admin");
});
test("client onboarding resumes from its durable identity and never treats a Hub check as MCP proof", async ({ page }) => {
await page.goto("/clients");
const clientName = `onboarding-resume-${Date.now()}`;
await page.getByLabel("Name").fill(clientName);
await page.getByLabel("Client profile").selectOption("native-2026");
const createResponsePromise = page.waitForResponse((response) => {
const url = new URL(response.url());
return response.request().method() === "POST" && url.pathname === "/api/client-management";
});
await page.getByRole("button", { name: "Create pending client" }).click();
const created = await (await createResponsePromise).json() as {
client: { id: string };
bootstrap: { reference: string };
config: { kind: string; protocolVersion: string; transport: string; credentialReference: string };
provisioning: { oauthUsername: string; issuer: string; tokenEndpoint: string; oauthClientId: string };
};
await expect(page.getByText(`Client created: ${clientName}`)).toBeVisible();
await expect.poll(() => page.url()).toMatch(/\/clients\?onboard=[0-9a-f-]+$/i);
const savedUrl = page.url();
expect(new URL(savedUrl).searchParams.get("onboard")).toBe(created.client.id);
await page.reload();
await expect(page.getByText(/Resumable Hub identity/)).toBeVisible();
const handoff = page.locator(".onboarding-config-auth");
const configPanel = page.locator(".onboarding-config");
const configValue = (label: string) => configPanel.locator("dl > div").filter({ hasText: label }).locator("dd");
await expect(configValue("Client profile")).toHaveText(created.config.kind);
await expect(configValue("Protocol version")).toHaveText(created.config.protocolVersion);
await expect(configValue("Transport")).toHaveText(created.config.transport);
await expect(configPanel.getByText("Credential environment variable", { exact: true })).toHaveCount(0);
await expect(configPanel.getByText("Bridge command", { exact: true })).toHaveCount(0);
await expect(handoff).toContainText(`External account: ${created.provisioning.oauthUsername}`);
await expect(handoff).toContainText(`Issuer: ${created.provisioning.issuer}`);
await expect(handoff).toContainText(`Token endpoint: ${created.provisioning.tokenEndpoint}`);
await expect(handoff).toContainText(`OAuth client ID: ${created.provisioning.oauthClientId}`);
const resumedText = await page.locator("body").innerText();
expect(resumedText).not.toContain(created.bootstrap.reference);
expect(resumedText).not.toContain(created.config.credentialReference);
await expect(page.getByText("OAuth scopes", { exact: true })).toBeVisible();
await expect(page.getByText(/External verification still required/)).toBeVisible();
await expect(page.getByRole("button", { name: "Record Hub checks" })).toBeEnabled();
await page.getByRole("button", { name: "Record Hub checks" }).click();
await expect(page.locator(".onboarding-check-recorded")).toContainText("Hub checks were recorded");
await expect(page.getByText(/does not mark external OAuth grants or MCP discovery as complete/)).toBeVisible();
await expect(page.locator(".onboarding-identity-bar small")).toContainText("· pending");
expect(page.url()).toBe(savedUrl);
});
test("a delayed verify response cannot overwrite the onboarding identity after the route changes", async ({ page }) => {
const onboardingStatus = (clientId: string, subjectDetail: string, verified = false) => ({
clientId,
project: "ludarium",
environment: "prod",
clientStatus: "pending",
provisioning: {
status: "pending-authentik-service-account",
subject: null,
issuer: "https://identity.example.invalid",
oauthUsername: `svc-mcp-codex-ludarium-${clientId}`,
tokenEndpoint: "https://identity.example.invalid/application/o/token/",
oauthClientId: "hub-fixture-client",
steps: ["Complete external account setup with the administrator."],
},
checks: {
externalSubject: { state: "pending", detail: subjectDetail },
externalScope: { state: "unavailable", detail: "External OAuth grants are not visible to the Hub." },
hubBinding: { state: "pending", detail: "The client remains disabled." },
catalog: { state: "unknown", detail: "Catalog check has not passed." },
connector: { state: "unknown", detail: "Connector state is unknown." , observedAt: null },
mcpProof: { state: "unavailable", detail: "MCP discovery and invocation must be confirmed externally." },
},
verification: { checkedAt: verified ? "2026-10-01T10:00:00.000Z" : null, checkedByManagement: verified, verifiedByManagement: verified, hubChecksPassed: verified, detail: verified ? "Client A checks passed." : "No verification record." },
});
let releaseVerify!: () => void;
const verifyGate = new Promise<void>((resolve) => { releaseVerify = resolve; });
await page.route("**/api/client-management/a/onboarding-status", (route) => route.fulfill({ json: onboardingStatus("a", "Client A is awaiting its external subject.") }));
await page.route("**/api/client-management/a/config", (route) => route.fulfill({ json: { clientId: "a", endpoint: "https://hub.example.invalid/mcp", scopes: ["project:ludarium:read"] } }));
await page.route("**/api/client-management/b/onboarding-status", (route) => route.fulfill({ json: onboardingStatus("b", "Client B is awaiting its external subject.") }));
await page.route("**/api/client-management/b/config", (route) => route.fulfill({ json: { clientId: "b", endpoint: "https://hub.example.invalid/mcp", scopes: ["project:ludarium:read"] } }));
await page.route("**/api/client-management/a/verify", async (route) => {
await verifyGate;
await route.fulfill({ json: onboardingStatus("a", "Client A is verified.", true) });
});
await page.goto("/clients?onboard=a");
await expect(page.getByText("Client A is awaiting its external subject.")).toBeVisible();
const verifyRequest = page.waitForRequest((request) => request.url().endsWith("/api/client-management/a/verify"));
await page.getByRole("button", { name: "Record Hub checks" }).click();
await verifyRequest;
await page.evaluate(() => {
window.history.pushState({}, "", "/clients?onboard=b");
window.dispatchEvent(new PopStateEvent("popstate"));
});
await expect(page.getByText("Client B is awaiting its external subject.")).toBeVisible();
releaseVerify();
await expect(page.getByText("Client B is awaiting its external subject.")).toBeVisible();
await expect(page.getByText("Client A is verified.")).toHaveCount(0);
await expect(page.getByText(/Hub checks passed/)).toHaveCount(0);
await expect(page).toHaveURL(/\/clients\?onboard=b$/);
});
test("a delayed catalog preview for the previous project cannot be shown or applied", async ({ page }) => {
await page.route((url) => url.pathname === "/api/projects" && url.searchParams.has("limit"), (route) => route.fulfill({ json: {
kind: "projects",
generatedAt: "2026-10-01T10:00:00.000Z",
rows: [
{ id: "ludarium", name: "Ludarium", status: "healthy", summary: "Fixture project A" },
{ id: "forgeflow", name: "ForgeFlow", status: "healthy", summary: "Fixture project B" },
],
page: { total: 2 },
} }));
let releasePreview!: () => void;
const previewGate = new Promise<void>((resolve) => { releasePreview = resolve; });
const previewProjects: string[] = [];
await page.route("**/api/catalog/preview", async (route) => {
const body = route.request().postDataJSON() as { project: string };
previewProjects.push(body.project);
if (body.project === "ludarium") {
await previewGate;
await route.fulfill({ json: { valid: true, changes: ["Project A private preview"], errors: [] } });
return;
}
await route.fulfill({ json: { valid: true, changes: ["Project B preview"], errors: [] } });
});
await page.goto("/tools");
const workflow = page.locator(".workflow-panel").filter({ has: page.getByRole("heading", { name: "Catalog validation and preview" }) });
const project = workflow.getByLabel("Project");
await expect(project.locator("option")).toHaveCount(2);
await project.selectOption("ludarium");
const previewRequest = page.waitForRequest((request) => new URL(request.url()).pathname === "/api/catalog/preview");
await workflow.getByRole("button", { name: "Validate and preview" }).click();
await previewRequest;
try {
await project.selectOption("forgeflow");
await expect(workflow.locator(".diff-box")).toHaveCount(0);
releasePreview();
await expect(workflow.getByText("Project A private preview")).toHaveCount(0);
await expect(workflow.getByRole("button", { name: "Apply approved catalog" })).toHaveCount(0);
expect(previewProjects).toEqual(["ludarium"]);
} finally {
releasePreview();
}
});
test("a delayed project detail response is ignored after navigating to another project", async ({ page }) => {
let releaseProjectA!: () => void;
const projectGate = new Promise<void>((resolve) => { releaseProjectA = resolve; });
await page.route("**/api/projects/a", async (route) => {
await projectGate;
await route.fulfill({ json: projectDetailFixture("a") });
});
await page.route("**/api/projects/b", (route) => route.fulfill({ json: projectDetailFixture("b") }));
const projectARequest = page.waitForRequest((request) => new URL(request.url()).pathname === "/api/projects/a");
await page.goto("/projects/a");
await projectARequest;
await page.evaluate(() => {
window.history.pushState({}, "", "/projects/b");
window.dispatchEvent(new PopStateEvent("popstate"));
});
try {
await expect(page.getByRole("heading", { name: "Project B", exact: true })).toBeVisible();
releaseProjectA();
await expect(page.getByRole("heading", { name: "Project A", exact: true })).toHaveCount(0);
await expect(page).toHaveURL(/\/projects\/b$/);
} finally {
releaseProjectA();
}
});
test("a delayed connector detail response is ignored after navigating to another connector", async ({ page }) => {
let releaseConnectorA!: () => void;
const connectorGate = new Promise<void>((resolve) => { releaseConnectorA = resolve; });
await page.route("**/api/connectors/a", async (route) => {
await connectorGate;
await route.fulfill({ json: connectorDetailFixture("a") });
});
await page.route("**/api/connectors/b", (route) => route.fulfill({ json: connectorDetailFixture("b") }));
const connectorARequest = page.waitForRequest((request) => new URL(request.url()).pathname === "/api/connectors/a");
await page.goto("/connectors/a");
await connectorARequest;
await page.evaluate(() => {
window.history.pushState({}, "", "/connectors/b");
window.dispatchEvent(new PopStateEvent("popstate"));
});
try {
await expect(page.getByRole("heading", { name: "Connector B", exact: true })).toBeVisible();
releaseConnectorA();
await expect(page.getByRole("heading", { name: "Connector A", exact: true })).toHaveCount(0);
await expect(page).toHaveURL(/\/connectors\/b$/);
} finally {
releaseConnectorA();
}
});
test("typed tool arguments reach the readiness API and recent runs retain metadata only", async ({ page }) => {
const capturedRequests: Array<Record<string, unknown>> = [];
await page.route(/\/api\/tools(?:\?.*)?$/, (route) => route.fulfill({ json: {
kind: "tools",
generatedAt: "2026-10-01T10:00:00.000Z",
rows: [{ id: "typed-tool", name: "Typed Tool", status: "healthy", summary: "Typed schema fixture", project: "ludarium", environment: "prod", risk: "R1", canonicalName: "test.typed" }],
page: { total: 1 },
} }));
await page.route("**/api/tool-definitions/test.typed", (route) => route.fulfill({ json: {
canonicalName: "test.typed",
description: "Typed schema fixture",
readOnly: true,
riskClass: "R1",
inputSchema: { type: "object", required: ["count", "enabled", "mode", "tags", "filters"], properties: {
count: { type: "integer", title: "Count", minimum: 1, maximum: 10 },
enabled: { type: "boolean", title: "Enabled" },
mode: { type: "string", title: "Mode", enum: ["safe", "full"] },
tags: { type: "array", title: "Tags" },
filters: { type: "object", title: "Filters" },
} },
limits: { timeoutMs: 1000, maxOutputBytes: 2048, maxItems: 10 },
} }));
await page.route("**/api/tools/test", async (route) => {
capturedRequests.push(route.request().postDataJSON() as Record<string, unknown>);
await route.fulfill({ json: {
ready: true,
tool: "test.typed",
project: "ludarium",
environment: "prod",
note: "Readiness only; no MCP tool invocation was performed.",
checks: {
catalog: { valid: true, readOnly: true, connectorType: "fixture" },
input: { valid: true, errors: [] },
binding: { valid: true, detail: "Fixture binding" },
policy: { allowed: true, decision: "allow", reasonCode: "FIXTURE_ALLOWED", traceId: "ready-trace" },
connector: { reachable: true, status: "healthy", detail: "Fixture route" },
},
} });
});
await page.route("**/api/tools/invoke", (route) => route.fulfill({ json: {
ok: false,
traceId: "trace-typed-001",
tool: "test.typed",
content: [{ type: "text", text: "controlled fixture result" }],
error: { code: "controlled", message: "The fixture returned a controlled error." },
} }));
await page.goto("/tools");
await page.getByRole("button", { name: "Readiness" }).click();
const dialog = page.getByRole("dialog");
await expect(dialog.getByRole("button", { name: "Run readiness check" })).toBeDisabled();
await dialog.getByLabel("Project").selectOption("ludarium");
await dialog.getByLabel("Environment").selectOption("prod");
await dialog.getByLabel("Count").fill("4");
await dialog.getByLabel("Enabled").selectOption("true");
await dialog.getByLabel("Mode").selectOption({ label: "full" });
await dialog.getByLabel("Tags").fill('["never-store-tags"]');
await dialog.getByLabel("Filters").fill('{"author":"never-store-filter"}');
await dialog.getByRole("button", { name: "Run readiness check" }).click();
await expect(dialog.getByText("Ready", { exact: true })).toBeVisible();
expect(capturedRequests[0]).toMatchObject({
project: "ludarium",
environment: "prod",
tool: "test.typed",
arguments: { count: 4, enabled: true, mode: "full", tags: ["never-store-tags"], filters: { author: "never-store-filter" } },
});
await dialog.getByLabel("Count").fill("5");
await expect(dialog.getByText("Ready", { exact: true })).toHaveCount(0);
await expect(dialog.getByRole("button", { name: "Run tool" })).toBeDisabled();
await dialog.getByRole("button", { name: "Run readiness check" }).click();
await expect(dialog.getByText("Ready", { exact: true })).toBeVisible();
await dialog.getByRole("button", { name: "Run tool" }).click();
await expect(dialog.getByRole("heading", { name: "Controlled error" })).toBeVisible();
const storedHistory = await page.evaluate(() => sessionStorage.getItem("itworx-tool-history-v1"));
expect(storedHistory).toContain("trace-typed-001");
expect(storedHistory).not.toContain("never-store-tags");
expect(storedHistory).not.toContain("never-store-filter");
expect(storedHistory).not.toContain("controlled fixture result");
await dialog.getByRole("button", { name: "Close dialog" }).click();
const recentRuns = page.getByRole("region", { name: "Recent Tool Runs" });
await expect(recentRuns.getByText("Controlled error")).toBeVisible();
await expect(recentRuns.getByText("trace-typed-001")).toBeVisible();
await recentRuns.getByRole("button", { name: "Open tool context" }).click();
await expect(page).toHaveURL(/\/tools\?reopen=test\.typed&project=ludarium&environment=prod/);
await expect(page.getByRole("dialog", { name: /Tool console/ })).toBeVisible();
await expect(page.getByRole("dialog").getByLabel("Project")).toHaveValue("ludarium");
await expect(page.getByRole("dialog").getByLabel("Environment")).toHaveValue("prod");
});
});
+5 -5
View File
@@ -33,12 +33,12 @@ test.describe("product-quality interaction and composition", () => {
const dialog = page.getByRole("dialog", { name: "Quick jump" });
await expect(dialog).toBeVisible();
const search = dialog.getByLabel("Find a workspace");
const search = dialog.getByRole("combobox", { name: "Find a workspace or resource" });
await expect(search).toBeFocused();
await expect(dialog.getByRole("button", { name: /Audit/ })).toHaveCount(0);
await expect(dialog.getByRole("option", { name: /Audit/ })).toHaveCount(0);
await search.fill("catalog");
await dialog.getByRole("button", { name: /Catalog/ }).click();
await dialog.getByRole("group", { name: "Build", exact: true }).getByRole("option", { name: /Catalog/ }).click();
await expect(page).toHaveURL(/\/tools$/);
await expect(page.getByRole("heading", { name: "Catalog", level: 2 })).toBeVisible();
});
@@ -48,7 +48,7 @@ test.describe("product-quality interaction and composition", () => {
await page.goto("/tools");
const filter = page.getByLabel("Filter records");
await filter.fill(" Fleet Ops ");
await expect(page).toHaveURL(/\/tools\?q=Fleet%20Ops$/);
await expect.poll(() => new URL(page.url()).searchParams.get("q")).toBe("Fleet Ops");
await expect(page.getByText("Updating results…")).toHaveCount(0);
await filter.fill(" ");
@@ -79,7 +79,7 @@ test.describe("product-quality interaction and composition", () => {
await page.setViewportSize({ width: 1440, height: 900 });
await page.goto("/tools");
await expect(page.getByRole("heading", { name: "Catalog", level: 2 })).toBeVisible();
await expect(page.getByRole("heading", { name: "Catalog", level: 2 })).toBeVisible({ timeout: 15_000 });
await expect(page.getByLabel("Filter records")).toBeVisible();
await expectNoPageOverflow(page);
if (captureReview) await page.screenshot({ path: testInfo.outputPath("catalog-desktop.png"), fullPage: false });
+8 -6
View File
@@ -29,13 +29,13 @@ test.describe("management workflows, driven end to end from the browser", () =>
}) => {
await page.goto("/projects/ludarium?tab=Environments");
await expect(page.getByLabel("Project detail sections").getByRole("link", { name: "Environments" })).toHaveAttribute("aria-current", "page");
await expect(page.getByRole("cell", { name: "prod", exact: true })).toBeVisible();
await expect(page.getByLabel("Project environments", { exact: true }).getByRole("heading", { name: "prod", exact: true })).toBeVisible();
// ludarium never registered a staging environment (docs/known-state, and
// tests/test_wp230_ui_completion.py's equivalent backend assertion).
await expect(page.getByRole("cell", { name: "staging" })).toHaveCount(0);
await expect(page.getByLabel("Project environments", { exact: true }).getByRole("heading", { name: "staging", exact: true })).toHaveCount(0);
await page.getByLabel("Project detail sections").getByRole("link", { name: "Connectors" }).click();
await expect(page.getByRole("cell", { name: "knowledge", exact: true })).toBeVisible();
await expect(page.getByLabel("Connector bindings", { exact: true }).getByRole("heading", { name: "knowledge", exact: true })).toBeVisible();
});
test("an unregistered project is a real 404, not a fabricated default", async ({ page }) => {
@@ -63,12 +63,14 @@ test.describe("management workflows, driven end to end from the browser", () =>
await page.goto("/tools");
await page.getByRole("button", { name: "Validate and preview" }).click();
const outcome = page.getByText(/Valid preview|Validation blocked/);
await expect(outcome).toBeVisible();
await expect(outcome).toBeVisible({ timeout: 15_000 });
const applyButton = page.getByRole("button", { name: "Apply approved catalog" });
if (await outcome.filter({ hasText: "Valid preview" }).count()) {
await expect(applyButton).toBeEnabled();
await applyButton.click();
await expect(page.getByRole("dialog")).toContainText("All registered project routes may be updated");
await page.getByRole("button", { name: "Confirm apply" }).click();
await expect(page.getByText(/Catalog reconciliation completed and verified/)).toBeVisible({ timeout: 15_000 });
} else {
await expect(applyButton).toBeDisabled();
}
@@ -167,10 +169,10 @@ test.describe("management workflows, driven end to end from the browser", () =>
test("tool readiness validates the full registered path without invoking the tool", async ({ page }) => {
await page.goto("/tools");
await page.getByRole("button", { name: "Validate and preview" }).click();
await expect(page.getByText("Valid preview")).toBeVisible();
await expect(page.getByText("Valid preview")).toBeVisible({ timeout: 15_000 });
await page.getByRole("button", { name: "Apply approved catalog" }).click();
await page.getByRole("button", { name: "Confirm apply" }).click();
await expect(page.getByText(/Catalog reconciliation completed and verified/)).toBeVisible();
await expect(page.getByText(/Catalog reconciliation completed and verified/)).toBeVisible({ timeout: 15_000 });
await page.getByLabel("Filter records").fill("Fleet Ops Operations Summary");
const row = page.getByRole("row", { name: /Fleet Ops Operations Summary/ });
+51
View File
@@ -0,0 +1,51 @@
import { describe, expect, it } from "vitest";
import type { DashboardSnapshot } from "../src/contracts";
import { actionInbox, aggregateTrend, alertDestination } from "../src/operator-data";
const snapshot: DashboardSnapshot = {
generatedAt: "2026-10-01T10:00:00Z", staleAfterSeconds: 90,
health: { hub: "healthy", gateway: "healthy", detail: "Available" },
projects: [], connectors: [{ id: "gitea-alpha", name: "gitea", project: "alpha", state: "degraded", registrationState: "registered", readiness: { state: "unknown", detail: "Not probed", observedAt: null, source: "not-probed" }, detail: "Readiness failed" }],
denials: { count: 2, window: "24h", latestTool: "gitea.repository.get" },
drift: { count: 1, projects: ["alpha"], known: true, detail: "Catalog differs" },
activeClients: 1, backup: { state: "degraded", lastSuccessfulAt: null, detail: "No backup observed" },
version: { application: "1.0.0", schema: "1" },
};
describe("operator signals", () => {
it("routes a connector signal to its exact project binding context", () => {
const item = actionInbox(snapshot, false, false).find((signal) => signal.project === "alpha" && signal.subsystem === "gitea");
expect(item?.href).toBe("/connectors/gitea-alpha");
expect(item?.detail).toBe("Readiness failed");
expect(item?.observedAt).toBe("2026-10-01T10:00:00Z");
});
it("exposes audit navigation only to an authorized auditor", () => {
expect(actionInbox(snapshot, false, false).find((signal) => signal.subsystem === "Authorization")?.href).toBe("/operations?section=alerts");
expect(actionInbox(snapshot, false, true).find((signal) => signal.subsystem === "Authorization")?.href).toBe("/audit?q=gitea.repository.get");
});
it("links drift and backup to their bounded resolution workspace", () => {
const signals = actionInbox(snapshot, false, true);
expect(signals.find((signal) => signal.subsystem === "Catalog")?.href).toBe("/operations?section=checks");
expect(signals.find((signal) => signal.subsystem === "Recovery")?.href).toBe("/operations?section=recovery");
expect(alertDestination("BACKUP_STALE", false)).toBe("/operations?section=recovery");
});
it("keeps unknown and stale state explicit without claiming a diagnosis", () => {
const signals = actionInbox({ ...snapshot, drift: { ...snapshot.drift, known: false } }, true, false);
expect(signals[0]?.subsystem).toBe("Freshness");
expect(signals.find((signal) => signal.subsystem === "Catalog")?.status).toBe("unknown");
});
});
describe("mobile call buckets", () => {
it("aggregates each four-hour interval without dropping outcomes", () => {
const trend = Array.from({ length: 24 }, (_, hour) => ({ hour: `2026-10-01T${String(hour).padStart(2, "0")}:00:00Z`, success: 1, denied: 2, error: 3, rate_limited: 4 }));
const buckets = aggregateTrend(trend, 4);
expect(buckets).toHaveLength(6);
expect(buckets[0]).toEqual({ hour: "2026-10-01T00:00:00Z", endHour: "2026-10-01T03:00:00Z", success: 4, denied: 8, error: 12, rate_limited: 16 });
expect(buckets[5]?.success).toBe(4);
});
it("retains partial buckets and handles empty observations", () => {
expect(aggregateTrend([], 4)).toEqual([]);
expect(aggregateTrend([{ hour: "2026-10-01T00:00:00Z", success: 2, denied: 0, error: 1, rate_limited: 0 }], 4)[0]?.error).toBe(1);
});
});
+16
View File
@@ -0,0 +1,16 @@
import { afterEach, expect, it, vi } from "vitest";
import { readPreference, writePreference } from "../src/preferences";
afterEach(() => vi.unstubAllGlobals());
it("accepts only known presentation preferences", () => {
const values = new Map<string, string>([["hub-density", "arbitrary"]]);
vi.stubGlobal("localStorage", { getItem: (key: string) => values.get(key) ?? null, setItem: (key: string, value: string) => values.set(key, value) });
expect(readPreference("hub-density", ["compact", "comfortable"], "comfortable")).toBe("comfortable");
writePreference("hub-density", "compact");
expect(readPreference("hub-density", ["compact", "comfortable"], "comfortable")).toBe("compact");
});
it("continues with safe defaults when storage is denied", () => {
vi.stubGlobal("localStorage", { getItem: () => { throw new Error("storage denied"); }, setItem: () => { throw new Error("storage denied"); } });
expect(readPreference("hub-theme", ["dark", "light"], "dark")).toBe("dark");
expect(() => writePreference("hub-theme", "light")).not.toThrow();
});
+91
View File
@@ -0,0 +1,91 @@
import { describe, expect, it } from "vitest";
import { NULL_INPUT_VALUE, parseSchemaArguments } from "../src/schema-input";
import type { ToolDefinition } from "../src/contracts";
const schema = (properties: NonNullable<ToolDefinition["inputSchema"]["properties"]>, required: string[] = []): ToolDefinition["inputSchema"] => ({
type: "object",
properties,
required,
});
describe("schema-driven tool arguments", () => {
it("parses strings, integers, decimals, booleans, enums, arrays and objects by declared type", () => {
const inputSchema = schema({
query: { type: "string" },
limit: { type: "integer", minimum: 1, maximum: 20 },
score: { type: "number" },
includeClosed: { type: "boolean" },
format: { type: "string", enum: ["compact", "full"] },
labels: { type: "array" },
filters: { type: "object" },
}, ["query", "limit", "includeClosed", "format"]);
expect(parseSchemaArguments({
query: "pull request",
limit: "4",
score: "3.5",
includeClosed: "false",
format: JSON.stringify("full"),
labels: "[\"bug\",\"urgent\"]",
filters: "{\"author\":\"jens\"}",
}, inputSchema)).toEqual({
arguments: {
query: "pull request",
limit: 4,
score: 3.5,
includeClosed: false,
format: "full",
labels: ["bug", "urgent"],
filters: { author: "jens" },
},
errors: {},
valid: true,
});
});
it("reports missing required values, malformed JSON and type mismatches clearly", () => {
const inputSchema = schema({
count: { type: "integer" },
enabled: { type: "boolean" },
tags: { type: "array" },
options: { type: "object" },
}, ["count", "enabled", "tags", "options"]);
const parsed = parseSchemaArguments({ count: "2.3", enabled: "sometimes", tags: "{oops", options: "[]" }, inputSchema);
expect(parsed.valid).toBe(false);
expect(parsed.arguments).toEqual({});
expect(parsed.errors).toEqual({
count: "Enter a whole number.",
enabled: "Choose yes or no.",
tags: "Enter valid JSON for this array.",
options: "Enter a JSON object.",
});
});
it("applies string and numeric bounds and rejects enum values outside the schema", () => {
const inputSchema = schema({
query: { type: "string", minLength: 2, maxLength: 5 },
limit: { type: "integer", minimum: 1, maximum: 8 },
mode: { type: "string", enum: ["safe", "full"] },
});
const parsed = parseSchemaArguments({ query: "x", limit: "9", mode: JSON.stringify("unsafe") }, inputSchema);
expect(parsed.valid).toBe(false);
expect(parsed.errors.query).toBe("Enter at least 2 characters.");
expect(parsed.errors.limit).toBe("Enter a number no greater than 8.");
expect(parsed.errors.mode).toBe("Choose a value listed by this tool's schema.");
});
it("omits empty optional fields and accepts explicit null only for nullable fields", () => {
const inputSchema = schema({ optional: { type: "string" }, nullable: { type: ["string", "null"] } });
expect(parseSchemaArguments({ optional: "", nullable: NULL_INPUT_VALUE }, inputSchema)).toEqual({
arguments: { nullable: null },
errors: {},
valid: true,
});
expect(parseSchemaArguments({ nullable: NULL_INPUT_VALUE }, schema({ nullable: { type: "string" } })).errors.nullable).toBe("This field does not accept null.");
});
});
+61
View File
@@ -0,0 +1,61 @@
import { describe, expect, it } from "vitest";
import { MAX_TOOL_RUNS, recordToolRun, readToolHistory, type ToolRunMetadata } from "../src/tool-history";
function createStorage(seed: string | null = null) {
let value = seed;
return {
getItem: () => value,
setItem: (_key: string, next: string) => { value = next; },
value: () => value,
};
}
const run = (overrides: Partial<ToolRunMetadata> = {}): ToolRunMetadata => ({
tool: "gitea.pullrequest.status",
project: "ludarium",
environment: "prod",
ok: true,
traceId: "trace-123",
at: "2026-09-30T10:00:00.000Z",
...overrides,
});
describe("bounded recent tool history", () => {
it("stores only safe invocation metadata and never serializes arguments or results", () => {
const storage = createStorage();
recordToolRun({ ...run(), arguments: { token: "fixture-do-not-store" }, result: "private output" } as ToolRunMetadata, storage);
expect(JSON.parse(storage.value() ?? "null")).toEqual([run()]);
expect(storage.value()).not.toContain("fixture-do-not-store");
expect(storage.value()).not.toContain("private output");
});
it("bounds history to the newest twenty valid entries", () => {
const storage = createStorage();
for (let index = 0; index < MAX_TOOL_RUNS + 4; index += 1) {
recordToolRun(run({ traceId: `trace-${index}`, at: `2026-09-30T10:${String(index).padStart(2, "0")}:00.000Z` }), storage);
}
const history = readToolHistory(storage);
expect(history).toHaveLength(MAX_TOOL_RUNS);
expect(history[0]?.traceId).toBe(`trace-${MAX_TOOL_RUNS + 3}`);
expect(history.at(-1)?.traceId).toBe("trace-4");
});
it("drops malformed and oversized stored entries without exposing extra fields", () => {
const storage = createStorage(JSON.stringify([
{ ...run(), arguments: { apiKey: "secret" }, content: "result" },
{ ...run(), traceId: "trace\nsecret" },
{ ...run(), at: "yesterday" },
null,
]));
expect(readToolHistory(storage)).toEqual([run()]);
});
it("treats corrupt or unavailable browser storage as an empty history", () => {
expect(readToolHistory(createStorage("not-json"))).toEqual([]);
expect(() => readToolHistory({ getItem: () => { throw new Error("blocked"); }, setItem: () => undefined })).not.toThrow();
expect(() => recordToolRun(run(), { getItem: () => null, setItem: () => { throw new Error("blocked"); } })).not.toThrow();
});
});