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