register(new DummyCommand()); $exitCode = $app->run(['bin/console', 'list']); self::assertSame(0, $exitCode); } public function testEmptyArgvShowsListAndReturnsZero(): void { $app = new ConsoleApplication(); $exitCode = $app->run(['bin/console']); self::assertSame(0, $exitCode); } public function testUnknownCommandReturnsOne(): void { $app = new ConsoleApplication(); $exitCode = $app->run(['bin/console', 'nonexistent']); self::assertSame(1, $exitCode); } public function testDispatchesRegisteredCommand(): void { $command = new DummyCommand(); $app = new ConsoleApplication(); $app->register($command); $exitCode = $app->run(['bin/console', 'test:dummy', 'arg1', '--flag', '--key=value']); self::assertSame(0, $exitCode); self::assertSame(['arg1'], $command->lastArgs); self::assertSame(['flag' => true, 'key' => 'value'], $command->lastOptions); } public function testCommandExitCodePropagates(): void { $app = new ConsoleApplication(); $app->register(new FailingCommand()); $exitCode = $app->run(['bin/console', 'test:fail']); self::assertSame(42, $exitCode); } public function testMultiplePositionalArgs(): void { $command = new DummyCommand(); $app = new ConsoleApplication(); $app->register($command); $app->run(['bin/console', 'test:dummy', 'first', 'second', 'third']); self::assertSame(['first', 'second', 'third'], $command->lastArgs); } public function testOptionWithEqualsSign(): void { $command = new DummyCommand(); $app = new ConsoleApplication(); $app->register($command); $app->run(['bin/console', 'test:dummy', '--module=addressbook']); self::assertSame([], $command->lastArgs); self::assertSame(['module' => 'addressbook'], $command->lastOptions); } public function testHelpOptionShowsUsageAndReturnsZero(): void { $command = new CommandWithUsage(); $app = new ConsoleApplication(); $app->register($command); $exitCode = $app->run(['bin/console', 'test:usage', '--help']); self::assertSame(0, $exitCode); self::assertFalse($command->wasExecuted, 'Command should not execute when --help is passed'); } public function testHelpOnCommandWithoutUsageShowsNameAndDescription(): void { $command = new DummyCommand(); $app = new ConsoleApplication(); $app->register($command); $exitCode = $app->run(['bin/console', 'test:dummy', '--help']); self::assertSame(0, $exitCode); self::assertSame([], $command->lastArgs, 'Command should not execute when --help is passed'); } public function testAutoDiscoverFindsCommandsInDirectory(): void { $app = new ConsoleApplication(); $commandsDir = dirname(__DIR__, 2) . '/core/Console/Commands'; $app->discoverCommands($commandsDir); // Should find at least the 8 commands we have $exitCode = $app->run(['bin/console', 'module:sync', '--help']); self::assertSame(0, $exitCode); $exitCode = $app->run(['bin/console', 'scheduler:run', '--help']); self::assertSame(0, $exitCode); $exitCode = $app->run(['bin/console', 'doctor', '--help']); self::assertSame(0, $exitCode); } public function testAutoDiscoverIgnoresNonexistentDirectory(): void { $app = new ConsoleApplication(); $app->discoverCommands(sys_get_temp_dir() . '/nonexistent-console-' . uniqid()); // Should not throw, just have no commands $exitCode = $app->run(['bin/console', 'anything']); self::assertSame(1, $exitCode); } public function testProjectRootResolvesCorrectly(): void { $command = new ProjectRootTestCommand(); $expectedRoot = realpath(dirname(__DIR__, 2)); self::assertSame($expectedRoot, realpath($command->getProjectRoot())); } } final class DummyCommand extends Command { public array $lastArgs = []; public array $lastOptions = []; public function name(): string { return 'test:dummy'; } public function description(): string { return 'A dummy command'; } public function execute(array $args, array $options): int { $this->lastArgs = $args; $this->lastOptions = $options; return 0; } } final class FailingCommand extends Command { public function name(): string { return 'test:fail'; } public function description(): string { return 'Always fails'; } public function execute(array $args, array $options): int { return 42; } } final class CommandWithUsage extends Command { public bool $wasExecuted = false; public function name(): string { return 'test:usage'; } public function description(): string { return 'Has custom usage text'; } public function usage(): string { return "Usage: php bin/console test:usage \n\nA test command with usage."; } public function execute(array $args, array $options): int { $this->wasExecuted = true; return 0; } } final class ProjectRootTestCommand extends Command { public function name(): string { return 'test:root'; } public function description(): string { return 'Tests project root'; } public function getProjectRoot(): string { return $this->projectRoot(); } public function execute(array $args, array $options): int { return 0; } }