Files
michaelschiemer/tests/Unit/Application/LiveComponents/LiveComponentStateManagerTest.php
Michael Schiemer fc3d7e6357 feat(Production): Complete production deployment infrastructure
- Add comprehensive health check system with multiple endpoints
- Add Prometheus metrics endpoint
- Add production logging configurations (5 strategies)
- Add complete deployment documentation suite:
  * QUICKSTART.md - 30-minute deployment guide
  * DEPLOYMENT_CHECKLIST.md - Printable verification checklist
  * DEPLOYMENT_WORKFLOW.md - Complete deployment lifecycle
  * PRODUCTION_DEPLOYMENT.md - Comprehensive technical reference
  * production-logging.md - Logging configuration guide
  * ANSIBLE_DEPLOYMENT.md - Infrastructure as Code automation
  * README.md - Navigation hub
  * DEPLOYMENT_SUMMARY.md - Executive summary
- Add deployment scripts and automation
- Add DEPLOYMENT_PLAN.md - Concrete plan for immediate deployment
- Update README with production-ready features

All production infrastructure is now complete and ready for deployment.
2025-10-25 19:18:37 +02:00

286 lines
10 KiB
PHP

<?php
declare(strict_types=1);
use App\Application\LiveComponents\LiveComponentStateManager;
use App\Application\LiveComponents\ShoppingCart\ShoppingCartState;
use App\Framework\Core\ValueObjects\Duration;
use App\Framework\LiveComponents\ValueObjects\ComponentId;
use App\Framework\StateManagement\InMemoryStateManager;
use App\Framework\StateManagement\StateManagerStatistics;
describe('LiveComponentStateManager', function () {
beforeEach(function () {
// Create type-safe StateManager for ShoppingCartState
$this->stateManager = InMemoryStateManager::for(ShoppingCartState::class);
$this->manager = new LiveComponentStateManager($this->stateManager);
$this->componentId = ComponentId::create('shopping-cart', 'user-123');
});
it('stores and retrieves component state', function () {
$cartState = ShoppingCartState::fromArray([
'items' => [
['id' => '1', 'name' => 'Product A', 'price' => 29.99, 'quantity' => 2],
],
'discount_code' => 'SAVE10',
'discount_percentage' => 10
]);
$this->manager->setComponentState($this->componentId, $cartState);
$retrieved = $this->manager->getComponentState($this->componentId);
expect($retrieved)->toBeInstanceOf(ShoppingCartState::class);
expect($retrieved->discountCode)->toBe('SAVE10');
expect($retrieved->discountPercentage)->toBe(10);
expect(count($retrieved->items))->toBe(1);
});
it('preserves type safety throughout', function () {
$cartState = ShoppingCartState::fromArray([
'items' => [
['id' => '1', 'name' => 'Product A', 'price' => 19.99, 'quantity' => 1],
],
'discount_code' => 'TEST',
'discount_percentage' => 5
]);
$this->manager->setComponentState($this->componentId, $cartState);
$retrieved = $this->manager->getComponentState($this->componentId);
// Type is preserved - not mixed or LiveComponentState, but ShoppingCartState
expect($retrieved)->toBeInstanceOf(ShoppingCartState::class);
expect($retrieved->items)->toBeArray();
expect($retrieved->discountCode)->toBeString();
});
it('checks if component state exists', function () {
expect($this->manager->hasComponentState($this->componentId))->toBeFalse();
$cartState = ShoppingCartState::fromArray([
'items' => [],
'discount_code' => null,
'discount_percentage' => 0
]);
$this->manager->setComponentState($this->componentId, $cartState);
expect($this->manager->hasComponentState($this->componentId))->toBeTrue();
});
it('removes component state', function () {
$cartState = ShoppingCartState::fromArray([
'items' => [],
'discount_code' => null,
'discount_percentage' => 0
]);
$this->manager->setComponentState($this->componentId, $cartState);
expect($this->manager->hasComponentState($this->componentId))->toBeTrue();
$this->manager->removeComponentState($this->componentId);
expect($this->manager->hasComponentState($this->componentId))->toBeFalse();
expect($this->manager->getComponentState($this->componentId))->toBeNull();
});
it('atomically updates component state', function () {
$initialCart = ShoppingCartState::fromArray([
'items' => [
['id' => '1', 'name' => 'Product A', 'price' => 29.99, 'quantity' => 1],
],
'discount_code' => 'SAVE10',
'discount_percentage' => 10
]);
$this->manager->setComponentState($this->componentId, $initialCart);
$updatedCart = $this->manager->updateComponentState(
$this->componentId,
function ($state) {
return $state ? $state->withDiscountCode('SAVE20', 20) : ShoppingCartState::fromArray([]);
}
);
expect($updatedCart)->toBeInstanceOf(ShoppingCartState::class);
expect($updatedCart->discountCode)->toBe('SAVE20');
expect($updatedCart->discountPercentage)->toBe(20);
expect(count($updatedCart->items))->toBe(1);
});
it('handles null state in atomic update', function () {
$newCart = $this->manager->updateComponentState(
$this->componentId,
function ($state) {
return $state ?? ShoppingCartState::fromArray([
'items' => [
['id' => '2', 'name' => 'New Product', 'price' => 15.50, 'quantity' => 1],
],
'discount_code' => 'FIRST',
'discount_percentage' => 5
]);
}
);
expect($newCart)->toBeInstanceOf(ShoppingCartState::class);
expect($newCart->discountCode)->toBe('FIRST');
expect(count($newCart->items))->toBe(1);
});
it('respects TTL for state expiration', function () {
$cartState = ShoppingCartState::fromArray([
'items' => [],
'discount_code' => null,
'discount_percentage' => 0
]);
// Store with very short TTL
$this->manager->setComponentState(
$this->componentId,
$cartState,
Duration::fromSeconds(1)
);
expect($this->manager->hasComponentState($this->componentId))->toBeTrue();
// Wait for expiration
sleep(2);
// State should be expired (for TTL-aware implementations)
// Note: InMemoryStateManager doesn't enforce TTL, but the interface supports it
});
it('retrieves all component states', function () {
$cart1 = ShoppingCartState::fromArray([
'items' => [
['id' => '1', 'name' => 'Product A', 'price' => 29.99, 'quantity' => 1],
],
'discount_code' => 'SAVE10',
'discount_percentage' => 10
]);
$cart2 = ShoppingCartState::fromArray([
'items' => [
['id' => '2', 'name' => 'Product B', 'price' => 15.50, 'quantity' => 2],
],
'discount_code' => 'SAVE20',
'discount_percentage' => 20
]);
$componentId1 = ComponentId::create('shopping-cart', 'user-123');
$componentId2 = ComponentId::create('shopping-cart', 'user-456');
$this->manager->setComponentState($componentId1, $cart1);
$this->manager->setComponentState($componentId2, $cart2);
$allStates = $this->manager->getAllComponentStates();
expect($allStates)->toBeArray();
expect(count($allStates))->toBeGreaterThanOrEqual(2);
});
it('clears all component states', function () {
$cart1 = ShoppingCartState::fromArray([
'items' => [],
'discount_code' => null,
'discount_percentage' => 0
]);
$cart2 = ShoppingCartState::fromArray([
'items' => [],
'discount_code' => 'TEST',
'discount_percentage' => 5
]);
$componentId1 = ComponentId::create('shopping-cart', 'user-123');
$componentId2 = ComponentId::create('shopping-cart', 'user-456');
$this->manager->setComponentState($componentId1, $cart1);
$this->manager->setComponentState($componentId2, $cart2);
$this->manager->clearAllComponentStates();
expect($this->manager->hasComponentState($componentId1))->toBeFalse();
expect($this->manager->hasComponentState($componentId2))->toBeFalse();
});
it('provides statistics about state operations', function () {
$cartState = ShoppingCartState::fromArray([
'items' => [],
'discount_code' => null,
'discount_percentage' => 0
]);
$this->manager->setComponentState($this->componentId, $cartState);
$this->manager->getComponentState($this->componentId);
$this->manager->hasComponentState($this->componentId);
$stats = $this->manager->getStatistics();
expect($stats)->toBeInstanceOf(StateManagerStatistics::class);
expect($stats->totalKeys)->toBeGreaterThanOrEqual(1);
expect($stats->setCount)->toBeGreaterThanOrEqual(1);
expect($stats->hitCount)->toBeGreaterThanOrEqual(1);
});
it('can be created via factory method', function () {
$stateManager = InMemoryStateManager::for(ShoppingCartState::class);
$manager = LiveComponentStateManager::from($stateManager);
expect($manager)->toBeInstanceOf(LiveComponentStateManager::class);
});
it('generates consistent cache keys', function () {
$componentId1 = ComponentId::fromString('shopping-cart:user-123');
$componentId2 = ComponentId::fromString('shopping-cart:user-123');
$componentId3 = ComponentId::fromString('shopping-cart:user-456');
$cartState = ShoppingCartState::fromArray([
'items' => [],
'discount_code' => null,
'discount_percentage' => 0
]);
$this->manager->setComponentState($componentId1, $cartState);
// Same component ID should retrieve same state
expect($this->manager->hasComponentState($componentId2))->toBeTrue();
// Different instance ID should not have state
expect($this->manager->hasComponentState($componentId3))->toBeFalse();
});
it('handles complex state transformations', function () {
$initialCart = ShoppingCartState::fromArray([
'items' => [
['id' => '1', 'name' => 'Product A', 'price' => 29.99, 'quantity' => 1],
],
'discount_code' => null,
'discount_percentage' => 0
]);
$this->manager->setComponentState($this->componentId, $initialCart);
// Multiple transformations
$updatedCart = $this->manager->updateComponentState(
$this->componentId,
fn($state) => $state->withDiscountCode('SAVE10', 10)
);
$updatedCart = $this->manager->updateComponentState(
$this->componentId,
function($state) {
return ShoppingCartState::fromArray([
'items' => array_merge($state->items, [
['id' => '2', 'name' => 'Product B', 'price' => 15.50, 'quantity' => 2]
]),
'discount_code' => $state->discountCode,
'discount_percentage' => $state->discountPercentage
]);
}
);
expect(count($updatedCart->items))->toBe(2);
expect($updatedCart->discountCode)->toBe('SAVE10');
});
});