import { test, expect } from "@playwright/test" const summary = (id: string) => ({ serverId: id, serverName: "Роутер " + id, initialized: false, ike2Ready: false, natRuleManaged: false, clientsTotal: 1, clientsOnline: 0, peers: [] }) const client = (id: string, name = "Клиент " + id) => ({ id: id + "-*1", rosId: "*1", serverId: id, serverName: "Роутер " + id, name, authMethod: "certificate", online: false, disabled: false, managed: true }) const servers = [1, 2].map(id => ({ id, name: "Роутер " + id, host: "192.0.2." + id, enabled: true, country: "RU", type: "exit-node", status: "online" })) const full = { servers: [summary("1"), summary("2")], clients: [client("1"), client("2")] } for (const width of [1280, 320]) { test(`IPsec: несколько сессий одного клиента без произвольного адреса ${width}px`, async ({ page }) => { await page.setViewportSize({ width, height: 1000 }) let phase = 0 await page.route("**/health", r => r.fulfill({ json: { ok: true } })) await page.route("**/api/**", r => { const path = new URL(r.request().url()).pathname return r.fulfill({ json: path === "/api/ipsec" ? { servers: [{ ...summary("1"), clientsOnline: phase === 2 ? 0 : 1 }], clients: [{ ...client("1"), online: phase !== 2, ...(phase === 1 ? { activeAddress: "198.18.113.250", activeSince: "1m" } : {}) }], } : path === "/api/servers" ? servers : path === "/api/auth/config" ? { required: false } : {} }) }) await page.goto("/ipsec?server=1") const rows = page.locator("tr[data-row-id]") await expect(rows.getByText("онлайн", { exact: true })).toHaveCount(1) await expect(rows.getByText("офлайн", { exact: true })).toHaveCount(0) await expect(rows.getByText("Не определено", { exact: true })).toHaveCount(0) await expect(page.getByLabel("Сводка IPsec")).not.toContainText("—") expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true) phase = 1 await page.getByRole("button", { name: "Обновить", exact: true }).click() await expect(rows.getByText("198.18.113.250", { exact: true })).toHaveCount(1) phase = 2 await page.getByRole("button", { name: "Обновить", exact: true }).click() await expect(rows.getByText("офлайн", { exact: true })).toHaveCount(1) await expect(rows.getByText("198.18.113.250", { exact: true })).toHaveCount(0) }) test(`IPsec: неоднозначная сессия и восстановление статуса ${width}px`, async ({ page }) => { await page.setViewportSize({ width, height: 1000 }) let phase = 0 const rows = [client("1", "Сертификат"), { ...client("1", "PSK"), id: "1-*2", rosId: "*2", kind: "psk", authMethod: "pre-shared-key" }] await page.route("**/health", r => r.fulfill({ json: { ok: true } })) await page.route("**/api/**", r => { const path = new URL(r.request().url()).pathname const values = phase === 0 ? rows.map(c => ({ ...c, sessionAmbiguous: true })) : [ { ...rows[0], online: true, activeAddress: "198.18.112.250", ...(phase === 2 ? { sessionAmbiguous: true } : {}) }, rows[1], ] return r.fulfill({ json: path === "/api/ipsec" ? { servers: [{ ...summary("1"), clientsTotal: 2, clientsOnline: phase === 0 ? 0 : 1, ...(phase === 0 ? { clientsSessionAmbiguous: 2 } : {}) }], clients: values } : path === "/api/servers" ? servers : path === "/api/auth/config" ? { required: false } : {} }) }) await page.goto("/ipsec?server=1") const gridRows = page.locator("tr[data-row-id]") const ambiguity = page.getByRole("alert").filter({ hasText: "Статус части клиентов не определён" }) await expect(ambiguity).toContainText("У 2 клиентов") await expect(gridRows).toHaveCount(2) await expect(gridRows.getByText("Не определено", { exact: true })).toHaveCount(2) await expect(gridRows.getByText("офлайн", { exact: true })).toHaveCount(0) await expect(page.getByLabel("Сводка IPsec")).toContainText("—") await page.getByRole("tab", { name: "Сервер", exact: true }).click() await expect(page.getByText("0 подтверждено онлайн / 2; статус 2 не определён", { exact: true })).toBeVisible() await page.getByRole("tab", { name: "Клиенты", exact: true }).click() expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true) phase = 1 await page.getByRole("button", { name: "Обновить", exact: true }).click() await expect(ambiguity).toHaveCount(0) await expect(gridRows.getByText("офлайн", { exact: true })).toHaveCount(1) await expect(gridRows.getByText("198.18.112.250", { exact: true })).toHaveCount(1) phase = 2 await page.getByRole("button", { name: "Обновить", exact: true }).click() await expect(page.getByRole("alert").filter({ hasText: "Не удалось загрузить IPsec" })).toBeVisible() await expect(gridRows.getByText("198.18.112.250", { exact: true })).toHaveCount(1) }) } for (const broken of ["http", "payload", "duplicate", "conflict", "peer-context", "pool-context", "blank-failure"]) { test(`IPsec: неизвестное начало и сохранение данных при ${broken}`, async ({ page }) => { let phase = 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/ipsec") { if (phase === 0) return r.fulfill({ status: 503, json: { error: "Начальный отказ IPsec" } }) if (phase === 1 || phase === 3) return r.fulfill({ json: full }) return broken === "http" ? r.fulfill({ status: 503, json: { error: "Повторный отказ IPsec" } }) : r.fulfill({ json: broken === "payload" ? { servers: [], clients: [{}] } : broken === "duplicate" ? { ...full, clients: [client("1"), client("1")] } : broken === "peer-context" ? { ...full, servers: [{ ...summary("1"), peers: [{ id: "1-*p1", rosId: "*p1", serverId: "2", serverName: "Чужой роутер", name: "peer", managed: true, disabled: false }] }, summary("2")] } : broken === "pool-context" ? { ...full, servers: [{ ...summary("1"), pool: { id: "1-*p1", rosId: "*p1", serverId: "2", name: "pool", ranges: "10.0.0.1-10.0.0.20", managed: true } }, summary("2")] } : broken === "blank-failure" ? { servers: [], clients: [], failures: [{ serverId: "", error: "Неизвестный отказ" }] } : { ...full, failures: [{ serverId: "1", error: "Сбой устройства" }] } }) } return r.fulfill({ json: path === "/api/servers" ? servers : path === "/api/auth/config" ? { required: false } : {} }) }) await page.goto("/ipsec") const alert = page.getByRole("alert").filter({ hasText: "Не удалось загрузить IPsec" }) await expect(alert).toContainText("Данные пока неизвестны") await expect(page.getByText("Нет клиентов IKEv2", { exact: true })).toHaveCount(0) await expect(page.locator("tr[data-row-id]")).toHaveCount(0) await expect(page.getByLabel("Сводка IPsec")).toContainText("неизвестно") phase = 1 await alert.getByRole("button", { name: "Повторить загрузку IPsec" }).click() await expect(page.locator("tr[data-row-id]")).toHaveCount(2) phase = 2 await page.getByRole("button", { name: "Обновить", exact: true }).click() await expect(alert).toContainText("Показаны последние полученные данные") await expect(page.locator("tr[data-row-id]")).toHaveCount(2) await expect(page.getByLabel("Сводка IPsec")).toContainText("неизвестно") phase = 3 await alert.getByRole("button", { name: "Повторить загрузку IPsec" }).click() await expect(alert).toHaveCount(0) await expect(page.locator("tr[data-row-id]")).toHaveCount(2) }) } test("IPsec: частичный отказ сохраняет строки роутера, восстановление удаляет устаревшие", async ({ page }) => { let phase = 0 await page.route("**/health", r => r.fulfill({ json: { ok: true } })) await page.route("**/api/**", r => { const path = new URL(r.request().url()).pathname return r.fulfill({ json: path === "/api/ipsec" ? phase === 0 ? full : phase === 1 ? { servers: [summary("1")], clients: [client("1", "Обновлённый клиент")], failures: [{ serverId: "2", serverName: "Роутер 2", error: "Таймаут тест" }], } : { servers: [summary("1"), { ...summary("2"), clientsTotal: 0 }], clients: [client("1", "Обновлённый клиент")] } : path === "/api/servers" ? servers : path === "/api/auth/config" ? { required: false } : {} }) }) await page.goto("/ipsec") const rows = page.locator("tr[data-row-id]") await expect(rows).toHaveCount(2) phase = 1 await page.getByRole("button", { name: "Обновить", exact: true }).click() await expect(page.getByRole("alert").filter({ hasText: "Не удалось опросить часть роутеров" })).toContainText("Роутер 2: Таймаут тест") await expect(rows).toHaveCount(2) await expect(rows.filter({ hasText: "Обновлённый клиент" })).toHaveCount(1) await expect(rows.filter({ hasText: "Клиент 2" })).toHaveCount(1) await expect(page.getByLabel("Сводка IPsec")).toContainText("неизвестно") phase = 2 await page.getByRole("button", { name: "Обновить", exact: true }).click() await expect(page.getByRole("alert").filter({ hasText: "Не удалось опросить часть роутеров" })).toHaveCount(0) await expect(rows).toHaveCount(1) await expect(rows.filter({ hasText: "Клиент 2" })).toHaveCount(0) }) test("IPsec: ошибка списка устройств не стирает успешный опрос", async ({ page }) => { 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") return r.fulfill({ status: 503, json: { error: "Устройства недоступны тест" } }) return r.fulfill({ json: path === "/api/ipsec" ? full : path === "/api/auth/config" ? { required: false } : {} }) }) await page.goto("/ipsec") await expect(page.getByRole("alert").filter({ hasText: "Не удалось загрузить устройства" })).toBeVisible() await expect(page.locator("tr[data-row-id]")).toHaveCount(2) })