import { ConflictException, NotFoundException } from '@nestjs/common'; import { WarehouseYardsService } from './warehouse-yards.service'; import { WarehouseZonesService } from './warehouse-zones.service'; /** * Soft-deleting a parent would leave its children pointing at a row every * joining query drops, so both removes refuse while children exist. */ function makeYardsService(yard: unknown) { const yardsRepository = { findById: jest.fn().mockResolvedValue(yard), softDelete: jest.fn().mockResolvedValue(undefined), }; const service = Object.create(WarehouseYardsService.prototype) as Record; service.yardsRepository = yardsRepository; return { service: service as unknown as WarehouseYardsService, yardsRepository }; } function makeZonesService(zone: unknown, heldInventory: number, configuredStacks = 0) { const zonesRepository = { findById: jest.fn().mockResolvedValue(zone), softDelete: jest.fn().mockResolvedValue(undefined), }; const inventoryRepository = { findAndCount: jest.fn().mockResolvedValue([[], heldInventory]), }; const service = Object.create(WarehouseZonesService.prototype) as Record; service.zonesRepository = zonesRepository; service.inventoryRepository = inventoryRepository; service.dataSource = { query: jest.fn().mockResolvedValue([{ count: configuredStacks }]) }; return { service: service as unknown as WarehouseZonesService, zonesRepository }; } describe('WarehouseYardsService.remove', () => { it('soft-deletes a yard with no zones', async () => { const { service, yardsRepository } = makeYardsService({ id: 'y1', code: 'CY-A', zones: [] }); await expect(service.remove('y1')).resolves.toEqual({ id: 'y1', deleted: true }); expect(yardsRepository.softDelete).toHaveBeenCalledWith('y1'); }); it('refuses while zones remain', async () => { const { service, yardsRepository } = makeYardsService({ id: 'y1', code: 'CY-A', zones: [{ id: 'z1' }], }); await expect(service.remove('y1')).rejects.toBeInstanceOf(ConflictException); expect(yardsRepository.softDelete).not.toHaveBeenCalled(); }); it('404s on an unknown yard', async () => { const { service } = makeYardsService(null); await expect(service.remove('nope')).rejects.toBeInstanceOf(NotFoundException); }); }); describe('WarehouseZonesService.remove', () => { it('soft-deletes an empty zone', async () => { const { service, zonesRepository } = makeZonesService({ id: 'z1', code: 'ZA' }, 0); await expect(service.remove('z1')).resolves.toEqual({ id: 'z1', deleted: true }); expect(zonesRepository.softDelete).toHaveBeenCalledWith('z1'); }); it('refuses while inventory sits in it', async () => { const { service, zonesRepository } = makeZonesService({ id: 'z1', code: 'ZA' }, 16); await expect(service.remove('z1')).rejects.toBeInstanceOf(ConflictException); expect(zonesRepository.softDelete).not.toHaveBeenCalled(); }); it('refuses while ground stacks are still configured in it', async () => { const { service, zonesRepository } = makeZonesService({ id: 'z1', code: 'ZA' }, 0, 20); await expect(service.remove('z1')).rejects.toThrow(/still has 20 configured stack\(s\)/); expect(zonesRepository.softDelete).not.toHaveBeenCalled(); }); it('404s on an unknown zone', async () => { const { service } = makeZonesService(null, 0); await expect(service.remove('nope')).rejects.toBeInstanceOf(NotFoundException); }); });