config = Config::getInstance(); $this->config->selectedServer['DisableIS'] = false; Current::$table = 'table'; Current::$database = 'db'; $table = $this->createMock(Table::class); // Expect the table will have 6 rows $table->method('getRealRowCountTable')->willReturn(6); $table->method('countRecords')->willReturn(6); $dbi = $this->createMock(DatabaseInterface::class); $dbi->method('getTable')->willReturn($table); DatabaseInterface::$instance = $dbi; $this->template = new Template($this->config); $this->response = new ResponseStub(); $this->relation = new Relation($dbi); $this->replication = new Replication($dbi); } /** * Tests for getValuesForInnodbTable() */ public function testGetValuesForInnodbTable(): void { $class = new ReflectionClass(StructureController::class); $method = $class->getMethod('getValuesForInnodbTable'); $dbi = DatabaseInterface::getInstance(); $config = Config::getInstance(); $controller = new StructureController( $this->response, $this->template, $this->relation, $this->replication, $dbi, self::createStub(TrackingChecker::class), self::createStub(PageSettings::class), new DbTableExists($dbi), $config, ); // Showing statistics $property = $class->getProperty('isShowStats'); $property->setValue($controller, true); $currentTable = [ 'ENGINE' => 'InnoDB', 'TABLE_ROWS' => 5, 'Data_length' => 16384, 'Index_length' => 0, 'TABLE_NAME' => 'table', ]; [$currentTable, , , $sumSize] = $method->invokeArgs( $controller, [$currentTable, 10], ); self::assertTrue($currentTable['COUNTED']); self::assertSame(6, $currentTable['TABLE_ROWS']); self::assertSame(16394, $sumSize); $currentTable['ENGINE'] = 'MYISAM'; [$currentTable, , , $sumSize] = $method->invokeArgs( $controller, [$currentTable, 10], ); self::assertFalse($currentTable['COUNTED']); self::assertSame(16394, $sumSize); $controller = new StructureController( $this->response, $this->template, $this->relation, $this->replication, $dbi, self::createStub(TrackingChecker::class), self::createStub(PageSettings::class), new DbTableExists($dbi), $config, ); $currentTable['ENGINE'] = 'InnoDB'; [$currentTable, , , $sumSize] = $method->invokeArgs($controller, [$currentTable, 10]); self::assertTrue($currentTable['COUNTED']); self::assertSame(10, $sumSize); $currentTable['ENGINE'] = 'MYISAM'; [$currentTable, , , $sumSize] = $method->invokeArgs($controller, [$currentTable, 10]); self::assertFalse($currentTable['COUNTED']); self::assertSame(10, $sumSize); } /** * Tests for the getValuesForAriaTable() */ public function testGetValuesForAriaTable(): void { $class = new ReflectionClass(StructureController::class); $method = $class->getMethod('getValuesForAriaTable'); $dbi = DatabaseInterface::getInstance(); $config = Config::getInstance(); $controller = new StructureController( $this->response, $this->template, $this->relation, $this->replication, $dbi, self::createStub(TrackingChecker::class), self::createStub(PageSettings::class), new DbTableExists($dbi), $config, ); // Showing statistics $property = $class->getProperty('isShowStats'); $property->setValue($controller, true); $property = $class->getProperty('dbIsSystemSchema'); $property->setValue($controller, true); $currentTable = ['Data_length' => 16384, 'Index_length' => 0, 'Name' => 'table', 'Data_free' => 300]; [$currentTable, , , , , $overheadSize, $sumSize] = $method->invokeArgs( $controller, [$currentTable, 0, 0, 0, 0, 0, 0], ); self::assertSame(6, $currentTable['Rows']); self::assertSame(16384, $sumSize); self::assertSame(300, $overheadSize); unset($currentTable['Data_free']); [$currentTable, , , , , $overheadSize] = $method->invokeArgs( $controller, [$currentTable, 0, 0, 0, 0, 0, 0], ); self::assertSame(0, $overheadSize); $controller = new StructureController( $this->response, $this->template, $this->relation, $this->replication, $dbi, self::createStub(TrackingChecker::class), self::createStub(PageSettings::class), new DbTableExists($dbi), $config, ); [$currentTable, , , , , , $sumSize] = $method->invokeArgs( $controller, [$currentTable, 0, 0, 0, 0, 0, 0], ); self::assertSame(0, $sumSize); $controller = new StructureController( $this->response, $this->template, $this->relation, $this->replication, $dbi, self::createStub(TrackingChecker::class), self::createStub(PageSettings::class), new DbTableExists($dbi), $config, ); [$currentTable] = $method->invokeArgs( $controller, [$currentTable, 0, 0, 0, 0, 0, 0], ); self::assertArrayNotHasKey('Row', $currentTable); } /** * Tests for hasTable() */ public function testHasTable(): void { $class = new ReflectionClass(StructureController::class); $method = $class->getMethod('hasTable'); $dbi = DatabaseInterface::getInstance(); $controller = new StructureController( $this->response, $this->template, $this->relation, $this->replication, $dbi, self::createStub(TrackingChecker::class), self::createStub(PageSettings::class), new DbTableExists($dbi), Config::getInstance(), ); // When parameter $db is empty self::assertFalse( $method->invokeArgs($controller, [[], 'table']), ); // Correct parameter $tables = ['db.table']; self::assertTrue( $method->invokeArgs($controller, [$tables, 'table']), ); // Table not in database $tables = ['db.tab1e']; self::assertFalse( $method->invokeArgs($controller, [$tables, 'table']), ); } /** @throws ReflectionException */ public function testDisplayTableList(): void { $class = new ReflectionClass(StructureController::class); $method = $class->getMethod('displayTableList'); $dbi = DatabaseInterface::getInstance(); $controller = new StructureController( $this->response, $this->template, $this->relation, $this->replication, $dbi, self::createStub(TrackingChecker::class), self::createStub(PageSettings::class), new DbTableExists($dbi), Config::getInstance(), ); // Showing statistics $class = new ReflectionClass(StructureController::class); $showStatsProperty = $class->getProperty('isShowStats'); $showStatsProperty->setValue($controller, true); $tablesProperty = $class->getProperty('tables'); $numTables = $class->getProperty('numTables'); $numTables->setValue($controller, 1); //no tables $_REQUEST['db'] = 'my_unique_test_db'; $tablesProperty->setValue($controller, []); $result = $method->invoke($controller, ['status' => false]); self::assertStringContainsString($_REQUEST['db'], $result); self::assertStringNotContainsString('id="overhead"', $result); //with table $_REQUEST['db'] = 'my_unique_test_db'; $tablesProperty->setValue($controller, [ [ 'TABLE_NAME' => 'my_unique_test_db', 'ENGINE' => 'Maria', 'TABLE_TYPE' => 'BASE TABLE', 'TABLE_ROWS' => 0, 'TABLE_COMMENT' => 'test', 'Data_length' => 5000, 'Index_length' => 100, 'Data_free' => 10000, ], ]); $result = $method->invoke($controller, ['status' => false]); self::assertStringContainsString($_REQUEST['db'], $result); self::assertStringContainsString('id="overhead"', $result); self::assertStringContainsString('9.8', $result); } /** * Tests for getValuesForMroongaTable() */ public function testGetValuesForMroongaTable(): void { Current::$database = 'testdb'; Current::$table = 'mytable'; $dbi = DatabaseInterface::getInstance(); $config = Config::getInstance(); $structureController = new StructureController( $this->response, $this->template, $this->relation, $this->replication, $dbi, self::createStub(TrackingChecker::class), self::createStub(PageSettings::class), new DbTableExists($dbi), $config, ); self::assertSame( [[], '', '', 0], (new ReflectionMethod(StructureController::class, 'getValuesForMroongaTable')) ->invokeArgs($structureController, [[], 0]), ); (new ReflectionMethod(StructureController::class, 'getDatabaseInfo')) ->invokeArgs($structureController, [self::createStub(ServerRequest::class), 0]); self::assertSame( [['Data_length' => 45, 'Index_length' => 60], '105', 'B', 105], (new ReflectionMethod(StructureController::class, 'getValuesForMroongaTable')) ->invokeArgs($structureController, [['Data_length' => 45, 'Index_length' => 60], 0]), ); self::assertSame( [ ['Data_length' => 45, 'Index_length' => 60], '105', 'B', 180, //105 + 75 ], (new ReflectionMethod(StructureController::class, 'getValuesForMroongaTable')) ->invokeArgs($structureController, [['Data_length' => 45, 'Index_length' => 60], 75]), ); } public function testGetDbInfo(): void { Config::getInstance()->selectedServer['DisableIS'] = true; $dbiDummy = $this->createDbiDummy(); $dbiDummy->addResult('SHOW TABLES FROM `test_db`;', [['test_table']], ['Tables_in_test_db']); DatabaseInterface::$instance = $this->createDatabaseInterface($dbiDummy); $dbi = DatabaseInterface::getInstance(); $structureController = new StructureController( $this->response, $this->template, $this->relation, $this->replication, $dbi, self::createStub(TrackingChecker::class), self::createStub(PageSettings::class), new DbTableExists($dbi), Config::getInstance(), ); $tableInfo = [ 'Name' => 'test_table', 'Engine' => 'InnoDB', 'Version' => '10', 'Row_format' => 'Dynamic', 'Rows' => '3', 'Avg_row_length' => '5461', 'Data_length' => '16384', 'Max_data_length' => '0', 'Index_length' => '0', 'Data_free' => '0', 'Auto_increment' => '4', 'Create_time' => '2011-12-13 14:15:16', 'Update_time' => null, 'Check_time' => null, 'Collation' => 'utf8mb4_general_ci', 'Checksum' => null, 'Create_options' => '', 'Comment' => '', 'Max_index_length' => '0', 'Temporary' => 'N', 'Type' => 'InnoDB', 'TABLE_SCHEMA' => 'test_db', 'TABLE_NAME' => 'test_table', 'ENGINE' => 'InnoDB', 'VERSION' => '10', 'ROW_FORMAT' => 'Dynamic', 'TABLE_ROWS' => '3', 'AVG_ROW_LENGTH' => '5461', 'DATA_LENGTH' => '16384', 'MAX_DATA_LENGTH' => '0', 'INDEX_LENGTH' => '0', 'DATA_FREE' => '0', 'AUTO_INCREMENT' => '4', 'CREATE_TIME' => '2011-12-13 14:15:16', 'UPDATE_TIME' => null, 'CHECK_TIME' => null, 'TABLE_COLLATION' => 'utf8mb4_general_ci', 'CHECKSUM' => null, 'CREATE_OPTIONS' => '', 'TABLE_COMMENT' => '', 'TABLE_TYPE' => 'BASE TABLE', ]; $expected = [['test_table' => $tableInfo], 1]; $actual = $structureController->getDbInfo('test_db', null, null, null, null, 0); self::assertSame($expected, $actual); } public function testGetDbInfoWithSortedColumnAndDescendingOrder(): void { $this->config->selectedServer['DisableIS'] = true; $dbiDummy = $this->createDbiDummy(); $dbiDummy->addResult('SHOW TABLES FROM `test_db`;', [['a_table'], ['b_table']], ['Tables_in_test_db']); $dbiDummy->addResult( 'SHOW TABLE STATUS FROM `test_db` WHERE `Name` IN (\'a_table\', \'b_table\')', [['a_table', 'InnoDB', '3'], ['b_table', 'InnoDB', '10']], ['Name', 'Engine', 'Rows'], ); $dbi = $this->createDatabaseInterface($dbiDummy, $this->config); [$tables, $totalNumTables] = $this->createController($dbi)->getDbInfo('test_db', 'records', 'DESC', null, null, 0); self::assertSame(['b_table', 'a_table'], array_keys($tables)); self::assertSame(2, $totalNumTables); $dbiDummy->assertAllQueriesConsumed(); } public function testGetDbInfoWithUnknownSortColumnFallsBackToNameAscending(): void { $this->config->selectedServer['DisableIS'] = true; $dbiDummy = $this->createDbiDummy(); $dbiDummy->addResult('SHOW TABLES FROM `test_db`;', [['a_table'], ['b_table']], ['Tables_in_test_db']); // The 'DESC' sort order must be ignored for an unrecognized sort column $dbiDummy->addResult( 'SHOW TABLE STATUS FROM `test_db` WHERE `Name` IN (\'a_table\', \'b_table\')', [['a_table', 'InnoDB', '3'], ['b_table', 'InnoDB', '10']], ['Name', 'Engine', 'Rows'], ); $dbi = $this->createDatabaseInterface($dbiDummy, $this->config); [$tables, $totalNumTables] = $this->createController($dbi)->getDbInfo('test_db', 'invalid', 'DESC', null, null, 0); self::assertSame(['a_table', 'b_table'], array_keys($tables)); self::assertSame(2, $totalNumTables); $dbiDummy->assertAllQueriesConsumed(); } public function testGetDbInfoWithTableGroup(): void { $this->config->selectedServer['DisableIS'] = true; $dbiDummy = $this->createDbiDummy(); // The table with the exact name of the group is fetched first $dbiDummy->addResult( 'SHOW TABLE STATUS FROM `test_db` WHERE `Name` LIKE \'2024%\'', [['2024', 'InnoDB', '3']], ['Name', 'Engine', 'Rows'], ); $dbiDummy->addResult( 'SHOW TABLE STATUS FROM `test_db` WHERE `Name` LIKE \'2024\\\\_\\\\_%\'', [['2024__child', 'InnoDB', '5']], ['Name', 'Engine', 'Rows'], ); $dbi = $this->createDatabaseInterface($dbiDummy, $this->config); [$tables, $totalNumTables] = $this->createController($dbi)->getDbInfo('test_db', null, null, '2024', null, 0); // Numerical keys must be preserved when both results are merged self::assertSame([2024, '2024__child'], array_keys($tables)); self::assertSame(2, $totalNumTables); $dbiDummy->assertAllQueriesConsumed(); } public function testGetDbInfoWithTableGroupAndTableType(): void { $this->config->selectedServer['DisableIS'] = true; $dbiDummy = $this->createDbiDummy(); $dbiDummy->addResult( 'SHOW TABLE STATUS FROM `test_db` WHERE `Name` LIKE \'2024%\' AND `Comment` != \'VIEW\'', [['2024', 'InnoDB', '3']], ['Name', 'Engine', 'Rows'], ); $dbiDummy->addResult( 'SHOW TABLE STATUS FROM `test_db` WHERE `Name` LIKE \'2024\\\\_\\\\_%\' AND `Comment` != \'VIEW\'', [['2024__child', 'InnoDB', '5']], ['Name', 'Engine', 'Rows'], ); $dbi = $this->createDatabaseInterface($dbiDummy, $this->config); [$tables, $totalNumTables] = $this->createController($dbi)->getDbInfo('test_db', null, null, '2024', 'table', 0); self::assertSame([2024, '2024__child'], array_keys($tables)); self::assertSame(2, $totalNumTables); $dbiDummy->assertAllQueriesConsumed(); } public function testGetDbInfoWithTableType(): void { $this->config->selectedServer['DisableIS'] = true; $dbiDummy = $this->createDbiDummy(); $dbiDummy->addResult( 'SHOW TABLE STATUS FROM `test_db` WHERE `Comment` = \'VIEW\'', [['test_view', null, '0', 'VIEW']], ['Name', 'Engine', 'Rows', 'Comment'], ); $dbi = $this->createDatabaseInterface($dbiDummy, $this->config); [$tables, $totalNumTables] = $this->createController($dbi)->getDbInfo('test_db', null, null, null, 'view', 0); self::assertSame(['test_view'], array_keys($tables)); self::assertSame('VIEW', $tables['test_view']['TABLE_TYPE']); self::assertSame(1, $totalNumTables); $dbiDummy->assertAllQueriesConsumed(); } public function testGetDbInfoWithUnknownTableTypeFallsBackToFullList(): void { $this->config->selectedServer['DisableIS'] = true; $dbiDummy = $this->createDbiDummy(); $dbiDummy->addResult('SHOW TABLES FROM `test_db`;', [['a_table']], ['Tables_in_test_db']); $dbiDummy->addResult( 'SHOW TABLE STATUS FROM `test_db` WHERE `Name` IN (\'a_table\')', [['a_table', 'InnoDB', '3']], ['Name', 'Engine', 'Rows'], ); $dbi = $this->createDatabaseInterface($dbiDummy, $this->config); [$tables, $totalNumTables] = $this->createController($dbi)->getDbInfo('test_db', null, null, null, 'bogus', 0); self::assertSame(['a_table'], array_keys($tables)); self::assertSame(1, $totalNumTables); $dbiDummy->assertAllQueriesConsumed(); } public function testGetDbInfoWithLimitedTableList(): void { $this->config->selectedServer['DisableIS'] = true; $this->config->config = new Settings(['MaxTableList' => 1]); $dbiDummy = $this->createDbiDummy(); $dbiDummy->addResult('SHOW TABLES FROM `test_db`;', [['a_table'], ['b_table']], ['Tables_in_test_db']); // Only the page starting at the current position is fetched in detail $dbiDummy->addResult( 'SHOW TABLE STATUS FROM `test_db` WHERE `Name` IN (\'b_table\')', [['b_table', 'InnoDB', '10']], ['Name', 'Engine', 'Rows'], ); $dbi = $this->createDatabaseInterface($dbiDummy, $this->config); [$tables, $totalNumTables] = $this->createController($dbi)->getDbInfo('test_db', null, null, null, null, 1); self::assertSame(['b_table'], array_keys($tables)); self::assertSame(2, $totalNumTables); $dbiDummy->assertAllQueriesConsumed(); } private function createController(DatabaseInterface $dbi): StructureController { return new StructureController( $this->response, $this->template, $this->relation, $this->replication, $dbi, self::createStub(TrackingChecker::class), self::createStub(PageSettings::class), new DbTableExists($dbi), $this->config, ); } public function testGetTableListPosition(): void { $dbi = DatabaseInterface::getInstance(); $structureController = new StructureController( $this->response, $this->template, $this->relation, $this->replication, $dbi, self::createStub(TrackingChecker::class), self::createStub(PageSettings::class), new DbTableExists($dbi), Config::getInstance(), ); // Default 0 $actual = $structureController->getTableListPosition(null, 'test_db'); self::assertSame(0, $actual); // From POST $actual = $structureController->getTableListPosition('250', 'test_db'); self::assertSame(250, $actual); // From SESSION $actual = $structureController->getTableListPosition(null, 'test_db'); self::assertSame(250, $actual); // Negative positions are clamped to 0 $actual = $structureController->getTableListPosition('-250', 'test_db'); self::assertSame(0, $actual); } public function testRedirectsToLastPageWhenPositionIsBeyondTheTableList(): void { Current::$database = 'test_db'; $this->config->selectedServer['DisableIS'] = true; $dbiDummy = $this->createDbiDummy(); $dbiDummy->addSelectDb('test_db'); $dbiDummy->addResult('SHOW TABLES FROM `test_db`;', [['test_table']], ['Tables_in_test_db']); $dbiDummy->addResult( 'SHOW TABLE STATUS FROM `test_db`', [['test_table', 'InnoDB', '3']], ['Name', 'Engine', 'Rows'], ); $dbi = $this->createDatabaseInterface($dbiDummy, $this->config); // The first (and only) page holds the whole table list, so position 1 is past the end $request = ServerRequestFactory::create()->createServerRequest('GET', 'http://example.com/') ->withQueryParams(['db' => 'test_db', 'pos' => '1']); $response = $this->createController($dbi)($request); self::assertSame(302, $response->getStatusCode()); self::assertStringContainsString('pos=0', $response->getHeaderLine('Location')); self::assertStringContainsString('route=/database/structure', $response->getHeaderLine('Location')); $dbiDummy->assertAllQueriesConsumed(); } }