Completed
Push — 2.0 ( 8503aa...92db27 )
by Alexis
02:49
created

WebTestCase::configureVerbosityForSymfony20301()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 22
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 22
ccs 0
cts 0
cp 0
rs 8.6737
cc 5
eloc 15
nc 5
nop 1
crap 30
1
<?php
0 ignored issues
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 58 and the first side effect is on line 41.

The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.

The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.

To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.

Loading history...
2
3
/*
4
 * This file is part of the Liip/FunctionalTestBundle
5
 *
6
 * (c) Lukas Kahwe Smith <[email protected]>
7
 *
8
 * This source file is subject to the MIT license that is bundled
9
 * with this source code in the file LICENSE.
10
 */
11
12
namespace Liip\FunctionalTestBundle\Test;
13
14
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase as BaseWebTestCase;
15
use Symfony\Bundle\FrameworkBundle\Console\Application;
16
use Symfony\Bundle\FrameworkBundle\Client;
17
use Symfony\Component\Console\Input\ArrayInput;
18
use Symfony\Component\Console\Output\OutputInterface;
19
use Symfony\Component\Console\Output\StreamOutput;
20
use Symfony\Component\DomCrawler\Crawler;
21
use Symfony\Component\BrowserKit\Cookie;
22
use Symfony\Component\HttpKernel\Kernel;
23
use Symfony\Component\HttpFoundation\Response;
24
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
25
use Symfony\Component\Security\Core\User\UserInterface;
26
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
27
use Symfony\Component\DependencyInjection\ContainerInterface;
28
use Symfony\Component\HttpFoundation\Session\Session;
29
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
30
use Symfony\Bundle\DoctrineFixturesBundle\Common\DataFixtures\Loader;
31
use Doctrine\Common\Persistence\ObjectManager;
32
use Doctrine\Common\DataFixtures\Executor\AbstractExecutor;
33
use Doctrine\Common\DataFixtures\ProxyReferenceRepository;
34
use Liip\FunctionalTestBundle\Utils\FixturesLoader;
35
36
/**
37
 * @author Lea Haensenberger
38
 * @author Lukas Kahwe Smith <[email protected]>
39
 * @author Benjamin Eberlei <[email protected]>
40
 */
