Files
SGF/scripts/functional-access-check.mjs
2026-10-30 23:20:27 +02:00

8 lines
3.4 KiB
JavaScript

import fs from 'node:fs/promises';import assert from 'node:assert/strict';import{createServer}from'vite';import{createHash}from'node:crypto';import React from'react';import{renderToStaticMarkup}from'react-dom/server';
const plan=JSON.parse(await fs.readFile('docs/SGF_FUNCTIONAL_ACCESS_PROPOSAL_20261030.json','utf8'));const check=JSON.parse(await fs.readFile('docs/proofs/functional-access-server-checks-20261030.json','utf8'));assert.equal(check.grants.length,82);for(const e of plan.entries)assert(check.grants.some(g=>g.permission===e.permission&&g.unit_id===e.unit_id));assert(Object.values(check.checks).every(v=>v.status==='ok'));assert(Object.values(check.guards).every(v=>v.refused===true&&v.status===403));
const server=await createServer({server:{middlewareMode:true},appType:'custom'});let before,after,nav;
try{const{navigationGroups,navigationTopics,default:Navigation}=await server.ssrLoadModule('/resources/js/WorkspaceNavigation.jsx');const canFrom=gs=>(p,u=null)=>gs.some(g=>g.permission===p&&g.unit_id===u);before=navigationTopics(navigationGroups(canFrom(plan.entries.filter(e=>e.already_granted)),1)).map(t=>t.label);const groups=navigationGroups(canFrom(check.grants),1);const topics=navigationTopics(groups);after=topics.map(t=>t.label);assert.deepEqual(after,['Início','Comercial','Compras','Stock','Oficinas','Financeiro','Contabilidade','Configuração']);assert(topics.find(t=>t.id==='commercial').sections.some(g=>g.id==='billing'&&g.items.some(i=>i.id==='service_invoices')));const ids=groups.flatMap(g=>g.items.map(i=>i.id));assert.equal(new Set(ids).size,ids.length);nav=renderToStaticMarkup(React.createElement(Navigation,{groups,page:'home',navigate:()=>{},user:{name:'Conta autorizada',email:'nala@tempo.co.mz'},busy:false,logout:()=>{}}));assert(nav.includes('topic-commercial'));}finally{await server.close();}
const root=await fetch('https://sgf.tempo.co.mz/',{signal:AbortSignal.timeout(15000)});assert.equal(root.status,200);const html=await root.text();const urls=[...new Set([...html.matchAll(/(?:src|href)="([^"]*\/build\/[^" ]+)"/g)].map(m=>m[1]))];const assets=[];for(const url of urls){const r=await fetch(url,{signal:AbortSignal.timeout(15000)});assert.equal(r.status,200);const bytes=Buffer.from(await r.arrayBuffer());const local=await fs.readFile('public'+new URL(url).pathname);assert(bytes.equals(local));assets.push({path:new URL(url).pathname,identical_local:true,sha256:createHash('sha256').update(bytes).digest('hex')});}
const demos=[];for(const url of ['http://127.0.0.1:8002/avaliar','http://127.0.0.1:8003/','http://127.0.0.1:8005/demo','http://127.0.0.1:8006/demo']){const r=await fetch(url,{signal:AbortSignal.timeout(10000)});demos.push({url,status:r.status});assert.equal(r.status,200);}
const proof={checked_at:new Date().toISOString(),manifest_matches_all_82:true,menus_before:before,menus_after:after,commercial_contains_billing_and_invoices:true,duplicates:false,public_assets:assets,demo_entries:demos,verification:'public HTTP/asset comparison, static navigation with effective grants, and server read-only probes; no interactive authenticated browser test',visual_review:false,public_test_writes:false};await fs.writeFile('docs/proofs/functional-access-navigation-20261030.json',JSON.stringify(proof,null,2)+'\n');console.log(JSON.stringify({menus:after,account_rights:82,server_checks:Object.keys(check.checks).length,guards_preserved:check.guards,demo_entries:demos}));