import { test, expect } from "@playwright/test" for (const kind of ["rule", "address"] as const) { for (const family of ["ip", "ip6"] as const) { test(`Firewall: удаление ${kind}/${family} требует подтверждения`, async ({ page }) => { let attempts = 0 let deleted = false let release: (() => void) | undefined const gate = new Promise(resolve => { release = resolve }) const rule = { id: "rule-1", rosId: "*R/1", serverId: "1", serverName: "Роутер", family, table: "filter", chain: "forward", action: "accept", proto: "tcp", src: "", dst: "", port: "443", iface: "", comment: "Правило тест", enabled: true, hits: 1 } const entry = { id: "address-1", rosId: "*A/1", serverId: "1", serverName: "Роутер", family, list: "trusted", address: family === "ip" ? "192.0.2.10" : "2001:db8::10", comment: "Адрес тест", disabled: false } await page.route("**/health", route => route.fulfill({ json: { ok: true } })) await page.route("**/api/**", async route => { const path = new URL(route.request().url()).pathname if (route.request().method() === "DELETE") { attempts++ expect(path).toBe(kind === "rule" ? "/api/firewall/rules" : "/api/firewall/address-lists") expect(route.request().postDataJSON()).toEqual(kind === "rule" ? { serverId: "1", family, table: "filter", rosId: "*R/1" } : { serverId: "1", family, rosId: "*A/1" }) if (attempts === 1) { await gate; return route.fulfill({ status: 403, json: { error: "Удаление запрещено тест" } }) } if (attempts === 2) return route.fulfill({ json: {} }) if (attempts === 3) return route.fulfill({ json: { ok: false } }) deleted = true return route.fulfill({ json: { ok: true } }) } return route.fulfill({ json: path === "/api/auth/config" ? { required: false } : path === "/api/firewall/all" ? { rules: kind === "rule" && !deleted ? [rule] : [], addressLists: kind === "address" && !deleted ? [entry] : [] } : path === "/api/servers" ? [{ id: 1, name: "Роутер", host: "192.0.2.1", type: "exit-node", enabled: true, country: "RU", status: "online" }] : {} }) }) await page.goto("/firewall?server=1") if (family === "ip6") await page.getByRole("button", { name: "IPv6", exact: true }).click() if (kind === "address") await page.getByRole("button", { name: /Адр. листы/ }).click() const open = async () => { await page.getByRole("button", { name: kind === "rule" ? "Действия: forward accept" : "Действия: " + entry.address, exact: true }).click() await page.getByRole("menuitem", { name: kind === "rule" ? "Удалить правило" : "Удалить", exact: true }).click() } await open() const dialog = page.getByRole("alertdialog") await expect(dialog).toContainText("Роутер") await expect(dialog).toContainText(kind === "rule" ? rule.comment : entry.address) await dialog.getByRole("button", { name: "Отмена" }).click() await expect(dialog).toHaveCount(0) expect(attempts).toBe(0) await open() const remove = dialog.getByRole("button", { name: "Удалить", exact: true }) await remove.evaluate(button => { (button as HTMLButtonElement).click(); (button as HTMLButtonElement).click() }) await expect.poll(() => attempts).toBe(1) await expect(remove).toBeDisabled() await expect(dialog.getByRole("button", { name: "Отмена" })).toBeDisabled() await expect(dialog.getByRole("status")).toContainText("Ожидание результата") await page.keyboard.press("Escape") await expect(dialog).toBeVisible() release!() await expect(dialog.getByRole("alert")).toContainText("Удаление запрещено тест") await dialog.getByRole("button", { name: "Отмена" }).click() await expect(dialog).toHaveCount(0) await open() await expect(dialog.getByRole("alert")).toHaveCount(0) for (let i = 2; i <= 3; i++) { await remove.click() await expect.poll(() => attempts).toBe(i) await expect(dialog.getByRole("alert")).toContainText("Сервер не подтвердил удаление") await expect(page.getByText(kind === "rule" ? "Правило удалено" : "Запись удалена", { exact: true })).toHaveCount(0) } await remove.click() await expect(dialog).toHaveCount(0) expect(attempts).toBe(4) await expect(page.getByRole("button", { name: kind === "rule" ? "Действия: forward accept" : "Действия: " + entry.address, exact: true })).toHaveCount(0) }) } }