[FEATURE] Add support for PSR-15 HTTP middlewares
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Tests / Unit / Http / MiddlewareDispatcherTest.php
1 <?php
2 declare(strict_types = 1);
3 namespace TYPO3\CMS\Core\Tests\Unit\Http;
4
5 /*
6 * This file is part of the TYPO3 CMS project.
7 *
8 * It is free software; you can redistribute it and/or modify it under
9 * the terms of the GNU General Public License, either version 2
10 * of the License, or any later version.
11 *
12 * For the full copyright and license information, please read the
13 * LICENSE.txt file that was distributed with this source code.
14 *
15 * The TYPO3 project - inspiring people to share!
16 */
17
18 use Psr\Http\Message\ResponseInterface;
19 use Psr\Http\Message\ServerRequestInterface;
20 use Psr\Http\Server\MiddlewareInterface;
21 use Psr\Http\Server\RequestHandlerInterface;
22 use TYPO3\CMS\Core\Http\MiddlewareDispatcher;
23 use TYPO3\CMS\Core\Http\Response;
24 use TYPO3\CMS\Core\Http\ServerRequest;
25 use TYPO3\CMS\Core\Tests\Unit\Http\Fixtures\MiddlewareFixture;
26
27 /**
28 * Test case
29 */
30 class MiddlewareDispatcherTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestCase
31 {
32 /**
33 * @test
34 */
35 public function executesKernelWithEmptyMiddlewareStack()
36 {
37 $kernel = new class implements RequestHandlerInterface {
38 public function handle(ServerRequestInterface $request): ResponseInterface
39 {
40 return (new Response)->withStatus(204);
41 }
42 };
43
44 $dispatcher = new MiddlewareDispatcher($kernel);
45 $response = $dispatcher->handle(new ServerRequest);
46
47 $this->assertSame(204, $response->getStatusCode());
48 }
49
50 /**
51 * @test
52 */
53 public function executesMiddlewaresLastInFirstOut()
54 {
55 $kernel = new class implements RequestHandlerInterface {
56 public function handle(ServerRequestInterface $request): ResponseInterface
57 {
58 return (new Response)
59 ->withStatus(204)
60 ->withHeader('X-SEQ-PRE-REQ-HANDLER', $request->getHeader('X-SEQ-PRE-REQ-HANDLER'));
61 }
62 };
63
64 $middleware1 = new class implements MiddlewareInterface {
65 public $id = '0';
66
67 public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
68 {
69 $request = $request->withAddedHeader('X-SEQ-PRE-REQ-HANDLER', $this->id);
70 $response = $handler->handle($request);
71
72 return $response->withAddedHeader('X-SEQ-POST-REQ-HANDLER', $this->id);
73 }
74 };
75
76 $middleware2 = clone $middleware1;
77 $middleware2->id = '1';
78
79 MiddlewareFixture::$id = '2';
80
81 $middleware4 = clone $middleware1;
82 $middleware4->id = '3';
83
84 $dispatcher = new MiddlewareDispatcher($kernel, [$middleware1, $middleware2]);
85 $dispatcher->lazy(MiddlewareFixture::class);
86 $dispatcher->add($middleware4);
87
88 $response = $dispatcher->handle(new ServerRequest);
89
90 $this->assertSame(['3', '2', '1', '0'], $response->getHeader('X-SEQ-PRE-REQ-HANDLER'));
91 $this->assertSame(['0', '1', '2', '3'], $response->getHeader('X-SEQ-POST-REQ-HANDLER'));
92 $this->assertSame(204, $response->getStatusCode());
93 }
94
95 /**
96 * @test
97 */
98 public function doesNotInstantiateLazyMiddlewareInCaseOfAnEarlyReturningOuterMiddleware()
99 {
100 $kernel = new class implements RequestHandlerInterface {
101 public function handle(ServerRequestInterface $request): ResponseInterface
102 {
103 return new Response;
104 }
105 };
106 $middleware = new class implements MiddlewareInterface {
107 public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
108 {
109 return (new Response)->withStatus(404);
110 }
111 };
112
113 MiddlewareFixture::$hasBeenInstantiated = false;
114 $dispatcher = new MiddlewareDispatcher($kernel, [MiddlewareFixture::class, $middleware]);
115 $response = $dispatcher->handle(new ServerRequest);
116
117 $this->assertFalse(MiddlewareFixture::$hasBeenInstantiated);
118 $this->assertSame(404, $response->getStatusCode());
119 }
120
121 /**
122 * @test
123 */
124 public function throwsExceptionForLazyNonMiddlewareInterfaceClasses()
125 {
126 $this->expectException(\InvalidArgumentException::class);
127 $this->expectExceptionCode(1516821342);
128
129 $kernel = new class implements RequestHandlerInterface {
130 public function handle(ServerRequestInterface $request): ResponseInterface
131 {
132 return new Response;
133 }
134 };
135
136 MiddlewareFixture::$hasBeenInstantiated = false;
137 $dispatcher = new MiddlewareDispatcher($kernel);
138 $dispatcher->lazy(\stdClass::class);
139 $response = $dispatcher->handle(new ServerRequest);
140 }
141
142 /**
143 * @test
144 */
145 public function canBeExcutedMultipleTimes()
146 {
147 $kernel = new class implements RequestHandlerInterface {
148 public function handle(ServerRequestInterface $request): ResponseInterface
149 {
150 return new Response;
151 }
152 };
153 $middleware = new class implements MiddlewareInterface {
154 public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
155 {
156 return (new Response)->withStatus(204);
157 }
158 };
159
160 $dispatcher = new MiddlewareDispatcher($kernel);
161 $dispatcher->add($middleware);
162
163 $response1 = $dispatcher->handle(new ServerRequest);
164 $response2 = $dispatcher->handle(new ServerRequest);
165
166 $this->assertSame(204, $response1->getStatusCode());
167 $this->assertSame(204, $response2->getStatusCode());
168 }
169
170 /**
171 * @test
172 */
173 public function canBeReExecutedRecursivelyDuringDispatch()
174 {
175 $kernel = new class implements RequestHandlerInterface {
176 public function handle(ServerRequestInterface $request): ResponseInterface
177 {
178 return new Response;
179 }
180 };
181
182 $dispatcher = new MiddlewareDispatcher($kernel);
183
184 $dispatcher->add(new class($dispatcher) implements MiddlewareInterface {
185 private $dispatcher = null;
186
187 public function __construct(RequestHandlerInterface $dispatcher)
188 {
189 $this->dispatcher = $dispatcher;
190 }
191
192 public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
193 {
194 if ($request->hasHeader('X-NESTED')) {
195 return (new Response)->withStatus(204)->withAddedHeader('X-TRACE', 'nested');
196 }
197
198 $response = $this->dispatcher->handle($request->withAddedHeader('X-NESTED', '1'));
199
200 return $response->withAddedHeader('X-TRACE', 'outer');
201 }
202 });
203
204 $response = $dispatcher->handle(new ServerRequest);
205
206 $this->assertSame(204, $response->getStatusCode());
207 $this->assertSame(['nested', 'outer'], $response->getHeader('X-TRACE'));
208 }
209 }