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

Test/WebTestCase.php (2 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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()
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()
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);
379 2
                $schemaTool->dropDatabase($name);
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)
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)
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)
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) {
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