41
abstract class WebTestCase extends BaseWebTestCase
0 ignored issues
show
Bug introduced by
Possible parse error: class missing opening or closing brace
Loading history...
42
{
43
    protected $environment = 'test';
44
    protected $containers;
45
    protected $kernelDir;
46
    // 5 * 1024 * 1024 KB
47
    protected $maxMemory = 5242880;
48
49
    // RUN COMMAND
50
    protected $verbosityLevel;
51
    protected $decorated;
52
53
    /**
54
     * @var array
55
     */
56
    private $firewallLogins = array();
57
58 1
    protected static function getKernelClass()
59
    {
60 1
        $dir = isset($_SERVER['KERNEL_DIR']) ? $_SERVER['KERNEL_DIR'] : self::getPhpUnitXmlDir();
61
62 1
        list($appname) = explode('\\', get_called_class());
63
64 1
        $class = $appname.'Kernel';
65 1
        $file = $dir.'/'.strtolower($appname).'/'.$class.'.php';
66 1
        if (!file_exists($file)) {
67 1
            return parent::getKernelClass();
68
        }
69
        require_once $file;
70
71
        return $class;
72
    }
73
74
    /**
75
     * Creates a mock object of a service identified by its id.
76
     *
77
     * @param string $id
78
     *
79
     * @return \PHPUnit_Framework_MockObject_MockBuilder
80
     */
81
    protected function getServiceMockBuilder($id)
82
    {
83
        $service = $this->getContainer()->get($id);
84
        $class = get_class($service);
85
86
        return $this->getMockBuilder($class)->disableOriginalConstructor();
87
    }
88
89
    /**
90
     * Builds up the environment to run the given command.
91
     *
92
     * @param string $name
93
     * @param array  $params
94
     * @param bool   $reuseKernel
95
     *
96
     * @return string
97
     */
98 12
    protected function runCommand($name, array $params = array(), $reuseKernel = false)
99
    {
100 12
        array_unshift($params, $name);
101
102 12
        if (!$reuseKernel) {
103 12
            if (null !== static::$kernel) {
104 9
                static::$kernel->shutdown();
105 9
            }
106
107 12
            $kernel = static::$kernel = $this->createKernel(array('environment' => $this->environment));
108 12
            $kernel->boot();
109 12
        } else {
110 2
            $kernel = $this->getContainer()->get('kernel');
111
        }
112
113 12
        $application = new Application($kernel);
114 12
        $application->setAutoExit(false);
115
116
        // @codeCoverageIgnoreStart
117
        if ('20301' === Kernel::VERSION_ID) {
118
            $params = $this->configureVerbosityForSymfony20301($params);
119
        }
120
        // @codeCoverageIgnoreEnd
121
122 12
        $input = new ArrayInput($params);
123 12
        $input->setInteractive(false);
124
125 12
        $fp = fopen('php://temp/maxmemory:'.$this->maxMemory, 'r+');
126 12
        $output = new StreamOutput($fp, $this->getVerbosityLevel(), $this->getDecorated());
127
128 11
        $application->run($input, $output);
129
130 11
        rewind($fp);
131
132 11
        return stream_get_contents($fp);
133
    }
134
135
    /**
136
     * Retrieves the output verbosity level.
137
     *
138
     * @see Symfony\Component\Console\Output\OutputInterface for available levels
139
     *
140
     * @return int
141
     *
142
     * @throws \OutOfBoundsException If the set value isn't accepted
143
     */
144 12
    protected function getVerbosityLevel()
145
    {
146
        // If `null`, is not yet set
147 12
        if (null === $this->verbosityLevel) {
148
            // Set the global verbosity level that is set as NORMAL by the TreeBuilder in Configuration
149 6
            $level = strtoupper($this->getContainer()->getParameter('liip_functional_test.command_verbosity'));
150 6
            $verbosity = '\Symfony\Component\Console\Output\StreamOutput::VERBOSITY_'.$level;
151
152 6
            $this->verbosityLevel = constant($verbosity);
153 6
        }
154
155
        // If string, it is set by the developer, so check that the value is an accepted one
156 12
        if (is_string($this->verbosityLevel)) {
157 6
            $level = strtoupper($this->verbosityLevel);
158 6
            $verbosity = '\Symfony\Component\Console\Output\StreamOutput::VERBOSITY_'.$level;
159
160 6
            if (!defined($verbosity)) {
161 1
                throw new \OutOfBoundsException(
162 1
                    sprintf('The set value "%s" for verbosityLevel is not valid. Accepted are: "quiet", "normal", "verbose", "very_verbose" and "debug".', $level)
163 1
                    );
164
            }
165
166 5
            $this->verbosityLevel = constant($verbosity);
167 5
        }
168
169 11
        return $this->verbosityLevel;
170
    }
171
172
    /**
173
     * In Symfony 2.3.1 the verbosity level has to be set through {Symfony\Component\Console\Input\ArrayInput} and not
174
     * in {Symfony\Component\Console\Output\OutputInterface}.
175
     *
176
     * This method builds $params to be passed to {Symfony\Component\Console\Input\ArrayInput}.
177
     *
178
     * @codeCoverageIgnore
179
     *
180
     * @param array $params
181
     *
182
     * @return array
183
     */
184
    private function configureVerbosityForSymfony20301(array $params)
185
    {
186
        switch ($this->getVerbosityLevel()) {
187
            case OutputInterface::VERBOSITY_QUIET:
188
                $params['-q'] = '-q';
189
                break;
190
191
            case OutputInterface::VERBOSITY_VERBOSE:
192
                $params['-v'] = '';
193
                break;
194
195
            case OutputInterface::VERBOSITY_VERY_VERBOSE:
196
                $params['-vv'] = '';
197
                break;
198
199
            case OutputInterface::VERBOSITY_DEBUG:
200
                $params['-vvv'] = '';
201
                break;
202
        }
203
204
        return $params;
205
    }
206
207 6
    public function setVerbosityLevel($level)
208
    {
209 6
        $this->verbosityLevel = $level;
210 6
    }
211
212
    /**
213
     * Retrieves the flag indicating if the output should be decorated or not.
214
     *
215
     * @return bool
216
     */
217 11
    protected function getDecorated()
218
    {
219 11
        if (null === $this->decorated) {
220
            // Set the global decoration flag that is set to `true` by the TreeBuilder in Configuration
221 5
            $this->decorated = $this->getContainer()->getParameter('liip_functional_test.command_decoration');
222 5
        }
223
224
        // Check the local decorated flag
225 11
        if (false === is_bool($this->decorated)) {
226
            throw new \OutOfBoundsException(
227
                sprintf('`WebTestCase::decorated` has to be `bool`. "%s" given.', gettype($this->decorated))
228
            );
229
        }
230
231 11
        return $this->decorated;
232
    }
233
234 6
    public function isDecorated($decorated)
235
    {
236 6
        $this->decorated = $decorated;
237 6
    }
238
239
    /**
240
     * Get an instance of the dependency injection container.
241
     * (this creates a kernel *without* parameters).
242
     *
243
     * @return ContainerInterface
244
     */
245 37
    protected function getContainer()
246
    {
247 37
        if (!empty($this->kernelDir)) {
248
            $tmpKernelDir = isset($_SERVER['KERNEL_DIR']) ? $_SERVER['KERNEL_DIR'] : null;
249
            $_SERVER['KERNEL_DIR'] = getcwd().$this->kernelDir;
250
        }
251
252 37
        $cacheKey = $this->kernelDir.'|'.$this->environment;
253 37
        if (empty($this->containers[$cacheKey])) {
254
            $options = array(
255 37
                'environment' => $this->environment,
256 37
            );
257 37
            $kernel = $this->createKernel($options);
258 37
            $kernel->boot();
259
260 37
            $this->containers[$cacheKey] = $kernel->getContainer();
261 37
        }
262
263 37
        if (isset($tmpKernelDir)) {
264 1
            $_SERVER['KERNEL_DIR'] = $tmpKernelDir;
265
        }
266
267 37
        return $this->containers[$cacheKey];
268
    }
269
270
    /**
271
     * Set the database to the provided fixtures.
272
     *
273
     * Drops the current database and then loads fixtures using the specified
274
     * classes. The parameter is a list of fully qualified class names of
275
     * classes that implement Doctrine\Common\DataFixtures\FixtureInterface
276
     * so that they can be loaded by the DataFixtures Loader::addFixture
277
     *
278
     * When using SQLite this method will automatically make a copy of the
279
     * loaded schema and fixtures which will be restored automatically in
280
     * case the same fixture classes are to be loaded again. Caveat: changes
281
     * to references and/or identities may go undetected.
282
     *
283
     * Depends on the doctrine data-fixtures library being available in the
284
     * class path.
285
     *
286
     * @param array  $classNames   List of fully qualified class names of fixtures to load
287
     * @param string $omName       The name of object manager to use
288
     * @param string $registryName The service id of manager registry to use
289
     * @param int    $purgeMode    Sets the ORM purge mode
290
     *
291
     * @return null|AbstractExecutor
292
     */
293 24
    protected function loadFixtures(array $classNames, $omName = null, $registryName = 'doctrine', $purgeMode = null)
294
    {
295 24
        $loader = new FixturesLoader($this->getContainer());
296
297 24
        return $loader->loadFixtures($classNames, $omName, $registryName, $purgeMode);
298
    }
299
300
    /**
301
     * @param array  $paths        Either symfony resource locators (@ BundleName/etc) or actual file paths
302
     * @param bool   $append
303
     * @param null   $omName
304
     * @param string $registryName
305
     *
306
     * @return array
307
     *
308
     * @throws \BadMethodCallException
309
     */
310 3
    public function loadFixtureFiles(array $paths = array(), $append = false, $omName = null, $registryName = 'doctrine')
311
    {
312 3
        $loader = new FixturesLoader($this->getContainer());
313
314 3
<<<<<<< HEAD
0 ignored issues
show
Bug introduced by
This code did not parse for me. Apparently, there is an error somewhere around this line:

Syntax error, unexpected T_SL
Loading history...
315
        return $loader->loadFixtureFiles($paths, $append, $omName, $registryName);
316
=======
317
        /** @var ContainerInterface $container */
318
        $container = $this->getContainer();
319
320
        /** @var ManagerRegistry $registry */
321
        $registry = $container->get($registryName);
322
323
        /** @var EntityManager $om */
324
        $om = $registry->getManager($omName);
325
326
        if ($append === false) {
327
            $this->cleanDatabase($registry, $om);
328
        }
329
330
        $files = $this->locateResources($paths);
331
332
        // Check if the Hautelook AliceBundle is registered and if yes, use it instead of Nelmio Alice
333
        $hautelookLoaderServiceName = 'hautelook_alice.fixtures.loader';
334
        if ($container->has($hautelookLoaderServiceName)) {
335
            $loaderService = $container->get($hautelookLoaderServiceName);
336
            $persisterClass = class_exists('Nelmio\Alice\ORM\Doctrine') ?
337
                'Nelmio\Alice\ORM\Doctrine' :
338
                'Nelmio\Alice\Persister\Doctrine';
339
340
            return $loaderService->load(new $persisterClass($om), $files);
341
        }
342
343
        return Fixtures::load($files, $om);
344
>>>>>>> 2c95f380e7b3cc356e76373c0e4cdbbc4d1a323f
345
    }
346
347
    /**
348
     * Callback function to be executed after Schema creation.
349
     * Use this to execute acl:init or other things necessary.
350
     */
351
    protected function postFixtureSetup()
352
    {
353
    }
354
355
    /**
356
     * Callback function to be executed after Schema restore.
357
     *
358
     * @return WebTestCase
359
     */
360
    protected function postFixtureRestore()
361
    {
362
    }
363
364
    /**
365
     * Callback function to be executed before Schema restore.
366
     *
367
     * @param ObjectManager            $manager             The object manager
368
     * @param ProxyReferenceRepository $referenceRepository The reference repository
369
     *
370
     * @return WebTestCase
371
     */
372
    protected function preFixtureRestore(ObjectManager $manager, ProxyReferenceRepository $referenceRepository)
373
    {
374
    }
375
376
    /**
377
     * Callback function to be executed after save of references.
378
     *
379
     * @param ObjectManager    $manager        The object manager
380
     * @param AbstractExecutor $executor       Executor of the data fixtures
381
     * @param string           $backupFilePath Path of file used to backup the references of the data fixtures
382
     *
383
     * @return WebTestCase
384
     */
385
    protected function postReferenceSave(ObjectManager $manager, AbstractExecutor $executor, $backupFilePath)
386
    {
387 46
    }
388
389 46
    /**
390 2
     * Callback function to be executed before save of references.
391
     *
392 1
     * @param ObjectManager    $manager        The object manager
393 1
     * @param AbstractExecutor $executor       Executor of the data fixtures
394 1
     * @param string           $backupFilePath Path of file used to backup the references of the data fixtures
395 1
     *
396 1
     * @return WebTestCase
397 1
     */
398
    protected function preReferenceSave(ObjectManager $manager, AbstractExecutor $executor, $backupFilePath)
399 2
    {
400 2
    }
401 2
402 2
    /**
403 2
     * Creates an instance of a lightweight Http client.
404
     *
405 46
     * If $authentication is set to 'true' it will use the content of
406
     * 'liip_functional_test.authentication' to log in.
407 46
     *
408
     * $params can be used to pass headers to the client, note that they have
409 2
     * to follow the naming format used in $_SERVER.
410
     * Example: 'HTTP_X_REQUESTED_WITH' instead of 'X-Requested-With'
411 2
     *
412
     * @param bool|array $authentication
413
     * @param array      $params
414
     *
415 2
     * @return Client
416
     */
417 2
    protected function makeClient($authentication = false, array $params = array())
418 2
    {
419 2
        if ($authentication) {
420
            if ($authentication === true) {
421 2
                $authentication = array(
422
                    'username' => $this->getContainer()
423
                        ->getParameter('liip_functional_test.authentication.username'),
424 2
                    'password' => $this->getContainer()
425 2
                        ->getParameter('liip_functional_test.authentication.password'),
426
                );
427
            }
428
429 2
            $params = array_merge($params, array(
430 2
                'PHP_AUTH_USER' => $authentication['username'],
431 2
                'PHP_AUTH_PW' => $authentication['password'],
432
            ));
433
        }
434
435
        $client = static::createClient(array('environment' => $this->environment), $params);
436
437
        if ($this->firewallLogins) {
438 2
            // has to be set otherwise "hasPreviousSession" in Request returns false.
439 2
            $options = $client->getContainer()->getParameter('session.storage.options');
440 2
441
            if (!$options || !isset($options['name'])) {
442 2
                throw new \InvalidArgumentException('Missing session.storage.options#name');
443 2
            }
444
445 46
            $session = $client->getContainer()->get('session');
446
            // Since the namespace of the session changed in symfony 2.1, instanceof can be used to check the version.
447
            if ($session instanceof Session) {
448
                $session->setId(uniqid());
449
            }
450
451
            $client->getCookieJar()->set(new Cookie($options['name'], $session->getId()));
452
453
            /** @var $user UserInterface */
454
            foreach ($this->firewallLogins as $firewallName => $user) {
455
                $token = $this->createUserToken($user, $firewallName);
456
457
                // BC: security.token_storage is available on Symfony 2.6+
458
                // see http://symfony.com/blog/new-in-symfony-2-6-security-component-improvements
459
                if ($client->getContainer()->has('security.token_storage')) {
460
                    $tokenStorage = $client->getContainer()->get('security.token_storage');
461 2
                } else {
462
                    // This block will never be reached with Symfony 2.6+
463 2
                    // @codeCoverageIgnoreStart
464 2
                    $tokenStorage = $client->getContainer()->get('security.context');
465 2
                    // @codeCoverageIgnoreEnd
466 2
                }
467 2
468 2
                $tokenStorage->setToken($token);
469
                $session->set('_security_'.$firewallName, serialize($token));
470
            }
471
472
            $session->save();
473
        }
474
475
        return $client;
476
    }
477
478
    /**
479
     * Create User Token.
480 1
     *
481
     * Factory method for creating a User Token object for the firewall based on
482 1
     * the user object provided. By default it will be a Username/Password
483
     * Token based on the user's credentials, but may be overridden for custom
484
     * tokens in your applications.
485
     *
486
     * @param UserInterface $user         The user object to base the token off of
487
     * @param string        $firewallName name of the firewall provider to use
488
     *
489
     * @return TokenInterface The token to be used in the security context
490
     */
491
    protected function createUserToken(UserInterface $user, $firewallName)
492 5
    {
493
        return new UsernamePasswordToken(
494 5
            $user,
495 5
            null,
496 5
            $firewallName,
497
            $user->getRoles()
498
        );
499
    }
500
501
    /**
502
     * Extracts the location from the given route.
503
     *
504
     * @param string $route    The name of the route
505
     * @param array  $params   Set of parameters
506
     * @param int    $absolute
507
     *
508
     * @return string
509
     */
510 1
    protected function getUrl($route, $params = array(), $absolute = UrlGeneratorInterface::ABSOLUTE_PATH)
511
    {
512 1
        return $this->getContainer()->get('router')->generate($route, $params, $absolute);
513 1
    }
514
515 1
    /**
516 1
     * Checks the success state of a response.
517 1
     *
518 1
     * @param Response $response Response object
519
     * @param bool     $success  to define whether the response is expected to be successful
520 1
     * @param string   $type
521
     */
522
    public function isSuccessful(Response $response, $success = true, $type = 'text/html')
523
    {
524
        $this->getContainer()->get('liip_functional_test.http_assertions')
525
            ->isSuccessful($response, $success, $type);
526
    }
527
528
    /**
529
     * Executes a request on the given url and returns the response contents.
530
     *
531
     * This method also asserts the request was successful.
532
     *
533
     * @param string $path           path of the requested page
534
     * @param string $method         The HTTP method to use, defaults to GET
535 1
     * @param bool   $authentication Whether to use authentication, defaults to false
536
     * @param bool   $success        to define whether the response is expected to be successful
537 1
     *
538 1
     * @return string
539
     */
540 1
    public function fetchContent($path, $method = 'GET', $authentication = false, $success = true)
541
    {
542 1
        $client = $this->makeClient($authentication);
543
        $client->request($method, $path);
544
545
        $content = $client->getResponse()->getContent();
546
        if (is_bool($success)) {
547
            $this->isSuccessful($client->getResponse(), $success);
548
        }
549
550
        return $content;
551 2
    }
552
553 2
    /**
554
     * Executes a request on the given url and returns a Crawler object.
555 2
     *
556
     * This method also asserts the request was successful.
557
     *
558
     * @param string $path           path of the requested page
559
     * @param string $method         The HTTP method to use, defaults to GET
560
     * @param bool   $authentication Whether to use authentication, defaults to false
561
     * @param bool   $success        Whether the response is expected to be successful
562
     *
563
     * @return Crawler
564
     */
565
    public function fetchCrawler($path, $method = 'GET', $authentication = false, $success = true)
566 9
    {
567
        $client = $this->makeClient($authentication);
568 9
        $crawler = $client->request($method, $path);
569 9
570 9
        $this->isSuccessful($client->getResponse(), $success);
571
572
        return $crawler;
573
    }
574
575
    /**
576
     * @param UserInterface $user
577
     * @param string        $firewallName
578
     *
579 2
     * @return WebTestCase
580
     */
581 2
    public function loginAs(UserInterface $user, $firewallName)
582 2
    {
583 1
        $this->firewallLogins[$firewallName] = $user;
584
585
        return $this;
586
    }
587
588
    /**
589
     * Asserts that the HTTP response code of the last request performed by
590
     * $client matches the expected code. If not, raises an error with more
591
     * information.
592
     *
593
     * @param $expectedStatusCode
594
     * @param Client $client
595
     */
596
    public function assertStatusCode($expectedStatusCode, Client $client)
597
    {
598
        $this->getContainer()->get('liip_functional_test.http_assertions')
599
            ->assertStatusCode($expectedStatusCode, $client);
600
    }
601
602
    /**
603
     * Assert that the last validation errors within $container match the
604
     * expected keys.
605
     *
606
     * @param array              $expected  A flat array of field names
607
     * @param ContainerInterface $container
608
     */
609
    public function assertValidationErrors(array $expected, ContainerInterface $container)
610
    {
611
        $this->getContainer()->get('liip_functional_test.http_assertions')
612
            ->assertValidationErrors($expected, $container);
613
    }
614
}
615