59 lines
9.4 KiB
TypeScript
59 lines
9.4 KiB
TypeScript
import { test, expect, type Page } from "@playwright/test"
|
|
import { geoip } from "./fixtures/geoip"
|
|
async function setup(page: 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/auth/config") return r.fulfill({ json: { required: false } })
|
|
if (path === "/api/geoip") return r.fulfill({ json: geoip })
|
|
if (path === "/api/scheduler/status") return r.fulfill({ json: { jobs: [] } })
|
|
if (path === "/api/scheduler/runs") return r.fulfill({ json: { runs: [] } })
|
|
return r.fulfill({ status: 503, json: { error: "outside GeoIP fixture" } })
|
|
})
|
|
}
|
|
test("GeoIP: unknown блокирует действия и не выдаётся за пустую базу", async ({ page }) => {
|
|
await setup(page); await page.route("**/api/geoip", r => r.fulfill({ json: {} })); await page.goto("/data-collection")
|
|
await expect(page.getByText("GeoIP не получен", { exact: true })).toBeVisible(); await expect(page.getByText("Country: —", { exact: true })).toBeVisible(); await expect(page.getByRole("button", { name: "Сохранить GeoIP", exact: true })).toBeDisabled(); await expect(page.getByRole("button", { name: "Обновить сейчас", exact: true })).toBeDisabled()
|
|
})
|
|
test("GeoIP: сохранение проверяет receipt и отправляет фактический интервал", async ({ page }) => {
|
|
await setup(page); const writes: object[] = []
|
|
await page.route("**/api/geoip", r => { if (r.request().method() !== "PUT") return r.fallback(); const patch = r.request().postDataJSON(); writes.push(patch); return r.fulfill({ json: { ok: true, status: { ...geoip, settings: { ...geoip.settings, ...patch } } } }) })
|
|
await page.goto("/data-collection"); const field = page.getByRole("textbox", { name: "Интервал GeoIP в часах", exact: true }); await expect(field).toHaveValue("168"); await field.fill("168.5"); await page.getByRole("button", { name: "Сохранить GeoIP", exact: true }).click()
|
|
await expect(page.getByText("Сохранение настроек GeoIP подтверждено", { exact: true })).toBeVisible(); await expect(page.getByRole("button", { name: "Сохранить GeoIP", exact: true })).toBeDisabled(); expect(writes).toEqual([{ enabled: false, updateIntervalSec: 606600 }])
|
|
})
|
|
test("GeoIP: некорректный ввод не заменяется defaults", async ({ page }) => {
|
|
await setup(page); let writes = 0; page.on("request", r => { if (r.method() === "PUT") writes++ }); await page.goto("/data-collection")
|
|
const field = page.getByRole("textbox", { name: "Интервал GeoIP в часах", exact: true }); await expect(field).toHaveValue("168"); await field.fill("invalid"); await page.getByRole("button", { name: "Сохранить GeoIP", exact: true }).click(); await expect(page.getByText("Укажите интервал от 6 до 720 часов с точностью до секунды", { exact: true })).toBeVisible(); expect(writes).toBe(0)
|
|
})
|
|
test("GeoIP: фоновая ошибка сохраняет известные данные и время", async ({ page }) => {
|
|
await setup(page); let fails = false; await page.route("**/api/geoip", r => r.fulfill(fails ? { status: 503, json: { error: "GeoIP unavailable" } } : { json: geoip })); await page.goto("/data-collection")
|
|
const stamp = page.getByRole("status").filter({ hasText: /^Статус GeoIP получен:/ }); await expect(stamp).toBeVisible(); const before = await stamp.textContent(); fails = true; await page.getByRole("button", { name: "Обновить статус GeoIP", exact: true }).click(); await expect(page.getByText("GeoIP не обновлён", { exact: true })).toBeVisible(); await expect(stamp).toHaveText(before!); await expect(page.getByRole("textbox", { name: "Интервал GeoIP в часах", exact: true })).toHaveValue("168")
|
|
})
|
|
test("GeoIP: partial update сохраняет скачанные данные, ошибку и dirty", async ({ page }) => {
|
|
await setup(page); let posts = 0; await page.route("**/api/geoip/update", r => { posts++; return r.fulfill({ json: { ok: false, snapshot: { v: 1, job: "geoip_update", sampledAt: "2026-10-03T00:00:00Z", checked: 2, downloaded: 1, skippedUnchanged: 0, bytes: 1024, errors: ["ASN unavailable"] } } }) }); await page.goto("/data-collection")
|
|
const field = page.getByRole("textbox", { name: "Интервал GeoIP в часах", exact: true }); await expect(field).toHaveValue("168"); await field.fill("180"); await page.getByRole("button", { name: "Обновить сейчас", exact: true }).click(); await expect(page.getByRole("status").filter({ hasText: "Обновление GeoIP завершилось частично или с ошибками" })).toContainText("скачано: 1"); await expect(page.getByText("ASN unavailable", { exact: true })).toBeVisible(); await expect(field).toHaveValue("180"); expect(posts).toBe(1)
|
|
})
|
|
test("GeoIP: malformed POST не вызывает успех или повтор", async ({ page }) => {
|
|
await setup(page); let posts = 0; await page.route("**/api/geoip/update", r => { posts++; return r.fulfill({ json: {} }) }); await page.goto("/data-collection"); const run = page.getByRole("button", { name: "Обновить сейчас", exact: true }); await expect(run).toBeEnabled(); await run.click(); await expect(page.getByText(/Результат операции неизвестен; обновите статус перед повтором/)).toBeVisible(); expect(posts).toBe(1); await expect(run).toBeDisabled(); await expect(page.getByRole("status").filter({ hasText: "Обновление GeoIP подтверждено" })).toHaveCount(0)
|
|
await page.getByRole("button", { name: "Разрешить новое обновление GeoIP", exact: true }).click(); await expect(run).toBeEnabled(); expect(posts).toBe(1)
|
|
})
|
|
test("GeoIP: read-only claims не разрешают PUT и POST", async ({ page }) => {
|
|
const token = "header." + Buffer.from(JSON.stringify({ sub: "fixture", apps: ["mm"], permissions: ["mm:dashboard:read", "mm:traffic:read"], exp: Math.floor(Date.now()/1000)+3600 })).toString("base64url") + ".fixture"
|
|
await page.addInitScript(value => localStorage.setItem("mmapp_token", value), token); await setup(page); await page.route("**/api/auth/config", r => r.fulfill({ json: { required: true } })); await page.goto("/data-collection"); await expect(page.getByRole("status").filter({ hasText: /^Статус GeoIP получен:/ })).toBeVisible(); await expect(page.getByRole("switch", { name: "Автообновление GeoIP", exact: true })).toBeDisabled(); await expect(page.getByRole("button", { name: "Обновить сейчас", exact: true })).toBeDisabled()
|
|
})
|
|
|
|
test("GeoIP: неизвестный PUT сохраняет черновик без повторов", async ({ page }) => {
|
|
await setup(page); let writes = 0; await page.route("**/api/geoip", r => { if (r.request().method() !== "PUT") return r.fallback(); writes++; return r.fulfill({ json: {} }) }); await page.goto("/data-collection")
|
|
const field = page.getByRole("textbox", { name: "Интервал GeoIP в часах", exact: true }); await expect(field).toHaveValue("168"); await field.fill("180"); await page.getByRole("button", { name: "Сохранить GeoIP", exact: true }).click(); await expect(page.getByText("Данные GeoIP не подтверждены: получен некорректный ответ сервера", { exact: true })).toBeVisible(); await expect(field).toHaveValue("180"); await expect(page.getByRole("button", { name: "Сохранить GeoIP", exact: true })).toBeEnabled(); expect(writes).toBe(1)
|
|
})
|
|
test("GeoIP: lock защищает сохранение и обновление", async ({ page }) => {
|
|
await setup(page); let writes = 0, release!: () => void; const gate = new Promise<void>(resolve => release = resolve)
|
|
await page.route("**/api/geoip", async r => { if (r.request().method() !== "PUT") return r.fallback(); writes++; await gate; await r.fulfill({ json: { ok: true, status: { ...geoip, settings: { ...geoip.settings, ...r.request().postDataJSON() } } } }) })
|
|
try {
|
|
await page.goto("/data-collection"); const field = page.getByRole("textbox", { name: "Интервал GeoIP в часах", exact: true }); await expect(field).toHaveValue("168"); await field.fill("180"); const save = page.getByRole("button", { name: "Сохранить GeoIP", exact: true }); await save.click(); await expect.poll(() => writes).toBe(1); await expect(save).toBeDisabled(); await expect(page.getByRole("button", { name: "Обновить сейчас", exact: true })).toBeDisabled(); release(); await expect(page.getByText("Сохранение настроек GeoIP подтверждено", { exact: true })).toBeVisible(); expect(writes).toBe(1)
|
|
} finally { release() }
|
|
})
|
|
test("GeoIP: обновление статуса не затирает draft", async ({ page }) => {
|
|
await setup(page); await page.goto("/data-collection"); const field = page.getByRole("textbox", { name: "Интервал GeoIP в часах", exact: true }); await expect(field).toHaveValue("168"); await field.fill("180"); await page.getByRole("button", { name: "Обновить статус GeoIP", exact: true }).click(); await expect(page.getByRole("button", { name: "Обновить статус GeoIP", exact: true })).toBeEnabled(); await expect(field).toHaveValue("180")
|
|
})
|