34 lines
4.6 KiB
JavaScript
34 lines
4.6 KiB
JavaScript
import fs from 'node:fs';import path from 'node:path';import net from 'node:net';import {spawn} from 'node:child_process';import assert from 'node:assert/strict';
|
|
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('Owned QA tag required');
|
|
const root=path.join(state.root,'private/report-profile-'+tag),settings=path.join(root,'settings.json'),owned=read(settings),g=read(path.join(root,'fixture.json'));
|
|
if(owned.tag!==tag||owned.cluster!=='sgf_modular_regression_'+state.id)throw Error('Owned cluster mismatch');
|
|
const output=path.join(root,'profile-http.json');if(fs.existsSync(output))throw Error('Completed report HTTP proof preserved');
|
|
await new Promise((resolve,reject)=>{const p=net.createServer();p.once('error',()=>reject(Error('Proof port unavailable')));p.listen(8047,'127.0.0.1',()=>p.close(resolve));});
|
|
const log=fs.openSync(path.join(root,'report-http-server.log'),'a'),server=spawn('C:/laragon/tools/sgf-tempo/php-8.4.27/php.exe',['-S','127.0.0.1:8047',path.resolve('scripts/payable-report-http-router.php')],{cwd:process.cwd(),env:{...process.env,SETTLEMENT_PAGE_QA_SETTINGS:settings},windowsHide:true,stdio:['ignore',log,log]});
|
|
let checks=0,auth;const jar=new Map(),check=(ok,why)=>{checks++;assert(ok,why);};
|
|
async function request(url,extra={}){
|
|
const r=await fetch('http://127.0.0.1:8047'+url,{headers:{Accept:'application/json',Cookie:[...jar].map(([k,v])=>k+'='+v).join('; '),'X-SGF-Context':auth?.context??'',...extra},redirect:'manual',signal:AbortSignal.timeout(30000)});
|
|
for(const c of r.headers.getSetCookie()){const p=c.split(';')[0],at=p.indexOf('=');jar.set(p.slice(0,at),p.slice(at+1));}const text=await r.text();let data;try{data=JSON.parse(text);}catch{}return{status:r.status,data,text,headers:r.headers};
|
|
}
|
|
try{
|
|
let ready=false;for(let n=0;n<50&&!ready;n++){try{if((await request('/api/supplier-payables/report')).status===401)ready=true;}catch{}if(!ready)await new Promise(r=>setTimeout(r,100));}
|
|
check(ready,'Unauthenticated report refused');const entry=await request('/qa/report-enter');check(entry.status===200,'Owned QA entry');auth=entry.data;
|
|
const all=await request('/api/supplier-payables/report',{'X-QA-Report-Proof':'capture'});
|
|
check(all.status===200&&all.data.totals.count===10000&&all.data.totals.balance_minor===30000000,'Complete report reconciles obligation and allocation books');
|
|
const mode=read(path.join(root,'report-http-mode.json'));check(mode.transaction.isolation==='repeatable read'&&mode.transaction.read_only==='on'&&mode.writer_locks===0,'Whole report uses read-only snapshot');
|
|
const source=g.source_id,one=await request('/api/supplier-payables/report?source='+source);
|
|
check(one.status===200&&one.data.totals.count===1&&one.data.totals.balance_minor===3000,'Source focus retains net allocations and reversal');
|
|
const html=await request('/supplier-payables/report?source='+source);
|
|
check(html.status===200&&html.text.includes('Obrigações reconhecidas — posição actual')&&html.text.includes('30,00 MZN'),'Report view displays financial position');
|
|
check(html.text.includes('/supplier-payables?')&&html.text.includes('source='+source),'Report links back to evidence');
|
|
const list=await request('/supplier-payables?source='+source);check(list.status===200&&list.text.includes('/supplier-payables/report?'),'Existing workspace links to report');
|
|
for(const query of ['page=2','company_id=999','cutoff=2026-01-01','source=0','source%5Bx%5D=1'])check((await request('/api/supplier-payables/report?'+query)).status===422,'Unsupported or injected report criteria refused');
|
|
check((await request('/api/supplier-payables/report?source=9223372036854775807')).status===403,'Unavailable source refused');
|
|
check((await request('/api/supplier-payables/report',{'X-SGF-Context':'obsolete'})).status===409,'Current context required');
|
|
const xss=await request('/supplier-payables/report?q='+encodeURIComponent('<script>report_xss</script>'));check(xss.status===200&&!xss.text.includes('<script>report_xss</script>')&&xss.text.includes('<script>report_xss</script>'),'Criteria escaped in HTML');
|
|
check(all.headers.get('cache-control').includes('no-store'),'Private report');
|
|
const result={passed:true,checks,real_http_kernel:true,transaction:mode,visual_review:false,no_browser:true};fs.writeFileSync(output,JSON.stringify(result,null,2)+'\n');console.log(JSON.stringify(result));
|
|
}finally{if(server.exitCode===null){server.kill();await new Promise(resolve=>server.once('exit',resolve));}fs.closeSync(log);}
|