dbMigrateResult = [ 'command' => 'db:migrate', 'status' => 'ok', 'exit_code' => 0, 'vendor_warnings_ignored' => 0, 'message' => 'db-migrate: applied 3 migration(s).', ]; $command = new MigrateCommand($runner); ob_start(); $exitCode = $command->execute([], []); $output = (string) ob_get_clean(); self::assertSame(0, $exitCode); self::assertStringContainsString('db-migrate: applied 3 migration(s).', $output); } public function testDbMigrateFailureWritesToStderrAndReturnsNonZero(): void { $runner = new FakeModuleRunner(); $runner->dbMigrateResult = [ 'command' => 'db:migrate', 'status' => 'failed', 'exit_code' => 1, 'vendor_warnings_ignored' => 0, 'message' => 'db-migrate: failure: Core migration foo.sql failed: SQLSTATE[42S02]', ]; $command = new MigrateCommand($runner); ob_start(); $exitCode = $command->execute([], []); ob_end_clean(); self::assertSame(1, $exitCode); } public function testDbMigrateStatusListsAppliedAndPending(): void { $runner = new FakeModuleRunner(); $runner->dbMigrateStatusResult = [ 'command' => 'db:migrate --status', 'applied' => [ '2026-02-23-tenant-scope-global.sql', '2026-03-13-seed-default-roles.sql', ], 'pending' => [ '2026-04-29-core-migrations-table.sql', ], ]; $command = new MigrateCommand($runner); ob_start(); $exitCode = $command->execute([], ['status' => true]); $output = (string) ob_get_clean(); self::assertSame(0, $exitCode); self::assertStringContainsString('2 applied, 1 pending', $output); self::assertStringContainsString('2026-02-23-tenant-scope-global.sql', $output); self::assertStringContainsString('2026-04-29-core-migrations-table.sql', $output); self::assertStringContainsString("applied:\n", $output); self::assertStringContainsString("pending:\n", $output); } public function testDbMigrateStatusHandlesEmptyLists(): void { $runner = new FakeModuleRunner(); $runner->dbMigrateStatusResult = [ 'command' => 'db:migrate --status', 'applied' => [], 'pending' => [], ]; $command = new MigrateCommand($runner); ob_start(); $exitCode = $command->execute([], ['status' => true]); $output = (string) ob_get_clean(); self::assertSame(0, $exitCode); self::assertStringContainsString('0 applied, 0 pending', $output); self::assertStringContainsString("applied:\n (none)", $output); self::assertStringContainsString("pending:\n (none)", $output); } }