Completed
Pull Request — master (#239)
by Adamo
06:54
created

WebTestCase::tearDown()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 10
ccs 9
cts 9
cp 1
rs 9.4285
cc 1
eloc 8
nc 1
nop 0
crap 1
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\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\Bridge\Doctrine\ManagerRegistry;
31
use Symfony\Bundle\DoctrineFixturesBundle\Common\DataFixtures\Loader;
32
use Doctrine\Common\Persistence\ObjectManager;
33
use Doctrine\Common\DataFixtures\DependentFixtureInterface;
34
use Doctrine\Common\DataFixtures\Executor\AbstractExecutor;
35
use Doctrine\Common\DataFixtures\ProxyReferenceRepository;
36
use Doctrine\DBAL\Driver\PDOSqlite\Driver as SqliteDriver;
37
use Doctrine\DBAL\Platforms\MySqlPlatform;
38
use Doctrine\ORM\Tools\SchemaTool;
39
use Nelmio\Alice\Fixtures;
40
41
/**
42
 * @author Lea Haensenberger
43
 * @author Lukas Kahwe Smith <[email protected]>
44
 * @author Benjamin Eberlei <[email protected]>
45
 */
46
abstract class WebTestCase extends BaseWebTestCase
47
{
48
    /** @var string $environment */
49
    protected $environment = 'test';
50
51
    /** @var  array $containers */
52
    protected $containers;
53
54
    /** @var  string $kernelDir */
55
    protected $kernelDir;
56
57
    /** @var int $maxMemory 5 * 1024 * 1024 KB */
58
    protected $maxMemory = 5242880;
59
60
    /** @var Client $localClient */
61
    private $localClient;
62
63
    /** @var  string $localContent */
64
    private $localContent;
65
66
    /** @var  Crawler $localCrawler */
67
    private $localCrawler;
68
69
    /** @var \Symfony\Component\HttpKernel\KernelInterface $kernel */
70
    private $localKernel;
71
72
    /** @var  int $verbosityLevel Used by runCommand() */
73
    protected $verbosityLevel;
74
75
    /** @var  bool $decorated Used by runCommand() */
76
    protected $decorated;
77
78
    /**
79
     * @var array
80
     */
81
    private $firewallLogins = array();
82
83
    /**
84
     * @var array
85
     */
86
    private static $cachedMetadatas = array();
87
88 2
    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...
89
    {
90 1
        $dir = isset($_SERVER['KERNEL_DIR']) ? $_SERVER['KERNEL_DIR'] : self::getPhpUnitXmlDir();
91
92 1
        list($appname) = explode('\\', get_called_class());
93
94 1
        $class = $appname.'Kernel';
95 2
        $file = $dir.'/'.strtolower($appname).'/'.$class.'.php';
96 1
        if (!file_exists($file)) {
97 1
            return parent::getKernelClass();
98
        }
99
        require_once $file;
100
101
        return $class;
102
    }
103
104
    /**
105
     * Creates a mock object of a service identified by its id.
106
     *
107
     * @param string $id
108
     *
109
     * @return PHPUnit_Framework_MockObject_MockBuilder
110
     */
111
    protected function getServiceMockBuilder($id)
112
    {
113
        $service = $this->getContainer()->get($id);
114
        $class = get_class($service);
115
116
        return $this->getMockBuilder($class)->disableOriginalConstructor();
117
    }
118
119
    /**
120
     * Builds up the environment to run the given command.
121
     *
122
     * @param string $name
123
     * @param array  $params
124
     * @param bool   $reuseKernel
125
     *
126
     * @return string
127
     */
128 10
    protected function runCommand($name, array $params = array(), $reuseKernel = false)
129
    {
130 10
        array_unshift($params, $name);
131
132 10
        if (!$reuseKernel) {
133 10
            if (null !== static::$kernel) {
134 8
                static::$kernel->shutdown();
135 8
            }
136
137 10
            $this->localKernel = static::$kernel = $this->createKernel(array('environment' => $this->environment));
138 10
            $this->localKernel->boot();
139 10
        } else {
140 2
            $this->localKernel = $this->getContainer()->get('kernel');
141
        }
142
143 10
        $application = new Application($this->localKernel);
144 10
        $application->setAutoExit(false);
145
146
        // @codeCoverageIgnoreStart
147
        if ('20301' === Kernel::VERSION_ID) {
148
            $params = $this->configureVerbosityForSymfony20301($params);
149
        }
150
        // @codeCoverageIgnoreEnd
151
152 10
        $input = new ArrayInput($params);
153 10
        $input->setInteractive(false);
154
155 10
        $fp = fopen('php://temp/maxmemory:'.$this->maxMemory, 'r+');
156 10
        $output = new StreamOutput($fp, $this->getVerbosityLevel(), $this->getDecorated());
157
158 10
        $application->run($input, $output);
159
160 10
        rewind($fp);
161
162 10
        return stream_get_contents($fp);
163
    }
164
165
    /**
166
     * Retrieves the output verbosity level.
167
     *
168
     * @see Symfony\Component\Console\Output\OutputInterface for available levels
169
     *
170
     * @return int
171
     *
172
     * @throws \OutOfBoundsException If the set value isn't accepted
173
     */
174 10
    protected function getVerbosityLevel()
175
    {
176
        // If `null`, is not yet set
177 10
        if (null === $this->verbosityLevel) {
178
            // Set the global verbosity level that is set as NORMAL by the TreeBuilder in Configuration
179 5
            $level = strtoupper($this->getContainer()->getParameter('liip_functional_test.command_verbosity'));
180 5
            $verbosity = '\Symfony\Component\Console\Output\StreamOutput::VERBOSITY_'.$level;
181
182 5
            $this->verbosityLevel = constant($verbosity);
183 5
        }
184
185
        // If string, it is set by the developer, so check that the value is an accepted one
186 10
        if (is_string($this->verbosityLevel)) {
187 5
            $level = strtoupper($this->verbosityLevel);
188 5
            $verbosity = '\Symfony\Component\Console\Output\StreamOutput::VERBOSITY_'.$level;
189
190 5
            if (null === constant($verbosity)) {
191
                throw new \OutOfBoundsException(
192
                    'The set value "%s" for verbosityLevel is not valid. Accepted are: "quiet", "normal", "verbose", "very_verbose" and "debug".
193
                    ');
194
            }
195
196 5
            $this->verbosityLevel = constant($verbosity);
197 5
        }
198
199 10
        return $this->verbosityLevel;
200
    }
201
202
    /**
203
     * In Symfony 2.3.1 the verbosity level has to be set through {Symfony\Component\Console\Input\ArrayInput} and not
204
     * in {Symfony\Component\Console\Output\OutputInterface}.
205
     *
206
     * This method builds $params to be passed to {Symfony\Component\Console\Input\ArrayInput}.
207
     *
208
     * @codeCoverageIgnore
209
     *
210
     * @param array $params
211
     *
212
     * @return array
213
     */
214
    private function configureVerbosityForSymfony20301(array $params)
215
    {
216
        switch ($this->getVerbosityLevel()) {
217
            case OutputInterface::VERBOSITY_QUIET:
218
                $params['-q'] = '-q';
219
                break;
220
221
            case OutputInterface::VERBOSITY_VERBOSE:
222
                $params['-v'] = '';
223
                break;
224
225
            case OutputInterface::VERBOSITY_VERY_VERBOSE:
226
                $params['-vv'] = '';
227
                break;
228
229
            case OutputInterface::VERBOSITY_DEBUG:
230
                $params['-vvv'] = '';
231
                break;
232
        }
233
234
        return $params;
235
    }
236
237
    /**
238
     * Set the verbosity level to use when using runCommand().
239
     *
240
     * @param $level
241
     */
242 5
    public function setVerbosityLevel($level)
243
    {
244 5
        $this->verbosityLevel = $level;
245 5
    }
246
247
    /**
248
     * Retrieves the flag indicating if the output should be decorated or not.
249
     *
250
     * @return bool
251
     */
252 10
    protected function getDecorated()
253
    {
254 10
        if (null === $this->decorated) {
255
            // Set the global decoration flag that is set to `true` by the TreeBuilder in Configuration
256 4
            $this->decorated = $this->getContainer()->getParameter('liip_functional_test.command_decoration');
257 4
        }
258
259
        // Check the local decorated flag
260 10
        if (false === is_bool($this->decorated)) {
261
            throw new \OutOfBoundsException(
262
                sprintf('`WebTestCase::decorated` has to be `bool`. "%s" given.', gettype($this->decorated))
263
            );
264
        }
265
266 10
        return $this->decorated;
267
    }
268
269
    /**
270
     * @param $decorated
271
     */
272 6
    public function isDecorated($decorated)
273
    {
274 6
        $this->decorated = $decorated;
275 6
    }
276
277
    /**
278
     * Get an instance of the dependency injection container.
279
     * (this creates a kernel *without* parameters).
280
     *
281
     * @return ContainerInterface
282
     */
283 27
    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...
284
    {
285 27
        if (!empty($this->kernelDir)) {
286
            $tmpKernelDir = isset($_SERVER['KERNEL_DIR']) ? $_SERVER['KERNEL_DIR'] : null;
287
            $_SERVER['KERNEL_DIR'] = getcwd().$this->kernelDir;
288
        }
289
290 27
        $cacheKey = $this->kernelDir.'|'.$this->environment;
291 27
        if (empty($this->containers[$cacheKey])) {
292
            $options = array(
293 27
                'environment' => $this->environment,
294 27
            );
295 27
            $this->localKernel = $this->createKernel($options);
296 27
            $this->localKernel->boot();
297
298 27
            $this->containers[$cacheKey] = $this->localKernel->getContainer();
299 27
        }
300
301 27
        if (isset($tmpKernelDir)) {
302
            $_SERVER['KERNEL_DIR'] = $tmpKernelDir;
303
        }
304
305 27
        return $this->containers[$cacheKey];
306
    }
307
308
    /**
309
     * This function finds the time when the data blocks of a class definition
310
     * file were being written to, that is, the time when the content of the
311
     * file was changed.
312
     *
313
     * @param string $class The fully qualified class name of the fixture class to
314
     *                      check modification date on.
315
     *
316
     * @return \DateTime|null
317
     */
318
    protected function getFixtureLastModified($class)
319
    {
320
        $lastModifiedDateTime = null;
321
322
        $reflClass = new \ReflectionClass($class);
323
        $classFileName = $reflClass->getFileName();
324
325
        if (file_exists($classFileName)) {
326
            $lastModifiedDateTime = new \DateTime();
327
            $lastModifiedDateTime->setTimestamp(filemtime($classFileName));
328
        }
329
330
        return $lastModifiedDateTime;
331
    }
332
333
    /**
334
     * Determine if the Fixtures that define a database backup have been
335
     * modified since the backup was made.
336
     *
337
     * @param array  $classNames The fixture classnames to check
338
     * @param string $backup     The fixture backup SQLite database file path
339
     *
340
     * @return bool TRUE if the backup was made since the modifications to the
341
     *              fixtures; FALSE otherwise
342
     */
343 1
    protected function isBackupUpToDate(array $classNames, $backup)
344
    {
345 1
        $backupLastModifiedDateTime = new \DateTime();
346 1
        $backupLastModifiedDateTime->setTimestamp(filemtime($backup));
347
348 1
        foreach ($classNames as &$className) {
349
            $fixtureLastModifiedDateTime = $this->getFixtureLastModified($className);
350
            if ($backupLastModifiedDateTime < $fixtureLastModifiedDateTime) {
351
                return false;
352
            }
353 1
        }
354
355 1
        return true;
356
    }
357
358
    /**
359
     * Set the database to the provided fixtures.
360
     *
361
     * Drops the current database and then loads fixtures using the specified
362
     * classes. The parameter is a list of fully qualified class names of
363
     * classes that implement Doctrine\Common\DataFixtures\FixtureInterface
364
     * so that they can be loaded by the DataFixtures Loader::addFixture
365
     *
366
     * When using SQLite this method will automatically make a copy of the
367
     * loaded schema and fixtures which will be restored automatically in
368
     * case the same fixture classes are to be loaded again. Caveat: changes
369
     * to references and/or identities may go undetected.
370
     *
371
     * Depends on the doctrine data-fixtures library being available in the
372
     * class path.
373
     *
374
     * @param array  $classNames   List of fully qualified class names of fixtures to load
375
     * @param string $omName       The name of object manager to use
376
     * @param string $registryName The service id of manager registry to use
377
     * @param int    $purgeMode    Sets the ORM purge mode
378
     *
379
     * @return null|AbstractExecutor
380
     */
381 19
    protected function loadFixtures(array $classNames, $omName = null, $registryName = 'doctrine', $purgeMode = null)
382
    {
383 19
        $container = $this->getContainer();
384
        /** @var ManagerRegistry $registry */
385 19
        $registry = $container->get($registryName);
386 19
        $om = $registry->getManager($omName);
387 19
        $type = $registry->getName();
388
389 19
        $executorClass = 'PHPCR' === $type && class_exists('Doctrine\Bundle\PHPCRBundle\DataFixtures\PHPCRExecutor')
390 19
            ? 'Doctrine\Bundle\PHPCRBundle\DataFixtures\PHPCRExecutor'
391 19
            : 'Doctrine\\Common\\DataFixtures\\Executor\\'.$type.'Executor';
392 19
        $referenceRepository = new ProxyReferenceRepository($om);
393 19
        $cacheDriver = $om->getMetadataFactory()->getCacheDriver();
394
395 19
        if ($cacheDriver) {
396 19
            $cacheDriver->deleteAll();
397 19
        }
398
399 19
        if ('ORM' === $type) {
400 19
            $connection = $om->getConnection();
401 19
            if ($connection->getDriver() instanceof SqliteDriver) {
402 19
                $params = $connection->getParams();
403 19
                if (isset($params['master'])) {
404
                    $params = $params['master'];
405
                }
406
407 19
                $name = isset($params['path']) ? $params['path'] : (isset($params['dbname']) ? $params['dbname'] : false);
408 19
                if (!$name) {
409
                    throw new \InvalidArgumentException("Connection does not contain a 'path' or 'dbname' parameter and cannot be dropped.");
410
                }
411
412 19
                if (!isset(self::$cachedMetadatas[$omName])) {
413 19
                    self::$cachedMetadatas[$omName] = $om->getMetadataFactory()->getAllMetadata();
414
                    usort(self::$cachedMetadatas[$omName], function ($a, $b) { return strcmp($a->name, $b->name); });
415 19
                }
416 19
                $metadatas = self::$cachedMetadatas[$omName];
417
418 19
                if ($container->getParameter('liip_functional_test.cache_sqlite_db')) {
419 3
                    $backup = $container->getParameter('kernel.cache_dir').'/test_'.md5(serialize($metadatas).serialize($classNames)).'.db';
420 3
                    if (file_exists($backup) && file_exists($backup.'.ser') && $this->isBackupUpToDate($classNames, $backup)) {
421 1
                        $om->flush();
422 1
                        $om->clear();
423
424 1
                        $this->preFixtureRestore($om, $referenceRepository);
425
426 1
                        copy($backup, $name);
427
428 1
                        $executor = new $executorClass($om);
429 1
                        $executor->setReferenceRepository($referenceRepository);
430 1
                        $executor->getReferenceRepository()->load($backup);
431
432 1
                        $this->postFixtureRestore();
433
434 1
                        return $executor;
435
                    }
436 2
                }
437
438
                // TODO: handle case when using persistent connections. Fail loudly?
439 18
                $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...
440 18
                $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...
441 18
                if (!empty($metadatas)) {
442 18
                    $schemaTool->createSchema($metadatas);
443 18
                }
444 18
                $this->postFixtureSetup();
445
446 18
                $executor = new $executorClass($om);
447 18
                $executor->setReferenceRepository($referenceRepository);
448 18
            }
449 18
        }
450
451 18
        if (empty($executor)) {
452
            $purgerClass = 'Doctrine\\Common\\DataFixtures\\Purger\\'.$type.'Purger';
453
            if ('PHPCR' === $type) {
454
                $purger = new $purgerClass($om);
455
                $initManager = $container->has('doctrine_phpcr.initializer_manager')
456
                    ? $container->get('doctrine_phpcr.initializer_manager')
457
                    : null;
458
459
                $executor = new $executorClass($om, $purger, $initManager);
460
            } else {
461
                $purger = new $purgerClass();
462
                if (null !== $purgeMode) {
463
                    $purger->setPurgeMode($purgeMode);
464
                }
465
466
                $executor = new $executorClass($om, $purger);
467
            }
468
469
            $executor->setReferenceRepository($referenceRepository);
470
            $executor->purge();
471
        }
472
473 18
        $loader = $this->getFixtureLoader($container, $classNames);
474
475 18
        $executor->execute($loader->getFixtures(), true);
476
477 18
        if (isset($name) && isset($backup)) {
478 2
            $this->preReferenceSave($om, $executor, $backup);
479
480 2
            $executor->getReferenceRepository()->save($backup);
481 2
            copy($name, $backup);
482
483 2
            $this->postReferenceSave($om, $executor, $backup);
484 2
        }
485
486 18
        return $executor;
487
    }
488
489
    /**
490
     * @param array  $paths        Either symfony resource locators (@ BundleName/etc) or actual file paths
491
     * @param bool   $append
492
     * @param null   $omName
493
     * @param string $registryName
494
     *
495
     * @return array
496
     *
497
     * @throws \BadMethodCallException
498
     */
499 2
    public function loadFixtureFiles(array $paths = array(), $append = false, $omName = null, $registryName = 'doctrine')
500
    {
501 2
        if (!class_exists('Nelmio\Alice\Fixtures')) {
502
            throw new \BadMethodCallException('nelmio/alice should be installed to use this method.');
503
        }
504
505
        /** @var ManagerRegistry $registry */
506 2
        $registry = $this->getContainer()->get($registryName);
507 2
        $om = $registry->getManager($omName);
508
509 2
        if ($append === false) {
510
            //Clean database
511 2
            $connection = $om->getConnection();
512 2
            if ($registry->getName() === 'ORM' && $connection->getDatabasePlatform() instanceof MySqlPlatform) {
513
                $connection->query('SET FOREIGN_KEY_CHECKS=0');
514
            }
515
516 2
            $this->loadFixtures(array());
517
518 2
            if ($registry->getName() === 'ORM' && $connection->getDatabasePlatform() instanceof MySqlPlatform) {
519
                $connection->query('SET FOREIGN_KEY_CHECKS=1');
520
            }
521 2
        }
522
523 2
        $files = array();
524 2
        $kernel = $this->getContainer()->get('kernel');
525 2
        foreach ($paths as $path) {
526 2
            if ($path[0] !== '@' && file_exists($path) === true) {
527 1
                $files[] = $path;
528 1
                continue;
529
            }
530
531 1
            $files[] = $kernel->locateResource($path);
532 2
        }
533
534 2
        return Fixtures::load($files, $om);
535
    }
536
537
    /**
538
     * Callback function to be executed after Schema creation.
539
     * Use this to execute acl:init or other things necessary.
540
     */
541 18
    protected function postFixtureSetup()
542
    {
543 18
    }
544
545
    /**
546
     * Callback function to be executed after Schema restore.
547
     *
548
     * @return WebTestCase
549
     */
550 1
    protected function postFixtureRestore()
551
    {
552 1
    }
553
554
    /**
555
     * Callback function to be executed before Schema restore.
556
     *
557
     * @param ObjectManager            $manager             The object manager
558
     * @param ProxyReferenceRepository $referenceRepository The reference repository
559
     *
560
     * @return WebTestCase
561
     */
562 1
    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...
563
    {
564 1
    }
565
566
    /**
567
     * Callback function to be executed after save of references.
568
     *
569
     * @param ObjectManager    $manager        The object manager
570
     * @param AbstractExecutor $executor       Executor of the data fixtures
571
     * @param string           $backupFilePath Path of file used to backup the references of the data fixtures
572
     *
573
     * @return WebTestCase
574
     */
575 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...
576
    {
577 2
    }
578
579
    /**
580
     * Callback function to be executed before save of references.
581
     *
582
     * @param ObjectManager    $manager        The object manager
583
     * @param AbstractExecutor $executor       Executor of the data fixtures
584
     * @param string           $backupFilePath Path of file used to backup the references of the data fixtures
585
     *
586
     * @return WebTestCase
587
     */
588 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...
589
    {
590 2
    }
591
592
    /**
593
     * Retrieve Doctrine DataFixtures loader.
594
     *
595
     * @param ContainerInterface $container
596
     * @param array              $classNames
597
     *
598
     * @return Loader
599
     */
600 18
    protected function getFixtureLoader(ContainerInterface $container, array $classNames)
601
    {
602 18
        $loaderClass = class_exists('Symfony\Bridge\Doctrine\DataFixtures\ContainerAwareLoader')
603 18
            ? 'Symfony\Bridge\Doctrine\DataFixtures\ContainerAwareLoader'
604 18
            : (class_exists('Doctrine\Bundle\FixturesBundle\Common\DataFixtures\Loader')
605
                ? 'Doctrine\Bundle\FixturesBundle\Common\DataFixtures\Loader'
606 18
                : 'Symfony\Bundle\DoctrineFixturesBundle\Common\DataFixtures\Loader');
607
608 18
        $loader = new $loaderClass($container);
609
610 18
        foreach ($classNames as $className) {
611 3
            $this->loadFixtureClass($loader, $className);
612 18
        }
613
614 18
        return $loader;
615
    }
616
617
    /**
618
     * Load a data fixture class.
619
     *
620
     * @param Loader $loader
621
     * @param string $className
622
     */
623 3
    protected function loadFixtureClass($loader, $className)
624
    {
625 3
        $fixture = new $className();
626
627 3
        if ($loader->hasFixture($fixture)) {
628
            return;
629
        }
630
631 3
        $loader->addFixture($fixture);
632
633 3
        if ($fixture instanceof DependentFixtureInterface) {
634
            foreach ($fixture->getDependencies() as $dependency) {
635
                $this->loadFixtureClass($loader, $dependency);
636
            }
637
        }
638 3
    }
639
640
    /**
641
     * Creates an instance of a lightweight Http client.
642
     *
643
     * If $authentication is set to 'true' it will use the content of
644
     * 'liip_functional_test.authentication' to log in.
645
     *
646
     * $params can be used to pass headers to the client, note that they have
647
     * to follow the naming format used in $_SERVER.
648
     * Example: 'HTTP_X_REQUESTED_WITH' instead of 'X-Requested-With'
649
     *
650
     * @param bool|array $authentication
651
     * @param array      $params
652
     *
653
     * @return Client
654
     */
655 35
    protected function makeClient($authentication = false, array $params = array())
656
    {
657 35
        if ($authentication) {
658 2
            if ($authentication === true) {
659 1
                $authentication = $this->getContainer()->getParameter('liip_functional_test.authentication');
660 1
            }
661
662 2
            $params = array_merge($params, array(
663 2
                'PHP_AUTH_USER' => $authentication['username'],
664 2
                'PHP_AUTH_PW' => $authentication['password'],
665 2
            ));
666 2
        }
667
668 35
        $this->localClient = static::createClient(array('environment' => $this->environment), $params);
669
670 35
        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...
671
            // has to be set otherwise "hasPreviousSession" in Request returns false.
672 1
            $options = $this->localClient->getContainer()->getParameter('session.storage.options');
673
674 1
            if (!$options || !isset($options['name'])) {
675
                throw new \InvalidArgumentException('Missing session.storage.options#name');
676
            }
677
678 1
            $session = $this->localClient->getContainer()->get('session');
679
            // Since the namespace of the session changed in symfony 2.1, instanceof can be used to check the version.
680 1
            if ($session instanceof Session) {
681 1
                $session->setId(uniqid());
682 1
            }
683
684 1
            $this->localClient->getCookieJar()->set(new Cookie($options['name'], $session->getId()));
685
686
            /** @var $user UserInterface */
687 1
            foreach ($this->firewallLogins as $firewallName => $user) {
688 1
                $token = $this->createUserToken($user, $firewallName);
689
690
                // BC: security.token_storage is available on Symfony 2.6+
691
                // see http://symfony.com/blog/new-in-symfony-2-6-security-component-improvements
692 1
                if ($this->localClient->getContainer()->has('security.token_storage')) {
693 1
                    $tokenStorage = $this->localClient->getContainer()->get('security.token_storage');
694 1
                } else {
695
                    // This block will never be reached with Symfony 2.5+
696
                    // @codeCoverageIgnoreStart
697
                    $tokenStorage = $this->localClient->getContainer()->get('security.context');
698
                    // @codeCoverageIgnoreEnd
699
                }
700
701 1
                $tokenStorage->setToken($token);
702 1
                $session->set('_security_'.$firewallName, serialize($token));
703 1
            }
704
705 1
            $session->save();
706 1
        }
707
708 35
        return $this->localClient;
709
    }
710
711
    /**
712
     * Create User Token.
713
     *
714
     * Factory method for creating a User Token object for the firewall based on
715
     * the user object provided. By default it will be a Username/Password
716
     * Token based on the user's credentials, but may be overridden for custom
717
     * tokens in your applications.
718
     *
719
     * @param UserInterface $user         The user object to base the token off of
720
     * @param string        $firewallName name of the firewall provider to use
721
     *
722
     * @return TokenInterface The token to be used in the security context
723
     */
724 1
    protected function createUserToken(UserInterface $user, $firewallName)
725
    {
726 1
        return new UsernamePasswordToken(
727 1
            $user,
728 1
            null,
729 1
            $firewallName,
730 1
            $user->getRoles()
731 1
        );
732
    }
733
734
    /**
735
     * Extracts the location from the given route.
736
     *
737
     * @param string $route    The name of the route
738
     * @param array  $params   Set of parameters
739
     * @param bool   $absolute
740
     *
741
     * @return string
742
     */
743 1
    protected function getUrl($route, $params = array(), $absolute = UrlGeneratorInterface::ABSOLUTE_PATH)
744
    {
745 1
        return $this->getContainer()->get('router')->generate($route, $params, $absolute);
746
    }
747
748
    /**
749
     * Checks the success state of a response.
750
     *
751
     * @param Response $response Response object
752
     * @param bool     $success  to define whether the response is expected to be successful
753
     * @param string   $type
754
     */
755 5
    public function isSuccessful(Response $response, $success = true, $type = 'text/html')
756
    {
757
        try {
758 5
            $crawler = new Crawler();
759 5
            $crawler->addContent($response->getContent(), $type);
760 5
            if (!count($crawler->filter('title'))) {
761 1
                $title = '['.$response->getStatusCode().'] - '.$response->getContent();
762 1
            } else {
763 4
                $title = $crawler->filter('title')->text();
764
            }
765 5
        } catch (\Exception $e) {
766
            $title = $e->getMessage();
767
        }
768
769 5
        if ($success) {
770 4
            $this->assertTrue($response->isSuccessful(), 'The Response was not successful: '.$title);
771 4
        } else {
772 1
            $this->assertFalse($response->isSuccessful(), 'The Response was successful: '.$title);
773
        }
774 5
    }
775
776
    /**
777
     * Executes a request on the given url and returns the response contents.
778
     *
779
     * This method also asserts the request was successful.
780
     *
781
     * @param string $path           path of the requested page
782
     * @param string $method         The HTTP method to use, defaults to GET
783
     * @param bool   $authentication Whether to use authentication, defaults to false
784
     * @param bool   $success        to define whether the response is expected to be successful
785
     *
786
     * @return string
787
     */
788 1
    public function fetchContent($path, $method = 'GET', $authentication = false, $success = true)
789
    {
790 1
        $this->localClient = $this->makeClient($authentication);
791 1
        $this->localClient->request($method, $path);
792
793 1
        $this->localContent = $this->localClient->getResponse()->getContent();
794 1
        if (is_bool($success)) {
795 1
            $this->isSuccessful($this->localClient->getResponse(), $success);
796 1
        }
797
798 1
        return $this->localContent;
799
    }
800
801
    /**
802
     * Executes a request on the given url and returns a Crawler object.
803
     *
804
     * This method also asserts the request was successful.
805
     *
806
     * @param string $path           path of the requested page
807
     * @param string $method         The HTTP method to use, defaults to GET
808
     * @param bool   $authentication Whether to use authentication, defaults to false
809
     * @param bool   $success        Whether the response is expected to be successful
810
     *
811
     * @return Crawler
812
     */
813 1
    public function fetchCrawler($path, $method = 'GET', $authentication = false, $success = true)
814
    {
815 1
        $this->localClient = $this->makeClient($authentication);
816 1
        $this->localCrawler = $this->localClient->request($method, $path);
817
818 1
        $this->isSuccessful($this->localClient->getResponse(), $success);
819
820 1
        return $this->localCrawler;
821
    }
822
823
    /**
824
     * @param UserInterface $user
825
     *
826
     * @return WebTestCase
827
     */
828 1
    public function loginAs(UserInterface $user, $firewallName)
829
    {
830 1
        $this->firewallLogins[$firewallName] = $user;
831
832 1
        return $this;
833
    }
834
835
    /**
836
     * Asserts that the HTTP response code of the last request performed by
837
     * $client matches the expected code. If not, raises an error with more
838
     * information.
839
     *
840
     * @param $expectedStatusCode
841
     * @param Client $client
842
     */
843 9
    public function assertStatusCode($expectedStatusCode, Client $client)
844
    {
845 9
        $helpfulErrorMessage = null;
846
847 9
        if ($expectedStatusCode !== $client->getResponse()->getStatusCode()) {
848
            // Get a more useful error message, if available
849
            if ($exception = $client->getContainer()->get('liip_functional_test.exception_listener')->getLastException()) {
850
                $helpfulErrorMessage = $exception->getMessage();
851
            } elseif (count($validationErrors = $client->getContainer()->get('liip_functional_test.validator')->getLastErrors())) {
852
                $helpfulErrorMessage = "Unexpected validation errors:\n";
853
854
                foreach ($validationErrors as $error) {
855
                    $helpfulErrorMessage .= sprintf("+ %s: %s\n", $error->getPropertyPath(), $error->getMessage());
856
                }
857
            } else {
858
                $helpfulErrorMessage = substr($client->getResponse(), 0, 200);
859
            }
860
        }
861
862 9
        self::assertEquals($expectedStatusCode, $client->getResponse()->getStatusCode(), $helpfulErrorMessage);
863 9
    }
864
865
    /**
866
     * Assert that the last validation errors within $container match the
867
     * expected keys.
868
     *
869
     * @param array              $expected  A flat array of field names
870
     * @param ContainerInterface $container
871
     */
872 2
    public function assertValidationErrors(array $expected, ContainerInterface $container)
873
    {
874 2
        self::assertThat(
875 2
            $container->get('liip_functional_test.validator')->getLastErrors(),
876 2
            new ValidationErrorsConstraint($expected),
877
            'Validation errors should match.'
878 2
        );
879 1
    }
880
881
    /**
882
     * Tears down after the test ends.
883
     */
884 45
    public function tearDown()
885
    {
886 45
        self::$cachedMetadatas = array();
887 45
        unset($this->containers);
888 45
        unset($this->firewallLogins);
889 45
        unset($this->localClient);
890 45
        unset($this->localContent);
891 45
        unset($this->localCrawler);
892 45
        unset($this->localKernel);
893 45
    }
894
}
895