xpeditis2.0/apps/backend/src/domain/entities/csv-booking.entity.spec.ts
David 08787c89c8
Some checks failed
Dev CI / Unit Tests (${{ matrix.app }}) (backend) (push) Blocked by required conditions
Dev CI / Unit Tests (${{ matrix.app }}) (frontend) (push) Blocked by required conditions
Dev CI / Notify Failure (push) Blocked by required conditions
Dev CI / Quality (${{ matrix.app }}) (backend) (push) Has been cancelled
Dev CI / Quality (${{ matrix.app }}) (frontend) (push) Has been cancelled
chore: sync full codebase from cicd branch
Aligns dev with the complete application codebase (cicd branch).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 12:56:16 +02:00

488 lines
13 KiB
TypeScript

import {
CsvBooking,
CsvBookingStatus,
DocumentType,
CsvBookingDocument,
} from './csv-booking.entity';
import { PortCode } from '../value-objects/port-code.vo';
describe('CsvBooking Entity', () => {
// Test data factory
const createValidBooking = (
_overrides?: Partial<ConstructorParameters<typeof CsvBooking>[0]>
): CsvBooking => {
const documents: CsvBookingDocument[] = [
{
id: 'doc-1',
type: DocumentType.BILL_OF_LADING,
fileName: 'bill-of-lading.pdf',
filePath: '/uploads/bill-of-lading.pdf',
mimeType: 'application/pdf',
size: 1024,
uploadedAt: new Date(),
},
{
id: 'doc-2',
type: DocumentType.PACKING_LIST,
fileName: 'packing-list.pdf',
filePath: '/uploads/packing-list.pdf',
mimeType: 'application/pdf',
size: 2048,
uploadedAt: new Date(),
},
{
id: 'doc-3',
type: DocumentType.COMMERCIAL_INVOICE,
fileName: 'invoice.pdf',
filePath: '/uploads/invoice.pdf',
mimeType: 'application/pdf',
size: 3072,
uploadedAt: new Date(),
},
];
return new CsvBooking(
'booking-123',
'user-456',
'org-789',
'SSC Consolidation',
'bookings@sscconsolidation.com',
PortCode.create('NLRTM'),
PortCode.create('USNYC'),
10.5,
1500,
3,
1200.0,
1100.0,
'USD',
15,
'LCL',
CsvBookingStatus.PENDING,
documents,
'token-abc123',
new Date(),
undefined,
'Test booking',
undefined
);
};
describe('Constructor and Validation', () => {
it('should create a valid booking', () => {
const booking = createValidBooking();
expect(booking.id).toBe('booking-123');
expect(booking.userId).toBe('user-456');
expect(booking.organizationId).toBe('org-789');
expect(booking.carrierName).toBe('SSC Consolidation');
expect(booking.carrierEmail).toBe('bookings@sscconsolidation.com');
expect(booking.status).toBe(CsvBookingStatus.PENDING);
expect(booking.documents).toHaveLength(3);
});
it('should throw error if ID is empty', () => {
expect(() => {
const docs: CsvBookingDocument[] = [
{
id: 'doc-1',
type: DocumentType.BILL_OF_LADING,
fileName: 'test.pdf',
filePath: '/test.pdf',
mimeType: 'application/pdf',
size: 1024,
uploadedAt: new Date(),
},
];
new CsvBooking(
'',
'user-456',
'org-789',
'SSC Consolidation',
'bookings@sscconsolidation.com',
PortCode.create('NLRTM'),
PortCode.create('USNYC'),
10.5,
1500,
3,
1200.0,
1100.0,
'USD',
15,
'LCL',
CsvBookingStatus.PENDING,
docs,
'token-abc123',
new Date()
);
}).toThrow('Booking ID is required');
});
it('should throw error if volume is negative', () => {
expect(() => {
const docs: CsvBookingDocument[] = [
{
id: 'doc-1',
type: DocumentType.BILL_OF_LADING,
fileName: 'test.pdf',
filePath: '/test.pdf',
mimeType: 'application/pdf',
size: 1024,
uploadedAt: new Date(),
},
];
new CsvBooking(
'booking-123',
'user-456',
'org-789',
'SSC Consolidation',
'bookings@sscconsolidation.com',
PortCode.create('NLRTM'),
PortCode.create('USNYC'),
-10.5, // Negative volume
1500,
3,
1200.0,
1100.0,
'USD',
15,
'LCL',
CsvBookingStatus.PENDING,
docs,
'token-abc123',
new Date()
);
}).toThrow('Volume must be positive');
});
it('should throw error if email format is invalid', () => {
expect(() => {
const docs: CsvBookingDocument[] = [
{
id: 'doc-1',
type: DocumentType.BILL_OF_LADING,
fileName: 'test.pdf',
filePath: '/test.pdf',
mimeType: 'application/pdf',
size: 1024,
uploadedAt: new Date(),
},
];
new CsvBooking(
'booking-123',
'user-456',
'org-789',
'SSC Consolidation',
'invalid-email', // Invalid email
PortCode.create('NLRTM'),
PortCode.create('USNYC'),
10.5,
1500,
3,
1200.0,
1100.0,
'USD',
15,
'LCL',
CsvBookingStatus.PENDING,
docs,
'token-abc123',
new Date()
);
}).toThrow('Invalid carrier email format');
});
it('should throw error if no documents provided', () => {
expect(() => {
new CsvBooking(
'booking-123',
'user-456',
'org-789',
'SSC Consolidation',
'bookings@sscconsolidation.com',
PortCode.create('NLRTM'),
PortCode.create('USNYC'),
10.5,
1500,
3,
1200.0,
1100.0,
'USD',
15,
'LCL',
CsvBookingStatus.PENDING,
[], // Empty documents
'token-abc123',
new Date()
);
}).toThrow('At least one document is required for booking');
});
});
describe('Accept Method', () => {
it('should accept a pending booking', () => {
const booking = createValidBooking();
expect(booking.status).toBe(CsvBookingStatus.PENDING);
booking.accept();
expect(booking.status).toBe(CsvBookingStatus.ACCEPTED);
expect(booking.respondedAt).toBeDefined();
});
it('should throw error when accepting non-pending booking', () => {
const booking = createValidBooking();
booking.accept(); // First acceptance
expect(() => {
booking.accept(); // Try to accept again
}).toThrow('Cannot accept booking with status ACCEPTED');
});
it('should throw error when accepting expired booking', () => {
const booking = createValidBooking();
// Set requestedAt to 8 days ago (expired)
(booking as any).requestedAt = new Date(Date.now() - 8 * 24 * 60 * 60 * 1000);
expect(booking.isExpired()).toBe(true);
expect(() => {
booking.accept();
}).toThrow('Cannot accept expired booking');
});
});
describe('Reject Method', () => {
it('should reject a pending booking', () => {
const booking = createValidBooking();
expect(booking.status).toBe(CsvBookingStatus.PENDING);
booking.reject('Capacity full');
expect(booking.status).toBe(CsvBookingStatus.REJECTED);
expect(booking.respondedAt).toBeDefined();
expect(booking.rejectionReason).toBe('Capacity full');
});
it('should reject without reason', () => {
const booking = createValidBooking();
booking.reject();
expect(booking.status).toBe(CsvBookingStatus.REJECTED);
expect(booking.rejectionReason).toBeUndefined();
});
it('should throw error when rejecting non-pending booking', () => {
const booking = createValidBooking();
booking.reject(); // First rejection
expect(() => {
booking.reject(); // Try to reject again
}).toThrow('Cannot reject booking with status REJECTED');
});
});
describe('Cancel Method', () => {
it('should cancel a pending booking', () => {
const booking = createValidBooking();
booking.cancel();
expect(booking.status).toBe(CsvBookingStatus.CANCELLED);
expect(booking.respondedAt).toBeDefined();
});
it('should throw error when cancelling accepted booking', () => {
const booking = createValidBooking();
booking.accept();
expect(() => {
booking.cancel();
}).toThrow('Cannot cancel accepted booking');
});
it('should throw error when cancelling rejected booking', () => {
const booking = createValidBooking();
booking.reject();
expect(() => {
booking.cancel();
}).toThrow('Cannot cancel rejected booking');
});
});
describe('Expiration Logic', () => {
it('should not be expired for recent bookings', () => {
const booking = createValidBooking();
expect(booking.isExpired()).toBe(false);
});
it('should be expired after 7 days', () => {
const booking = createValidBooking();
// Set requestedAt to 8 days ago
(booking as any).requestedAt = new Date(Date.now() - 8 * 24 * 60 * 60 * 1000);
expect(booking.isExpired()).toBe(true);
});
it('should not be expired if already accepted', () => {
const booking = createValidBooking();
booking.accept();
// Set requestedAt to 8 days ago
(booking as any).requestedAt = new Date(Date.now() - 8 * 24 * 60 * 60 * 1000);
expect(booking.isExpired()).toBe(false);
});
it('should calculate days until expiration correctly', () => {
const booking = createValidBooking();
const days = booking.getDaysUntilExpiration();
expect(days).toBeGreaterThan(6);
expect(days).toBeLessThanOrEqual(7);
});
it('should return 0 days for accepted bookings', () => {
const booking = createValidBooking();
booking.accept();
expect(booking.getDaysUntilExpiration()).toBe(0);
});
});
describe('Status Check Methods', () => {
it('should correctly identify pending booking', () => {
const booking = createValidBooking();
expect(booking.isPending()).toBe(true);
expect(booking.isAccepted()).toBe(false);
expect(booking.isRejected()).toBe(false);
expect(booking.isCancelled()).toBe(false);
});
it('should correctly identify accepted booking', () => {
const booking = createValidBooking();
booking.accept();
expect(booking.isPending()).toBe(false);
expect(booking.isAccepted()).toBe(true);
expect(booking.isRejected()).toBe(false);
expect(booking.isCancelled()).toBe(false);
});
it('should correctly identify rejected booking', () => {
const booking = createValidBooking();
booking.reject();
expect(booking.isPending()).toBe(false);
expect(booking.isAccepted()).toBe(false);
expect(booking.isRejected()).toBe(true);
expect(booking.isCancelled()).toBe(false);
});
});
describe('Document Methods', () => {
it('should check if document type exists', () => {
const booking = createValidBooking();
expect(booking.hasDocumentType(DocumentType.BILL_OF_LADING)).toBe(true);
expect(booking.hasDocumentType(DocumentType.CERTIFICATE_OF_ORIGIN)).toBe(false);
});
it('should get documents by type', () => {
const booking = createValidBooking();
const billOfLading = booking.getDocumentsByType(DocumentType.BILL_OF_LADING);
expect(billOfLading).toHaveLength(1);
expect(billOfLading[0].fileName).toBe('bill-of-lading.pdf');
});
it('should check if all required documents are present', () => {
const booking = createValidBooking();
expect(booking.hasAllRequiredDocuments()).toBe(true);
});
it('should return false if required documents are missing', () => {
const docs: CsvBookingDocument[] = [
{
id: 'doc-1',
type: DocumentType.BILL_OF_LADING,
fileName: 'bill-of-lading.pdf',
filePath: '/uploads/bill-of-lading.pdf',
mimeType: 'application/pdf',
size: 1024,
uploadedAt: new Date(),
},
];
const booking = new CsvBooking(
'booking-123',
'user-456',
'org-789',
'SSC Consolidation',
'bookings@sscconsolidation.com',
PortCode.create('NLRTM'),
PortCode.create('USNYC'),
10.5,
1500,
3,
1200.0,
1100.0,
'USD',
15,
'LCL',
CsvBookingStatus.PENDING,
docs,
'token-abc123',
new Date()
);
expect(booking.hasAllRequiredDocuments()).toBe(false);
});
});
describe('Helper Methods', () => {
it('should return route description', () => {
const booking = createValidBooking();
expect(booking.getRouteDescription()).toBe('NLRTM → USNYC');
});
it('should return booking summary', () => {
const booking = createValidBooking();
expect(booking.getSummary()).toContain('CSV Booking booking-123');
expect(booking.getSummary()).toContain('SSC Consolidation');
expect(booking.getSummary()).toContain('NLRTM → USNYC');
expect(booking.getSummary()).toContain('PENDING');
});
it('should return price in specified currency', () => {
const booking = createValidBooking();
expect(booking.getPriceInCurrency('USD')).toBe(1200.0);
expect(booking.getPriceInCurrency('EUR')).toBe(1100.0);
});
it('should calculate response time in hours', () => {
const booking = createValidBooking();
// No response yet
expect(booking.getResponseTimeHours()).toBeNull();
// Accept booking
booking.accept();
const responseTime = booking.getResponseTimeHours();
expect(responseTime).toBeGreaterThanOrEqual(0);
expect(responseTime).toBeLessThan(1); // Should be less than 1 hour for this test
});
});
});