Passed
Push — master ( 421eba...09fd73 )
by Alexis
03:20 queued 11s
created

WebTestCase::getDecorated()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 3.0416

Importance

Changes 0
Metric Value
dl 0
loc 14
ccs 5
cts 6
cp 0.8333
rs 9.7998
c 0
b 0
f 0
cc 3
nc 4
nop 0
crap 3.0416
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the Liip/FunctionalTestBundle
7
 *
8
 * (c) Lukas Kahwe Smith <[email protected]>
9
 *
10
 * This source file is subject to the MIT license that is bundled
11
 * with this source code in the file LICENSE.
12
 */
13
14
namespace Liip\FunctionalTestBundle\Test;
15
16
use Liip\FunctionalTestBundle\Utils\HttpAssertions;
17
use PHPUnit\Framework\MockObject\MockBuilder;
18
use Symfony\Bundle\FrameworkBundle\Client;
19
use Symfony\Bundle\FrameworkBundle\Console\Application;
20
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase as BaseWebTestCase;
21
use Symfony\Component\BrowserKit\Cookie;
22
use Symfony\Component\Console\Tester\CommandTester;
23
use Symfony\Component\DependencyInjection\ContainerInterface;
24
use Symfony\Component\DependencyInjection\ResettableContainerInterface;
25
use Symfony\Component\DomCrawler\Crawler;
26
use Symfony\Component\HttpFoundation\Response;
27
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
28
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
29
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
30
use Symfony\Component\Security\Core\User\UserInterface;
31
32
/**
33
 * @author Lea Haensenberger
34
 * @author Lukas Kahwe Smith <[email protected]>
35
 * @author Benjamin Eberlei <[email protected]>
36
 */
