Subversion-Projekte lars-tiefland.laravel_shop

Revision

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