Completed
Push — master ( e32eeb...714cda )
by
unknown
89:50 queued 46:29
created

WebTestCase::getApiRequestsData()   C

Complexity

Conditions 7
Paths 20

Size

Total Lines 39
Code Lines 23

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 39
rs 6.7272
cc 7
eloc 23
nc 20
nop 1
1
<?php
2
3
namespace Oro\Bundle\TestFrameworkBundle\Test;
4
5
use Doctrine\Common\DataFixtures\DependentFixtureInterface;
6
use Doctrine\Common\DataFixtures\Executor\ORMExecutor;
7
use Doctrine\Common\DataFixtures\Purger\ORMPurger;
8
use Doctrine\Common\DataFixtures\ReferenceRepository;
9
use Doctrine\DBAL\Connection;
10
11
use Symfony\Bridge\Doctrine\DataFixtures\ContainerAwareLoader as DataFixturesLoader;
12
use Symfony\Component\Console\Output\StreamOutput;
13
use Symfony\Component\Yaml\Yaml;
14
use Symfony\Component\Finder\Finder;
15
use Symfony\Component\HttpFoundation\Response;
16
use Symfony\Component\Console\Input\ArgvInput;
17
use Symfony\Component\DependencyInjection\ContainerInterface;
18
use Symfony\Bundle\FrameworkBundle\Console\Application;
19
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase as BaseWebTestCase;
20
21
use Oro\Bundle\NavigationBundle\Event\ResponseHashnavListener;
22
23
/**
24
 * Abstract class for functional and integration tests
25
 *
26
 * @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
27
 */
