Completed
Push — master ( 18bc97...e7bdcd )
by Yaroslav
09:10
created

SensioExtraProviderTest::testIsGrantedAnnotation()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 16
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 9
dl 0
loc 16
rs 9.9666
c 0
b 0
f 0
cc 1
nc 1
nop 4
1
<?php
2
3
/*
4
 *
5
 * (c) Yaroslav Honcharuk <[email protected]>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
11
namespace Yarhon\RouteGuardBundle\Tests\Security\TestProvider;
12
13
use PHPUnit\Framework\TestCase;
14
use Symfony\Component\Routing\Route;
15
use Symfony\Component\ExpressionLanguage\Expression;
16
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
17
use Symfony\Component\ExpressionLanguage\SyntaxError;
18
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
19
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security as SecurityAnnotation;
20
use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted as IsGrantedAnnotation;
21
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter as ParamConverterAnnotation;
22
use Yarhon\RouteGuardBundle\Annotations\ClassMethodAnnotationReaderInterface;
23
use Yarhon\RouteGuardBundle\ExpressionLanguage\ExpressionDecorator;
24
use Yarhon\RouteGuardBundle\Controller\ControllerMetadata;
25
use Yarhon\RouteGuardBundle\Routing\RequestAttributesFactory;
26
use Yarhon\RouteGuardBundle\Routing\RouteMetadataFactory;
27
use Yarhon\RouteGuardBundle\Routing\RouteMetadata;
28
use Yarhon\RouteGuardBundle\Security\Test\SensioExtraTest;
29
use Yarhon\RouteGuardBundle\Security\Test\TestBag;
30
use Yarhon\RouteGuardBundle\Security\Authorization\SensioSecurityExpressionVoter;
31
use Yarhon\RouteGuardBundle\Security\TestProvider\SensioExtraProvider;
32
use Yarhon\RouteGuardBundle\Exception\LogicException;
33
use Yarhon\RouteGuardBundle\Exception\InvalidArgumentException;
34
35
/**
36
 * @author Yaroslav Honcharuk <[email protected]>
37
 */
