Add tests for CopyStructureController and fix HTML fixture

Signed-off-by: Satyam <satyam.gupta756@gmail.com>
This commit is contained in:
Satyam 2026-04-15 15:26:26 +05:30
parent 3d7ad3e477
commit fd5e5b84a7
3 changed files with 362 additions and 0 deletions

View File

@ -0,0 +1,181 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\Tests\Controllers\Database\Structure;
use PhpMyAdmin\Controllers\Database\Structure\CopyStructureController;
use PhpMyAdmin\Current;
use PhpMyAdmin\Dbal\DatabaseInterface;
use PhpMyAdmin\DbTableExists;
use PhpMyAdmin\Http\Factory\ServerRequestFactory;
use PhpMyAdmin\Tests\AbstractTestCase;
use PhpMyAdmin\Tests\Stubs\ResponseRenderer as ResponseStub;
use PHPUnit\Framework\Attributes\CoversClass;
#[CoversClass(CopyStructureController::class)]
final class CopyStructureControllerTest extends AbstractTestCase
{
public function testReturnErrorWhenNoDatabaseSet(): void
{
Current::$database = '';
$dbi = $this->createDatabaseInterface();
DatabaseInterface::$instance = $dbi;
$response = new ResponseStub();
$request = ServerRequestFactory::create()->createServerRequest('POST', 'http://example.com/')
->withQueryParams(['db' => '']);
(new CopyStructureController($response, $dbi, new DbTableExists($dbi)))($request);
self::assertFalse($response->hasSuccessState());
self::assertStringContainsString('No databases selected', $response->getJSONResult()['message']);
}
public function testReturnErrorWhenDatabaseNameInvalid(): void
{
Current::$database = 'test_db';
$dbi = $this->createDatabaseInterface();
DatabaseInterface::$instance = $dbi;
$response = new ResponseStub();
// empty 'db' param → DatabaseName::tryFrom returns null
$request = ServerRequestFactory::create()->createServerRequest('POST', 'http://example.com/')
->withQueryParams(['db' => '']);
(new CopyStructureController($response, $dbi, new DbTableExists($dbi)))($request);
self::assertFalse($response->hasSuccessState());
self::assertStringContainsString('No databases selected', $response->getJSONResult()['message']);
}
public function testReturnsSqlForTablesOnly(): void
{
Current::$database = 'test_db';
$createSql = "CREATE TABLE `orders` (\n `id` int(11) NOT NULL\n) ENGINE=InnoDB";
$dbiDummy = $this->createDbiDummy();
// DbTableExists::selectDatabase() + $this->dbi->selectDb() both call selectDb
$dbiDummy->addSelectDb('test_db');
$dbiDummy->addSelectDb('test_db');
// getTables() call
$dbiDummy->addResult(
'SHOW TABLES FROM `test_db`;',
[['orders']],
);
// showCreate() for orders
$dbiDummy->addResult(
'SHOW CREATE TABLE `test_db`.`orders`',
[['orders', $createSql]],
['Table', 'Create Table'],
);
$dbi = $this->createDatabaseInterface($dbiDummy);
DatabaseInterface::$instance = $dbi;
// Pre-seed the TABLE_TYPE cache so isView() returns false without an extra query
$dbi->getCache()->cacheTableValue('test_db', 'orders', 'TABLE_TYPE', 'BASE TABLE');
$response = new ResponseStub();
$request = ServerRequestFactory::create()->createServerRequest('POST', 'http://example.com/')
->withQueryParams(['db' => 'test_db'])
->withParsedBody(['db' => 'test_db']);
(new CopyStructureController($response, $dbi, new DbTableExists($dbi)))($request);
self::assertTrue($response->hasSuccessState());
$sql = $response->getJSONResult()['sql'];
self::assertStringContainsString('-- Database: test_db', $sql);
self::assertStringContainsString($createSql, $sql);
self::assertStringNotContainsString('-- Views', $sql);
$dbiDummy->assertAllSelectsConsumed();
$dbiDummy->assertAllQueriesConsumed();
}
public function testReturnsSqlWithViewsSeparated(): void
{
Current::$database = 'test_db';
$tableSql = "CREATE TABLE `products` (\n `id` int(11) NOT NULL\n) ENGINE=InnoDB";
$viewSql = "CREATE VIEW `v_products` AS SELECT * FROM `products`";
$dbiDummy = $this->createDbiDummy();
$dbiDummy->addSelectDb('test_db');
$dbiDummy->addSelectDb('test_db');
$dbiDummy->addResult(
'SHOW TABLES FROM `test_db`;',
[['products'], ['v_products']],
);
$dbiDummy->addResult(
'SHOW CREATE TABLE `test_db`.`products`',
[['products', $tableSql]],
['Table', 'Create Table'],
);
$dbiDummy->addResult(
'SHOW CREATE TABLE `test_db`.`v_products`',
[['v_products', $viewSql]],
['Table', 'Create Table'],
);
$dbi = $this->createDatabaseInterface($dbiDummy);
DatabaseInterface::$instance = $dbi;
$dbi->getCache()->cacheTableValue('test_db', 'products', 'TABLE_TYPE', 'BASE TABLE');
$dbi->getCache()->cacheTableValue('test_db', 'v_products', 'TABLE_TYPE', 'VIEW');
$response = new ResponseStub();
$request = ServerRequestFactory::create()->createServerRequest('POST', 'http://example.com/')
->withQueryParams(['db' => 'test_db'])
->withParsedBody(['db' => 'test_db']);
(new CopyStructureController($response, $dbi, new DbTableExists($dbi)))($request);
self::assertTrue($response->hasSuccessState());
$sql = $response->getJSONResult()['sql'];
self::assertStringContainsString('-- Database: test_db', $sql);
self::assertStringContainsString($tableSql, $sql);
self::assertStringContainsString('-- Views', $sql);
self::assertStringContainsString($viewSql, $sql);
// Views section must come after tables
self::assertGreaterThan(
strpos($sql, $tableSql),
strpos($sql, '-- Views'),
);
$dbiDummy->assertAllSelectsConsumed();
$dbiDummy->assertAllQueriesConsumed();
}
public function testReturnsSqlForEmptyDatabase(): void
{
Current::$database = 'empty_db';
$dbiDummy = $this->createDbiDummy();
$dbiDummy->addSelectDb('empty_db');
$dbiDummy->addSelectDb('empty_db');
$dbiDummy->addResult('SHOW TABLES FROM `empty_db`;', []);
$dbi = $this->createDatabaseInterface($dbiDummy);
DatabaseInterface::$instance = $dbi;
$response = new ResponseStub();
$request = ServerRequestFactory::create()->createServerRequest('POST', 'http://example.com/')
->withQueryParams(['db' => 'empty_db'])
->withParsedBody(['db' => 'empty_db']);
(new CopyStructureController($response, $dbi, new DbTableExists($dbi)))($request);
self::assertTrue($response->hasSuccessState());
$sql = $response->getJSONResult()['sql'];
self::assertStringContainsString('-- Database: empty_db', $sql);
self::assertStringNotContainsString('CREATE TABLE', $sql);
self::assertStringNotContainsString('-- Views', $sql);
$dbiDummy->assertAllSelectsConsumed();
$dbiDummy->assertAllQueriesConsumed();
}
}

