import test from 'node:test'; import assert from 'node:assert/strict'; import vm from 'node:vm'; import { readFileSync } from 'node:fs'; import { PGlite } from '@electric-sql/pglite'; const extension = readFileSync(new URL('../cloud/mobile-teaching.js', import.meta.url), 'utf8'); const gateway = readFileSync(new URL('../deploy-admin-functions.mjs', import.meta.url), 'utf8'); test('学生已分配词库保留待分类和缺少通用记录的历史开通,排除回收、停用及其他学员', async () => { const db = new PGlite(); try { await db.exec(`CREATE TABLE "CourseBinding"("objectId" text,company text,id bigint,yhid numeric,kcid text,"enrollmentStatus" text,syjd text,yxx numeric); CREATE TABLE "CommonModel"(company text,"modelId" int,"itemId" bigint,"generalId" bigint,title text,"createTime" timestamptz,status int); CREATE TABLE "Node"(company text,"nodeId" numeric,"nodeName" text,zstatus int); INSERT INTO "Node" VALUES ('co',81,'历史词库',99),('co',82,'停用词库',0),('other',81,'其他帐套',99);`); const states = ['active', 'pending', null, '', 'recycled', 'disabled', 'active', 'pending', 'pending', 'pending', 'pending']; for (const [i,state] of states.entries()) { const id = i + 1; await db.query('INSERT INTO "CourseBinding" VALUES($1,$2,$3,$4,$5,$6,$7,$8)', [`b${id}`,id===11?'other':'co',id,id===10?43:42,id===9?'82':id===2?'81.0':'81',state,'{"91":"91@已分配单元"}',12]); if (id !== 8) await db.query('INSERT INTO "CommonModel" VALUES($1,58,$2,$2,$3,now(),$4)', [id===11?'other':'co',id,'已开通词库',id===7?-2:99]); } // An explicitly unpublished common record must remain unavailable. await db.exec(`INSERT INTO "CourseBinding" VALUES('draft','co',12,42,'81','pending','',0); INSERT INTO "CommonModel" VALUES('co',58,12,12,'未发布',now(),0);`); const context = { DEFAULT_COMPANY_ID: 'co', Psql: { query: async (sql,args) => (await db.query(sql,args)).rows }, number: value => Number(value) || 0, ownLegacyId: current => Number(current.get('legacyUserId')), }; vm.runInNewContext(extension,context); const courses = await context.mobileReadableCourseBindings(42); assert.deepEqual(courses.map(row=>row.bindingObjectId),['b8','b4','b3','b2','b1']); assert.ok(courses.every(row=>Number(row.yxx)===12 && row.syjd.includes('已分配单元'))); assert.equal(courses.find(row=>row.bindingObjectId==='b2').courseTitle,'历史词库'); const student = {get:key=>({legacyGroupId:1,legacyUserId:42})[key]}; const coach = {get:key=>({legacyGroupId:3,legacyUserId:99})[key]}; assert.equal((await context.mobileUnitBindingRows(42,'b2',81,student)).length,1); assert.equal((await context.mobileUnitBindingRows(42,'8',81,student)).length,1); assert.equal((await context.mobileUnitBindingRows(42,'b7',81,student)).length,0); assert.equal((await context.mobileUnitBindingRows(42,'b5',81,student)).length,0); assert.equal((await context.mobileUnitBindingRows(42,'b9',82,student)).length,0); assert.equal((await context.mobileUnitBindingRows(42,'b10',81,student)).length,0); assert.equal((await context.mobileUnitBindingRows(42,'b2',81,coach)).length,0); assert.equal((await context.mobileUnitBindingRows(42,'b1',81,coach)).length,1); assert.equal((await context.mobileUnitBindingRows(43,'b2',81,student)).length,0); const retained = (await db.query('SELECT "enrollmentStatus",yxx FROM "CourseBinding" WHERE "objectId"=\'b2\'' )).rows[0]; assert.equal(retained.enrollmentStatus,'pending'); assert.equal(Number(retained.yxx),12); } finally { await db.close(); } }); test('伴学中心、学习记录和单元入口采用统一的词库读取规则', () => { const studentOverview = gateway.slice(gateway.indexOf('async function appStudentCompanionOverview'),gateway.indexOf('async function appCompanionOverview')); const learningOverview = gateway.slice(gateway.indexOf('async function appLearningOverview'),gateway.indexOf('async function appStudentCompanionOverview')); const unitPage = gateway.slice(gateway.indexOf('async function nodeUnitPage'),gateway.indexOf('function legacyAppRow')); assert.match(studentOverview,/courses=await mobileReadableCourseBindings\(uid\)/); assert.match(learningOverview,/mobileReadableCourseBindings\(targetId\)/); assert.match(unitPage,/mobileUnitBindingRows\(uid,bindingId,parentId,current\)/); });