import { Test, TestingModule } from "@nestjs/testing"; import { getRepositoryToken } from "@nestjs/typeorm"; import { NotFoundException, ForbiddenException, BadRequestException, } from "@nestjs/common"; import { SchedulesService } from "./schedules.service"; import { Schedule } from "../../entities/schedule.entity"; import { Group } from "../../entities/group.entity"; import { GroupMember } from "../../entities/group-member.entity"; import { TimeSlotDto } from "./dto/schedule.dto"; describe("SchedulesService", () => { let service: SchedulesService; let mockScheduleRepository: any; let mockGroupRepository: any; let mockGroupMemberRepository: any; const mockUser = { id: "user-1", username: "testuser" }; const mockGroup = { id: "group-1", name: "测试小组", isActive: true }; const mockMembership = { id: "member-1", userId: "user-1", groupId: "group-1", role: "member", isActive: true, }; const mockTimeSlots: TimeSlotDto[] = [ { startTime: new Date("2024-01-20T19:00:00Z"), endTime: new Date("2024-01-20T21:00:00Z"), note: "晚上空闲", }, { startTime: new Date("2024-01-21T14:00:00Z"), endTime: new Date("2024-01-21T17:00:00Z"), note: "下午空闲", }, ]; const mockSchedule = { id: "schedule-1", userId: "user-1", groupId: "group-1", availableSlots: mockTimeSlots, createdAt: new Date(), updatedAt: new Date(), }; beforeEach(async () => { mockScheduleRepository = { create: jest.fn(), save: jest.fn(), find: jest.fn(), findOne: jest.fn(), remove: jest.fn(), createQueryBuilder: jest.fn(), }; mockGroupRepository = { findOne: jest.fn(), }; mockGroupMemberRepository = { find: jest.fn(), findOne: jest.fn(), }; const module: TestingModule = await Test.createTestingModule({ providers: [ SchedulesService, { provide: getRepositoryToken(Schedule), useValue: mockScheduleRepository, }, { provide: getRepositoryToken(Group), useValue: mockGroupRepository, }, { provide: getRepositoryToken(GroupMember), useValue: mockGroupMemberRepository, }, ], }).compile(); service = module.get(SchedulesService); }); describe("create", () => { it("应该成功创建排班", async () => { mockGroupRepository.findOne.mockResolvedValue(mockGroup); mockGroupMemberRepository.findOne.mockResolvedValue(mockMembership); mockScheduleRepository.create.mockReturnValue(mockSchedule); mockScheduleRepository.save.mockResolvedValue(mockSchedule); mockScheduleRepository.findOne.mockResolvedValue({ ...mockSchedule, user: mockUser, group: mockGroup, }); const result = await service.create("user-1", { groupId: "group-1", title: "测试排班", availableSlots: mockTimeSlots, }); expect(result).toHaveProperty("id"); expect(mockScheduleRepository.save).toHaveBeenCalled(); }); it("应该在小组不存在时抛出异常", async () => { mockGroupRepository.findOne.mockResolvedValue(null); await expect( service.create("user-1", { groupId: "group-1", title: "测试排班", availableSlots: mockTimeSlots, }), ).rejects.toThrow(NotFoundException); }); it("应该在用户不在小组中时抛出异常", async () => { mockGroupRepository.findOne.mockResolvedValue(mockGroup); mockGroupMemberRepository.findOne.mockResolvedValue(null); await expect( service.create("user-1", { groupId: "group-1", title: "测试排班", availableSlots: mockTimeSlots, }), ).rejects.toThrow(ForbiddenException); }); it("应该在时间段为空时抛出异常", async () => { mockGroupRepository.findOne.mockResolvedValue(mockGroup); mockGroupMemberRepository.findOne.mockResolvedValue(mockMembership); await expect( service.create("user-1", { groupId: "group-1", title: "测试排班", availableSlots: [], }), ).rejects.toThrow(BadRequestException); }); it("应该在时间段无效时抛出异常", async () => { mockGroupRepository.findOne.mockResolvedValue(mockGroup); mockGroupMemberRepository.findOne.mockResolvedValue(mockMembership); await expect( service.create("user-1", { groupId: "group-1", title: "测试排班", availableSlots: [ { startTime: new Date("2024-01-20T21:00:00Z"), endTime: new Date("2024-01-20T19:00:00Z"), // 结束时间早于开始时间 }, ], }), ).rejects.toThrow(BadRequestException); }); }); describe("findAll", () => { it("应该成功获取排班列表", async () => { const mockQueryBuilder = { leftJoinAndSelect: jest.fn().mockReturnThis(), andWhere: jest.fn().mockReturnThis(), orderBy: jest.fn().mockReturnThis(), skip: jest.fn().mockReturnThis(), take: jest.fn().mockReturnThis(), getManyAndCount: jest.fn().mockResolvedValue([[mockSchedule], 1]), }; mockScheduleRepository.createQueryBuilder.mockReturnValue( mockQueryBuilder, ); mockGroupMemberRepository.findOne.mockResolvedValue(mockMembership); const result = await service.findAll("user-1", { groupId: "group-1", page: 1, limit: 10, }); expect(result).toHaveProperty("items"); expect(result).toHaveProperty("total"); expect(result.items).toHaveLength(1); expect(result.total).toBe(1); }); it("应该在指定小组且用户不在小组时抛出异常", async () => { const mockQueryBuilder = { leftJoinAndSelect: jest.fn().mockReturnThis(), andWhere: jest.fn().mockReturnThis(), orderBy: jest.fn().mockReturnThis(), skip: jest.fn().mockReturnThis(), take: jest.fn().mockReturnThis(), getManyAndCount: jest.fn().mockResolvedValue([[mockSchedule], 1]), }; mockScheduleRepository.createQueryBuilder.mockReturnValue( mockQueryBuilder, ); mockGroupMemberRepository.findOne.mockResolvedValue(null); await expect( service.findAll("user-1", { groupId: "group-1", }), ).rejects.toThrow(ForbiddenException); }); it("应该在无小组ID时返回用户所在所有小组的排班", async () => { const mockQueryBuilder = { leftJoinAndSelect: jest.fn().mockReturnThis(), andWhere: jest.fn().mockReturnThis(), orderBy: jest.fn().mockReturnThis(), skip: jest.fn().mockReturnThis(), take: jest.fn().mockReturnThis(), getManyAndCount: jest.fn().mockResolvedValue([[mockSchedule], 1]), }; mockScheduleRepository.createQueryBuilder.mockReturnValue( mockQueryBuilder, ); mockGroupMemberRepository.find.mockResolvedValue([ { groupId: "group-1" }, { groupId: "group-2" }, ]); const result = await service.findAll("user-1", {}); expect(result.items).toHaveLength(1); expect(mockGroupMemberRepository.find).toHaveBeenCalled(); }); }); describe("findOne", () => { it("应该成功获取排班详情", async () => { mockScheduleRepository.findOne.mockResolvedValue({ ...mockSchedule, user: mockUser, group: mockGroup, }); const result = await service.findOne("schedule-1"); expect(result).toHaveProperty("id"); expect(result.id).toBe("schedule-1"); }); it("应该在排班不存在时抛出异常", async () => { mockScheduleRepository.findOne.mockResolvedValue(null); await expect(service.findOne("schedule-1")).rejects.toThrow( NotFoundException, ); }); }); describe("update", () => { it("应该成功更新排班", async () => { mockScheduleRepository.findOne .mockResolvedValueOnce(mockSchedule) .mockResolvedValueOnce({ ...mockSchedule, user: mockUser, group: mockGroup, }); mockScheduleRepository.save.mockResolvedValue(mockSchedule); const result = await service.update("user-1", "schedule-1", { availableSlots: mockTimeSlots, }); expect(result).toHaveProperty("id"); expect(mockScheduleRepository.save).toHaveBeenCalled(); }); it("应该在排班不存在时抛出异常", async () => { mockScheduleRepository.findOne.mockResolvedValue(null); await expect( service.update("user-1", "schedule-1", { availableSlots: mockTimeSlots, }), ).rejects.toThrow(NotFoundException); }); it("应该在非创建者更新时抛出异常", async () => { mockScheduleRepository.findOne.mockResolvedValue(mockSchedule); await expect( service.update("user-2", "schedule-1", { availableSlots: mockTimeSlots, }), ).rejects.toThrow(ForbiddenException); }); }); describe("remove", () => { it("应该成功删除排班", async () => { mockScheduleRepository.findOne.mockResolvedValue(mockSchedule); mockScheduleRepository.remove.mockResolvedValue(mockSchedule); const result = await service.remove("user-1", "schedule-1"); expect(result).toHaveProperty("message"); expect(mockScheduleRepository.remove).toHaveBeenCalled(); }); it("应该在排班不存在时抛出异常", async () => { mockScheduleRepository.findOne.mockResolvedValue(null); await expect(service.remove("user-1", "schedule-1")).rejects.toThrow( NotFoundException, ); }); it("应该在非创建者删除时抛出异常", async () => { mockScheduleRepository.findOne.mockResolvedValue(mockSchedule); await expect(service.remove("user-2", "schedule-1")).rejects.toThrow( ForbiddenException, ); }); }); describe("findCommonSlots", () => { it("应该成功查找共同空闲时间", async () => { mockGroupMemberRepository.findOne.mockResolvedValue(mockMembership); mockScheduleRepository.find.mockResolvedValue([ { ...mockSchedule, userId: "user-1", user: { id: "user-1" }, }, { ...mockSchedule, id: "schedule-2", userId: "user-2", user: { id: "user-2" }, availableSlots: [ { startTime: new Date("2024-01-20T19:30:00Z"), endTime: new Date("2024-01-20T22:00:00Z"), }, ], }, ]); const result = await service.findCommonSlots("user-1", { groupId: "group-1", startTime: new Date("2024-01-20T00:00:00Z"), endTime: new Date("2024-01-22T00:00:00Z"), minParticipants: 2, }); expect(result).toHaveProperty("commonSlots"); expect(result).toHaveProperty("totalParticipants"); expect(result.totalParticipants).toBe(2); }); it("应该在用户不在小组时抛出异常", async () => { mockGroupMemberRepository.findOne.mockResolvedValue(null); await expect( service.findCommonSlots("user-1", { groupId: "group-1", startTime: new Date("2024-01-20T00:00:00Z"), endTime: new Date("2024-01-22T00:00:00Z"), }), ).rejects.toThrow(ForbiddenException); }); it("应该在没有排班数据时返回空结果", async () => { mockGroupMemberRepository.findOne.mockResolvedValue(mockMembership); mockScheduleRepository.find.mockResolvedValue([]); const result = await service.findCommonSlots("user-1", { groupId: "group-1", startTime: new Date("2024-01-20T00:00:00Z"), endTime: new Date("2024-01-22T00:00:00Z"), }); expect(result.commonSlots).toEqual([]); expect(result.message).toBe("暂无排班数据"); }); }); });