View File

@ -363,6 +363,9 @@
<hr>
<p class="d-print-none">
<button type="button" class="btn btn-link p-0 jsPrintButton"><span class="text-nowrap"><img src="themes/dot.gif" title="Print" alt="Print" class="icon ic_b_print">&nbsp;Print</span></button>
<button type="button" id="copyStructureBtn" class="btn btn-link p-0">
<span class="text-nowrap"><img src="themes/dot.gif" title="Copy DB Structure" alt="Copy DB Structure" class="icon ic_b_export">&nbsp;Copy DB Structure</span>
</button>
<a href="index.php?route=/database/data-dictionary&db=test_db&goto=index.php%3Froute%3D%2Fdatabase%2Fstructure%26lang%3Den&lang=en">
<span class="text-nowrap"><img src="themes/dot.gif" title="Data dictionary" alt="Data dictionary" class="icon ic_b_tblanalyse">&nbsp;Data dictionary</span>
</a>

View File

@ -0,0 +1,178 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\Tests\Controllers\Table\Structure;
use PhpMyAdmin\Controllers\Table\Structure\CopyStructureController;
use PhpMyAdmin\Current;
use PhpMyAdmin\Dbal\DatabaseInterface;
use PhpMyAdmin\DbTableExists;
use PhpMyAdmin\Http\Factory\ServerRequestFactory;
use PhpMyAdmin\Tests\AbstractTestCase;
use PhpMyAdmin\Tests\Stubs\ResponseRenderer as ResponseStub;
use PHPUnit\Framework\Attributes\CoversClass;
#[CoversClass(CopyStructureController::class)]
final class CopyStructureControllerTest extends AbstractTestCase
{
public function testReturnErrorWhenNoDatabaseSet(): void
{
Current::$database = '';
Current::$table = 'orders';
$dbi = $this->createDatabaseInterface();
DatabaseInterface::$instance = $dbi;
$response = new ResponseStub();
$request = ServerRequestFactory::create()->createServerRequest('POST', 'http://example.com/')
->withQueryParams(['db' => '', 'table' => 'orders']);
(new CopyStructureController($response, $dbi, new DbTableExists($dbi)))($request);
self::assertFalse($response->hasSuccessState());
self::assertStringContainsString('No databases selected', $response->getJSONResult()['message']);
}
public function testReturnErrorWhenNoTableSet(): void
{
Current::$database = 'test_db';
Current::$table = '';
$dbi = $this->createDatabaseInterface();
DatabaseInterface::$instance = $dbi;
$response = new ResponseStub();
$request = ServerRequestFactory::create()->createServerRequest('POST', 'http://example.com/')
->withQueryParams(['db' => 'test_db', 'table' => '']);
(new CopyStructureController($response, $dbi, new DbTableExists($dbi)))($request);
self::assertFalse($response->hasSuccessState());
self::assertStringContainsString('No table selected', $response->getJSONResult()['message']);
}
public function testReturnErrorWhenDatabaseNameInvalid(): void
{
Current::$database = 'test_db';
Current::$table = 'orders';
// Controller calls selectDb(Current::$database) before the DatabaseName check
$dbiDummy = $this->createDbiDummy();
$dbiDummy->addSelectDb('test_db');
$dbi = $this->createDatabaseInterface($dbiDummy);
DatabaseInterface::$instance = $dbi;
$response = new ResponseStub();
// 'db' param is empty → DatabaseName::tryFrom returns null
$request = ServerRequestFactory::create()->createServerRequest('POST', 'http://example.com/')
->withQueryParams(['db' => '', 'table' => 'orders']);
(new CopyStructureController($response, $dbi, new DbTableExists($dbi)))($request);
self::assertFalse($response->hasSuccessState());
self::assertStringContainsString('No databases selected', $response->getJSONResult()['message']);
$dbiDummy->assertAllSelectsConsumed();
}
public function testReturnsSqlForTable(): void
{
Current::$database = 'test_db';
Current::$table = 'orders';
$createSql = "CREATE TABLE `orders` (\n `id` int(11) NOT NULL\n) ENGINE=InnoDB";
$dbiDummy = $this->createDbiDummy();
// 1st: controller calls selectDb(Current::$database)
// 2nd: DbTableExists::selectDatabase() calls selectDb($databaseName)
$dbiDummy->addSelectDb('test_db');
$dbiDummy->addSelectDb('test_db');
// DbTableExists::hasTable issues SELECT 1 FROM `db`.`table` LIMIT 1
$dbiDummy->addResult('SELECT 1 FROM `test_db`.`orders` LIMIT 1;', [['1']]);
// showCreate()
$dbiDummy->addResult(
'SHOW CREATE TABLE `test_db`.`orders`',
[['orders', $createSql]],
['Table', 'Create Table'],
);
$dbi = $this->createDatabaseInterface($dbiDummy);
DatabaseInterface::$instance = $dbi;
$response = new ResponseStub();
$request = ServerRequestFactory::create()->createServerRequest('POST', 'http://example.com/')
->withQueryParams(['db' => 'test_db', 'table' => 'orders'])
->withParsedBody(['db' => 'test_db', 'table' => 'orders']);
(new CopyStructureController($response, $dbi, new DbTableExists($dbi)))($request);
self::assertTrue($response->hasSuccessState());
self::assertSame($createSql, $response->getJSONResult()['sql']);
$dbiDummy->assertAllSelectsConsumed();
$dbiDummy->assertAllQueriesConsumed();
}
public function testReturnsSqlForView(): void
{
Current::$database = 'test_db';
Current::$table = 'v_orders';
$viewSql = 'CREATE VIEW `v_orders` AS SELECT * FROM `orders`';
$dbiDummy = $this->createDbiDummy();
$dbiDummy->addSelectDb('test_db');
$dbiDummy->addSelectDb('test_db');
$dbiDummy->addResult('SELECT 1 FROM `test_db`.`v_orders` LIMIT 1;', [['1']]);
$dbiDummy->addResult(
'SHOW CREATE TABLE `test_db`.`v_orders`',
[['v_orders', $viewSql]],
['Table', 'Create Table'],
);
$dbi = $this->createDatabaseInterface($dbiDummy);
DatabaseInterface::$instance = $dbi;
$response = new ResponseStub();
$request = ServerRequestFactory::create()->createServerRequest('POST', 'http://example.com/')
->withQueryParams(['db' => 'test_db', 'table' => 'v_orders'])
->withParsedBody(['db' => 'test_db', 'table' => 'v_orders']);
(new CopyStructureController($response, $dbi, new DbTableExists($dbi)))($request);
self::assertTrue($response->hasSuccessState());
self::assertSame($viewSql, $response->getJSONResult()['sql']);
$dbiDummy->assertAllSelectsConsumed();
$dbiDummy->assertAllQueriesConsumed();
}
public function testReturnErrorWhenTableDoesNotExist(): void
{
Current::$database = 'test_db';
Current::$table = 'ghost_table';
$dbiDummy = $this->createDbiDummy();
$dbiDummy->addSelectDb('test_db');
$dbiDummy->addSelectDb('test_db');
// hasTable SELECT fails (table not found)
$dbiDummy->addResult('SELECT 1 FROM `test_db`.`ghost_table` LIMIT 1;', false);
$dbi = $this->createDatabaseInterface($dbiDummy);
DatabaseInterface::$instance = $dbi;
$response = new ResponseStub();
$request = ServerRequestFactory::create()->createServerRequest('POST', 'http://example.com/')
->withQueryParams(['db' => 'test_db', 'table' => 'ghost_table'])
->withParsedBody(['db' => 'test_db', 'table' => 'ghost_table']);
(new CopyStructureController($response, $dbi, new DbTableExists($dbi)))($request);
self::assertFalse($response->hasSuccessState());
self::assertStringContainsString('No table selected', $response->getJSONResult()['message']);
$dbiDummy->assertAllSelectsConsumed();
$dbiDummy->assertAllQueriesConsumed();
}
}