PaginatorTest.php 47 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  5. * Copyright (c) Cake Software Foundation, Inc. (http://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. (http://cakefoundation.org)
  12. * @link http://cakephp.org CakePHP(tm) Project
  13. * @since 3.5.0
  14. * @license http://www.opensource.org/licenses/mit-license.php MIT License
  15. */
  16. namespace Cake\Test\TestCase\Datasource;
  17. use Cake\Core\Configure;
  18. use Cake\Datasource\ConnectionManager;
  19. use Cake\Datasource\EntityInterface;
  20. use Cake\Datasource\Exception\PageOutOfBoundsException;
  21. use Cake\Datasource\Paginator;
  22. use Cake\Datasource\RepositoryInterface;
  23. use Cake\ORM\Entity;
  24. use Cake\TestSuite\TestCase;
  25. class PaginatorTest extends TestCase
  26. {
  27. /**
  28. * fixtures property
  29. *
  30. * @var array
  31. */
  32. public $fixtures = [
  33. 'core.Posts', 'core.Articles', 'core.ArticlesTags',
  34. 'core.Authors', 'core.AuthorsTags', 'core.Tags',
  35. ];
  36. /**
  37. * Don't load data for fixtures for all tests
  38. *
  39. * @var bool
  40. */
  41. public $autoFixtures = false;
  42. /**
  43. * @var \Cake\Datasource\Paginator
  44. */
  45. public $Paginator;
  46. /**
  47. * setup
  48. *
  49. * @return void
  50. */
  51. public function setUp()
  52. {
  53. parent::setUp();
  54. Configure::write('App.namespace', 'TestApp');
  55. $this->Paginator = new Paginator();
  56. $this->Post = $this->getMockRepository();
  57. }
  58. /**
  59. * tearDown
  60. *
  61. * @return void
  62. */
  63. public function tearDown()
  64. {
  65. parent::tearDown();
  66. $this->getTableLocator()->clear();
  67. }
  68. /**
  69. * Test that non-numeric values are rejected for page, and limit
  70. *
  71. * @return void
  72. */
  73. public function testPageParamCasting()
  74. {
  75. $this->Post->expects($this->any())
  76. ->method('getAlias')
  77. ->will($this->returnValue('Posts'));
  78. $query = $this->_getMockFindQuery();
  79. $this->Post->expects($this->any())
  80. ->method('find')
  81. ->will($this->returnValue($query));
  82. $params = ['page' => '1 " onclick="alert(\'xss\');">'];
  83. $settings = ['limit' => 1, 'maxLimit' => 10];
  84. $this->Paginator->paginate($this->Post, $params, $settings);
  85. $pagingParams = $this->Paginator->getPagingParams();
  86. $this->assertSame(1, $pagingParams['Posts']['page'], 'XSS exploit opened');
  87. }
  88. /**
  89. * test that unknown keys in the default settings are
  90. * passed to the find operations.
  91. *
  92. * @return void
  93. */
  94. public function testPaginateExtraParams()
  95. {
  96. $params = ['page' => '-1'];
  97. $settings = [
  98. 'PaginatorPosts' => [
  99. 'contain' => ['PaginatorAuthor'],
  100. 'maxLimit' => 10,
  101. 'group' => 'PaginatorPosts.published',
  102. 'order' => ['PaginatorPosts.id' => 'ASC'],
  103. ],
  104. ];
  105. $table = $this->_getMockPosts(['query']);
  106. $query = $this->_getMockFindQuery();
  107. $table->expects($this->once())
  108. ->method('query')
  109. ->will($this->returnValue($query));
  110. $query->expects($this->once())
  111. ->method('applyOptions')
  112. ->with([
  113. 'contain' => ['PaginatorAuthor'],
  114. 'group' => 'PaginatorPosts.published',
  115. 'limit' => 10,
  116. 'order' => ['PaginatorPosts.id' => 'ASC'],
  117. 'page' => 1,
  118. 'whitelist' => ['limit', 'sort', 'page', 'direction'],
  119. 'scope' => null,
  120. 'sort' => 'PaginatorPosts.id',
  121. ]);
  122. $this->Paginator->paginate($table, $params, $settings);
  123. }
  124. /**
  125. * Test to make sure options get sent to custom finder methods via paginate
  126. *
  127. * @return void
  128. */
  129. public function testPaginateCustomFinderOptions()
  130. {
  131. $this->loadFixtures('Posts');
  132. $settings = [
  133. 'PaginatorPosts' => [
  134. 'finder' => ['author' => ['author_id' => 1]],
  135. ],
  136. ];
  137. $table = $this->getTableLocator()->get('PaginatorPosts');
  138. $expected = $table
  139. ->find('author', [
  140. 'conditions' => [
  141. 'PaginatorPosts.author_id' => 1,
  142. ],
  143. ])
  144. ->count();
  145. $result = $this->Paginator->paginate($table, [], $settings)->count();
  146. $this->assertEquals($expected, $result);
  147. }
  148. /**
  149. * Test that special paginate types are called and that the type param doesn't leak out into defaults or options.
  150. *
  151. * @return void
  152. */
  153. public function testPaginateCustomFinder()
  154. {
  155. $settings = [
  156. 'PaginatorPosts' => [
  157. 'finder' => 'popular',
  158. 'fields' => ['id', 'title'],
  159. 'maxLimit' => 10,
  160. ],
  161. ];
  162. $table = $this->_getMockPosts(['findPopular']);
  163. $query = $this->_getMockFindQuery();
  164. $table->expects($this->any())
  165. ->method('findPopular')
  166. ->will($this->returnValue($query));
  167. $this->Paginator->paginate($table, [], $settings);
  168. $pagingParams = $this->Paginator->getPagingParams();
  169. $this->assertSame('popular', $pagingParams['PaginatorPosts']['finder']);
  170. $this->assertSame(1, $pagingParams['PaginatorPosts']['start']);
  171. $this->assertSame(2, $pagingParams['PaginatorPosts']['end']);
  172. }
  173. /**
  174. * Test that nested eager loaders don't trigger invalid SQL errors.
  175. *
  176. * @return void
  177. */
  178. public function testPaginateNestedEagerLoader()
  179. {
  180. $this->loadFixtures('Articles', 'Tags', 'Authors', 'ArticlesTags', 'AuthorsTags');
  181. $articles = $this->getTableLocator()->get('Articles');
  182. $articles->belongsToMany('Tags');
  183. $tags = $this->getTableLocator()->get('Tags');
  184. $tags->belongsToMany('Authors');
  185. $articles->getEventManager()->on('Model.beforeFind', function ($event, $query) {
  186. $query ->matching('Tags', function ($q) {
  187. return $q->matching('Authors', function ($q) {
  188. return $q->where(['Authors.name' => 'larry']);
  189. });
  190. });
  191. });
  192. $results = $this->Paginator->paginate($articles);
  193. $result = $results->first();
  194. $this->assertInstanceOf(EntityInterface::class, $result);
  195. $this->assertInstanceOf(EntityInterface::class, $result->_matchingData['Tags']);
  196. $this->assertInstanceOf(EntityInterface::class, $result->_matchingData['Authors']);
  197. }
  198. /**
  199. * test that flat default pagination parameters work.
  200. *
  201. * @return void
  202. */
  203. public function testDefaultPaginateParams()
  204. {
  205. $settings = [
  206. 'order' => ['PaginatorPosts.id' => 'DESC'],
  207. 'maxLimit' => 10,
  208. ];
  209. $table = $this->_getMockPosts(['query']);
  210. $query = $this->_getMockFindQuery();
  211. $table->expects($this->once())
  212. ->method('query')
  213. ->will($this->returnValue($query));
  214. $query->expects($this->once())
  215. ->method('applyOptions')
  216. ->with([
  217. 'limit' => 10,
  218. 'page' => 1,
  219. 'order' => ['PaginatorPosts.id' => 'DESC'],
  220. 'whitelist' => ['limit', 'sort', 'page', 'direction'],
  221. 'scope' => null,
  222. 'sort' => 'PaginatorPosts.id',
  223. ]);
  224. $this->Paginator->paginate($table, [], $settings);
  225. }
  226. /**
  227. * Tests that flat default pagination parameters work for multi order.
  228. *
  229. * @return void
  230. */
  231. public function testDefaultPaginateParamsMultiOrder()
  232. {
  233. $settings = [
  234. 'order' => ['PaginatorPosts.id' => 'DESC', 'PaginatorPosts.title' => 'ASC'],
  235. ];
  236. $table = $this->_getMockPosts(['query']);
  237. $query = $this->_getMockFindQuery();
  238. $table->expects($this->once())
  239. ->method('query')
  240. ->will($this->returnValue($query));
  241. $query->expects($this->once())
  242. ->method('applyOptions')
  243. ->with([
  244. 'limit' => 20,
  245. 'page' => 1,
  246. 'order' => $settings['order'],
  247. 'whitelist' => ['limit', 'sort', 'page', 'direction'],
  248. 'scope' => null,
  249. 'sort' => null,
  250. ]);
  251. $this->Paginator->paginate($table, [], $settings);
  252. $pagingParams = $this->Paginator->getPagingParams();
  253. $this->assertNull($pagingParams['PaginatorPosts']['direction']);
  254. $this->assertFalse($pagingParams['PaginatorPosts']['sortDefault']);
  255. $this->assertFalse($pagingParams['PaginatorPosts']['directionDefault']);
  256. }
  257. /**
  258. * test that default sort and default direction are injected into request
  259. *
  260. * @return void
  261. */
  262. public function testDefaultPaginateParamsIntoRequest()
  263. {
  264. $settings = [
  265. 'order' => ['PaginatorPosts.id' => 'DESC'],
  266. 'maxLimit' => 10,
  267. ];
  268. $table = $this->_getMockPosts(['query']);
  269. $query = $this->_getMockFindQuery();
  270. $table->expects($this->once())
  271. ->method('query')
  272. ->will($this->returnValue($query));
  273. $query->expects($this->once())
  274. ->method('applyOptions')
  275. ->with([
  276. 'limit' => 10,
  277. 'page' => 1,
  278. 'order' => ['PaginatorPosts.id' => 'DESC'],
  279. 'whitelist' => ['limit', 'sort', 'page', 'direction'],
  280. 'scope' => null,
  281. 'sort' => 'PaginatorPosts.id',
  282. ]);
  283. $this->Paginator->paginate($table, [], $settings);
  284. $pagingParams = $this->Paginator->getPagingParams();
  285. $this->assertEquals('PaginatorPosts.id', $pagingParams['PaginatorPosts']['sortDefault']);
  286. $this->assertEquals('DESC', $pagingParams['PaginatorPosts']['directionDefault']);
  287. }
  288. /**
  289. * test that option merging prefers specific models
  290. *
  291. * @return void
  292. */
  293. public function testMergeOptionsModelSpecific()
  294. {
  295. $settings = [
  296. 'page' => 1,
  297. 'limit' => 20,
  298. 'maxLimit' => 100,
  299. 'Posts' => [
  300. 'page' => 1,
  301. 'limit' => 10,
  302. 'maxLimit' => 50,
  303. ],
  304. 'whitelist' => ['limit', 'sort', 'page', 'direction'],
  305. ];
  306. $defaults = $this->Paginator->getDefaults('Silly', $settings);
  307. $result = $this->Paginator->mergeOptions([], $defaults);
  308. $this->assertEquals($settings, $result);
  309. $defaults = $this->Paginator->getDefaults('Posts', $settings);
  310. $result = $this->Paginator->mergeOptions([], $defaults);
  311. $expected = ['page' => 1, 'limit' => 10, 'maxLimit' => 50, 'whitelist' => ['limit', 'sort', 'page', 'direction']];
  312. $this->assertEquals($expected, $result);
  313. }
  314. /**
  315. * test mergeOptions with custom scope
  316. *
  317. * @return void
  318. */
  319. public function testMergeOptionsCustomScope()
  320. {
  321. $params = [
  322. 'page' => 10,
  323. 'limit' => 10,
  324. 'scope' => [
  325. 'page' => 2,
  326. 'limit' => 5,
  327. ],
  328. ];
  329. $settings = [
  330. 'page' => 1,
  331. 'limit' => 20,
  332. 'maxLimit' => 100,
  333. 'finder' => 'myCustomFind',
  334. ];
  335. $defaults = $this->Paginator->getDefaults('Post', $settings);
  336. $result = $this->Paginator->mergeOptions($params, $defaults);
  337. $expected = [
  338. 'page' => 10,
  339. 'limit' => 10,
  340. 'maxLimit' => 100,
  341. 'finder' => 'myCustomFind',
  342. 'whitelist' => ['limit', 'sort', 'page', 'direction'],
  343. ];
  344. $this->assertEquals($expected, $result);
  345. $settings = [
  346. 'page' => 1,
  347. 'limit' => 20,
  348. 'maxLimit' => 100,
  349. 'finder' => 'myCustomFind',
  350. 'scope' => 'non-existent',
  351. ];
  352. $defaults = $this->Paginator->getDefaults('Post', $settings);
  353. $result = $this->Paginator->mergeOptions($params, $defaults);
  354. $expected = [
  355. 'page' => 1,
  356. 'limit' => 20,
  357. 'maxLimit' => 100,
  358. 'finder' => 'myCustomFind',
  359. 'whitelist' => ['limit', 'sort', 'page', 'direction'],
  360. 'scope' => 'non-existent',
  361. ];
  362. $this->assertEquals($expected, $result);
  363. $settings = [
  364. 'page' => 1,
  365. 'limit' => 20,
  366. 'maxLimit' => 100,
  367. 'finder' => 'myCustomFind',
  368. 'scope' => 'scope',
  369. ];
  370. $defaults = $this->Paginator->getDefaults('Post', $settings);
  371. $result = $this->Paginator->mergeOptions($params, $defaults);
  372. $expected = [
  373. 'page' => 2,
  374. 'limit' => 5,
  375. 'maxLimit' => 100,
  376. 'finder' => 'myCustomFind',
  377. 'whitelist' => ['limit', 'sort', 'page', 'direction'],
  378. 'scope' => 'scope',
  379. ];
  380. $this->assertEquals($expected, $result);
  381. }
  382. /**
  383. * test mergeOptions with customFind key
  384. *
  385. * @return void
  386. */
  387. public function testMergeOptionsCustomFindKey()
  388. {
  389. $params = [
  390. 'page' => 10,
  391. 'limit' => 10,
  392. ];
  393. $settings = [
  394. 'page' => 1,
  395. 'limit' => 20,
  396. 'maxLimit' => 100,
  397. 'finder' => 'myCustomFind',
  398. ];
  399. $defaults = $this->Paginator->getDefaults('Post', $settings);
  400. $result = $this->Paginator->mergeOptions($params, $defaults);
  401. $expected = [
  402. 'page' => 10,
  403. 'limit' => 10,
  404. 'maxLimit' => 100,
  405. 'finder' => 'myCustomFind',
  406. 'whitelist' => ['limit', 'sort', 'page', 'direction'],
  407. ];
  408. $this->assertEquals($expected, $result);
  409. }
  410. /**
  411. * test merging options from the querystring.
  412. *
  413. * @return void
  414. */
  415. public function testMergeOptionsQueryString()
  416. {
  417. $params = [
  418. 'page' => 99,
  419. 'limit' => 75,
  420. ];
  421. $settings = [
  422. 'page' => 1,
  423. 'limit' => 20,
  424. 'maxLimit' => 100,
  425. ];
  426. $defaults = $this->Paginator->getDefaults('Post', $settings);
  427. $result = $this->Paginator->mergeOptions($params, $defaults);
  428. $expected = ['page' => 99, 'limit' => 75, 'maxLimit' => 100, 'whitelist' => ['limit', 'sort', 'page', 'direction']];
  429. $this->assertEquals($expected, $result);
  430. }
  431. /**
  432. * test that the default whitelist doesn't let people screw with things they should not be allowed to.
  433. *
  434. * @return void
  435. */
  436. public function testMergeOptionsDefaultWhiteList()
  437. {
  438. $params = [
  439. 'page' => 10,
  440. 'limit' => 10,
  441. 'fields' => ['bad.stuff'],
  442. 'recursive' => 1000,
  443. 'conditions' => ['bad.stuff'],
  444. 'contain' => ['bad'],
  445. ];
  446. $settings = [
  447. 'page' => 1,
  448. 'limit' => 20,
  449. 'maxLimit' => 100,
  450. ];
  451. $defaults = $this->Paginator->getDefaults('Post', $settings);
  452. $result = $this->Paginator->mergeOptions($params, $defaults);
  453. $expected = ['page' => 10, 'limit' => 10, 'maxLimit' => 100, 'whitelist' => ['limit', 'sort', 'page', 'direction']];
  454. $this->assertEquals($expected, $result);
  455. }
  456. /**
  457. * test that modifying the whitelist works.
  458. *
  459. * @return void
  460. */
  461. public function testMergeOptionsExtraWhitelist()
  462. {
  463. $params = [
  464. 'page' => 10,
  465. 'limit' => 10,
  466. 'fields' => ['bad.stuff'],
  467. 'recursive' => 1000,
  468. 'conditions' => ['bad.stuff'],
  469. 'contain' => ['bad'],
  470. ];
  471. $settings = [
  472. 'page' => 1,
  473. 'limit' => 20,
  474. 'maxLimit' => 100,
  475. ];
  476. $this->Paginator->setConfig('whitelist', ['fields']);
  477. $defaults = $this->Paginator->getDefaults('Post', $settings);
  478. $result = $this->Paginator->mergeOptions($params, $defaults);
  479. $expected = [
  480. 'page' => 10, 'limit' => 10, 'maxLimit' => 100, 'fields' => ['bad.stuff'], 'whitelist' => ['limit', 'sort', 'page', 'direction', 'fields'],
  481. ];
  482. $this->assertEquals($expected, $result);
  483. }
  484. /**
  485. * test mergeOptions with limit > maxLimit in code.
  486. *
  487. * @return void
  488. */
  489. public function testMergeOptionsMaxLimit()
  490. {
  491. $settings = [
  492. 'limit' => 200,
  493. 'paramType' => 'named',
  494. ];
  495. $defaults = $this->Paginator->getDefaults('Post', $settings);
  496. $result = $this->Paginator->mergeOptions([], $defaults);
  497. $expected = [
  498. 'page' => 1,
  499. 'limit' => 100,
  500. 'maxLimit' => 100,
  501. 'paramType' => 'named',
  502. 'whitelist' => ['limit', 'sort', 'page', 'direction'],
  503. ];
  504. $this->assertEquals($expected, $result);
  505. $settings = [
  506. 'maxLimit' => 10,
  507. 'paramType' => 'named',
  508. ];
  509. $defaults = $this->Paginator->getDefaults('Post', $settings);
  510. $result = $this->Paginator->mergeOptions([], $defaults);
  511. $expected = [
  512. 'page' => 1,
  513. 'limit' => 10,
  514. 'maxLimit' => 10,
  515. 'paramType' => 'named',
  516. 'whitelist' => ['limit', 'sort', 'page', 'direction'],
  517. ];
  518. $this->assertEquals($expected, $result);
  519. }
  520. /**
  521. * test getDefaults with limit > maxLimit in code.
  522. *
  523. * @return void
  524. */
  525. public function testGetDefaultMaxLimit()
  526. {
  527. $settings = [
  528. 'page' => 1,
  529. 'limit' => 2,
  530. 'maxLimit' => 10,
  531. 'order' => [
  532. 'Users.username' => 'asc',
  533. ],
  534. ];
  535. $defaults = $this->Paginator->getDefaults('Post', $settings);
  536. $result = $this->Paginator->mergeOptions([], $defaults);
  537. $expected = [
  538. 'page' => 1,
  539. 'limit' => 2,
  540. 'maxLimit' => 10,
  541. 'order' => [
  542. 'Users.username' => 'asc',
  543. ],
  544. 'whitelist' => ['limit', 'sort', 'page', 'direction'],
  545. ];
  546. $this->assertEquals($expected, $result);
  547. $settings = [
  548. 'page' => 1,
  549. 'limit' => 100,
  550. 'maxLimit' => 10,
  551. 'order' => [
  552. 'Users.username' => 'asc',
  553. ],
  554. ];
  555. $defaults = $this->Paginator->getDefaults('Post', $settings);
  556. $result = $this->Paginator->mergeOptions([], $defaults);
  557. $expected = [
  558. 'page' => 1,
  559. 'limit' => 10,
  560. 'maxLimit' => 10,
  561. 'order' => [
  562. 'Users.username' => 'asc',
  563. ],
  564. 'whitelist' => ['limit', 'sort', 'page', 'direction'],
  565. ];
  566. $this->assertEquals($expected, $result);
  567. }
  568. /**
  569. * Integration test to ensure that validateSort is being used by paginate()
  570. *
  571. * @return void
  572. */
  573. public function testValidateSortInvalid()
  574. {
  575. $table = $this->_getMockPosts(['query']);
  576. $query = $this->_getMockFindQuery();
  577. $table->expects($this->once())
  578. ->method('query')
  579. ->will($this->returnValue($query));
  580. $query->expects($this->once())->method('applyOptions')
  581. ->with([
  582. 'limit' => 20,
  583. 'page' => 1,
  584. 'order' => ['PaginatorPosts.id' => 'asc'],
  585. 'whitelist' => ['limit', 'sort', 'page', 'direction'],
  586. 'scope' => null,
  587. 'sort' => 'id',
  588. ]);
  589. $params = [
  590. 'page' => 1,
  591. 'sort' => 'id',
  592. 'direction' => 'herp',
  593. ];
  594. $this->Paginator->paginate($table, $params);
  595. $pagingParams = $this->Paginator->getPagingParams();
  596. $this->assertEquals('id', $pagingParams['PaginatorPosts']['sort']);
  597. $this->assertEquals('asc', $pagingParams['PaginatorPosts']['direction']);
  598. }
  599. /**
  600. * test that invalid directions are ignored.
  601. *
  602. * @return void
  603. */
  604. public function testValidateSortInvalidDirection()
  605. {
  606. $model = $this->getMockRepository();
  607. $model->expects($this->any())
  608. ->method('getAlias')
  609. ->will($this->returnValue('model'));
  610. $model->expects($this->any())
  611. ->method('hasField')
  612. ->will($this->returnValue(true));
  613. $options = ['sort' => 'something', 'direction' => 'boogers'];
  614. $result = $this->Paginator->validateSort($model, $options);
  615. $this->assertEquals('asc', $result['order']['model.something']);
  616. }
  617. /**
  618. * Test that "sort" and "direction" in paging params is properly set based
  619. * on initial value of "order" in paging settings.
  620. *
  621. * @return void
  622. */
  623. public function testValidaSortInitialSortAndDirection()
  624. {
  625. $table = $this->_getMockPosts(['query']);
  626. $query = $this->_getMockFindQuery();
  627. $table->expects($this->once())
  628. ->method('query')
  629. ->will($this->returnValue($query));
  630. $query->expects($this->once())->method('applyOptions')
  631. ->with([
  632. 'limit' => 20,
  633. 'page' => 1,
  634. 'order' => ['PaginatorPosts.id' => 'asc'],
  635. 'whitelist' => ['limit', 'sort', 'page', 'direction'],
  636. 'sort' => 'id',
  637. 'scope' => null,
  638. 'sortWhitelist' => ['id'],
  639. ]);
  640. $options = [
  641. 'order' => [
  642. 'id' => 'asc',
  643. ],
  644. 'sortWhitelist' => ['id'],
  645. ];
  646. $this->Paginator->paginate($table, [], $options);
  647. $pagingParams = $this->Paginator->getPagingParams();
  648. $this->assertEquals('id', $pagingParams['PaginatorPosts']['sort']);
  649. $this->assertEquals('asc', $pagingParams['PaginatorPosts']['direction']);
  650. }
  651. /**
  652. * Test that "sort" and "direction" in paging params is properly set based
  653. * on initial value of "order" in paging settings.
  654. *
  655. * @return void
  656. */
  657. public function testValidateSortAndDirectionAliased()
  658. {
  659. $table = $this->_getMockPosts(['query']);
  660. $query = $this->_getMockFindQuery();
  661. $table->expects($this->once())
  662. ->method('query')
  663. ->will($this->returnValue($query));
  664. $query->expects($this->once())->method('applyOptions')
  665. ->with([
  666. 'limit' => 20,
  667. 'page' => 1,
  668. 'order' => ['PaginatorPosts.title' => 'asc'],
  669. 'whitelist' => ['limit', 'sort', 'page', 'direction'],
  670. 'sort' => 'title',
  671. 'scope' => null,
  672. ]);
  673. $options = [
  674. 'order' => [
  675. 'Articles.title' => 'desc',
  676. ],
  677. ];
  678. $queryParams = [
  679. 'page' => 1,
  680. 'sort' => 'title',
  681. 'direction' => 'asc',
  682. ];
  683. $this->Paginator->paginate($table, $queryParams, $options);
  684. $pagingParams = $this->Paginator->getPagingParams();
  685. $this->assertEquals('title', $pagingParams['PaginatorPosts']['sort']);
  686. $this->assertEquals('asc', $pagingParams['PaginatorPosts']['direction']);
  687. $this->assertEquals('Articles.title', $pagingParams['PaginatorPosts']['sortDefault']);
  688. $this->assertEquals('desc', $pagingParams['PaginatorPosts']['directionDefault']);
  689. }
  690. /**
  691. * testValidateSortRetainsOriginalSortValue
  692. *
  693. * @return void
  694. * @see https://github.com/cakephp/cakephp/issues/11740
  695. */
  696. public function testValidateSortRetainsOriginalSortValue()
  697. {
  698. $table = $this->_getMockPosts(['query']);
  699. $query = $this->_getMockFindQuery();
  700. $table->expects($this->once())
  701. ->method('query')
  702. ->will($this->returnValue($query));
  703. $query->expects($this->once())->method('applyOptions')
  704. ->with([
  705. 'limit' => 20,
  706. 'page' => 1,
  707. 'order' => ['PaginatorPosts.id' => 'asc'],
  708. 'whitelist' => ['limit', 'sort', 'page', 'direction'],
  709. 'scope' => null,
  710. 'sortWhitelist' => ['id'],
  711. 'sort' => 'id',
  712. ]);
  713. $params = [
  714. 'page' => 1,
  715. 'sort' => 'id',
  716. 'direction' => 'herp',
  717. ];
  718. $options = [
  719. 'sortWhitelist' => ['id'],
  720. ];
  721. $this->Paginator->paginate($table, $params, $options);
  722. $pagingParams = $this->Paginator->getPagingParams();
  723. $this->assertEquals('id', $pagingParams['PaginatorPosts']['sort']);
  724. }
  725. /**
  726. * Test that a really large page number gets clamped to the max page size.
  727. *
  728. * @return void
  729. */
  730. public function testOutOfRangePageNumberGetsClamped()
  731. {
  732. $this->loadFixtures('Posts');
  733. $params['page'] = 3000;
  734. $table = $this->getTableLocator()->get('PaginatorPosts');
  735. try {
  736. $this->Paginator->paginate($table, $params);
  737. $this->fail('No exception raised');
  738. } catch (PageOutOfBoundsException $exception) {
  739. $this->assertEquals(
  740. 'Page number 3000 could not be found.',
  741. $exception->getMessage()
  742. );
  743. $this->assertSame(
  744. [
  745. 'requestedPage' => 3000,
  746. 'pagingParams' => $this->Paginator->getPagingParams(),
  747. ],
  748. $exception->getAttributes()
  749. );
  750. }
  751. }
  752. /**
  753. * Test that a really REALLY large page number gets clamped to the max page size.
  754. *
  755. * @return void
  756. */
  757. public function testOutOfVeryBigPageNumberGetsClamped()
  758. {
  759. $this->expectException(\Cake\Datasource\Exception\PageOutOfBoundsException::class);
  760. $this->loadFixtures('Posts');
  761. $params = [
  762. 'page' => '3000000000000000000000000',
  763. ];
  764. $table = $this->getTableLocator()->get('PaginatorPosts');
  765. $this->Paginator->paginate($table, $params);
  766. }
  767. /**
  768. * test that fields not in whitelist won't be part of order conditions.
  769. *
  770. * @return void
  771. */
  772. public function testValidateSortWhitelistFailure()
  773. {
  774. $model = $this->mockAliasHasFieldModel();
  775. $options = [
  776. 'sort' => 'body',
  777. 'direction' => 'asc',
  778. 'sortWhitelist' => ['title', 'id'],
  779. ];
  780. $result = $this->Paginator->validateSort($model, $options);
  781. $this->assertEquals([], $result['order']);
  782. }
  783. /**
  784. * test that fields in the whitelist are not validated
  785. *
  786. * @return void
  787. */
  788. public function testValidateSortWhitelistTrusted()
  789. {
  790. $model = $this->mockAliasHasFieldModel();
  791. $options = [
  792. 'sort' => 'body',
  793. 'direction' => 'asc',
  794. 'sortWhitelist' => ['body'],
  795. ];
  796. $result = $this->Paginator->validateSort($model, $options);
  797. $expected = ['model.body' => 'asc'];
  798. $this->assertEquals(
  799. $expected,
  800. $result['order'],
  801. 'Trusted fields in schema should be prefixed'
  802. );
  803. }
  804. /**
  805. * test that whitelist as empty array does not allow any sorting
  806. *
  807. * @return void
  808. */
  809. public function testValidateSortWhitelistEmpty()
  810. {
  811. $model = $this->mockAliasHasFieldModel();
  812. $options = [
  813. 'order' => [
  814. 'body' => 'asc',
  815. 'foo.bar' => 'asc',
  816. ],
  817. 'sort' => 'body',
  818. 'direction' => 'asc',
  819. 'sortWhitelist' => [],
  820. ];
  821. $result = $this->Paginator->validateSort($model, $options);
  822. $this->assertSame([], $result['order'], 'No sort should be applied');
  823. }
  824. /**
  825. * test that fields in the whitelist are not validated
  826. *
  827. * @return void
  828. */
  829. public function testValidateSortWhitelistNotInSchema()
  830. {
  831. $model = $this->getMockRepository();
  832. $model->expects($this->any())
  833. ->method('getAlias')
  834. ->will($this->returnValue('model'));
  835. $model->expects($this->once())->method('hasField')
  836. ->will($this->returnValue(false));
  837. $options = [
  838. 'sort' => 'score',
  839. 'direction' => 'asc',
  840. 'sortWhitelist' => ['score'],
  841. ];
  842. $result = $this->Paginator->validateSort($model, $options);
  843. $expected = ['score' => 'asc'];
  844. $this->assertEquals(
  845. $expected,
  846. $result['order'],
  847. 'Trusted fields not in schema should not be altered'
  848. );
  849. }
  850. /**
  851. * test that multiple fields in the whitelist are not validated and properly aliased.
  852. *
  853. * @return void
  854. */
  855. public function testValidateSortWhitelistMultiple()
  856. {
  857. $model = $this->mockAliasHasFieldModel();
  858. $options = [
  859. 'order' => [
  860. 'body' => 'asc',
  861. 'foo.bar' => 'asc',
  862. ],
  863. 'sortWhitelist' => ['body', 'foo.bar'],
  864. ];
  865. $result = $this->Paginator->validateSort($model, $options);
  866. $expected = [
  867. 'model.body' => 'asc',
  868. 'foo.bar' => 'asc',
  869. ];
  870. $this->assertEquals($expected, $result['order']);
  871. }
  872. /**
  873. * @return \Cake\Datasource\RepositoryInterface|\PHPUnit\Framework\MockObject\MockObject
  874. */
  875. protected function getMockRepository()
  876. {
  877. $model = $this->getMockBuilder(RepositoryInterface::class)
  878. ->setMethods([
  879. 'getAlias', 'setAlias', 'setRegistryAlias', 'getRegistryAlias', 'hasField', 'find', 'get', 'query', 'updateAll', 'deleteAll',
  880. 'exists', 'save', 'delete', 'newEntity', 'newEntities', 'patchEntity', 'patchEntities',
  881. ])
  882. ->getMock();
  883. return $model;
  884. }
  885. /**
  886. * @param string $modelAlias Model alias to use.
  887. * @return \Cake\Datasource\RepositoryInterface|\PHPUnit\Framework\MockObject\MockObject
  888. */
  889. protected function mockAliasHasFieldModel($modelAlias = 'model')
  890. {
  891. $model = $this->getMockRepository();
  892. $model->expects($this->any())
  893. ->method('getAlias')
  894. ->will($this->returnValue($modelAlias));
  895. $model->expects($this->any())
  896. ->method('hasField')
  897. ->will($this->returnValue(true));
  898. return $model;
  899. }
  900. /**
  901. * test that multiple sort works.
  902. *
  903. * @return void
  904. */
  905. public function testValidateSortMultiple()
  906. {
  907. $model = $this->mockAliasHasFieldModel();
  908. $options = [
  909. 'order' => [
  910. 'author_id' => 'asc',
  911. 'title' => 'asc',
  912. ],
  913. ];
  914. $result = $this->Paginator->validateSort($model, $options);
  915. $expected = [
  916. 'model.author_id' => 'asc',
  917. 'model.title' => 'asc',
  918. ];
  919. $this->assertEquals($expected, $result['order']);
  920. }
  921. /**
  922. * test that multiple sort adds in query data.
  923. *
  924. * @return void
  925. */
  926. public function testValidateSortMultipleWithQuery()
  927. {
  928. $model = $this->mockAliasHasFieldModel();
  929. $options = [
  930. 'sort' => 'created',
  931. 'direction' => 'desc',
  932. 'order' => [
  933. 'author_id' => 'asc',
  934. 'title' => 'asc',
  935. ],
  936. ];
  937. $result = $this->Paginator->validateSort($model, $options);
  938. $expected = [
  939. 'model.created' => 'desc',
  940. 'model.author_id' => 'asc',
  941. 'model.title' => 'asc',
  942. ];
  943. $this->assertEquals($expected, $result['order']);
  944. $options = [
  945. 'sort' => 'title',
  946. 'direction' => 'desc',
  947. 'order' => [
  948. 'author_id' => 'asc',
  949. 'title' => 'asc',
  950. ],
  951. ];
  952. $result = $this->Paginator->validateSort($model, $options);
  953. $expected = [
  954. 'model.title' => 'desc',
  955. 'model.author_id' => 'asc',
  956. ];
  957. $this->assertEquals($expected, $result['order']);
  958. }
  959. /**
  960. * Tests that sort query string and model prefixes default match on assoc merging.
  961. *
  962. * @return void
  963. */
  964. public function testValidateSortMultipleWithQueryAndAliasedDefault()
  965. {
  966. $model = $this->mockAliasHasFieldModel();
  967. $options = [
  968. 'sort' => 'created',
  969. 'direction' => 'desc',
  970. 'order' => [
  971. 'model.created' => 'asc',
  972. ],
  973. ];
  974. $result = $this->Paginator->validateSort($model, $options);
  975. $expected = [
  976. 'sort' => 'created',
  977. 'order' => [
  978. 'model.created' => 'desc',
  979. ],
  980. ];
  981. $this->assertEquals($expected, $result);
  982. }
  983. /**
  984. * Tests that order strings can used by Paginator
  985. *
  986. * @return void
  987. */
  988. public function testValidateSortWithString()
  989. {
  990. $model = $this->mockAliasHasFieldModel();
  991. $options = [
  992. 'order' => 'model.author_id DESC',
  993. ];
  994. $result = $this->Paginator->validateSort($model, $options);
  995. $expected = 'model.author_id DESC';
  996. $this->assertEquals($expected, $result['order']);
  997. }
  998. /**
  999. * Test that no sort doesn't trigger an error.
  1000. *
  1001. * @return void
  1002. */
  1003. public function testValidateSortNoSort()
  1004. {
  1005. $model = $this->mockAliasHasFieldModel();
  1006. $options = [
  1007. 'direction' => 'asc',
  1008. 'sortWhitelist' => ['title', 'id'],
  1009. ];
  1010. $result = $this->Paginator->validateSort($model, $options);
  1011. $this->assertEquals([], $result['order']);
  1012. }
  1013. /**
  1014. * Test sorting with incorrect aliases on valid fields.
  1015. *
  1016. * @return void
  1017. */
  1018. public function testValidateSortInvalidAlias()
  1019. {
  1020. $model = $this->mockAliasHasFieldModel();
  1021. $options = ['sort' => 'Derp.id'];
  1022. $result = $this->Paginator->validateSort($model, $options);
  1023. $this->assertEquals([], $result['order']);
  1024. }
  1025. /**
  1026. * @return array
  1027. */
  1028. public function checkLimitProvider()
  1029. {
  1030. return [
  1031. 'out of bounds' => [
  1032. ['limit' => 1000000, 'maxLimit' => 100],
  1033. 100,
  1034. ],
  1035. 'limit is nan' => [
  1036. ['limit' => 'sheep!', 'maxLimit' => 100],
  1037. 1,
  1038. ],
  1039. 'negative limit' => [
  1040. ['limit' => '-1', 'maxLimit' => 100],
  1041. 1,
  1042. ],
  1043. 'unset limit' => [
  1044. ['limit' => null, 'maxLimit' => 100],
  1045. 1,
  1046. ],
  1047. 'limit = 0' => [
  1048. ['limit' => 0, 'maxLimit' => 100],
  1049. 1,
  1050. ],
  1051. 'limit = 0 v2' => [
  1052. ['limit' => 0, 'maxLimit' => 0],
  1053. 1,
  1054. ],
  1055. 'limit = null' => [
  1056. ['limit' => null, 'maxLimit' => 0],
  1057. 1,
  1058. ],
  1059. 'bad input, results in 1' => [
  1060. ['limit' => null, 'maxLimit' => null],
  1061. 1,
  1062. ],
  1063. 'bad input, results in 1 v2' => [
  1064. ['limit' => false, 'maxLimit' => false],
  1065. 1,
  1066. ],
  1067. ];
  1068. }
  1069. /**
  1070. * test that maxLimit is respected
  1071. *
  1072. * @dataProvider checkLimitProvider
  1073. * @return void
  1074. */
  1075. public function testCheckLimit($input, $expected)
  1076. {
  1077. $result = $this->Paginator->checkLimit($input);
  1078. $this->assertSame($expected, $result['limit']);
  1079. }
  1080. /**
  1081. * Integration test for checkLimit() being applied inside paginate()
  1082. *
  1083. * @return void
  1084. */
  1085. public function testPaginateMaxLimit()
  1086. {
  1087. $this->loadFixtures('Posts');
  1088. $table = $this->getTableLocator()->get('PaginatorPosts');
  1089. $settings = [
  1090. 'maxLimit' => 100,
  1091. ];
  1092. $params = [
  1093. 'limit' => '1000',
  1094. ];
  1095. $this->Paginator->paginate($table, $params, $settings);
  1096. $pagingParams = $this->Paginator->getPagingParams();
  1097. $this->assertEquals(100, $pagingParams['PaginatorPosts']['limit']);
  1098. $this->assertEquals(100, $pagingParams['PaginatorPosts']['perPage']);
  1099. $params = [
  1100. 'limit' => '10',
  1101. ];
  1102. $this->Paginator->paginate($table, $params, $settings);
  1103. $pagingParams = $this->Paginator->getPagingParams();
  1104. $this->assertEquals(10, $pagingParams['PaginatorPosts']['limit']);
  1105. $this->assertEquals(10, $pagingParams['PaginatorPosts']['perPage']);
  1106. }
  1107. /**
  1108. * test paginate() and custom find, to make sure the correct count is returned.
  1109. *
  1110. * @return void
  1111. */
  1112. public function testPaginateCustomFind()
  1113. {
  1114. $this->loadFixtures('Posts');
  1115. $titleExtractor = function ($result) {
  1116. $ids = [];
  1117. foreach ($result as $record) {
  1118. $ids[] = $record->title;
  1119. }
  1120. return $ids;
  1121. };
  1122. $table = $this->getTableLocator()->get('PaginatorPosts');
  1123. $data = ['author_id' => 3, 'title' => 'Fourth Post', 'body' => 'Article Body, unpublished', 'published' => 'N'];
  1124. $result = $table->save(new Entity($data));
  1125. $this->assertNotEmpty($result);
  1126. $result = $this->Paginator->paginate($table);
  1127. $this->assertCount(4, $result, '4 rows should come back');
  1128. $this->assertEquals(['First Post', 'Second Post', 'Third Post', 'Fourth Post'], $titleExtractor($result));
  1129. $pagingParams = $this->Paginator->getPagingParams();
  1130. $this->assertEquals(4, $pagingParams['PaginatorPosts']['current']);
  1131. $this->assertEquals(4, $pagingParams['PaginatorPosts']['count']);
  1132. $settings = ['finder' => 'published'];
  1133. $result = $this->Paginator->paginate($table, [], $settings);
  1134. $this->assertCount(3, $result, '3 rows should come back');
  1135. $this->assertEquals(['First Post', 'Second Post', 'Third Post'], $titleExtractor($result));
  1136. $pagingParams = $this->Paginator->getPagingParams();
  1137. $this->assertEquals(3, $pagingParams['PaginatorPosts']['current']);
  1138. $this->assertEquals(3, $pagingParams['PaginatorPosts']['count']);
  1139. $settings = ['finder' => 'published', 'limit' => 2, 'page' => 2];
  1140. $result = $this->Paginator->paginate($table, [], $settings);
  1141. $this->assertCount(1, $result, '1 rows should come back');
  1142. $this->assertEquals(['Third Post'], $titleExtractor($result));
  1143. $pagingParams = $this->Paginator->getPagingParams();
  1144. $this->assertEquals(1, $pagingParams['PaginatorPosts']['current']);
  1145. $this->assertEquals(3, $pagingParams['PaginatorPosts']['count']);
  1146. $this->assertEquals(2, $pagingParams['PaginatorPosts']['pageCount']);
  1147. $settings = ['finder' => 'published', 'limit' => 2];
  1148. $result = $this->Paginator->paginate($table, [], $settings);
  1149. $this->assertCount(2, $result, '2 rows should come back');
  1150. $this->assertEquals(['First Post', 'Second Post'], $titleExtractor($result));
  1151. $pagingParams = $this->Paginator->getPagingParams();
  1152. $this->assertEquals(2, $pagingParams['PaginatorPosts']['current']);
  1153. $this->assertEquals(3, $pagingParams['PaginatorPosts']['count']);
  1154. $this->assertEquals(2, $pagingParams['PaginatorPosts']['pageCount']);
  1155. $this->assertTrue($pagingParams['PaginatorPosts']['nextPage']);
  1156. $this->assertFalse($pagingParams['PaginatorPosts']['prevPage']);
  1157. $this->assertEquals(2, $pagingParams['PaginatorPosts']['perPage']);
  1158. $this->assertNull($pagingParams['PaginatorPosts']['limit']);
  1159. }
  1160. /**
  1161. * test paginate() and custom find with fields array, to make sure the correct count is returned.
  1162. *
  1163. * @return void
  1164. */
  1165. public function testPaginateCustomFindFieldsArray()
  1166. {
  1167. $this->loadFixtures('Posts');
  1168. $table = $this->getTableLocator()->get('PaginatorPosts');
  1169. $data = ['author_id' => 3, 'title' => 'Fourth Article', 'body' => 'Article Body, unpublished', 'published' => 'N'];
  1170. $table->save(new Entity($data));
  1171. $settings = [
  1172. 'finder' => 'list',
  1173. 'conditions' => ['PaginatorPosts.published' => 'Y'],
  1174. 'limit' => 2,
  1175. ];
  1176. $results = $this->Paginator->paginate($table, [], $settings);
  1177. $result = $results->toArray();
  1178. $expected = [
  1179. 1 => 'First Post',
  1180. 2 => 'Second Post',
  1181. ];
  1182. $this->assertEquals($expected, $result);
  1183. $result = $this->Paginator->getPagingParams()['PaginatorPosts'];
  1184. $this->assertEquals(2, $result['current']);
  1185. $this->assertEquals(3, $result['count']);
  1186. $this->assertEquals(2, $result['pageCount']);
  1187. $this->assertTrue($result['nextPage']);
  1188. $this->assertFalse($result['prevPage']);
  1189. }
  1190. /**
  1191. * test paginate() and custom finders to ensure the count + find
  1192. * use the custom type.
  1193. *
  1194. * @return void
  1195. */
  1196. public function testPaginateCustomFindCount()
  1197. {
  1198. $settings = [
  1199. 'finder' => 'published',
  1200. 'limit' => 2,
  1201. ];
  1202. $table = $this->_getMockPosts(['query']);
  1203. $query = $this->_getMockFindQuery();
  1204. $table->expects($this->once())
  1205. ->method('query')
  1206. ->will($this->returnValue($query));
  1207. $query->expects($this->once())->method('applyOptions')
  1208. ->with([
  1209. 'limit' => 2,
  1210. 'page' => 1,
  1211. 'order' => [],
  1212. 'whitelist' => ['limit', 'sort', 'page', 'direction'],
  1213. 'scope' => null,
  1214. 'sort' => null,
  1215. ]);
  1216. $this->Paginator->paginate($table, [], $settings);
  1217. }
  1218. /**
  1219. * Tests that it is possible to pass an already made query object to
  1220. * paginate()
  1221. *
  1222. * @return void
  1223. */
  1224. public function testPaginateQuery()
  1225. {
  1226. $params = ['page' => '-1'];
  1227. $settings = [
  1228. 'PaginatorPosts' => [
  1229. 'contain' => ['PaginatorAuthor'],
  1230. 'maxLimit' => 10,
  1231. 'group' => 'PaginatorPosts.published',
  1232. 'order' => ['PaginatorPosts.id' => 'ASC'],
  1233. ],
  1234. ];
  1235. $table = $this->_getMockPosts(['find']);
  1236. $query = $this->_getMockFindQuery($table);
  1237. $table->expects($this->never())->method('find');
  1238. $query->expects($this->once())
  1239. ->method('applyOptions')
  1240. ->with([
  1241. 'contain' => ['PaginatorAuthor'],
  1242. 'group' => 'PaginatorPosts.published',
  1243. 'limit' => 10,
  1244. 'order' => ['PaginatorPosts.id' => 'ASC'],
  1245. 'page' => 1,
  1246. 'whitelist' => ['limit', 'sort', 'page', 'direction'],
  1247. 'scope' => null,
  1248. 'sort' => 'PaginatorPosts.id',
  1249. ]);
  1250. $this->Paginator->paginate($query, $params, $settings);
  1251. }
  1252. /**
  1253. * test paginate() with bind()
  1254. *
  1255. * @return void
  1256. */
  1257. public function testPaginateQueryWithBindValue()
  1258. {
  1259. $config = ConnectionManager::getConfig('test');
  1260. $this->skipIf(strpos($config['driver'], 'Sqlserver') !== false, 'Test temporarily broken in SQLServer');
  1261. $this->loadFixtures('Posts');
  1262. $table = $this->getTableLocator()->get('PaginatorPosts');
  1263. $query = $table->find()
  1264. ->where(['PaginatorPosts.author_id BETWEEN :start AND :end'])
  1265. ->bind(':start', 1)
  1266. ->bind(':end', 2);
  1267. $results = $this->Paginator->paginate($query, []);
  1268. $result = $results->toArray();
  1269. $this->assertCount(2, $result);
  1270. $this->assertEquals('First Post', $result[0]->title);
  1271. $this->assertEquals('Third Post', $result[1]->title);
  1272. }
  1273. /**
  1274. * Tests that passing a query object with a limit clause set will
  1275. * overwrite it with the passed defaults.
  1276. *
  1277. * @return void
  1278. */
  1279. public function testPaginateQueryWithLimit()
  1280. {
  1281. $params = ['page' => '-1'];
  1282. $settings = [
  1283. 'PaginatorPosts' => [
  1284. 'contain' => ['PaginatorAuthor'],
  1285. 'maxLimit' => 10,
  1286. 'limit' => 5,
  1287. 'group' => 'PaginatorPosts.published',
  1288. 'order' => ['PaginatorPosts.id' => 'ASC'],
  1289. ],
  1290. ];
  1291. $table = $this->_getMockPosts(['find']);
  1292. $query = $this->_getMockFindQuery($table);
  1293. $query->limit(2);
  1294. $table->expects($this->never())->method('find');
  1295. $query->expects($this->once())
  1296. ->method('applyOptions')
  1297. ->with([
  1298. 'contain' => ['PaginatorAuthor'],
  1299. 'group' => 'PaginatorPosts.published',
  1300. 'limit' => 5,
  1301. 'order' => ['PaginatorPosts.id' => 'ASC'],
  1302. 'page' => 1,
  1303. 'whitelist' => ['limit', 'sort', 'page', 'direction'],
  1304. 'scope' => null,
  1305. 'sort' => 'PaginatorPosts.id',
  1306. ]);
  1307. $this->Paginator->paginate($query, $params, $settings);
  1308. }
  1309. /**
  1310. * Helper method for making mocks.
  1311. *
  1312. * @param array $methods
  1313. * @return \Cake\ORM\Table|\PHPUnit\Framework\MockObject\MockObject
  1314. */
  1315. protected function _getMockPosts($methods = [])
  1316. {
  1317. return $this->getMockBuilder('TestApp\Model\Table\PaginatorPostsTable')
  1318. ->setMethods($methods)
  1319. ->setConstructorArgs([[
  1320. 'connection' => ConnectionManager::get('test'),
  1321. 'alias' => 'PaginatorPosts',
  1322. 'schema' => [
  1323. 'id' => ['type' => 'integer'],
  1324. 'author_id' => ['type' => 'integer', 'null' => false],
  1325. 'title' => ['type' => 'string', 'null' => false],
  1326. 'body' => 'text',
  1327. 'published' => ['type' => 'string', 'length' => 1, 'default' => 'N'],
  1328. '_constraints' => ['primary' => ['type' => 'primary', 'columns' => ['id']]],
  1329. ],
  1330. ]])
  1331. ->getMock();
  1332. }
  1333. /**
  1334. * Helper method for mocking queries.
  1335. *
  1336. * @param string|null $table
  1337. * @return \Cake\ORM\Query|\PHPUnit\Framework\MockObject\MockObject
  1338. */
  1339. protected function _getMockFindQuery($table = null)
  1340. {
  1341. /** @var \Cake\ORM\Query|\PHPUnit\Framework\MockObject\MockObject $query */
  1342. $query = $this->getMockBuilder('Cake\ORM\Query')
  1343. ->setMethods(['total', 'all', 'count', 'applyOptions'])
  1344. ->disableOriginalConstructor()
  1345. ->getMock();
  1346. $results = $this->getMockBuilder('Cake\ORM\ResultSet')
  1347. ->disableOriginalConstructor()
  1348. ->getMock();
  1349. $query->expects($this->any())
  1350. ->method('count')
  1351. ->will($this->returnValue(2));
  1352. $query->expects($this->any())
  1353. ->method('all')
  1354. ->will($this->returnValue($results));
  1355. $query->expects($this->any())
  1356. ->method('count')
  1357. ->will($this->returnValue(2));
  1358. if ($table) {
  1359. $query->repository($table);
  1360. }
  1361. return $query;
  1362. }
  1363. }