Files
SGF/scripts/navigation-live-check.mjs
2026-10-30 08:13:34 +02:00

51 lines
4.8 KiB
JavaScript

// 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();}