Subversion-Projekte lars-tiefland.laravel_shop

Revision

Revision 399 | Details | Vergleich mit vorheriger | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
148 lars 1
<?php
2
 
3
namespace Illuminate\Testing;
4
 
5
use ArrayAccess;
6
use Closure;
7
use Illuminate\Contracts\Support\MessageBag;
8
use Illuminate\Contracts\View\View;
9
use Illuminate\Cookie\CookieValuePrefix;
10
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
11
use Illuminate\Database\Eloquent\Model;
12
use Illuminate\Http\RedirectResponse;
13
use Illuminate\Http\Request;
14
use Illuminate\Support\Arr;
15
use Illuminate\Support\Carbon;
16
use Illuminate\Support\Collection;
17
use Illuminate\Support\Str;
18
use Illuminate\Support\Traits\Macroable;
19
use Illuminate\Support\Traits\Tappable;
20
use Illuminate\Support\ViewErrorBag;
21
use Illuminate\Testing\Assert as PHPUnit;
22
use Illuminate\Testing\Constraints\SeeInOrder;
23
use Illuminate\Testing\Fluent\AssertableJson;
24
use LogicException;
25
use PHPUnit\Framework\ExpectationFailedException;
26
use ReflectionProperty;
27
use Symfony\Component\HttpFoundation\Cookie;
28
use Symfony\Component\HttpFoundation\StreamedResponse;
29
 
30
/**
31
 * @mixin \Illuminate\Http\Response
32
 */
