Completed
Push — master ( c6a5c1...3d4d4a )
by Alexis
08:07 queued 02:27
created

WebTestCase::getVerbosityLevel()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 27

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 4

Importance

Changes 0
Metric Value
dl 0
loc 27
ccs 13
cts 13
cp 1
rs 9.488
c 0
b 0
f 0
cc 4
nc 6
nop 0
crap 4
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
53
     */
54
    private $firewallLogins = [];
55
56
    /**
57
     * @var array
58
     */
59
    private $excludedDoctrineTables = [];
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 12
    protected function runCommand(string $name, array $params = [], bool $reuseKernel = false): CommandTester
86
    {
87 12
        if (!$reuseKernel) {
88 12
            if (null !== static::$kernel) {
89 10
                static::$kernel->shutdown();
90
            }
91
92 12
            $kernel = static::$kernel = static::createKernel(['environment' => $this->environment]);
93 12
            $kernel->boot();
94
        } else {
95 2
            $kernel = $this->getContainer()->get('kernel');
96
        }
97
98 12
        $application = new Application($kernel);
99
100 12
        $command = $application->find($name);
101 12
        $commandTester = new CommandTester($command);
102 12
        $commandTester->execute(
103 12
            array_merge(['command' => $command->getName()], $params),
104
            [
105 12
                'interactive' => false,
106 12
                'decorated' => $this->getDecorated(),
107 12
                'verbosity' => $this->getVerbosityLevel(),
108
            ]
109
        );
110
111 11
        return $commandTester;
112
    }
113
114
    /**
115
     * Retrieves the output verbosity level.
116
     *
117
     * @see \Symfony\Component\Console\Output\OutputInterface for available levels
118
     *
119
     * @throws \OutOfBoundsException If the set value isn't accepted
120
     *
121
     * @return int
122
     */
123 12
    protected function getVerbosityLevel(): int
124
    {
125
        // If `null`, is not yet set
126 12
        if (null === $this->verbosityLevel) {
127
            // Set the global verbosity level that is set as NORMAL by the TreeBuilder in Configuration
128 6
            $level = strtoupper($this->getContainer()->getParameter('liip_functional_test.command_verbosity'));
129 6
            $verbosity = '\Symfony\Component\Console\Output\StreamOutput::VERBOSITY_'.$level;
130
131 6
            $this->verbosityLevel = constant($verbosity);
132
        }
133
134
        // If string, it is set by the developer, so check that the value is an accepted one
135 12
        if (is_string($this->verbosityLevel)) {
136 6
            $level = strtoupper($this->verbosityLevel);
137 6
            $verbosity = '\Symfony\Component\Console\Output\StreamOutput::VERBOSITY_'.$level;
138
139 6
            if (!defined($verbosity)) {
140 1
                throw new \OutOfBoundsException(
141 1
                    sprintf('The set value "%s" for verbosityLevel is not valid. Accepted are: "quiet", "normal", "verbose", "very_verbose" and "debug".', $level)
142
                );
143
            }
144
145 5
            $this->verbosityLevel = constant($verbosity);
146
        }
147
148 11
        return $this->verbosityLevel;
149
    }
150
151 6
    public function setVerbosityLevel($level): void
152
    {
153 6
        $this->verbosityLevel = $level;
154 6
    }
155
156
    /**
157
     * Set verbosity for Symfony 3.4+.
158
     *
159
     * @see https://github.com/symfony/symfony/pull/24425
160
     *
161
     * @param $level
162
     */
163
    private function setVerbosityLevelEnv($level): void
164
    {
165
        putenv('SHELL_VERBOSITY='.$level);
166
    }
167
168
    /**
169
     * Retrieves the flag indicating if the output should be decorated or not.
170
     *
171
     * @return bool
172
     */
173 12
    protected function getDecorated(): bool
174
    {
175 12
        if (null === $this->decorated) {
176
            // Set the global decoration flag that is set to `true` by the TreeBuilder in Configuration
177 6
            $this->decorated = $this->getContainer()->getParameter('liip_functional_test.command_decoration');
178
        }
179
180
        // Check the local decorated flag
181 12
        if (false === is_bool($this->decorated)) {
182
            throw new \OutOfBoundsException(
183
                sprintf('`WebTestCase::decorated` has to be `bool`. "%s" given.', gettype($this->decorated))
184
            );
185
        }
186
187 12
        return $this->decorated;
188
    }
189
190 6
    public function isDecorated(bool $decorated): void
191
    {
192 6
        $this->decorated = $decorated;
193 6
    }
194
195
    /**
196
     * Get an instance of the dependency injection container.
197
     * (this creates a kernel *without* parameters).
198
     *
199
     * @return ContainerInterface
200
     */
