fix permissions and teacher attendance workflows
This commit is contained in:
@@ -10,6 +10,7 @@ import { Student } from '../entities/student.entity';
|
||||
import { ClassSchedule } from '../entities/class-schedule.entity';
|
||||
import { ClassStudent } from '../entities/class-student.entity';
|
||||
import { StudentDingMapping } from '../entities/student-ding-mapping.entity';
|
||||
import { ClassTeacher } from '../entities/class-teacher.entity';
|
||||
import { BatchCreateAttendanceDto } from './dto/attendance.dto';
|
||||
|
||||
describe('AttendanceService — batchCreate', () => {
|
||||
@@ -51,6 +52,7 @@ describe('AttendanceService — batchCreate', () => {
|
||||
{ provide: getRepositoryToken(ClassSchedule), useValue: mockScheduleRepo },
|
||||
{ provide: getRepositoryToken(StudentDingMapping), useValue: mockStudentDingMappingRepo },
|
||||
{ provide: getRepositoryToken(ClassStudent), useValue: mockClassStudentRepo },
|
||||
{ provide: getRepositoryToken(ClassTeacher), useValue: { findOne: jest.fn() } },
|
||||
],
|
||||
}).compile();
|
||||
|
||||
@@ -105,3 +107,115 @@ describe('AttendanceService — batchCreate', () => {
|
||||
// Requires mock setup for StudentDingMapping, ClassSchedule, ClassStudent, and DingAttendanceRaw repos.
|
||||
});
|
||||
});
|
||||
|
||||
describe('AttendanceService — teacher DingTalk class scope', () => {
|
||||
const classTeacherRepo = {
|
||||
findOne: jest.fn(),
|
||||
find: jest.fn(),
|
||||
};
|
||||
const classStudentRepo = {
|
||||
find: jest.fn(),
|
||||
};
|
||||
const mappingRepo = {
|
||||
find: jest.fn(),
|
||||
};
|
||||
|
||||
const createService = () =>
|
||||
new AttendanceService(
|
||||
{} as never,
|
||||
{} as never,
|
||||
{} as never,
|
||||
{} as never,
|
||||
{} as never,
|
||||
classStudentRepo as never,
|
||||
mappingRepo as never,
|
||||
classTeacherRepo as never,
|
||||
);
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('returns only mapped active students for a class assigned to the teacher', async () => {
|
||||
classTeacherRepo.findOne.mockResolvedValue({ classId: 8, userId: 21 });
|
||||
classStudentRepo.find.mockResolvedValue([
|
||||
{ studentId: 2 },
|
||||
{ studentId: 1 },
|
||||
{ studentId: 2 },
|
||||
]);
|
||||
mappingRepo.find.mockResolvedValue([
|
||||
{ studentId: 1, dingUserId: 'ding-1' },
|
||||
{ studentId: 2, dingUserId: 'ding-2' },
|
||||
]);
|
||||
|
||||
await expect(createService().getTeacherClassDingUserIds(21, 8, false)).resolves.toEqual([
|
||||
'ding-1',
|
||||
'ding-2',
|
||||
]);
|
||||
});
|
||||
|
||||
it('lists distinct classes assigned to a teacher', async () => {
|
||||
classTeacherRepo.find.mockResolvedValue([
|
||||
{ classId: 8, class: { name: '八班' } },
|
||||
{ classId: 8, class: { name: '八班' } },
|
||||
{ classId: 9, class: { name: '九班' } },
|
||||
]);
|
||||
|
||||
await expect(createService().getImportableClasses(21, false)).resolves.toEqual([
|
||||
{ classId: 8, className: '八班' },
|
||||
{ classId: 9, className: '九班' },
|
||||
]);
|
||||
expect(classTeacherRepo.find).toHaveBeenCalledWith({
|
||||
where: { userId: 21 },
|
||||
relations: ['class'],
|
||||
});
|
||||
});
|
||||
|
||||
it('rejects a class that is not assigned to the teacher', async () => {
|
||||
classTeacherRepo.findOne.mockResolvedValue(null);
|
||||
|
||||
await expect(createService().getTeacherClassDingUserIds(21, 99, false)).rejects.toBeInstanceOf(
|
||||
BadRequestException,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('AttendanceService — DingTalk raw query', () => {
|
||||
it('returns the paginated shape and filters by class student mappings', async () => {
|
||||
const qb = {
|
||||
leftJoinAndSelect: jest.fn().mockReturnThis(),
|
||||
andWhere: jest.fn().mockReturnThis(),
|
||||
orderBy: jest.fn().mockReturnThis(),
|
||||
addOrderBy: jest.fn().mockReturnThis(),
|
||||
skip: jest.fn().mockReturnThis(),
|
||||
take: jest.fn().mockReturnThis(),
|
||||
getManyAndCount: jest.fn().mockResolvedValue([[{ id: 1 }], 1]),
|
||||
};
|
||||
const dingRepo = { createQueryBuilder: jest.fn().mockReturnValue(qb) };
|
||||
const classStudentRepo = { find: jest.fn().mockResolvedValue([{ studentId: 3 }]) };
|
||||
const mappingRepo = { find: jest.fn().mockResolvedValue([{ studentId: 3, dingUserId: 'ding-3' }]) };
|
||||
const service = new AttendanceService(
|
||||
{} as never,
|
||||
dingRepo as never,
|
||||
{} as never,
|
||||
{} as never,
|
||||
{} as never,
|
||||
classStudentRepo as never,
|
||||
mappingRepo as never,
|
||||
{} as never,
|
||||
);
|
||||
|
||||
await expect(
|
||||
service.getDingRaw({ classId: 8, dateFrom: '2026-07-01', page: 2, pageSize: 10 }),
|
||||
).resolves.toEqual({ list: [{ id: 1 }], total: 1, page: 2, pageSize: 10 });
|
||||
|
||||
expect(qb.andWhere).toHaveBeenCalledWith('ar.dingUserId IN (:...dingUserIds)', {
|
||||
dingUserIds: ['ding-3'],
|
||||
});
|
||||
expect(qb.andWhere).toHaveBeenCalledWith('ar.attendanceDate >= :dateFrom', {
|
||||
dateFrom: '2026-07-01',
|
||||
});
|
||||
expect(qb.skip).toHaveBeenCalledWith(10);
|
||||
expect(qb.take).toHaveBeenCalledWith(10);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user