Passed
Pull Request — master (#582)
by
unknown
10:26
created

WebTestCase::getInputs()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
crap 1
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\KernelBrowser;
21
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase as BaseWebTestCase;
22
use Symfony\Component\BrowserKit\Cookie;
23
use Symfony\Component\Console\Tester\CommandTester;
24
use Symfony\Component\DependencyInjection\ContainerInterface;
25
use Symfony\Component\DependencyInjection\ResettableContainerInterface;
26
use Symfony\Component\DomCrawler\Crawler;
27
use Symfony\Component\HttpFoundation\Response;
28
use Symfony\Component\HttpKernel\KernelInterface;
29
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
30
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
31
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
32
use Symfony\Component\Security\Core\User\UserInterface;
33
34
if (!class_exists(Client::class)) {
35
    class_alias(KernelBrowser::class, Client::class);
36
}
37
38
/**
39
 * @author Lea Haensenberger
40
 * @author Lukas Kahwe Smith <[email protected]>
41
 * @author Benjamin Eberlei <[email protected]>
42
 */
43
abstract class WebTestCase extends BaseWebTestCase
44
{
45
    protected $environment = 'test';
46
47
    protected $containers;
48
49
    // 5 * 1024 * 1024 KB
50
    protected $maxMemory = 5242880;
51
52
    // RUN COMMAND
53
    protected $verbosityLevel;
54
55
    protected $decorated;
56
57
    /**
58
     * @var array|null
59
     */
60
    private $inputs = null;
61
62
    /**
63
     * @var array
64
     */
65
    private $firewallLogins = [];
66
67
    /**
68
     * @var KernelInterface
69
     */
70
    protected static $testCaseKernel;
71
72
    /**
73
     * Creates a mock object of a service identified by its id.
74
     */
75
    protected function getServiceMockBuilder(string $id): MockBuilder
76
    {
77
        $service = $this->getContainer()->get($id);
78
        $class = get_class($service);
79
80
        return $this->getMockBuilder($class)->disableOriginalConstructor();
81
    }
82
83
    /**
84
     * Builds up the environment to run the given command.
85
     */
86 13
    protected function runCommand(string $name, array $params = [], bool $reuseKernel = false): CommandTester
87
    {
88 13
        if (!$reuseKernel) {
89 13
            if (null !== static::$kernel) {
90 1
                static::$kernel->shutdown();
91
            }
92
93 13
            $kernel = static::$kernel = static::createKernel(['environment' => $this->environment]);
94 13
            $kernel->boot();
95
        } else {
96 2
            $kernel = $this->getContainer()->get('kernel');
97
        }
98
99 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...
100
101
        $options = [
102 13
            'interactive' => false,
103 13
            'decorated' => $this->getDecorated(),
104 13
            'verbosity' => $this->getVerbosityLevel(),
105
        ];
106
107 12
        $command = $application->find($name);
108 12
        $commandTester = new CommandTester($command);
109
110 12
        if (null !== $inputs = $this->getInputs()) {
111 1
            $commandTester->setInputs($inputs);
112 1
            $options['interactive'] = true;
113 1
            $this->inputs = null;
114
        }
115
116 12
        $commandTester->execute(
117 12
            array_merge(['command' => $command->getName()], $params),
118 12
            $options
119
        );
120
121 12
        return $commandTester;
122
    }
123
124
    /**
125
     * Retrieves the output verbosity level.
126
     *
127
     * @see \Symfony\Component\Console\Output\OutputInterface for available levels
128
     *
129
     * @throws \OutOfBoundsException If the set value isn't accepted
130
     */
131 13
    protected function getVerbosityLevel(): int
132
    {
133
        // If `null`, is not yet set
134 13
        if (null === $this->verbosityLevel) {
135
            // Set the global verbosity level that is set as NORMAL by the TreeBuilder in Configuration
136 7
            $level = strtoupper($this->getContainer()->getParameter('liip_functional_test.command_verbosity'));
137 7
            $verbosity = '\Symfony\Component\Console\Output\StreamOutput::VERBOSITY_'.$level;
138
139 7
            $this->verbosityLevel = constant($verbosity);
140
        }
141
142
        // If string, it is set by the developer, so check that the value is an accepted one
143 13
        if (is_string($this->verbosityLevel)) {
144 6
            $level = strtoupper($this->verbosityLevel);
145 6
            $verbosity = '\Symfony\Component\Console\Output\StreamOutput::VERBOSITY_'.$level;
146
147 6
            if (!defined($verbosity)) {
148 1
                throw new \OutOfBoundsException(sprintf('The set value "%s" for verbosityLevel is not valid. Accepted are: "quiet", "normal", "verbose", "very_verbose" and "debug".', $level));
149
            }
150
151 5
            $this->verbosityLevel = constant($verbosity);
152
        }
153
154 12
        return $this->verbosityLevel;
155
    }
156
157 6
    public function setVerbosityLevel($level): void
158
    {
159 6
        $this->verbosityLevel = $level;
160 6
    }
161
162 1
    protected function setInputs(array $inputs): void
163
    {
164 1
        $this->inputs = $inputs;
165 1
    }
166
167 12
    protected function getInputs(): ?array
168
    {
169 12
        return $this->inputs;
170
    }
171
172
    /**
173
     * Set verbosity for Symfony 3.4+.
174
     *
175
     * @see https://github.com/symfony/symfony/pull/24425
176
     *
177
     * @param $level
178
     */
179
    private function setVerbosityLevelEnv($level): void
180
    {
181
        putenv('SHELL_VERBOSITY='.$level);
182
    }
183
184
    /**
185
     * Retrieves the flag indicating if the output should be decorated or not.
186
     */
187 13
    protected function getDecorated(): bool
188
    {
189 13
        if (null === $this->decorated) {
190
            // Set the global decoration flag that is set to `true` by the TreeBuilder in Configuration
191 7
            $this->decorated = $this->getContainer()->getParameter('liip_functional_test.command_decoration');
192
        }
193
194
        // Check the local decorated flag
195 13
        if (false === is_bool($this->decorated)) {
196
            throw new \OutOfBoundsException(sprintf('`WebTestCase::decorated` has to be `bool`. "%s" given.', gettype($this->decorated)));
197
        }
198
199 13
        return $this->decorated;
200
    }
201
202 6
    public function isDecorated(bool $decorated): void
203
    {
204 6
        $this->decorated = $decorated;
205 6
    }
206
207
    /**
208
     * Get an instance of the dependency injection container.
209
     * (this creates a kernel *without* parameters).
210
     */
211 15
    protected function getContainer(): ContainerInterface
212
    {
213 15
        $cacheKey = $this->environment;
214 15
        if (empty($this->containers[$cacheKey])) {
215
            $options = [
216 15
                'environment' => $this->environment,
217
            ];
218 15
            self::$testCaseKernel = $this->createKernel($options);
219 15
            self::$testCaseKernel->boot();
220
221 15
            $container = self::$testCaseKernel->getContainer();
222 15
            if ($container->has('test.service_container')) {
223 15
                $this->containers[$cacheKey] = $container->get('test.service_container');
224
            } else {
225
                $this->containers[$cacheKey] = $container;
226
            }
227
        }
228
229 15
        return $this->containers[$cacheKey];
230
    }
231
232
    /**
233
     * Creates an instance of a lightweight Http client.
234
     *
235
     * $params can be used to pass headers to the client, note that they have
236
     * to follow the naming format used in $_SERVER.
237
     * Example: 'HTTP_X_REQUESTED_WITH' instead of 'X-Requested-With'
238
     */
239 30
    protected function makeClient(array $params = []): Client
240
    {
241 30
        return $this->createClientWithParams($params);
242
    }
243
244
    /**
245
     * Creates an instance of a lightweight Http client.
246
     *
247
     * $params can be used to pass headers to the client, note that they have
248
     * to follow the naming format used in $_SERVER.
249
     * Example: 'HTTP_X_REQUESTED_WITH' instead of 'X-Requested-With'
250
     */
251 1
    protected function makeAuthenticatedClient(array $params = []): Client
252
    {
253 1
        $username = $this->getContainer()
254 1
            ->getParameter('liip_functional_test.authentication.username');
255 1
        $password = $this->getContainer()
256 1
            ->getParameter('liip_functional_test.authentication.password');
257
258 1
        return $this->createClientWithParams($params, $username, $password);
259
    }
260
261
    /**
262
     * Creates an instance of a lightweight Http client and log in user with
263
     * username and password params.
264
     *
265
     * $params can be used to pass headers to the client, note that they have
266
     * to follow the naming format used in $_SERVER.
267
     * Example: 'HTTP_X_REQUESTED_WITH' instead of 'X-Requested-With'
268
     */
269 1
    protected function makeClientWithCredentials(string $username, string $password, array $params = []): Client
270
    {
271 1
        return $this->createClientWithParams($params, $username, $password);
272
    }
273
274
    /**
275
     * Create User Token.
276
     *
277
     * Factory method for creating a User Token object for the firewall based on
278
     * the user object provided. By default it will be a Username/Password
279
     * Token based on the user's credentials, but may be overridden for custom
280
     * tokens in your applications.
281
     *
282
     * @param UserInterface $user         The user object to base the token off of
283
     * @param string        $firewallName name of the firewall provider to use
284
     *
285
     * @return TokenInterface The token to be used in the security context
286
     */
287 3
    protected function createUserToken(UserInterface $user, string $firewallName): TokenInterface
288
    {
289 3
        return new UsernamePasswordToken(
290 3
            $user,
291 3
            null,
292 3
            $firewallName,
293 3
            $user->getRoles()
294
        );
295
    }
296
297
    /**
298
     * Extracts the location from the given route.
299
     *
300
     * @param string $route  The name of the route
301
     * @param array  $params Set of parameters
302
     */
303 1
    protected function getUrl(string $route, array $params = [], int $absolute = UrlGeneratorInterface::ABSOLUTE_PATH): string
304
    {
305 1
        return $this->getContainer()->get('router')->generate($route, $params, $absolute);
306
    }
307
308
    /**
309
     * Checks the success state of a response.
310
     *
311
     * @param Response $response Response object
312
     * @param bool     $success  to define whether the response is expected to be successful
313
     * @param string   $type
314
     */
315 6
    public function isSuccessful(Response $response, $success = true, $type = 'text/html'): void
316
    {
317 6
        HttpAssertions::isSuccessful($response, $success, $type);
318 5
    }
319
320
    /**
321
     * Executes a request on the given url and returns the response contents.
322
     *
323
     * This method also asserts the request was successful.
324
     *
325
     * @param string $path           path of the requested page
326
     * @param string $method         The HTTP method to use, defaults to GET
327
     * @param bool   $authentication Whether to use authentication, defaults to false
328
     * @param bool   $success        to define whether the response is expected to be successful
329
     */
330 1
    public function fetchContent(string $path, string $method = 'GET', bool $authentication = false, bool $success = true): string
331
    {
332 1
        $client = ($authentication) ? $this->makeAuthenticatedClient() : $this->makeClient();
333
334 1
        $client->request($method, $path);
335
336 1
        $content = $client->getResponse()->getContent();
337 1
        $this->isSuccessful($client->getResponse(), $success);
338
339 1
        return $content;
340
    }
341
342
    /**
343
     * Executes a request on the given url and returns a Crawler object.
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        Whether the response is expected to be successful
351
     */
352 1
    public function fetchCrawler(string $path, string $method = 'GET', bool $authentication = false, bool $success = true): Crawler
353
    {
354 1
        $client = ($authentication) ? $this->makeAuthenticatedClient() : $this->makeClient();
355
356 1
        $crawler = $client->request($method, $path);
357
358 1
        $this->isSuccessful($client->getResponse(), $success);
359
360 1
        return $crawler;
361
    }
362
363
    /**
364
     * @return WebTestCase
365
     */
366 2
    public function loginAs(UserInterface $user, string $firewallName): self
367
    {
368 2
        @trigger_error(sprintf('"%s()" is deprecated, use loginClient() after creating a client.', __METHOD__), E_USER_DEPRECATED);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
369
370 2
        $this->firewallLogins[$firewallName] = $user;
371
372 2
        return $this;
373
    }
374
375 1
    public function loginClient(KernelBrowser $client, UserInterface $user, string $firewallName): void
376
    {
377
        // has to be set otherwise "hasPreviousSession" in Request returns false.
378 1
        $options = $client->getContainer()->getParameter('session.storage.options');
379
380 1
        if (!$options || !isset($options['name'])) {
381
            throw new \InvalidArgumentException('Missing session.storage.options#name');
382
        }
383
384 1
        $session = $client->getContainer()->get('session');
385 1
        $session->setId(uniqid());
386
387 1
        $client->getCookieJar()->set(new Cookie($options['name'], $session->getId()));
388
389 1
        $token = $this->createUserToken($user, $firewallName);
390
391 1
        $tokenStorage = $client->getContainer()->get('security.token_storage');
392
393 1
        $tokenStorage->setToken($token);
394 1
        $session->set('_security_'.$firewallName, serialize($token));
395
396 1
        $session->save();
397 1
    }
398
399
    /**
400
     * Asserts that the HTTP response code of the last request performed by
401
     * $client matches the expected code. If not, raises an error with more
402
     * information.
403
     */
404 13
    public static function assertStatusCode(int $expectedStatusCode, Client $client): void
405
    {
406 13
        HttpAssertions::assertStatusCode($expectedStatusCode, $client);
407 10
    }
408
409
    /**
410
     * Assert that the last validation errors within $container match the
411
     * expected keys.
412
     *
413
     * @param array $expected A flat array of field names
414
     */
415 4
    public static function assertValidationErrors(array $expected, ContainerInterface $container): void
416
    {
417 4
        HttpAssertions::assertValidationErrors($expected, $container);
418 2
    }
419
420 48
    protected function tearDown(): void
421
    {
422 48
        if (null !== $this->containers) {
423 15
            foreach ($this->containers as $container) {
424 15
                if ($container instanceof ResettableContainerInterface) {
0 ignored issues
show
Bug introduced by
The class Symfony\Component\Depend...tableContainerInterface does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
425 15
                    $container->reset();
426
                }
427
            }
428
        }
429
430 48
        if (self::$testCaseKernel !== null) {
431 39
            self::$testCaseKernel->shutdown();
432
        }
433
434 48
        $this->containers = null;
435
436 48
        parent::tearDown();
437 48
    }
438
439 32
    protected function createClientWithParams(array $params, ?string $username = null, ?string $password = null): Client
440
    {
441 32
        if ($username && $password) {
442 2
            $params = array_merge($params, [
443 2
                'PHP_AUTH_USER' => $username,
444 2
                'PHP_AUTH_PW' => $password,
445
            ]);
446
        }
447
448 32
        $client = static::createClient(['environment' => $this->environment], $params);
449
450 32
        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...
451
            // has to be set otherwise "hasPreviousSession" in Request returns false.
452 2
            $options = $client->getContainer()->getParameter('session.storage.options');
453
454 2
            if (!$options || !isset($options['name'])) {
455
                throw new \InvalidArgumentException('Missing session.storage.options#name');
456
            }
457
458 2
            $session = $client->getContainer()->get('session');
459 2
            $session->setId(uniqid());
460
461 2
            $client->getCookieJar()->set(new Cookie($options['name'], $session->getId()));
462
463
            /** @var $user UserInterface */
464 2
            foreach ($this->firewallLogins as $firewallName => $user) {
465 2
                $token = $this->createUserToken($user, $firewallName);
466
467 2
                $tokenStorage = $client->getContainer()->get('security.token_storage');
468
469 2
                $tokenStorage->setToken($token);
470 2
                $session->set('_security_'.$firewallName, serialize($token));
471
            }
472
473 2
            $session->save();
474
        }
475
476 32
        return $client;
477
    }
478
}
479