8 lines
6.3 KiB
JavaScript
8 lines
6.3 KiB
JavaScript
import fs from 'node:fs';import assert from 'node:assert/strict';import crypto from 'node:crypto';import {session} from './sgf-single-http.mjs';
|
|
const run=JSON.parse(fs.readFileSync('C:/laragon/data/sgf-tempo/run/modular-regression-current.json')),root=run.root+'/private/receipt-composition-cc50a276c10c',file=root+'/bank-http-20261110.json';let p=fs.existsSync(file)?JSON.parse(fs.readFileSync(file)):{operations:{},checks:[]};if(p.passed){console.log(JSON.stringify({passed:true,reused:true,proof:file}));process.exit();}const save=()=>fs.writeFileSync(file,JSON.stringify(p,null,2));const ok=(v,n)=>{assert(v,n);if(!p.checks.includes(n))p.checks.push(n);save();};const prep=await session('sgf:finance_prepare','finance'),review=await session('sgf:finance_review','finance');const get=async(s=prep,query='')=>{const r=await s.get('/api/bank-reconciliation'+query);assert.equal(r.status,200);return r.json();};let state=await get();ok(prep.identity.context.bank_qa_enabled===true,'feature available in authorized finance context');ok(state.source_kind==='native_qa_ledger'&&state.source_lines.length===2,'actual native reader no harness fallback');ok(state.source_lines[0].id===4&&state.source_lines[0].credit_minor===2000&&state.source_lines[1].id===5&&state.source_lines[1].debit_minor===2000,'two existing native20 lines exact');
|
|
const importData={account_ref:'QA-BANCO-LOCAL',currency:'MZN',period:'2026-11',opening:'0.00',closing:'0.00',csv:state.template_csv};const input=(action,version,data={},proposal)=>({action,version,data,...(proposal?{proposal_id:proposal}:{}),reason:'Conferência QA individual do extracto e da correspondência exacta, sem efeito financeiro.'});
|
|
const command=async(name,s,body)=>{if(p.operations[name]?.result)return p.operations[name].result;let op=p.operations[name];if(!op){op=p.operations[name]={key:crypto.randomUUID(),input:body};save();}if(!op.cut){const r=await s.command('/api/bank-reconciliation/preview',op.input,op.key);assert.equal(r.status,200,JSON.stringify(r.value));op.cut=r.value.preview;ok(r.value.impact.new_journals===0&&r.value.impact.bank_operations===false,'preview no financial or real bank effect');save();}const r=await s.command('/api/bank-reconciliation/confirm',{preview:op.cut});assert.equal(r.status,200,JSON.stringify(r.value));op.result=r.value;save();const repeat=await s.command('/api/bank-reconciliation/confirm',{preview:op.cut});ok(repeat.status===200&&repeat.value.replayed&&repeat.value.id===r.value.id,'repeat '+name+' no duplication');return r.value;};
|
|
const imported=await command('import',prep,input('import',0,importData));state=await get();ok(state.statement.entries.length===2&&state.rows.every(r=>r.status==='unmatched'),'import does not automatically match either row');let r=await prep.command('/api/bank-reconciliation/preview',input('prepare_match',1,{row_reference:'QA-OUT-20',ledger_line_id:4}));ok(r.status===409,'unreviewed extract blocks matching');await command('review-extract',review,input('review_statement',1,{decision:'reviewed'}));r=await prep.command('/api/bank-reconciliation/preview',input('prepare_match',2,{row_reference:'QA-OUT-20',ledger_line_id:5}));ok(r.status===422,'wrong debit/credit direction refused');r=await prep.command('/api/bank-reconciliation/preview',input('prepare_match',2,{row_reference:'QA-OUT-20',ledger_line_id:999999}));ok(r.status===403,'unbound source line refused');const a=await command('prepare-out',prep,input('prepare_match',2,{row_reference:'QA-OUT-20',ledger_line_id:4}));await command('review-out',review,input('review_match',3,{decision:'reviewed'},a.id));r=await prep.command('/api/bank-reconciliation/preview',input('prepare_match',4,{row_reference:'QA-OUT-20',ledger_line_id:4}));ok(r.status===409,'already matched line cannot be reused');const b=await command('prepare-in',prep,input('prepare_match',4,{row_reference:'QA-IN-20',ledger_line_id:5}));await command('review-in',review,input('review_match',5,{decision:'reviewed'},b.id));r=await prep.get('/api/bank-reconciliation/report');ok(r.status===409,'report unavailable until final review');await command('finalize',review,input('finalize',6));state=await get();ok(state.finalized&&state.version===7&&state.history.total===7&&state.rows.every(r=>r.status==='matched'),'exact7 events and2 individually matched rows final');
|
|
r=await prep.get('/api/bank-reconciliation/report');ok(r.status===200&&r.headers.get('content-type').includes('text/csv'),'internal report downloadable');const bytes=Buffer.from(await r.arrayBuffer());ok(crypto.createHash('sha256').update(bytes).digest('hex')===r.headers.get('x-content-sha256'),'report checksum verified');ok(bytes.toString().includes('QA-OUT-20;4;2;20.00;0.00')&&bytes.toString().includes('QA-IN-20;5;3;0.00;20.00')&&bytes.toString().includes('NAO E EXTRACTO BANCARIO REAL'),'report exact actual associations and clear artificial mark');
|
|
const read=await session('sgf:read_only','finance'),readonly=await get(read,'?status=matched&per_page=100');ok(readonly.rows.length===2&&!readonly.capabilities.import&&!readonly.capabilities.prepare&&!readonly.capabilities.review,'matched filter and read-only capabilities');r=await read.command('/api/bank-reconciliation/preview',input('import',7,importData));ok(r.status===403,'read-only import refused');r=await prep.get('/api/bank-reconciliation?tenant_id=2');ok(r.status===422,'tenant override refused');const wrong=await session('sgf:finance_prepare','accounting');r=await wrong.get('/api/bank-reconciliation');ok([403,409].includes(r.status),'wrong module refused');const fresh=await session('sgf:finance_prepare','finance');r=await fresh.get('/api/bank-reconciliation/receipts/'+p.operations.import.key);const recovered=await r.json();ok(r.status===200&&recovered.result.id===imported.id&&recovered.operation_executed===false,'own import receipt recovered in fresh session');r=await review.get('/api/bank-reconciliation/receipts/'+p.operations.import.key);ok(r.status===404,'other actor receipt denied');r=await read.get('/finance/bank',{headers:{Accept:'text/html'}});ok(r.status===200&&(await r.text()).includes('sem banco ou dinheiro real'),'screen explicit synthetic scope');p.passed=true;p.business_confirmations=7;p.retained_statement_headers=1;p.retained_statement_rows=2;p.retained_events=7;p.origin_simulated=false;p.native_reader_proved=true;p.new_journals=0;p.browser_visual_review=false;save();console.log(JSON.stringify({passed:true,checks:p.checks.length,proof:file}));
|