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); }); }); });