201 13
    protected function getContainer(): ContainerInterface
202
    {
203 13
        $cacheKey = $this->environment;
204 13
        if (empty($this->containers[$cacheKey])) {
205
            $options = [
206 13
                'environment' => $this->environment,
207
            ];
208 13
            $kernel = $this->createKernel($options);
209 13
            $kernel->boot();
210
211 13
            $container = $kernel->getContainer();
212 13
            if ($container->has('test.service_container')) {
213
                $this->containers[$cacheKey] = $container->get('test.service_container');
214
            } else {
215 13
                $this->containers[$cacheKey] = $container;
216
            }
217
        }
218
219 13
        return $this->containers[$cacheKey];
220
    }
221
222
    /**
223
     * Creates an instance of a lightweight Http client.
224
     *
225
     * $params can be used to pass headers to the client, note that they have
226
     * to follow the naming format used in $_SERVER.
227
     * Example: 'HTTP_X_REQUESTED_WITH' instead of 'X-Requested-With'
228
     *
229
     * @param array $params
230
     *
231
     * @return Client
232
     */
233 36
    protected function makeClient(array $params = []): Client
234
    {
235 36
        return $this->createClientWithParams($params);
236
    }
237
238
    /**
239
     * Creates an instance of a lightweight Http client.
240
     *
241
     * $params can be used to pass headers to the client, note that they have
242
     * to follow the naming format used in $_SERVER.
243
     * Example: 'HTTP_X_REQUESTED_WITH' instead of 'X-Requested-With'
244
     *
245
     * @param array $params
246
     *
247
     * @return Client
248
     */
249 1
    protected function makeAuthenticatedClient(array $params = []): Client
250
    {
251 1
        $username = $this->getContainer()
252 1
            ->getParameter('liip_functional_test.authentication.username');
253 1
        $password = $this->getContainer()
254 1
            ->getParameter('liip_functional_test.authentication.password');
255
256 1
        return $this->createClientWithParams($params, $username, $password);
257
    }
258
259
    /**
260
     * Creates an instance of a lightweight Http client and log in user with
261
     * username and password params.
262
     *
263
     * $params can be used to pass headers to the client, note that they have
264
     * to follow the naming format used in $_SERVER.
265
     * Example: 'HTTP_X_REQUESTED_WITH' instead of 'X-Requested-With'
266
     *
267
     * @param string $username
268
     * @param string $password
269
     * @param array  $params
270
     *
271
     * @return Client
272
     */
273 1
    protected function makeClientWithCredentials(string $username, string $password, array $params = []): Client
274
    {
275 1
        return $this->createClientWithParams($params, $username, $password);
276
    }
277
278
    /**
279
     * Create User Token.
280
     *
281
     * Factory method for creating a User Token object for the firewall based on
282
     * the user object provided. By default it will be a Username/Password
283
     * Token based on the user's credentials, but may be overridden for custom
284
     * tokens in your applications.
285
     *
286
     * @param UserInterface $user         The user object to base the token off of
287
     * @param string        $firewallName name of the firewall provider to use
288
     *
289
     * @return TokenInterface The token to be used in the security context
290
     */
291 2
    protected function createUserToken(UserInterface $user, string $firewallName): TokenInterface
292
    {
293 2
        return new UsernamePasswordToken(
294 2
            $user,
295 2
            null,
296 2
            $firewallName,
297 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...
298
        );
299
    }
300
301
    /**
302
     * Extracts the location from the given route.
303
     *
304
     * @param string $route    The name of the route
305
     * @param array  $params   Set of parameters
306
     * @param int    $absolute
307
     *
308
     * @return string
309
     */
310 1
    protected function getUrl(string $route, array $params = [], int $absolute = UrlGeneratorInterface::ABSOLUTE_PATH): string
311
    {
312 1
        return $this->getContainer()->get('router')->generate($route, $params, $absolute);
313
    }
314
315
    /**
316
     * Checks the success state of a response.
317
     *
318
     * @param Response $response Response object
319
     * @param bool     $success  to define whether the response is expected to be successful
320
     * @param string   $type
321
     */
322 6
    public function isSuccessful(Response $response, $success = true, $type = 'text/html'): void
323
    {
324 6
        HttpAssertions::isSuccessful($response, $success, $type);
325 5
    }
326
327
    /**
328
     * Executes a request on the given url and returns the response contents.
329
     *
330
     * This method also asserts the request was successful.
331
     *
332
     * @param string $path           path of the requested page
333
     * @param string $method         The HTTP method to use, defaults to GET
334
     * @param bool   $authentication Whether to use authentication, defaults to false
335
     * @param bool   $success        to define whether the response is expected to be successful
336
     *
337
     * @return string
338
     */
