Files
MikrotikManager/tests/e2e/data-collection-operations.spec.ts
T

56 lines
6.8 KiB
TypeScript

import { test, expect, type Page } from "@playwright/test"
import { geoip } from "./fixtures/geoip"
const job = { jobKey: "backups", enabled: true, intervalSec: 60, running: false, lastFinishedAt: null, lastStatus: null, lastDurationMs: 0, lastError: null }
const snapshot = { v: 1, job: "backups", sampledAt: "2026-10-03T00:00:00Z", due: true, created: 1, failures: 0, pruned: 0 }
const ack = { ok: true, result: { status: "ok", error: null, result: snapshot }, status: { jobs: [job] } }
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: [job] } })
if (path === "/api/scheduler/runs") return r.fulfill({ json: { runs: [] } })
if (path === "/api/traffic/settings") return r.fulfill({ json: { enabled: true, intervalSec: 45, retentionDays: 14, lastCollectedAt: null, lastDurationMs: 0, lastError: null } })
return r.fulfill({ status: 503, json: { error: "outside POST fixture" } })
})
}
for (const [label, result, message] of [
["успех", ack, "Запуск подтверждён: Бэкапы"],
["пропуск", { ...ack, result: { ...ack.result, result: { ...snapshot, due: false, created: 0 } } }, "Прогон пропущен: Бэкапы"],
["частичный сбой", { ...ack, ok: false, result: { status: "error", error: "S3 unavailable", eventError: "event unavailable", result: { ...snapshot, failures: 1 } }, status: null, statusError: "status unavailable" }, "Запуск завершился с ошибками: Бэкапы"],
] as const) test("Ручной запуск: " + label + " отдельно от черновика", async ({ page }) => {
await setup(page); let posts = 0, puts = 0; page.on("request", r => { if (r.method() === "PUT") puts++ })
await page.route("**/api/scheduler/jobs/backups/run-now", r => { posts++; return r.fulfill({ json: result }) })
await page.goto("/data-collection"); const field = page.getByRole("textbox", { name: "Интервал: Трафик", exact: true }); await expect(field).toHaveValue("45"); await field.fill("99")
const run = page.getByRole("button", { name: "Запустить: Бэкапы", exact: true }); await expect(run).toBeEnabled(); await run.click(); await expect(page.getByText(message, { exact: true })).toBeVisible(); await expect(field).toHaveValue("99"); expect(posts).toBe(1); expect(puts).toBe(0)
if (label === "частичный сбой") { await expect(page.getByText(/Журнал событий: event unavailable/)).toBeVisible(); await expect(page.getByText("Статус планировщика не обновлён", { exact: true })).toBeVisible() }
})
for (const response of [{}, { ...ack, result: { ...ack.result, result: { v: 1, job: "traffic", sampledAt: snapshot.sampledAt, servers: [] } } }]) test("Ручной запуск: неверное подтверждение не повторяет POST/" + JSON.stringify(response), async ({ page }) => {
await setup(page); let posts = 0; await page.route("**/api/scheduler/jobs/backups/run-now", r => { posts++; return r.fulfill({ json: response }) })
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(); await expect(page.getByText("Запуск подтверждён: Бэкапы", { exact: true })).toHaveCount(0); expect(posts).toBe(1)
})
test("Ручной запуск: общий lock блокирует повтор и сохранение", async ({ page }) => {
await setup(page); let posts = 0, release!: () => void; const gate = new Promise<void>(resolve => release = resolve)
await page.route("**/api/scheduler/jobs/backups/run-now", async r => { posts++; await gate; await r.fulfill({ json: ack }) })
try {
await page.goto("/data-collection"); const run = page.getByRole("button", { name: "Запустить: Бэкапы", exact: true }); await expect(run).toBeEnabled(); await run.click(); await expect.poll(() => posts).toBe(1); await expect(run).toBeDisabled(); await expect(page.getByRole("button", { name: "Перечитать таймеры", exact: true })).toBeDisabled(); release(); await expect(page.getByText("Запуск подтверждён: Бэкапы", { exact: true })).toBeVisible(); expect(posts).toBe(1)
} finally { release() }
})
for (const valid of [false, true]) test("Таймеры: строгий receipt/" + valid, async ({ page }) => {
await setup(page); let posts = 0; await page.route("**/api/scheduler/refresh", r => { posts++; return r.fulfill({ json: valid ? { ok: true, status: { jobs: [] } } : { ok: true } }) })
await page.goto("/data-collection"); const refresh = page.getByRole("button", { name: "Перечитать таймеры", exact: true }); await expect(refresh).toBeEnabled(); await refresh.click()
await expect(page.getByText(valid ? "Перечитывание таймеров подтверждено сервером" : "Ответ планировщика не подтверждён: получены некорректные данные", { exact: true })).toBeVisible(); expect(posts).toBe(1)
})
for (const fails of [false, true]) test("Ручной запуск: поздний ответ после смены режима/" + fails, async ({ page }) => {
await setup(page); let started = false, completed = false, release!: () => void; const gate = new Promise<void>(resolve => release = resolve)
await page.route("**/api/scheduler/jobs/backups/run-now", async r => { started = true; await gate; try { await r.fulfill(fails ? { status: 503, json: { error: "СТАРАЯ ОШИБКА ЗАПУСКА" } } : { json: ack }) } catch {} finally { completed = true } })
try {
await page.goto("/data-collection"); const run = page.getByRole("button", { name: "Запустить: Бэкапы", exact: true }); await expect(run).toBeEnabled(); await run.click(); await expect.poll(() => started).toBe(true)
await page.goto("/settings"); await page.getByRole("button", { name: "Моковые", exact: true }).click(); await page.goto("/data-collection"); release(); await expect.poll(() => completed).toBe(true)
await expect(page.getByText("Нужен live-режим", { exact: true })).toBeVisible(); await expect(page.getByText("Запуск подтверждён: Бэкапы", { exact: true })).toHaveCount(0); await expect(page.getByText(/СТАРАЯ ОШИБКА ЗАПУСКА/)).toHaveCount(0)
} finally { release() }
})