import { test, expect, type Page } from "@playwright/test" import { setupBackups, schedule, storage } from "./fixtures/backups" async function open(page: Page) { await page.goto("/backups"); await page.getByRole("tab", { name: "Настройки", exact: true }).click(); await expect(page.getByRole("spinbutton", { name: "Час запуска" })).toBeVisible() } const save = (page: Page) => page.getByRole("button", { name: "Сохранить расписание", exact: true }) test("Бэкапы: save расписания не сохраняет хранилище", async ({ page }) => { await setupBackups(page); let puts = 0, storagePuts = 0, body: Record = {} await page.route("**/api/backups/storage", r => { if (r.request().method() === "PUT") storagePuts++; return r.fallback() }) await page.route("**/api/backups/schedule", r => { if (r.request().method() !== "PUT") return r.fallback(); puts++; body = r.request().postDataJSON(); return r.fulfill({ json: { ...schedule, ...body } }) }) await open(page); await page.getByRole("spinbutton", { name: "Час запуска" }).fill("7"); await page.getByRole("spinbutton", { name: "Минута запуска" }).fill("7") await save(page).click(); await expect(page.getByText("Расписание сохранено", { exact: true })).toBeVisible() expect(puts).toBe(1); expect(storagePuts).toBe(0); expect(body.hour).toBe(7); expect(body.minute).toBe(7); expect(body.format).toBe("rsc") }) for (const failure of ["API", "mismatch"]) test("Бэкапы: ошибка " + failure + " сохраняет расписание", async ({ page }) => { await setupBackups(page) await page.route("**/api/backups/schedule", r => r.request().method() !== "PUT" ? r.fallback() : r.fulfill(failure === "API" ? { status: 503, json: { error: "Расписание отказало" } } : { json: schedule })) await open(page); await page.getByRole("spinbutton", { name: "Час запуска" }).fill("7"); await save(page).click() await expect(page.getByText("Расписание сохранено", { exact: true })).toHaveCount(0) await expect(page.getByRole("spinbutton", { name: "Час запуска" })).toHaveValue("7") await expect(save(page)).toBeEnabled() }) test("Бэкапы: неверные числа и пустой явный выбор не отправляются", async ({ page }) => { await setupBackups(page); let puts = 0 await page.route("**/api/backups/schedule", r => { if (r.request().method() === "PUT") puts++; return r.fallback() }) await open(page) for (const value of ["", "1.5", "24"]) { await page.getByRole("spinbutton", { name: "Час запуска" }).fill(value); await save(page).click(); await expect(page.getByRole("spinbutton", { name: "Час запуска" })).toHaveValue(value) } await page.getByRole("spinbutton", { name: "Час запуска" }).fill("0") await page.getByRole("button", { name: "Сбросить", exact: true }).click(); await save(page).click(); expect(puts).toBe(0) }) test("Бэкапы: все включённые устройства передаются явно как пустой список", async ({ page }) => { await setupBackups(page); let body: Record = {} await page.route("**/api/backups/schedule", r => { if (r.request().method() !== "PUT") return r.fallback(); body = r.request().postDataJSON(); return r.fulfill({ json: { ...schedule, ...body } }) }) await open(page); await page.getByRole("checkbox", { name: "Все включённые серверы", exact: true }).check(); await save(page).click() await expect(page.getByText("Расписание сохранено", { exact: true })).toBeVisible(); expect(body.serverIds).toEqual([]) }) test("Бэкапы: общий lock не допускает второй PUT и блокирует поля", async ({ page }) => { await setupBackups(page); let puts = 0, release!: () => void; const gate = new Promise(r => release = r) await page.route("**/api/backups/schedule", async r => { if (r.request().method() !== "PUT") return r.fallback(); puts++; await gate; await r.fulfill({ json: { ...schedule, ...r.request().postDataJSON() } }).catch(() => {}) }) await open(page); await save(page).dblclick() await expect(page.getByRole("spinbutton", { name: "Час запуска" })).toBeDisabled() await expect(page.getByRole("button", { name: "Сохранить хранилище", exact: true })).toBeDisabled() release(); await expect(save(page)).toBeEnabled(); expect(puts).toBe(1) }) test("Бэкапы: отказ расписания не скрывает форму хранилища", async ({ page }) => { await setupBackups(page); await page.route("**/api/backups/schedule", r => r.fulfill({ status: 503, json: { error: "Schedule unavailable" } })) await page.goto("/backups"); await page.getByRole("tab", { name: "Настройки", exact: true }).click() await expect(page.getByRole("button", { name: "Сохранить хранилище", exact: true })).toBeEnabled() await expect(save(page)).toBeDisabled(); await expect(page.getByRole("spinbutton", { name: "Час запуска" })).toHaveCount(0) }) test("Бэкапы: проверка S3 не сохраняет черновик незаметно", async ({ page }) => { await setupBackups(page); let mutations = 0 await page.route("**/api/backups/storage**", r => { if (r.request().method() !== "GET") mutations++; return r.fallback() }) await open(page); await page.getByRole("button", { name: "S3", exact: true }).click() await page.getByPlaceholder("mikrotik-backups", { exact: true }).fill("draft-bucket") await page.getByRole("button", { name: "Проверить", exact: true }).click() await expect(page.getByRole("alert", { name: "Операция резервных копий" })).toContainText("Сначала сохраните хранилище; проверка использует подтверждённые настройки") expect(mutations).toBe(0) await expect(page.getByPlaceholder("mikrotik-backups", { exact: true })).toHaveValue("draft-bucket") }) test("Бэкапы: независимое подтверждение хранилища не отправляет расписание", async ({ page }) => { await setupBackups(page); let schedulePuts = 0, storagePuts = 0 await page.route("**/api/backups/schedule", r => { if (r.request().method() === "PUT") schedulePuts++; return r.fallback() }) await page.route("**/api/backups/storage", r => { if (r.request().method() !== "PUT") return r.fallback(); storagePuts++; return r.fulfill({ json: { ...storage, ...r.request().postDataJSON() } }) }) await open(page); await page.getByRole("button", { name: "Сохранить хранилище", exact: true }).click() await expect(page.getByText("Хранилище сохранено", { exact: true })).toBeVisible(); expect(storagePuts).toBe(1); expect(schedulePuts).toBe(0) })