Passed
Pull Request — master (#195)
by
unknown
02:50
created

exceptionWhenHeadersHaveBeenSent()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 5
nc 1
nop 0
dl 0
loc 8
rs 10
c 0
b 0
f 0
1
<?php
2
namespace Yiisoft\Yii\Web\Tests\Emitter;
3
4
include 'httpFunctionMocks.php';
5
6
use Nyholm\Psr7\Response;
7
use PHPUnit\Framework\TestCase;
8
use Psr\Http\Message\ResponseInterface;
9
use Psr\Http\Message\StreamInterface;
10
use Yiisoft\FriendlyException\FriendlyExceptionInterface;
11
use Yiisoft\Yii\Web\Emitter\EmitterInterface;
12
use Yiisoft\Yii\Web\Emitter\SapiEmitter;
13
use Yiisoft\Yii\Web\Exception\HeadersHaveBeenSentException;
0 ignored issues
show
Bug introduced by
The type Yiisoft\Yii\Web\Exceptio...rsHaveBeenSentException was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
14
15
16
/**
17
 * @runTestsInSeparateProcesses
18
 */
19
class SapiEmitterTest extends TestCase
20
{
21
    public function setUp(): void
22
    {
23
        HTTPFunctions::reset();
24
    }
25
26
    public static function tearDownAfterClass(): void
27
    {
28
        HTTPFunctions::reset();
29
    }
30
31
    public function noBodyHTTPCodeProvider(): array
32
    {
33
        return [[100], [101], [102], [204], [205], [304]];
34
    }
35
36
    public function testEmit(): void
37
    {
38
        $body = 'Example body';
39
        $response = $this->createResponse(200, ['X-Test' => 1], $body);
40
41
        $this->createEmitter()->emit($response);
42
43
        $this->assertEquals(200, $this->getResponseCode());
44
        $this->assertCount(2, $this->getHeaders());
45
        $this->assertContains('X-Test: 1', $this->getHeaders());
46
        $this->assertContains('Content-Length: ' . strlen($body), $this->getHeaders());
47
        $this->expectOutputString($body);
48
    }
49
50
    /**
51
     * @test
52
     * @dataProvider noBodyHTTPCodeProvider
53
     */
54
    public function shouldNotOutputBodyByResponseCode(int $code): void
55
    {
56
        $response = $this->createResponse($code, ['X-Test' => 1], 'Example body');
57
58
        $this->createEmitter()->emit($response);
59
60
        $this->assertEquals($code, $this->getResponseCode());
61
        $this->assertTrue(HTTPFunctions::hasHeader('X-Test'));
62
        $this->assertFalse(HTTPFunctions::hasHeader('Content-Length'));
63
        $this->expectOutputString('');
64
    }
65
66
    /**
67
     * @test
68
     */
69
    public function shouldNotOutputBodyAndContentLengthIfEmitToldSo(): void
70
    {
71
        $response = $this->createResponse(200, ['X-Test' => 1], 'Example body');
72
73
        $this->createEmitter()->emit($response, true);
74
75
        $this->assertEquals(200, $this->getResponseCode());
76
        $this->assertTrue(HTTPFunctions::hasHeader('X-Test'));
77
        $this->assertFalse(HTTPFunctions::hasHeader('Content-Length'));
78
        $this->expectOutputString('');
79
    }
80
81
    /**
82
     * @test
83
     */
84
    public function contentLengthShouldNotBeOverwrittenIfPresent(): void
85
    {
86
        $length = 100;
87
        $response = $this->createResponse(200, ['Content-Length' => $length, 'X-Test' => 1], 'Example body');
88
89
        $this->createEmitter()->emit($response);
90
91
        $this->assertEquals(200, $this->getResponseCode());
92
        $this->assertCount(2, $this->getHeaders());
93
        $this->assertContains('X-Test: 1', $this->getHeaders());
94
        $this->assertContains('Content-Length: ' . $length, $this->getHeaders());
95
        $this->expectOutputString('Example body');
96
    }
97
98
    /**
99
     * @test
100
     */
101
    public function contentLengthShouldNotOutputWhenBodyIsEmpty(): void
102
    {
103
        $length = 100;
104
        $response = $this->createResponse(200, ['Content-Length' => $length, 'X-Test' => 1], '');
105
106
        $this->createEmitter()->emit($response);
107
108
        $this->assertEquals(200, $this->getResponseCode());
109
        $this->assertEquals(['X-Test: 1'], $this->getHeaders());
110
        $this->expectOutputString('');
111
    }
112
113
    /**
114
     * @test
115
     */
116
    public function contentAlwaysShouldBeFullyEmitted(): void
117
    {
118
        $body = 'Example body';
119
        $response = $this->createResponse(200, ['Content-length' => 1, 'X-Test' => 1], $body);
120
121
        $this->createEmitter()->emit($response);
122
123
        $this->expectOutputString($body);
124
    }
125
126
    /**
127
     * @test
128
     */
129
    public function sentHeadersShouldBeRemoved(): void
130
    {
131
        HTTPFunctions::header('Cookie-Set: First Cookie');
132
        HTTPFunctions::header('X-Test: 1');
133
        $body = 'Example body';
134
        $response = $this->createResponse(200, [], $body);
135
136
        $this->createEmitter()->emit($response);
137
138
        $this->assertEquals(['Content-Length: ' . strlen($body)], $this->getHeaders());
139
        $this->expectOutputString($body);
140
    }
141
142
    /**
143
     * @test
144
     */
145
    public function exceptionWhenHeadersHaveBeenSent(): void
146
    {
147
        $body = 'Example body';
148
        $response = $this->createResponse(200, [], $body);
149
        HTTPFunctions::set_headers_sent(true, 'test-file.php', 200);
150
151
        $this->expectException(HeadersHaveBeenSentException::class);
152
        $this->createEmitter()->emit($response);
153
    }
154
155
    /**
156
     * @test
157
     */
158
    public function shouldEmitDuplicateHeaders(): void
159
    {
160
        $body = 'Example body';
161
        $response = $this->createResponse(200, [], $body)
162
                         ->withHeader('X-Test', '1')
163
                         ->withAddedHeader('X-Test', '2')
164
                         ->withAddedHeader('X-Test', '3; 3.5')
165
                         ->withHeader('Cookie-Set', '1')
166
                         ->withAddedHeader('cookie-Set', '2')
167
                         ->withAddedHeader('Cookie-set', '3');
168
169
        $this->createEmitter()->emit($response);
170
        $this->assertEquals(200, $this->getResponseCode());
171
        $this->assertContains('X-Test: 1', $this->getHeaders());
172
        $this->assertContains('X-Test: 2', $this->getHeaders());
173
        $this->assertContains('X-Test: 3; 3.5', $this->getHeaders());
174
        $this->assertContains('Cookie-Set: 1', $this->getHeaders());
175
        $this->assertContains('Cookie-Set: 2', $this->getHeaders());
176
        $this->assertContains('Cookie-Set: 3', $this->getHeaders());
177
        $this->assertContains('Content-Length: ' . strlen($body), $this->getHeaders());
178
        $this->expectOutputString($body);
179
    }
180
181
    private function createEmitter(?int $bufferSize = null): EmitterInterface
182
    {
183
        return new SapiEmitter($bufferSize);
184
    }
185
186
    private function createResponse(
187
        int $status = 200,
188
        array $headers = [],
189
        $body = null,
190
        string $version = '1.1'
191
    ): ResponseInterface {
192
        $response = (new Response())
193
            ->withStatus($status)
194
            ->withProtocolVersion($version);
195
        foreach ($headers as $header => $value) {
196
            $response = $response->withHeader($header, $value);
197
        }
198
        if ($body instanceof StreamInterface) {
199
            $response = $response->withBody($body);
200
        } elseif (is_string($body)) {
201
            $response->getBody()->write($body);
202
        }
203
        return $response;
204
    }
205
206
    private function getHeaders(): array
207
    {
208
        return HTTPFunctions::headers_list();
209
    }
210
211
    private function getResponseCode(): int
212
    {
213
        return HTTPFunctions::http_response_code();
214
    }
215
}
216