- 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.
286 lines
10 KiB
PHP
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');
|
|
});
|
|
});
|