28
abstract class WebTestCase extends BaseWebTestCase
29
{
30
    /** Annotation names */
31
    const DB_ISOLATION_ANNOTATION = 'dbIsolation';
32
    const DB_REINDEX_ANNOTATION   = 'dbReindex';
33
34
    /** Default WSSE credentials */
35
    const USER_NAME     = 'admin';
36
    const USER_PASSWORD = 'admin_api_key';
37
38
    /**  Default user name and password */
39
    const AUTH_USER         = '[email protected]';
40
    const AUTH_PW           = 'admin';
41
    const AUTH_ORGANIZATION = 1;
42
43
    /**
44
     * @var bool[]
45
     */
46
    private static $dbIsolation;
47
48
    /**
49
     * @var bool[]
50
     */
51
    private static $dbReindex;
52
53
    /**
54
     * @var Client
55
     */
56
    private static $clientInstance;
57
58
    /**
59
     * @var Client
60
     */
61
    private static $soapClientInstance;
62
63
    /**
64
     * @var Client
65
     */
66
    protected $client;
67
68
    /**
69
     * @var SoapClient
70
     */
71
    protected $soapClient;
72
73
    /**
74
     * @var array
75
     */
76
    private static $loadedFixtures = [];
77
78
    /**
79
     * @var Connection[]
80
     */
81
    private static $connections = [];
82
83
    /**
84
     * @var ReferenceRepository
85
     */
86
    private static $referenceRepository;
87
88
    protected function tearDown()
89
    {
90
        $refClass = new \ReflectionClass($this);
91
        foreach ($refClass->getProperties() as $prop) {
92
            if (!$prop->isStatic() && 0 !== strpos($prop->getDeclaringClass()->getName(), 'PHPUnit_')) {
93
                $prop->setAccessible(true);
94
                $prop->setValue($this, null);
95
            }
96
        }
97
98
        /**
99
         * We should collect and close (in tearDownAfterClass) all opened DB connection
100
         *  to avoid exceeding the `max_connections` limitation.
101
         * Due all test cases runs in single process.
102
         */
103
        /** @var Connection[] $connections */
104
        $connections = self::getContainer()->get('doctrine')->getConnections();
105
        foreach ($connections as $connection) {
106
            if ($connection->isConnected()) {
107
                self::$connections[] = $connection;
108
            }
109
        }
110
    }
111
112
    public static function tearDownAfterClass()
113
    {
114
        if (self::$clientInstance) {
115
            if (self::getDbIsolationSetting()) {
116
                self::$clientInstance->rollbackTransaction();
117
            }
118
119
            self::cleanUpConnections();
120
121
            self::$clientInstance = null;
122
            self::$soapClientInstance = null;
123
            self::$loadedFixtures = [];
124
        }
125
    }
126
127
    /**
128
     * Closes opened DB connections to avoid exceeding the `max_connections` limitation.
129
     */
130
    public static function cleanUpConnections()
131
    {
132
        $connections = self::$connections;
133
        foreach ($connections as $connection) {
134
            $connection->close();
135
        }
136
        self::$connections = [];
137
    }
138
139
    /**
140
     * Creates a Client.
141
     *
142
     * @param array $options An array of options to pass to the createKernel class
143
     * @param array $server  An array of server parameters
144
     * @param bool  $force If this option - true, will reset client on each initClient call
145
     *
146
     * @return Client A Client instance
147
     */
148
    protected function initClient(array $options = [], array $server = [], $force = false)
149
    {
150
        if ($force) {
151
            $this->resetClient();
152
        }
153
154
        if (!self::$clientInstance) {
155
            // Fix for: The "native_profiler" extension is not enabled in "*.html.twig".
156
            // If you still getting this exception please run "php app/console cache:clear --env=test --no-debug".
157
            // The cache will be cleared and warmed up without the twig profiler.
158
            if (!isset($options['debug'])) {
159
                $options['debug'] = false;
160
            }
161
162
            /** @var Client $client */
163
            $client = self::$clientInstance = static::createClient($options, $server);
164
165
            if (self::getDbIsolationSetting()) {
166
                //This is a workaround for MyISAM search tables that are not transactional
167
                if (self::getDbReindexSetting()) {
168
                    self::getContainer()->get('oro_search.search.engine')->reindex();
169
                }
170
171
                $client->startTransaction();
172
            }
173
        } else {
174
            self::$clientInstance->setServerParameters($server);
175
        }
176
177
        $this->client = self::$clientInstance;
0 ignored issues
show
Documentation Bug introduced by
self::$clientInstance is of type object<Symfony\Bundle\FrameworkBundle\Client>, but the property $client was declared to be of type object<Oro\Bundle\TestFr...workBundle\Test\Client>. Are you sure that you always receive this specific sub-class here, or does it make sense to add an instanceof check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a given class or a super-class is assigned to a property that is type hinted more strictly.

Either this assignment is in error or an instanceof check should be added for that assignment.

class Alien {}

class Dalek extends Alien {}

class Plot
{
    /** @var  Dalek */
    public $villain;
}

$alien = new Alien();
$plot = new Plot();
if ($alien instanceof Dalek) {
    $plot->villain = $alien;
}
Loading history...
178
    }
179
180
    /**
181
     * Reset client and rollback transaction
182
     */
183
    protected function resetClient()
184
    {
185
        if (self::$clientInstance) {
186
            if (self::getDbIsolationSetting()) {
187
                self::$clientInstance->rollbackTransaction();
188
            }
189
190
            $this->client = null;
191
            self::$clientInstance = null;
192
            self::$connections = [];
193
        }
194
    }
195
196
    /**
197
     * {@inheritdoc}
198
     */
199
    protected static function getKernelClass()
200
    {
201
        $dir = isset($_SERVER['KERNEL_DIR']) ? $_SERVER['KERNEL_DIR'] : static::getPhpUnitXmlDir();
202
203
        $finder = new Finder();
204
        $finder->name('AppKernel.php')->depth(0)->in($dir);
205
        $results = iterator_to_array($finder);
206
207
        if (count($results)) {
208
            $file  = current($results);
209
            $class = $file->getBasename('.php');
210
211
            require_once $file;
212
        } else {
213
            $class = parent::getKernelClass();
214
        }
215
216
        return $class;
217
    }
218
219
    /**
220
     * Get value of dbIsolation option from annotation of called class
221
     *
222
     * @return bool
223
     */
224 View Code Duplication
    private static function getDbIsolationSetting()
225
    {
226
        $calledClass = get_called_class();
227
        if (!isset(self::$dbIsolation[$calledClass])) {
228
            self::$dbIsolation[$calledClass] = self::isClassHasAnnotation($calledClass, self::DB_ISOLATION_ANNOTATION);
229
        }
230
231
        return self::$dbIsolation[$calledClass];
232
    }
233
234
    /**
235
     * Get value of dbIsolation option from annotation of called class
236
     *
237
     * @return bool
238
     */
239 View Code Duplication
    private static function getDbReindexSetting()
240
    {
241
        $calledClass = get_called_class();
242
        if (!isset(self::$dbReindex[$calledClass])) {
243
            self::$dbReindex[$calledClass] = self::isClassHasAnnotation($calledClass, self::DB_REINDEX_ANNOTATION);
244
        }
245
246
        return self::$dbReindex[$calledClass];
247
    }
248
249
    /**
250
     * @param string $className
251
     * @param string $annotationName
252
     *
253
     * @return bool
254
     */
255
    private static function isClassHasAnnotation($className, $annotationName)
256
    {
257
        $annotations = \PHPUnit_Util_Test::parseTestMethodAnnotations($className);
258
        return isset($annotations['class'][$annotationName]);
259
    }
260
261
    /**
262
     * @param string $wsdl
263
     * @param array  $options
264
     * @param bool   $force
265
     *
266
     * @return SoapClient
267
     * @throws \Exception
268
     */
269
    protected function initSoapClient($wsdl = null, array $options = [], $force = false)
270
    {
271
        if (!self::$soapClientInstance || $force) {
272
            if ($wsdl === null) {
273
                $wsdl = "http://localhost/api/soap";
274
            }
275
276
            $options = array_merge(
277
                [
278
                    'location' => $wsdl,
279
                    'soap_version' => SOAP_1_2
280
                ],
281
                $options
282
            );
283
284
            $client = $this->getClientInstance();
285
            if ($options['soap_version'] == SOAP_1_2) {
286
                $contentType = 'application/soap+xml';
287
            } else {
288
                $contentType = 'text/xml';
289
            }
290
            $client->request('GET', $wsdl, [], [], ['CONTENT_TYPE' => $contentType]);
291
            $status = $client->getResponse()->getStatusCode();
292
            $wsdl = $client->getResponse()->getContent();
293
            if ($status >= 400) {
294
                throw new \Exception($wsdl, $status);
295
            }
296
            //save to file
297
            $file = tempnam(sys_get_temp_dir(), date("Ymd") . '_') . '.xml';
298
            $fl = fopen($file, "w");
299
            fwrite($fl, $wsdl);
300
            fclose($fl);
301
302
            self::$soapClientInstance = new SoapClient($file, $options, $client);
0 ignored issues
show
Documentation Bug introduced by
It seems like new \Oro\Bundle\TestFram...ile, $options, $client) of type object<Oro\Bundle\TestFr...Bundle\Test\SoapClient> is incompatible with the declared type object<Oro\Bundle\TestFr...workBundle\Test\Client> of property $soapClientInstance.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
303
304
            unlink($file);
305
        }
306
307
        $this->soapClient = self::$soapClientInstance;
0 ignored issues
show
Documentation Bug introduced by
It seems like self::$soapClientInstance can also be of type object<Oro\Bundle\TestFr...workBundle\Test\Client>. However, the property $soapClient is declared as type object<Oro\Bundle\TestFr...Bundle\Test\SoapClient>. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
308
    }
