fix(ui): подтверждать удаление пользователя и сохранять ошибку

This commit is contained in:
Denozordec committed 2026-10-01 09:39:39 +07:00
1 parent 2d5fb3598f
commit 02ca48ef9c
8 files changed
+92 -23

No files matched your search

+2
View File
@@ -0,0 +1,2 @@
export const user={id:"u-test",name:"Настоящий пользователь",login:"test@example.test",email:"test@example.test",role:"viewer",active:true,avatar:"НП",lastSeen:null,sections:[],servers:[],bindings:[],createdAt:"2026-10-01T00:00:00Z",updatedAt:"2026-10-01T00:00:00Z"}
export const server={id:1,name:"Роутер",host:"192.0.2.1",port:443,useSsl:true,verifySsl:false,username:"admin",password:"",type:"exit-node",site:"",country:"RU",asn:"",comment:"",enabled:true,lanSubnet:"",wanUplinks:[],createdAt:"",updatedAt:"",status:null,latency:null,os:null,model:null,uptime:null,cpuLoad:null,freeMemory:null,totalMemory:null,identityName:null,sessions:0,polledAt:null}
+10
View File
@@ -11,3 +11,13 @@ for(const change of ["backend","mode"])for(const fails of [false,true])test("П
if(change==="backend")await expect(page.getByText(user.name,{exact:true})).toBeVisible();else await expect(page.getByRole("status").filter({hasText:"Демо: изменения"})).toBeVisible()
await expect(page.getByText("СТАРЫЙ ПОЛЬЗОВАТЕЛЬ",{exact:true})).toHaveCount(0);await expect(page.getByText("СТАРАЯ ОШИБКА пользователей",{exact:true})).toHaveCount(0)
})
for(const fails of [false,true])test("Пользователи: позднее удаление не меняет новый backend/"+fails,async({page})=>{
let started=false,completed=false,release!:()=>void;const gate=new Promise<void>(r=>release=r)
await page.route("**/health",r=>r.fulfill({headers,json:{ok:true}}));await page.route("**/api/**",r=>{const p=new URL(r.request().url()).pathname;return r.fulfill({headers,json:p==="/api/auth/config"?{required:false}:p==="/api/users"?{users:[user]}:[]})})
const catalog={id:1,name:"Сервер",host:"192.0.2.1",port:443,useSsl:true,verifySsl:false,username:"admin",password:"",type:"exit-node",site:"",country:"RU",asn:"",comment:"",enabled:true,lanSubnet:"",wanUplinks:[],createdAt:"",updatedAt:"",status:null,latency:null,os:null,model:null,uptime:null,cpuLoad:null,freeMemory:null,totalMemory:null,identityName:null,sessions:0,polledAt:null}
await page.route("**/api/servers",r=>r.fulfill({headers,json:[catalog]}));await page.route("**/api/users/u-test",async r=>{started=true;await gate;try{await r.fulfill(fails?{headers,status:503,json:{error:"СТАРОЕ УДАЛЕНИЕ ОТКЛОНЕНО"}}:{headers,status:204,body:""})}catch{}finally{completed=true}})
await page.goto("/users");await page.getByRole("button",{name:"Удалить пользователя "+user.name}).click();await page.getByRole("alertdialog").getByRole("button",{name:"Удалить",exact:true}).click();await expect.poll(()=>started).toBe(true)
await page.goto("/settings");const input=page.getByPlaceholder("http://localhost:8000");await input.fill("http://localhost:8101");await input.press("Enter");await page.goto("/users");await expect(page.getByText(user.name,{exact:true})).toBeVisible();release();await expect.poll(()=>completed).toBe(true)
await expect(page.getByText(user.name,{exact:true})).toBeVisible();await expect(page.getByText("СТАРОЕ УДАЛЕНИЕ ОТКЛОНЕНО",{exact:true})).toHaveCount(0);await expect(page.getByRole("status").filter({hasText:"Удаление пользователя подтверждено"})).toHaveCount(0)
})
+1 -2
View File
@@ -1,6 +1,5 @@
import { test, expect, type Page } from "@playwright/test"
const user={id:"u-test",name:"Настоящий пользователь",login:"test@example.test",email:"test@example.test",role:"viewer",active:true,avatar:"НП",lastSeen:null,sections:[],servers:[],bindings:[],createdAt:"2026-10-01T00:00:00Z",updatedAt:"2026-10-01T00:00:00Z"}
const server={id:1,name:"Роутер",host:"192.0.2.1",port:443,useSsl:true,verifySsl:false,username:"admin",password:"",type:"exit-node",site:"",country:"RU",asn:"",comment:"",enabled:true,lanSubnet:"",wanUplinks:[],createdAt:"",updatedAt:"",status:null,latency:null,os:null,model:null,uptime:null,cpuLoad:null,freeMemory:null,totalMemory:null,identityName:null,sessions:0,polledAt:null}
import {user,server} from "./fixtures/users"
async function routes(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;return r.fulfill({json:path==="/api/users"?{users:[user]}:path==="/api/servers"?[server]:path==="/api/auth/config"?{required:false}:{interfaces:[]}})})
+21
View File
@@ -0,0 +1,21 @@
import {test,expect} from "@playwright/test"
import {user,server} from "./fixtures/users"
test.beforeEach(async({page})=>{
await page.route("**/health",r=>r.fulfill({json:{ok:true}}));await page.route("**/api/**",r=>{const p=new URL(r.request().url()).pathname;return r.fulfill({json:p==="/api/auth/config"?{required:false}:p==="/api/users"?{users:[user]}:p==="/api/servers"?[server]:{interfaces:[]}})})
})
for(const kind of ["403","503","malformed"] as const)test("Удаление пользователя: ошибка сохраняет диалог "+kind,async({page})=>{
let fail=true,deleted=false;await page.route("**/api/users",r=>r.fulfill({json:{users:deleted?[]:[user]}}))
await page.route("**/api/users/u-test",r=>{if(fail)return r.fulfill(kind==="malformed"?{status:200,json:{success:true}}:{status:Number(kind),json:{error:"УДАЛЕНИЕ ОТКЛОНЕНО"}});deleted=true;return r.fulfill({status:204,body:""})})
await page.goto("/users");await page.getByRole("button",{name:"Удалить пользователя "+user.name}).click();const dialog=page.getByRole("alertdialog");await dialog.getByRole("button",{name:"Удалить",exact:true}).click();await expect(dialog.getByRole("alert")).toBeVisible();await expect(dialog).toBeVisible();await expect(page.getByText(user.name,{exact:true})).toBeVisible()
fail=false;await dialog.getByRole("button",{name:"Удалить",exact:true}).click();await expect(dialog).toHaveCount(0);await expect(page.getByText("Нет пользователей",{exact:true})).toBeVisible();await expect(page.getByRole("status").filter({hasText:"Удаление пользователя подтверждено API."})).toBeVisible()
})
test("Удаление пользователя: lock, Escape и один запрос",async({page})=>{
let calls=0,deleted=false,release!:()=>void;const gate=new Promise<void>(r=>release=r)
await page.route("**/api/users",r=>r.fulfill({json:{users:deleted?[]:[user]}}));await page.route("**/api/users/u-test",async r=>{calls++;await gate;deleted=true;await r.fulfill({status:204,body:""})})
await page.goto("/users");await page.getByRole("button",{name:"Удалить пользователя "+user.name}).click();const dialog=page.getByRole("alertdialog");await dialog.getByRole("button",{name:"Удалить",exact:true}).evaluate(el=>{(el as HTMLButtonElement).click();(el as HTMLButtonElement).click()});await expect.poll(()=>calls).toBe(1);await expect(dialog.getByRole("button",{name:"Удаление…"})).toBeDisabled();await expect(dialog.getByRole("button",{name:"Отмена",exact:true})).toBeDisabled();await page.keyboard.press("Escape");await expect(dialog).toBeVisible();release();await expect(dialog).toHaveCount(0);expect(calls).toBe(1)
})
test("Удаление пользователя: подтверждение отдельно от отказа refresh",async({page})=>{
let deleted=false,fail=true;await page.route("**/api/users",r=>r.fulfill(deleted&&fail?{status:503,json:{error:"Список не обновлён"}}:{json:{users:deleted?[]:[user]}}));await page.route("**/api/users/u-test",r=>{deleted=true;return r.fulfill({status:204,body:""})})
await page.goto("/users");await page.getByRole("button",{name:"Удалить пользователя "+user.name}).click();await page.getByRole("alertdialog").getByRole("button",{name:"Удалить",exact:true}).click();await expect(page.getByRole("status").filter({hasText:"Удаление пользователя подтверждено API. Обновление каталогов не удалось"})).toBeVisible();await expect(page.getByRole("alert",{name:"Пользователи",exact:true})).toContainText("Список не обновлён");await expect(page.getByText(user.name,{exact:true})).toHaveCount(0)
fail=false;await page.getByRole("button",{name:"Обновить пользователей",exact:true}).click();await expect(page.getByRole("button",{name:"Добавить пользователя",exact:true})).toBeEnabled()
})