Completed
Pull Request — master (#230)
by Adamo
12:58 queued 04:29
created

WebTestCase::getDecorated()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 16
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 3.3332

Importance

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