| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768 |
- import {test,before,after} from 'node:test';import assert from 'node:assert/strict';import fs from 'node:fs';import vm from 'node:vm';import {webcrypto} from 'node:crypto';import {PGlite} from '@electric-sql/pglite';import {teacherEarningsSupport} from '../teacher-earnings-transform.mjs';
- const read=p=>fs.readFileSync(new URL(p,import.meta.url),'utf8'),gateway=read('../deploy-admin-functions.mjs').match(/const operationsGatewayCode = String.raw`([\s\S]*?)\n`;/)[1],support=teacherEarningsSupport(gateway,read('../cloud/payroll-integrity.js'));
- const currentCode=read('../cloud/teacher-earnings.js'),oldCode=read('./fixtures/teacher-earnings-monthly-baseline.js');
- const db=new PGlite();before(()=>db.exec(read('../sql/teacher-earned-months.sql')));after(()=>db.close());
- const clone=v=>JSON.parse(JSON.stringify(v));
- const lesson=(id,date,pay=40,extra={})=>({objectId:id,lessonObjectId:id,standardLessonKey:'lesson:'+id,jsmz:'30',xymz:'20',kclx:'2',lessonAt:date,courseCategoryKey:'word',teacherPaySnapshot:pay,commonStatus:99,...extra});
- function harness({company='co',rows=[],rules=[],rates=[],mappings=[],old=false,crypto=true,index=true,code=support,store=true}={}){
- const state={rows:clone(rows),rules:clone(rules),rates:clone(rates),mappings:clone(mappings),review:2.5},calls=[];
- const c=vm.createContext({Date,Promise,TextEncoder,Uint8Array,crypto:crypto?webcrypto:undefined,calcCalls:0,DEFAULT_COMPANY_ID:company});
- const queryFor=snapshot=>async(sql,args)=>{calls.push({sql,args});if(sql.includes('xs_payroll_sources'))return clone(snapshot.rows.filter(r=>String(r.coachId??r.jsmz)==args[1]&&(!args[2]||args[2].includes(String(r.appointmentGeneralId??r.yyds))))).map(s=>({s}));if(sql.includes('"CoachPayRate"'))return clone(snapshot.rates);if(sql.includes('"TeachingCompensationRule"'))return clone(snapshot.rules);if(sql.startsWith('WITH RECURSIVE'))return clone(snapshot.mappings);if(sql.includes('LessonCompletionReport'))return[];throw Error('Unexpected SQL '+sql);};
- const one=async(sql,args,inSnapshot=false)=>{calls.push({sql,args,inSnapshot});if(sql.includes('to_regprocedure'))return{available:sql.includes('xs_teacher_earned_month_read')?store:index};if(!store)throw Error('monthly helpers unavailable');return(await db.query(sql,args)).rows[0];};
- c.Psql={query:(s,a)=>queryFor(state)(s,a),one,tx:async run=>{const fresh=clone(state);let ready=false;return run({none:async sql=>{assert.equal(sql,'SET TRANSACTION ISOLATION LEVEL REPEATABLE READ READ ONLY');ready=true;calls.push({sql});},one:async(s,a)=>{assert.equal(ready,true);return one(s,a,true);},query:async(s,a)=>{assert.equal(ready,true);return queryFor(fresh)(s,a);}});}};
- vm.runInContext(code.replace('calculate:payrollCalculatedLine','calculate:(...args)=>{calcCalls++;return payrollCalculatedLine(...args)}')+'\n'+(old?oldCode:currentCode),c);
- c.reviewIncomeTotal=async()=>state.review;
- return{c,state,calls,monthly:()=>c.teacherMonthlyLessonEarnings(company,30),summary:()=>c.teacherIncomeSummary(company,30)};
- }
- function baseline(h){const b=harness({old:true,rows:h.state.rows,rates:h.state.rates,rules:h.state.rules,mappings:h.state.mappings});b.state.review=h.state.review;return b.summary();}
- test('unchanged historical months reuse exactly; a changed month alone recalculates and reviews stay live',async()=>{
- const h=harness({company:'reuse',rows:[lesson('old','2026-09-10T00:00:00Z',40.01),lesson('current','2026-11-10T00:00:00Z',0),lesson('unknown',null,40)]});
- assert.deepEqual(clone(await h.summary()),clone(await baseline(h)));assert.equal(h.c.calcCalls,3);assert.equal((await h.monthly()).monthlyReuse.matched,3);assert.equal(h.c.calcCalls,3);
- h.state.rows[1].teacherPaySnapshot=45.17;const changed=await h.monthly();assert.deepEqual(clone(changed.monthlyReuse),{available:true,matched:2,computed:1});assert.equal(h.c.calcCalls,4);assert.deepEqual(clone(await h.summary()),clone(await baseline(h)));
- h.state.review=-.5;const correction=await h.summary();assert.equal(correction.reviewIncome,-.5);assert.deepEqual(clone(correction),clone(await baseline(h)));assert.equal(h.c.calcCalls,4);
- assert.ok(h.calls.filter(call=>call.sql.startsWith('SELECT xs_teacher_earned_month_read')).every(call=>call.inSnapshot===true));
- assert.ok(h.calls.filter(call=>call.sql.startsWith('SELECT xs_teacher_earned_month_write')).every(call=>call.inSnapshot===false));
- });
- test('cross-month duplicates preserve global first representative; move/delete/empty months cannot retain phantom income',async()=>{
- const h=harness({company:'moves',rows:[lesson('first','2026-09-30T15:59:59.999Z',40,{standardLessonKey:'duplicate'}),lesson('second','2026-11-01T00:00:00Z',40,{standardLessonKey:'duplicate'}),lesson('free','2026-10-31T16:00:00Z',0),lesson('cancelled','2026-11-02T00:00:00Z',99,{commonStatus:-2})]});
- assert.deepEqual(clone(await h.summary()),clone(await baseline(h)));const initial=await h.monthly();assert.equal(initial.pendingPayCount,1);assert.equal(initial.monthlyReuse.matched,2);
- h.state.rows[1].teacherPaySnapshot=60;assert.equal((await h.monthly()).monthlyReuse.computed,1,'duplicate outside representative month still invalidates that group');
- h.state.rows.splice(0,1);assert.deepEqual(clone(await h.summary()),clone(await baseline(h)));h.state.rows[0].lessonAt='2026-12-01T00:00:00Z';assert.deepEqual(clone(await h.summary()),clone(await baseline(h)));
- h.state.rows=[];assert.deepEqual(clone(await h.summary()),{lessonIncome:0,reviewIncome:2.5,totalIncome:2.5,pendingPayCount:0});assert.equal((await h.monthly()).monthlyReuse.matched,0);
- });
- test('rules, mapping input and complete calculator closure invalidate every affected cached calculation',async()=>{
- const rows=[lesson('early','2026-08-01T00:00:00Z',null),lesson('later','2026-11-01T00:00:00Z',20)],h=harness({company:'policy',rows});await h.summary();assert.equal((await h.monthly()).monthlyReuse.matched,2);
- h.state.rates=[{classType:2,effectiveFrom:'1970-01-01',amount:50,durationHours:1,active:true}];assert.equal((await h.monthly()).monthlyReuse.computed,2);assert.deepEqual(clone(await h.summary()),clone(await baseline(h)));
- h.state.mappings=[{courseId:99,confirmed:false,unknownPolicyField:'new'}];assert.equal((await h.monthly()).monthlyReuse.computed,2);
- // Alter a private default helper, while calculate's own function text stays unchanged.
- const changedSupport=support.replace("amount:40,durationHours:1,effectiveFrom:'1970-01-01'","amount:41,durationHours:1,effectiveFrom:'1970-01-01'");assert.notEqual(changedSupport,support);
- const other=harness({company:'policy',rows:h.state.rows,rates:h.state.rates,mappings:h.state.mappings,code:changedSupport});assert.equal((await other.monthly()).monthlyReuse.computed,2);
- });
- test('details bypass monthly summaries and preserve appointment filtering; missing index and crypto have explicit exact fallbacks',async()=>{
- const h=harness({company:'details',index:false,rows:[lesson('a','2026-11-01T00:00:00Z',40,{yyds:1}),lesson('b','2026-10-01T00:00:00Z',50,{yyds:2})]});
- const actual=await h.c.teacherLessonEarnings('details',30,['2']),before=harness({old:true,rows:h.state.rows});assert.deepEqual(clone(actual),clone(await before.c.teacherLessonEarnings('co',30,['2'])));assert.equal(h.calls.some(call=>call.sql.includes('xs_teacher_earned_month')),false);assert.ok(h.calls.some(call=>call.sql.startsWith('SELECT s FROM xs_payroll_sources(')));
- const noCrypto=harness({company:'nocrypto',rows:h.state.rows,crypto:false});assert.equal((await noCrypto.monthly()).monthlyReuse.available,false);assert.deepEqual(clone(await noCrypto.summary()),clone(await baseline(noCrypto)));assert.equal(noCrypto.calls.some(call=>call.sql.includes('xs_teacher_earned_month')),false);
- const noStore=harness({company:'nostore',rows:h.state.rows,store:false});assert.equal((await noStore.monthly()).monthlyReuse.available,false);assert.deepEqual(clone(await noStore.summary()),clone(await baseline(noStore)));
- });
- test('SQL derived summaries reject direct writes, enforce numeric fields, isolate tenants and reject stale CAS overwrites',async()=>{
- const sig='a'.repeat(64),next='b'.repeat(64),entry={month:'2026-11',signature:sig,amountCents:100,pendingPayCount:0,lineCount:1,expectedSignature:null};
- const write=async(co,entries)=>(await db.query('SELECT xs_teacher_earned_month_write($1,30,$2::jsonb) n',[co,JSON.stringify(entries)])).rows[0].n;
- assert.equal(await write('cas',[entry]),1);assert.equal(await write('cas',[{...entry,signature:next,expectedSignature:sig,amountCents:200}]),1);assert.equal(await write('cas',[{...entry,amountCents:999,expectedSignature:sig}]),0);
- const stored=(await db.query('SELECT xs_teacher_earned_month_read($1,30,$2::jsonb) rows',['cas','["2026-11"]'])).rows[0].rows;assert.equal(stored[0].amountCents,200);assert.deepEqual((await db.query('SELECT xs_teacher_earned_month_read($1,30,$2::jsonb) rows',['other','["2026-11"]'])).rows[0].rows,[]);
- await assert.rejects(db.exec('UPDATE "TeacherEarnedMonth" SET "amountCents"=999 WHERE company=\'cas\''),/controlled writer/);await assert.rejects(write('cas',[{...entry,amountCents:1.5}]),/invalid teacher monthly/);await assert.rejects(write('cas',[{...entry,month:'2026-13'}]));
- });
- test('new raw fields invalidate the relevant month; hash or derived write failure leaves amounts exact',async()=>{
- const h=harness({company:'failure',rows:[lesson('a','2026-09-01T00:00:00Z',40),lesson('b','2026-10-01T00:00:00Z',50)]});await h.summary();
- h.state.rows[0].futureSourceField={value:'changed'};assert.deepEqual(clone((await h.monthly()).monthlyReuse),{available:true,matched:1,computed:1});
- const fingerprint=h.c.teacherEarningsFingerprint;let calls=0;h.c.teacherEarningsFingerprint=async value=>{if(++calls>1)throw Error('digest unavailable');return fingerprint(value);};
- assert.equal((await h.monthly()).monthlyReuse.available,false);assert.deepEqual(clone(await h.summary()),clone(await baseline(h)));
- h.c.teacherEarningsFingerprint=fingerprint;const one=h.c.Psql.one;h.c.Psql.one=async(sql,args)=>{if(sql.includes('xs_teacher_earned_month_write'))throw Error('derived write unavailable');return one(sql,args);};
- h.state.rows[0].teacherPaySnapshot=41.99;assert.deepEqual(clone(await h.summary()),clone(await baseline(h)));assert.equal((await h.monthly()).monthlyReuse.computed,1);
- });
- test('scoped source conservative supersets retain exact SQL ownership, appointment text semantics and original order',async()=>{
- await db.exec(`CREATE TABLE scoped_test_sources(ord bigint,raw jsonb);CREATE FUNCTION xs_payroll_source_entries_scoped(text,text,text,text[],boolean) RETURNS TABLE(row_data jsonb,ordinal bigint) LANGUAGE sql STABLE AS $$ SELECT raw,ord FROM scoped_test_sources ORDER BY ord DESC $$;`);
- const rows=[{coachId:'30',appointmentGeneralId:'7',tag:'first'},{coachId:'030',appointmentGeneralId:'7',tag:'padded-other'},{coachId:'',jsmz:'30',appointmentGeneralId:'7',tag:'empty-primary'},{jsmz:'30',yyds:'7',tag:'second'},{coachId:'30',appointmentGeneralId:'',yyds:'7',tag:'empty-appointment'},{coachId:'30',appointmentGeneralId:'8',tag:'other-appointment'}];
- for(let i=0;i<rows.length;i++)await db.query('INSERT INTO scoped_test_sources VALUES($1,$2)',[i,JSON.stringify(rows[i])]);
- const h=harness(),sqlDb={one:async(s,a)=>(await db.query(s,a)).rows[0],query:async(s,a)=>(await db.query(s,a)).rows};
- const actual=await h.c.teacherEarningsSourceRows(sqlDb,'scoped',30,['7']);assert.deepEqual(actual.map(row=>row.s.tag),['first','second']);
- });
|