| 123456789101112131415161718192021 |
- import {test} from 'node:test';import assert from 'node:assert/strict';import fs from 'node:fs';import vm from 'node:vm';
- const source=fs.readFileSync(new URL('../cloud/payroll-integrity.js',import.meta.url),'utf8');
- function setup(){const calls=[],c=vm.createContext({Date,Psql:{one:async(sql,args)=>{calls.push({sql,args});return sql.includes('_read')?{result:{stamp:'current',payload:null}}:{saved:true};}}});vm.runInContext(source,c);c.text=v=>String(v||'');c.number=v=>Number(v)||0;c.payrollTenant=()=> 'co';let count=0;c.payrollPreviewUncached=async()=>{count++;return{payableAmount:123,sourceStatus:{sync:{state:'batch_verified',settlementReady:false}}};};return{c,calls,count:()=>count};}
- test('commands and require-live callers cannot receive a cached approval',async()=>{const {c,calls,count}=setup();for(const [used,live,targetId]of[[false,false,''],[true,true,''],[true,false,'batch']])await c.payrollPreviewData({}, {targetId},'2026-11',used,live);assert.equal(count(),3);assert.equal(calls.length,0);});
- test('cache miss computes authoritative output and publishes with original dependency stamp',async()=>{const {c,calls,count}=setup();const r=await c.payrollPreviewData({storeId:12},{storeId:99,search:'a'},'2026-11',true);assert.equal(r.payableAmount,123);assert.equal(count(),1);assert.equal(calls.length,2);assert.equal(calls[1].args[2],'current');assert.match(calls[0].args[1],/12,99,"a",false,false/);});
- test('cached hit preserves amounts and denied settlement; roles are checked before reading cache',async()=>{const {c,count}=setup();c.Psql.one=async()=>({result:{stamp:'current',payload:{payableAmount:123,sourceStatus:{sync:{state:'batch_verified',settlementReady:false}}}}});const r=await c.payrollPreviewData({}, {},'2026-11',true);assert.equal(r.payableAmount,123);assert.equal(r.sourceStatus.sync.settlementReady,false);assert.equal(r.sourceStatus.cacheHit,true);assert.equal(count(),0);c.payrollTenant=()=>{throw Error('denied');};await assert.rejects(c.payrollPreviewData({}, {},'2026-11',true),/denied/);});
- test('cache error falls back; failed verification is never cached',async()=>{const {c,calls,count}=setup();c.Psql.one=async()=>{throw Error('database missing helper');};assert.equal((await c.payrollPreviewData({}, {},'2026-11',true)).payableAmount,123);assert.equal(count(),1);const other=setup();other.c.payrollPreviewUncached=async()=>({sourceStatus:{sync:{state:'error'}}});await other.c.payrollPreviewData({}, {},'2026-11',true);assert.equal(other.calls.length,1);});
- test('summary transport preserves every verified total; coach details preserve scope and exceptions',async()=>{
- const {c}=setup(),lines=[{coachId:1,storeId:10,amount:20},{coachId:2,storeId:20,amount:30,exception:'needs review'}];
- c.businessDate=()=> '2026-11-10';c.payrollPreviewData=async()=>({lines,sourceStatus:{sync:{state:'confirmed',settlementReady:true}}});c.payrollPage=async()=>({items:[]});c.storeNameOf=()=> 'store';c.Psql.query=async()=>[];
- c.payrollResult=(month,rows,status,input)=>{const visible=rows.filter(l=>!input.storeId||l.storeId===input.storeId);return{month,lines:visible,exceptions:visible.filter(l=>l.exception),coachItems:visible.map(l=>({coachId:l.coachId,amount:l.amount})),payableAmount:visible.reduce((a,l)=>a+l.amount,0),exceptionCount:visible.filter(l=>l.exception).length,sourceStatus:status};};
- const full=await c.payrollOverviewData({},{}),summary=await c.payrollOverviewData({},{summaryOnly:true}),detail=await c.payrollOverviewData({},{lineCoachId:2});
- const withoutRows=r=>JSON.parse(JSON.stringify({...r,lines:[],exceptions:[]}));assert.deepEqual(withoutRows(full),withoutRows(summary));assert.deepEqual(withoutRows(full),withoutRows(detail));assert.equal(summary.lines.length,0);assert.equal(detail.lines.length,1);assert.equal(detail.exceptions.length,1);assert.equal(detail.lines[0].coachId,2);
- const scoped=await c.payrollOverviewData({storeId:10,store:{toJSON:()=>({})}},{storeId:20,lineCoachId:2});assert.equal(scoped.lines.length,0);assert.equal(scoped.payableAmount,20);
- });
- test('export rejects presentation-only projections even when caller forwards overview flags',async()=>{
- const {c}=setup();let seen;c.payrollOverviewData=async(_context,input)=>{seen=input;return{month:'2026-11',lines:[],sourceStatus:{}};};c.payrollVisibleLines=rows=>rows;c.payrollResult=()=>({coachItems:[]});
- await c.payrollExportData({},{summaryOnly:true,lineCoachId:9,coachId:2});assert.equal(seen.summaryOnly,false);assert.equal(seen.lineCoachId,0);assert.equal(seen.coachId,2);
- });
|