payroll-read-timeout-recovery.test.mjs 3.0 KB

1234567891011121314151617181920212223242526272829303132
  1. import {test} from 'node:test';
  2. import assert from 'node:assert/strict';
  3. import {readFileSync} from 'node:fs';
  4. import {PGlite} from '@electric-sql/pglite';
  5. test('read recovery preserves corrected receipts, tenant fields and function results; rerun is safe',async()=>{
  6. const db=new PGlite();try{
  7. const rewards=readFileSync(new URL('../sql/review-rewards.sql',import.meta.url),'utf8');
  8. const table=name=>rewards.match(new RegExp('CREATE TABLE IF NOT EXISTS "'+name+'" \\([^]*?\\n\\);'))[0];
  9. await db.exec(table('ReviewReward')+`;ALTER TABLE "ReviewReward" ADD COLUMN "ineligibleReason" text NOT NULL DEFAULT '';`);
  10. await db.exec(table('ReviewRewardCorrection'));
  11. await db.exec(`CREATE FUNCTION xs_payroll_sources(text) RETURNS SETOF jsonb LANGUAGE sql STABLE AS $$ SELECT '{"amount":1}'::jsonb $$;
  12. CREATE FUNCTION xs_payroll_source_version(text) RETURNS text LANGUAGE sql STABLE AS $$ SELECT 'unchanged'::text $$;
  13. CREATE FUNCTION xs_payroll_cutoff_snapshot(text,text) RETURNS jsonb LANGUAGE sql STABLE AS $$ SELECT '{"approved":false}'::jsonb $$;
  14. INSERT INTO "ReviewReward" ("objectId",company,"taskId","coachId","completedAt","incomeMonth",status,amount,"ineligibleReason") VALUES
  15. ('earned','a','t1',1,now(),'2026-10','earned',1,''),('corrected','a','t2',1,now(),'2026-10','earned',1,''),
  16. ('overdue','a','t3',1,now(),'2026-09','ineligible',0,'overdue'),('other','b','t4',2,now(),'2026-10','earned',1,'');
  17. INSERT INTO "ReviewRewardCorrection" ("objectId",company,"rewardId",amount,reason,actor) VALUES ('fix','a','corrected',-1,'核对逾期','admin');
  18. CREATE VIEW "ReviewRewardIncome" AS SELECT projected.*,r.amount AS "originalAmount",c."objectId" AS "correctionId",c.reason AS "correctionReason",c.amount AS "correctionAmount",c."adjustmentBatchId"
  19. FROM "ReviewReward" r LEFT JOIN "ReviewRewardCorrection" c ON c.company=r.company AND c."rewardId"=r."objectId"
  20. CROSS JOIN LATERAL jsonb_populate_record(NULL::"ReviewReward",to_jsonb(r)||jsonb_build_object('amount',r.amount+COALESCE(c.amount,0),'status',CASE WHEN c."objectId" IS NULL THEN r.status ELSE 'ineligible' END,'ineligibleReason',CASE WHEN c."objectId" IS NULL THEN r."ineligibleReason" ELSE 'corrected_overdue' END)) projected;`);
  21. const sql='SELECT * FROM "ReviewRewardIncome" ORDER BY "objectId"',before=(await db.query(sql)).rows;
  22. const patch=readFileSync(new URL('../sql/payroll-read-timeout-recovery.sql',import.meta.url),'utf8');
  23. for(let i=0;i<2;i++){
  24. await db.exec(patch);assert.deepEqual((await db.query(sql)).rows,before);
  25. assert.equal((await db.query("SELECT xs_payroll_source_version('a') v")).rows[0].v,'unchanged');
  26. assert.deepEqual((await db.query('SELECT "objectId" FROM "ReviewRewardIncome" WHERE company=$1 AND "incomeMonth"=$2 AND status=\'earned\'',['a','2026-10'])).rows,[{objectId:'earned'}]);
  27. const functions=(await db.query("SELECT proconfig FROM pg_proc WHERE proname IN ('xs_payroll_sources','xs_payroll_source_version','xs_payroll_cutoff_snapshot')")).rows;
  28. assert.equal(functions.length,3);assert.ok(functions.every(f=>f.proconfig.includes('jit=off')));
  29. }
  30. }finally{await db.close();}
  31. });