Completed
Pull Request — master (#222)
by Adamo
05:07
created

WebTestCase::createAuthenticationCredentials()   C

Complexity

Conditions 7
Paths 7

Size

Total Lines 31
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 13.125

Importance

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