Multiple implementations of the same back-end application. The aim is to provide quick, side-by-side comparisons of different technologies (languages, frameworks, libraries) while preserving consistent business logic across all implementations.
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

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');
});
});
});
});