getMethods(ReflectionMethod::IS_PUBLIC); $foundCommands = []; foreach ($methods as $method) { $attributes = $method->getAttributes(ConsoleCommand::class); foreach ($attributes as $attribute) { $command = $attribute->newInstance(); $foundCommands[$command->name] = [ 'name' => $command->name, 'description' => $command->description, 'method' => $method->getName(), ]; } } // Should find all the demo commands expect($foundCommands)->toHaveKey('demo:hello'); expect($foundCommands)->toHaveKey('demo:colors'); expect($foundCommands)->toHaveKey('demo:interactive'); expect($foundCommands)->toHaveKey('demo:menu'); expect($foundCommands)->toHaveKey('demo:simple-menu'); expect($foundCommands)->toHaveKey('demo:wizard'); // Verify command details expect($foundCommands['demo:hello']['description'])->toBe('Zeigt eine einfache Hallo-Welt-Nachricht'); expect($foundCommands['demo:hello']['method'])->toBe('hello'); expect($foundCommands['demo:colors']['description'])->toBe('Zeigt alle verfügbaren Farben'); expect($foundCommands['demo:colors']['method'])->toBe('colors'); expect($foundCommands['demo:interactive']['description'])->toBe('Interaktive Demo mit Benutzereingaben'); expect($foundCommands['demo:interactive']['method'])->toBe('interactive'); }); it('correctly parses ConsoleCommand attribute syntax', function () { // Test that the correct syntax #[ConsoleCommand] works properly $reflection = new ReflectionMethod(DemoCommand::class, 'hello'); $attributes = $reflection->getAttributes(ConsoleCommand::class); expect($attributes)->toHaveCount(1); $command = $attributes[0]->newInstance(); expect($command)->toBeInstanceOf(ConsoleCommand::class); expect($command->name)->toBe('demo:hello'); expect($command->description)->toBe('Zeigt eine einfache Hallo-Welt-Nachricht'); }); it('validates that all discovered commands have unique names', function () { $reflection = new ReflectionClass(DemoCommand::class); $methods = $reflection->getMethods(ReflectionMethod::IS_PUBLIC); $commandNames = []; $duplicates = []; foreach ($methods as $method) { $attributes = $method->getAttributes(ConsoleCommand::class); foreach ($attributes as $attribute) { $command = $attribute->newInstance(); if (isset($commandNames[$command->name])) { $duplicates[] = $command->name; } else { $commandNames[$command->name] = true; } } } // Should have no duplicate command names expect($duplicates)->toBeEmpty(); // Should have 6 unique commands expect(count($commandNames))->toBe(6); }); it('ensures command names are properly formatted', function () { $reflection = new ReflectionClass(DemoCommand::class); $methods = $reflection->getMethods(ReflectionMethod::IS_PUBLIC); foreach ($methods as $method) { $attributes = $method->getAttributes(ConsoleCommand::class); foreach ($attributes as $attribute) { $command = $attribute->newInstance(); // Command names should not be empty expect($command->name)->not->toBeEmpty(); // Command names should contain a colon (namespace:command format) expect($command->name)->toContain(':'); // Command names should start with 'demo:' expect($command->name)->toStartWith('demo:'); // Command names should not contain spaces expect($command->name)->not->toContain(' '); // Command descriptions should not be empty expect($command->description)->not->toBeEmpty(); } } }); it('can create attribute instances from discovered attributes', function () { $reflection = new ReflectionMethod(DemoCommand::class, 'hello'); $attributes = $reflection->getAttributes(ConsoleCommand::class); $attribute = $attributes[0]; $command = $attribute->newInstance(); expect($command)->toBeInstanceOf(ConsoleCommand::class); expect($command->name)->toBe('demo:hello'); expect($command->description)->toBe('Zeigt eine einfache Hallo-Welt-Nachricht'); }); it('handles edge cases in command discovery', function () { // Test that methods without ConsoleCommand attributes are ignored $reflection = new ReflectionClass(DemoCommand::class); $userMenuMethod = $reflection->getMethod('userMenu'); $attributes = $userMenuMethod->getAttributes(ConsoleCommand::class); expect($attributes)->toBeEmpty(); }); it('validates that discovered commands have public methods', function () { $reflection = new ReflectionClass(DemoCommand::class); $methods = $reflection->getMethods(ReflectionMethod::IS_PUBLIC); foreach ($methods as $method) { $attributes = $method->getAttributes(ConsoleCommand::class); if (! empty($attributes)) { expect($method->isPublic())->toBeTrue(); expect($method->isStatic())->toBeFalse(); } } }); }); describe('ConsoleCommand Attribute Integration with Discovery System', function () { it('can be discovered through DiscoveredAttribute system', function () { // Create a mock DiscoveredAttribute for a ConsoleCommand $className = \App\Framework\Core\ValueObjects\ClassName::create(DemoCommand::class); $methodName = \App\Framework\Core\ValueObjects\MethodName::create('hello'); $discoveredAttribute = new DiscoveredAttribute( attributeClass: ConsoleCommand::class, className: $className, methodName: $methodName, arguments: ['demo:hello', 'Zeigt eine einfache Hallo-Welt-Nachricht'], target: \App\Framework\Discovery\ValueObjects\AttributeTarget::METHOD ); // Test that we can create the attribute instance $command = $discoveredAttribute->createAttributeInstance(); expect($command)->toBeInstanceOf(ConsoleCommand::class); expect($command->name)->toBe('demo:hello'); expect($command->description)->toBe('Zeigt eine einfache Hallo-Welt-Nachricht'); }); it('properly handles attribute target validation', function () { // ConsoleCommand attributes should only be on methods $className = \App\Framework\Core\ValueObjects\ClassName::create(DemoCommand::class); $methodName = \App\Framework\Core\ValueObjects\MethodName::create('hello'); $discoveredAttribute = new DiscoveredAttribute( attributeClass: ConsoleCommand::class, className: $className, methodName: $methodName, arguments: ['demo:hello', 'Test description'], target: \App\Framework\Discovery\ValueObjects\AttributeTarget::METHOD ); expect($discoveredAttribute->target)->toBe(\App\Framework\Discovery\ValueObjects\AttributeTarget::METHOD); }); }); describe('ConsoleCommand Discovery Cache Behavior', function () { it('generates context-specific cache keys', function () { $paths = ['/test/path']; // Test different execution contexts generate different cache keys $consoleKey = DiscoveryCacheIdentifiers::fullDiscoveryKey($paths, 'console'); $cliScriptKey = DiscoveryCacheIdentifiers::fullDiscoveryKey($paths, 'cli-script'); $webKey = DiscoveryCacheIdentifiers::fullDiscoveryKey($paths, 'web'); expect($consoleKey)->not->toBe($cliScriptKey); expect($consoleKey)->not->toBe($webKey); expect($cliScriptKey)->not->toBe($webKey); // All should be valid cache key format expect($consoleKey->toString())->toMatch('/^[a-zA-Z0-9_\-:\.]+$/'); expect($cliScriptKey->toString())->toMatch('/^[a-zA-Z0-9_\-:\.]+$/'); expect($webKey->toString())->toMatch('/^[a-zA-Z0-9_\-:\.]+$/'); }); it('cache keys include execution context information', function () { $paths = ['/test/path']; $context = 'console'; $cacheKey = DiscoveryCacheIdentifiers::fullDiscoveryKey($paths, $context); // Cache key should include context information expect($cacheKey->toString())->toContain($context); }); });