Files
compras/scripts/sgf-demo-8039-http.mjs
2026-11-02 20:27:11 +02:00

89 lines
8.1 KiB
JavaScript

import fs from 'node:fs';
import assert from 'node:assert/strict';
import crypto from 'node:crypto';
const root='C:/laragon/data/sgf-tempo/run/modular-regression-2576427d1939';
const privateData=JSON.parse(fs.readFileSync(root+'/private/development.json','utf8'));
assert.deepEqual([privateData.tenant_id,privateData.company_id,privateData.unit_id],[1,1,1]);
const stateFile=root+'/private/requisitions-recovery/http-state.json';
const state=fs.existsSync(stateFile)?JSON.parse(fs.readFileSync(stateFile,'utf8')):{};
const persist=()=>fs.writeFileSync(stateFile,JSON.stringify(state,null,2));
const key=name=>{state.keys??={};state.keys[name]??=crypto.randomUUID();persist();return state.keys[name];};
const checks=[];
const clients={};
for(const p of privateData.profiles){
const jar=new Map();let csrf='',token='';
const api=async(path,method='GET',body,expected=200,commandKey)=>{
const r=await fetch('http://127.0.0.1:8039'+path,{method,headers:{Accept:'application/json','Content-Type':'application/json',Cookie:[...jar].map(([k,v])=>k+'='+v).join('; '),'X-CSRF-TOKEN':csrf,'X-SGF-Context':token,'X-Purchasing-Context':'procurement.internal','Idempotency-Key':commandKey??crypto.randomUUID()},...(body===undefined?{}:{body:JSON.stringify(body)})});
for(const cookie of r.headers.getSetCookie()){const pair=cookie.split(';')[0],at=pair.indexOf('=');jar.set(pair.slice(0,at),pair.slice(at+1));}
const text=await r.text();let d;try{d=JSON.parse(text);}catch{d={message:text.slice(0,120)};}
assert((Array.isArray(expected)?expected:[expected]).includes(r.status),`${p.role} ${path}: expected ${expected}, got ${r.status}: ${d.message??text.slice(0,180)}`);
if(d.csrf)csrf=d.csrf;if(d.context?.token)token=d.context.token;
return d;
};
await api('/api/session');await api('/api/login','POST',{email:p.email,password:privateData.password});
await api('/api/context','POST',{tenant_id:1,company_id:1,unit_id:1});clients[p.role]=api;
}
checks.push('Existing three synthetic identities authenticate without password changes');
const prep=clients.preparador,review=clients.revisor,read=clients.consulta;
const rights={1:['purchasing_requisitions.view','purchasing_requisitions.prepare','purchasing_requisitions.configure','purchasing_requisitions.approve.department_head','purchasing_budget.view','purchasing_budget.create','purchasing_budget.approve','purchasing_budget.record'],2:['purchasing_requisitions.view','purchasing_requisitions.approve.department_head','purchasing_budget.view','purchasing_budget.approve'],3:['purchasing_requisitions.view','purchasing_budget.view']};
await prep('/api/internal-requisitions/access-initialisation','POST',{operation_key:key('initial'),reason:'Owner-confirmed fourteen rights in synthetic demonstration 8039 only.',administrators:[{user_id:1,rights:rights[1]}]});
for(const [role,id] of [['revisor',2],['consulta',3]]){
const profile=privateData.profiles.find(p=>p.role===role);
for(const permission of rights[id])await prep('/api/accesses','POST',{email:profile.email,unit_id:1,permission,reason:'Owner-confirmed synthetic demonstration access 8039.'});
}
checks.push('Eight plus four plus two explicit authorised grants');
for(const api of Object.values(clients))await api('/api/context','POST',{tenant_id:1,company_id:1,unit_id:1,module_id:'purchasing'});
const centre=await prep('/api/internal-requisitions/centres','POST',{operation_key:key('centre'),code:'SINTETICO-REPRO',reason:'Owner-authorised synthetic centre for demonstration only.'});
state.centre_id=centre.centre_id;persist();
await read('/api/internal-requisitions/centres','POST',{operation_key:key('read-centre'),code:'REFUSED',reason:'Synthetic read-only refusal verification.'},403);
if(!state.budget_id){
await prep('/api/purchasing-budget/create','POST',{cost_center_id:state.centre_id,year:2026,kind:'operating',includes_tax:true,amount:'10000.00',reason:'Owner-authorised synthetic 10000 MZN allocation for 2026.'},200,key('budget'));
const list=await prep('/api/purchasing-budget');
fs.writeFileSync(root+'/private/requisitions-recovery/budget-response.json',JSON.stringify(list,null,2));
state.budget_pending=true;persist();
}
// Obtain exact budget identifier from the native response; no guessing IDs.
const workspace=await prep('/api/purchasing-budget');
const candidates=workspace.budgets??workspace.allocations??workspace.records;
if(!state.budget_id){
assert(Array.isArray(candidates),'Inspect native budget response shape before proceeding');
const found=candidates.filter(b=>Number(b.cost_center_id??b.centre_id)===Number(state.centre_id)&&Number(b.year)===2026);
assert.equal(found.length,1,'Exactly one synthetic allocation');state.budget_id=found[0].id;persist();
}
if(!state.budget_approved){
await prep('/api/purchasing-budget/approve','POST',{budget_id:state.budget_id},403,key('self-budget'));
await read('/api/purchasing-budget/approve','POST',{budget_id:state.budget_id},403,key('read-budget'));
await review('/api/purchasing-budget/approve','POST',{budget_id:state.budget_id},200,key('approve-budget'));
state.budget_approved=true;persist();
}
checks.push('Synthetic allocation 10000 MZN approved by second identity; self and read-only approvals refused');
const policy={version:0,reason:'Owner-confirmed synthetic OPER policy, demonstration 8039 only.',enabled:true,budget_required:true,first_limit_minor:1000000,second_limit_minor:2000000,approval_includes_tax:true,centres:[{id:state.centre_id,department_ref:'OPER'}],members:[{user_id:1,department_ref:'OPER'},{user_id:2,department_ref:'OPER'}]};
await read('/api/internal-requisitions/policy','POST',policy,403);
if(!state.policy_configured){await prep('/api/internal-requisitions/policy','POST',policy);state.policy_configured=true;persist();}
const draft={operation_key:key('draft'),cost_center_id:state.centre_id,priority:'normal',needed_by:'2026-11-30',justification:'Synthetic demonstration requisition, no Tempo business operation.',items:[{description:'Synthetic demonstration material',category:'Consumiveis',purchase_type:'consumable',quantity:'1.000',unit:'UN',unit_price:'100.00',tax_rate:'0.00'}]};
await read('/api/internal-requisitions/save','POST',{...draft,operation_key:key('read-save')},403);
if(!state.request_id){const saved=await prep('/api/internal-requisitions/save','POST',draft);state.request_id=saved.id;persist();}
const path='/api/internal-requisitions/'+state.request_id;
if(!state.submitted){
const submitted=await prep(path+'/submit','POST',{operation_key:key('submit'),version:1});
state.submitted=submitted;persist();
const replay=await prep(path+'/submit','POST',{operation_key:key('submit'),version:1});
assert.equal(replay.id,state.request_id);checks.push('Submission retry reuses same requisition');
}
if(!state.approved){
const decision={operation_key:key('decision'),version:state.submitted.version,decision:'approved'};
await prep(path+'/decide','POST',decision,403);
await read(path+'/decide','POST',decision,[403,404]);
await read(path+'/submit','POST',{operation_key:key('read-submit'),version:state.submitted.version},[403,404]);
const approved=await review(path+'/decide','POST',decision);assert.equal(approved.status,'approved');state.approved=true;persist();
}
for(const api of [prep,review]){const d=await api(path);assert.equal(d.record.status,'approved');assert.equal(d.history.length,3);}
await read(path,'GET',undefined,404);
await read('/api/internal-requisitions');
await read('/api/accesses','GET',undefined,403);
checks.push('Full request lifecycle, independent approval and three-event history consulted by preparer and reviewer');
checks.push('Existing record visibility rule returns 404 to read-only third identity; listing remains accessible; no extra rights granted');
checks.push('Read-only profile refused centre, budget approval, policy, preparation, submission, decision and access administration');
fs.writeFileSync('C:/laragon/www/compras/docs/proofs/sgf-demo-8039-http.json',JSON.stringify({passed:true,url:'http://127.0.0.1:8039',scope:'1:1:1',rights,centre_id:state.centre_id,budget_id:state.budget_id,request_id:state.request_id,checks,checked_at:new Date().toISOString(),visual_review:false,real_installation_changed:false},null,2));
console.log(JSON.stringify({passed:true,checks,request_id:state.request_id}));