testitest
This commit is contained in:
parent
43ca79f650
commit
66c4c1fe4f
30 changed files with 4443 additions and 184 deletions
4
.env.test
Normal file
4
.env.test
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
# define your env variables for the test env here
|
||||||
|
KERNEL_CLASS='App\Kernel'
|
||||||
|
APP_SECRET='$ecretf0rt3st'
|
||||||
|
DATABASE_URL="sqlite:///%kernel.project_dir%/var/data_%kernel.environment%${TEST_TOKEN}.db"
|
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -14,3 +14,8 @@ phpstan.neon
|
||||||
###< phpstan/phpstan ###
|
###< phpstan/phpstan ###
|
||||||
|
|
||||||
.idea
|
.idea
|
||||||
|
|
||||||
|
###> phpunit/phpunit ###
|
||||||
|
/phpunit.xml
|
||||||
|
/.phpunit.cache/
|
||||||
|
###< phpunit/phpunit ###
|
||||||
|
|
23
bin/phpunit
Executable file
23
bin/phpunit
Executable file
|
@ -0,0 +1,23 @@
|
||||||
|
#!/usr/bin/env php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
if (!ini_get('date.timezone')) {
|
||||||
|
ini_set('date.timezone', 'UTC');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_file(dirname(__DIR__).'/vendor/phpunit/phpunit/phpunit')) {
|
||||||
|
if (PHP_VERSION_ID >= 80000) {
|
||||||
|
require dirname(__DIR__).'/vendor/phpunit/phpunit/phpunit';
|
||||||
|
} else {
|
||||||
|
define('PHPUNIT_COMPOSER_INSTALL', dirname(__DIR__).'/vendor/autoload.php');
|
||||||
|
require PHPUNIT_COMPOSER_INSTALL;
|
||||||
|
PHPUnit\TextUI\Command::main();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!is_file(dirname(__DIR__).'/vendor/symfony/phpunit-bridge/bin/simple-phpunit.php')) {
|
||||||
|
echo "Unable to find the `simple-phpunit.php` script in `vendor/symfony/phpunit-bridge/bin/`.\n";
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
require dirname(__DIR__).'/vendor/symfony/phpunit-bridge/bin/simple-phpunit.php';
|
||||||
|
}
|
|
@ -23,6 +23,7 @@
|
||||||
"symfony/yaml": "7.3.*"
|
"symfony/yaml": "7.3.*"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
|
"pestphp/pest": "^3.8",
|
||||||
"rector/rector": "^2.0",
|
"rector/rector": "^2.0",
|
||||||
"symfony/maker-bundle": "^1.63",
|
"symfony/maker-bundle": "^1.63",
|
||||||
"symfony/stopwatch": "7.3.*",
|
"symfony/stopwatch": "7.3.*",
|
||||||
|
@ -32,6 +33,7 @@
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"allow-plugins": {
|
"allow-plugins": {
|
||||||
|
"pestphp/pest-plugin": true,
|
||||||
"php-http/discovery": true,
|
"php-http/discovery": true,
|
||||||
"symfony/flex": true,
|
"symfony/flex": true,
|
||||||
"symfony/runtime": true
|
"symfony/runtime": true
|
||||||
|
@ -46,7 +48,7 @@
|
||||||
},
|
},
|
||||||
"autoload-dev": {
|
"autoload-dev": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
"App\\Tests\\": "tests/"
|
"Tests\\": "tests/"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"replace": {
|
"replace": {
|
||||||
|
@ -57,7 +59,9 @@
|
||||||
"symfony/polyfill-php74": "*",
|
"symfony/polyfill-php74": "*",
|
||||||
"symfony/polyfill-php80": "*",
|
"symfony/polyfill-php80": "*",
|
||||||
"symfony/polyfill-php81": "*",
|
"symfony/polyfill-php81": "*",
|
||||||
"symfony/polyfill-php82": "*"
|
"symfony/polyfill-php82": "*",
|
||||||
|
"symfony/polyfill-php83": "*",
|
||||||
|
"symfony/polyfill-php84": "*"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"auto-scripts": {
|
"auto-scripts": {
|
||||||
|
@ -73,6 +77,9 @@
|
||||||
"lint": [
|
"lint": [
|
||||||
"rector",
|
"rector",
|
||||||
"ecs --fix || ecs --fix"
|
"ecs --fix || ecs --fix"
|
||||||
|
],
|
||||||
|
"test": [
|
||||||
|
"pest --parallel"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"conflict": {
|
"conflict": {
|
||||||
|
|
2985
composer.lock
generated
2985
composer.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -12,4 +12,8 @@ return static function (ContainerConfigurator $containerConfigurator): void {
|
||||||
->autoconfigure();
|
->autoconfigure();
|
||||||
|
|
||||||
$services->load('App\\', __DIR__ . '/../src/');
|
$services->load('App\\', __DIR__ . '/../src/');
|
||||||
|
|
||||||
|
$services->load('App\\Service\\', __DIR__ . '/../src/Service')
|
||||||
|
->public();
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
44
phpunit.dist.xml
Normal file
44
phpunit.dist.xml
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<!-- https://phpunit.readthedocs.io/en/latest/configuration.html -->
|
||||||
|
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
|
||||||
|
colors="true"
|
||||||
|
failOnDeprecation="true"
|
||||||
|
failOnNotice="true"
|
||||||
|
failOnWarning="true"
|
||||||
|
bootstrap="tests/bootstrap.php"
|
||||||
|
cacheDirectory=".phpunit.cache"
|
||||||
|
>
|
||||||
|
<php>
|
||||||
|
<ini name="display_errors" value="1" />
|
||||||
|
<ini name="error_reporting" value="-1" />
|
||||||
|
<server name="APP_ENV" value="test" force="true" />
|
||||||
|
<server name="SHELL_VERBOSITY" value="-1" />
|
||||||
|
</php>
|
||||||
|
|
||||||
|
<testsuites>
|
||||||
|
<testsuite name="Project Test Suite">
|
||||||
|
<directory>tests</directory>
|
||||||
|
</testsuite>
|
||||||
|
</testsuites>
|
||||||
|
|
||||||
|
<source ignoreSuppressionOfDeprecations="true"
|
||||||
|
ignoreIndirectDeprecations="true"
|
||||||
|
restrictNotices="true"
|
||||||
|
restrictWarnings="true"
|
||||||
|
>
|
||||||
|
<include>
|
||||||
|
<directory>src</directory>
|
||||||
|
</include>
|
||||||
|
|
||||||
|
<deprecationTrigger>
|
||||||
|
<method>Doctrine\Deprecations\Deprecation::trigger</method>
|
||||||
|
<method>Doctrine\Deprecations\Deprecation::delegateTriggerToBackend</method>
|
||||||
|
<function>trigger_deprecation</function>
|
||||||
|
</deprecationTrigger>
|
||||||
|
</source>
|
||||||
|
|
||||||
|
<extensions>
|
||||||
|
</extensions>
|
||||||
|
</phpunit>
|
|
@ -4,7 +4,9 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Controller;
|
namespace App\Controller;
|
||||||
|
|
||||||
|
use App\Enum\StockState;
|
||||||
use App\Service\InventoryService;
|
use App\Service\InventoryService;
|
||||||
|
use App\ValueObject\DrinkStock;
|
||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
use Symfony\Component\Routing\Attribute\Route;
|
use Symfony\Component\Routing\Attribute\Route;
|
||||||
|
@ -20,8 +22,14 @@ final class Index extends AbstractController
|
||||||
{
|
{
|
||||||
$drinkStocks = $this->inventoryService->getAllDrinkTypesWithStockLevels();
|
$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', [
|
return $this->render('index.html.twig', [
|
||||||
'drinkStocks' => $drinkStocks,
|
'drinkStocks' => $drinkStocks,
|
||||||
|
'low' => $low
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,11 +21,16 @@ class Order
|
||||||
private null|int $id = null;
|
private null|int $id = null;
|
||||||
|
|
||||||
#[ORM\Column(type: 'datetime_immutable')]
|
#[ORM\Column(type: 'datetime_immutable')]
|
||||||
private readonly DateTimeImmutable $createdAt;
|
private DateTimeImmutable $createdAt;
|
||||||
|
|
||||||
#[ORM\Column(type: 'datetime_immutable')]
|
#[ORM\Column(type: 'datetime_immutable')]
|
||||||
private DateTimeImmutable $updatedAt;
|
private DateTimeImmutable $updatedAt;
|
||||||
|
|
||||||
|
public function setUpdatedAt(DateTimeImmutable $updatedAt): void
|
||||||
|
{
|
||||||
|
$this->updatedAt = $updatedAt;
|
||||||
|
}
|
||||||
|
|
||||||
#[ORM\OneToMany(mappedBy: 'order', targetEntity: OrderItem::class, cascade: ['persist', 'remove'])]
|
#[ORM\OneToMany(mappedBy: 'order', targetEntity: OrderItem::class, cascade: ['persist', 'remove'])]
|
||||||
private Collection $orderItems;
|
private Collection $orderItems;
|
||||||
|
|
||||||
|
@ -96,6 +101,7 @@ class Order
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private function updateTimestamp(): void
|
private function updateTimestamp(): void
|
||||||
{
|
{
|
||||||
$this->updatedAt = new DateTimeImmutable();
|
$this->updatedAt = new DateTimeImmutable();
|
||||||
|
|
|
@ -32,7 +32,7 @@ class OrderItem
|
||||||
|
|
||||||
#[ORM\ManyToOne(targetEntity: Order::class, inversedBy: 'orderItems')]
|
#[ORM\ManyToOne(targetEntity: Order::class, inversedBy: 'orderItems')]
|
||||||
#[ORM\JoinColumn(name: 'order_id', referencedColumnName: 'id', nullable: false)]
|
#[ORM\JoinColumn(name: 'order_id', referencedColumnName: 'id', nullable: false)]
|
||||||
private Order $order;
|
private null|Order $order;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
) {
|
) {
|
||||||
|
@ -53,7 +53,7 @@ class OrderItem
|
||||||
public function setOrder(null|Order $order): self
|
public function setOrder(null|Order $order): self
|
||||||
{
|
{
|
||||||
// Remove from old order if exists
|
// Remove from old order if exists
|
||||||
if ($this->order instanceof Order && $this->order !== $order) {
|
if (isset($this->order) && $this->order instanceof Order && $this->order !== $order) {
|
||||||
$this->order->removeOrderItem($this);
|
$this->order->removeOrderItem($this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,12 +13,12 @@ use Doctrine\ORM\Mapping as ORM;
|
||||||
#[ORM\Table(name: 'system_config')]
|
#[ORM\Table(name: 'system_config')]
|
||||||
class SystemConfig
|
class SystemConfig
|
||||||
{
|
{
|
||||||
public const DEFAULT_STOCK_ADJUSTMENT_LOOKBACK_ORDERS = '3';
|
public const string DEFAULT_STOCK_ADJUSTMENT_LOOKBACK_ORDERS = '3';
|
||||||
public const DEFAULT_DEFAULT_DESIRED_STOCK = '2';
|
public const string DEFAULT_DEFAULT_DESIRED_STOCK = '2';
|
||||||
public const DEFAULT_SYSTEM_NAME = 'Zaufen';
|
public const string DEFAULT_SYSTEM_NAME = 'Zaufen';
|
||||||
public const DEFAULT_STOCK_INCREASE_AMOUNT = '1';
|
public const string DEFAULT_STOCK_INCREASE_AMOUNT = '1';
|
||||||
public const DEFAULT_STOCK_DECREASE_AMOUNT = '1';
|
public const string DEFAULT_STOCK_DECREASE_AMOUNT = '1';
|
||||||
public const DEFAULT_STOCK_LOW_MULTIPLIER = '0.3';
|
public const string DEFAULT_STOCK_LOW_MULTIPLIER = '0.3';
|
||||||
|
|
||||||
#[ORM\Id]
|
#[ORM\Id]
|
||||||
#[ORM\GeneratedValue]
|
#[ORM\GeneratedValue]
|
||||||
|
|
|
@ -21,10 +21,12 @@ class SystemConfigRepository extends AbstractRepository
|
||||||
public function findByKey(SystemSettingKey $key): SystemConfig
|
public function findByKey(SystemSettingKey $key): SystemConfig
|
||||||
{
|
{
|
||||||
$config = $this->findOneBy([
|
$config = $this->findOneBy([
|
||||||
'key' => $key->value,
|
'key' => $key,
|
||||||
]);
|
]);
|
||||||
if (!($config instanceof SystemConfig)) {
|
if (!($config instanceof SystemConfig)) {
|
||||||
$config = new SystemConfig($key, SystemSettingKey::getDefaultValue($key));
|
$config = new SystemConfig();
|
||||||
|
$config->setKey($key);
|
||||||
|
$config->setValue($key->getDefaultValue($key));
|
||||||
$this->save($config);
|
$this->save($config);
|
||||||
}
|
}
|
||||||
return $config;
|
return $config;
|
||||||
|
@ -43,7 +45,9 @@ class SystemConfigRepository extends AbstractRepository
|
||||||
if ($config instanceof SystemConfig) {
|
if ($config instanceof SystemConfig) {
|
||||||
$config->setValue($value);
|
$config->setValue($value);
|
||||||
} else {
|
} else {
|
||||||
$config = new SystemConfig($key, $value);
|
$config = new SystemConfig();
|
||||||
|
$config->setKey($key);
|
||||||
|
$config->setValue($value);
|
||||||
}
|
}
|
||||||
$this->save($config);
|
$this->save($config);
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,7 +46,9 @@ readonly class ConfigurationService
|
||||||
throw new InvalidArgumentException("A configuration with the key '{$key->value}' already exists");
|
throw new InvalidArgumentException("A configuration with the key '{$key->value}' already exists");
|
||||||
}
|
}
|
||||||
|
|
||||||
$config = new SystemConfig($key, $value);
|
$config = new SystemConfig();
|
||||||
|
$config->setKey($key);
|
||||||
|
$config->setValue($value);
|
||||||
$this->systemConfigRepository->save($config);
|
$this->systemConfigRepository->save($config);
|
||||||
|
|
||||||
return $config;
|
return $config;
|
||||||
|
|
|
@ -75,10 +75,13 @@ readonly class DrinkTypeService
|
||||||
|
|
||||||
// If no desired stock is provided, use the default from configuration
|
// If no desired stock is provided, use the default from configuration
|
||||||
if ($desiredStock === null) {
|
if ($desiredStock === null) {
|
||||||
$desiredStock = (int) $this->configService->getConfigByKey(SystemSettingKey::DEFAULT_DESIRED_STOCK);
|
$desiredStock = (int) $this->configService->getConfigByKey(SystemSettingKey::DEFAULT_DESIRED_STOCK)->getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
$drinkType = new DrinkType($name, $description, $desiredStock);
|
$drinkType = new DrinkType();
|
||||||
|
$drinkType->setName($name);
|
||||||
|
$drinkType->setDescription($description);
|
||||||
|
$drinkType->setDesiredStock($desiredStock);
|
||||||
$this->drinkTypeRepository->save($drinkType);
|
$this->drinkTypeRepository->save($drinkType);
|
||||||
|
|
||||||
return $drinkType;
|
return $drinkType;
|
||||||
|
|
|
@ -51,7 +51,10 @@ readonly class InventoryService
|
||||||
{
|
{
|
||||||
$record = $this->inventoryRecordRepository->findLatestByDrinkType($drinkType);
|
$record = $this->inventoryRecordRepository->findLatestByDrinkType($drinkType);
|
||||||
if (!($record instanceof InventoryRecord)) {
|
if (!($record instanceof InventoryRecord)) {
|
||||||
return new InventoryRecord($drinkType, 0);
|
$record = new InventoryRecord();
|
||||||
|
$record->setDrinkType($drinkType);
|
||||||
|
$record->setQuantity(0);
|
||||||
|
$this->inventoryRecordRepository->save($record);
|
||||||
}
|
}
|
||||||
return $record;
|
return $record;
|
||||||
}
|
}
|
||||||
|
@ -83,7 +86,10 @@ readonly class InventoryService
|
||||||
int $quantity,
|
int $quantity,
|
||||||
DateTimeImmutable $timestamp = new DateTimeImmutable(),
|
DateTimeImmutable $timestamp = new DateTimeImmutable(),
|
||||||
): InventoryRecord {
|
): InventoryRecord {
|
||||||
$inventoryRecord = new InventoryRecord($drinkType, $quantity, $timestamp);
|
$inventoryRecord = new InventoryRecord();
|
||||||
|
$inventoryRecord->setDrinkType($drinkType);
|
||||||
|
$inventoryRecord->setQuantity($quantity);
|
||||||
|
$inventoryRecord->setTimestamp($timestamp);
|
||||||
|
|
||||||
$this->inventoryRecordRepository->save($inventoryRecord);
|
$this->inventoryRecordRepository->save($inventoryRecord);
|
||||||
|
|
||||||
|
@ -91,7 +97,7 @@ readonly class InventoryService
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return InventoryRecord[]
|
* @return DrinkStock[]
|
||||||
*/
|
*/
|
||||||
public function getAllDrinkTypesWithStockLevels(bool $includeZeroDesiredStock = false): array
|
public function getAllDrinkTypesWithStockLevels(bool $includeZeroDesiredStock = false): array
|
||||||
{
|
{
|
||||||
|
|
|
@ -124,7 +124,10 @@ readonly class OrderService
|
||||||
throw new InvalidArgumentException("Invalid drink type ID: {$item['drinkTypeId']}");
|
throw new InvalidArgumentException("Invalid drink type ID: {$item['drinkTypeId']}");
|
||||||
}
|
}
|
||||||
|
|
||||||
$orderItem = new OrderItem($drinkType, $item['quantity'], $order);
|
$orderItem = new OrderItem();
|
||||||
|
$orderItem->setDrinkType($drinkType);
|
||||||
|
$orderItem->setQuantity($item['quantity']);
|
||||||
|
$orderItem->setOrder($order);
|
||||||
$this->orderItemRepository->save($orderItem);
|
$this->orderItemRepository->save($orderItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -142,10 +145,10 @@ readonly class OrderService
|
||||||
$orderItems = [];
|
$orderItems = [];
|
||||||
|
|
||||||
foreach ($lowStockItems as $item) {
|
foreach ($lowStockItems as $item) {
|
||||||
if ($item['currentStock'] < $item['desiredStock']) {
|
if ($item->record->getQuantity() < $item->record->getDrinkType()->getDesiredStock()) {
|
||||||
$orderItems[] = [
|
$orderItems[] = [
|
||||||
'drinkTypeId' => $item['drinkType']->getId(),
|
'drinkTypeId' => $item->record->getDrinkType()->getId(),
|
||||||
'quantity' => $item['desiredStock'] - $item['currentStock'],
|
'quantity' => $item->record->getDrinkType()->getDesiredStock() - $item->record->getQuantity(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -178,7 +181,7 @@ readonly class OrderService
|
||||||
*/
|
*/
|
||||||
public function addOrderItem(Order $order, DrinkType $drinkType, int $quantity): OrderItem
|
public function addOrderItem(Order $order, DrinkType $drinkType, int $quantity): OrderItem
|
||||||
{
|
{
|
||||||
if (!in_array($order->getStatus(), [OrderStatus::STATUS_NEW, OrderStatus::STATUS_IN_WORK], true)) {
|
if (!in_array($order->getStatus(), [OrderStatus::NEW, OrderStatus::IN_WORK], true)) {
|
||||||
throw new InvalidArgumentException(
|
throw new InvalidArgumentException(
|
||||||
"Cannot add items to an order with status '{$order->getStatus()->value}'",
|
"Cannot add items to an order with status '{$order->getStatus()->value}'",
|
||||||
);
|
);
|
||||||
|
@ -194,7 +197,10 @@ readonly class OrderService
|
||||||
return $existingItem;
|
return $existingItem;
|
||||||
}
|
}
|
||||||
// Create a new item
|
// Create a new item
|
||||||
$orderItem = new OrderItem($drinkType, $quantity, $order);
|
$orderItem = new OrderItem();
|
||||||
|
$orderItem->setQuantity($quantity);
|
||||||
|
$orderItem->setOrder($order);
|
||||||
|
$orderItem->setDrinkType($drinkType);
|
||||||
$this->orderItemRepository->save($orderItem);
|
$this->orderItemRepository->save($orderItem);
|
||||||
return $orderItem;
|
return $orderItem;
|
||||||
}
|
}
|
||||||
|
@ -209,7 +215,7 @@ readonly class OrderService
|
||||||
*/
|
*/
|
||||||
public function removeOrderItem(Order $order, OrderItem $orderItem): void
|
public function removeOrderItem(Order $order, OrderItem $orderItem): void
|
||||||
{
|
{
|
||||||
if (!in_array($order->getStatus(), [OrderStatus::STATUS_NEW, OrderStatus::STATUS_IN_WORK], true)) {
|
if (!in_array($order->getStatus(), [OrderStatus::NEW, OrderStatus::IN_WORK], true)) {
|
||||||
throw new InvalidArgumentException(
|
throw new InvalidArgumentException(
|
||||||
"Cannot remove items from an order with status '{$order->getStatus()->value}'",
|
"Cannot remove items from an order with status '{$order->getStatus()->value}'",
|
||||||
);
|
);
|
||||||
|
|
15
src/ValueObject/StockAdjustmentProposal.php
Normal file
15
src/ValueObject/StockAdjustmentProposal.php
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\ValueObject;
|
||||||
|
|
||||||
|
use App\Entity\DrinkType;
|
||||||
|
|
||||||
|
final readonly class StockAdjustmentProposal
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
public DrinkType $drinkType,
|
||||||
|
public int $proposedStock,
|
||||||
|
) {}
|
||||||
|
}
|
15
symfony.lock
15
symfony.lock
|
@ -47,6 +47,21 @@
|
||||||
"phpstan.dist.neon"
|
"phpstan.dist.neon"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"phpunit/phpunit": {
|
||||||
|
"version": "11.5",
|
||||||
|
"recipe": {
|
||||||
|
"repo": "github.com/symfony/recipes",
|
||||||
|
"branch": "main",
|
||||||
|
"version": "11.1",
|
||||||
|
"ref": "c6658a60fc9d594805370eacdf542c3d6b5c0869"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
".env.test",
|
||||||
|
"phpunit.dist.xml",
|
||||||
|
"tests/bootstrap.php",
|
||||||
|
"bin/phpunit"
|
||||||
|
]
|
||||||
|
},
|
||||||
"symfony/console": {
|
"symfony/console": {
|
||||||
"version": "7.3",
|
"version": "7.3",
|
||||||
"recipe": {
|
"recipe": {
|
||||||
|
|
|
@ -5,6 +5,35 @@
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h1>Drink Inventory</h1>
|
<h1>Drink Inventory</h1>
|
||||||
|
{% if low is not same as([]) %}
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h5 class="card-title">Low Stock Alert</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<table class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Drink Name</th>
|
||||||
|
<th>Current Stock</th>
|
||||||
|
<th>Desired Stock</th>
|
||||||
|
<th>Status</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for lowStock in low %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ lowStock.record.drinkType.name }}</td>
|
||||||
|
<td>{{ lowStock.record.quantity }}</td>
|
||||||
|
<td>{{ lowStock.record.drinkType.desiredStock }}</td>
|
||||||
|
<td>{{ lowStock.stock.value|capitalize }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h5 class="card-title">Drink Inventory Overview</h5>
|
<h5 class="card-title">Drink Inventory Overview</h5>
|
||||||
|
@ -17,7 +46,7 @@
|
||||||
<th>Current Stock</th>
|
<th>Current Stock</th>
|
||||||
<th>Desired Stock</th>
|
<th>Desired Stock</th>
|
||||||
<th>Status</th>
|
<th>Status</th>
|
||||||
<th>Description</th>
|
<th>Actions</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
|
@ -35,7 +64,6 @@
|
||||||
<td>{{ drinkStock.record.quantity }}</td>
|
<td>{{ drinkStock.record.quantity }}</td>
|
||||||
<td>{{ drinkStock.record.drinkType.desiredStock }}</td>
|
<td>{{ drinkStock.record.drinkType.desiredStock }}</td>
|
||||||
<td>{{ drinkStock.stock.value|capitalize }}</td>
|
<td>{{ drinkStock.stock.value|capitalize }}</td>
|
||||||
<td>{{ drinkStock.record.drinkType.description }}</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|
36
tests/DbTestCase.php
Normal file
36
tests/DbTestCase.php
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Tests;
|
||||||
|
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Doctrine\ORM\Tools\SchemaTool;
|
||||||
|
use Tests\TestCase;
|
||||||
|
|
||||||
|
abstract class DbTestCase extends TestCase
|
||||||
|
{
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
||||||
|
$metadata = $em->getMetadataFactory()->getAllMetadata();
|
||||||
|
if (empty($metadata)) {
|
||||||
|
throw new \Exception('No metadata found. Did you forget to map entities?');
|
||||||
|
}
|
||||||
|
$schemaTool = new SchemaTool($em);
|
||||||
|
$schemaTool->dropDatabase(); // Clean slate, in case anything exists
|
||||||
|
$schemaTool->createSchema($metadata);
|
||||||
|
parent::setUp();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function tearDown(): void
|
||||||
|
{
|
||||||
|
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
||||||
|
$metadata = $em->getMetadataFactory()->getAllMetadata();
|
||||||
|
if (empty($metadata)) {
|
||||||
|
throw new \Exception('No metadata found. Did you forget to map entities?');
|
||||||
|
}
|
||||||
|
$schemaTool = new SchemaTool($em);
|
||||||
|
$schemaTool->dropDatabase(); // Clean slate, in case anything exists
|
||||||
|
parent::tearDown();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
17
tests/Feature/FeatureTestBase.php
Normal file
17
tests/Feature/FeatureTestBase.php
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
<?php declare(strict_types=1);
|
||||||
|
// tests/Feature/FeatureTestBootstrap.php
|
||||||
|
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||||
|
|
||||||
|
uses(KernelTestCase::class)->in(__DIR__);
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
$em = self::getContainer()->get(EntityManagerInterface::class);
|
||||||
|
createDatabaseSchema($em);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(function () {
|
||||||
|
$em = self::getContainer()->get(EntityManagerInterface::class);
|
||||||
|
deleteDatabaseFile($em);
|
||||||
|
});
|
86
tests/Feature/Service/Config/ConfigServicesTest.php
Normal file
86
tests/Feature/Service/Config/ConfigServicesTest.php
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Entity\SystemConfig;
|
||||||
|
use App\Enum\SystemSettingKey;
|
||||||
|
use App\Service\Config\AppName;
|
||||||
|
use App\Service\Config\LowStockMultiplier;
|
||||||
|
use App\Service\ConfigurationService;
|
||||||
|
|
||||||
|
test('AppName returns system name from configuration', function () {
|
||||||
|
// Arrange
|
||||||
|
$appName = $this->getContainer()->get(AppName::class);
|
||||||
|
$configService = $this->getContainer()->get(ConfigurationService::class);
|
||||||
|
$testSystemName = 'Test System Name';
|
||||||
|
|
||||||
|
// Set a custom system name
|
||||||
|
$configService->setConfigValue(SystemSettingKey::SYSTEM_NAME, $testSystemName);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$result = (string)$appName;
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect($result)->toBe($testSystemName);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('AppName returns default system name when not configured', function () {
|
||||||
|
// Arrange
|
||||||
|
$appName = $this->getContainer()->get(AppName::class);
|
||||||
|
$configService = $this->getContainer()->get(ConfigurationService::class);
|
||||||
|
|
||||||
|
// Reset to default value
|
||||||
|
$configService->setDefaultValue(SystemSettingKey::SYSTEM_NAME);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$result = (string)$appName;
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect($result)->toBe(SystemConfig::DEFAULT_SYSTEM_NAME);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('LowStockMultiplier returns multiplier from configuration', function () {
|
||||||
|
// Arrange
|
||||||
|
$lowStockMultiplier = $this->getContainer()->get(LowStockMultiplier::class);
|
||||||
|
$configService = $this->getContainer()->get(ConfigurationService::class);
|
||||||
|
$testMultiplier = '0.5';
|
||||||
|
|
||||||
|
// Set a custom multiplier
|
||||||
|
$configService->setConfigValue(SystemSettingKey::STOCK_LOW_MULTIPLIER, $testMultiplier);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$result = $lowStockMultiplier->getValue();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect($result)->toBe((float)$testMultiplier);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('LowStockMultiplier returns default multiplier when not configured', function () {
|
||||||
|
// Arrange
|
||||||
|
$lowStockMultiplier = $this->getContainer()->get(LowStockMultiplier::class);
|
||||||
|
$configService = $this->getContainer()->get(ConfigurationService::class);
|
||||||
|
|
||||||
|
// Reset to default value
|
||||||
|
$configService->setDefaultValue(SystemSettingKey::STOCK_LOW_MULTIPLIER);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$result = $lowStockMultiplier->getValue();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect($result)->toBe((float)SystemConfig::DEFAULT_STOCK_LOW_MULTIPLIER);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('LowStockMultiplier converts string value to float', function () {
|
||||||
|
// Arrange
|
||||||
|
$lowStockMultiplier = $this->getContainer()->get(LowStockMultiplier::class);
|
||||||
|
$configService = $this->getContainer()->get(ConfigurationService::class);
|
||||||
|
$testMultiplier = '0.75';
|
||||||
|
|
||||||
|
// Set a custom multiplier
|
||||||
|
$configService->setConfigValue(SystemSettingKey::STOCK_LOW_MULTIPLIER, $testMultiplier);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$result = $lowStockMultiplier->getValue();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect($result)->toBe(0.75);
|
||||||
|
expect($result)->toBeFloat();
|
||||||
|
});
|
147
tests/Feature/Service/ConfigurationServiceTest.php
Normal file
147
tests/Feature/Service/ConfigurationServiceTest.php
Normal file
|
@ -0,0 +1,147 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Entity\SystemConfig;
|
||||||
|
use App\Enum\SystemSettingKey;
|
||||||
|
use App\Repository\SystemConfigRepository;
|
||||||
|
use App\Service\ConfigurationService;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
|
||||||
|
test('getAllConfigs returns all configurations', function () {
|
||||||
|
// Arrange
|
||||||
|
$configService = $this->getContainer()->get(ConfigurationService::class);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$configs = $configService->getAllConfigs();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect($configs)->toBeArray();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('getConfigValue returns correct value', function () {
|
||||||
|
// Arrange
|
||||||
|
$configService = $this->getContainer()->get(ConfigurationService::class);
|
||||||
|
$key = SystemSettingKey::SYSTEM_NAME;
|
||||||
|
$expectedValue = SystemSettingKey::getDefaultValue($key);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$value = $configService->getConfigValue($key);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect($value)->toBe($expectedValue);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('setConfigValue updates configuration value', function () {
|
||||||
|
// Arrange
|
||||||
|
$configService = $this->getContainer()->get(ConfigurationService::class);
|
||||||
|
$key = SystemSettingKey::SYSTEM_NAME;
|
||||||
|
$newValue = 'Test System Name';
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$configService->setConfigValue($key, $newValue);
|
||||||
|
$value = $configService->getConfigValue($key);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect($value)->toBe($newValue);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('getConfigByKey returns correct config', function () {
|
||||||
|
// Arrange
|
||||||
|
$configService = $this->getContainer()->get(ConfigurationService::class);
|
||||||
|
$key = SystemSettingKey::SYSTEM_NAME;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$config = $configService->getConfigByKey($key);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect($config)->toBeInstanceOf(SystemConfig::class)
|
||||||
|
->and($config->getKey())->toBe($key);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('createConfig throws exception when config already exists', function () {
|
||||||
|
// Arrange
|
||||||
|
$configService = $this->getContainer()->get(ConfigurationService::class);
|
||||||
|
$key = SystemSettingKey::SYSTEM_NAME;
|
||||||
|
$value = 'Test System Name';
|
||||||
|
|
||||||
|
// Ensure config exists
|
||||||
|
$configService->setConfigValue($key, $value);
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
expect(fn() => $configService->createConfig($key, $value))
|
||||||
|
->toThrow(InvalidArgumentException::class);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('updateConfig updates configuration value', function () {
|
||||||
|
// Arrange
|
||||||
|
$configService = $this->getContainer()->get(ConfigurationService::class);
|
||||||
|
$key = SystemSettingKey::SYSTEM_NAME;
|
||||||
|
$initialValue = 'Initial System Name';
|
||||||
|
$newValue = 'Updated System Name';
|
||||||
|
|
||||||
|
// Create or update config with initial value
|
||||||
|
$configService->setConfigValue($key, $initialValue);
|
||||||
|
$config = $configService->getConfigByKey($key);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$updatedConfig = $configService->updateConfig($config, $newValue);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect($updatedConfig->getValue())->toBe($newValue)
|
||||||
|
->and($configService->getConfigValue($key))->toBe($newValue);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('updateConfig does not update when value is empty', function () {
|
||||||
|
// Arrange
|
||||||
|
$configService = $this->getContainer()->get(ConfigurationService::class);
|
||||||
|
$key = SystemSettingKey::SYSTEM_NAME;
|
||||||
|
$initialValue = 'Initial System Name';
|
||||||
|
|
||||||
|
// Create or update config with initial value
|
||||||
|
$configService->setConfigValue($key, $initialValue);
|
||||||
|
$config = $configService->getConfigByKey($key);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$updatedConfig = $configService->updateConfig($config, '');
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect($updatedConfig->getValue())->toBe($initialValue);
|
||||||
|
expect($configService->getConfigValue($key))->toBe($initialValue);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('resetAllConfigs resets all configurations to default values', function () {
|
||||||
|
// Arrange
|
||||||
|
$configService = $this->getContainer()->get(ConfigurationService::class);
|
||||||
|
|
||||||
|
// Set non-default values for all configs
|
||||||
|
foreach (SystemSettingKey::cases() as $key) {
|
||||||
|
$configService->setConfigValue($key, 'non-default-value');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$configService->resetAllConfigs();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
foreach (SystemSettingKey::cases() as $key) {
|
||||||
|
$expectedValue = SystemSettingKey::getDefaultValue($key);
|
||||||
|
$actualValue = $configService->getConfigValue($key);
|
||||||
|
expect($actualValue)->toBe($expectedValue);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('setDefaultValue sets default value for specific key', function () {
|
||||||
|
// Arrange
|
||||||
|
$configService = $this->getContainer()->get(ConfigurationService::class);
|
||||||
|
$key = SystemSettingKey::SYSTEM_NAME;
|
||||||
|
$nonDefaultValue = 'Non-Default System Name';
|
||||||
|
|
||||||
|
// Set non-default value
|
||||||
|
$configService->setConfigValue($key, $nonDefaultValue);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$configService->setDefaultValue($key);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
$expectedValue = SystemSettingKey::getDefaultValue($key);
|
||||||
|
$actualValue = $configService->getConfigValue($key);
|
||||||
|
expect($actualValue)->toBe($expectedValue);
|
||||||
|
});
|
242
tests/Feature/Service/DrinkTypeServiceTest.php
Normal file
242
tests/Feature/Service/DrinkTypeServiceTest.php
Normal file
|
@ -0,0 +1,242 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Entity\DrinkType;
|
||||||
|
use App\Enum\SystemSettingKey;
|
||||||
|
use App\Repository\DrinkTypeRepository;
|
||||||
|
use App\Service\ConfigurationService;
|
||||||
|
use App\Service\DrinkTypeService;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
|
||||||
|
test('getAllDrinkTypes returns all drink types', function () {
|
||||||
|
// Arrange
|
||||||
|
$drinkTypeService = $this->getContainer()->get(DrinkTypeService::class);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$drinkTypes = $drinkTypeService->getAllDrinkTypes();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect($drinkTypes)->toBeArray();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('getDrinkTypeById returns correct drink type', function () {
|
||||||
|
// Arrange
|
||||||
|
$drinkTypeService = $this->getContainer()->get(DrinkTypeService::class);
|
||||||
|
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
||||||
|
|
||||||
|
// Create a drink type
|
||||||
|
$drinkType = new DrinkType();
|
||||||
|
$drinkType->setName('Test Drink Type');
|
||||||
|
$drinkType->setDescription('Test Description');
|
||||||
|
$drinkType->setDesiredStock(5);
|
||||||
|
$em->persist($drinkType);
|
||||||
|
$em->flush();
|
||||||
|
|
||||||
|
$id = $drinkType->getId();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$retrievedDrinkType = $drinkTypeService->getDrinkTypeById($id);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect($retrievedDrinkType)->toBeInstanceOf(DrinkType::class);
|
||||||
|
expect($retrievedDrinkType->getId())->toBe($id);
|
||||||
|
expect($retrievedDrinkType->getName())->toBe('Test Drink Type');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('getDrinkTypeById returns null for non-existent id', function () {
|
||||||
|
// Arrange
|
||||||
|
$drinkTypeService = $this->getContainer()->get(DrinkTypeService::class);
|
||||||
|
$nonExistentId = 9999;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$drinkType = $drinkTypeService->getDrinkTypeById($nonExistentId);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect($drinkType)->toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('getDrinkTypeByName returns correct drink type', function () {
|
||||||
|
// Arrange
|
||||||
|
$drinkTypeService = $this->getContainer()->get(DrinkTypeService::class);
|
||||||
|
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
||||||
|
|
||||||
|
// Create a drink type
|
||||||
|
$drinkType = new DrinkType();
|
||||||
|
$drinkType->setName('Test Drink Type By Name');
|
||||||
|
$drinkType->setDescription('Test Description');
|
||||||
|
$drinkType->setDesiredStock(5);
|
||||||
|
$em->persist($drinkType);
|
||||||
|
$em->flush();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$retrievedDrinkType = $drinkTypeService->getDrinkTypeByName('Test Drink Type By Name');
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect($retrievedDrinkType)->toBeInstanceOf(DrinkType::class);
|
||||||
|
expect($retrievedDrinkType->getName())->toBe('Test Drink Type By Name');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('getDrinkTypeByName returns null for non-existent name', function () {
|
||||||
|
// Arrange
|
||||||
|
$drinkTypeService = $this->getContainer()->get(DrinkTypeService::class);
|
||||||
|
$nonExistentName = 'Non-Existent Drink Type';
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$drinkType = $drinkTypeService->getDrinkTypeByName($nonExistentName);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect($drinkType)->toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('createDrinkType creates new drink type with provided values', function () {
|
||||||
|
// Arrange
|
||||||
|
$drinkTypeService = $this->getContainer()->get(DrinkTypeService::class);
|
||||||
|
$name = 'New Drink Type';
|
||||||
|
$description = 'New Description';
|
||||||
|
$desiredStock = 10;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$drinkType = $drinkTypeService->createDrinkType($name, $description, $desiredStock);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect($drinkType)->toBeInstanceOf(DrinkType::class);
|
||||||
|
expect($drinkType->getName())->toBe($name);
|
||||||
|
expect($drinkType->getDescription())->toBe($description);
|
||||||
|
expect($drinkType->getDesiredStock())->toBe($desiredStock);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('createDrinkType creates new drink type with default desired stock', function () {
|
||||||
|
// Arrange
|
||||||
|
$drinkTypeService = $this->getContainer()->get(DrinkTypeService::class);
|
||||||
|
$configService = $this->getContainer()->get(ConfigurationService::class);
|
||||||
|
$name = 'New Drink Type Default Stock';
|
||||||
|
$description = 'New Description';
|
||||||
|
|
||||||
|
// Set default desired stock in configuration
|
||||||
|
$defaultDesiredStock = '15';
|
||||||
|
$configService->setConfigValue(SystemSettingKey::DEFAULT_DESIRED_STOCK, $defaultDesiredStock);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$drinkType = $drinkTypeService->createDrinkType($name, $description);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect($drinkType)->toBeInstanceOf(DrinkType::class);
|
||||||
|
expect($drinkType->getName())->toBe($name);
|
||||||
|
expect($drinkType->getDescription())->toBe($description);
|
||||||
|
expect($drinkType->getDesiredStock())->toBe((int)$defaultDesiredStock);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('createDrinkType throws exception when drink type with same name exists', function () {
|
||||||
|
// Arrange
|
||||||
|
$drinkTypeService = $this->getContainer()->get(DrinkTypeService::class);
|
||||||
|
$name = 'Duplicate Drink Type';
|
||||||
|
|
||||||
|
// Create a drink type with the same name
|
||||||
|
$drinkTypeService->createDrinkType($name);
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
expect(fn() => $drinkTypeService->createDrinkType($name))
|
||||||
|
->toThrow(InvalidArgumentException::class);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('updateDrinkType updates drink type properties', function () {
|
||||||
|
// Arrange
|
||||||
|
$drinkTypeService = $this->getContainer()->get(DrinkTypeService::class);
|
||||||
|
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
||||||
|
|
||||||
|
// Create a drink type
|
||||||
|
$drinkType = new DrinkType();
|
||||||
|
$drinkType->setName('Original Drink Type');
|
||||||
|
$drinkType->setDescription('Original Description');
|
||||||
|
$drinkType->setDesiredStock(5);
|
||||||
|
$em->persist($drinkType);
|
||||||
|
$em->flush();
|
||||||
|
|
||||||
|
$newName = 'Updated Drink Type';
|
||||||
|
$newDescription = 'Updated Description';
|
||||||
|
$newDesiredStock = 15;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$updatedDrinkType = $drinkTypeService->updateDrinkType(
|
||||||
|
$drinkType,
|
||||||
|
$newName,
|
||||||
|
$newDescription,
|
||||||
|
$newDesiredStock
|
||||||
|
);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect($updatedDrinkType)->toBeInstanceOf(DrinkType::class);
|
||||||
|
expect($updatedDrinkType->getName())->toBe($newName);
|
||||||
|
expect($updatedDrinkType->getDescription())->toBe($newDescription);
|
||||||
|
expect($updatedDrinkType->getDesiredStock())->toBe($newDesiredStock);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('updateDrinkType throws exception when updating to existing name', function () {
|
||||||
|
// Arrange
|
||||||
|
$drinkTypeService = $this->getContainer()->get(DrinkTypeService::class);
|
||||||
|
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
||||||
|
|
||||||
|
// Create two drink types
|
||||||
|
$drinkType1 = new DrinkType();
|
||||||
|
$drinkType1->setName('First Drink Type');
|
||||||
|
$em->persist($drinkType1);
|
||||||
|
|
||||||
|
$drinkType2 = new DrinkType();
|
||||||
|
$drinkType2->setName('Second Drink Type');
|
||||||
|
$em->persist($drinkType2);
|
||||||
|
$em->flush();
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
expect(fn() => $drinkTypeService->updateDrinkType($drinkType2, 'First Drink Type'))
|
||||||
|
->toThrow(InvalidArgumentException::class);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('updateDrinkType only updates provided properties', function () {
|
||||||
|
// Arrange
|
||||||
|
$drinkTypeService = $this->getContainer()->get(DrinkTypeService::class);
|
||||||
|
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
||||||
|
|
||||||
|
// Create a drink type
|
||||||
|
$drinkType = new DrinkType();
|
||||||
|
$drinkType->setName('Partial Update Drink Type');
|
||||||
|
$drinkType->setDescription('Original Description');
|
||||||
|
$drinkType->setDesiredStock(5);
|
||||||
|
$em->persist($drinkType);
|
||||||
|
$em->flush();
|
||||||
|
|
||||||
|
$newDescription = 'Updated Description';
|
||||||
|
|
||||||
|
// Act - only update description
|
||||||
|
$updatedDrinkType = $drinkTypeService->updateDrinkType(
|
||||||
|
$drinkType,
|
||||||
|
null,
|
||||||
|
$newDescription,
|
||||||
|
null
|
||||||
|
);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect($updatedDrinkType)->toBeInstanceOf(DrinkType::class);
|
||||||
|
expect($updatedDrinkType->getName())->toBe('Partial Update Drink Type');
|
||||||
|
expect($updatedDrinkType->getDescription())->toBe($newDescription);
|
||||||
|
expect($updatedDrinkType->getDesiredStock())->toBe(5);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('deleteDrinkType removes drink type', function () {
|
||||||
|
// Arrange
|
||||||
|
$drinkTypeService = $this->getContainer()->get(DrinkTypeService::class);
|
||||||
|
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
||||||
|
|
||||||
|
// Create a drink type
|
||||||
|
$drinkType = new DrinkType();
|
||||||
|
$drinkType->setName('Drink Type To Delete');
|
||||||
|
$em->persist($drinkType);
|
||||||
|
$em->flush();
|
||||||
|
|
||||||
|
$id = $drinkType->getId();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$drinkTypeService->deleteDrinkType($drinkType);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
$deletedDrinkType = $drinkTypeService->getDrinkTypeById($id);
|
||||||
|
expect($deletedDrinkType)->toBeNull();
|
||||||
|
});
|
364
tests/Feature/Service/InventoryServiceTest.php
Normal file
364
tests/Feature/Service/InventoryServiceTest.php
Normal file
|
@ -0,0 +1,364 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Entity\DrinkType;
|
||||||
|
use App\Entity\InventoryRecord;
|
||||||
|
use App\Enum\StockState;
|
||||||
|
use App\Enum\SystemSettingKey;
|
||||||
|
use App\Repository\DrinkTypeRepository;
|
||||||
|
use App\Repository\InventoryRecordRepository;
|
||||||
|
use App\Service\ConfigurationService;
|
||||||
|
use App\Service\InventoryService;
|
||||||
|
use App\ValueObject\DrinkStock;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
|
||||||
|
test('getAllInventoryRecords returns all inventory records', function () {
|
||||||
|
// Arrange
|
||||||
|
$inventoryService = $this->getContainer()->get(InventoryService::class);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$records = $inventoryService->getAllInventoryRecords();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect($records)->toBeArray();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('getInventoryRecordsByDrinkType returns records for specific drink type', function () {
|
||||||
|
// Arrange
|
||||||
|
$inventoryService = $this->getContainer()->get(InventoryService::class);
|
||||||
|
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
||||||
|
|
||||||
|
// Create a drink type
|
||||||
|
$drinkType = new DrinkType();
|
||||||
|
$drinkType->setName('Test Drink Type for Inventory');
|
||||||
|
$drinkType->setDesiredStock(10);
|
||||||
|
$em->persist($drinkType);
|
||||||
|
$em->flush();
|
||||||
|
|
||||||
|
// Create inventory records
|
||||||
|
$record1 = new InventoryRecord();
|
||||||
|
$record1->setDrinkType($drinkType);
|
||||||
|
$record1->setQuantity(5);
|
||||||
|
$em->persist($record1);
|
||||||
|
|
||||||
|
$record2 = new InventoryRecord();
|
||||||
|
$record2->setDrinkType($drinkType);
|
||||||
|
$record2->setQuantity(8);
|
||||||
|
$em->persist($record2);
|
||||||
|
$em->flush();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$records = $inventoryService->getInventoryRecordsByDrinkType($drinkType);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect($records)->toBeArray();
|
||||||
|
expect(count($records))->toBeGreaterThanOrEqual(2);
|
||||||
|
expect($records[0])->toBeInstanceOf(InventoryRecord::class);
|
||||||
|
expect($records[0]->getDrinkType()->getId())->toBe($drinkType->getId());
|
||||||
|
});
|
||||||
|
|
||||||
|
test('getLatestInventoryRecord returns latest record for drink type', function () {
|
||||||
|
// Arrange
|
||||||
|
$inventoryService = $this->getContainer()->get(InventoryService::class);
|
||||||
|
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
||||||
|
|
||||||
|
// Create a drink type
|
||||||
|
$drinkType = new DrinkType();
|
||||||
|
$drinkType->setName('Test Drink Type for Latest Record');
|
||||||
|
$drinkType->setDesiredStock(10);
|
||||||
|
$em->persist($drinkType);
|
||||||
|
$em->flush();
|
||||||
|
|
||||||
|
// Create inventory records with different timestamps
|
||||||
|
$record1 = new InventoryRecord();
|
||||||
|
$record1->setDrinkType($drinkType);
|
||||||
|
$record1->setQuantity(5);
|
||||||
|
$record1->setTimestamp(new DateTimeImmutable('-2 days'));
|
||||||
|
$em->persist($record1);
|
||||||
|
|
||||||
|
$record2 = new InventoryRecord();
|
||||||
|
$record2->setDrinkType($drinkType);
|
||||||
|
$record2->setQuantity(8);
|
||||||
|
$record2->setTimestamp(new DateTimeImmutable('-1 day'));
|
||||||
|
$em->persist($record2);
|
||||||
|
|
||||||
|
$record3 = new InventoryRecord();
|
||||||
|
$record3->setDrinkType($drinkType);
|
||||||
|
$record3->setQuantity(12);
|
||||||
|
$record3->setTimestamp(new DateTimeImmutable());
|
||||||
|
$em->persist($record3);
|
||||||
|
$em->flush();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$latestRecord = $inventoryService->getLatestInventoryRecord($drinkType);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect($latestRecord)->toBeInstanceOf(InventoryRecord::class);
|
||||||
|
expect($latestRecord->getDrinkType()->getId())->toBe($drinkType->getId());
|
||||||
|
expect($latestRecord->getQuantity())->toBe(12);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('getLatestInventoryRecord creates new record if none exists', function () {
|
||||||
|
// Arrange
|
||||||
|
$inventoryService = $this->getContainer()->get(InventoryService::class);
|
||||||
|
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
||||||
|
$repository = $this->getContainer()->get(InventoryRecordRepository::class);
|
||||||
|
|
||||||
|
// Create a drink type with no inventory records
|
||||||
|
$drinkType = new DrinkType();
|
||||||
|
$drinkType->setName('Test Drink Type with No Records');
|
||||||
|
$drinkType->setDesiredStock(10);
|
||||||
|
$em->persist($drinkType);
|
||||||
|
$em->flush();
|
||||||
|
|
||||||
|
// Delete any existing records for this drink type
|
||||||
|
foreach ($repository->findByDrinkType($drinkType) as $record) {
|
||||||
|
$em->remove($record);
|
||||||
|
}
|
||||||
|
$em->flush();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$latestRecord = $inventoryService->getLatestInventoryRecord($drinkType);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect($latestRecord)->toBeInstanceOf(InventoryRecord::class);
|
||||||
|
expect($latestRecord->getDrinkType()->getId())->toBe($drinkType->getId());
|
||||||
|
expect($latestRecord->getQuantity())->toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('getCurrentStockLevel returns correct stock level', function () {
|
||||||
|
// Arrange
|
||||||
|
$inventoryService = $this->getContainer()->get(InventoryService::class);
|
||||||
|
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
||||||
|
|
||||||
|
// Create a drink type
|
||||||
|
$drinkType = new DrinkType();
|
||||||
|
$drinkType->setName('Test Drink Type for Stock Level');
|
||||||
|
$drinkType->setDesiredStock(10);
|
||||||
|
$em->persist($drinkType);
|
||||||
|
$em->flush();
|
||||||
|
|
||||||
|
// Create inventory record
|
||||||
|
$record = new InventoryRecord();
|
||||||
|
$record->setDrinkType($drinkType);
|
||||||
|
$record->setQuantity(15);
|
||||||
|
$em->persist($record);
|
||||||
|
$em->flush();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$stockLevel = $inventoryService->getCurrentStockLevel($drinkType);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect($stockLevel)->toBe(15);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('updateStockLevel creates new inventory record', function () {
|
||||||
|
// Arrange
|
||||||
|
$inventoryService = $this->getContainer()->get(InventoryService::class);
|
||||||
|
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
||||||
|
|
||||||
|
// Create a drink type
|
||||||
|
$drinkType = new DrinkType();
|
||||||
|
$drinkType->setName('Test Drink Type for Update');
|
||||||
|
$drinkType->setDesiredStock(10);
|
||||||
|
$em->persist($drinkType);
|
||||||
|
$em->flush();
|
||||||
|
|
||||||
|
$newQuantity = 25;
|
||||||
|
$timestamp = new DateTimeImmutable();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$record = $inventoryService->updateStockLevel($drinkType, $newQuantity, $timestamp);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect($record)->toBeInstanceOf(InventoryRecord::class);
|
||||||
|
expect($record->getDrinkType()->getId())->toBe($drinkType->getId());
|
||||||
|
expect($record->getQuantity())->toBe($newQuantity);
|
||||||
|
expect($record->getTimestamp()->getTimestamp())->toBe($timestamp->getTimestamp());
|
||||||
|
|
||||||
|
// Verify the stock level was updated
|
||||||
|
$currentLevel = $inventoryService->getCurrentStockLevel($drinkType);
|
||||||
|
expect($currentLevel)->toBe($newQuantity);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('getAllDrinkTypesWithStockLevels returns all drink types with stock', function () {
|
||||||
|
// Arrange
|
||||||
|
$inventoryService = $this->getContainer()->get(InventoryService::class);
|
||||||
|
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
||||||
|
$drinkTypeRepo = $this->getContainer()->get(DrinkTypeRepository::class);
|
||||||
|
|
||||||
|
// Create drink types and inventory records
|
||||||
|
$drinkType1 = new DrinkType();
|
||||||
|
$drinkType1->setName('Drink Type 1 for Stock Levels');
|
||||||
|
$drinkType1->setDesiredStock(10);
|
||||||
|
$em->persist($drinkType1);
|
||||||
|
|
||||||
|
$drinkType2 = new DrinkType();
|
||||||
|
$drinkType2->setName('Drink Type 2 for Stock Levels');
|
||||||
|
$drinkType2->setDesiredStock(0); // Zero desired stock
|
||||||
|
$em->persist($drinkType2);
|
||||||
|
$em->flush();
|
||||||
|
|
||||||
|
// Create inventory records
|
||||||
|
$record1 = new InventoryRecord();
|
||||||
|
$record1->setDrinkType($drinkType1);
|
||||||
|
$record1->setQuantity(5);
|
||||||
|
$em->persist($record1);
|
||||||
|
|
||||||
|
$record2 = new InventoryRecord();
|
||||||
|
$record2->setDrinkType($drinkType2);
|
||||||
|
$record2->setQuantity(8);
|
||||||
|
$em->persist($record2);
|
||||||
|
$em->flush();
|
||||||
|
|
||||||
|
// Act - without zero desired stock
|
||||||
|
$stockLevels1 = $inventoryService->getAllDrinkTypesWithStockLevels(false);
|
||||||
|
|
||||||
|
// Act - with zero desired stock
|
||||||
|
$stockLevels2 = $inventoryService->getAllDrinkTypesWithStockLevels(true);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect($stockLevels1)->toBeArray();
|
||||||
|
expect($stockLevels2)->toBeArray();
|
||||||
|
expect(count($stockLevels2))->toBeGreaterThanOrEqual(count($stockLevels1));
|
||||||
|
|
||||||
|
foreach ($stockLevels2 as $stockLevel) {
|
||||||
|
expect($stockLevel)->toBeInstanceOf(DrinkStock::class);
|
||||||
|
expect($stockLevel->record)->toBeInstanceOf(InventoryRecord::class);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('getDrinkStock returns correct DrinkStock object with CRITICAL state', function () {
|
||||||
|
// Arrange
|
||||||
|
$inventoryService = $this->getContainer()->get(InventoryService::class);
|
||||||
|
$configService = $this->getContainer()->get(ConfigurationService::class);
|
||||||
|
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
||||||
|
|
||||||
|
// Set low stock multiplier
|
||||||
|
$configService->setConfigValue(SystemSettingKey::STOCK_LOW_MULTIPLIER, '0.3');
|
||||||
|
|
||||||
|
// Create a drink type with zero quantity (CRITICAL)
|
||||||
|
$drinkType = new DrinkType();
|
||||||
|
$drinkType->setName('Critical Stock Drink Type');
|
||||||
|
$drinkType->setDesiredStock(10);
|
||||||
|
$em->persist($drinkType);
|
||||||
|
$em->flush();
|
||||||
|
|
||||||
|
// Create inventory record with zero quantity
|
||||||
|
$record = new InventoryRecord();
|
||||||
|
$record->setDrinkType($drinkType);
|
||||||
|
$record->setQuantity(0);
|
||||||
|
$em->persist($record);
|
||||||
|
$em->flush();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$drinkStock = $inventoryService->getDrinkStock($drinkType);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect($drinkStock)->toBeInstanceOf(DrinkStock::class);
|
||||||
|
expect($drinkStock->record->getDrinkType()->getId())->toBe($drinkType->getId());
|
||||||
|
expect($drinkStock->stock)->toBe(StockState::CRITICAL);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('getDrinkStock returns correct DrinkStock object with LOW state', function () {
|
||||||
|
// Arrange
|
||||||
|
$inventoryService = $this->getContainer()->get(InventoryService::class);
|
||||||
|
$configService = $this->getContainer()->get(ConfigurationService::class);
|
||||||
|
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
||||||
|
|
||||||
|
// Set low stock multiplier
|
||||||
|
$lowStockMultiplier = 0.3;
|
||||||
|
$configService->setConfigValue(SystemSettingKey::STOCK_LOW_MULTIPLIER, (string)$lowStockMultiplier);
|
||||||
|
|
||||||
|
// Create a drink type with low quantity
|
||||||
|
$desiredStock = 10;
|
||||||
|
$drinkType = new DrinkType();
|
||||||
|
$drinkType->setName('Low Stock Drink Type');
|
||||||
|
$drinkType->setDesiredStock($desiredStock);
|
||||||
|
$em->persist($drinkType);
|
||||||
|
$em->flush();
|
||||||
|
|
||||||
|
// Create inventory record with low quantity (between 0 and lowStockMultiplier * desiredStock)
|
||||||
|
$lowQuantity = (int)($desiredStock * $lowStockMultiplier) - 1;
|
||||||
|
$record = new InventoryRecord();
|
||||||
|
$record->setDrinkType($drinkType);
|
||||||
|
$record->setQuantity($lowQuantity);
|
||||||
|
$em->persist($record);
|
||||||
|
$em->flush();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$drinkStock = $inventoryService->getDrinkStock($drinkType);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect($drinkStock)->toBeInstanceOf(DrinkStock::class);
|
||||||
|
expect($drinkStock->record->getDrinkType()->getId())->toBe($drinkType->getId());
|
||||||
|
expect($drinkStock->stock)->toBe(StockState::LOW);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('getDrinkStock returns correct DrinkStock object with NORMAL state', function () {
|
||||||
|
// Arrange
|
||||||
|
$inventoryService = $this->getContainer()->get(InventoryService::class);
|
||||||
|
$configService = $this->getContainer()->get(ConfigurationService::class);
|
||||||
|
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
||||||
|
|
||||||
|
// Set low stock multiplier
|
||||||
|
$lowStockMultiplier = 0.3;
|
||||||
|
$configService->setConfigValue(SystemSettingKey::STOCK_LOW_MULTIPLIER, (string)$lowStockMultiplier);
|
||||||
|
|
||||||
|
// Create a drink type with normal quantity
|
||||||
|
$desiredStock = 10;
|
||||||
|
$drinkType = new DrinkType();
|
||||||
|
$drinkType->setName('Normal Stock Drink Type');
|
||||||
|
$drinkType->setDesiredStock($desiredStock);
|
||||||
|
$em->persist($drinkType);
|
||||||
|
$em->flush();
|
||||||
|
|
||||||
|
// Create inventory record with normal quantity (between lowStockMultiplier * desiredStock and desiredStock)
|
||||||
|
$normalQuantity = (int)($desiredStock * $lowStockMultiplier) + 1;
|
||||||
|
$record = new InventoryRecord();
|
||||||
|
$record->setDrinkType($drinkType);
|
||||||
|
$record->setQuantity($normalQuantity);
|
||||||
|
$em->persist($record);
|
||||||
|
$em->flush();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$drinkStock = $inventoryService->getDrinkStock($drinkType);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect($drinkStock)->toBeInstanceOf(DrinkStock::class);
|
||||||
|
expect($drinkStock->record->getDrinkType()->getId())->toBe($drinkType->getId());
|
||||||
|
expect($drinkStock->stock)->toBe(StockState::NORMAL);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('getDrinkStock returns correct DrinkStock object with HIGH state', function () {
|
||||||
|
// Arrange
|
||||||
|
$inventoryService = $this->getContainer()->get(InventoryService::class);
|
||||||
|
$configService = $this->getContainer()->get(ConfigurationService::class);
|
||||||
|
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
||||||
|
|
||||||
|
// Set low stock multiplier
|
||||||
|
$configService->setConfigValue(SystemSettingKey::STOCK_LOW_MULTIPLIER, '0.3');
|
||||||
|
|
||||||
|
// Create a drink type with high quantity
|
||||||
|
$desiredStock = 10;
|
||||||
|
$drinkType = new DrinkType();
|
||||||
|
$drinkType->setName('High Stock Drink Type');
|
||||||
|
$drinkType->setDesiredStock($desiredStock);
|
||||||
|
$em->persist($drinkType);
|
||||||
|
$em->flush();
|
||||||
|
|
||||||
|
// Create inventory record with high quantity (greater than desiredStock)
|
||||||
|
$highQuantity = $desiredStock + 1;
|
||||||
|
$record = new InventoryRecord();
|
||||||
|
$record->setDrinkType($drinkType);
|
||||||
|
$record->setQuantity($highQuantity);
|
||||||
|
$em->persist($record);
|
||||||
|
$em->flush();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$drinkStock = $inventoryService->getDrinkStock($drinkType);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect($drinkStock)->toBeInstanceOf(DrinkStock::class);
|
||||||
|
expect($drinkStock->record->getDrinkType()->getId())->toBe($drinkType->getId());
|
||||||
|
expect($drinkStock->stock)->toBe(StockState::HIGH);
|
||||||
|
});
|
455
tests/Feature/Service/OrderServiceTest.php
Normal file
455
tests/Feature/Service/OrderServiceTest.php
Normal file
|
@ -0,0 +1,455 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Entity\DrinkType;
|
||||||
|
use App\Entity\Order;
|
||||||
|
use App\Entity\OrderItem;
|
||||||
|
use App\Enum\OrderStatus;
|
||||||
|
use App\Repository\DrinkTypeRepository;
|
||||||
|
use App\Repository\OrderItemRepository;
|
||||||
|
use App\Repository\OrderRepository;
|
||||||
|
use App\Service\InventoryService;
|
||||||
|
use App\Service\OrderService;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
|
||||||
|
test('getAllOrders returns all orders', function () {
|
||||||
|
// Arrange
|
||||||
|
$orderService = $this->getContainer()->get(OrderService::class);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$orders = $orderService->getAllOrders();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect($orders)->toBeArray();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('getOrderById returns correct order', function () {
|
||||||
|
// Arrange
|
||||||
|
$orderService = $this->getContainer()->get(OrderService::class);
|
||||||
|
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
||||||
|
|
||||||
|
// Create an order
|
||||||
|
$order = new Order();
|
||||||
|
$order->setStatus(OrderStatus::NEW);
|
||||||
|
$em->persist($order);
|
||||||
|
$em->flush();
|
||||||
|
|
||||||
|
$id = $order->getId();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$retrievedOrder = $orderService->getOrderById($id);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect($retrievedOrder)->toBeInstanceOf(Order::class);
|
||||||
|
expect($retrievedOrder->getId())->toBe($id);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('getOrderById returns null for non-existent id', function () {
|
||||||
|
// Arrange
|
||||||
|
$orderService = $this->getContainer()->get(OrderService::class);
|
||||||
|
$nonExistentId = 9999;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$order = $orderService->getOrderById($nonExistentId);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect($order)->toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('getOrdersByStatus returns orders with specific status', function () {
|
||||||
|
// Arrange
|
||||||
|
$orderService = $this->getContainer()->get(OrderService::class);
|
||||||
|
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
||||||
|
|
||||||
|
// Create orders with different statuses
|
||||||
|
$order1 = new Order();
|
||||||
|
$order1->setStatus(OrderStatus::NEW);
|
||||||
|
$em->persist($order1);
|
||||||
|
|
||||||
|
$order2 = new Order();
|
||||||
|
$order2->setStatus(OrderStatus::ORDERED);
|
||||||
|
$em->persist($order2);
|
||||||
|
|
||||||
|
$order3 = new Order();
|
||||||
|
$order3->setStatus(OrderStatus::NEW);
|
||||||
|
$em->persist($order3);
|
||||||
|
$em->flush();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$newOrders = $orderService->getOrdersByStatus(OrderStatus::NEW);
|
||||||
|
$orderedOrders = $orderService->getOrdersByStatus(OrderStatus::ORDERED);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect($newOrders)->toBeArray();
|
||||||
|
expect(count($newOrders))->toBeGreaterThanOrEqual(2);
|
||||||
|
expect($orderedOrders)->toBeArray();
|
||||||
|
expect(count($orderedOrders))->toBeGreaterThanOrEqual(1);
|
||||||
|
|
||||||
|
foreach ($newOrders as $order) {
|
||||||
|
expect($order)->toBeInstanceOf(Order::class);
|
||||||
|
expect($order->getStatus())->toBe(OrderStatus::NEW);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($orderedOrders as $order) {
|
||||||
|
expect($order)->toBeInstanceOf(Order::class);
|
||||||
|
expect($order->getStatus())->toBe(OrderStatus::ORDERED);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('getActiveOrders returns orders with active statuses', function () {
|
||||||
|
// Arrange
|
||||||
|
$orderService = $this->getContainer()->get(OrderService::class);
|
||||||
|
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
||||||
|
|
||||||
|
// Create orders with different statuses
|
||||||
|
$order1 = new Order();
|
||||||
|
$order1->setStatus(OrderStatus::NEW);
|
||||||
|
$em->persist($order1);
|
||||||
|
|
||||||
|
$order2 = new Order();
|
||||||
|
$order2->setStatus(OrderStatus::ORDERED);
|
||||||
|
$em->persist($order2);
|
||||||
|
|
||||||
|
$order3 = new Order();
|
||||||
|
$order3->setStatus(OrderStatus::FULFILLED);
|
||||||
|
$em->persist($order3);
|
||||||
|
|
||||||
|
$order4 = new Order();
|
||||||
|
$order4->setStatus(OrderStatus::CANCELLED);
|
||||||
|
$em->persist($order4);
|
||||||
|
$em->flush();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$activeOrders = $orderService->getActiveOrders();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect($activeOrders)->toBeArray();
|
||||||
|
|
||||||
|
foreach ($activeOrders as $order) {
|
||||||
|
expect($order)->toBeInstanceOf(Order::class);
|
||||||
|
expect($order->getStatus())->toBeIn([OrderStatus::NEW, OrderStatus::ORDERED, OrderStatus::IN_WORK]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('getMostRecentActiveOrder returns most recent active order', function () {
|
||||||
|
// Arrange
|
||||||
|
$orderService = $this->getContainer()->get(OrderService::class);
|
||||||
|
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
||||||
|
|
||||||
|
// Create orders with different statuses and timestamps
|
||||||
|
$order1 = new Order();
|
||||||
|
$order1->setStatus(OrderStatus::NEW);
|
||||||
|
$order1->setUpdatedAt(new DateTimeImmutable('-2 days'));
|
||||||
|
$em->persist($order1);
|
||||||
|
$em->flush();
|
||||||
|
|
||||||
|
// Sleep to ensure different timestamps
|
||||||
|
|
||||||
|
$order2 = new Order();
|
||||||
|
$order2->setStatus(OrderStatus::IN_WORK);
|
||||||
|
$order2->setUpdatedAt(new DateTimeImmutable('-1 day'));
|
||||||
|
$em->persist($order2);
|
||||||
|
$em->flush();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$recentOrder = $orderService->getMostRecentActiveOrder();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect($recentOrder)->toBeInstanceOf(Order::class);
|
||||||
|
expect($recentOrder->getId())->toBe($order2->getId());
|
||||||
|
});
|
||||||
|
|
||||||
|
test('hasActiveOrders returns true when active orders exist', function () {
|
||||||
|
// Arrange
|
||||||
|
$orderService = $this->getContainer()->get(OrderService::class);
|
||||||
|
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
||||||
|
|
||||||
|
// Create an active order
|
||||||
|
$order = new Order();
|
||||||
|
$order->setStatus(OrderStatus::NEW);
|
||||||
|
$em->persist($order);
|
||||||
|
$em->flush();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$hasActiveOrders = $orderService->hasActiveOrders();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect($hasActiveOrders)->toBeTrue();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('getOrdersByDateRange returns orders within date range', function () {
|
||||||
|
// Arrange
|
||||||
|
$orderService = $this->getContainer()->get(OrderService::class);
|
||||||
|
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
||||||
|
|
||||||
|
// Create orders
|
||||||
|
$order1 = new Order();
|
||||||
|
$order1->setStatus(OrderStatus::NEW);
|
||||||
|
$em->persist($order1);
|
||||||
|
$em->flush();
|
||||||
|
|
||||||
|
$start = new DateTimeImmutable('-1 day');
|
||||||
|
$end = new DateTimeImmutable('+1 day');
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$orders = $orderService->getOrdersByDateRange($start, $end);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect($orders)->toBeArray();
|
||||||
|
expect(count($orders))->toBeGreaterThanOrEqual(1);
|
||||||
|
|
||||||
|
foreach ($orders as $order) {
|
||||||
|
expect($order)->toBeInstanceOf(Order::class);
|
||||||
|
expect($order->getCreatedAt()->getTimestamp())->toBeGreaterThanOrEqual($start->getTimestamp());
|
||||||
|
expect($order->getCreatedAt()->getTimestamp())->toBeLessThanOrEqual($end->getTimestamp());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('createOrder creates new order with items', function () {
|
||||||
|
// Arrange
|
||||||
|
$orderService = $this->getContainer()->get(OrderService::class);
|
||||||
|
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
||||||
|
|
||||||
|
// Create drink types
|
||||||
|
$drinkType1 = new DrinkType();
|
||||||
|
$drinkType1->setName('Drink Type 1 for Order');
|
||||||
|
$drinkType1->setDesiredStock(10);
|
||||||
|
$em->persist($drinkType1);
|
||||||
|
|
||||||
|
$drinkType2 = new DrinkType();
|
||||||
|
$drinkType2->setName('Drink Type 2 for Order');
|
||||||
|
$drinkType2->setDesiredStock(5);
|
||||||
|
$em->persist($drinkType2);
|
||||||
|
$em->flush();
|
||||||
|
|
||||||
|
$items = [
|
||||||
|
['drinkTypeId' => $drinkType1->getId(), 'quantity' => 3],
|
||||||
|
['drinkTypeId' => $drinkType2->getId(), 'quantity' => 2],
|
||||||
|
];
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$order = $orderService->createOrder($items);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect($order)->toBeInstanceOf(Order::class);
|
||||||
|
expect($order->getStatus())->toBe(OrderStatus::NEW);
|
||||||
|
expect($order->getOrderItems()->count())->toBe(2);
|
||||||
|
|
||||||
|
$orderItems = $order->getOrderItems()->toArray();
|
||||||
|
expect($orderItems[0]->getDrinkType()->getId())->toBe($drinkType1->getId());
|
||||||
|
expect($orderItems[0]->getQuantity())->toBe(3);
|
||||||
|
expect($orderItems[1]->getDrinkType()->getId())->toBe($drinkType2->getId());
|
||||||
|
expect($orderItems[1]->getQuantity())->toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('createOrderFromStockLevels creates order based on stock levels', function () {
|
||||||
|
// Arrange
|
||||||
|
$orderService = $this->getContainer()->get(OrderService::class);
|
||||||
|
$inventoryService = $this->getContainer()->get(InventoryService::class);
|
||||||
|
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
||||||
|
|
||||||
|
// Create drink types with stock levels below desired stock
|
||||||
|
$drinkType1 = new DrinkType();
|
||||||
|
$drinkType1->setName('Low Stock Drink Type 1');
|
||||||
|
$drinkType1->setDesiredStock(10);
|
||||||
|
$em->persist($drinkType1);
|
||||||
|
|
||||||
|
$drinkType2 = new DrinkType();
|
||||||
|
$drinkType2->setName('Low Stock Drink Type 2');
|
||||||
|
$drinkType2->setDesiredStock(8);
|
||||||
|
$em->persist($drinkType2);
|
||||||
|
|
||||||
|
$drinkType3 = new DrinkType();
|
||||||
|
$drinkType3->setName('Sufficient Stock Drink Type');
|
||||||
|
$drinkType3->setDesiredStock(5);
|
||||||
|
$em->persist($drinkType3);
|
||||||
|
$em->flush();
|
||||||
|
|
||||||
|
// Set stock levels
|
||||||
|
$inventoryService->updateStockLevel($drinkType1, 2); // Low stock
|
||||||
|
$inventoryService->updateStockLevel($drinkType2, 3); // Low stock
|
||||||
|
$inventoryService->updateStockLevel($drinkType3, 10); // Sufficient stock
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$order = $orderService->createOrderFromStockLevels();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect($order)->toBeInstanceOf(Order::class);
|
||||||
|
expect($order->getStatus())->toBe(OrderStatus::NEW);
|
||||||
|
|
||||||
|
// Should only include items for drink types with low stock
|
||||||
|
$orderItems = $order->getOrderItems()->toArray();
|
||||||
|
$drinkTypeIds = array_map(fn($item) => $item->getDrinkType()->getId(), $orderItems);
|
||||||
|
|
||||||
|
expect($drinkTypeIds)->toContain($drinkType1->getId());
|
||||||
|
expect($drinkTypeIds)->toContain($drinkType2->getId());
|
||||||
|
expect($drinkTypeIds)->not->toContain($drinkType3->getId());
|
||||||
|
|
||||||
|
// Check quantities
|
||||||
|
foreach ($orderItems as $item) {
|
||||||
|
$drinkType = $item->getDrinkType();
|
||||||
|
$currentStock = $inventoryService->getCurrentStockLevel($drinkType);
|
||||||
|
$desiredStock = $drinkType->getDesiredStock();
|
||||||
|
$expectedQuantity = $desiredStock - $currentStock;
|
||||||
|
|
||||||
|
expect($item->getQuantity())->toBe($expectedQuantity);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('updateOrderStatus updates order status', function () {
|
||||||
|
// Arrange
|
||||||
|
$orderService = $this->getContainer()->get(OrderService::class);
|
||||||
|
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
||||||
|
|
||||||
|
// Create an order
|
||||||
|
$order = new Order();
|
||||||
|
$order->setStatus(OrderStatus::NEW);
|
||||||
|
$em->persist($order);
|
||||||
|
$em->flush();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$updatedOrder = $orderService->updateOrderStatus($order, OrderStatus::ORDERED);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect($updatedOrder)->toBeInstanceOf(Order::class);
|
||||||
|
expect($updatedOrder->getStatus())->toBe(OrderStatus::ORDERED);
|
||||||
|
|
||||||
|
// Verify the status was updated in the database
|
||||||
|
});
|
||||||
|
|
||||||
|
test('addOrderItem adds item to order', function () {
|
||||||
|
// Arrange
|
||||||
|
$orderService = $this->getContainer()->get(OrderService::class);
|
||||||
|
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
||||||
|
|
||||||
|
// Create an order
|
||||||
|
$order = new Order();
|
||||||
|
$order->setStatus(OrderStatus::NEW);
|
||||||
|
$em->persist($order);
|
||||||
|
|
||||||
|
// Create a drink type
|
||||||
|
$drinkType = new DrinkType();
|
||||||
|
$drinkType->setName('Drink Type for Order Item');
|
||||||
|
$drinkType->setDesiredStock(10);
|
||||||
|
$em->persist($drinkType);
|
||||||
|
$em->flush();
|
||||||
|
|
||||||
|
$quantity = 5;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$orderItem = $orderService->addOrderItem($order, $drinkType, $quantity);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect($orderItem)->toBeInstanceOf(OrderItem::class);
|
||||||
|
expect($orderItem->getOrder()->getId())->toBe($order->getId());
|
||||||
|
expect($orderItem->getDrinkType()->getId())->toBe($drinkType->getId());
|
||||||
|
expect($orderItem->getQuantity())->toBe($quantity);
|
||||||
|
|
||||||
|
// Verify the item was added to the order
|
||||||
|
expect($order->getOrderItems()->contains($orderItem))->toBeTrue();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('addOrderItem updates quantity if item already exists', function () {
|
||||||
|
// Arrange
|
||||||
|
$orderService = $this->getContainer()->get(OrderService::class);
|
||||||
|
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
||||||
|
|
||||||
|
// Create an order
|
||||||
|
$order = new Order();
|
||||||
|
$order->setStatus(OrderStatus::NEW);
|
||||||
|
$em->persist($order);
|
||||||
|
|
||||||
|
// Create a drink type
|
||||||
|
$drinkType = new DrinkType();
|
||||||
|
$drinkType->setName('Drink Type for Existing Order Item');
|
||||||
|
$drinkType->setDesiredStock(10);
|
||||||
|
$em->persist($drinkType);
|
||||||
|
$em->flush();
|
||||||
|
|
||||||
|
// Add an item
|
||||||
|
$initialQuantity = 3;
|
||||||
|
$orderItem = $orderService->addOrderItem($order, $drinkType, $initialQuantity);
|
||||||
|
|
||||||
|
// Act - add another item with the same drink type
|
||||||
|
$additionalQuantity = 2;
|
||||||
|
$updatedOrderItem = $orderService->addOrderItem($order, $drinkType, $additionalQuantity);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect($updatedOrderItem)->toBeInstanceOf(OrderItem::class);
|
||||||
|
expect($updatedOrderItem->getId())->toBe($orderItem->getId());
|
||||||
|
expect($updatedOrderItem->getQuantity())->toBe($initialQuantity + $additionalQuantity);
|
||||||
|
|
||||||
|
// Verify the order still has only one item for this drink type
|
||||||
|
$matchingItems = $order->getOrderItems()->filter(
|
||||||
|
fn($item) => $item->getDrinkType()->getId() === $drinkType->getId()
|
||||||
|
);
|
||||||
|
expect($matchingItems->count())->toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('removeOrderItem removes item from order', function () {
|
||||||
|
// Arrange
|
||||||
|
$orderService = $this->getContainer()->get(OrderService::class);
|
||||||
|
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
||||||
|
|
||||||
|
// Create an order
|
||||||
|
$order = new Order();
|
||||||
|
$order->setStatus(OrderStatus::NEW);
|
||||||
|
$em->persist($order);
|
||||||
|
|
||||||
|
// Create a drink type
|
||||||
|
$drinkType = new DrinkType();
|
||||||
|
$drinkType->setName('Drink Type for Order Item Removal');
|
||||||
|
$drinkType->setDesiredStock(10);
|
||||||
|
$em->persist($drinkType);
|
||||||
|
$em->flush();
|
||||||
|
|
||||||
|
// Add an item
|
||||||
|
$orderItem = $orderService->addOrderItem($order, $drinkType, 5);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$orderService->removeOrderItem($order, $orderItem);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect($order->getOrderItems()->contains($orderItem))->toBeFalse();
|
||||||
|
|
||||||
|
// Verify the item was removed from the database
|
||||||
|
$em->refresh($order);
|
||||||
|
$matchingItems = $order->getOrderItems()->filter(
|
||||||
|
fn($item) => $item->getDrinkType()->getId() === $drinkType->getId()
|
||||||
|
);
|
||||||
|
expect($matchingItems->count())->toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('deleteOrder removes order and its items', function () {
|
||||||
|
// Arrange
|
||||||
|
$orderService = $this->getContainer()->get(OrderService::class);
|
||||||
|
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
||||||
|
|
||||||
|
// Create an order
|
||||||
|
$order = new Order();
|
||||||
|
$order->setStatus(OrderStatus::NEW);
|
||||||
|
$em->persist($order);
|
||||||
|
|
||||||
|
// Create a drink type
|
||||||
|
$drinkType = new DrinkType();
|
||||||
|
$drinkType->setName('Drink Type for Order Deletion');
|
||||||
|
$drinkType->setDesiredStock(10);
|
||||||
|
$em->persist($drinkType);
|
||||||
|
$em->flush();
|
||||||
|
|
||||||
|
// Add an item
|
||||||
|
$orderItem = $orderService->addOrderItem($order, $drinkType, 5);
|
||||||
|
$orderItemId = $orderItem->getId();
|
||||||
|
$orderId = $order->getId();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$orderService->deleteOrder($order);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
$deletedOrder = $orderService->getOrderById($orderId);
|
||||||
|
expect($deletedOrder)->toBeNull();
|
||||||
|
|
||||||
|
// Verify the order items were also deleted
|
||||||
|
$orderItemRepo = $this->getContainer()->get(OrderItemRepository::class);
|
||||||
|
$deletedOrderItem = $orderItemRepo->find($orderItemId);
|
||||||
|
expect($deletedOrderItem)->toBeNull();
|
||||||
|
});
|
29
tests/Pest.php
Normal file
29
tests/Pest.php
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Doctrine\ORM\Tools\SchemaTool;
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Test Case
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| The closure you provide to your test functions is always bound to a specific PHPUnit test
|
||||||
|
| case class. By default, that class is "PHPUnit\Framework\TestCase". Of course, you may
|
||||||
|
| need to change it using the "pest()" function to bind a different classes or traits.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
pest()->extend(Tests\DbTestCase::class)->in('Feature');
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Functions
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| While Pest is very powerful out-of-the-box, you may have some testing code specific to your
|
||||||
|
| project that you don't want to repeat in every file. Here you can also expose helpers as
|
||||||
|
| global functions to help you to reduce the number of lines of code in your test files.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
10
tests/TestCase.php
Normal file
10
tests/TestCase.php
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests;
|
||||||
|
|
||||||
|
use PHPUnit\Framework\TestCase as BaseTestCase;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||||
|
|
||||||
|
abstract class TestCase extends KernelTestCase
|
||||||
|
{
|
||||||
|
}
|
5
tests/Unit/ExampleTest.php
Normal file
5
tests/Unit/ExampleTest.php
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
test('example', function () {
|
||||||
|
expect(true)->toBeTrue();
|
||||||
|
});
|
13
tests/bootstrap.php
Normal file
13
tests/bootstrap.php
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Symfony\Component\Dotenv\Dotenv;
|
||||||
|
|
||||||
|
require dirname(__DIR__).'/vendor/autoload.php';
|
||||||
|
|
||||||
|
if (method_exists(Dotenv::class, 'bootEnv')) {
|
||||||
|
(new Dotenv())->bootEnv(dirname(__DIR__).'/.env');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($_SERVER['APP_DEBUG']) {
|
||||||
|
umask(0000);
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue