forked from wangziqi/gongxue-base
129 lines
3.8 KiB
TypeScript
129 lines
3.8 KiB
TypeScript
import { BadRequestException } from '@nestjs/common';
|
|
import { AttendanceImportService } from './attendance-import.service';
|
|
import { DingTalkService } from '../integration/dingtalk.service';
|
|
import { AttendanceService } from './attendance.service';
|
|
|
|
describe('AttendanceImportService', () => {
|
|
const dingRawRepo = {
|
|
find: jest.fn(),
|
|
findOne: jest.fn(),
|
|
save: jest.fn(),
|
|
};
|
|
const studentRepo = { findOne: jest.fn() };
|
|
const studentDingMappingRepo = { findOne: jest.fn() };
|
|
const dingTalkService = {
|
|
fetchAttendanceResults: jest.fn(),
|
|
};
|
|
const attendanceService = {
|
|
autoMatchDingRecords: jest.fn(),
|
|
};
|
|
|
|
let service: AttendanceImportService;
|
|
|
|
beforeEach(() => {
|
|
jest.clearAllMocks();
|
|
service = new AttendanceImportService(
|
|
dingRawRepo as never,
|
|
studentRepo as never,
|
|
studentDingMappingRepo as never,
|
|
dingTalkService as unknown as DingTalkService,
|
|
attendanceService as unknown as AttendanceService,
|
|
);
|
|
});
|
|
|
|
it('splits DingTalk requests by at most 50 users and 7 calendar days without offset pagination', async () => {
|
|
const userIds = Array.from({ length: 51 }, (_, index) => `user-${index + 1}`);
|
|
dingTalkService.fetchAttendanceResults.mockResolvedValue([]);
|
|
|
|
await (service as any).fetchAllPages({
|
|
startDate: '2026-07-01',
|
|
endDate: '2026-07-10',
|
|
userIds,
|
|
});
|
|
|
|
expect(dingTalkService.fetchAttendanceResults).toHaveBeenCalledTimes(4);
|
|
expect(dingTalkService.fetchAttendanceResults.mock.calls.map(([params]) => params)).toEqual([
|
|
{
|
|
startDate: '2026-07-01',
|
|
endDate: '2026-07-07',
|
|
userIds: userIds.slice(0, 50),
|
|
},
|
|
{
|
|
startDate: '2026-07-01',
|
|
endDate: '2026-07-07',
|
|
userIds: userIds.slice(50),
|
|
},
|
|
{
|
|
startDate: '2026-07-08',
|
|
endDate: '2026-07-10',
|
|
userIds: userIds.slice(0, 50),
|
|
},
|
|
{
|
|
startDate: '2026-07-08',
|
|
endDate: '2026-07-10',
|
|
userIds: userIds.slice(50),
|
|
},
|
|
]);
|
|
});
|
|
|
|
it('rejects an attendance import without DingTalk user IDs', async () => {
|
|
await expect(
|
|
(service as any).fetchAllPages({
|
|
startDate: '2026-07-01',
|
|
endDate: '2026-07-01',
|
|
userIds: [],
|
|
}),
|
|
).rejects.toBeInstanceOf(BadRequestException);
|
|
|
|
expect(dingTalkService.fetchAttendanceResults).not.toHaveBeenCalled();
|
|
});
|
|
it('stores the DingTalk user name returned with the attendance record', async () => {
|
|
const entity = await (service as any).mapToEntity({
|
|
userId: 'ding-1',
|
|
userName: '张三',
|
|
workDate: '2026-07-01',
|
|
timeResult: 'Normal',
|
|
locationResult: '',
|
|
planCheckTime: '',
|
|
actualCheckTime: '2026-07-01T08:00:00.000Z',
|
|
checkId: 'check-1',
|
|
checkType: 'OnDuty',
|
|
});
|
|
|
|
expect(entity.userName).toBe('张三');
|
|
});
|
|
|
|
it('fills the student name from the DingTalk mapping when saving an imported record', async () => {
|
|
dingTalkService.fetchAttendanceResults.mockResolvedValue([
|
|
{
|
|
userId: 'ding-1',
|
|
userName: '',
|
|
workDate: '2026-07-01',
|
|
timeResult: 'Normal',
|
|
locationResult: '',
|
|
planCheckTime: '',
|
|
actualCheckTime: '2026-07-01T08:00:00.000Z',
|
|
checkId: 'check-1',
|
|
checkType: 'OnDuty',
|
|
},
|
|
]);
|
|
dingRawRepo.find.mockResolvedValue([]);
|
|
studentDingMappingRepo.findOne.mockResolvedValue({ studentId: 3 });
|
|
studentRepo.findOne.mockResolvedValue({ id: 3, name: '张三' });
|
|
dingRawRepo.save.mockImplementation(async (entities) => entities);
|
|
|
|
await service.importFromDingTalk({
|
|
startDate: '2026-07-01',
|
|
endDate: '2026-07-01',
|
|
userIds: ['ding-1'],
|
|
autoMatch: false,
|
|
});
|
|
|
|
expect(dingRawRepo.save).toHaveBeenCalledWith(
|
|
[expect.objectContaining({ userName: '张三' })],
|
|
{ chunk: 50 },
|
|
);
|
|
});
|
|
|
|
});
|