/** * CarrierDashboardService Unit Tests */ import { Test, TestingModule } from '@nestjs/testing'; import { NotFoundException, ForbiddenException } from '@nestjs/common'; import { getRepositoryToken } from '@nestjs/typeorm'; import { CarrierDashboardService } from './carrier-dashboard.service'; import { CarrierProfileRepository } from '@infrastructure/persistence/typeorm/repositories/carrier-profile.repository'; import { CarrierActivityRepository } from '@infrastructure/persistence/typeorm/repositories/carrier-activity.repository'; import { CsvBookingOrmEntity } from '@infrastructure/persistence/typeorm/entities/csv-booking.orm-entity'; describe('CarrierDashboardService', () => { let service: CarrierDashboardService; let carrierProfileRepository: jest.Mocked; let carrierActivityRepository: jest.Mocked; let csvBookingRepository: any; const mockCarrierProfile = { id: 'carrier-1', userId: 'user-1', organizationId: 'org-1', companyName: 'Test Carrier', notificationEmail: 'carrier@test.com', isActive: true, isVerified: true, acceptanceRate: 85.5, totalRevenueUsd: 50000, totalRevenueEur: 45000, totalBookingsAccepted: 10, totalBookingsRejected: 2, }; const mockBooking = { id: 'booking-1', carrierId: 'carrier-1', carrierName: 'Test Carrier', carrierEmail: 'carrier@test.com', origin: 'Rotterdam', destination: 'New York', volumeCBM: 10, weightKG: 1000, palletCount: 5, priceUSD: 1500, priceEUR: 1350, primaryCurrency: 'USD', transitDays: 15, containerType: '40HC', status: 'PENDING', documents: [ { id: 'doc-1', fileName: 'invoice.pdf', type: 'INVOICE', url: 'https://example.com/doc.pdf', }, ], confirmationToken: 'test-token', requestedAt: new Date(), carrierViewedAt: null, carrierAcceptedAt: null, carrierRejectedAt: null, createdAt: new Date(), updatedAt: new Date(), }; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ CarrierDashboardService, { provide: CarrierProfileRepository, useValue: { findById: jest.fn(), }, }, { provide: CarrierActivityRepository, useValue: { findByCarrierId: jest.fn(), create: jest.fn(), }, }, { provide: getRepositoryToken(CsvBookingOrmEntity), useValue: { find: jest.fn(), findOne: jest.fn(), save: jest.fn(), createQueryBuilder: jest.fn(), }, }, ], }).compile(); service = module.get(CarrierDashboardService); carrierProfileRepository = module.get(CarrierProfileRepository); carrierActivityRepository = module.get(CarrierActivityRepository); csvBookingRepository = module.get(getRepositoryToken(CsvBookingOrmEntity)); }); describe('getCarrierStats', () => { it('should return carrier dashboard statistics', async () => { carrierProfileRepository.findById.mockResolvedValue(mockCarrierProfile as any); csvBookingRepository.find.mockResolvedValue([ { ...mockBooking, status: 'PENDING' }, { ...mockBooking, status: 'ACCEPTED' }, { ...mockBooking, status: 'REJECTED' }, ]); carrierActivityRepository.findByCarrierId.mockResolvedValue([ { id: 'activity-1', activityType: 'BOOKING_ACCEPTED', description: 'Booking accepted', createdAt: new Date(), bookingId: 'booking-1', }, ] as any); const result = await service.getCarrierStats('carrier-1'); expect(result).toEqual({ totalBookings: 3, pendingBookings: 1, acceptedBookings: 1, rejectedBookings: 1, acceptanceRate: 85.5, totalRevenue: { usd: 50000, eur: 45000, }, recentActivities: [ { id: 'activity-1', type: 'BOOKING_ACCEPTED', description: 'Booking accepted', createdAt: expect.any(Date), bookingId: 'booking-1', }, ], }); }); it('should throw NotFoundException for non-existent carrier', async () => { carrierProfileRepository.findById.mockResolvedValue(null); await expect(service.getCarrierStats('non-existent')).rejects.toThrow( NotFoundException ); }); }); describe('getCarrierBookings', () => { it('should return paginated bookings for carrier', async () => { carrierProfileRepository.findById.mockResolvedValue(mockCarrierProfile as any); const queryBuilder = { where: jest.fn().mockReturnThis(), andWhere: jest.fn().mockReturnThis(), getCount: jest.fn().mockResolvedValue(15), orderBy: jest.fn().mockReturnThis(), skip: jest.fn().mockReturnThis(), take: jest.fn().mockReturnThis(), getMany: jest.fn().mockResolvedValue([mockBooking]), }; csvBookingRepository.createQueryBuilder.mockReturnValue(queryBuilder); const result = await service.getCarrierBookings('carrier-1', 1, 10); expect(result).toEqual({ data: [ { id: 'booking-1', origin: 'Rotterdam', destination: 'New York', status: 'PENDING', priceUsd: 1500, priceEur: 1350, primaryCurrency: 'USD', requestedAt: expect.any(Date), carrierViewedAt: null, documentsCount: 1, volumeCBM: 10, weightKG: 1000, palletCount: 5, transitDays: 15, containerType: '40HC', }, ], total: 15, page: 1, limit: 10, }); }); it('should filter bookings by status', async () => { carrierProfileRepository.findById.mockResolvedValue(mockCarrierProfile as any); const queryBuilder = { where: jest.fn().mockReturnThis(), andWhere: jest.fn().mockReturnThis(), getCount: jest.fn().mockResolvedValue(5), orderBy: jest.fn().mockReturnThis(), skip: jest.fn().mockReturnThis(), take: jest.fn().mockReturnThis(), getMany: jest.fn().mockResolvedValue([mockBooking]), }; csvBookingRepository.createQueryBuilder.mockReturnValue(queryBuilder); await service.getCarrierBookings('carrier-1', 1, 10, 'ACCEPTED'); expect(queryBuilder.andWhere).toHaveBeenCalledWith('booking.status = :status', { status: 'ACCEPTED', }); }); it('should throw NotFoundException for non-existent carrier', async () => { carrierProfileRepository.findById.mockResolvedValue(null); await expect( service.getCarrierBookings('non-existent', 1, 10) ).rejects.toThrow(NotFoundException); }); }); describe('getBookingDetails', () => { it('should return booking details and mark as viewed', async () => { const booking = { ...mockBooking, carrierViewedAt: null }; csvBookingRepository.findOne.mockResolvedValue(booking); csvBookingRepository.save.mockResolvedValue({ ...booking, carrierViewedAt: new Date() }); carrierActivityRepository.create.mockResolvedValue({} as any); const result = await service.getBookingDetails('carrier-1', 'booking-1'); expect(result.id).toBe('booking-1'); expect(result.origin).toBe('Rotterdam'); expect(csvBookingRepository.save).toHaveBeenCalled(); expect(carrierActivityRepository.create).toHaveBeenCalled(); }); it('should not update view if already viewed', async () => { const booking = { ...mockBooking, carrierViewedAt: new Date() }; csvBookingRepository.findOne.mockResolvedValue(booking); await service.getBookingDetails('carrier-1', 'booking-1'); expect(csvBookingRepository.save).not.toHaveBeenCalled(); }); it('should throw NotFoundException for non-existent booking', async () => { csvBookingRepository.findOne.mockResolvedValue(null); await expect( service.getBookingDetails('carrier-1', 'non-existent') ).rejects.toThrow(NotFoundException); }); it('should throw ForbiddenException for unauthorized access', async () => { csvBookingRepository.findOne.mockResolvedValue(mockBooking); await expect( service.getBookingDetails('other-carrier', 'booking-1') ).rejects.toThrow(ForbiddenException); }); }); describe('downloadDocument', () => { it('should allow authorized carrier to download document', async () => { csvBookingRepository.findOne.mockResolvedValue(mockBooking); carrierActivityRepository.create.mockResolvedValue({} as any); const result = await service.downloadDocument('carrier-1', 'booking-1', 'doc-1'); expect(result.document).toEqual({ id: 'doc-1', fileName: 'invoice.pdf', type: 'INVOICE', url: 'https://example.com/doc.pdf', }); expect(carrierActivityRepository.create).toHaveBeenCalled(); }); it('should throw ForbiddenException for unauthorized carrier', async () => { csvBookingRepository.findOne.mockResolvedValue(mockBooking); await expect( service.downloadDocument('other-carrier', 'booking-1', 'doc-1') ).rejects.toThrow(ForbiddenException); }); it('should throw NotFoundException for non-existent booking', async () => { csvBookingRepository.findOne.mockResolvedValue(null); await expect( service.downloadDocument('carrier-1', 'booking-1', 'doc-1') ).rejects.toThrow(ForbiddenException); }); it('should throw NotFoundException for non-existent document', async () => { csvBookingRepository.findOne.mockResolvedValue(mockBooking); await expect( service.downloadDocument('carrier-1', 'booking-1', 'non-existent-doc') ).rejects.toThrow(NotFoundException); }); }); });