Completed
Push — master ( 3d4d4a...b2e1b7 )
by Alexis
30:15 queued 26:21
created

WebTestCase::tearDown()   A

Complexity

Conditions 4
Paths 2

Size

Total Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 4

Importance

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