309
310
    /**
311
     * Builds up the environment to run the given command.
312
     *
313
     * @param string $name
314
     * @param array  $params
315
     *
316
     * @return string
317
     */
318
    protected static function runCommand($name, array $params = [])
319
    {
320
        $kernel = self::getContainer()->get('kernel');
321
322
        $application = new Application($kernel);
323
        $application->setAutoExit(false);
324
325
        $argv = ['application', $name];
326
        foreach ($params as $k => $v) {
327
            if (is_bool($v)) {
328
                if ($v) {
329
                    $argv[] = $k;
330
                }
331
            } else {
332
                if (!is_int($k)) {
333
                    $argv[] = $k;
334
                }
335
                $argv[] = $v;
336
            }
337
        }
338
        $input = new ArgvInput($argv);
339
        $input->setInteractive(false);
340
341
        $fp = fopen('php://temp/maxmemory:' . (1024 * 1024 * 1), 'r+');
342
        $output = new StreamOutput($fp);
343
344
        $application->run($input, $output);
345
346
        rewind($fp);
347
        return stream_get_contents($fp);
348
    }
349
350
    /**
351
     * @param array $classNames
352
     * @param bool  $force
353
     */
354
    protected function loadFixtures(array $classNames, $force = false)
355
    {
356
        if (!$force) {
357
            $classNames = array_filter(
358
                $classNames,
359
                function ($value) {
360
                    return !in_array($value, self::$loadedFixtures);
361
                }
362
            );
363
364
            if (!$classNames) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $classNames 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...
365
                return;
366
            }
367
        }
