import { test, expect } from "@playwright/test" const device = { id: 1, name: "Роутер 1", host: "192.0.2.1", enabled: true, country: "RU", status: null } const iface = { id: "*1", serverId: 1, serverName: "Роутер 1", serverSite: "", serverCountry: "RU", instance: "default", area: "backbone", areaId: "0.0.0.0", interface: "ether1", cost: 20, type: "broadcast", disabled: false, inactive: false, priority: 1, helloInterval: 10, deadInterval: 40, useBfd: false } const catalog = { neighbors: [], interfaces: [iface], instances: [], bfdSessions: [], routes: [], failures: [] } const result = { serverId: 1, serverName: "Роутер 1", pingWeight: 60, optimizedCount: 1, applied: [{ interface: "ether1", from: 20, to: 10 }], interfaces: [{ id: "*1", interface: "ether1", currentCost: 20, optimalCost: 10, score: 1, pingMs: 10, dlMbps: 100, ulMbps: 100 }] } for (const outcome of ["success", "http", "foreign", "malformed", "poll-failure"]) { test("OSPF: оптимизация, общий lock и результат " + outcome, async ({ page }) => { let writes = 0, polled = false, reads = 0 let release: (() => void) | undefined const gate = new Promise(resolve => { release = resolve }) await page.route("**/health", r => r.fulfill({ json: { ok: true } })) await page.route("**/api/**", async r => { const path = new URL(r.request().url()).pathname if (path === "/api/servers/1/ospf/optimize") { writes++ expect(r.request().method()).toBe("POST") expect(r.request().postDataJSON().pingWeight).toBeGreaterThanOrEqual(0) await gate polled = true return r.fulfill(outcome === "http" ? { status: 503, json: { error: "Применение не завершено" } } : { json: outcome === "foreign" ? { ...result, serverId: 2 } : outcome === "malformed" ? { ...result, optimizedCount: 99 } : result }) } if (path === "/api/ospf/catalog") { reads++ if (polled && outcome === "poll-failure") return r.fulfill({ status: 503, json: { error: "Опрос после записи недоступен" } }) return r.fulfill({ json: catalog }) } return r.fulfill({ json: path === "/api/servers" ? [device] : path === "/api/auth/config" ? { required: false } : {} }) }) await page.goto("/ospf") await page.getByRole("option").filter({ hasText: "Роутер 1" }).click() const optimize = page.getByRole("button", { name: "Оптимизировать OSPF", exact: true }) await expect(optimize).toBeEnabled() await expect(page.getByText("opt 10", { exact: true })).toHaveCount(0) await optimize.evaluate(el => { (el as HTMLButtonElement).click(); (el as HTMLButtonElement).click() }) await expect.poll(() => writes).toBe(1) await expect(page.getByRole("button", { name: "Оптимизация OSPF…", exact: true })).toBeDisabled() await expect(page.getByRole("button", { name: "Обновить", exact: true })).toBeDisabled() await page.getByRole("button", { name: "Обновить", exact: true }).evaluate(el => (el as HTMLButtonElement).click()) expect(reads).toBe(1) release!() await expect.poll(() => reads).toBe(2) if (["http", "foreign", "malformed"].includes(outcome)) { await expect(page.getByRole("alert").filter({ hasText: "Оптимизация OSPF не подтверждена" })).toBeVisible() await expect(page.getByText("API подтвердил оптимизацию OSPF", { exact: false })).toHaveCount(0) } else { await expect(page.getByText("API подтвердил оптимизацию OSPF: 1 интерфейсов", { exact: true })).toBeVisible() await expect(page.getByRole("group", { name: "Свежесть OSPF" }).locator('[data-ospf-time="write"]')).toContainText("API подтвердил оптимизацию устройства 1") } if (outcome === "poll-failure") { await expect(page.getByRole("alert").filter({ hasText: "Опрос после записи недоступен" })).toBeVisible() await expect(optimize).toBeDisabled() } else await expect(optimize).toBeEnabled() expect(writes).toBe(1) }) } test("OSPF: read-права не разрешают оптимизацию устройства", async ({ page }) => { const payload = { sub: "test", email: "test@example.test", name: "Reader", apps: ["mm"], permissions: ["mm:network:read", "mm:servers:read"], exp: 4102444800 } await page.addInitScript(value => localStorage.setItem("mmapp_token", "test." + btoa(JSON.stringify(value)) + ".test"), payload) let writes = 0 await page.route("**/health", r => r.fulfill({ json: { ok: true } })) await page.route("**/api/**", r => { const path = new URL(r.request().url()).pathname if (r.request().method() === "POST") writes++ return r.fulfill({ json: path === "/api/auth/config" ? { required: true } : path === "/api/servers" ? [device] : path === "/api/ospf/catalog" ? catalog : {} }) }) await page.goto("/ospf") await page.getByRole("option").filter({ hasText: "Роутер 1" }).click() await expect(page.getByRole("button", { name: "Оптимизировать OSPF", exact: true })).toBeDisabled() expect(writes).toBe(0) }) for (const permission of ["mm:network:write", "mm:servers:write"]) { test("OSPF: право оптимизации совпадает с backend — " + permission, async ({ page }) => { await page.setViewportSize({ width: 320, height: 1000 }) const payload = { sub: "test", apps: ["mm"], permissions: ["mm:network:read", "mm:servers:read", permission], exp: 4102444800 } await page.addInitScript(value => localStorage.setItem("mmapp_token", "test." + btoa(JSON.stringify(value)) + ".test"), payload) let writes = 0 await page.route("**/health", r => r.fulfill({ json: { ok: true } })) await page.route("**/api/**", r => { const path = new URL(r.request().url()).pathname if (path === "/api/servers/1/ospf/optimize") { writes++; return r.fulfill({ json: result }) } return r.fulfill({ json: path === "/api/auth/config" ? { required: true } : path === "/api/servers" ? [device] : path === "/api/ospf/catalog" ? catalog : {} }) }) await page.goto("/ospf?server=1&tab=interfaces") const button = page.getByRole("button", { name: "Оптимизировать OSPF", exact: true }) if (permission === "mm:network:write") { await expect(button).toBeEnabled(); await button.click() await expect.poll(() => writes).toBe(1) await expect(page.getByText("API подтвердил оптимизацию OSPF: 1 интерфейсов", { exact: true })).toBeVisible() } else { await expect(button).toBeDisabled() await button.evaluate(el => (el as HTMLButtonElement).click()) expect(writes).toBe(0) } expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true) }) }