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
This commit is contained in:
372
tests/Framework/Discovery/DiscoveryCacheIntegrationTest.php
Normal file
372
tests/Framework/Discovery/DiscoveryCacheIntegrationTest.php
Normal file
@@ -0,0 +1,372 @@
|
||||
<?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);
|
||||
});
|
||||
});
|
||||
|
||||
853
tests/Framework/Discovery/DiscoveryCacheManagerTest.php
Normal file
853
tests/Framework/Discovery/DiscoveryCacheManagerTest.php
Normal file
@@ -0,0 +1,853 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Framework\Discovery;
|
||||
|
||||
use Mockery;
|
||||
use App\Framework\Cache\Cache;
|
||||
use App\Framework\Cache\CacheItem;
|
||||
use App\Framework\Cache\CacheKey;
|
||||
use App\Framework\Cache\CachePrefix;
|
||||
use App\Framework\Cache\CacheResult;
|
||||
use App\Framework\Cache\Driver\InMemoryCache;
|
||||
use App\Framework\Cache\GeneralCache;
|
||||
use App\Framework\Core\Events\EventDispatcher;
|
||||
use App\Framework\Core\ValueObjects\Byte;
|
||||
use App\Framework\Core\ValueObjects\Percentage;
|
||||
use App\Framework\Core\ValueObjects\Timestamp;
|
||||
use App\Framework\DateTime\Clock;
|
||||
use App\Framework\DateTime\SystemClock;
|
||||
use App\Framework\Discovery\Events\CacheCompressionEvent;
|
||||
use App\Framework\Discovery\Events\CacheHitEvent;
|
||||
use App\Framework\Discovery\Events\CacheMissEvent;
|
||||
use App\Framework\Discovery\Memory\DiscoveryMemoryManager;
|
||||
use App\Framework\Discovery\Memory\MemoryStatus;
|
||||
use App\Framework\Discovery\Memory\MemoryStatusInfo;
|
||||
use App\Framework\Discovery\Results\AttributeRegistry;
|
||||
use App\Framework\Discovery\Results\DiscoveryRegistry;
|
||||
use App\Framework\Discovery\Results\InterfaceRegistry;
|
||||
use App\Framework\Discovery\Results\TemplateRegistry;
|
||||
use App\Framework\Discovery\Storage\DiscoveryCacheManager;
|
||||
use App\Framework\Discovery\ValueObjects\CacheLevel;
|
||||
use App\Framework\Discovery\ValueObjects\CacheTier;
|
||||
use App\Framework\Discovery\ValueObjects\CompressionLevel;
|
||||
use App\Framework\Discovery\ValueObjects\DiscoveryContext;
|
||||
use App\Framework\Discovery\ValueObjects\DiscoveryOptions;
|
||||
use App\Framework\Discovery\ValueObjects\MemoryStrategy;
|
||||
use App\Framework\Discovery\ValueObjects\ScanType;
|
||||
use App\Framework\Filesystem\FileSystemService;
|
||||
use App\Framework\Filesystem\ValueObjects\FilePath;
|
||||
use App\Framework\Filesystem\ValueObjects\FileMetadata;
|
||||
use App\Framework\Serializer\Php\PhpSerializer;
|
||||
use App\Framework\Serializer\Php\PhpSerializerConfig;
|
||||
|
||||
describe('DiscoveryCacheManager - Basic Operations', function () {
|
||||
beforeEach(function () {
|
||||
$cacheDriver = new InMemoryCache();
|
||||
$serializer = new PhpSerializer(PhpSerializerConfig::safe());
|
||||
$this->cache = new GeneralCache($cacheDriver, $serializer);
|
||||
$this->clock = new SystemClock();
|
||||
$this->fileSystemService = new FileSystemService();
|
||||
|
||||
// Use a real existing path to avoid stale detection issues
|
||||
$basePath = file_exists('/var/www/html/src') ? '/var/www/html' : '/home/michael/dev/michaelschiemer';
|
||||
$testPath = $basePath . '/src';
|
||||
|
||||
$this->cacheManager = new DiscoveryCacheManager(
|
||||
cache: $this->cache,
|
||||
clock: $this->clock,
|
||||
fileSystemService: $this->fileSystemService
|
||||
);
|
||||
|
||||
// Use a future time to avoid stale detection issues
|
||||
$futureTime = new \DateTimeImmutable('2099-01-01 00:00:00');
|
||||
$this->testContext = new DiscoveryContext(
|
||||
paths: [$testPath],
|
||||
scanType: ScanType::FULL,
|
||||
options: new DiscoveryOptions(),
|
||||
startTime: $futureTime
|
||||
);
|
||||
|
||||
$this->testRegistry = new DiscoveryRegistry(
|
||||
attributes: new AttributeRegistry(),
|
||||
interfaces: new InterfaceRegistry(),
|
||||
templates: new TemplateRegistry()
|
||||
);
|
||||
});
|
||||
|
||||
it('can be instantiated', function () {
|
||||
expect($this->cacheManager)->toBeInstanceOf(DiscoveryCacheManager::class);
|
||||
});
|
||||
|
||||
it('stores discovery results in cache', function () {
|
||||
$success = $this->cacheManager->store($this->testContext, $this->testRegistry);
|
||||
|
||||
expect($success)->toBeTrue();
|
||||
|
||||
// Verify cache contains the data
|
||||
$cached = $this->cacheManager->get($this->testContext);
|
||||
expect($cached)->toBeInstanceOf(DiscoveryRegistry::class);
|
||||
expect($cached->isEmpty())->toBe($this->testRegistry->isEmpty());
|
||||
});
|
||||
|
||||
it('retrieves cached discovery results', function () {
|
||||
// Store first
|
||||
$this->cacheManager->store($this->testContext, $this->testRegistry);
|
||||
|
||||
// Retrieve
|
||||
$cached = $this->cacheManager->get($this->testContext);
|
||||
|
||||
expect($cached)->toBeInstanceOf(DiscoveryRegistry::class);
|
||||
expect($cached)->not->toBeNull();
|
||||
});
|
||||
|
||||
it('returns null for cache miss', function () {
|
||||
$cached = $this->cacheManager->get($this->testContext);
|
||||
|
||||
expect($cached)->toBeNull();
|
||||
});
|
||||
|
||||
it('invalidates cache for a context', function () {
|
||||
// Store first
|
||||
$this->cacheManager->store($this->testContext, $this->testRegistry);
|
||||
expect($this->cacheManager->get($this->testContext))->not->toBeNull();
|
||||
|
||||
// Invalidate
|
||||
$success = $this->cacheManager->invalidate($this->testContext);
|
||||
expect($success)->toBeTrue();
|
||||
|
||||
// Should be null now
|
||||
expect($this->cacheManager->get($this->testContext))->toBeNull();
|
||||
});
|
||||
|
||||
it('clears all discovery caches', function () {
|
||||
// Store multiple contexts
|
||||
$context1 = new DiscoveryContext(
|
||||
paths: ['/test/path1'],
|
||||
scanType: ScanType::FULL,
|
||||
options: new DiscoveryOptions(),
|
||||
startTime: $this->clock->now()
|
||||
);
|
||||
$context2 = new DiscoveryContext(
|
||||
paths: ['/test/path2'],
|
||||
scanType: ScanType::FULL,
|
||||
options: new DiscoveryOptions(),
|
||||
startTime: $this->clock->now()
|
||||
);
|
||||
|
||||
$this->cacheManager->store($context1, $this->testRegistry);
|
||||
$this->cacheManager->store($context2, $this->testRegistry);
|
||||
|
||||
expect($this->cacheManager->get($context1))->not->toBeNull();
|
||||
expect($this->cacheManager->get($context2))->not->toBeNull();
|
||||
|
||||
// Clear all
|
||||
$success = $this->cacheManager->clearAll();
|
||||
expect($success)->toBeTrue();
|
||||
|
||||
// Both should be null
|
||||
expect($this->cacheManager->get($context1))->toBeNull();
|
||||
expect($this->cacheManager->get($context2))->toBeNull();
|
||||
});
|
||||
|
||||
it('optimizes registry before caching', function () {
|
||||
$registry = new DiscoveryRegistry(
|
||||
attributes: new AttributeRegistry(),
|
||||
interfaces: new InterfaceRegistry(),
|
||||
templates: new TemplateRegistry()
|
||||
);
|
||||
|
||||
// Store should call optimize internally
|
||||
$success = $this->cacheManager->store($this->testContext, $registry);
|
||||
expect($success)->toBeTrue();
|
||||
});
|
||||
});
|
||||
|
||||
describe('DiscoveryCacheManager - Cache Hit/Miss Scenarios', function () {
|
||||
beforeEach(function () {
|
||||
$cacheDriver = new InMemoryCache();
|
||||
$serializer = new PhpSerializer(PhpSerializerConfig::safe());
|
||||
$this->cache = new GeneralCache($cacheDriver, $serializer);
|
||||
$this->clock = new SystemClock();
|
||||
$this->fileSystemService = new FileSystemService();
|
||||
|
||||
// Use a real existing path
|
||||
$basePath = file_exists('/var/www/html/src') ? '/var/www/html' : '/home/michael/dev/michaelschiemer';
|
||||
$testPath = $basePath . '/src';
|
||||
|
||||
$this->cacheManager = new DiscoveryCacheManager(
|
||||
cache: $this->cache,
|
||||
clock: $this->clock,
|
||||
fileSystemService: $this->fileSystemService
|
||||
);
|
||||
|
||||
$this->testContext = new DiscoveryContext(
|
||||
paths: ['/test/path'],
|
||||
scanType: ScanType::FULL,
|
||||
options: new DiscoveryOptions(),
|
||||
startTime: $this->clock->now()
|
||||
);
|
||||
|
||||
$this->testRegistry = new DiscoveryRegistry(
|
||||
attributes: new AttributeRegistry(),
|
||||
interfaces: new InterfaceRegistry(),
|
||||
templates: new TemplateRegistry()
|
||||
);
|
||||
});
|
||||
|
||||
it('handles cache hit correctly', function () {
|
||||
$this->cacheManager->store($this->testContext, $this->testRegistry);
|
||||
|
||||
$cached = $this->cacheManager->get($this->testContext);
|
||||
|
||||
expect($cached)->not->toBeNull();
|
||||
expect($cached)->toBeInstanceOf(DiscoveryRegistry::class);
|
||||
});
|
||||
|
||||
it('handles cache miss when key does not exist', function () {
|
||||
$cached = $this->cacheManager->get($this->testContext);
|
||||
|
||||
expect($cached)->toBeNull();
|
||||
});
|
||||
|
||||
it('handles cache miss when data is corrupted', function () {
|
||||
// Store invalid data directly in cache
|
||||
$key = $this->testContext->getCacheKey();
|
||||
$invalidItem = CacheItem::forSet($key, 'invalid_data');
|
||||
$this->cache->set($invalidItem);
|
||||
|
||||
$cached = $this->cacheManager->get($this->testContext);
|
||||
|
||||
expect($cached)->toBeNull();
|
||||
});
|
||||
|
||||
it('handles cache miss when data type is wrong', function () {
|
||||
// Store wrong type
|
||||
$key = $this->testContext->getCacheKey();
|
||||
$wrongTypeItem = CacheItem::forSet($key, ['not' => 'a', 'registry' => 'object']);
|
||||
$this->cache->set($wrongTypeItem);
|
||||
|
||||
$cached = $this->cacheManager->get($this->testContext);
|
||||
|
||||
expect($cached)->toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('DiscoveryCacheManager - Stale Cache Detection', function () {
|
||||
beforeEach(function () {
|
||||
$cacheDriver = new InMemoryCache();
|
||||
$serializer = new PhpSerializer(PhpSerializerConfig::safe());
|
||||
$this->cache = new GeneralCache($cacheDriver, $serializer);
|
||||
$this->clock = new SystemClock();
|
||||
|
||||
// FileSystemService is final, so we need to use a real instance
|
||||
// For stale detection tests, we'll use a real path and manipulate timing
|
||||
$this->fileSystemService = new FileSystemService();
|
||||
|
||||
$this->cacheManager = new DiscoveryCacheManager(
|
||||
cache: $this->cache,
|
||||
clock: $this->clock,
|
||||
fileSystemService: $this->fileSystemService
|
||||
);
|
||||
|
||||
$this->testRegistry = new DiscoveryRegistry(
|
||||
attributes: new AttributeRegistry(),
|
||||
interfaces: new InterfaceRegistry(),
|
||||
templates: new TemplateRegistry()
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
Mockery::close();
|
||||
});
|
||||
|
||||
it('considers incremental scans as always stale', function () {
|
||||
$basePath = file_exists('/var/www/html/src') ? '/var/www/html' : '/home/michael/dev/michaelschiemer';
|
||||
$testPath = $basePath . '/src';
|
||||
|
||||
$incrementalContext = new DiscoveryContext(
|
||||
paths: [$testPath],
|
||||
scanType: ScanType::INCREMENTAL,
|
||||
options: new DiscoveryOptions(),
|
||||
startTime: $this->clock->now()
|
||||
);
|
||||
|
||||
$this->cacheManager->store($incrementalContext, $this->testRegistry);
|
||||
|
||||
// Incremental scans should always be considered stale
|
||||
$cached = $this->cacheManager->get($incrementalContext);
|
||||
|
||||
expect($cached)->toBeNull();
|
||||
});
|
||||
|
||||
it('detects stale cache when directory is modified', function () {
|
||||
// Use a real path that exists
|
||||
$basePath = file_exists('/var/www/html/src') ? '/var/www/html' : '/home/michael/dev/michaelschiemer';
|
||||
$testPath = $basePath . '/src';
|
||||
|
||||
$startTime = new \DateTimeImmutable('2020-01-01 00:00:00');
|
||||
$context = new DiscoveryContext(
|
||||
paths: [$testPath],
|
||||
scanType: ScanType::FULL,
|
||||
options: new DiscoveryOptions(),
|
||||
startTime: $startTime
|
||||
);
|
||||
|
||||
// Store cache
|
||||
$this->cacheManager->store($context, $this->testRegistry);
|
||||
|
||||
// The directory will have a modification time after startTime, so it should be stale
|
||||
// This test verifies that stale detection works
|
||||
$cached = $this->cacheManager->get($context);
|
||||
|
||||
// Should be stale because real directory modification time is after startTime
|
||||
expect($cached)->toBeNull();
|
||||
});
|
||||
|
||||
it('considers cache fresh when directory is not modified', function () {
|
||||
// Use a real path that exists
|
||||
$basePath = file_exists('/var/www/html/src') ? '/var/www/html' : '/home/michael/dev/michaelschiemer';
|
||||
$testPath = $basePath . '/src';
|
||||
|
||||
// Use a future time so the directory modification time is before it
|
||||
$futureTime = new \DateTimeImmutable('2099-01-01 00:00:00');
|
||||
$context = new DiscoveryContext(
|
||||
paths: [$testPath],
|
||||
scanType: ScanType::FULL,
|
||||
options: new DiscoveryOptions(),
|
||||
startTime: $futureTime
|
||||
);
|
||||
|
||||
// Store cache
|
||||
$this->cacheManager->store($context, $this->testRegistry);
|
||||
|
||||
// Should be fresh because directory modification time is before futureTime
|
||||
$cached = $this->cacheManager->get($context);
|
||||
|
||||
expect($cached)->not->toBeNull();
|
||||
expect($cached)->toBeInstanceOf(DiscoveryRegistry::class);
|
||||
});
|
||||
|
||||
it('handles file system errors gracefully in stale detection', function () {
|
||||
// Use a non-existent path to trigger file system error
|
||||
$context = new DiscoveryContext(
|
||||
paths: ['/nonexistent/path/that/does/not/exist'],
|
||||
scanType: ScanType::FULL,
|
||||
options: new DiscoveryOptions(),
|
||||
startTime: $this->clock->now()
|
||||
);
|
||||
|
||||
$this->cacheManager->store($context, $this->testRegistry);
|
||||
|
||||
// Should assume stale on error (non-existent path throws exception)
|
||||
$cached = $this->cacheManager->get($context);
|
||||
|
||||
expect($cached)->toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('DiscoveryCacheManager - Health Status', function () {
|
||||
beforeEach(function () {
|
||||
$cacheDriver = new InMemoryCache();
|
||||
$serializer = new PhpSerializer(PhpSerializerConfig::safe());
|
||||
$this->cache = new GeneralCache($cacheDriver, $serializer);
|
||||
$this->clock = new SystemClock();
|
||||
$this->fileSystemService = new FileSystemService();
|
||||
|
||||
$this->cacheManager = new DiscoveryCacheManager(
|
||||
cache: $this->cache,
|
||||
clock: $this->clock,
|
||||
fileSystemService: $this->fileSystemService
|
||||
);
|
||||
});
|
||||
|
||||
it('returns health status without memory manager', function () {
|
||||
$health = $this->cacheManager->getHealthStatus();
|
||||
|
||||
expect($health)->toBeArray();
|
||||
expect($health)->toHaveKey('cache_driver');
|
||||
expect($health)->toHaveKey('ttl_hours');
|
||||
expect($health)->toHaveKey('prefix');
|
||||
expect($health)->toHaveKey('memory_aware');
|
||||
expect($health['memory_aware'])->toBeFalse();
|
||||
});
|
||||
|
||||
it('returns health status with memory manager', function () {
|
||||
$memoryManager = new DiscoveryMemoryManager(
|
||||
strategy: MemoryStrategy::BATCH,
|
||||
memoryLimit: Byte::fromMegabytes(128),
|
||||
memoryPressureThreshold: 0.8,
|
||||
memoryMonitor: null,
|
||||
logger: null,
|
||||
eventDispatcher: null,
|
||||
clock: $this->clock
|
||||
);
|
||||
|
||||
$cacheManager = new DiscoveryCacheManager(
|
||||
cache: $this->cache,
|
||||
clock: $this->clock,
|
||||
fileSystemService: $this->fileSystemService,
|
||||
logger: null,
|
||||
ttlHours: 24,
|
||||
memoryManager: $memoryManager
|
||||
);
|
||||
|
||||
$health = $cacheManager->getHealthStatus();
|
||||
|
||||
expect($health)->toBeArray();
|
||||
expect($health['memory_aware'])->toBeTrue();
|
||||
expect($health)->toHaveKey('memory_management');
|
||||
expect($health['memory_management'])->toBeArray();
|
||||
expect($health['memory_management'])->toHaveKey('status');
|
||||
expect($health['memory_management'])->toHaveKey('current_usage');
|
||||
expect($health['memory_management'])->toHaveKey('memory_pressure');
|
||||
expect($health['memory_management'])->toHaveKey('cache_level');
|
||||
});
|
||||
});
|
||||
|
||||
describe('DiscoveryCacheManager - Memory-Aware Caching', function () {
|
||||
beforeEach(function () {
|
||||
$cacheDriver = new InMemoryCache();
|
||||
$serializer = new PhpSerializer(PhpSerializerConfig::safe());
|
||||
$this->cache = new GeneralCache($cacheDriver, $serializer);
|
||||
$this->clock = new SystemClock();
|
||||
$this->fileSystemService = new FileSystemService();
|
||||
|
||||
$this->memoryManager = new DiscoveryMemoryManager(
|
||||
strategy: MemoryStrategy::BATCH,
|
||||
memoryLimit: Byte::fromMegabytes(128),
|
||||
memoryPressureThreshold: 0.8,
|
||||
memoryMonitor: null,
|
||||
logger: null,
|
||||
eventDispatcher: null,
|
||||
clock: $this->clock
|
||||
);
|
||||
|
||||
$this->cacheManager = new DiscoveryCacheManager(
|
||||
cache: $this->cache,
|
||||
clock: $this->clock,
|
||||
fileSystemService: $this->fileSystemService,
|
||||
logger: null,
|
||||
ttlHours: 24,
|
||||
memoryManager: $this->memoryManager
|
||||
);
|
||||
|
||||
$this->testContext = new DiscoveryContext(
|
||||
paths: ['/test/path'],
|
||||
scanType: ScanType::FULL,
|
||||
options: new DiscoveryOptions(),
|
||||
startTime: $this->clock->now()
|
||||
);
|
||||
|
||||
$this->testRegistry = new DiscoveryRegistry(
|
||||
attributes: new AttributeRegistry(),
|
||||
interfaces: new InterfaceRegistry(),
|
||||
templates: new TemplateRegistry()
|
||||
);
|
||||
});
|
||||
|
||||
it('uses memory-aware storage when memory manager is available', function () {
|
||||
$success = $this->cacheManager->store($this->testContext, $this->testRegistry);
|
||||
|
||||
expect($success)->toBeTrue();
|
||||
|
||||
// Should be retrievable
|
||||
$cached = $this->cacheManager->get($this->testContext);
|
||||
expect($cached)->toBeInstanceOf(DiscoveryRegistry::class);
|
||||
});
|
||||
|
||||
it('determines cache level based on memory pressure', function () {
|
||||
// This is tested indirectly through storage, but we can verify health status
|
||||
$health = $this->cacheManager->getHealthStatus();
|
||||
|
||||
expect($health['memory_aware'])->toBeTrue();
|
||||
expect($health['memory_management'])->toBeArray();
|
||||
expect($health['memory_management'])->toHaveKey('cache_level');
|
||||
});
|
||||
|
||||
it('performs memory pressure management', function () {
|
||||
$result = $this->cacheManager->performMemoryPressureManagement();
|
||||
|
||||
expect($result)->toBeArray();
|
||||
expect($result)->toHaveKey('actions');
|
||||
expect($result)->toHaveKey('memory_status');
|
||||
expect($result)->toHaveKey('cache_level');
|
||||
});
|
||||
|
||||
it('clears cache in critical memory situations', function () {
|
||||
// Store some data
|
||||
$this->cacheManager->store($this->testContext, $this->testRegistry);
|
||||
expect($this->cacheManager->get($this->testContext))->not->toBeNull();
|
||||
|
||||
// Create memory manager with very low limit to trigger critical status
|
||||
$lowMemoryManager = new DiscoveryMemoryManager(
|
||||
strategy: MemoryStrategy::BATCH,
|
||||
memoryLimit: Byte::fromBytes(1024), // Very low limit
|
||||
memoryPressureThreshold: 0.8,
|
||||
memoryMonitor: null,
|
||||
logger: null,
|
||||
eventDispatcher: null,
|
||||
clock: $this->clock
|
||||
);
|
||||
|
||||
$cacheManager = new DiscoveryCacheManager(
|
||||
cache: $this->cache,
|
||||
clock: $this->clock,
|
||||
fileSystemService: $this->fileSystemService,
|
||||
logger: null,
|
||||
ttlHours: 24,
|
||||
memoryManager: $lowMemoryManager
|
||||
);
|
||||
|
||||
// Store with low memory manager
|
||||
$cacheManager->store($this->testContext, $this->testRegistry);
|
||||
|
||||
// Force memory usage to trigger critical
|
||||
// Note: This is hard to test directly, but the method exists
|
||||
$result = $cacheManager->performMemoryPressureManagement();
|
||||
expect($result)->toBeArray();
|
||||
});
|
||||
});
|
||||
|
||||
describe('DiscoveryCacheManager - Tiered Caching', function () {
|
||||
beforeEach(function () {
|
||||
$cacheDriver = new InMemoryCache();
|
||||
$serializer = new PhpSerializer(PhpSerializerConfig::safe());
|
||||
$this->cache = new GeneralCache($cacheDriver, $serializer);
|
||||
$this->clock = new SystemClock();
|
||||
$this->fileSystemService = new FileSystemService();
|
||||
|
||||
$this->memoryManager = new DiscoveryMemoryManager(
|
||||
strategy: MemoryStrategy::BATCH,
|
||||
memoryLimit: Byte::fromMegabytes(128),
|
||||
memoryPressureThreshold: 0.8,
|
||||
memoryMonitor: null,
|
||||
logger: null,
|
||||
eventDispatcher: null,
|
||||
clock: $this->clock
|
||||
);
|
||||
|
||||
$this->cacheManager = new DiscoveryCacheManager(
|
||||
cache: $this->cache,
|
||||
clock: $this->clock,
|
||||
fileSystemService: $this->fileSystemService,
|
||||
logger: null,
|
||||
ttlHours: 24,
|
||||
memoryManager: $this->memoryManager
|
||||
);
|
||||
|
||||
$this->testContext = new DiscoveryContext(
|
||||
paths: ['/test/path'],
|
||||
scanType: ScanType::FULL,
|
||||
options: new DiscoveryOptions(),
|
||||
startTime: $this->clock->now()
|
||||
);
|
||||
|
||||
$this->testRegistry = new DiscoveryRegistry(
|
||||
attributes: new AttributeRegistry(),
|
||||
interfaces: new InterfaceRegistry(),
|
||||
templates: new TemplateRegistry()
|
||||
);
|
||||
});
|
||||
|
||||
it('uses tiered caching when memory manager is available', function () {
|
||||
$success = $this->cacheManager->store($this->testContext, $this->testRegistry);
|
||||
|
||||
expect($success)->toBeTrue();
|
||||
|
||||
// Should be retrievable from tiered cache
|
||||
$cached = $this->cacheManager->get($this->testContext);
|
||||
expect($cached)->toBeInstanceOf(DiscoveryRegistry::class);
|
||||
});
|
||||
|
||||
it('determines appropriate cache tier based on data size and access frequency', function () {
|
||||
// Store data - tier determination happens internally
|
||||
$success = $this->cacheManager->store($this->testContext, $this->testRegistry);
|
||||
|
||||
expect($success)->toBeTrue();
|
||||
|
||||
// Verify it can be retrieved (tiered cache lookup)
|
||||
$cached = $this->cacheManager->get($this->testContext);
|
||||
expect($cached)->toBeInstanceOf(DiscoveryRegistry::class);
|
||||
});
|
||||
|
||||
it('applies different TTL multipliers per tier', function () {
|
||||
// Store data - TTL adjustment happens internally based on tier
|
||||
$success = $this->cacheManager->store($this->testContext, $this->testRegistry);
|
||||
|
||||
expect($success)->toBeTrue();
|
||||
|
||||
// Verify retrieval works
|
||||
$cached = $this->cacheManager->get($this->testContext);
|
||||
expect($cached)->toBeInstanceOf(DiscoveryRegistry::class);
|
||||
});
|
||||
|
||||
it('tracks access patterns for tier determination', function () {
|
||||
// Store and retrieve multiple times to build access pattern
|
||||
$this->cacheManager->store($this->testContext, $this->testRegistry);
|
||||
|
||||
// Multiple accesses
|
||||
$this->cacheManager->get($this->testContext);
|
||||
$this->cacheManager->get($this->testContext);
|
||||
$this->cacheManager->get($this->testContext);
|
||||
|
||||
// Should still work
|
||||
$cached = $this->cacheManager->get($this->testContext);
|
||||
expect($cached)->toBeInstanceOf(DiscoveryRegistry::class);
|
||||
});
|
||||
});
|
||||
|
||||
describe('DiscoveryCacheManager - Cache Compression', function () {
|
||||
beforeEach(function () {
|
||||
$cacheDriver = new InMemoryCache();
|
||||
$serializer = new PhpSerializer(PhpSerializerConfig::safe());
|
||||
$this->cache = new GeneralCache($cacheDriver, $serializer);
|
||||
$this->clock = new SystemClock();
|
||||
$this->fileSystemService = new FileSystemService();
|
||||
|
||||
$this->memoryManager = new DiscoveryMemoryManager(
|
||||
strategy: MemoryStrategy::BATCH,
|
||||
memoryLimit: Byte::fromMegabytes(128),
|
||||
memoryPressureThreshold: 0.8,
|
||||
memoryMonitor: null,
|
||||
logger: null,
|
||||
eventDispatcher: null,
|
||||
clock: $this->clock
|
||||
);
|
||||
|
||||
$this->cacheManager = new DiscoveryCacheManager(
|
||||
cache: $this->cache,
|
||||
clock: $this->clock,
|
||||
fileSystemService: $this->fileSystemService,
|
||||
logger: null,
|
||||
ttlHours: 24,
|
||||
memoryManager: $this->memoryManager
|
||||
);
|
||||
|
||||
$this->testContext = new DiscoveryContext(
|
||||
paths: ['/test/path'],
|
||||
scanType: ScanType::FULL,
|
||||
options: new DiscoveryOptions(),
|
||||
startTime: $this->clock->now()
|
||||
);
|
||||
|
||||
$this->testRegistry = new DiscoveryRegistry(
|
||||
attributes: new AttributeRegistry(),
|
||||
interfaces: new InterfaceRegistry(),
|
||||
templates: new TemplateRegistry()
|
||||
);
|
||||
});
|
||||
|
||||
it('compresses data for appropriate tiers', function () {
|
||||
// Store data - compression happens internally for appropriate tiers
|
||||
$success = $this->cacheManager->store($this->testContext, $this->testRegistry);
|
||||
|
||||
expect($success)->toBeTrue();
|
||||
|
||||
// Should be retrievable and decompressed
|
||||
$cached = $this->cacheManager->get($this->testContext);
|
||||
expect($cached)->toBeInstanceOf(DiscoveryRegistry::class);
|
||||
});
|
||||
|
||||
it('decompresses data when retrieving from cache', function () {
|
||||
// Store compressed data
|
||||
$this->cacheManager->store($this->testContext, $this->testRegistry);
|
||||
|
||||
// Retrieve should decompress automatically
|
||||
$cached = $this->cacheManager->get($this->testContext);
|
||||
|
||||
expect($cached)->toBeInstanceOf(DiscoveryRegistry::class);
|
||||
expect($cached->isEmpty())->toBe($this->testRegistry->isEmpty());
|
||||
});
|
||||
|
||||
it('handles uncompressed data correctly', function () {
|
||||
// Store data that might not need compression (small size, hot tier)
|
||||
$success = $this->cacheManager->store($this->testContext, $this->testRegistry);
|
||||
|
||||
expect($success)->toBeTrue();
|
||||
|
||||
// Should still be retrievable
|
||||
$cached = $this->cacheManager->get($this->testContext);
|
||||
expect($cached)->toBeInstanceOf(DiscoveryRegistry::class);
|
||||
});
|
||||
});
|
||||
|
||||
describe('DiscoveryCacheManager - Cache Metrics', function () {
|
||||
beforeEach(function () {
|
||||
$cacheDriver = new InMemoryCache();
|
||||
$serializer = new PhpSerializer(PhpSerializerConfig::safe());
|
||||
$this->cache = new GeneralCache($cacheDriver, $serializer);
|
||||
$this->clock = new SystemClock();
|
||||
$this->fileSystemService = new FileSystemService();
|
||||
|
||||
$this->memoryManager = new DiscoveryMemoryManager(
|
||||
strategy: MemoryStrategy::BATCH,
|
||||
memoryLimit: Byte::fromMegabytes(128),
|
||||
memoryPressureThreshold: 0.8,
|
||||
memoryMonitor: null,
|
||||
logger: null,
|
||||
eventDispatcher: null,
|
||||
clock: $this->clock
|
||||
);
|
||||
|
||||
$this->cacheManager = new DiscoveryCacheManager(
|
||||
cache: $this->cache,
|
||||
clock: $this->clock,
|
||||
fileSystemService: $this->fileSystemService,
|
||||
logger: null,
|
||||
ttlHours: 24,
|
||||
memoryManager: $this->memoryManager
|
||||
);
|
||||
|
||||
$this->testContext = new DiscoveryContext(
|
||||
paths: ['/test/path'],
|
||||
scanType: ScanType::FULL,
|
||||
options: new DiscoveryOptions(),
|
||||
startTime: $this->clock->now()
|
||||
);
|
||||
|
||||
$this->testRegistry = new DiscoveryRegistry(
|
||||
attributes: new AttributeRegistry(),
|
||||
interfaces: new InterfaceRegistry(),
|
||||
templates: new TemplateRegistry()
|
||||
);
|
||||
});
|
||||
|
||||
it('returns null metrics when memory manager is not available', function () {
|
||||
$cacheManager = new DiscoveryCacheManager(
|
||||
cache: $this->cache,
|
||||
clock: $this->clock,
|
||||
fileSystemService: $this->fileSystemService
|
||||
);
|
||||
|
||||
$metrics = $cacheManager->getCacheMetrics();
|
||||
|
||||
expect($metrics)->toBeNull();
|
||||
});
|
||||
|
||||
it('returns cache metrics when memory manager is available', function () {
|
||||
// Store and retrieve to generate metrics
|
||||
$this->cacheManager->store($this->testContext, $this->testRegistry);
|
||||
$this->cacheManager->get($this->testContext);
|
||||
|
||||
$metrics = $this->cacheManager->getCacheMetrics();
|
||||
|
||||
expect($metrics)->not->toBeNull();
|
||||
expect($metrics)->toBeInstanceOf(\App\Framework\Discovery\ValueObjects\CacheMetrics::class);
|
||||
});
|
||||
|
||||
it('calculates hit rate correctly', function () {
|
||||
// Store data
|
||||
$this->cacheManager->store($this->testContext, $this->testRegistry);
|
||||
|
||||
// Multiple hits
|
||||
$this->cacheManager->get($this->testContext);
|
||||
$this->cacheManager->get($this->testContext);
|
||||
|
||||
$metrics = $this->cacheManager->getCacheMetrics();
|
||||
|
||||
expect($metrics)->not->toBeNull();
|
||||
// Hit rate should be calculated (exact value depends on implementation)
|
||||
});
|
||||
|
||||
it('tracks total cache size', function () {
|
||||
$this->cacheManager->store($this->testContext, $this->testRegistry);
|
||||
|
||||
$metrics = $this->cacheManager->getCacheMetrics();
|
||||
|
||||
expect($metrics)->not->toBeNull();
|
||||
expect($metrics->totalSize)->toBeInstanceOf(Byte::class);
|
||||
});
|
||||
});
|
||||
|
||||
describe('DiscoveryCacheManager - Cache Events', function () {
|
||||
beforeEach(function () {
|
||||
$cacheDriver = new InMemoryCache();
|
||||
$serializer = new PhpSerializer(PhpSerializerConfig::safe());
|
||||
$this->cache = new GeneralCache($cacheDriver, $serializer);
|
||||
$this->clock = new SystemClock();
|
||||
$this->fileSystemService = new FileSystemService();
|
||||
// EventDispatcher is final and requires a Container
|
||||
// We'll use a real instance but can't verify events directly
|
||||
$this->container = new \App\Framework\DI\DefaultContainer();
|
||||
$this->eventDispatcher = new EventDispatcher($this->container);
|
||||
|
||||
$this->memoryManager = new DiscoveryMemoryManager(
|
||||
strategy: MemoryStrategy::BATCH,
|
||||
memoryLimit: Byte::fromMegabytes(128),
|
||||
memoryPressureThreshold: 0.8,
|
||||
memoryMonitor: null,
|
||||
logger: null,
|
||||
eventDispatcher: $this->eventDispatcher,
|
||||
clock: $this->clock
|
||||
);
|
||||
|
||||
$this->cacheManager = new DiscoveryCacheManager(
|
||||
cache: $this->cache,
|
||||
clock: $this->clock,
|
||||
fileSystemService: $this->fileSystemService,
|
||||
logger: null,
|
||||
ttlHours: 24,
|
||||
memoryManager: $this->memoryManager,
|
||||
eventDispatcher: $this->eventDispatcher
|
||||
);
|
||||
|
||||
// Use a future time to avoid stale detection issues
|
||||
$futureTime = new \DateTimeImmutable('2099-01-01 00:00:00');
|
||||
$this->testContext = new DiscoveryContext(
|
||||
paths: [$testPath],
|
||||
scanType: ScanType::FULL,
|
||||
options: new DiscoveryOptions(),
|
||||
startTime: $futureTime
|
||||
);
|
||||
|
||||
$this->testRegistry = new DiscoveryRegistry(
|
||||
attributes: new AttributeRegistry(),
|
||||
interfaces: new InterfaceRegistry(),
|
||||
templates: new TemplateRegistry()
|
||||
);
|
||||
});
|
||||
|
||||
it('dispatches CacheHitEvent on cache hit', function () {
|
||||
// Store first
|
||||
$this->cacheManager->store($this->testContext, $this->testRegistry);
|
||||
|
||||
// EventDispatcher is final, so we can't verify events directly
|
||||
// But we can verify the cache hit works
|
||||
$cached = $this->cacheManager->get($this->testContext);
|
||||
expect($cached)->toBeInstanceOf(DiscoveryRegistry::class);
|
||||
});
|
||||
|
||||
it('dispatches CacheMissEvent on cache miss', function () {
|
||||
// EventDispatcher is final, so we can't verify events directly
|
||||
// But we can verify the cache miss works
|
||||
$cached = $this->cacheManager->get($this->testContext);
|
||||
expect($cached)->toBeNull();
|
||||
});
|
||||
|
||||
it('dispatches CacheCompressionEvent when compressing data', function () {
|
||||
// EventDispatcher is final, so we can't verify events directly
|
||||
// But we can verify compression works by storing and retrieving
|
||||
$success = $this->cacheManager->store($this->testContext, $this->testRegistry);
|
||||
expect($success)->toBeTrue();
|
||||
|
||||
$cached = $this->cacheManager->get($this->testContext);
|
||||
expect($cached)->toBeInstanceOf(DiscoveryRegistry::class);
|
||||
});
|
||||
|
||||
it('handles multiple miss scenarios for different reasons', function () {
|
||||
// Not found scenario
|
||||
$cached = $this->cacheManager->get($this->testContext);
|
||||
expect($cached)->toBeNull();
|
||||
|
||||
// Corrupted data scenario
|
||||
$key = $this->testContext->getCacheKey();
|
||||
$invalidItem = CacheItem::forSet($key, 'invalid');
|
||||
$this->cache->set($invalidItem);
|
||||
|
||||
$cached = $this->cacheManager->get($this->testContext);
|
||||
expect($cached)->toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user