fix(ui): защищать оптимизацию OSPF и подтверждение результата
This commit is contained in:
1 parent
4254904c3a
commit
baa43b7ad4
8 files changed
+193
-42
No files matched your search
@@ -0,0 +1,52 @@
|
||||
import { test, expect } from "@playwright/test"
|
||||
const headers = { "access-control-allow-origin": "*", "access-control-allow-methods": "GET,POST,OPTIONS", "access-control-allow-headers": "Content-Type,Authorization" }
|
||||
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 change of ["backend", "mode"]) for (const operation of ["read", "write"]) for (const fails of [false, true]) {
|
||||
test('OSPF: поздний ' + operation + ' после смены ' + change + '/' + fails, async ({ page }) => {
|
||||
let newReads = 0
|
||||
await page.route("**/health", r => r.fulfill({ headers, json: { ok: true } }))
|
||||
await page.route("**/api/**", r => {
|
||||
const url = new URL(r.request().url()), path = url.pathname
|
||||
if (path === "/api/ospf/catalog" && url.port === "8101") newReads++
|
||||
return r.fulfill({ headers, json: path === "/api/ospf/catalog" ? { ...catalog, interfaces: [{ ...iface, interface: url.port === "8101" ? "Новый интерфейс" : "Старый интерфейс" }] } : path === "/api/servers" ? [device] : path === "/api/auth/config" ? { required: false } : {} })
|
||||
})
|
||||
await page.goto("/settings")
|
||||
await page.getByRole("link", { name: /^OSPF/ }).click()
|
||||
await expect(page.getByText("Старый интерфейс", { exact: true })).toBeVisible()
|
||||
await page.getByRole("option").filter({ hasText: "Роутер 1" }).click()
|
||||
let release: (() => void) | undefined, started = false
|
||||
const gate = new Promise<void>(resolve => { release = resolve })
|
||||
const endpoint = operation === "read" ? "/api/ospf/catalog" : "/api/servers/1/ospf/optimize"
|
||||
await page.route("**" + endpoint, async r => {
|
||||
if (new URL(r.request().url()).port !== "8000") return r.fallback()
|
||||
started = true
|
||||
await gate
|
||||
return r.fulfill(fails ? { headers, status: 503, json: { error: "Старая ошибка OSPF" } } : { headers, json: operation === "read" ? { ...catalog, interfaces: [] } : result })
|
||||
})
|
||||
await page.getByRole("button", { name: operation === "read" ? "Обновить" : "Оптимизировать OSPF", exact: true }).click()
|
||||
await expect.poll(() => started).toBe(true)
|
||||
await page.goBack()
|
||||
await expect(page).toHaveURL(/settings/)
|
||||
if (change === "backend") {
|
||||
const input = page.getByPlaceholder("http://localhost:8000")
|
||||
await input.fill("http://localhost:8101")
|
||||
await input.press("Enter")
|
||||
} else await page.getByRole("button", { name: "Моковые", exact: true }).click()
|
||||
await page.getByRole("link", { name: /^OSPF/ }).click()
|
||||
if (change === "backend") await expect(page.getByText("Новый интерфейс", { exact: true })).toBeVisible()
|
||||
else await expect(page.getByText("Моковые данные", { exact: true })).toBeVisible()
|
||||
const before = newReads
|
||||
const response = page.waitForResponse(r => r.url().includes(":8000" + endpoint))
|
||||
release!()
|
||||
await (await response).finished()
|
||||
await page.evaluate(() => new Promise<void>(resolve => requestAnimationFrame(() => requestAnimationFrame(() => resolve()))))
|
||||
if (change === "backend") await expect(page.getByText("Новый интерфейс", { exact: true })).toBeVisible()
|
||||
await expect(page.getByText("Старая ошибка OSPF", { exact: false })).toHaveCount(0)
|
||||
await expect(page.getByText("API подтвердил оптимизацию OSPF", { exact: false })).toHaveCount(0)
|
||||
expect(newReads).toBe(before)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
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<void>(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()
|
||||
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)
|
||||
})
|
||||
Reference in new issue
Block a user