78 lines
8.1 KiB
TypeScript
78 lines
8.1 KiB
TypeScript
import { test, expect } from "@playwright/test"
|
|
import { setupBackups, backup } from "./fixtures/backups"
|
|
import { openBackup, backupJob, backupAck } from "./fixtures/backup-job"
|
|
|
|
test("Бэкапы job: single-flight POST и подтверждённый результат", async ({ page }) => {
|
|
await setupBackups(page); let posts = 0, id = "", release!: () => void; const gate = new Promise<void>(r => release = r)
|
|
await page.route("**/api/backups/create", async r => { posts++; id = r.request().postDataJSON().requestId; await gate; await r.fulfill({ status: 202, json: backupAck(id) }).catch(() => {}) })
|
|
await page.route("**/api/backups/jobs/*", r => r.fulfill({ json: backupJob(id) }))
|
|
const dialog = await openBackup(page); await dialog.getByRole("button", { name: /Снять бэкап/ }).dblclick()
|
|
await expect(dialog.getByRole("button", { name: "Проверить задачу", exact: true })).toBeDisabled()
|
|
release(); await expect(page.getByText("API подтвердил создание всех резервных копий", { exact: true })).toBeVisible()
|
|
expect(posts).toBe(1); await expect(dialog.getByRole("button", { name: "Новая задача", exact: true })).toBeVisible()
|
|
})
|
|
test("Бэкапы job: отклонённый POST сохраняет форму для исправления", async ({ page }) => {
|
|
await setupBackups(page); await page.route("**/api/backups/create", r => r.fulfill({ status: 403, json: { error: "Создание запрещено" } }))
|
|
const dialog = await openBackup(page); await dialog.getByRole("button", { name: /Снять бэкап/ }).click()
|
|
await expect(dialog.getByRole("button", { name: /Снять бэкап/ })).toBeEnabled()
|
|
await dialog.getByRole("button", { name: "Назад", exact: true }).click()
|
|
await expect(dialog.getByRole("textbox", { name: "Заметка резервной копии", exact: true })).toHaveValue("manual")
|
|
await expect(page.getByText("API подтвердил создание всех резервных копий", { exact: true })).toHaveCount(0)
|
|
})
|
|
for (const kind of ["device", "S3"]) test("Бэкапы job: partial " + kind + " не называется полным успехом", async ({ page }) => {
|
|
await setupBackups(page); let id = ""
|
|
await page.route("**/api/backups/create", r => { id = r.request().postDataJSON().requestId; return r.fulfill({ status: 202, json: backupAck(id) }) })
|
|
await page.route("**/api/backups/jobs/*", r => r.fulfill({ json: backupJob(id, kind === "device" ? { created: [], failures: [{ serverId: "1", error: "RouterOS offline" }] } : { created: [{ ...backup, uploadError: "S3 upload denied" }] }) }))
|
|
const dialog = await openBackup(page); await dialog.getByRole("button", { name: /Снять бэкап/ }).click()
|
|
await expect(page.getByRole("alert", { name: "Задача резервного копирования", exact: true })).toContainText(kind === "device" ? "RouterOS offline" : "S3 upload denied")
|
|
await expect(page.getByText("API подтвердил создание всех резервных копий", { exact: true })).toHaveCount(0)
|
|
await expect(dialog.getByRole("button", { name: "Новая задача", exact: true })).toBeVisible()
|
|
})
|
|
test("Бэкапы job: отказ GET и повтор только чтения той же задачи", async ({ page }) => {
|
|
await setupBackups(page); let posts = 0, id = "", fail = true
|
|
await page.route("**/api/backups/create", r => { posts++; id = r.request().postDataJSON().requestId; return r.fulfill({ status: 202, json: backupAck(id) }) })
|
|
await page.route("**/api/backups/jobs/*", r => r.fulfill(fail ? { status: 503, json: { error: "Наблюдение отказало" } } : { json: backupJob(id) }))
|
|
const dialog = await openBackup(page); await dialog.getByRole("button", { name: /Снять бэкап/ }).click()
|
|
await expect(dialog.getByRole("button", { name: "Проверить задачу", exact: true })).toBeEnabled()
|
|
await expect(page.getByRole("alert", { name: "Задача резервного копирования", exact: true })).toContainText("Наблюдение отказало")
|
|
fail = false; await dialog.getByRole("button", { name: "Проверить задачу", exact: true }).click()
|
|
await expect(page.getByText("API подтвердил создание всех резервных копий", { exact: true })).toBeVisible(); expect(posts).toBe(1)
|
|
})
|
|
test("Бэкапы job: потерянный POST/404 после reload не запускает второй POST", async ({ page }) => {
|
|
await setupBackups(page); let posts = 0
|
|
await page.route("**/api/backups/create", r => { posts++; return r.fulfill({ status: 503, json: { error: "Неизвестный результат POST" } }) })
|
|
await page.route("**/api/backups/jobs/*", r => r.fulfill({ status: 404, json: { error: "Job lost" } }))
|
|
const dialog = await openBackup(page); await dialog.getByRole("button", { name: /Снять бэкап/ }).click()
|
|
await expect(dialog.getByRole("button", { name: "Проверить задачу", exact: true })).toBeEnabled()
|
|
await page.reload(); await page.getByRole("button", { name: "Задача резервного копирования", exact: true }).click()
|
|
await page.getByRole("dialog").getByRole("button", { name: "Проверить задачу", exact: true }).click()
|
|
await expect(page.getByRole("alert", { name: "Задача резервного копирования", exact: true })).toContainText("После рестарта")
|
|
expect(posts).toBe(1); await expect(page.getByRole("dialog").getByRole("button", { name: "Новая задача", exact: true })).toHaveCount(0)
|
|
})
|
|
test("Бэкапы job: чужая цель не подтверждает завершение", async ({ page }) => {
|
|
await setupBackups(page); let id = ""
|
|
await page.route("**/api/backups/create", r => { id = r.request().postDataJSON().requestId; return r.fulfill({ status: 202, json: backupAck(id) }) })
|
|
await page.route("**/api/backups/jobs/*", r => r.fulfill({ json: backupJob(id, { notes: "foreign" }) }))
|
|
const dialog = await openBackup(page); await dialog.getByRole("button", { name: /Снять бэкап/ }).click()
|
|
await expect(page.getByRole("alert", { name: "Задача резервного копирования", exact: true })).toContainText("не соответствует")
|
|
await expect(dialog.getByRole("button", { name: "Новая задача", exact: true })).toHaveCount(0)
|
|
})
|
|
|
|
test("Бэкапы job: неизвестный POST восстанавливается GET без второго создания", async ({ page }) => {
|
|
await setupBackups(page); let posts = 0, id = ""
|
|
await page.route("**/api/backups/create", r => { posts++; id = r.request().postDataJSON().requestId; return r.fulfill({ status: 503, json: { error: "Ack lost" } }) })
|
|
await page.route("**/api/backups/jobs/*", r => r.fulfill({ json: backupJob(id) }))
|
|
const dialog = await openBackup(page); await dialog.getByRole("button", { name: /Снять бэкап/ }).click()
|
|
await expect(dialog.getByRole("button", { name: "Проверить задачу", exact: true })).toBeEnabled()
|
|
await dialog.getByRole("button", { name: "Проверить задачу", exact: true }).click()
|
|
await expect(page.getByText("API подтвердил создание всех резервных копий", { exact: true })).toBeVisible(); expect(posts).toBe(1)
|
|
})
|
|
test("Бэкапы job: невозможность checkpoint записи не отправляет POST", async ({ page }) => {
|
|
await setupBackups(page); let posts = 0
|
|
await page.addInitScript(() => { const set = Storage.prototype.setItem; Storage.prototype.setItem = function(key, value) { if (key.startsWith("mmapp:backup-job:")) throw new Error("denied"); return set.call(this, key, value) } })
|
|
await page.route("**/api/backups/create", r => { posts++; return r.fallback() })
|
|
const dialog = await openBackup(page); await dialog.getByRole("button", { name: /Снять бэкап/ }).click()
|
|
await expect(page.getByRole("alert", { name: "Задача резервного копирования", exact: true })).toContainText("Запрос не отправлен")
|
|
expect(posts).toBe(0)
|
|
})
|