Files
michaelschiemer/tests/Framework/Discovery/DiscoveryCacheIntegrationTest.php
Michael Schiemer 8f3c15ddbb fix(console): comprehensive TUI rendering fixes
- Fix Enter key detection: handle multiple Enter key formats (\n, \r, \r\n)
- Reduce flickering: lower render frequency from 60 FPS to 30 FPS
- Fix menu bar visibility: re-render menu bar after content to prevent overwriting
- Fix content positioning: explicit line positioning for categories and commands
- Fix line shifting: clear lines before writing, control newlines manually
- Limit visible items: prevent overflow with maxVisibleCategories/Commands
- Improve CPU usage: increase sleep interval when no events processed

This fixes:
- Enter key not working for selection
- Strong flickering of the application
- Menu bar not visible or being overwritten
- Top half of selection list not displayed
- Lines being shifted/misaligned
2025-11-10 11:06:07 +01:00

373 lines
14 KiB
PHP

<?php
declare(strict_types=1);
namespace Tests\Framework\Discovery;
use App\Framework\Cache\Cache;
use App\Framework\Cache\Driver\InMemoryCache;
use App\Framework\Cache\GeneralCache;
use App\Framework\Console\ConsoleCommand;
use App\Framework\Core\PathProvider;
use App\Framework\DateTime\SystemClock;
use App\Framework\Discovery\DiscoveryServiceBootstrapper;
use App\Framework\Discovery\Results\DiscoveryRegistry;
use App\Framework\Discovery\UnifiedDiscoveryService;
use App\Framework\Discovery\ValueObjects\DiscoveryConfiguration;
use App\Framework\Discovery\ValueObjects\DiscoveryOptions;
use App\Framework\Discovery\ValueObjects\ScanType;
use App\Framework\ReflectionLegacy\CachedReflectionProvider;
use App\Framework\Serializer\Php\PhpSerializer;
use App\Framework\Serializer\Php\PhpSerializerConfig;
describe('Discovery + Caching Integration', function () {
beforeEach(function () {
$cacheDriver = new InMemoryCache();
$serializer = new PhpSerializer(PhpSerializerConfig::safe());
$this->cache = new GeneralCache($cacheDriver, $serializer);
$this->clock = new SystemClock();
$basePath = file_exists('/var/www/html/src') ? '/var/www/html' : '/home/michael/dev/michaelschiemer';
$this->pathProvider = new PathProvider($basePath);
$this->reflectionProvider = new CachedReflectionProvider();
$this->configuration = new DiscoveryConfiguration(
paths: [$this->pathProvider->getSourcePath()],
useCache: true,
enableMemoryMonitoring: false,
memoryLimitMB: 128,
maxFilesPerBatch: 100
);
// Clear cache between tests
$this->cache->clear();
});
it('uses cache when discovery is performed with cache enabled', function () {
$service = new UnifiedDiscoveryService(
pathProvider: $this->pathProvider,
cache: $this->cache,
clock: $this->clock,
reflectionProvider: $this->reflectionProvider,
configuration: $this->configuration
);
// First discovery - should cache
$registry1 = $service->discover();
expect($registry1)->toBeInstanceOf(DiscoveryRegistry::class);
// Second discovery - should use cache
$registry2 = $service->discover();
expect($registry2)->toBeInstanceOf(DiscoveryRegistry::class);
// Results should be equivalent
expect($registry1->isEmpty())->toBe($registry2->isEmpty());
});
it('performs fresh discovery when cache is disabled', function () {
$noCacheConfig = new DiscoveryConfiguration(
paths: [$this->pathProvider->getSourcePath()],
useCache: false,
enableMemoryMonitoring: false,
memoryLimitMB: 128,
maxFilesPerBatch: 100
);
$service = new UnifiedDiscoveryService(
pathProvider: $this->pathProvider,
cache: $this->cache,
clock: $this->clock,
reflectionProvider: $this->reflectionProvider,
configuration: $noCacheConfig
);
$registry = $service->discover();
expect($registry)->toBeInstanceOf(DiscoveryRegistry::class);
});
it('invalidates cache when source files change', function () {
$service = new UnifiedDiscoveryService(
pathProvider: $this->pathProvider,
cache: $this->cache,
clock: $this->clock,
reflectionProvider: $this->reflectionProvider,
configuration: $this->configuration
);
// First discovery
$registry1 = $service->discover();
expect($registry1)->toBeInstanceOf(DiscoveryRegistry::class);
// Clear cache to simulate invalidation
$this->cache->clear();
// Second discovery should perform fresh scan
$registry2 = $service->discover();
expect($registry2)->toBeInstanceOf(DiscoveryRegistry::class);
});
it('handles incremental discovery with caching', function () {
$service = new UnifiedDiscoveryService(
pathProvider: $this->pathProvider,
cache: $this->cache,
clock: $this->clock,
reflectionProvider: $this->reflectionProvider,
configuration: $this->configuration
);
// Full discovery first
$fullRegistry = $service->discover();
expect($fullRegistry)->toBeInstanceOf(DiscoveryRegistry::class);
// Incremental discovery
$incrementalRegistry = $service->incrementalDiscover();
expect($incrementalRegistry)->toBeInstanceOf(DiscoveryRegistry::class);
});
it('maintains cache consistency across multiple discovery calls', function () {
$service = new UnifiedDiscoveryService(
pathProvider: $this->pathProvider,
cache: $this->cache,
clock: $this->clock,
reflectionProvider: $this->reflectionProvider,
configuration: $this->configuration
);
// Multiple discoveries
$registry1 = $service->discover();
$registry2 = $service->discover();
$registry3 = $service->discover();
expect($registry1)->toBeInstanceOf(DiscoveryRegistry::class);
expect($registry2)->toBeInstanceOf(DiscoveryRegistry::class);
expect($registry3)->toBeInstanceOf(DiscoveryRegistry::class);
// All should have same structure
expect($registry1->isEmpty())->toBe($registry2->isEmpty());
expect($registry2->isEmpty())->toBe($registry3->isEmpty());
});
it('handles cache misses gracefully', function () {
$service = new UnifiedDiscoveryService(
pathProvider: $this->pathProvider,
cache: $this->cache,
clock: $this->clock,
reflectionProvider: $this->reflectionProvider,
configuration: $this->configuration
);
// Ensure cache is empty
$this->cache->clear();
// Should perform fresh discovery
$registry = $service->discover();
expect($registry)->toBeInstanceOf(DiscoveryRegistry::class);
expect($registry->isEmpty())->toBeFalse();
});
});
describe('Discovery + Caching Performance', function () {
beforeEach(function () {
$cacheDriver = new InMemoryCache();
$serializer = new PhpSerializer(PhpSerializerConfig::safe());
$this->cache = new GeneralCache($cacheDriver, $serializer);
$this->clock = new SystemClock();
$basePath = file_exists('/var/www/html/src') ? '/var/www/html' : '/home/michael/dev/michaelschiemer';
$this->pathProvider = new PathProvider($basePath);
$this->reflectionProvider = new CachedReflectionProvider();
$this->configuration = new DiscoveryConfiguration(
paths: [$this->pathProvider->getSourcePath()],
useCache: true,
enableMemoryMonitoring: false,
memoryLimitMB: 128,
maxFilesPerBatch: 100
);
$this->cache->clear();
});
it('improves performance with cache on subsequent calls', function () {
$service = new UnifiedDiscoveryService(
pathProvider: $this->pathProvider,
cache: $this->cache,
clock: $this->clock,
reflectionProvider: $this->reflectionProvider,
configuration: $this->configuration
);
// First call - no cache
$start1 = microtime(true);
$registry1 = $service->discover();
$time1 = microtime(true) - $start1;
expect($registry1)->toBeInstanceOf(DiscoveryRegistry::class);
// Second call - with cache
$start2 = microtime(true);
$registry2 = $service->discover();
$time2 = microtime(true) - $start2;
expect($registry2)->toBeInstanceOf(DiscoveryRegistry::class);
// Cached call should be faster (or at least not slower)
// Note: In some cases, the difference might be minimal, so we just verify it completes
expect($time2)->toBeLessThanOrEqual($time1 + 0.1); // Allow small margin
});
it('handles large codebase discovery with caching', function () {
$service = new UnifiedDiscoveryService(
pathProvider: $this->pathProvider,
cache: $this->cache,
clock: $this->clock,
reflectionProvider: $this->reflectionProvider,
configuration: $this->configuration
);
$start = microtime(true);
$registry = $service->discover();
$duration = microtime(true) - $start;
expect($registry)->toBeInstanceOf(DiscoveryRegistry::class);
expect($duration)->toBeLessThan(30.0); // Should complete within 30 seconds
});
});
describe('Discovery + Caching with DiscoveryServiceBootstrapper', function () {
beforeEach(function () {
$cacheDriver = new InMemoryCache();
$serializer = new PhpSerializer(PhpSerializerConfig::safe());
$this->cache = new GeneralCache($cacheDriver, $serializer);
$this->clock = new SystemClock();
$basePath = file_exists('/var/www/html/src') ? '/var/www/html' : '/home/michael/dev/michaelschiemer';
$this->pathProvider = new PathProvider($basePath);
$this->container = new \App\Framework\DI\DefaultContainer();
$this->container->singleton(Cache::class, $this->cache);
$this->container->singleton(\App\Framework\DateTime\Clock::class, $this->clock);
$this->container->singleton(PathProvider::class, $this->pathProvider);
$this->cache->clear();
});
it('bootstraps discovery with caching', function () {
$bootstrapper = new DiscoveryServiceBootstrapper($this->container, $this->clock);
$registry = $bootstrapper->bootstrap();
expect($registry)->toBeInstanceOf(DiscoveryRegistry::class);
expect($registry->isEmpty())->toBeFalse();
});
it('uses cached results on subsequent bootstrap calls', function () {
$bootstrapper = new DiscoveryServiceBootstrapper($this->container, $this->clock);
// First bootstrap
$registry1 = $bootstrapper->bootstrap();
expect($registry1)->toBeInstanceOf(DiscoveryRegistry::class);
// Second bootstrap should use cache
$registry2 = $bootstrapper->bootstrap();
expect($registry2)->toBeInstanceOf(DiscoveryRegistry::class);
// Results should be equivalent
expect($registry1->isEmpty())->toBe($registry2->isEmpty());
});
it('handles incremental bootstrap with caching', function () {
$bootstrapper = new DiscoveryServiceBootstrapper($this->container, $this->clock);
// Full bootstrap first
$fullRegistry = $bootstrapper->bootstrap();
expect($fullRegistry)->toBeInstanceOf(DiscoveryRegistry::class);
// Incremental bootstrap
$incrementalRegistry = $bootstrapper->incrementalBootstrap();
expect($incrementalRegistry)->toBeInstanceOf(DiscoveryRegistry::class);
});
it('clears cache when requested', function () {
$bootstrapper = new DiscoveryServiceBootstrapper($this->container, $this->clock);
// Bootstrap and cache
$registry1 = $bootstrapper->bootstrap();
expect($registry1)->toBeInstanceOf(DiscoveryRegistry::class);
// Clear cache
$this->cache->clear();
// Next bootstrap should perform fresh discovery
$registry2 = $bootstrapper->bootstrap();
expect($registry2)->toBeInstanceOf(DiscoveryRegistry::class);
});
});
describe('Discovery + Caching Error Handling', function () {
beforeEach(function () {
$cacheDriver = new InMemoryCache();
$serializer = new PhpSerializer(PhpSerializerConfig::safe());
$this->cache = new GeneralCache($cacheDriver, $serializer);
$this->clock = new SystemClock();
$basePath = file_exists('/var/www/html/src') ? '/var/www/html' : '/home/michael/dev/michaelschiemer';
$this->pathProvider = new PathProvider($basePath);
$this->reflectionProvider = new CachedReflectionProvider();
$this->configuration = new DiscoveryConfiguration(
paths: [$this->pathProvider->getSourcePath()],
useCache: true,
enableMemoryMonitoring: false,
memoryLimitMB: 128,
maxFilesPerBatch: 100
);
});
it('handles corrupted cache data gracefully', function () {
$service = new UnifiedDiscoveryService(
pathProvider: $this->pathProvider,
cache: $this->cache,
clock: $this->clock,
reflectionProvider: $this->reflectionProvider,
configuration: $this->configuration
);
// Store invalid data in cache
$options = new DiscoveryOptions(
scanType: ScanType::FULL,
paths: [$this->pathProvider->getSourcePath()],
useCache: true
);
$context = new \App\Framework\Discovery\ValueObjects\DiscoveryContext(
paths: [$this->pathProvider->getSourcePath()],
scanType: ScanType::FULL,
options: $options,
startTime: $this->clock->now()
);
$key = $context->getCacheKey();
$invalidItem = \App\Framework\Cache\CacheItem::forSet($key, 'corrupted_data');
$this->cache->set($invalidItem);
// Should handle gracefully and perform fresh discovery
$registry = $service->discover();
expect($registry)->toBeInstanceOf(DiscoveryRegistry::class);
});
it('handles cache write failures gracefully', function () {
// This is harder to test directly, but we can verify the service still works
$service = new UnifiedDiscoveryService(
pathProvider: $this->pathProvider,
cache: $this->cache,
clock: $this->clock,
reflectionProvider: $this->reflectionProvider,
configuration: $this->configuration
);
// Should still work even if cache write fails
$registry = $service->discover();
expect($registry)->toBeInstanceOf(DiscoveryRegistry::class);
});
});