PaginatorComponentTest.php 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071
  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->once())
  502. ->method('hasField')
  503. ->will($this->returnValue(true));
  504. $options = [
  505. 'sort' => 'body',
  506. 'direction' => 'asc',
  507. 'sortWhitelist' => ['body']
  508. ];
  509. $result = $this->Paginator->validateSort($model, $options);
  510. $expected = ['model.body' => 'asc'];
  511. $this->assertEquals(
  512. $expected,
  513. $result['order'],
  514. 'Trusted fields in schema should be prefixed'
  515. );
  516. }
  517. /**
  518. * test that whitelist as empty array does not allow any sorting
  519. *
  520. * @return void
  521. */
  522. public function testValidateSortWhitelistEmpty()
  523. {
  524. $model = $this->getMock('Cake\ORM\Table');
  525. $model->expects($this->any())
  526. ->method('alias')
  527. ->will($this->returnValue('model'));
  528. $model->expects($this->any())->method('hasField')
  529. ->will($this->returnValue(true));
  530. $options = [
  531. 'order' => [
  532. 'body' => 'asc',
  533. 'foo.bar' => 'asc'
  534. ],
  535. 'sort' => 'body',
  536. 'direction' => 'asc',
  537. 'sortWhitelist' => []
  538. ];
  539. $result = $this->Paginator->validateSort($model, $options);
  540. $this->assertSame([], $result['order'], 'No sort should be applied');
  541. }
  542. /**
  543. * test that fields in the whitelist are not validated
  544. *
  545. * @return void
  546. */
  547. public function testValidateSortWhitelistNotInSchema()
  548. {
  549. $model = $this->getMock('Cake\ORM\Table');
  550. $model->expects($this->any())
  551. ->method('alias')
  552. ->will($this->returnValue('model'));
  553. $model->expects($this->once())->method('hasField')
  554. ->will($this->returnValue(false));
  555. $options = [
  556. 'sort' => 'score',
  557. 'direction' => 'asc',
  558. 'sortWhitelist' => ['score']
  559. ];
  560. $result = $this->Paginator->validateSort($model, $options);
  561. $expected = ['score' => 'asc'];
  562. $this->assertEquals(
  563. $expected,
  564. $result['order'],
  565. 'Trusted fields not in schema should not be altered'
  566. );
  567. }
  568. /**
  569. * test that multiple fields in the whitelist are not validated and properly aliased.
  570. *
  571. * @return void
  572. */
  573. public function testValidateSortWhitelistMultiple()
  574. {
  575. $model = $this->getMock('Cake\ORM\Table');
  576. $model->expects($this->any())
  577. ->method('alias')
  578. ->will($this->returnValue('model'));
  579. $model->expects($this->once())
  580. ->method('hasField')
  581. ->will($this->returnValue(true));
  582. $options = [
  583. 'order' => [
  584. 'body' => 'asc',
  585. 'foo.bar' => 'asc'
  586. ],
  587. 'sortWhitelist' => ['body', 'foo.bar']
  588. ];
  589. $result = $this->Paginator->validateSort($model, $options);
  590. $expected = [
  591. 'model.body' => 'asc',
  592. 'foo.bar' => 'asc'
  593. ];
  594. $this->assertEquals($expected, $result['order']);
  595. }
  596. /**
  597. * test that multiple sort works.
  598. *
  599. * @return void
  600. */
  601. public function testValidateSortMultiple()
  602. {
  603. $model = $this->getMock('Cake\ORM\Table');
  604. $model->expects($this->any())
  605. ->method('alias')
  606. ->will($this->returnValue('model'));
  607. $model->expects($this->any())->method('hasField')->will($this->returnValue(true));
  608. $options = [
  609. 'order' => [
  610. 'author_id' => 'asc',
  611. 'title' => 'asc'
  612. ]
  613. ];
  614. $result = $this->Paginator->validateSort($model, $options);
  615. $expected = [
  616. 'model.author_id' => 'asc',
  617. 'model.title' => 'asc'
  618. ];
  619. $this->assertEquals($expected, $result['order']);
  620. }
  621. /**
  622. * Tests that order strings can used by Paginator
  623. *
  624. * @return void
  625. */
  626. public function testValidateSortWithString()
  627. {
  628. $model = $this->getMock('Cake\ORM\Table');
  629. $model->expects($this->any())
  630. ->method('alias')
  631. ->will($this->returnValue('model'));
  632. $model->expects($this->any())->method('hasField')->will($this->returnValue(true));
  633. $options = [
  634. 'order' => 'model.author_id DESC'
  635. ];
  636. $result = $this->Paginator->validateSort($model, $options);
  637. $expected = 'model.author_id DESC';
  638. $this->assertEquals($expected, $result['order']);
  639. }
  640. /**
  641. * Test that no sort doesn't trigger an error.
  642. *
  643. * @return void
  644. */
  645. public function testValidateSortNoSort()
  646. {
  647. $model = $this->getMock('Cake\ORM\Table');
  648. $model->expects($this->any())
  649. ->method('alias')
  650. ->will($this->returnValue('model'));
  651. $model->expects($this->any())->method('hasField')
  652. ->will($this->returnValue(true));
  653. $options = [
  654. 'direction' => 'asc',
  655. 'sortWhitelist' => ['title', 'id'],
  656. ];
  657. $result = $this->Paginator->validateSort($model, $options);
  658. $this->assertEquals([], $result['order']);
  659. }
  660. /**
  661. * Test sorting with incorrect aliases on valid fields.
  662. *
  663. * @return void
  664. */
  665. public function testValidateSortInvalidAlias()
  666. {
  667. $model = $this->getMock('Cake\ORM\Table');
  668. $model->expects($this->any())
  669. ->method('alias')
  670. ->will($this->returnValue('model'));
  671. $model->expects($this->any())->method('hasField')->will($this->returnValue(true));
  672. $options = ['sort' => 'Derp.id'];
  673. $result = $this->Paginator->validateSort($model, $options);
  674. $this->assertEquals([], $result['order']);
  675. }
  676. /**
  677. * test that maxLimit is respected
  678. *
  679. * @return void
  680. */
  681. public function testCheckLimit()
  682. {
  683. $result = $this->Paginator->checkLimit(['limit' => 1000000, 'maxLimit' => 100]);
  684. $this->assertEquals(100, $result['limit']);
  685. $result = $this->Paginator->checkLimit(['limit' => 'sheep!', 'maxLimit' => 100]);
  686. $this->assertEquals(1, $result['limit']);
  687. $result = $this->Paginator->checkLimit(['limit' => '-1', 'maxLimit' => 100]);
  688. $this->assertEquals(1, $result['limit']);
  689. $result = $this->Paginator->checkLimit(['limit' => null, 'maxLimit' => 100]);
  690. $this->assertEquals(1, $result['limit']);
  691. $result = $this->Paginator->checkLimit(['limit' => 0, 'maxLimit' => 100]);
  692. $this->assertEquals(1, $result['limit']);
  693. }
  694. /**
  695. * Integration test for checkLimit() being applied inside paginate()
  696. *
  697. * @return void
  698. */
  699. public function testPaginateMaxLimit()
  700. {
  701. $this->loadFixtures('Posts');
  702. $table = TableRegistry::get('PaginatorPosts');
  703. $settings = [
  704. 'maxLimit' => 100,
  705. ];
  706. $this->request->query = [
  707. 'limit' => '1000'
  708. ];
  709. $this->Paginator->paginate($table, $settings);
  710. $this->assertEquals(100, $this->request->params['paging']['PaginatorPosts']['limit']);
  711. $this->assertEquals(100, $this->request->params['paging']['PaginatorPosts']['perPage']);
  712. $this->request->query = [
  713. 'limit' => '10'
  714. ];
  715. $this->Paginator->paginate($table, $settings);
  716. $this->assertEquals(10, $this->request->params['paging']['PaginatorPosts']['limit']);
  717. $this->assertEquals(10, $this->request->params['paging']['PaginatorPosts']['perPage']);
  718. }
  719. /**
  720. * test paginate() and custom find, to make sure the correct count is returned.
  721. *
  722. * @return void
  723. */
  724. public function testPaginateCustomFind()
  725. {
  726. $this->loadFixtures('Posts');
  727. $titleExtractor = function ($result) {
  728. $ids = [];
  729. foreach ($result as $record) {
  730. $ids[] = $record->title;
  731. }
  732. return $ids;
  733. };
  734. $table = TableRegistry::get('PaginatorPosts');
  735. $data = ['author_id' => 3, 'title' => 'Fourth Post', 'body' => 'Article Body, unpublished', 'published' => 'N'];
  736. $result = $table->save(new \Cake\ORM\Entity($data));
  737. $this->assertNotEmpty($result);
  738. $result = $this->Paginator->paginate($table);
  739. $this->assertCount(4, $result, '4 rows should come back');
  740. $this->assertEquals(['First Post', 'Second Post', 'Third Post', 'Fourth Post'], $titleExtractor($result));
  741. $result = $this->request->params['paging']['PaginatorPosts'];
  742. $this->assertEquals(4, $result['current']);
  743. $this->assertEquals(4, $result['count']);
  744. $settings = ['finder' => 'published'];
  745. $result = $this->Paginator->paginate($table, $settings);
  746. $this->assertCount(3, $result, '3 rows should come back');
  747. $this->assertEquals(['First Post', 'Second Post', 'Third Post'], $titleExtractor($result));
  748. $result = $this->request->params['paging']['PaginatorPosts'];
  749. $this->assertEquals(3, $result['current']);
  750. $this->assertEquals(3, $result['count']);
  751. $settings = ['finder' => 'published', 'limit' => 2, 'page' => 2];
  752. $result = $this->Paginator->paginate($table, $settings);
  753. $this->assertCount(1, $result, '1 rows should come back');
  754. $this->assertEquals(['Third Post'], $titleExtractor($result));
  755. $result = $this->request->params['paging']['PaginatorPosts'];
  756. $this->assertEquals(1, $result['current']);
  757. $this->assertEquals(3, $result['count']);
  758. $this->assertEquals(2, $result['pageCount']);
  759. $settings = ['finder' => 'published', 'limit' => 2];
  760. $result = $this->Paginator->paginate($table, $settings);
  761. $this->assertCount(2, $result, '2 rows should come back');
  762. $this->assertEquals(['First Post', 'Second Post'], $titleExtractor($result));
  763. $result = $this->request->params['paging']['PaginatorPosts'];
  764. $this->assertEquals(2, $result['current']);
  765. $this->assertEquals(3, $result['count']);
  766. $this->assertEquals(2, $result['pageCount']);
  767. $this->assertTrue($result['nextPage']);
  768. $this->assertFalse($result['prevPage']);
  769. $this->assertEquals(2, $result['perPage']);
  770. $this->assertNull($result['limit']);
  771. }
  772. /**
  773. * test paginate() and custom find with fields array, to make sure the correct count is returned.
  774. *
  775. * @return void
  776. */
  777. public function testPaginateCustomFindFieldsArray()
  778. {
  779. $this->loadFixtures('Posts');
  780. $table = TableRegistry::get('PaginatorPosts');
  781. $data = ['author_id' => 3, 'title' => 'Fourth Article', 'body' => 'Article Body, unpublished', 'published' => 'N'];
  782. $table->save(new \Cake\ORM\Entity($data));
  783. $settings = [
  784. 'finder' => 'list',
  785. 'conditions' => ['PaginatorPosts.published' => 'Y'],
  786. 'limit' => 2
  787. ];
  788. $results = $this->Paginator->paginate($table, $settings);
  789. $result = $results->toArray();
  790. $expected = [
  791. 1 => 'First Post',
  792. 2 => 'Second Post',
  793. ];
  794. $this->assertEquals($expected, $result);
  795. $result = $this->request->params['paging']['PaginatorPosts'];
  796. $this->assertEquals(2, $result['current']);
  797. $this->assertEquals(3, $result['count']);
  798. $this->assertEquals(2, $result['pageCount']);
  799. $this->assertTrue($result['nextPage']);
  800. $this->assertFalse($result['prevPage']);
  801. }
  802. /**
  803. * test paginate() and custom finders to ensure the count + find
  804. * use the custom type.
  805. *
  806. * @return void
  807. */
  808. public function testPaginateCustomFindCount()
  809. {
  810. $settings = [
  811. 'finder' => 'published',
  812. 'limit' => 2
  813. ];
  814. $table = $this->_getMockPosts(['query']);
  815. $query = $this->_getMockFindQuery();
  816. $table->expects($this->once())
  817. ->method('query')
  818. ->will($this->returnValue($query));
  819. $query->expects($this->once())->method('applyOptions')
  820. ->with(['limit' => 2, 'page' => 1, 'order' => [], 'whitelist' => ['limit', 'sort', 'page', 'direction']]);
  821. $this->Paginator->paginate($table, $settings);
  822. }
  823. /**
  824. * Tests that it is possible to pass an already made query object to
  825. * paginate()
  826. *
  827. * @return void
  828. */
  829. public function testPaginateQuery()
  830. {
  831. $this->request->query = ['page' => '-1'];
  832. $settings = [
  833. 'PaginatorPosts' => [
  834. 'contain' => ['PaginatorAuthor'],
  835. 'maxLimit' => 10,
  836. 'group' => 'PaginatorPosts.published',
  837. 'order' => ['PaginatorPosts.id' => 'ASC']
  838. ]
  839. ];
  840. $table = $this->_getMockPosts(['find']);
  841. $query = $this->_getMockFindQuery($table);
  842. $table->expects($this->never())->method('find');
  843. $query->expects($this->once())
  844. ->method('applyOptions')
  845. ->with([
  846. 'contain' => ['PaginatorAuthor'],
  847. 'group' => 'PaginatorPosts.published',
  848. 'limit' => 10,
  849. 'order' => ['PaginatorPosts.id' => 'ASC'],
  850. 'page' => 1,
  851. 'whitelist' => ['limit', 'sort', 'page', 'direction'],
  852. ]);
  853. $this->Paginator->paginate($query, $settings);
  854. }
  855. /**
  856. * test paginate() with bind()
  857. *
  858. * @return void
  859. */
  860. public function testPaginateQueryWithBindValue()
  861. {
  862. $this->loadFixtures('Posts');
  863. $table = TableRegistry::get('PaginatorPosts');
  864. $query = $table->find()
  865. ->where(['PaginatorPosts.author_id BETWEEN :start AND :end'])
  866. ->bind(':start', 1)
  867. ->bind(':end', 2);
  868. $results = $this->Paginator->paginate($query, []);
  869. $result = $results->toArray();
  870. $this->assertCount(2, $result);
  871. $this->assertEquals('First Post', $result[0]->title);
  872. $this->assertEquals('Third Post', $result[1]->title);
  873. }
  874. /**
  875. * Tests that passing a query object with a limit clause set will
  876. * overwrite it with the passed defaults.
  877. *
  878. * @return void
  879. */
  880. public function testPaginateQueryWithLimit()
  881. {
  882. $this->request->query = ['page' => '-1'];
  883. $settings = [
  884. 'PaginatorPosts' => [
  885. 'contain' => ['PaginatorAuthor'],
  886. 'maxLimit' => 10,
  887. 'limit' => 5,
  888. 'group' => 'PaginatorPosts.published',
  889. 'order' => ['PaginatorPosts.id' => 'ASC']
  890. ]
  891. ];
  892. $table = $this->_getMockPosts(['find']);
  893. $query = $this->_getMockFindQuery($table);
  894. $query->limit(2);
  895. $table->expects($this->never())->method('find');
  896. $query->expects($this->once())
  897. ->method('applyOptions')
  898. ->with([
  899. 'contain' => ['PaginatorAuthor'],
  900. 'group' => 'PaginatorPosts.published',
  901. 'limit' => 5,
  902. 'order' => ['PaginatorPosts.id' => 'ASC'],
  903. 'page' => 1,
  904. 'whitelist' => ['limit', 'sort', 'page', 'direction'],
  905. ]);
  906. $this->Paginator->paginate($query, $settings);
  907. }
  908. /**
  909. * Helper method for making mocks.
  910. *
  911. * @param array $methods
  912. * @return Table
  913. */
  914. protected function _getMockPosts($methods = [])
  915. {
  916. return $this->getMock(
  917. 'TestApp\Model\Table\PaginatorPostsTable',
  918. $methods,
  919. [[
  920. 'connection' => ConnectionManager::get('test'),
  921. 'alias' => 'PaginatorPosts',
  922. 'schema' => [
  923. 'id' => ['type' => 'integer'],
  924. 'author_id' => ['type' => 'integer', 'null' => false],
  925. 'title' => ['type' => 'string', 'null' => false],
  926. 'body' => 'text',
  927. 'published' => ['type' => 'string', 'length' => 1, 'default' => 'N'],
  928. '_constraints' => ['primary' => ['type' => 'primary', 'columns' => ['id']]]
  929. ]
  930. ]]
  931. );
  932. }
  933. /**
  934. * Helper method for mocking queries.
  935. *
  936. * @return Query
  937. */
  938. protected function _getMockFindQuery($table = null)
  939. {
  940. $query = $this->getMockBuilder('Cake\ORM\Query')
  941. ->setMethods(['total', 'all', 'count', 'applyOptions'])
  942. ->disableOriginalConstructor()
  943. ->getMock();
  944. $results = $this->getMock('Cake\ORM\ResultSet', [], [], '', false);
  945. $query->expects($this->any())
  946. ->method('count')
  947. ->will($this->returnValue(2));
  948. $query->expects($this->any())
  949. ->method('all')
  950. ->will($this->returnValue($results));
  951. $query->expects($this->any())
  952. ->method('count')
  953. ->will($this->returnValue(2));
  954. $query->repository($table);
  955. return $query;
  956. }
  957. }