PaginatorComponentTest.php 33 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073
  1. <?php
  2. /**
  3. * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  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://cakephp.org CakePHP(tm) Project
  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. $config = ConnectionManager::config('test');
  863. $this->skipIf(strpos($config['driver'], 'Sqlserver') !== false, 'Test temporarily broken in SQLServer');
  864. $this->loadFixtures('Posts');
  865. $table = TableRegistry::get('PaginatorPosts');
  866. $query = $table->find()
  867. ->where(['PaginatorPosts.author_id BETWEEN :start AND :end'])
  868. ->bind(':start', 1)
  869. ->bind(':end', 2);
  870. $results = $this->Paginator->paginate($query, []);
  871. $result = $results->toArray();
  872. $this->assertCount(2, $result);
  873. $this->assertEquals('First Post', $result[0]->title);
  874. $this->assertEquals('Third Post', $result[1]->title);
  875. }
  876. /**
  877. * Tests that passing a query object with a limit clause set will
  878. * overwrite it with the passed defaults.
  879. *
  880. * @return void
  881. */
  882. public function testPaginateQueryWithLimit()
  883. {
  884. $this->request->query = ['page' => '-1'];
  885. $settings = [
  886. 'PaginatorPosts' => [
  887. 'contain' => ['PaginatorAuthor'],
  888. 'maxLimit' => 10,
  889. 'limit' => 5,
  890. 'group' => 'PaginatorPosts.published',
  891. 'order' => ['PaginatorPosts.id' => 'ASC']
  892. ]
  893. ];
  894. $table = $this->_getMockPosts(['find']);
  895. $query = $this->_getMockFindQuery($table);
  896. $query->limit(2);
  897. $table->expects($this->never())->method('find');
  898. $query->expects($this->once())
  899. ->method('applyOptions')
  900. ->with([
  901. 'contain' => ['PaginatorAuthor'],
  902. 'group' => 'PaginatorPosts.published',
  903. 'limit' => 5,
  904. 'order' => ['PaginatorPosts.id' => 'ASC'],
  905. 'page' => 1,
  906. 'whitelist' => ['limit', 'sort', 'page', 'direction'],
  907. ]);
  908. $this->Paginator->paginate($query, $settings);
  909. }
  910. /**
  911. * Helper method for making mocks.
  912. *
  913. * @param array $methods
  914. * @return Table
  915. */
  916. protected function _getMockPosts($methods = [])
  917. {
  918. return $this->getMock(
  919. 'TestApp\Model\Table\PaginatorPostsTable',
  920. $methods,
  921. [[
  922. 'connection' => ConnectionManager::get('test'),
  923. 'alias' => 'PaginatorPosts',
  924. 'schema' => [
  925. 'id' => ['type' => 'integer'],
  926. 'author_id' => ['type' => 'integer', 'null' => false],
  927. 'title' => ['type' => 'string', 'null' => false],
  928. 'body' => 'text',
  929. 'published' => ['type' => 'string', 'length' => 1, 'default' => 'N'],
  930. '_constraints' => ['primary' => ['type' => 'primary', 'columns' => ['id']]]
  931. ]
  932. ]]
  933. );
  934. }
  935. /**
  936. * Helper method for mocking queries.
  937. *
  938. * @return Query
  939. */
  940. protected function _getMockFindQuery($table = null)
  941. {
  942. $query = $this->getMockBuilder('Cake\ORM\Query')
  943. ->setMethods(['total', 'all', 'count', 'applyOptions'])
  944. ->disableOriginalConstructor()
  945. ->getMock();
  946. $results = $this->getMock('Cake\ORM\ResultSet', [], [], '', false);
  947. $query->expects($this->any())
  948. ->method('count')
  949. ->will($this->returnValue(2));
  950. $query->expects($this->any())
  951. ->method('all')
  952. ->will($this->returnValue($results));
  953. $query->expects($this->any())
  954. ->method('count')
  955. ->will($this->returnValue(2));
  956. $query->repository($table);
  957. return $query;
  958. }
  959. }