xpeditis2.0/apps/backend/src/application/services/carrier-dashboard.service.spec.ts
2025-12-05 13:55:40 +01:00

310 lines
9.9 KiB
TypeScript

/**
* 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<CarrierProfileRepository>;
let carrierActivityRepository: jest.Mocked<CarrierActivityRepository>;
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>(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);
});
});
});