This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
import { AttendanceLeaveSyncService } from './attendance-leave-sync.service';
|
||||
import { DingTalkService } from '../integration/dingtalk.service';
|
||||
|
||||
describe('AttendanceLeaveSyncService', () => {
|
||||
const dingLeaveRawRepo = {
|
||||
find: jest.fn(),
|
||||
findOne: jest.fn(),
|
||||
create: jest.fn((value: Record<string, unknown>) => value),
|
||||
save: jest.fn(),
|
||||
};
|
||||
const studentRepo = { findOne: jest.fn() };
|
||||
const studentDingMappingRepo = { findOne: jest.fn(), find: jest.fn() };
|
||||
const dingTalkService = {
|
||||
fetchDailyLeaveStatus: jest.fn(),
|
||||
};
|
||||
|
||||
let service: AttendanceLeaveSyncService;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
service = new AttendanceLeaveSyncService(
|
||||
dingLeaveRawRepo as never,
|
||||
studentRepo as never,
|
||||
studentDingMappingRepo as never,
|
||||
dingTalkService as unknown as DingTalkService,
|
||||
);
|
||||
});
|
||||
|
||||
it('syncs approved DingTalk leaves per user per day and auto-matches them', async () => {
|
||||
dingTalkService.fetchDailyLeaveStatus.mockImplementation(
|
||||
async (userId: string, workDate: string) => [
|
||||
{
|
||||
userId,
|
||||
workDate,
|
||||
procInstId: `leave-${userId}-${workDate}`,
|
||||
tagName: '请假',
|
||||
leaveType: '事假',
|
||||
beginTime: new Date(`${workDate}T08:00:00+08:00`),
|
||||
endTime: new Date(`${workDate}T12:00:00+08:00`),
|
||||
approvedAt: new Date(`${workDate}T09:00:00+08:00`),
|
||||
duration: '0.5',
|
||||
durationUnit: 'day',
|
||||
},
|
||||
],
|
||||
);
|
||||
dingLeaveRawRepo.findOne.mockResolvedValue(null);
|
||||
dingLeaveRawRepo.save.mockImplementation(async (entities) => entities);
|
||||
studentDingMappingRepo.find.mockResolvedValue([{ dingUserId: 'ding-1', studentId: 7 }]);
|
||||
dingLeaveRawRepo.find.mockResolvedValue([
|
||||
{ dingId: 'leave-ding-1-2026-07-01', dingUserId: 'ding-1', matchStatus: 'unmatched' },
|
||||
]);
|
||||
|
||||
const result = await service.syncLeaveStatusForLesson({
|
||||
startDate: '2026-07-01',
|
||||
endDate: '2026-07-02',
|
||||
userIds: ['ding-1', 'ding-2'],
|
||||
autoMatch: true,
|
||||
});
|
||||
|
||||
expect(dingTalkService.fetchDailyLeaveStatus).toHaveBeenCalledTimes(4);
|
||||
expect(dingTalkService.fetchDailyLeaveStatus).toHaveBeenCalledWith(
|
||||
'ding-1',
|
||||
'2026-07-01',
|
||||
);
|
||||
expect(dingTalkService.fetchDailyLeaveStatus).toHaveBeenCalledWith(
|
||||
'ding-2',
|
||||
'2026-07-02',
|
||||
);
|
||||
expect(dingLeaveRawRepo.save).toHaveBeenCalled();
|
||||
expect(result.synced).toBe(4);
|
||||
expect(result.matched).toBe(1);
|
||||
});
|
||||
|
||||
it('keeps syncing remaining users when one leave fetch fails', async () => {
|
||||
dingTalkService.fetchDailyLeaveStatus
|
||||
.mockRejectedValueOnce(new Error('DingTalk unavailable'))
|
||||
.mockResolvedValue([]);
|
||||
dingLeaveRawRepo.findOne.mockResolvedValue(null);
|
||||
|
||||
const result = await service.syncLeaveStatusForLesson({
|
||||
startDate: '2026-07-01',
|
||||
endDate: '2026-07-01',
|
||||
userIds: ['ding-1', 'ding-2'],
|
||||
autoMatch: false,
|
||||
});
|
||||
|
||||
expect(dingTalkService.fetchDailyLeaveStatus).toHaveBeenCalledTimes(2);
|
||||
expect(result.errors).toHaveLength(1);
|
||||
expect(result.synced).toBe(0);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user