ExceptionRendererTest.php 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690
  1. <?php
  2. /**
  3. * ExceptionRendererTest file
  4. *
  5. * CakePHP(tm) Tests <http://book.cakephp.org/2.0/en/development/testing.html>
  6. * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  7. *
  8. * Licensed under The MIT License
  9. * For full copyright and license information, please see the LICENSE.txt
  10. * Redistributions of files must retain the above copyright notice
  11. *
  12. * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  13. * @link http://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests
  14. * @since 2.0.0
  15. * @license http://www.opensource.org/licenses/mit-license.php MIT License
  16. */
  17. namespace Cake\Test\TestCase\Error;
  18. use Cake\Controller\Component;
  19. use Cake\Controller\Controller;
  20. use Cake\Controller\Error\MissingActionException;
  21. use Cake\Controller\Error\MissingComponentException;
  22. use Cake\Controller\Error\MissingControllerException;
  23. use Cake\Controller\Error\PrivateActionException;
  24. use Cake\Core\App;
  25. use Cake\Core\Configure;
  26. use Cake\Error;
  27. use Cake\Error\ExceptionRenderer;
  28. use Cake\Event\Event;
  29. use Cake\Network\Error\SocketException;
  30. use Cake\Network\Request;
  31. use Cake\ORM\Error\MissingBehaviorException;
  32. use Cake\Routing\Router;
  33. use Cake\TestSuite\TestCase;
  34. use Cake\View\Error\MissingHelperException;
  35. use Cake\View\Error\MissingLayoutException;
  36. use Cake\View\Error\MissingViewException;
  37. /**
  38. * BlueberryComponent class
  39. *
  40. */
  41. class BlueberryComponent extends Component {
  42. /**
  43. * testName property
  44. *
  45. * @return void
  46. */
  47. public $testName = null;
  48. /**
  49. * initialize method
  50. *
  51. * @param Event $event
  52. * @return void
  53. */
  54. public function initialize(Event $event) {
  55. $this->testName = 'BlueberryComponent';
  56. }
  57. }
  58. /**
  59. * TestErrorController class
  60. *
  61. */
  62. class TestErrorController extends Controller {
  63. /**
  64. * uses property
  65. *
  66. * @var array
  67. */
  68. public $uses = array();
  69. /**
  70. * components property
  71. *
  72. * @return void
  73. */
  74. public $components = array('Blueberry');
  75. /**
  76. * beforeRender method
  77. *
  78. * @return void
  79. */
  80. public function beforeRender(Event $event) {
  81. echo $this->Blueberry->testName;
  82. }
  83. /**
  84. * index method
  85. *
  86. * @return void
  87. */
  88. public function index() {
  89. $this->autoRender = false;
  90. return 'what up';
  91. }
  92. }
  93. /**
  94. * MyCustomExceptionRenderer class
  95. *
  96. */
  97. class MyCustomExceptionRenderer extends ExceptionRenderer {
  98. }
  99. /**
  100. * Exception class for testing app error handlers and custom errors.
  101. *
  102. */
  103. class MissingWidgetThingException extends Error\NotFoundException {
  104. }
  105. /**
  106. * ExceptionRendererTest class
  107. *
  108. */
  109. class ExceptionRendererTest extends TestCase {
  110. /**
  111. * @var boolean
  112. */
  113. protected $_restoreError = false;
  114. /**
  115. * setup create a request object to get out of router later.
  116. *
  117. * @return void
  118. */
  119. public function setUp() {
  120. parent::setUp();
  121. Configure::write('Config.language', 'eng');
  122. Router::reload();
  123. $request = new Request();
  124. $request->base = '';
  125. Router::setRequestInfo($request);
  126. Configure::write('debug', true);
  127. }
  128. /**
  129. * tearDown
  130. *
  131. * @return void
  132. */
  133. public function tearDown() {
  134. parent::tearDown();
  135. if ($this->_restoreError) {
  136. restore_error_handler();
  137. }
  138. }
  139. /**
  140. * Mocks out the response on the ExceptionRenderer object so headers aren't modified.
  141. *
  142. * @return void
  143. */
  144. protected function _mockResponse($error) {
  145. $error->controller->response = $this->getMock('Cake\Network\Response', array('_sendHeader'));
  146. return $error;
  147. }
  148. /**
  149. * test that ExceptionRenderer subclasses properly convert framework errors.
  150. *
  151. * @return void
  152. */
  153. public function testSubclassConvertingFrameworkErrors() {
  154. Configure::write('debug', false);
  155. $exception = new MissingControllerException('PostsController');
  156. $ExceptionRenderer = $this->_mockResponse(new MyCustomExceptionRenderer($exception));
  157. ob_start();
  158. $ExceptionRenderer->render();
  159. $result = ob_get_clean();
  160. $this->assertRegExp('/Not Found/', $result, 'Method declared in error handler not converted to error400. %s');
  161. }
  162. /**
  163. * test things in the constructor.
  164. *
  165. * @return void
  166. */
  167. public function testConstruction() {
  168. $exception = new Error\NotFoundException('Page not found');
  169. $ExceptionRenderer = new ExceptionRenderer($exception);
  170. $this->assertInstanceOf('Cake\Controller\ErrorController', $ExceptionRenderer->controller);
  171. $this->assertEquals($exception, $ExceptionRenderer->error);
  172. }
  173. /**
  174. * test that exception gets coerced when debug = 0
  175. *
  176. * @return void
  177. */
  178. public function testExceptionCoercion() {
  179. Configure::write('debug', false);
  180. $exception = new MissingActionException('Page not found');
  181. $ExceptionRenderer = new ExceptionRenderer($exception);
  182. $this->assertInstanceOf('Cake\Controller\ErrorController', $ExceptionRenderer->controller);
  183. $this->assertTrue($ExceptionRenderer->error instanceof Error\NotFoundException);
  184. }
  185. /**
  186. * test that helpers in custom CakeErrorController are not lost
  187. *
  188. * @return void
  189. */
  190. public function testCakeErrorHelpersNotLost() {
  191. Configure::write('App.namespace', 'TestApp');
  192. $exception = new SocketException('socket exception');
  193. $renderer = new \TestApp\Error\TestAppsExceptionRenderer($exception);
  194. ob_start();
  195. $renderer->render();
  196. $result = ob_get_clean();
  197. $this->assertContains('<b>peeled</b>', $result);
  198. }
  199. /**
  200. * test that unknown exception types with valid status codes are treated correctly.
  201. *
  202. * @return void
  203. */
  204. public function testUnknownExceptionTypeWithExceptionThatHasA400Code() {
  205. $exception = new MissingWidgetThingException('coding fail.');
  206. $ExceptionRenderer = new ExceptionRenderer($exception);
  207. $ExceptionRenderer->controller->response = $this->getMock('Cake\Network\Response', array('statusCode', '_sendHeader'));
  208. $ExceptionRenderer->controller->response->expects($this->once())->method('statusCode')->with(404);
  209. ob_start();
  210. $ExceptionRenderer->render();
  211. $result = ob_get_clean();
  212. $this->assertFalse(method_exists($ExceptionRenderer, 'missingWidgetThing'), 'no method should exist.');
  213. $this->assertContains('coding fail', $result, 'Text should show up.');
  214. }
  215. /**
  216. * test that unknown exception types with valid status codes are treated correctly.
  217. *
  218. * @return void
  219. */
  220. public function testUnknownExceptionTypeWithNoCodeIsA500() {
  221. $exception = new \OutOfBoundsException('foul ball.');
  222. $ExceptionRenderer = new ExceptionRenderer($exception);
  223. $ExceptionRenderer->controller->response = $this->getMock('Cake\Network\Response', array('statusCode', '_sendHeader'));
  224. $ExceptionRenderer->controller->response->expects($this->once())
  225. ->method('statusCode')
  226. ->with(500);
  227. ob_start();
  228. $ExceptionRenderer->render();
  229. $result = ob_get_clean();
  230. $this->assertContains('foul ball.', $result, 'Text should show up as its debug mode.');
  231. }
  232. /**
  233. * test that unknown exceptions have messages ignored.
  234. *
  235. * @return void
  236. */
  237. public function testUnknownExceptionInProduction() {
  238. Configure::write('debug', false);
  239. $exception = new \OutOfBoundsException('foul ball.');
  240. $ExceptionRenderer = new ExceptionRenderer($exception);
  241. $ExceptionRenderer->controller->response = $this->getMock('Cake\Network\Response', array('statusCode', '_sendHeader'));
  242. $ExceptionRenderer->controller->response->expects($this->once())
  243. ->method('statusCode')
  244. ->with(500);
  245. ob_start();
  246. $ExceptionRenderer->render();
  247. $result = ob_get_clean();
  248. $this->assertNotContains('foul ball.', $result, 'Text should no show up.');
  249. $this->assertContains('Internal Error', $result, 'Generic message only.');
  250. }
  251. /**
  252. * test that unknown exception types with valid status codes are treated correctly.
  253. *
  254. * @return void
  255. */
  256. public function testUnknownExceptionTypeWithCodeHigherThan500() {
  257. $exception = new \OutOfBoundsException('foul ball.', 501);
  258. $ExceptionRenderer = new ExceptionRenderer($exception);
  259. $ExceptionRenderer->controller->response = $this->getMock('Cake\Network\Response', array('statusCode', '_sendHeader'));
  260. $ExceptionRenderer->controller->response->expects($this->once())->method('statusCode')->with(501);
  261. ob_start();
  262. $ExceptionRenderer->render();
  263. $result = ob_get_clean();
  264. $this->assertContains('foul ball.', $result, 'Text should show up as its debug mode.');
  265. }
  266. /**
  267. * testerror400 method
  268. *
  269. * @return void
  270. */
  271. public function testError400() {
  272. Router::reload();
  273. $request = new Request('posts/view/1000');
  274. Router::setRequestInfo($request);
  275. $exception = new Error\NotFoundException('Custom message');
  276. $ExceptionRenderer = new ExceptionRenderer($exception);
  277. $ExceptionRenderer->controller->response = $this->getMock('Cake\Network\Response', array('statusCode', '_sendHeader'));
  278. $ExceptionRenderer->controller->response->expects($this->once())->method('statusCode')->with(404);
  279. ob_start();
  280. $ExceptionRenderer->render();
  281. $result = ob_get_clean();
  282. $this->assertRegExp('/<h2>Custom message<\/h2>/', $result);
  283. $this->assertRegExp("/<strong>'.*?\/posts\/view\/1000'<\/strong>/", $result);
  284. }
  285. /**
  286. * test that error400 only modifies the messages on Cake Exceptions.
  287. *
  288. * @return void
  289. */
  290. public function testerror400OnlyChangingCakeException() {
  291. Configure::write('debug', false);
  292. $exception = new Error\NotFoundException('Custom message');
  293. $ExceptionRenderer = $this->_mockResponse(new ExceptionRenderer($exception));
  294. ob_start();
  295. $ExceptionRenderer->render();
  296. $result = ob_get_clean();
  297. $this->assertContains('Custom message', $result);
  298. $exception = new MissingActionException(array('controller' => 'PostsController', 'action' => 'index'));
  299. $ExceptionRenderer = $this->_mockResponse(new ExceptionRenderer($exception));
  300. ob_start();
  301. $ExceptionRenderer->render();
  302. $result = ob_get_clean();
  303. $this->assertContains('Not Found', $result);
  304. }
  305. /**
  306. * test that error400 doesn't expose XSS
  307. *
  308. * @return void
  309. */
  310. public function testError400NoInjection() {
  311. Router::reload();
  312. $request = new Request('pages/<span id=333>pink</span></id><script>document.body.style.background = t=document.getElementById(333).innerHTML;window.alert(t);</script>');
  313. Router::setRequestInfo($request);
  314. $exception = new Error\NotFoundException('Custom message');
  315. $ExceptionRenderer = $this->_mockResponse(new ExceptionRenderer($exception));
  316. ob_start();
  317. $ExceptionRenderer->render();
  318. $result = ob_get_clean();
  319. $this->assertNotRegExp('#<script>document#', $result);
  320. $this->assertNotRegExp('#alert\(t\);</script>#', $result);
  321. }
  322. /**
  323. * testError500 method
  324. *
  325. * @return void
  326. */
  327. public function testError500Message() {
  328. $exception = new Error\InternalErrorException('An Internal Error Has Occurred');
  329. $ExceptionRenderer = new ExceptionRenderer($exception);
  330. $ExceptionRenderer->controller->response = $this->getMock('Cake\Network\Response', array('statusCode', '_sendHeader'));
  331. $ExceptionRenderer->controller->response->expects($this->once())->method('statusCode')->with(500);
  332. ob_start();
  333. $ExceptionRenderer->render();
  334. $result = ob_get_clean();
  335. $this->assertRegExp('/<h2>An Internal Error Has Occurred<\/h2>/', $result);
  336. }
  337. /**
  338. * testExceptionResponseHeader method
  339. *
  340. * @return void
  341. */
  342. public function testExceptionResponseHeader() {
  343. $exception = new Error\MethodNotAllowedException('Only allowing POST and DELETE');
  344. $exception->responseHeader(array('Allow: POST, DELETE'));
  345. $ExceptionRenderer = new ExceptionRenderer($exception);
  346. //Replace response object with mocked object add back the original headers which had been set in ExceptionRenderer constructor
  347. $headers = $ExceptionRenderer->controller->response->header();
  348. $ExceptionRenderer->controller->response = $this->getMock('Cake\Network\Response', array('_sendHeader'));
  349. $ExceptionRenderer->controller->response->header($headers);
  350. $ExceptionRenderer->controller->response->expects($this->at(1))->method('_sendHeader')->with('Allow', 'POST, DELETE');
  351. ob_start();
  352. $ExceptionRenderer->render();
  353. ob_get_clean();
  354. }
  355. /**
  356. * testMissingController method
  357. *
  358. * @return void
  359. */
  360. public function testMissingController() {
  361. $exception = new MissingControllerException(array(
  362. 'class' => 'Posts',
  363. 'prefix' => '',
  364. 'plugin' => '',
  365. ));
  366. $ExceptionRenderer = $this->_mockResponse(new MyCustomExceptionRenderer($exception));
  367. ob_start();
  368. $ExceptionRenderer->render();
  369. $result = ob_get_clean();
  370. $this->assertEquals('missingController', $ExceptionRenderer->template);
  371. $this->assertRegExp('/<h2>Missing Controller<\/h2>/', $result);
  372. $this->assertRegExp('/<em>PostsController<\/em>/', $result);
  373. }
  374. /**
  375. * Returns an array of tests to run for the various Cake Exception classes.
  376. *
  377. * @return void
  378. */
  379. public static function testProvider() {
  380. return array(
  381. array(
  382. new MissingActionException(array(
  383. 'controller' => 'PostsController',
  384. 'action' => 'index',
  385. 'prefix' => '',
  386. 'plugin' => '',
  387. )),
  388. array(
  389. '/<h2>Missing Method in PostsController<\/h2>/',
  390. '/<em>PostsController::index\(\)<\/em>/'
  391. ),
  392. 404
  393. ),
  394. array(
  395. new PrivateActionException(array('controller' => 'PostsController', 'action' => '_secretSauce')),
  396. array(
  397. '/<h2>Private Method in PostsController<\/h2>/',
  398. '/<em>PostsController::_secretSauce\(\)<\/em>/'
  399. ),
  400. 404
  401. ),
  402. array(
  403. new MissingViewException(array('file' => '/posts/about.ctp')),
  404. array(
  405. "/posts\/about.ctp/"
  406. ),
  407. 500
  408. ),
  409. array(
  410. new MissingLayoutException(array('file' => 'layouts/my_layout.ctp')),
  411. array(
  412. "/Missing Layout/",
  413. "/layouts\/my_layout.ctp/"
  414. ),
  415. 500
  416. ),
  417. array(
  418. new MissingHelperException(array('class' => 'MyCustomHelper')),
  419. array(
  420. '/<h2>Missing Helper<\/h2>/',
  421. '/<em>MyCustomHelper<\/em> could not be found./',
  422. '/Create the class <em>MyCustomHelper<\/em> below in file:/',
  423. '/(\/|\\\)MyCustomHelper.php/'
  424. ),
  425. 500
  426. ),
  427. array(
  428. new MissingBehaviorException(array('class' => 'MyCustomBehavior')),
  429. array(
  430. '/<h2>Missing Behavior<\/h2>/',
  431. '/Create the class <em>MyCustomBehavior<\/em> below in file:/',
  432. '/(\/|\\\)MyCustomBehavior.php/'
  433. ),
  434. 500
  435. ),
  436. array(
  437. new MissingComponentException(array('class' => 'SideboxComponent')),
  438. array(
  439. '/<h2>Missing Component<\/h2>/',
  440. '/Create the class <em>SideboxComponent<\/em> below in file:/',
  441. '/(\/|\\\)SideboxComponent.php/'
  442. ),
  443. 500
  444. ),
  445. array(
  446. new \Exception('boom'),
  447. array(
  448. '/Internal Error/'
  449. ),
  450. 500
  451. ),
  452. array(
  453. new \RuntimeException('another boom'),
  454. array(
  455. '/Internal Error/'
  456. ),
  457. 500
  458. ),
  459. array(
  460. new Error\Exception('base class'),
  461. array('/Internal Error/'),
  462. 500
  463. )
  464. );
  465. }
  466. /**
  467. * Test the various Cake Exception sub classes
  468. *
  469. * @dataProvider testProvider
  470. * @return void
  471. */
  472. public function testCakeExceptionHandling($exception, $patterns, $code) {
  473. $ExceptionRenderer = new ExceptionRenderer($exception);
  474. $ExceptionRenderer->controller->response = $this->getMock('Cake\Network\Response', array('statusCode', '_sendHeader'));
  475. $ExceptionRenderer->controller->response->expects($this->once())
  476. ->method('statusCode')
  477. ->with($code);
  478. ob_start();
  479. $ExceptionRenderer->render();
  480. $result = ob_get_clean();
  481. foreach ($patterns as $pattern) {
  482. $this->assertRegExp($pattern, $result);
  483. }
  484. }
  485. /**
  486. * Test exceptions being raised when helpers are missing.
  487. *
  488. * @return void
  489. */
  490. public function testMissingRenderSafe() {
  491. $exception = new MissingHelperException(array('class' => 'Fail'));
  492. $ExceptionRenderer = new ExceptionRenderer($exception);
  493. $ExceptionRenderer->controller = $this->getMock('Cake\Controller\Controller', array('render'));
  494. $ExceptionRenderer->controller->helpers = array('Fail', 'Boom');
  495. $ExceptionRenderer->controller->request = new Request;
  496. $ExceptionRenderer->controller->expects($this->at(0))
  497. ->method('render')
  498. ->with('missingHelper')
  499. ->will($this->throwException($exception));
  500. $response = $this->getMock('Cake\Network\Response');
  501. $response->expects($this->once())
  502. ->method('body')
  503. ->with($this->stringContains('Helper class Fail'));
  504. $ExceptionRenderer->controller->response = $response;
  505. $ExceptionRenderer->render();
  506. sort($ExceptionRenderer->controller->helpers);
  507. $this->assertEquals(array('Form', 'Html', 'Session'), $ExceptionRenderer->controller->helpers);
  508. }
  509. /**
  510. * Test that exceptions in beforeRender() are handled by outputMessageSafe
  511. *
  512. * @return void
  513. */
  514. public function testRenderExceptionInBeforeRender() {
  515. $exception = new Error\NotFoundException('Not there, sorry');
  516. $ExceptionRenderer = new ExceptionRenderer($exception);
  517. $ExceptionRenderer->controller = $this->getMock('Cake\Controller\Controller', array('beforeRender'));
  518. $ExceptionRenderer->controller->request = new Request;
  519. $ExceptionRenderer->controller->expects($this->any())
  520. ->method('beforeRender')
  521. ->will($this->throwException($exception));
  522. $response = $this->getMock('Cake\Network\Response');
  523. $response->expects($this->once())
  524. ->method('body')
  525. ->with($this->stringContains('Not there, sorry'));
  526. $ExceptionRenderer->controller->response = $response;
  527. $ExceptionRenderer->render();
  528. }
  529. /**
  530. * Test that missing subDir/layoutPath don't cause other fatal errors.
  531. *
  532. * @return void
  533. */
  534. public function testMissingSubdirRenderSafe() {
  535. $exception = new Error\NotFoundException();
  536. $ExceptionRenderer = new ExceptionRenderer($exception);
  537. $ExceptionRenderer->controller = $this->getMock('Cake\Controller\Controller', array('render'));
  538. $ExceptionRenderer->controller->helpers = array('Fail', 'Boom');
  539. $ExceptionRenderer->controller->layoutPath = 'boom';
  540. $ExceptionRenderer->controller->subDir = 'boom';
  541. $ExceptionRenderer->controller->request = new Request;
  542. $ExceptionRenderer->controller->expects($this->once())
  543. ->method('render')
  544. ->with('error400')
  545. ->will($this->throwException($exception));
  546. $response = $this->getMock('Cake\Network\Response');
  547. $response->expects($this->once())
  548. ->method('body')
  549. ->with($this->stringContains('Not Found'));
  550. $response->expects($this->once())
  551. ->method('type')
  552. ->with('html');
  553. $ExceptionRenderer->controller->response = $response;
  554. $ExceptionRenderer->render();
  555. $this->assertEquals('', $ExceptionRenderer->controller->layoutPath);
  556. $this->assertEquals('', $ExceptionRenderer->controller->subDir);
  557. $this->assertEquals('Error', $ExceptionRenderer->controller->viewPath);
  558. }
  559. /**
  560. * Test that exceptions can be rendered when an request hasn't been registered
  561. * with Router
  562. *
  563. * @return void
  564. */
  565. public function testRenderWithNoRequest() {
  566. Router::reload();
  567. $this->assertNull(Router::getRequest(false));
  568. $exception = new \Exception('Terrible');
  569. $ExceptionRenderer = new ExceptionRenderer($exception);
  570. $ExceptionRenderer->controller->response = $this->getMock('Cake\Network\Response', array('statusCode', '_sendHeader'));
  571. $ExceptionRenderer->controller->response->expects($this->once())
  572. ->method('statusCode')
  573. ->with(500);
  574. ob_start();
  575. $ExceptionRenderer->render();
  576. $result = ob_get_clean();
  577. $this->assertContains('Internal Error', $result);
  578. }
  579. /**
  580. * Tests the output of rendering a PDOException
  581. *
  582. * @return void
  583. */
  584. public function testPDOException() {
  585. $exception = new \PDOException('There was an error in the SQL query');
  586. $exception->queryString = 'SELECT * from poo_query < 5 and :seven';
  587. $exception->params = array('seven' => 7);
  588. $ExceptionRenderer = new ExceptionRenderer($exception);
  589. $ExceptionRenderer->controller->response = $this->getMock('Cake\Network\Response', array('statusCode', '_sendHeader'));
  590. $ExceptionRenderer->controller->response->expects($this->once())->method('statusCode')->with(500);
  591. ob_start();
  592. $ExceptionRenderer->render();
  593. $result = ob_get_clean();
  594. $this->assertContains('<h2>Database Error</h2>', $result);
  595. $this->assertContains('There was an error in the SQL query', $result);
  596. $this->assertContains(h('SELECT * from poo_query < 5 and :seven'), $result);
  597. $this->assertContains("'seven' => (int) 7", $result);
  598. }
  599. }