lesson-unit-progress.test.mjs 3.9 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243
  1. import test from 'node:test';
  2. import assert from 'node:assert/strict';
  3. import {readFileSync} from 'node:fs';
  4. import vm from 'node:vm';
  5. import {PGlite} from '@electric-sql/pglite';
  6. import {randomUUID} from 'node:crypto';
  7. const code=readFileSync(new URL('../cloud/lesson-unit-progress.js',import.meta.url),'utf8');
  8. test('whole-unit acknowledgement is atomic, durable, authorized and idempotent without financial or lesson writes',async()=>{
  9. const db=new PGlite();try{
  10. await db.exec(`
  11. CREATE TABLE "CourseAppointment"("objectId" text,company text,id numeric,szyh numeric,pl numeric,dszt text);
  12. CREATE TABLE "CourseBinding"("objectId" text,company text,id numeric,yhid text,kcid text,"enrollmentStatus" text,syjd text,"updatedAt" timestamptz);
  13. CREATE TABLE "CommonModel"(company text,"generalId" numeric,"modelId" numeric,"itemId" numeric,"nodeId" numeric,status int);
  14. CREATE TABLE "Node"(company text,"nodeId" numeric,"parentId" numeric,"nodeName" text,zstatus int);
  15. CREATE TABLE "PracticeRecord"("objectId" text primary key,"createdAt" timestamptz,"updatedAt" timestamptz,company text,"sourceKey" text,kcid text,yhid numeric,scid numeric,xxcs numeric,jrscb text);
  16. INSERT INTO "CourseAppointment" VALUES('a','co',1,110,69,'10');
  17. INSERT INTO "CourseBinding" VALUES('b','co',2,'110','100','active','{"202":"202@Other@0"}',now());
  18. INSERT INTO "CommonModel" VALUES('co',900,54,1,0,99),('co',901,58,2,0,99);
  19. INSERT INTO "Node" VALUES('co',100,0,'Book',99),('co',201,100,'Unit',99),('co',202,100,'Other',99);
  20. INSERT INTO "CommonModel" SELECT 'co',n,52,n,201,99 FROM generate_series(1,205) n;
  21. INSERT INTO "PracticeRecord" VALUES('old',now(),now(),'co','old','100',110,1,7,'1');
  22. `);
  23. await db.exec(readFileSync(new URL('../sql/mobile-teaching.sql',import.meta.url),'utf8'));
  24. const adapter=d=>({one:async(s,p)=>(await d.query(s,p)).rows[0],oneOrNone:async(s,p)=>(await d.query(s,p)).rows[0]||null,any:async(s,p)=>(await d.query(s,p)).rows,none:async(s,p)=>{await d.query(s,p);}});
  25. const context={DEFAULT_COMPANY_ID:'co',number:x=>Number(x)||0,ownLegacyId:c=>c.uid,assertLessonTenant:()=>{},mobileCommandKey:i=>i.requestKey,randomObjectId:randomUUID,courseAssignedUnits:()=>[],fail:(status,message)=>{throw Object.assign(Error(message),{status});},Psql:{tx:fn=>db.transaction(t=>fn(adapter(t)))}};
  26. vm.runInNewContext(code+';this.complete=completeLessonUnit;',context);
  27. const teacher={id:'teacher',uid:69,get:()=>3},input={orderId:'900',bindingId:'b',nodeId:'201',requestKey:'request-unit-0001'};
  28. await assert.rejects(context.complete(input,{...teacher,uid:70}),/仅预约老师/);
  29. await assert.rejects(context.complete({...input,nodeId:'999'},teacher),/不属于/);
  30. const out=await context.complete(input,teacher);assert.equal(out.learned,205);assert.equal(out.percent,100);
  31. assert.equal((await db.query('SELECT count(*) n FROM "PracticeRecord" WHERE xxcs>0')).rows[0].n,205);
  32. assert.equal((await db.query('SELECT xxcs,jrscb FROM "PracticeRecord" WHERE scid=1')).rows[0].xxcs,'7');
  33. assert.equal(JSON.parse(out.syjd)['202'],'202@Other@0');
  34. await context.complete(input,teacher);await context.complete({...input,requestKey:'request-unit-0002'},teacher);
  35. assert.equal((await db.query('SELECT count(*) n FROM "PracticeRecord"')).rows[0].n,205);
  36. assert.equal((await db.query('SELECT xxcs FROM "PracticeRecord" WHERE scid=2')).rows[0].xxcs,'1');
  37. await db.exec('UPDATE "CourseAppointment" SET dszt=\'30\'');await assert.rejects(context.complete(input,teacher),/已结束/);
  38. // A downstream failure must roll back every chunk of word progress.
  39. await db.exec('UPDATE "CourseAppointment" SET dszt=\'10\'; DELETE FROM "PracticeRecord" WHERE scid>1; ALTER TABLE "CourseBinding" ADD CONSTRAINT no_completion CHECK (syjd NOT LIKE \'%Blocked%\'); UPDATE "Node" SET "nodeName"=\'Blocked\' WHERE "nodeId"=201;');
  40. await assert.rejects(context.complete({...input,requestKey:'rollback-unit-0003'},teacher),/no_completion/);
  41. assert.equal((await db.query('SELECT count(*) n FROM "PracticeRecord"')).rows[0].n,1);
  42. }finally{await db.close();}
  43. });