import { Test, TestingModule } from '@nestjs/testing'; import { getRepositoryToken } from '@nestjs/typeorm'; import { BadRequestException, ValidationPipe } from '@nestjs/common'; import { Repository } from 'typeorm'; import { AttendanceService } from './attendance.service'; import { AttendanceRecord } from '../entities/attendance-record.entity'; import { DingAttendanceRaw } from '../entities/ding-attendance-raw.entity'; import { Class } from '../entities/class.entity'; import { Student } from '../entities/student.entity'; import { CampusScope } from '../common/campus-scope'; import { BatchCreateAttendanceDto } from './dto/attendance.dto'; describe('AttendanceService — batchCreate', () => { let service: AttendanceService; let attendanceRepo: jest.Mocked, 'create' | 'save'>>; const savedRecords: AttendanceRecord[] = []; beforeEach(async () => { savedRecords.length = 0; const mockRepo = { create: jest .fn() .mockImplementation((data: Partial) => ({ id: 1, ...data } as AttendanceRecord)), save: jest .fn() .mockImplementation((entities: AttendanceRecord[]) => { const result = entities.map((e, i) => ({ ...e, id: i + 1 } as AttendanceRecord)); savedRecords.push(...result); return Promise.resolve(result); }), }; const mockDingRepo = {}; const mockClassRepo = { find: jest.fn().mockResolvedValue([]) }; const mockStudentRepo = { find: jest.fn().mockResolvedValue([]) }; const mockCampusScope = { getScopeDepartmentIds: jest.fn().mockResolvedValue(null), filter: jest.fn((w: any) => w) }; const module: TestingModule = await Test.createTestingModule({ providers: [ AttendanceService, { provide: getRepositoryToken(AttendanceRecord), useValue: mockRepo }, { provide: getRepositoryToken(DingAttendanceRaw), useValue: mockDingRepo }, { provide: getRepositoryToken(Class), useValue: mockClassRepo }, { provide: getRepositoryToken(Student), useValue: mockStudentRepo }, { provide: CampusScope, useValue: mockCampusScope }, ], }).compile(); service = module.get(AttendanceService); attendanceRepo = module.get(getRepositoryToken(AttendanceRecord)); }); it('valid batch with morning_reading, evening_study, and night_check sessions → succeeds', async () => { const dto: BatchCreateAttendanceDto = { records: [ { studentId: 1, classId: 10, attendanceDate: '2026-07-05', session: 'morning_reading', status: 'present' }, { studentId: 2, classId: 10, attendanceDate: '2026-07-05', session: 'evening_study', status: 'present' }, { studentId: 3, classId: 10, attendanceDate: '2026-07-05', session: 'night_check', status: 'present' }, ], }; const result = await service.batchCreate(dto); expect(result.count).toBe(3); expect(result.records).toHaveLength(3); expect(result.records[0].session).toBe('morning_reading'); expect(result.records[1].session).toBe('evening_study'); expect(result.records[2].session).toBe('night_check'); }); it('invalid session → validation error (DTO-level)', async () => { const pipe = new ValidationPipe({ whitelist: true }); const invalidPayload = { records: [ { studentId: 1, attendanceDate: '2026-07-05', session: 'invalid_session', status: 'present' }, ], }; await expect( pipe.transform(invalidPayload, { type: 'body', metatype: BatchCreateAttendanceDto, }), ).rejects.toThrow(); }); it('empty batch → BadRequestException', async () => { const dto: BatchCreateAttendanceDto = { records: [] }; await expect(service.batchCreate(dto)).rejects.toThrow(BadRequestException); }); });