ResponseEmitterTest.php 10 KB

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