30 lines
5.9 KiB
JavaScript
30 lines
5.9 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 state=JSON.parse(fs.readFileSync('C:/laragon/data/sgf-tempo/run/modular-regression-current.json','utf8')),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/payable-history-'+tag),settings=path.join(root,'settings.json'),config=JSON.parse(fs.readFileSync(settings,'utf8'));
|
|
if(config.tag!==tag||config.cluster!=='sgf_modular_regression_'+state.id)throw Error('Wrong owned host');
|
|
await new Promise((resolve,reject)=>{const probe=net.createServer();probe.once('error',()=>reject(Error('Loopback proof port occupied')));probe.listen(8047,'127.0.0.1',()=>probe.close(resolve));});
|
|
const log=fs.openSync(path.join(root,'page-http-server.log'),'a'),child=spawn('C:/laragon/tools/sgf-tempo/php-8.4.27/php.exe',['-S','127.0.0.1:8047',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 checks=0;const check=(v,why)=>{checks++;assert(v,why);};
|
|
const client=()=>{const jar=new Map();let auth;const request=async(url,{body,headers:extra={}}={})=>{const headers={Accept:'application/json','X-SGF-Context':auth?.context??'',Cookie:[...jar].map(([k,v])=>k+'='+v).join('; '),...extra};if(body){headers['Content-Type']='application/json';headers['X-CSRF-TOKEN']=auth?.csrf??'';headers['Idempotency-Key']=crypto.randomUUID();}const r=await fetch('http://127.0.0.1:8047'+url,{method:body?'POST':'GET',headers,body:body?JSON.stringify(body):undefined,redirect:'manual',signal:AbortSignal.timeout(8000)});for(const c of r.headers.getSetCookie()){const pair=c.split(';')[0],at=pair.indexOf('=');jar.set(pair.slice(0,at),pair.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};};return {request,enter:async role=>{const r=await request('/qa/enter/'+role);if(r.status!==200)throw Error('QA entry failed');auth=r.data;return auth;}};};
|
|
try {
|
|
const reader=client();let ready=false;for(let n=0;n<50&&!ready;n++){try{await reader.enter('read');ready=true;}catch{await new Promise(r=>setTimeout(r,100));}}check(ready,'Owned host ready');
|
|
const first=await reader.request('/api/supplier-payables');check(first.status===200,'Page endpoint available');const a=first.data,volume=a.pagination.documents.total;check(volume>=1000&&a.documents.length===25&&a.obligations.length===25,'Default source and position window bounded');check(a.proposals.length===25&&a.events.length===25,'Evidence windows bounded');
|
|
const second=await reader.request('/api/supplier-payables?page=2');check(second.status===200&&second.data.documents.length===25,'Second source page');const keys=new Set(a.documents.map(d=>d.local_id));check(!second.data.documents.some(d=>keys.has(d.local_id)),'Disjoint pages');check(a.documents.at(-1).local_id>second.data.documents[0].local_id,'Stable descending order');
|
|
const large=await reader.request('/api/supplier-payables?per_page=100');check(large.status===200&&large.data.documents.length===100&&large.data.events.length===100,'100 row server page');
|
|
const events=await reader.request('/api/supplier-payables?event_page=2');check(events.status===200&&!events.data.events.some(e=>a.events.some(old=>old.id===e.id)),'Independent disjoint event window');check(events.data.documents[0].local_id===a.documents[0].local_id,'Event pager preserves source window');
|
|
const one=await reader.request('/api/supplier-payables?q=BENCH-00002');check(one.status===200&&one.data.pagination.documents.total===1&&one.data.documents.length===1,'Number filter before pagination');
|
|
const missing=await reader.request('/api/supplier-payables?recognition=unrecognised');check(missing.status===200&&missing.data.pagination.documents.total===0&&missing.data.events.length===0,'Recognition filter does not expose other records');
|
|
for(const query of ['page=0','per_page=1000','event_page=0','recognition=paid','company_id=999','q%5Ba%5D=x'])check((await reader.request('/api/supplier-payables?'+query)).status===422,'Unsupported/invalid filters refused');
|
|
check((await reader.request('/api/supplier-payables',{headers:{'X-SGF-Context':'obsolete'}})).status===409,'Stale context refused on reads');
|
|
check((await reader.request('/api/supplier-payables/prepare_history',{body:{id:a.documents[0].local_id,reason:'Reader must not acquire write rights from a page.'}})).status===403,'Read page grants no write access');
|
|
const html=await reader.request('/supplier-payables?q=BENCH&recognition=recognised&per_page=25');check(html.status===200&&html.text.includes('Filtrar documentos')&&html.text.includes('Paginação documents')&&html.text.includes('Paginação events'),'Existing HTML has server filters and pagers');check(html.text.includes('q=BENCH&recognition=recognised'),'Pager preserves search/recognition');
|
|
const injection=await reader.request('/supplier-payables?q='+encodeURIComponent('<script>page_injection</script>'));check(injection.status===200&&!injection.text.includes('<script>page_injection</script>')&&injection.text.includes('<script>page_injection</script>'),'Search text escaped in HTML');
|
|
check(first.headers.get('cache-control').includes('no-store'),'Private response');
|
|
const report={kind:'financial_pagination_actual_http',checks,passed:true,volume,default_page:25,larger_page:100,filter_before_count:true,source_and_evidence_pages_disjoint:true,context_and_rights_enforced:true,unknown_parameters_refused:true,html_escaping:true,visual_review:false,browser_interactions_tested:false};fs.writeFileSync(path.join(root,'page-http.json'),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);}
|