Files
diegosouzapw--omniroute/tests/e2e/proxy-registry.smoke.spec.ts
2026-07-13 13:39:12 +08:00

219 lines
7.1 KiB
TypeScript

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<ProxyStub>;
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<ProxyStub> & { 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);
});
});