'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 { self::assertFalse(Cache::has(Tracker::TRACKER_ENABLED_CACHE_KEY)); Tracker::enable(); self::assertTrue(Cache::get(Tracker::TRACKER_ENABLED_CACHE_KEY)); } /** * Test for Tracker::isActive() */ public function testIsActive(): void { self::assertFalse(Cache::has(Tracker::TRACKER_ENABLED_CACHE_KEY)); self::assertFalse(Tracker::isActive()); Tracker::enable(); $_SESSION['relation'] = []; $_SESSION['relation'][$GLOBALS['server']] = RelationParameters::fromArray([])->toArray(); self::assertFalse(Tracker::isActive()); $_SESSION['relation'] = []; $_SESSION['relation'][$GLOBALS['server']] = RelationParameters::fromArray([ 'trackingwork' => true, 'db' => 'pmadb', 'tracking' => 'tracking', ])->toArray(); self::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 { self::assertSame($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 { self::assertFalse(Cache::has(Tracker::TRACKER_ENABLED_CACHE_KEY)); self::assertFalse(Tracker::isTracked('', '')); Tracker::enable(); $_SESSION['relation'] = []; $_SESSION['relation'][$GLOBALS['server']] = RelationParameters::fromArray([])->toArray(); self::assertFalse(Tracker::isTracked('', '')); $_SESSION['relation'] = []; $_SESSION['relation'][$GLOBALS['server']] = RelationParameters::fromArray([ 'trackingwork' => true, 'db' => 'pmadb', 'tracking' => 'tracking', ])->toArray(); self::assertTrue(Tracker::isTracked('pma_test_db', 'pma_test_table')); self::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'; self::assertSame('# log ' . $date . " pma_test_user\n", Tracker::getLogComment()); } /** * Test for Tracker::createVersion() * * @requires PHPUnit < 10 */ 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)); $dbi->expects($this->exactly(3)) ->method('tryQuery') ->withConsecutive( ["SHOW TABLE STATUS FROM `pma_test` WHERE Name = 'pma_tbl'"], ['USE `pma_test`'], ['SHOW CREATE TABLE `pma_test`.`pma_tbl`'] ) ->willReturnOnConsecutiveCalls($resultStub, $resultStub, $resultStub); $dbi->expects($this->any())->method('query') ->will($this->returnValue($resultStub)); $dbi->expects($this->any())->method('escapeString') ->will($this->returnArgument(0)); $dbi->expects($this->any())->method('getCompatibilities') ->will($this->returnValue([])); $GLOBALS['dbi'] = $dbi; self::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(); $sql_query = "/*NOTRACK*/\n" . 'DELETE FROM `pmadb`.`tracking`' . " WHERE `db_name` = 'testdb'" . " AND `table_name` = 'testtable'"; $dbi->expects($this->exactly(1)) ->method('queryAsControlUser') ->with($sql_query) ->will($this->returnValue($resultStub)); $dbi->expects($this->any())->method('escapeString') ->will($this->returnArgument(0)); $GLOBALS['dbi'] = $dbi; self::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; self::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 $new_state 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', $new_state = '1', ?string $type = null ): void { $dbi = $this->getMockBuilder(DatabaseInterface::class) ->disableOriginalConstructor() ->getMock(); $resultStub = $this->createMock(DummyResult::class); $sql_query = 'UPDATE `pmadb`.`tracking` SET `tracking_active` = ' . "'" . $new_state . "'" . " WHERE `db_name` = '" . $dbname . "'" . " AND `table_name` = '" . $tablename . "'" . " AND `version` = '" . $version . "'"; $dbi->expects($this->exactly(1)) ->method('queryAsControlUser') ->with($sql_query) ->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'); if (PHP_VERSION_ID < 80100) { $method->setAccessible(true); } $method->invoke(null, $dbname, $tablename, $version, $new_state); } 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 { self::assertFalse(Tracker::changeTrackingData('', '', '', '', '')); $dbi = $this->getMockBuilder(DatabaseInterface::class) ->disableOriginalConstructor() ->getMock(); $sql_query_1 = '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'); $new_data = [ [ 'username' => 'user1', 'statement' => 'test_statement1', ], [ 'username' => 'user2', 'statement' => 'test_statement2', ], ]; $sql_query_2 = '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( [ [ $sql_query_1, $resultStub1, ], [ $sql_query_2, $resultStub2, ], ] ) ); $dbi->expects($this->any())->method('escapeString') ->will($this->returnArgument(0)); $GLOBALS['dbi'] = $dbi; self::assertTrue(Tracker::changeTrackingData( 'pma_db', 'pma_table', '1.0', 'DDL', '# new_data_processed' )); self::assertTrue(Tracker::changeTrackingData( 'pma_db', 'pma_table', '1.0', 'DML', $new_data )); } /** * 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", "pma\'db", ], [ "pma'table", "pma\'table", ], [ '1.0', '1.0', ], ] ) ); $GLOBALS['dbi'] = $dbi; $result = Tracker::getTrackedData("pma'db", "pma'table", '1.0'); self::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 $tablename_after_rename Expected name after rename * * @dataProvider parseQueryData */ public function testParseQuery( string $query, string $type, string $identifier, ?string $tablename, ?string $db = null, ?string $tablename_after_rename = null ): void { $result = Tracker::parseQuery($query); self::assertSame($type, $result['type']); self::assertSame($identifier, $result['identifier']); self::assertSame($tablename, $result['tablename']); if ($db) { self::assertSame($db, $GLOBALS['db']); } if (! $tablename_after_rename) { return; } self::assertSame($result['tablename_after_rename'], $tablename_after_rename); } /** * 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; } }