container = new DefaultContainer(); $cacheDriver = new InMemoryCache(); $serializer = new PhpSerializer(PhpSerializerConfig::safe()); $this->cache = new GeneralCache($cacheDriver, $serializer); $this->clock = new SystemClock(); $this->pathProvider = new PathProvider('/home/michael/dev/michaelschiemer'); // Register required dependencies $this->container->singleton(Cache::class, $this->cache); $this->container->singleton(Clock::class, $this->clock); $this->container->singleton(PathProvider::class, $this->pathProvider); // Register ReflectionProvider and ExecutionContext dependencies $reflectionProvider = new CachedReflectionProvider(); $executionContext = ExecutionContext::detect(); $this->container->singleton(ReflectionProvider::class, $reflectionProvider); $this->container->singleton(ExecutionContext::class, $executionContext); $this->container->singleton(InitializerProcessor::class, fn ($c) => new InitializerProcessor( $c, $c->get(ReflectionProvider::class), $c->get(ExecutionContext::class) )); // Register DemoCommand $this->container->singleton(DemoCommand::class, fn () => new DemoCommand()); // Create a simple AppConfig for testing $appConfig = new AppConfig( environment: 'testing', debug: true, timezone: Timezone::UTC, locale: 'en' ); $this->container->singleton(AppConfig::class, $appConfig); // Clear cache $this->cache->clear(); // Create a test output to capture console 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 writeError(string $message): void { $this->capturedLines[] = "ERROR: $message"; } public function writeSuccess(string $message): void { $this->capturedLines[] = "SUCCESS: $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('properly initializes CommandRegistry with discovery', function () { $app = new ConsoleApplication($this->container, 'test-console', 'Test Console', $this->output); // Should not throw exception during initialization expect($app)->toBeInstanceOf(ConsoleApplication::class); // Initialization should complete successfully (no exceptions thrown) // The fact that we got here means discovery and command registry setup worked }); it('handles empty command list with fallback discovery', function () { // First create a scenario where no commands would be found initially $emptyRegistry = new DiscoveryRegistry( attributes: new \App\Framework\Discovery\Results\AttributeRegistry([]), interfaces: new \App\Framework\Discovery\Results\InterfaceRegistry([]), templates: new \App\Framework\Discovery\Results\TemplateRegistry([]) ); // Register the empty registry initially $this->container->singleton(DiscoveryRegistry::class, $emptyRegistry); // ConsoleApplication should trigger fallback discovery $app = new ConsoleApplication($this->container, 'test-console', 'Test Console', $this->output); // Should have attempted fallback discovery $loggedOutput = implode(' ', $this->output->capturedLines); // Either it found commands through fallback or handled the empty case gracefully expect($app)->toBeInstanceOf(ConsoleApplication::class); }); it('can execute demo commands successfully', function () { $app = new ConsoleApplication($this->container, 'test-console', 'Test Console', $this->output); // Test demo:hello command $argv = ['test-console', 'demo:hello', 'TestName']; $exitCode = $app->run($argv); expect($exitCode)->toBe(ExitCode::SUCCESS->value); // Should have captured the hello output $output = implode(' ', $this->output->capturedLines); expect($output)->toContain('Hallo, TestName!'); }); it('shows help when no command is provided', function () { $app = new ConsoleApplication($this->container, 'test-console', 'Test Console', $this->output); // Run with no command $argv = ['test-console']; $exitCode = $app->run($argv); expect($exitCode)->toBe(ExitCode::SUCCESS->value); // Should show help/available commands $output = implode(' ', $this->output->capturedLines); expect($output)->toContain('Verfügbare Kommandos'); }); it('handles unknown commands gracefully', function () { $app = new ConsoleApplication($this->container, 'test-console', 'Test Console', $this->output); // Try to run non-existent command $argv = ['test-console', 'nonexistent:command']; $exitCode = $app->run($argv); expect($exitCode)->toBe(ExitCode::COMMAND_NOT_FOUND->value); // Should show error message $output = implode(' ', $this->output->capturedLines); expect($output)->toContain('nicht gefunden'); }); it('suggests similar commands for typos', function () { $app = new ConsoleApplication($this->container, 'test-console', 'Test Console', $this->output); // Try command with typo (demo:helo instead of demo:hello) $argv = ['test-console', 'demo:helo']; $exitCode = $app->run($argv); expect($exitCode)->toBe(ExitCode::COMMAND_NOT_FOUND->value); // Should suggest similar commands $output = implode(' ', $this->output->capturedLines); expect($output)->toContain('Meinten Sie vielleicht'); }); it('handles help command', function () { $app = new ConsoleApplication($this->container, 'test-console', 'Test Console', $this->output); // Test various help invocations $helpCommands = ['help', '--help', '-h']; foreach ($helpCommands as $helpCmd) { $this->output->capturedLines = []; // Clear previous output $argv = ['test-console', $helpCmd]; $exitCode = $app->run($argv); expect($exitCode)->toBe(ExitCode::SUCCESS->value); $output = implode(' ', $this->output->capturedLines); expect($output)->toContain('Verfügbare Kommandos'); } }); it('finds expected number of commands after discovery', function () { // Force fresh discovery by clearing cache $this->cache->clear(); $app = new ConsoleApplication($this->container, 'test-console', 'Test Console', $this->output); // Run help to see available commands $argv = ['test-console', 'help']; $app->run($argv); $output = implode(' ', $this->output->capturedLines); // Should find demo commands expect($output)->toContain('demo:hello'); expect($output)->toContain('demo:colors'); expect($output)->toContain('demo:interactive'); // Should not show "Keine Kommandos verfügbar" expect($output)->not->toContain('Keine Kommandos verfügbar'); }); it('validates and sanitizes input arguments', function () { $app = new ConsoleApplication($this->container, 'test-console', 'Test Console', $this->output); // Test with various argument types $argv = ['test-console', 'demo:hello', 'ValidName', '--option=value']; $exitCode = $app->run($argv); expect($exitCode)->toBe(ExitCode::SUCCESS->value); // Should have processed arguments without error $output = implode(' ', $this->output->capturedLines); expect($output)->toContain('Hallo, ValidName!'); }); it('handles empty argv gracefully', function () { $app = new ConsoleApplication($this->container, 'test-console', 'Test Console', $this->output); // Test with empty argv (should not happen in practice, but test robustness) try { $exitCode = $app->run([]); // If it doesn't throw, should return an error code expect($exitCode)->toBeInt(); } catch (\InvalidArgumentException $e) { // Expected behavior for invalid input expect($e->getMessage())->toContain('No arguments provided'); } }); it('shows command usage for invalid arguments', function () { $app = new ConsoleApplication($this->container, 'test-console', 'Test Console', $this->output); // This test depends on actual command implementation // For now, just ensure the app can handle commands $argv = ['test-console', 'demo:hello']; $exitCode = $app->run($argv); // Should succeed or fail gracefully expect($exitCode)->toBeInt(); expect($exitCode)->toBeGreaterThanOrEqual(0); expect($exitCode)->toBeLessThan(256); }); }); describe('ConsoleApplication Error Handling', function () { beforeEach(function () { $this->container = new DefaultContainer(); $cacheDriver = new InMemoryCache(); $serializer = new PhpSerializer(PhpSerializerConfig::safe()); $this->cache = new GeneralCache($cacheDriver, $serializer); $this->clock = new SystemClock(); $this->pathProvider = new PathProvider('/home/michael/dev/michaelschiemer'); $this->container->singleton(Cache::class, $this->cache); $this->container->singleton(Clock::class, $this->clock); $this->container->singleton(PathProvider::class, $this->pathProvider); // Register ReflectionProvider and ExecutionContext dependencies $reflectionProvider = new CachedReflectionProvider(); $executionContext = ExecutionContext::detect(); $this->container->singleton(ReflectionProvider::class, $reflectionProvider); $this->container->singleton(ExecutionContext::class, $executionContext); $this->container->singleton(InitializerProcessor::class, fn ($c) => new InitializerProcessor( $c, $c->get(ReflectionProvider::class), $c->get(ExecutionContext::class) )); $this->container->singleton(DemoCommand::class, fn () => new DemoCommand()); $appConfig = new AppConfig( environment: 'testing', debug: true, timezone: Timezone::UTC, locale: 'en' ); $this->container->singleton(AppConfig::class, $appConfig); $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 writeError(string $message): void { $this->capturedLines[] = "ERROR: $message"; } public function writeSuccess(string $message): void { $this->capturedLines[] = "SUCCESS: $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 framework exceptions gracefully', function () { $app = new ConsoleApplication($this->container, 'test-console', 'Test Console', $this->output); // Test with valid command to ensure basic functionality $argv = ['test-console', 'demo:hello']; $exitCode = $app->run($argv); // Should handle any framework exceptions gracefully expect($exitCode)->toBeInt(); expect($exitCode)->toBeGreaterThanOrEqual(0); }); it('provides appropriate exit codes for different error types', function () { $app = new ConsoleApplication($this->container, 'test-console', 'Test Console', $this->output); // Test command not found $argv = ['test-console', 'nonexistent:command']; $exitCode = $app->run($argv); expect($exitCode)->toBe(ExitCode::COMMAND_NOT_FOUND->value); // Test successful command $argv = ['test-console', 'demo:hello']; $exitCode = $app->run($argv); expect($exitCode)->toBe(ExitCode::SUCCESS->value); }); it('shows debug information in development mode', function () { // AppConfig is already set to debug mode in beforeEach $app = new ConsoleApplication($this->container, 'test-console', 'Test Console', $this->output); // Test with any command $argv = ['test-console', 'demo:hello']; $exitCode = $app->run($argv); // Should complete without throwing exceptions expect($exitCode)->toBeInt(); }); it('handles malformed arguments gracefully', function () { $app = new ConsoleApplication($this->container, 'test-console', 'Test Console', $this->output); // Test with various potentially problematic arguments $testCases = [ ['test-console', 'demo:hello', ''], // Empty argument ['test-console', 'demo:hello', 'normal-arg'], // Normal case ]; foreach ($testCases as $argv) { $this->output->capturedLines = []; $exitCode = $app->run($argv); // Should not crash, return valid exit code expect($exitCode)->toBeInt(); expect($exitCode)->toBeGreaterThanOrEqual(0); expect($exitCode)->toBeLessThan(256); } }); });