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.
 
 
 
 
 
 

212 lines
8.0 KiB

<?php
declare(strict_types=1);
namespace AutoStore\Tests\Unit;
use AutoStore\Application\Interfaces\IItemRepository;
use AutoStore\Application\Interfaces\IOrderService;
use AutoStore\Application\Interfaces\ITimeProvider;
use AutoStore\Application\Commands\AddItem;
use AutoStore\Domain\Entities\Item;
use DateTimeImmutable;
use PHPUnit\Framework\TestCase;
use Psr\Log\LoggerInterface;
class AddItemTest extends TestCase
{
private AddItem $addItem;
private IItemRepository&\PHPUnit\Framework\MockObject\MockObject $itemRepository;
private IOrderService&\PHPUnit\Framework\MockObject\MockObject $orderService;
private ITimeProvider&\PHPUnit\Framework\MockObject\MockObject $timeProvider;
private LoggerInterface&\PHPUnit\Framework\MockObject\MockObject $logger;
protected function setUp(): void
{
$this->itemRepository = $this->createMock(IItemRepository::class);
$this->orderService = $this->createMock(IOrderService::class);
$this->timeProvider = $this->createMock(ITimeProvider::class);
$this->logger = $this->createMock(LoggerInterface::class);
$this->addItem = new AddItem(
$this->itemRepository,
$this->orderService,
$this->timeProvider,
$this->logger
);
}
public function testExecuteShouldSaveItemWhenNotExpired(): void
{
$userId = 'test-user-id';
$itemName = 'Test Item';
$expirationDate = new DateTimeImmutable('+1 day');
$orderUrl = 'http://example.com/order';
$this->timeProvider->method('now')
->willReturn(new DateTimeImmutable());
// Capture the saved item
$savedItem = null;
$this->itemRepository->expects($this->once())
->method('save')
->with($this->callback(function (Item $item) use ($itemName, $orderUrl, $userId, &$savedItem) {
$savedItem = $item;
return $item->getName() === $itemName &&
$item->getOrderUrl() === $orderUrl &&
$item->getUserId() === $userId &&
!$item->isOrdered();
}));
$this->orderService->expects($this->never())
->method('orderItem');
// Mock findById to return the saved item
$this->itemRepository->expects($this->once())
->method('findById')
->willReturnCallback(function ($id) use (&$savedItem) {
return $savedItem;
});
$resultId = $this->addItem->execute($itemName, $expirationDate->format('Y-m-d H:i:s'), $orderUrl, $userId);
// Retrieve the saved item to verify its properties
$result = $this->itemRepository->findById($resultId);
$this->assertSame($itemName, $result->getName());
// Compare DateTime objects without microseconds
$this->assertEquals($expirationDate->format('Y-m-d H:i:s'), $result->getExpirationDate()->format('Y-m-d H:i:s'));
$this->assertSame($orderUrl, $result->getOrderUrl());
$this->assertSame($userId, $result->getUserId());
$this->assertFalse($result->isOrdered());
}
public function testExecuteShouldPlaceOrderWhenItemIsExpired(): void
{
$userId = 'test-user-id';
$itemName = 'Test Item';
$expirationDate = new DateTimeImmutable('-1 day');
$orderUrl = 'http://example.com/order';
$this->timeProvider->method('now')
->willReturn(new DateTimeImmutable());
$savedItem = null;
$orderedItem = null;
$this->itemRepository->expects($this->once())
->method('save')
->with($this->callback(function (Item $item) use (&$savedItem) {
$savedItem = $item;
return true;
}));
$this->orderService->expects($this->once())
->method('orderItem')
->with($this->callback(function (Item $item) use (&$orderedItem) {
$orderedItem = $item;
return true;
}));
// Mock findById to return the ordered item
$this->itemRepository->expects($this->once())
->method('findById')
->willReturnCallback(function ($id) use (&$orderedItem) {
// Mark the item as ordered before returning it
if ($orderedItem) {
$orderedItem->markAsOrdered();
}
return $orderedItem;
});
$resultId = $this->addItem->execute($itemName, $expirationDate->format('Y-m-d H:i:s'), $orderUrl, $userId);
// Retrieve the saved item to verify its properties
$result = $this->itemRepository->findById($resultId);
$this->assertTrue($result->isOrdered());
}
public function testExecuteShouldThrowExceptionWhenItemNameIsEmpty(): void
{
$userId = 'test-user-id';
$itemName = '';
$expirationDate = new DateTimeImmutable('+1 day');
$orderUrl = 'http://example.com/order';
$this->expectException(\AutoStore\Application\Exceptions\ApplicationException::class);
$this->expectExceptionMessage('Failed to add item: Item name cannot be empty');
$this->addItem->execute($itemName, $expirationDate->format('Y-m-d H:i:s'), $orderUrl, $userId);
}
public function testExecuteShouldThrowExceptionWhenOrderUrlIsEmpty(): void
{
$userId = 'test-user-id';
$itemName = 'Test Item';
$expirationDate = new DateTimeImmutable('+1 day');
$orderUrl = '';
$this->expectException(\AutoStore\Application\Exceptions\ApplicationException::class);
$this->expectExceptionMessage('Failed to add item: Order URL cannot be empty');
$this->addItem->execute($itemName, $expirationDate->format('Y-m-d H:i:s'), $orderUrl, $userId);
}
public function testExecuteShouldThrowExceptionWhenUserIdIsEmpty(): void
{
$userId = '';
$itemName = 'Test Item';
$expirationDate = new DateTimeImmutable('+1 day');
$orderUrl = 'http://example.com/order';
$this->expectException(\AutoStore\Application\Exceptions\ApplicationException::class);
$this->expectExceptionMessage('Failed to add item: User ID cannot be empty');
$this->addItem->execute($itemName, $expirationDate->format('Y-m-d H:i:s'), $orderUrl, $userId);
}
public function testExecuteShouldLogErrorWhenOrderServiceFails(): void
{
$userId = 'test-user-id';
$itemName = 'Test Item';
$expirationDate = new DateTimeImmutable('-1 day');
$orderUrl = 'http://example.com/order';
$this->timeProvider->method('now')
->willReturn(new DateTimeImmutable());
// Mock the repository to return a saved item
$savedItem = null;
$this->itemRepository->expects($this->once())
->method('save')
->with($this->callback(function (Item $item) use (&$savedItem) {
$savedItem = $item;
return true;
}));
// Mock the order service to throw an exception
$this->orderService->expects($this->once())
->method('orderItem')
->willThrowException(new \RuntimeException('Order service failed'));
$this->logger->expects($this->once())
->method('error')
->with($this->stringContains('Failed to place order for expired item'));
// Mock findById to return the saved item
$this->itemRepository->expects($this->once())
->method('findById')
->willReturnCallback(function ($id) use (&$savedItem) {
return $savedItem;
});
// The handler should not throw an exception when the order service fails
// It should log the error and continue
$resultId = $this->addItem->execute($itemName, $expirationDate->format('Y-m-d H:i:s'), $orderUrl, $userId);
// Retrieve the saved item to verify its properties
$result = $this->itemRepository->findById($resultId);
$this->assertFalse($result->isOrdered());
}
}