getType()->value); $webKey = DiscoveryCacheIdentifiers::fullDiscoveryKey($paths, $webContext->getType()->value); $workerKey = DiscoveryCacheIdentifiers::fullDiscoveryKey($paths, $workerContext->getType()->value); $testKey = DiscoveryCacheIdentifiers::fullDiscoveryKey($paths, $testContext->getType()->value); // All keys should be different $keys = [$consoleKey, $webKey, $workerKey, $testKey]; $uniqueKeys = array_unique(array_map(fn ($key) => $key->toString(), $keys)); expect(count($uniqueKeys))->toBe(4); }); it('console and cli-script contexts have separate cache keys', function () { $paths = ['/test/src']; $consoleKey = DiscoveryCacheIdentifiers::fullDiscoveryKey($paths, 'console'); $cliScriptKey = DiscoveryCacheIdentifiers::fullDiscoveryKey($paths, 'cli-script'); expect($consoleKey->toString())->not->toBe($cliScriptKey->toString()); // Both should contain their respective context type expect($consoleKey->toString())->toContain('console'); expect($cliScriptKey->toString())->toContain('cli-script'); }); it('cache keys are consistent for the same context', function () { $paths = ['/test/src']; $context = 'console'; $key1 = DiscoveryCacheIdentifiers::fullDiscoveryKey($paths, $context); $key2 = DiscoveryCacheIdentifiers::fullDiscoveryKey($paths, $context); expect($key1->toString())->toBe($key2->toString()); }); it('cache keys change with different paths', function () { $paths1 = ['/test/src']; $paths2 = ['/test/src', '/test/additional']; $context = 'console'; $key1 = DiscoveryCacheIdentifiers::fullDiscoveryKey($paths1, $context); $key2 = DiscoveryCacheIdentifiers::fullDiscoveryKey($paths2, $context); expect($key1->toString())->not->toBe($key2->toString()); }); it('validates cache key format is safe for storage', function () { $paths = ['/test/src']; $contexts = ['console', 'cli-script', 'web', 'worker', 'test']; foreach ($contexts as $context) { $key = DiscoveryCacheIdentifiers::fullDiscoveryKey($paths, $context); // Cache key should only contain safe characters expect($key->toString())->toMatch('/^[a-zA-Z0-9_\-:\.]+$/'); // Should not be empty expect($key->toString())->not->toBeEmpty(); // Should have reasonable length (not too short or too long) expect(strlen($key->toString()))->toBeGreaterThan(10); expect(strlen($key->toString()))->toBeLessThan(200); } }); }); describe('ExecutionContext Detection and Metadata', function () { it('can detect different execution contexts', function () { // Test forced contexts $consoleContext = ExecutionContext::forConsole(); expect($consoleContext->isConsole())->toBeTrue(); expect($consoleContext->isCli())->toBeTrue(); expect($consoleContext->isWeb())->toBeFalse(); $webContext = ExecutionContext::forWeb(); expect($webContext->isWeb())->toBeTrue(); expect($webContext->isCli())->toBeFalse(); expect($webContext->isConsole())->toBeFalse(); $workerContext = ExecutionContext::forWorker(); expect($workerContext->isWorker())->toBeTrue(); expect($workerContext->isCli())->toBeTrue(); expect($workerContext->isWeb())->toBeFalse(); $testContext = ExecutionContext::forTest(); expect($testContext->isTest())->toBeTrue(); expect($testContext->isCli())->toBeFalse(); // Test context is separate from CLI }); it('provides consistent metadata for cache key generation', function () { $context = ExecutionContext::forConsole(); $metadata1 = $context->getMetadata(); $metadata2 = $context->getMetadata(); // Metadata should be consistent expect($metadata1['type'])->toBe($metadata2['type']); expect($metadata1['sapi'])->toBe($metadata2['sapi']); // Type should match context expect($metadata1['type'])->toBe('console'); }); it('includes execution context type in metadata', function () { $contexts = [ ExecutionContext::forConsole(), ExecutionContext::forWeb(), ExecutionContext::forWorker(), ExecutionContext::forTest(), ]; foreach ($contexts as $context) { $metadata = $context->getMetadata(); expect($metadata)->toHaveKey('type'); expect($metadata['type'])->toBe($context->getType()->value); expect($metadata)->toHaveKey('sapi'); expect($metadata)->toHaveKey('pid'); } }); }); describe('ExecutionContext ContextType Enum', function () { it('has all required context types', function () { $types = [ ContextType::CONSOLE, ContextType::WEB, ContextType::WORKER, ContextType::TEST, ContextType::CLI_SCRIPT, ]; foreach ($types as $type) { expect($type)->toBeInstanceOf(ContextType::class); expect($type->value)->toBeString(); expect($type->value)->not->toBeEmpty(); } }); it('context type values are suitable for cache keys', function () { $types = [ ContextType::CONSOLE, ContextType::WEB, ContextType::WORKER, ContextType::TEST, ContextType::CLI_SCRIPT, ]; foreach ($types as $type) { // Values should be valid for cache keys (no spaces, special chars) expect($type->value)->toMatch('/^[a-z\-]+$/'); expect($type->value)->not->toContain(' '); expect($type->value)->not->toContain('/'); expect($type->value)->not->toContain('\\'); } }); it('console and cli-script are different types', function () { expect(ContextType::CONSOLE->value)->not->toBe(ContextType::CLI_SCRIPT->value); expect(ContextType::CONSOLE->value)->toBe('console'); expect(ContextType::CLI_SCRIPT->value)->toBe('cli-script'); }); }); describe('Cache Key Collision Prevention', function () { it('prevents cache collisions between console.php and other CLI scripts', function () { $paths = ['/test/src']; // Before the fix, console.php was detected as CLI_SCRIPT // Now it should be handled separately as CONSOLE context $consoleKey = DiscoveryCacheIdentifiers::fullDiscoveryKey($paths, ContextType::CONSOLE->value); $cliScriptKey = DiscoveryCacheIdentifiers::fullDiscoveryKey($paths, ContextType::CLI_SCRIPT->value); expect($consoleKey->toString())->not->toBe($cliScriptKey->toString()); // Verify the fix: different contexts should have distinct cache keys $keyDifferences = array_diff_assoc( str_split($consoleKey->toString()), str_split($cliScriptKey->toString()) ); // There should be differences between the keys expect(count($keyDifferences))->toBeGreaterThan(0); }); it('ensures discovery cache separation between execution contexts', function () { $paths = ['/test/src']; // All different contexts should produce different cache keys $contexts = ['console', 'cli-script', 'web', 'worker', 'test']; $keys = []; foreach ($contexts as $context) { $key = DiscoveryCacheIdentifiers::fullDiscoveryKey($paths, $context); $keys[$context] = $key->toString(); } // All keys should be unique $uniqueKeys = array_unique($keys); expect(count($uniqueKeys))->toBe(count($contexts)); // Each key should be identifiable by its context foreach ($contexts as $context) { expect($keys[$context])->toContain($context); } }); });