| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510 |
- <?php
- declare(strict_types=1);
- /**
- * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
- * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
- *
- * Licensed under The MIT License
- * For full copyright and license information, please see the LICENSE.txt
- * Redistributions of files must retain the above copyright notice.
- *
- * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
- * @link https://cakephp.org CakePHP(tm) Project
- * @since 3.2.12
- * @license https://opensource.org/licenses/mit-license.php MIT License
- */
- namespace Cake\Test\TestCase\Database;
- use Cake\Database\Driver\Sqlserver;
- use Cake\Database\Exception\MissingConnectionException;
- use Cake\Database\Exception\QueryException;
- use Cake\Database\Log\QueryLogger;
- use Cake\Database\Query;
- use Cake\Database\QueryCompiler;
- use Cake\Database\Schema\TableSchema;
- use Cake\Database\Statement\Statement;
- use Cake\Database\ValueBinder;
- use Cake\Datasource\ConnectionManager;
- use Cake\Log\Log;
- use Cake\TestSuite\TestCase;
- use DateTime;
- use Exception;
- use PDO;
- use PDOException;
- use PDOStatement;
- use PHPUnit\Framework\Attributes\DataProvider;
- use TestApp\Database\Driver\RetryDriver;
- use TestApp\Database\Driver\StubDriver;
- /**
- * Tests Driver class
- */
- class DriverTest extends TestCase
- {
- /**
- * @var \Cake\Database\Driver|\PHPUnit\Framework\MockObject\MockObject
- */
- protected $driver;
- /**
- * Setup.
- */
- public function setUp(): void
- {
- parent::setUp();
- Log::setConfig('queries', [
- 'className' => 'Array',
- 'scopes' => ['queriesLog'],
- ]);
- $this->driver = $this->getMockBuilder(StubDriver::class)
- ->onlyMethods(['createPdo', 'prepare'])
- ->getMock();
- }
- public function tearDown(): void
- {
- parent::tearDown();
- Log::drop('queries');
- }
- /**
- * Test if building the object throws an exception if we're not passing
- * required config data.
- */
- public function testConstructorException(): void
- {
- try {
- new StubDriver(['login' => 'Bear']);
- } catch (Exception $e) {
- $this->assertStringContainsString(
- 'Please pass "username" instead of "login" for connecting to the database',
- $e->getMessage()
- );
- }
- }
- /**
- * Test the constructor.
- */
- public function testConstructor(): void
- {
- $driver = new StubDriver(['quoteIdentifiers' => true]);
- $this->assertTrue($driver->isAutoQuotingEnabled());
- $driver = new StubDriver(['username' => 'GummyBear']);
- $this->assertFalse($driver->isAutoQuotingEnabled());
- }
- /**
- * Test schemaValue().
- * Uses a provider for all the different values we can pass to the method.
- *
- * @param mixed $input
- */
- #[DataProvider('schemaValueProvider')]
- public function testSchemaValue($input, string $expected): void
- {
- $result = $this->driver->schemaValue($input);
- $this->assertSame($expected, $result);
- }
- /**
- * Test schemaValue().
- * Asserting that quote() is being called because none of the conditions were met before.
- */
- public function testSchemaValueConnectionQuoting(): void
- {
- $value = 'string';
- $connection = $this->getMockBuilder(PDO::class)
- ->disableOriginalConstructor()
- ->onlyMethods(['quote'])
- ->getMock();
- $connection
- ->expects($this->once())
- ->method('quote')
- ->with($value, PDO::PARAM_STR)
- ->willReturn('string');
- $this->driver->expects($this->any())
- ->method('createPdo')
- ->willReturn($connection);
- $this->driver->schemaValue($value);
- }
- /**
- * Test lastInsertId().
- */
- public function testLastInsertId(): void
- {
- $connection = $this->getMockBuilder(PDO::class)
- ->disableOriginalConstructor()
- ->onlyMethods(['lastInsertId'])
- ->getMock();
- $connection
- ->expects($this->once())
- ->method('lastInsertId')
- ->willReturn('all-the-bears');
- $this->driver->expects($this->any())
- ->method('createPdo')
- ->willReturn($connection);
- $this->assertSame('all-the-bears', $this->driver->lastInsertId());
- }
- /**
- * Test isConnected().
- */
- public function testIsConnected(): void
- {
- $this->assertFalse($this->driver->isConnected());
- $connection = $this->getMockBuilder(PDO::class)
- ->disableOriginalConstructor()
- ->onlyMethods(['query'])
- ->getMock();
- $connection
- ->expects($this->once())
- ->method('query')
- ->willReturn(new PDOStatement());
- $this->driver->expects($this->any())
- ->method('createPdo')
- ->willReturn($connection);
- $this->driver->connect();
- $this->assertTrue($this->driver->isConnected());
- }
- /**
- * test autoQuoting().
- */
- public function testAutoQuoting(): void
- {
- $this->assertFalse($this->driver->isAutoQuotingEnabled());
- $this->assertSame($this->driver, $this->driver->enableAutoQuoting(true));
- $this->assertTrue($this->driver->isAutoQuotingEnabled());
- $this->driver->disableAutoQuoting();
- $this->assertFalse($this->driver->isAutoQuotingEnabled());
- }
- /**
- * Test compileQuery().
- */
- public function testCompileQuery(): void
- {
- $compiler = $this->getMockBuilder(QueryCompiler::class)
- ->onlyMethods(['compile'])
- ->getMock();
- $compiler
- ->expects($this->once())
- ->method('compile')
- ->willReturn('1');
- $driver = $this->getMockBuilder(StubDriver::class)
- ->onlyMethods(['newCompiler', 'transformQuery'])
- ->getMock();
- $driver
- ->expects($this->once())
- ->method('newCompiler')
- ->willReturn($compiler);
- $query = $this->getMockBuilder(Query::class)
- ->disableOriginalConstructor()
- ->getMock();
- $query->method('type')->willReturn('select');
- $driver
- ->expects($this->once())
- ->method('transformQuery')
- ->willReturn($query);
- $result = $driver->compileQuery($query, new ValueBinder());
- $this->assertSame('1', $result);
- }
- /**
- * Test newCompiler().
- */
- public function testNewCompiler(): void
- {
- $this->assertInstanceOf(QueryCompiler::class, $this->driver->newCompiler());
- }
- /**
- * Test newTableSchema().
- */
- public function testNewTableSchema(): void
- {
- $tableName = 'articles';
- $actual = $this->driver->newTableSchema($tableName);
- $this->assertInstanceOf(TableSchema::class, $actual);
- $this->assertSame($tableName, $actual->name());
- }
- public function testConnectRetry(): void
- {
- $this->skipIf(!ConnectionManager::get('test')->getDriver() instanceof Sqlserver);
- $driver = new RetryDriver();
- try {
- $driver->connect();
- } catch (MissingConnectionException) {
- }
- $this->assertSame(4, $driver->getConnectRetries());
- }
- /**
- * Test __destruct().
- */
- public function testDestructor(): void
- {
- $this->driver->__destruct();
- $this->assertFalse($this->driver->__debugInfo()['connected']);
- }
- /**
- * Data provider for testSchemaValue().
- *
- * @return array
- */
- public static function schemaValueProvider(): array
- {
- return [
- [null, 'NULL'],
- [false, 'FALSE'],
- [true, 'TRUE'],
- [1, '1'],
- ['0', '0'],
- ['42', '42'],
- ];
- }
- /**
- * Tests that queries are logged when executed without params
- */
- public function testExecuteNoParams(): void
- {
- $inner = $this->getMockBuilder(PDOStatement::class)->getMock();
- $statement = $this->getMockBuilder(Statement::class)
- ->setConstructorArgs([$inner, $this->driver])
- ->onlyMethods(['queryString','rowCount','execute'])
- ->getMock();
- $statement->expects($this->any())->method('queryString')->willReturn('SELECT bar FROM foo');
- $statement->method('rowCount')->willReturn(3);
- $statement->method('execute')->willReturn(true);
- $this->driver->expects($this->any())
- ->method('prepare')
- ->willReturn($statement);
- $this->driver->setLogger(new QueryLogger(['connection' => 'test']));
- $this->driver->execute('SELECT bar FROM foo');
- $messages = Log::engine('queries')->read();
- $this->assertCount(1, $messages);
- $this->assertMatchesRegularExpression('/^debug: connection=test role=write duration=[\d\.]+ rows=3 SELECT bar FROM foo$/', $messages[0]);
- }
- /**
- * Tests that queries are logged when executed with bound params
- */
- public function testExecuteWithBinding(): void
- {
- $inner = $this->getMockBuilder(PDOStatement::class)->getMock();
- $statement = $this->getMockBuilder(Statement::class)
- ->setConstructorArgs([$inner, $this->driver])
- ->onlyMethods(['queryString','rowCount','execute'])
- ->getMock();
- $statement->method('rowCount')->willReturn(3);
- $statement->method('execute')->willReturn(true);
- $statement->expects($this->any())->method('queryString')->willReturn('SELECT bar FROM foo WHERE a=:a AND b=:b');
- $this->driver->setLogger(new QueryLogger(['connection' => 'test']));
- $this->driver->expects($this->any())
- ->method('prepare')
- ->willReturn($statement);
- $this->driver->execute(
- 'SELECT bar FROM foo WHERE a=:a AND b=:b',
- [
- 'a' => 1,
- 'b' => new DateTime('2013-01-01'),
- ],
- ['b' => 'date']
- );
- $messages = Log::engine('queries')->read();
- $this->assertCount(1, $messages);
- $this->assertMatchesRegularExpression("/^debug: connection=test role=write duration=[\d\.]+ rows=3 SELECT bar FROM foo WHERE a='1' AND b='2013-01-01'$/", $messages[0]);
- }
- /**
- * Tests that queries are logged despite database errors
- */
- public function testExecuteWithError(): void
- {
- $inner = $this->getMockBuilder(PDOStatement::class)->getMock();
- $statement = $this->getMockBuilder(Statement::class)
- ->setConstructorArgs([$inner, $this->driver])
- ->onlyMethods(['queryString','rowCount','execute'])
- ->getMock();
- $statement->expects($this->any())->method('queryString')->willReturn('SELECT bar FROM foo');
- $statement->method('rowCount')->willReturn(0);
- $statement->method('execute')->will($this->throwException(new PDOException()));
- $this->driver->setLogger(new QueryLogger(['connection' => 'test']));
- $this->driver->expects($this->any())
- ->method('prepare')
- ->willReturn($statement);
- try {
- $this->driver->execute('SELECT foo FROM bar');
- } catch (PDOException) {
- }
- $messages = Log::engine('queries')->read();
- $this->assertCount(1, $messages);
- $this->assertMatchesRegularExpression('/^debug: connection=test role=write duration=\d+ rows=0 SELECT bar FROM foo$/', $messages[0]);
- }
- public function testGetLoggerDefault(): void
- {
- $driver = $this->getMockBuilder(StubDriver::class)
- ->onlyMethods(['createPdo', 'prepare'])
- ->getMock();
- $this->assertNull($driver->getLogger());
- $driver = $this->getMockBuilder(StubDriver::class)
- ->setConstructorArgs([['log' => true]])
- ->onlyMethods(['createPdo'])
- ->getMock();
- $logger = $driver->getLogger();
- $this->assertInstanceOf(QueryLogger::class, $logger);
- }
- public function testSetLogger(): void
- {
- $logger = new QueryLogger();
- $this->driver->setLogger($logger);
- $this->assertSame($logger, $this->driver->getLogger());
- }
- public function testLogTransaction(): void
- {
- $pdo = $this->getMockBuilder(PDO::class)
- ->disableOriginalConstructor()
- ->onlyMethods(['beginTransaction', 'commit', 'rollback', 'inTransaction'])
- ->getMock();
- $pdo
- ->expects($this->any())
- ->method('beginTransaction')
- ->willReturn(true);
- $pdo
- ->expects($this->any())
- ->method('commit')
- ->willReturn(true);
- $pdo
- ->expects($this->any())
- ->method('rollBack')
- ->willReturn(true);
- $pdo->expects($this->exactly(5))
- ->method('inTransaction')
- ->willReturn(
- false,
- true,
- true,
- false,
- true,
- );
- $driver = $this->getMockBuilder(StubDriver::class)
- ->setConstructorArgs([['log' => true]])
- ->onlyMethods(['getPdo'])
- ->getMock();
- $driver->expects($this->any())
- ->method('getPdo')
- ->willReturn($pdo);
- $driver->beginTransaction();
- $driver->beginTransaction(); //This one will not be logged
- $driver->rollbackTransaction();
- $driver->beginTransaction();
- $driver->commitTransaction();
- $messages = Log::engine('queries')->read();
- $this->assertCount(4, $messages);
- $this->assertSame('debug: connection= role= duration=0 rows=0 BEGIN', $messages[0]);
- $this->assertSame('debug: connection= role= duration=0 rows=0 ROLLBACK', $messages[1]);
- $this->assertSame('debug: connection= role= duration=0 rows=0 BEGIN', $messages[2]);
- $this->assertSame('debug: connection= role= duration=0 rows=0 COMMIT', $messages[3]);
- }
- public function testQueryException(): void
- {
- $this->expectException(QueryException::class);
- ConnectionManager::get('default')->execute('SELECT * FROM non_existent_table');
- }
- public function testQueryExceptionStatementExecute(): void
- {
- $this->expectException(QueryException::class);
- ConnectionManager::get('default')->getDriver()
- ->execute('SELECT * FROM :foo', ['foo' => 'bar']);
- }
- /**
- * Tests that queries are logged when executed without params
- */
- public function testDisableQueryLogging(): void
- {
- $inner = $this->getMockBuilder(PDOStatement::class)->getMock();
- $statement = $this->getMockBuilder(Statement::class)
- ->setConstructorArgs([$inner, $this->driver])
- ->onlyMethods(['queryString','rowCount','execute'])
- ->getMock();
- $statement->expects($this->any())->method('queryString')->willReturn('SELECT bar FROM foo');
- $statement->method('rowCount')->willReturn(3);
- $statement->method('execute')->willReturn(true);
- $this->driver->expects($this->any())
- ->method('prepare')
- ->willReturn($statement);
- $this->driver->setLogger(new QueryLogger(['connection' => 'test']));
- $this->driver->execute('SELECT bar FROM foo');
- $messages = Log::engine('queries')->read();
- $this->driver->disableQueryLogging();
- $this->driver->execute('SELECT bar FROM foo');
- $this->assertCount(1, $messages);
- $this->assertMatchesRegularExpression('/^debug: connection=test role=write duration=[\d\.]+ rows=3 SELECT bar FROM foo$/', $messages[0]);
- }
- }
|