Completed
Pull Request — master (#230)
by Adamo
09:28
created

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