Files
MikrotikManager/tests/e2e/recursive-routes-data.spec.ts
T

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)
})