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