92 lines
3.0 KiB
TypeScript
92 lines
3.0 KiB
TypeScript
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);
|
|
});
|
|
});
|