testitest

This commit is contained in:
lubiana 2025-06-08 21:22:26 +02:00
parent 43ca79f650
commit 66c4c1fe4f
Signed by: lubiana
SSH key fingerprint: SHA256:vW1EA0fRR3Fw+dD/sM0K+x3Il2gSry6YRYHqOeQwrfk
30 changed files with 4443 additions and 184 deletions

36
tests/DbTestCase.php Normal file
View 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();
}
}

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

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

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

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

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

View 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
View 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
View 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
{
}

View file

@ -0,0 +1,5 @@
<?php
test('example', function () {
expect(true)->toBeTrue();
});

13
tests/bootstrap.php Normal file
View 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);
}