PageRenderTime 45ms CodeModel.GetById 16ms RepoModel.GetById 1ms app.codeStats 0ms

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

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