Passed
Push — master ( 974680...fc5316 )
by Kirill
03:59
created

CookiesTest   A

Complexity

Total Complexity 16

Size/Duplication

Total Lines 307
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 16
eloc 155
c 1
b 0
f 0
dl 0
loc 307
rs 10

15 Methods

Rating   Name   Duplication   Size   Complexity  
A testDecryptBroken() 0 16 1
A get() 0 8 1
A fetchCookies() 0 14 2
A httpCore() 0 15 1
A request() 0 16 1
A testDelete() 0 20 1
A testDecrypt() 0 16 1
A setUp() 0 18 1
A testGetUnprotected() 0 22 1
A testUnprotected() 0 23 1
A testHMAC() 0 30 1
A testSetNotProtectedCookie() 0 17 1
A testDecryptArray() 0 16 1
A testScope() 0 22 1
A testSetEncryptedCookie() 0 19 1
1
<?php
2
3
/**
4
 * Spiral Framework.
5
 *
6
 * @license   MIT
7
 * @author    Anton Titov (Wolfy-J)
8
 */
9
10
declare(strict_types=1);
11
12
namespace Spiral\Tests\Cookies;
13
14
use Defuse\Crypto\Key;
15
use PHPUnit\Framework\TestCase;
16
use Psr\Http\Message\ResponseInterface;
17
use Psr\Http\Message\ServerRequestInterface;
18
use Spiral\Cookies\Config\CookiesConfig;
19
use Spiral\Cookies\CookieQueue;
20
use Spiral\Cookies\Middleware\CookiesMiddleware;
21
use Spiral\Core\Container;
22
use Spiral\Encrypter\Config\EncrypterConfig;
23
use Spiral\Encrypter\Encrypter;
24
use Spiral\Encrypter\EncrypterFactory;
25
use Spiral\Encrypter\EncrypterInterface;
26
use Spiral\Encrypter\EncryptionInterface;
27
use Spiral\Http\Config\HttpConfig;
28
use Spiral\Http\Http;
29
use Spiral\Http\Pipeline;
30
use Laminas\Diactoros\ServerRequest;
31
32
class CookiesTest extends TestCase
33
{
34
    private $container;
35
36
    public function setUp(): void
37
    {
38
        $this->container = new Container();
39
        $this->container->bind(CookiesConfig::class, new CookiesConfig([
0 ignored issues
show
Bug introduced by
new Spiral\Cookies\Confi...ESSID', 'csrf-token'))) of type Spiral\Cookies\Config\CookiesConfig is incompatible with the type array|callable|string expected by parameter $resolver of Spiral\Core\Container::bind(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

39
        $this->container->bind(CookiesConfig::class, /** @scrutinizer ignore-type */ new CookiesConfig([
Loading history...
40
            'domain'   => '.%s',
41
            'method'   => CookiesConfig::COOKIE_ENCRYPT,
42
            'excluded' => ['PHPSESSID', 'csrf-token']
43
        ]));
44
45
        $this->container->bind(
46
            EncrypterFactory::class,
47
            new EncrypterFactory(new EncrypterConfig([
0 ignored issues
show
Bug introduced by
new Spiral\Encrypter\Enc...eToAsciiSafeString()))) of type Spiral\Encrypter\EncrypterFactory is incompatible with the type array|callable|string expected by parameter $resolver of Spiral\Core\Container::bind(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

47
            /** @scrutinizer ignore-type */ new EncrypterFactory(new EncrypterConfig([
Loading history...
48
                'key' => Key::createNewRandomKey()->saveToAsciiSafeString()
49
            ]))
50
        );
51
52
        $this->container->bind(EncryptionInterface::class, EncrypterFactory::class);
53
        $this->container->bind(EncrypterInterface::class, Encrypter::class);
54
    }
55
56
    public function testScope(): void
57
    {
58
        $core = $this->httpCore([CookiesMiddleware::class]);
59
        $core->setHandler(function ($r) {
60
            $this->assertInstanceOf(
61
                CookieQueue::class,
62
                $this->container->get(ServerRequestInterface::class)
63
                    ->getAttribute(CookieQueue::ATTRIBUTE)
64
            );
65
66
            $this->assertSame(
67
                $this->container->get(ServerRequestInterface::class)
68
                    ->getAttribute(CookieQueue::ATTRIBUTE),
69
                $r->getAttribute(CookieQueue::ATTRIBUTE)
70
            );
71
72
            return 'all good';
73
        });
74
75
        $response = $this->get($core, '/');
76
        $this->assertSame(200, $response->getStatusCode());
77
        $this->assertSame('all good', (string)$response->getBody());
78
    }
79
80
    public function testSetEncryptedCookie(): void
81
    {
82
        $core = $this->httpCore([CookiesMiddleware::class]);
83
        $core->setHandler(function ($r) {
0 ignored issues
show
Unused Code introduced by
The parameter $r is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

83
        $core->setHandler(function (/** @scrutinizer ignore-unused */ $r) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
84
            $this->container->get(ServerRequestInterface::class)
85
                ->getAttribute(CookieQueue::ATTRIBUTE)->set('name', 'value');
86
87
            return 'all good';
88
        });
89
90
        $response = $this->get($core, '/');
91
        $this->assertSame(200, $response->getStatusCode());
92
        $this->assertSame('all good', (string)$response->getBody());
93
94
        $cookies = $this->fetchCookies($response);
95
        $this->assertArrayHasKey('name', $cookies);
96
        $this->assertSame(
97
            'value',
98
            $this->container->get(EncrypterInterface::class)->decrypt($cookies['name'])
99
        );
100
    }
101
102
    public function testSetNotProtectedCookie(): void
103
    {
104
        $core = $this->httpCore([CookiesMiddleware::class]);
105
        $core->setHandler(function ($r) {
0 ignored issues
show
Unused Code introduced by
The parameter $r is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

105
        $core->setHandler(function (/** @scrutinizer ignore-unused */ $r) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
106
            $this->container->get(ServerRequestInterface::class)
107
                ->getAttribute(CookieQueue::ATTRIBUTE)->set('PHPSESSID', 'value');
108
109
            return 'all good';
110
        });
111
112
        $response = $this->get($core, '/');
113
        $this->assertSame(200, $response->getStatusCode());
114
        $this->assertSame('all good', (string)$response->getBody());
115
116
        $cookies = $this->fetchCookies($response);
117
        $this->assertArrayHasKey('PHPSESSID', $cookies);
118
        $this->assertSame('value', $cookies['PHPSESSID']);
119
    }
120
121
    public function testDecrypt(): void
122
    {
123
        $core = $this->httpCore([CookiesMiddleware::class]);
124
        $core->setHandler(function ($r) {
125
126
            /**
127
             * @var ServerRequest $r
128
             */
129
            return $r->getCookieParams()['name'];
130
        });
131
132
        $value = $this->container->get(EncrypterInterface::class)->encrypt('cookie-value');
133
134
        $response = $this->get($core, '/', [], [], ['name' => $value]);
135
        $this->assertSame(200, $response->getStatusCode());
136
        $this->assertSame('cookie-value', (string)$response->getBody());
137
    }
138
139
    public function testDecryptArray(): void
140
    {
141
        $core = $this->httpCore([CookiesMiddleware::class]);
142
        $core->setHandler(function ($r) {
143
144
            /**
145
             * @var ServerRequest $r
146
             */
147
            return $r->getCookieParams()['name'][0];
148
        });
149
150
        $value[] = $this->container->get(EncrypterInterface::class)->encrypt('cookie-value');
0 ignored issues
show
Comprehensibility Best Practice introduced by
$value was never initialized. Although not strictly required by PHP, it is generally a good practice to add $value = array(); before regardless.
Loading history...
151
152
        $response = $this->get($core, '/', [], [], ['name' => $value]);
153
        $this->assertSame(200, $response->getStatusCode());
154
        $this->assertSame('cookie-value', (string)$response->getBody());
155
    }
156
157
    public function testDecryptBroken(): void
158
    {
159
        $core = $this->httpCore([CookiesMiddleware::class]);
160
        $core->setHandler(function ($r) {
161
162
            /**
163
             * @var ServerRequest $r
164
             */
165
            return $r->getCookieParams()['name'];
166
        });
167
168
        $value = $this->container->get(EncrypterInterface::class)->encrypt('cookie-value') . 'BROKEN';
169
170
        $response = $this->get($core, '/', [], [], ['name' => $value]);
171
        $this->assertSame(200, $response->getStatusCode());
172
        $this->assertSame('', (string)$response->getBody());
173
    }
174
175
    public function testDelete(): void
176
    {
177
        $core = $this->httpCore([CookiesMiddleware::class]);
178
        $core->setHandler(function ($r) {
0 ignored issues
show
Unused Code introduced by
The parameter $r is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

178
        $core->setHandler(function (/** @scrutinizer ignore-unused */ $r) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
179
            $this->container->get(ServerRequestInterface::class)
180
                ->getAttribute(CookieQueue::ATTRIBUTE)->set('name', 'value');
181
182
            $this->container->get(ServerRequestInterface::class)
183
                ->getAttribute(CookieQueue::ATTRIBUTE)->delete('name');
184
185
            return 'all good';
186
        });
187
188
        $response = $this->get($core, '/');
189
        $this->assertSame(200, $response->getStatusCode());
190
        $this->assertSame('all good', (string)$response->getBody());
191
192
        $cookies = $this->fetchCookies($response);
193
        $this->assertArrayHasKey('name', $cookies);
194
        $this->assertSame('', $cookies['name']);
195
    }
196
197
    public function testUnprotected(): void
198
    {
199
        $this->container->bind(CookiesConfig::class, new CookiesConfig([
200
            'domain'   => '.%s',
201
            'method'   => CookiesConfig::COOKIE_UNPROTECTED,
202
            'excluded' => ['PHPSESSID', 'csrf-token']
203
        ]));
204
205
        $core = $this->httpCore([CookiesMiddleware::class]);
206
        $core->setHandler(function ($r) {
0 ignored issues
show
Unused Code introduced by
The parameter $r is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

206
        $core->setHandler(function (/** @scrutinizer ignore-unused */ $r) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
207
            $this->container->get(ServerRequestInterface::class)
208
                ->getAttribute(CookieQueue::ATTRIBUTE)->set('name', 'value');
209
210
            return 'all good';
211
        });
212
213
        $response = $this->get($core, '/');
214
        $this->assertSame(200, $response->getStatusCode());
215
        $this->assertSame('all good', (string)$response->getBody());
216
217
        $cookies = $this->fetchCookies($response);
218
        $this->assertArrayHasKey('name', $cookies);
219
        $this->assertSame('value', $cookies['name']);
220
    }
221
222
    public function testGetUnprotected(): void
223
    {
224
        $this->container->bind(CookiesConfig::class, new CookiesConfig([
225
            'domain'   => '.%s',
226
            'method'   => CookiesConfig::COOKIE_UNPROTECTED,
227
            'excluded' => ['PHPSESSID', 'csrf-token']
228
        ]));
229
230
        $core = $this->httpCore([CookiesMiddleware::class]);
231
        $core->setHandler(function ($r) {
232
233
            /**
234
             * @var ServerRequest $r
235
             */
236
            return $r->getCookieParams()['name'];
237
        });
238
239
        $value = 'cookie-value';
240
241
        $response = $this->get($core, '/', [], [], ['name' => $value]);
242
        $this->assertSame(200, $response->getStatusCode());
243
        $this->assertSame('cookie-value', (string)$response->getBody());
244
    }
245
246
    public function testHMAC(): void
247
    {
248
        $this->container->bind(CookiesConfig::class, new CookiesConfig([
249
            'domain'   => '.%s',
250
            'method'   => CookiesConfig::COOKIE_HMAC,
251
            'excluded' => ['PHPSESSID', 'csrf-token']
252
        ]));
253
254
        $core = $this->httpCore([CookiesMiddleware::class]);
255
        $core->setHandler(function ($r) {
0 ignored issues
show
Unused Code introduced by
The parameter $r is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

255
        $core->setHandler(function (/** @scrutinizer ignore-unused */ $r) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
256
            $this->container->get(ServerRequestInterface::class)
257
                ->getAttribute(CookieQueue::ATTRIBUTE)->set('name', 'value');
258
259
            return 'all good';
260
        });
261
262
        $response = $this->get($core, '/');
263
        $this->assertSame(200, $response->getStatusCode());
264
        $this->assertSame('all good', (string)$response->getBody());
265
266
        $cookies = $this->fetchCookies($response);
267
        $this->assertArrayHasKey('name', $cookies);
268
269
        $core->setHandler(function ($r) {
270
            return $r->getCookieParams()['name'];
271
        });
272
273
        $response = $this->get($core, '/', [], [], $cookies);
274
        $this->assertSame(200, $response->getStatusCode());
275
        $this->assertSame('value', (string)$response->getBody());
276
    }
277
278
    protected function httpCore(array $middleware = []): Http
279
    {
280
        $config = new HttpConfig([
281
            'basePath'   => '/',
282
            'headers'    => [
283
                'Content-Type' => 'text/html; charset=UTF-8'
284
            ],
285
            'middleware' => $middleware
286
        ]);
287
288
        return new Http(
289
            $config,
290
            new Pipeline($this->container),
291
            new TestResponseFactory($config),
292
            $this->container
293
        );
294
    }
295
296
    protected function get(
297
        Http $core,
298
        $uri,
299
        array $query = [],
300
        array $headers = [],
301
        array $cookies = []
302
    ): ResponseInterface {
303
        return $core->handle($this->request($uri, 'GET', $query, $headers, $cookies));
304
    }
305
306
    protected function request(
307
        $uri,
308
        string $method,
309
        array $query = [],
310
        array $headers = [],
311
        array $cookies = []
312
    ): ServerRequest {
313
        return new ServerRequest(
314
            [],
315
            [],
316
            $uri,
317
            $method,
318
            'php://input',
319
            $headers,
320
            $cookies,
321
            $query
322
        );
323
    }
324
325
    protected function fetchCookies(ResponseInterface $response)
326
    {
327
        $result = [];
328
329
        foreach ($response->getHeaders() as $line) {
330
            $cookie = explode('=', implode('', $line));
331
            $result[$cookie[0]] = rawurldecode(substr(
332
                (string)$cookie[1],
333
                0,
334
                (int)strpos((string)$cookie[1], ';')
335
            ));
336
        }
337
338
        return $result;
339
    }
340
}
341