import { BadRequestException } from '@nestjs/common'; import { ContractTemplatesRepository } from './contract-templates.repository'; import { ContractTemplatesService } from './contract-templates.service'; import { bulkTemplateCode, bulkTemplateDirectionFor, } from './entities/contract-template.entity'; describe('bulkTemplateDirectionFor', () => { it('maps the contract-side DOMESTIC onto the template-side INTERCITY', () => { expect(bulkTemplateDirectionFor('DOMESTIC')).toBe('INTERCITY'); expect(bulkTemplateDirectionFor('INTERCITY')).toBe('INTERCITY'); expect(bulkTemplateDirectionFor(null)).toBe('INTERCITY'); expect(bulkTemplateDirectionFor(undefined)).toBe('INTERCITY'); expect(bulkTemplateDirectionFor('IMPORT')).toBe('IMPORT'); expect(bulkTemplateDirectionFor('EXPORT')).toBe('EXPORT'); }); }); describe('bulkTemplateCode', () => { it('gives each direction/customs combination its own code', () => { expect(bulkTemplateCode('STEEL', 'IMPORT', true)).toBe('BULK_IMPORT_STEEL_CUSTOMS'); expect(bulkTemplateCode('STEEL', 'IMPORT', false)).toBe( 'BULK_IMPORT_STEEL_NO_CUSTOMS', ); expect(bulkTemplateCode('STEEL', 'EXPORT', true)).toBe('BULK_EXPORT_STEEL_CUSTOMS'); expect(bulkTemplateCode('STEEL', 'EXPORT', false)).toBe( 'BULK_EXPORT_STEEL_NO_CUSTOMS', ); }); it('leaves intercity unsuffixed — it crosses no border', () => { expect(bulkTemplateCode('STEEL', 'INTERCITY', null)).toBe('BULK_INTERCITY_STEEL'); }); it('gives the Ethiopian-customs-only variant its own suffix', () => { expect(bulkTemplateCode('STEEL', 'IMPORT', true, true)).toBe( 'BULK_IMPORT_STEEL_ETHIOPIAN_CUSTOMS', ); // The flag is meaningless without customs clearing. expect(bulkTemplateCode('STEEL', 'IMPORT', false, true)).toBe( 'BULK_IMPORT_STEEL_NO_CUSTOMS', ); }); it('produces 7 distinct codes per cargo type', () => { const codes = [ bulkTemplateCode('STEEL', 'IMPORT', true), bulkTemplateCode('STEEL', 'IMPORT', true, true), bulkTemplateCode('STEEL', 'IMPORT', false), bulkTemplateCode('STEEL', 'EXPORT', true), bulkTemplateCode('STEEL', 'EXPORT', true, true), bulkTemplateCode('STEEL', 'EXPORT', false), bulkTemplateCode('STEEL', 'INTERCITY', null), ]; expect(new Set(codes).size).toBe(7); }); }); describe('ContractTemplatesService bulk create/resolve', () => { function build() { const repository = { findCargoType: jest.fn(() => Promise.resolve({ id: 'cargo-1', code: 'STEEL', cargoTypeName: 'Steel', hasContractTemplate: true, }), ), findByCargoCombo: jest.fn(() => Promise.resolve(null)), findActiveBulkTemplate: jest.fn(() => Promise.resolve(null)), findByCode: jest.fn(() => Promise.resolve(null)), saveTemplate: jest.fn((template) => Promise.resolve(template)), } as unknown as ContractTemplatesRepository; return { repository, service: new ContractTemplatesService(repository, {} as never), }; } it('stores direction and customs on an import template', async () => { const { service } = build(); const created = await service.create({ cargoTypeId: 'cargo-1', tradeDirection: 'EXPORT', withCustoms: true, }); expect(created.code).toBe('BULK_EXPORT_STEEL_CUSTOMS'); expect(created.tradeDirection).toBe('EXPORT'); expect(created.withCustoms).toBe(true); expect(created.documentTitle).toBe( 'Steel Transportation and Customs Clearance Services', ); }); it('stores a null customs flag for intercity', async () => { const { service } = build(); const created = await service.create({ cargoTypeId: 'cargo-1', tradeDirection: 'INTERCITY', }); expect(created.code).toBe('BULK_INTERCITY_STEEL'); expect(created.withCustoms).toBeNull(); expect(created.documentTitle).toBe('Steel Transportation Services'); }); it('rejects a customs flag on intercity', async () => { const { service } = build(); await expect( service.create({ cargoTypeId: 'cargo-1', tradeDirection: 'INTERCITY', withCustoms: false, }), ).rejects.toBeInstanceOf(BadRequestException); }); it('requires a customs flag on import and export', async () => { const { service } = build(); await expect( service.create({ cargoTypeId: 'cargo-1', tradeDirection: 'IMPORT' }), ).rejects.toBeInstanceOf(BadRequestException); }); it('creates the Ethiopian-customs-only variant alongside the full-customs one', async () => { const { service } = build(); const created = await service.create({ cargoTypeId: 'cargo-1', tradeDirection: 'IMPORT', withCustoms: true, ethiopianCustomsOnly: true, }); expect(created.code).toBe('BULK_IMPORT_STEEL_ETHIOPIAN_CUSTOMS'); expect(created.ethiopianCustomsOnly).toBe(true); expect(created.documentTitle).toBe( 'Steel Transportation and Ethiopian Customs Clearance Services', ); }); it('rejects Ethiopian-customs-only without customs clearing', async () => { const { service } = build(); await expect( service.create({ cargoTypeId: 'cargo-1', tradeDirection: 'IMPORT', withCustoms: false, ethiopianCustomsOnly: true, }), ).rejects.toBeInstanceOf(BadRequestException); }); it('resolves a domestic bulk contract to the intercity template, ignoring its customs flag', async () => { const { repository, service } = build(); await service.findActiveForContract('DOMESTIC', 'BULK', true, 'cargo-1'); expect(repository.findActiveBulkTemplate).toHaveBeenCalledWith( 'cargo-1', 'INTERCITY', null, false, ); }); it('resolves an import bulk contract on direction and customs', async () => { const { repository, service } = build(); await service.findActiveForContract('IMPORT', 'BULK', false, 'cargo-1'); expect(repository.findActiveBulkTemplate).toHaveBeenCalledWith( 'cargo-1', 'IMPORT', false, false, ); }); it('resolves an Ethiopian-customs-only contract to the Ethiopian variant', async () => { const { repository, service } = build(); await service.findActiveForContract('IMPORT', 'BULK', true, 'cargo-1', true); expect(repository.findActiveBulkTemplate).toHaveBeenCalledWith( 'cargo-1', 'IMPORT', true, true, ); }); });