37
abstract class WebTestCase extends BaseWebTestCase
38
{
39
    protected $environment = 'test';
40
41
    protected $containers;
42
43
    // 5 * 1024 * 1024 KB
44
    protected $maxMemory = 5242880;
45
46
    // RUN COMMAND
47
    protected $verbosityLevel;
48
49
    protected $decorated;
50
51
    /**
52
     * @var array|null
53
     */
54
    private $inputs = null;
55
56
    /**
57
     * @var array
58
     */
59
    private $firewallLogins = [];
60
61
    /**
62
     * Creates a mock object of a service identified by its id.
63
     *
64
     * @param string $id
65
     *
66
     * @return MockBuilder
67
     */
68
    protected function getServiceMockBuilder(string $id): MockBuilder
69
    {
70
        $service = $this->getContainer()->get($id);
71
        $class = get_class($service);
72
73
        return $this->getMockBuilder($class)->disableOriginalConstructor();
74
    }
75
76
    /**
77
     * Builds up the environment to run the given command.
78
     *
79
     * @param string $name
80
     * @param array  $params
81
     * @param bool   $reuseKernel
82
     *
83
     * @return CommandTester
84
     */
85 13
    protected function runCommand(string $name, array $params = [], bool $reuseKernel = false): CommandTester
86
    {
87 13
        if (!$reuseKernel) {
88 13
            if (null !== static::$kernel) {
89 1
                static::$kernel->shutdown();
90
            }
91
92 13
            $kernel = static::$kernel = static::createKernel(['environment' => $this->environment]);
93 13
            $kernel->boot();
94
        } else {
95 2
            $kernel = $this->getContainer()->get('kernel');
96
        }
97
98 13
        $application = new Application($kernel);
0 ignored issues
show
Documentation introduced by
$kernel is of type object|null, but the function expects a object<Symfony\Component...Kernel\KernelInterface>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
99
100
        $options = [
101 13
            'interactive' => false,
102 13
            'decorated' => $this->getDecorated(),
103 13
            'verbosity' => $this->getVerbosityLevel(),
104
        ];
105
106 12
        $command = $application->find($name);
107 12
        $commandTester = new CommandTester($command);
108
109 12
        if (null !== $inputs = $this->getInputs()) {
110 1
            $commandTester->setInputs($inputs);
111 1
            $options['interactive'] = true;
112 1
            $this->inputs = null;
113
        }
114
115 12
        $commandTester->execute(
116 12
            array_merge(['command' => $command->getName()], $params),
117 12
            $options
118
        );
119
120 12
        return $commandTester;
121
    }
122
123
    /**
124
     * Retrieves the output verbosity level.
125
     *
126
     * @see \Symfony\Component\Console\Output\OutputInterface for available levels
127
     *
128
     * @throws \OutOfBoundsException If the set value isn't accepted
129
     *
130
     * @return int
131
     */
132 13
    protected function getVerbosityLevel(): int
133
    {
134
        // If `null`, is not yet set
135 13
        if (null === $this->verbosityLevel) {
136
            // Set the global verbosity level that is set as NORMAL by the TreeBuilder in Configuration
137 7
            $level = strtoupper($this->getContainer()->getParameter('liip_functional_test.command_verbosity'));
138 7
            $verbosity = '\Symfony\Component\Console\Output\StreamOutput::VERBOSITY_'.$level;
139
140 7
            $this->verbosityLevel = constant($verbosity);
141
        }
142
143
        // If string, it is set by the developer, so check that the value is an accepted one
144 13
        if (is_string($this->verbosityLevel)) {
145 6
            $level = strtoupper($this->verbosityLevel);
146 6
            $verbosity = '\Symfony\Component\Console\Output\StreamOutput::VERBOSITY_'.$level;
147
148 6
            if (!defined($verbosity)) {
149 1
                throw new \OutOfBoundsException(sprintf('The set value "%s" for verbosityLevel is not valid. Accepted are: "quiet", "normal", "verbose", "very_verbose" and "debug".', $level));
150
            }
151
152 5
            $this->verbosityLevel = constant($verbosity);
153
        }
154
155 12
        return $this->verbosityLevel;
156
    }
157
158 6
    public function setVerbosityLevel($level): void
159
    {
160 6
        $this->verbosityLevel = $level;
161 6
    }
162
163 1
    protected function setInputs(array $inputs): void
164
    {
165 1
        $this->inputs = $inputs;
166 1
    }
167
168 12
    protected function getInputs(): ?array
169
    {
170 12
        return $this->inputs;
171
    }
172
173
    /**
174
     * Set verbosity for Symfony 3.4+.
175
     *
176
     * @see https://github.com/symfony/symfony/pull/24425
177
     *
178
     * @param $level
179
     */
180
    private function setVerbosityLevelEnv($level): void
181
    {
182
        putenv('SHELL_VERBOSITY='.$level);
183
    }
184
185
    /**
186
     * Retrieves the flag indicating if the output should be decorated or not.
187
     *
188
     * @return bool
189
     */
190 13
    protected function getDecorated(): bool
191
    {
192 13
        if (null === $this->decorated) {
193
            // Set the global decoration flag that is set to `true` by the TreeBuilder in Configuration
194 7
            $this->decorated = $this->getContainer()->getParameter('liip_functional_test.command_decoration');
195
        }
196
197
        // Check the local decorated flag
198 13
        if (false === is_bool($this->decorated)) {
199
            throw new \OutOfBoundsException(sprintf('`WebTestCase::decorated` has to be `bool`. "%s" given.', gettype($this->decorated)));
200
        }
201
202 13
        return $this->decorated;
203
    }
204
205 6
    public function isDecorated(bool $decorated): void
206
    {
207 6
        $this->decorated = $decorated;
208 6
    }
209
210
    /**
211
     * Get an instance of the dependency injection container.
212
     * (this creates a kernel *without* parameters).
213
     *
214
     * @return ContainerInterface
215
     */
216 14
    protected function getContainer(): ContainerInterface
217
    {
218 14
        $cacheKey = $this->environment;
219 14
        if (empty($this->containers[$cacheKey])) {
220
            $options = [
221 14
                'environment' => $this->environment,
222
            ];
223 14
            $kernel = $this->createKernel($options);
224 14
            $kernel->boot();
225
226 14
            $container = $kernel->getContainer();
227 14
            if ($container->has('test.service_container')) {
228
                $this->containers[$cacheKey] = $container->get('test.service_container');
229
            } else {
230 14
                $this->containers[$cacheKey] = $container;
231
            }
232
        }
233
234 14
        return $this->containers[$cacheKey];
235
    }
236
237
    /**
238
     * Creates an instance of a lightweight Http client.
239
     *
240
     * $params can be used to pass headers to the client, note that they have
241
     * to follow the naming format used in $_SERVER.
242
     * Example: 'HTTP_X_REQUESTED_WITH' instead of 'X-Requested-With'
243
     *
244
     * @param array $params
245
     *
246
     * @return Client
247
     */
248 36
    protected function makeClient(array $params = []): Client
249
    {
250 36
        return $this->createClientWithParams($params);
251
    }
252
253
    /**
254
     * Creates an instance of a lightweight Http client.
255
     *
256
     * $params can be used to pass headers to the client, note that they have
257
     * to follow the naming format used in $_SERVER.
258
     * Example: 'HTTP_X_REQUESTED_WITH' instead of 'X-Requested-With'
259
     *
260
     * @param array $params
261
     *
262
     * @return Client
263
     */
264 1
    protected function makeAuthenticatedClient(array $params = []): Client
265
    {
266 1
        $username = $this->getContainer()
267 1
            ->getParameter('liip_functional_test.authentication.username');
268 1
        $password = $this->getContainer()
269 1
            ->getParameter('liip_functional_test.authentication.password');
270
271 1
        return $this->createClientWithParams($params, $username, $password);
272
    }
273
274
    /**
275
     * Creates an instance of a lightweight Http client and log in user with
276
     * username and password params.
277
     *
278
     * $params can be used to pass headers to the client, note that they have
279
     * to follow the naming format used in $_SERVER.
280
     * Example: 'HTTP_X_REQUESTED_WITH' instead of 'X-Requested-With'
281
     *
282
     * @param string $username
283
     * @param string $password
284
     * @param array  $params
285
     *
286
     * @return Client
287
     */
288 1
    protected function makeClientWithCredentials(string $username, string $password, array $params = []): Client
289
    {
290 1
        return $this->createClientWithParams($params, $username, $password);
291
    }
292
293
    /**
294
     * Create User Token.
295
     *
296
     * Factory method for creating a User Token object for the firewall based on
297
     * the user object provided. By default it will be a Username/Password
298
     * Token based on the user's credentials, but may be overridden for custom
299
     * tokens in your applications.
300
     *
301
     * @param UserInterface $user         The user object to base the token off of
302
     * @param string        $firewallName name of the firewall provider to use
303
     *
304
     * @return TokenInterface The token to be used in the security context
305
     */
306 2
    protected function createUserToken(UserInterface $user, string $firewallName): TokenInterface
307
    {
308 2
        return new UsernamePasswordToken(
309 2
            $user,
310 2
            null,
311 2
            $firewallName,
312 2
            $user->getRoles()
0 ignored issues
show
Documentation introduced by
$user->getRoles() is of type array<integer,object<Sym...Core\Role\Role>|string>, but the function expects a array<integer,string>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
313
        );
314
    }
315
316
    /**
317
     * Extracts the location from the given route.
318
     *
319
     * @param string $route    The name of the route
320
     * @param array  $params   Set of parameters
321
     * @param int    $absolute
322
     *
323
     * @return string
324
     */
325 1
    protected function getUrl(string $route, array $params = [], int $absolute = UrlGeneratorInterface::ABSOLUTE_PATH): string
326
    {
327 1
        return $this->getContainer()->get('router')->generate($route, $params, $absolute);
328
    }
329
330
    /**
331
     * Checks the success state of a response.
332
     *
333
     * @param Response $response Response object
334
     * @param bool     $success  to define whether the response is expected to be successful
335
     * @param string   $type
336
     */
337 6
    public function isSuccessful(Response $response, $success = true, $type = 'text/html'): void
338
    {
339 6
        HttpAssertions::isSuccessful($response, $success, $type);
340 5
    }
341
342
    /**
343
     * Executes a request on the given url and returns the response contents.
344
     *
345
     * This method also asserts the request was successful.
346
     *
347
     * @param string $path           path of the requested page
348
     * @param string $method         The HTTP method to use, defaults to GET
349
     * @param bool   $authentication Whether to use authentication, defaults to false
350
     * @param bool   $success        to define whether the response is expected to be successful
351
     *
352
     * @return string
353
     */
354 1
    public function fetchContent(string $path, string $method = 'GET', bool $authentication = false, bool $success = true): string
355
    {
356 1
        $client = ($authentication) ? $this->makeAuthenticatedClient() : $this->makeClient();
357
358 1
        $client->request($method, $path);
359
360 1
        $content = $client->getResponse()->getContent();
361 1
        $this->isSuccessful($client->getResponse(), $success);
362
363 1
        return $content;
364
    }
365
366
    /**
367
     * Executes a request on the given url and returns a Crawler object.
368
     *
369
     * This method also asserts the request was successful.
370
     *
371
     * @param string $path           path of the requested page
372
     * @param string $method         The HTTP method to use, defaults to GET
373
     * @param bool   $authentication Whether to use authentication, defaults to false
374
     * @param bool   $success        Whether the response is expected to be successful
375
     *
376
     * @return Crawler
377
     */
378 1
    public function fetchCrawler(string $path, string $method = 'GET', bool $authentication = false, bool $success = true): Crawler
379
    {
380 1
        $client = ($authentication) ? $this->makeAuthenticatedClient() : $this->makeClient();
381
382 1
        $crawler = $client->request($method, $path);
383
384 1
        $this->isSuccessful($client->getResponse(), $success);
385
386 1
        return $crawler;
387
    }
388
389
    /**
390
     * @param UserInterface $user
391
     * @param string        $firewallName
392
     *
393
     * @return WebTestCase
394
     */
395 2
    public function loginAs(UserInterface $user, string $firewallName): self
396
    {
397 2
        $this->firewallLogins[$firewallName] = $user;
398
399 2
        return $this;
400
    }
401
402
    /**
403
     * Asserts that the HTTP response code of the last request performed by
404
     * $client matches the expected code. If not, raises an error with more
405
     * information.
406
     *
407
     * @param int    $expectedStatusCode
408
     * @param Client $client
409
     */
410 12
    public static function assertStatusCode(int $expectedStatusCode, Client $client): void
411
    {
412 12
        HttpAssertions::assertStatusCode($expectedStatusCode, $client);
413 9
    }
414
415
    /**
416
     * Assert that the last validation errors within $container match the
417
     * expected keys.
418
     *
419
     * @param array              $expected  A flat array of field names
420
     * @param ContainerInterface $container
421
     */
422 4
    public static function assertValidationErrors(array $expected, ContainerInterface $container): void
423
    {
424 4
        HttpAssertions::assertValidationErrors($expected, $container);
425 2
    }
426
427 51
    protected function tearDown(): void
428
    {
429 51
        if (null !== $this->containers) {
430 14
            foreach ($this->containers as $container) {
431 14
                if ($container instanceof ResettableContainerInterface) {
432 14
                    $container->reset();
433
                }
434
            }
435
        }
436
437 51
        $this->containers = null;
438
439 51
        parent::tearDown();
440 51
    }
441
442 38
    protected function createClientWithParams(array $params, ?string $username = null, ?string $password = null): Client
443
    {
444 38
        if ($username && $password) {
445 2
            $params = array_merge($params, [
446 2
                'PHP_AUTH_USER' => $username,
447 2
                'PHP_AUTH_PW' => $password,
448
            ]);
449
        }
450
451 38
        $client = static::createClient(['environment' => $this->environment], $params);
452
453 38
        if ($this->firewallLogins) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->firewallLogins of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
454
            // has to be set otherwise "hasPreviousSession" in Request returns false.
455 2
            $options = $client->getContainer()->getParameter('session.storage.options');
456
457 2
            if (!$options || !isset($options['name'])) {
458
                throw new \InvalidArgumentException('Missing session.storage.options#name');
459
            }
460
461 2
            $session = $client->getContainer()->get('session');
462 2
            $session->setId(uniqid());
463
464 2
            $client->getCookieJar()->set(new Cookie($options['name'], $session->getId()));
465
466
            /** @var $user UserInterface */
467 2
            foreach ($this->firewallLogins as $firewallName => $user) {
468 2
                $token = $this->createUserToken($user, $firewallName);
469
470 2
                $tokenStorage = $client->getContainer()->get('security.token_storage');
471
472 2
                $tokenStorage->setToken($token);
473 2
                $session->set('_security_'.$firewallName, serialize($token));
474
            }
475
476 2
            $session->save();
477
        }
478
479 38
        return $client;
480
    }
481
}
482