import { RuleEngineService } from './rule-engine.service'; import type { BookingEvaluationInput } from './rule-engine.service'; import type { Rate } from './entities/rate.entity'; describe('RuleEngineService — requested service without a configured surcharge rate', () => { const hazardRate: Rate = { id: 'rate-hazard', rateType: 'HAZARD_SURCHARGE', trigger: 'HAZARDOUS', rateValue: 50, rateUnit: 'PER_CONTAINER', currency: 'USD', status: 'LIVE', containerTypeId: null, cargoTypeId: null, } as Rate; let ratesRepo: { findLiveRates: jest.Mock }; let service: RuleEngineService; beforeEach(() => { ratesRepo = { findLiveRates: jest.fn().mockResolvedValue([]) }; service = new RuleEngineService( { findById: jest.fn().mockResolvedValue(null) } as never, // cargoTypes { findById: jest.fn().mockResolvedValue(null) } as never, // serviceTypes { findActiveByContainerTypeId: jest.fn().mockResolvedValue([]) } as never, // weightLimits { findAllActive: jest.fn().mockResolvedValue([]) } as never, // priorityConfigs ratesRepo as never, { findById: jest.fn().mockResolvedValue(null) } as never, // shippingLines {} as never, // dataSource (unused by evaluate) ); }); const input = (overrides: Partial): BookingEvaluationInput => ({ serviceTypeId: 'svc-1', paymentCurrency: 'USD', tradeDirection: 'IMPORT', isHazardous: false, totalWagons: 1, containers: [], ...overrides, }); it('hard-blocks a hazardous booking when no HAZARDOUS surcharge rate is LIVE', async () => { const result = await service.evaluate(input({ isHazardous: true })); expect(result.hardBlocked).toHaveLength(1); expect(result.hardBlocked[0]).toContain('hazardous'); }); it('passes a hazardous booking when a HAZARDOUS surcharge rate is LIVE', async () => { ratesRepo.findLiveRates.mockResolvedValue([hazardRate]); const result = await service.evaluate(input({ isHazardous: true })); expect(result.hardBlocked).toHaveLength(0); }); it('does not block a non-hazardous booking when no surcharge rates exist', async () => { const result = await service.evaluate(input({})); expect(result.hardBlocked).toHaveLength(0); }); it('hard-blocks on per-container opt-in counts even without the booking-level flag', async () => { const result = await service.evaluate( input({ containers: [ { containerTypeId: 'ct-20', quantity: 2, vgmPerUnitTons: 10, totalVgmTons: 20, reeferQuantity: 1, }, ], }), ); expect(result.hardBlocked).toHaveLength(1); expect(result.hardBlocked[0]).toContain('reefer'); }); });