DbTest.php 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. namespace Magento\Framework\Data\Test\Unit\Collection;
  7. /**
  8. * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
  9. */
  10. class DbTest extends \PHPUnit\Framework\TestCase
  11. {
  12. use \Magento\Framework\TestFramework\Unit\Helper\SelectRendererTrait;
  13. /**
  14. * @var \Magento\Framework\Data\Collection\AbstractDb
  15. */
  16. protected $collection;
  17. /**
  18. * @var \Psr\Log\LoggerInterface|\PHPUnit_Framework_MockObject_MockObject
  19. */
  20. protected $loggerMock;
  21. /**
  22. * @var \Magento\Framework\Data\Collection\EntityFactory|\PHPUnit_Framework_MockObject_MockObject
  23. */
  24. protected $entityFactoryMock;
  25. /**
  26. * @var \Magento\Framework\Data\Collection\Db\FetchStrategyInterface|\PHPUnit_Framework_MockObject_MockObject
  27. */
  28. protected $fetchStrategyMock;
  29. /**
  30. * @var \Magento\Framework\TestFramework\Unit\Helper\ObjectManager
  31. */
  32. protected $objectManager;
  33. protected function setUp()
  34. {
  35. $this->objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this);
  36. $this->fetchStrategyMock =
  37. $this->createPartialMock(\Magento\Framework\Data\Collection\Db\FetchStrategy\Query::class, ['fetchAll']);
  38. $this->entityFactoryMock =
  39. $this->createPartialMock(\Magento\Framework\Data\Collection\EntityFactory::class, ['create']);
  40. $this->loggerMock = $this->createMock(\Psr\Log\LoggerInterface::class);
  41. $this->collection = new \Magento\Framework\Data\Test\Unit\Collection\DbCollection(
  42. $this->entityFactoryMock,
  43. $this->loggerMock,
  44. $this->fetchStrategyMock
  45. );
  46. }
  47. protected function tearDown()
  48. {
  49. unset($this->collection);
  50. }
  51. public function testSetAddOrder()
  52. {
  53. $adapter = $this->createPartialMock(\Magento\Framework\DB\Adapter\Pdo\Mysql::class, ['fetchAll', 'select']);
  54. $renderer = $this->getSelectRenderer($this->objectManager);
  55. $select = new \Magento\Framework\DB\Select($adapter, $renderer);
  56. $adapter
  57. ->expects($this->any())
  58. ->method('select')
  59. ->willReturn($select);
  60. $this->collection->setConnection($adapter);
  61. $select = $this->collection->getSelect();
  62. $this->assertEmpty($select->getPart(\Magento\Framework\DB\Select::ORDER));
  63. /* Direct access to select object is available and many places are using it for sort order declaration */
  64. $select->order('select_field', \Magento\Framework\Data\Collection::SORT_ORDER_ASC);
  65. $this->collection->addOrder('some_field', \Magento\Framework\Data\Collection::SORT_ORDER_ASC);
  66. $this->collection->setOrder('other_field', \Magento\Framework\Data\Collection::SORT_ORDER_ASC);
  67. $this->collection->addOrder('other_field', \Magento\Framework\Data\Collection::SORT_ORDER_DESC);
  68. $this->collection->load();
  69. $selectOrders = $select->getPart(\Magento\Framework\DB\Select::ORDER);
  70. $this->assertEquals(['select_field', 'ASC'], array_shift($selectOrders));
  71. $this->assertEquals('some_field ASC', (string)array_shift($selectOrders));
  72. $this->assertEquals('other_field DESC', (string)array_shift($selectOrders));
  73. $this->assertEmpty(array_shift($selectOrders));
  74. }
  75. public function testUnshiftOrder()
  76. {
  77. $adapter = $this->createPartialMock(\Magento\Framework\DB\Adapter\Pdo\Mysql::class, ['fetchAll', 'select']);
  78. $renderer = $this->getSelectRenderer($this->objectManager);
  79. $select = new \Magento\Framework\DB\Select($adapter, $renderer);
  80. $adapter
  81. ->expects($this->any())
  82. ->method('select')
  83. ->willReturn($select);
  84. $this->collection->setConnection($adapter);
  85. $this->collection->addOrder('some_field', \Magento\Framework\Data\Collection::SORT_ORDER_ASC);
  86. $this->collection->unshiftOrder('other_field', \Magento\Framework\Data\Collection::SORT_ORDER_ASC);
  87. $this->collection->load();
  88. $selectOrders = $this->collection->getSelect()->getPart(\Magento\Framework\DB\Select::ORDER);
  89. $this->assertEquals('other_field ASC', (string)array_shift($selectOrders));
  90. $this->assertEquals('some_field ASC', (string)array_shift($selectOrders));
  91. $this->assertEmpty(array_shift($selectOrders));
  92. }
  93. /**
  94. * Test that adding field to filter builds proper sql WHERE condition
  95. */
  96. public function testAddFieldToFilter()
  97. {
  98. $adapter =
  99. $this->createPartialMock(\Magento\Framework\DB\Adapter\Pdo\Mysql::class, ['prepareSqlCondition', 'select']);
  100. $adapter->expects(
  101. $this->any()
  102. )->method(
  103. 'prepareSqlCondition'
  104. )->with(
  105. $this->stringContains('is_imported'),
  106. $this->anything()
  107. )->will(
  108. $this->returnValue('is_imported = 1')
  109. );
  110. $renderer = $this->getSelectRenderer($this->objectManager);
  111. $select = new \Magento\Framework\DB\Select($adapter, $renderer);
  112. $adapter
  113. ->expects($this->any())
  114. ->method('select')
  115. ->willReturn($select);
  116. $this->collection->setConnection($adapter);
  117. $select = $this->collection->getSelect()->from('test');
  118. $this->collection->addFieldToFilter('is_imported', ['eq' => '1']);
  119. $this->assertEquals('SELECT `test`.* FROM `test` WHERE (is_imported = 1)', $select->assemble());
  120. }
  121. /**
  122. * Test that adding multiple fields to filter at once
  123. * builds proper sql WHERE condition and created conditions are joined with OR
  124. */
  125. public function testAddFieldToFilterWithMultipleParams()
  126. {
  127. $adapter =
  128. $this->createPartialMock(\Magento\Framework\DB\Adapter\Pdo\Mysql::class, ['prepareSqlCondition', 'select']);
  129. $adapter->expects(
  130. $this->exactly(3)
  131. )->method(
  132. 'prepareSqlCondition'
  133. )->withConsecutive(
  134. ["`weight`", ['in' => [1, 3]]],
  135. ['`name`', ['like' => 'M%']],
  136. ['`is_imported`', $this->anything()]
  137. )->willReturnOnConsecutiveCalls(
  138. 'weight in (1, 3)',
  139. "name like 'M%'",
  140. 'is_imported = 1'
  141. );
  142. $renderer = $this->getSelectRenderer($this->objectManager);
  143. $select = new \Magento\Framework\DB\Select($adapter, $renderer);
  144. $adapter
  145. ->expects($this->any())
  146. ->method('select')
  147. ->willReturn($select);
  148. $this->collection->setConnection($adapter);
  149. $select = $this->collection->getSelect()->from("test");
  150. $this->collection->addFieldToFilter(
  151. ['weight', 'name'],
  152. [['in' => [1, 3]], ['like' => 'M%']]
  153. );
  154. $this->assertEquals(
  155. "SELECT `test`.* FROM `test` WHERE ((weight in (1, 3)) OR (name like 'M%'))",
  156. $select->assemble()
  157. );
  158. $this->collection->addFieldToFilter('is_imported', ['eq' => '1']);
  159. $this->assertEquals(
  160. "SELECT `test`.* FROM `test` WHERE ((weight in (1, 3)) OR (name like 'M%')) AND (is_imported = 1)",
  161. $select->assemble()
  162. );
  163. }
  164. /**
  165. * Test that adding field to filter by value which contains question mark produce correct SQL
  166. */
  167. public function testAddFieldToFilterValueContainsQuestionMark()
  168. {
  169. $adapter = $this->createPartialMock(
  170. \Magento\Framework\DB\Adapter\Pdo\Mysql::class,
  171. ['select', 'prepareSqlCondition', 'supportStraightJoin']
  172. );
  173. $adapter->expects(
  174. $this->once()
  175. )->method(
  176. 'prepareSqlCondition'
  177. )->with(
  178. '`email`',
  179. ['like' => 'value?']
  180. )->will(
  181. $this->returnValue('email LIKE \'%value?%\'')
  182. );
  183. $renderer = $this->getSelectRenderer($this->objectManager);
  184. $select = new \Magento\Framework\DB\Select($adapter, $renderer);
  185. $adapter
  186. ->expects($this->any())
  187. ->method('select')
  188. ->willReturn($select);
  189. $this->collection->setConnection($adapter);
  190. $select = $this->collection->getSelect()->from('test');
  191. $this->collection->addFieldToFilter('email', ['like' => 'value?']);
  192. $this->assertEquals("SELECT `test`.* FROM `test` WHERE (email LIKE '%value?%')", $select->assemble());
  193. }
  194. /**
  195. * Test that field is quoted when added to SQL via addFieldToFilter()
  196. */
  197. public function testAddFieldToFilterFieldIsQuoted()
  198. {
  199. $adapter = $this->createPartialMock(
  200. \Magento\Framework\DB\Adapter\Pdo\Mysql::class,
  201. ['quoteIdentifier', 'prepareSqlCondition', 'select']
  202. );
  203. $adapter->expects(
  204. $this->once()
  205. )->method(
  206. 'quoteIdentifier'
  207. )->with(
  208. 'email'
  209. )->will(
  210. $this->returnValue('`email`')
  211. );
  212. $adapter->expects(
  213. $this->any()
  214. )->method(
  215. 'prepareSqlCondition'
  216. )->with(
  217. $this->stringContains('`email`'),
  218. $this->anything()
  219. )->will(
  220. $this->returnValue('`email` = "foo@example.com"')
  221. );
  222. $renderer = $this->getSelectRenderer($this->objectManager);
  223. $select = new \Magento\Framework\DB\Select($adapter, $renderer);
  224. $adapter
  225. ->expects($this->any())
  226. ->method('select')
  227. ->willReturn($select);
  228. $this->collection->setConnection($adapter);
  229. $select = $this->collection->getSelect()->from('test');
  230. $this->collection->addFieldToFilter('email', ['eq' => 'foo@example.com']);
  231. $this->assertEquals('SELECT `test`.* FROM `test` WHERE (`email` = "foo@example.com")', $select->assemble());
  232. }
  233. /**
  234. * Test that after cloning collection $this->_select in initial and cloned collections
  235. * do not reference the same object
  236. *
  237. * @covers \Magento\Framework\Data\Collection\AbstractDb::__clone
  238. */
  239. public function testClone()
  240. {
  241. $adapter = $this->createMock(\Magento\Framework\DB\Adapter\Pdo\Mysql::class);
  242. $adapter
  243. ->expects($this->any())
  244. ->method('select')
  245. ->willReturn($this->createMock(\Magento\Framework\DB\Select::class));
  246. $this->collection->setConnection($adapter);
  247. $this->assertInstanceOf(\Magento\Framework\DB\Select::class, $this->collection->getSelect());
  248. $clonedCollection = clone $this->collection;
  249. $this->assertInstanceOf(\Magento\Framework\DB\Select::class, $clonedCollection->getSelect());
  250. $this->assertNotSame(
  251. $clonedCollection->getSelect(),
  252. $this->collection->getSelect(),
  253. 'Collection was cloned but $this->_select in both initial and cloned collections reference the same object'
  254. );
  255. }
  256. /**
  257. * @param bool $printQuery
  258. * @param bool $printFlag
  259. * @param string $query
  260. * @param string $expected
  261. *
  262. * @dataProvider printLogQueryPrintingDataProvider
  263. */
  264. public function testPrintLogQueryPrinting($printQuery, $printFlag, $query, $expected)
  265. {
  266. $this->expectOutputString($expected);
  267. $this->collection->setFlag('print_query', $printFlag);
  268. $this->collection->printLogQuery($printQuery, false, $query);
  269. }
  270. /**
  271. * @return array
  272. */
  273. public function printLogQueryPrintingDataProvider()
  274. {
  275. return [
  276. [false, false, 'some_query', ''],
  277. [true, false, 'some_query', 'some_query'],
  278. [false, true, 'some_query', 'some_query']
  279. ];
  280. }
  281. /**
  282. * @param bool $logQuery
  283. * @param bool $logFlag
  284. * @param int $expectedCalls
  285. *
  286. * @dataProvider printLogQueryLoggingDataProvider
  287. */
  288. public function testPrintLogQueryLogging($logQuery, $logFlag, $expectedCalls)
  289. {
  290. $this->collection->setFlag('log_query', $logFlag);
  291. $this->loggerMock->expects($this->exactly($expectedCalls))->method('info');
  292. $this->collection->printLogQuery(false, $logQuery, 'some_query');
  293. }
  294. /**
  295. * @return array
  296. */
  297. public function printLogQueryLoggingDataProvider()
  298. {
  299. return [
  300. [true, false, 1],
  301. [false, true, 1],
  302. [false, false, 0],
  303. ];
  304. }
  305. public function testFetchItem()
  306. {
  307. $data = [1 => 'test'];
  308. $counter = 0;
  309. $statementMock = $this->createPartialMock(\Zend_Db_Statement_Pdo::class, ['fetch']);
  310. $statementMock->expects($this->exactly(2))
  311. ->method('fetch')
  312. ->will($this->returnCallback(function () use (&$counter, $data) {
  313. return ++$counter % 2 ? [] : $data;
  314. }));
  315. $adapterMock = $this->createPartialMock(\Magento\Framework\DB\Adapter\Pdo\Mysql::class, ['select', 'query']);
  316. $selectMock = $this->getMockBuilder(\Magento\Framework\DB\Select::class)
  317. ->setConstructorArgs(
  318. [
  319. 'adapter' => $adapterMock,
  320. 'selectRenderer' => $this->getSelectRenderer($this->objectManager)
  321. ]
  322. )
  323. ->getMock();
  324. $adapterMock->expects($this->once())
  325. ->method('query')
  326. ->with($selectMock, $this->anything())
  327. ->will($this->returnValue($statementMock));
  328. $adapterMock->expects($this->once())
  329. ->method('select')
  330. ->will($this->returnValue($selectMock));
  331. $this->collection->setConnection($adapterMock);
  332. $this->assertFalse($this->collection->fetchItem());
  333. $objectMock = $this->createPartialMock(\Magento\Framework\DataObject::class, ['setData']);
  334. $objectMock->expects($this->once())
  335. ->method('setData')
  336. ->with($data);
  337. $this->entityFactoryMock->expects($this->once())
  338. ->method('create')
  339. ->with(\Magento\Framework\DataObject::class)
  340. ->will($this->returnValue($objectMock));
  341. $this->assertEquals($objectMock, $this->collection->fetchItem());
  342. }
  343. public function testGetSize()
  344. {
  345. $countSql = 500;
  346. $adapterMock = $this->createPartialMock(
  347. \Magento\Framework\DB\Adapter\Pdo\Mysql::class,
  348. ['select', 'quoteInto', 'prepareSqlCondition', 'fetchOne']
  349. );
  350. $selectMock = $this->getMockBuilder(\Magento\Framework\DB\Select::class)
  351. ->setMethods(['orWhere', 'where', 'reset', 'columns'])
  352. ->setConstructorArgs(
  353. [
  354. 'adapter' => $adapterMock,
  355. 'selectRenderer' => $this->getSelectRenderer($this->objectManager)
  356. ]
  357. )
  358. ->getMock();
  359. $selectMock->expects($this->exactly(4))
  360. ->method('reset');
  361. $selectMock->expects($this->once())
  362. ->method('columns')
  363. ->with('COUNT(*)');
  364. $adapterMock->expects($this->once())
  365. ->method('select')
  366. ->will($this->returnValue($selectMock));
  367. $adapterMock->expects($this->exactly(2))
  368. ->method('quoteInto')
  369. ->will($this->returnValueMap([
  370. ['testField1=?', 'testValue1', null, null, 'testField1=testValue1'],
  371. ['testField4=?', 'testValue4', null, null, 'testField4=testValue4'],
  372. ]));
  373. $selectMock->expects($this->once())
  374. ->method('orWhere')
  375. ->with('testField1=testValue1');
  376. $selectMock->expects($this->exactly(3))
  377. ->method('where')
  378. ->will($this->returnValueMap([
  379. ['testValue2', $this->returnSelf()],
  380. [
  381. 'testField3 = testValue3',
  382. null,
  383. \Magento\Framework\DB\Select::TYPE_CONDITION,
  384. $this->returnSelf()
  385. ],
  386. ['testField4=testValue4', $this->returnSelf()],
  387. ]));
  388. $adapterMock->expects($this->once())
  389. ->method('prepareSqlCondition')
  390. ->with('testField3', 'testValue3')
  391. ->will($this->returnValue('testField3 = testValue3'));
  392. $adapterMock->expects($this->once())
  393. ->method('fetchOne')
  394. ->with($selectMock, [])
  395. ->will($this->returnValue($countSql));
  396. $this->collection->addFilter('testField1', 'testValue1', 'or');
  397. $this->collection->addFilter('testField2', 'testValue2', 'string');
  398. $this->collection->addFilter('testField3', 'testValue3', 'public');
  399. $this->collection->addFilter('testField4', 'testValue4');
  400. $this->collection->setConnection($adapterMock);
  401. $this->assertEquals($countSql, $this->collection->getSize());
  402. $this->assertEquals($countSql, $this->collection->getSize());
  403. }
  404. public function testGetSelectSql()
  405. {
  406. $adapterMock = $this->createPartialMock(\Magento\Framework\DB\Adapter\Pdo\Mysql::class, ['select']);
  407. $selectMock = $this->getMockBuilder(\Magento\Framework\DB\Select::class)
  408. ->setMethods(['__toString'])
  409. ->setConstructorArgs(
  410. [
  411. 'adapter' => $adapterMock,
  412. 'selectRenderer' => $this->getSelectRenderer($this->objectManager)
  413. ]
  414. )
  415. ->getMock();
  416. $adapterMock->expects($this->once())
  417. ->method('select')
  418. ->will($this->returnValue($selectMock));
  419. $sql = 'query';
  420. $selectMock->expects($this->once())
  421. ->method('__toString')
  422. ->will($this->returnValue($sql));
  423. $this->collection->setConnection($adapterMock);
  424. $this->assertEquals($sql, $this->collection->getSelectSql(true));
  425. $this->assertEquals($selectMock, $this->collection->getSelectSql());
  426. }
  427. public function testGetData()
  428. {
  429. $adapterMock = $this->createPartialMock(
  430. \Magento\Framework\DB\Adapter\Pdo\Mysql::class,
  431. ['select', 'quoteInto', 'prepareSqlCondition', 'fetchOne']
  432. );
  433. $selectMock = $this->getMockBuilder(\Magento\Framework\DB\Select::class)
  434. ->setMethods(['orWhere', 'where', 'reset', 'columns'])
  435. ->setConstructorArgs(
  436. [
  437. 'adapter' => $adapterMock,
  438. 'selectRenderer' => $this->getSelectRenderer($this->objectManager)
  439. ]
  440. )
  441. ->getMock();
  442. $selectMock->expects($this->once())
  443. ->method('where')
  444. ->with('aliasField3 = testValue3', null, \Magento\Framework\DB\Select::TYPE_CONDITION)
  445. ->will($this->returnSelf());
  446. $adapterMock->expects($this->once())
  447. ->method('select')
  448. ->will($this->returnValue($selectMock));
  449. $adapterMock->expects($this->once())
  450. ->method('prepareSqlCondition')
  451. ->with('aliasField3', 'testValue3')
  452. ->will($this->returnValue('aliasField3 = testValue3'));
  453. $this->collection->addFilter('testField3', 'testValue3', 'public');
  454. $this->collection->addFilterToMap('testFilter', 'testAlias', 'testGroup');
  455. $this->collection->addFilterToMap('testField3', 'aliasField3');
  456. $this->collection->setConnection($adapterMock);
  457. $this->assertNull($this->collection->getData());
  458. }
  459. /**
  460. * @dataProvider distinctDataProvider
  461. */
  462. public function testDistinct($flag, $expectedFlag)
  463. {
  464. $adapterMock = $this->createPartialMock(\Magento\Framework\DB\Adapter\Pdo\Mysql::class, ['select']);
  465. $selectMock = $this->getMockBuilder(\Magento\Framework\DB\Select::class)
  466. ->setMethods(['distinct'])
  467. ->setConstructorArgs(
  468. [
  469. 'adapter' => $adapterMock,
  470. 'selectRenderer' => $this->getSelectRenderer($this->objectManager)
  471. ]
  472. )
  473. ->getMock();
  474. $adapterMock->expects($this->once())
  475. ->method('select')
  476. ->will($this->returnValue($selectMock));
  477. $selectMock->expects($this->once())
  478. ->method('distinct')
  479. ->with($expectedFlag);
  480. $this->collection->setConnection($adapterMock);
  481. $this->collection->distinct($flag);
  482. }
  483. /**
  484. * @return array
  485. */
  486. public function distinctDataProvider()
  487. {
  488. return [
  489. [true, true],
  490. [false, false],
  491. ];
  492. }
  493. public function testToOptionHash()
  494. {
  495. $data = [10 => 'test'];
  496. $adapterMock = $this->createPartialMock(\Magento\Framework\DB\Adapter\Pdo\Mysql::class, ['select', 'query']);
  497. $selectMock = $this->getMockBuilder(\Magento\Framework\DB\Select::class)
  498. ->setConstructorArgs(
  499. [
  500. 'adapter' => $adapterMock,
  501. 'selectRenderer' => $this->getSelectRenderer($this->objectManager)
  502. ]
  503. )
  504. ->getMock();
  505. $adapterMock->expects($this->once())
  506. ->method('select')
  507. ->will($this->returnValue($selectMock));
  508. $this->fetchStrategyMock->expects($this->once())
  509. ->method('fetchAll')
  510. ->with($selectMock, [])
  511. ->will($this->returnValue([$data]));
  512. $objectMock = $this->createPartialMock(
  513. \Magento\Framework\DataObject::class,
  514. ['addData', 'setIdFieldName', 'getData']
  515. );
  516. $objectMock->expects($this->once())
  517. ->method('addData')
  518. ->with($data);
  519. $objectMock->expects($this->any())
  520. ->method('getData')
  521. ->will($this->returnValueMap([
  522. [null, null, 10],
  523. ['name', null, 'test'],
  524. ]));
  525. $this->entityFactoryMock->expects($this->once())
  526. ->method('create')
  527. ->with(\Magento\Framework\DataObject::class)
  528. ->will($this->returnValue($objectMock));
  529. $this->collection->setConnection($adapterMock);
  530. $this->collection->loadData(false, false);
  531. $this->collection->loadData(false, false);
  532. $this->assertEquals($data, $this->collection->toOptionHash());
  533. }
  534. }