Files
SGF/scripts/stock-ledger-ui-check.mjs
2026-11-04 09:37:20 +02:00

15 lines
5.2 KiB
JavaScript

import fs from 'node:fs';import path from 'node:path';import vm from 'node:vm';import assert from 'node:assert/strict';import React from 'react';import {renderToStaticMarkup} from 'react-dom/server';import {createServer} from 'vite';
const tag=process.argv[2];if(!/^[a-f0-9]{12}$/.test(tag??''))throw Error('Explicit HTTP proof identifier required');const state=JSON.parse(fs.readFileSync('C:/laragon/data/sgf-tempo/run/modular-regression-current.json')),root=path.join(state.root,'private/stock-ledger-'+tag),out='docs/proofs/stock-ledger-ui-20261103.json';if(fs.existsSync(out))throw Error('Completed UI proof preserved');
const html=fs.readFileSync(path.join(root,'http-open.html'),'utf8'),contract=JSON.parse(html.match(/<script id="stock-contract" type="application\/json">([\s\S]*?)<\/script>/)[1]);
// The rendered page predates the final extra stale-field guard. Compile/run the final template's JS.
const script=fs.readFileSync('packages/general-ledger/ui/stock.php','utf8').match(/<script>([\s\S]*?)<\/script>/)[1];new vm.Script(script);let checks=0;const check=(v,n)=>{assert(v,n);checks++;};
for(const file of ['http-open.html','http-review-1.html','http-closed.html'])for(const m of fs.readFileSync(path.join(root,file),'utf8').matchAll(/<script>([\s\S]*?)<\/script>/g))new vm.Script(m[1]);check(true,'rendered and final scripts compile');
let requests=[],reloads=0,n=0;const message={},result={},events={},commandForm={dataset:{action:'post',body:JSON.stringify({proposal_id:1,revision:'a'.repeat(64)})},data:{reason:'Explicit individual synthetic confirmation.'}},rate={data:{amount:'50,00',reason:'Explicit selected synthetic rate proposal.'}},receipt={elements:{key:{value:'11111111-1111-1111-1111-111111111111'},domain:{value:'ledger'}},addEventListener:(event,fn)=>events[event]=fn};
const sandbox={AbortController,crypto:{randomUUID:()=>`11111111-1111-1111-1111-${String(++n).padStart(12,'0')}`},confirm:()=>true,location:{reload(){reloads++;}},FormData:class{constructor(f){this.f=f;}get(k){return this.f.data[k];}},document:{getElementById:id=>({'stock-contract':{textContent:JSON.stringify(contract)},message,rate,receipt,'receipt-result':result}[id]),querySelectorAll:()=>[commandForm]},fetch:(url,options)=>new Promise(resolve=>requests.push({url,options,resolve}))};vm.createContext(sandbox);vm.runInContext(script,sandbox);const event={preventDefault(){}},flush=()=>new Promise(resolve=>setImmediate(resolve)),reply=(i,data,ok=true)=>requests[i].resolve({ok,json:async()=>data});
commandForm.onsubmit(event);commandForm.onsubmit(event);check(requests.length===1,'busy form prevents duplicate submission');reply(0,{message:'Source changed'},false);await flush();check(reloads===0&&requests.length===1&&message.textContent.includes('11111111-1111-1111-1111-000000000001'),'uncertain/refused command preserves key without automatic retry');
rate.data.amount='invalid';rate.onsubmit(event);check(requests.length===1,'invalid decimal does not submit');rate.data.amount='50,00';rate.onsubmit(event);check(JSON.parse(requests[1].options.body).unit_minor===5000&&requests[1].options.method==='POST','exact50MZN converts to5000 minor units');reply(1,{rate_id:1});await flush();check(reloads===1,'known successful command reloads current evidence');
const first=receipt.onsubmit(event);receipt.elements.domain.value='rate';events.change();const second=receipt.onsubmit(event);reply(2,{result:'OLD'});await first;check(requests[2].options.signal.aborted&&result.textContent==='','changed domain cancels and discards old lookup');reply(3,{result:'CURRENT',operation_executed:false});await second;check(result.textContent.includes('CURRENT')&&!requests[3].options.method,'current receipt lookup uses GET only');const third=receipt.onsubmit(event);receipt.elements.key.value='22222222-2222-2222-2222-222222222222';reply(4,{result:'WRONG-KEY'});await third;check(!result.textContent.includes('WRONG-KEY'),'late lookup cannot paint another key even without an input event');
const server=await createServer({configFile:false,cacheDir:path.join(root,'vite-ui-cache'),optimizeDeps:{noDiscovery:true,include:[]},server:{middlewareMode:true},appType:'custom'});
try{const {default:Menu}=await server.ssrLoadModule('/resources/js/AccountingMenuWorkspace.jsx');const render=(flag,stock=true,gl=true,unit=2)=>renderToStaticMarkup(React.createElement(Menu,{section:'accounting_panel',context:{token:'qa',company:{name:'QA'},unit:unit?{id:unit,name:'QA'}:null,accounting:{stock_valuation_read:flag}},can:(p,u)=>p==='company.view'||(u===2&&((p==='stock.view'&&stock)||(p==='general_ledger.view'&&gl))),navigate(){}}));check(render(true).includes('href="/general-ledger/stock-valuation"'),'SGF menu links configured observer with both scoped rights');for(const args of [[false],[true,false],[true,true,false],[true,true,true,3],[true,true,true,null]])check(!render(...args).includes('href="/general-ledger/stock-valuation"'),'SGF observer link is withheld without flag/dual rights/current unit');}finally{await server.close();}
fs.writeFileSync(out,JSON.stringify({passed:true,checks,node_vm_and_react_render:true,actual_browser:false,no_automatic_command_retry:true,stale_receipt_responses_discarded:true,sgf_scoped_navigation:true},null,2));console.log(JSON.stringify({passed:true,checks}));