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

WebTestCase::getVerbosityLevel()   B

Complexity

Conditions 4
Paths 6

Size

Total Lines 27
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 4.0378

Importance

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