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 mobile=readFileSync(new URL('../cloud/mobile-teaching.js',import.meta.url),'utf8'); const baseline=readFileSync(new URL('./fixtures/mobile-review-performance-baseline.js',import.meta.url),'utf8'); const plain=v=>JSON.parse(JSON.stringify(v)); test('indexed correlated review reads preserve every page, summary, reward, date and identity-format result',async()=>{ const db=new PGlite();try{ await db.exec(readFileSync(new URL('./fixtures/teacher-review-schema.sql',import.meta.url),'utf8')); await db.exec(`INSERT INTO "MemoryPracticeRecord" VALUES ('scaled','co',101,30.0,20.0,101,1,'1','2026-10-14','2026-10-14'), ('foreign','other',102,30,20,101,1,'1','2026-10-14','2026-10-14'), ('missing-model','co',103,30,20,102,1,'0','2026-10-14','2026-10-14'), ('empty-source','co',104,30,20,103,1,'0','2026-10-14','2026-10-14'); INSERT INTO "CommonModel"(company,"modelId","generalId","itemId") VALUES('co',60,1101,101),('other',60,1102,102),('co',60,1104,104),('co',56,103,203); INSERT INTO "DailyStudyRecord"(company,id,learned,xxqs,"userId") VALUES('co',203,0,'[]',20); INSERT INTO "ReviewRewardIncome"(company,"taskId",amount,status,"originalAmount","correctionAmount","correctionReason") VALUES('co','r1',0,'ineligible',1,-1,'逾期'),('co','r2',1,'earned',1,NULL,NULL);`); const query=async(sql,args)=>(await db.query(sql,args)).rows; function context(old,coach){const ctx=vm.createContext({Date,Promise,DEFAULT_COMPANY_ID:'co',number:(v,f=0)=>Number(v)||f,authorizeRequestedUser:async()=>coach?30:20,appReviewTarget:async()=>20,appOverviewToday:()=> '2026-10-14',legacyAliases:r=>r,appLiveVersionSnapshot:async()=>({}),syncLegacyReviews:async()=>({state:'current'}),Psql:{query,one:async(s,a)=>(await query(s,a))[0]}});vm.runInContext(mobile+(old?'\n'+baseline:''),ctx);ctx.mobileSource=()=>({source:'database',refreshedAt:'fixed',complete:true});return ctx;} const cases=[{}, {scope:'today'}, {scope:'today',page:2,pageSize:1}, {scope:'overdue'}, {status:'completed'}, {status:'pending',month:'2026-10'}, {date:'2026-10-20',pageSize:100}, {studentId:20,includeMetadata:false,includeIncome:false}]; for(const coach of [true,false])for(const input of cases){const current={get:()=>coach?3:1},a=context(true,coach),b=context(false,coach);assert.deepEqual(plain(await b.appReviewPage(input,current)),plain(await a.appReviewPage(input,current)),JSON.stringify({coach,input}));} }finally{await db.close();} }); test('review version uses ownership indexes without changing numeric text identity or version data',async()=>{ const db=new PGlite();try{ await db.exec(`CREATE TABLE "MemoryPracticeRecord"(company text,yhid numeric,plid numeric,"updatedAt" timestamptz,"createdAt" timestamptz); INSERT INTO "MemoryPracticeRecord" VALUES('co',20,30,'2026-11-10','2026-11-01'),('co',20.0,30.0,'2026-11-11','2026-11-01'),('other',20,30,'2026-11-12','2026-11-01'),('co',NULL,20,NULL,'2026-11-09');`); const gateway=readFileSync(new URL('../deploy-admin-functions.mjs',import.meta.url),'utf8'); const current=gateway.slice(gateway.indexOf('async function appLiveScopeVersion('),gateway.indexOf('async function appLiveSyncStatus(')); const context=code=>{const c=vm.createContext({DEFAULT_COMPANY_ID:'co',appLiveVersion:r=>r,Psql:{one:async(s,a)=>(await db.query(s,a)).rows[0]}});vm.runInContext(code,c);return c;}; const before=context(baseline),after=context(current); for(const uid of [20,30,0,999])assert.deepEqual(plain(await after.appLiveScopeVersion('review',uid,{})),plain(await before.appLiveScopeVersion('review',uid,{}))); }finally{await db.close();} });