38
class SensioExtraProviderTest extends TestCase
39
{
40
    private $annotationReader;
41
42
    private $requestAttributesFactory;
43
44
    private $expressionLanguage;
45
46
    private $provider;
47
48
    private $route;
49
50
    public function setUp()
51
    {
52
        $this->annotationReader = $this->createMock(ClassMethodAnnotationReaderInterface::class);
53
54
        $this->requestAttributesFactory = $this->createMock(RequestAttributesFactory::class);
55
56
        $routeMetadataFactory = $this->createMock(RouteMetadataFactory::class);
57
        $routeMetadataFactory->method('createMetadata')
0 ignored issues
show
Bug introduced by
The method method() does not exist on PHPUnit\Framework\MockObject\MockObject. ( Ignorable by Annotation )

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

57
        $routeMetadataFactory->/** @scrutinizer ignore-call */ 
58
                               method('createMetadata')

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
58
            ->willReturn(new RouteMetadata([], []));
59
60
        $this->expressionLanguage = $this->createMock(ExpressionLanguage::class);
61
62
        $this->provider = new SensioExtraProvider($this->annotationReader, $this->requestAttributesFactory, $routeMetadataFactory);
63
64
        $this->route = new Route('/');
65
    }
66
67
    /**
68
     * @dataProvider securityAnnotationDataProvider
69
     */
70
    public function testSecurityAnnotation($annotation, $controllerArguments, $requestAttributes, $expected)
71
    {
72
        $allowedVariables = array_unique(array_merge($controllerArguments, $requestAttributes));
73
74
        $this->provider->setExpressionLanguage($this->expressionLanguage);
75
76
        $this->annotationReader->method('read')
77
            ->willReturn([$annotation]);
78
79
        $this->requestAttributesFactory->method('getAttributeNames')
80
            ->willReturn($requestAttributes);
81
82
        $namesToParse = SensioSecurityExpressionVoter::getVariableNames();
83
84
        $this->expressionLanguage->expects($this->at(0))
85
            ->method('parse')
86
            ->with($annotation->getExpression(), $namesToParse)
87
            ->willThrowException(new SyntaxError('syntax'));
88
89
        $namesToParse = array_merge($namesToParse, $allowedVariables);
90
91
        $this->expressionLanguage->expects($this->at(1))
92
            ->method('parse')
93
            ->with($annotation->getExpression(), $namesToParse)
94
            ->willReturnCallback(function ($expressionString) {
95
                return new Expression($expressionString);
96
            });
97
98
        $controllerMetadata = $this->createControllerMetadata('class::method', $controllerArguments);
99
100
        $testBag = $this->provider->getTests('index', $this->route, $controllerMetadata);
101
102
        $this->assertInstanceOf(TestBag::class, $testBag);
103
        $test = $testBag->getTests()[0];
104
105
        $this->assertEquals($expected, $test);
106
    }
107
108
    public function securityAnnotationDataProvider()
109
    {
110
        return [
111
            [
112
                new SecurityAnnotation(['expression' => 'request.isSecure']),
113
                [],
114
                [],
115
                new SensioExtraTest([new ExpressionDecorator(new Expression('request.isSecure'), [])]),
116
            ],
117
            [
118
                new SecurityAnnotation(['expression' => 'request.isSecure']),
119
                ['foo'],
120
                ['foo'],
121
                new SensioExtraTest([new ExpressionDecorator(new Expression('request.isSecure'), ['foo'])]),
122
            ],
123
            [
124
                new SecurityAnnotation(['expression' => 'request.isSecure']),
125
                ['foo', 'bar'],
126
                ['baz'],
127
                (new SensioExtraTest([new ExpressionDecorator(new Expression('request.isSecure'), ['foo', 'bar', 'baz'])]))->setMetadata('request_attributes', ['baz']),
128
            ],
129
        ];
130
    }
131
132
    public function testSecurityAnnotationWithoutExpressionLanguageException()
133
    {
134
        $annotation = new SecurityAnnotation([]);
135
136
        $this->annotationReader->method('read')
137
            ->willReturn([$annotation]);
138
139
        $this->requestAttributesFactory->method('getAttributeNames')
140
            ->willReturn([]);
141
142
        $controllerMetadata = $this->createControllerMetadata('class::method', []);
143
144
        $this->expectException(LogicException::class);
145
        $this->expectExceptionMessage('Cannot create expression because ExpressionLanguage is not provided.');
146
147
        $this->provider->getTests('index', $this->route, $controllerMetadata);
148
    }
149
150
    public function testSecurityAnnotationExpressionException()
151
    {
152
        $this->provider->setExpressionLanguage($this->expressionLanguage);
153
154
        $annotation = new SecurityAnnotation(['expression' => 'request.isSecure']);
155
156
        $this->annotationReader->method('read')
157
            ->willReturn([$annotation]);
158
159
        $this->requestAttributesFactory->method('getAttributeNames')
160
            ->willReturn(['bar', 'baz']);
161
162
        $this->expressionLanguage->method('parse')
163
            ->willThrowException(new SyntaxError('syntax'));
164
165
        $controllerMetadata = $this->createControllerMetadata('class::method', ['foo', 'bar']);
166
167
        $this->expectException(InvalidArgumentException::class);
168
        $this->expectExceptionMessage('Cannot parse expression "request.isSecure" with following variables: "token", "user", "object", "subject", "roles", "trust_resolver", "auth_checker", "request", "foo", "bar", "baz".');
169
170
        $this->provider->getTests('index', $this->route, $controllerMetadata);
171
    }
172
173
    /**
174
     * @dataProvider isGrantedAnnotationDataProvider
175
     */
176
    public function testIsGrantedAnnotation($annotation, $controllerArguments, $requestAttributes, $expected)
177
    {
178
        $this->annotationReader->method('read')
179
            ->willReturn([$annotation]);
180
181
        $this->requestAttributesFactory->method('getAttributeNames')
182
            ->willReturn($requestAttributes);
183
184
        $controllerMetadata = $this->createControllerMetadata('class::method', $controllerArguments);
185
186
        $testBag = $this->provider->getTests('index', $this->route, $controllerMetadata);
187
188
        $this->assertInstanceOf(TestBag::class, $testBag);
189
        $test = $testBag->getTests()[0];
190
191
        $this->assertEquals($expected, $test);
192
    }
193
194
    public function isGrantedAnnotationDataProvider()
195
    {
196
        return [
197
            [
198
                new IsGrantedAnnotation(['attributes' => 'ROLE_ADMIN']),
199
                ['foo', 'bar'],
200
                ['bar', 'baz'],
201
                new SensioExtraTest(['ROLE_ADMIN']),
202
            ],
203
            [
204
                new IsGrantedAnnotation(['attributes' => 'ROLE_ADMIN', 'subject' => 'foo']),
205
                ['foo', 'bar'],
206
                ['bar', 'baz'],
207
                new SensioExtraTest(['ROLE_ADMIN'], 'foo'),
208
            ],
209
            [
210
                new IsGrantedAnnotation(['attributes' => 'ROLE_ADMIN', 'subject' => 'bar']),
211
                ['foo', 'bar'],
212
                ['bar', 'baz'],
213
                new SensioExtraTest(['ROLE_ADMIN'], 'bar'),
214
            ],
215
            [
216
                new IsGrantedAnnotation(['attributes' => 'ROLE_ADMIN', 'subject' => 'baz']),
217
                ['foo', 'bar'],
218
                ['bar', 'baz'],
219
                (new SensioExtraTest(['ROLE_ADMIN'], 'baz'))->setMetadata('request_attributes', ['baz']),
220
            ],
221
        ];
222
    }
223
224
    public function testIsGrantedAnnotationSubjectException()
225
    {
226
        $annotation = new IsGrantedAnnotation(['attributes' => 'ROLE_ADMIN', 'subject' => 'foo']);
227
228
        $this->annotationReader->method('read')
229
            ->willReturn([$annotation]);
230
231
        $this->requestAttributesFactory->method('getAttributeNames')
232
            ->willReturn(['baz']);
233
234
        $controllerMetadata = $this->createControllerMetadata('class::method', ['bar']);
235
236
        $this->expectException(InvalidArgumentException::class);
237
        $this->expectExceptionMessage('Unknown subject variable "foo". Allowed variables: "bar", "baz');
238
239
        $this->provider->getTests('index', $this->route, $controllerMetadata);
240
    }
241
242
    public function testNoAnnotations()
243
    {
244
        $this->annotationReader->method('read')
245
            ->willReturn([]);
246
247
        $controllerMetadata = $this->createControllerMetadata('class::method', []);
248
249
        $testBag = $this->provider->getTests('index', $this->route, $controllerMetadata);
250
251
        $this->assertNull($testBag);
252
    }
253
254
    public function testNoControllerMetadata()
255
    {
256
        $testBag = $this->provider->getTests('index', $this->route, null);
257
258
        $this->assertNull($testBag);
259
    }
260
261
    /**
262
     * @dataProvider sameInstancesOfEqualTestsDataProvider
263
     */
264
    public function testSameInstancesOfEqualTests($callOne, $callTwo, $expected)
265
    {
266
        $annotations = [$callOne[0], $callTwo[0]];
267
        $controllerArguments = [$callOne[1], $callTwo[1]];
268
        $requestAttributes = [$callOne[2], $callTwo[2]];
269
270
        $this->provider->setExpressionLanguage($this->expressionLanguage);
271
272
        $this->annotationReader->method('read')
273
            ->willReturnOnConsecutiveCalls([$annotations[0]], [$annotations[1]]);
274
275
        $this->requestAttributesFactory->method('getAttributeNames')
276
            ->willReturnOnConsecutiveCalls($requestAttributes[0], $requestAttributes[1]);
277
278
        $this->expressionLanguage->method('parse')
279
            ->willReturnCallback(function ($expressionString) {
280
                return new Expression($expressionString);
281
            });
282
283
        $controllerMetadata = $this->createControllerMetadata('class::method', $controllerArguments[0]);
284
285
        $testBag = $this->provider->getTests('index', $this->route, $controllerMetadata);
286
        $testOne = $testBag->getTests()[0];
287
288
        $controllerMetadata = $this->createControllerMetadata('class::method', $controllerArguments[1]);
289
290
        $testBag = $this->provider->getTests('index', $this->route, $controllerMetadata);
291
        $testTwo = $testBag->getTests()[0];
292
293
        if ($expected) {
294
            $this->assertSame($testOne, $testTwo);
295
        } else {
296
            $this->assertNotSame($testOne, $testTwo);
297
        }
298
    }
299
300
    public function sameInstancesOfEqualTestsDataProvider()
301
    {
302
        return [
303
            [
304
                [new SecurityAnnotation(['expression' => 'request.isSecure']), [], []],
305
                [new SecurityAnnotation(['expression' => 'not request.isSecure']), [], []],
306
                false,
307
            ],
308
            [
309
                [new SecurityAnnotation(['expression' => 'request.isSecure']), [], []],
310
                [new SecurityAnnotation(['expression' => 'request.isSecure']), [], []],
311
                true,
312
            ],
313
            // TODO: uncomment this tests when Expression parser would be ready
314
            /*
315
            [
316
                [ new SecurityAnnotation(['expression' => 'request.isSecure']), ['arg1'], [] ],
317
                [ new SecurityAnnotation(['expression' => 'request.isSecure']), [], [] ],
318
                false,
319
            ],
320
            [
321
                [ new SecurityAnnotation(['expression' => 'request.isSecure']), [], ['attr1'] ],
322
                [ new SecurityAnnotation(['expression' => 'request.isSecure']), [], [] ],
323
                false,
324
            ],
325
            */
326
            [
327
                [new IsGrantedAnnotation(['attributes' => 'ROLE_ADMIN']), ['arg1'], ['attr1']],
328
                [new IsGrantedAnnotation(['attributes' => 'ROLE_ADMIN']), ['arg1'], ['attr1']],
329
                true,
330
            ],
331
            [
332
                [new IsGrantedAnnotation(['attributes' => 'ROLE_ADMIN']), ['arg1'], ['attr1']],
333
                [new IsGrantedAnnotation(['attributes' => 'ROLE_USER']), ['arg1'], ['attr1']],
334
                false,
335
            ],
336
            [
337
                [new IsGrantedAnnotation(['attributes' => 'ROLE_ADMIN', 'subject' => 'arg1']), ['arg1'], ['attr1']],
338
                [new IsGrantedAnnotation(['attributes' => 'ROLE_ADMIN']), ['arg1'], ['attr1']],
339
                false,
340
            ],
341
        ];
342
    }
343
344
    private function createControllerMetadata($controllerName, $argumentNames)
345
    {
346
        $arguments = [];
347
348
        foreach ($argumentNames as $name) {
349
            $arguments[] = new ArgumentMetadata($name, 'int', false, false, null);
350
        }
351
352
        return new ControllerMetadata($controllerName, 'class', 'method', $arguments);
353
    }
354
}
355