Completed
Pull Request — master (#230)
by Adamo
20:12 queued 11:56
created

WebTestCase::isDecorated()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

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