import fs from 'node:fs'; import path from 'node:path'; import net from 'node:net'; import crypto from 'node:crypto'; import assert from 'node:assert/strict'; import {spawn} from 'node:child_process'; const read=p=>JSON.parse(fs.readFileSync(p,'utf8')); const state=read('C:/laragon/data/sgf-tempo/run/modular-regression-current.json'),tag=process.argv[2]; if(state.port!==55439||!/^[a-f0-9]{12}$/.test(tag||''))throw Error('Wrong QA context'); const root=path.join(state.root,'private/payable-history-'+tag),settings=path.join(root,'settings.json'); const owned=read(settings);if(owned.tag!==tag||owned.cluster!=='sgf_modular_regression_'+state.id)throw Error('Wrong owned fixture'); const port=8047; await new Promise((resolve,reject)=>{const probe=net.createServer();probe.once('error',()=>reject(Error('QA HTTP port occupied; no process stopped')));probe.listen(port,'127.0.0.1',()=>probe.close(resolve));}); const log=fs.openSync(path.join(root,'http-server.log'),'a'); const child=spawn('C:/laragon/tools/sgf-tempo/php-8.4.27/php.exe',['-S','127.0.0.1:'+port,path.resolve('scripts/payable-history-sgf-http-router.php')],{cwd:process.cwd(),env:{...process.env,PAYABLE_HISTORY_QA_SETTINGS:settings},windowsHide:true,stdio:['ignore',log,log]}); let assertions=0;const check=(value,message)=>{assertions++;assert(value,message);}; const client=()=>{const cookies=new Map();let auth; const request=async(url,body,options={})=>{ const headers={Accept:'application/json',Cookie:[...cookies].map(([k,v])=>k+'='+v).join('; '),'X-SGF-Context':auth?.context??''}; if(body){headers['Content-Type']='application/json';headers['X-CSRF-TOKEN']=auth?.csrf??'';headers['X-SGF-Context']=auth?.context??'';headers['Idempotency-Key']=options.key??crypto.randomUUID();} Object.assign(headers,options.headers??{}); const r=await fetch('http://127.0.0.1:'+port+url,{method:body?'POST':'GET',headers,body:body?JSON.stringify(body):undefined,redirect:'manual',signal:AbortSignal.timeout(8000)}); for(const cookie of r.headers.getSetCookie()){const pair=cookie.split(';')[0],i=pair.indexOf('=');cookies.set(pair.slice(0,i),pair.slice(i+1));} const text=await r.text();let data;try{data=JSON.parse(text);}catch{}return {status:r.status,data,text,headers:r.headers}; }; return {request,login:async role=>{const r=await request('/qa/enter/'+role);check(r.status===200,'QA role entry');auth=r.data;return auth;}}; }; try { const preparer=client();let ready=false;for(let n=0;n<50&&!ready;n++){try{await preparer.login('prepare');ready=true;}catch{await new Promise(resolve=>setTimeout(resolve,100));}}check(ready,'Owned HTTP host ready'); const readOnly=client(),validator=client();const actor=await readOnly.login('read');await validator.login('validate'); const listing=await readOnly.request('/api/supplier-payables');check(listing.status===200,'Authenticated history listing: '+listing.status+' '+(listing.data?.message??''));check(listing.data.history_import_available===true&&listing.data.history_decisions_available===false,'History linked without pending-payment decisions'); const o=listing.data.obligations[0];check(o.principal_minor===6000&&o.balance_minor===3000&&o.pending_payments_minor===1000&&o.rejected_payments_minor===500,'Same authoritative monetary position');check(listing.headers.get('cache-control').includes('no-store'),'Private finance response'); const body={id:actor.source_id,reason:'Synthetic HTTP historical source review.'}; check((await readOnly.request('/api/supplier-payables/prepare_history',body)).status===403,'Reader cannot prepare'); check((await preparer.request('/api/supplier-payables/prepare_history',body,{headers:{'X-SGF-Context':'obsolete'}})).status===409,'Obsolete context refused'); check((await preparer.request('/api/supplier-payables/prepare_history',body,{headers:{'X-CSRF-TOKEN':''}})).status===419,'Missing CSRF refused'); check((await preparer.request('/api/supplier-payables/prepare_history',{...body,company_id:999})).status===422,'Context injection refused'); const key=crypto.randomUUID();const first=await preparer.request('/api/supplier-payables/prepare_history',body,{key});check(first.status===200&&first.data.state==='recognised','HTTP reuses recognised historical origin'); const again=await preparer.request('/api/supplier-payables/prepare_history',body,{key});check(again.status===200&&again.data.replayed===true,'HTTP response loss can replay original'); const page=await readOnly.request('/supplier-payables');check(page.status===200&&page.text.includes('Saldo por liquidar: 30.00 MZN')&&page.text.includes('sem abatimento'),'Existing HTML displays original history and balance');check(page.text.includes('Não há leitura histórica em tempo real nem passagem operacional de responsabilidade validada.'),'Receipt and operational cutover distinguished visibly'); const final=await validator.request('/api/supplier-payables');check(final.status===200&&final.data.obligations.length===1&&final.data.obligations[0].balance_minor===3000,'No second debt after HTTP replay'); const report={kind:'sgf_historical_payable_actual_http_kernel',assertions,passed:true,existing_html_reused:true,csrf_and_context_enforced:true,reader_write_refused:true,no_pending_payment_decision_route_added:true,one_obligation_same_balance_after_replay:true,visual_review:false,browser_interactions_tested:false}; const output=path.join(root,'http-proof.json');if(fs.existsSync(output)){const prior=fs.readFileSync(output),archive=output+'.'+crypto.createHash('sha256').update(prior).digest('hex')+'.archive';if(!fs.existsSync(archive))fs.writeFileSync(archive,prior,{flag:'wx'});}fs.writeFileSync(output,JSON.stringify(report,null,2)+'\n');console.log(JSON.stringify(report)); } finally { if(child.exitCode===null){child.kill();await new Promise(resolve=>child.once('exit',resolve));} fs.closeSync(log); }