Files
michaelschiemer/tests/Unit/Framework/View/Caching/CacheManagerTest.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

260 lines
9.1 KiB
PHP

<?php
declare(strict_types=1);
use App\Framework\Cache\Cache;
use App\Framework\Cache\CacheItem;
use App\Framework\Cache\CacheKey;
use App\Framework\Cache\CacheResult;
use App\Framework\View\Caching\Analysis\CacheabilityScore;
use App\Framework\View\Caching\Analysis\CacheStrategy;
use App\Framework\View\Caching\Analysis\TemplateAnalysis;
use App\Framework\View\Caching\Analysis\TemplateAnalyzer;
use App\Framework\View\Caching\CacheManager;
use App\Framework\View\Caching\FragmentCache;
use App\Framework\View\Caching\TemplateContext;
// Helper function to create TemplateAnalysis
function createAnalysis(
string $template,
CacheStrategy $strategy,
float $staticRatio = 1.0,
bool $hasUserContent = false,
bool $hasCsrf = false,
bool $hasTimestamps = false,
bool $hasRandom = false
): TemplateAnalysis {
$cacheability = new CacheabilityScore();
$cacheability->staticContentRatio = $staticRatio;
$cacheability->hasUserSpecificContent = $hasUserContent;
$cacheability->hasCsrfTokens = $hasCsrf;
$cacheability->hasTimestamps = $hasTimestamps;
$cacheability->hasRandomElements = $hasRandom;
return new TemplateAnalysis(
template: $template,
recommendedStrategy: $strategy,
recommendedTtl: $strategy->getTtl(),
dependencies: [],
cacheability: $cacheability,
fragments: []
);
}
describe('CacheManager', function () {
beforeEach(function () {
$this->cache = Mockery::mock(Cache::class);
$this->analyzer = Mockery::mock(TemplateAnalyzer::class);
$this->fragmentCache = Mockery::mock(FragmentCache::class);
$this->cacheManager = new CacheManager(
$this->cache,
$this->analyzer,
$this->fragmentCache
);
});
afterEach(function () {
Mockery::close();
});
describe('render() with cacheable strategies', function () {
it('renders and caches on cache miss for FULL_PAGE strategy', function () {
$context = new TemplateContext(
template: 'pages/contact',
data: ['email' => 'contact@example.com']
);
$analysis = createAnalysis(
'pages/contact',
CacheStrategy::FULL_PAGE,
staticRatio: 0.9
);
$this->analyzer->shouldReceive('analyze')
->once()
->with('pages/contact')
->andReturn($analysis);
// Create actual CacheResult with cache miss
$cacheKey = CacheKey::fromString('page:pages/contact:' . md5(serialize(['email' => 'contact@example.com'])));
$missItem = CacheItem::miss($cacheKey);
$cacheResult = CacheResult::fromItems($missItem);
$this->cache->shouldReceive('get')
->once()
->andReturn($cacheResult);
// Expect cache set with rendered content
$renderedContent = '<html>Contact Page</html>';
$this->cache->shouldReceive('set')
->once()
->with(Mockery::type(CacheItem::class))
->andReturn(true);
$result = $this->cacheManager->render($context, function () use ($renderedContent) {
return $renderedContent;
});
expect($result)->toBe($renderedContent);
});
it('caches component templates with COMPONENT strategy', function () {
$context = new TemplateContext(
template: 'components/button',
data: ['text' => 'Click me']
);
$analysis = createAnalysis(
'components/button',
CacheStrategy::COMPONENT,
staticRatio: 1.0
);
$this->analyzer->shouldReceive('analyze')
->once()
->andReturn($analysis);
// Create actual CacheResult with cache miss
$cacheKey = CacheKey::fromString('component:button:' . md5(serialize(['text' => 'Click me'])));
$missItem = CacheItem::miss($cacheKey);
$cacheResult = CacheResult::fromItems($missItem);
$this->cache->shouldReceive('get')->once()->andReturn($cacheResult);
$this->cache->shouldReceive('set')->once()->andReturn(true);
$renderedContent = '<button>Click me</button>';
$result = $this->cacheManager->render($context, fn() => $renderedContent);
expect($result)->toBe($renderedContent);
});
it('caches fragment with FRAGMENT strategy', function () {
$context = new TemplateContext(
template: 'pages/dashboard',
data: ['stats' => []],
metadata: ['fragment_id' => 'user-stats']
);
$analysis = createAnalysis(
'pages/dashboard',
CacheStrategy::FRAGMENT,
staticRatio: 0.6
);
$this->analyzer->shouldReceive('analyze')
->once()
->andReturn($analysis);
// Create actual CacheResult with cache miss
$cacheKey = CacheKey::fromString('fragment:pages/dashboard:user-stats:' . md5(serialize(['stats' => []])));
$missItem = CacheItem::miss($cacheKey);
$cacheResult = CacheResult::fromItems($missItem);
$this->cache->shouldReceive('get')->once()->andReturn($cacheResult);
$this->cache->shouldReceive('set')->once()->andReturn(true);
$renderedContent = '<div class="stats">Stats Fragment</div>';
$result = $this->cacheManager->render($context, fn() => $renderedContent);
expect($result)->toBe($renderedContent);
});
});
describe('render() with NO_CACHE strategy', function () {
it('skips caching for non-cacheable content', function () {
$context = new TemplateContext(
template: 'pages/dynamic',
data: ['user' => ['id' => 1], 'timestamp' => time()]
);
$analysis = createAnalysis(
'pages/dynamic',
CacheStrategy::NO_CACHE,
staticRatio: 0.2,
hasUserContent: true,
hasTimestamps: true
);
$this->analyzer->shouldReceive('analyze')
->once()
->andReturn($analysis);
// No cache operations expected
$this->cache->shouldReceive('get')->never();
$this->cache->shouldReceive('set')->never();
$renderedContent = '<html>Dynamic Content</html>';
$result = $this->cacheManager->render($context, fn() => $renderedContent);
expect($result)->toBe($renderedContent);
});
});
describe('invalidateTemplate()', function () {
it('invalidates cache for given template', function () {
// clear() is called once per strategy that canInvalidate
$this->cache->shouldReceive('clear')
->atLeast()->once()
->andReturn(true);
$invalidated = $this->cacheManager->invalidateTemplate('pages/about');
// Returns count of invalidated caches
expect($invalidated)->toBeGreaterThanOrEqual(0);
});
it('builds correct invalidation patterns for different strategies', function () {
// This test verifies the pattern building logic by invalidating
$this->cache->shouldReceive('clear')
->atLeast()->once()
->andReturn(true);
// Invalidate various template types
$this->cacheManager->invalidateTemplate('pages/home');
$this->cacheManager->invalidateTemplate('components/button');
$this->cacheManager->invalidateTemplate('fragments/stats');
expect(true)->toBeTrue(); // Verify no exceptions thrown
});
});
describe('edge cases', function () {
it('handles empty template content', function () {
$context = new TemplateContext(
template: 'pages/empty',
data: []
);
$analysis = createAnalysis(
'pages/empty',
CacheStrategy::NO_CACHE
);
$this->analyzer->shouldReceive('analyze')
->once()
->andReturn($analysis);
$result = $this->cacheManager->render($context, fn() => '');
expect($result)->toBe('');
});
it('handles large rendered content', function () {
$context = new TemplateContext(
template: 'pages/large',
data: []
);
$analysis = createAnalysis(
'pages/large',
CacheStrategy::NO_CACHE
);
$this->analyzer->shouldReceive('analyze')
->once()
->andReturn($analysis);
$largeContent = str_repeat('<p>Content</p>', 1000);
$result = $this->cacheManager->render($context, fn() => $largeContent);
expect($result)->toBe($largeContent);
expect(strlen($result))->toBeGreaterThan(10000);
});
});
});