24 lines
5.5 KiB
TypeScript
24 lines
5.5 KiB
TypeScript
import {test,expect,type Page} from "@playwright/test"
|
|
async function setup(page:Page,restore:{status?:number;json?:unknown}={json:{ok:true}}){
|
|
let posts=0,backups=0
|
|
await page.route("**/health",r=>r.fulfill({json:{status:"ok"}}))
|
|
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/system/database/restore"){posts++;return r.fulfill(restore)}if(path==="/api/system/database/backup"){backups++;return r.fulfill({body:Buffer.from("PGDMPfixture"),contentType:"application/octet-stream",headers:{"Content-Disposition":"attachment; filename=fixture.dump","X-System-Database-Outcome":encodeURIComponent(JSON.stringify({listenerError:"Listener restart failed"}))}})}return r.fulfill({status:503,json:{error:"outside fixture"}})})
|
|
await page.goto("/settings");return {posts:()=>posts,backups:()=>backups}
|
|
}
|
|
async function choose(page:Page,bytes="PGDMPfixture"){
|
|
await page.getByRole("button",{name:"Выбрать файл",exact:true}).click();await page.getByRole("dialog").locator('input[type="file"]').setInputFiles({name:"fixture.dump",mimeType:"application/octet-stream",buffer:Buffer.from(bytes)});await page.getByRole("button",{name:"Продолжить",exact:true}).click();await expect(page.getByRole("alertdialog")).toBeVisible()
|
|
}
|
|
test("System DB: unknown checkpoint survives cancel, section navigation and reload; explicit review enables retry",async({page})=>{
|
|
const f=await setup(page,{status:503,json:{error:"Receipt lost"}});await choose(page);await page.getByRole("button",{name:"Восстановить",exact:true}).click();await expect(page.getByRole("alert")).toHaveText("Receipt lost");await expect(page.getByRole("button",{name:"Восстановить",exact:true})).toBeDisabled();expect(f.posts()).toBe(1)
|
|
await page.getByRole("button",{name:"Отмена",exact:true}).click();await page.getByRole("button",{name:"Безопасность",exact:true}).click();await page.getByRole("button",{name:"Общие",exact:true}).click();await expect(page.getByRole("button",{name:"Выбрать файл",exact:true})).toBeDisabled();await page.reload();await expect(page.getByRole("button",{name:"Выбрать файл",exact:true})).toBeDisabled();await expect(page.getByText(/Запрос мог выполниться на сервере/)).toBeVisible();expect(f.posts()).toBe(1)
|
|
await page.getByRole("button",{name:"Я проверил состояние — разрешить новый запрос",exact:true}).click();await expect(page.getByRole("button",{name:"Выбрать файл",exact:true})).toBeEnabled()
|
|
})
|
|
test("System DB: invalid archive sends no POST and preserves selected file",async({page})=>{const f=await setup(page);await choose(page,"not a dump");await page.getByRole("button",{name:"Восстановить",exact:true}).click();await expect(page.getByRole("alert")).toContainText("PGDMP");expect(f.posts()).toBe(0);await expect(page.getByRole("alertdialog")).toContainText("fixture.dump");await expect(page.getByRole("button",{name:"Восстановить",exact:true})).toBeEnabled()})
|
|
test("System DB: conflict is a rejection without unknown checkpoint",async({page})=>{const f=await setup(page,{status:409,json:{error:"Операция уже выполняется"}});await choose(page);await page.getByRole("button",{name:"Восстановить",exact:true}).click();await expect(page.getByRole("alert")).toContainText("уже выполняется");await expect(page.getByRole("button",{name:"Восстановить",exact:true})).toBeEnabled();expect(f.posts()).toBe(1)})
|
|
test("System DB: prepared download keeps secondary warning and makes one request",async({page})=>{const f=await setup(page);await page.getByRole("region",{name:"Операции базы приложения"}).getByRole("button",{name:"Скачать",exact:true}).click();await expect(page.getByText("Бэкап базы приложения подготовлен к скачиванию",{exact:true})).toBeVisible();await expect(page.getByText("Listener restart failed",{exact:true})).toBeVisible();expect(f.backups()).toBe(1)})
|
|
test("System DB: synchronous restore lock prevents double dispatch and cancellation while pending",async({page})=>{
|
|
await setup(page);let calls=0,release!:()=>void;const gate=new Promise<void>(resolve=>release=resolve);await page.route("**/api/system/database/restore",async r=>{calls++;await gate;await r.fulfill({json:{ok:true}})})
|
|
await choose(page);await page.getByRole("button",{name:"Восстановить",exact:true}).evaluate((button:HTMLButtonElement)=>{button.click();button.click()});await expect(page.getByRole("button",{name:"Восстановление…",exact:true})).toBeDisabled();await expect(page.getByRole("button",{name:"Отмена",exact:true})).toBeDisabled();await expect.poll(()=>calls).toBe(1);release();await expect(page.getByText("База приложения восстановлена",{exact:true})).toBeVisible();expect(calls).toBe(1)
|
|
})
|
|
for(const width of [1440,390,320])test("System DB: adaptive confirmation/"+width,async({page})=>{await page.setViewportSize({width,height:900});await setup(page);await choose(page);await expect(page.getByRole("button",{name:"Восстановить",exact:true})).toBeVisible();expect(await page.evaluate(()=>document.documentElement.scrollWidth<=innerWidth)).toBe(true);await page.screenshot({path:"docs/ux-ui/screens/system-database-confirm-"+width+".png",fullPage:true,animations:"disabled"})})
|