|
|
@@ -41,9 +41,10 @@ before(async()=>{
|
|
|
});
|
|
|
after(()=>db.close());
|
|
|
let serial=0;
|
|
|
-async function appointment(start=true){
|
|
|
+async function appointment(start=true,timeOverride=''){
|
|
|
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 time=timeOverride||String(5+(serial++%16)).padStart(2,'0')+':00';
|
|
|
+ if(timeOverride)serial++;
|
|
|
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];
|
|
|
@@ -208,7 +209,7 @@ test('选填复习情况和训练状态允许留空或自由文本,报告保
|
|
|
});
|
|
|
|
|
|
test('修改报告只更新文字;重试幂等、旧版本和越权拒绝,保留课时、学习成果与评价',async()=>{
|
|
|
- const a=await appointment();await complete(a.gid,a.updatedAt,'edit-setup-request');await review(a.gid,4,'学生原评价');
|
|
|
+ const a=await appointment();await complete(a.gid,a.updatedAt,'edit-setup-request');
|
|
|
const report=await one('SELECT *,"updatedAt"::text revision FROM "LessonCompletionReport" WHERE "appointmentGeneralId"::text=$1',[a.gid]);
|
|
|
const before=await balance();
|
|
|
const lesson=await one('SELECT * FROM "LessonRecord" WHERE yyds=$1',[a.gid]);
|
|
|
@@ -224,6 +225,9 @@ test('修改报告只更新文字;重试幂等、旧版本和越权拒绝,
|
|
|
const afterLesson=await one('SELECT * FROM "LessonRecord" WHERE yyds=$1',[a.gid]);
|
|
|
assert.equal(afterLesson.kzsj,lesson.kzsj);assert.equal(afterLesson.pf,lesson.pf);assert.equal(afterLesson.pjnr,lesson.pjnr);assert.equal(afterLesson.pldp,'新的点评');assert.equal(await balance(),before);
|
|
|
assert.equal((await one('SELECT dszt FROM "CourseAppointment" WHERE "objectId"=$1',[a.objectId])).dszt,'30');
|
|
|
+ await review(a.gid,4,'学生原评价');
|
|
|
+ assert.equal((await edit(report.revision)).saved,true); // A successful request retry stays successful after review.
|
|
|
+ await assert.rejects(edit(updated.updatedAt,'edit-after-review'),/已锁定/);
|
|
|
const audit=await one('SELECT result FROM "LessonWorkflowCommand" WHERE "requestKey"=$1',['edit-feedback-request']);assert.equal(audit.result.previousFeedback.contentSummary,feedback.contentSummary);
|
|
|
await db.query('UPDATE "CommonModel" SET status=-2 WHERE "objectId"=$1',[a.common]);await assert.rejects(edit(updated.updatedAt,'edit-cancel-request'),/已取消/);
|
|
|
});
|
|
|
@@ -276,7 +280,7 @@ test('历史已结课且无报告可首次补交,重复并发只生成一份
|
|
|
await db.exec('ALTER TABLE "LessonCompletionReport" DROP CONSTRAINT fail_first_report');
|
|
|
const results=await Promise.all([edit('history-rollback-test'),edit('history-rollback-test')]);
|
|
|
assert.ok(results.every(r=>r.result.saved));assert.equal(results[0].result.feedbackId,results[1].result.feedbackId);
|
|
|
- await assert.rejects(edit('history-distinct-request'),/报告已被更新/);
|
|
|
+ await assert.rejects(edit('history-distinct-request'),/已锁定/);
|
|
|
const reports=(await db.query('SELECT * FROM "LessonCompletionReport" WHERE "appointmentGeneralId"::text=$1',[a.gid])).rows;
|
|
|
assert.equal(reports.length,1);assert.equal(reports[0].feedbackStatus,'submitted');assert.equal(reports[0].contentSummary,feedback.contentSummary);
|
|
|
assert.equal(reports[0].lessonId,beforeLesson.objectId);assert.equal(await balance(),beforeBalance);assert.deepEqual(await snapshots(),before);
|
|
|
@@ -297,3 +301,20 @@ test('历史补交入口仅属于本课老师,学生保持独立评价入口',
|
|
|
const student=await context.lessonOrderCapabilities(order,user(20));assert.ok(student.allowedActions.includes('review'));assert.ok(!student.allowedActions.includes('editFeedback'));
|
|
|
assert.equal((await context.lessonOrderCapabilities({...order,cancelled:true},user(30))).allowedActions.length,0);
|
|
|
});
|
|
|
+
|
|
|
+
|
|
|
+test('学生评价后锁定报告,包括旧评分;先评价仍允许一次补交',async()=>{
|
|
|
+ const a=await appointment(true,'22:30');await complete(a.gid,a.updatedAt,'review-lock-setup');
|
|
|
+ const report=await one('SELECT *,"updatedAt"::text revision FROM "LessonCompletionReport" WHERE "appointmentGeneralId"::text=$1',[a.gid]);
|
|
|
+ await db.query('UPDATE "LessonRecord" SET pf=5 WHERE yyds=$1',[a.gid]);
|
|
|
+ await assert.rejects(one('SELECT xs_lesson_feedback_update($1,$2,$3,$4,$5,$6,$7::jsonb) result',['co',a.gid,'teacher',30,report.revision,'legacy-rating-lock',JSON.stringify(feedback)]),/已锁定/);
|
|
|
+ assert.deepEqual(await one('SELECT * FROM "LessonCompletionReport" WHERE "objectId"=$1',[report.objectId]),Object.fromEntries(Object.entries(report).filter(([k])=>k!=='revision')));
|
|
|
+ const {default:vm}=await import('node:vm');const context={DEFAULT_COMPANY_ID:'co',number:Number,ownLegacyId:()=>30,fail:(_c,m)=>{throw Error(m);},Psql:{oneOrNone:async(s,p)=>(await db.query(s,p)).rows[0]||null}};
|
|
|
+ vm.createContext(context);vm.runInContext(readFileSync(new URL('../cloud/lesson-workflow.js',import.meta.url),'utf8'),context);
|
|
|
+ const order={GeneralID:a.gid,pl:30,szyh:20,dszt:30},user={get:()=>({id:'co'})};
|
|
|
+ const locked=await context.lessonOrderCapabilities(order,user);assert.equal(locked.feedbackLocked,true);assert.ok(!locked.allowedActions.includes('editFeedback'));
|
|
|
+ await db.query('UPDATE "LessonCompletionReport" SET "feedbackStatus"=$1 WHERE "objectId"=$2',['pending',report.objectId]);
|
|
|
+ const pending=await context.lessonOrderCapabilities(order,user);assert.equal(pending.feedbackLocked,false);assert.ok(pending.allowedActions.includes('editFeedback'));
|
|
|
+ await one('SELECT xs_lesson_feedback_update($1,$2,$3,$4,$5,$6,$7::jsonb) result',['co',a.gid,'teacher',30,report.revision,'legacy-rating-first-submit',JSON.stringify(feedback)]);
|
|
|
+ assert.equal((await context.lessonOrderCapabilities(order,user)).feedbackLocked,true);
|
|
|
+});
|