Completed
Pull Request — master (#238)
by Adamo
06:17
created

WebTestCase::getVerbosityLevel()   B

Complexity

Conditions 4
Paths 6

Size

Total Lines 27
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 4.1054

Importance

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