template-saas/apps/backend/dist/modules/users/__tests__/invitation.service.spec.js
rckrdmrd 50a821a415
Some checks failed
CI / Backend CI (push) Has been cancelled
CI / Frontend CI (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / CI Summary (push) Has been cancelled
[SIMCO-V38] feat: Actualizar a SIMCO v3.8.0
- HERENCIA-SIMCO.md actualizado con directivas v3.7 y v3.8
- Actualizaciones de configuracion

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-10 08:53:08 -06:00

261 lines
13 KiB
JavaScript

"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