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

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
915 45
        unset($this->containers);
916 45
        unset($this->firewallLogins);
917 45
        unset($this->localClient);
918 45
        unset($this->localContent);
919 45
        unset($this->localCrawler);
920 45
        unset($this->localKernel);
921 45
    }
922
}
923