1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Jasny; |
4
|
|
|
|
5
|
|
|
use Jasny\ErrorHandler; |
6
|
|
|
use Psr\Http\Message\ServerRequestInterface; |
7
|
|
|
use Psr\Http\Message\ResponseInterface; |
8
|
|
|
use Psr\Http\Message\StreamInterface; |
9
|
|
|
use Psr\Log\LoggerInterface; |
10
|
|
|
use Psr\Log\LogLevel; |
11
|
|
|
|
12
|
|
|
/** |
13
|
|
|
* @covers Jasny\ErrorHandler |
14
|
|
|
*/ |
15
|
|
|
class ErrorHandlerTest extends \PHPUnit_Framework_TestCase |
16
|
|
|
{ |
17
|
|
|
/** |
18
|
|
|
* Test invoke with invalid 'next' param |
19
|
|
|
* |
20
|
|
|
* @expectedException \InvalidArgumentException |
21
|
|
|
*/ |
22
|
|
|
public function testInvokeInvalidNext() |
23
|
|
|
{ |
24
|
|
|
$request = $this->createMock(ServerRequestInterface::class); |
25
|
|
|
$response = $this->createMock(ResponseInterface::class); |
26
|
|
|
|
27
|
|
|
$middleware = new ErrorHandler(); |
28
|
|
|
|
29
|
|
|
$middleware($request, $response, 'not callable'); |
30
|
|
|
} |
31
|
|
|
|
32
|
|
|
/** |
33
|
|
|
* Test case when there is no error |
34
|
|
|
*/ |
35
|
|
|
public function testInvokeNoError() |
36
|
|
|
{ |
37
|
|
|
$request = $this->createMock(ServerRequestInterface::class); |
38
|
|
|
$response = $this->createMock(ResponseInterface::class); |
39
|
|
|
$finalResponse = $this->createMock(ResponseInterface::class); |
40
|
|
|
|
41
|
|
|
$next = $this->getMockBuilder(\stdClass::class)->setMethods(['__invoke'])->getMock(); |
42
|
|
|
$next->expects($this->once())->method('__invoke') |
43
|
|
|
->with($request, $response) |
44
|
|
|
->willReturn($finalResponse); |
45
|
|
|
|
46
|
|
|
$errorHandler = new ErrorHandler(); |
47
|
|
|
|
48
|
|
|
$result = $errorHandler($request, $response, $next); |
49
|
|
|
|
50
|
|
|
$this->assertSame($finalResponse, $result); |
51
|
|
|
} |
52
|
|
|
|
53
|
|
|
/** |
54
|
|
|
* Test that Exception in 'next' callback is caught |
55
|
|
|
*/ |
56
|
|
|
public function testInvokeCatchException() |
57
|
|
|
{ |
58
|
|
|
$request = $this->createMock(ServerRequestInterface::class); |
59
|
|
|
$response = $this->createMock(ResponseInterface::class); |
60
|
|
|
$errorResponse = $this->createMock(ResponseInterface::class); |
61
|
|
|
$stream = $this->createMock(StreamInterface::class); |
62
|
|
|
|
63
|
|
|
$exception = $this->createMock(\Exception::class); |
64
|
|
|
|
65
|
|
|
$stream->expects($this->once())->method('write')->with('Unexpected error'); |
66
|
|
|
$response->expects($this->once())->method('withStatus')->with(500)->willReturn($errorResponse); |
67
|
|
|
|
68
|
|
|
$errorResponse->expects($this->once())->method('getBody')->willReturn($stream); |
69
|
|
|
|
70
|
|
|
$next = $this->getMockBuilder(\stdClass::class)->setMethods(['__invoke'])->getMock(); |
71
|
|
|
$next->expects($this->once())->method('__invoke') |
72
|
|
|
->with($request, $response) |
73
|
|
|
->willThrowException($exception); |
74
|
|
|
|
75
|
|
|
$errorHandler = new ErrorHandler(); |
76
|
|
|
|
77
|
|
|
$result = $errorHandler($request, $response, $next); |
78
|
|
|
|
79
|
|
|
$this->assertSame($errorResponse, $result); |
80
|
|
|
$this->assertSame($exception, $errorHandler->getError()); |
81
|
|
|
} |
82
|
|
|
|
83
|
|
|
/** |
84
|
|
|
* Test that an error in 'next' callback is caught |
85
|
|
|
*/ |
86
|
|
|
public function testInvokeCatchError() |
87
|
|
|
{ |
88
|
|
|
if (!class_exists('Error')) { |
89
|
|
|
$this->markTestSkipped(PHP_VERSION . " doesn't throw errors yet"); |
90
|
|
|
} |
91
|
|
|
|
92
|
|
|
$request = $this->createMock(ServerRequestInterface::class); |
93
|
|
|
$response = $this->createMock(ResponseInterface::class); |
94
|
|
|
$errorResponse = $this->createMock(ResponseInterface::class); |
95
|
|
|
$stream = $this->createMock(StreamInterface::class); |
96
|
|
|
|
97
|
|
|
$stream->expects($this->once())->method('write')->with('Unexpected error'); |
98
|
|
|
$response->expects($this->once())->method('withStatus')->with(500)->willReturn($errorResponse); |
99
|
|
|
|
100
|
|
|
$errorResponse->expects($this->once())->method('getBody')->willReturn($stream); |
101
|
|
|
|
102
|
|
|
$next = $this->getMockBuilder(\stdClass::class)->setMethods(['__invoke'])->getMock(); |
103
|
|
|
$next->expects($this->once())->method('__invoke') |
104
|
|
|
->with($request, $response) |
105
|
|
|
->willReturnCallback(function() { |
106
|
|
|
\this_function_does_not_exist(); |
107
|
|
|
}); |
108
|
|
|
|
109
|
|
|
$errorHandler = new ErrorHandler(); |
110
|
|
|
|
111
|
|
|
$result = $errorHandler($request, $response, $next); |
112
|
|
|
|
113
|
|
|
$this->assertSame($errorResponse, $result); |
114
|
|
|
|
115
|
|
|
$error = $errorHandler->getError(); |
116
|
|
|
$this->assertEquals("Call to undefined function this_function_does_not_exist()", $error->getMessage()); |
117
|
|
|
} |
118
|
|
|
|
119
|
|
|
|
120
|
|
|
public function testSetLogger() |
121
|
|
|
{ |
122
|
|
|
$logger = $this->createMock(LoggerInterface::class); |
123
|
|
|
|
124
|
|
|
$errorHandler = new ErrorHandler(); |
125
|
|
|
$errorHandler->setLogger($logger); |
126
|
|
|
|
127
|
|
|
$this->assertSame($logger, $errorHandler->getLogger()); |
128
|
|
|
} |
129
|
|
|
|
130
|
|
|
|
131
|
|
|
public function testInvokeLog() |
132
|
|
|
{ |
133
|
|
|
$request = $this->createMock(ServerRequestInterface::class); |
134
|
|
|
$response = $this->createMock(ResponseInterface::class); |
135
|
|
|
$stream = $this->createMock(StreamInterface::class); |
136
|
|
|
|
137
|
|
|
$response->method('withStatus')->willReturnSelf(); |
138
|
|
|
$response->method('getBody')->willReturn($stream); |
139
|
|
|
|
140
|
|
|
$exception = $this->createMock(\Exception::class); |
141
|
|
|
|
142
|
|
|
$message = $this->stringStartsWith('Uncaught Exception ' . get_class($exception)); |
143
|
|
|
$context = ['exception' => $exception]; |
144
|
|
|
|
145
|
|
|
$logger = $this->createMock(LoggerInterface::class); |
146
|
|
|
$logger->expects($this->once())->method('log') |
147
|
|
|
->with(LogLevel::ERROR, $message, $context); |
148
|
|
|
|
149
|
|
|
$errorHandler = new ErrorHandler(); |
150
|
|
|
$errorHandler->setLogger($logger); |
151
|
|
|
|
152
|
|
|
$next = $this->getMockBuilder(\stdClass::class)->setMethods(['__invoke'])->getMock(); |
153
|
|
|
$next->expects($this->once())->method('__invoke') |
154
|
|
|
->with($request, $response) |
155
|
|
|
->willThrowException($exception); |
156
|
|
|
|
157
|
|
|
$errorHandler($request, $response, $next); |
158
|
|
|
} |
159
|
|
|
|
160
|
|
|
public function errorProvider() |
161
|
|
|
{ |
162
|
|
|
return [ |
163
|
|
|
[E_ERROR, LogLevel::ERROR, 'Fatal error'], |
164
|
|
|
[E_USER_ERROR, LogLevel::ERROR, 'Fatal error'], |
165
|
|
|
[E_RECOVERABLE_ERROR, LogLevel::ERROR, 'Fatal error'], |
166
|
|
|
[E_WARNING, LogLevel::WARNING, 'Warning'], |
167
|
|
|
[E_USER_WARNING, LogLevel::WARNING, 'Warning'], |
168
|
|
|
[E_PARSE, LogLevel::CRITICAL, 'Parse error'], |
169
|
|
|
[E_NOTICE, LogLevel::NOTICE, 'Notice'], |
170
|
|
|
[E_USER_NOTICE, LogLevel::NOTICE, 'Notice'], |
171
|
|
|
[E_CORE_ERROR, LogLevel::CRITICAL, 'Core error'], |
172
|
|
|
[E_CORE_WARNING, LogLevel::WARNING, 'Core warning'], |
173
|
|
|
[E_COMPILE_ERROR, LogLevel::CRITICAL, 'Compile error'], |
174
|
|
|
[E_COMPILE_WARNING, LogLevel::WARNING, 'Compile warning'], |
175
|
|
|
[E_STRICT, LogLevel::INFO, 'Strict standards'], |
176
|
|
|
[E_DEPRECATED, LogLevel::INFO, 'Deprecated'], |
177
|
|
|
[E_USER_DEPRECATED, LogLevel::INFO, 'Deprecated'], |
178
|
|
|
[99999999, LogLevel::ERROR, 'Unknown error'] |
179
|
|
|
]; |
180
|
|
|
} |
181
|
|
|
|
182
|
|
|
/** |
183
|
|
|
* @dataProvider errorProvider |
184
|
|
|
* |
185
|
|
|
* @param int $code |
186
|
|
|
* @param string $level |
187
|
|
|
* @param string $type |
188
|
|
|
*/ |
189
|
|
|
public function testLogError($code, $level, $type) |
190
|
|
|
{ |
191
|
|
|
$error = new \ErrorException("no good", 0, $code, "foo.php", 42); |
192
|
|
|
$context = ['error' => $error, 'code' => $code, 'message' => "no good", 'file' => 'foo.php', 'line' => 42]; |
193
|
|
|
|
194
|
|
|
$logger = $this->createMock(LoggerInterface::class); |
195
|
|
|
$logger->expects($this->once())->method('log') |
196
|
|
|
->with($level, "$type: no good at foo.php line 42", $context); |
197
|
|
|
|
198
|
|
|
$errorHandler = new ErrorHandler(); |
199
|
|
|
$errorHandler->setLogger($logger); |
200
|
|
|
|
201
|
|
|
$errorHandler->log($error); |
202
|
|
|
} |
203
|
|
|
|
204
|
|
|
public function testLogException() |
205
|
|
|
{ |
206
|
|
|
$exception = $this->createMock(\Exception::class); |
207
|
|
|
|
208
|
|
|
$message = $this->stringStartsWith('Uncaught Exception ' . get_class($exception)); |
209
|
|
|
$context = ['exception' => $exception]; |
210
|
|
|
|
211
|
|
|
$logger = $this->createMock(LoggerInterface::class); |
212
|
|
|
$logger->expects($this->once())->method('log') |
213
|
|
|
->with(LogLevel::ERROR, $message, $context); |
214
|
|
|
|
215
|
|
|
$errorHandler = new ErrorHandler(); |
216
|
|
|
$errorHandler->setLogger($logger); |
217
|
|
|
|
218
|
|
|
$errorHandler->log($exception); |
219
|
|
|
} |
220
|
|
|
|
221
|
|
View Code Duplication |
public function testLogString() |
|
|
|
|
222
|
|
|
{ |
223
|
|
|
$logger = $this->createMock(LoggerInterface::class); |
224
|
|
|
$logger->expects($this->once())->method('log')->with(LogLevel::WARNING, "Unable to log a string"); |
225
|
|
|
|
226
|
|
|
$errorHandler = new ErrorHandler(); |
227
|
|
|
$errorHandler->setLogger($logger); |
228
|
|
|
|
229
|
|
|
$errorHandler->log('foo'); |
|
|
|
|
230
|
|
|
} |
231
|
|
|
|
232
|
|
View Code Duplication |
public function testLogObject() |
|
|
|
|
233
|
|
|
{ |
234
|
|
|
$logger = $this->createMock(LoggerInterface::class); |
235
|
|
|
$logger->expects($this->once())->method('log')->with(LogLevel::WARNING, "Unable to log a stdClass object"); |
236
|
|
|
|
237
|
|
|
$errorHandler = new ErrorHandler(); |
238
|
|
|
$errorHandler->setLogger($logger); |
239
|
|
|
|
240
|
|
|
$errorHandler->log(new \stdClass()); |
|
|
|
|
241
|
|
|
} |
242
|
|
|
} |
243
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.