Files
MikrotikManager/tests/e2e/users-operations.spec.ts

56 lines
13 KiB
TypeScript

import {test,expect} from "@playwright/test"
import {user,server,catalogResponse} from "./fixtures/users"
test.beforeEach(async({page})=>{
await page.route("**/health",r=>catalogResponse(r, {json:{ok:true}}));await page.route("**/api/**",r=>{const p=new URL(r.request().url()).pathname;return catalogResponse(r, {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=>catalogResponse(r, {json:{users:deleted?[]:[user]}}))
await page.route("**/api/users/u-test",r=>{if(fail)return catalogResponse(r, kind==="malformed"?{status:200,json:{success:true}}:{status:Number(kind),json:{error:"УДАЛЕНИЕ ОТКЛОНЕНО"}});deleted=true;return catalogResponse(r, {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=>catalogResponse(r, {json:{users:deleted?[]:[user]}}));await page.route("**/api/users/u-test",async r=>{calls++;await gate;deleted=true;await catalogResponse(r, {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=>catalogResponse(r, deleted&&fail?{status:503,json:{error:"Список не обновлён"}}:{json:{users:deleted?[]:[user]}}));await page.route("**/api/users/u-test",r=>{deleted=true;return catalogResponse(r, {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()
})
async function openCreate(page:import("@playwright/test").Page){
await page.getByRole("button",{name:"Добавить пользователя",exact:true}).click();await page.getByPlaceholder("Иван Иванов").fill("Новый профиль");await page.getByPlaceholder("i.ivanov@company.io").fill("new@example.test");for(let i=0;i<3;i++)await page.getByRole("dialog").getByRole("button",{name:"Далее",exact:true}).click()
}
for(const kind of ["403","malformed"] as const)test("Создание пользователя: сохранение черновика "+kind,async({page})=>{
let fail=true,created=false,posts=0;await page.route("**/api/users",r=>{if(r.request().method()==="POST"){posts++;if(fail)return catalogResponse(r, kind==="403"?{status:403,json:{error:"Создание запрещено"}}:{json:{user:{}}});created=true;return catalogResponse(r, {status:201,json:{user:{...user,...r.request().postDataJSON(),id:"new"}}})}return catalogResponse(r, {json:{users:created?[{...user,id:"new",name:"Новый профиль",login:"new@example.test",email:"new@example.test"}]:[user]}})})
await page.goto("/users");await openCreate(page);const dialog=page.getByRole("dialog");await dialog.getByRole("button",{name:"Создать",exact:true}).click();await expect(dialog.getByRole("alert",{name:"Сохранение пользователя"})).toBeVisible();await dialog.getByRole("button",{name:"Назад",exact:true}).click();await dialog.getByRole("button",{name:"Назад",exact:true}).click();await dialog.getByRole("button",{name:"Назад",exact:true}).click();await expect(page.getByPlaceholder("Иван Иванов")).toHaveValue("Новый профиль");await expect(page.getByPlaceholder("i.ivanov@company.io")).toHaveValue("new@example.test")
if(kind==="403"){fail=false;for(let i=0;i<3;i++)await dialog.getByRole("button",{name:"Далее",exact:true}).click();await dialog.getByRole("button",{name:"Создать",exact:true}).click();await expect(dialog).toHaveCount(0);await expect(page.getByRole("status").filter({hasText:"Профиль и привязки подтверждены API."})).toBeVisible();expect(posts).toBe(2)}
else{for(let i=0;i<3;i++)await dialog.getByRole("button",{name:"Далее",exact:true}).click();await expect(dialog.getByRole("button",{name:"Создать",exact:true})).toBeDisabled();await dialog.getByRole("button",{name:"Проверить список пользователей"}).click();await expect(dialog).toBeVisible();expect(posts).toBe(1)}
})
test("Создание пользователя: lock формы и успех отдельно от refresh",async({page})=>{
let posts=0,created=false,release!:()=>void;const gate=new Promise<void>(r=>release=r)
await page.route("**/api/users",async r=>{if(r.request().method()==="POST"){posts++;await gate;created=true;return catalogResponse(r, {status:201,json:{user:{...user,...r.request().postDataJSON(),id:"new"}}})}return catalogResponse(r, created?{status:503,json:{error:"REFRESH профиля недоступен"}}:{json:{users:[user]}})})
await page.goto("/users");await openCreate(page);const dialog=page.getByRole("dialog");await dialog.getByRole("button",{name:"Создать",exact:true}).evaluate(el=>{(el as HTMLButtonElement).click();(el as HTMLButtonElement).click()});await expect.poll(()=>posts).toBe(1);await expect(dialog.getByRole("button",{name:"Отмена",exact:true})).toBeDisabled();await page.keyboard.press("Escape");await expect(dialog).toBeVisible();release();await expect(dialog).toHaveCount(0);await expect(page.getByText("Новый профиль",{exact:true})).toBeVisible();await expect(page.getByRole("status").filter({hasText:"Профиль и привязки подтверждены API. Обновление каталогов не удалось"})).toBeVisible()
})
for(const bad of [true,false])test("Редактирование пользователя: неверный id и ошибка API "+bad,async({page})=>{
let fail=true;await page.route("**/api/users/u-test",r=>catalogResponse(r, fail?(bad?{json:{user:{...user,id:"foreign"}}}:{status:503,json:{error:"PATCH отклонён"}}):{json:{user:{...user,...r.request().postDataJSON()}}}))
await page.goto("/users");await page.getByRole("button",{name:"Редактировать пользователя "+user.name}).click();await page.getByPlaceholder("Иван Иванов").fill("Исправленный профиль");const dialog=page.getByRole("dialog");for(let i=0;i<3;i++)await dialog.getByRole("button",{name:"Далее",exact:true}).click();await dialog.getByRole("button",{name:"Сохранить",exact:true}).click();await expect(dialog.getByRole("alert",{name:"Сохранение пользователя"})).toBeVisible();fail=false;await dialog.getByRole("button",{name:"Сохранить",exact:true}).click();await expect(dialog).toHaveCount(0)
})
for(const lost of [false,true])test("Создание пользователя: частичные привязки и повтор без дублей "+lost,async({page})=>{
let fail=true,profiles=0,patches=0;const adds:string[]=[];let profile={...user,id:"new",name:"Новый профиль",login:"new@example.test",email:"new@example.test"};const bindings:Array<Record<string,unknown>>=[]
await page.route("**/api/users/interface-catalog-state?*",r=>catalogResponse(r, {json:{interfaces:["ether1","ether2"].map(name=>({name,type:"ether",running:true,disabled:false,boundUserId:null,boundUserLogin:null}))}}))
await page.route("**/api/users",r=>{if(r.request().method()==="POST"){profiles++;profile={...profile,...r.request().postDataJSON()};return catalogResponse(r, {status:201,json:{user:{...profile,bindings}}})}return catalogResponse(r, {json:{users:[user,{...profile,bindings}]}})})
await page.route("**/api/users/new",r=>{if(r.request().method()==="PATCH"){patches++;profile={...profile,...r.request().postDataJSON()}}return catalogResponse(r, {json:{user:{...profile,bindings}}})})
await page.route("**/api/users/new/bindings",r=>{const data=r.request().postDataJSON();adds.push(data.interfaceName);const binding={...data,id:"saved-"+data.interfaceName,userId:"new",serverName:"Роутер",serverSite:"",serverCountry:"RU",createdAt:"",updatedAt:"",peerPublicKey:"",peerName:"",comment:""};if(fail&&((lost&&data.interfaceName==="ether1")||(!lost&&data.interfaceName==="ether2"))){if(lost)bindings.push(binding);return catalogResponse(r, {status:lost?500:409,json:{error:"ПРИВЯЗКА НЕ ПОДТВЕРЖДЕНА"}})}bindings.push(binding);return catalogResponse(r, {status:201,json:{binding}})})
await page.goto("/users");await openCreate(page);const dialog=page.getByRole("dialog");await dialog.getByRole("button",{name:/ether1.*Ethernet/}).click();await dialog.getByRole("button",{name:/ether2.*Ethernet/}).click();await dialog.getByRole("button",{name:"Привязать",exact:true}).click();await dialog.getByRole("button",{name:"Создать",exact:true}).click();await expect(dialog.getByRole("alert",{name:"Сохранение пользователя"})).toContainText("привязки сохранены частично");await expect(dialog.getByRole("button",{name:"Продолжить сохранение",exact:true})).toBeEnabled()
fail=false;await dialog.getByRole("button",{name:"Продолжить сохранение",exact:true}).click();await expect(dialog).toHaveCount(0);expect(profiles).toBe(1);expect(patches).toBe(1);expect(adds.filter(name=>name==="ether1")).toHaveLength(1);expect(bindings).toHaveLength(2);expect(adds.filter(name=>name==="ether2")).toHaveLength(lost?1:2)
})
for(const field of ["role","sections","servers"] as const)test("Сохранение профиля: ответ с другими разрешениями "+field,async({page})=>{
let fail=true;await page.route("**/api/users/u-test",r=>{const data=r.request().postDataJSON();return catalogResponse(r, {json:{user:{...user,...data,...(fail?(field==="role"?{role:"admin"}:field==="sections"?{sections:[]}:{servers:[]}):{})}}})})
await page.goto("/users");await page.getByRole("button",{name:"Редактировать пользователя "+user.name}).click();const dialog=page.getByRole("dialog");await dialog.getByRole("button",{name:"Оператор",exact:true}).click();for(let i=0;i<3;i++)await dialog.getByRole("button",{name:"Далее",exact:true}).click();await dialog.getByRole("button",{name:"Сохранить",exact:true}).click();await expect(dialog.getByRole("alert",{name:"Сохранение пользователя"})).toContainText("не подтверждён");fail=false;await dialog.getByRole("button",{name:"Сохранить",exact:true}).click();await expect(dialog).toHaveCount(0)
})