368
369
        self::$loadedFixtures = array_merge(self::$loadedFixtures, $classNames);
370
371
        $loader = $this->getFixtureLoader($classNames);
372
        $fixtures = array_values($loader->getFixtures());
373
374
        $em = $this->getContainer()->get('doctrine')->getManager();
375
        $executor = new ORMExecutor($em, new ORMPurger($em));
376
        $executor->execute($fixtures, true);
377
        self::$referenceRepository = $executor->getReferenceRepository();
378
        $this->postFixtureLoad();
379
    }
380
381
    /**
382
     * @param string $referenceUID
383
     *
384
     * @return object
385
     */
386
    protected function getReference($referenceUID)
387
    {
388
        return $this->getReferenceRepository()->getReference($referenceUID);
389
    }
390
391
    /**
392
     * @return ReferenceRepository|null
393
     */
394
    protected function getReferenceRepository()
395
    {
396
        return self::$referenceRepository;
397
    }
398
399
    /**
400
     * Callback function to be executed after fixture load.
401
     */
402
    protected function postFixtureLoad()
403
    {
404
405
    }
406
407
    /**
408
     * Retrieve Doctrine DataFixtures loader.
409
     *
410
     * @param array $classNames
411
     *
412
     * @return DataFixturesLoader
413
     */
414
    private function getFixtureLoader(array $classNames)
415
    {
416
        $loader = new DataFixturesLoader($this->getContainer());
417
418
        foreach ($classNames as $className) {
419
            $this->loadFixtureClass($loader, $className);
420
        }
421
422
        return $loader;
423
    }
424
425
    /**
426
     * Load a data fixture class.
427
     *
428
     * @param DataFixturesLoader $loader
429
     * @param string             $className
430
     */
431
    private function loadFixtureClass(DataFixturesLoader $loader, $className)
432
    {
433
        $fixture = new $className();
434
435
        if ($loader->hasFixture($fixture)) {
436
            unset($fixture);
437
            return;
438
        }
439
440
        $loader->addFixture($fixture);
441
442
        if ($fixture instanceof DependentFixtureInterface) {
443
            foreach ($fixture->getDependencies() as $dependency) {
444
                $this->loadFixtureClass($loader, $dependency);
445
            }
446
        }
447
    }
448
449
    /**
450
     * Creates a mock object of a service identified by its id.
451
     *
452
     * @param string $id
453
     *
454
     * @return \PHPUnit_Framework_MockObject_MockBuilder
455
     */
456
    protected function getServiceMockBuilder($id)
457
    {
458
        $service = $this->getContainer()->get($id);
459
        $class = get_class($service);
460
        return $this->getMockBuilder($class)->disableOriginalConstructor();
461
    }
462
463
    /**
464
     * Generates a URL or path for a specific route based on the given parameters.
465
     *
466
     * @param string $name
467
     * @param array  $parameters
468
     * @param bool   $absolute
469
     *
470
     * @return string
471
     */
472
    protected function getUrl($name, $parameters = [], $absolute = false)
473
    {
474
        return self::getContainer()->get('router')->generate($name, $parameters, $absolute);
475
    }
476
477
    /**
478
     * Get an instance of the dependency injection container.
479
     *
480
     * @return ContainerInterface
481
     */
482
    protected static function getContainer()
483
    {
484
        return static::getClientInstance()->getContainer();
485
    }
486
487
    /**
488
     * @return Client
489
     * @throws \BadMethodCallException
490
     */
491
    public static function getClientInstance()
492
    {
493
        if (!self::$clientInstance) {
494
            throw new \BadMethodCallException('Client instance is not initialized.');
495
        }
496
497
        return self::$clientInstance;
498
    }
499
500
    /**
501
     * Data provider for REST/SOAP API tests
502
     *
503
     * @param string $folder
504
     *
505
     * @return array
506
     */
507
    public static function getApiRequestsData($folder)
