130 lines
11 KiB
TypeScript
130 lines
11 KiB
TypeScript
import { test, expect } from "@playwright/test"
|
|
const routeRow = (id = "a", destination = "10.0.1.0/24", table = "main") => ({
|
|
id, dstAddress: destination, gateway: "192.0.2.1", distance: 1, scope: 30, targetScope: 10,
|
|
routingTable: table, checkGateway: "ping", comment: "Сохранённый маршрут", disabled: false, country: "RU",
|
|
})
|
|
const servers = [1,2].map(id => ({ id, name: "Роутер " + id, host: "192.0.2." + id, enabled: true, country: "RU", status: null }))
|
|
for (const result of ["success", "http", "false", "missing", "foreign", "count"] as const) {
|
|
test("Рекурсивные маршруты: подтверждение записи и форма " + result, async ({ page }) => {
|
|
let attempts = 0, saved = [routeRow()]
|
|
let release!: () => void; const gate = new Promise<void>(resolve => { release = resolve })
|
|
await page.route("**/health", r => r.fulfill({ json: { ok: true } }))
|
|
await page.route("**/api/**", async r => {
|
|
const u = new URL(r.request().url()), p = u.pathname
|
|
if (p === "/api/recursive-routes" && r.request().method() === "PUT") {
|
|
attempts++; const body = r.request().postDataJSON()
|
|
expect(body.serverId).toBe("1")
|
|
await gate
|
|
if (attempts === 1 && result !== "success") return r.fulfill(result === "http" ? { status: 403, json: { error: "Запись запрещена тест" } }
|
|
: { json: result === "missing" ? {} : { ok: result !== "false", serverId: result === "foreign" ? 2 : 1, routes: body.routes, pushedRoutes: result === "count" ? 0 : body.routes.length } })
|
|
saved = body.routes; return r.fulfill({ json: { ok: true, serverId: 1, routes: saved, pushedRoutes: saved.length } })
|
|
}
|
|
return r.fulfill({ json: p === "/api/servers" ? servers : p === "/api/auth/config" ? { required: false }
|
|
: p === "/api/recursive-routes" ? { serverId: Number(u.searchParams.get("serverId")), routes: saved, live: true, stale: false }
|
|
: p === "/api/recursive-routes/gateways" ? { serverId: Number(u.searchParams.get("serverId")), gateways: [] } : {} })
|
|
})
|
|
await page.goto("/recursive-routes")
|
|
await expect(page.locator("tr[data-row-id]")).toHaveCount(1)
|
|
await page.getByRole("button", { name: "Добавить", exact: true }).click()
|
|
const sheet = page.getByRole("dialog")
|
|
await sheet.getByPlaceholder("8.8.8.8/32").fill("203.0.113.0/24")
|
|
await sheet.getByPlaceholder("1.2.3.4%GW-NAME").fill("192.0.2.9")
|
|
await sheet.getByRole("button", { name: "Добавить", exact: true }).evaluate(el => { (el as HTMLButtonElement).click(); (el as HTMLButtonElement).click() })
|
|
await expect.poll(() => attempts).toBe(1)
|
|
await expect(sheet.getByRole("status")).toContainText("Ожидание подтверждения")
|
|
await expect(sheet.getByRole("button", { name: "Отмена", exact: true })).toBeDisabled()
|
|
await expect(sheet.getByPlaceholder("8.8.8.8/32")).toBeDisabled()
|
|
await page.keyboard.press("Escape"); await expect(sheet).toBeVisible()
|
|
await expect(page.locator("tr[data-row-id]")).toHaveCount(1)
|
|
release()
|
|
if (result !== "success") {
|
|
await expect(sheet.getByRole("alert")).toContainText(result === "http" ? "Запись запрещена тест" : "Сервер не подтвердил")
|
|
await expect(sheet.getByPlaceholder("8.8.8.8/32")).toHaveValue("203.0.113.0/24")
|
|
await expect(page.getByText("Маршруты применены на роутер", { exact: true })).toHaveCount(0)
|
|
await expect(page.locator("tr[data-row-id]")).toHaveCount(1)
|
|
await sheet.getByRole("button", { name: "Добавить", exact: true }).click()
|
|
}
|
|
await expect(sheet).toHaveCount(0); await expect(page.locator("tr[data-row-id]")).toHaveCount(2)
|
|
await expect(page.getByRole("status").filter({ hasText: "Запись подтверждена" })).toBeVisible()
|
|
})
|
|
}
|
|
for (const bad of ["http", "foreign", "payload", "duplicate", "stale"] as const) test("Рекурсивные маршруты: неизвестность, кэш и повтор " + bad, async ({ page }) => {
|
|
let phase = 0
|
|
await page.route("**/health", r => r.fulfill({ json: { ok: true } }))
|
|
await page.route("**/api/**", r => {
|
|
const p = new URL(r.request().url()).pathname
|
|
if (p === "/api/recursive-routes") {
|
|
if (phase !== 1 && bad === "http") return r.fulfill({ status: 503, json: { error: "Чтение недоступно тест" } })
|
|
const valid = { serverId: 1, routes: phase === 0 ? [] : [routeRow()], live: true, stale: false }
|
|
return r.fulfill({ json: phase === 1 ? valid : bad === "foreign" ? { ...valid, serverId: 2 } : bad === "payload" ? { ...valid, routes: [{}] }
|
|
: bad === "duplicate" ? { ...valid, routes: [routeRow(), routeRow()] } : { ...valid, live: false, stale: true, error: "Кэш тест" } })
|
|
}
|
|
return r.fulfill({ json: p === "/api/servers" ? [servers[0]] : p === "/api/auth/config" ? { required: false } : p === "/api/recursive-routes/gateways" ? { serverId: 1, gateways: [] } : {} })
|
|
})
|
|
await page.goto("/recursive-routes")
|
|
await expect(page.getByRole("button", { name: "Добавить", exact: true })).toBeDisabled()
|
|
await expect(page.getByText("Нет маршрутов", { exact: true })).toHaveCount(0)
|
|
phase = 1; await page.getByRole("button", { name: "Обновить", exact: true }).click()
|
|
await expect(page.locator("tr[data-row-id]")).toHaveCount(1)
|
|
await expect(page.getByRole("button", { name: "Добавить", exact: true })).toBeEnabled()
|
|
phase = 2; await page.getByRole("button", { name: "Обновить", exact: true }).click()
|
|
await expect(page.getByRole("button", { name: "Добавить", exact: true })).toBeDisabled()
|
|
await expect(page.locator("tr[data-row-id]")).toHaveCount(1)
|
|
await expect(page.getByRole("status").filter({ hasText: "Успешный ответ получен" })).toBeVisible()
|
|
})
|
|
test("Рекурсивные маршруты: чтение прежнего устройства не заменяет новый срез", async ({ page }) => {
|
|
let release!: () => void; const gate = new Promise<void>(resolve => { release = resolve }); let requested = false
|
|
await page.route("**/health", r => r.fulfill({ json: { ok: true } }))
|
|
await page.route("**/api/**", async r => {
|
|
const u = new URL(r.request().url()), p = u.pathname, id = Number(u.searchParams.get("serverId"))
|
|
if (p === "/api/recursive-routes") { if (id === 1) { requested = true; await gate }
|
|
return r.fulfill({ json: { serverId: id, routes: [routeRow("r" + id, "10.0." + id + ".0/24")], live: true, stale: false } }) }
|
|
return r.fulfill({ json: p === "/api/servers" ? servers : p === "/api/auth/config" ? { required: false } : p === "/api/recursive-routes/gateways" ? { serverId: id, gateways: [] } : {} })
|
|
})
|
|
await page.goto("/recursive-routes"); await expect.poll(() => requested).toBe(true)
|
|
await page.getByRole("option").filter({ hasText: "Роутер 2" }).click()
|
|
await expect(page.locator("tr[data-row-id]")).toContainText("10.0.2.0/24")
|
|
release(); await expect(page.getByRole("button", { name: "Обновить", exact: true })).toBeEnabled()
|
|
await expect(page.locator("tr[data-row-id]")).not.toContainText("10.0.1.0/24")
|
|
})
|
|
test("Рекурсивные маршруты: редактирование поиска сохраняет скрытые endpoints и таблицы", async ({ page }) => {
|
|
let saved: unknown; const rows = [{ ...routeRow(), gateway: "192.0.2.11" }, { ...routeRow("b"), gateway: "192.0.2.22" }, { ...routeRow("c", "10.0.1.0/24", "vpn"), gateway: "192.0.2.33" }]
|
|
await page.route("**/health", r => r.fulfill({ json: { ok: true } }))
|
|
await page.route("**/api/**", r => {
|
|
const p = new URL(r.request().url()).pathname
|
|
if (p === "/api/recursive-routes" && r.request().method() === "PUT") { const next = r.request().postDataJSON().routes; saved = next; return r.fulfill({ json: { ok: true, serverId: 1, routes: next, pushedRoutes: next.length } }) }
|
|
return r.fulfill({ json: p === "/api/servers" ? [servers[0]] : p === "/api/auth/config" ? { required: false } : p === "/api/recursive-routes" ? { serverId: 1, routes: rows, live: true, stale: false } : p === "/api/recursive-routes/gateways" ? { serverId: 1, gateways: [] } : {} })
|
|
})
|
|
await page.goto("/recursive-routes"); await expect(page.locator("tr[data-row-id]")).toHaveCount(2)
|
|
await page.getByRole("textbox", { name: "Поиск маршрутов" }).fill("192.0.2.11")
|
|
await expect(page.locator("tr[data-row-id]")).toHaveCount(1)
|
|
await page.getByRole("button", { name: "Редактировать маршрут 10.0.1.0/24 · main", exact: true }).click()
|
|
const sheet = page.getByRole("dialog")
|
|
await expect(sheet.getByPlaceholder("1.2.3.4%GW-NAME")).toHaveCount(2)
|
|
await sheet.getByRole("button", { name: "Сохранить", exact: true }).click(); await expect(sheet).toHaveCount(0)
|
|
expect(saved).toEqual(expect.arrayContaining([expect.objectContaining({ routingTable: "main", gateway: "192.0.2.11" }), expect.objectContaining({ routingTable: "main", gateway: "192.0.2.22" }), expect.objectContaining({ routingTable: "vpn", gateway: "192.0.2.33" })]))
|
|
expect(saved).toHaveLength(3)
|
|
})
|
|
|
|
|
|
for (const allowed of [false, true]) test("Рекурсивные маршруты: права и disabled устройство " + allowed, async ({ page }) => {
|
|
const claims = { sub: "test", email: "[email protected]", name: "Reader", apps: ["mm"], permissions: ["mm:network:read", ...(allowed ? ["mm:network:write"] : [])], exp: 4102444800 }
|
|
await page.addInitScript(value => localStorage.setItem("mmapp_token", "test." + btoa(JSON.stringify(value)) + ".test"), claims)
|
|
let writes = 0
|
|
await page.route("**/health", r => r.fulfill({ json: { ok: true } }))
|
|
await page.route("**/api/**", r => {
|
|
const p = new URL(r.request().url()).pathname
|
|
if (r.request().method() !== "GET") writes++
|
|
return r.fulfill({ json: p === "/api/auth/config" ? { required: true } : p === "/api/servers" ? [{ ...servers[0], enabled: !allowed }]
|
|
: p === "/api/recursive-routes" ? { serverId: 1, routes: [routeRow()], live: true, stale: false }
|
|
: p === "/api/recursive-routes/gateways" ? { serverId: 1, gateways: [] }
|
|
: p.endsWith("/revisions") ? { revisions: [{ id: "rev", serverId: "1", section: "recursive-routes", source: "apply", fingerprint: "fixture", createdAt: "2026-09-30T00:00:00Z", note: null, itemCount: 1 }] } : {} })
|
|
})
|
|
await page.goto("/recursive-routes"); await expect(page.locator("tr[data-row-id]")).toHaveCount(1)
|
|
await expect(page.getByRole("button", { name: "Добавить", exact: true })).toBeDisabled()
|
|
await expect(page.getByRole("button", { name: "Редактировать маршрут 10.0.1.0/24 · main", exact: true })).toBeDisabled()
|
|
await page.getByRole("button", { name: "История", exact: true }).click()
|
|
await expect(page.getByRole("dialog").getByRole("button", { name: "Откатить", exact: true })).toBeDisabled()
|
|
expect(writes).toBe(0)
|
|
})
|