Passed
Pull Request — master (#256)
by Aleksei
14:38
created

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