fix(ui): учитывать права и состояние устройства при откате истории

This commit is contained in:
Denozordec committed 2026-10-13 19:45:39 +07:00
1 parent 5ce20e8892
commit c8f9c83f45
17 files changed
+388 -16

No files matched your search

+30
View File
@@ -0,0 +1,30 @@
import {test,expect,type Page} from '@playwright/test'
const sections=['wireguard','ipsec','gre','firewall','filters','recursive-routes'] as const
function token(writer:boolean){return [Buffer.from('{}').toString('base64url'),Buffer.from(JSON.stringify({sub:'own207',apps:['mm'],permissions:['mm:dashboard:read','mm:servers:read','mm:network:read','mm:filters:read',...(writer?['mm:network:write','mm:filters:write']:[])],exp:Math.floor(Date.now()/1000)+600})).toString('base64url'),'fixture'].join('.')}
async function setup(page:Page,section:string,writer:boolean,enabled=true){let writes=0
await page.addInitScript(t=>localStorage.setItem('mmapp_token',t),token(writer))
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'){writes++;return r.fulfill({status:403,json:{error:'Unexpected mutation in read-only history fixture'}})}
const json=path.endsWith('/revisions')?{revisions:[{id:'own207-revision',serverId:'1',section,source:'apply',fingerprint:'fixture',createdAt:'2026-10-13T00:00:00Z',note:null,itemCount:1}]}:
path==='/api/auth/config'?{required:true,portal_url:'http://portal.own.invalid'}:
path==='/api/servers'?[{id:1,name:'Own207 router',host:'192.0.2.207',type:'exit-node',enabled,country:'RU',site:'OWN',asn:'',status:'online',latency:1}]:
path==='/api/wireguard'?{interfaces:[]}:
path==='/api/ipsec'?{servers:[],clients:[]}:
path==='/api/gre/tunnels'||path==='/api/filters/gre-tunnels'?{tunnels:[]}:
path==='/api/firewall/all'?{rules:[],addressLists:[]}:
path==='/api/filters/rules'?{rulesets:[{serverId:'1',rules:[]}],greTunnels:[],live:true}:
path==='/api/recursive-routes/catalog'?{serverId:1,routes:[],live:true,stale:false}:
path==='/api/recursive-routes/gateways/catalog'?{serverId:1,gateways:[]}:
path==='/api/evobgp/settings'?{enabled:false,baseUrl:'',secretConfigured:false}:{}
return r.fulfill({json})})
await page.goto('/'+section+'?server=1');return ()=>writes
}
for(const width of [1440,320])for(const section of sections)test(`История207 reader не может откатить ${section} ${width}`,async({page})=>{
await page.setViewportSize({width,height:1000});const writes=await setup(page,section,false)
const history=page.getByRole('button',{name:'История',exact:true});await expect(history).toBeEnabled();await history.click();const sheet=page.getByRole('dialog');const restore=sheet.getByRole('button',{name:'Откатить',exact:true});await expect(restore).toBeDisabled();await expect(sheet.getByRole('status')).toContainText('Откат недоступен');await restore.evaluate(b=>(b as HTMLButtonElement).click());expect(writes()).toBe(0);expect(await page.evaluate(()=>document.documentElement.scrollWidth<=innerWidth+1)).toBe(true)
})
for(const section of sections){test(`История207 writer сохраняет подтверждение ${section}`,async({page})=>{
const writes=await setup(page,section,true);const history=page.getByRole('button',{name:'История',exact:true});await expect(history).toBeEnabled();await history.click();const restore=page.getByRole('dialog').getByRole('button',{name:'Откатить',exact:true});await expect(restore).toBeEnabled();await restore.click();await expect(page.getByRole('alertdialog')).toBeVisible();await page.getByRole('alertdialog').getByRole('button',{name:'Отмена',exact:true}).click();expect(writes()).toBe(0)
});test(`История207 выключенный сервер блокирует откат ${section}`,async({page})=>{
const writes=await setup(page,section,true,false);const history=page.getByRole('button',{name:'История',exact:true});await expect(history).toBeVisible();if(await history.isEnabled()){await history.click();await expect(page.getByRole('dialog').getByRole('button',{name:'Откатить',exact:true})).toBeDisabled()}expect(writes()).toBe(0)
})}