| 12345678910111213141516171819202122232425262728293031323334353637383940414243 |
- import test from 'node:test';
- import assert from 'node:assert/strict';
- import {readFileSync} from 'node:fs';
- import vm from 'node:vm';
- import {PGlite} from '@electric-sql/pglite';
- import {randomUUID} from 'node:crypto';
- const code=readFileSync(new URL('../cloud/lesson-unit-progress.js',import.meta.url),'utf8');
- test('whole-unit acknowledgement is atomic, durable, authorized and idempotent without financial or lesson writes',async()=>{
- const db=new PGlite();try{
- await db.exec(`
- CREATE TABLE "CourseAppointment"("objectId" text,company text,id numeric,szyh numeric,pl numeric,dszt text);
- CREATE TABLE "CourseBinding"("objectId" text,company text,id numeric,yhid text,kcid text,"enrollmentStatus" text,syjd text,"updatedAt" timestamptz);
- CREATE TABLE "CommonModel"(company text,"generalId" numeric,"modelId" numeric,"itemId" numeric,"nodeId" numeric,status int);
- CREATE TABLE "Node"(company text,"nodeId" numeric,"parentId" numeric,"nodeName" text,zstatus int);
- 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);
- INSERT INTO "CourseAppointment" VALUES('a','co',1,110,69,'10');
- INSERT INTO "CourseBinding" VALUES('b','co',2,'110','100','active','{"202":"202@Other@0"}',now());
- INSERT INTO "CommonModel" VALUES('co',900,54,1,0,99),('co',901,58,2,0,99);
- INSERT INTO "Node" VALUES('co',100,0,'Book',99),('co',201,100,'Unit',99),('co',202,100,'Other',99);
- INSERT INTO "CommonModel" SELECT 'co',n,52,n,201,99 FROM generate_series(1,205) n;
- INSERT INTO "PracticeRecord" VALUES('old',now(),now(),'co','old','100',110,1,7,'1');
- `);
- await db.exec(readFileSync(new URL('../sql/mobile-teaching.sql',import.meta.url),'utf8'));
- 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);}});
- 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)))}};
- vm.runInNewContext(code+';this.complete=completeLessonUnit;',context);
- const teacher={id:'teacher',uid:69,get:()=>3},input={orderId:'900',bindingId:'b',nodeId:'201',requestKey:'request-unit-0001'};
- await assert.rejects(context.complete(input,{...teacher,uid:70}),/仅预约老师/);
- await assert.rejects(context.complete({...input,nodeId:'999'},teacher),/不属于/);
- const out=await context.complete(input,teacher);assert.equal(out.learned,205);assert.equal(out.percent,100);
- assert.equal((await db.query('SELECT count(*) n FROM "PracticeRecord" WHERE xxcs>0')).rows[0].n,205);
- assert.equal((await db.query('SELECT xxcs,jrscb FROM "PracticeRecord" WHERE scid=1')).rows[0].xxcs,'7');
- assert.equal(JSON.parse(out.syjd)['202'],'202@Other@0');
- await context.complete(input,teacher);await context.complete({...input,requestKey:'request-unit-0002'},teacher);
- assert.equal((await db.query('SELECT count(*) n FROM "PracticeRecord"')).rows[0].n,205);
- assert.equal((await db.query('SELECT xxcs FROM "PracticeRecord" WHERE scid=2')).rows[0].xxcs,'1');
- await db.exec('UPDATE "CourseAppointment" SET dszt=\'30\'');await assert.rejects(context.complete(input,teacher),/已结束/);
- // A downstream failure must roll back every chunk of word progress.
- 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;');
- await assert.rejects(context.complete({...input,requestKey:'rollback-unit-0003'},teacher),/no_completion/);
- assert.equal((await db.query('SELECT count(*) n FROM "PracticeRecord"')).rows[0].n,1);
- }finally{await db.close();}
- });
|