phpmyadmin/test/classes/Tracking/TrackerTest.php
Maurício Meneghini Fauth dd885dc7b8
Use single-line arrays when possible
Signed-off-by: Maurício Meneghini Fauth <mauricio@fauth.dev>
2023-03-27 17:39:17 -03:00

639 lines
21 KiB
PHP

<?php
declare(strict_types=1);
namespace PhpMyAdmin\Tests\Tracking;
use PhpMyAdmin\Cache;
use PhpMyAdmin\ConfigStorage\RelationParameters;
use PhpMyAdmin\DatabaseInterface;
use PhpMyAdmin\Dbal\Connection;
use PhpMyAdmin\Tests\AbstractTestCase;
use PhpMyAdmin\Tests\Stubs\DummyResult;
use PhpMyAdmin\Tracking\Tracker;
use PhpMyAdmin\Util;
use ReflectionMethod;
/** @covers \PhpMyAdmin\Tracking\Tracker */
class TrackerTest extends AbstractTestCase
{
/**
* Setup function for test cases
*/
protected function setUp(): void
{
parent::setUp();
$this->loadContainerBuilder();
$GLOBALS['dbi'] = $this->createDatabaseInterface();
parent::loadDbiIntoContainerBuilder();
/**
* SET these to avoid undefined index error
*/
$GLOBALS['server'] = 1;
$GLOBALS['cfg']['Server']['tracking_add_drop_table'] = '';
$GLOBALS['cfg']['Server']['tracking_add_drop_view'] = '';
$GLOBALS['cfg']['Server']['tracking_add_drop_database'] = '';
$GLOBALS['cfg']['Server']['tracking_default_statements'] = '';
$GLOBALS['cfg']['Server']['tracking_version_auto_create'] = '';
$GLOBALS['cfg']['Server']['DisableIS'] = false;
$GLOBALS['export_type'] = null;
$_SESSION['relation'] = [];
$_SESSION['relation'][$GLOBALS['server']] = RelationParameters::fromArray([
'db' => 'pmadb',
'trackingwork' => true,
'tracking' => 'tracking',
])->toArray();
$dbi = $this->getMockBuilder(DatabaseInterface::class)
->disableOriginalConstructor()
->getMock();
$dbi->expects($this->any())->method('escapeString')
->will($this->returnArgument(0));
}
/**
* Test for Tracker::enable
*/
public function testEnabled(): void
{
$this->assertFalse(
Cache::has(Tracker::TRACKER_ENABLED_CACHE_KEY),
);
Tracker::enable();
$this->assertTrue(
Cache::get(Tracker::TRACKER_ENABLED_CACHE_KEY),
);
}
/**
* Test for Tracker::isActive()
*/
public function testIsActive(): void
{
$this->assertFalse(
Cache::has(Tracker::TRACKER_ENABLED_CACHE_KEY),
);
$this->assertFalse(
Tracker::isActive(),
);
Tracker::enable();
$_SESSION['relation'] = [];
$_SESSION['relation'][$GLOBALS['server']] = RelationParameters::fromArray([])->toArray();
$this->assertFalse(
Tracker::isActive(),
);
$_SESSION['relation'] = [];
$_SESSION['relation'][$GLOBALS['server']] = RelationParameters::fromArray([
'trackingwork' => true,
'db' => 'pmadb',
'tracking' => 'tracking',
])->toArray();
$this->assertTrue(
Tracker::isActive(),
);
}
/**
* Test for Tracker::getTableName()
*
* @param string $string String to test against
* @param string $expected Expected Table Name
*
* @dataProvider getTableNameData
*/
public function testGetTableName(string $string, string $expected): void
{
$this->assertEquals(
$expected,
$this->callFunction(null, Tracker::class, 'getTableName', [$string]),
);
}
/**
* Data Provider for testGetTableName
*
* @return array Test data
*/
public static function getTableNameData(): array
{
return [['`tbl`;', 'tbl'], [' `pma.table` ', 'table'], [" `pma.table\nfoobar` ", 'table']];
}
/**
* Test for Tracker::isTracked()
*/
public function testIsTracked(): void
{
$this->assertFalse(
Cache::has(Tracker::TRACKER_ENABLED_CACHE_KEY),
);
$this->assertFalse(
Tracker::isTracked('', ''),
);
Tracker::enable();
$_SESSION['relation'] = [];
$_SESSION['relation'][$GLOBALS['server']] = RelationParameters::fromArray([])->toArray();
$this->assertFalse(
Tracker::isTracked('', ''),
);
$_SESSION['relation'] = [];
$_SESSION['relation'][$GLOBALS['server']] = RelationParameters::fromArray([
'trackingwork' => true,
'db' => 'pmadb',
'tracking' => 'tracking',
])->toArray();
$this->assertTrue(
Tracker::isTracked('pma_test_db', 'pma_test_table'),
);
$this->assertFalse(
Tracker::isTracked('pma_test_db', 'pma_test_table2'),
);
}
/**
* Test for Tracker::getLogComment()
*/
public function testGetLogComment(): void
{
$date = Util::date('Y-m-d H:i:s');
$GLOBALS['cfg']['Server']['user'] = 'pma_test_user';
$this->assertEquals(
'# log ' . $date . " pma_test_user\n",
Tracker::getLogComment(),
);
}
/**
* Test for Tracker::createVersion()
*/
public function testCreateVersion(): void
{
$GLOBALS['cfg']['Server']['tracking_add_drop_table'] = true;
$GLOBALS['cfg']['Server']['tracking_add_drop_view'] = true;
$GLOBALS['cfg']['Server']['user'] = 'pma_test_user';
$resultStub = $this->createMock(DummyResult::class);
$dbi = $this->getMockBuilder(DatabaseInterface::class)
->disableOriginalConstructor()
->getMock();
/**
* set up mock objects
* passing null to with() for an argument is equivalent
* to passing $this->anything()
*/
$getColumnsResult = [
['Field' => 'field1', 'Type' => 'int(11)', 'Key' => 'PRI'],
['Field' => 'field2', 'Type' => 'text', 'Key' => ''],
];
$dbi->expects($this->once())->method('getColumns')
->with('pma_test', 'pma_tbl')
->will($this->returnValue($getColumnsResult));
$getIndexesResult = [['Table' => 'pma_tbl', 'Field' => 'field1', 'Key' => 'PRIMARY']];
$dbi->expects($this->once())->method('getTableIndexes')
->with('pma_test', 'pma_tbl')
->will($this->returnValue($getIndexesResult));
$showTableStatusQuery = 'SHOW TABLE STATUS FROM `pma_test` WHERE Name = \'pma_tbl\'';
$useStatement = 'USE `pma_test`';
$showCreateTableQuery = 'SHOW CREATE TABLE `pma_test`.`pma_tbl`';
$dbi->expects($this->exactly(3))->method('tryQuery')->willReturnMap([
[$showTableStatusQuery, Connection::TYPE_USER, DatabaseInterface::QUERY_BUFFERED, true, $resultStub],
[$useStatement, Connection::TYPE_USER, DatabaseInterface::QUERY_BUFFERED, true, $resultStub],
[$showCreateTableQuery, Connection::TYPE_USER, DatabaseInterface::QUERY_BUFFERED, true, $resultStub],
]);
$dbi->expects($this->any())->method('query')
->will($this->returnValue($resultStub));
$dbi->expects($this->any())->method('getCompatibilities')
->will($this->returnValue([]));
$dbi->expects($this->any())->method('quoteString')
->will($this->returnCallback(static function (string $string) {
return "'" . $string . "'";
}));
$GLOBALS['dbi'] = $dbi;
$this->assertTrue(Tracker::createVersion('pma_test', 'pma_tbl', '1', '11', true));
}
/**
* Test for Tracker::deleteTracking()
*/
public function testDeleteTracking(): void
{
$resultStub = $this->createMock(DummyResult::class);
$dbi = $this->getMockBuilder(DatabaseInterface::class)
->disableOriginalConstructor()
->getMock();
$sqlQuery = "/*NOTRACK*/\n"
. 'DELETE FROM `pmadb`.`tracking`'
. " WHERE `db_name` = 'testdb'"
. " AND `table_name` = 'testtable'";
$dbi->expects($this->exactly(1))
->method('queryAsControlUser')
->with($sqlQuery)
->will($this->returnValue($resultStub));
$dbi->expects($this->any())->method('escapeString')
->will($this->returnArgument(0));
$GLOBALS['dbi'] = $dbi;
$this->assertTrue(Tracker::deleteTracking('testdb', 'testtable'));
}
/**
* Test for Tracker::createDatabaseVersion()
*/
public function testCreateDatabaseVersion(): void
{
$GLOBALS['cfg']['Server']['tracking_add_drop_table'] = true;
$GLOBALS['cfg']['Server']['tracking_add_drop_view'] = true;
$GLOBALS['cfg']['Server']['user'] = 'pma_test_user';
$resultStub = $this->createMock(DummyResult::class);
$dbi = $this->getMockBuilder(DatabaseInterface::class)
->disableOriginalConstructor()
->getMock();
$expectedMainQuery = '/*NOTRACK*/' . "\n" . 'INSERT INTO `pmadb`.`tracking` (db_name, table_name, version,'
. ' date_created, date_updated, schema_snapshot, schema_sql, data_sql, tracking)'
. ' values (\'pma_test\', \'\', \'1\', \'%d-%d-%d %d:%d:%d\', \'%d-%d-%d %d:%d:%d\','
. ' \'\', \'# log %d-%d-%d %d:%d:%d pma_test_user' . "\n" . 'SHOW DATABASES\', \'' . "\n"
. '\', \'CREATE DATABASE,ALTER DATABASE,DROP DATABASE\')';
$dbi->expects($this->exactly(1))
->method('queryAsControlUser')
->with($this->matches($expectedMainQuery))
->will($this->returnValue($resultStub));
$dbi->expects($this->any())->method('escapeString')
->will($this->returnArgument(0));
$GLOBALS['dbi'] = $dbi;
$this->assertTrue(Tracker::createDatabaseVersion('pma_test', '1', 'SHOW DATABASES'));
}
/**
* Test for Tracker::changeTracking(). This test is also invoked by two
* other tests: testActivateTracking() and testDeactivateTracking()
*
* @param string $dbname Database name
* @param string $tablename Table name
* @param string $version Version
* @param string|int $newState State to change to
* @param string $type Type of test
*/
public function testChangeTracking(
string $dbname = 'pma_db',
string $tablename = 'pma_tbl',
string $version = '0.1',
string|int $newState = '1',
string|null $type = null,
): void {
$dbi = $this->getMockBuilder(DatabaseInterface::class)
->disableOriginalConstructor()
->getMock();
$resultStub = $this->createMock(DummyResult::class);
$sqlQuery = 'UPDATE `pmadb`.`tracking` SET `tracking_active` = ' .
"'" . $newState . "'" .
" WHERE `db_name` = '" . $dbname . "'" .
" AND `table_name` = '" . $tablename . "'" .
" AND `version` = '" . $version . "'";
$dbi->expects($this->exactly(1))
->method('queryAsControlUser')
->with($sqlQuery)
->will($this->returnValue($resultStub));
$dbi->expects($this->any())->method('escapeString')
->will($this->returnArgument(0));
$GLOBALS['dbi'] = $dbi;
if ($type === null) {
$method = new ReflectionMethod(Tracker::class, 'changeTracking');
$method->invoke(null, $dbname, $tablename, $version, $newState);
} elseif ($type === 'activate') {
Tracker::activateTracking($dbname, $tablename, $version);
} elseif ($type === 'deactivate') {
Tracker::deactivateTracking($dbname, $tablename, $version);
}
// What's the success criteria? What is the expected result?
}
/**
* Test for Tracker::testChangeTrackingData()
*/
public function testChangeTrackingData(): void
{
$this->assertFalse(
Tracker::changeTrackingData('', '', '', '', ''),
);
$dbi = $this->getMockBuilder(DatabaseInterface::class)
->disableOriginalConstructor()
->getMock();
$sqlQuery1 = 'UPDATE `pmadb`.`tracking`' .
" SET `schema_sql` = '# new_data_processed'" .
" WHERE `db_name` = 'pma_db'" .
" AND `table_name` = 'pma_table'" .
" AND `version` = '1.0'";
$date = Util::date('Y-m-d H:i:s');
$newData = [
['username' => 'user1', 'statement' => 'test_statement1'],
['username' => 'user2', 'statement' => 'test_statement2'],
];
$sqlQuery2 = 'UPDATE `pmadb`.`tracking`' .
" SET `data_sql` = '# log " . $date . " user1test_statement1\n" .
'# log ' . $date . " user2test_statement2\n'" .
" WHERE `db_name` = 'pma_db'" .
" AND `table_name` = 'pma_table'" .
" AND `version` = '1.0'";
$resultStub1 = $this->createMock(DummyResult::class);
$resultStub2 = $this->createMock(DummyResult::class);
$dbi->method('queryAsControlUser')
->will(
$this->returnValueMap(
[[$sqlQuery1, $resultStub1], [$sqlQuery2, $resultStub2]],
),
);
$dbi->expects($this->any())->method('escapeString')
->will($this->returnArgument(0));
$GLOBALS['dbi'] = $dbi;
$this->assertTrue(
Tracker::changeTrackingData(
'pma_db',
'pma_table',
'1.0',
'DDL',
'# new_data_processed',
),
);
$this->assertTrue(
Tracker::changeTrackingData(
'pma_db',
'pma_table',
'1.0',
'DML',
$newData,
),
);
}
/**
* Test for Tracker::activateTracking()
*/
public function testActivateTracking(): void
{
$this->testChangeTracking('pma_db', 'pma_tbl', '0.1', 1, 'activate');
}
/**
* Test for Tracker::deactivateTracking()
*/
public function testDeactivateTracking(): void
{
$this->testChangeTracking('pma_db', 'pma_tbl', '0.1', '0', 'deactivate');
}
/**
* Test for PMA_Tracker::getTrackedData()
*
* @param array $fetchArrayReturn Value to be returned by mocked fetchArray
* @param array $expectedArray Expected array
*
* @dataProvider getTrackedDataProvider
*/
public function testGetTrackedData(array $fetchArrayReturn, array $expectedArray): void
{
$resultStub = $this->createMock(DummyResult::class);
$dbi = $this->getMockBuilder(DatabaseInterface::class)
->disableOriginalConstructor()
->getMock();
$dbi->expects($this->once())
->method('queryAsControlUser')
->will($this->returnValue($resultStub));
$resultStub->expects($this->once())
->method('fetchAssoc')
->will($this->returnValue($fetchArrayReturn));
$dbi->expects($this->any())
->method('escapeString')
->will(
$this->returnValueMap(
[
["pma'db", Connection::TYPE_USER, "pma\'db"],
["pma'table", Connection::TYPE_USER, "pma\'table"],
['1.0', Connection::TYPE_USER, '1.0'],
],
),
);
$GLOBALS['dbi'] = $dbi;
$result = Tracker::getTrackedData("pma'db", "pma'table", '1.0');
$this->assertEquals($expectedArray, $result);
}
/**
* Data provider for testGetTrackedData
*
* @return array Test data
*/
public static function getTrackedDataProvider(): array
{
$fetchArrayReturn = [
[
'schema_sql' => "# log 20-03-2013 23:33:58 user1\nstat1" .
"# log 20-03-2013 23:39:58 user2\n",
'data_sql' => '# log ',
'schema_snapshot' => 'dataschema',
'tracking' => 'SELECT, DELETE',
],
];
$data = [
[
'date_from' => '20-03-2013 23:33:58',
'date_to' => '20-03-2013 23:39:58',
'ddlog' => [
['date' => '20-03-2013 23:33:58', 'username' => 'user1', 'statement' => "\nstat1"],
['date' => '20-03-2013 23:39:58', 'username' => 'user2', 'statement' => ''],
],
'dmlog' => [],
'schema_snapshot' => 'dataschema',
'tracking' => 'SELECT, DELETE',
],
];
$fetchArrayReturn[1] = [
'schema_sql' => "# log 20-03-2012 23:33:58 user1\n" .
"# log 20-03-2012 23:39:58 user2\n",
'data_sql' => "# log 20-03-2013 23:33:58 user3\n" .
"# log 20-03-2013 23:39:58 user4\n",
'schema_snapshot' => 'dataschema',
'tracking' => 'SELECT, DELETE',
];
$data[1] = [
'date_from' => '20-03-2012 23:33:58',
'date_to' => '20-03-2013 23:39:58',
'ddlog' => [
['date' => '20-03-2012 23:33:58', 'username' => 'user1', 'statement' => ''],
['date' => '20-03-2012 23:39:58', 'username' => 'user2', 'statement' => ''],
],
'dmlog' => [
['date' => '20-03-2013 23:33:58', 'username' => 'user3', 'statement' => ''],
['date' => '20-03-2013 23:39:58', 'username' => 'user4', 'statement' => ''],
],
'schema_snapshot' => 'dataschema',
'tracking' => 'SELECT, DELETE',
];
return [[$fetchArrayReturn[0], $data[0]], [$fetchArrayReturn[1], $data[1]]];
}
/**
* Test for Tracker::parseQuery
*
* @param string $query Query to parse
* @param string $type Expected type
* @param string $identifier Expected identifier
* @param string|null $tableName Expected tablename
* @param string|null $db Expected dbname
* @param string|null $tableNameAfterRename Expected name after rename
*
* @dataProvider parseQueryData
*/
public function testParseQuery(
string $query,
string $type,
string $identifier,
string|null $tableName,
string|null $db = null,
string|null $tableNameAfterRename = null,
): void {
$result = Tracker::parseQuery($query);
$this->assertEquals($type, $result['type']);
$this->assertEquals($identifier, $result['identifier']);
$this->assertEquals($tableName, $result['tablename']);
if ($db) {
$this->assertEquals($db, $GLOBALS['db']);
}
if (! $tableNameAfterRename) {
return;
}
$this->assertEquals($result['tablename_after_rename'], $tableNameAfterRename);
}
/**
* Data provider for testParseQuery
*
* @return array Test data
*/
public static function parseQueryData(): array
{
// query
// type
// identifier
// table name
// db (optional)
// table name after rename (optional)
$query = [];
/* TODO: Should test fail when USE is in conjunction with * identifiers?
$query[] = array(
" - USE db1;\n- CREATE VIEW db1.v AS SELECT * FROM t;",
"DDL",
"CREATE VIEW",
"v",
"db1"
);
*/
$query[] = ['CREATE VIEW v AS SELECT * FROM t;', 'DDL', 'CREATE VIEW', 'v'];
$query[] = ['ALTER VIEW db1.v AS SELECT col1, col2, col3, col4 FROM t', 'DDL', 'ALTER VIEW', 'v'];
$query[] = ['DROP VIEW db1.v;', 'DDL', 'DROP VIEW', 'v'];
$query[] = ['DROP VIEW IF EXISTS db1.v;', 'DDL', 'DROP VIEW', 'v'];
$query[] = ['CREATE DATABASE db1;', 'DDL', 'CREATE DATABASE', '', 'db1'];
$query[] = ['ALTER DATABASE db1;', 'DDL', 'ALTER DATABASE', ''];
$query[] = ['DROP DATABASE db1;', 'DDL', 'DROP DATABASE', '', 'db1'];
$query[] = ['CREATE TABLE db1.t1 (c1 INT);', 'DDL', 'CREATE TABLE', 't1'];
$query[] = ['ALTER TABLE db1.t1 ADD c2 TEXT;', 'DDL', 'ALTER TABLE', 't1'];
$query[] = ['DROP TABLE db1.t1', 'DDL', 'DROP TABLE', 't1'];
$query[] = ['DROP TABLE IF EXISTS db1.t1', 'DDL', 'DROP TABLE', 't1'];
$query[] = ['CREATE INDEX ind ON db1.t1 (c2(10));', 'DDL', 'CREATE INDEX', 't1'];
$query[] = ['CREATE UNIQUE INDEX ind ON db1.t1 (c2(10));', 'DDL', 'CREATE INDEX', 't1'];
$query[] = ['CREATE SPATIAL INDEX ind ON db1.t1 (c2(10));', 'DDL', 'CREATE INDEX', 't1'];
$query[] = ['DROP INDEX ind ON db1.t1;', 'DDL', 'DROP INDEX', 't1'];
$query[] = ['RENAME TABLE db1.t1 TO db1.t2', 'DDL', 'RENAME TABLE', 't1', '', 't2'];
$query[] = ['UPDATE db1.t1 SET a = 2', 'DML', 'UPDATE', 't1'];
$query[] = ['INSERT INTO db1.t1 (a, b, c) VALUES(1, 2, 3)', 'DML', 'INSERT', 't1'];
$query[] = ['DELETE FROM db1.t1', 'DML', 'DELETE', 't1'];
$query[] = ['TRUNCATE db1.t1', 'DML', 'TRUNCATE', 't1'];
$query[] = [
'create table event(' . "\n"
. 'eventID varchar(10) not null,' . "\n"
. 'b char(30),' . "\n"
. 'c varchar(20),' . "\n"
. 'd TIME,' . "\n"
. 'e Date,' . "\n"
. 'f int,' . "\n"
. 'g char(70),' . "\n"
. 'h char(90),' . "\n"
. 'primary key(eventID)' . "\n"
. ')' . "\n",
'DDL',
'CREATE TABLE',
null,// switch this to 'event' when sql-parse is fixed
];
return $query;
}
}