import { test, expect, type Page } from "@playwright/test" const host=(id:number)=>({serverId:id,serverName:"Роутер "+id,serverSite:"",serverCountry:"RU"}) const iface=(id:number)=>({...host(id),id:"*1",instance:"default",area:"backbone",areaId:"0.0.0.0",interface:"ether"+id,cost:20,type:"broadcast",disabled:false,inactive:false,priority:1,helloInterval:10,deadInterval:40,useBfd:false}) const catalog={interfaces:[iface(1),iface(2)],neighbors:[],instances:[],bfdSessions:[],routes:[],failures:[]} async function setup(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/ospf/catalog"?catalog:path==="/api/servers"?[1,2].map(id=>({id,name:"Роутер "+id,host:"192.0.2."+id,country:"RU",enabled:true,status:null})):path==="/api/auth/config"?{required:false}:{}})})} test("OSPF: устройство и вкладка переживают reload/back",async({page})=>{ await setup(page);await page.goto("/ospf?server=2&tab=bfd&keep=x") await expect(page.getByRole("button",{name:"BFD",exact:true})).toHaveAttribute("aria-pressed","true") await expect(page.getByRole("option").filter({hasText:"Роутер 2"})).toHaveAttribute("aria-selected","true") await page.getByRole("button",{name:"Интерфейсы",exact:true}).click() await expect(page.getByText("ether2",{exact:true})).toBeVisible();await expect(page.getByText("ether1",{exact:true})).toHaveCount(0) await page.reload();await expect(page.getByText("ether2",{exact:true})).toBeVisible() await page.getByRole("option").filter({hasText:"Роутер 1"}).click() await expect(page).toHaveURL(/server=1/);await expect(page).toHaveURL(/keep=x/) await page.goBack();await expect(page.getByText("ether2",{exact:true})).toBeVisible() await page.goBack();await expect(page.getByRole("button",{name:"BFD",exact:true})).toHaveAttribute("aria-pressed","true") }) for(const context of ["server=999","tab=broken","server=1&server=2","tab=bfd&tab=routes"])test("OSPF: недоступный URL не показывает общий каталог "+context,async({page})=>{ await setup(page);await page.goto("/ospf?"+context+"&keep=x") const alert=page.getByRole("alert").filter({hasText:"Контекст OSPF недоступен"});await expect(alert).toBeVisible() await expect(page.getByText("ether1",{exact:true})).toHaveCount(0) await expect(page.getByLabel("Сводка OSPF",{exact:true})).toContainText("неизвестно") await alert.getByRole("button",{name:"Сбросить контекст OSPF"}).click() await expect(page).toHaveURL(/keep=x/);await expect(alert).toHaveCount(0);await expect(page.getByText("ether1",{exact:true})).toBeVisible() }) for(const fails of [false,true])test("OSPF: history во время записи освобождает lock "+fails,async({page})=>{ await setup(page);let writes=0,started=false,reads=0;let release!:()=>void;const gate=new Promise(resolve=>{release=resolve}) await page.route("**/api/ospf/catalog",r=>{reads++;return r.fulfill({json:catalog})}) await page.route("**/api/servers/1/ospf/optimize",async r=>{writes++;started=true;await gate;return r.fulfill(fails?{status:503,json:{error:"Старая ошибка"}}:{json:{serverId:1,serverName:"Роутер 1",pingWeight:60,optimizedCount:0,applied:[],interfaces:[]}})}) await page.goto("/ospf");await page.getByRole("option").filter({hasText:"Роутер 1"}).click() await page.getByRole("button",{name:"Оптимизировать OSPF",exact:true}).click();await expect.poll(()=>started).toBe(true) await page.goBack();await expect(page).not.toHaveURL(/server=1/) await page.goForward();const optimize=page.getByRole("button",{name:"Оптимизировать OSPF",exact:true});await expect(optimize).toBeDisabled() const before=reads;const response=page.waitForResponse(r=>r.url().includes("/api/servers/1/ospf/optimize"));release();await (await response).finished() await expect.poll(()=>reads).toBe(before+1);await expect(optimize).toBeEnabled();expect(writes).toBe(1) await expect(page.getByText("Старая ошибка",{exact:false})).toHaveCount(0) await expect(page.locator("[data-sonner-toast]").filter({hasText:"API подтвердил оптимизацию OSPF"})).toHaveCount(0) }) test("OSPF: поиск из URL не выдаёт пустой срез за отсутствие настройки",async({page})=>{ await setup(page);await page.goto("/ospf?server=2&q=ether2&keep=x") await expect(page.getByPlaceholder("Поиск в текущем разделе OSPF…")).toHaveValue("ether2") await expect(page.getByText("ether2",{exact:true})).toBeVisible();await expect(page.getByText("ether1",{exact:true})).toHaveCount(0) await page.getByPlaceholder("Поиск в текущем разделе OSPF…").fill("несуществующий") await expect(page.getByText("Нет интерфейсов по текущему поиску",{exact:true})).toBeVisible() await expect(page.getByText("OSPF интерфейсы не настроены ни на одном сервере",{exact:true})).toHaveCount(0) await page.reload();await expect(page.getByText("Нет интерфейсов по текущему поиску",{exact:true})).toBeVisible() await page.getByRole("button",{name:"Сбросить поиск",exact:true}).click() await expect(page).toHaveURL(/server=2/);await expect(page).toHaveURL(/keep=x/);await expect(page.getByText("ether2",{exact:true})).toBeVisible() }) test("OSPF: недоступный узел из URL исправляется без сброса устройства",async({page})=>{ await setup(page);await page.goto("/ospf?server=2&tab=neighbors&node=999&keep=x") const alert=page.getByRole("alert").filter({hasText:"Узел OSPF недоступен"});await expect(alert).toBeVisible() await alert.getByRole("button",{name:"Закрыть узел OSPF"}).click() await expect(alert).toHaveCount(0);await expect(page).toHaveURL(/server=2/);await expect(page).toHaveURL(/keep=x/) }) test("OSPF: выбранный узел и поиск сохраняются после reload/back",async({page})=>{ await setup(page) await page.route("**/api/ospf/catalog",r=>r.fulfill({json:{...catalog,neighbors:[1,2].map(id=>({...host(id),id:"n",address:"198.51.100."+id,routerId:"198.51.100."+id,instance:"default",area:"backbone",areaId:"0.0.0.0",interface:"ether"+id,state:"Full",uptime:"1h",priority:1,stateChanges:0}))}})) await page.goto("/ospf?tab=neighbors&q=ether1&keep=x") const rows=page.locator("tr[data-row-id]");await expect(rows).toHaveCount(1) await rows.click();await expect(page).toHaveURL(/node=1/) const details=page.getByRole("region",{name:"Детали узла OSPF"});await expect(details).toBeVisible() await page.reload();await expect(details).toBeVisible();await expect(rows).toHaveCount(1) await page.getByRole("button",{name:"Закрыть",exact:true}).click();await expect(details).toHaveCount(0) await page.goBack();await expect(details).toBeVisible();await expect(page).toHaveURL(/keep=x/) })