- HERENCIA-SIMCO.md actualizado con directivas v3.7 y v3.8 - Actualizaciones de configuracion Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
325 lines
14 KiB
JavaScript
325 lines
14 KiB
JavaScript
"use strict";
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
const testing_1 = require("@nestjs/testing");
|
|
const typeorm_1 = require("@nestjs/typeorm");
|
|
const common_1 = require("@nestjs/common");
|
|
const notifications_service_1 = require("../services/notifications.service");
|
|
const entities_1 = require("../entities");
|
|
const email_1 = require("@modules/email");
|
|
describe('NotificationsService', () => {
|
|
let service;
|
|
let notificationRepository;
|
|
let templateRepository;
|
|
let preferenceRepository;
|
|
let emailService;
|
|
const mockTenantId = '550e8400-e29b-41d4-a716-446655440001';
|
|
const mockUserId = '550e8400-e29b-41d4-a716-446655440000';
|
|
const mockNotification = {
|
|
id: 'notif-001',
|
|
tenant_id: mockTenantId,
|
|
user_id: mockUserId,
|
|
type: 'info',
|
|
channel: 'in_app',
|
|
title: 'Test Notification',
|
|
message: 'This is a test notification',
|
|
is_read: false,
|
|
delivery_status: 'pending',
|
|
created_at: new Date(),
|
|
};
|
|
const mockTemplate = {
|
|
id: 'template-001',
|
|
code: 'welcome_email',
|
|
name: 'Welcome Email',
|
|
channel: 'email',
|
|
subject: 'Welcome {{userName}}!',
|
|
body: 'Hello {{userName}}, welcome to our platform!',
|
|
is_active: true,
|
|
};
|
|
const mockPreferences = {
|
|
id: 'pref-001',
|
|
user_id: mockUserId,
|
|
tenant_id: mockTenantId,
|
|
email_enabled: true,
|
|
push_enabled: true,
|
|
in_app_enabled: true,
|
|
sms_enabled: false,
|
|
marketing_emails: true,
|
|
product_updates: true,
|
|
security_alerts: true,
|
|
};
|
|
beforeEach(async () => {
|
|
const mockNotificationRepo = {
|
|
create: jest.fn(),
|
|
save: jest.fn(),
|
|
findOne: jest.fn(),
|
|
findAndCount: jest.fn(),
|
|
count: jest.fn(),
|
|
update: jest.fn(),
|
|
delete: jest.fn(),
|
|
};
|
|
const mockTemplateRepo = {
|
|
find: jest.fn(),
|
|
findOne: jest.fn(),
|
|
};
|
|
const mockPreferenceRepo = {
|
|
findOne: jest.fn(),
|
|
create: jest.fn(),
|
|
save: jest.fn(),
|
|
};
|
|
const mockEmailService = {
|
|
sendEmail: jest.fn(),
|
|
isEnabled: jest.fn(),
|
|
};
|
|
const module = await testing_1.Test.createTestingModule({
|
|
providers: [
|
|
notifications_service_1.NotificationsService,
|
|
{ provide: (0, typeorm_1.getRepositoryToken)(entities_1.Notification), useValue: mockNotificationRepo },
|
|
{ provide: (0, typeorm_1.getRepositoryToken)(entities_1.NotificationTemplate), useValue: mockTemplateRepo },
|
|
{ provide: (0, typeorm_1.getRepositoryToken)(entities_1.UserNotificationPreference), useValue: mockPreferenceRepo },
|
|
{ provide: email_1.EmailService, useValue: mockEmailService },
|
|
],
|
|
}).compile();
|
|
service = module.get(notifications_service_1.NotificationsService);
|
|
notificationRepository = module.get((0, typeorm_1.getRepositoryToken)(entities_1.Notification));
|
|
templateRepository = module.get((0, typeorm_1.getRepositoryToken)(entities_1.NotificationTemplate));
|
|
preferenceRepository = module.get((0, typeorm_1.getRepositoryToken)(entities_1.UserNotificationPreference));
|
|
emailService = module.get(email_1.EmailService);
|
|
});
|
|
afterEach(() => {
|
|
jest.clearAllMocks();
|
|
});
|
|
describe('create', () => {
|
|
const createDto = {
|
|
userId: mockUserId,
|
|
title: 'Test Notification',
|
|
message: 'This is a test',
|
|
type: 'info',
|
|
channel: 'in_app',
|
|
};
|
|
it('should create an in_app notification and mark as delivered', async () => {
|
|
notificationRepository.create.mockReturnValue(mockNotification);
|
|
notificationRepository.save.mockResolvedValue({
|
|
...mockNotification,
|
|
delivery_status: 'delivered',
|
|
sent_at: new Date(),
|
|
});
|
|
const result = await service.create(createDto, mockTenantId);
|
|
expect(result).toHaveProperty('id');
|
|
expect(notificationRepository.create).toHaveBeenCalled();
|
|
expect(notificationRepository.save).toHaveBeenCalled();
|
|
});
|
|
it('should create an email notification and send via EmailService', async () => {
|
|
const emailDto = {
|
|
...createDto,
|
|
channel: 'email',
|
|
email: 'user@example.com',
|
|
userName: 'Test User',
|
|
};
|
|
const emailNotification = {
|
|
...mockNotification,
|
|
channel: 'email',
|
|
delivery_status: 'pending',
|
|
};
|
|
notificationRepository.create.mockReturnValue(emailNotification);
|
|
notificationRepository.save.mockResolvedValue(emailNotification);
|
|
emailService.sendEmail.mockResolvedValue({
|
|
success: true,
|
|
messageId: 'msg-123',
|
|
provider: 'sendgrid',
|
|
});
|
|
const result = await service.create(emailDto, mockTenantId);
|
|
expect(result).toHaveProperty('id');
|
|
expect(emailService.sendEmail).toHaveBeenCalledWith(expect.objectContaining({
|
|
to: { email: 'user@example.com', name: 'Test User' },
|
|
subject: emailDto.title,
|
|
}));
|
|
});
|
|
it('should handle email delivery failure gracefully', async () => {
|
|
const emailDto = {
|
|
...createDto,
|
|
channel: 'email',
|
|
email: 'user@example.com',
|
|
};
|
|
const emailNotification = {
|
|
...mockNotification,
|
|
channel: 'email',
|
|
delivery_status: 'pending',
|
|
};
|
|
notificationRepository.create.mockReturnValue(emailNotification);
|
|
notificationRepository.save.mockResolvedValue(emailNotification);
|
|
emailService.sendEmail.mockResolvedValue({
|
|
success: false,
|
|
provider: 'sendgrid',
|
|
error: 'API error',
|
|
});
|
|
const result = await service.create(emailDto, mockTenantId);
|
|
expect(result).toHaveProperty('id');
|
|
expect(notificationRepository.save).toHaveBeenLastCalledWith(expect.objectContaining({ delivery_status: 'failed' }));
|
|
});
|
|
});
|
|
describe('sendFromTemplate', () => {
|
|
const templateDto = {
|
|
userId: mockUserId,
|
|
templateCode: 'welcome_email',
|
|
variables: { userName: 'John' },
|
|
};
|
|
it('should send notification from template', async () => {
|
|
templateRepository.findOne.mockResolvedValue(mockTemplate);
|
|
notificationRepository.create.mockReturnValue(mockNotification);
|
|
notificationRepository.save.mockResolvedValue(mockNotification);
|
|
const result = await service.sendFromTemplate(templateDto, mockTenantId);
|
|
expect(result).toHaveProperty('id');
|
|
expect(templateRepository.findOne).toHaveBeenCalledWith({
|
|
where: { code: 'welcome_email', is_active: true },
|
|
});
|
|
});
|
|
it('should throw NotFoundException for invalid template', async () => {
|
|
templateRepository.findOne.mockResolvedValue(null);
|
|
await expect(service.sendFromTemplate(templateDto, mockTenantId)).rejects.toThrow(common_1.NotFoundException);
|
|
});
|
|
});
|
|
describe('findAllForUser', () => {
|
|
it('should return paginated notifications with unread count', async () => {
|
|
const notifications = [mockNotification];
|
|
notificationRepository.findAndCount.mockResolvedValue([notifications, 1]);
|
|
notificationRepository.count.mockResolvedValue(1);
|
|
const result = await service.findAllForUser(mockUserId, mockTenantId, {
|
|
page: 1,
|
|
limit: 20,
|
|
});
|
|
expect(result).toHaveProperty('data');
|
|
expect(result).toHaveProperty('total', 1);
|
|
expect(result).toHaveProperty('unread', 1);
|
|
expect(notificationRepository.findAndCount).toHaveBeenCalled();
|
|
});
|
|
it('should filter unread only when specified', async () => {
|
|
notificationRepository.findAndCount.mockResolvedValue([[], 0]);
|
|
notificationRepository.count.mockResolvedValue(0);
|
|
await service.findAllForUser(mockUserId, mockTenantId, {
|
|
unreadOnly: true,
|
|
});
|
|
expect(notificationRepository.findAndCount).toHaveBeenCalledWith(expect.objectContaining({
|
|
where: expect.objectContaining({ is_read: false }),
|
|
}));
|
|
});
|
|
});
|
|
describe('markAsRead', () => {
|
|
it('should mark notification as read', async () => {
|
|
notificationRepository.findOne.mockResolvedValue(mockNotification);
|
|
notificationRepository.save.mockResolvedValue({
|
|
...mockNotification,
|
|
is_read: true,
|
|
read_at: new Date(),
|
|
});
|
|
const result = await service.markAsRead(mockNotification.id, mockUserId, mockTenantId);
|
|
expect(result.is_read).toBe(true);
|
|
expect(result.read_at).toBeDefined();
|
|
});
|
|
it('should throw NotFoundException for invalid notification', async () => {
|
|
notificationRepository.findOne.mockResolvedValue(null);
|
|
await expect(service.markAsRead('invalid-id', mockUserId, mockTenantId)).rejects.toThrow(common_1.NotFoundException);
|
|
});
|
|
});
|
|
describe('markAllAsRead', () => {
|
|
it('should mark all notifications as read', async () => {
|
|
notificationRepository.update.mockResolvedValue({ affected: 5 });
|
|
const result = await service.markAllAsRead(mockUserId, mockTenantId);
|
|
expect(result).toBe(5);
|
|
expect(notificationRepository.update).toHaveBeenCalledWith({ user_id: mockUserId, tenant_id: mockTenantId, is_read: false }, expect.objectContaining({ is_read: true }));
|
|
});
|
|
});
|
|
describe('delete', () => {
|
|
it('should delete notification successfully', async () => {
|
|
notificationRepository.delete.mockResolvedValue({ affected: 1 });
|
|
await service.delete(mockNotification.id, mockUserId, mockTenantId);
|
|
expect(notificationRepository.delete).toHaveBeenCalledWith({
|
|
id: mockNotification.id,
|
|
user_id: mockUserId,
|
|
tenant_id: mockTenantId,
|
|
});
|
|
});
|
|
it('should throw NotFoundException for invalid notification', async () => {
|
|
notificationRepository.delete.mockResolvedValue({ affected: 0 });
|
|
await expect(service.delete('invalid-id', mockUserId, mockTenantId)).rejects.toThrow(common_1.NotFoundException);
|
|
});
|
|
});
|
|
describe('getUnreadCount', () => {
|
|
it('should return unread notification count', async () => {
|
|
notificationRepository.count.mockResolvedValue(3);
|
|
const result = await service.getUnreadCount(mockUserId, mockTenantId);
|
|
expect(result).toBe(3);
|
|
expect(notificationRepository.count).toHaveBeenCalledWith({
|
|
where: {
|
|
user_id: mockUserId,
|
|
tenant_id: mockTenantId,
|
|
channel: 'in_app',
|
|
is_read: false,
|
|
},
|
|
});
|
|
});
|
|
});
|
|
describe('findAllTemplates', () => {
|
|
it('should return all active templates', async () => {
|
|
templateRepository.find.mockResolvedValue([mockTemplate]);
|
|
const result = await service.findAllTemplates();
|
|
expect(result).toHaveLength(1);
|
|
expect(templateRepository.find).toHaveBeenCalledWith({
|
|
where: { is_active: true },
|
|
order: { category: 'ASC', code: 'ASC' },
|
|
});
|
|
});
|
|
});
|
|
describe('findTemplateByCode', () => {
|
|
it('should return template by code', async () => {
|
|
templateRepository.findOne.mockResolvedValue(mockTemplate);
|
|
const result = await service.findTemplateByCode('welcome_email');
|
|
expect(result).toHaveProperty('code', 'welcome_email');
|
|
});
|
|
it('should throw NotFoundException for invalid template', async () => {
|
|
templateRepository.findOne.mockResolvedValue(null);
|
|
await expect(service.findTemplateByCode('invalid_code')).rejects.toThrow(common_1.NotFoundException);
|
|
});
|
|
});
|
|
describe('getPreferences', () => {
|
|
it('should return existing preferences', async () => {
|
|
preferenceRepository.findOne.mockResolvedValue(mockPreferences);
|
|
const result = await service.getPreferences(mockUserId, mockTenantId);
|
|
expect(result).toHaveProperty('email_enabled', true);
|
|
});
|
|
it('should create default preferences if not exist', async () => {
|
|
preferenceRepository.findOne.mockResolvedValue(null);
|
|
preferenceRepository.create.mockReturnValue(mockPreferences);
|
|
preferenceRepository.save.mockResolvedValue(mockPreferences);
|
|
const result = await service.getPreferences(mockUserId, mockTenantId);
|
|
expect(result).toHaveProperty('email_enabled', true);
|
|
expect(preferenceRepository.create).toHaveBeenCalled();
|
|
expect(preferenceRepository.save).toHaveBeenCalled();
|
|
});
|
|
});
|
|
describe('updatePreferences', () => {
|
|
const updateDto = {
|
|
emailEnabled: false,
|
|
pushEnabled: false,
|
|
};
|
|
it('should update preferences', async () => {
|
|
preferenceRepository.findOne.mockResolvedValue(mockPreferences);
|
|
preferenceRepository.save.mockResolvedValue({
|
|
...mockPreferences,
|
|
email_enabled: false,
|
|
push_enabled: false,
|
|
});
|
|
const result = await service.updatePreferences(mockUserId, mockTenantId, updateDto);
|
|
expect(result.email_enabled).toBe(false);
|
|
expect(result.push_enabled).toBe(false);
|
|
});
|
|
});
|
|
describe('cleanupOldNotifications', () => {
|
|
it('should delete old read notifications', async () => {
|
|
notificationRepository.delete.mockResolvedValue({ affected: 10 });
|
|
const result = await service.cleanupOldNotifications(30);
|
|
expect(result).toBe(10);
|
|
expect(notificationRepository.delete).toHaveBeenCalled();
|
|
});
|
|
});
|
|
});
|
|
//# sourceMappingURL=notifications.service.spec.js.map
|