Completed
Pull Request — master (#230)
by Adamo
06:13
created

WebTestCase::isDecorated()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 4
ccs 3
cts 3
cp 1
rs 10
cc 1
eloc 2
nc 1
nop 1
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\StreamOutput;
19
use Symfony\Component\DomCrawler\Crawler;
20
use Symfony\Component\BrowserKit\Cookie;
21
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
22
use Symfony\Component\Security\Core\User\UserInterface;
23
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
24
use Symfony\Component\DependencyInjection\ContainerInterface;
25
use Symfony\Component\HttpFoundation\Session\Session;
26
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
27
use Symfony\Bridge\Doctrine\ManagerRegistry;
28
use Symfony\Bundle\DoctrineFixturesBundle\Common\DataFixtures\Loader;
29
use Doctrine\Common\Persistence\ObjectManager;
30
use Doctrine\Common\DataFixtures\DependentFixtureInterface;
31
use Doctrine\Common\DataFixtures\Executor\AbstractExecutor;
32
use Doctrine\Common\DataFixtures\ProxyReferenceRepository;
33
use Doctrine\DBAL\Driver\PDOSqlite\Driver as SqliteDriver;
34
use Doctrine\DBAL\Platforms\MySqlPlatform;
35
use Doctrine\ORM\Tools\SchemaTool;
36
use Nelmio\Alice\Fixtures;
37
38
/**
39
 * @author Lea Haensenberger
40
 * @author Lukas Kahwe Smith <[email protected]>
41
 * @author Benjamin Eberlei <[email protected]>
42
 */
43
abstract class WebTestCase extends BaseWebTestCase
44
{
45
    protected $environment = 'test';
46
    protected $containers;
47
    protected $kernelDir;
48
    // 5 * 1024 * 1024 KB
49
    protected $maxMemory = 5242880;
50
51
    // RUN COMMAND
52
    protected $verbosityLevel;
53
    protected $decorated;
54
55
    /**
56
     * @var array
57
     */
58
    private $firewallLogins = array();
59
60
    /**
61
     * @var array
62
     */
63
    private static $cachedMetadatas = array();
64
65 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...
66
    {
67 1
        $dir = isset($_SERVER['KERNEL_DIR']) ? $_SERVER['KERNEL_DIR'] : self::getPhpUnitXmlDir();
68
69 1
        list($appname) = explode('\\', get_called_class());
70
71 1
        $class = $appname.'Kernel';
72 1
        $file = $dir.'/'.strtolower($appname).'/'.$class.'.php';
73 1
        if (!file_exists($file)) {
74 1
            return parent::getKernelClass();
75
        }
76
        require_once $file;
77
78
        return $class;
79
    }
80
81
    /**
82
     * Creates a mock object of a service identified by its id.
83
     *
84
     * @param string $id
85
     *
86
     * @return PHPUnit_Framework_MockObject_MockBuilder
87
     */
88
    protected function getServiceMockBuilder($id)
89
    {
90
        $service = $this->getContainer()->get($id);
91
        $class = get_class($service);
92
93
        return $this->getMockBuilder($class)->disableOriginalConstructor();
94
    }
95
96
    /**
97
     * Builds up the environment to run the given command.
98
     *
99
     * @param string $name
100
     * @param array  $params
101
     * @param bool   $reuseKernel
102
     *
103
     * @return string
104
     */
105 9
    protected function runCommand($name, array $params = array(), $reuseKernel = false)
106
    {
107 9
        array_unshift($params, $name);
108
109 9
        if (!$reuseKernel) {
110 9
            if (null !== static::$kernel) {
111 8
                static::$kernel->shutdown();
112 8
            }
113
114 9
            $kernel = static::$kernel = $this->createKernel(array('environment' => $this->environment));
115 9
            $kernel->boot();
116 9
        } else {
117 2
            $kernel = $this->getContainer()->get('kernel');
118
        }
119
120 9
        $application = new Application($kernel);
121 9
        $application->setAutoExit(false);
122
123 9
        $input = new ArrayInput($params);
124 9
        $input->setInteractive(false);
125
126 9
        $fp = fopen('php://temp/maxmemory:'.$this->maxMemory, 'r+');
127 9
        $output = new StreamOutput($fp, $this->getVerbosityLevel(), $this->getDecorated());
128
129 9
        $application->run($input, $output);
130
131 9
        rewind($fp);
132
133 9
        return stream_get_contents($fp);
134
    }
135
136
    /**
137
     * Retrieves the output verbosity level.
138
     *
139
     * @see Symfony\Component\Console\Output\OutputInterface for available levels
140
     *
141
     * @return integer
142
     * @throws \OutOfBoundsException If the set value isn't accepted
143
     */
144 9
    protected function getVerbosityLevel()
145
    {
146
        // If `null`, is not yet set
147 9
        if (null === $this->verbosityLevel) {
148
            // Set the global verbosity level that is set as NORMAL by the TreeBuilder in Configuration
149 4
            $level = strtoupper($this->getContainer()->getParameter('liip_functional_test.command_verbosity'));
150 4
            $verbosity = '\Symfony\Component\Console\Output\StreamOutput::VERBOSITY_'.$level;
151
152 4
            $this->verbosityLevel = constant($verbosity);
153 4
        }
154
155
        // If string, it is set by the developer, so check that the value is an accepted one
156 9
        if (is_string($this->verbosityLevel)) {
157 5
            $level = strtoupper($this->verbosityLevel);
158 5
            $verbosity = '\Symfony\Component\Console\Output\StreamOutput::VERBOSITY_'.$level;
159
160 5
            if (null === constant($verbosity)) {
161
                throw new \OutOfBoundsException(
162
                    'The set value "%s" for verbosityLevel is not valid. Accepted are: "quiet", "normal", "verbose", "very_verbose" and "debug".
163
                    ');
164
            }
165
166 5
            $this->verbosityLevel = constant($verbosity);
167 5
        }
168
169 9
        return $this->verbosityLevel;
170
    }
171
172 5
    public function setVerbosityLevel($level)
173
    {
174 5
        $this->verbosityLevel = $level;
175 5
    }
176
177
    /**
178
     * Retrieves the flag indicating if the output should be decorated or not.
179
     *
180
     * @return bool
181
     */
182 9
    protected function getDecorated()
183
    {
184 9
        if (null === $this->decorated) {
185
            // Set the global decoration flag that is set to `true` by the TreeBuilder in Configuration
186 3
            $this->decorated = $this->getContainer()->getParameter('liip_functional_test.command_decoration');
187 3
        }
188
189
        // Check the local decorated flag
190 9
        if (false === is_bool($this->decorated)) {
191
            throw new \OutOfBoundsException(
192
                sprintf('`WebTestCase::decorated` has to be `bool`. "%s" given.', gettype($this->decorated))
193
            );
194
        }
195
196 9
        return $this->decorated;
197
    }
198
199 6
    public function isDecorated($decorated)
200
    {
201 6
        $this->decorated = $decorated;
202 6
    }
203
204
    /**
205
     * Get an instance of the dependency injection container.
206
     * (this creates a kernel *without* parameters).
207
     *
208
     * @return ContainerInterface
209
     */
210 29
    protected function getContainer()
0 ignored issues
show
Coding Style introduced by
getContainer uses the super-global variable $_SERVER which is generally not recommended.

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

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

// Better
class Router
{
    private $host;

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

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

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

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

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

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

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

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

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

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

Loading history...
368 2
                if (!empty($metadatas)) {
369 2
                    $schemaTool->createSchema($metadatas);
370 2
                }
371 2
                $this->postFixtureSetup();
372
373 2
                $executor = new $executorClass($om);
374 2
                $executor->setReferenceRepository($referenceRepository);
375 2
            }
376 2
        }
377
378 2
        if (empty($executor)) {
379
            $purgerClass = 'Doctrine\\Common\\DataFixtures\\Purger\\'.$type.'Purger';
380
            if ('PHPCR' === $type) {
381
                $purger = new $purgerClass($om);
382
                $initManager = $container->has('doctrine_phpcr.initializer_manager')
383
                    ? $container->get('doctrine_phpcr.initializer_manager')
384
                    : null;
385
386
                $executor = new $executorClass($om, $purger, $initManager);
387
            } else {
388
                $purger = new $purgerClass();
389
                if (null !== $purgeMode) {
390
                    $purger->setPurgeMode($purgeMode);
391
                }
392
393
                $executor = new $executorClass($om, $purger);
394
            }
395
396
            $executor->setReferenceRepository($referenceRepository);
397
            $executor->purge();
398
        }
399
400 2
        $loader = $this->getFixtureLoader($container, $classNames);
401
402 2
        $executor->execute($loader->getFixtures(), true);
403
404 2
        if (isset($name) && isset($backup)) {
405 2
            $this->preReferenceSave($om, $executor, $backup);
406
407 2
            $executor->getReferenceRepository()->save($backup);
408 2
            copy($name, $backup);
409
410 2
            $this->postReferenceSave($om, $executor, $backup);
411 2
        }
412
413 2
        return $executor;
414
    }
415
416
    /**
417
     * @param array  $paths        Either symfony resource locators (@ BundleName/etc) or actual file paths
418
     * @param bool   $append
419
     * @param null   $omName
420
     * @param string $registryName
421
     *
422
     * @return array
423
     *
424
     * @throws \BadMethodCallException
425
     */
426 2
    public function loadFixtureFiles(array $paths = array(), $append = false, $omName = null, $registryName = 'doctrine')
427
    {
428 2
        if (!class_exists('Nelmio\Alice\Fixtures')) {
429
            throw new \BadMethodCallException('nelmio/alice should be installed to use this method.');
430
        }
431
432
        /** @var ManagerRegistry $registry */
433 2
        $registry = $this->getContainer()->get($registryName);
434 2
        $om = $registry->getManager($omName);
435
436 2
        if ($append === false) {
437
            //Clean database
438 2
            $connection = $om->getConnection();
439 2
            if ($registry->getName() === 'ORM' && $connection->getDatabasePlatform() instanceof MySqlPlatform) {
440
                $connection->query('SET FOREIGN_KEY_CHECKS=0');
441
            }
442
443 2
            $this->loadFixtures(array());
444
445 2
            if ($registry->getName() === 'ORM' && $connection->getDatabasePlatform() instanceof MySqlPlatform) {
446
                $connection->query('SET FOREIGN_KEY_CHECKS=1');
447
            }
448 2
        }
449
450 2
        $files = array();
451 2
        $kernel = $this->getContainer()->get('kernel');
452 2
        foreach ($paths as $path) {
453 2
            if ($path[0] !== '@' && file_exists($path) === true) {
454 1
                $files[] = $path;
455 1
                continue;
456
            }
457
458 1
            $files[] = $kernel->locateResource($path);
459 2
        }
460
461 2
        return Fixtures::load($files, $om);
462
    }
463
464
    /**
465
     * Callback function to be executed after Schema creation.
466
     * Use this to execute acl:init or other things necessary.
467
     */
468 2
    protected function postFixtureSetup()
469
    {
470 2
    }
471
472
    /**
473
     * Callback function to be executed after Schema restore.
474
     *
475
     * @return WebTestCase
476
     */
477 22
    protected function postFixtureRestore()
478
    {
479 22
    }
480
481
    /**
482
     * Callback function to be executed before Schema restore.
483
     *
484
     * @param ObjectManager            $manager             The object manager
485
     * @param ProxyReferenceRepository $referenceRepository The reference repository
486
     *
487
     * @return WebTestCase
488
     */
489 22
    protected function preFixtureRestore(ObjectManager $manager, ProxyReferenceRepository $referenceRepository)
0 ignored issues
show
Unused Code introduced by
The parameter $manager is not used and could be removed.

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

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

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

Loading history...
490
    {
491 22
    }
492
493
    /**
494
     * Callback function to be executed after save of references.
495
     *
496
     * @param ObjectManager    $manager        The object manager
497
     * @param AbstractExecutor $executor       Executor of the data fixtures
498
     * @param string           $backupFilePath Path of file used to backup the references of the data fixtures
499
     *
500
     * @return WebTestCase
501
     */
502 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...
503
    {
504 2
    }
505
506
    /**
507
     * Callback function to be executed before save of references.
508
     *
509
     * @param ObjectManager    $manager        The object manager
510
     * @param AbstractExecutor $executor       Executor of the data fixtures
511
     * @param string           $backupFilePath Path of file used to backup the references of the data fixtures
512
     *
513
     * @return WebTestCase
514
     */
515 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...
516
    {
517 2
    }
518
519
    /**
520
     * Retrieve Doctrine DataFixtures loader.
521
     *
522
     * @param ContainerInterface $container
523
     * @param array              $classNames
524
     *
525
     * @return Loader
526
     */
527 2
    protected function getFixtureLoader(ContainerInterface $container, array $classNames)
528
    {
529 2
        $loaderClass = class_exists('Symfony\Bridge\Doctrine\DataFixtures\ContainerAwareLoader')
530 2
            ? 'Symfony\Bridge\Doctrine\DataFixtures\ContainerAwareLoader'
531 2
            : (class_exists('Doctrine\Bundle\FixturesBundle\Common\DataFixtures\Loader')
532
                ? 'Doctrine\Bundle\FixturesBundle\Common\DataFixtures\Loader'
533 2
                : 'Symfony\Bundle\DoctrineFixturesBundle\Common\DataFixtures\Loader');
534
535 2
        $loader = new $loaderClass($container);
536
537 2
        foreach ($classNames as $className) {
538 1
            $this->loadFixtureClass($loader, $className);
539 2
        }
540
541 2
        return $loader;
542
    }
543
544
    /**
545
     * Load a data fixture class.
546
     *
547
     * @param Loader $loader
548
     * @param string $className
549
     */
550 1
    protected function loadFixtureClass($loader, $className)
551
    {
552 1
        $fixture = new $className();
553
554 1
        if ($loader->hasFixture($fixture)) {
555
            unset($fixture);
556
557
            return;
558
        }
559
560 1
        $loader->addFixture($fixture);
561
562 1
        if ($fixture instanceof DependentFixtureInterface) {
563
            foreach ($fixture->getDependencies() as $dependency) {
564
                $this->loadFixtureClass($loader, $dependency);
565
            }
566
        }
567 1
    }
568
569
    /**
570
     * Creates an instance of a lightweight Http client.
571
     *
572
     * If $authentication is set to 'true' it will use the content of
573
     * 'liip_functional_test.authentication' to log in.
574
     *
575
     * $params can be used to pass headers to the client, note that they have
576
     * to follow the naming format used in $_SERVER.
577
     * Example: 'HTTP_X_REQUESTED_WITH' instead of 'X-Requested-With'
578
     *
579
     * @param bool|array $authentication
580
     * @param array      $params
581
     *
582
     * @return Client
583
     */
584 24
    protected function makeClient($authentication = false, array $params = array())
585
    {
586 24
        if ($authentication) {
587 5
            if ($authentication === true) {
588 3
                $authentication = $this->getContainer()->getParameter('liip_functional_test.authentication');
589 3
            }
590
591 5
            $params = array_merge($params, array(
592 5
                'PHP_AUTH_USER' => $authentication['username'],
593 5
                'PHP_AUTH_PW' => $authentication['password'],
594 5
            ));
595 5
        }
596
597 24
        $client = static::createClient(array('environment' => $this->environment), $params);
598
599 24
        if ($this->firewallLogins) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->firewallLogins of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
600
            // has to be set otherwise "hasPreviousSession" in Request returns false.
601 1
            $options = $client->getContainer()->getParameter('session.storage.options');
602
603 1
            if (!$options || !isset($options['name'])) {
604
                throw new \InvalidArgumentException('Missing session.storage.options#name');
605
            }
606
607 1
            $session = $client->getContainer()->get('session');
608
            // Since the namespace of the session changed in symfony 2.1, instanceof can be used to check the version.
609 1
            if ($session instanceof Session) {
610 1
                $session->setId(uniqid());
611 1
            }
612
613 1
            $client->getCookieJar()->set(new Cookie($options['name'], $session->getId()));
614
615
            /** @var $user UserInterface */
616 1
            foreach ($this->firewallLogins as $firewallName => $user) {
617 1
                $token = $this->createUserToken($user, $firewallName);
618
619
                // BC: security.token_storage is available on Symfony 2.6+
620
                // see http://symfony.com/blog/new-in-symfony-2-6-security-component-improvements
621 1
                if ($client->getContainer()->has('security.token_storage')) {
622 1
                    $tokenStorage = $client->getContainer()->get('security.token_storage');
623 1
                } else {
624
                    // This block will never be reached with Symfony 2.5+
625
                    // @codeCoverageIgnoreStart
626
                    $tokenStorage = $client->getContainer()->get('security.context');
627
                    // @codeCoverageIgnoreEnd
628
                }
629
630 1
                $tokenStorage->setToken($token);
631 1
                $session->set('_security_'.$firewallName, serialize($token));
632 1
            }
633
634 1
            $session->save();
635 1
        }
636
637 24
        return $client;
638
    }
