import { test, expect } from "@playwright/test" for (const kind of ["rule", "address"] as const) { for (const editing of [false, true]) { for (const family of ["ip", "ip6"] as const) { test(`Firewall: сохранение ${kind}/${editing ? "edit" : "create"}/${family} сохраняет форму при ошибке`, async ({ page }) => { let attempts = 0 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 (["POST", "PUT"].includes(route.request().method())) { attempts++ expect(path).toBe(kind === "rule" ? "/api/firewall/rules" : "/api/firewall/address-lists") expect(route.request().method()).toBe(editing ? "PUT" : "POST") const payload = route.request().postDataJSON() expect(payload).toMatchObject({ serverId: "1", family, comment: "Сохранить ввод", disabled: false }) if (kind === "rule") expect(payload).toMatchObject({ table: "filter", action: "accept", chain: editing ? "forward" : "input" }) else expect(payload).toMatchObject({ list: "trusted", address: entry.address }) if (editing) expect(payload.rosId).toBe(kind === "rule" ? "*R/1" : "*A/1") else expect(payload).not.toHaveProperty("rosId") 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 } }) return route.fulfill({ json: { ok: true } }) } return route.fulfill({ json: path === "/api/auth/config" ? { required: false } : path === "/api/firewall/all" ? { rules: [rule], addressLists: [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") await expect(page.locator("tr[data-row-id]")).toHaveCount(family === "ip" ? 1 : 0) if (family === "ip6") await page.getByRole("button", { name: "IPv6", exact: true }).click() if (kind === "address") await page.getByRole("button", { name: /Адр. листы/ }).click() const open = async () => { if (editing) { await page.getByRole("button", { name: kind === "rule" ? "Действия: forward accept" : "Действия: " + entry.address, exact: true }).click() await page.getByRole("menuitem", { name: "Редактировать", exact: true }).click() } else { await page.getByRole("button", { name: kind === "rule" ? "Новое правило" : "Добавить", exact: true }).click() } } await open() const dialog = page.getByRole("dialog") const comment = dialog.locator('[data-slot="field"]').filter({ has: page.getByText("Комментарий", { exact: true }) }).getByRole("textbox") await comment.fill("Сохранить ввод") if (kind === "address" && !editing) { await dialog.getByPlaceholder("cdn-bypass").fill("trusted") await dialog.getByPlaceholder("10.0.0.0/8").fill(entry.address) } const submit = dialog.getByRole("button", { name: editing ? "Сохранить" : kind === "rule" ? "Создать правило" : "Добавить", exact: true }) await submit.evaluate(button => { (button as HTMLButtonElement).click(); (button as HTMLButtonElement).click() }) await expect.poll(() => attempts).toBe(1) await expect(dialog.getByRole("status")).toContainText("Ожидание результата сохранения") await expect(comment).toBeDisabled() for (const toggle of await dialog.getByRole("switch").all()) await expect(toggle).toBeDisabled() await expect(dialog.getByRole("button", { name: "Отмена" })).toBeDisabled() await page.keyboard.press("Escape") await expect(dialog).toBeVisible() release!() await expect(dialog.getByRole("alert")).toContainText("Сохранение запрещено тест") await expect(comment).toHaveValue("Сохранить ввод") for (let i = 2; i <= 3; i++) { await submit.click() await expect.poll(() => attempts).toBe(i) await expect(dialog.getByRole("alert")).toContainText("Сервер не подтвердил сохранение") await expect(comment).toHaveValue("Сохранить ввод") await expect(page.getByText(kind === "rule" ? editing ? "Правило сохранено" : "Правило создано" : editing ? "Запись сохранена" : "Запись создана", { exact: true })).toHaveCount(0) } await submit.click() await expect(dialog).toHaveCount(0) expect(attempts).toBe(4) await open() await expect(dialog.getByRole("alert")).toHaveCount(0) }) } } }