forked from wangziqi/gongxue-base
feat: schedule student supervision rules
This commit is contained in:
@@ -9426,6 +9426,57 @@ async function testTenantStudentOperations() {
|
||||
'supervision generation should be idempotent for the same batch and student',
|
||||
);
|
||||
|
||||
const supervisionRule = await request('/api/tenant-admin/students/supervision/rules', {
|
||||
userId: TENANT_TEACHER_USER_ID,
|
||||
method: 'PUT',
|
||||
body: {
|
||||
name: '集成测试教师班级督导规则',
|
||||
classId: ids.tenantClass,
|
||||
status: 'active',
|
||||
rules: {
|
||||
inactivityDays: 1,
|
||||
wrongQuestionThreshold: 1,
|
||||
minAnswers: 1,
|
||||
lowAccuracyThreshold: 0.99,
|
||||
vocabularyDueThreshold: 1,
|
||||
},
|
||||
schedule: {
|
||||
enabled: true,
|
||||
frequency: 'daily',
|
||||
hour: 9,
|
||||
minute: 0,
|
||||
},
|
||||
limit: 5,
|
||||
},
|
||||
});
|
||||
assert.ok(supervisionRule.item?.id, 'teacher should create class-scoped supervision rule');
|
||||
assert.equal(supervisionRule.item?.classId, ids.tenantClass, 'supervision rule should bind scoped class');
|
||||
assert.ok(supervisionRule.item?.nextRunAt, 'active scheduled supervision rule should compute next run time');
|
||||
|
||||
const supervisionRules = await request('/api/tenant-admin/students/supervision/rules', {
|
||||
userId: TENANT_TEACHER_USER_ID,
|
||||
query: { limit: 20 },
|
||||
});
|
||||
assert.equal(supervisionRules.scoped, true, 'teacher supervision rule list should be scoped');
|
||||
assert.ok(
|
||||
supervisionRules.items?.some(item => item.id === supervisionRule.item.id),
|
||||
'teacher should list own scoped supervision rule',
|
||||
);
|
||||
|
||||
const teacherGlobalSupervisionRuleDenied = await request('/api/tenant-admin/students/supervision/rules', {
|
||||
userId: TENANT_TEACHER_USER_ID,
|
||||
method: 'PUT',
|
||||
body: {
|
||||
name: '不应保存全局规则',
|
||||
status: 'active',
|
||||
rules: { inactivityDays: 1 },
|
||||
schedule: { enabled: true, frequency: 'daily' },
|
||||
limit: 5,
|
||||
},
|
||||
expectStatus: 403,
|
||||
});
|
||||
assert.equal(teacherGlobalSupervisionRuleDenied.code, 'CLASS_SCOPE_REQUIRED', 'teacher should not create global supervision rules');
|
||||
|
||||
const teacherOtherSupervisionDenied = await request('/api/tenant-admin/students/supervision/generate', {
|
||||
userId: TENANT_TEACHER_USER_ID,
|
||||
method: 'POST',
|
||||
@@ -9446,6 +9497,13 @@ async function testTenantStudentOperations() {
|
||||
});
|
||||
assert.equal(operatorSupervisionDenied.code, 'TENANT_PERMISSION_REQUIRED', 'operator without supervision permission should be denied');
|
||||
|
||||
const operatorSupervisionRuleDenied = await request('/api/tenant-admin/students/supervision/rules', {
|
||||
userId: TENANT_OPERATOR_USER_ID,
|
||||
query: { limit: 5 },
|
||||
expectStatus: 403,
|
||||
});
|
||||
assert.equal(operatorSupervisionRuleDenied.code, 'TENANT_PERMISSION_REQUIRED', 'operator without supervision permission should not list rules');
|
||||
|
||||
const teacherOtherFollowupDenied = await request('/api/tenant-admin/students/followups', {
|
||||
userId: TENANT_TEACHER_USER_ID,
|
||||
method: 'PUT',
|
||||
@@ -9504,6 +9562,7 @@ async function testTenantStudentOperations() {
|
||||
assert.ok(auditLogs.items?.some(item => item.action === 'tenant.student_note.upserted'), 'audit logs should include student note upsert');
|
||||
assert.ok(auditLogs.items?.some(item => item.action === 'tenant.student_followup.upserted'), 'audit logs should include student follow-up upsert');
|
||||
assert.ok(auditLogs.items?.some(item => item.action === 'tenant.students.supervision_generated'), 'audit logs should include supervision generation');
|
||||
assert.ok(auditLogs.items?.some(item => item.action === 'tenant.students.supervision_rule_upserted'), 'audit logs should include supervision rule upsert');
|
||||
}
|
||||
|
||||
async function testReferralAndCrmGrowth() {
|
||||
|
||||
Reference in New Issue
Block a user