639
640
    /**
641
     * Create User Token.
642
     *
643
     * Factory method for creating a User Token object for the firewall based on
644
     * the user object provided. By default it will be a Username/Password
645
     * Token based on the user's credentials, but may be overridden for custom
646
     * tokens in your applications.
647
     *
648
     * @param UserInterface $user         The user object to base the token off of
649
     * @param string        $firewallName name of the firewall provider to use
650
     *
651
     * @return TokenInterface The token to be used in the security context
652
     */
653 1
    protected function createUserToken(UserInterface $user, $firewallName)
654
    {
655 1
        return new UsernamePasswordToken(
656 1
            $user,
657 1
            null,
658 1
            $firewallName,
659 1
            $user->getRoles()
660 1
        );
661
    }
662
663
    /**
664
     * Extracts the location from the given route.
665
     *
666
     * @param string $route    The name of the route
667
     * @param array  $params   Set of parameters
668
     * @param bool   $absolute
669
     *
670
     * @return string
671
     */
672 1
    protected function getUrl($route, $params = array(), $absolute = UrlGeneratorInterface::ABSOLUTE_PATH)
673
    {
674 1
        return $this->getContainer()->get('router')->generate($route, $params, $absolute);
675
    }
676
677
    /**
678
     * Checks the success state of a response.
679
     *
680
     * @param Response $response Response object
681
     * @param bool     $success  to define whether the response is expected to be successful
682
     * @param string   $type
683
     */
