// Existing synthetic host only: entry/session/logout and served assets; no business commands. import assert from 'node:assert/strict'; import fs from 'node:fs/promises'; import React from 'react'; import {renderToStaticMarkup} from 'react-dom/server'; import {createServer} from 'vite'; const base='http://127.0.0.1:8002'; const vite=await createServer({server:{middlewareMode:true},appType:'custom'}); try{ const {navigationGroups,navigationTopics,default:Navigation}=await vite.ssrLoadModule('/resources/js/WorkspaceNavigation.jsx'); const {serviceAreas}=await vite.ssrLoadModule('/resources/js/ServicesWorkspace.jsx'); const registry=new Set(Object.keys(serviceAreas)); const source=await fs.readFile('resources/js/app.jsx','utf8'); for(const m of source.matchAll(/page\s*===\s*['"]([^'"]+)['"]/g))registry.add(m[1]); for(const [file,field] of [['billing-menu.js','billingSections'],['workshop-menu.js','workshopSections'],['stock-menu.js','stockSections'],['accounting-menu.js','accountingSections']]){ const module=await vite.ssrLoadModule('/resources/js/'+file);for(const item of module[field])registry.add(item.id); } const manifest=JSON.parse(await fs.readFile('public/build/manifest.json','utf8'))['resources/js/app.jsx']; const root=await fetch(base+'/',{signal:AbortSignal.timeout(15000),headers:{Connection:'close'}});assert.equal(root.status,200); assert((await root.text()).includes(manifest.file)); const asset=await fetch(base+'/build/'+manifest.file,{signal:AbortSignal.timeout(15000),headers:{Connection:'close'}});assert.equal(asset.status,200); assert((await asset.text()).includes('purchasing_home')); const results=[]; for(const role of ['proprietario','preparador','validador','tecnico','verificador']){ const cookies=new Map(); async function request(path,method='GET',body){ const r=await fetch(base+path,{signal:AbortSignal.timeout(15000),method,redirect:'manual',headers:{Connection:'close',Cookie:[...cookies].map(([k,v])=>`${k}=${v}`).join('; '),...(body?{'Content-Type':'application/x-www-form-urlencoded'}:{})},...(body?{body:new URLSearchParams(body)}:{})}); for(const c of r.headers.getSetCookie()){const pair=c.split(';',1)[0],i=pair.indexOf('=');cookies.set(pair.slice(0,i),pair.slice(i+1));} return {status:r.status,text:await r.text()}; } const landing=await request('/avaliar');assert.equal(landing.status,200); const csrf=landing.text.match(/name="_token" value="([^"]+)"/)?.[1];assert(csrf); assert.equal((await request('/avaliar/entrar','POST',{role,_token:csrf})).status,302,role+' entry'); const session=JSON.parse((await request('/api/session')).text);assert(session.user&&session.context); const can=(p,u=null)=>session.context.grants.some(g=>g.permission===p&&g.unit_id===u); const groups=navigationGroups(can,session.context.unit?.id??null),topics=navigationTopics(groups); const destinations=groups.flatMap(g=>g.items.map(i=>i.id)); for(const id of destinations)assert(registry.has(id),role+' unregistered destination '+id); assert.equal(new Set(destinations).size,destinations.length); assert(topics.some(t=>t.id==='purchasing'),role+' purchasing visible'); assert(topics.some(t=>t.id==='stock'),role+' stock separately visible'); assert(topics.some(t=>t.id==='accounting'&&t.label==='Contabilidade'),role+' accounting separately visible'); const html=renderToStaticMarkup(React.createElement(Navigation,{groups,page:'purchasing_home',navigate:()=>{},user:{name:'Conta sintética',email:'avaliacao@example.test'},busy:false,logout:()=>{}})); assert(html.includes('topic-purchasing'));assert(html.includes('topic-stock'));assert(html.includes('topic-accounting'));assert(html.includes('Área de Compras')); const logout=await fetch(base+'/api/logout',{signal:AbortSignal.timeout(15000),method:'POST',headers:{Connection:'close',Accept:'application/json',Cookie:[...cookies].map(([k,v])=>`${k}=${v}`).join('; '),'X-CSRF-TOKEN':session.csrf}});assert.equal(logout.status,200); results.push({role,topics:topics.map(t=>t.label),destinations:destinations.length,purchasing_visible:true,stock_separate:true,accounting_separate:true,all_destinations_registered:true}); } await fs.writeFile('docs/proofs/navigation-live.json',JSON.stringify({checked_at:new Date().toISOString(),host:base,current_bundle_served:true,results,business_commands:0,accounts_created:0,permissions_changed:0,type:'HTTP/session plus static render; not interactive browser review',visual_review:false},null,2)+'\n'); console.log('Live navigation passed for five existing synthetic roles: Compras and Stock separate, all destinations registered, current bundle served. No business commands or access changes.'); }finally{await vite.close();}