33
class TestResponse implements ArrayAccess
34
{
399 lars 35
    use Concerns\AssertsStatusCodes, Tappable, Macroable {
148 lars 36
        __call as macroCall;
37
    }
38
 
39
    /**
40
     * The response to delegate to.
41
     *
42
     * @var \Illuminate\Http\Response
43
     */
44
    public $baseResponse;
45
 
46
    /**
47
     * The collection of logged exceptions for the request.
48
     *
49
     * @var \Illuminate\Support\Collection
50
     */
51
    public $exceptions;
52
 
53
    /**
54
     * The streamed content of the response.
55
     *
56
     * @var string
57
     */
58
    protected $streamedContent;
59
 
60
    /**
61
     * Create a new test response instance.
62
     *
63
     * @param  \Illuminate\Http\Response  $response
64
     * @return void
65
     */
66
    public function __construct($response)
67
    {
68
        $this->baseResponse = $response;
69
        $this->exceptions = new Collection;
70
    }
71
 
72
    /**
73
     * Create a new TestResponse from another response.
74
     *
75
     * @param  \Illuminate\Http\Response  $response
76
     * @return static
77
     */
78
    public static function fromBaseResponse($response)
79
    {
80
        return new static($response);
81
    }
82
 
83
    /**
84
     * Assert that the response has a successful status code.
85
     *
86
     * @return $this
87
     */
88
    public function assertSuccessful()
89
    {
90
        PHPUnit::assertTrue(
91
            $this->isSuccessful(),
92
            $this->statusMessageWithDetails('>=200, <300', $this->getStatusCode())
93
        );
94
 
95
        return $this;
96
    }
97
 
98
    /**
99
     * Assert that the response is a server error.
100
     *
101
     * @return $this
102
     */
103
    public function assertServerError()
104
    {
105
        PHPUnit::assertTrue(
106
            $this->isServerError(),
107
            $this->statusMessageWithDetails('>=500, < 600', $this->getStatusCode())
108
        );
109
 
110
        return $this;
111
    }
112
 
113
    /**
114
     * Assert that the response has the given status code.
115
     *
116
     * @param  int  $status
117
     * @return $this
118
     */
119
    public function assertStatus($status)
120
    {
121
        $message = $this->statusMessageWithDetails($status, $actual = $this->getStatusCode());
122
 
123
        PHPUnit::assertSame($actual, $status, $message);
124
 
125
        return $this;
126
    }
127
 
128
    /**
129
     * Get an assertion message for a status assertion containing extra details when available.
130
     *
131
     * @param  string|int  $expected
132
     * @param  string|int  $actual
133
     * @return string
134
     */
135
    protected function statusMessageWithDetails($expected, $actual)
136
    {
137
        return "Expected response status code [{$expected}] but received {$actual}.";
138
    }
139
 
140
    /**
141
     * Assert whether the response is redirecting to a given URI.
142
     *
143
     * @param  string|null  $uri
144
     * @return $this
145
     */
146
    public function assertRedirect($uri = null)
147
    {
148
        PHPUnit::assertTrue(
149
            $this->isRedirect(),
150
            $this->statusMessageWithDetails('201, 301, 302, 303, 307, 308', $this->getStatusCode()),
151
        );
152
 
153
        if (! is_null($uri)) {
154
            $this->assertLocation($uri);
155
        }
156
 
157
        return $this;
158
    }
159
 
160
    /**
161
     * Assert whether the response is redirecting to a URI that contains the given URI.
162
     *
163
     * @param  string  $uri
164
     * @return $this
165
     */
166
    public function assertRedirectContains($uri)
167
    {
168
        PHPUnit::assertTrue(
169
            $this->isRedirect(),
170
            $this->statusMessageWithDetails('201, 301, 302, 303, 307, 308', $this->getStatusCode()),
171
        );
172
 
173
        PHPUnit::assertTrue(
174
            Str::contains($this->headers->get('Location'), $uri), 'Redirect location ['.$this->headers->get('Location').'] does not contain ['.$uri.'].'
175
        );
176
 
177
        return $this;
178
    }
179
 
180
    /**
181
     * Assert whether the response is redirecting to a given route.
182
     *
183
     * @param  string  $name
184
     * @param  mixed  $parameters
185
     * @return $this
186
     */
187
    public function assertRedirectToRoute($name, $parameters = [])
188
    {
189
        $uri = route($name, $parameters);
190
 
191
        PHPUnit::assertTrue(
192
            $this->isRedirect(),
193
            $this->statusMessageWithDetails('201, 301, 302, 303, 307, 308', $this->getStatusCode()),
194
        );
195
 
196
        $request = Request::create($this->headers->get('Location'));
197
 
198
        PHPUnit::assertEquals(
199
            app('url')->to($uri), $request->fullUrl()
200
        );
201
 
202
        return $this;
203
    }
204
 
205
    /**
206
     * Assert whether the response is redirecting to a given signed route.
207
     *
208
     * @param  string|null  $name
209
     * @param  mixed  $parameters
210
     * @return $this
211
     */
212
    public function assertRedirectToSignedRoute($name = null, $parameters = [])
213
    {
214
        if (! is_null($name)) {
215
            $uri = route($name, $parameters);
216
        }
217
 
218
        PHPUnit::assertTrue(
219
            $this->isRedirect(),
220
            $this->statusMessageWithDetails('201, 301, 302, 303, 307, 308', $this->getStatusCode()),
221
        );
222
 
223
        $request = Request::create($this->headers->get('Location'));
224
 
225
        PHPUnit::assertTrue(
226
            $request->hasValidSignature(), 'The response is not a redirect to a signed route.'
227
        );
228
 
229
        if (! is_null($name)) {
230
            $expectedUri = rtrim($request->fullUrlWithQuery([
231
                'signature' => null,
232
                'expires' => null,
233
            ]), '?');
234
 
235
            PHPUnit::assertEquals(
236
                app('url')->to($uri), $expectedUri
237
            );
238
        }
239
 
240
        return $this;
241
    }
242
 
243
    /**
244
     * Asserts that the response contains the given header and equals the optional value.
245
     *
246
     * @param  string  $headerName
247
     * @param  mixed  $value
248
     * @return $this
249
     */
250
    public function assertHeader($headerName, $value = null)
251
    {
252
        PHPUnit::assertTrue(
253
            $this->headers->has($headerName), "Header [{$headerName}] not present on response."
254
        );
255
 
256
        $actual = $this->headers->get($headerName);
257
 
258
        if (! is_null($value)) {
259
            PHPUnit::assertEquals(
260
                $value, $this->headers->get($headerName),
261
                "Header [{$headerName}] was found, but value [{$actual}] does not match [{$value}]."
262
            );
263
        }
264
 
265
        return $this;
266
    }
267
 
268
    /**
269
     * Asserts that the response does not contain the given header.
270
     *
271
     * @param  string  $headerName
272
     * @return $this
273
     */
274
    public function assertHeaderMissing($headerName)
275
    {
276
        PHPUnit::assertFalse(
277
            $this->headers->has($headerName), "Unexpected header [{$headerName}] is present on response."
278
        );
279
 
280
        return $this;
281
    }
282
 
283
    /**
284
     * Assert that the current location header matches the given URI.
285
     *
286
     * @param  string  $uri
287
     * @return $this
288
     */
289
    public function assertLocation($uri)
290
    {
291
        PHPUnit::assertEquals(
292
            app('url')->to($uri), app('url')->to($this->headers->get('Location'))
293
        );
294
 
295
        return $this;
296
    }
297
 
298
    /**
299
     * Assert that the response offers a file download.
300
     *
301
     * @param  string|null  $filename
302
     * @return $this
303
     */
304
    public function assertDownload($filename = null)
305
    {
306
        $contentDisposition = explode(';', $this->headers->get('content-disposition'));
307
 
308
        if (trim($contentDisposition[0]) !== 'attachment') {
309
            PHPUnit::fail(
310
                'Response does not offer a file download.'.PHP_EOL.
311
                'Disposition ['.trim($contentDisposition[0]).'] found in header, [attachment] expected.'
312
            );
313
        }
314
 
315
        if (! is_null($filename)) {
316
            if (isset($contentDisposition[1]) &&
317
                trim(explode('=', $contentDisposition[1])[0]) !== 'filename') {
318
                PHPUnit::fail(
319
                    'Unsupported Content-Disposition header provided.'.PHP_EOL.
320
                    'Disposition ['.trim(explode('=', $contentDisposition[1])[0]).'] found in header, [filename] expected.'
321
                );
322
            }
323
 
324
            $message = "Expected file [{$filename}] is not present in Content-Disposition header.";
325
 
326
            if (! isset($contentDisposition[1])) {
327
                PHPUnit::fail($message);
328
            } else {
329
                PHPUnit::assertSame(
330
                    $filename,
331
                    isset(explode('=', $contentDisposition[1])[1])
332
                        ? trim(explode('=', $contentDisposition[1])[1], " \"'")
333
                        : '',
334
                    $message
335
                );
336
 
337
                return $this;
338
            }
339
        } else {
340
            PHPUnit::assertTrue(true);
341
 
342
            return $this;
343
        }
344
    }
345
 
346
    /**
347
     * Asserts that the response contains the given cookie and equals the optional value.
348
     *
349
     * @param  string  $cookieName
350
     * @param  mixed  $value
351
     * @return $this
352
     */
353
    public function assertPlainCookie($cookieName, $value = null)
354
    {
355
        $this->assertCookie($cookieName, $value, false);
356
 
357
        return $this;
358
    }
359
 
360
    /**
361
     * Asserts that the response contains the given cookie and equals the optional value.
362
     *
363
     * @param  string  $cookieName
364
     * @param  mixed  $value
365
     * @param  bool  $encrypted
366
     * @param  bool  $unserialize
367
     * @return $this
368
     */
369
    public function assertCookie($cookieName, $value = null, $encrypted = true, $unserialize = false)
370
    {
371
        PHPUnit::assertNotNull(
372
            $cookie = $this->getCookie($cookieName, $encrypted && ! is_null($value), $unserialize),
373
            "Cookie [{$cookieName}] not present on response."
374
        );
375
 
376
        if (! $cookie || is_null($value)) {
377
            return $this;
378
        }
379
 
380
        $cookieValue = $cookie->getValue();
381
 
382
        PHPUnit::assertEquals(
383
            $value, $cookieValue,
384
            "Cookie [{$cookieName}] was found, but value [{$cookieValue}] does not match [{$value}]."
385
        );
386
 
387
        return $this;
388
    }
389
 
390
    /**
391
     * Asserts that the response contains the given cookie and is expired.
392
     *
393
     * @param  string  $cookieName
394
     * @return $this
395
     */
396
    public function assertCookieExpired($cookieName)
397
    {
398
        PHPUnit::assertNotNull(
399
            $cookie = $this->getCookie($cookieName, false),
400
            "Cookie [{$cookieName}] not present on response."
401
        );
402
 
403
        $expiresAt = Carbon::createFromTimestamp($cookie->getExpiresTime());
404
 
405
        PHPUnit::assertTrue(
406
            $cookie->getExpiresTime() !== 0 && $expiresAt->lessThan(Carbon::now()),
407
            "Cookie [{$cookieName}] is not expired, it expires at [{$expiresAt}]."
408
        );
409
 
410
        return $this;
411
    }
412
 
413
    /**
414
     * Asserts that the response contains the given cookie and is not expired.
415
     *
416
     * @param  string  $cookieName
417
     * @return $this
418
     */
419
    public function assertCookieNotExpired($cookieName)
420
    {
421
        PHPUnit::assertNotNull(
422
            $cookie = $this->getCookie($cookieName, false),
423
            "Cookie [{$cookieName}] not present on response."
424
        );
425
 
426
        $expiresAt = Carbon::createFromTimestamp($cookie->getExpiresTime());
427
 
428
        PHPUnit::assertTrue(
429
            $cookie->getExpiresTime() === 0 || $expiresAt->greaterThan(Carbon::now()),
430
            "Cookie [{$cookieName}] is expired, it expired at [{$expiresAt}]."
431
        );
432
 
433
        return $this;
434
    }
435
 
436
    /**
437
     * Asserts that the response does not contain the given cookie.
438
     *
439
     * @param  string  $cookieName
440
     * @return $this
441
     */
442
    public function assertCookieMissing($cookieName)
443
    {
444
        PHPUnit::assertNull(
445
            $this->getCookie($cookieName, false),
446
            "Cookie [{$cookieName}] is present on response."
447
        );
448
 
449
        return $this;
450
    }
451
 
452
    /**
453
     * Get the given cookie from the response.
454
     *
455
     * @param  string  $cookieName
456
     * @param  bool  $decrypt
457
     * @param  bool  $unserialize
458
     * @return \Symfony\Component\HttpFoundation\Cookie|null
459
     */
460
    public function getCookie($cookieName, $decrypt = true, $unserialize = false)
461
    {
462
        foreach ($this->headers->getCookies() as $cookie) {
463
            if ($cookie->getName() === $cookieName) {
464
                if (! $decrypt) {
465
                    return $cookie;
466
                }
467
 
468
                $decryptedValue = CookieValuePrefix::remove(
469
                    app('encrypter')->decrypt($cookie->getValue(), $unserialize)
470
                );
471
 
472
                return new Cookie(
473
                    $cookie->getName(),
474
                    $decryptedValue,
475
                    $cookie->getExpiresTime(),
476
                    $cookie->getPath(),
477
                    $cookie->getDomain(),
478
                    $cookie->isSecure(),
479
                    $cookie->isHttpOnly(),
480
                    $cookie->isRaw(),
481
                    $cookie->getSameSite()
482
                );
483
            }
484
        }
485
    }
486
 
487
    /**
488
     * Assert that the given string matches the response content.
489
     *
490
     * @param  string  $value
491
     * @return $this
492
     */
493
    public function assertContent($value)
494
    {
495
        PHPUnit::assertSame($value, $this->content());
496
 
497
        return $this;
498
    }
499
 
500
    /**
501
     * Assert that the given string matches the streamed response content.
502
     *
150 lars 503
     * @param  string  $value
148 lars 504
     * @return $this
505
     */
506
    public function assertStreamedContent($value)
507
    {
508
        PHPUnit::assertSame($value, $this->streamedContent());
509
 
510
        return $this;
511
    }
512
 
513
    /**
514
     * Assert that the given string or array of strings are contained within the response.
515
     *
516
     * @param  string|array  $value
517
     * @param  bool  $escape
518
     * @return $this
519
     */
520
    public function assertSee($value, $escape = true)
521
    {
522
        $value = Arr::wrap($value);
523
 
524
        $values = $escape ? array_map('e', $value) : $value;
525
 
526
        foreach ($values as $value) {
527
            PHPUnit::assertStringContainsString((string) $value, $this->getContent());
528
        }
529
 
530
        return $this;
531
    }
532
 
533
    /**
534
     * Assert that the given strings are contained in order within the response.
535
     *
536
     * @param  array  $values
537
     * @param  bool  $escape
538
     * @return $this
539
     */
540
    public function assertSeeInOrder(array $values, $escape = true)
541
    {
542
        $values = $escape ? array_map('e', $values) : $values;
543
 
544
        PHPUnit::assertThat($values, new SeeInOrder($this->getContent()));
545
 
546
        return $this;
547
    }
548
 
549
    /**
550
     * Assert that the given string or array of strings are contained within the response text.
551
     *
552
     * @param  string|array  $value
553
     * @param  bool  $escape
554
     * @return $this
555
     */
556
    public function assertSeeText($value, $escape = true)
557
    {
558
        $value = Arr::wrap($value);
559
 
560
        $values = $escape ? array_map('e', $value) : $value;
561
 
562
        $content = strip_tags($this->getContent());
563
 
564
        foreach ($values as $value) {
565
            PHPUnit::assertStringContainsString((string) $value, $content);
566
        }
567
 
568
        return $this;
569
    }
570
 
571
    /**
572
     * Assert that the given strings are contained in order within the response text.
573
     *
574
     * @param  array  $values
575
     * @param  bool  $escape
576
     * @return $this
577
     */
578
    public function assertSeeTextInOrder(array $values, $escape = true)
579
    {
580
        $values = $escape ? array_map('e', $values) : $values;
581
 
582
        PHPUnit::assertThat($values, new SeeInOrder(strip_tags($this->getContent())));
583
 
584
        return $this;
585
    }
586
 
587
    /**
588
     * Assert that the given string or array of strings are not contained within the response.
589
     *
590
     * @param  string|array  $value
591
     * @param  bool  $escape
592
     * @return $this
593
     */
594
    public function assertDontSee($value, $escape = true)
595
    {
596
        $value = Arr::wrap($value);
597
 
598
        $values = $escape ? array_map('e', $value) : $value;
599
 
600
        foreach ($values as $value) {
601
            PHPUnit::assertStringNotContainsString((string) $value, $this->getContent());
602
        }
603
 
604
        return $this;
605
    }
606
 
607
    /**
608
     * Assert that the given string or array of strings are not contained within the response text.
609
     *
610
     * @param  string|array  $value
611
     * @param  bool  $escape
612
     * @return $this
613
     */
614
    public function assertDontSeeText($value, $escape = true)
615
    {
616
        $value = Arr::wrap($value);
617
 
618
        $values = $escape ? array_map('e', $value) : $value;
619
 
620
        $content = strip_tags($this->getContent());
621
 
622
        foreach ($values as $value) {
623
            PHPUnit::assertStringNotContainsString((string) $value, $content);
624
        }
625
 
626
        return $this;
627
    }
628
 
629
    /**
630
     * Assert that the response is a superset of the given JSON.
631
     *
632
     * @param  array|callable  $value
633
     * @param  bool  $strict
634
     * @return $this
635
     */
636
    public function assertJson($value, $strict = false)
637
    {
638
        $json = $this->decodeResponseJson();
639
 
640
        if (is_array($value)) {
641
            $json->assertSubset($value, $strict);
642
        } else {
643
            $assert = AssertableJson::fromAssertableJsonString($json);
644
 
645
            $value($assert);
646
 
647
            if (Arr::isAssoc($assert->toArray())) {
648
                $assert->interacted();
649
            }
650
        }
651
 
652
        return $this;
653
    }
654
 
655
    /**
656
     * Assert that the expected value and type exists at the given path in the response.
657
     *
658
     * @param  string  $path
659
     * @param  mixed  $expect
660
     * @return $this
661
     */
662
    public function assertJsonPath($path, $expect)
663
    {
664
        $this->decodeResponseJson()->assertPath($path, $expect);
665
 
666
        return $this;
667
    }
668
 
669
    /**
670
     * Assert that the response has the exact given JSON.
671
     *
672
     * @param  array  $data
673
     * @return $this
674
     */
675
    public function assertExactJson(array $data)
676
    {
677
        $this->decodeResponseJson()->assertExact($data);
678
 
679
        return $this;
680
    }
681
 
682
    /**
683
     * Assert that the response has the similar JSON as given.
684
     *
685
     * @param  array  $data
686
     * @return $this
687
     */
688
    public function assertSimilarJson(array $data)
689
    {
690
        $this->decodeResponseJson()->assertSimilar($data);
691
 
692
        return $this;
693
    }
694
 
695
    /**
696
     * Assert that the response contains the given JSON fragment.
697
     *
698
     * @param  array  $data
699
     * @return $this
700
     */
701
    public function assertJsonFragment(array $data)
702
    {
703
        $this->decodeResponseJson()->assertFragment($data);
704
 
705
        return $this;
706
    }
707
 
708
    /**
709
     * Assert that the response does not contain the given JSON fragment.
710
     *
711
     * @param  array  $data
712
     * @param  bool  $exact
713
     * @return $this
714
     */
715
    public function assertJsonMissing(array $data, $exact = false)
716
    {
717
        $this->decodeResponseJson()->assertMissing($data, $exact);
718
 
719
        return $this;
720
    }
721
 
722
    /**
723
     * Assert that the response does not contain the exact JSON fragment.
724
     *
725
     * @param  array  $data
726
     * @return $this
727
     */
728
    public function assertJsonMissingExact(array $data)
729
    {
730
        $this->decodeResponseJson()->assertMissingExact($data);
731
 
732
        return $this;
733
    }
734
 
735
    /**
736
     * Assert that the response does not contain the given path.
737
     *
738
     * @param  string  $path
739
     * @return $this
740
     */
741
    public function assertJsonMissingPath(string $path)
742
    {
743
        $this->decodeResponseJson()->assertMissingPath($path);
744
 
745
        return $this;
746
    }
747
 
748
    /**
749
     * Assert that the response has a given JSON structure.
750
     *
751
     * @param  array|null  $structure
752
     * @param  array|null  $responseData
753
     * @return $this
754
     */
755
    public function assertJsonStructure(array $structure = null, $responseData = null)
756
    {
757
        $this->decodeResponseJson()->assertStructure($structure, $responseData);
758
 
759
        return $this;
760
    }
761
 
762
    /**
763
     * Assert that the response JSON has the expected count of items at the given key.
764
     *
765
     * @param  int  $count
766
     * @param  string|null  $key
767
     * @return $this
768
     */
769
    public function assertJsonCount(int $count, $key = null)
770
    {
771
        $this->decodeResponseJson()->assertCount($count, $key);
772
 
773
        return $this;
774
    }
775
 
776
    /**
777
     * Assert that the response has the given JSON validation errors.
778
     *
779
     * @param  string|array  $errors
780
     * @param  string  $responseKey
781
     * @return $this
782
     */
783
    public function assertJsonValidationErrors($errors, $responseKey = 'errors')
784
    {
785
        $errors = Arr::wrap($errors);
786
 
787
        PHPUnit::assertNotEmpty($errors, 'No validation errors were provided.');
788
 
789
        $jsonErrors = Arr::get($this->json(), $responseKey) ?? [];
790
 
791
        $errorMessage = $jsonErrors
792
                ? 'Response has the following JSON validation errors:'.
793
                        PHP_EOL.PHP_EOL.json_encode($jsonErrors, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE).PHP_EOL
794
                : 'Response does not have JSON validation errors.';
795
 
796
        foreach ($errors as $key => $value) {
797
            if (is_int($key)) {
798
                $this->assertJsonValidationErrorFor($value, $responseKey);
799
 
800
                continue;
801
            }
802
 
803
            $this->assertJsonValidationErrorFor($key, $responseKey);
804
 
805
            foreach (Arr::wrap($value) as $expectedMessage) {
806
                $errorMissing = true;
807
 
808
                foreach (Arr::wrap($jsonErrors[$key]) as $jsonErrorMessage) {
809
                    if (Str::contains($jsonErrorMessage, $expectedMessage)) {
810
                        $errorMissing = false;
811
 
812
                        break;
813
                    }
814
                }
815
            }
816
 
817
            if ($errorMissing) {
818
                PHPUnit::fail(
819
                    "Failed to find a validation error in the response for key and message: '$key' => '$expectedMessage'".PHP_EOL.PHP_EOL.$errorMessage
820
                );
821
            }
822
        }
823
 
824
        return $this;
825
    }
826
 
827
    /**
828
     * Assert the response has any JSON validation errors for the given key.
829
     *
830
     * @param  string  $key
831
     * @param  string  $responseKey
832
     * @return $this
833
     */
834
    public function assertJsonValidationErrorFor($key, $responseKey = 'errors')
835
    {
836
        $jsonErrors = Arr::get($this->json(), $responseKey) ?? [];
837
 
838
        $errorMessage = $jsonErrors
839
            ? 'Response has the following JSON validation errors:'.
840
            PHP_EOL.PHP_EOL.json_encode($jsonErrors, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE).PHP_EOL
841
            : 'Response does not have JSON validation errors.';
842
 
843
        PHPUnit::assertArrayHasKey(
844
            $key,
845
            $jsonErrors,
846
            "Failed to find a validation error in the response for key: '{$key}'".PHP_EOL.PHP_EOL.$errorMessage
847
        );
848
 
849
        return $this;
850
    }
851
 
852
    /**
853
     * Assert that the response has no JSON validation errors for the given keys.
854
     *
855
     * @param  string|array|null  $keys
856
     * @param  string  $responseKey
857
     * @return $this
858
     */
859
    public function assertJsonMissingValidationErrors($keys = null, $responseKey = 'errors')
860
    {
861
        if ($this->getContent() === '') {
862
            PHPUnit::assertTrue(true);
863
 
864
            return $this;
865
        }
866
 
867
        $json = $this->json();
868
 
869
        if (! Arr::has($json, $responseKey)) {
870
            PHPUnit::assertTrue(true);
871
 
872
            return $this;
873
        }
874
 
875
        $errors = Arr::get($json, $responseKey, []);
876
 
877
        if (is_null($keys) && count($errors) > 0) {
878
            PHPUnit::fail(
879
                'Response has unexpected validation errors: '.PHP_EOL.PHP_EOL.
880
                json_encode($errors, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)
881
            );
882
        }
883
 
884
        foreach (Arr::wrap($keys) as $key) {
885
            PHPUnit::assertFalse(
886
                isset($errors[$key]),
887
                "Found unexpected validation error for key: '{$key}'"
888
            );
889
        }
890
 
891
        return $this;
892
    }
893
 
894
    /**
399 lars 895
     * Assert that the given key is a JSON array.
896
     *
897
     * @param $key
898
     * @return $this
899
     */
900
    public function assertJsonIsArray($key = null)
901
    {
902
        $data = $this->json($key);
903
 
904
        $encodedData = json_encode($data);
905
 
906
        PHPUnit::assertTrue(
907
            is_array($data)
908
            && str_starts_with($encodedData, '[')
909
            && str_ends_with($encodedData, ']')
910
        );
911
 
912
        return $this;
913
    }
914
 
915
    /**
916
     * Assert that the given key is a JSON object.
917
     *
918
     * @param $key
919
     * @return $this
920
     */
921
    public function assertJsonIsObject($key = null)
922
    {
923
        $data = $this->json($key);
924
 
925
        $encodedData = json_encode($data);
926
 
927
        PHPUnit::assertTrue(
928
            is_array($data)
929
            && str_starts_with($encodedData, '{')
930
            && str_ends_with($encodedData, '}')
931
        );
932
 
933
        return $this;
934
    }
935
 
936
    /**
148 lars 937
     * Validate and return the decoded response JSON.
938
     *
939
     * @return \Illuminate\Testing\AssertableJsonString
940
     *
941
     * @throws \Throwable
942
     */
943
    public function decodeResponseJson()
944
    {
945
        $testJson = new AssertableJsonString($this->getContent());
946
 
947
        $decodedResponse = $testJson->json();
948
 
949
        if (is_null($decodedResponse) || $decodedResponse === false) {
950
            if ($this->exception) {
951
                throw $this->exception;
952
            } else {
953
                PHPUnit::fail('Invalid JSON was returned from the route.');
954
            }
955
        }
956
 
957
        return $testJson;
958
    }
959
 
960
    /**
961
     * Validate and return the decoded response JSON.
962
     *
963
     * @param  string|null  $key
964
     * @return mixed
965
     */
966
    public function json($key = null)
967
    {
968
        return $this->decodeResponseJson()->json($key);
969
    }
970
 
971
    /**
972
     * Get the JSON decoded body of the response as a collection.
973
     *
974
     * @param  string|null  $key
975
     * @return \Illuminate\Support\Collection
976
     */
977
    public function collect($key = null)
978
    {
979
        return Collection::make($this->json($key));
980
    }
981
 
982
    /**
983
     * Assert that the response view equals the given value.
984
     *
985
     * @param  string  $value
986
     * @return $this
987
     */
988
    public function assertViewIs($value)
989
    {
990
        $this->ensureResponseHasView();
991
 
992
        PHPUnit::assertEquals($value, $this->original->name());
993
 
994
        return $this;
995
    }
996
 
997
    /**
998
     * Assert that the response view has a given piece of bound data.
999
     *
1000
     * @param  string|array  $key
1001
     * @param  mixed  $value
1002
     * @return $this
1003
     */
1004
    public function assertViewHas($key, $value = null)
1005
    {
1006
        if (is_array($key)) {
1007
            return $this->assertViewHasAll($key);
1008
        }
1009
 
1010
        $this->ensureResponseHasView();
1011
 
1012
        if (is_null($value)) {
1013
            PHPUnit::assertTrue(Arr::has($this->original->gatherData(), $key));
1014
        } elseif ($value instanceof Closure) {
1015
            PHPUnit::assertTrue($value(Arr::get($this->original->gatherData(), $key)));
1016
        } elseif ($value instanceof Model) {
1017
            PHPUnit::assertTrue($value->is(Arr::get($this->original->gatherData(), $key)));
1018
        } elseif ($value instanceof EloquentCollection) {
1019
            $actual = Arr::get($this->original->gatherData(), $key);
1020
 
1021
            PHPUnit::assertInstanceOf(EloquentCollection::class, $actual);
1022
            PHPUnit::assertSameSize($value, $actual);
1023
 
1024
            $value->each(fn ($item, $index) => PHPUnit::assertTrue($actual->get($index)->is($item)));
1025
        } else {
1026
            PHPUnit::assertEquals($value, Arr::get($this->original->gatherData(), $key));
1027
        }
1028
 
1029
        return $this;
1030
    }
1031
 
1032
    /**
1033
     * Assert that the response view has a given list of bound data.
1034
     *
1035
     * @param  array  $bindings
1036
     * @return $this
1037
     */
1038
    public function assertViewHasAll(array $bindings)
1039
    {
1040
        foreach ($bindings as $key => $value) {
1041
            if (is_int($key)) {
1042
                $this->assertViewHas($value);
1043
            } else {
1044
                $this->assertViewHas($key, $value);
1045
            }
1046
        }
1047
 
1048
        return $this;
1049
    }
1050
 
1051
    /**
1052
     * Get a piece of data from the original view.
1053
     *
1054
     * @param  string  $key
1055
     * @return mixed
1056
     */
1057
    public function viewData($key)
1058
    {
1059
        $this->ensureResponseHasView();
1060
 
1061
        return $this->original->gatherData()[$key];
1062
    }
1063
 
1064
    /**
1065
     * Assert that the response view is missing a piece of bound data.
1066
     *
1067
     * @param  string  $key
1068
     * @return $this
1069
     */
1070
    public function assertViewMissing($key)
1071
    {
1072
        $this->ensureResponseHasView();
1073
 
1074
        PHPUnit::assertFalse(Arr::has($this->original->gatherData(), $key));
1075
 
1076
        return $this;
1077
    }
1078
 
1079
    /**
1080
     * Ensure that the response has a view as its original content.
1081
     *
1082
     * @return $this
1083
     */
1084
    protected function ensureResponseHasView()
1085
    {
1086
        if (! $this->responseHasView()) {
1087
            return PHPUnit::fail('The response is not a view.');
1088
        }
1089
 
1090
        return $this;
1091
    }
1092
 
1093
    /**
1094
     * Determine if the original response is a view.
1095
     *
1096
     * @return bool
1097
     */
1098
    protected function responseHasView()
1099
    {
1100
        return isset($this->original) && $this->original instanceof View;
1101
    }
1102
 
1103
    /**
1104
     * Assert that the given keys do not have validation errors.
1105
     *
1106
     * @param  string|array|null  $keys
1107
     * @param  string  $errorBag
1108
     * @param  string  $responseKey
1109
     * @return $this
1110
     */
1111
    public function assertValid($keys = null, $errorBag = 'default', $responseKey = 'errors')
1112
    {
1113
        if ($this->baseResponse->headers->get('Content-Type') === 'application/json') {
1114
            return $this->assertJsonMissingValidationErrors($keys, $responseKey);
1115
        }
1116
 
1117
        if ($this->session()->get('errors')) {
1118
            $errors = $this->session()->get('errors')->getBag($errorBag)->getMessages();
1119
        } else {
1120
            $errors = [];
1121
        }
1122
 
1123
        if (empty($errors)) {
1124
            PHPUnit::assertTrue(true);
1125
 
1126
            return $this;
1127
        }
1128
 
1129
        if (is_null($keys) && count($errors) > 0) {
1130
            PHPUnit::fail(
1131
                'Response has unexpected validation errors: '.PHP_EOL.PHP_EOL.
1132
                json_encode($errors, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)
1133
            );
1134
        }
1135
 
1136
        foreach (Arr::wrap($keys) as $key) {
1137
            PHPUnit::assertFalse(
1138
                isset($errors[$key]),
1139
                "Found unexpected validation error for key: '{$key}'"
1140
            );
1141
        }
1142
 
1143
        return $this;
1144
    }
1145
 
1146
    /**
1147
     * Assert that the response has the given validation errors.
1148
     *
1149
     * @param  string|array|null  $errors
1150
     * @param  string  $errorBag
1151
     * @param  string  $responseKey
1152
     * @return $this
1153
     */
1154
    public function assertInvalid($errors = null,
1155
                                  $errorBag = 'default',
1156
                                  $responseKey = 'errors')
1157
    {
1158
        if ($this->baseResponse->headers->get('Content-Type') === 'application/json') {
1159
            return $this->assertJsonValidationErrors($errors, $responseKey);
1160
        }
1161
 
1162
        $this->assertSessionHas('errors');
1163
 
1164
        $sessionErrors = $this->session()->get('errors')->getBag($errorBag)->getMessages();
1165
 
1166
        $errorMessage = $sessionErrors
1167
                ? 'Response has the following validation errors in the session:'.
1168
                        PHP_EOL.PHP_EOL.json_encode($sessionErrors, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE).PHP_EOL
1169
                : 'Response does not have validation errors in the session.';
1170
 
1171
        foreach (Arr::wrap($errors) as $key => $value) {
1172
            PHPUnit::assertArrayHasKey(
1173
                (is_int($key)) ? $value : $key,
1174
                $sessionErrors,
1175
                "Failed to find a validation error in session for key: '{$value}'".PHP_EOL.PHP_EOL.$errorMessage
1176
            );
1177
 
1178
            if (! is_int($key)) {
1179
                $hasError = false;
1180
 
1181
                foreach (Arr::wrap($sessionErrors[$key]) as $sessionErrorMessage) {
1182
                    if (Str::contains($sessionErrorMessage, $value)) {
1183
                        $hasError = true;
1184
 
1185
                        break;
1186
                    }
1187
                }
1188
 
1189
                if (! $hasError) {
1190
                    PHPUnit::fail(
1191
                        "Failed to find a validation error for key and message: '$key' => '$value'".PHP_EOL.PHP_EOL.$errorMessage
1192
                    );
1193
                }
1194
            }
1195
        }
1196
 
1197
        return $this;
1198
    }
1199
 
1200
    /**
1201
     * Assert that the session has a given value.
1202
     *
1203
     * @param  string|array  $key
1204
     * @param  mixed  $value
1205
     * @return $this
1206
     */
1207
    public function assertSessionHas($key, $value = null)
1208
    {
1209
        if (is_array($key)) {
1210
            return $this->assertSessionHasAll($key);
1211
        }
1212
 
1213
        if (is_null($value)) {
1214
            PHPUnit::assertTrue(
1215
                $this->session()->has($key),
1216
                "Session is missing expected key [{$key}]."
1217
            );
1218
        } elseif ($value instanceof Closure) {
1219
            PHPUnit::assertTrue($value($this->session()->get($key)));
1220
        } else {
1221
            PHPUnit::assertEquals($value, $this->session()->get($key));
1222
        }
1223
 
1224
        return $this;
1225
    }
1226
 
1227
    /**
1228
     * Assert that the session has a given list of values.
1229
     *
1230
     * @param  array  $bindings
1231
     * @return $this
1232
     */
1233
    public function assertSessionHasAll(array $bindings)
1234
    {
1235
        foreach ($bindings as $key => $value) {
1236
            if (is_int($key)) {
1237
                $this->assertSessionHas($value);
1238
            } else {
1239
                $this->assertSessionHas($key, $value);
1240
            }
1241
        }
1242
 
1243
        return $this;
1244
    }
1245
 
1246
    /**
1247
     * Assert that the session has a given value in the flashed input array.
1248
     *
1249
     * @param  string|array  $key
1250
     * @param  mixed  $value
1251
     * @return $this
1252
     */
1253
    public function assertSessionHasInput($key, $value = null)
1254
    {
1255
        if (is_array($key)) {
1256
            foreach ($key as $k => $v) {
1257
                if (is_int($k)) {
1258
                    $this->assertSessionHasInput($v);
1259
                } else {
1260
                    $this->assertSessionHasInput($k, $v);
1261
                }
1262
            }
1263
 
1264
            return $this;
1265
        }
1266
 
1267
        if (is_null($value)) {
1268
            PHPUnit::assertTrue(
1269
                $this->session()->hasOldInput($key),
1270
                "Session is missing expected key [{$key}]."
1271
            );
1272
        } elseif ($value instanceof Closure) {
1273
            PHPUnit::assertTrue($value($this->session()->getOldInput($key)));
1274
        } else {
1275
            PHPUnit::assertEquals($value, $this->session()->getOldInput($key));
1276
        }
1277
 
1278
        return $this;
1279
    }
1280
 
1281
    /**
1282
     * Assert that the session has the given errors.
1283
     *
1284
     * @param  string|array  $keys
1285
     * @param  mixed  $format
1286
     * @param  string  $errorBag
1287
     * @return $this
1288
     */
1289
    public function assertSessionHasErrors($keys = [], $format = null, $errorBag = 'default')
1290
    {
1291
        $this->assertSessionHas('errors');
1292
 
1293
        $keys = (array) $keys;
1294
 
1295
        $errors = $this->session()->get('errors')->getBag($errorBag);
1296
 
1297
        foreach ($keys as $key => $value) {
1298
            if (is_int($key)) {
1299
                PHPUnit::assertTrue($errors->has($value), "Session missing error: $value");
1300
            } else {
1301
                PHPUnit::assertContains(is_bool($value) ? (string) $value : $value, $errors->get($key, $format));
1302
            }
1303
        }
1304
 
1305
        return $this;
1306
    }
1307
 
1308
    /**
1309
     * Assert that the session is missing the given errors.
1310
     *
1311
     * @param  string|array  $keys
1312
     * @param  string|null  $format
1313
     * @param  string  $errorBag
1314
     * @return $this
1315
     */
1316
    public function assertSessionDoesntHaveErrors($keys = [], $format = null, $errorBag = 'default')
1317
    {
1318
        $keys = (array) $keys;
1319
 
1320
        if (empty($keys)) {
1321
            return $this->assertSessionHasNoErrors();
1322
        }
1323
 
1324
        if (is_null($this->session()->get('errors'))) {
1325
            PHPUnit::assertTrue(true);
1326
 
1327
            return $this;
1328
        }
1329
 
1330
        $errors = $this->session()->get('errors')->getBag($errorBag);
1331
 
1332
        foreach ($keys as $key => $value) {
1333
            if (is_int($key)) {
1334
                PHPUnit::assertFalse($errors->has($value), "Session has unexpected error: $value");
1335
            } else {
1336
                PHPUnit::assertNotContains($value, $errors->get($key, $format));
1337
            }
1338
        }
1339
 
1340
        return $this;
1341
    }
1342
 
1343
    /**
1344
     * Assert that the session has no errors.
1345
     *
1346
     * @return $this
1347
     */
1348
    public function assertSessionHasNoErrors()
1349
    {
1350
        $hasErrors = $this->session()->has('errors');
1351
 
1352
        PHPUnit::assertFalse(
1353
            $hasErrors,
1354
            'Session has unexpected errors: '.PHP_EOL.PHP_EOL.
1355
            json_encode((function () use ($hasErrors) {
1356
                $errors = [];
1357
 
1358
                $sessionErrors = $this->session()->get('errors');
1359
 
1360
                if ($hasErrors && is_a($sessionErrors, ViewErrorBag::class)) {
1361
                    foreach ($sessionErrors->getBags() as $bag => $messages) {
1362
                        if (is_a($messages, MessageBag::class)) {
1363
                            $errors[$bag] = $messages->all();
1364
                        }
1365
                    }
1366
                }
1367
 
1368
                return $errors;
1369
            })(), JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE),
1370
        );
1371
 
1372
        return $this;
1373
    }
1374
 
1375
    /**
1376
     * Assert that the session has the given errors.
1377
     *
1378
     * @param  string  $errorBag
1379
     * @param  string|array  $keys
1380
     * @param  mixed  $format
1381
     * @return $this
1382
     */
1383
    public function assertSessionHasErrorsIn($errorBag, $keys = [], $format = null)
1384
    {
1385
        return $this->assertSessionHasErrors($keys, $format, $errorBag);
1386
    }
1387
 
1388
    /**
1389
     * Assert that the session does not have a given key.
1390
     *
1391
     * @param  string|array  $key
1392
     * @return $this
1393
     */
1394
    public function assertSessionMissing($key)
1395
    {
1396
        if (is_array($key)) {
1397
            foreach ($key as $value) {
1398
                $this->assertSessionMissing($value);
1399
            }
1400
        } else {
1401
            PHPUnit::assertFalse(
1402
                $this->session()->has($key),
1403
                "Session has unexpected key [{$key}]."
1404
            );
1405
        }
1406
 
1407
        return $this;
1408
    }
1409
 
1410
    /**
1411
     * Get the current session store.
1412
     *
1413
     * @return \Illuminate\Session\Store
1414
     */
1415
    protected function session()
1416
    {
1417
        $session = app('session.store');
1418
 
1419
        if (! $session->isStarted()) {
1420
            $session->start();
1421
        }
1422
 
1423
        return $session;
1424
    }
1425
 
1426
    /**
1427
     * Dump the content from the response and end the script.
1428
     *
1429
     * @return never
1430
     */
1431
    public function dd()
1432
    {
1433
        $this->dump();
1434
 
1435
        exit(1);
1436
    }
1437
 
1438
    /**
1439
     * Dump the headers from the response and end the script.
1440
     *
1441
     * @return never
1442
     */
1443
    public function ddHeaders()
1444
    {
1445
        $this->dumpHeaders();
1446
 
1447
        exit(1);
1448
    }
1449
 
1450
    /**
1451
     * Dump the session from the response and end the script.
1452
     *
1453
     * @param  string|array  $keys
1454
     * @return never
1455
     */
1456
    public function ddSession($keys = [])
1457
    {
1458
        $this->dumpSession($keys);
1459
 
1460
        exit(1);
1461
    }
1462
 
1463
    /**
1464
     * Dump the content from the response.
1465
     *
1466
     * @param  string|null  $key
1467
     * @return $this
1468
     */
1469
    public function dump($key = null)
1470
    {
1471
        $content = $this->getContent();
1472
 
1473
        $json = json_decode($content);
1474
 
1475
        if (json_last_error() === JSON_ERROR_NONE) {
1476
            $content = $json;
1477
        }
1478
 
1479
        if (! is_null($key)) {
1480
            dump(data_get($content, $key));
1481
        } else {
1482
            dump($content);
1483
        }
1484
 
1485
        return $this;
1486
    }
1487
 
1488
    /**
1489
     * Dump the headers from the response.
1490
     *
1491
     * @return $this
1492
     */
1493
    public function dumpHeaders()
1494
    {
1495
        dump($this->headers->all());
1496
 
1497
        return $this;
1498
    }
1499
 
1500
    /**
1501
     * Dump the session from the response.
1502
     *
1503
     * @param  string|array  $keys
1504
     * @return $this
1505
     */
1506
    public function dumpSession($keys = [])
1507
    {
1508
        $keys = (array) $keys;
1509
 
1510
        if (empty($keys)) {
1511
            dump($this->session()->all());
1512
        } else {
1513
            dump($this->session()->only($keys));
1514
        }
1515
 
1516
        return $this;
1517
    }
1518
 
1519
    /**
1520
     * Get the streamed content from the response.
1521
     *
1522
     * @return string
1523
     */
1524
    public function streamedContent()
1525
    {
1526
        if (! is_null($this->streamedContent)) {
1527
            return $this->streamedContent;
1528
        }
1529
 
1530
        if (! $this->baseResponse instanceof StreamedResponse) {
1531
            PHPUnit::fail('The response is not a streamed response.');
1532
        }
1533
 
1534
        ob_start(function (string $buffer): string {
1535
            $this->streamedContent .= $buffer;
1536
 
1537
            return '';
1538
        });
1539
 
1540
        $this->sendContent();
1541
 
1542
        ob_end_clean();
1543
 
1544
        return $this->streamedContent;
1545
    }
1546
 
1547
    /**
1548
     * Set the previous exceptions on the response.
1549
     *
1550
     * @param  \Illuminate\Support\Collection  $exceptions
1551
     * @return $this
1552
     */
1553
    public function withExceptions(Collection $exceptions)
1554
    {
1555
        $this->exceptions = $exceptions;
1556
 
1557
        return $this;
1558
    }
1559
 
1560
    /**
1561
     * This method is called when test method did not execute successfully.
1562
     *
1563
     * @param  \Throwable  $exception
1564
     * @return \Throwable
1565
     */
1566
    public function transformNotSuccessfulException($exception)
1567
    {
1568
        if (! $exception instanceof ExpectationFailedException) {
1569
            return $exception;
1570
        }
1571
 
1572
        if ($lastException = $this->exceptions->last()) {
1573
            return $this->appendExceptionToException($lastException, $exception);
1574
        }
1575
 
1576
        if ($this->baseResponse instanceof RedirectResponse) {
1577
            $session = $this->baseResponse->getSession();
1578
 
1579
            if (! is_null($session) && $session->has('errors')) {
1580
                return $this->appendErrorsToException($session->get('errors')->all(), $exception);
1581
            }
1582
        }
1583
 
1584
        if ($this->baseResponse->headers->get('Content-Type') === 'application/json') {
1585
            $testJson = new AssertableJsonString($this->getContent());
1586
 
1587
            if (isset($testJson['errors'])) {
1588
                return $this->appendErrorsToException($testJson->json(), $exception, true);
1589
            }
1590
        }
1591
 
1592
        return $exception;
1593
    }
1594
 
1595
    /**
1596
     * Append an exception to the message of another exception.
1597
     *
1598
     * @param  \Throwable  $exceptionToAppend
1599
     * @param  \Throwable  $exception
1600
     * @return \Throwable
1601
     */
1602
    protected function appendExceptionToException($exceptionToAppend, $exception)
1603
    {
1604
        $exceptionMessage = $exceptionToAppend->getMessage();
1605
 
1606
        $exceptionToAppend = (string) $exceptionToAppend;
1607
 
1608
        $message = <<<"EOF"
1609
            The following exception occurred during the last request:
1610
 
1611
            $exceptionToAppend
1612
 
1613
            ----------------------------------------------------------------------------------
1614
 
1615
            $exceptionMessage
1616
            EOF;
1617
 
1618
        return $this->appendMessageToException($message, $exception);
1619
    }
1620
 
1621
    /**
1622
     * Append errors to an exception message.
1623
     *
1624
     * @param  array  $errors
1625
     * @param  \Throwable  $exception
1626
     * @param  bool  $json
1627
     * @return \Throwable
1628
     */
1629
    protected function appendErrorsToException($errors, $exception, $json = false)
1630
    {
1631
        $errors = $json
1632
            ? json_encode($errors, JSON_PRETTY_PRINT)
1633
            : implode(PHP_EOL, Arr::flatten($errors));
1634
 
1635
        // JSON error messages may already contain the errors, so we shouldn't duplicate them...
1636
        if (str_contains($exception->getMessage(), $errors)) {
1637
            return $exception;
1638
        }
1639
 
1640
        $message = <<<"EOF"
1641
            The following errors occurred during the last request:
1642
 
1643
            $errors
1644
            EOF;
1645
 
1646
        return $this->appendMessageToException($message, $exception);
1647
    }
1648
 
1649
    /**
1650
     * Append a message to an exception.
1651
     *
1652
     * @param  string  $message
1653
     * @param  \Throwable  $exception
1654
     * @return \Throwable
1655
     */
1656
    protected function appendMessageToException($message, $exception)
1657
    {
1658
        $property = new ReflectionProperty($exception, 'message');
1659
 
1660
        $property->setAccessible(true);
1661
 
1662
        $property->setValue(
1663
            $exception,
1664
            $exception->getMessage().PHP_EOL.PHP_EOL.$message.PHP_EOL
1665
        );
1666
 
1667
        return $exception;
1668
    }
1669
 
1670
    /**
1671
     * Dynamically access base response parameters.
1672
     *
1673
     * @param  string  $key
1674
     * @return mixed
1675
     */
1676
    public function __get($key)
1677
    {
1678
        return $this->baseResponse->{$key};
1679
    }
1680
 
1681
    /**
1682
     * Proxy isset() checks to the underlying base response.
1683
     *
1684
     * @param  string  $key
621 lars 1685
     * @return bool
148 lars 1686
     */
1687
    public function __isset($key)
1688
    {
1689
        return isset($this->baseResponse->{$key});
1690
    }
1691
 
1692
    /**
1693
     * Determine if the given offset exists.
1694
     *
1695
     * @param  string  $offset
1696
     * @return bool
1697
     */
1698
    public function offsetExists($offset): bool
1699
    {
1700
        return $this->responseHasView()
1701
                    ? isset($this->original->gatherData()[$offset])
1702
                    : isset($this->json()[$offset]);
1703
    }
1704
 
1705
    /**
1706
     * Get the value for a given offset.
1707
     *
1708
     * @param  string  $offset
1709
     * @return mixed
1710
     */
1711
    public function offsetGet($offset): mixed
1712
    {
1713
        return $this->responseHasView()
1714
                    ? $this->viewData($offset)
1715
                    : $this->json()[$offset];
1716
    }
1717
 
1718
    /**
1719
     * Set the value at the given offset.
1720
     *
1721
     * @param  string  $offset
1722
     * @param  mixed  $value
1723
     * @return void
1724
     *
1725
     * @throws \LogicException
1726
     */
1727
    public function offsetSet($offset, $value): void
1728
    {
1729
        throw new LogicException('Response data may not be mutated using array access.');
1730
    }
1731
 
1732
    /**
1733
     * Unset the value at the given offset.
1734
     *
1735
     * @param  string  $offset
1736
     * @return void
1737
     *
1738
     * @throws \LogicException
1739
     */
1740
    public function offsetUnset($offset): void
1741
    {
1742
        throw new LogicException('Response data may not be mutated using array access.');
1743
    }
1744
 
1745
    /**
1746
     * Handle dynamic calls into macros or pass missing methods to the base response.
1747
     *
1748
     * @param  string  $method
1749
     * @param  array  $args
1750
     * @return mixed
1751
     */
1752
    public function __call($method, $args)
1753
    {
1754
        if (static::hasMacro($method)) {
1755
            return $this->macroCall($method, $args);
1756
        }
1757
 
1758
        return $this->baseResponse->{$method}(...$args);
1759
    }
1760
}