Files
michaelschiemer/tests/Unit/Framework/LiveComponents/Cache/ComponentStateCacheTest.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

315 lines
11 KiB
PHP

<?php
declare(strict_types=1);
use App\Framework\Core\ValueObjects\Duration;
use App\Framework\LiveComponents\Cache\ComponentStateCache;
use App\Framework\LiveComponents\ValueObjects\ComponentId;
use App\Framework\LiveComponents\ValueObjects\ComponentState;
use App\Framework\StateManagement\InMemoryStateManager;
use App\Framework\StateManagement\StateManagerStatistics;
describe('ComponentStateCache with StateManager', function () {
beforeEach(function () {
// Create StateManager for ComponentState
$this->stateManager = InMemoryStateManager::for(ComponentState::class);
$this->cache = new ComponentStateCache($this->stateManager);
$this->componentId = ComponentId::create('counter', 'instance-1');
});
it('stores component state', function () {
$state = ComponentState::fromArray([
'count' => 5,
'lastUpdated' => time()
]);
$this->cache->store($this->componentId, $state);
expect($this->cache->has($this->componentId))->toBeTrue();
});
it('retrieves stored component state', function () {
$state = ComponentState::fromArray([
'count' => 10,
'label' => 'Counter'
]);
$this->cache->store($this->componentId, $state);
$retrieved = $this->cache->retrieve($this->componentId);
expect($retrieved)->toBeInstanceOf(ComponentState::class);
expect($retrieved->toArray()['count'])->toBe(10);
expect($retrieved->toArray()['label'])->toBe('Counter');
});
it('returns null for non-existent state', function () {
$nonExistentId = ComponentId::create('counter', 'does-not-exist');
$retrieved = $this->cache->retrieve($nonExistentId);
expect($retrieved)->toBeNull();
});
it('checks state existence', function () {
$state = ComponentState::fromArray(['value' => 42]);
expect($this->cache->has($this->componentId))->toBeFalse();
$this->cache->store($this->componentId, $state);
expect($this->cache->has($this->componentId))->toBeTrue();
});
it('invalidates cached state', function () {
$state = ComponentState::fromArray(['value' => 100]);
$this->cache->store($this->componentId, $state);
expect($this->cache->has($this->componentId))->toBeTrue();
$this->cache->invalidate($this->componentId);
expect($this->cache->has($this->componentId))->toBeFalse();
expect($this->cache->retrieve($this->componentId))->toBeNull();
});
it('clears all cached states', function () {
$state1 = ComponentState::fromArray(['value' => 1]);
$state2 = ComponentState::fromArray(['value' => 2]);
$id1 = ComponentId::create('counter', 'instance-1');
$id2 = ComponentId::create('counter', 'instance-2');
$this->cache->store($id1, $state1);
$this->cache->store($id2, $state2);
$this->cache->clear();
expect($this->cache->has($id1))->toBeFalse();
expect($this->cache->has($id2))->toBeFalse();
});
it('atomically updates component state', function () {
$initialState = ComponentState::fromArray([
'count' => 5,
'version' => 1
]);
$this->cache->store($this->componentId, $initialState);
$updatedState = $this->cache->update(
$this->componentId,
function ($state) {
if ($state === null) {
return ComponentState::fromArray(['count' => 1, 'version' => 1]);
}
$data = $state->toArray();
return ComponentState::fromArray([
'count' => $data['count'] + 1,
'version' => $data['version'] + 1
]);
}
);
expect($updatedState->toArray()['count'])->toBe(6);
expect($updatedState->toArray()['version'])->toBe(2);
});
it('handles null state in atomic update', function () {
$newState = $this->cache->update(
$this->componentId,
function ($state) {
return $state ?? ComponentState::fromArray([
'count' => 1,
'initialized' => true
]);
}
);
expect($newState->toArray()['count'])->toBe(1);
expect($newState->toArray()['initialized'])->toBe(true);
});
it('respects custom TTL', function () {
$state = ComponentState::fromArray(['value' => 42]);
$customTtl = Duration::fromMinutes(30);
$this->cache->store($this->componentId, $state, $customTtl);
expect($this->cache->has($this->componentId))->toBeTrue();
});
it('uses default TTL when not specified', function () {
$state = ComponentState::fromArray(['value' => 100]);
$this->cache->store($this->componentId, $state);
expect($this->cache->has($this->componentId))->toBeTrue();
});
it('provides cache statistics', function () {
$state = ComponentState::fromArray(['value' => 1]);
$this->cache->store($this->componentId, $state);
$this->cache->retrieve($this->componentId);
$this->cache->has($this->componentId);
$stats = $this->cache->getStats();
expect($stats)->toBeInstanceOf(StateManagerStatistics::class);
expect($stats->totalKeys)->toBeGreaterThanOrEqual(1);
expect($stats->setCount)->toBeGreaterThanOrEqual(1);
expect($stats->hitCount)->toBeGreaterThanOrEqual(1);
});
it('stores with automatic TTL based on component type', function () {
$state = ComponentState::fromArray(['value' => 42]);
$this->cache->storeWithAutoTTL($this->componentId, $state, 'counter');
expect($this->cache->has($this->componentId))->toBeTrue();
});
it('applies different TTLs for different component types', function () {
$timerState = ComponentState::fromArray(['seconds' => 30]);
$chartState = ComponentState::fromArray(['data' => [1, 2, 3]]);
$cardState = ComponentState::fromArray(['title' => 'Card Title']);
$timerId = ComponentId::create('timer', 'instance-1');
$chartId = ComponentId::create('chart', 'instance-1');
$cardId = ComponentId::create('card', 'instance-1');
// timer: 5 minutes TTL
$this->cache->storeWithAutoTTL($timerId, $timerState, 'timer');
// chart: 30 minutes TTL
$this->cache->storeWithAutoTTL($chartId, $chartState, 'chart');
// card: 2 hours TTL
$this->cache->storeWithAutoTTL($cardId, $cardState, 'card');
expect($this->cache->has($timerId))->toBeTrue();
expect($this->cache->has($chartId))->toBeTrue();
expect($this->cache->has($cardId))->toBeTrue();
});
it('uses default TTL for unknown component types', function () {
$state = ComponentState::fromArray(['value' => 123]);
$unknownId = ComponentId::create('unknown-type', 'instance-1');
$this->cache->storeWithAutoTTL($unknownId, $state, 'unknown-type');
expect($this->cache->has($unknownId))->toBeTrue();
});
it('generates consistent cache keys for same component', function () {
$state = ComponentState::fromArray(['value' => 42]);
$id1 = ComponentId::create('counter', 'instance-1');
$id2 = ComponentId::create('counter', 'instance-1');
$this->cache->store($id1, $state);
// Same component ID should find the cached state
expect($this->cache->has($id2))->toBeTrue();
expect($this->cache->retrieve($id2))->not->toBeNull();
});
it('generates different cache keys for different instances', function () {
$state1 = ComponentState::fromArray(['value' => 1]);
$state2 = ComponentState::fromArray(['value' => 2]);
$id1 = ComponentId::create('counter', 'instance-1');
$id2 = ComponentId::create('counter', 'instance-2');
$this->cache->store($id1, $state1);
$this->cache->store($id2, $state2);
$retrieved1 = $this->cache->retrieve($id1);
$retrieved2 = $this->cache->retrieve($id2);
expect($retrieved1->toArray()['value'])->toBe(1);
expect($retrieved2->toArray()['value'])->toBe(2);
});
it('handles complex state data', function () {
$complexState = ComponentState::fromArray([
'user' => [
'id' => 123,
'name' => 'John Doe',
'email' => 'john@example.com'
],
'preferences' => [
'theme' => 'dark',
'notifications' => true
],
'metadata' => [
'created_at' => time(),
'updated_at' => time()
]
]);
$this->cache->store($this->componentId, $complexState);
$retrieved = $this->cache->retrieve($this->componentId);
expect($retrieved->toArray()['user']['name'])->toBe('John Doe');
expect($retrieved->toArray()['preferences']['theme'])->toBe('dark');
expect($retrieved->toArray()['metadata'])->toHaveKey('created_at');
});
it('preserves state through multiple updates', function () {
$initialState = ComponentState::fromArray(['count' => 0]);
$this->cache->store($this->componentId, $initialState);
// Update 1
$this->cache->update(
$this->componentId,
fn($state) => ComponentState::fromArray(['count' => $state->toArray()['count'] + 1])
);
// Update 2
$this->cache->update(
$this->componentId,
fn($state) => ComponentState::fromArray(['count' => $state->toArray()['count'] + 1])
);
// Update 3
$finalState = $this->cache->update(
$this->componentId,
fn($state) => ComponentState::fromArray(['count' => $state->toArray()['count'] + 1])
);
expect($finalState->toArray()['count'])->toBe(3);
});
it('correctly reports statistics after various operations', function () {
$state = ComponentState::fromArray(['value' => 1]);
// Set operations
$this->cache->store($this->componentId, $state);
$this->cache->store($this->componentId, $state);
// Get operations
$this->cache->retrieve($this->componentId);
$this->cache->retrieve($this->componentId);
// Has operations
$this->cache->has($this->componentId);
// Update operation
$this->cache->update(
$this->componentId,
fn($s) => ComponentState::fromArray(['value' => 2])
);
$stats = $this->cache->getStats();
expect($stats->setCount)->toBeGreaterThanOrEqual(2);
expect($stats->hitCount)->toBeGreaterThanOrEqual(2);
expect($stats->updateCount)->toBeGreaterThanOrEqual(1);
});
});