Completed
Pull Request — master (#230)
by Adamo
51:32 queued 40:28
created

WebTestCase::isDecorated()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 4
ccs 3
cts 3
cp 1
rs 10
cc 1
eloc 2
nc 1
nop 1
crap 1
1
<?php
2
3
/*
4
 * This file is part of the Liip/FunctionalTestBundle
5
 *
6
 * (c) Lukas Kahwe Smith <[email protected]>
7
 *
8
 * This source file is subject to the MIT license that is bundled
9
 * with this source code in the file LICENSE.
10
 */
11
12
namespace Liip\FunctionalTestBundle\Test;
13
14
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase as BaseWebTestCase;
15
use Symfony\Bundle\FrameworkBundle\Console\Application;
16
use Symfony\Bundle\FrameworkBundle\Client;
17
use Symfony\Component\Console\Input\ArrayInput;
18
use Symfony\Component\Console\Output\StreamOutput;
19
use Symfony\Component\DomCrawler\Crawler;
20
use Symfony\Component\BrowserKit\Cookie;
21
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
22
use Symfony\Component\Security\Core\User\UserInterface;
23
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
24
use Symfony\Component\DependencyInjection\ContainerInterface;
25
use Symfony\Component\HttpFoundation\Session\Session;
26
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
27
use Symfony\Bridge\Doctrine\ManagerRegistry;
28
use Symfony\Bundle\DoctrineFixturesBundle\Common\DataFixtures\Loader;
29
use Doctrine\Common\Persistence\ObjectManager;
30
use Doctrine\Common\DataFixtures\DependentFixtureInterface;
31
use Doctrine\Common\DataFixtures\Executor\AbstractExecutor;
32
use Doctrine\Common\DataFixtures\ProxyReferenceRepository;
33
use Doctrine\DBAL\Driver\PDOSqlite\Driver as SqliteDriver;
34
use Doctrine\DBAL\Platforms\MySqlPlatform;
35
use Doctrine\ORM\Tools\SchemaTool;
36
use Nelmio\Alice\Fixtures;
37
38
/**
39
 * @author Lea Haensenberger
40
 * @author Lukas Kahwe Smith <[email protected]>
41
 * @author Benjamin Eberlei <[email protected]>
42
 */
43
abstract class WebTestCase extends BaseWebTestCase
44
{
45
    protected $environment = 'test';
46
    protected $containers;
47
    protected $kernelDir;
48
    // 5 * 1024 * 1024 KB
49
    protected $maxMemory = 5242880;
50
51
    // RUN COMMAND
52
    protected $verbosityLevel;
53
    protected $decorated;
54
55
    /**
56
     * @var array
57
     */
58
    private $firewallLogins = array();
59
60
    /**
61
     * @var array
62
     */
63
    private static $cachedMetadatas = array();
64
65 1
    protected static function getKernelClass()
0 ignored issues
show
Coding Style introduced by
getKernelClass uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

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