339 1
    public function fetchContent(string $path, string $method = 'GET', bool $authentication = false, bool $success = true): string
340
    {
341 1
        $client = ($authentication) ? $this->makeAuthenticatedClient() : $this->makeClient();
342
343 1
        $client->request($method, $path);
344
345 1
        $content = $client->getResponse()->getContent();
346 1
        $this->isSuccessful($client->getResponse(), $success);
347
348 1
        return $content;
349
    }
350
351
    /**
352
     * Executes a request on the given url and returns a Crawler object.
353
     *
354
     * This method also asserts the request was successful.
355
     *
356
     * @param string $path           path of the requested page
357
     * @param string $method         The HTTP method to use, defaults to GET
358
     * @param bool   $authentication Whether to use authentication, defaults to false
359
     * @param bool   $success        Whether the response is expected to be successful
360
     *
361
     * @return Crawler
362
     */
363 1
    public function fetchCrawler(string $path, string $method = 'GET', bool $authentication = false, bool $success = true): Crawler
364
    {
365 1
        $client = ($authentication) ? $this->makeAuthenticatedClient() : $this->makeClient();
366
367 1
        $crawler = $client->request($method, $path);
368
369 1
        $this->isSuccessful($client->getResponse(), $success);
370
371 1
        return $crawler;
372
    }
373
374
    /**
375
     * @param UserInterface $user
376
     * @param string        $firewallName
377
     *
378
     * @return WebTestCase
379
     */
380 2
    public function loginAs(UserInterface $user, string $firewallName): self
381
    {
382 2
        $this->firewallLogins[$firewallName] = $user;
383
384 2
        return $this;
385
    }
386
387
    /**
388
     * Asserts that the HTTP response code of the last request performed by
389
     * $client matches the expected code. If not, raises an error with more
390
     * information.
391
     *
392
     * @param int    $expectedStatusCode
393
     * @param Client $client
394
     */
395 12
    public static function assertStatusCode(int $expectedStatusCode, Client $client): void
396
    {
397 12
        HttpAssertions::assertStatusCode($expectedStatusCode, $client);
398 9
    }
399
400
    /**
401
     * Assert that the last validation errors within $container match the
402
     * expected keys.
403
     *
404
     * @param array              $expected  A flat array of field names
405
     * @param ContainerInterface $container
406
     */
407 4
    public static function assertValidationErrors(array $expected, ContainerInterface $container): void
408
    {
409 4
        HttpAssertions::assertValidationErrors($expected, $container);
410 2
    }
411
412
    /**
413
     * @param array $excludedDoctrineTables
414
     */
415
    public function setExcludedDoctrineTables(array $excludedDoctrineTables): void
416
    {
417
        $this->excludedDoctrineTables = $excludedDoctrineTables;
418
    }
419
420 50
    protected function tearDown(): void
421
    {
422 50
        if (null !== $this->containers) {
423 13
            foreach ($this->containers as $container) {
424 13
                if ($container instanceof ResettableContainerInterface) {
425 13
                    $container->reset();
426
                }
427
            }
428
        }
429
430 50
        $this->containers = null;
431
432 50
        parent::tearDown();
433 50
    }
434
435 38
    protected function createClientWithParams(array $params, ?string $username = null, ?string $password = null): Client
436
    {
437 38
        if ($username && $password) {
438 2
            $params = array_merge($params, [
439 2
                'PHP_AUTH_USER' => $username,
440 2
                'PHP_AUTH_PW' => $password,
441
            ]);
442
        }
443
444 38
        $client = static::createClient(['environment' => $this->environment], $params);
445
446 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...
447
            // has to be set otherwise "hasPreviousSession" in Request returns false.
448 2
            $options = $client->getContainer()->getParameter('session.storage.options');
449
450 2
            if (!$options || !isset($options['name'])) {
451
                throw new \InvalidArgumentException('Missing session.storage.options#name');
452
            }
453
454 2
            $session = $client->getContainer()->get('session');
455 2
            $session->setId(uniqid());
456
457 2
            $client->getCookieJar()->set(new Cookie($options['name'], $session->getId()));
458
459
            /** @var $user UserInterface */
460 2
            foreach ($this->firewallLogins as $firewallName => $user) {
461 2
                $token = $this->createUserToken($user, $firewallName);
462
463 2
                $tokenStorage = $client->getContainer()->get('security.token_storage');
464
465 2
                $tokenStorage->setToken($token);
466 2
                $session->set('_security_'.$firewallName, serialize($token));
467
            }
468
469 2
            $session->save();
470
        }
471
472 38
        return $client;
473
    }
474
}
475