Completed
Push — master ( b149f0...42b317 )
by Arnold
03:09
created

ErrorHandlerTest::testInvokeLog()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 28
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 28
rs 8.8571
c 0
b 0
f 0
cc 1
eloc 19
nc 1
nop 0
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()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
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');
0 ignored issues
show
Documentation introduced by
'foo' is of type string, but the function expects a object<Exception>|object<Error>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
230
    }
231
    
232 View Code Duplication
    public function testLogObject()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
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());
0 ignored issues
show
Documentation introduced by
new \stdClass() is of type object<stdClass>, but the function expects a object<Exception>|object<Error>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
241
    }
242
}
243