684 7
    public function isSuccessful($response, $success = true, $type = 'text/html')
685
    {
686
        try {
687 7
            $crawler = new Crawler();
688 7
            $crawler->addContent($response->getContent(), $type);
689 7
            if (!count($crawler->filter('title'))) {
690 2
                $title = '['.$response->getStatusCode().'] - '.$response->getContent();
691 2
            } else {
692 5
                $title = $crawler->filter('title')->text();
693
            }
694 7
        } catch (\Exception $e) {
695
            $title = $e->getMessage();
696
        }
697
698 7
        if ($success) {
699 5
            $this->assertTrue($response->isSuccessful(), 'The Response was not successful: '.$title);
700 5
        } else {
701 2
            $this->assertFalse($response->isSuccessful(), 'The Response was successful: '.$title);
702
        }
703 7
    }
704
705
    /**
706
     * Executes a request on the given url and returns the response contents.
707
     *
708
     * This method also asserts the request was successful.
709
     *
710
     * @param string $path           path of the requested page
711
     * @param string $method         The HTTP method to use, defaults to GET
712
     * @param bool   $authentication Whether to use authentication, defaults to false
713
     * @param bool   $success        to define whether the response is expected to be successful
714
     *
715
     * @return string
716
     */
717 1
    public function fetchContent($path, $method = 'GET', $authentication = false, $success = true)
