241 lines
7.0 KiB
TypeScript
241 lines
7.0 KiB
TypeScript
/**
|
|
* 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);
|
|
});
|
|
});
|
|
});
|