508
    {
509
        static $randomString;
510
511
        // generate unique value
512
        if (!$randomString) {
513
            $randomString = self::generateRandomString(5);
514
        }
515
516
        $parameters = [];
517
        $testFiles = new \RecursiveDirectoryIterator($folder, \RecursiveDirectoryIterator::SKIP_DOTS);
518
        foreach ($testFiles as $fileName => $object) {
519
            $parameters[$fileName] = Yaml::parse(file_get_contents($fileName)) ?: [];
520
            if (is_null($parameters[$fileName]['response'])) {
521
                unset($parameters[$fileName]['response']);
522
            }
523
        }
524
525
        $replaceCallback = function (&$value) use ($randomString) {
526
            if (!is_null($value)) {
527
                $value = str_replace('%str%', $randomString, $value);
528
            }
529
        };
530
531
        foreach ($parameters as $key => $value) {
532
            array_walk(
533
                $parameters[$key]['request'],
534
                $replaceCallback,
535
                $randomString
536
            );
537
            array_walk(
538
                $parameters[$key]['response'],
539
                $replaceCallback,
540
                $randomString
541
            );
542
        }
543
544
        return $parameters;
545
    }
546
547
    /**
548
     * @param int $length
549
     *
550
     * @return string
551
     */
552
    public static function generateRandomString($length = 10)
553
    {
554
        $random = "";
555
        srand((double) microtime() * 1000000);
556
        $char_list = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
557
        $char_list .= "abcdefghijklmnopqrstuvwxyz";
558
        $char_list .= "1234567890_";
559
560
        for ($i = 0; $i < $length; $i++) {
561
            $random .= substr($char_list, (rand() % (strlen($char_list))), 1);
562
        }
563
564
        return $random;
565
    }
566
567
    /**
568
     * Generate WSSE authorization header
569
     *
570
     * @param string      $userName
571
     * @param string      $userPassword
572
     * @param string|null $nonce
573
     *
574
     * @return array
575
     */
576
    public static function generateWsseAuthHeader(
577
        $userName = self::USER_NAME,
578
        $userPassword = self::USER_PASSWORD,
579
        $nonce = null
580
    ) {
581
        if (null === $nonce) {
582
            $nonce = uniqid();
583
        }
584
585
        $created  = date('c');
586
        $digest   = base64_encode(sha1(base64_decode($nonce) . $created . $userPassword, true));
587
        $wsseHeader = [
588
            'CONTENT_TYPE' => 'application/json',
589
            'HTTP_Authorization' => 'WSSE profile="UsernameToken"',
590
            'HTTP_X-WSSE' => sprintf(
591
                'UsernameToken Username="%s", PasswordDigest="%s", Nonce="%s", Created="%s"',
592
                $userName,
593
                $digest,
594
                $nonce,
595
                $created
596
            )
597
        ];
598
599
        return $wsseHeader;
600
    }
601
602
    /**
603
     * Generate Basic  authorization header
604
     *
605
     * @param string $userName
606
     * @param string $userPassword
607
     * @param int    $userOrganization
608
     *
609
     * @return array
610
     */
611
    public static function generateBasicAuthHeader(
612
        $userName = self::AUTH_USER,
613
        $userPassword = self::AUTH_PW,
614
        $userOrganization = self::AUTH_ORGANIZATION
615
    ) {
616
        return [
617
            'PHP_AUTH_USER'         => $userName,
618
            'PHP_AUTH_PW'           => $userPassword,
619
            'PHP_AUTH_ORGANIZATION' => $userOrganization
620
        ];
621
    }
622
623
    /**
624
     * @return array
625
     */
626
    public static function generateNoHashNavigationHeader()
627
    {
628
        return ['HTTP_' . strtoupper(ResponseHashnavListener::HASH_NAVIGATION_HEADER) => 0];
629
    }
630
631
    /**
632
     * Convert value to array
633
     *
634
     * @param mixed $value
635
     *
636
     * @return array
637
     */
638
    public static function valueToArray($value)
639
    {
640
        return self::jsonToArray(json_encode($value));
641
    }
642
643
    /**
644
     * Convert json to array
645
     *
646
     * @param string $json
647
     *
648
     * @return array
649
     */
650
    public static function jsonToArray($json)
