diff --git a/composer.json b/composer.json index 18a169a..36c97de 100644 --- a/composer.json +++ b/composer.json @@ -25,6 +25,9 @@ "require-dev": { "pestphp/pest": "^3.8", "rector/rector": "^2.0", + "symfony/browser-kit": "^7.3", + "symfony/css-selector": "7.3.*", + "symfony/dom-crawler": "7.3.*", "symfony/maker-bundle": "^1.63", "symfony/stopwatch": "7.3.*", "symfony/web-profiler-bundle": "7.3.*", diff --git a/composer.lock b/composer.lock index e726044..8d9fa95 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "139ab419de1a7d3014e5595dbe864775", + "content-hash": "7dbc573cc9b3d6ab3fb532611e469c70", "packages": [ { "name": "doctrine/cache", @@ -5196,6 +5196,73 @@ }, "time": "2025-03-19T14:43:43+00:00" }, + { + "name": "masterminds/html5", + "version": "2.9.0", + "source": { + "type": "git", + "url": "https://github.com/Masterminds/html5-php.git", + "reference": "f5ac2c0b0a2eefca70b2ce32a5809992227e75a6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/f5ac2c0b0a2eefca70b2ce32a5809992227e75a6", + "reference": "f5ac2c0b0a2eefca70b2ce32a5809992227e75a6", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7.21 || ^6 || ^7 || ^8 || ^9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Masterminds\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Matt Butcher", + "email": "technosophos@gmail.com" + }, + { + "name": "Matt Farina", + "email": "matt@mattfarina.com" + }, + { + "name": "Asmir Mustafic", + "email": "goetas@gmail.com" + } + ], + "description": "An HTML5 parser and serializer.", + "homepage": "http://masterminds.github.io/html5-php", + "keywords": [ + "HTML5", + "dom", + "html", + "parser", + "querypath", + "serializer", + "xml" + ], + "support": { + "issues": "https://github.com/Masterminds/html5-php/issues", + "source": "https://github.com/Masterminds/html5-php/tree/2.9.0" + }, + "time": "2024-03-31T07:05:07+00:00" + }, { "name": "myclabs/deep-copy", "version": "1.13.1", @@ -7746,6 +7813,206 @@ ], "time": "2024-10-20T05:08:20+00:00" }, + { + "name": "symfony/browser-kit", + "version": "v7.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/browser-kit.git", + "reference": "5384291845e74fd7d54f3d925c4a86ce12336593" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/browser-kit/zipball/5384291845e74fd7d54f3d925c4a86ce12336593", + "reference": "5384291845e74fd7d54f3d925c4a86ce12336593", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/dom-crawler": "^6.4|^7.0" + }, + "require-dev": { + "symfony/css-selector": "^6.4|^7.0", + "symfony/http-client": "^6.4|^7.0", + "symfony/mime": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\BrowserKit\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Simulates the behavior of a web browser, allowing you to make requests, click on links and submit forms programmatically", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/browser-kit/tree/v7.3.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-03-05T10:15:41+00:00" + }, + { + "name": "symfony/css-selector", + "version": "v7.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/css-selector.git", + "reference": "601a5ce9aaad7bf10797e3663faefce9e26c24e2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/601a5ce9aaad7bf10797e3663faefce9e26c24e2", + "reference": "601a5ce9aaad7bf10797e3663faefce9e26c24e2", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\CssSelector\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Jean-François Simon", + "email": "jeanfrancois.simon@sensiolabs.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Converts CSS selectors to XPath expressions", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/css-selector/tree/v7.3.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:21:43+00:00" + }, + { + "name": "symfony/dom-crawler", + "version": "v7.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/dom-crawler.git", + "reference": "0fabbc3d6a9c473b716a93fc8e7a537adb396166" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/0fabbc3d6a9c473b716a93fc8e7a537adb396166", + "reference": "0fabbc3d6a9c473b716a93fc8e7a537adb396166", + "shasum": "" + }, + "require": { + "masterminds/html5": "^2.6", + "php": ">=8.2", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.0" + }, + "require-dev": { + "symfony/css-selector": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\DomCrawler\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Eases DOM navigation for HTML and XML documents", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/dom-crawler/tree/v7.3.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-03-05T10:15:41+00:00" + }, { "name": "symfony/maker-bundle", "version": "v1.63.0", diff --git a/config/services.php b/config/services.php index 6168c78..d9df3a6 100644 --- a/config/services.php +++ b/config/services.php @@ -1,8 +1,6 @@ findBy([ 'entityClass' => DrinkType::class, 'propertyName' => 'desiredStock', - 'entityId' => $drinkType->getId() - ], ['changeDate' => 'DESC']); + 'entityId' => $drinkType->getId(), + ], [ + 'changeDate' => 'DESC', + ]); return $this->render('drink_type/show.html.twig', [ 'drink_type' => $drinkType, diff --git a/src/Controller/Index.php b/src/Controller/Index.php index aaabdfd..20b6077 100644 --- a/src/Controller/Index.php +++ b/src/Controller/Index.php @@ -4,9 +4,6 @@ declare(strict_types=1); namespace App\Controller; -use App\Enum\StockState; -use App\Service\InventoryService; -use App\ValueObject\DrinkStock; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Attribute\Route; @@ -14,19 +11,8 @@ use Symfony\Component\Routing\Attribute\Route; #[Route(path: '/', name: 'app_index')] final class Index extends AbstractController { - public function __invoke(): Response { - $drinkStocks = $this->inventoryService->getAllDrinkTypesWithStockLevels(); - - $low = array_filter( - $drinkStocks, - fn(DrinkStock $stock): bool => $stock->stock === StockState::LOW || $stock->stock === StockState::CRITICAL, - ); - - return $this->render('index.html.twig', [ - 'drinkStocks' => $drinkStocks, - 'low' => $low, - ]); + return new Response('

Hello World!

'); } } diff --git a/src/Entity/PropertyChangeLog.php b/src/Entity/PropertyChangeLog.php index 43bff16..f444baf 100644 --- a/src/Entity/PropertyChangeLog.php +++ b/src/Entity/PropertyChangeLog.php @@ -6,13 +6,14 @@ namespace App\Entity; use App\Repository\PropertyChangeLogRepository; use Doctrine\ORM\Mapping\Column; +use Doctrine\ORM\Mapping\Entity; use Doctrine\ORM\Mapping\GeneratedValue; use DateTimeImmutable; use Doctrine\ORM\Mapping\Id; -use Doctrine\ORM\Mapping as ORM; +use Doctrine\ORM\Mapping\Table; -#[ORM\Entity(repositoryClass: PropertyChangeLogRepository::class)] -#[ORM\Table(name: 'property_change_log')] +#[Entity(repositoryClass: PropertyChangeLogRepository::class)] +#[Table(name: 'property_change_log')] final class PropertyChangeLog { #[Id] diff --git a/src/EventListener/PostPersistUpdateListener.php b/src/EventListener/PostPersistUpdateListener.php index 43965f8..b94badc 100644 --- a/src/EventListener/PostPersistUpdateListener.php +++ b/src/EventListener/PostPersistUpdateListener.php @@ -1,21 +1,20 @@ -log(Events::postPersist, $args); @@ -25,12 +24,12 @@ final class PostPersistUpdateListener { $this->log(Events::postUpdate, $args); } - private function log(string $action, LifecycleEventArgs $args): void - { - $entity = $args->getObject(); - match($entity::class) { - DrinkType::class => $this->drinkTypeUpdate->handleLog($action, $entity, $args->getObjectManager()), - default => null, - }; - } + private function log(string $action, LifecycleEventArgs $args): void + { + $entity = $args->getObject(); + match ($entity::class) { + DrinkType::class => $this->drinkTypeUpdate->handleLog($action, $entity, $args->getObjectManager()), + default => null, + }; + } } diff --git a/src/Repository/DrinkTypeRepository.php b/src/Repository/DrinkTypeRepository.php index f4f2bc4..bd26f83 100644 --- a/src/Repository/DrinkTypeRepository.php +++ b/src/Repository/DrinkTypeRepository.php @@ -25,20 +25,20 @@ class DrinkTypeRepository extends AbstractRepository return parent::findBy( criteria: [], orderBy: [ - 'desiredStock' => 'DESC', + 'wantedStock' => 'DESC', ], ); } /** @return DrinkType[] */ - public function findDesired(): array + public function findWanted(): array { $qb = $this->getEntityManager()->createQueryBuilder(); $qb ->select('d') ->from(DrinkType::class, 'd') - ->where('d.desiredStock > 0') - ->orderBy('d.desiredStock', 'DESC') + ->where('d.wantedStock > 0') + ->orderBy('d.wantedStock', 'DESC') ->addOrderBy('d.name', 'ASC'); /** @var array $result */ diff --git a/src/Repository/SystemConfigRepository.php b/src/Repository/SystemConfigRepository.php index 4b813ed..0875798 100644 --- a/src/Repository/SystemConfigRepository.php +++ b/src/Repository/SystemConfigRepository.php @@ -26,7 +26,7 @@ class SystemConfigRepository extends AbstractRepository if (!($config instanceof SystemConfig)) { $config = new SystemConfig(); $config->setKey($key); - $config->setValue($key->defaultValue($key)); + $config->setValue($key->defaultValue()); $this->save($config); } return $config; diff --git a/src/Service/Config/AppName.php b/src/Service/Config/AppName.php index 43cc62d..6137828 100644 --- a/src/Service/Config/AppName.php +++ b/src/Service/Config/AppName.php @@ -4,7 +4,6 @@ declare(strict_types=1); namespace App\Service\Config; -use App\Entity\SystemConfig; use App\Enum\SystemSettingKey; use App\Service\ConfigurationService; use Stringable; diff --git a/src/Service/Config/LowStockMultiplier.php b/src/Service/Config/LowStockMultiplier.php index 3f85cb7..195adb2 100644 --- a/src/Service/Config/LowStockMultiplier.php +++ b/src/Service/Config/LowStockMultiplier.php @@ -4,7 +4,6 @@ declare(strict_types=1); namespace App\Service\Config; -use App\Entity\SystemConfig; use App\Enum\SystemSettingKey; use App\Service\ConfigurationService; @@ -16,11 +15,6 @@ final readonly class LowStockMultiplier public function getValue(): float { - $value = $this->configService->getConfigValue( - SystemSettingKey::STOCK_LOW_MULTIPLIER, - SystemConfig::DEFAULT_STOCK_LOW_MULTIPLIER, - ); - - return (float) $value; + return (float) $this->configService->get(SystemSettingKey::STOCK_LOW_MULTIPLIER); } } diff --git a/src/Service/ConfigurationService.php b/src/Service/ConfigurationService.php index 57d1202..296eda8 100644 --- a/src/Service/ConfigurationService.php +++ b/src/Service/ConfigurationService.php @@ -4,10 +4,8 @@ declare(strict_types=1); namespace App\Service; -use App\Entity\SystemConfig; use App\Enum\SystemSettingKey; use App\Repository\SystemConfigRepository; -use InvalidArgumentException; readonly class ConfigurationService { diff --git a/src/Service/DrinkType/CreateNewOrderItems.php b/src/Service/DrinkType/CreateNewOrderItems.php new file mode 100644 index 0000000..2c139fb --- /dev/null +++ b/src/Service/DrinkType/CreateNewOrderItems.php @@ -0,0 +1,30 @@ +drinkTypeRepository->findWanted())->forAll( + fn (DrinkType $drinkType) => $order + ->addOrderItem( + new OrderItem() + ->setDrinkType($drinkType) + ->setQuantity($drinkType->getWantedStock() - $drinkType->getCurrentStock()) + ), + ); + } +} diff --git a/src/Service/DrinkType/DrinkTypeUpdateLog.php b/src/Service/DrinkType/DrinkTypeUpdateLog.php new file mode 100644 index 0000000..307e20a --- /dev/null +++ b/src/Service/DrinkType/DrinkTypeUpdateLog.php @@ -0,0 +1,63 @@ + $this->handleCreate($entity, $em), + Events::postUpdate => $this->handleUpdate($entity, $em), + }; + } + + private function handleCreate(DrinkType $entity, EntityManagerInterface $em): void + { + $this->logWanted($entity, $em); + $this->logCurrent($entity, $em); + } + + private function logWanted(DrinkType $entity, EntityManagerInterface $em): void + { + $logWanted = new PropertyChangeLog(); + $logWanted->setNewValue((string) $entity->getWantedStock()); + $logWanted->setEntityClass(DrinkType::class); + $logWanted->setEntityId($entity->getId()); + $logWanted->setPropertyName('wantedStock'); + + $em->persist($logWanted); + $em->flush(); + } + + private function logCurrent(DrinkType $entity, EntityManagerInterface $em): void + { + $logCurrent = new PropertyChangeLog(); + $logCurrent->setNewValue((string) $entity->getCurrentStock()); + $logCurrent->setEntityClass(DrinkType::class); + $logCurrent->setEntityId($entity->getId()); + $logCurrent->setPropertyName('currentStock'); + + $em->persist($logCurrent); + $em->flush(); + } + + private function handleUpdate(DrinkType $entity, EntityManagerInterface $em): void + { + $uow = $em->getUnitOfWork(); + $changeSet = $uow->getEntityChangeSet($entity); + if (isset($changeSet['wantedStock'])) { + $this->logWanted($entity, $em); + } + if (isset($changeSet['currentStock'])) { + $this->logCurrent($entity, $em); + } + } +} diff --git a/src/Service/DrinkType/GetStockHistory.php b/src/Service/DrinkType/GetStockHistory.php new file mode 100644 index 0000000..a9f34c1 --- /dev/null +++ b/src/Service/DrinkType/GetStockHistory.php @@ -0,0 +1,32 @@ +getId() === null) { + return[]; + } + return $this->propertyChangeLogRepository->findBy([ + 'entityClass' => DrinkType::class, + 'propertyName' => 'currentStock', + 'entityId' => $drinkType->getId(), + ]); + } +} diff --git a/src/Service/DrinkType/GetWantedHistory.php b/src/Service/DrinkType/GetWantedHistory.php new file mode 100644 index 0000000..9e1533b --- /dev/null +++ b/src/Service/DrinkType/GetWantedHistory.php @@ -0,0 +1,27 @@ +getId() === null) { + return[]; + } + + return $this->propertyChangeLogRepository->findBy([ + 'entityClass' => DrinkType::class, + 'propertyName' => 'wantedStock', + 'entityId' => $drinkType->getId(), + ]); + } +} diff --git a/src/Service/DrinkTypeUpdate.php b/src/Service/DrinkTypeUpdate.php deleted file mode 100644 index 8b2b087..0000000 --- a/src/Service/DrinkTypeUpdate.php +++ /dev/null @@ -1,61 +0,0 @@ - $this->handleCreate($entity, $em), - Events::postUpdate => $this->handleUpdate($entity, $em), - }; - } - - private function handleCreate(DrinkType $entity, EntityManagerInterface $em): void - { - $this->logWanted($entity, $em); - $this->logCurrent($entity, $em); - } - - private function logWanted(DrinkType $entity, EntityManagerInterface $em): void - { - $logWanted = new PropertyChangeLog(); - $logWanted->setNewValue((string) $entity->getWantedStock()); - $logWanted->setEntityClass(DrinkType::class); - $logWanted->setEntityId($entity->getId()); - $logWanted->setPropertyName('wantedStock'); - - $em->persist($logWanted); - $em->flush(); - } - - private function logCurrent(DrinkType $entity, EntityManagerInterface $em): void - { - $logCurrent = new PropertyChangeLog(); - $logCurrent->setNewValue((string) $entity->getCurrentStock()); - $logCurrent->setEntityClass(DrinkType::class); - $logCurrent->setEntityId($entity->getId()); - $logCurrent->setPropertyName('currentStock'); - - $em->persist($logCurrent); - $em->flush(); - } - - private function handleUpdate(DrinkType $entity, EntityManagerInterface $em): void - { - $uow = $em->getUnitOfWork(); - $changeSet = $uow->getEntityChangeSet($entity); - if (isset($changeSet['wantedStock'])) { - $this->logWanted($entity, $em);; - } - if (isset($changeSet['currentStock'])) { - $this->logCurrent($entity,$em); - } - } -} diff --git a/src/Service/OrderService.php b/src/Service/OrderService.php index 733e905..1bb1f64 100644 --- a/src/Service/OrderService.php +++ b/src/Service/OrderService.php @@ -4,7 +4,6 @@ declare(strict_types=1); namespace App\Service; -use App\Entity\DrinkType; use App\Entity\Order; use App\Entity\OrderItem; use App\Enum\OrderStatus; @@ -132,111 +131,4 @@ readonly class OrderService return $order; } - - /** - * Create an order based on current stock levels - * - * @return Order - */ - public function createOrderFromStockLevels(): Order - { - $lowStockItems = $this->inventoryService->getAllDrinkTypesWithStockLevels(); - $orderItems = []; - - foreach ($lowStockItems as $item) { - if ($item->record->getQuantity() < $item->record->getDrinkType()->getDesiredStock()) { - $orderItems[] = [ - 'drinkTypeId' => $item->record->getDrinkType()->getId(), - 'quantity' => $item->record->getDrinkType()->getDesiredStock() - $item->record->getQuantity(), - ]; - } - } - - return $this->createOrder($orderItems); - } - - /** - * Update an order's status - * - * @return Order - * @throws InvalidArgumentException If the status is invalid - */ - public function updateOrderStatus(Order $order, OrderStatus $status): Order - { - $order->setStatus($status); - $this->orderRepository->save($order); - - return $order; - } - - /** - * Add an item to an order - * - * @param Order $order - * @param DrinkType $drinkType - * @param int $quantity - * @return OrderItem - * @throws InvalidArgumentException If the order is not in 'new' or 'in_work' status - */ - public function addOrderItem(Order $order, DrinkType $drinkType, int $quantity): OrderItem - { - if (!in_array($order->getStatus(), [OrderStatus::NEW, OrderStatus::IN_WORK], true)) { - throw new InvalidArgumentException( - "Cannot add items to an order with status '{$order->getStatus()->value}'", - ); - } - - // Check if the order already has an item for this drink type - $existingItem = $this->orderItemRepository->findByOrderAndDrinkType($order, $drinkType); - - if ($existingItem instanceof OrderItem) { - // Update the existing item - $existingItem->setQuantity($existingItem->getQuantity() + $quantity); - $this->orderItemRepository->save($existingItem); - return $existingItem; - } - // Create a new item - $orderItem = new OrderItem(); - $orderItem->setQuantity($quantity); - $orderItem->setOrder($order); - $orderItem->setDrinkType($drinkType); - $this->orderItemRepository->save($orderItem); - return $orderItem; - } - - /** - * Remove an item from an order - * - * @param Order $order - * @param OrderItem $orderItem - * @return void - * @throws InvalidArgumentException If the order is not in 'new' or 'in_work' status - */ - public function removeOrderItem(Order $order, OrderItem $orderItem): void - { - if (!in_array($order->getStatus(), [OrderStatus::NEW, OrderStatus::IN_WORK], true)) { - throw new InvalidArgumentException( - "Cannot remove items from an order with status '{$order->getStatus()->value}'", - ); - } - - $order->removeOrderItem($orderItem); - $this->orderItemRepository->remove($orderItem); - } - - /** - * Delete an order - * - * @param Order $order - * @return void - * @throws InvalidArgumentException If the order is not in 'new' status - */ - public function deleteOrder(Order $order): void - { - if ($order->getStatus() !== OrderStatus::NEW && $order->getStatus() !== OrderStatus::IN_WORK) { - throw new InvalidArgumentException("Cannot delete an order with status '{$order->getStatus()->value}'"); - } - - $this->orderRepository->remove($order); - } } diff --git a/src/ValueObject/DrinkStock.php b/src/ValueObject/DrinkStock.php deleted file mode 100644 index 3b27e8a..0000000 --- a/src/ValueObject/DrinkStock.php +++ /dev/null @@ -1,31 +0,0 @@ -getQuantity() === 0 && $record->getDrinkType()->getDesiredStock() > 0) { - return new self($record, StockState::CRITICAL); - } - if ($record->getQuantity() < ($record->getDrinkType()->getDesiredStock() * $lowStockMultiplier)) { - return new self($record, StockState::LOW); - } - if ($record->getQuantity() > $record->getDrinkType()->getDesiredStock()) { - return new self($record, StockState::HIGH); - } - - return new self($record, StockState::NORMAL); - } -} diff --git a/tests/Feature/Entity/DrinkTypeTest.php b/tests/Feature/Entity/DrinkTypeTest.php index 9c3236c..3e8a829 100644 --- a/tests/Feature/Entity/DrinkTypeTest.php +++ b/tests/Feature/Entity/DrinkTypeTest.php @@ -1,10 +1,12 @@ setName('test'); $drinkType->setWantedStock(10); @@ -16,26 +18,34 @@ test('Update Listener', function () { $em->flush(); $propertyLogRepository = $em->getRepository(PropertyChangeLog::class); - $logs = $propertyLogRepository->findBy(['entityClass' => DrinkType::class]); + $logs = $propertyLogRepository->findBy([ + 'entityClass' => DrinkType::class, + ]); expect($logs)->toHaveCount(2); $drinkType->setWantedStock(15); $em->persist($drinkType); $em->flush(); - $logs = $propertyLogRepository->findBy(['entityClass' => DrinkType::class]); + $logs = $propertyLogRepository->findBy([ + 'entityClass' => DrinkType::class, + ]); expect($logs)->toHaveCount(3); $drinkType->setCurrentStock(15); $em->persist($drinkType); $em->flush(); - $logs = $propertyLogRepository->findBy(['entityClass' => DrinkType::class]); + $logs = $propertyLogRepository->findBy([ + 'entityClass' => DrinkType::class, + ]); expect($logs)->toHaveCount(4); $drinkType->setDescription('test'); $em->persist($drinkType); $em->flush(); - $logs = $propertyLogRepository->findBy(['entityClass' => DrinkType::class]); + $logs = $propertyLogRepository->findBy([ + 'entityClass' => DrinkType::class, + ]); expect($logs)->toHaveCount(4); }); diff --git a/tests/Feature/Repository/DrinkTypeRepositoryTest.php b/tests/Feature/Repository/DrinkTypeRepositoryTest.php new file mode 100644 index 0000000..5f8209b --- /dev/null +++ b/tests/Feature/Repository/DrinkTypeRepositoryTest.php @@ -0,0 +1,105 @@ +getContainer()->get(EntityManagerInterface::class); + $repository = $this->getContainer()->get(DrinkTypeRepository::class); + + // Clear existing drink types + $existingDrinkTypes = $repository->findAll(); + foreach ($existingDrinkTypes as $drinkType) { + $em->remove($drinkType); + } + $em->flush(); + + // Create test drink types with different wantedStock values + $drinkType1 = new DrinkType(); + $drinkType1->setName('Drink Type 1'); + $drinkType1->setWantedStock(5); + + $drinkType2 = new DrinkType(); + $drinkType2->setName('Drink Type 2'); + $drinkType2->setWantedStock(10); + + $drinkType3 = new DrinkType(); + $drinkType3->setName('Drink Type 3'); + $drinkType3->setWantedStock(0); + + // Persist the drink types + $em->persist($drinkType1); + $em->persist($drinkType2); + $em->persist($drinkType3); + $em->flush(); + + // Test findAll method + $result = $repository->findAll(); + + // Verify the result + expect($result)->toHaveCount(3); + expect($result[0]->getName())->toBe('Drink Type 2'); + expect($result[1]->getName())->toBe('Drink Type 1'); + expect($result[2]->getName())->toBe('Drink Type 3'); +}); + +test('findWanted returns only drink types with wantedStock > 0 ordered by wantedStock DESC and name ASC', function (): void { + $em = $this->getContainer()->get(EntityManagerInterface::class); + $repository = $this->getContainer()->get(DrinkTypeRepository::class); + + // Clear existing drink types + $existingDrinkTypes = $repository->findAll(); + foreach ($existingDrinkTypes as $drinkType) { + $em->remove($drinkType); + } + $em->flush(); + + // Create test drink types with different wantedStock values + $drinkType1 = new DrinkType(); + $drinkType1->setName('Cola'); + $drinkType1->setWantedStock(10); + + $drinkType2 = new DrinkType(); + $drinkType2->setName('Beer'); + $drinkType2->setWantedStock(10); + + $drinkType3 = new DrinkType(); + $drinkType3->setName('Water'); + $drinkType3->setWantedStock(5); + + $drinkType4 = new DrinkType(); + $drinkType4->setName('Juice'); + $drinkType4->setWantedStock(0); + + // Persist the drink types + $em->persist($drinkType1); + $em->persist($drinkType2); + $em->persist($drinkType3); + $em->persist($drinkType4); + $em->flush(); + + // Test findWanted method + $result = $repository->findWanted(); + + // Verify the result + expect($result)->toHaveCount(3); + + // First should be Beer (wantedStock 10, name starts with B) + expect($result[0]->getName())->toBe('Beer'); + expect($result[0]->getWantedStock())->toBe(10); + + // Second should be Cola (wantedStock 10, name starts with C) + expect($result[1]->getName())->toBe('Cola'); + expect($result[1]->getWantedStock())->toBe(10); + + // Third should be Water (wantedStock 5) + expect($result[2]->getName())->toBe('Water'); + expect($result[2]->getWantedStock())->toBe(5); + + // Juice should not be in the result (wantedStock 0) + $names = array_map(fn($dt) => $dt->getName(), $result); + expect($names)->not->toContain('Juice'); +}); diff --git a/tests/Feature/Service/Config/AppNameTest.php b/tests/Feature/Service/Config/AppNameTest.php index bd4cb89..6fa1cec 100644 --- a/tests/Feature/Service/Config/AppNameTest.php +++ b/tests/Feature/Service/Config/AppNameTest.php @@ -1,18 +1,20 @@ getContainer()->get(ConfigurationService::class); $appName = new AppName($configService); - expect((string)$appName)->toBe(SystemSettingKey::SYSTEM_NAME->defaultValue()); + expect((string) $appName)->toBe(SystemSettingKey::SYSTEM_NAME->defaultValue()); $expected = 'Test System Name'; $configService->set(SystemSettingKey::SYSTEM_NAME, $expected); - expect((string)$appName)->toBe($expected); + expect((string) $appName)->toBe($expected); }); diff --git a/tests/Feature/Service/ConfigurationServiceTest.php b/tests/Feature/Service/ConfigurationServiceTest.php index f7af002..7b4773c 100644 --- a/tests/Feature/Service/ConfigurationServiceTest.php +++ b/tests/Feature/Service/ConfigurationServiceTest.php @@ -1,12 +1,9 @@ setName('Test Drink'); + + $getStockHistory = $this->getContainer()->get(GetStockHistory::class); + $result = $getStockHistory($drinkType); + + expect($result)->toBeArray(); + expect($result)->toBeEmpty(); +}); + +test('it returns stock history for a drink type', function (): void { + // Create a drink type + $drinkType = new DrinkType(); + $drinkType->setName('Test Drink'); + $drinkType->setCurrentStock(10); + + $em = $this->getContainer()->get(EntityManagerInterface::class); + $em->persist($drinkType); + $em->flush(); + + // Change the current stock to create logs + $drinkType->setCurrentStock(15); + $em->persist($drinkType); + $em->flush(); + + $drinkType->setCurrentStock(5); + $em->persist($drinkType); + $em->flush(); + + // Get the stock history + $getStockHistory = $this->getContainer()->get(GetStockHistory::class); + $result = $getStockHistory($drinkType); + + // Verify the result + expect($result)->toBeArray(); + expect($result)->toHaveCount(3); + + // Verify that all logs are for currentStock + foreach ($result as $log) { + expect($log)->toBeInstanceOf(PropertyChangeLog::class); + expect($log->getPropertyName())->toBe('currentStock'); + expect($log->getEntityClass())->toBe(DrinkType::class); + expect($log->getEntityId())->toBe($drinkType->getId()); + } + + // Verify the values in order (oldest first) + expect($result[0]->getNewValue())->toBe('10'); + expect($result[1]->getNewValue())->toBe('15'); + expect($result[2]->getNewValue())->toBe('5'); +}); + +test('it only returns logs for the specified drink type', function (): void { + $em = $this->getContainer()->get(EntityManagerInterface::class); + + // Create first drink type + $drinkType1 = new DrinkType(); + $drinkType1->setName('Drink 1'); + $drinkType1->setCurrentStock(10); + $em->persist($drinkType1); + $em->flush(); + + // Create second drink type + $drinkType2 = new DrinkType(); + $drinkType2->setName('Drink 2'); + $drinkType2->setCurrentStock(20); + $em->persist($drinkType2); + $em->flush(); + + // Get history for first drink type + $getStockHistory = $this->getContainer()->get(GetStockHistory::class); + $result = $getStockHistory($drinkType1); + + // Verify we only get logs for the first drink type + expect($result)->toHaveCount(1); + expect($result[0]->getEntityId())->toBe($drinkType1->getId()); + expect($result[0]->getNewValue())->toBe('10'); +}); diff --git a/tests/Feature/Service/DrinkType/GetWantedHistoryTest.php b/tests/Feature/Service/DrinkType/GetWantedHistoryTest.php new file mode 100644 index 0000000..1259ab2 --- /dev/null +++ b/tests/Feature/Service/DrinkType/GetWantedHistoryTest.php @@ -0,0 +1,87 @@ +setName('Test Drink'); + + $getWantedHistory = $this->getContainer()->get(GetWantedHistory::class); + $result = $getWantedHistory($drinkType); + + expect($result)->toBeArray(); + expect($result)->toBeEmpty(); +}); + +test('it returns wanted stock history for a drink type', function (): void { + // Create a drink type + $drinkType = new DrinkType(); + $drinkType->setName('Test Drink'); + $drinkType->setWantedStock(10); + + $em = $this->getContainer()->get(EntityManagerInterface::class); + $em->persist($drinkType); + $em->flush(); + + // Change the wanted stock to create logs + $drinkType->setWantedStock(15); + $em->persist($drinkType); + $em->flush(); + + $drinkType->setWantedStock(5); + $em->persist($drinkType); + $em->flush(); + + // Get the wanted stock history + $getWantedHistory = $this->getContainer()->get(GetWantedHistory::class); + $result = $getWantedHistory($drinkType); + + // Verify the result + expect($result)->toBeArray(); + expect($result)->toHaveCount(3); + + // Verify that all logs are for wantedStock + foreach ($result as $log) { + expect($log)->toBeInstanceOf(PropertyChangeLog::class); + expect($log->getPropertyName())->toBe('wantedStock'); + expect($log->getEntityClass())->toBe(DrinkType::class); + expect($log->getEntityId())->toBe($drinkType->getId()); + } + + // Verify the values in order (oldest first) + expect($result[0]->getNewValue())->toBe('10'); + expect($result[1]->getNewValue())->toBe('15'); + expect($result[2]->getNewValue())->toBe('5'); +}); + +test('it only returns logs for the specified drink type', function (): void { + $em = $this->getContainer()->get(EntityManagerInterface::class); + + // Create first drink type + $drinkType1 = new DrinkType(); + $drinkType1->setName('Drink 1'); + $drinkType1->setWantedStock(10); + $em->persist($drinkType1); + $em->flush(); + + // Create second drink type + $drinkType2 = new DrinkType(); + $drinkType2->setName('Drink 2'); + $drinkType2->setWantedStock(20); + $em->persist($drinkType2); + $em->flush(); + + // Get history for first drink type + $getWantedHistory = $this->getContainer()->get(GetWantedHistory::class); + $result = $getWantedHistory($drinkType1); + + // Verify we only get logs for the first drink type + expect($result)->toHaveCount(1); + expect($result[0]->getEntityId())->toBe($drinkType1->getId()); + expect($result[0]->getNewValue())->toBe('10'); +}); diff --git a/tests/Feature/Web/HelloWorldTest.php b/tests/Feature/Web/HelloWorldTest.php new file mode 100644 index 0000000..78303d2 --- /dev/null +++ b/tests/Feature/Web/HelloWorldTest.php @@ -0,0 +1,18 @@ +ensureKernelShutdown(); + $client = static::createClient(); + + // Request a specific page + $crawler = $client->request('GET', '/'); + + // Validate a successful response and some content + $this->assertResponseIsSuccessful(); + $this->assertSelectorTextContains('h1', 'Hello World'); +}); diff --git a/tests/TestCase.php b/tests/TestCase.php index 8966cfd..142058b 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -4,6 +4,6 @@ declare(strict_types=1); namespace Tests; -use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; +use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; -abstract class TestCase extends KernelTestCase {} +abstract class TestCase extends WebTestCase {} diff --git a/tests/Unit/ExampleTest.php b/tests/Unit/ExampleTest.php deleted file mode 100644 index 3dbe6e4..0000000 --- a/tests/Unit/ExampleTest.php +++ /dev/null @@ -1,7 +0,0 @@ -toBeTrue(); -});