DriverTest.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495
  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->assertIsArray($result);
  211. $this->assertSame($query, $result[0]);
  212. $this->assertSame('1', $result[1]);
  213. }
  214. /**
  215. * Test newCompiler().
  216. */
  217. public function testNewCompiler(): void
  218. {
  219. $this->assertInstanceOf(QueryCompiler::class, $this->driver->newCompiler());
  220. }
  221. /**
  222. * Test newTableSchema().
  223. */
  224. public function testNewTableSchema(): void
  225. {
  226. $tableName = 'articles';
  227. $actual = $this->driver->newTableSchema($tableName);
  228. $this->assertInstanceOf(TableSchema::class, $actual);
  229. $this->assertSame($tableName, $actual->name());
  230. }
  231. public function testConnectRetry(): void
  232. {
  233. $this->skipIf(!ConnectionManager::get('test')->getDriver() instanceof Sqlserver);
  234. $driver = new RetryDriver();
  235. try {
  236. $driver->connect();
  237. } catch (MissingConnectionException) {
  238. }
  239. $this->assertSame(4, $driver->getConnectRetries());
  240. }
  241. /**
  242. * Test __destruct().
  243. */
  244. public function testDestructor(): void
  245. {
  246. $this->driver->__destruct();
  247. $this->assertFalse($this->driver->__debugInfo()['connected']);
  248. }
  249. /**
  250. * Data provider for testSchemaValue().
  251. *
  252. * @return array
  253. */
  254. public function schemaValueProvider(): array
  255. {
  256. return [
  257. [null, 'NULL'],
  258. [false, 'FALSE'],
  259. [true, 'TRUE'],
  260. [1, '1'],
  261. ['0', '0'],
  262. ['42', '42'],
  263. ];
  264. }
  265. /**
  266. * Tests that queries are logged when executed without params
  267. */
  268. public function testExecuteNoParams(): void
  269. {
  270. $inner = $this->getMockBuilder(PDOStatement::class)->getMock();
  271. $statement = $this->getMockBuilder(Statement::class)
  272. ->setConstructorArgs([$inner, $this->driver])
  273. ->onlyMethods(['queryString','rowCount','execute'])
  274. ->getMock();
  275. $statement->expects($this->any())->method('queryString')->will($this->returnValue('SELECT bar FROM foo'));
  276. $statement->method('rowCount')->will($this->returnValue(3));
  277. $statement->method('execute')->will($this->returnValue(true));
  278. $this->driver->expects($this->any())
  279. ->method('prepare')
  280. ->willReturn($statement);
  281. $this->driver->setLogger(new QueryLogger(['connection' => 'test']));
  282. $this->driver->execute('SELECT bar FROM foo');
  283. $messages = Log::engine('queries')->read();
  284. $this->assertCount(1, $messages);
  285. $this->assertMatchesRegularExpression('/^debug: connection=test duration=[\d\.]+ rows=3 SELECT bar FROM foo$/', $messages[0]);
  286. }
  287. /**
  288. * Tests that queries are logged when executed with bound params
  289. */
  290. public function testExecuteWithBinding(): void
  291. {
  292. $inner = $this->getMockBuilder(PDOStatement::class)->getMock();
  293. $statement = $this->getMockBuilder(Statement::class)
  294. ->setConstructorArgs([$inner, $this->driver])
  295. ->onlyMethods(['queryString','rowCount','execute'])
  296. ->getMock();
  297. $statement->method('rowCount')->will($this->returnValue(3));
  298. $statement->method('execute')->will($this->returnValue(true));
  299. $statement->expects($this->any())->method('queryString')->will($this->returnValue('SELECT bar FROM foo WHERE a=:a AND b=:b'));
  300. $this->driver->setLogger(new QueryLogger(['connection' => 'test']));
  301. $this->driver->expects($this->any())
  302. ->method('prepare')
  303. ->willReturn($statement);
  304. $this->driver->execute(
  305. 'SELECT bar FROM foo WHERE a=:a AND b=:b',
  306. [
  307. 'a' => 1,
  308. 'b' => new DateTime('2013-01-01'),
  309. ],
  310. ['b' => 'date']
  311. );
  312. $messages = Log::engine('queries')->read();
  313. $this->assertCount(1, $messages);
  314. $this->assertMatchesRegularExpression("/^debug: connection=test duration=[\d\.]+ rows=3 SELECT bar FROM foo WHERE a='1' AND b='2013-01-01'$/", $messages[0]);
  315. }
  316. /**
  317. * Tests that queries are logged despite database errors
  318. */
  319. public function testExecuteWithError(): void
  320. {
  321. $inner = $this->getMockBuilder(PDOStatement::class)->getMock();
  322. $statement = $this->getMockBuilder(Statement::class)
  323. ->setConstructorArgs([$inner, $this->driver])
  324. ->onlyMethods(['queryString','rowCount','execute'])
  325. ->getMock();
  326. $statement->expects($this->any())->method('queryString')->will($this->returnValue('SELECT bar FROM foo'));
  327. $statement->method('rowCount')->will($this->returnValue(0));
  328. $statement->method('execute')->will($this->throwException(new PDOException()));
  329. $this->driver->setLogger(new QueryLogger(['connection' => 'test']));
  330. $this->driver->expects($this->any())
  331. ->method('prepare')
  332. ->willReturn($statement);
  333. try {
  334. $this->driver->execute('SELECT foo FROM bar');
  335. } catch (PDOException $e) {
  336. }
  337. $messages = Log::engine('queries')->read();
  338. $this->assertCount(1, $messages);
  339. $this->assertMatchesRegularExpression('/^debug: connection=test duration=\d+ rows=0 SELECT bar FROM foo$/', $messages[0]);
  340. }
  341. public function testGetLoggerDefault(): void
  342. {
  343. $driver = $this->getMockForAbstractClass(
  344. StubDriver::class,
  345. [],
  346. '',
  347. true,
  348. true,
  349. true,
  350. ['createPdo', 'prepare']
  351. );
  352. $this->assertNull($driver->getLogger());
  353. $driver = $this->getMockForAbstractClass(
  354. StubDriver::class,
  355. [['log' => true]],
  356. '',
  357. true,
  358. true,
  359. true,
  360. ['createPdo']
  361. );
  362. $logger = $driver->getLogger();
  363. $this->assertInstanceOf(QueryLogger::class, $logger);
  364. }
  365. public function testSetLogger(): void
  366. {
  367. $logger = new QueryLogger();
  368. $this->driver->setLogger($logger);
  369. $this->assertSame($logger, $this->driver->getLogger());
  370. }
  371. public function testLogTransaction(): void
  372. {
  373. $pdo = $this->getMockBuilder(PDO::class)
  374. ->disableOriginalConstructor()
  375. ->onlyMethods(['beginTransaction', 'commit', 'rollback', 'inTransaction'])
  376. ->getMock();
  377. $pdo
  378. ->expects($this->any())
  379. ->method('beginTransaction')
  380. ->willReturn(true);
  381. $pdo
  382. ->expects($this->any())
  383. ->method('commit')
  384. ->willReturn(true);
  385. $pdo
  386. ->expects($this->any())
  387. ->method('rollBack')
  388. ->willReturn(true);
  389. $pdo->expects($this->exactly(5))
  390. ->method('inTransaction')
  391. ->will($this->onConsecutiveCalls(
  392. false,
  393. true,
  394. true,
  395. false,
  396. true,
  397. ));
  398. $driver = $this->getMockForAbstractClass(
  399. StubDriver::class,
  400. [['log' => true]],
  401. '',
  402. true,
  403. true,
  404. true,
  405. ['getPdo']
  406. );
  407. $driver->expects($this->any())
  408. ->method('getPdo')
  409. ->willReturn($pdo);
  410. $driver->beginTransaction();
  411. $driver->beginTransaction(); //This one will not be logged
  412. $driver->rollbackTransaction();
  413. $driver->beginTransaction();
  414. $driver->commitTransaction();
  415. $messages = Log::engine('queries')->read();
  416. $this->assertCount(4, $messages);
  417. $this->assertSame('debug: connection= duration=0 rows=0 BEGIN', $messages[0]);
  418. $this->assertSame('debug: connection= duration=0 rows=0 ROLLBACK', $messages[1]);
  419. $this->assertSame('debug: connection= duration=0 rows=0 BEGIN', $messages[2]);
  420. $this->assertSame('debug: connection= duration=0 rows=0 COMMIT', $messages[3]);
  421. }
  422. }