You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
178 lines
5.9 KiB
178 lines
5.9 KiB
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'); |
|
}); |
|
}); |
|
}); |
|
}); |