import { Test, TestingModule } from "@nestjs/testing"; import { getRepositoryToken } from "@nestjs/typeorm"; import { NotFoundException, BadRequestException, ForbiddenException, } from "@nestjs/common"; import { AppointmentsService } from "./appointments.service"; import { Appointment } from "../../entities/appointment.entity"; import { AppointmentParticipant } from "../../entities/appointment-participant.entity"; import { Group } from "../../entities/group.entity"; import { GroupMember } from "../../entities/group-member.entity"; import { Game } from "../../entities/game.entity"; import { User } from "../../entities/user.entity"; import { CacheService } from "../../common/services/cache.service"; enum AppointmentStatus { PENDING = "pending", CONFIRMED = "confirmed", CANCELLED = "cancelled", COMPLETED = "completed", } describe("AppointmentsService", () => { let service: AppointmentsService; let mockAppointmentRepository: any; let mockParticipantRepository: any; let mockGroupRepository: any; let mockGroupMemberRepository: any; let mockGameRepository: any; let mockUserRepository: any; const mockUser = { id: "user-1", username: "testuser" }; const mockGroup = { id: "group-1", name: "测试小组", isActive: true }; const mockGame = { id: "game-1", name: "测试游戏" }; const mockMembership = { id: "member-1", userId: "user-1", groupId: "group-1", role: "member", isActive: true, }; const mockAppointment = { id: "appointment-1", groupId: "group-1", gameId: "game-1", creatorId: "user-1", title: "周末开黑", description: "描述", startTime: new Date("2024-01-20T19:00:00Z"), endTime: new Date("2024-01-20T23:00:00Z"), maxParticipants: 5, status: AppointmentStatus.PENDING, createdAt: new Date(), updatedAt: new Date(), }; const mockParticipant = { id: "participant-1", appointmentId: "appointment-1", userId: "user-1", status: "accepted", joinedAt: new Date(), }; beforeEach(async () => { mockAppointmentRepository = { create: jest.fn(), save: jest.fn(), findOne: jest.fn(), remove: jest.fn(), createQueryBuilder: jest.fn(), }; mockParticipantRepository = { create: jest.fn(), save: jest.fn(), findOne: jest.fn(), find: jest.fn(), count: jest.fn(), remove: jest.fn(), createQueryBuilder: jest.fn(), }; mockGroupRepository = { findOne: jest.fn(), }; mockGroupMemberRepository = { findOne: jest.fn(), find: jest.fn(), }; mockGameRepository = { findOne: jest.fn(), }; mockUserRepository = { findOne: jest.fn(), }; const mockCacheService = { get: jest.fn(), set: jest.fn(), del: jest.fn(), clear: jest.fn(), clearByPrefix: jest.fn(), }; const module: TestingModule = await Test.createTestingModule({ providers: [ AppointmentsService, { provide: getRepositoryToken(Appointment), useValue: mockAppointmentRepository, }, { provide: getRepositoryToken(AppointmentParticipant), useValue: mockParticipantRepository, }, { provide: getRepositoryToken(Group), useValue: mockGroupRepository, }, { provide: getRepositoryToken(GroupMember), useValue: mockGroupMemberRepository, }, { provide: getRepositoryToken(Game), useValue: mockGameRepository, }, { provide: getRepositoryToken(User), useValue: mockUserRepository, }, { provide: CacheService, useValue: mockCacheService, }, ], }).compile(); service = module.get(AppointmentsService); }); describe("create", () => { it("应该成功创建预约", async () => { mockGroupRepository.findOne.mockResolvedValue(mockGroup); mockGroupMemberRepository.findOne.mockResolvedValue(mockMembership); mockGameRepository.findOne.mockResolvedValue(mockGame); mockAppointmentRepository.create.mockReturnValue(mockAppointment); mockAppointmentRepository.save.mockResolvedValue(mockAppointment); mockParticipantRepository.create.mockReturnValue(mockParticipant); mockParticipantRepository.save.mockResolvedValue(mockParticipant); mockAppointmentRepository.findOne.mockResolvedValue({ ...mockAppointment, group: mockGroup, game: mockGame, creator: mockUser, participants: [mockParticipant], }); const result = await service.create("user-1", { groupId: "group-1", gameId: "game-1", title: "周末开黑", startTime: new Date("2024-01-20T19:00:00Z"), maxParticipants: 5, }); expect(result).toHaveProperty("id"); expect(result.title).toBe("周末开黑"); expect(mockAppointmentRepository.save).toHaveBeenCalled(); expect(mockParticipantRepository.save).toHaveBeenCalled(); }); it("应该在小组不存在时抛出异常", async () => { mockGroupRepository.findOne.mockResolvedValue(null); await expect( service.create("user-1", { groupId: "group-1", gameId: "game-1", title: "周末开黑", startTime: new Date("2024-01-20T19:00:00Z"), maxParticipants: 5, }), ).rejects.toThrow(NotFoundException); }); it("应该在用户不在小组中时抛出异常", async () => { mockGroupRepository.findOne.mockResolvedValue(mockGroup); mockGroupMemberRepository.findOne.mockResolvedValue(null); await expect( service.create("user-1", { groupId: "group-1", gameId: "game-1", title: "周末开黑", startTime: new Date("2024-01-20T19:00:00Z"), maxParticipants: 5, }), ).rejects.toThrow(ForbiddenException); }); it("应该在游戏不存在时抛出异常", async () => { mockGroupRepository.findOne.mockResolvedValue(mockGroup); mockGroupMemberRepository.findOne.mockResolvedValue(mockMembership); mockGameRepository.findOne.mockResolvedValue(null); await expect( service.create("user-1", { groupId: "group-1", gameId: "game-1", title: "周末开黑", startTime: new Date("2024-01-20T19:00:00Z"), maxParticipants: 5, }), ).rejects.toThrow(NotFoundException); }); }); describe("findAll", () => { it("应该成功获取预约列表", async () => { const mockQueryBuilder = { leftJoinAndSelect: jest.fn().mockReturnThis(), where: jest.fn().mockReturnThis(), andWhere: jest.fn().mockReturnThis(), orderBy: jest.fn().mockReturnThis(), skip: jest.fn().mockReturnThis(), take: jest.fn().mockReturnThis(), getManyAndCount: jest.fn().mockResolvedValue([[mockAppointment], 1]), }; mockAppointmentRepository.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); }); }); describe("findOne", () => { it("应该成功获取预约详情", async () => { mockAppointmentRepository.findOne.mockResolvedValue({ ...mockAppointment, group: mockGroup, game: mockGame, creator: mockUser, }); const result = await service.findOne("appointment-1"); expect(result).toHaveProperty("id"); expect(result.id).toBe("appointment-1"); }); it("应该在预约不存在时抛出异常", async () => { mockAppointmentRepository.findOne.mockResolvedValue(null); await expect(service.findOne("appointment-1")).rejects.toThrow( NotFoundException, ); }); }); describe("update", () => { it("应该成功更新预约", async () => { mockAppointmentRepository.findOne .mockResolvedValueOnce(mockAppointment) .mockResolvedValueOnce({ ...mockAppointment, title: "更新后的标题", group: mockGroup, game: mockGame, creator: mockUser, }); mockAppointmentRepository.save.mockResolvedValue({ ...mockAppointment, title: "更新后的标题", }); const result = await service.update("user-1", "appointment-1", { title: "更新后的标题", }); expect(result.title).toBe("更新后的标题"); }); it("应该在非创建者更新时抛出异常", async () => { mockAppointmentRepository.findOne.mockResolvedValue(mockAppointment); await expect( service.update("user-2", "appointment-1", { title: "新标题" }), ).rejects.toThrow(ForbiddenException); }); }); describe("cancel", () => { it("应该成功取消预约", async () => { mockAppointmentRepository.findOne.mockResolvedValue(mockAppointment); mockAppointmentRepository.save.mockResolvedValue({ ...mockAppointment, status: AppointmentStatus.CANCELLED, }); const result = await service.cancel("user-1", "appointment-1"); expect(result).toHaveProperty("message"); }); it("应该在非创建者取消时抛出异常", async () => { mockAppointmentRepository.findOne.mockResolvedValue(mockAppointment); await expect(service.cancel("user-2", "appointment-1")).rejects.toThrow( ForbiddenException, ); }); }); describe("join", () => { it("应该成功加入预约", async () => { mockAppointmentRepository.findOne.mockResolvedValue(mockAppointment); mockGroupMemberRepository.findOne.mockResolvedValue(mockMembership); mockParticipantRepository.findOne.mockResolvedValue(null); mockParticipantRepository.count.mockResolvedValue(3); mockParticipantRepository.create.mockReturnValue(mockParticipant); mockParticipantRepository.save.mockResolvedValue(mockParticipant); const result = await service.join("user-2", "appointment-1"); expect(result).toHaveProperty("message"); expect(mockParticipantRepository.save).toHaveBeenCalled(); }); it("应该在预约已满时抛出异常", async () => { mockAppointmentRepository.findOne.mockResolvedValue(mockAppointment); mockGroupMemberRepository.findOne.mockResolvedValue(mockMembership); mockParticipantRepository.findOne.mockResolvedValue(null); mockParticipantRepository.count.mockResolvedValue(5); await expect(service.join("user-2", "appointment-1")).rejects.toThrow( BadRequestException, ); }); it("应该在已加入时抛出异常", async () => { mockAppointmentRepository.findOne.mockResolvedValue(mockAppointment); mockGroupMemberRepository.findOne.mockResolvedValue(mockMembership); mockParticipantRepository.findOne.mockResolvedValue(mockParticipant); await expect(service.join("user-1", "appointment-1")).rejects.toThrow( BadRequestException, ); }); }); describe("leave", () => { it("应该成功离开预约", async () => { mockAppointmentRepository.findOne.mockResolvedValue(mockAppointment); mockParticipantRepository.findOne.mockResolvedValue(mockParticipant); mockParticipantRepository.remove.mockResolvedValue(mockParticipant); const result = await service.leave("user-1", "appointment-1"); expect(result).toHaveProperty("message"); expect(mockParticipantRepository.remove).toHaveBeenCalled(); }); it("应该在创建者尝试离开时抛出异常", async () => { mockAppointmentRepository.findOne.mockResolvedValue(mockAppointment); await expect(service.leave("user-1", "appointment-1")).rejects.toThrow( BadRequestException, ); }); it("应该在未加入时抛出异常", async () => { mockAppointmentRepository.findOne.mockResolvedValue(mockAppointment); mockParticipantRepository.findOne.mockResolvedValue(null); await expect(service.leave("user-2", "appointment-1")).rejects.toThrow( BadRequestException, ); }); }); });