saufen/tests/Feature/Service/InventoryServiceTest.php
2025-06-09 19:56:08 +02:00

366 lines
13 KiB
PHP

<?php
declare(strict_types=1);
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 (): void {
// Arrange
$inventoryService = $this->getContainer()->get(InventoryService::class);
// Act
$records = $inventoryService->getAllInventoryRecords();
// Assert
expect($records)->toBeArray();
});
test('getInventoryRecordsByDrinkType returns records for specific drink type', function (): void {
// 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 (): void {
// 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 (): void {
// 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 (): void {
// 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 (): void {
// 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 (): void {
// 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 (): void {
// 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 (): void {
// 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 (): void {
// 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 (): void {
// 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);
});