10
10
11
11
namespace Slim \Tests \Middleware ;
12
12
13
+ use DOMDocument ;
14
+ use PHPUnit \Framework \Attributes \DataProvider ;
13
15
use PHPUnit \Framework \TestCase ;
14
- use Psr \Http \Message \ResponseFactoryInterface ;
15
- use Psr \Http \Message \ResponseInterface ;
16
16
use Psr \Http \Message \ServerRequestFactoryInterface ;
17
- use Psr \Http \Message \ServerRequestInterface ;
18
17
use RuntimeException ;
19
18
use Slim \Builder \AppBuilder ;
20
- use Slim \Interfaces \ExceptionHandlerInterface ;
19
+ use Slim \Error \Renderers \HtmlExceptionRenderer ;
20
+ use Slim \Error \Renderers \JsonExceptionRenderer ;
21
+ use Slim \Error \Renderers \XmlExceptionRenderer ;
22
+ use Slim \Media \MediaType ;
21
23
use Slim \Middleware \EndpointMiddleware ;
22
24
use Slim \Middleware \ExceptionHandlingMiddleware ;
23
25
use Slim \Middleware \RoutingMiddleware ;
24
26
use Slim \Tests \Traits \AppTestTrait ;
25
- use Throwable ;
26
27
27
28
final class ExceptionHandlingMiddlewareTest extends TestCase
28
29
{
29
30
use AppTestTrait;
30
31
31
- public function testExceptionHandlingMiddlewareHandlesException ()
32
+ public function testDefaultHandlerWithoutDetails (): void
32
33
{
33
34
$ builder = new AppBuilder ();
35
+ $ builder ->addDefinitions (
36
+ [
37
+ ExceptionHandlingMiddleware::class => function ($ container ) {
38
+ $ middleware = ExceptionHandlingMiddleware::createFromContainer ($ container );
39
+
40
+ return $ middleware
41
+ ->withDisplayErrorDetails (false )
42
+ ->withDefaultHandler (HtmlExceptionRenderer::class);
43
+ },
44
+ ]
45
+ );
34
46
$ app = $ builder ->build ();
35
47
36
- $ responseFactory = $ app ->getContainer ()->get (ResponseFactoryInterface::class);
37
-
38
- // Custom ExceptionHandlerInterface implementation
39
- $ exceptionHandler = new class ($ responseFactory ) implements ExceptionHandlerInterface {
40
- private ResponseFactoryInterface $ responseFactory ;
41
-
42
- public function __construct ($ responseFactory )
43
- {
44
- $ this ->responseFactory = $ responseFactory ;
45
- }
46
-
47
- public function __invoke (ServerRequestInterface $ request , Throwable $ exception ): ResponseInterface
48
- {
49
- $ response = $ this ->responseFactory ->createResponse (500 , 'Internal Server Error ' );
50
- $ response ->getBody ()->write ($ exception ->getMessage ());
51
-
52
- return $ response ;
53
- }
54
- };
55
-
56
- $ app ->add ((new ExceptionHandlingMiddleware ())->withExceptionHandler ($ exceptionHandler ));
48
+ $ app ->add (ExceptionHandlingMiddleware::class);
57
49
$ app ->add (RoutingMiddleware::class);
58
50
$ app ->add (EndpointMiddleware::class);
59
51
60
- $ app ->get ('/ ' , function () {
61
- throw new RuntimeException ('Something went wrong ' );
62
- });
63
-
64
52
$ request = $ app ->getContainer ()
65
53
->get (ServerRequestFactoryInterface::class)
66
54
->createServerRequest ('GET ' , '/ ' );
67
55
68
- $ response = $ app ->handle ($ request );
69
- $ this ->assertEquals (500 , $ response ->getStatusCode ());
70
- $ this ->assertSame ('Something went wrong ' , (string )$ response ->getBody ());
71
- }
72
-
73
- public function testExceptionHandlingMiddlewarePassesThroughNonExceptionRequest ()
74
- {
75
- $ builder = new AppBuilder ();
76
- $ app = $ builder ->build ();
77
-
78
- $ responseFactory = $ app ->getContainer ()->get (ResponseFactoryInterface::class);
79
-
80
- // This handler should not be called in this test
81
- $ exceptionHandler = new class ($ responseFactory ) implements ExceptionHandlerInterface {
82
- private ResponseFactoryInterface $ responseFactory ;
83
-
84
- public function __construct ($ responseFactory )
85
- {
86
- $ this ->responseFactory = $ responseFactory ;
87
- }
88
-
89
- public function __invoke (ServerRequestInterface $ request , Throwable $ exception ): ResponseInterface
90
- {
91
- $ response = $ this ->responseFactory ->createResponse (500 );
92
- $ response ->getBody ()->write ($ exception ->getMessage ());
93
-
94
- return $ response ;
95
- }
96
- };
97
-
98
- $ app ->add (new ExceptionHandlingMiddleware ($ exceptionHandler ));
99
- $ app ->add (RoutingMiddleware::class);
100
- $ app ->add (EndpointMiddleware::class);
101
-
102
- $ app ->get ('/ ' , function (ServerRequestInterface $ request , ResponseInterface $ response ) {
103
- $ response ->getBody ()->write ('Hello World ' );
104
-
105
- return $ response ;
56
+ $ app ->get ('/ ' , function () {
57
+ throw new RuntimeException ('Test error ' );
106
58
});
107
59
108
- $ request = $ app ->getContainer ()
109
- ->get (ServerRequestFactoryInterface::class)
110
- ->createServerRequest ('GET ' , '/ ' );
111
-
112
60
$ response = $ app ->handle ($ request );
113
- $ this ->assertEquals (200 , $ response ->getStatusCode ());
114
- $ this ->assertSame ('Hello World ' , (string )$ response ->getBody ());
61
+
62
+ $ this ->assertSame (500 , $ response ->getStatusCode ());
63
+ $ this ->assertSame ('text/html ' , $ response ->getHeaderLine ('Content-Type ' ));
64
+ $ this ->assertStringNotContainsString ('Test Error message ' , (string )$ response ->getBody ());
65
+ $ this ->assertStringContainsString ('<h1>Application Error</h1> ' , (string )$ response ->getBody ());
115
66
}
116
67
117
- public function testDefaultMediaTypeWithoutDetails (): void
68
+ public function testDefaultHandlerWithDetails (): void
118
69
{
119
70
$ builder = new AppBuilder ();
71
+ $ builder ->addDefinitions (
72
+ [
73
+ ExceptionHandlingMiddleware::class => function ($ container ) {
74
+ $ middleware = ExceptionHandlingMiddleware::createFromContainer ($ container );
75
+
76
+ return $ middleware
77
+ ->withDisplayErrorDetails (true )
78
+ ->withDefaultHandler (HtmlExceptionRenderer::class);
79
+ },
80
+ ]
81
+ );
120
82
$ app = $ builder ->build ();
121
83
122
84
$ app ->add (ExceptionHandlingMiddleware::class);
@@ -128,21 +90,32 @@ public function testDefaultMediaTypeWithoutDetails(): void
128
90
->createServerRequest ('GET ' , '/ ' );
129
91
130
92
$ app ->get ('/ ' , function () {
131
- throw new RuntimeException ('Test error ' );
93
+ throw new RuntimeException ('Test error ' , 123 );
132
94
});
133
95
134
96
$ response = $ app ->handle ($ request );
135
97
136
98
$ this ->assertSame (500 , $ response ->getStatusCode ());
137
- $ this ->assertSame ('text/html ' , $ response ->getHeaderLine ('Content-Type ' ));
99
+ $ this ->assertSame ('text/html ' , ( string ) $ response ->getHeaderLine ('Content-Type ' ));
138
100
$ this ->assertStringNotContainsString ('Test Error message ' , (string )$ response ->getBody ());
139
101
$ this ->assertStringContainsString ('<h1>Application Error</h1> ' , (string )$ response ->getBody ());
140
102
}
141
103
142
104
public function testDefaultHtmlMediaTypeWithDetails (): void
143
105
{
144
106
$ builder = new AppBuilder ();
145
- $ builder ->addSettings (['display_error_details ' => true ]);
107
+ $ builder ->addDefinitions (
108
+ [
109
+ ExceptionHandlingMiddleware::class => function ($ container ) {
110
+ $ middleware = ExceptionHandlingMiddleware::createFromContainer ($ container );
111
+
112
+ return $ middleware
113
+ ->withDisplayErrorDetails (true )
114
+ ->withDefaultMediaType (MediaType::TEXT_HTML )
115
+ ->withHandler (MediaType::TEXT_HTML , HtmlExceptionRenderer::class);
116
+ },
117
+ ]
118
+ );
146
119
$ app = $ builder ->build ();
147
120
148
121
$ app ->add (ExceptionHandlingMiddleware::class);
@@ -151,7 +124,8 @@ public function testDefaultHtmlMediaTypeWithDetails(): void
151
124
152
125
$ request = $ app ->getContainer ()
153
126
->get (ServerRequestFactoryInterface::class)
154
- ->createServerRequest ('GET ' , '/ ' );
127
+ ->createServerRequest ('GET ' , '/ ' )
128
+ ->withHeader ('Accept ' , 'application/json ' );
155
129
156
130
$ app ->get ('/ ' , function () {
157
131
throw new RuntimeException ('Test error ' , 123 );
@@ -165,10 +139,22 @@ public function testDefaultHtmlMediaTypeWithDetails(): void
165
139
$ this ->assertStringContainsString ('<h1>Application Error</h1> ' , (string )$ response ->getBody ());
166
140
}
167
141
168
- public function testJsonMediaTypeWithDetails (): void
142
+ public function testJsonMediaTypeDisplayErrorDetails (): void
169
143
{
170
144
$ builder = new AppBuilder ();
171
- $ builder ->addSettings (['display_error_details ' => true ]);
145
+
146
+ $ builder ->addDefinitions (
147
+ [
148
+ ExceptionHandlingMiddleware::class => function ($ container ) {
149
+ $ middleware = ExceptionHandlingMiddleware::createFromContainer ($ container );
150
+
151
+ return $ middleware
152
+ ->withDisplayErrorDetails (true )
153
+ ->withHandler (MediaType::APPLICATION_JSON , JsonExceptionRenderer::class);
154
+ },
155
+ ]
156
+ );
157
+
172
158
$ app = $ builder ->build ();
173
159
174
160
$ app ->add (ExceptionHandlingMiddleware::class);
@@ -197,12 +183,12 @@ public function testJsonMediaTypeWithDetails(): void
197
183
public function testWithoutHandler (): void
198
184
{
199
185
$ this ->expectException (RuntimeException::class);
200
- $ this ->expectExceptionMessage ('Test error ' );
186
+ $ this ->expectExceptionMessage ('Exception handler for "text/html" not found ' );
201
187
202
188
$ builder = new AppBuilder ();
203
189
$ app = $ builder ->build ();
204
190
205
- $ app ->add (new ExceptionHandlingMiddleware () );
191
+ $ app ->add (ExceptionHandlingMiddleware::class );
206
192
$ app ->add (RoutingMiddleware::class);
207
193
$ app ->add (EndpointMiddleware::class);
208
194
@@ -216,4 +202,155 @@ public function testWithoutHandler(): void
216
202
217
203
$ app ->handle ($ request );
218
204
}
205
+
206
+ #[DataProvider('textHmlHeaderProvider ' )]
207
+ public function testWithTextHtml (string $ header , string $ headerValue ): void
208
+ {
209
+ $ builder = new AppBuilder ();
210
+ $ builder ->addDefinitions (
211
+ [
212
+ ExceptionHandlingMiddleware::class => function ($ container ) {
213
+ $ middleware = ExceptionHandlingMiddleware::createFromContainer ($ container );
214
+
215
+ return $ middleware
216
+ ->withDisplayErrorDetails (true )
217
+ ->withHandler (MediaType::TEXT_HTML , HtmlExceptionRenderer::class);
218
+ },
219
+ ]
220
+ );
221
+ $ app = $ builder ->build ();
222
+
223
+ $ app ->add (ExceptionHandlingMiddleware::class);
224
+ $ app ->add (RoutingMiddleware::class);
225
+ $ app ->add (EndpointMiddleware::class);
226
+
227
+ $ app ->get ('/ ' , function () {
228
+ throw new RuntimeException ('Test Error message ' );
229
+ });
230
+
231
+ $ request = $ app ->getContainer ()
232
+ ->get (ServerRequestFactoryInterface::class)
233
+ ->createServerRequest ('GET ' , '/ ' )
234
+ ->withHeader ($ header , $ headerValue );
235
+
236
+ $ response = $ app ->handle ($ request );
237
+
238
+ $ this ->assertSame (500 , $ response ->getStatusCode ());
239
+ $ this ->assertSame ('text/html ' , (string )$ response ->getHeaderLine ('Content-Type ' ));
240
+ $ this ->assertStringContainsString ('Test Error message ' , (string )$ response ->getBody ());
241
+ }
242
+
243
+ public static function textHmlHeaderProvider (): array
244
+ {
245
+ return [
246
+ ['Accept ' , 'text/html ' ],
247
+ ['Accept ' , 'text/html, application/xhtml+xml, application/xml;q=0.9, image/webp, */*;q=0.8 ' ],
248
+ ['Content-Type ' , 'text/html ' ],
249
+ ['Content-Type ' , 'text/html; charset=utf-8 ' ],
250
+ ];
251
+ }
252
+
253
+ // todo: Add test for other media types
254
+
255
+ public function testWithAcceptJson (): void
256
+ {
257
+ $ builder = new AppBuilder ();
258
+ $ builder ->addDefinitions (
259
+ [
260
+ ExceptionHandlingMiddleware::class => function ($ container ) {
261
+ $ middleware = ExceptionHandlingMiddleware::createFromContainer ($ container );
262
+
263
+ return $ middleware
264
+ ->withDisplayErrorDetails (false )
265
+ ->withHandler (MediaType::APPLICATION_JSON , JsonExceptionRenderer::class);
266
+ },
267
+ ]
268
+ );
269
+ $ app = $ builder ->build ();
270
+
271
+ $ app ->add (ExceptionHandlingMiddleware::class);
272
+ $ app ->add (RoutingMiddleware::class);
273
+ $ app ->add (EndpointMiddleware::class);
274
+
275
+ $ request = $ app ->getContainer ()
276
+ ->get (ServerRequestFactoryInterface::class)
277
+ ->createServerRequest ('GET ' , '/ ' )
278
+ ->withHeader ('Accept ' , 'application/json ' );
279
+
280
+ $ app ->get ('/ ' , function () {
281
+ throw new RuntimeException ('Test exception ' );
282
+ });
283
+
284
+ $ response = $ app ->handle ($ request );
285
+
286
+ $ this ->assertSame (500 , $ response ->getStatusCode ());
287
+ $ expected = [
288
+ 'message ' => 'Application Error ' ,
289
+ ];
290
+ $ this ->assertJsonResponse ($ expected , $ response );
291
+ }
292
+
293
+ public static function xmlHeaderProvider (): array
294
+ {
295
+ return [
296
+ ['Accept ' , 'application/xml ' ],
297
+ ['Accept ' , 'application/xml, application/json ' ],
298
+ ['Content-Type ' , 'application/xml ' ],
299
+ ['Content-Type ' , 'application/xml; charset=utf-8 ' ],
300
+ ];
301
+ }
302
+
303
+ #[DataProvider('xmlHeaderProvider ' )]
304
+ public function testWithAcceptXml (string $ header , string $ headerValue ): void
305
+ {
306
+ $ builder = new AppBuilder ();
307
+ $ builder ->addDefinitions (
308
+ [
309
+ ExceptionHandlingMiddleware::class => function ($ container ) {
310
+ $ middleware = ExceptionHandlingMiddleware::createFromContainer ($ container );
311
+
312
+ return $ middleware ->withDisplayErrorDetails (false )
313
+ ->withoutHandlers ()
314
+ ->withHandler ('application/json ' , JsonExceptionRenderer::class)
315
+ ->withHandler ('application/xml ' , XmlExceptionRenderer::class);
316
+ },
317
+ ]
318
+ );
319
+ $ app = $ builder ->build ();
320
+
321
+ $ app ->add (ExceptionHandlingMiddleware::class);
322
+ $ app ->add (RoutingMiddleware::class);
323
+ $ app ->add (EndpointMiddleware::class);
324
+
325
+ $ request = $ app ->getContainer ()
326
+ ->get (ServerRequestFactoryInterface::class)
327
+ ->createServerRequest ('GET ' , '/ ' )
328
+ ->withHeader ($ header , $ headerValue );
329
+
330
+ $ app ->get ('/ ' , function () {
331
+ throw new RuntimeException ('Test exception ' );
332
+ });
333
+
334
+ $ response = $ app ->handle ($ request );
335
+
336
+ $ this ->assertSame (500 , $ response ->getStatusCode ());
337
+ $ expected = '<?xml version="1.0" encoding="UTF-8"?>
338
+ <error>
339
+ <message>Application Error</message>
340
+ </error> ' ;
341
+
342
+ $ dom = new DOMDocument ();
343
+ $ dom ->preserveWhiteSpace = false ;
344
+ $ dom ->formatOutput = true ;
345
+ $ dom ->loadXML ($ expected );
346
+ $ expected = $ dom ->saveXML ();
347
+
348
+ $ dom2 = new DOMDocument ();
349
+ $ dom2 ->preserveWhiteSpace = false ;
350
+ $ dom2 ->formatOutput = true ;
351
+ $ dom2 ->loadXML ((string )$ response ->getBody ());
352
+ $ actual = $ dom2 ->saveXML ();
353
+
354
+ $ this ->assertSame ($ expected , $ actual );
355
+ }
219
356
}
0 commit comments