import { AddItemCommand } from '../add-item.command'; import { ItemEntity } from '../../../domain/entities/item.entity'; // Mock implementations const mockItemRepository = { save: jest.fn(), }; const mockOrderService = { orderItem: jest.fn(), }; const mockTimeProvider = { now: jest.fn(), }; const mockExpirationSpec = { isExpired: jest.fn(), }; const mockLogger = { log: jest.fn(), error: jest.fn(), warn: jest.fn(), debug: jest.fn(), }; describe('AddItemCommand', () => { let addItemCommand: AddItemCommand; const MOCKED_NOW = '2023-01-01T12:00:00Z'; const NOT_EXPIRED_DATE = '2023-01-02T12:00:00Z'; const EXPIRED_DATE = '2022-12-31T12:00:00Z'; const ITEM_NAME = 'Test Item'; const ORDER_URL = 'https://example.com/order'; const USER_ID = '550e8400-e29b-41d4-a716-446655440001'; beforeEach(() => { jest.clearAllMocks(); addItemCommand = new AddItemCommand( mockItemRepository as any, mockOrderService as any, mockTimeProvider as any, mockLogger as any, mockExpirationSpec as any, ); mockTimeProvider.now.mockReturnValue(new Date(MOCKED_NOW)); }); describe('execute', () => { describe('when item is not expired', () => { beforeEach(() => { mockExpirationSpec.isExpired.mockReturnValue(false); }); it('should save item to repository', async () => { await addItemCommand.execute(ITEM_NAME, NOT_EXPIRED_DATE, ORDER_URL, USER_ID); expect(mockItemRepository.save).toHaveBeenCalledTimes(1); expect(mockItemRepository.save).toHaveBeenCalledWith(expect.any(ItemEntity)); }); it('should not call order service', async () => { await addItemCommand.execute(ITEM_NAME, NOT_EXPIRED_DATE, ORDER_URL, USER_ID); expect(mockOrderService.orderItem).not.toHaveBeenCalled(); }); it('should return item ID', async () => { const result = await addItemCommand.execute(ITEM_NAME, NOT_EXPIRED_DATE, ORDER_URL, USER_ID); expect(result).toBeTruthy(); expect(typeof result).toBe('string'); expect(result).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i); }); it('should validate expiration with ItemExpirationSpec', async () => { await addItemCommand.execute(ITEM_NAME, NOT_EXPIRED_DATE, ORDER_URL, USER_ID); expect(mockExpirationSpec.isExpired).toHaveBeenCalledTimes(1); expect(mockExpirationSpec.isExpired).toHaveBeenCalledWith( expect.any(ItemEntity), new Date(MOCKED_NOW) ); }); }); describe('when item is expired', () => { beforeEach(() => { mockExpirationSpec.isExpired.mockReturnValue(true); }); it('should call order service', async () => { await addItemCommand.execute(ITEM_NAME, EXPIRED_DATE, ORDER_URL, USER_ID); expect(mockOrderService.orderItem).toHaveBeenCalledTimes(1); expect(mockOrderService.orderItem).toHaveBeenCalledWith(expect.any(ItemEntity)); }); it('should not save item to repository', async () => { await addItemCommand.execute(ITEM_NAME, EXPIRED_DATE, ORDER_URL, USER_ID); expect(mockItemRepository.save).not.toHaveBeenCalled(); }); it('should return item ID', async () => { const result = await addItemCommand.execute(ITEM_NAME, EXPIRED_DATE, ORDER_URL, USER_ID); expect(result).toBeTruthy(); expect(typeof result).toBe('string'); }); it('should handle order service failure gracefully', async () => { mockOrderService.orderItem.mockRejectedValue(new Error('Order service failed')); const result = await addItemCommand.execute(ITEM_NAME, EXPIRED_DATE, ORDER_URL, USER_ID); expect(result).toBeTruthy(); expect(typeof result).toBe('string'); expect(result).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i); expect(mockOrderService.orderItem).toHaveBeenCalledTimes(1); expect(mockItemRepository.save).not.toHaveBeenCalled(); }); }); describe('input validation', () => { it('should throw error when name is empty', async () => { await expect( addItemCommand.execute('', NOT_EXPIRED_DATE, ORDER_URL, USER_ID) ).rejects.toThrow('Item name cannot be empty'); }); it('should throw error when name is only whitespace', async () => { await expect( addItemCommand.execute(' ', NOT_EXPIRED_DATE, ORDER_URL, USER_ID) ).rejects.toThrow('Item name cannot be empty'); }); it('should throw error when expirationDate is empty', async () => { await expect( addItemCommand.execute(ITEM_NAME, '', ORDER_URL, USER_ID) ).rejects.toThrow('Expiration date cannot be empty'); }); it('should throw error when expirationDate is only whitespace', async () => { await expect( addItemCommand.execute(ITEM_NAME, ' ', ORDER_URL, USER_ID) ).rejects.toThrow('Expiration date cannot be empty'); }); it('should throw error when orderUrl is empty', async () => { await expect( addItemCommand.execute(ITEM_NAME, NOT_EXPIRED_DATE, '', USER_ID) ).rejects.toThrow('Order URL cannot be empty'); }); it('should throw error when orderUrl is only whitespace', async () => { await expect( addItemCommand.execute(ITEM_NAME, NOT_EXPIRED_DATE, ' ', USER_ID) ).rejects.toThrow('Order URL cannot be empty'); }); it('should throw error when userId is empty', async () => { await expect( addItemCommand.execute(ITEM_NAME, NOT_EXPIRED_DATE, ORDER_URL, '') ).rejects.toThrow('User ID cannot be empty'); }); it('should throw error when userId is only whitespace', async () => { await expect( addItemCommand.execute(ITEM_NAME, NOT_EXPIRED_DATE, ORDER_URL, ' ') ).rejects.toThrow('User ID cannot be empty'); }); }); }); });