"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const testing_1 = require("@nestjs/testing"); const typeorm_1 = require("@nestjs/typeorm"); const config_1 = require("@nestjs/config"); const common_1 = require("@nestjs/common"); const invitation_service_1 = require("../services/invitation.service"); const invitation_entity_1 = require("../entities/invitation.entity"); const user_entity_1 = require("../../auth/entities/user.entity"); const tenant_entity_1 = require("../../tenants/entities/tenant.entity"); const email_service_1 = require("../../email/services/email.service"); const invite_user_dto_1 = require("../dto/invite-user.dto"); describe('InvitationService', () => { let service; let invitationRepository; let userRepository; let tenantRepository; let emailService; let configService; const mockUser = { id: 'user-123', tenant_id: 'tenant-123', email: 'inviter@example.com', first_name: 'John', last_name: 'Doe', get fullName() { return 'John Doe'; }, }; const mockTenant = { id: 'tenant-123', name: 'Test Organization', slug: 'test-org', }; const mockInvitation = { id: 'inv-123', tenant_id: 'tenant-123', email: 'invited@example.com', token: 'secure-token-123', status: 'pending', expires_at: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), created_at: new Date(), created_by: 'user-123', metadata: { role: 'member' }, }; beforeEach(async () => { const module = await testing_1.Test.createTestingModule({ providers: [ invitation_service_1.InvitationService, { provide: (0, typeorm_1.getRepositoryToken)(invitation_entity_1.Invitation), useValue: { findOne: jest.fn(), find: jest.fn(), create: jest.fn(), save: jest.fn(), remove: jest.fn(), createQueryBuilder: jest.fn(), }, }, { provide: (0, typeorm_1.getRepositoryToken)(user_entity_1.User), useValue: { findOne: jest.fn(), }, }, { provide: (0, typeorm_1.getRepositoryToken)(tenant_entity_1.Tenant), useValue: { findOne: jest.fn(), }, }, { provide: email_service_1.EmailService, useValue: { sendTemplateEmail: jest.fn().mockResolvedValue({ success: true }), }, }, { provide: config_1.ConfigService, useValue: { get: jest.fn((key, defaultValue) => { const config = { APP_URL: 'http://localhost:3000', APP_NAME: 'Test App', }; return config[key] || defaultValue; }), }, }, ], }).compile(); service = module.get(invitation_service_1.InvitationService); invitationRepository = module.get((0, typeorm_1.getRepositoryToken)(invitation_entity_1.Invitation)); userRepository = module.get((0, typeorm_1.getRepositoryToken)(user_entity_1.User)); tenantRepository = module.get((0, typeorm_1.getRepositoryToken)(tenant_entity_1.Tenant)); emailService = module.get(email_service_1.EmailService); configService = module.get(config_1.ConfigService); const mockQueryBuilder = { update: jest.fn().mockReturnThis(), set: jest.fn().mockReturnThis(), where: jest.fn().mockReturnThis(), andWhere: jest.fn().mockReturnThis(), execute: jest.fn().mockResolvedValue({ affected: 0 }), }; invitationRepository.createQueryBuilder.mockReturnValue(mockQueryBuilder); }); afterEach(() => { jest.clearAllMocks(); }); describe('invite', () => { const inviteDto = { email: 'newuser@example.com', role: invite_user_dto_1.InviteRole.MEMBER, }; it('should create and send an invitation successfully', async () => { const createdInvitation = { ...mockInvitation, email: inviteDto.email.toLowerCase(), metadata: { role: inviteDto.role }, }; userRepository.findOne .mockResolvedValueOnce(null) .mockResolvedValueOnce(mockUser); invitationRepository.findOne.mockResolvedValue(null); tenantRepository.findOne.mockResolvedValue(mockTenant); invitationRepository.create.mockReturnValue(createdInvitation); invitationRepository.save.mockResolvedValue(createdInvitation); const result = await service.invite(inviteDto, 'user-123', 'tenant-123'); expect(result).toHaveProperty('id'); expect(result).toHaveProperty('email', inviteDto.email.toLowerCase()); expect(result).toHaveProperty('role', inviteDto.role); expect(result).toHaveProperty('status', 'pending'); expect(result).toHaveProperty('expires_at'); expect(emailService.sendTemplateEmail).toHaveBeenCalledTimes(1); }); it('should throw ConflictException if email already registered in tenant', async () => { userRepository.findOne.mockResolvedValue(mockUser); await expect(service.invite(inviteDto, 'user-123', 'tenant-123')).rejects.toThrow(common_1.ConflictException); expect(emailService.sendTemplateEmail).not.toHaveBeenCalled(); }); it('should throw ConflictException if pending invitation exists', async () => { userRepository.findOne.mockResolvedValue(null); invitationRepository.findOne.mockResolvedValue(mockInvitation); await expect(service.invite(inviteDto, 'user-123', 'tenant-123')).rejects.toThrow(common_1.ConflictException); expect(emailService.sendTemplateEmail).not.toHaveBeenCalled(); }); it('should throw NotFoundException if inviter not found', async () => { userRepository.findOne .mockResolvedValueOnce(null) .mockResolvedValueOnce(null); invitationRepository.findOne.mockResolvedValue(null); tenantRepository.findOne.mockResolvedValue(mockTenant); await expect(service.invite(inviteDto, 'user-123', 'tenant-123')).rejects.toThrow(common_1.NotFoundException); }); it('should throw NotFoundException if tenant not found', async () => { userRepository.findOne .mockResolvedValueOnce(null) .mockResolvedValueOnce(mockUser); invitationRepository.findOne.mockResolvedValue(null); tenantRepository.findOne.mockResolvedValue(null); await expect(service.invite(inviteDto, 'user-123', 'tenant-123')).rejects.toThrow(common_1.NotFoundException); }); it('should normalize email to lowercase', async () => { const uppercaseEmailDto = { email: 'UPPERCASE@EXAMPLE.COM', role: invite_user_dto_1.InviteRole.ADMIN, }; userRepository.findOne .mockResolvedValueOnce(null) .mockResolvedValueOnce(mockUser); invitationRepository.findOne.mockResolvedValue(null); tenantRepository.findOne.mockResolvedValue(mockTenant); invitationRepository.create.mockReturnValue(mockInvitation); invitationRepository.save.mockResolvedValue(mockInvitation); await service.invite(uppercaseEmailDto, 'user-123', 'tenant-123'); expect(userRepository.findOne).toHaveBeenCalledWith({ where: { email: 'uppercase@example.com', tenant_id: 'tenant-123' }, }); }); }); describe('findAllByTenant', () => { it('should return all invitations for a tenant', async () => { const invitations = [mockInvitation]; invitationRepository.find.mockResolvedValue(invitations); const result = await service.findAllByTenant('tenant-123'); expect(result).toHaveLength(1); expect(result[0]).toHaveProperty('id', 'inv-123'); expect(result[0]).toHaveProperty('email', 'invited@example.com'); }); it('should return empty array when no invitations exist', async () => { invitationRepository.find.mockResolvedValue([]); const result = await service.findAllByTenant('tenant-123'); expect(result).toEqual([]); }); }); describe('resend', () => { it('should resend invitation with new token and expiration', async () => { const originalToken = mockInvitation.token; invitationRepository.findOne.mockResolvedValue({ ...mockInvitation }); userRepository.findOne.mockResolvedValue(mockUser); tenantRepository.findOne.mockResolvedValue(mockTenant); invitationRepository.save.mockImplementation(async (inv) => inv); const result = await service.resend(originalToken, 'user-123', 'tenant-123'); expect(result).toHaveProperty('id'); expect(invitationRepository.save).toHaveBeenCalled(); expect(emailService.sendTemplateEmail).toHaveBeenCalledTimes(1); }); it('should throw NotFoundException if invitation not found', async () => { invitationRepository.findOne.mockResolvedValue(null); await expect(service.resend('invalid-token', 'user-123', 'tenant-123')).rejects.toThrow(common_1.NotFoundException); }); it('should throw BadRequestException if invitation is not pending', async () => { const acceptedInvitation = { ...mockInvitation, status: 'accepted' }; invitationRepository.findOne.mockResolvedValue(acceptedInvitation); await expect(service.resend('token', 'user-123', 'tenant-123')).rejects.toThrow(common_1.BadRequestException); }); }); describe('cancel', () => { it('should cancel a pending invitation', async () => { invitationRepository.findOne.mockResolvedValue(mockInvitation); invitationRepository.remove.mockResolvedValue(mockInvitation); await service.cancel('inv-123', 'tenant-123'); expect(invitationRepository.remove).toHaveBeenCalledWith(mockInvitation); }); it('should throw NotFoundException if invitation not found', async () => { invitationRepository.findOne.mockResolvedValue(null); await expect(service.cancel('invalid-id', 'tenant-123')).rejects.toThrow(common_1.NotFoundException); }); it('should throw BadRequestException if invitation is not pending', async () => { const acceptedInvitation = { ...mockInvitation, status: 'accepted' }; invitationRepository.findOne.mockResolvedValue(acceptedInvitation); await expect(service.cancel('inv-123', 'tenant-123')).rejects.toThrow(common_1.BadRequestException); }); }); describe('findByToken', () => { it('should return invitation by token', async () => { invitationRepository.findOne.mockResolvedValue(mockInvitation); const result = await service.findByToken('secure-token-123'); expect(result).toEqual(mockInvitation); }); it('should return null if invitation not found', async () => { invitationRepository.findOne.mockResolvedValue(null); const result = await service.findByToken('invalid-token'); expect(result).toBeNull(); }); it('should mark invitation as expired if past expiration date', async () => { const expiredInvitation = { ...mockInvitation, expires_at: new Date(Date.now() - 1000), status: 'pending', }; invitationRepository.findOne.mockResolvedValue(expiredInvitation); invitationRepository.save.mockResolvedValue({ ...expiredInvitation, status: 'expired' }); const result = await service.findByToken('token'); expect(result?.status).toBe('expired'); expect(invitationRepository.save).toHaveBeenCalled(); }); }); }); //# sourceMappingURL=invitation.service.spec.js.map