718
    {
719 1
        $client = $this->makeClient($authentication);
720 1
        $client->request($method, $path);
721
722 1
        $content = $client->getResponse()->getContent();
723 1
        if (is_bool($success)) {
724 1
            $this->isSuccessful($client->getResponse(), $success);
725 1
        }
726
727 1
        return $content;
728
    }
729
730
    /**
731
     * Executes a request on the given url and returns a Crawler object.
732
     *
733
     * This method also asserts the request was successful.
734
     *
735
     * @param string $path           path of the requested page
736
     * @param string $method         The HTTP method to use, defaults to GET
737
     * @param bool   $authentication Whether to use authentication, defaults to false
738
     * @param bool   $success        Whether the response is expected to be successful
739
     *
740
     * @return Crawler
741
     */
742 1
    public function fetchCrawler($path, $method = 'GET', $authentication = false, $success = true)
743
    {
744 1
        $client = $this->makeClient($authentication);
745 1
        $crawler = $client->request($method, $path);
746
747 1
        $this->isSuccessful($client->getResponse(), $success);
748
749 1
        return $crawler;
750
    }
751
752
    /**
753
     * @param UserInterface $user
754
     *
755
     * @return WebTestCase
756
     */
757 1
    public function loginAs(UserInterface $user, $firewallName)
