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