import {test,before,after} 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 db=new PGlite(),query=async(sql,args=[])=>(await db.query(sql,args)).rows; const source=readFileSync(new URL('../deploy-admin-functions.mjs',import.meta.url),'utf8'); const start=source.indexOf('async function queryUsers(context,input)'); const code=source.slice(start,source.indexOf('\nfunction pageInput(',start)); const ctx={Psql:{query,one:async(sql,args)=>(await query(sql,args))[0]},companyIdOf:c=>c.company,text:v=>String(v??'').trim(),number:(v,d=0)=>Number.isFinite(Number(v))?Number(v):d,legacyOf:r=>r.legacyUserData||{},displayName:r=>r.nickname,normalizeUser:r=>r,memberAppointmentStats:async()=>[],queryUsersLegacy:()=>{throw Error('Unexpected legacy request');}}; vm.createContext(ctx);vm.runInContext(code,ctx); before(async()=>{ await db.exec(`CREATE TABLE "_User"("objectId" text,company text,"legacyUserId" numeric,"legacyGroupId" numeric,"legacyUserData" jsonb,"identityType" text,"isDeleted" boolean,"isAdmin" boolean,username text,nickname text,"realName" text,"registeredAt" timestamptz,"createdAt" timestamptz); CREATE TABLE "Group"(company text,"groupId" numeric,"groupName" text);CREATE TABLE "Manager"(company text,"addUserId" numeric,"userName" text,"adminName" text); CREATE TABLE "CourseBinding"(company text,id numeric,yhid text,cksl text,yxx text); CREATE TABLE "DailyStudyRecord"(company text,"userId" numeric,"objectId" text,dsid text); CREATE TABLE "CommonModel"(company text,"objectId" text,"modelId" numeric,"itemId" numeric,"generalId" numeric,status numeric,"createdAt" timestamptz); CREATE TABLE "CourseAppointment"(company text,id numeric,jssj text); CREATE TABLE "LessonRecord"(company text,"objectId" text,id numeric,xymz text,yyds text,kclx text,"lessonAt" timestamptz,plpjsj text); INSERT INTO "_User" VALUES('student','co',1826,1,'{}','member',false,false,'student','学员','',now(),now()); INSERT INTO "LessonRecord" SELECT 'co','lesson-'||i,i,'1826',(1000+i)::text,CASE WHEN i=1 THEN '3' ELSE '2' END,now(),'' FROM generate_series(1,36) i; INSERT INTO "CommonModel" SELECT 'co','common-'||i,59,i,2000+i,99,now() FROM generate_series(1,36) i; INSERT INTO "DailyStudyRecord" SELECT 'co',1826,'study-'||i,(1000+i)::text FROM generate_series(1,36) i; INSERT INTO "DailyStudyRecord" VALUES('co',1826,'second-word-batch','1002');`); }); after(()=>db.close()); test('会员概览按已上课次统计:36节课有37条学习明细仍显示36,原明细保留',async()=>{ const result=await ctx.queryUsers({company:'co'},{});assert.equal(result[0].studyRecordCount,36); assert.equal((await query('SELECT count(*)::int n FROM "DailyStudyRecord"'))[0].n,37); }); test('概览课次数排除作废、合并重复预约,保留无预约的独立课次并隔离帐套',async()=>{ await db.exec(`INSERT INTO "LessonRecord" VALUES('co','duplicate',37,'1826','1002','2',now(),''),('co','void',38,'1826','9998','2',now(),''),('other','other-tenant',39,'1826','9999','2',now(),''),('co','unlinked-a',40,'1826',null,'2',now(),''),('co','unlinked-b',41,'1826','0','2',now(),''); INSERT INTO "CommonModel" VALUES('co','void-common',59,38,9998,-2,now());`); const result=await ctx.queryUsers({company:'co'},{});assert.equal(result[0].studyRecordCount,38); assert.equal((await ctx.queryUsers({company:'empty'},{})).length,0); });