- HERENCIA-SIMCO.md actualizado con directivas v3.7 y v3.8 - Actualizaciones de configuracion Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
261 lines
13 KiB
JavaScript
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
|