- Add CommonModule to provide/export CampusScope across business modules - Replace DepartmentsModule imports with CommonModule in 11 modules - Add missing Class/Tenant repository imports to StudentsModule and SchedulesModule - Fix service specs with CampusScope, ClassRepository, StudentRepository mocks - Move better-sqlite3 to optionalDependencies for production Docker builds - Rewrite Dockerfiles for monorepo root build context - Update docker-compose.yml service build contexts
95 lines
3.8 KiB
TypeScript
95 lines
3.8 KiB
TypeScript
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<Pick<Repository<AttendanceRecord>, 'create' | 'save'>>;
|
|
|
|
const savedRecords: AttendanceRecord[] = [];
|
|
|
|
beforeEach(async () => {
|
|
savedRecords.length = 0;
|
|
const mockRepo = {
|
|
create: jest
|
|
.fn()
|
|
.mockImplementation((data: Partial<AttendanceRecord>) => ({ 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>(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);
|
|
});
|
|
});
|