Completed
Pull Request — master (#230)
by Adamo
09:53 queued 22s
created

WebTestCase::configureVerbosityForSymfony20301()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 25
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

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