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[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 }); }); });