import { test, expect } from "@playwright/test"; import { gotoDashboardRoute } from "./helpers/dashboardAuth"; type ProxyStub = { id: string; name: string; type: string; host: string; port: number; status: string; region?: string | null; notes?: string | null; }; test.describe("Proxy Registry smoke flow", () => { test("create, edit, bulk-assign modal, and delete proxy from settings advanced", async ({ page, }) => { const state: { proxies: ProxyStub[]; nextId: number; bulkAssignCalls: number; } = { proxies: [], nextId: 1, bulkAssignCalls: 0, }; await page.route("**/api/settings/proxy?level=global", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ proxy: null }), }); }); await page.route("**/api/settings/proxies/health?hours=24", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ items: state.proxies.map((p) => ({ proxyId: p.id, totalRequests: 0, successRate: null, avgLatencyMs: null, lastSeenAt: null, })), total: state.proxies.length, windowHours: 24, }), }); }); await page.route("**/api/settings/proxies/bulk-assign", async (route) => { if (route.request().method() !== "PUT") { await route.fulfill({ status: 405, contentType: "application/json", body: JSON.stringify({ error: "method not allowed in test stub" }), }); return; } state.bulkAssignCalls += 1; await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ success: true, scope: "provider", requested: 2, updated: 2, failed: [], }), }); }); await page.route("**/api/settings/proxies*", async (route) => { const req = route.request(); const method = req.method(); const url = new URL(req.url()); const id = url.searchParams.get("id"); const whereUsed = url.searchParams.get("whereUsed"); if (method === "GET" && id && whereUsed === "1") { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ count: 0, assignments: [] }), }); return; } if (method === "GET") { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ items: state.proxies, total: state.proxies.length }), }); return; } if (method === "POST") { const payload = req.postDataJSON() as Partial; const proxy: ProxyStub = { id: `proxy-${state.nextId++}`, name: payload.name || "Proxy", type: payload.type || "http", host: payload.host || "localhost", port: Number(payload.port || 8080), status: payload.status || "active", region: payload.region || null, notes: payload.notes || null, }; state.proxies.unshift(proxy); await route.fulfill({ status: 201, contentType: "application/json", body: JSON.stringify(proxy), }); return; } if (method === "PATCH") { const payload = req.postDataJSON() as Partial & { id?: string }; const index = state.proxies.findIndex((p) => p.id === payload.id); if (index === -1) { await route.fulfill({ status: 404, contentType: "application/json", body: JSON.stringify({ error: { message: "Proxy not found", type: "not_found" } }), }); return; } const updated = { ...state.proxies[index], ...payload, } as ProxyStub; state.proxies[index] = updated; await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify(updated), }); return; } if (method === "DELETE") { if (!id) { await route.fulfill({ status: 400, contentType: "application/json", body: JSON.stringify({ error: { message: "id is required", type: "invalid_request" } }), }); return; } state.proxies = state.proxies.filter((p) => p.id !== id); await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ success: true }), }); return; } await route.fulfill({ status: 405, contentType: "application/json", body: JSON.stringify({ error: "method not allowed in test stub" }), }); }); // The proxy registry now lives under the "Proxy Pool" sub-tab of the proxy // settings page; navigate directly to it so the heading renders. await gotoDashboardRoute(page, "/dashboard/system/proxy?tab=proxy-pool"); await expect(page.getByRole("heading", { name: "Proxy Registry" })).toBeVisible(); await page.getByTestId("proxy-registry-open-create").click(); const createDialog = page.getByRole("dialog"); await expect(createDialog.getByText("Create Proxy")).toBeVisible(); await createDialog.getByTestId("proxy-registry-name-input").fill("Registry Smoke Proxy"); await createDialog.getByTestId("proxy-registry-host-input").fill("smoke.local"); await createDialog.getByRole("button", { name: "Save" }).click(); await expect(page.locator("table")).toContainText("Registry Smoke Proxy"); await expect(page.locator("table")).toContainText("http://smoke.local:8080"); const row = page.locator("tr", { hasText: "Registry Smoke Proxy" }); await row.getByRole("button", { name: "Edit" }).click(); const editDialog = page.getByRole("dialog"); await expect(editDialog.getByText("Edit Proxy")).toBeVisible(); await editDialog.getByTestId("proxy-registry-host-input").fill("smoke-updated.local"); await editDialog.getByRole("button", { name: "Save" }).click(); await expect(page.locator("table")).toContainText("http://smoke-updated.local:8080"); await page.getByTestId("proxy-registry-open-bulk").click(); const bulkDialog = page.getByRole("dialog"); await expect(bulkDialog.getByText("Bulk Proxy Assignment")).toBeVisible(); await bulkDialog.getByTestId("proxy-registry-bulk-scopeids-input").fill("openai,anthropic"); await bulkDialog.getByTestId("proxy-registry-bulk-apply").click(); await expect .poll(() => state.bulkAssignCalls, { message: "Expected bulk assign API to be called exactly once", }) .toBe(1); await row.getByRole("button", { name: "Delete" }).click(); await expect .poll(() => state.proxies.some((proxy) => proxy.name === "Registry Smoke Proxy")) .toBe(false); await expect(page.locator("tr", { hasText: "Registry Smoke Proxy" })).toHaveCount(0); }); });