diff --git a/core/Command/Status.php b/core/Command/Status.php index a00d4a94658fd..092fe00f81cb9 100644 --- a/core/Command/Status.php +++ b/core/Command/Status.php @@ -9,6 +9,7 @@ use OCP\Defaults; use OCP\IConfig; +use OCP\IDBConnection; use OCP\ServerVersion; use OCP\Util; use Symfony\Component\Console\Input\InputInterface; @@ -20,6 +21,7 @@ public function __construct( private IConfig $config, private Defaults $themingDefaults, private ServerVersion $serverVersion, + private IDBConnection $connection, ) { parent::__construct('status'); } @@ -33,26 +35,56 @@ protected function configure() { 'exit-code', 'e', InputOption::VALUE_NONE, - 'exit with 0 if running in normal mode, 1 when in maintenance mode, 2 when `./occ upgrade` is needed. Does not write any output to STDOUT.' + 'exit with 0 if running in normal mode, 1 when in maintenance mode, 2 when `./occ upgrade` is needed, 3 when the database connection failed. Does not write any output to STDOUT.' ); } protected function execute(InputInterface $input, OutputInterface $output): int { $maintenanceMode = $this->config->getSystemValueBool('maintenance', false); $needUpgrade = Util::needUpgrade(); + $installed = $this->config->getSystemValueBool('installed', false); + + // Test database connection if Nextcloud is installed + $databaseStatus = 'not_configured'; + $databaseError = null; + if ($installed) { + try { + $result = $this->connection->executeQuery('SELECT 1'); + $result->closeCursor(); + $databaseStatus = 'connected'; + } catch (\Exception $e) { + $databaseStatus = 'connection_failed'; + $databaseError = $e->getMessage(); + } + } + $values = [ - 'installed' => $this->config->getSystemValueBool('installed', false), + 'installed' => $installed, 'version' => implode('.', $this->serverVersion->getVersion()), 'versionstring' => $this->serverVersion->getVersionString(), 'edition' => '', 'maintenance' => $maintenanceMode, 'needsDbUpgrade' => $needUpgrade, 'productname' => $this->themingDefaults->getProductName(), - 'extendedSupport' => Util::hasExtendedSupport() + 'extendedSupport' => Util::hasExtendedSupport(), + 'database' => $databaseStatus ]; + + if ($databaseError) { + $values['database_error'] = $databaseError; + } if ($input->getOption('verbose') || !$input->getOption('exit-code')) { $this->writeArrayInOutputFormat($input, $output, $values); + + // Add friendly status messages + if (!$installed) { + $output->writeln('Nextcloud is not installed yet'); + } elseif ($maintenanceMode) { + $output->writeln('Nextcloud is in maintenance mode'); + } elseif ($databaseStatus === 'connection_failed') { + $output->writeln('Database connection failed: ' . $databaseError . ''); + } } if ($input->getOption('exit-code')) { @@ -62,6 +94,9 @@ protected function execute(InputInterface $input, OutputInterface $output): int if ($needUpgrade === true) { return 2; } + if ($databaseStatus === 'connection_failed') { + return 3; + } } return 0; } diff --git a/tests/Core/Command/StatusTest.php b/tests/Core/Command/StatusTest.php new file mode 100644 index 0000000000000..57444ee50308f --- /dev/null +++ b/tests/Core/Command/StatusTest.php @@ -0,0 +1,316 @@ +config = $this->createMock(IConfig::class); + $this->themingDefaults = $this->createMock(Defaults::class); + $this->serverVersion = $this->createMock(ServerVersion::class); + $this->connection = $this->createMock(IDBConnection::class); + + $command = new Status( + $this->config, + $this->themingDefaults, + $this->serverVersion, + $this->connection + ); + $this->commandTester = new CommandTester($command); + } + + public function testStatusWithNotInstalled(): void { + $this->config->expects($this->exactly(2)) + ->method('getSystemValueBool') + ->willReturnMap([ + ['maintenance', false, false], + ['installed', false, false], + ]); + + $this->serverVersion->expects($this->once()) + ->method('getVersion') + ->willReturn([30, 0, 0]); + + $this->serverVersion->expects($this->once()) + ->method('getVersionString') + ->willReturn('30.0.0.0'); + + $this->themingDefaults->expects($this->once()) + ->method('getProductName') + ->willReturn('Nextcloud'); + + // Database connection should not be tested when not installed + $this->connection->expects($this->never()) + ->method('executeQuery'); + + $this->commandTester->execute([]); + + $output = $this->commandTester->getDisplay(); + $this->assertStringContainsString('"installed":false', $output); + $this->assertStringContainsString('"database":"not_configured"', $output); + $this->assertStringContainsString('Nextcloud is not installed yet', $output); + $this->assertEquals(0, $this->commandTester->getStatusCode()); + } + + public function testStatusWithDatabaseConnectionSuccess(): void { + $this->config->expects($this->exactly(2)) + ->method('getSystemValueBool') + ->willReturnMap([ + ['maintenance', false, false], + ['installed', false, true], + ]); + + $this->serverVersion->expects($this->once()) + ->method('getVersion') + ->willReturn([30, 0, 0]); + + $this->serverVersion->expects($this->once()) + ->method('getVersionString') + ->willReturn('30.0.0.0'); + + $this->themingDefaults->expects($this->once()) + ->method('getProductName') + ->willReturn('Nextcloud'); + + // Mock successful database connection + $this->connection->expects($this->once()) + ->method('executeQuery') + ->with('SELECT 1') + ->willReturn(true); + + $this->commandTester->execute([]); + + $output = $this->commandTester->getDisplay(); + $this->assertStringContainsString('"installed":true', $output); + $this->assertStringContainsString('"database":"connected"', $output); + $this->assertStringNotContainsString('database_error', $output); + $this->assertEquals(0, $this->commandTester->getStatusCode()); + } + + public function testStatusWithDatabaseConnectionFailure(): void { + $this->config->expects($this->exactly(2)) + ->method('getSystemValueBool') + ->willReturnMap([ + ['maintenance', false, false], + ['installed', false, true], + ]); + + $this->serverVersion->expects($this->once()) + ->method('getVersion') + ->willReturn([30, 0, 0]); + + $this->serverVersion->expects($this->once()) + ->method('getVersionString') + ->willReturn('30.0.0.0'); + + $this->themingDefaults->expects($this->once()) + ->method('getProductName') + ->willReturn('Nextcloud'); + + // Mock failed database connection + $this->connection->expects($this->once()) + ->method('executeQuery') + ->with('SELECT 1') + ->willThrowException(new \Exception('Connection failed')); + + $this->commandTester->execute([]); + + $output = $this->commandTester->getDisplay(); + $this->assertStringContainsString('"installed":true', $output); + $this->assertStringContainsString('"database":"connection_failed"', $output); + $this->assertStringContainsString('"database_error":"Connection failed"', $output); + $this->assertStringContainsString('Database connection failed: Connection failed', $output); + $this->assertEquals(0, $this->commandTester->getStatusCode()); + } + + public function testStatusWithMaintenanceMode(): void { + $this->config->expects($this->exactly(2)) + ->method('getSystemValueBool') + ->willReturnMap([ + ['maintenance', false, true], + ['installed', false, true], + ]); + + $this->serverVersion->expects($this->once()) + ->method('getVersion') + ->willReturn([30, 0, 0]); + + $this->serverVersion->expects($this->once()) + ->method('getVersionString') + ->willReturn('30.0.0.0'); + + $this->themingDefaults->expects($this->once()) + ->method('getProductName') + ->willReturn('Nextcloud'); + + // Mock successful database connection + $this->connection->expects($this->once()) + ->method('executeQuery') + ->with('SELECT 1') + ->willReturn(true); + + $this->commandTester->execute([]); + + $output = $this->commandTester->getDisplay(); + $this->assertStringContainsString('"maintenance":true', $output); + $this->assertStringContainsString('Nextcloud is in maintenance mode', $output); + $this->assertEquals(0, $this->commandTester->getStatusCode()); + } + + public function testStatusExitCodeNormal(): void { + $this->config->expects($this->exactly(2)) + ->method('getSystemValueBool') + ->willReturnMap([ + ['maintenance', false, false], + ['installed', false, true], + ]); + + $this->serverVersion->expects($this->once()) + ->method('getVersion') + ->willReturn([30, 0, 0]); + + $this->serverVersion->expects($this->once()) + ->method('getVersionString') + ->willReturn('30.0.0.0'); + + $this->themingDefaults->expects($this->once()) + ->method('getProductName') + ->willReturn('Nextcloud'); + + // Mock successful database connection + $this->connection->expects($this->once()) + ->method('executeQuery') + ->with('SELECT 1') + ->willReturn(true); + + $this->commandTester->execute(['--exit-code' => true]); + + $this->assertEquals(0, $this->commandTester->getStatusCode()); + $this->assertEmpty($this->commandTester->getDisplay()); // No output in exit-code mode + } + + public function testStatusExitCodeMaintenanceMode(): void { + $this->config->expects($this->exactly(2)) + ->method('getSystemValueBool') + ->willReturnMap([ + ['maintenance', false, true], + ['installed', false, true], + ]); + + $this->serverVersion->expects($this->once()) + ->method('getVersion') + ->willReturn([30, 0, 0]); + + $this->serverVersion->expects($this->once()) + ->method('getVersionString') + ->willReturn('30.0.0.0'); + + $this->themingDefaults->expects($this->once()) + ->method('getProductName') + ->willReturn('Nextcloud'); + + // Mock successful database connection + $this->connection->expects($this->once()) + ->method('executeQuery') + ->with('SELECT 1') + ->willReturn(true); + + $this->commandTester->execute(['--exit-code' => true]); + + $this->assertEquals(1, $this->commandTester->getStatusCode()); + $this->assertEmpty($this->commandTester->getDisplay()); // No output in exit-code mode + } + + public function testStatusExitCodeDatabaseConnectionFailure(): void { + $this->config->expects($this->exactly(2)) + ->method('getSystemValueBool') + ->willReturnMap([ + ['maintenance', false, false], + ['installed', false, true], + ]); + + $this->serverVersion->expects($this->once()) + ->method('getVersion') + ->willReturn([30, 0, 0]); + + $this->serverVersion->expects($this->once()) + ->method('getVersionString') + ->willReturn('30.0.0.0'); + + $this->themingDefaults->expects($this->once()) + ->method('getProductName') + ->willReturn('Nextcloud'); + + // Mock failed database connection + $this->connection->expects($this->once()) + ->method('executeQuery') + ->with('SELECT 1') + ->willThrowException(new \Exception('Connection failed')); + + $this->commandTester->execute(['--exit-code' => true]); + + $this->assertEquals(3, $this->commandTester->getStatusCode()); // New exit code for DB failure + $this->assertEmpty($this->commandTester->getDisplay()); // No output in exit-code mode + } + + public function testStatusOutputFormat(): void { + $this->config->expects($this->exactly(2)) + ->method('getSystemValueBool') + ->willReturnMap([ + ['maintenance', false, false], + ['installed', false, true], + ]); + + $this->serverVersion->expects($this->once()) + ->method('getVersion') + ->willReturn([30, 0, 1]); + + $this->serverVersion->expects($this->once()) + ->method('getVersionString') + ->willReturn('30.0.1.2'); + + $this->themingDefaults->expects($this->once()) + ->method('getProductName') + ->willReturn('Nextcloud Test'); + + // Mock successful database connection + $this->connection->expects($this->once()) + ->method('executeQuery') + ->with('SELECT 1') + ->willReturn(true); + + $this->commandTester->execute([]); + + $output = $this->commandTester->getDisplay(); + $this->assertStringContainsString('"installed":true', $output); + $this->assertStringContainsString('"version":"30.0.1"', $output); + $this->assertStringContainsString('"versionstring":"30.0.1.2"', $output); + $this->assertStringContainsString('"edition":""', $output); + $this->assertStringContainsString('"maintenance":false', $output); + $this->assertStringContainsString('"productname":"Nextcloud Test"', $output); + $this->assertStringContainsString('"database":"connected"', $output); + } +} \ No newline at end of file