310 lines
9.9 KiB
TypeScript
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);
|
|
});
|
|
});
|
|
});
|