651
    {
652
        return (array)json_decode($json, true);
653
    }
654
655
    /**
656
     * Checks json response status code and return content as array
657
     *
658
     * @param Response $response
659
     * @param int      $statusCode
660
     *
661
     * @return array
662
     */
663
    public static function getJsonResponseContent(Response $response, $statusCode)
664
    {
665
        self::assertJsonResponseStatusCodeEquals($response, $statusCode);
666
        return self::jsonToArray($response->getContent());
667
    }
668
669
    /**
670
     * Assert response is json and has status code
671
     *
672
     * @param Response $response
673
     * @param int      $statusCode
674
     */
675
    public static function assertEmptyResponseStatusCodeEquals(Response $response, $statusCode)
676
    {
677
        self::assertResponseStatusCodeEquals($response, $statusCode);
678
        self::assertEmpty(
679
            $response->getContent(),
680
            sprintf('HTTP response with code %d must have empty body', $statusCode)
681
        );
682
    }
683
684
    /**
685
     * Assert response is json and has status code
686
     *
687
     * @param Response $response
688
     * @param int      $statusCode
689
     */
690
    public static function assertJsonResponseStatusCodeEquals(Response $response, $statusCode)
691
    {
692
        self::assertResponseStatusCodeEquals($response, $statusCode);
693
        self::assertResponseContentTypeEquals($response, 'application/json');
694
    }
695
696
    /**
697
     * Assert response is html and has status code
698
     *
699
     * @param Response $response
700
     * @param int      $statusCode
701
     */
702
    public static function assertHtmlResponseStatusCodeEquals(Response $response, $statusCode)
703
    {
704
        self::assertResponseStatusCodeEquals($response, $statusCode);
705
        self::assertResponseContentTypeEquals($response, 'text/html; charset=UTF-8');
706
    }
707
708
    /**
709
     * Assert response status code equals
710
     *
711
     * @param Response $response
712
     * @param int      $statusCode
713
     */
714
    public static function assertResponseStatusCodeEquals(Response $response, $statusCode)
715
    {
716
        try {
717
            \PHPUnit_Framework_TestCase::assertEquals(
718
                $statusCode,
719
                $response->getStatusCode(),
720
                $response->getContent()
721
            );
722
        } catch (\PHPUnit_Framework_ExpectationFailedException $e) {
723
            if ($statusCode < 400
724
                && $response->getStatusCode() >= 400
725
                && $response->headers->contains('Content-Type', 'application/json')
726
            ) {
727
                $content = self::jsonToArray($response->getContent());
728
                if (!empty($content['message'])) {
729
                    $errors = null;
730
                    if (!empty($content['errors'])) {
731
                        $errors = is_array($content['errors'])
732
                            ? json_encode($content['errors'])
733
                            : $content['errors'];
734
                    }
735
                    $e = new \PHPUnit_Framework_ExpectationFailedException(
736
                        $e->getMessage()
737
                        . ' Error message: ' . $content['message']
738
                        . ($errors ? '. Errors: ' . $errors : ''),
739
                        $e->getComparisonFailure()
740
                    );
741
                }
742
            }
743
            throw $e;
744
        }
745
    }
746
747
    /**
748
     * Assert response content type equals
749
     *
750
     * @param Response $response
751
     * @param string   $contentType
752
     */
753
    public static function assertResponseContentTypeEquals(Response $response, $contentType)
754
    {
755
        \PHPUnit_Framework_TestCase::assertTrue(
756
            $response->headers->contains('Content-Type', $contentType),
757
            $response->headers
758
        );
759
    }
760
761
    /**
762
     * Assert that intersect of $actual with $expected equals $expected
763
     *
764
     * @param array  $expected
765
     * @param array  $actual
766
     * @param string $message
767
     */
768
    public static function assertArrayIntersectEquals(array $expected, array $actual, $message = null)
769
    {
770
        $actualIntersect = [];
771
        foreach (array_keys($expected) as $expectedKey) {
772
            if (array_key_exists($expectedKey, $actual)) {
773
                $actualIntersect[$expectedKey] = $actual[$expectedKey];
774
            }
775
        }
776
        \PHPUnit_Framework_TestCase::assertEquals(
777
            $expected,
778
            $actualIntersect,
779
            $message
780
        );
781
    }
782
}
783