Completed
Pull Request — develop (#616)
by Alejandro
04:43
created

expectedStatusCodeIsReturnDependingOnRequestMethod()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 13
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 6
nc 1
nop 3
dl 0
loc 13
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace ShlinkioTest\Shlink\Rest\Middleware;
6
7
use Laminas\Diactoros\Response;
8
use Laminas\Diactoros\ServerRequest;
9
use Mezzio\Router\Route;
10
use Mezzio\Router\RouteResult;
11
use PHPUnit\Framework\TestCase;
12
use Prophecy\Argument;
13
use Prophecy\Prophecy\ObjectProphecy;
14
use Psr\Http\Server\RequestHandlerInterface;
15
use Shlinkio\Shlink\Rest\Authentication;
16
use Shlinkio\Shlink\Rest\Middleware\CrossDomainMiddleware;
17
18
use function Laminas\Stratigility\middleware;
19
20
class CrossDomainMiddlewareTest extends TestCase
21
{
22
    private CrossDomainMiddleware $middleware;
23
    private ObjectProphecy $handler;
24
25
    public function setUp(): void
26
    {
27
        $this->middleware = new CrossDomainMiddleware();
28
        $this->handler = $this->prophesize(RequestHandlerInterface::class);
29
    }
30
31
    /** @test */
32
    public function nonCrossDomainRequestsAreNotAffected(): void
33
    {
34
        $originalResponse = (new Response())->withStatus(404);
35
        $this->handler->handle(Argument::any())->willReturn($originalResponse)->shouldBeCalledOnce();
36
37
        $response = $this->middleware->process(new ServerRequest(), $this->handler->reveal());
38
        $headers = $response->getHeaders();
39
40
        $this->assertSame($originalResponse, $response);
41
        $this->assertEquals(404, $response->getStatusCode());
42
        $this->assertArrayNotHasKey('Access-Control-Allow-Origin', $headers);
43
        $this->assertArrayNotHasKey('Access-Control-Expose-Headers', $headers);
44
        $this->assertArrayNotHasKey('Access-Control-Allow-Methods', $headers);
45
        $this->assertArrayNotHasKey('Access-Control-Max-Age', $headers);
46
        $this->assertArrayNotHasKey('Access-Control-Allow-Headers', $headers);
47
    }
48
49
    /** @test */
50
    public function anyRequestIncludesTheAllowAccessHeader(): void
51
    {
52
        $originalResponse = new Response();
53
        $this->handler->handle(Argument::any())->willReturn($originalResponse)->shouldBeCalledOnce();
54
55
        $response = $this->middleware->process(
56
            (new ServerRequest())->withHeader('Origin', 'local'),
57
            $this->handler->reveal(),
58
        );
59
        $this->assertNotSame($originalResponse, $response);
60
61
        $headers = $response->getHeaders();
62
63
        $this->assertEquals('local', $response->getHeaderLine('Access-Control-Allow-Origin'));
64
        $this->assertEquals(
65
            Authentication\Plugin\ApiKeyHeaderPlugin::HEADER_NAME,
66
            $response->getHeaderLine('Access-Control-Expose-Headers'),
67
        );
68
        $this->assertArrayNotHasKey('Access-Control-Allow-Methods', $headers);
69
        $this->assertArrayNotHasKey('Access-Control-Max-Age', $headers);
70
        $this->assertArrayNotHasKey('Access-Control-Allow-Headers', $headers);
71
    }
72
73
    /** @test */
74
    public function optionsRequestIncludesMoreHeaders(): void
75
    {
76
        $originalResponse = new Response();
77
        $request = (new ServerRequest())
78
            ->withMethod('OPTIONS')
79
            ->withHeader('Origin', 'local')
80
            ->withHeader('Access-Control-Request-Headers', 'foo, bar, baz');
81
        $this->handler->handle(Argument::any())->willReturn($originalResponse)->shouldBeCalledOnce();
82
83
        $response = $this->middleware->process($request, $this->handler->reveal());
84
        $this->assertNotSame($originalResponse, $response);
85
86
        $headers = $response->getHeaders();
87
88
        $this->assertEquals('local', $response->getHeaderLine('Access-Control-Allow-Origin'));
89
        $this->assertEquals(
90
            Authentication\Plugin\ApiKeyHeaderPlugin::HEADER_NAME,
91
            $response->getHeaderLine('Access-Control-Expose-Headers'),
92
        );
93
        $this->assertArrayHasKey('Access-Control-Allow-Methods', $headers);
94
        $this->assertEquals('1000', $response->getHeaderLine('Access-Control-Max-Age'));
95
        $this->assertEquals('foo, bar, baz', $response->getHeaderLine('Access-Control-Allow-Headers'));
96
        $this->assertEquals(204, $response->getStatusCode());
97
    }
98
99
    /**
100
     * @test
101
     * @dataProvider provideRouteResults
102
     */
103
    public function optionsRequestParsesRouteMatchToDetermineAllowedMethods(
104
        ?RouteResult $result,
105
        string $expectedAllowedMethods
106
    ): void {
107
        $originalResponse = new Response();
108
        $request = (new ServerRequest())->withAttribute(RouteResult::class, $result)
109
                                        ->withMethod('OPTIONS')
110
                                        ->withHeader('Origin', 'local');
111
        $this->handler->handle(Argument::any())->willReturn($originalResponse)->shouldBeCalledOnce();
112
113
        $response = $this->middleware->process($request, $this->handler->reveal());
114
115
        $this->assertEquals($response->getHeaderLine('Access-Control-Allow-Methods'), $expectedAllowedMethods);
116
        $this->assertEquals(204, $response->getStatusCode());
117
    }
118
119
    public function provideRouteResults(): iterable
120
    {
121
        yield 'with no route result' => [null, 'GET,POST,PUT,PATCH,DELETE,OPTIONS'];
122
        yield 'with failed route result' => [RouteResult::fromRouteFailure(['POST', 'GET']), 'POST,GET'];
123
        yield 'with success route result' => [
124
            RouteResult::fromRoute(
125
                new Route('/', middleware(function (): void {
126
                }), ['DELETE', 'PATCH', 'PUT']),
127
            ),
128
            'DELETE,PATCH,PUT',
129
        ];
130
    }
131
132
    /**
133
     * @test
134
     * @dataProvider provideMethods
135
     */
136
    public function expectedStatusCodeIsReturnDependingOnRequestMethod(
137
        string $method,
138
        int $status,
139
        int $expectedStatus
140
    ): void {
141
        $originalResponse = (new Response())->withStatus($status);
142
        $request = (new ServerRequest())->withMethod($method)
143
                                        ->withHeader('Origin', 'local');
144
        $this->handler->handle(Argument::any())->willReturn($originalResponse)->shouldBeCalledOnce();
145
146
        $response = $this->middleware->process($request, $this->handler->reveal());
147
148
        $this->assertEquals($expectedStatus, $response->getStatusCode());
149
    }
150
151
    public function provideMethods(): iterable
152
    {
153
        yield 'POST 200' => ['POST', 200, 200];
154
        yield 'POST 400' => ['POST', 400, 400];
155
        yield 'POST 500' => ['POST', 500, 500];
156
        yield 'GET 200' => ['GET', 200, 200];
157
        yield 'GET 400' => ['GET', 400, 400];
158
        yield 'GET 500' => ['GET', 500, 500];
159
        yield 'PATCH 200' => ['PATCH', 200, 200];
160
        yield 'PATCH 400' => ['PATCH', 400, 400];
161
        yield 'PATCH 500' => ['PATCH', 500, 500];
162
        yield 'DELETE 200' => ['DELETE', 200, 200];
163
        yield 'DELETE 400' => ['DELETE', 400, 400];
164
        yield 'DELETE 500' => ['DELETE', 500, 500];
165
        yield 'OPTIONS 200' => ['OPTIONS', 200, 204];
166
        yield 'OPTIONS 400' => ['OPTIONS', 400, 204];
167
        yield 'OPTIONS 500' => ['OPTIONS', 500, 204];
168
    }
169
}
170