container = new DefaultContainer(); // Register DemoCommand in container $this->container->singleton(DemoCommand::class, fn () => new DemoCommand()); // Create a mock DiscoveryRegistry with console commands $this->discoveredAttributes = [ new DiscoveredAttribute( attributeClass: ConsoleCommand::class, className: ClassName::create(DemoCommand::class), methodName: MethodName::create('hello'), arguments: ['demo:hello', 'Zeigt eine einfache Hallo-Welt-Nachricht'], target: AttributeTarget::METHOD ), new DiscoveredAttribute( attributeClass: ConsoleCommand::class, className: ClassName::create(DemoCommand::class), methodName: MethodName::create('colors'), arguments: ['demo:colors', 'Zeigt alle verfügbaren Farben'], target: AttributeTarget::METHOD ), new DiscoveredAttribute( attributeClass: ConsoleCommand::class, className: ClassName::create(DemoCommand::class), methodName: MethodName::create('interactive'), arguments: ['demo:interactive', 'Interaktive Demo mit Benutzereingaben'], target: AttributeTarget::METHOD ), ]; $attributeRegistry = new AttributeRegistry(); foreach ($this->discoveredAttributes as $attribute) { $attributeRegistry->add(ConsoleCommand::class, $attribute); } $this->discoveryRegistry = new DiscoveryRegistry( attributes: $attributeRegistry, interfaces: new InterfaceRegistry([]), templates: new TemplateRegistry([]) ); // Create test output $this->output = new class () implements \App\Framework\Console\ConsoleOutputInterface { public array $capturedLines = []; public function write(string $message, null|\App\Framework\Console\ConsoleColor|\App\Framework\Console\ConsoleStyle $style = null): void { $this->capturedLines[] = $message; } public function writeLine(string $message = '', ?\App\Framework\Console\ConsoleColor $color = null): void { $this->capturedLines[] = $message; } public function writeSuccess(string $message): void { $this->capturedLines[] = "SUCCESS: $message"; } public function writeError(string $message): void { $this->capturedLines[] = "ERROR: $message"; } public function writeWarning(string $message): void { $this->capturedLines[] = "WARNING: $message"; } public function writeInfo(string $message): void { $this->capturedLines[] = "INFO: $message"; } public function newLine(int $count = 1): void { for ($i = 0; $i < $count; $i++) { $this->capturedLines[] = ''; } } public function askQuestion(string $question, ?string $default = null): string { return $default ?? ''; } public function confirm(string $question, bool $default = false): bool { return $default; } public function writeWindowTitle(string $title, int $mode = 0): void { // No-op for testing } }; }); // Helper function to create DiscoveryRegistry with attributes function createRegistryWithAttributes(array $attributes): DiscoveryRegistry { $attributeRegistry = new AttributeRegistry(); foreach ($attributes as $attribute) { $attributeRegistry->add(ConsoleCommand::class, $attribute); } return new DiscoveryRegistry( attributes: $attributeRegistry, interfaces: new InterfaceRegistry([]), templates: new TemplateRegistry([]) ); } it('creates CommandRegistry with DiscoveryRegistry', function () { $registry = new CommandRegistry($this->container, $this->discoveryRegistry); expect($registry)->toBeInstanceOf(CommandRegistry::class); $commandList = $registry->getCommandList(); expect($commandList)->toBeInstanceOf(CommandList::class); expect($commandList->count())->toBe(3); }); it('discovers all demo commands correctly', function () { $registry = new CommandRegistry($this->container, $this->discoveryRegistry); $commandList = $registry->getCommandList(); // Should have all demo commands expect($commandList->has('demo:hello'))->toBeTrue(); expect($commandList->has('demo:colors'))->toBeTrue(); expect($commandList->has('demo:interactive'))->toBeTrue(); // Check command details $helloCommand = $commandList->get('demo:hello'); expect($helloCommand->name)->toBe('demo:hello'); expect($helloCommand->description)->toBe('Zeigt eine einfache Hallo-Welt-Nachricht'); }); it('stores discovered attributes for commands', function () { $registry = new CommandRegistry($this->container, $this->discoveryRegistry); // Should be able to get discovered attribute for each command $discoveredAttribute = $registry->getDiscoveredAttribute('demo:hello'); expect($discoveredAttribute)->toBeInstanceOf(DiscoveredAttribute::class); expect($discoveredAttribute->className->getFullyQualified())->toBe(DemoCommand::class); expect($discoveredAttribute->methodName->toString())->toBe('hello'); $discoveredAttribute = $registry->getDiscoveredAttribute('demo:colors'); expect($discoveredAttribute->className->getFullyQualified())->toBe(DemoCommand::class); expect($discoveredAttribute->methodName->toString())->toBe('colors'); }); it('throws exception for unknown command attribute', function () { $registry = new CommandRegistry($this->container, $this->discoveryRegistry); expect(fn () => $registry->getDiscoveredAttribute('unknown:command')) ->toThrow(FrameworkException::class, 'No discovered attribute found for command'); }); it('can execute demo commands', function () { $registry = new CommandRegistry($this->container, $this->discoveryRegistry); // Execute demo:hello command $exitCode = $registry->executeCommand('demo:hello', ['TestName'], $this->output); expect($exitCode)->toBe(ExitCode::SUCCESS); // Check output was captured $output = implode(' ', $this->output->capturedLines); expect($output)->toContain('Hallo, TestName!'); }); it('executes commands with proper argument handling', function () { $registry = new CommandRegistry($this->container, $this->discoveryRegistry); // Execute command with different arguments $exitCode = $registry->executeCommand('demo:hello', [], $this->output); expect($exitCode)->toBe(ExitCode::SUCCESS); // Default name should be used when no argument provided $output = implode(' ', $this->output->capturedLines); expect($output)->toContain('Hallo, Welt!'); }); it('handles command execution errors gracefully', function () { // Create registry with invalid command setup $invalidDiscoveredAttribute = new DiscoveredAttribute( attributeClass: ConsoleCommand::class, className: ClassName::create('NonExistentClass'), methodName: MethodName::create('invalidMethod'), arguments: ['invalid:command', 'Invalid command'], target: AttributeTarget::METHOD ); $invalidRegistry = createRegistryWithAttributes([$invalidDiscoveredAttribute]); $registry = new CommandRegistry($this->container, $invalidRegistry); expect(fn () => $registry->executeCommand('invalid:command', [], $this->output)) ->toThrow(FrameworkException::class); }); it('validates command structure during registration', function () { // Create a command with invalid method $invalidMethodAttribute = new DiscoveredAttribute( attributeClass: ConsoleCommand::class, className: ClassName::create(DemoCommand::class), methodName: MethodName::create('nonExistentMethod'), arguments: ['demo:invalid', 'Invalid method'], target: AttributeTarget::METHOD ); $registryWithInvalid = createRegistryWithAttributes([$invalidMethodAttribute]); // Should handle invalid commands gracefully during registration $registry = new CommandRegistry($this->container, $registryWithInvalid); $commandList = $registry->getCommandList(); // Invalid command should be filtered out expect($commandList->has('demo:invalid'))->toBeFalse(); }); it('normalizes command return values to ExitCode', function () { $registry = new CommandRegistry($this->container, $this->discoveryRegistry); // Demo commands return int 0, should be converted to ExitCode::SUCCESS $exitCode = $registry->executeCommand('demo:hello', [], $this->output); expect($exitCode)->toBeInstanceOf(ExitCode::class); expect($exitCode)->toBe(ExitCode::SUCCESS); }); it('logs warnings for long-running commands', function () { // This test would require a command that takes longer than 30 seconds // For now, just verify the mechanism exists $registry = new CommandRegistry($this->container, $this->discoveryRegistry); // Execute a normal command (should complete quickly) $startTime = microtime(true); $exitCode = $registry->executeCommand('demo:hello', [], $this->output); $endTime = microtime(true); expect($exitCode)->toBe(ExitCode::SUCCESS); expect($endTime - $startTime)->toBeLessThan(1.0); // Should be fast }); it('handles empty discovery registry gracefully', function () { $emptyRegistry = createRegistryWithAttributes([]); $registry = new CommandRegistry($this->container, $emptyRegistry); $commandList = $registry->getCommandList(); expect($commandList->count())->toBe(0); expect($commandList->isEmpty())->toBeTrue(); }); it('prevents duplicate command names', function () { // Create duplicate commands $duplicateAttributes = [ new DiscoveredAttribute( attributeClass: ConsoleCommand::class, className: ClassName::create(DemoCommand::class), methodName: MethodName::create('hello'), arguments: ['duplicate:name', 'First command'], target: AttributeTarget::METHOD ), new DiscoveredAttribute( attributeClass: ConsoleCommand::class, className: ClassName::create(DemoCommand::class), methodName: MethodName::create('colors'), arguments: ['duplicate:name', 'Second command'], target: AttributeTarget::METHOD ), ]; $duplicateRegistry = createRegistryWithAttributes($duplicateAttributes); // Should throw exception for duplicate command names expect(fn () => new CommandRegistry($this->container, $duplicateRegistry)) ->toThrow(FrameworkException::class, "Duplicate command name 'duplicate:name'"); }); }); describe('CommandRegistry Error Scenarios', function () { beforeEach(function () { $this->container = new DefaultContainer(); $this->container->singleton(DemoCommand::class, fn () => new DemoCommand()); $this->output = new class () implements \App\Framework\Console\ConsoleOutputInterface { public array $capturedLines = []; public function write(string $message, null|\App\Framework\Console\ConsoleColor|\App\Framework\Console\ConsoleStyle $style = null): void { $this->capturedLines[] = $message; } public function writeLine(string $message = '', ?\App\Framework\Console\ConsoleColor $color = null): void { $this->capturedLines[] = $message; } public function writeSuccess(string $message): void { $this->capturedLines[] = "SUCCESS: $message"; } public function writeError(string $message): void { $this->capturedLines[] = "ERROR: $message"; } public function writeWarning(string $message): void { $this->capturedLines[] = "WARNING: $message"; } public function writeInfo(string $message): void { $this->capturedLines[] = "INFO: $message"; } public function newLine(int $count = 1): void { for ($i = 0; $i < $count; $i++) { $this->capturedLines[] = ''; } } public function askQuestion(string $question, ?string $default = null): string { return $default ?? ''; } public function confirm(string $question, bool $default = false): bool { return $default; } public function writeWindowTitle(string $title, int $mode = 0): void { // No-op for testing } }; }); it('handles missing class gracefully', function () { $invalidAttribute = new DiscoveredAttribute( attributeClass: ConsoleCommand::class, className: ClassName::create('App\\NonExistent\\Class'), methodName: MethodName::create('method'), arguments: ['test:command', 'Test command'], target: AttributeTarget::METHOD ); $registry = createRegistryWithAttributes([$invalidAttribute]); // Should handle missing class gracefully $commandRegistry = new CommandRegistry($this->container, $registry); $commandList = $commandRegistry->getCommandList(); // Command with missing class should be filtered out expect($commandList->has('test:command'))->toBeFalse(); }); it('handles empty command names gracefully', function () { $emptyNameAttribute = new DiscoveredAttribute( attributeClass: ConsoleCommand::class, className: ClassName::create(DemoCommand::class), methodName: MethodName::create('hello'), arguments: ['', 'Empty name command'], target: AttributeTarget::METHOD ); $registry = createRegistryWithAttributes([$emptyNameAttribute]); // Should handle empty command names gracefully $commandRegistry = new CommandRegistry($this->container, $registry); $commandList = $commandRegistry->getCommandList(); // Command with empty name should be filtered out expect($commandList->count())->toBe(0); }); it('continues registration despite individual command failures', function () { $attributes = [ // Valid command new DiscoveredAttribute( attributeClass: ConsoleCommand::class, className: ClassName::create(DemoCommand::class), methodName: MethodName::create('hello'), arguments: ['demo:hello', 'Valid command'], target: AttributeTarget::METHOD ), // Invalid command (missing class) new DiscoveredAttribute( attributeClass: ConsoleCommand::class, className: ClassName::create('NonExistent\\Class'), methodName: MethodName::create('method'), arguments: ['invalid:command', 'Invalid command'], target: AttributeTarget::METHOD ), // Another valid command new DiscoveredAttribute( attributeClass: ConsoleCommand::class, className: ClassName::create(DemoCommand::class), methodName: MethodName::create('colors'), arguments: ['demo:colors', 'Another valid command'], target: AttributeTarget::METHOD ), ]; $registry = createRegistryWithAttributes($attributes); $commandRegistry = new CommandRegistry($this->container, $registry); $commandList = $commandRegistry->getCommandList(); // Should have registered the valid commands despite the invalid one expect($commandList->count())->toBe(2); expect($commandList->has('demo:hello'))->toBeTrue(); expect($commandList->has('demo:colors'))->toBeTrue(); expect($commandList->has('invalid:command'))->toBeFalse(); }); });