Completed
Pull Request — master (#40)
by Alexis
10:37 queued 04:15
created

WebTestCase::getDecorated()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 16
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 3.3332

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 16
ccs 6
cts 9
cp 0.6667
rs 9.4285
cc 3
eloc 7
nc 4
nop 0
crap 3.3332
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\HttpKernel\Kernel;
23
use Symfony\Component\HttpFoundation\Response;
24
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
25
use Symfony\Component\Security\Core\User\UserInterface;
26
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
27
use Symfony\Component\DependencyInjection\ContainerInterface;
28
use Symfony\Component\HttpFoundation\Session\Session;
29
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
30
31
/**
32
 * @author Lea Haensenberger
33
 * @author Lukas Kahwe Smith <[email protected]>
34
 * @author Benjamin Eberlei <[email protected]>
35
 */
36
abstract class WebTestCase extends BaseWebTestCase
37
{
38
    protected $environment = 'test';
39
    protected $containers;
40
    protected $kernelDir;
41
    // 5 * 1024 * 1024 KB
42
    protected $maxMemory = 5242880;
43
44
    // RUN COMMAND
45
    protected $verbosityLevel;
46
    protected $decorated;
47
48
    /**
49
     * @var array
50
     */
51
    private $firewallLogins = array();
52
53
    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...
54
    {
55
        $dir = isset($_SERVER['KERNEL_DIR']) ? $_SERVER['KERNEL_DIR'] : static::getPhpUnitXmlDir();
56
57
        list($appname) = explode('\\', get_called_class());
58
59
        $class = $appname.'Kernel';
60
        $file = $dir.'/'.strtolower($appname).'/'.$class.'.php';
61
        if (!file_exists($file)) {
62
            return parent::getKernelClass();
63
        }
64
        require_once $file;
65
66
        return $class;
67
    }
68
69
    /**
70
     * Creates a mock object of a service identified by its id.
71
     *
72
     * @param string $id
73
     *
74
     * @return \PHPUnit_Framework_MockObject_MockBuilder
75
     */
76
    protected function getServiceMockBuilder($id)
77
    {
78
        $service = $this->getContainer()->get($id);
79
        $class = get_class($service);
80
81
        return $this->getMockBuilder($class)->disableOriginalConstructor();
82
    }
83
84
    /**
85
     * Builds up the environment to run the given command.
86
     *
87
     * @param string $name
88
     * @param array  $params
89
     * @param bool   $reuseKernel
90
     *
91
     * @return string
92
     */
93 13
    protected function runCommand($name, array $params = array(), $reuseKernel = false)
94
    {
95 13
        array_unshift($params, $name);
96
97 13
        if (!$reuseKernel) {
98 13
            if (null !== static::$kernel) {
99 9
                static::$kernel->shutdown();
100 9
            }
101
102 13
            $kernel = static::$kernel = $this->createKernel(array('environment' => $this->environment));
103 13
            $kernel->boot();
104 13
        } else {
105 2
            $kernel = $this->getContainer()->get('kernel');
106
        }
107
108 13
        $application = new Application($kernel);
109 13
        $application->setAutoExit(false);
110
111
        // @codeCoverageIgnoreStart
112
        if ('20301' === Kernel::VERSION_ID) {
113
            $params = $this->configureVerbosityForSymfony20301($params);
114
        }
115
        // @codeCoverageIgnoreEnd
116
117 13
        $input = new ArrayInput($params);
118 13
        $input->setInteractive(false);
119
120 13
        $fp = fopen('php://temp/maxmemory:'.$this->maxMemory, 'r+');
121 13
        $output = new StreamOutput($fp, $this->getVerbosityLevel(), $this->getDecorated());
122
123 12
        $application->run($input, $output);
124
125 12
        rewind($fp);
126
127 12
        return stream_get_contents($fp);
128
    }
129
130
    /**
131
     * Retrieves the output verbosity level.
132
     *
133
     * @see Symfony\Component\Console\Output\OutputInterface for available levels
134
     *
135
     * @return int
136
     *
137
     * @throws \OutOfBoundsException If the set value isn't accepted
138
     */
139 13
    protected function getVerbosityLevel()
140
    {
141
        // If `null`, is not yet set
142 13
        if (null === $this->verbosityLevel) {
143
            // Set the global verbosity level that is set as NORMAL by the TreeBuilder in Configuration
144 7
            $level = strtoupper($this->getContainer()->getParameter('liip_functional_test.command_verbosity'));
145 7
            $verbosity = '\Symfony\Component\Console\Output\StreamOutput::VERBOSITY_'.$level;
146
147 7
            $this->verbosityLevel = constant($verbosity);
148 7
        }
149
150
        // If string, it is set by the developer, so check that the value is an accepted one
151 13
        if (is_string($this->verbosityLevel)) {
152 6
            $level = strtoupper($this->verbosityLevel);
153 6
            $verbosity = '\Symfony\Component\Console\Output\StreamOutput::VERBOSITY_'.$level;
154
155 6
            if (!defined($verbosity)) {
156 1
                throw new \OutOfBoundsException(
157 1
                    sprintf('The set value "%s" for verbosityLevel is not valid. Accepted are: "quiet", "normal", "verbose", "very_verbose" and "debug".', $level)
158 1
                    );
159
            }
160
161 5
            $this->verbosityLevel = constant($verbosity);
162 5
        }
163
164 12
        return $this->verbosityLevel;
165
    }
166
167
    /**
168
     * In Symfony 2.3.1 the verbosity level has to be set through {Symfony\Component\Console\Input\ArrayInput} and not
169
     * in {Symfony\Component\Console\Output\OutputInterface}.
170
     *
171
     * This method builds $params to be passed to {Symfony\Component\Console\Input\ArrayInput}.
172
     *
173
     * @codeCoverageIgnore
174
     *
175
     * @param array $params
176
     *
177
     * @return array
178
     */
179
    private function configureVerbosityForSymfony20301(array $params)
180
    {
181
        switch ($this->getVerbosityLevel()) {
182
            case OutputInterface::VERBOSITY_QUIET:
183
                $params['-q'] = '-q';
184
                break;
185
186
            case OutputInterface::VERBOSITY_VERBOSE:
187
                $params['-v'] = '';
188
                break;
189
190
            case OutputInterface::VERBOSITY_VERY_VERBOSE:
191
                $params['-vv'] = '';
192
                break;
193
194
            case OutputInterface::VERBOSITY_DEBUG:
195
                $params['-vvv'] = '';
196
                break;
197
        }
198
199
        return $params;
200
    }
201
202 6
    public function setVerbosityLevel($level)
203
    {
204 6
        $this->verbosityLevel = $level;
205 6
    }
206
207
    /**
208
     * Retrieves the flag indicating if the output should be decorated or not.
209
     *
210
     * @return bool
211
     */
212 12
    protected function getDecorated()
213
    {
214 12
        if (null === $this->decorated) {
215
            // Set the global decoration flag that is set to `true` by the TreeBuilder in Configuration
216 5
            $this->decorated = $this->getContainer()->getParameter('liip_functional_test.command_decoration');
217 5
        }
218
219
        // Check the local decorated flag
220 12
        if (false === is_bool($this->decorated)) {
221
            throw new \OutOfBoundsException(
222
                sprintf('`WebTestCase::decorated` has to be `bool`. "%s" given.', gettype($this->decorated))
223
            );
224
        }
225
226 12
        return $this->decorated;
227
    }
228
229 7
    public function isDecorated($decorated)
230
    {
231 7
        $this->decorated = $decorated;
232 7
    }
233
234
    /**
235
     * Get an instance of the dependency injection container.
236
     * (this creates a kernel *without* parameters).
237
     *
238
     * @return ContainerInterface
239
     */
240 46
    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...
241
    {
242 46
        if (!empty($this->kernelDir)) {
243
            $tmpKernelDir = isset($_SERVER['KERNEL_DIR']) ? $_SERVER['KERNEL_DIR'] : null;
244
            $_SERVER['KERNEL_DIR'] = getcwd().$this->kernelDir;
245
        }
246
247 46
        $cacheKey = $this->kernelDir.'|'.$this->environment;
248 46
        if (empty($this->containers[$cacheKey])) {
249
            $options = array(
250 45
                'environment' => $this->environment,
251 45
            );
252 45
            $kernel = $this->createKernel($options);
253 45
            $kernel->boot();
254
255 45
            $this->containers[$cacheKey] = $kernel->getContainer();
256 45
        }
257
258 46
        if (isset($tmpKernelDir)) {
259
            $_SERVER['KERNEL_DIR'] = $tmpKernelDir;
260
        }
261
262 46
        return $this->containers[$cacheKey];
263
    }
264
265
    /**
266
     * Creates an instance of a lightweight Http client.
267
     *
268
     * If $authentication is set to 'true' it will use the content of
269
     * 'liip_functional_test.authentication' to log in.
270
     *
271
     * $params can be used to pass headers to the client, note that they have
272
     * to follow the naming format used in $_SERVER.
273
     * Example: 'HTTP_X_REQUESTED_WITH' instead of 'X-Requested-With'
274
     *
275
     * @param bool|array $authentication
276
     * @param array      $params
277
     *
278
     * @return Client
279
     */
280 49
    protected function makeClient($authentication = false, array $params = array())
281
    {
282 49
        if ($authentication) {
283 2
            if ($authentication === true) {
284
                $authentication = array(
285 1
                    'username' => $this->getContainer()
286 1
                        ->getParameter('liip_functional_test.authentication.username'),
287 1
                    'password' => $this->getContainer()
288 1
                        ->getParameter('liip_functional_test.authentication.password'),
289 1
                );
290 1
            }
291
292 2
            $params = array_merge($params, array(
293 2
                'PHP_AUTH_USER' => $authentication['username'],
294 2
                'PHP_AUTH_PW' => $authentication['password'],
295 2
            ));
296 2
        }
297
298 49
        $client = static::createClient(array('environment' => $this->environment), $params);
299
300 49
        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...
301
            // has to be set otherwise "hasPreviousSession" in Request returns false.
302 2
            $options = $client->getContainer()->getParameter('session.storage.options');
303
304 2
            if (!$options || !isset($options['name'])) {
305
                throw new \InvalidArgumentException('Missing session.storage.options#name');
306
            }
307
308 2
            $session = $client->getContainer()->get('session');
309
            // Since the namespace of the session changed in symfony 2.1, instanceof can be used to check the version.
310 2
            if ($session instanceof Session) {
311 2
                $session->setId(uniqid());
312 2
            }
313
314 2
            $client->getCookieJar()->set(new Cookie($options['name'], $session->getId()));
315
316
            /** @var $user UserInterface */
317 2
            foreach ($this->firewallLogins as $firewallName => $user) {
318 2
                $token = $this->createUserToken($user, $firewallName);
319
320
                // BC: security.token_storage is available on Symfony 2.6+
321
                // see http://symfony.com/blog/new-in-symfony-2-6-security-component-improvements
322 2
                if ($client->getContainer()->has('security.token_storage')) {
323 2
                    $tokenStorage = $client->getContainer()->get('security.token_storage');
324 2
                } else {
325
                    // This block will never be reached with Symfony 2.6+
326
                    // @codeCoverageIgnoreStart
327
                    $tokenStorage = $client->getContainer()->get('security.context');
328
                    // @codeCoverageIgnoreEnd
329
                }
330
331 2
                $tokenStorage->setToken($token);
332 2
                $session->set('_security_'.$firewallName, serialize($token));
333 2
            }
334
335 2
            $session->save();
336 2
        }
337
338 49
        return $client;
339
    }
340
341
    /**
342
     * Create User Token.
343
     *
344
     * Factory method for creating a User Token object for the firewall based on
345
     * the user object provided. By default it will be a Username/Password
346
     * Token based on the user's credentials, but may be overridden for custom
347
     * tokens in your applications.
348
     *
349
     * @param UserInterface $user         The user object to base the token off of
350
     * @param string        $firewallName name of the firewall provider to use
351
     *
352
     * @return TokenInterface The token to be used in the security context
353
     */
354 2
    protected function createUserToken(UserInterface $user, $firewallName)
355
    {
356 2
        return new UsernamePasswordToken(
357 2
            $user,
358 2
            null,
359 2
            $firewallName,
360 2
            $user->getRoles()
361 2
        );
362
    }
363
364
    /**
365
     * Extracts the location from the given route.
366
     *
367
     * @param string $route    The name of the route
368
     * @param array  $params   Set of parameters
369
     * @param int    $absolute
370
     *
371
     * @return string
372
     */
373 1
    protected function getUrl($route, $params = array(), $absolute = UrlGeneratorInterface::ABSOLUTE_PATH)
374
    {
375 1
        return $this->getContainer()->get('router')->generate($route, $params, $absolute);
376
    }
377
378
    /**
379
     * Executes a request on the given url and returns the response contents.
380
     *
381
     * This method also asserts the request was successful.
382
     *
383
     * @param string $path           path of the requested page
384
     * @param string $method         The HTTP method to use, defaults to GET
385
     * @param bool   $authentication Whether to use authentication, defaults to false
386
     * @param bool   $success        to define whether the response is expected to be successful
387
     *
388
     * @return string
389
     */
390 1
    public function fetchContent($path, $method = 'GET', $authentication = false, $success = true)
391
    {
392 1
        $client = $this->makeClient($authentication);
393 1
        $client->request($method, $path);
394
395 1
        $content = $client->getResponse()->getContent();
396 1
        if (is_bool($success)) {
397 1
            $this->getContainer()->get('liip_functional_test.http_assertions')->isSuccessful($client->getResponse(), $success);
398 1
        }
399
400 1
        return $content;
401
    }
402
403
    /**
404
     * Executes a request on the given url and returns a Crawler object.
405
     *
406
     * This method also asserts the request was successful.
407
     *
408
     * @param string $path           path of the requested page
409
     * @param string $method         The HTTP method to use, defaults to GET
410
     * @param bool   $authentication Whether to use authentication, defaults to false
411
     * @param bool   $success        Whether the response is expected to be successful
412
     *
413
     * @return Crawler
414
     */
415 1
    public function fetchCrawler($path, $method = 'GET', $authentication = false, $success = true)
416
    {
417 1
        $client = $this->makeClient($authentication);
418 1
        $crawler = $client->request($method, $path);
419
420 1
        $this->getContainer()->get('liip_functional_test.http_assertions')->isSuccessful($client->getResponse(), $success);
421
422 1
        return $crawler;
423
    }
424
425
    /**
426
     * @param UserInterface $user
427
     * @param string        $firewallName
428
     *
429
     * @return WebTestCase
430
     */
431 2
    public function loginAs(UserInterface $user, $firewallName)
432
    {
433 2
        $this->firewallLogins[$firewallName] = $user;
434
435 2
        return $this;
436
    }
437
}
438