admin-speed.test.mjs 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142
  1. import {test} from 'node:test';
  2. import assert from 'node:assert/strict';
  3. import vm from 'node:vm';
  4. import {readFileSync} from 'node:fs';
  5. const source=readFileSync(new URL('../deploy-admin-functions.mjs',import.meta.url),'utf8').match(/const operationsGatewayCode = String.raw`([\s\S]*?)\n`;/)[1];
  6. const payroll=readFileSync(new URL('../cloud/payroll-integrity.js',import.meta.url),'utf8');
  7. test('business dates preserve Shanghai boundaries and invalid values without constructing formatters per record',()=>{
  8. let slowCalls=0,formatters=0;
  9. class MeasuredDate extends Date {toLocaleDateString(...args){slowCalls++;return super.toLocaleDateString(...args);}}
  10. const c=vm.createContext({Date:MeasuredDate,Intl:{DateTimeFormat:function(...args){formatters++;return new Intl.DateTimeFormat(...args);}}});
  11. vm.runInContext(source+'\n'+payroll,c);
  12. const values=['2026-10-31T15:59:59Z','2026-10-31T16:00:00Z','1990-07-01T16:00:00Z','2024-02-29T16:00:00Z','bad','',null,0];
  13. for(let i=0;i<1000;i++)values.push(new Date(Date.UTC(1985+i%45,i%12,i%28+1,i%24,i%60)).toISOString());
  14. for(const value of values)assert.equal(c.businessDate(value),new Date(value).toLocaleDateString('sv-SE',{timeZone:'Asia/Shanghai'}));
  15. for(const value of values){const iso=c.payrollDateIso(value);assert.equal(c.payrollBusinessDate(value),iso?new Date(iso).toLocaleDateString('sv-SE',{timeZone:'Asia/Shanghai'}):'');}
  16. assert.equal(slowCalls,0,'record loops must reuse the timezone formatter');
  17. assert.equal(formatters,2);
  18. });
  19. test('coach list starts its independent reads together instead of serial round trips',async()=>{
  20. const pending=[],calls=[];
  21. const c=vm.createContext({Date,Psql:{query:sql=>{calls.push(sql);return new Promise(resolve=>pending.push(resolve));}}});vm.runInContext(source,c);
  22. c.companyIdOf=()=> 'tenant';c.availabilityRows=async()=>[];
  23. const result=c.coachRows({roles:['ops-manager']},{});
  24. await Promise.resolve();
  25. const started=calls.length;
  26. // Let both old serial and new parallel implementations finish before asserting.
  27. for(let i=0;i<10;i++){for(const resolve of pending.splice(0))resolve([]);await new Promise(r=>setImmediate(r));}
  28. await result;
  29. assert.equal(started,3,'users, appointments and lesson facts have no dependency on one another');
  30. });
  31. test('release patch changes only the named read helpers and preserves unrelated deployed fixes',async()=>{
  32. const {patchAdminSpeed,speedHelpers}=await import('../admin-speed-patch.mjs');
  33. const fixture='const privateDeploymentSetting="preserve";\nfunction unrelatedLatestFix(){return 71;}\n'+speedHelpers.map(name=>`function ${name}(){return "old";}`).join('\n');
  34. const patched=patchAdminSpeed(fixture);assert.ok(patched.startsWith('const privateDeploymentSetting="preserve";\nfunction unrelatedLatestFix(){return 71;}'));
  35. assert.match(patched,/function lessonHistoryLabelColumns/);assert.match(patched,/function lessonHistoryLabelJoins/);assert.throws(()=>patchAdminSpeed('function unrelatedLatestFix(){}'),/Missing helper/);
  36. });
  37. test('store membership does not load coach payroll or lesson history',async()=>{
  38. const c=vm.createContext({Date,Psql:{query:async()=>[]}});vm.runInContext(source,c);
  39. c.companyIdOf=()=> 'tenant';c.queryUsers=async()=>[];c.optionalLegacyMemberSnapshot=async()=>({available:false,items:[],coachIds:[]});c.appointmentSnapshot=async()=>({appointments:[],live:{}});c.operationalSyncIndicator=async()=>({});
  40. c.coachRows=()=>{throw Error('unnecessary coach history lookup');};
  41. const page=await c.storeRows({roles:['ops-manager']},{});assert.equal(page.total,0);
  42. });