/** * RateQuote Entity Unit Tests */ import { RateQuote } from './rate-quote.entity'; describe('RateQuote Entity', () => { const validProps = { id: 'quote-1', carrierId: 'carrier-1', carrierName: 'Maersk', carrierCode: 'MAERSK', origin: { code: 'NLRTM', name: 'Rotterdam', country: 'Netherlands', }, destination: { code: 'USNYC', name: 'New York', country: 'United States', }, pricing: { baseFreight: 1000, surcharges: [ { type: 'BAF', description: 'Bunker Adjustment Factor', amount: 100, currency: 'USD' }, ], totalAmount: 1100, currency: 'USD', }, containerType: '40HC', mode: 'FCL' as const, etd: new Date('2025-11-01'), eta: new Date('2025-11-20'), transitDays: 19, route: [ { portCode: 'NLRTM', portName: 'Rotterdam', departure: new Date('2025-11-01'), }, { portCode: 'USNYC', portName: 'New York', arrival: new Date('2025-11-20'), }, ], availability: 50, frequency: 'Weekly', vesselType: 'Container Ship', co2EmissionsKg: 2500, }; describe('create', () => { it('should create rate quote with valid props', () => { const rateQuote = RateQuote.create(validProps); expect(rateQuote.id).toBe('quote-1'); expect(rateQuote.carrierName).toBe('Maersk'); expect(rateQuote.origin.code).toBe('NLRTM'); expect(rateQuote.destination.code).toBe('USNYC'); expect(rateQuote.pricing.totalAmount).toBe(1100); }); it('should set validUntil to 15 minutes from now', () => { const before = new Date(); const rateQuote = RateQuote.create(validProps); const after = new Date(); const expectedValidUntil = new Date(before.getTime() + 15 * 60 * 1000); const diff = Math.abs(rateQuote.validUntil.getTime() - expectedValidUntil.getTime()); // Allow 1 second tolerance for test execution time expect(diff).toBeLessThan(1000); }); it('should throw error for non-positive total price', () => { expect(() => RateQuote.create({ ...validProps, pricing: { ...validProps.pricing, totalAmount: 0 }, }) ).toThrow('Total price must be positive'); }); it('should throw error for non-positive base freight', () => { expect(() => RateQuote.create({ ...validProps, pricing: { ...validProps.pricing, baseFreight: 0 }, }) ).toThrow('Base freight must be positive'); }); it('should throw error if ETA is not after ETD', () => { expect(() => RateQuote.create({ ...validProps, eta: new Date('2025-10-31'), }) ).toThrow('ETA must be after ETD'); }); it('should throw error for non-positive transit days', () => { expect(() => RateQuote.create({ ...validProps, transitDays: 0, }) ).toThrow('Transit days must be positive'); }); it('should throw error for negative availability', () => { expect(() => RateQuote.create({ ...validProps, availability: -1, }) ).toThrow('Availability cannot be negative'); }); it('should throw error if route has less than 2 segments', () => { expect(() => RateQuote.create({ ...validProps, route: [{ portCode: 'NLRTM', portName: 'Rotterdam' }], }) ).toThrow('Route must have at least origin and destination'); }); }); describe('isValid', () => { it('should return true for non-expired quote', () => { const rateQuote = RateQuote.create(validProps); expect(rateQuote.isValid()).toBe(true); }); it('should return false for expired quote', () => { const expiredQuote = RateQuote.fromPersistence({ ...validProps, validUntil: new Date(Date.now() - 1000), // 1 second ago createdAt: new Date(), updatedAt: new Date(), }); expect(expiredQuote.isValid()).toBe(false); }); }); describe('isExpired', () => { it('should return false for non-expired quote', () => { const rateQuote = RateQuote.create(validProps); expect(rateQuote.isExpired()).toBe(false); }); it('should return true for expired quote', () => { const expiredQuote = RateQuote.fromPersistence({ ...validProps, validUntil: new Date(Date.now() - 1000), createdAt: new Date(), updatedAt: new Date(), }); expect(expiredQuote.isExpired()).toBe(true); }); }); describe('hasAvailability', () => { it('should return true when availability > 0', () => { const rateQuote = RateQuote.create(validProps); expect(rateQuote.hasAvailability()).toBe(true); }); it('should return false when availability = 0', () => { const rateQuote = RateQuote.create({ ...validProps, availability: 0 }); expect(rateQuote.hasAvailability()).toBe(false); }); }); describe('getTotalSurcharges', () => { it('should calculate total surcharges', () => { const rateQuote = RateQuote.create({ ...validProps, pricing: { baseFreight: 1000, surcharges: [ { type: 'BAF', description: 'BAF', amount: 100, currency: 'USD' }, { type: 'CAF', description: 'CAF', amount: 50, currency: 'USD' }, ], totalAmount: 1150, currency: 'USD', }, }); expect(rateQuote.getTotalSurcharges()).toBe(150); }); }); describe('getTransshipmentCount', () => { it('should return 0 for direct route', () => { const rateQuote = RateQuote.create(validProps); expect(rateQuote.getTransshipmentCount()).toBe(0); }); it('should return correct count for route with transshipments', () => { const rateQuote = RateQuote.create({ ...validProps, route: [ { portCode: 'NLRTM', portName: 'Rotterdam' }, { portCode: 'ESBCN', portName: 'Barcelona' }, { portCode: 'USNYC', portName: 'New York' }, ], }); expect(rateQuote.getTransshipmentCount()).toBe(1); }); }); describe('isDirectRoute', () => { it('should return true for direct route', () => { const rateQuote = RateQuote.create(validProps); expect(rateQuote.isDirectRoute()).toBe(true); }); it('should return false for route with transshipments', () => { const rateQuote = RateQuote.create({ ...validProps, route: [ { portCode: 'NLRTM', portName: 'Rotterdam' }, { portCode: 'ESBCN', portName: 'Barcelona' }, { portCode: 'USNYC', portName: 'New York' }, ], }); expect(rateQuote.isDirectRoute()).toBe(false); }); }); describe('getPricePerDay', () => { it('should calculate price per day', () => { const rateQuote = RateQuote.create(validProps); const pricePerDay = rateQuote.getPricePerDay(); expect(pricePerDay).toBeCloseTo(1100 / 19, 2); }); }); });