DriverTest.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
  5. * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
  6. *
  7. * Licensed under The MIT License
  8. * For full copyright and license information, please see the LICENSE.txt
  9. * Redistributions of files must retain the above copyright notice.
  10. *
  11. * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
  12. * @link https://cakephp.org CakePHP(tm) Project
  13. * @since 3.2.12
  14. * @license https://opensource.org/licenses/mit-license.php MIT License
  15. */
  16. namespace Cake\Test\TestCase\Database;
  17. use Cake\Database\Driver;
  18. use Cake\Database\Driver\Sqlserver;
  19. use Cake\Database\Exception\MissingConnectionException;
  20. use Cake\Database\Log\QueryLogger;
  21. use Cake\Database\Query;
  22. use Cake\Database\QueryCompiler;
  23. use Cake\Database\Schema\TableSchema;
  24. use Cake\Database\Statement\Statement;
  25. use Cake\Database\ValueBinder;
  26. use Cake\Datasource\ConnectionManager;
  27. use Cake\Log\Log;
  28. use Cake\TestSuite\TestCase;
  29. use DateTime;
  30. use Exception;
  31. use PDO;
  32. use PDOException;
  33. use PDOStatement;
  34. use TestApp\Database\Driver\RetryDriver;
  35. use TestApp\Database\Driver\StubDriver;
  36. /**
  37. * Tests Driver class
  38. */
  39. class DriverTest extends TestCase
  40. {
  41. /**
  42. * @var \Cake\Database\Driver|\PHPUnit\Framework\MockObject\MockObject
  43. */
  44. protected $driver;
  45. /**
  46. * Setup.
  47. */
  48. public function setUp(): void
  49. {
  50. parent::setUp();
  51. Log::setConfig('queries', [
  52. 'className' => 'Array',
  53. 'scopes' => ['queriesLog'],
  54. ]);
  55. $this->driver = $this->getMockForAbstractClass(
  56. StubDriver::class,
  57. [],
  58. '',
  59. true,
  60. true,
  61. true,
  62. ['createPdo', 'prepare']
  63. );
  64. }
  65. public function tearDown(): void
  66. {
  67. parent::tearDown();
  68. Log::drop('queries');
  69. }
  70. /**
  71. * Test if building the object throws an exception if we're not passing
  72. * required config data.
  73. */
  74. public function testConstructorException(): void
  75. {
  76. $arg = ['login' => 'Bear'];
  77. try {
  78. $this->getMockForAbstractClass(Driver::class, [$arg]);
  79. } catch (Exception $e) {
  80. $this->assertStringContainsString(
  81. 'Please pass "username" instead of "login" for connecting to the database',
  82. $e->getMessage()
  83. );
  84. }
  85. }
  86. /**
  87. * Test the constructor.
  88. */
  89. public function testConstructor(): void
  90. {
  91. $arg = ['quoteIdentifiers' => true];
  92. $driver = $this->getMockForAbstractClass(Driver::class, [$arg]);
  93. $this->assertTrue($driver->isAutoQuotingEnabled());
  94. $arg = ['username' => 'GummyBear'];
  95. $driver = $this->getMockForAbstractClass(Driver::class, [$arg]);
  96. $this->assertFalse($driver->isAutoQuotingEnabled());
  97. }
  98. /**
  99. * Test schemaValue().
  100. * Uses a provider for all the different values we can pass to the method.
  101. *
  102. * @dataProvider schemaValueProvider
  103. * @param mixed $input
  104. */
  105. public function testSchemaValue($input, string $expected): void
  106. {
  107. $result = $this->driver->schemaValue($input);
  108. $this->assertSame($expected, $result);
  109. }
  110. /**
  111. * Test schemaValue().
  112. * Asserting that quote() is being called because none of the conditions were met before.
  113. */
  114. public function testSchemaValueConnectionQuoting(): void
  115. {
  116. $value = 'string';
  117. $connection = $this->getMockBuilder(PDO::class)
  118. ->disableOriginalConstructor()
  119. ->onlyMethods(['quote'])
  120. ->getMock();
  121. $connection
  122. ->expects($this->once())
  123. ->method('quote')
  124. ->with($value, PDO::PARAM_STR)
  125. ->will($this->returnValue('string'));
  126. $this->driver->expects($this->any())
  127. ->method('createPdo')
  128. ->willReturn($connection);
  129. $this->driver->schemaValue($value);
  130. }
  131. /**
  132. * Test lastInsertId().
  133. */
  134. public function testLastInsertId(): void
  135. {
  136. $connection = $this->getMockBuilder(PDO::class)
  137. ->disableOriginalConstructor()
  138. ->onlyMethods(['lastInsertId'])
  139. ->getMock();
  140. $connection
  141. ->expects($this->once())
  142. ->method('lastInsertId')
  143. ->willReturn('all-the-bears');
  144. $this->driver->expects($this->any())
  145. ->method('createPdo')
  146. ->willReturn($connection);
  147. $this->assertSame('all-the-bears', $this->driver->lastInsertId());
  148. }
  149. /**
  150. * Test isConnected().
  151. */
  152. public function testIsConnected(): void
  153. {
  154. $this->assertFalse($this->driver->isConnected());
  155. $connection = $this->getMockBuilder(PDO::class)
  156. ->disableOriginalConstructor()
  157. ->onlyMethods(['query'])
  158. ->getMock();
  159. $connection
  160. ->expects($this->once())
  161. ->method('query')
  162. ->willReturn(new PDOStatement());
  163. $this->driver->expects($this->any())
  164. ->method('createPdo')
  165. ->willReturn($connection);
  166. $this->driver->connect();
  167. $this->assertTrue($this->driver->isConnected());
  168. }
  169. /**
  170. * test autoQuoting().
  171. */
  172. public function testAutoQuoting(): void
  173. {
  174. $this->assertFalse($this->driver->isAutoQuotingEnabled());
  175. $this->assertSame($this->driver, $this->driver->enableAutoQuoting(true));
  176. $this->assertTrue($this->driver->isAutoQuotingEnabled());
  177. $this->driver->disableAutoQuoting();
  178. $this->assertFalse($this->driver->isAutoQuotingEnabled());
  179. }
  180. /**
  181. * Test compileQuery().
  182. */
  183. public function testCompileQuery(): void
  184. {
  185. $compiler = $this->getMockBuilder(QueryCompiler::class)
  186. ->onlyMethods(['compile'])
  187. ->getMock();
  188. $compiler
  189. ->expects($this->once())
  190. ->method('compile')
  191. ->willReturn('1');
  192. $driver = $this->getMockBuilder(Driver::class)
  193. ->onlyMethods(['newCompiler', 'queryTranslator'])
  194. ->getMockForAbstractClass();
  195. $driver
  196. ->expects($this->once())
  197. ->method('newCompiler')
  198. ->willReturn($compiler);
  199. $driver
  200. ->expects($this->once())
  201. ->method('queryTranslator')
  202. ->willReturn(function ($query) {
  203. return $query;
  204. });
  205. $query = $this->getMockBuilder(Query::class)
  206. ->disableOriginalConstructor()
  207. ->getMock();
  208. $query->method('type')->will($this->returnValue('select'));
  209. $result = $driver->compileQuery($query, new ValueBinder());
  210. $this->assertSame('1', $result);
  211. }
  212. /**
  213. * Test newCompiler().
  214. */
  215. public function testNewCompiler(): void
  216. {
  217. $this->assertInstanceOf(QueryCompiler::class, $this->driver->newCompiler());
  218. }
  219. /**
  220. * Test newTableSchema().
  221. */
  222. public function testNewTableSchema(): void
  223. {
  224. $tableName = 'articles';
  225. $actual = $this->driver->newTableSchema($tableName);
  226. $this->assertInstanceOf(TableSchema::class, $actual);
  227. $this->assertSame($tableName, $actual->name());
  228. }
  229. public function testConnectRetry(): void
  230. {
  231. $this->skipIf(!ConnectionManager::get('test')->getDriver() instanceof Sqlserver);
  232. $driver = new RetryDriver();
  233. try {
  234. $driver->connect();
  235. } catch (MissingConnectionException) {
  236. }
  237. $this->assertSame(4, $driver->getConnectRetries());
  238. }
  239. /**
  240. * Test __destruct().
  241. */
  242. public function testDestructor(): void
  243. {
  244. $this->driver->__destruct();
  245. $this->assertFalse($this->driver->__debugInfo()['connected']);
  246. }
  247. /**
  248. * Data provider for testSchemaValue().
  249. *
  250. * @return array
  251. */
  252. public function schemaValueProvider(): array
  253. {
  254. return [
  255. [null, 'NULL'],
  256. [false, 'FALSE'],
  257. [true, 'TRUE'],
  258. [1, '1'],
  259. ['0', '0'],
  260. ['42', '42'],
  261. ];
  262. }
  263. /**
  264. * Tests that queries are logged when executed without params
  265. */
  266. public function testExecuteNoParams(): void
  267. {
  268. $inner = $this->getMockBuilder(PDOStatement::class)->getMock();
  269. $statement = $this->getMockBuilder(Statement::class)
  270. ->setConstructorArgs([$inner, $this->driver])
  271. ->onlyMethods(['queryString','rowCount','execute'])
  272. ->getMock();
  273. $statement->expects($this->any())->method('queryString')->will($this->returnValue('SELECT bar FROM foo'));
  274. $statement->method('rowCount')->will($this->returnValue(3));
  275. $statement->method('execute')->will($this->returnValue(true));
  276. $this->driver->expects($this->any())
  277. ->method('prepare')
  278. ->willReturn($statement);
  279. $this->driver->setLogger(new QueryLogger(['connection' => 'test']));
  280. $this->driver->execute('SELECT bar FROM foo');
  281. $messages = Log::engine('queries')->read();
  282. $this->assertCount(1, $messages);
  283. $this->assertMatchesRegularExpression('/^debug: connection=test duration=[\d\.]+ rows=3 SELECT bar FROM foo$/', $messages[0]);
  284. }
  285. /**
  286. * Tests that queries are logged when executed with bound params
  287. */
  288. public function testExecuteWithBinding(): void
  289. {
  290. $inner = $this->getMockBuilder(PDOStatement::class)->getMock();
  291. $statement = $this->getMockBuilder(Statement::class)
  292. ->setConstructorArgs([$inner, $this->driver])
  293. ->onlyMethods(['queryString','rowCount','execute'])
  294. ->getMock();
  295. $statement->method('rowCount')->will($this->returnValue(3));
  296. $statement->method('execute')->will($this->returnValue(true));
  297. $statement->expects($this->any())->method('queryString')->will($this->returnValue('SELECT bar FROM foo WHERE a=:a AND b=:b'));
  298. $this->driver->setLogger(new QueryLogger(['connection' => 'test']));
  299. $this->driver->expects($this->any())
  300. ->method('prepare')
  301. ->willReturn($statement);
  302. $this->driver->execute(
  303. 'SELECT bar FROM foo WHERE a=:a AND b=:b',
  304. [
  305. 'a' => 1,
  306. 'b' => new DateTime('2013-01-01'),
  307. ],
  308. ['b' => 'date']
  309. );
  310. $messages = Log::engine('queries')->read();
  311. $this->assertCount(1, $messages);
  312. $this->assertMatchesRegularExpression("/^debug: connection=test duration=[\d\.]+ rows=3 SELECT bar FROM foo WHERE a='1' AND b='2013-01-01'$/", $messages[0]);
  313. }
  314. /**
  315. * Tests that queries are logged despite database errors
  316. */
  317. public function testExecuteWithError(): void
  318. {
  319. $inner = $this->getMockBuilder(PDOStatement::class)->getMock();
  320. $statement = $this->getMockBuilder(Statement::class)
  321. ->setConstructorArgs([$inner, $this->driver])
  322. ->onlyMethods(['queryString','rowCount','execute'])
  323. ->getMock();
  324. $statement->expects($this->any())->method('queryString')->will($this->returnValue('SELECT bar FROM foo'));
  325. $statement->method('rowCount')->will($this->returnValue(0));
  326. $statement->method('execute')->will($this->throwException(new PDOException()));
  327. $this->driver->setLogger(new QueryLogger(['connection' => 'test']));
  328. $this->driver->expects($this->any())
  329. ->method('prepare')
  330. ->willReturn($statement);
  331. try {
  332. $this->driver->execute('SELECT foo FROM bar');
  333. } catch (PDOException $e) {
  334. }
  335. $messages = Log::engine('queries')->read();
  336. $this->assertCount(1, $messages);
  337. $this->assertMatchesRegularExpression('/^debug: connection=test duration=\d+ rows=0 SELECT bar FROM foo$/', $messages[0]);
  338. }
  339. public function testGetLoggerDefault(): void
  340. {
  341. $driver = $this->getMockForAbstractClass(
  342. StubDriver::class,
  343. [],
  344. '',
  345. true,
  346. true,
  347. true,
  348. ['createPdo', 'prepare']
  349. );
  350. $this->assertNull($driver->getLogger());
  351. $driver = $this->getMockForAbstractClass(
  352. StubDriver::class,
  353. [['log' => true]],
  354. '',
  355. true,
  356. true,
  357. true,
  358. ['createPdo']
  359. );
  360. $logger = $driver->getLogger();
  361. $this->assertInstanceOf(QueryLogger::class, $logger);
  362. }
  363. public function testSetLogger(): void
  364. {
  365. $logger = new QueryLogger();
  366. $this->driver->setLogger($logger);
  367. $this->assertSame($logger, $this->driver->getLogger());
  368. }
  369. public function testLogTransaction(): void
  370. {
  371. $pdo = $this->getMockBuilder(PDO::class)
  372. ->disableOriginalConstructor()
  373. ->onlyMethods(['beginTransaction', 'commit', 'rollback', 'inTransaction'])
  374. ->getMock();
  375. $pdo
  376. ->expects($this->any())
  377. ->method('beginTransaction')
  378. ->willReturn(true);
  379. $pdo
  380. ->expects($this->any())
  381. ->method('commit')
  382. ->willReturn(true);
  383. $pdo
  384. ->expects($this->any())
  385. ->method('rollBack')
  386. ->willReturn(true);
  387. $pdo->expects($this->exactly(5))
  388. ->method('inTransaction')
  389. ->will($this->onConsecutiveCalls(
  390. false,
  391. true,
  392. true,
  393. false,
  394. true,
  395. ));
  396. $driver = $this->getMockForAbstractClass(
  397. StubDriver::class,
  398. [['log' => true]],
  399. '',
  400. true,
  401. true,
  402. true,
  403. ['getPdo']
  404. );
  405. $driver->expects($this->any())
  406. ->method('getPdo')
  407. ->willReturn($pdo);
  408. $driver->beginTransaction();
  409. $driver->beginTransaction(); //This one will not be logged
  410. $driver->rollbackTransaction();
  411. $driver->beginTransaction();
  412. $driver->commitTransaction();
  413. $messages = Log::engine('queries')->read();
  414. $this->assertCount(4, $messages);
  415. $this->assertSame('debug: connection= duration=0 rows=0 BEGIN', $messages[0]);
  416. $this->assertSame('debug: connection= duration=0 rows=0 ROLLBACK', $messages[1]);
  417. $this->assertSame('debug: connection= duration=0 rows=0 BEGIN', $messages[2]);
  418. $this->assertSame('debug: connection= duration=0 rows=0 COMMIT', $messages[3]);
  419. }
  420. }