import {test,expect} from "@playwright/test" for(const hasProbe of [true,false])test("Оптимизатор: исходные измерения без clamp/"+hasProbe,async({page})=>{ await page.route("**/health",r=>r.fulfill({json:{ok:true}})) const probe={id:"p",srcServerId:"1",dstServerId:"2",srcInterface:"ether1",dstInterface:"ether2",enabled:true,lastPingRttMs:0,lastPingLossPct:0,lastTxAvgMbps:150.25,lastRxAvgMbps:null,lastPingAt:"2020-01-01T00:00:00Z"} 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/servers"?["home-router","jump-host","exit-node"].map((type,i)=>({id:i+1,name:"Router"+i,host:"192.0.2."+i,type,country:"RU",enabled:true,status:"online",latency:10,...(i===0?{wanUplinks:[{id:"w",name:"WAN",iface:"ether1",isp:"ISP",ip:"198.51.100.1",maxDl:100,maxUl:100}]}:{})})):p==="/api/filters/rules"?{rulesets:[]}:p==="/api/uptime/speed-probes"?{probes:hasProbe?[probe]:[]}:p==="/api/ospf/catalog"?{interfaces:[],instances:[],neighbors:[],bfdSessions:[],routes:[],failures:[]}:p.endsWith("/preview")?{serverId:1,serverName:"Router0",pingWeight:60,interfacesTotal:0,changedCount:0,unchangedCount:0,interfaces:[],changes:[]}:{}})}) await page.goto("/route-optimizer") const matrix=page.getByRole("table").filter({hasText:"WAN-аплинк"}) await expect(matrix.locator("tr[data-row-id]")).toHaveCount(1) await expect(matrix).toContainText(hasProbe?"0 мс":"— мс") await expect(matrix).toContainText(hasProbe?"↓— ↑150.25":"↓— ↑—") await expect(matrix).toContainText(hasProbe?"Скорость: дата неизвестна":"Измерения отсутствуют") if(hasProbe)await expect(page.getByRole("region",{name:"Матрица WAN и Jump Host"})).toContainText("Сопоставление с WAN расчётное") if(hasProbe)await expect(matrix.locator("time")).toHaveAttribute("datetime","2020-01-01T00:00:00Z") await page.getByRole("button",{name:"Маршруты (1)",exact:true}).click() await expect(page.getByRole("table").filter({hasText:"# Маршрут"})).toContainText("Ping модели") }) test("Оптимизатор: OSPF исходный ноль и происхождение метрик",async({page})=>{ const unknown={value:null,source:"unknown",sourceId:null,measuredAt:null} const item={id:"i",interface:"ether1",currentCost:10,optimalCost:20,score:50,pingMs:1,dlMbps:10,ulMbps:10,observed:{ping:{value:0,source:"speed-probe",sourceId:"p",measuredAt:"2020-01-01T00:00:00Z"},dl:unknown,ul:{value:0,source:"interface-traffic",sourceId:"ether1",measuredAt:"2020-01-02T00:00:00Z"}}} 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/servers"?[{id:1,name:"Router",host:"192.0.2.1",type:"home-router",country:"RU",enabled:true,status:null}]:p==="/api/filters/rules"?{rulesets:[]}:p==="/api/uptime/speed-probes"?{probes:[]}:p==="/api/ospf/catalog"?{interfaces:[],instances:[],neighbors:[],bfdSessions:[],routes:[],failures:[]}:p.endsWith("/preview")?{serverId:1,serverName:"Router",pingWeight:60,interfacesTotal:1,changedCount:1,unchangedCount:0,interfaces:[item],changes:[item]}:{}})}) await page.goto("/route-optimizer") const grid=page.getByRole("table").filter({hasText:"Speed (dl/ul)"}) await expect(grid).toContainText("Ping модели") await expect(grid).toContainText("1ms") await expect(grid).toContainText("Проба: 0 мс") await expect(grid).toContainText("Измерение: —") await expect(grid).toContainText("Трафик интерфейса: 0 Мбит/с") }) test("Оптимизатор: WAN не подставляется из демо",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/servers"?[{id:1,name:"Router",host:"192.0.2.1",type:"home-router",country:"RU",enabled:true,status:null}]:p==="/api/filters/rules"?{rulesets:[]}:p==="/api/uptime/speed-probes"?{probes:[]}:p==="/api/ospf/catalog"?{interfaces:[],instances:[],neighbors:[],bfdSessions:[],routes:[],failures:[]}:p.endsWith("/preview")?{serverId:1,serverName:"Router",pingWeight:60,interfacesTotal:0,changedCount:0,unchangedCount:0,interfaces:[],changes:[]}:{}})}) await page.goto("/route-optimizer") await expect(page.getByText("Нет WAN-аплинков для расчёта.",{exact:false})).toBeVisible() await expect(page.getByRole("table").filter({hasText:"WAN-аплинк"}).locator("tr[data-row-id]")).toHaveCount(0) await expect(page.getByRole("link",{name:"Настроить сервер",exact:true})).toHaveAttribute("href","/servers?server=1") }) for(const reversed of [false,true])test("Оптимизатор: направление peer и асимметричные лимиты/"+reversed,async({page})=>{ const common={host:"192.0.2.1",country:"RU",enabled:true,status:"online",latency:10} const devices=[{...common,id:1,name:"Home",type:"home-router",wanUplinks:[{id:"w",name:"WAN",iface:"ether1",isp:"ISP",ip:"198.51.100.1",maxDl:100,maxUl:20}]},{...common,id:2,name:"Jump",type:"jump-host"},{...common,id:3,name:"Exit",type:"exit-node"}] const base={srcInterface:"",dstInterface:"",enabled:true,lastPingRttMs:5,lastPingLossPct:0,lastPingAt:"2026-10-07T00:00:00Z"} const probes=[{...base,id:"home",srcServerId:"1",dstServerId:"2",srcInterface:"ether1",lastTxAvgMbps:30,lastRxAvgMbps:80},{...base,id:"peer",srcServerId:reversed?"3":"2",dstServerId:reversed?"2":"3",lastTxAvgMbps:reversed?90:40,lastRxAvgMbps:reversed?40:90}] const writes:string[]=[] await page.route("**/health",r=>r.fulfill({json:{ok:true}})) await page.route("**/api/**",r=>{const path=new URL(r.request().url()).pathname;if(r.request().method()!=="GET"&&!path.endsWith("/preview"))writes.push(path);return r.fulfill({json:path==="/api/auth/config"?{required:false}:path==="/api/servers"?devices:path==="/api/filters/rules"?{rulesets:[]}:path==="/api/uptime/speed-probes"?{probes}:path==="/api/ospf/catalog"?{interfaces:[],instances:[],neighbors:[],bfdSessions:[],routes:[],failures:[]}:path.endsWith("/preview")?{serverId:1,serverName:"Home",pingWeight:60,interfacesTotal:0,changedCount:0,unchangedCount:0,interfaces:[],changes:[]}: {}})}) await page.goto("/route-optimizer") await expect(page.getByRole("table").filter({hasText:"WAN-аплинк"})).toContainText("↓80 ↑30") await page.getByRole("button",{name:"Маршруты (1)",exact:true}).click() const routes=page.getByRole("table").filter({hasText:"# Маршрут"}) await expect(routes).toContainText("Модель: 5 мс · ↓80 ↑20") await expect(routes).toContainText("Модель: 5 мс · ↓90 ↑40") expect(writes).toEqual([]) })