PaginatorComponentTest.php 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967
  1. <?php
  2. /**
  3. * CakePHP(tm) Tests <http://book.cakephp.org/2.0/en/development/testing.html>
  4. * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  5. *
  6. * Licensed under The MIT License
  7. * For full copyright and license information, please see the LICENSE.txt
  8. * Redistributions of files must retain the above copyright notice
  9. *
  10. * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  11. * @link http://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests
  12. * @since 2.0.0
  13. * @license http://www.opensource.org/licenses/mit-license.php MIT License
  14. */
  15. namespace Cake\Test\TestCase\Controller\Component;
  16. use Cake\Controller\ComponentRegistry;
  17. use Cake\Controller\Component\PaginatorComponent;
  18. use Cake\Controller\Controller;
  19. use Cake\Core\Configure;
  20. use Cake\Datasource\ConnectionManager;
  21. use Cake\Network\Exception\NotFoundException;
  22. use Cake\Network\Request;
  23. use Cake\ORM\TableRegistry;
  24. use Cake\TestSuite\TestCase;
  25. use Cake\Utility\Hash;
  26. /**
  27. * PaginatorTestController class
  28. *
  29. */
  30. class PaginatorTestController extends Controller
  31. {
  32. /**
  33. * components property
  34. *
  35. * @var array
  36. */
  37. public $components = ['Paginator'];
  38. }
  39. class PaginatorComponentTest extends TestCase
  40. {
  41. /**
  42. * fixtures property
  43. *
  44. * @var array
  45. */
  46. public $fixtures = ['core.posts'];
  47. /**
  48. * Don't load data for fixtures for all tests
  49. *
  50. * @var bool
  51. */
  52. public $autoFixtures = false;
  53. /**
  54. * setup
  55. *
  56. * @return void
  57. */
  58. public function setUp()
  59. {
  60. parent::setUp();
  61. Configure::write('App.namespace', 'TestApp');
  62. $this->request = new Request('controller_posts/index');
  63. $this->request->params['pass'] = [];
  64. $controller = new Controller($this->request);
  65. $registry = new ComponentRegistry($controller);
  66. $this->Paginator = new PaginatorComponent($registry, []);
  67. $this->Post = $this->getMock('Cake\ORM\Table', [], [], '', false);
  68. }
  69. /**
  70. * tearDown
  71. *
  72. * @return void
  73. */
  74. public function tearDown()
  75. {
  76. parent::tearDown();
  77. TableRegistry::clear();
  78. }
  79. /**
  80. * Test that non-numeric values are rejected for page, and limit
  81. *
  82. * @return void
  83. */
  84. public function testPageParamCasting()
  85. {
  86. $this->Post->expects($this->any())
  87. ->method('alias')
  88. ->will($this->returnValue('Posts'));
  89. $query = $this->_getMockFindQuery();
  90. $this->Post->expects($this->any())
  91. ->method('find')
  92. ->will($this->returnValue($query));
  93. $this->request->query = ['page' => '1 " onclick="alert(\'xss\');">'];
  94. $settings = ['limit' => 1, 'maxLimit' => 10];
  95. $this->Paginator->paginate($this->Post, $settings);
  96. $this->assertSame(1, $this->request->params['paging']['Posts']['page'], 'XSS exploit opened');
  97. }
  98. /**
  99. * test that unknown keys in the default settings are
  100. * passed to the find operations.
  101. *
  102. * @return void
  103. */
  104. public function testPaginateExtraParams()
  105. {
  106. $this->request->query = ['page' => '-1'];
  107. $settings = [
  108. 'PaginatorPosts' => [
  109. 'contain' => ['PaginatorAuthor'],
  110. 'maxLimit' => 10,
  111. 'group' => 'PaginatorPosts.published',
  112. 'order' => ['PaginatorPosts.id' => 'ASC']
  113. ],
  114. ];
  115. $table = $this->_getMockPosts(['query']);
  116. $query = $this->_getMockFindQuery();
  117. $table->expects($this->once())
  118. ->method('query')
  119. ->will($this->returnValue($query));
  120. $query->expects($this->once())
  121. ->method('applyOptions')
  122. ->with([
  123. 'contain' => ['PaginatorAuthor'],
  124. 'group' => 'PaginatorPosts.published',
  125. 'limit' => 10,
  126. 'order' => ['PaginatorPosts.id' => 'ASC'],
  127. 'page' => 1,
  128. 'whitelist' => ['limit', 'sort', 'page', 'direction'],
  129. ]);
  130. $this->Paginator->paginate($table, $settings);
  131. }
  132. /**
  133. * Test to make sure options get sent to custom finder methods via paginate
  134. *
  135. * @return void
  136. */
  137. public function testPaginateCustomFinderOptions()
  138. {
  139. $this->loadFixtures('Posts');
  140. $settings = [
  141. 'PaginatorPosts' => [
  142. 'finder' => ['author' => ['author_id' => 1]]
  143. ]
  144. ];
  145. $table = TableRegistry::get('PaginatorPosts');
  146. $expected = $table
  147. ->find('author', [
  148. 'conditions' => [
  149. 'PaginatorPosts.author_id' => 1
  150. ]
  151. ])
  152. ->count();
  153. $result = $this->Paginator->paginate($table, $settings)->count();
  154. $this->assertEquals($expected, $result);
  155. }
  156. /**
  157. * Test that special paginate types are called and that the type param doesn't leak out into defaults or options.
  158. *
  159. * @return void
  160. */
  161. public function testPaginateCustomFinder()
  162. {
  163. $settings = [
  164. 'PaginatorPosts' => [
  165. 'finder' => 'popular',
  166. 'fields' => ['id', 'title'],
  167. 'maxLimit' => 10,
  168. ]
  169. ];
  170. $table = $this->_getMockPosts(['findPopular']);
  171. $query = $this->_getMockFindQuery();
  172. $table->expects($this->any())
  173. ->method('findPopular')
  174. ->will($this->returnValue($query));
  175. $this->Paginator->paginate($table, $settings);
  176. $this->assertEquals('popular', $this->request->params['paging']['PaginatorPosts']['finder']);
  177. }
  178. /**
  179. * test that flat default pagination parameters work.
  180. *
  181. * @return void
  182. */
  183. public function testDefaultPaginateParams()
  184. {
  185. $settings = [
  186. 'order' => ['PaginatorPosts.id' => 'DESC'],
  187. 'maxLimit' => 10,
  188. ];
  189. $table = $this->_getMockPosts(['query']);
  190. $query = $this->_getMockFindQuery();
  191. $table->expects($this->once())
  192. ->method('query')
  193. ->will($this->returnValue($query));
  194. $query->expects($this->once())
  195. ->method('applyOptions')
  196. ->with([
  197. 'limit' => 10,
  198. 'page' => 1,
  199. 'order' => ['PaginatorPosts.id' => 'DESC'],
  200. 'whitelist' => ['limit', 'sort', 'page', 'direction'],
  201. ]);
  202. $this->Paginator->paginate($table, $settings);
  203. }
  204. /**
  205. * test that default sort and default direction are injected into request
  206. *
  207. * @return void
  208. */
  209. public function testDefaultPaginateParamsIntoRequest()
  210. {
  211. $settings = [
  212. 'order' => ['PaginatorPosts.id' => 'DESC'],
  213. 'maxLimit' => 10,
  214. ];
  215. $table = $this->_getMockPosts(['query']);
  216. $query = $this->_getMockFindQuery();
  217. $table->expects($this->once())
  218. ->method('query')
  219. ->will($this->returnValue($query));
  220. $query->expects($this->once())
  221. ->method('applyOptions')
  222. ->with([
  223. 'limit' => 10,
  224. 'page' => 1,
  225. 'order' => ['PaginatorPosts.id' => 'DESC'],
  226. 'whitelist' => ['limit', 'sort', 'page', 'direction'],
  227. ]);
  228. $this->Paginator->paginate($table, $settings);
  229. $this->assertEquals('PaginatorPosts.id', $this->request->params['paging']['PaginatorPosts']['sortDefault']);
  230. $this->assertEquals('DESC', $this->request->params['paging']['PaginatorPosts']['directionDefault']);
  231. }
  232. /**
  233. * test that option merging prefers specific models
  234. *
  235. * @return void
  236. */
  237. public function testMergeOptionsModelSpecific()
  238. {
  239. $settings = [
  240. 'page' => 1,
  241. 'limit' => 20,
  242. 'maxLimit' => 100,
  243. 'Posts' => [
  244. 'page' => 1,
  245. 'limit' => 10,
  246. 'maxLimit' => 50,
  247. ],
  248. 'whitelist' => ['limit', 'sort', 'page', 'direction'],
  249. ];
  250. $result = $this->Paginator->mergeOptions('Silly', $settings);
  251. $this->assertEquals($settings, $result);
  252. $result = $this->Paginator->mergeOptions('Posts', $settings);
  253. $expected = ['page' => 1, 'limit' => 10, 'maxLimit' => 50, 'whitelist' => ['limit', 'sort', 'page', 'direction']];
  254. $this->assertEquals($expected, $result);
  255. }
  256. /**
  257. * test mergeOptions with customFind key
  258. *
  259. * @return void
  260. */
  261. public function testMergeOptionsCustomFindKey()
  262. {
  263. $this->request->query = [
  264. 'page' => 10,
  265. 'limit' => 10
  266. ];
  267. $settings = [
  268. 'page' => 1,
  269. 'limit' => 20,
  270. 'maxLimit' => 100,
  271. 'finder' => 'myCustomFind'
  272. ];
  273. $result = $this->Paginator->mergeOptions('Post', $settings);
  274. $expected = [
  275. 'page' => 10,
  276. 'limit' => 10,
  277. 'maxLimit' => 100,
  278. 'finder' => 'myCustomFind',
  279. 'whitelist' => ['limit', 'sort', 'page', 'direction'],
  280. ];
  281. $this->assertEquals($expected, $result);
  282. }
  283. /**
  284. * test merging options from the querystring.
  285. *
  286. * @return void
  287. */
  288. public function testMergeOptionsQueryString()
  289. {
  290. $this->request->query = [
  291. 'page' => 99,
  292. 'limit' => 75
  293. ];
  294. $settings = [
  295. 'page' => 1,
  296. 'limit' => 20,
  297. 'maxLimit' => 100,
  298. ];
  299. $result = $this->Paginator->mergeOptions('Post', $settings);
  300. $expected = ['page' => 99, 'limit' => 75, 'maxLimit' => 100, 'whitelist' => ['limit', 'sort', 'page', 'direction']];
  301. $this->assertEquals($expected, $result);
  302. }
  303. /**
  304. * test that the default whitelist doesn't let people screw with things they should not be allowed to.
  305. *
  306. * @return void
  307. */
  308. public function testMergeOptionsDefaultWhiteList()
  309. {
  310. $this->request->query = [
  311. 'page' => 10,
  312. 'limit' => 10,
  313. 'fields' => ['bad.stuff'],
  314. 'recursive' => 1000,
  315. 'conditions' => ['bad.stuff'],
  316. 'contain' => ['bad']
  317. ];
  318. $settings = [
  319. 'page' => 1,
  320. 'limit' => 20,
  321. 'maxLimit' => 100,
  322. ];
  323. $result = $this->Paginator->mergeOptions('Post', $settings);
  324. $expected = ['page' => 10, 'limit' => 10, 'maxLimit' => 100, 'whitelist' => ['limit', 'sort', 'page', 'direction']];
  325. $this->assertEquals($expected, $result);
  326. }
  327. /**
  328. * test that modifying the whitelist works.
  329. *
  330. * @return void
  331. */
  332. public function testMergeOptionsExtraWhitelist()
  333. {
  334. $this->request->query = [
  335. 'page' => 10,
  336. 'limit' => 10,
  337. 'fields' => ['bad.stuff'],
  338. 'recursive' => 1000,
  339. 'conditions' => ['bad.stuff'],
  340. 'contain' => ['bad']
  341. ];
  342. $settings = [
  343. 'page' => 1,
  344. 'limit' => 20,
  345. 'maxLimit' => 100,
  346. ];
  347. $this->Paginator->config('whitelist', ['fields']);
  348. $result = $this->Paginator->mergeOptions('Post', $settings);
  349. $expected = [
  350. 'page' => 10, 'limit' => 10, 'maxLimit' => 100, 'fields' => ['bad.stuff'], 'whitelist' => ['limit', 'sort', 'page', 'direction', 'fields']
  351. ];
  352. $this->assertEquals($expected, $result);
  353. }
  354. /**
  355. * test mergeOptions with limit > maxLimit in code.
  356. *
  357. * @return void
  358. */
  359. public function testMergeOptionsMaxLimit()
  360. {
  361. $settings = [
  362. 'limit' => 200,
  363. 'paramType' => 'named',
  364. ];
  365. $result = $this->Paginator->mergeOptions('Post', $settings);
  366. $expected = [
  367. 'page' => 1,
  368. 'limit' => 200,
  369. 'maxLimit' => 200,
  370. 'paramType' => 'named',
  371. 'whitelist' => ['limit', 'sort', 'page', 'direction']
  372. ];
  373. $this->assertEquals($expected, $result);
  374. $settings = [
  375. 'maxLimit' => 10,
  376. 'paramType' => 'named',
  377. ];
  378. $result = $this->Paginator->mergeOptions('Post', $settings);
  379. $expected = [
  380. 'page' => 1,
  381. 'limit' => 20,
  382. 'maxLimit' => 10,
  383. 'paramType' => 'named',
  384. 'whitelist' => ['limit', 'sort', 'page', 'direction']
  385. ];
  386. $this->assertEquals($expected, $result);
  387. }
  388. /**
  389. * Integration test to ensure that validateSort is being used by paginate()
  390. *
  391. * @return void
  392. */
  393. public function testValidateSortInvalid()
  394. {
  395. $table = $this->_getMockPosts(['query']);
  396. $query = $this->_getMockFindQuery();
  397. $table->expects($this->once())
  398. ->method('query')
  399. ->will($this->returnValue($query));
  400. $query->expects($this->once())->method('applyOptions')
  401. ->with([
  402. 'limit' => 20,
  403. 'page' => 1,
  404. 'order' => ['PaginatorPosts.id' => 'asc'],
  405. 'whitelist' => ['limit', 'sort', 'page', 'direction'],
  406. ]);
  407. $this->request->query = [
  408. 'page' => 1,
  409. 'sort' => 'id',
  410. 'direction' => 'herp'
  411. ];
  412. $this->Paginator->paginate($table);
  413. $this->assertEquals('PaginatorPosts.id', $this->request->params['paging']['PaginatorPosts']['sort']);
  414. $this->assertEquals('asc', $this->request->params['paging']['PaginatorPosts']['direction']);
  415. }
  416. /**
  417. * test that invalid directions are ignored.
  418. *
  419. * @return void
  420. */
  421. public function testValidateSortInvalidDirection()
  422. {
  423. $model = $this->getMock('Cake\ORM\Table');
  424. $model->expects($this->any())
  425. ->method('alias')
  426. ->will($this->returnValue('model'));
  427. $model->expects($this->any())
  428. ->method('hasField')
  429. ->will($this->returnValue(true));
  430. $options = ['sort' => 'something', 'direction' => 'boogers'];
  431. $result = $this->Paginator->validateSort($model, $options);
  432. $this->assertEquals('asc', $result['order']['model.something']);
  433. }
  434. /**
  435. * Test that a really large page number gets clamped to the max page size.
  436. *
  437. * @return void
  438. */
  439. public function testOutOfRangePageNumberGetsClamped()
  440. {
  441. $this->loadFixtures('Posts');
  442. $this->request->query['page'] = 3000;
  443. $table = TableRegistry::get('PaginatorPosts');
  444. try {
  445. $this->Paginator->paginate($table);
  446. $this->fail('No exception raised');
  447. } catch (NotFoundException $e) {
  448. $this->assertEquals(
  449. 1,
  450. $this->request->params['paging']['PaginatorPosts']['page'],
  451. 'Page number should not be 0'
  452. );
  453. }
  454. }
  455. /**
  456. * Test that a really REALLY large page number gets clamped to the max page size.
  457. *
  458. * @expectedException \Cake\Network\Exception\NotFoundException
  459. * @return void
  460. */
  461. public function testOutOfVeryBigPageNumberGetsClamped()
  462. {
  463. $this->loadFixtures('Posts');
  464. $this->request->query = [
  465. 'page' => '3000000000000000000000000',
  466. ];
  467. $table = TableRegistry::get('PaginatorPosts');
  468. $this->Paginator->paginate($table);
  469. }
  470. /**
  471. * test that fields not in whitelist won't be part of order conditions.
  472. *
  473. * @return void
  474. */
  475. public function testValidateSortWhitelistFailure()
  476. {
  477. $model = $this->getMock('Cake\ORM\Table');
  478. $model->expects($this->any())
  479. ->method('alias')
  480. ->will($this->returnValue('model'));
  481. $model->expects($this->any())->method('hasField')->will($this->returnValue(true));
  482. $options = [
  483. 'sort' => 'body',
  484. 'direction' => 'asc',
  485. 'sortWhitelist' => ['title', 'id']
  486. ];
  487. $result = $this->Paginator->validateSort($model, $options);
  488. $this->assertEquals([], $result['order']);
  489. }
  490. /**
  491. * test that fields in the whitelist are not validated
  492. *
  493. * @return void
  494. */
  495. public function testValidateSortWhitelistTrusted()
  496. {
  497. $model = $this->getMock('Cake\ORM\Table');
  498. $model->expects($this->any())
  499. ->method('alias')
  500. ->will($this->returnValue('model'));
  501. $model->expects($this->never())->method('hasField');
  502. $options = [
  503. 'sort' => 'body',
  504. 'direction' => 'asc',
  505. 'sortWhitelist' => ['body']
  506. ];
  507. $result = $this->Paginator->validateSort($model, $options);
  508. $expected = ['body' => 'asc'];
  509. $this->assertEquals($expected, $result['order']);
  510. }
  511. /**
  512. * test that multiple sort works.
  513. *
  514. * @return void
  515. */
  516. public function testValidateSortMultiple()
  517. {
  518. $model = $this->getMock('Cake\ORM\Table');
  519. $model->expects($this->any())
  520. ->method('alias')
  521. ->will($this->returnValue('model'));
  522. $model->expects($this->any())->method('hasField')->will($this->returnValue(true));
  523. $options = [
  524. 'order' => [
  525. 'author_id' => 'asc',
  526. 'title' => 'asc'
  527. ]
  528. ];
  529. $result = $this->Paginator->validateSort($model, $options);
  530. $expected = [
  531. 'model.author_id' => 'asc',
  532. 'model.title' => 'asc'
  533. ];
  534. $this->assertEquals($expected, $result['order']);
  535. }
  536. /**
  537. * Tests that order strings can used by Paginator
  538. *
  539. * @return void
  540. */
  541. public function testValidateSortWithString()
  542. {
  543. $model = $this->getMock('Cake\ORM\Table');
  544. $model->expects($this->any())
  545. ->method('alias')
  546. ->will($this->returnValue('model'));
  547. $model->expects($this->any())->method('hasField')->will($this->returnValue(true));
  548. $options = [
  549. 'order' => 'model.author_id DESC'
  550. ];
  551. $result = $this->Paginator->validateSort($model, $options);
  552. $expected = 'model.author_id DESC';
  553. $this->assertEquals($expected, $result['order']);
  554. }
  555. /**
  556. * Test that no sort doesn't trigger an error.
  557. *
  558. * @return void
  559. */
  560. public function testValidateSortNoSort()
  561. {
  562. $model = $this->getMock('Cake\ORM\Table');
  563. $model->expects($this->any())
  564. ->method('alias')
  565. ->will($this->returnValue('model'));
  566. $model->expects($this->any())->method('hasField')
  567. ->will($this->returnValue(true));
  568. $options = [
  569. 'direction' => 'asc',
  570. 'sortWhitelist' => ['title', 'id'],
  571. ];
  572. $result = $this->Paginator->validateSort($model, $options);
  573. $this->assertEquals([], $result['order']);
  574. }
  575. /**
  576. * Test sorting with incorrect aliases on valid fields.
  577. *
  578. * @return void
  579. */
  580. public function testValidateSortInvalidAlias()
  581. {
  582. $model = $this->getMock('Cake\ORM\Table');
  583. $model->expects($this->any())
  584. ->method('alias')
  585. ->will($this->returnValue('model'));
  586. $model->expects($this->any())->method('hasField')->will($this->returnValue(true));
  587. $options = ['sort' => 'Derp.id'];
  588. $result = $this->Paginator->validateSort($model, $options);
  589. $this->assertEquals([], $result['order']);
  590. }
  591. /**
  592. * test that maxLimit is respected
  593. *
  594. * @return void
  595. */
  596. public function testCheckLimit()
  597. {
  598. $result = $this->Paginator->checkLimit(['limit' => 1000000, 'maxLimit' => 100]);
  599. $this->assertEquals(100, $result['limit']);
  600. $result = $this->Paginator->checkLimit(['limit' => 'sheep!', 'maxLimit' => 100]);
  601. $this->assertEquals(1, $result['limit']);
  602. $result = $this->Paginator->checkLimit(['limit' => '-1', 'maxLimit' => 100]);
  603. $this->assertEquals(1, $result['limit']);
  604. $result = $this->Paginator->checkLimit(['limit' => null, 'maxLimit' => 100]);
  605. $this->assertEquals(1, $result['limit']);
  606. $result = $this->Paginator->checkLimit(['limit' => 0, 'maxLimit' => 100]);
  607. $this->assertEquals(1, $result['limit']);
  608. }
  609. /**
  610. * Integration test for checkLimit() being applied inside paginate()
  611. *
  612. * @return void
  613. */
  614. public function testPaginateMaxLimit()
  615. {
  616. $this->loadFixtures('Posts');
  617. $table = TableRegistry::get('PaginatorPosts');
  618. $settings = [
  619. 'maxLimit' => 100,
  620. ];
  621. $this->request->query = [
  622. 'limit' => '1000'
  623. ];
  624. $this->Paginator->paginate($table, $settings);
  625. $this->assertEquals(100, $this->request->params['paging']['PaginatorPosts']['limit']);
  626. $this->assertEquals(100, $this->request->params['paging']['PaginatorPosts']['perPage']);
  627. $this->request->query = [
  628. 'limit' => '10'
  629. ];
  630. $this->Paginator->paginate($table, $settings);
  631. $this->assertEquals(10, $this->request->params['paging']['PaginatorPosts']['limit']);
  632. $this->assertEquals(10, $this->request->params['paging']['PaginatorPosts']['perPage']);
  633. }
  634. /**
  635. * test paginate() and custom find, to make sure the correct count is returned.
  636. *
  637. * @return void
  638. */
  639. public function testPaginateCustomFind()
  640. {
  641. $this->loadFixtures('Posts');
  642. $idExtractor = function ($result) {
  643. $ids = [];
  644. foreach ($result as $record) {
  645. $ids[] = $record->id;
  646. }
  647. return $ids;
  648. };
  649. $table = TableRegistry::get('PaginatorPosts');
  650. $data = ['author_id' => 3, 'title' => 'Fourth Article', 'body' => 'Article Body, unpublished', 'published' => 'N'];
  651. $result = $table->save(new \Cake\ORM\Entity($data));
  652. $this->assertNotEmpty($result);
  653. $result = $this->Paginator->paginate($table);
  654. $this->assertCount(4, $result, '4 rows should come back');
  655. $this->assertEquals([1, 2, 3, 4], $idExtractor($result));
  656. $result = $this->request->params['paging']['PaginatorPosts'];
  657. $this->assertEquals(4, $result['current']);
  658. $this->assertEquals(4, $result['count']);
  659. $settings = ['finder' => 'published'];
  660. $result = $this->Paginator->paginate($table, $settings);
  661. $this->assertCount(3, $result, '3 rows should come back');
  662. $this->assertEquals([1, 2, 3], $idExtractor($result));
  663. $result = $this->request->params['paging']['PaginatorPosts'];
  664. $this->assertEquals(3, $result['current']);
  665. $this->assertEquals(3, $result['count']);
  666. $settings = ['finder' => 'published', 'limit' => 2];
  667. $result = $this->Paginator->paginate($table, $settings);
  668. $this->assertCount(2, $result, '2 rows should come back');
  669. $this->assertEquals([1, 2], $idExtractor($result));
  670. $result = $this->request->params['paging']['PaginatorPosts'];
  671. $this->assertEquals(2, $result['current']);
  672. $this->assertEquals(3, $result['count']);
  673. $this->assertEquals(2, $result['pageCount']);
  674. $this->assertTrue($result['nextPage']);
  675. $this->assertFalse($result['prevPage']);
  676. $this->assertEquals(2, $result['perPage']);
  677. $this->assertNull($result['limit']);
  678. }
  679. /**
  680. * test paginate() and custom find with fields array, to make sure the correct count is returned.
  681. *
  682. * @return void
  683. */
  684. public function testPaginateCustomFindFieldsArray()
  685. {
  686. $this->loadFixtures('Posts');
  687. $table = TableRegistry::get('PaginatorPosts');
  688. $data = ['author_id' => 3, 'title' => 'Fourth Article', 'body' => 'Article Body, unpublished', 'published' => 'N'];
  689. $table->save(new \Cake\ORM\Entity($data));
  690. $settings = [
  691. 'finder' => 'list',
  692. 'conditions' => ['PaginatorPosts.published' => 'Y'],
  693. 'limit' => 2
  694. ];
  695. $results = $this->Paginator->paginate($table, $settings);
  696. $result = $results->toArray();
  697. $expected = [
  698. 1 => 'First Post',
  699. 2 => 'Second Post',
  700. ];
  701. $this->assertEquals($expected, $result);
  702. $result = $this->request->params['paging']['PaginatorPosts'];
  703. $this->assertEquals(2, $result['current']);
  704. $this->assertEquals(3, $result['count']);
  705. $this->assertEquals(2, $result['pageCount']);
  706. $this->assertTrue($result['nextPage']);
  707. $this->assertFalse($result['prevPage']);
  708. }
  709. /**
  710. * test paginate() and custom finders to ensure the count + find
  711. * use the custom type.
  712. *
  713. * @return void
  714. */
  715. public function testPaginateCustomFindCount()
  716. {
  717. $settings = [
  718. 'finder' => 'published',
  719. 'limit' => 2
  720. ];
  721. $table = $this->_getMockPosts(['query']);
  722. $query = $this->_getMockFindQuery();
  723. $table->expects($this->once())
  724. ->method('query')
  725. ->will($this->returnValue($query));
  726. $query->expects($this->once())->method('applyOptions')
  727. ->with(['limit' => 2, 'page' => 1, 'order' => [], 'whitelist' => ['limit', 'sort', 'page', 'direction']]);
  728. $this->Paginator->paginate($table, $settings);
  729. }
  730. /**
  731. * Tests that it is possible to pass an already made query object to
  732. * paginate()
  733. *
  734. * @return void
  735. */
  736. public function testPaginateQuery()
  737. {
  738. $this->request->query = ['page' => '-1'];
  739. $settings = [
  740. 'PaginatorPosts' => [
  741. 'contain' => ['PaginatorAuthor'],
  742. 'maxLimit' => 10,
  743. 'group' => 'PaginatorPosts.published',
  744. 'order' => ['PaginatorPosts.id' => 'ASC']
  745. ]
  746. ];
  747. $table = $this->_getMockPosts(['find']);
  748. $query = $this->_getMockFindQuery($table);
  749. $table->expects($this->never())->method('find');
  750. $query->expects($this->once())
  751. ->method('applyOptions')
  752. ->with([
  753. 'contain' => ['PaginatorAuthor'],
  754. 'group' => 'PaginatorPosts.published',
  755. 'limit' => 10,
  756. 'order' => ['PaginatorPosts.id' => 'ASC'],
  757. 'page' => 1,
  758. 'whitelist' => ['limit', 'sort', 'page', 'direction'],
  759. ]);
  760. $this->Paginator->paginate($query, $settings);
  761. }
  762. /**
  763. * test paginate() with bind()
  764. *
  765. * @return void
  766. */
  767. public function testPaginateQueryWithBindValue()
  768. {
  769. $this->loadFixtures('Posts');
  770. $table = TableRegistry::get('PaginatorPosts');
  771. $query = $table->find()
  772. ->where(['PaginatorPosts.author_id BETWEEN :start AND :end'])
  773. ->bind(':start', 1)
  774. ->bind(':end', 2);
  775. $results = $this->Paginator->paginate($query, []);
  776. $result = $results->toArray();
  777. $this->assertCount(2, $result);
  778. $this->assertEquals('First Post', $result[0]->title);
  779. $this->assertEquals('Third Post', $result[1]->title);
  780. }
  781. /**
  782. * Tests that passing a query object with a limit clause set will
  783. * overwrite it with the passed defaults.
  784. *
  785. * @return void
  786. */
  787. public function testPaginateQueryWithLimit()
  788. {
  789. $this->request->query = ['page' => '-1'];
  790. $settings = [
  791. 'PaginatorPosts' => [
  792. 'contain' => ['PaginatorAuthor'],
  793. 'maxLimit' => 10,
  794. 'limit' => 5,
  795. 'group' => 'PaginatorPosts.published',
  796. 'order' => ['PaginatorPosts.id' => 'ASC']
  797. ]
  798. ];
  799. $table = $this->_getMockPosts(['find']);
  800. $query = $this->_getMockFindQuery($table);
  801. $query->limit(2);
  802. $table->expects($this->never())->method('find');
  803. $query->expects($this->once())
  804. ->method('applyOptions')
  805. ->with([
  806. 'contain' => ['PaginatorAuthor'],
  807. 'group' => 'PaginatorPosts.published',
  808. 'limit' => 5,
  809. 'order' => ['PaginatorPosts.id' => 'ASC'],
  810. 'page' => 1,
  811. 'whitelist' => ['limit', 'sort', 'page', 'direction'],
  812. ]);
  813. $this->Paginator->paginate($query, $settings);
  814. }
  815. /**
  816. * Helper method for making mocks.
  817. *
  818. * @param array $methods
  819. * @return Table
  820. */
  821. protected function _getMockPosts($methods = [])
  822. {
  823. return $this->getMock(
  824. 'TestApp\Model\Table\PaginatorPostsTable',
  825. $methods,
  826. [[
  827. 'connection' => ConnectionManager::get('test'),
  828. 'alias' => 'PaginatorPosts',
  829. 'schema' => [
  830. 'id' => ['type' => 'integer'],
  831. 'author_id' => ['type' => 'integer', 'null' => false],
  832. 'title' => ['type' => 'string', 'null' => false],
  833. 'body' => 'text',
  834. 'published' => ['type' => 'string', 'length' => 1, 'default' => 'N'],
  835. '_constraints' => ['primary' => ['type' => 'primary', 'columns' => ['id']]]
  836. ]
  837. ]]
  838. );
  839. }
  840. /**
  841. * Helper method for mocking queries.
  842. *
  843. * @return Query
  844. */
  845. protected function _getMockFindQuery($table = null)
  846. {
  847. $query = $this->getMockBuilder('Cake\ORM\Query')
  848. ->setMethods(['total', 'all', 'count', 'applyOptions'])
  849. ->disableOriginalConstructor()
  850. ->getMock();
  851. $results = $this->getMock('Cake\ORM\ResultSet', [], [], '', false);
  852. $query->expects($this->any())
  853. ->method('count')
  854. ->will($this->returnValue(2));
  855. $query->expects($this->any())
  856. ->method('all')
  857. ->will($this->returnValue($results));
  858. $query->expects($this->any())
  859. ->method('count')
  860. ->will($this->returnValue(2));
  861. $query->repository($table);
  862. return $query;
  863. }
  864. }