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