/tests/TestCase/Http/ResponseEmitterTest.php

https://github.com/LubosRemplik/cakephp · PHP · 366 lines · 253 code · 35 blank · 78 comment · 0 complexity · d4eda13c4274c7eda55b6bf23a7b2b1e MD5 · raw file

  1. <?php
  2. /**
  3. * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
  4. * Copyright (c) Cake Software Foundation, Inc. (https://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. (https://cakefoundation.org)
  11. * @link https://cakephp.org CakePHP(tm) Project
  12. * @since 3.3.5
  13. * @license https://opensource.org/licenses/mit-license.php MIT License
  14. */
  15. namespace Cake\Test\TestCase;
  16. use Cake\Http\CallbackStream;
  17. use Cake\Http\Response;
  18. use Cake\Http\ResponseEmitter;
  19. use Cake\TestSuite\TestCase;
  20. require_once __DIR__ . '/server_mocks.php';
  21. /**
  22. * Response emitter test.
  23. */
  24. class ResponseEmitterTest extends TestCase
  25. {
  26. protected $emitter;
  27. /**
  28. * setup
  29. *
  30. * @return void
  31. */
  32. public function setUp()
  33. {
  34. parent::setUp();
  35. $GLOBALS['mockedHeadersSent'] = false;
  36. $GLOBALS['mockedHeaders'] = $GLOBALS['mockedCookies'] = [];
  37. $this->emitter = new ResponseEmitter();
  38. }
  39. /**
  40. * teardown
  41. *
  42. * @return void
  43. */
  44. public function tearDown()
  45. {
  46. parent::tearDown();
  47. unset($GLOBALS['mockedHeadersSent']);
  48. }
  49. /**
  50. * Test emitting simple responses.
  51. *
  52. * @return void
  53. */
  54. public function testEmitResponseSimple()
  55. {
  56. $response = (new Response())
  57. ->withStatus(201)
  58. ->withHeader('Content-Type', 'text/html')
  59. ->withHeader('Location', 'http://example.com/cake/1');
  60. $response->getBody()->write('It worked');
  61. ob_start();
  62. $this->emitter->emit($response);
  63. $out = ob_get_clean();
  64. $this->assertEquals('It worked', $out);
  65. $expected = [
  66. 'HTTP/1.1 201 Created',
  67. 'Content-Type: text/html',
  68. 'Location: http://example.com/cake/1'
  69. ];
  70. $this->assertEquals($expected, $GLOBALS['mockedHeaders']);
  71. }
  72. /**
  73. * Test emitting a no-content response
  74. *
  75. * @return void
  76. */
  77. public function testEmitNoContentResponse()
  78. {
  79. $response = (new Response())
  80. ->withHeader('X-testing', 'value')
  81. ->withStatus(204);
  82. $response->getBody()->write('It worked');
  83. ob_start();
  84. $this->emitter->emit($response);
  85. $out = ob_get_clean();
  86. $this->assertEquals('', $out);
  87. $expected = [
  88. 'HTTP/1.1 204 No Content',
  89. 'X-testing: value',
  90. ];
  91. $this->assertEquals($expected, $GLOBALS['mockedHeaders']);
  92. }
  93. /**
  94. * Test emitting responses with array cookes
  95. *
  96. * @return void
  97. */
  98. public function testEmitResponseArrayCookies()
  99. {
  100. $response = (new Response())
  101. ->withCookie('simple', ['value' => 'val', 'secure' => true])
  102. ->withAddedHeader('Set-Cookie', 'google=not=nice;Path=/accounts; HttpOnly')
  103. ->withHeader('Content-Type', 'text/plain');
  104. $response->getBody()->write('ok');
  105. ob_start();
  106. $this->emitter->emit($response);
  107. $out = ob_get_clean();
  108. $this->assertEquals('ok', $out);
  109. $expected = [
  110. 'HTTP/1.1 200 OK',
  111. 'Content-Type: text/plain'
  112. ];
  113. $this->assertEquals($expected, $GLOBALS['mockedHeaders']);
  114. $expected = [
  115. [
  116. 'name' => 'simple',
  117. 'value' => 'val',
  118. 'path' => '/',
  119. 'expire' => 0,
  120. 'domain' => '',
  121. 'secure' => true,
  122. 'httponly' => false
  123. ],
  124. [
  125. 'name' => 'google',
  126. 'value' => 'not=nice',
  127. 'path' => '/accounts',
  128. 'expire' => 0,
  129. 'domain' => '',
  130. 'secure' => false,
  131. 'httponly' => true
  132. ],
  133. ];
  134. $this->assertEquals($expected, $GLOBALS['mockedCookies']);
  135. }
  136. /**
  137. * Test emitting responses with cookies
  138. *
  139. * @return void
  140. */
  141. public function testEmitResponseCookies()
  142. {
  143. $response = (new Response())
  144. ->withAddedHeader('Set-Cookie', "simple=val;\tSecure")
  145. ->withAddedHeader('Set-Cookie', 'people=jim,jack,jonny";";Path=/accounts')
  146. ->withAddedHeader('Set-Cookie', 'google=not=nice;Path=/accounts; HttpOnly')
  147. ->withAddedHeader('Set-Cookie', 'a=b; Expires=Wed, 13 Jan 2021 22:23:01 GMT; Domain=www.example.com;')
  148. ->withAddedHeader('Set-Cookie', 'list%5B%5D=a%20b%20c')
  149. ->withHeader('Content-Type', 'text/plain');
  150. $response->getBody()->write('ok');
  151. ob_start();
  152. $this->emitter->emit($response);
  153. $out = ob_get_clean();
  154. $this->assertEquals('ok', $out);
  155. $expected = [
  156. 'HTTP/1.1 200 OK',
  157. 'Content-Type: text/plain'
  158. ];
  159. $this->assertEquals($expected, $GLOBALS['mockedHeaders']);
  160. $expected = [
  161. [
  162. 'name' => 'simple',
  163. 'value' => 'val',
  164. 'path' => '',
  165. 'expire' => 0,
  166. 'domain' => '',
  167. 'secure' => true,
  168. 'httponly' => false
  169. ],
  170. [
  171. 'name' => 'people',
  172. 'value' => 'jim,jack,jonny";"',
  173. 'path' => '/accounts',
  174. 'expire' => 0,
  175. 'domain' => '',
  176. 'secure' => false,
  177. 'httponly' => false
  178. ],
  179. [
  180. 'name' => 'google',
  181. 'value' => 'not=nice',
  182. 'path' => '/accounts',
  183. 'expire' => 0,
  184. 'domain' => '',
  185. 'secure' => false,
  186. 'httponly' => true
  187. ],
  188. [
  189. 'name' => 'a',
  190. 'value' => 'b',
  191. 'path' => '',
  192. 'expire' => 1610576581,
  193. 'domain' => 'www.example.com',
  194. 'secure' => false,
  195. 'httponly' => false
  196. ],
  197. [
  198. 'name' => 'list[]',
  199. 'value' => 'a b c',
  200. 'path' => '',
  201. 'expire' => 0,
  202. 'domain' => '',
  203. 'secure' => false,
  204. 'httponly' => false
  205. ],
  206. ];
  207. $this->assertEquals($expected, $GLOBALS['mockedCookies']);
  208. }
  209. /**
  210. * Test emitting responses using callback streams.
  211. *
  212. * We use callback streams for closure based responses.
  213. *
  214. * @return void
  215. */
  216. public function testEmitResponseCallbackStream()
  217. {
  218. $stream = new CallbackStream(function () {
  219. echo 'It worked';
  220. });
  221. $response = (new Response())
  222. ->withStatus(201)
  223. ->withBody($stream)
  224. ->withHeader('Content-Type', 'text/plain');
  225. ob_start();
  226. $this->emitter->emit($response);
  227. $out = ob_get_clean();
  228. $this->assertEquals('It worked', $out);
  229. $expected = [
  230. 'HTTP/1.1 201 Created',
  231. 'Content-Type: text/plain',
  232. ];
  233. $this->assertEquals($expected, $GLOBALS['mockedHeaders']);
  234. }
  235. /**
  236. * Test valid body ranges.
  237. *
  238. * @return void
  239. */
  240. public function testEmitResponseBodyRange()
  241. {
  242. $response = (new Response())
  243. ->withHeader('Content-Type', 'text/plain')
  244. ->withHeader('Content-Range', 'bytes 1-4/9');
  245. $response->getBody()->write('It worked');
  246. ob_start();
  247. $this->emitter->emit($response);
  248. $out = ob_get_clean();
  249. $this->assertEquals('t wo', $out);
  250. $expected = [
  251. 'HTTP/1.1 200 OK',
  252. 'Content-Type: text/plain',
  253. 'Content-Range: bytes 1-4/9',
  254. ];
  255. $this->assertEquals($expected, $GLOBALS['mockedHeaders']);
  256. }
  257. /**
  258. * Test valid body ranges.
  259. *
  260. * @return void
  261. */
  262. public function testEmitResponseBodyRangeComplete()
  263. {
  264. $response = (new Response())
  265. ->withHeader('Content-Type', 'text/plain')
  266. ->withHeader('Content-Range', 'bytes 0-20/9');
  267. $response->getBody()->write('It worked');
  268. ob_start();
  269. $this->emitter->emit($response, 2);
  270. $out = ob_get_clean();
  271. $this->assertEquals('It worked', $out);
  272. }
  273. /**
  274. * Test out of bounds body ranges.
  275. *
  276. * @return void
  277. */
  278. public function testEmitResponseBodyRangeOverflow()
  279. {
  280. $response = (new Response())
  281. ->withHeader('Content-Type', 'text/plain')
  282. ->withHeader('Content-Range', 'bytes 5-20/9');
  283. $response->getBody()->write('It worked');
  284. ob_start();
  285. $this->emitter->emit($response);
  286. $out = ob_get_clean();
  287. $this->assertEquals('rked', $out);
  288. }
  289. /**
  290. * Test malformed content-range header
  291. *
  292. * @return void
  293. */
  294. public function testEmitResponseBodyRangeMalformed()
  295. {
  296. $response = (new Response())
  297. ->withHeader('Content-Type', 'text/plain')
  298. ->withHeader('Content-Range', 'bytes 9-ba/a');
  299. $response->getBody()->write('It worked');
  300. ob_start();
  301. $this->emitter->emit($response);
  302. $out = ob_get_clean();
  303. $this->assertEquals('It worked', $out);
  304. }
  305. /**
  306. * Test callback streams returning content and ranges
  307. *
  308. * @return void
  309. */
  310. public function testEmitResponseBodyRangeCallbackStream()
  311. {
  312. $stream = new CallbackStream(function () {
  313. return 'It worked';
  314. });
  315. $response = (new Response())
  316. ->withStatus(201)
  317. ->withBody($stream)
  318. ->withHeader('Content-Range', 'bytes 1-4/9')
  319. ->withHeader('Content-Type', 'text/plain');
  320. ob_start();
  321. $this->emitter->emit($response);
  322. $out = ob_get_clean();
  323. $this->assertEquals('t wo', $out);
  324. $expected = [
  325. 'HTTP/1.1 201 Created',
  326. 'Content-Range: bytes 1-4/9',
  327. 'Content-Type: text/plain',
  328. ];
  329. $this->assertEquals($expected, $GLOBALS['mockedHeaders']);
  330. }
  331. }