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); }); });