Completed
Pull Request — master (#230)
by Adamo
20:26 queued 11:21
created

WebTestCase::getDecorated()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 16
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 3.3332

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 16
ccs 6
cts 9
cp 0.6667
rs 9.4285
cc 3
eloc 7
nc 4
nop 0
crap 3.3332
1
<?php
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\Security\Core\Authentication\Token\TokenInterface;
23
use Symfony\Component\Security\Core\User\UserInterface;
24
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
25
use Symfony\Component\DependencyInjection\ContainerInterface;
26
use Symfony\Component\HttpFoundation\Session\Session;
27
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
28
use Symfony\Bridge\Doctrine\ManagerRegistry;
29
use Symfony\Bundle\DoctrineFixturesBundle\Common\DataFixtures\Loader;
30
use Doctrine\Common\Persistence\ObjectManager;
31
use Doctrine\Common\DataFixtures\DependentFixtureInterface;
32
use Doctrine\Common\DataFixtures\Executor\AbstractExecutor;
33
use Doctrine\Common\DataFixtures\ProxyReferenceRepository;
34
use Doctrine\DBAL\Driver\PDOSqlite\Driver as SqliteDriver;
35
use Doctrine\DBAL\Platforms\MySqlPlatform;
36
use Doctrine\ORM\Tools\SchemaTool;
37
use Nelmio\Alice\Fixtures;
38
39
/**
40
 * @author Lea Haensenberger
41
 * @author Lukas Kahwe Smith <[email protected]>
42
 * @author Benjamin Eberlei <[email protected]>
43
 */
44
abstract class WebTestCase extends BaseWebTestCase
45
{
46
    protected $environment = 'test';
47
    protected $containers;
48
    protected $kernelDir;
49
    // 5 * 1024 * 1024 KB
50
    protected $maxMemory = 5242880;
51
52
    // RUN COMMAND
53
    protected $verbosityLevel;
54
    protected $decorated;
55
56
    /**
57
     * @var array
58
     */
59
    private $firewallLogins = array();
60
61
    /**
62
     * @var array
63
     */
64
    private static $cachedMetadatas = array();
65
66 1
    protected static function getKernelClass()
0 ignored issues
show
Coding Style introduced by
getKernelClass uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
67
    {
68 1
        $dir = isset($_SERVER['KERNEL_DIR']) ? $_SERVER['KERNEL_DIR'] : self::getPhpUnitXmlDir();
69
70 1
        list($appname) = explode('\\', get_called_class());
71
72 1
        $class = $appname.'Kernel';
73 1
        $file = $dir.'/'.strtolower($appname).'/'.$class.'.php';
74 1
        if (!file_exists($file)) {
75 1
            return parent::getKernelClass();
76
        }
77
        require_once $file;
78
79
        return $class;
80
    }
81
82
    /**
83
     * Creates a mock object of a service identified by its id.
84
     *
85
     * @param string $id
86
     *
87
     * @return PHPUnit_Framework_MockObject_MockBuilder
88
     */
89 1
    protected function getServiceMockBuilder($id)
90
    {
91
        $service = $this->getContainer()->get($id);
92
        $class = get_class($service);
93
94
        return $this->getMockBuilder($class)->disableOriginalConstructor();
95 1
    }
96
97
    /**
98
     * Builds up the environment to run the given command.
99
     *
100
     * @param string $name
101
     * @param array  $params
102
     * @param bool   $reuseKernel
103
     *
104
     * @return string
105
     */
106 9
    protected function runCommand($name, array $params = array(), $reuseKernel = false)
107
    {
108 9
        array_unshift($params, $name);
109
110 9
        if (!$reuseKernel) {
111 9
            if (null !== static::$kernel) {
112 8
                static::$kernel->shutdown();
113 8
            }
114
115 9
            $kernel = static::$kernel = $this->createKernel(array('environment' => $this->environment));
116 9
            $kernel->boot();
117 9
        } else {
118 2
            $kernel = $this->getContainer()->get('kernel');
119
        }
120
121 9
        $application = new Application($kernel);
122 9
        $application->setAutoExit(false);
123
124 9
        if (\Symfony\Component\HttpKernel\Kernel::VERSION_ID === '20301') {
125
            $params = $this->configureVerbosityForSymfony20301($params);
126
        }
127
128 9
        $input = new ArrayInput($params);
129 9
        $input->setInteractive(false);
130
131 9
        $fp = fopen('php://temp/maxmemory:'.$this->maxMemory, 'r+');
132 9
        $output = new StreamOutput($fp, $this->getVerbosityLevel(), $this->getDecorated());
133
134 9
        $application->run($input, $output);
135
136 9
        rewind($fp);
137
138 9
        return stream_get_contents($fp);
139
    }
140
141
    /**
142
     * Retrieves the output verbosity level.
143
     *
144
     * @see Symfony\Component\Console\Output\OutputInterface for available levels
145
     *
146
     * @return int
147
     *
148
     * @throws \OutOfBoundsException If the set value isn't accepted
149
     */
150 9
    protected function getVerbosityLevel()
151
    {
152
        // If `null`, is not yet set
153 9
        if (null === $this->verbosityLevel) {
154
            // Set the global verbosity level that is set as NORMAL by the TreeBuilder in Configuration
155 4
            $level = strtoupper($this->getContainer()->getParameter('liip_functional_test.command_verbosity'));
156 4
            $verbosity = '\Symfony\Component\Console\Output\StreamOutput::VERBOSITY_'.$level;
157
158 4
            $this->verbosityLevel = constant($verbosity);
159 4
        }
160
161
        // If string, it is set by the developer, so check that the value is an accepted one
162 9
        if (is_string($this->verbosityLevel)) {
163 5
            $level = strtoupper($this->verbosityLevel);
164 5
            $verbosity = '\Symfony\Component\Console\Output\StreamOutput::VERBOSITY_'.$level;
165
166 5
            if (null === constant($verbosity)) {
167
                throw new \OutOfBoundsException(
168
                    'The set value "%s" for verbosityLevel is not valid. Accepted are: "quiet", "normal", "verbose", "very_verbose" and "debug".
169
                    ');
170
            }
171
172 5
            $this->verbosityLevel = constant($verbosity);
173 5
        }
174
175 9
        return $this->verbosityLevel;
176
    }
177
178
    /**
179
     * In Symfony 2.3.1 the verbosity level has to be set through {Symfony\Component\Console\Input\ArrayInput} and not
180
     * in {Symfony\Component\Console\Output\OutputInterface}.
181
     *
182
     * This method builds $params to be passed to {Symfony\Component\Console\Input\ArrayInput}.
183
     *
184
     * @param array $params
185
     *
186
     * @return array
187
     */
188
    private function configureVerbosityForSymfony20301(array $params)
189
    {
190
        switch ($this->getVerbosityLevel()) {
191
            case OutputInterface::VERBOSITY_QUIET:
192
                $params['-q'] = '-q';
193
                break;
194
195
            case OutputInterface::VERBOSITY_VERBOSE:
196
                $params['-v'] = '';
197
                break;
198
199
            case OutputInterface::VERBOSITY_VERY_VERBOSE:
200
                $params['-vv'] = '';
201
                break;
202
203
            case OutputInterface::VERBOSITY_DEBUG:
204
                $params['-vvv'] = '';
205
                break;
206
        }
207
208
        return $params;
209
    }
210
211 5
    public function setVerbosityLevel($level)
212
    {
213 5
        $this->verbosityLevel = $level;
214 5
    }
215
216
    /**
217
     * Retrieves the flag indicating if the output should be decorated or not.
218
     *
219
     * @return bool
220
     */
221 9
    protected function getDecorated()
222
    {
223 9
        if (null === $this->decorated) {
224
            // Set the global decoration flag that is set to `true` by the TreeBuilder in Configuration
225 3
            $this->decorated = $this->getContainer()->getParameter('liip_functional_test.command_decoration');
226 3
        }
227
228
        // Check the local decorated flag
229 9
        if (false === is_bool($this->decorated)) {
230
            throw new \OutOfBoundsException(
231
                sprintf('`WebTestCase::decorated` has to be `bool`. "%s" given.', gettype($this->decorated))
232
            );
233
        }
234
235 9
        return $this->decorated;
236
    }
237
238 6
    public function isDecorated($decorated)
239
    {
240 6
        $this->decorated = $decorated;
241 6
    }
242
243
    /**
244
     * Get an instance of the dependency injection container.
245
     * (this creates a kernel *without* parameters).
246
     *
247
     * @return ContainerInterface
248
     */
249 29
    protected function getContainer()
0 ignored issues
show
Coding Style introduced by
getContainer uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
250
    {
251 29
        if (!empty($this->kernelDir)) {
252
            $tmpKernelDir = isset($_SERVER['KERNEL_DIR']) ? $_SERVER['KERNEL_DIR'] : null;
253
            $_SERVER['KERNEL_DIR'] = getcwd().$this->kernelDir;
254
        }
255
256 29
        $cacheKey = $this->kernelDir.'|'.$this->environment;
257 29
        if (empty($this->containers[$cacheKey])) {
258
            $options = array(
259 29
                'environment' => $this->environment,
260 29
            );
261 29
            $kernel = $this->createKernel($options);
262 29
            $kernel->boot();
263
264 29
            $this->containers[$cacheKey] = $kernel->getContainer();
265 29
        }
266
267 29
        if (isset($tmpKernelDir)) {
268
            $_SERVER['KERNEL_DIR'] = $tmpKernelDir;
269
        }
270
271 29
        return $this->containers[$cacheKey];
272
    }
273
274
    /**
275
     * This function finds the time when the data blocks of a class definition
276
     * file were being written to, that is, the time when the content of the
277
     * file was changed.
278
     *
279
     * @param string $class The fully qualified class name of the fixture class to
280
     *                      check modification date on.
281
     *
282
     * @return \DateTime|null
283
     */
284 4
    protected function getFixtureLastModified($class)
285
    {
286 4
        $lastModifiedDateTime = null;
287
288 4
        $reflClass = new \ReflectionClass($class);
289 4
        $classFileName = $reflClass->getFileName();
290
291 4
        if (file_exists($classFileName)) {
292 4
            $lastModifiedDateTime = new \DateTime();
293 4
            $lastModifiedDateTime->setTimestamp(filemtime($classFileName));
294 4
        }
295
296 4
        return $lastModifiedDateTime;
297
    }
298
299
    /**
300
     * Determine if the Fixtures that define a database backup have been
301
     * modified since the backup was made.
302
     *
303
     * @param array  $classNames The fixture classnames to check
304
     * @param string $backup     The fixture backup SQLite database file path
305
     *
306
     * @return bool TRUE if the backup was made since the modifications to the
307
     *              fixtures; FALSE otherwise
308
     */
309 22
    protected function isBackupUpToDate(array $classNames, $backup)
310
    {
311 22
        $backupLastModifiedDateTime = new \DateTime();
312 22
        $backupLastModifiedDateTime->setTimestamp(filemtime($backup));
313
314 22
        foreach ($classNames as &$className) {
315 4
            $fixtureLastModifiedDateTime = $this->getFixtureLastModified($className);
316 4
            if ($backupLastModifiedDateTime < $fixtureLastModifiedDateTime) {
317
                return false;
318
            }
319 22
        }
320
321 22
        return true;
322
    }
323
324
    /**
325
     * Set the database to the provided fixtures.
326
     *
327
     * Drops the current database and then loads fixtures using the specified
328
     * classes. The parameter is a list of fully qualified class names of
329
     * classes that implement Doctrine\Common\DataFixtures\FixtureInterface
330
     * so that they can be loaded by the DataFixtures Loader::addFixture
331
     *
332
     * When using SQLite this method will automatically make a copy of the
333
     * loaded schema and fixtures which will be restored automatically in
334
     * case the same fixture classes are to be loaded again. Caveat: changes
335
     * to references and/or identities may go undetected.
336
     *
337
     * Depends on the doctrine data-fixtures library being available in the
338
     * class path.
339
     *
340
     * @param array  $classNames   List of fully qualified class names of fixtures to load
341
     * @param string $omName       The name of object manager to use
342
     * @param string $registryName The service id of manager registry to use
343
     * @param int    $purgeMode    Sets the ORM purge mode
344
     *
345
     * @return null|AbstractExecutor
346
     */
347 24
    protected function loadFixtures(array $classNames, $omName = null, $registryName = 'doctrine', $purgeMode = null)
348
    {
349 24
        $container = $this->getContainer();
350
        /** @var ManagerRegistry $registry */
351 24
        $registry = $container->get($registryName);
352 24
        $om = $registry->getManager($omName);
353 24
        $type = $registry->getName();
354
355 24
        $executorClass = 'PHPCR' === $type && class_exists('Doctrine\Bundle\PHPCRBundle\DataFixtures\PHPCRExecutor')
356 24
            ? 'Doctrine\Bundle\PHPCRBundle\DataFixtures\PHPCRExecutor'
357 24
            : 'Doctrine\\Common\\DataFixtures\\Executor\\'.$type.'Executor';
358 24
        $referenceRepository = new ProxyReferenceRepository($om);
359 24
        $cacheDriver = $om->getMetadataFactory()->getCacheDriver();
360
361 24
        if ($cacheDriver) {
362 24
            $cacheDriver->deleteAll();
363 24
        }
364
365 24
        if ('ORM' === $type) {
366 24
            $connection = $om->getConnection();
367 24
            if ($connection->getDriver() instanceof SqliteDriver) {
368 24
                $params = $connection->getParams();
369 24
                if (isset($params['master'])) {
370
                    $params = $params['master'];
371
                }
372
373 24
                $name = isset($params['path']) ? $params['path'] : (isset($params['dbname']) ? $params['dbname'] : false);
374 24
                if (!$name) {
375
                    throw new \InvalidArgumentException("Connection does not contain a 'path' or 'dbname' parameter and cannot be dropped.");
376
                }
377
378 24
                if (!isset(self::$cachedMetadatas[$omName])) {
379 1
                    self::$cachedMetadatas[$omName] = $om->getMetadataFactory()->getAllMetadata();
380
                    usort(self::$cachedMetadatas[$omName], function ($a, $b) { return strcmp($a->name, $b->name); });
381 1
                }
382 24
                $metadatas = self::$cachedMetadatas[$omName];
383
384 24
                if ($container->getParameter('liip_functional_test.cache_sqlite_db')) {
385 24
                    $backup = $container->getParameter('kernel.cache_dir').'/test_'.md5(serialize($metadatas).serialize($classNames)).'.db';
386 24
                    if (file_exists($backup) && file_exists($backup.'.ser') && $this->isBackupUpToDate($classNames, $backup)) {
387 22
                        $om->flush();
388 22
                        $om->clear();
389
390 22
                        $this->preFixtureRestore($om, $referenceRepository);
391
392 22
                        copy($backup, $name);
393
394 22
                        $executor = new $executorClass($om);
395 22
                        $executor->setReferenceRepository($referenceRepository);
396 22
                        $executor->getReferenceRepository()->load($backup);
397
398 22
                        $this->postFixtureRestore();
399
400 22
                        return $executor;
401
                    }
402 2
                }
403
404
                // TODO: handle case when using persistent connections. Fail loudly?
405 2
                $schemaTool = new SchemaTool($om);
0 ignored issues
show
Compatibility introduced by
$om of type object<Doctrine\Common\Persistence\ObjectManager> is not a sub-type of object<Doctrine\ORM\EntityManagerInterface>. It seems like you assume a child interface of the interface Doctrine\Common\Persistence\ObjectManager to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
406 2
                $schemaTool->dropDatabase($name);
0 ignored issues
show
Unused Code introduced by
The call to SchemaTool::dropDatabase() has too many arguments starting with $name.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
407 2
                if (!empty($metadatas)) {
408 2
                    $schemaTool->createSchema($metadatas);
409 2
                }
410 2
                $this->postFixtureSetup();
411
412 2
                $executor = new $executorClass($om);
413 2
                $executor->setReferenceRepository($referenceRepository);
414 2
            }
415 2
        }
416
417 2
        if (empty($executor)) {
418
            $purgerClass = 'Doctrine\\Common\\DataFixtures\\Purger\\'.$type.'Purger';
419
            if ('PHPCR' === $type) {
420
                $purger = new $purgerClass($om);
421
                $initManager = $container->has('doctrine_phpcr.initializer_manager')
422
                    ? $container->get('doctrine_phpcr.initializer_manager')
423
                    : null;
424
425
                $executor = new $executorClass($om, $purger, $initManager);
426
            } else {
427
                $purger = new $purgerClass();
428
                if (null !== $purgeMode) {
429
                    $purger->setPurgeMode($purgeMode);
430
                }
431
432
                $executor = new $executorClass($om, $purger);
433
            }
434
435
            $executor->setReferenceRepository($referenceRepository);
436
            $executor->purge();
437
        }
438
439 2
        $loader = $this->getFixtureLoader($container, $classNames);
440
441 2
        $executor->execute($loader->getFixtures(), true);
442
443 2
        if (isset($name) && isset($backup)) {
444 2
            $this->preReferenceSave($om, $executor, $backup);
445
446 2
            $executor->getReferenceRepository()->save($backup);
447 2
            copy($name, $backup);
448
449 2
            $this->postReferenceSave($om, $executor, $backup);
450 2
        }
451
452 2
        return $executor;
453
    }
454
455
    /**
456
     * @param array  $paths        Either symfony resource locators (@ BundleName/etc) or actual file paths
457
     * @param bool   $append
458
     * @param null   $omName
459
     * @param string $registryName
460
     *
461
     * @return array
462
     *
463
     * @throws \BadMethodCallException
464
     */
465 2
    public function loadFixtureFiles(array $paths = array(), $append = false, $omName = null, $registryName = 'doctrine')
466
    {
467 2
        if (!class_exists('Nelmio\Alice\Fixtures')) {
468
            throw new \BadMethodCallException('nelmio/alice should be installed to use this method.');
469
        }
470
471
        /** @var ManagerRegistry $registry */
472 2
        $registry = $this->getContainer()->get($registryName);
473 2
        $om = $registry->getManager($omName);
474
475 2
        if ($append === false) {
476
            //Clean database
477 2
            $connection = $om->getConnection();
478 2
            if ($registry->getName() === 'ORM' && $connection->getDatabasePlatform() instanceof MySqlPlatform) {
479
                $connection->query('SET FOREIGN_KEY_CHECKS=0');
480
            }
481
482 2
            $this->loadFixtures(array());
483
484 2
            if ($registry->getName() === 'ORM' && $connection->getDatabasePlatform() instanceof MySqlPlatform) {
485
                $connection->query('SET FOREIGN_KEY_CHECKS=1');
486
            }
487 2
        }
488
489 2
        $files = array();
490 2
        $kernel = $this->getContainer()->get('kernel');
491 2
        foreach ($paths as $path) {
492 2
            if ($path[0] !== '@' && file_exists($path) === true) {
493 1
                $files[] = $path;
494 1
                continue;
495
            }
496
497 1
            $files[] = $kernel->locateResource($path);
498 2
        }
499
500 2
        return Fixtures::load($files, $om);
501
    }
502
503
    /**
504
     * Callback function to be executed after Schema creation.
505
     * Use this to execute acl:init or other things necessary.
506
     */
507 2
    protected function postFixtureSetup()
508
    {
509 2
    }
510
511
    /**
512
     * Callback function to be executed after Schema restore.
513
     *
514
     * @return WebTestCase
515
     */
516 22
    protected function postFixtureRestore()
517
    {
518 22
    }
519
520
    /**
521
     * Callback function to be executed before Schema restore.
522
     *
523
     * @param ObjectManager            $manager             The object manager
524
     * @param ProxyReferenceRepository $referenceRepository The reference repository
525
     *
526
     * @return WebTestCase
527
     */
528 22
    protected function preFixtureRestore(ObjectManager $manager, ProxyReferenceRepository $referenceRepository)
0 ignored issues
show
Unused Code introduced by
The parameter $manager is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $referenceRepository is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
529
    {
530 22
    }
531
532
    /**
533
     * Callback function to be executed after save of references.
534
     *
535
     * @param ObjectManager    $manager        The object manager
536
     * @param AbstractExecutor $executor       Executor of the data fixtures
537
     * @param string           $backupFilePath Path of file used to backup the references of the data fixtures
538
     *
539
     * @return WebTestCase
540
     */
541 2
    protected function postReferenceSave(ObjectManager $manager, AbstractExecutor $executor, $backupFilePath)
0 ignored issues
show
Unused Code introduced by
The parameter $manager is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $executor is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $backupFilePath is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
542
    {
543 2
    }
544
545
    /**
546
     * Callback function to be executed before save of references.
547
     *
548
     * @param ObjectManager    $manager        The object manager
549
     * @param AbstractExecutor $executor       Executor of the data fixtures
550
     * @param string           $backupFilePath Path of file used to backup the references of the data fixtures
551
     *
552
     * @return WebTestCase
553
     */
554 2
    protected function preReferenceSave(ObjectManager $manager, AbstractExecutor $executor, $backupFilePath)
0 ignored issues
show
Unused Code introduced by
The parameter $manager is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $executor is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $backupFilePath is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
555
    {
556 2
    }
557
558
    /**
559
     * Retrieve Doctrine DataFixtures loader.
560
     *
561
     * @param ContainerInterface $container
562
     * @param array              $classNames
563
     *
564
     * @return Loader
565
     */
566 2
    protected function getFixtureLoader(ContainerInterface $container, array $classNames)
567
    {
568 2
        $loaderClass = class_exists('Symfony\Bridge\Doctrine\DataFixtures\ContainerAwareLoader')
569 2
            ? 'Symfony\Bridge\Doctrine\DataFixtures\ContainerAwareLoader'
570 2
            : (class_exists('Doctrine\Bundle\FixturesBundle\Common\DataFixtures\Loader')
571
                ? 'Doctrine\Bundle\FixturesBundle\Common\DataFixtures\Loader'
572 2
                : 'Symfony\Bundle\DoctrineFixturesBundle\Common\DataFixtures\Loader');
573
574 2
        $loader = new $loaderClass($container);
575
576 2
        foreach ($classNames as $className) {
577 1
            $this->loadFixtureClass($loader, $className);
578 2
        }
579
580 2
        return $loader;
581
    }
582
583
    /**
584
     * Load a data fixture class.
585
     *
586
     * @param Loader $loader
587
     * @param string $className
588
     */
589 1
    protected function loadFixtureClass($loader, $className)
590
    {
591 1
        $fixture = new $className();
592
593 1
        if ($loader->hasFixture($fixture)) {
594
            unset($fixture);
595
596
            return;
597
        }
598
599 1
        $loader->addFixture($fixture);
600
601 1
        if ($fixture instanceof DependentFixtureInterface) {
602
            foreach ($fixture->getDependencies() as $dependency) {
603
                $this->loadFixtureClass($loader, $dependency);
604
            }
605
        }
606 1
    }
607
608
    /**
609
     * Creates an instance of a lightweight Http client.
610
     *
611
     * If $authentication is set to 'true' it will use the content of
612
     * 'liip_functional_test.authentication' to log in.
613
     *
614
     * $params can be used to pass headers to the client, note that they have
615
     * to follow the naming format used in $_SERVER.
616
     * Example: 'HTTP_X_REQUESTED_WITH' instead of 'X-Requested-With'
617
     *
618
     * @param bool|array $authentication
619
     * @param array      $params
620
     *
621
     * @return Client
622
     */
623 24
    protected function makeClient($authentication = false, array $params = array())
624
    {
625 24
        if ($authentication) {
626 5
            if ($authentication === true) {
627 3
                $authentication = $this->getContainer()->getParameter('liip_functional_test.authentication');
628 3
            }
629
630 5
            $params = array_merge($params, array(
631 5
                'PHP_AUTH_USER' => $authentication['username'],
632 5
                'PHP_AUTH_PW' => $authentication['password'],
633 5
            ));
634 5
        }
635
636 24
        $client = static::createClient(array('environment' => $this->environment), $params);
637
638 24
        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...
639
            // has to be set otherwise "hasPreviousSession" in Request returns false.
640 1
            $options = $client->getContainer()->getParameter('session.storage.options');
641
642 1
            if (!$options || !isset($options['name'])) {
643
                throw new \InvalidArgumentException('Missing session.storage.options#name');
644
            }
645
646 1
            $session = $client->getContainer()->get('session');
647
            // Since the namespace of the session changed in symfony 2.1, instanceof can be used to check the version.
648 1
            if ($session instanceof Session) {
649 1
                $session->setId(uniqid());
650 1
            }
651
652 1
            $client->getCookieJar()->set(new Cookie($options['name'], $session->getId()));
653
654
            /** @var $user UserInterface */
655 1
            foreach ($this->firewallLogins as $firewallName => $user) {
656 1
                $token = $this->createUserToken($user, $firewallName);
657
658
                // BC: security.token_storage is available on Symfony 2.6+
659
                // see http://symfony.com/blog/new-in-symfony-2-6-security-component-improvements
660 1
                if ($client->getContainer()->has('security.token_storage')) {
661 1
                    $tokenStorage = $client->getContainer()->get('security.token_storage');
662 1
                } else {
663
                    // This block will never be reached with Symfony 2.5+
664
                    // @codeCoverageIgnoreStart
665
                    $tokenStorage = $client->getContainer()->get('security.context');
666
                    // @codeCoverageIgnoreEnd
667
                }
668
669 1
                $tokenStorage->setToken($token);
670 1
                $session->set('_security_'.$firewallName, serialize($token));
671 1
            }
672
673 1
            $session->save();
674 1
        }
675
676 24
        return $client;
677
    }
678
679
    /**
680
     * Create User Token.
681
     *
682
     * Factory method for creating a User Token object for the firewall based on
683
     * the user object provided. By default it will be a Username/Password
684
     * Token based on the user's credentials, but may be overridden for custom
685
     * tokens in your applications.
686
     *
687
     * @param UserInterface $user         The user object to base the token off of
688
     * @param string        $firewallName name of the firewall provider to use
689
     *
690
     * @return TokenInterface The token to be used in the security context
691
     */
692 1
    protected function createUserToken(UserInterface $user, $firewallName)
693
    {
694 1
        return new UsernamePasswordToken(
695 1
            $user,
696 1
            null,
697 1
            $firewallName,
698 1
            $user->getRoles()
699 1
        );
700
    }
701
702
    /**
703
     * Extracts the location from the given route.
704
     *
705
     * @param string $route    The name of the route
706
     * @param array  $params   Set of parameters
707
     * @param bool   $absolute
708
     *
709
     * @return string
710
     */
711 1
    protected function getUrl($route, $params = array(), $absolute = UrlGeneratorInterface::ABSOLUTE_PATH)
712
    {
713 1
        return $this->getContainer()->get('router')->generate($route, $params, $absolute);
714
    }
715
716
    /**
717
     * Checks the success state of a response.
718
     *
719
     * @param Response $response Response object
720
     * @param bool     $success  to define whether the response is expected to be successful
721
     * @param string   $type
722
     */
723 7
    public function isSuccessful($response, $success = true, $type = 'text/html')
724
    {
725
        try {
726 7
            $crawler = new Crawler();
727 7
            $crawler->addContent($response->getContent(), $type);
728 7
            if (!count($crawler->filter('title'))) {
729 2
                $title = '['.$response->getStatusCode().'] - '.$response->getContent();
730 2
            } else {
731 5
                $title = $crawler->filter('title')->text();
732
            }
733 7
        } catch (\Exception $e) {
734
            $title = $e->getMessage();
735
        }
736
737 7
        if ($success) {
738 5
            $this->assertTrue($response->isSuccessful(), 'The Response was not successful: '.$title);
739 5
        } else {
740 2
            $this->assertFalse($response->isSuccessful(), 'The Response was successful: '.$title);
741
        }
742 7
    }
743
744
    /**
745
     * Executes a request on the given url and returns the response contents.
746
     *
747
     * This method also asserts the request was successful.
748
     *
749
     * @param string $path           path of the requested page
750
     * @param string $method         The HTTP method to use, defaults to GET
751
     * @param bool   $authentication Whether to use authentication, defaults to false
752
     * @param bool   $success        to define whether the response is expected to be successful
753
     *
754
     * @return string
755
     */
756 1
    public function fetchContent($path, $method = 'GET', $authentication = false, $success = true)
757
    {
758 1
        $client = $this->makeClient($authentication);
759 1
        $client->request($method, $path);
760
761 1
        $content = $client->getResponse()->getContent();
762 1
        if (is_bool($success)) {
763 1
            $this->isSuccessful($client->getResponse(), $success);
764 1
        }
765
766 1
        return $content;
767
    }
768
769
    /**
770
     * Executes a request on the given url and returns a Crawler object.
771
     *
772
     * This method also asserts the request was successful.
773
     *
774
     * @param string $path           path of the requested page
775
     * @param string $method         The HTTP method to use, defaults to GET
776
     * @param bool   $authentication Whether to use authentication, defaults to false
777
     * @param bool   $success        Whether the response is expected to be successful
778
     *
779
     * @return Crawler
780
     */
781 1
    public function fetchCrawler($path, $method = 'GET', $authentication = false, $success = true)
782
    {
783 1
        $client = $this->makeClient($authentication);
784 1
        $crawler = $client->request($method, $path);
785
786 1
        $this->isSuccessful($client->getResponse(), $success);
787
788 1
        return $crawler;
789
    }
790
791
    /**
792
     * @param UserInterface $user
793
     *
794
     * @return WebTestCase
795
     */
796 1
    public function loginAs(UserInterface $user, $firewallName)
797
    {
798 1
        $this->firewallLogins[$firewallName] = $user;
799
800 1
        return $this;
801
    }
802
803
    /**
804
     * Asserts that the HTTP response code of the last request performed by
805
     * $client matches the expected code. If not, raises an error with more
806
     * information.
807
     *
808
     * @param $expectedStatusCode
809
     * @param Client $client
810
     */
811 13
    public function assertStatusCode($expectedStatusCode, Client $client)
812
    {
813 13
        $helpfulErrorMessage = null;
814
815 13
        if ($expectedStatusCode !== $client->getResponse()->getStatusCode()) {
816
            // Get a more useful error message, if available
817
            if ($exception = $client->getContainer()->get('liip_functional_test.exception_listener')->getLastException()) {
818
                $helpfulErrorMessage = $exception->getMessage();
819
            } elseif (count($validationErrors = $client->getContainer()->get('liip_functional_test.validator')->getLastErrors())) {
820
                $helpfulErrorMessage = "Unexpected validation errors:\n";
821
822
                foreach ($validationErrors as $error) {
823
                    $helpfulErrorMessage .= sprintf("+ %s: %s\n", $error->getPropertyPath(), $error->getMessage());
824
                }
825
            } else {
826
                $helpfulErrorMessage = substr($client->getResponse(), 0, 200);
827
            }
828
        }
829
830 13
        self::assertEquals($expectedStatusCode, $client->getResponse()->getStatusCode(), $helpfulErrorMessage);
831 13
    }
832
833
    /**
834
     * Assert that the last validation errors within $container match the
835
     * expected keys.
836
     *
837
     * @param array              $expected  A flat array of field names
838
     * @param ContainerInterface $container
839
     */
840 2
    public function assertValidationErrors(array $expected, ContainerInterface $container)
841
    {
842 2
        self::assertThat(
843 2
            $container->get('liip_functional_test.validator')->getLastErrors(),
844 2
            new ValidationErrorsConstraint($expected),
845
            'Validation errors should match.'
846 2
        );
847 1
    }
848
}
849