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'; const source=readFileSync(new URL('../deploy-admin-functions.mjs',import.meta.url),'utf8'); const gateway=source.match(/const appGatewayCode = String.raw`([\s\S]*?)\n`;/)[1]; async function setup(t){ const db=new PGlite();t.after(()=>db.close()); await db.exec(` CREATE TABLE "Node" (company text,"nodeId" int,"parentId" int); CREATE TABLE "CommonModel" (company text,"modelId" int,"generalId" int,"itemId" int,"nodeId" int,"orderId" int,title text); CREATE TABLE "VocabularyWord" (company text,id int,sy text); CREATE TABLE "PracticeRecord" (company text,yhid text,scid text,xxcs text,jrscb text); INSERT INTO "Node" VALUES ('7pIbDBJmKx',100,0),('7pIbDBJmKx',110,100),('7pIbDBJmKx',120,100),('other',130,100); INSERT INTO "CommonModel" VALUES ('7pIbDBJmKx',52,1,1,110,1,'one'),('7pIbDBJmKx',52,2,2,110,2,'two'), ('7pIbDBJmKx',52,3,3,110,3,'three'),('7pIbDBJmKx',52,4,4,110,4,'four'), ('7pIbDBJmKx',52,5,5,120,5,'other-unit'),('other',52,6,6,130,6,'foreign'); INSERT INTO "VocabularyWord" VALUES ('7pIbDBJmKx',2,'第二个词'); INSERT INTO "PracticeRecord" VALUES ('7pIbDBJmKx','42','2','1','0'),('7pIbDBJmKx','42','2','0','0'), ('7pIbDBJmKx','42','3','2.0','0'),('7pIbDBJmKx','42','4','7','0'), ('7pIbDBJmKx','99','1','3','0'),('other','42','1','3','0'); `); const context=vm.createContext({Psql:{one:async(s,a)=>(await db.query(s,a)).rows[0],query:async(s,a)=>(await db.query(s,a)).rows}}); vm.runInContext(gateway,context); context.authorizeRequestedUser=async(_,uid)=>{if(uid!==42)throw Error('无权访问');return uid;}; return {db,read:input=>context.courseWordPage({uid:42,nids:'110',pnid:100,...input},{})}; } test('四栏按最大有效学习次数分类,旧记录超过三遍归入第三遍,不重复计算',async t=>{ const {read}=await setup(t); for(let pass=0;pass<4;pass++){ const result=await read({learningPass:pass,psize:1}); assert.equal(result.rows.length,1);assert.equal(result.rows[0].w_learned,pass); assert.equal(result.rows[0].GeneralID,pass+1); assert.deepEqual(Array.from(result.page.passCounts),[1,1,1,1]); assert.deepEqual(Array.from(result.page.bookPassCounts),[2,1,1,1]); assert.equal(result.page.firstPassProgress,60); } const result=await read({learningPass:1});assert.equal(result.rows[0].detail.sy,'第二个词'); }); test('分页后仍保留整本计数,其他单元未学完不会被误判为首遍完成',async t=>{ const {read,db}=await setup(t); await db.exec(`UPDATE "PracticeRecord" SET xxcs='1' WHERE company='7pIbDBJmKx' AND yhid='42';INSERT INTO "PracticeRecord" VALUES('7pIbDBJmKx','42','1','1','0');`); const selected=await read({learningPass:1,psize:2,cpage:2}); assert.equal(selected.rows.length,2);assert.equal(selected.page.itemCount,4); assert.deepEqual(Array.from(selected.page.bookPassCounts),[1,4,0,0]);assert.equal(selected.page.firstPassProgress,80); const empty=await read({learningPass:1,psize:2,cpage:3});assert.equal(empty.rows.length,0);assert.equal(empty.page.itemCount,4); }); test('首遍全完成后进度始终100%,第二第三遍不重置;兼容旧客户端混合列表',async t=>{ const {read,db}=await setup(t); await db.exec(`INSERT INTO "PracticeRecord" VALUES ('7pIbDBJmKx','42','1','1','0'),('7pIbDBJmKx','42','5','1','0');`); assert.equal((await read({})).page.firstPassProgress,100); await db.exec(`UPDATE "PracticeRecord" SET xxcs='2' WHERE company='7pIbDBJmKx' AND yhid='42';`); assert.equal((await read({})).page.firstPassProgress,100); await db.exec(`UPDATE "PracticeRecord" SET xxcs='3' WHERE company='7pIbDBJmKx' AND yhid='42';`); const done=await read({});assert.equal(done.page.firstPassProgress,100);assert.equal(done.rows.length,4);assert.equal(done.page.passCounts[3],4); }); test('拒绝无效遍数和越权访问',async t=>{ const {read}=await setup(t); for(const learningPass of [-1,4,1.5,'bad'])await assert.rejects(read({learningPass}),/学习遍数无效/); await assert.rejects(read({uid:99}),/无权访问/); });