PageRenderTime 37ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 1ms

/lib/Cake/Test/Case/Error/ExceptionRendererTest.php

https://github.com/Bancha/cakephp
PHP | 663 lines | 379 code | 78 blank | 206 comment | 1 complexity | d0eed74f3e13483f56e7501259ee3760 MD5 | raw file
  1. <?php
  2. /**
  3. * ExceptionRendererTest file
  4. *
  5. * PHP 5
  6. *
  7. * CakePHP(tm) Tests <http://book.cakephp.org/view/1196/Testing>
  8. * Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org)
  9. *
  10. * Licensed under The MIT License
  11. * Redistributions of files must retain the above copyright notice
  12. *
  13. * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org)
  14. * @link http://book.cakephp.org/view/1196/Testing CakePHP(tm) Tests
  15. * @package cake.tests.cases.libs
  16. * @since CakePHP(tm) v 2.0
  17. * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
  18. */
  19. App::uses('ExceptionRenderer', 'Error');
  20. App::uses('Controller', 'Controller');
  21. App::uses('AppController', 'Controller');
  22. App::uses('Component', 'Controller');
  23. App::uses('Router', 'Routing');
  24. /**
  25. * Short description for class.
  26. *
  27. * @package cake.tests.cases.libs
  28. */
  29. class AuthBlueberryUser extends CakeTestModel {
  30. /**
  31. * name property
  32. *
  33. * @var string 'AuthBlueberryUser'
  34. * @access public
  35. */
  36. public $name = 'AuthBlueberryUser';
  37. /**
  38. * useTable property
  39. *
  40. * @var string
  41. * @access public
  42. */
  43. public $useTable = false;
  44. }
  45. /**
  46. * BlueberryComponent class
  47. *
  48. * @package cake.tests.cases.libs
  49. */
  50. class BlueberryComponent extends Component {
  51. /**
  52. * testName property
  53. *
  54. * @access public
  55. * @return void
  56. */
  57. public $testName = null;
  58. /**
  59. * initialize method
  60. *
  61. * @access public
  62. * @return void
  63. */
  64. function initialize(&$controller) {
  65. $this->testName = 'BlueberryComponent';
  66. }
  67. }
  68. /**
  69. * TestErrorController class
  70. *
  71. * @package cake.tests.cases.libs
  72. */
  73. class TestErrorController extends Controller {
  74. /**
  75. * uses property
  76. *
  77. * @var array
  78. * @access public
  79. */
  80. public $uses = array();
  81. /**
  82. * components property
  83. *
  84. * @access public
  85. * @return void
  86. */
  87. public $components = array('Blueberry');
  88. /**
  89. * beforeRender method
  90. *
  91. * @access public
  92. * @return void
  93. */
  94. function beforeRender() {
  95. echo $this->Blueberry->testName;
  96. }
  97. /**
  98. * index method
  99. *
  100. * @access public
  101. * @return void
  102. */
  103. function index() {
  104. $this->autoRender = false;
  105. return 'what up';
  106. }
  107. }
  108. /**
  109. * MyCustomExceptionRenderer class
  110. *
  111. * @package cake.tests.cases.libs
  112. */
  113. class MyCustomExceptionRenderer extends ExceptionRenderer {
  114. /**
  115. * custom error message type.
  116. *
  117. * @return void
  118. */
  119. function missingWidgetThing() {
  120. echo 'widget thing is missing';
  121. }
  122. }
  123. /**
  124. * Exception class for testing app error handlers and custom errors.
  125. *
  126. * @package cake.test.cases.libs
  127. */
  128. class MissingWidgetThingException extends NotFoundException { }
  129. /**
  130. * ExceptionRendererTest class
  131. *
  132. * @package cake.tests.cases.libs
  133. */
  134. class ExceptionRendererTest extends CakeTestCase {
  135. var $_restoreError = false;
  136. /**
  137. * setup create a request object to get out of router later.
  138. *
  139. * @return void
  140. */
  141. function setUp() {
  142. App::build(array(
  143. 'views' => array(
  144. CAKE . 'Test' . DS . 'test_app' . DS . 'View'. DS
  145. )
  146. ), true);
  147. Router::reload();
  148. $request = new CakeRequest(null, false);
  149. $request->base = '';
  150. Router::setRequestInfo($request);
  151. $this->_debug = Configure::read('debug');
  152. $this->_error = Configure::read('Error');
  153. Configure::write('debug', 2);
  154. }
  155. /**
  156. * teardown
  157. *
  158. * @return void
  159. */
  160. function teardown() {
  161. Configure::write('debug', $this->_debug);
  162. Configure::write('Error', $this->_error);
  163. App::build();
  164. if ($this->_restoreError) {
  165. restore_error_handler();
  166. }
  167. }
  168. /**
  169. * Mocks out the response on the ExceptionRenderer object so headers aren't modified.
  170. *
  171. * @return void
  172. */
  173. protected function _mockResponse($error) {
  174. $error->controller->response = $this->getMock('CakeResponse', array('_sendHeader'));
  175. return $error;
  176. }
  177. /**
  178. * test that methods declared in an ExceptionRenderer subclass are not converted
  179. * into error400 when debug > 0
  180. *
  181. * @return void
  182. */
  183. function testSubclassMethodsNotBeingConvertedToError() {
  184. Configure::write('debug', 2);
  185. $exception = new MissingWidgetThingException('Widget not found');
  186. $ExceptionRenderer = $this->_mockResponse(new MyCustomExceptionRenderer($exception));
  187. ob_start();
  188. $ExceptionRenderer->render();
  189. $result = ob_get_clean();
  190. $this->assertEqual($result, 'widget thing is missing');
  191. }
  192. /**
  193. * test that subclass methods are not converted when debug = 0
  194. *
  195. * @return void
  196. */
  197. function testSubclassMethodsNotBeingConvertedDebug0() {
  198. Configure::write('debug', 0);
  199. $exception = new MissingWidgetThingException('Widget not found');
  200. $ExceptionRenderer = $this->_mockResponse(new MyCustomExceptionRenderer($exception));
  201. $this->assertEqual('missingWidgetThing', $ExceptionRenderer->method);
  202. ob_start();
  203. $ExceptionRenderer->render();
  204. $result = ob_get_clean();
  205. $this->assertEqual($result, 'widget thing is missing', 'Method declared in subclass converted to error400');
  206. }
  207. /**
  208. * test that ExceptionRenderer subclasses properly convert framework errors.
  209. *
  210. * @return void
  211. */
  212. function testSubclassConvertingFrameworkErrors() {
  213. Configure::write('debug', 0);
  214. $exception = new MissingControllerException('PostsController');
  215. $ExceptionRenderer = $this->_mockResponse(new MyCustomExceptionRenderer($exception));
  216. $this->assertEqual('error400', $ExceptionRenderer->method);
  217. ob_start();
  218. $ExceptionRenderer->render();
  219. $result = ob_get_clean();
  220. $this->assertPattern('/Not Found/', $result, 'Method declared in error handler not converted to error400. %s');
  221. }
  222. /**
  223. * test things in the constructor.
  224. *
  225. * @return void
  226. */
  227. function testConstruction() {
  228. $exception = new NotFoundException('Page not found');
  229. $ExceptionRenderer = new ExceptionRenderer($exception);
  230. $this->assertInstanceOf('CakeErrorController', $ExceptionRenderer->controller);
  231. $this->assertEquals('error400', $ExceptionRenderer->method);
  232. $this->assertEquals($exception, $ExceptionRenderer->error);
  233. }
  234. /**
  235. * test that method gets coerced when debug = 0
  236. *
  237. * @return void
  238. */
  239. function testErrorMethodCoercion() {
  240. Configure::write('debug', 0);
  241. $exception = new MissingActionException('Page not found');
  242. $ExceptionRenderer = new ExceptionRenderer($exception);
  243. $this->assertInstanceOf('CakeErrorController', $ExceptionRenderer->controller);
  244. $this->assertEquals('error400', $ExceptionRenderer->method);
  245. $this->assertEquals($exception, $ExceptionRenderer->error);
  246. }
  247. /**
  248. * test that unknown exception types with valid status codes are treated correctly.
  249. *
  250. * @return void
  251. */
  252. function testUnknownExceptionTypeWithExceptionThatHasA400Code() {
  253. $exception = new MissingWidgetThingException('coding fail.');
  254. $ExceptionRenderer = new ExceptionRenderer($exception);
  255. $ExceptionRenderer->controller->response = $this->getMock('CakeResponse', array('statusCode', '_sendHeader'));
  256. $ExceptionRenderer->controller->response->expects($this->once())->method('statusCode')->with(404);
  257. ob_start();
  258. $ExceptionRenderer->render();
  259. $results = ob_get_clean();
  260. $this->assertFalse(method_exists($ExceptionRenderer, 'missingWidgetThing'), 'no method should exist.');
  261. $this->assertEquals('error400', $ExceptionRenderer->method, 'incorrect method coercion.');
  262. }
  263. /**
  264. * test that unknown exception types with valid status codes are treated correctly.
  265. *
  266. * @return void
  267. */
  268. function testUnknownExceptionTypeWithNoCodeIsA500() {
  269. $exception = new OutOfBoundsException('foul ball.');
  270. $ExceptionRenderer = new ExceptionRenderer($exception);
  271. $ExceptionRenderer->controller->response = $this->getMock('CakeResponse', array('statusCode', '_sendHeader'));
  272. $ExceptionRenderer->controller->response->expects($this->once())->method('statusCode')->with(500);
  273. ob_start();
  274. $ExceptionRenderer->render();
  275. $results = ob_get_clean();
  276. $this->assertEquals('error500', $ExceptionRenderer->method, 'incorrect method coercion.');
  277. }
  278. /**
  279. * test that unknown exception types with valid status codes are treated correctly.
  280. *
  281. * @return void
  282. */
  283. function testUnknownExceptionTypeWithCodeHigherThan500() {
  284. $exception = new OutOfBoundsException('foul ball.', 501);
  285. $ExceptionRenderer = new ExceptionRenderer($exception);
  286. $ExceptionRenderer->controller->response = $this->getMock('CakeResponse', array('statusCode', '_sendHeader'));
  287. $ExceptionRenderer->controller->response->expects($this->once())->method('statusCode')->with(501);
  288. ob_start();
  289. $ExceptionRenderer->render();
  290. $results = ob_get_clean();
  291. $this->assertEquals('error500', $ExceptionRenderer->method, 'incorrect method coercion.');
  292. }
  293. /**
  294. * testerror400 method
  295. *
  296. * @access public
  297. * @return void
  298. */
  299. function testError400() {
  300. Router::reload();
  301. $request = new CakeRequest('posts/view/1000', false);
  302. Router::setRequestInfo($request);
  303. $exception = new NotFoundException('Custom message');
  304. $ExceptionRenderer = new ExceptionRenderer($exception);
  305. $ExceptionRenderer->controller->response = $this->getMock('CakeResponse', array('statusCode', '_sendHeader'));
  306. $ExceptionRenderer->controller->response->expects($this->once())->method('statusCode')->with(404);
  307. ob_start();
  308. $ExceptionRenderer->render();
  309. $result = ob_get_clean();
  310. $this->assertPattern('/<h2>Custom message<\/h2>/', $result);
  311. $this->assertPattern("/<strong>'.*?\/posts\/view\/1000'<\/strong>/", $result);
  312. }
  313. /**
  314. * test that error400 only modifies the messages on CakeExceptions.
  315. *
  316. * @return void
  317. */
  318. function testerror400OnlyChangingCakeException() {
  319. Configure::write('debug', 0);
  320. $exception = new NotFoundException('Custom message');
  321. $ExceptionRenderer = $this->_mockResponse(new ExceptionRenderer($exception));
  322. ob_start();
  323. $ExceptionRenderer->render();
  324. $result = ob_get_clean();
  325. $this->assertContains('Custom message', $result);
  326. $exception = new MissingActionException(array('controller' => 'PostsController', 'action' => 'index'));
  327. $ExceptionRenderer = $this->_mockResponse(new ExceptionRenderer($exception));
  328. ob_start();
  329. $ExceptionRenderer->render();
  330. $result = ob_get_clean();
  331. $this->assertContains('Not Found', $result);
  332. }
  333. /**
  334. * test that error400 doesn't expose XSS
  335. *
  336. * @return void
  337. */
  338. function testError400NoInjection() {
  339. Router::reload();
  340. $request = new CakeRequest('pages/<span id=333>pink</span></id><script>document.body.style.background = t=document.getElementById(333).innerHTML;window.alert(t);</script>', false);
  341. Router::setRequestInfo($request);
  342. $exception = new NotFoundException('Custom message');
  343. $ExceptionRenderer = $this->_mockResponse(new ExceptionRenderer($exception));
  344. ob_start();
  345. $ExceptionRenderer->render();
  346. $result = ob_get_clean();
  347. $this->assertNoPattern('#<script>document#', $result);
  348. $this->assertNoPattern('#alert\(t\);</script>#', $result);
  349. }
  350. /**
  351. * testError500 method
  352. *
  353. * @access public
  354. * @return void
  355. */
  356. function testError500Message() {
  357. $exception = new InternalErrorException('An Internal Error Has Occurred');
  358. $ExceptionRenderer = new ExceptionRenderer($exception);
  359. $ExceptionRenderer->controller->response = $this->getMock('CakeResponse', array('statusCode', '_sendHeader'));
  360. $ExceptionRenderer->controller->response->expects($this->once())->method('statusCode')->with(500);
  361. ob_start();
  362. $ExceptionRenderer->render();
  363. $result = ob_get_clean();
  364. $this->assertPattern('/<h2>An Internal Error Has Occurred<\/h2>/', $result);
  365. }
  366. /**
  367. * testMissingController method
  368. *
  369. * @access public
  370. * @return void
  371. */
  372. function testMissingController() {
  373. $exception = new MissingControllerException(array('controller' => 'PostsController'));
  374. $ExceptionRenderer = $this->_mockResponse(new ExceptionRenderer($exception));
  375. ob_start();
  376. $ExceptionRenderer->render();
  377. $result = ob_get_clean();
  378. $this->assertPattern('/<h2>Missing Controller<\/h2>/', $result);
  379. $this->assertPattern('/<em>PostsController<\/em>/', $result);
  380. }
  381. /**
  382. * Returns an array of tests to run for the various CakeException classes.
  383. *
  384. * @return void
  385. */
  386. public static function testProvider() {
  387. return array(
  388. array(
  389. new MissingActionException(array('controller' => 'PostsController', 'action' => 'index')),
  390. array(
  391. '/<h2>Missing Method in PostsController<\/h2>/',
  392. '/<em>PostsController::<\/em><em>index\(\)<\/em>/'
  393. ),
  394. 404
  395. ),
  396. array(
  397. new PrivateActionException(array('controller' => 'PostsController' , 'action' => '_secretSauce')),
  398. array(
  399. '/<h2>Private Method in PostsController<\/h2>/',
  400. '/<em>PostsController::<\/em><em>_secretSauce\(\)<\/em>/'
  401. ),
  402. 404
  403. ),
  404. array(
  405. new MissingTableException(array('table' => 'articles', 'class' => 'Article')),
  406. array(
  407. '/<h2>Missing Database Table<\/h2>/',
  408. '/table <em>articles<\/em> for model <em>Article<\/em>/'
  409. ),
  410. 500
  411. ),
  412. array(
  413. new MissingDatabaseException(array('connection' => 'default')),
  414. array(
  415. '/<h2>Missing Database Connection<\/h2>/',
  416. '/Confirm you have created the file/'
  417. ),
  418. 500
  419. ),
  420. array(
  421. new MissingViewException(array('file' => '/posts/about.ctp')),
  422. array(
  423. "/posts\/about.ctp/"
  424. ),
  425. 500
  426. ),
  427. array(
  428. new MissingLayoutException(array('file' => 'layouts/my_layout.ctp')),
  429. array(
  430. "/Missing Layout/",
  431. "/layouts\/my_layout.ctp/"
  432. ),
  433. 500
  434. ),
  435. array(
  436. new MissingConnectionException(array('class' => 'Article')),
  437. array(
  438. '/<h2>Missing Database Connection<\/h2>/',
  439. '/Article requires a database connection/'
  440. ),
  441. 500
  442. ),
  443. array(
  444. new MissingHelperFileException(array('file' => 'my_custom.php', 'class' => 'MyCustomHelper')),
  445. array(
  446. '/<h2>Missing Helper File<\/h2>/',
  447. '/Create the class below in file:/',
  448. '/(\/|\\\)my_custom.php/'
  449. ),
  450. 500
  451. ),
  452. array(
  453. new MissingHelperClassException(array('file' => 'my_custom.php', 'class' => 'MyCustomHelper')),
  454. array(
  455. '/<h2>Missing Helper Class<\/h2>/',
  456. '/The helper class <em>MyCustomHelper<\/em> can not be found or does not exist./',
  457. '/(\/|\\\)my_custom.php/',
  458. ),
  459. 500
  460. ),
  461. array(
  462. new MissingBehaviorFileException(array('file' => 'my_custom.php', 'class' => 'MyCustomBehavior')),
  463. array(
  464. '/<h2>Missing Behavior File<\/h2>/',
  465. '/Create the class below in file:/',
  466. '/(\/|\\\)my_custom.php/',
  467. ),
  468. 500
  469. ),
  470. array(
  471. new MissingBehaviorClassException(array('file' => 'my_custom.php', 'class' => 'MyCustomBehavior')),
  472. array(
  473. '/The behavior class <em>MyCustomBehavior<\/em> can not be found or does not exist./',
  474. '/(\/|\\\)my_custom.php/'
  475. ),
  476. 500
  477. ),
  478. array(
  479. new MissingComponentFileException(array('file' => 'sidebox.php', 'class' => 'SideboxComponent')),
  480. array(
  481. '/<h2>Missing Component File<\/h2>/',
  482. '/Create the class <em>SideboxComponent<\/em> in file:/',
  483. '/(\/|\\\)sidebox.php/'
  484. ),
  485. 500
  486. ),
  487. array(
  488. new MissingComponentClassException(array('file' => 'sidebox.php', 'class' => 'SideboxComponent')),
  489. array(
  490. '/<h2>Missing Component Class<\/h2>/',
  491. '/Create the class <em>SideboxComponent<\/em> in file:/',
  492. '/(\/|\\\)sidebox.php/'
  493. ),
  494. 500
  495. ),
  496. array(
  497. new Exception('boom'),
  498. array(
  499. '/Internal Error/'
  500. ),
  501. 500
  502. ),
  503. array(
  504. new RuntimeException('another boom'),
  505. array(
  506. '/Internal Error/'
  507. ),
  508. 500
  509. ),
  510. array(
  511. new CakeException('base class'),
  512. array('/Internal Error/'),
  513. 500
  514. ),
  515. array(
  516. new ConfigureException('No file'),
  517. array('/Internal Error/'),
  518. 500
  519. )
  520. );
  521. }
  522. /**
  523. * Test the various CakeException sub classes
  524. *
  525. * @dataProvider testProvider
  526. * @return void
  527. */
  528. function testCakeExceptionHandling($exception, $patterns, $code) {
  529. $ExceptionRenderer = new ExceptionRenderer($exception);
  530. $ExceptionRenderer->controller->response = $this->getMock('CakeResponse', array('statusCode', '_sendHeader'));
  531. $ExceptionRenderer->controller->response->expects($this->once())
  532. ->method('statusCode')
  533. ->with($code);
  534. ob_start();
  535. $ExceptionRenderer->render();
  536. $result = ob_get_clean();
  537. foreach ($patterns as $pattern) {
  538. $this->assertPattern($pattern, $result);
  539. }
  540. }
  541. /**
  542. * Test exceptions being raised when helpers are missing.
  543. *
  544. * @return void
  545. */
  546. function testMissingRenderSafe() {
  547. $exception = new MissingHelperFileException(array('class' => 'Fail'));
  548. $ExceptionRenderer = new ExceptionRenderer($exception);
  549. $ExceptionRenderer->controller = $this->getMock('Controller');
  550. $ExceptionRenderer->controller->helpers = array('Fail', 'Boom');
  551. $ExceptionRenderer->controller->request = $this->getMock('CakeRequest');
  552. $ExceptionRenderer->controller->expects($this->at(2))
  553. ->method('render')
  554. ->with('missingHelperFile')
  555. ->will($this->throwException($exception));
  556. $ExceptionRenderer->controller->expects($this->at(3))
  557. ->method('render')
  558. ->with('error500')
  559. ->will($this->returnValue(true));
  560. $ExceptionRenderer->controller->response = $this->getMock('CakeResponse');
  561. $ExceptionRenderer->render();
  562. sort($ExceptionRenderer->controller->helpers);
  563. $this->assertEquals(array('Form', 'Html', 'Session'), $ExceptionRenderer->controller->helpers);
  564. }
  565. /**
  566. * Test that exceptions can be rendered when an request hasn't been registered
  567. * with Router
  568. *
  569. * @return void
  570. */
  571. function testRenderWithNoRequest() {
  572. Router::reload();
  573. $this->assertNull(Router::getRequest(false));
  574. $exception = new Exception('Terrible');
  575. $ExceptionRenderer = new ExceptionRenderer($exception);
  576. $ExceptionRenderer->controller->response = $this->getMock('CakeResponse', array('statusCode', '_sendHeader'));
  577. $ExceptionRenderer->controller->response->expects($this->once())
  578. ->method('statusCode')
  579. ->with(500);
  580. ob_start();
  581. $ExceptionRenderer->render();
  582. $result = ob_get_clean();
  583. $this->assertContains('Internal Error', $result);
  584. }
  585. }