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