fix(ui): сохранить достоверные состояния контейнеров
This commit is contained in:
1 parent
644ddb64b3
commit
860add8edf
9 files changed
+189
-93
No files matched your search
@@ -0,0 +1,13 @@
|
||||
import {test,expect} from "@playwright/test"
|
||||
import {container,server,state} from "./fixtures/containers"
|
||||
const headers={"access-control-allow-origin":"*","access-control-allow-methods":"GET,POST,OPTIONS","access-control-allow-headers":"Content-Type,Authorization"}
|
||||
for(const change of ["backend","mode"])for(const fails of [false,true])test("Контейнеры: позднее чтение/"+change+"/"+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/servers"?[server]:state()})})
|
||||
await page.route("**/api/containers/state",async r=>{if(new URL(r.request().url()).port!=="8000")return r.fallback();started=true;await gate;try{await r.fulfill(fails?{headers,status:503,json:{error:"СТАРАЯ ОШИБКА контейнеров"}}:{headers,json:state([{...container,name:"СТАРЫЙ КОНТЕЙНЕР"}])})}catch{}finally{completed=true}})
|
||||
await page.goto("/containers");await expect.poll(()=>started).toBe(true);await page.goto("/settings")
|
||||
if(change==="backend"){const input=page.getByPlaceholder("http://localhost:8000");await input.fill("http://localhost:8101");await input.press("Enter")}else await page.getByRole("button",{name:"Моковые",exact:true}).click()
|
||||
await page.goto("/containers");release();await expect.poll(()=>completed).toBe(true)
|
||||
if(change==="backend")await expect(page.getByText(container.name,{exact:true})).toBeVisible();else await expect(page.getByText("Моковые данные",{exact:true})).toBeVisible()
|
||||
await expect(page.getByText("СТАРЫЙ КОНТЕЙНЕР",{exact:true})).toHaveCount(0);await expect(page.getByText(/СТАРАЯ ОШИБКА контейнеров/)).toHaveCount(0)
|
||||
})
|
||||
@@ -0,0 +1,38 @@
|
||||
import {test,expect,type Page} from "@playwright/test"
|
||||
import {container,server,state} from "./fixtures/containers"
|
||||
async function routes(page:Page) {
|
||||
await page.route("**/health",r=>r.fulfill({json:{ok:true}}))
|
||||
await page.route("**/api/**",r=>r.fulfill({json:new URL(r.request().url()).pathname==="/api/auth/config"?{required:false}:new URL(r.request().url()).pathname==="/api/servers"?[server]:state()}))
|
||||
}
|
||||
test.beforeEach(async({page})=>routes(page))
|
||||
const refresh=(page:Page)=>page.getByRole("button",{name:"Обновить",exact:true})
|
||||
for(const kind of ["API","malformed","duplicate","foreign"] as const)test("Контейнеры: первоначальный отказ "+kind,async({page})=>{
|
||||
let fail=true;await page.route("**/api/containers/state",r=>r.fulfill(fail?(kind==="API"?{status:503,json:{error:"Каталог отказал"}}:{json:kind==="malformed"?{containers:[]}:state(kind==="duplicate"?[container,container]:[{...container,serverId:"2"}])}):{json:state()}))
|
||||
await page.goto("/containers");await expect(page.getByRole("alert",{name:"Каталог контейнеров",exact:true})).toContainText("Контейнеры пока неизвестны");await expect(page.getByText("Контейнеры не найдены",{exact:true})).toHaveCount(0);await expect(page.getByText(container.name,{exact:true})).toHaveCount(0);await expect(page.getByRole("button",{name:"Новый контейнер",exact:true})).toBeDisabled()
|
||||
fail=false;await refresh(page).click();await expect(page.getByText(container.name,{exact:true})).toBeVisible()
|
||||
})
|
||||
for(const kind of ["API","malformed","duplicate"] as const)test("Контейнеры: независимый отказ серверов "+kind,async({page})=>{
|
||||
await page.route("**/api/servers",r=>r.fulfill(kind==="API"?{status:403,json:{error:"Серверы отказали"}}:{json:kind==="duplicate"?[server,server]:[{}]}));await page.goto("/containers");await expect(page.getByText(container.name,{exact:true})).toBeVisible();await expect(page.getByRole("alert",{name:"Серверы контейнеров"})).toContainText("Каталог серверов пока неизвестен")
|
||||
await page.getByRole("button",{name:"Действия контейнера "+container.name}).click();await expect(page.getByRole("menuitem",{name:"Остановить",exact:true})).toBeDisabled()
|
||||
})
|
||||
test("Контейнеры: подтверждённое empty и unsupported",async({page})=>{
|
||||
let unsupported=false;await page.route("**/api/containers/state",r=>r.fulfill({json:state([],{availability:unsupported?"unsupported":"available"})}));await page.goto("/containers");await expect(page.getByText("Подтверждённый каталог пуст",{exact:true})).toBeVisible();await expect(page.getByText("Список контейнеров пока неизвестен",{exact:true})).toHaveCount(0);unsupported=true;await refresh(page).click();await expect(page.getByRole("status").filter({hasText:"пакет container недоступен"})).toBeVisible()
|
||||
})
|
||||
test("Контейнеры: фоновый отказ сохраняет снимок",async({page})=>{
|
||||
let fail=false;await page.route("**/api/containers/state",r=>r.fulfill(fail?{status:503,json:{error:"ФОНОВЫЙ ОТКАЗ"}}:{json:state()}));await page.goto("/containers");await expect(page.getByText(container.name,{exact:true})).toBeVisible();fail=true;await refresh(page).click();await expect(page.getByRole("alert",{name:"Каталог контейнеров",exact:true})).toContainText("Последние данные сохранены");await expect(page.getByText(container.name,{exact:true})).toBeVisible()
|
||||
})
|
||||
test("Контейнеры: частичный отказ устройства сохраняет его прошлые строки",async({page})=>{
|
||||
let fail=false;await page.route("**/api/containers/state",r=>r.fulfill({json:fail?state([],{availability:"error",failures:[{resource:"containers",error:"УСТРОЙСТВО ОТКАЗАЛО"}]}):state()}));await page.goto("/containers");await expect(page.getByText(container.name,{exact:true})).toBeVisible();fail=true;await refresh(page).click();await expect(page.getByRole("alert",{name:"Контейнеры сервера 1",exact:true})).toContainText("Последние контейнеры сохранены");await expect(page.getByText(container.name,{exact:true})).toBeVisible();await page.getByRole("button",{name:"Действия контейнера "+container.name}).click();await expect(page.getByRole("menuitem",{name:"Остановить",exact:true})).toBeDisabled()
|
||||
})
|
||||
test("Контейнеры: неизвестный статус и partial зависимости видимы",async({page})=>{
|
||||
await page.route("**/api/containers/state",r=>r.fulfill({json:state([{...container,status:"unknown",startOnBoot:null,image:"",tag:""}],{failures:[{resource:"envs",error:"ENVS ОТКАЗ"}]})}));await page.goto("/containers");await expect(page.getByText(container.name,{exact:true})).toBeVisible();await expect(page.getByText("Неизвестно",{exact:true})).toBeVisible();await expect(page.getByRole("alert",{name:"Контейнеры сервера 1"})).toContainText("ENVS ОТКАЗ")
|
||||
})
|
||||
test("Контейнеры: медленный каталог серверов не задерживает контейнеры",async({page})=>{
|
||||
let release!:()=>void;const gate=new Promise<void>(r=>release=r);await page.route("**/api/servers",async r=>{await gate;await r.fulfill({json:[server]}).catch(()=>{})});await page.goto("/containers");await expect(page.getByText(container.name,{exact:true})).toBeVisible();release();await expect(refresh(page)).toBeEnabled()
|
||||
})
|
||||
test("Контейнеры: до ответа нет демо и ложного empty",async({page})=>{
|
||||
let release!:()=>void;const gate=new Promise<void>(r=>release=r);await page.route("**/api/containers/state",async r=>{await gate;await r.fulfill({json:state()}).catch(()=>{})});await page.goto("/containers");await expect(page.getByRole("status").filter({hasText:"Загрузка контейнеров…"})).toBeVisible();await expect(page.getByText("Контейнеры не найдены",{exact:true})).toHaveCount(0);await expect(page.getByText(container.name,{exact:true})).toHaveCount(0);release();await expect(page.getByText(container.name,{exact:true})).toBeVisible()
|
||||
})
|
||||
test("Контейнеры: нет server read — запрос и команды недоступны",async({page})=>{
|
||||
await page.addInitScript(()=>localStorage.setItem("mmapp_token","header."+btoa(JSON.stringify({sub:"test",apps:["mm"],permissions:["mm:dashboard:read"],exp:Math.floor(Date.now()/1000)+3600}))+".signature"));await page.route("**/api/auth/config",r=>r.fulfill({json:{required:true}}));let count=0;await page.route("**/api/servers",r=>{count++;return r.fulfill({json:[server]})});await page.goto("/containers");await expect(page.getByText(container.name,{exact:true})).toBeVisible();await expect(page.getByRole("status").filter({hasText:"mm:servers:read"})).toBeVisible();expect(count).toBe(0);await page.getByRole("button",{name:"Действия контейнера "+container.name}).click();await expect(page.getByRole("menuitem",{name:"Остановить",exact:true})).toBeDisabled()
|
||||
})
|
||||
@@ -0,0 +1,6 @@
|
||||
import { server } from "./users"
|
||||
export { server }
|
||||
export const container = {id:"1-*A",rosId:"*A",name:"container-live",serverId:"1",image:"nginx",tag:"alpine",status:"running",envs:[],mounts:[],interfaces:["veth1"],startOnBoot:false,comment:"",cpu:0,memMb:0}
|
||||
export function state(containers:unknown[]=[container], overrides:Record<string,unknown>={}) {
|
||||
return {collectedAt:"2026-10-01T00:00:00Z",servers:[{serverId:1,collectedAt:"2026-10-01T00:00:00Z",availability:"available",containers,failures:[],...overrides}]}
|
||||
}
|
||||
Reference in new issue
Block a user