ExceptionRendererTest.php 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827
  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\Error;
  16. use Cake\Controller\Component;
  17. use Cake\Controller\Controller;
  18. use Cake\Controller\Exception\MissingActionException;
  19. use Cake\Controller\Exception\MissingComponentException;
  20. use Cake\Core\App;
  21. use Cake\Core\Configure;
  22. use Cake\Core\Exception\MissingPluginException;
  23. use Cake\Core\Plugin;
  24. use Cake\Datasource\Exception\MissingDatasourceConfigException;
  25. use Cake\Datasource\Exception\MissingDatasourceException;
  26. use Cake\Error;
  27. use Cake\Error\ExceptionRenderer;
  28. use Cake\Event\Event;
  29. use Cake\Event\EventManager;
  30. use Cake\Network\Exception\InternalErrorException;
  31. use Cake\Network\Exception\MethodNotAllowedException;
  32. use Cake\Network\Exception\NotFoundException;
  33. use Cake\Network\Exception\SocketException;
  34. use Cake\Network\Request;
  35. use Cake\ORM\Exception\MissingBehaviorException;
  36. use Cake\Routing\Exception\MissingControllerException;
  37. use Cake\Routing\Router;
  38. use Cake\TestSuite\TestCase;
  39. use Cake\View\Exception\MissingHelperException;
  40. use Cake\View\Exception\MissingLayoutException;
  41. use Cake\View\Exception\MissingTemplateException;
  42. /**
  43. * BlueberryComponent class
  44. *
  45. */
  46. class BlueberryComponent extends Component {
  47. /**
  48. * testName property
  49. *
  50. * @return void
  51. */
  52. public $testName = null;
  53. /**
  54. * initialize method
  55. *
  56. * @param array $config
  57. * @return void
  58. */
  59. public function initialize(array $config) {
  60. $this->testName = 'BlueberryComponent';
  61. }
  62. }
  63. /**
  64. * TestErrorController class
  65. *
  66. */
  67. class TestErrorController extends Controller {
  68. /**
  69. * uses property
  70. *
  71. * @var array
  72. */
  73. public $uses = array();
  74. /**
  75. * components property
  76. *
  77. * @return void
  78. */
  79. public $components = array('Blueberry');
  80. /**
  81. * beforeRender method
  82. *
  83. * @return void
  84. */
  85. public function beforeRender(Event $event) {
  86. echo $this->Blueberry->testName;
  87. }
  88. /**
  89. * index method
  90. *
  91. * @return void
  92. */
  93. public function index() {
  94. $this->autoRender = false;
  95. return 'what up';
  96. }
  97. }
  98. /**
  99. * MyCustomExceptionRenderer class
  100. *
  101. */
  102. class MyCustomExceptionRenderer extends ExceptionRenderer {
  103. /**
  104. * custom error message type.
  105. *
  106. * @return void
  107. */
  108. public function missingWidgetThing() {
  109. return 'widget thing is missing';
  110. }
  111. }
  112. /**
  113. * Exception class for testing app error handlers and custom errors.
  114. *
  115. */
  116. class MissingWidgetThingException extends NotFoundException {
  117. }
  118. /**
  119. * ExceptionRendererTest class
  120. *
  121. */
  122. class ExceptionRendererTest extends TestCase {
  123. /**
  124. * @var bool
  125. */
  126. protected $_restoreError = false;
  127. /**
  128. * setup create a request object to get out of router later.
  129. *
  130. * @return void
  131. */
  132. public function setUp() {
  133. parent::setUp();
  134. Configure::write('Config.language', 'eng');
  135. Router::reload();
  136. $request = new Request();
  137. $request->base = '';
  138. Router::setRequestInfo($request);
  139. Configure::write('debug', true);
  140. }
  141. /**
  142. * tearDown
  143. *
  144. * @return void
  145. */
  146. public function tearDown() {
  147. parent::tearDown();
  148. if ($this->_restoreError) {
  149. restore_error_handler();
  150. }
  151. }
  152. /**
  153. * Mocks out the response on the ExceptionRenderer object so headers aren't modified.
  154. *
  155. * @return void
  156. */
  157. protected function _mockResponse($error) {
  158. $error->controller->response = $this->getMock('Cake\Network\Response', array('_sendHeader'));
  159. return $error;
  160. }
  161. /**
  162. * test that methods declared in an ExceptionRenderer subclass are not converted
  163. * into error400 when debug > 0
  164. *
  165. * @return void
  166. */
  167. public function testSubclassMethodsNotBeingConvertedToError() {
  168. $exception = new MissingWidgetThingException('Widget not found');
  169. $ExceptionRenderer = $this->_mockResponse(new MyCustomExceptionRenderer($exception));
  170. $result = $ExceptionRenderer->render();
  171. $this->assertEquals('widget thing is missing', $result->body());
  172. }
  173. /**
  174. * test that subclass methods are not converted when debug = 0
  175. *
  176. * @return void
  177. */
  178. public function testSubclassMethodsNotBeingConvertedDebug0() {
  179. Configure::write('debug', false);
  180. $exception = new MissingWidgetThingException('Widget not found');
  181. $ExceptionRenderer = $this->_mockResponse(new MyCustomExceptionRenderer($exception));
  182. $result = $ExceptionRenderer->render();
  183. $this->assertEquals('missingWidgetThing', $ExceptionRenderer->method);
  184. $this->assertEquals(
  185. 'widget thing is missing',
  186. $result->body(),
  187. 'Method declared in subclass converted to error400'
  188. );
  189. }
  190. /**
  191. * test that ExceptionRenderer subclasses properly convert framework errors.
  192. *
  193. * @return void
  194. */
  195. public function testSubclassConvertingFrameworkErrors() {
  196. Configure::write('debug', false);
  197. $exception = new MissingControllerException('PostsController');
  198. $ExceptionRenderer = $this->_mockResponse(new MyCustomExceptionRenderer($exception));
  199. $result = $ExceptionRenderer->render();
  200. $this->assertRegExp(
  201. '/Not Found/',
  202. $result->body(),
  203. 'Method declared in error handler not converted to error400. %s'
  204. );
  205. }
  206. /**
  207. * test things in the constructor.
  208. *
  209. * @return void
  210. */
  211. public function testConstruction() {
  212. $exception = new NotFoundException('Page not found');
  213. $ExceptionRenderer = new ExceptionRenderer($exception);
  214. $this->assertInstanceOf('Cake\Controller\ErrorController', $ExceptionRenderer->controller);
  215. $this->assertEquals($exception, $ExceptionRenderer->error);
  216. }
  217. /**
  218. * test that exception message gets coerced when debug = 0
  219. *
  220. * @return void
  221. */
  222. public function testExceptionMessageCoercion() {
  223. Configure::write('debug', false);
  224. $exception = new MissingActionException('Secret info not to be leaked');
  225. $ExceptionRenderer = $this->_mockResponse(new ExceptionRenderer($exception));
  226. $this->assertInstanceOf('Cake\Controller\ErrorController', $ExceptionRenderer->controller);
  227. $this->assertEquals($exception, $ExceptionRenderer->error);
  228. $result = $ExceptionRenderer->render()->body();
  229. $this->assertEquals('error400', $ExceptionRenderer->template);
  230. $this->assertContains('Not Found', $result);
  231. $this->assertNotContains('Secret info not to be leaked', $result);
  232. }
  233. /**
  234. * test that helpers in custom CakeErrorController are not lost
  235. *
  236. * @return void
  237. */
  238. public function testCakeErrorHelpersNotLost() {
  239. Configure::write('App.namespace', 'TestApp');
  240. $exception = new SocketException('socket exception');
  241. $renderer = $this->_mockResponse(new \TestApp\Error\TestAppsExceptionRenderer($exception));
  242. $result = $renderer->render();
  243. $this->assertContains('<b>peeled</b>', $result->body());
  244. }
  245. /**
  246. * test that unknown exception types with valid status codes are treated correctly.
  247. *
  248. * @return void
  249. */
  250. public function testUnknownExceptionTypeWithExceptionThatHasA400Code() {
  251. $exception = new MissingWidgetThingException('coding fail.');
  252. $ExceptionRenderer = new ExceptionRenderer($exception);
  253. $ExceptionRenderer->controller->response = $this->getMock('Cake\Network\Response', array('statusCode', '_sendHeader'));
  254. $ExceptionRenderer->controller->response->expects($this->once())->method('statusCode')->with(404);
  255. $result = $ExceptionRenderer->render();
  256. $this->assertFalse(method_exists($ExceptionRenderer, 'missingWidgetThing'), 'no method should exist.');
  257. $this->assertContains('coding fail', $result->body(), 'Text should show up.');
  258. }
  259. /**
  260. * test that unknown exception types with valid status codes are treated correctly.
  261. *
  262. * @return void
  263. */
  264. public function testUnknownExceptionTypeWithNoCodeIsA500() {
  265. $exception = new \OutOfBoundsException('foul ball.');
  266. $ExceptionRenderer = new ExceptionRenderer($exception);
  267. $ExceptionRenderer->controller->response = $this->getMock('Cake\Network\Response', array('statusCode', '_sendHeader'));
  268. $ExceptionRenderer->controller->response->expects($this->once())
  269. ->method('statusCode')
  270. ->with(500);
  271. $result = $ExceptionRenderer->render();
  272. $this->assertContains('foul ball.', $result->body(), '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', false);
  281. $exception = new \OutOfBoundsException('foul ball.');
  282. $ExceptionRenderer = new ExceptionRenderer($exception);
  283. $ExceptionRenderer->controller->response = $this->getMock('Cake\Network\Response', array('statusCode', '_sendHeader'));
  284. $ExceptionRenderer->controller->response->expects($this->once())
  285. ->method('statusCode')
  286. ->with(500);
  287. $result = $ExceptionRenderer->render()->body();
  288. $this->assertNotContains('foul ball.', $result, 'Text should no show up.');
  289. $this->assertContains('Internal Error', $result, 'Generic message only.');
  290. }
  291. /**
  292. * test that unknown exception types with valid status codes are treated correctly.
  293. *
  294. * @return void
  295. */
  296. public function testUnknownExceptionTypeWithCodeHigherThan500() {
  297. $exception = new \OutOfBoundsException('foul ball.', 501);
  298. $ExceptionRenderer = new ExceptionRenderer($exception);
  299. $ExceptionRenderer->controller->response = $this->getMock('Cake\Network\Response', array('statusCode', '_sendHeader'));
  300. $ExceptionRenderer->controller->response->expects($this->once())->method('statusCode')->with(501);
  301. $result = $ExceptionRenderer->render();
  302. $this->assertContains('foul ball.', $result->body(), 'Text should show up as its debug mode.');
  303. }
  304. /**
  305. * testerror400 method
  306. *
  307. * @return void
  308. */
  309. public function testError400() {
  310. Router::reload();
  311. $request = new Request('posts/view/1000');
  312. Router::setRequestInfo($request);
  313. $exception = new NotFoundException('Custom message');
  314. $ExceptionRenderer = new ExceptionRenderer($exception);
  315. $ExceptionRenderer->controller->response = $this->getMock('Cake\Network\Response', array('statusCode', '_sendHeader'));
  316. $ExceptionRenderer->controller->response->expects($this->once())->method('statusCode')->with(404);
  317. $result = $ExceptionRenderer->render()->body();
  318. $this->assertContains('<h2>Custom message</h2>', $result);
  319. $this->assertRegExp("/<strong>'.*?\/posts\/view\/1000'<\/strong>/", $result);
  320. }
  321. /**
  322. * test that error400 only modifies the messages on Cake Exceptions.
  323. *
  324. * @return void
  325. */
  326. public function testerror400OnlyChangingCakeException() {
  327. Configure::write('debug', false);
  328. $exception = new NotFoundException('Custom message');
  329. $ExceptionRenderer = $this->_mockResponse(new ExceptionRenderer($exception));
  330. $result = $ExceptionRenderer->render();
  331. $this->assertContains('Custom message', $result->body());
  332. $exception = new MissingActionException(array('controller' => 'PostsController', 'action' => 'index'));
  333. $ExceptionRenderer = $this->_mockResponse(new ExceptionRenderer($exception));
  334. $result = $ExceptionRenderer->render();
  335. $this->assertContains('Not Found', $result->body());
  336. }
  337. /**
  338. * test that error400 doesn't expose XSS
  339. *
  340. * @return void
  341. */
  342. public function testError400NoInjection() {
  343. Router::reload();
  344. $request = new Request('pages/<span id=333>pink</span></id><script>document.body.style.background = t=document.getElementById(333).innerHTML;window.alert(t);</script>');
  345. Router::setRequestInfo($request);
  346. $exception = new NotFoundException('Custom message');
  347. $ExceptionRenderer = $this->_mockResponse(new ExceptionRenderer($exception));
  348. $result = $ExceptionRenderer->render()->body();
  349. $this->assertNotContains('<script>document', $result);
  350. $this->assertNotContains('alert(t);</script>', $result);
  351. }
  352. /**
  353. * testError500 method
  354. *
  355. * @return void
  356. */
  357. public function testError500Message() {
  358. $exception = new InternalErrorException('An Internal Error Has Occurred.');
  359. $ExceptionRenderer = new ExceptionRenderer($exception);
  360. $ExceptionRenderer->controller->response = $this->getMock('Cake\Network\Response', array('statusCode', '_sendHeader'));
  361. $ExceptionRenderer->controller->response->expects($this->once())->method('statusCode')->with(500);
  362. $result = $ExceptionRenderer->render();
  363. $this->assertContains('<h2>An Internal Error Has Occurred</h2>', $result->body());
  364. $this->assertContains('An Internal Error Has Occurred.</p>', $result->body());
  365. }
  366. /**
  367. * testExceptionResponseHeader method
  368. *
  369. * @return void
  370. */
  371. public function testExceptionResponseHeader() {
  372. $exception = new MethodNotAllowedException('Only allowing POST and DELETE');
  373. $exception->responseHeader(array('Allow: POST, DELETE'));
  374. $ExceptionRenderer = new ExceptionRenderer($exception);
  375. $result = $ExceptionRenderer->render();
  376. $headers = $result->header();
  377. $this->assertArrayHasKey('Allow', $headers);
  378. $this->assertEquals('POST, DELETE', $headers['Allow']);
  379. }
  380. /**
  381. * testMissingController method
  382. *
  383. * @return void
  384. */
  385. public function testMissingController() {
  386. $exception = new MissingControllerException(array(
  387. 'class' => 'Posts',
  388. 'prefix' => '',
  389. 'plugin' => '',
  390. ));
  391. $ExceptionRenderer = $this->_mockResponse(new MyCustomExceptionRenderer($exception));
  392. $result = $ExceptionRenderer->render()->body();
  393. $this->assertEquals('missingController', $ExceptionRenderer->template);
  394. $this->assertContains('<h2>Missing Controller</h2>', $result);
  395. $this->assertContains('<em>PostsController</em>', $result);
  396. }
  397. /**
  398. * Returns an array of tests to run for the various Cake Exception classes.
  399. *
  400. * @return array
  401. */
  402. public static function exceptionProvider() {
  403. return array(
  404. array(
  405. new MissingActionException(array(
  406. 'controller' => 'PostsController',
  407. 'action' => 'index',
  408. 'prefix' => '',
  409. 'plugin' => '',
  410. )),
  411. array(
  412. '/<h2>Missing Method in PostsController<\/h2>/',
  413. '/<em>PostsController::index\(\)<\/em>/'
  414. ),
  415. 404
  416. ),
  417. array(
  418. new MissingTemplateException(array('file' => '/posts/about.ctp')),
  419. array(
  420. "/posts\/about.ctp/"
  421. ),
  422. 500
  423. ),
  424. array(
  425. new MissingLayoutException(array('file' => 'layouts/my_layout.ctp')),
  426. array(
  427. "/Missing Layout/",
  428. "/layouts\/my_layout.ctp/"
  429. ),
  430. 500
  431. ),
  432. array(
  433. new MissingHelperException(array('class' => 'MyCustomHelper')),
  434. array(
  435. '/<h2>Missing Helper<\/h2>/',
  436. '/<em>MyCustomHelper<\/em> could not be found./',
  437. '/Create the class <em>MyCustomHelper<\/em> below in file:/',
  438. '/(\/|\\\)MyCustomHelper.php/'
  439. ),
  440. 500
  441. ),
  442. array(
  443. new MissingBehaviorException(array('class' => 'MyCustomBehavior')),
  444. array(
  445. '/<h2>Missing Behavior<\/h2>/',
  446. '/Create the class <em>MyCustomBehavior<\/em> below in file:/',
  447. '/(\/|\\\)MyCustomBehavior.php/'
  448. ),
  449. 500
  450. ),
  451. array(
  452. new MissingComponentException(array('class' => 'SideboxComponent')),
  453. array(
  454. '/<h2>Missing Component<\/h2>/',
  455. '/Create the class <em>SideboxComponent<\/em> below in file:/',
  456. '/(\/|\\\)SideboxComponent.php/'
  457. ),
  458. 500
  459. ),
  460. array(
  461. new MissingDatasourceConfigException(array('name' => 'MyDatasourceConfig')),
  462. array(
  463. '/<h2>Missing Datasource Configuration<\/h2>/',
  464. '/<em>MyDatasourceConfig<\/em> was not found/'
  465. ),
  466. 500
  467. ),
  468. array(
  469. new MissingDatasourceException(array('class' => 'MyDatasource', 'plugin' => 'MyPlugin')),
  470. array(
  471. '/<h2>Missing Datasource<\/h2>/',
  472. '/<em>MyPlugin.MyDatasource<\/em> could not be found./'
  473. ),
  474. 500
  475. ),
  476. array(
  477. new \Exception('boom'),
  478. array(
  479. '/Internal Error/'
  480. ),
  481. 500
  482. ),
  483. array(
  484. new \RuntimeException('another boom'),
  485. array(
  486. '/Internal Error/'
  487. ),
  488. 500
  489. ),
  490. array(
  491. new \Cake\Core\Exception\Exception('base class'),
  492. array('/Internal Error/'),
  493. 500
  494. )
  495. );
  496. }
  497. /**
  498. * Test the various Cake Exception sub classes
  499. *
  500. * @dataProvider exceptionProvider
  501. * @return void
  502. */
  503. public function testCakeExceptionHandling($exception, $patterns, $code) {
  504. $ExceptionRenderer = new ExceptionRenderer($exception);
  505. $ExceptionRenderer->controller->response = $this->getMock('Cake\Network\Response', array('statusCode', '_sendHeader'));
  506. $ExceptionRenderer->controller->response->expects($this->once())
  507. ->method('statusCode')
  508. ->with($code);
  509. $result = $ExceptionRenderer->render()->body();
  510. foreach ($patterns as $pattern) {
  511. $this->assertRegExp($pattern, $result);
  512. }
  513. }
  514. /**
  515. * Test exceptions being raised when helpers are missing.
  516. *
  517. * @return void
  518. */
  519. public function testMissingRenderSafe() {
  520. $exception = new MissingHelperException(array('class' => 'Fail'));
  521. $ExceptionRenderer = new ExceptionRenderer($exception);
  522. $ExceptionRenderer->controller = $this->getMock('Cake\Controller\Controller', array('render'));
  523. $ExceptionRenderer->controller->helpers = array('Fail', 'Boom');
  524. $ExceptionRenderer->controller->request = new Request;
  525. $ExceptionRenderer->controller->expects($this->at(0))
  526. ->method('render')
  527. ->with('missingHelper')
  528. ->will($this->throwException($exception));
  529. $response = $this->getMock('Cake\Network\Response');
  530. $response->expects($this->once())
  531. ->method('body')
  532. ->with($this->stringContains('Helper class Fail'));
  533. $ExceptionRenderer->controller->response = $response;
  534. $ExceptionRenderer->render();
  535. sort($ExceptionRenderer->controller->helpers);
  536. $this->assertEquals(array('Form', 'Html', 'Session'), $ExceptionRenderer->controller->helpers);
  537. }
  538. /**
  539. * Test that exceptions in beforeRender() are handled by outputMessageSafe
  540. *
  541. * @return void
  542. */
  543. public function testRenderExceptionInBeforeRender() {
  544. $exception = new NotFoundException('Not there, sorry');
  545. $ExceptionRenderer = new ExceptionRenderer($exception);
  546. $ExceptionRenderer->controller = $this->getMock('Cake\Controller\Controller', array('beforeRender'));
  547. $ExceptionRenderer->controller->request = new Request;
  548. $ExceptionRenderer->controller->expects($this->any())
  549. ->method('beforeRender')
  550. ->will($this->throwException($exception));
  551. $response = $this->getMock('Cake\Network\Response');
  552. $response->expects($this->once())
  553. ->method('body')
  554. ->with($this->stringContains('Not there, sorry'));
  555. $ExceptionRenderer->controller->response = $response;
  556. $ExceptionRenderer->render();
  557. }
  558. /**
  559. * Test that missing subDir/layoutPath don't cause other fatal errors.
  560. *
  561. * @return void
  562. */
  563. public function testMissingSubdirRenderSafe() {
  564. $exception = new NotFoundException();
  565. $ExceptionRenderer = new ExceptionRenderer($exception);
  566. $ExceptionRenderer->controller = $this->getMock('Cake\Controller\Controller', array('render'));
  567. $ExceptionRenderer->controller->helpers = array('Fail', 'Boom');
  568. $ExceptionRenderer->controller->layoutPath = 'boom';
  569. $ExceptionRenderer->controller->subDir = 'boom';
  570. $ExceptionRenderer->controller->request = new Request;
  571. $ExceptionRenderer->controller->expects($this->once())
  572. ->method('render')
  573. ->with('error400')
  574. ->will($this->throwException($exception));
  575. $response = $this->getMock('Cake\Network\Response');
  576. $response->expects($this->once())
  577. ->method('body')
  578. ->with($this->stringContains('Not Found'));
  579. $response->expects($this->once())
  580. ->method('type')
  581. ->with('html');
  582. $ExceptionRenderer->controller->response = $response;
  583. $ExceptionRenderer->render();
  584. $this->assertEquals('', $ExceptionRenderer->controller->layoutPath);
  585. $this->assertEquals('', $ExceptionRenderer->controller->subDir);
  586. $this->assertEquals('Error', $ExceptionRenderer->controller->viewPath);
  587. }
  588. /**
  589. * Test that missing plugin disables Controller::$plugin if the two are the same plugin.
  590. *
  591. * @return void
  592. */
  593. public function testMissingPluginRenderSafe() {
  594. $exception = new NotFoundException();
  595. $ExceptionRenderer = new ExceptionRenderer($exception);
  596. $ExceptionRenderer->controller = $this->getMock('Cake\Controller\Controller', array('render'));
  597. $ExceptionRenderer->controller->plugin = 'TestPlugin';
  598. $ExceptionRenderer->controller->request = $this->getMock('Cake\Network\Request');
  599. $exception = new MissingPluginException(array('plugin' => 'TestPlugin'));
  600. $ExceptionRenderer->controller->expects($this->once())
  601. ->method('render')
  602. ->with('error400')
  603. ->will($this->throwException($exception));
  604. $response = $this->getMock('Cake\Network\Response');
  605. $response->expects($this->once())
  606. ->method('body')
  607. ->with($this->logicalAnd(
  608. $this->logicalNot($this->stringContains('test plugin error500')),
  609. $this->stringContains('Not Found')
  610. ));
  611. $ExceptionRenderer->controller->response = $response;
  612. $ExceptionRenderer->render();
  613. }
  614. /**
  615. * Test that missing plugin doesn't disable Controller::$plugin if the two aren't the same plugin.
  616. *
  617. * @return void
  618. */
  619. public function testMissingPluginRenderSafeWithPlugin() {
  620. Plugin::load('TestPlugin');
  621. $exception = new NotFoundException();
  622. $ExceptionRenderer = new ExceptionRenderer($exception);
  623. $ExceptionRenderer->controller = $this->getMock('Cake\Controller\Controller', array('render'));
  624. $ExceptionRenderer->controller->plugin = 'TestPlugin';
  625. $ExceptionRenderer->controller->request = $this->getMock('Cake\Network\Request');
  626. $exception = new MissingPluginException(array('plugin' => 'TestPluginTwo'));
  627. $ExceptionRenderer->controller->expects($this->once())
  628. ->method('render')
  629. ->with('error400')
  630. ->will($this->throwException($exception));
  631. $response = $this->getMock('Cake\Network\Response');
  632. $response->expects($this->once())
  633. ->method('body')
  634. ->with($this->logicalAnd(
  635. $this->stringContains('test plugin error500'),
  636. $this->stringContains('Not Found')
  637. ));
  638. $ExceptionRenderer->controller->response = $response;
  639. $ExceptionRenderer->render();
  640. Plugin::unload();
  641. }
  642. /**
  643. * Test that exceptions can be rendered when a request hasn't been registered
  644. * with Router
  645. *
  646. * @return void
  647. */
  648. public function testRenderWithNoRequest() {
  649. Router::reload();
  650. $this->assertNull(Router::getRequest(false));
  651. $exception = new \Exception('Terrible');
  652. $ExceptionRenderer = new ExceptionRenderer($exception);
  653. $result = $ExceptionRenderer->render();
  654. $this->assertContains('Internal Error', $result->body());
  655. $this->assertEquals(500, $result->statusCode());
  656. }
  657. /**
  658. * Test that rendering exceptions triggers shutdown events.
  659. *
  660. * @return void
  661. */
  662. public function testRenderShutdownEvents() {
  663. $fired = [];
  664. $listener = function ($event) use (&$fired) {
  665. $fired[] = $event->name();
  666. };
  667. $events = EventManager::instance();
  668. $events->attach($listener, 'Controller.shutdown');
  669. $events->attach($listener, 'Dispatcher.afterDispatch');
  670. $exception = new \Exception('Terrible');
  671. $renderer = new ExceptionRenderer($exception);
  672. $renderer->render();
  673. $expected = ['Controller.shutdown', 'Dispatcher.afterDispatch'];
  674. $this->assertEquals($expected, $fired);
  675. }
  676. /**
  677. * test that subclass methods fire shutdown events.
  678. *
  679. * @return void
  680. */
  681. public function testSubclassTriggerShutdownEvents() {
  682. $fired = [];
  683. $listener = function ($event) use (&$fired) {
  684. $fired[] = $event->name();
  685. };
  686. $events = EventManager::instance();
  687. $events->attach($listener, 'Controller.shutdown');
  688. $events->attach($listener, 'Dispatcher.afterDispatch');
  689. $exception = new MissingWidgetThingException('Widget not found');
  690. $renderer = $this->_mockResponse(new MyCustomExceptionRenderer($exception));
  691. $renderer->render();
  692. $expected = ['Controller.shutdown', 'Dispatcher.afterDispatch'];
  693. $this->assertEquals($expected, $fired);
  694. }
  695. /**
  696. * Tests the output of rendering a PDOException
  697. *
  698. * @return void
  699. */
  700. public function testPDOException() {
  701. $exception = new \PDOException('There was an error in the SQL query');
  702. $exception->queryString = 'SELECT * from poo_query < 5 and :seven';
  703. $exception->params = array('seven' => 7);
  704. $ExceptionRenderer = new ExceptionRenderer($exception);
  705. $ExceptionRenderer->controller->response = $this->getMock('Cake\Network\Response', array('statusCode', '_sendHeader'));
  706. $ExceptionRenderer->controller->response->expects($this->once())->method('statusCode')->with(500);
  707. $result = $ExceptionRenderer->render()->body();
  708. $this->assertContains('<h2>Database Error</h2>', $result);
  709. $this->assertContains('There was an error in the SQL query', $result);
  710. $this->assertContains(h('SELECT * from poo_query < 5 and :seven'), $result);
  711. $this->assertContains("'seven' => (int) 7", $result);
  712. }
  713. }