Files
edr-platform/apps/edr-freight-api/src/contracts/contract-rate-schedule.builder.spec.ts
2026-07-18 09:12:52 +00:00

99 lines
3.2 KiB
TypeScript

import { ContractRateScheduleBuilder } from './contract-rate-schedule.builder';
import { Rate } from '../modules/rule-engine/entities/rate.entity';
/** Minimal Rate factory for the builder unit tests. */
function rate(partial: Partial<Rate>): Rate {
return {
trigger: 'ALWAYS',
appliesTo: 'CONTAINER',
tradeDirection: 'IMPORT',
rateType: 'CONTAINER_IMPORT',
currency: 'USD',
rateValue: 200,
rateUnit: 'PER_CONTAINER',
...partial,
} as Rate;
}
describe('ContractRateScheduleBuilder', () => {
const LIVE: Rate[] = [
rate({
appliesTo: 'CONTAINER',
tradeDirection: 'IMPORT',
rateType: 'CONTAINER_IMPORT',
rateValue: 200,
rateUnit: 'PER_CONTAINER',
originYard: { label: 'Negad' } as never,
destinationYard: { label: 'Mojo Dry Port' } as never,
containerType: { label: '40ft GP' } as never,
}),
rate({
appliesTo: 'CONTAINER',
tradeDirection: 'EXPORT', // wrong direction — must be filtered out for import
rateType: 'CONTAINER_EXPORT',
rateValue: 819,
originYard: { label: 'GMP' } as never,
destinationYard: { label: 'SGTD' } as never,
}),
rate({
appliesTo: 'BULK', // wrong freight — filtered out for a container contract
tradeDirection: 'IMPORT',
rateType: 'BULK_IMPORT',
rateUnit: 'PER_WAGON',
rateValue: 100,
}),
rate({
appliesTo: 'FIRST_MILE',
trigger: 'ALWAYS',
tradeDirection: null,
rateUnit: 'PER_CONTAINER',
rateValue: 50,
}),
rate({
appliesTo: 'OTHER',
trigger: 'CUSTOMS_CLEARANCE',
tradeDirection: null,
rateType: 'CUSTOMS_CLEARANCE',
rateUnit: 'FLAT',
rateValue: 120,
}),
];
const build = (dir: 'IMP' | 'EXP' | 'DOM', freight: 'CON' | 'BULK') => {
const service = { findLiveRatesDetailed: jest.fn().mockResolvedValue(LIVE) };
return new ContractRateScheduleBuilder(service as never).build(dir, freight);
};
it('shows only import container lanes for an import container contract', async () => {
const s = await build('IMP', 'CON');
expect(s.freightLanes).toHaveLength(1);
expect(s.freightLanes[0]).toMatchObject({
route: 'Negad → Mojo Dry Port',
cargo: '40ft GP',
currency: 'USD',
amount: '200',
unit: 'per container',
});
});
it('always lists route-agnostic services and surcharges', async () => {
const s = await build('IMP', 'CON');
expect(s.additionalServices).toHaveLength(1);
expect(s.additionalServices[0].route).toBe('First-mile pickup by truck');
expect(s.surcharges).toHaveLength(1);
expect(s.surcharges[0].route).toBe('Customs clearance service');
});
it('excludes container lanes from a bulk contract', async () => {
const s = await build('IMP', 'BULK');
expect(s.freightLanes).toHaveLength(1);
expect(s.freightLanes[0]).toMatchObject({ amount: '100', unit: 'per wagon' });
});
it('flags an empty schedule when nothing priced matches', async () => {
const service = { findLiveRatesDetailed: jest.fn().mockResolvedValue([]) };
const s = await new ContractRateScheduleBuilder(service as never).build('DOM', 'CON');
expect(s.isEmpty).toBe(true);
});
});