Completed
Pull Request — master (#230)
by Adamo
07:08
created

WebTestCase::getDecorated()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 16
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 3.1406

Importance

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