testDir = '/home/michael/dev/michaelschiemer/tests/tmp/smart_analyzer_' . uniqid();
if (!is_dir($this->testDir)) {
mkdir($this->testDir, 0755, true);
}
// Create real PathProvider
$this->pathProvider = new PathProvider($this->testDir);
// Create mocked Cache
$this->cache = Mockery::mock(Cache::class);
$this->cache->shouldReceive('get')->andReturn(CacheResult::fromItems(CacheItem::miss(CacheKey::fromString('test'))));
$this->cache->shouldReceive('set')->andReturn(true);
$this->cache->shouldReceive('clear')->andReturn(true);
// Create TemplateLoader with test directory as template path
$this->loader = new TemplateLoader(
pathProvider: $this->pathProvider,
cache: $this->cache,
templates: [],
templatePath: '', // Empty to use base path directly
cacheEnabled: false
);
$this->analyzer = new SmartTemplateAnalyzer($this->loader);
});
afterEach(function () {
Mockery::close();
// Clean up test files
if (is_dir($this->testDir)) {
$files = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($this->testDir, RecursiveDirectoryIterator::SKIP_DOTS),
RecursiveIteratorIterator::CHILD_FIRST
);
foreach ($files as $file) {
$file->isDir() ? rmdir($file->getRealPath()) : unlink($file->getRealPath());
}
rmdir($this->testDir);
}
});
describe('analyze()', function () {
it('recommends FULL_PAGE strategy for layouts with high static ratio', function () {
$content = '
SiteMostly static content here';
// Create template file
$filePath = $this->testDir . '/layouts/main.view.php';
$dir = dirname($filePath);
if (!is_dir($dir)) {
mkdir($dir, 0755, true);
}
file_put_contents($filePath, $content);
$analysis = $this->analyzer->analyze('layouts/main');
expect($analysis->recommendedStrategy)->toBe(CacheStrategy::FULL_PAGE);
expect($analysis->cacheability->staticContentRatio)->toBeGreaterThan(0.8);
});
it('recommends COMPONENT strategy for component templates', function () {
$content = '';
// Create template file
$filePath = $this->testDir . '/components/button.view.php';
$dir = dirname($filePath);
if (!is_dir($dir)) {
mkdir($dir, 0755, true);
}
file_put_contents($filePath, $content);
$analysis = $this->analyzer->analyze('components/button');
expect($analysis->recommendedStrategy)->toBe(CacheStrategy::COMPONENT);
});
it('recommends COMPONENT strategy for partial templates', function () {
$content = '';
// Create template file
$filePath = $this->testDir . '/partials/navigation.view.php';
$dir = dirname($filePath);
if (!is_dir($dir)) {
mkdir($dir, 0755, true);
}
file_put_contents($filePath, $content);
$analysis = $this->analyzer->analyze('partials/navigation');
expect($analysis->recommendedStrategy)->toBe(CacheStrategy::COMPONENT);
});
it('recommends NO_CACHE for templates with user content', function () {
$content = 'Welcome {{ user.name }}!
Your profile
';
// Create template file
$filePath = $this->testDir . '/pages/profile.view.php';
$dir = dirname($filePath);
if (!is_dir($dir)) {
mkdir($dir, 0755, true);
}
file_put_contents($filePath, $content);
$analysis = $this->analyzer->analyze('pages/profile');
expect($analysis->recommendedStrategy)->toBe(CacheStrategy::NO_CACHE);
expect($analysis->cacheability->hasUserSpecificContent)->toBeTrue();
});
it('detects CSRF tokens and adjusts cacheability score', function () {
$content = '';
// Create template file
$filePath = $this->testDir . '/forms/contact.view.php';
$dir = dirname($filePath);
if (!is_dir($dir)) {
mkdir($dir, 0755, true);
}
file_put_contents($filePath, $content);
$analysis = $this->analyzer->analyze('forms/contact');
// CSRF tokens detected but high static ratio (0.839) means still cacheable (0.839 - 0.2 = 0.639 > 0.5)
expect($analysis->cacheability->hasCsrfTokens)->toBeTrue();
expect($analysis->cacheability->getScore())->toBeGreaterThan(0.5);
});
it('detects timestamps and adjusts cacheability score', function () {
$content = 'Current time: {{ now }}
';
// Create template file
$filePath = $this->testDir . '/widgets/clock.view.php';
$dir = dirname($filePath);
if (!is_dir($dir)) {
mkdir($dir, 0755, true);
}
file_put_contents($filePath, $content);
$analysis = $this->analyzer->analyze('widgets/clock');
// Timestamps detected but high static ratio means still cacheable
expect($analysis->cacheability->hasTimestamps)->toBeTrue();
expect($analysis->cacheability->getScore())->toBeGreaterThan(0.5);
});
it('recommends NO_CACHE for templates with random elements', function () {
$content = 'Random ID: {{ uuid }}
';
// Create template file
$filePath = $this->testDir . '/widgets/random.view.php';
$dir = dirname($filePath);
if (!is_dir($dir)) {
mkdir($dir, 0755, true);
}
file_put_contents($filePath, $content);
$analysis = $this->analyzer->analyze('widgets/random');
expect($analysis->recommendedStrategy)->toBe(CacheStrategy::NO_CACHE);
expect($analysis->cacheability->hasRandomElements)->toBeTrue();
});
it('recommends FRAGMENT strategy for medium cacheability templates', function () {
$content = 'Title
Some content
{{ small_dynamic_part }} ';
// Create template file
$filePath = $this->testDir . '/pages/mixed.view.php';
$dir = dirname($filePath);
if (!is_dir($dir)) {
mkdir($dir, 0755, true);
}
file_put_contents($filePath, $content);
$analysis = $this->analyzer->analyze('pages/mixed');
expect($analysis->recommendedStrategy)->toBe(CacheStrategy::FRAGMENT);
expect($analysis->cacheability->getScore())->toBeGreaterThan(0.5);
});
it('creates fallback analysis when loader throws exception', function () {
$analysis = $this->analyzer->analyze('missing/template');
expect($analysis->recommendedStrategy)->toBe(CacheStrategy::NO_CACHE);
expect($analysis->recommendedTtl)->toBe(0);
expect($analysis->dependencies)->toBeEmpty();
});
it('adjusts TTL based on cacheability score', function () {
$staticContent = 'All static content here';
// Create template file
$filePath = $this->testDir . '/static/page.view.php';
$dir = dirname($filePath);
if (!is_dir($dir)) {
mkdir($dir, 0755, true);
}
file_put_contents($filePath, $staticContent);
$analysis = $this->analyzer->analyze('static/page');
expect($analysis->recommendedTtl)->toBeGreaterThan(0);
});
});
describe('getDependencies()', function () {
it('finds @include directives', function () {
$content = '@include("header") Content
@include("footer")';
// Create template file
$filePath = $this->testDir . '/page/test.view.php';
$dir = dirname($filePath);
if (!is_dir($dir)) {
mkdir($dir, 0755, true);
}
file_put_contents($filePath, $content);
$deps = $this->analyzer->getDependencies('page/test');
expect($deps)->toHaveKey('includes');
expect($deps['includes'])->toContain('header');
expect($deps['includes'])->toContain('footer');
});
it('finds tags', function () {
$content = ' ';
// Create template file
$filePath = $this->testDir . '/page/test2.view.php';
$dir = dirname($filePath);
if (!is_dir($dir)) {
mkdir($dir, 0755, true);
}
file_put_contents($filePath, $content);
$deps = $this->analyzer->getDependencies('page/test2');
expect($deps)->toHaveKey('components');
expect($deps['components'])->toContain('button');
expect($deps['components'])->toContain('card');
});
it('finds both includes and components', function () {
$content = '@include("layout") ';
// Create template file
$filePath = $this->testDir . '/page/test3.view.php';
$dir = dirname($filePath);
if (!is_dir($dir)) {
mkdir($dir, 0755, true);
}
file_put_contents($filePath, $content);
$deps = $this->analyzer->getDependencies('page/test3');
expect($deps)->toHaveKey('includes');
expect($deps)->toHaveKey('components');
expect($deps['includes'])->toContain('layout');
expect($deps['components'])->toContain('nav');
});
it('returns empty array when no dependencies found', function () {
$content = 'No dependencies here
';
// Create template file
$filePath = $this->testDir . '/page/simple.view.php';
$dir = dirname($filePath);
if (!is_dir($dir)) {
mkdir($dir, 0755, true);
}
file_put_contents($filePath, $content);
$deps = $this->analyzer->getDependencies('page/simple');
expect($deps)->toBeEmpty();
});
it('returns empty array when loader throws exception', function () {
$deps = $this->analyzer->getDependencies('page/error');
expect($deps)->toBeEmpty();
});
});
describe('getCacheability()', function () {
it('detects user-specific content patterns', function () {
$content = '{{ user.name }} {{ current_user.email }} {{ auth.role }}';
// Create template file
$filePath = $this->testDir . '/test/user.view.php';
$dir = dirname($filePath);
if (!is_dir($dir)) {
mkdir($dir, 0755, true);
}
file_put_contents($filePath, $content);
$score = $this->analyzer->getCacheability('test/user');
expect($score->hasUserSpecificContent)->toBeTrue();
expect($score->isCacheable())->toBeFalse();
});
it('detects CSRF token patterns', function () {
$content = '{{ csrf_token }} {{ _token }}';
// Create template file
$filePath = $this->testDir . '/test/csrf.view.php';
$dir = dirname($filePath);
if (!is_dir($dir)) {
mkdir($dir, 0755, true);
}
file_put_contents($filePath, $content);
$score = $this->analyzer->getCacheability('test/csrf');
expect($score->hasCsrfTokens)->toBeTrue();
expect($score->isCacheable())->toBeFalse();
});
it('detects timestamp patterns', function () {
$content = '{{ now }} {{ timestamp }} {{ date() }}';
// Create template file
$filePath = $this->testDir . '/test/time.view.php';
$dir = dirname($filePath);
if (!is_dir($dir)) {
mkdir($dir, 0755, true);
}
file_put_contents($filePath, $content);
$score = $this->analyzer->getCacheability('test/time');
expect($score->hasTimestamps)->toBeTrue();
expect($score->isCacheable())->toBeFalse();
});
it('detects random element patterns', function () {
$content = '{{ random }} {{ rand() }} {{ uuid }}';
// Create template file
$filePath = $this->testDir . '/test/random.view.php';
$dir = dirname($filePath);
if (!is_dir($dir)) {
mkdir($dir, 0755, true);
}
file_put_contents($filePath, $content);
$score = $this->analyzer->getCacheability('test/random');
expect($score->hasRandomElements)->toBeTrue();
expect($score->isCacheable())->toBeFalse();
});
it('calculates high static content ratio for mostly static templates', function () {
$content = 'Static content
More static content here with lots of text
Even more static HTML content{{ small_dynamic }}';
// Create template file
$filePath = $this->testDir . '/test/static.view.php';
$dir = dirname($filePath);
if (!is_dir($dir)) {
mkdir($dir, 0755, true);
}
file_put_contents($filePath, $content);
$score = $this->analyzer->getCacheability('test/static');
expect($score->staticContentRatio)->toBeGreaterThan(0.8);
expect($score->isCacheable())->toBeTrue();
});
it('calculates low static content ratio for mostly dynamic templates', function () {
$content = '{{ var1 }} {{ var2 }} {{ var3 }} {{ var4 }} static
';
// Create template file
$filePath = $this->testDir . '/test/dynamic.view.php';
$dir = dirname($filePath);
if (!is_dir($dir)) {
mkdir($dir, 0755, true);
}
file_put_contents($filePath, $content);
$score = $this->analyzer->getCacheability('test/dynamic');
expect($score->staticContentRatio)->toBeLessThan(0.5);
});
it('returns default score when loader throws exception', function () {
$score = $this->analyzer->getCacheability('test/error');
// Default CacheabilityScore has staticContentRatio = 0.0
expect($score->staticContentRatio)->toBe(0.0);
expect($score->hasUserSpecificContent)->toBeFalse();
expect($score->hasCsrfTokens)->toBeFalse();
expect($score->hasTimestamps)->toBeFalse();
expect($score->hasRandomElements)->toBeFalse();
});
it('handles empty content gracefully', function () {
// Create empty template file
$filePath = $this->testDir . '/test/empty.view.php';
$dir = dirname($filePath);
if (!is_dir($dir)) {
mkdir($dir, 0755, true);
}
file_put_contents($filePath, '');
$score = $this->analyzer->getCacheability('test/empty');
expect($score->staticContentRatio)->toBe(0.0);
});
});
describe('integration scenarios', function () {
it('correctly analyzes a typical blog post template', function () {
$content = <<
{{ post.title }}
{{ post.content }}
HTML;
// Create template file
$filePath = $this->testDir . '/blog/post.view.php';
$dir = dirname($filePath);
if (!is_dir($dir)) {
mkdir($dir, 0755, true);
}
file_put_contents($filePath, $content);
$analysis = $this->analyzer->analyze('blog/post');
expect($analysis->cacheability->hasTimestamps)->toBeFalse();
expect($analysis->recommendedStrategy)->toBe(CacheStrategy::FRAGMENT);
});
it('correctly analyzes a static landing page', function () {
$content = <<
Welcome
HTML;
// Create template file
$filePath = $this->testDir . '/pages/landing.view.php';
$dir = dirname($filePath);
if (!is_dir($dir)) {
mkdir($dir, 0755, true);
}
file_put_contents($filePath, $content);
$analysis = $this->analyzer->analyze('pages/landing');
expect($analysis->recommendedStrategy)->toBe(CacheStrategy::FULL_PAGE);
expect($analysis->cacheability->staticContentRatio)->toBeGreaterThan(0.9);
});
it('correctly analyzes a reusable button component', function () {
$content = '';
// Create template file
$filePath = $this->testDir . '/components/button2.view.php';
$dir = dirname($filePath);
if (!is_dir($dir)) {
mkdir($dir, 0755, true);
}
file_put_contents($filePath, $content);
$analysis = $this->analyzer->analyze('components/button2');
expect($analysis->recommendedStrategy)->toBe(CacheStrategy::COMPONENT);
expect($analysis->recommendedTtl)->toBeGreaterThan(0);
});
it('correctly analyzes a form with CSRF protection', function () {
$content = <<
{{ csrf_token }}
HTML;
// Create template file
$filePath = $this->testDir . '/forms/contact2.view.php';
$dir = dirname($filePath);
if (!is_dir($dir)) {
mkdir($dir, 0755, true);
}
file_put_contents($filePath, $content);
$analysis = $this->analyzer->analyze('forms/contact2');
// CSRF detected and high static ratio means still cacheable, recommends FULL_PAGE
expect($analysis->cacheability->hasCsrfTokens)->toBeTrue();
expect($analysis->cacheability->staticContentRatio)->toBeGreaterThan(0.8);
expect($analysis->recommendedStrategy)->toBe(CacheStrategy::FULL_PAGE);
});
});
});