758
    {
759 1
        $this->firewallLogins[$firewallName] = $user;
760
761 1
        return $this;
762
    }
763
764
    /**
765
     * Asserts that the HTTP response code of the last request performed by
766
     * $client matches the expected code. If not, raises an error with more
767
     * information.
768
     *
769
     * @param $expectedStatusCode
770
     * @param Client $client
771
     */
772 13
    public function assertStatusCode($expectedStatusCode, Client $client)
773
    {
774 13
        $helpfulErrorMessage = null;
775
776 13
        if ($expectedStatusCode !== $client->getResponse()->getStatusCode()) {
777
            // Get a more useful error message, if available
778
            if ($exception = $client->getContainer()->get('liip_functional_test.exception_listener')->getLastException()) {
779
                $helpfulErrorMessage = $exception->getMessage();
780
            } elseif (count($validationErrors = $client->getContainer()->get('liip_functional_test.validator')->getLastErrors())) {
781
                $helpfulErrorMessage = "Unexpected validation errors:\n";
782
783
                foreach ($validationErrors as $error) {
784
                    $helpfulErrorMessage .= sprintf("+ %s: %s\n", $error->getPropertyPath(), $error->getMessage());
785
                }
786
            } else {
787
                $helpfulErrorMessage = substr($client->getResponse(), 0, 200);
788
            }
789
        }
790
791 13
        self::assertEquals($expectedStatusCode, $client->getResponse()->getStatusCode(), $helpfulErrorMessage);
792 13
    }
793
794
    /**
795
     * Assert that the last validation errors within $container match the
796
     * expected keys.
797
     *
798
     * @param array              $expected  A flat array of field names
799
     * @param ContainerInterface $container
800
     */
801 2
    public function assertValidationErrors(array $expected, ContainerInterface $container)
802
    {
803 2
        self::assertThat(
804 2
            $container->get('liip_functional_test.validator')->getLastErrors(),
805 2
            new ValidationErrorsConstraint($expected),
806
            'Validation errors should match.'
807 2
        );
808 1
    }
809
}
810