|
2 | 2 |
|
3 | 3 | namespace Illuminate\Tests\Foundation;
|
4 | 4 |
|
| 5 | +use Closure; |
5 | 6 | use Exception;
|
| 7 | +use Illuminate\Cache\ArrayStore; |
| 8 | +use Illuminate\Cache\NullStore; |
| 9 | +use Illuminate\Cache\RateLimiter; |
| 10 | +use Illuminate\Cache\RateLimiting\Limit; |
| 11 | +use Illuminate\Cache\Repository; |
6 | 12 | use Illuminate\Config\Repository as Config;
|
7 | 13 | use Illuminate\Container\Container;
|
8 | 14 | use Illuminate\Contracts\Routing\ResponseFactory as ResponseFactoryContract;
|
|
15 | 21 | use Illuminate\Http\Request;
|
16 | 22 | use Illuminate\Routing\Redirector;
|
17 | 23 | use Illuminate\Routing\ResponseFactory;
|
| 24 | +use Illuminate\Support\Carbon; |
| 25 | +use Illuminate\Support\Lottery; |
18 | 26 | use Illuminate\Support\MessageBag;
|
19 | 27 | use Illuminate\Testing\Assert;
|
20 | 28 | use Illuminate\Validation\ValidationException;
|
@@ -470,6 +478,233 @@ public function testItCanDedupeExceptions()
|
470 | 478 |
|
471 | 479 | $this->assertSame($reported, [$one, $two]);
|
472 | 480 | }
|
| 481 | + |
| 482 | + public function testItDoesNotThrottleExceptionsByDefault() |
| 483 | + { |
| 484 | + $reported = []; |
| 485 | + $this->handler->reportable(function (\Throwable $e) use (&$reported) { |
| 486 | + $reported[] = $e; |
| 487 | + |
| 488 | + return false; |
| 489 | + }); |
| 490 | + |
| 491 | + for ($i = 0; $i < 100; $i++) { |
| 492 | + $this->handler->report(new RuntimeException("Exception {$i}")); |
| 493 | + } |
| 494 | + |
| 495 | + $this->assertCount(100, $reported); |
| 496 | + } |
| 497 | + |
| 498 | + public function testItDoesNotThrottleExceptionsWhenNullReturned() |
| 499 | + { |
| 500 | + $handler = new class($this->container) extends Handler |
| 501 | + { |
| 502 | + protected function throttle($e) |
| 503 | + { |
| 504 | + // |
| 505 | + } |
| 506 | + }; |
| 507 | + $reported = []; |
| 508 | + $handler->reportable(function (\Throwable $e) use (&$reported) { |
| 509 | + $reported[] = $e; |
| 510 | + |
| 511 | + return false; |
| 512 | + }); |
| 513 | + |
| 514 | + for ($i = 0; $i < 100; $i++) { |
| 515 | + $handler->report(new RuntimeException("Exception {$i}")); |
| 516 | + } |
| 517 | + |
| 518 | + $this->assertCount(100, $reported); |
| 519 | + } |
| 520 | + |
| 521 | + public function testItDoesNotThrottleExceptionsWhenUnlimitedLimit() |
| 522 | + { |
| 523 | + $handler = new class($this->container) extends Handler |
| 524 | + { |
| 525 | + protected function throttle($e) |
| 526 | + { |
| 527 | + return Limit::none(); |
| 528 | + } |
| 529 | + }; |
| 530 | + $reported = []; |
| 531 | + $handler->reportable(function (\Throwable $e) use (&$reported) { |
| 532 | + $reported[] = $e; |
| 533 | + |
| 534 | + return false; |
| 535 | + }); |
| 536 | + |
| 537 | + for ($i = 0; $i < 100; $i++) { |
| 538 | + $handler->report(new RuntimeException("Exception {$i}")); |
| 539 | + } |
| 540 | + |
| 541 | + $this->assertCount(100, $reported); |
| 542 | + } |
| 543 | + |
| 544 | + public function testItCanSampleExceptionsByClass() |
| 545 | + { |
| 546 | + $handler = new class($this->container) extends Handler |
| 547 | + { |
| 548 | + protected function throttle($e) |
| 549 | + { |
| 550 | + return match (true) { |
| 551 | + $e instanceof RuntimeException => Lottery::odds(2, 10), |
| 552 | + default => parent::throttle($e), |
| 553 | + }; |
| 554 | + } |
| 555 | + }; |
| 556 | + Lottery::forceResultWithSequence([ |
| 557 | + true, false, false, false, false, |
| 558 | + true, false, false, false, false, |
| 559 | + ]); |
| 560 | + $reported = []; |
| 561 | + $handler->reportable(function (\Throwable $e) use (&$reported) { |
| 562 | + $reported[] = $e; |
| 563 | + |
| 564 | + return false; |
| 565 | + }); |
| 566 | + |
| 567 | + for ($i = 0; $i < 10; $i++) { |
| 568 | + $handler->report(new Exception("Exception {$i}")); |
| 569 | + $handler->report(new RuntimeException("RuntimeException {$i}")); |
| 570 | + } |
| 571 | + |
| 572 | + [$runtimeExceptions, $baseExceptions] = collect($reported)->partition(fn ($e) => $e instanceof RuntimeException); |
| 573 | + $this->assertCount(10, $baseExceptions); |
| 574 | + $this->assertCount(2, $runtimeExceptions); |
| 575 | + } |
| 576 | + |
| 577 | + public function testItRescuesExceptionsWhileThrottlingAndReports() |
| 578 | + { |
| 579 | + $handler = new class($this->container) extends Handler |
| 580 | + { |
| 581 | + protected function throttle($e) |
| 582 | + { |
| 583 | + throw new RuntimeException('Something went wrong in the throttle method.'); |
| 584 | + } |
| 585 | + }; |
| 586 | + $reported = []; |
| 587 | + $handler->reportable(function (\Throwable $e) use (&$reported) { |
| 588 | + $reported[] = $e; |
| 589 | + |
| 590 | + return false; |
| 591 | + }); |
| 592 | + |
| 593 | + $handler->report(new Exception('Something in the app went wrong.')); |
| 594 | + |
| 595 | + $this->assertCount(1, $reported); |
| 596 | + $this->assertSame('Something in the app went wrong.', $reported[0]->getMessage()); |
| 597 | + } |
| 598 | + |
| 599 | + public function testItRescuesExceptionsIfThereIsAnIssueResolvingTheRateLimiter() |
| 600 | + { |
| 601 | + $handler = new class($this->container) extends Handler |
| 602 | + { |
| 603 | + protected function throttle($e) |
| 604 | + { |
| 605 | + return Limit::perDay(1); |
| 606 | + } |
| 607 | + }; |
| 608 | + $reported = []; |
| 609 | + $handler->reportable(function (\Throwable $e) use (&$reported) { |
| 610 | + $reported[] = $e; |
| 611 | + |
| 612 | + return false; |
| 613 | + }); |
| 614 | + $resolved = false; |
| 615 | + $this->container->bind(RateLimiter::class, function () use (&$resolved) { |
| 616 | + $resolved = true; |
| 617 | + |
| 618 | + throw new Exception('Error resolving rate limiter.'); |
| 619 | + }); |
| 620 | + |
| 621 | + $handler->report(new Exception('Something in the app went wrong.')); |
| 622 | + |
| 623 | + $this->assertTrue($resolved); |
| 624 | + $this->assertCount(1, $reported); |
| 625 | + $this->assertSame('Something in the app went wrong.', $reported[0]->getMessage()); |
| 626 | + } |
| 627 | + |
| 628 | + public function testItRescuesExceptionsIfThereIsAnIssueWithTheRateLimiter() |
| 629 | + { |
| 630 | + $handler = new class($this->container) extends Handler |
| 631 | + { |
| 632 | + protected function throttle($e) |
| 633 | + { |
| 634 | + return Limit::perDay(1); |
| 635 | + } |
| 636 | + }; |
| 637 | + $reported = []; |
| 638 | + $handler->reportable(function (\Throwable $e) use (&$reported) { |
| 639 | + $reported[] = $e; |
| 640 | + |
| 641 | + return false; |
| 642 | + }); |
| 643 | + $this->container->instance(RateLimiter::class, $limiter = new class(new Repository(new NullStore)) extends RateLimiter |
| 644 | + { |
| 645 | + public $attempted = false; |
| 646 | + |
| 647 | + public function attempt($key, $maxAttempts, Closure $callback, $decaySeconds = 60) |
| 648 | + { |
| 649 | + $this->attempted = true; |
| 650 | + |
| 651 | + throw new Exception('Unable to connect to Redis.'); |
| 652 | + } |
| 653 | + }); |
| 654 | + |
| 655 | + $handler->report(new Exception('Something in the app went wrong.')); |
| 656 | + |
| 657 | + $this->assertTrue($limiter->attempted); |
| 658 | + $this->assertCount(1, $reported); |
| 659 | + $this->assertSame('Something in the app went wrong.', $reported[0]->getMessage()); |
| 660 | + } |
| 661 | + |
| 662 | + public function testItCanRateLimitExceptions() |
| 663 | + { |
| 664 | + $handler = new class($this->container) extends Handler |
| 665 | + { |
| 666 | + protected function throttle($e) |
| 667 | + { |
| 668 | + return Limit::perMinute(7); |
| 669 | + } |
| 670 | + }; |
| 671 | + $reported = []; |
| 672 | + $handler->reportable(function (\Throwable $e) use (&$reported) { |
| 673 | + $reported[] = $e; |
| 674 | + |
| 675 | + return false; |
| 676 | + }); |
| 677 | + $this->container->instance(RateLimiter::class, $limiter = new class(new Repository(new ArrayStore)) extends RateLimiter |
| 678 | + { |
| 679 | + public $attempted = 0; |
| 680 | + |
| 681 | + public function attempt($key, $maxAttempts, Closure $callback, $decaySeconds = 60) |
| 682 | + { |
| 683 | + $this->attempted++; |
| 684 | + |
| 685 | + return parent::attempt(...func_get_args()); |
| 686 | + } |
| 687 | + }); |
| 688 | + Carbon::setTestNow(Carbon::now()->startOfDay()); |
| 689 | + |
| 690 | + for ($i = 0; $i < 100; $i++) { |
| 691 | + $handler->report(new Exception('Something in the app went wrong.')); |
| 692 | + } |
| 693 | + |
| 694 | + $this->assertSame(100, $limiter->attempted); |
| 695 | + $this->assertCount(7, $reported); |
| 696 | + $this->assertSame('Something in the app went wrong.', $reported[0]->getMessage()); |
| 697 | + |
| 698 | + Carbon::setTestNow(Carbon::now()->addMinute()); |
| 699 | + |
| 700 | + for ($i = 0; $i < 100; $i++) { |
| 701 | + $handler->report(new Exception('Something in the app went wrong.')); |
| 702 | + } |
| 703 | + |
| 704 | + $this->assertSame(200, $limiter->attempted); |
| 705 | + $this->assertCount(14, $reported); |
| 706 | + $this->assertSame('Something in the app went wrong.', $reported[0]->getMessage()); |
| 707 | + } |
473 | 708 | }
|
474 | 709 |
|
475 | 710 | class CustomException extends Exception
|
|
0 commit comments