219 lines
7.1 KiB
TypeScript
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);
|
|
});
|
|
});
|