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