diff --git a/apps/backend/src/application/services/audit.service.spec.ts b/apps/backend/src/application/services/audit.service.spec.ts deleted file mode 100644 index dbf55ca..0000000 --- a/apps/backend/src/application/services/audit.service.spec.ts +++ /dev/null @@ -1,162 +0,0 @@ -/** - * Audit Service Tests - */ - -import { Test, TestingModule } from '@nestjs/testing'; -import { AuditService } from './audit.service'; -import { - AUDIT_LOG_REPOSITORY, - AuditLogRepository, -} from '../../domain/ports/out/audit-log.repository'; -import { AuditAction, AuditStatus, AuditLog } from '../../domain/entities/audit-log.entity'; - -describe('AuditService', () => { - let service: AuditService; - let repository: jest.Mocked; - - beforeEach(async () => { - const mockRepository: jest.Mocked = { - save: jest.fn(), - findById: jest.fn(), - findByFilters: jest.fn(), - count: jest.fn(), - findByResource: jest.fn(), - findRecentByOrganization: jest.fn(), - findByUser: jest.fn(), - }; - - const module: TestingModule = await Test.createTestingModule({ - providers: [ - AuditService, - { - provide: AUDIT_LOG_REPOSITORY, - useValue: mockRepository, - }, - ], - }).compile(); - - service = module.get(AuditService); - repository = module.get(AUDIT_LOG_REPOSITORY); - }); - - describe('log', () => { - it('should create and save an audit log', async () => { - const input = { - action: AuditAction.BOOKING_CREATED, - status: AuditStatus.SUCCESS, - userId: 'user-123', - userEmail: 'user@example.com', - organizationId: 'org-123', - }; - - await service.log(input); - - expect(repository.save).toHaveBeenCalledWith( - expect.objectContaining({ - action: AuditAction.BOOKING_CREATED, - status: AuditStatus.SUCCESS, - userId: 'user-123', - }) - ); - }); - - it('should not throw error if save fails', async () => { - repository.save.mockRejectedValue(new Error('Database error')); - - const input = { - action: AuditAction.BOOKING_CREATED, - status: AuditStatus.SUCCESS, - userId: 'user-123', - userEmail: 'user@example.com', - organizationId: 'org-123', - }; - - await expect(service.log(input)).resolves.not.toThrow(); - }); - }); - - describe('logSuccess', () => { - it('should log a successful action', async () => { - await service.logSuccess( - AuditAction.BOOKING_CREATED, - 'user-123', - 'user@example.com', - 'org-123', - { resourceType: 'booking', resourceId: 'booking-123' } - ); - - expect(repository.save).toHaveBeenCalledWith( - expect.objectContaining({ - status: AuditStatus.SUCCESS, - }) - ); - }); - }); - - describe('logFailure', () => { - it('should log a failed action with error message', async () => { - await service.logFailure( - AuditAction.BOOKING_CREATED, - 'user-123', - 'user@example.com', - 'org-123', - 'Validation failed', - { resourceType: 'booking' } - ); - - expect(repository.save).toHaveBeenCalledWith( - expect.objectContaining({ - status: AuditStatus.FAILURE, - errorMessage: 'Validation failed', - }) - ); - }); - }); - - describe('getAuditLogs', () => { - it('should return audit logs with filters', async () => { - const mockLogs = [ - AuditLog.create({ - id: '1', - action: AuditAction.BOOKING_CREATED, - status: AuditStatus.SUCCESS, - userId: 'user-123', - userEmail: 'user@example.com', - organizationId: 'org-123', - }), - ]; - - repository.findByFilters.mockResolvedValue(mockLogs); - repository.count.mockResolvedValue(1); - - const result = await service.getAuditLogs({ organizationId: 'org-123' }); - - expect(result.logs).toEqual(mockLogs); - expect(result.total).toBe(1); - }); - }); - - describe('getResourceAuditTrail', () => { - it('should return audit trail for a resource', async () => { - const mockLogs = [ - AuditLog.create({ - id: '1', - action: AuditAction.BOOKING_CREATED, - status: AuditStatus.SUCCESS, - userId: 'user-123', - userEmail: 'user@example.com', - organizationId: 'org-123', - resourceType: 'booking', - resourceId: 'booking-123', - }), - ]; - - repository.findByResource.mockResolvedValue(mockLogs); - - const result = await service.getResourceAuditTrail('booking', 'booking-123'); - - expect(result).toEqual(mockLogs); - expect(repository.findByResource).toHaveBeenCalledWith('booking', 'booking-123'); - }); - }); -}); diff --git a/apps/backend/src/application/services/notification.service.spec.ts b/apps/backend/src/application/services/notification.service.spec.ts deleted file mode 100644 index 6bb620b..0000000 --- a/apps/backend/src/application/services/notification.service.spec.ts +++ /dev/null @@ -1,144 +0,0 @@ -/** - * Notification Service Tests - */ - -import { Test, TestingModule } from '@nestjs/testing'; -import { NotificationService } from './notification.service'; -import { - NOTIFICATION_REPOSITORY, - NotificationRepository, -} from '../../domain/ports/out/notification.repository'; -import { - Notification, - NotificationType, - NotificationPriority, -} from '../../domain/entities/notification.entity'; - -describe('NotificationService', () => { - let service: NotificationService; - let repository: jest.Mocked; - - beforeEach(async () => { - const mockRepository: jest.Mocked = { - save: jest.fn(), - findById: jest.fn(), - findByFilters: jest.fn(), - count: jest.fn(), - findUnreadByUser: jest.fn(), - countUnreadByUser: jest.fn(), - findRecentByUser: jest.fn(), - markAsRead: jest.fn(), - markAllAsReadForUser: jest.fn(), - delete: jest.fn(), - deleteOldReadNotifications: jest.fn(), - }; - - const module: TestingModule = await Test.createTestingModule({ - providers: [ - NotificationService, - { - provide: NOTIFICATION_REPOSITORY, - useValue: mockRepository, - }, - ], - }).compile(); - - service = module.get(NotificationService); - repository = module.get(NOTIFICATION_REPOSITORY); - }); - - describe('createNotification', () => { - it('should create and save a notification', async () => { - const input = { - userId: 'user-123', - organizationId: 'org-123', - type: NotificationType.BOOKING_CREATED, - priority: NotificationPriority.MEDIUM, - title: 'Booking Created', - message: 'Your booking has been created', - }; - - const result = await service.createNotification(input); - - expect(repository.save).toHaveBeenCalled(); - expect(result.type).toBe(NotificationType.BOOKING_CREATED); - expect(result.title).toBe('Booking Created'); - }); - }); - - describe('getUnreadNotifications', () => { - it('should return unread notifications for a user', async () => { - const mockNotifications = [ - Notification.create({ - id: '1', - userId: 'user-123', - organizationId: 'org-123', - type: NotificationType.BOOKING_CREATED, - priority: NotificationPriority.MEDIUM, - title: 'Test', - message: 'Test message', - }), - ]; - - repository.findUnreadByUser.mockResolvedValue(mockNotifications); - - const result = await service.getUnreadNotifications('user-123'); - - expect(result).toEqual(mockNotifications); - expect(repository.findUnreadByUser).toHaveBeenCalledWith('user-123', 50); - }); - }); - - describe('getUnreadCount', () => { - it('should return unread count for a user', async () => { - repository.countUnreadByUser.mockResolvedValue(5); - - const result = await service.getUnreadCount('user-123'); - - expect(result).toBe(5); - expect(repository.countUnreadByUser).toHaveBeenCalledWith('user-123'); - }); - }); - - describe('markAsRead', () => { - it('should mark notification as read', async () => { - await service.markAsRead('notification-123'); - - expect(repository.markAsRead).toHaveBeenCalledWith('notification-123'); - }); - }); - - describe('markAllAsRead', () => { - it('should mark all notifications as read for a user', async () => { - await service.markAllAsRead('user-123'); - - expect(repository.markAllAsReadForUser).toHaveBeenCalledWith('user-123'); - }); - }); - - describe('notifyBookingCreated', () => { - it('should create a booking created notification', async () => { - const result = await service.notifyBookingCreated( - 'user-123', - 'org-123', - 'BKG-123', - 'booking-id-123' - ); - - expect(repository.save).toHaveBeenCalled(); - expect(result.type).toBe(NotificationType.BOOKING_CREATED); - expect(result.message).toContain('BKG-123'); - }); - }); - - describe('cleanupOldNotifications', () => { - it('should delete old read notifications', async () => { - repository.deleteOldReadNotifications.mockResolvedValue(10); - - const result = await service.cleanupOldNotifications(30); - - expect(result).toBe(10); - expect(repository.deleteOldReadNotifications).toHaveBeenCalledWith(30); - }); - }); -}); diff --git a/apps/backend/src/application/services/webhook.service.spec.ts b/apps/backend/src/application/services/webhook.service.spec.ts deleted file mode 100644 index 92aff0b..0000000 --- a/apps/backend/src/application/services/webhook.service.spec.ts +++ /dev/null @@ -1,189 +0,0 @@ -/** - * Webhook Service Tests - */ - -import { Test, TestingModule } from '@nestjs/testing'; -import { HttpService } from '@nestjs/axios'; -import { of, throwError } from 'rxjs'; -import { WebhookService } from './webhook.service'; -import { WEBHOOK_REPOSITORY, WebhookRepository } from '../../domain/ports/out/webhook.repository'; -import { Webhook, WebhookEvent, WebhookStatus } from '../../domain/entities/webhook.entity'; - -describe('WebhookService', () => { - let service: WebhookService; - let repository: jest.Mocked; - let httpService: jest.Mocked; - - beforeEach(async () => { - const mockRepository: jest.Mocked = { - save: jest.fn(), - findById: jest.fn(), - findByOrganization: jest.fn(), - findActiveByEvent: jest.fn(), - findByFilters: jest.fn(), - delete: jest.fn(), - countByOrganization: jest.fn(), - }; - - const mockHttpService = { - post: jest.fn(), - }; - - const module: TestingModule = await Test.createTestingModule({ - providers: [ - WebhookService, - { - provide: WEBHOOK_REPOSITORY, - useValue: mockRepository, - }, - { - provide: HttpService, - useValue: mockHttpService, - }, - ], - }).compile(); - - service = module.get(WebhookService); - repository = module.get(WEBHOOK_REPOSITORY); - httpService = module.get(HttpService); - }); - - describe('createWebhook', () => { - it('should create and save a webhook with generated secret', async () => { - const input = { - organizationId: 'org-123', - url: 'https://example.com/webhook', - events: [WebhookEvent.BOOKING_CREATED], - description: 'Test webhook', - }; - - const result = await service.createWebhook(input); - - expect(repository.save).toHaveBeenCalled(); - expect(result.url).toBe('https://example.com/webhook'); - expect(result.secret).toBeDefined(); - expect(result.secret.length).toBeGreaterThan(0); - }); - }); - - describe('getWebhooksByOrganization', () => { - it('should return webhooks for an organization', async () => { - const mockWebhooks = [ - Webhook.create({ - id: '1', - organizationId: 'org-123', - url: 'https://example.com/webhook', - events: [WebhookEvent.BOOKING_CREATED], - secret: 'secret', - }), - ]; - - repository.findByOrganization.mockResolvedValue(mockWebhooks); - - const result = await service.getWebhooksByOrganization('org-123'); - - expect(result).toEqual(mockWebhooks); - }); - }); - - describe('activateWebhook', () => { - it('should activate a webhook', async () => { - const webhook = Webhook.create({ - id: '1', - organizationId: 'org-123', - url: 'https://example.com/webhook', - events: [WebhookEvent.BOOKING_CREATED], - secret: 'secret', - }); - - repository.findById.mockResolvedValue(webhook); - - await service.activateWebhook('1'); - - expect(repository.save).toHaveBeenCalledWith( - expect.objectContaining({ - status: WebhookStatus.ACTIVE, - }) - ); - }); - }); - - describe('triggerWebhooks', () => { - it('should trigger all active webhooks for an event', async () => { - const webhook = Webhook.create({ - id: '1', - organizationId: 'org-123', - url: 'https://example.com/webhook', - events: [WebhookEvent.BOOKING_CREATED], - secret: 'secret', - }); - - repository.findActiveByEvent.mockResolvedValue([webhook]); - httpService.post.mockReturnValue( - of({ status: 200, statusText: 'OK', data: {}, headers: {}, config: {} as any }) - ); - - await service.triggerWebhooks(WebhookEvent.BOOKING_CREATED, 'org-123', { - bookingId: 'booking-123', - }); - - expect(httpService.post).toHaveBeenCalledWith( - 'https://example.com/webhook', - expect.objectContaining({ - event: WebhookEvent.BOOKING_CREATED, - data: { bookingId: 'booking-123' }, - }), - expect.any(Object) - ); - }); - - it('should handle webhook failures and mark as failed after retries', async () => { - const webhook = Webhook.create({ - id: '1', - organizationId: 'org-123', - url: 'https://example.com/webhook', - events: [WebhookEvent.BOOKING_CREATED], - secret: 'secret', - }); - - repository.findActiveByEvent.mockResolvedValue([webhook]); - httpService.post.mockReturnValue(throwError(() => new Error('Network error'))); - - await service.triggerWebhooks(WebhookEvent.BOOKING_CREATED, 'org-123', { - bookingId: 'booking-123', - }); - - // Should be saved as failed after retries - expect(repository.save).toHaveBeenCalledWith( - expect.objectContaining({ - status: WebhookStatus.FAILED, - }) - ); - }, 20000); // Increase timeout to 20 seconds to account for retries - }); - - describe('verifySignature', () => { - it('should verify valid webhook signature', () => { - const payload = { test: 'data' }; - const secret = 'test-secret'; - - // Generate signature using the service's method - const signature = (service as any).generateSignature(payload, secret); - - const isValid = service.verifySignature(payload, signature, secret); - - expect(isValid).toBe(true); - }); - - it('should reject invalid webhook signature', () => { - const payload = { test: 'data' }; - const secret = 'test-secret'; - // Generate a valid-length (64 chars) but incorrect signature - const invalidSignature = '0000000000000000000000000000000000000000000000000000000000000000'; - - const isValid = service.verifySignature(payload, invalidSignature, secret); - - expect(isValid).toBe(false); - }); - }); -}); diff --git a/apps/frontend/package.json b/apps/frontend/package.json index 4426e3f..d14ac53 100644 --- a/apps/frontend/package.json +++ b/apps/frontend/package.json @@ -8,6 +8,7 @@ "build": "next build", "start": "next start", "lint": "next lint", + "type-check": "tsc --noEmit", "test": "jest", "test:watch": "jest --watch", "test:e2e": "playwright test"