|
@@ -0,0 +1,156 @@
|
|
|
|
|
+import {test,before,after} from 'node:test';
|
|
|
|
|
+import assert from 'node:assert/strict';
|
|
|
|
|
+import {readFileSync} from 'node:fs';
|
|
|
|
|
+import {PGlite} from '@electric-sql/pglite';
|
|
|
|
|
+const db=new PGlite();
|
|
|
|
|
+const enrollmentSql=readFileSync(new URL('../sql/member-enrollment.sql',import.meta.url),'utf8');
|
|
|
|
|
+const workflowSql=readFileSync(new URL('../sql/lesson-workflow.sql',import.meta.url),'utf8');
|
|
|
|
|
+const one=async(sql,args=[])=>(await db.query(sql,args)).rows[0];
|
|
|
|
|
+const feedback={contentSummary:'学习 Unit 1',reviewStatus:'完成',reviewSummary:'复习两个单词',trainingStatus:'正常进行',trainingSummary:'完成课堂练习',noHomework:true,homework:'',notes:'',coachComment:'继续保持'};
|
|
|
|
|
+const complete=async(gid,version,key='request-complete-001',actor='teacher',uid=30,f=feedback)=>(await one('SELECT xs_lesson_complete($1,$2,$3,$4,$5,$6,$7::jsonb) result',['co',gid,actor,uid,version,key,JSON.stringify(f)])).result;
|
|
|
|
|
+const review=async(gid,rating=5,comment='很好',actor='student',uid=20)=>(await one('SELECT xs_lesson_review($1,$2,$3,$4,$5,$6) result',['co',gid,actor,uid,rating,comment])).result;
|
|
|
|
|
+let binding;
|
|
|
|
|
+before(async()=>{
|
|
|
|
|
+
|
|
|
|
|
+ await db.exec(`CREATE TABLE "_User" ("objectId" text PRIMARY KEY,"company" text,"legacyUserId" double precision,"legacyGroupId" double precision,"identityType" text,"isDisabled" boolean,"legacyUserData" jsonb,"updatedAt" timestamptz);
|
|
|
|
|
+ CREATE TABLE "CourseBinding" ("objectId" text PRIMARY KEY,"company" text,"id" double precision,"createdAt" timestamptz,"updatedAt" timestamptz,"sourceKey" text,"yhid" text,"kcid" text,"yxx" double precision,"cksl" double precision,"syjd" text);
|
|
|
|
|
+ CREATE TABLE "CommonModel" ("objectId" text PRIMARY KEY,"company" text,"createdAt" timestamptz,"updatedAt" timestamptz,"createTime" timestamptz,"sourceKey" text,"generalId" double precision,"itemId" double precision,"modelId" double precision,"nodeId" double precision,"tableName" text,"title" text,"subtitle" text,"status" double precision,"inputer" text);
|
|
|
|
|
+ CREATE TABLE "CourseAppointment" ("objectId" text PRIMARY KEY,"company" text,"id" double precision,"szyh" text,"kcid" text,"pl" text,"fxpl" text,"dszt" text,"dslx" text,"jffs" text,"jssj" text,"updatedAt" timestamptz,"creditCostsSnapshot" jsonb,"creditHoldStatus" text,"courseCategoryKey" text,"courseCategoryName" text,"teacherPaySnapshot" double precision);
|
|
|
|
|
+ CREATE TABLE "LessonRecord" ("objectId" text PRIMARY KEY,"company" text,"id" double precision,"createdAt" timestamptz,"updatedAt" timestamptz,"sourceKey" text,"xymz" text,"jsmz" text,"kcid" text,"kclx" text,"kcmc" text,"yyds" text,"jffs" text,"kzsj" text,"lessonAt" timestamptz);
|
|
|
|
|
+ CREATE TABLE "DailyStudyRecord" ("objectId" text);
|
|
|
|
|
+ CREATE TABLE "CourseTeachingProfile" ("company" text,"courseId" double precision,"categoryKey" text,"confirmed" boolean);
|
|
|
|
|
+ CREATE TABLE "CourseCreditReservation" ("objectId" text PRIMARY KEY,"company" text,"studentId" double precision,"appointmentId" text,"creditCosts" jsonb,"state" text,"consumedAt" timestamptz,"updatedAt" timestamptz);`);
|
|
|
|
|
+ for(const table of ['UserExpDomP','UserSIcon','UserExpHis','UserUserPoint'])await db.exec(`CREATE TABLE "${table}" ("objectId" text PRIMARY KEY,"company" text,"sourceKey" text UNIQUE,"createdAt" timestamptz,"updatedAt" timestamptz,"userId" double precision,"score" double precision,"scoreBefore" double precision,"hisTime" timestamptz,"operator" double precision,"scoreType" double precision,"detail" text,"remark" text,"payMethod" text)`);
|
|
|
|
|
+ await db.exec(`INSERT INTO "_User" VALUES ('store','co',10,2,'store',false,'{}',now()),('student','co',20,1,'member',false,'{"ParentUserID":10}',now()),('empty','co',21,1,'member',false,'{}',now()),('foreign','other',22,1,'member',false,'{}',now());`);
|
|
|
|
|
+ await db.exec(`ALTER TABLE "CourseAppointment" ADD COLUMN "kssj" text,ADD COLUMN "plxm" text,ADD COLUMN "yykcid" text,ADD COLUMN "durationMinutes" numeric,ADD COLUMN "deliveryMode" text,ADD COLUMN "teachingRuleId" text,ADD COLUMN "bxrq" text,ADD COLUMN "sdsd" text,ADD COLUMN "yysj" text,ADD COLUMN "sourceKey" text,ADD COLUMN "createdAt" timestamptz;
|
|
|
|
|
+ ALTER TABLE "CourseCreditReservation" ADD COLUMN "sourceKey" text,ADD COLUMN "createdAt" timestamptz,ADD COLUMN "appointmentGeneralId" numeric,ADD COLUMN "ruleId" text,ADD COLUMN "reservedAt" timestamptz;`);
|
|
|
|
|
+ await db.exec(`CREATE TABLE "Node" ("company" text,"nodeId" numeric,"parentId" numeric,"nodeName" text,"zstatus" numeric);
|
|
|
|
|
+ CREATE TABLE "SysLog" ("objectId" text PRIMARY KEY,"company" text,"createdAt" timestamptz,"updatedAt" timestamptz,"sourceKey" text NOT NULL UNIQUE,"id" double precision,"cdate" timestamptz,"cname" text,"type1" text,"type2" text,"type3" text,"clevel" double precision,"cadminId" double precision,"detail" text,"content1" text,"content2" text);
|
|
|
|
|
+ INSERT INTO "Node" VALUES ('co',100,8,'词表',99),('co',101,8,'空词表',99);
|
|
|
|
|
+ INSERT INTO "CommonModel" ("objectId","company","nodeId","modelId","status") SELECT 'word-'||i,'co',100,52,99 FROM generate_series(1,200) i;`);
|
|
|
|
|
+ await db.exec(enrollmentSql);
|
|
|
|
|
+ await db.exec(`ALTER TABLE "LessonRecord" ADD pf text,ADD pjnr text,ADD pldp text,ADD plpjsj text;
|
|
|
|
|
+ ALTER TABLE "DailyStudyRecord" ADD company text,ADD id numeric,ADD "userId" numeric,ADD dsid text,ADD xxqs text,ADD fxrl text,ADD "updatedAt" timestamptz;
|
|
|
|
|
+ CREATE TABLE "MemoryPracticeRecord" ("objectId" text primary key,company text,id numeric,"sourceKey" text,"createdAt" timestamptz,"updatedAt" timestamptz,yhid numeric,plid numeric,"orderId" numeric,xxjlid numeric,kcid numeric,fxzt text,kywrq text,kywsj text);
|
|
|
|
|
+ CREATE TABLE "LessonCompletionReport" ("objectId" text primary key,company text,"createdAt" timestamptz,"updatedAt" timestamptz,"sourceKey" text,"appointmentId" text,"appointmentGeneralId" numeric,"lessonId" text,"studentId" numeric,"coachId" numeric,"courseCategoryKey" text,"courseCategoryName" text,"deliveryMode" text,"durationMinutes" numeric,"actualStartAt" timestamptz,"actualEndAt" timestamptz,"creditCosts" jsonb,"contentSummary" text,"vocabularyCount" numeric,"reviewStatus" text,"reviewSummary" text,"trainingStatus" text,"trainingSummary" text,"antiForgetSummary" jsonb,homework text,"noHomework" boolean,notes text,"submittedBy" text,"submittedAt" timestamptz);
|
|
|
|
|
+ INSERT INTO "_User" VALUES('teacher','co',30,3,'coach',false,'{}',now()),('other-teacher','co',31,3,'coach',false,'{}',now());`);
|
|
|
|
|
+ await db.exec(workflowSql);
|
|
|
|
|
+ await db.query("SELECT xs_credit_post('co','admin','fund','store','Purse',30,'','初始课时','转账','fund-workflow')");
|
|
|
|
|
+ await db.query("SELECT xs_credit_post('co','admin','transfer','student','Purse',30,'','学员课时','转账','transfer-workflow')");
|
|
|
|
|
+ binding=(await one(`SELECT xs_enroll('co','admin',20,'word','[{"courseId":100,"courseName":"词表","wordCount":200,"categoryName":"单词课"}]','[[{"account":"Purse","amount":1}]]','测试开课','enroll-workflow') result`)).result.bindingIds[0];
|
|
|
|
|
+});
|
|
|
|
|
+after(()=>db.close());
|
|
|
|
|
+let serial=0;
|
|
|
|
|
+async function appointment(start=true){
|
|
|
|
|
+ const date=(await one("SELECT to_char(now() AT TIME ZONE 'Asia/Shanghai','YYYY-MM-DD') AS day")).day;
|
|
|
|
|
+ const time=String(5+(serial++%16)).padStart(2,'0')+':00';
|
|
|
|
|
+ const entries=[{common:{title:'学员',subtitle:'词表'},addon:{szyh:'20',pl:'30',fxpl:'30',plxm:'老师',kcid:'100',yykcid:'100',dslx:'1',jffs:'线上',bindingId:binding,courseCategoryKey:'word',durationMinutes:30,deliveryMode:'online',creditCostsSnapshot:[{account:'Purse',amount:1}],teacherPaySnapshot:20,bxrq:date,sdsd:time,yysj:date+' '+time,dszt:'0'}}];
|
|
|
|
|
+ const result=(await one("SELECT xs_schedule_save('co','admin','{}',$1::jsonb,$2) result",[JSON.stringify(entries),'schedule-'+serial])).result;
|
|
|
|
|
+ const common=result.targetIds[0];
|
|
|
|
|
+ if(start) await db.query("SELECT xs_start_appointment('co',$1)",[common]);
|
|
|
|
|
+ const row=await one('SELECT c."generalId"::text gid,a."objectId",a."updatedAt" FROM "CommonModel" c JOIN "CourseAppointment" a ON a.company=c.company AND a.id=c."itemId" WHERE c."objectId"=$1',[common]);
|
|
|
|
|
+ return {...row,common};
|
|
|
|
|
+}
|
|
|
|
|
+const balance=async()=>Number((await one(`SELECT "legacyUserData"->>'Purse' balance FROM "_User" WHERE "objectId"='student'`)).balance);
|
|
|
|
|
+test('老师一次结课生成报告和学习快照,重复并发只扣一次;学生随后评价不改结课状态',async()=>{
|
|
|
|
|
+ const a=await appointment(), before=await balance();
|
|
|
|
|
+ await db.query('INSERT INTO "DailyStudyRecord" ("objectId",company,id,"userId",dsid,xxqs,fxrl,"updatedAt") VALUES($1,$2,$3,$4,$5,$6,$7,now())',['study','co',999,20,a.gid,'[{"GeneralID":1,"Title":"apple","check":1}]','20300101,20300102']);
|
|
|
|
|
+ await db.exec(`INSERT INTO "CommonModel" ("objectId",company,"modelId","itemId","generalId",status) VALUES('study-common','co',56,999,1000,99)`);
|
|
|
|
|
+ const results=await Promise.all(Array.from({length:4},()=>complete(a.gid,a.updatedAt)));
|
|
|
|
|
+ assert.ok(results.every(r=>r.status===30));assert.equal(await balance(),before-1);
|
|
|
|
|
+ assert.equal(Number((await one('SELECT count(*) n FROM "LessonRecord" WHERE yyds=$1',[a.gid])).n),1);
|
|
|
|
|
+ assert.equal(Number((await one('SELECT count(*) n FROM "LessonCompletionReport" WHERE "appointmentGeneralId"::text=$1',[a.gid])).n),1);
|
|
|
|
|
+ const report=await one('SELECT * FROM "LessonCompletionReport" WHERE "appointmentGeneralId"::text=$1',[a.gid]);assert.equal(report.vocabularyCount,'1');assert.equal(report.antiForgetSummary.total,2);
|
|
|
|
|
+ const lesson=await one('SELECT * FROM "LessonRecord" WHERE yyds=$1',[a.gid]);assert.equal(JSON.parse(lesson.kzsj)[0].Title,'apple');
|
|
|
|
|
+ assert.equal((await review(a.gid)).score,5);assert.equal((await review(a.gid,1,'changed')).score,5);
|
|
|
|
|
+ assert.equal((await one('SELECT dszt FROM "CourseAppointment" WHERE "objectId"=$1',[a.objectId])).dszt,'30');assert.equal(await balance(),before-1);
|
|
|
|
|
+ await assert.rejects(db.query(`UPDATE "CourseAppointment" SET dszt='11' WHERE "objectId"=$1`,[a.objectId]),/不可由旧流程回退/);
|
|
|
|
|
+ await assert.rejects(complete(a.gid,a.updatedAt,'request-complete-001','teacher',30,{...feedback,notes:'changed'}),/请求编号/);
|
|
|
|
|
+});
|
|
|
|
|
+test('报告写入失败回滚结课、课时和学习记录,可原请求重试',async()=>{
|
|
|
|
|
+ const a=await appointment(),before=await balance();
|
|
|
|
|
+ await db.exec(`ALTER TABLE "LessonCompletionReport" ADD CONSTRAINT fail_report CHECK("contentSummary"<>'失败注入')`);
|
|
|
|
|
+ await assert.rejects(complete(a.gid,a.updatedAt,'rollback-request-001','teacher',30,{...feedback,contentSummary:'失败注入'}),/fail_report/);
|
|
|
|
|
+ assert.equal(await balance(),before);assert.equal((await one('SELECT dszt FROM "CourseAppointment" WHERE "objectId"=$1',[a.objectId])).dszt,'10');
|
|
|
|
|
+ assert.equal(Number((await one('SELECT count(*) n FROM "LessonRecord" WHERE yyds=$1',[a.gid])).n),0);
|
|
|
|
|
+ await db.exec('ALTER TABLE "LessonCompletionReport" DROP CONSTRAINT fail_report');
|
|
|
|
|
+ assert.equal((await complete(a.gid,a.updatedAt,'rollback-request-001','teacher',30,{...feedback,contentSummary:'失败注入'})).status,30);
|
|
|
|
|
+});
|
|
|
|
|
+test('越权、未结课评价、过期版本、取消均拒绝',async()=>{
|
|
|
|
|
+ const a=await appointment();
|
|
|
|
|
+ await assert.rejects(complete(a.gid,a.updatedAt,'denied-request-001','other-teacher',31),/仅预约老师/);
|
|
|
|
|
+ await assert.rejects(complete(a.gid,'2000-01-01','denied-request-002'),/已更新/);
|
|
|
|
|
+ await assert.rejects(review(a.gid),/尚未结课/);
|
|
|
|
|
+ await assert.rejects(review(a.gid,5,'','other-teacher',31),/仅预约学生/);
|
|
|
|
|
+ await db.query('UPDATE "CommonModel" SET status=-2 WHERE "objectId"=$1',[a.common]);
|
|
|
|
|
+ await assert.rejects(complete(a.gid,a.updatedAt,'denied-request-003'),/已取消/);
|
|
|
|
|
+});
|
|
|
|
|
+test('旧11/20课次复用已扣凭据与评价,缺凭据不补扣',async()=>{
|
|
|
|
|
+ const a=await appointment();await db.query("SELECT xs_complete_appointment('co',$1,'teacher')",[a.common]);
|
|
|
|
|
+ await db.query(`UPDATE "LessonRecord" SET pf='4',pjnr='原评价' WHERE yyds=$1`,[a.gid]);
|
|
|
|
|
+ await db.query(`UPDATE "CourseAppointment" SET dszt='20' WHERE "objectId"=$1`,[a.objectId]);
|
|
|
|
|
+ const v=(await one('SELECT "updatedAt" FROM "CourseAppointment" WHERE "objectId"=$1',[a.objectId])).updatedAt,before=await balance();
|
|
|
|
|
+ assert.equal((await complete(a.gid,v,'legacy-request-001')).status,30);assert.equal(await balance(),before);assert.equal((await review(a.gid)).score,'4');
|
|
|
|
|
+ const broken=await appointment();await db.query(`UPDATE "CourseAppointment" SET dszt='11' WHERE "objectId"=$1`,[broken.objectId]);
|
|
|
|
|
+ await assert.rejects(complete(broken.gid,broken.updatedAt,'legacy-request-002'),/历史凭据/);assert.equal(await balance(),before);
|
|
|
|
|
+});
|
|
|
|
|
+test('评价原子拒绝无效评分与超长文字',async()=>{
|
|
|
|
|
+ const a=await appointment();await complete(a.gid,a.updatedAt,'review-request-001');
|
|
|
|
|
+ for (const score of [0,6]) await assert.rejects(review(a.gid,score),/评分/);
|
|
|
|
|
+ await assert.rejects(review(a.gid,5,'a'.repeat(201)),/200字/);
|
|
|
|
|
+ const all=await Promise.all([review(a.gid,3,'first'),review(a.gid,5,'second')]);assert.equal(all[0].score,all[1].score);
|
|
|
|
|
+ assert.equal(Number((await one('SELECT count(*) n FROM "LessonReview" WHERE "appointmentGeneralId"=$1',[a.gid])).n),1);
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+test('同日提前开课、午夜后继续、非预约当天与改派老师在数据库校验',async()=>{
|
|
|
|
|
+ const a=await appointment(false);
|
|
|
|
|
+ await db.query(`UPDATE "CourseAppointment" SET sdsd='23:59',yysj=bxrq||' 23:59' WHERE "objectId"=$1`,[a.objectId]);
|
|
|
|
|
+ await db.query("SELECT xs_start_appointment_for_actor('co',$1,'teacher',30)",[a.common]);
|
|
|
|
|
+ await db.query(`UPDATE "CourseAppointment" SET bxrq='2000-01-01',yysj='2000-01-01 00:00' WHERE "objectId"=$1`,[a.objectId]);
|
|
|
|
|
+ await db.query("SELECT xs_start_appointment_for_actor('co',$1,'teacher',30)",[a.common]);
|
|
|
|
|
+ const other=await appointment(false);
|
|
|
|
|
+ await db.query(`UPDATE "CourseAppointment" SET bxrq='2000-01-01',yysj='2000-01-01 00:00' WHERE "objectId"=$1`,[other.objectId]);
|
|
|
|
|
+ await assert.rejects(db.query("SELECT xs_start_appointment_for_actor('co',$1,'teacher',30)",[other.common]),/预约当天/);
|
|
|
|
|
+ await db.query(`UPDATE "CourseAppointment" SET pl='31' WHERE "objectId"=$1`,[a.objectId]);
|
|
|
|
|
+ await assert.rejects(db.query("SELECT xs_start_appointment_for_actor('co',$1,'teacher',30)",[a.common]),/仅预约老师/);
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+test('同一真实排课从教师、学生分页查询与详情读取,不访问旧接口;版本感知新增排课',async()=>{
|
|
|
|
|
+ const {default:vm}=await import('node:vm');
|
|
|
|
|
+ const source=readFileSync(new URL('../deploy-admin-functions.mjs',import.meta.url),'utf8');
|
|
|
|
|
+ const app=source.match(/const appGatewayCode = String.raw`([\s\S]*?)\n`;/)[1].replace("const DEFAULT_COMPANY_ID = '7pIbDBJmKx'", "const DEFAULT_COMPANY_ID = 'co'");
|
|
|
|
|
+ const context={Psql:{query:async(s,p)=>(await db.query(s,p)).rows,one:async(s,p)=>(await db.query(s,p)).rows[0],oneOrNone:async(s,p)=>(await db.query(s,p)).rows[0]||null}};
|
|
|
|
|
+ vm.createContext(context);vm.runInContext(app+'\n'+readFileSync(new URL('../cloud/mobile-teaching.js',import.meta.url),'utf8')+'\n'+readFileSync(new URL('../cloud/lesson-workflow.js',import.meta.url),'utf8'),context);
|
|
|
|
|
+ const teacher={id:'teacher',get:k=>({company:{id:'co'},legacyUserId:30,legacyGroupId:3}[k])},student={id:'student',get:k=>({company:{id:'co'},legacyUserId:20,legacyGroupId:1}[k])};
|
|
|
|
|
+ context.syncLegacyAppointments=async()=>{throw Error('不应读取旧端');};context.appLegacyConfig=async()=>{throw Error('不应查询旧配置');};
|
|
|
|
|
+ await db.exec('ALTER TABLE "_User" ADD COLUMN "realName" text,ADD COLUMN nickname text,ADD COLUMN username text');
|
|
|
|
|
+ const before=await context.databaseAppointmentVersions({},teacher),a=await appointment();
|
|
|
|
|
+ const after=await context.databaseAppointmentVersions({},teacher);assert.notEqual(before.version,after.version);
|
|
|
|
|
+ const t=await context.appCompanionOverview({source:'database',pageSize:100},teacher);
|
|
|
|
|
+ const s=await context.appCompanionOverview({source:'database',pageSize:100},student);
|
|
|
|
|
+ assert.ok(t.schedules.some(row=>String(row.GeneralID)===a.gid));assert.ok(s.schedules.some(row=>String(row.GeneralID)===a.gid));
|
|
|
|
|
+ assert.equal(t.syncStatus.state,'current');assert.equal(s.syncStatus.state,'current');
|
|
|
|
|
+ const found=await context.orderDetailRow(a.gid,teacher);
|
|
|
|
|
+ const detail=await context.lessonOrderCapabilities(found,teacher);assert.ok(detail.allowedActions.includes('complete'));assert.equal(detail.bindingId,binding);
|
|
|
|
|
+ await db.query(`INSERT INTO "CourseAppointment" SELECT (jsonb_populate_record(NULL::"CourseAppointment",to_jsonb(a)||jsonb_build_object('objectId','page-'||i,'id',900000+i))).* FROM "CourseAppointment" a CROSS JOIN generate_series(1,25) i WHERE a."objectId"=$1`,[a.objectId]);
|
|
|
|
|
+ await db.query(`INSERT INTO "CommonModel" SELECT (jsonb_populate_record(NULL::"CommonModel",to_jsonb(c)||jsonb_build_object('objectId','page-common-'||i,'itemId',900000+i,'generalId',910000+i))).* FROM "CommonModel" c CROSS JOIN generate_series(1,25) i WHERE c."objectId"=$1`,[a.common]);
|
|
|
|
|
+ const first=await context.appCompanionOverview({source:'database',page:1,pageSize:20},teacher),second=await context.appCompanionOverview({source:'database',page:2,pageSize:20},teacher);
|
|
|
|
|
+ assert.equal(first.schedules.length,20);assert.ok(second.schedules.length>0);
|
|
|
|
|
+ assert.equal(new Set([...first.schedules,...second.schedules].map(r=>r.GeneralID)).size,first.schedules.length+second.schedules.length);
|
|
|
|
|
+ assert.ok([...first.schedules,...second.schedules].every(r=>!r.cancelled&&Number(r.pl)===30));
|
|
|
|
|
+ const ops=source.match(/const operationsGatewayCode = String.raw`([\s\S]*?)\n`;/)[1],admin={Psql:context.Psql};vm.createContext(admin);vm.runInContext(ops+'\n'+readFileSync(new URL('../cloud/lesson-workflow-ops.js',import.meta.url),'utf8'),admin);
|
|
|
|
|
+ admin.requireRole=()=>{};admin.companyIdOf=()=> 'co';
|
|
|
|
|
+ const trace=await admin.lessonFeedbackDetail({}, {targetId:a.common});assert.equal(trace.appointmentGeneralId,a.gid);assert.equal(trace.lessonCount,0);assert.equal(trace.creditState,'reserved');
|
|
|
|
|
+ admin.professionalScheduleData=async()=>({appointments:[{generalId:a.gid}]});admin.cachedLiveAppointmentRows=async()=>{throw Error('后台不应读取旧端');};
|
|
|
|
|
+ assert.equal((await admin.professionalScheduleDataLocal({}, {source:'database'})).appointments[0].generalId,a.gid);
|
|
|
|
|
+ const foreign={...teacher,get:k=>k==='company'?{id:'other'}:teacher.get(k)};
|
|
|
|
|
+ await assert.rejects(context.databaseAppointmentVersions({},foreign),/帐套/);
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+test('旧客户端状态写入和旧分步完课明确拒绝,不触发数据库扣费',async()=>{
|
|
|
|
|
+ const {default:vm}=await import('node:vm');
|
|
|
|
|
+ const source=readFileSync(new URL('../deploy-admin-functions.mjs',import.meta.url),'utf8');
|
|
|
|
|
+ const c={};vm.createContext(c);vm.runInContext(source.match(/const appGatewayCode = String.raw`([\s\S]*?)\n`;/)[1],c);c.assertLessonTenant=()=>{};
|
|
|
|
|
+ for(const status of [11,20,30]) await assert.rejects(c.updateOrderStatus({status,orderId:1,content:'{}'},{}),/刷新课程详情/);
|
|
|
|
|
+ await assert.rejects(c.completeOrderWithFeedback({},{}),/一次结课/);
|
|
|
|
|
+});
|