RateLimitedTest.php 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  1. <?php
  2. namespace Illuminate\Tests\Integration\Queue;
  3. use Illuminate\Bus\Dispatcher;
  4. use Illuminate\Bus\Queueable;
  5. use Illuminate\Cache\RateLimiter;
  6. use Illuminate\Cache\RateLimiting\Limit;
  7. use Illuminate\Contracts\Cache\Repository as Cache;
  8. use Illuminate\Contracts\Queue\Job;
  9. use Illuminate\Queue\CallQueuedHandler;
  10. use Illuminate\Queue\InteractsWithQueue;
  11. use Illuminate\Queue\Middleware\RateLimited;
  12. use Mockery as m;
  13. use Orchestra\Testbench\TestCase;
  14. class RateLimitedTest extends TestCase
  15. {
  16. protected function tearDown(): void
  17. {
  18. parent::tearDown();
  19. m::close();
  20. }
  21. public function testUnlimitedJobsAreExecuted()
  22. {
  23. $rateLimiter = $this->app->make(RateLimiter::class);
  24. $rateLimiter->for('test', function ($job) {
  25. return Limit::none();
  26. });
  27. $this->assertJobRanSuccessfully(RateLimitedTestJob::class);
  28. $this->assertJobRanSuccessfully(RateLimitedTestJob::class);
  29. }
  30. public function testRateLimitedJobsAreNotExecutedOnLimitReached2()
  31. {
  32. $cache = m::mock(Cache::class);
  33. $cache->shouldReceive('get')->andReturn(0, 1, null);
  34. $cache->shouldReceive('add')->andReturn(true, true);
  35. $cache->shouldReceive('increment')->andReturn(1);
  36. $cache->shouldReceive('has')->andReturn(true);
  37. $rateLimiter = new RateLimiter($cache);
  38. $this->app->instance(RateLimiter::class, $rateLimiter);
  39. $rateLimiter = $this->app->make(RateLimiter::class);
  40. $rateLimiter->for('test', function ($job) {
  41. return Limit::perHour(1);
  42. });
  43. $this->assertJobRanSuccessfully(RateLimitedTestJob::class);
  44. // Assert Job was released and released with a delay greater than 0
  45. RateLimitedTestJob::$handled = false;
  46. $instance = new CallQueuedHandler(new Dispatcher($this->app), $this->app);
  47. $job = m::mock(Job::class);
  48. $job->shouldReceive('hasFailed')->once()->andReturn(false);
  49. $job->shouldReceive('release')->once()->withArgs(function ($delay) {
  50. return $delay >= 0;
  51. });
  52. $job->shouldReceive('isReleased')->andReturn(true);
  53. $job->shouldReceive('isDeletedOrReleased')->once()->andReturn(true);
  54. $instance->call($job, [
  55. 'command' => serialize($command = new RateLimitedTestJob),
  56. ]);
  57. $this->assertFalse(RateLimitedTestJob::$handled);
  58. }
  59. public function testRateLimitedJobsAreNotExecutedOnLimitReached()
  60. {
  61. $rateLimiter = $this->app->make(RateLimiter::class);
  62. $rateLimiter->for('test', function ($job) {
  63. return Limit::perHour(1);
  64. });
  65. $this->assertJobRanSuccessfully(RateLimitedTestJob::class);
  66. $this->assertJobWasReleased(RateLimitedTestJob::class);
  67. }
  68. public function testRateLimitedJobsCanBeSkippedOnLimitReached()
  69. {
  70. $rateLimiter = $this->app->make(RateLimiter::class);
  71. $rateLimiter->for('test', function ($job) {
  72. return Limit::perHour(1);
  73. });
  74. $this->assertJobRanSuccessfully(RateLimitedDontReleaseTestJob::class);
  75. $this->assertJobWasSkipped(RateLimitedDontReleaseTestJob::class);
  76. }
  77. public function testJobsCanHaveConditionalRateLimits()
  78. {
  79. $rateLimiter = $this->app->make(RateLimiter::class);
  80. $rateLimiter->for('test', function ($job) {
  81. if ($job->isAdmin()) {
  82. return Limit::none();
  83. }
  84. return Limit::perHour(1);
  85. });
  86. $this->assertJobRanSuccessfully(AdminTestJob::class);
  87. $this->assertJobRanSuccessfully(AdminTestJob::class);
  88. $this->assertJobRanSuccessfully(NonAdminTestJob::class);
  89. $this->assertJobWasReleased(NonAdminTestJob::class);
  90. }
  91. public function testMiddlewareSerialization()
  92. {
  93. $rateLimited = new RateLimited('limiterName');
  94. $rateLimited->shouldRelease = false;
  95. $restoredRateLimited = unserialize(serialize($rateLimited));
  96. $fetch = (function (string $name) {
  97. return $this->{$name};
  98. })->bindTo($restoredRateLimited, RateLimited::class);
  99. $this->assertFalse($restoredRateLimited->shouldRelease);
  100. $this->assertSame('limiterName', $fetch('limiterName'));
  101. $this->assertInstanceOf(RateLimiter::class, $fetch('limiter'));
  102. }
  103. protected function assertJobRanSuccessfully($class)
  104. {
  105. $class::$handled = false;
  106. $instance = new CallQueuedHandler(new Dispatcher($this->app), $this->app);
  107. $job = m::mock(Job::class);
  108. $job->shouldReceive('hasFailed')->once()->andReturn(false);
  109. $job->shouldReceive('isReleased')->andReturn(false);
  110. $job->shouldReceive('isDeletedOrReleased')->once()->andReturn(false);
  111. $job->shouldReceive('delete')->once();
  112. $instance->call($job, [
  113. 'command' => serialize($command = new $class),
  114. ]);
  115. $this->assertTrue($class::$handled);
  116. }
  117. protected function assertJobWasReleased($class)
  118. {
  119. $class::$handled = false;
  120. $instance = new CallQueuedHandler(new Dispatcher($this->app), $this->app);
  121. $job = m::mock(Job::class);
  122. $job->shouldReceive('hasFailed')->once()->andReturn(false);
  123. $job->shouldReceive('release')->once();
  124. $job->shouldReceive('isReleased')->andReturn(true);
  125. $job->shouldReceive('isDeletedOrReleased')->once()->andReturn(true);
  126. $instance->call($job, [
  127. 'command' => serialize($command = new $class),
  128. ]);
  129. $this->assertFalse($class::$handled);
  130. }
  131. protected function assertJobWasSkipped($class)
  132. {
  133. $class::$handled = false;
  134. $instance = new CallQueuedHandler(new Dispatcher($this->app), $this->app);
  135. $job = m::mock(Job::class);
  136. $job->shouldReceive('hasFailed')->once()->andReturn(false);
  137. $job->shouldReceive('isReleased')->andReturn(false);
  138. $job->shouldReceive('isDeletedOrReleased')->once()->andReturn(false);
  139. $job->shouldReceive('delete')->once();
  140. $instance->call($job, [
  141. 'command' => serialize($command = new $class),
  142. ]);
  143. $this->assertFalse($class::$handled);
  144. }
  145. }
  146. class RateLimitedTestJob
  147. {
  148. use InteractsWithQueue, Queueable;
  149. public static $handled = false;
  150. public function handle()
  151. {
  152. static::$handled = true;
  153. }
  154. public function middleware()
  155. {
  156. return [new RateLimited('test')];
  157. }
  158. }
  159. class AdminTestJob extends RateLimitedTestJob
  160. {
  161. public function isAdmin()
  162. {
  163. return true;
  164. }
  165. }
  166. class NonAdminTestJob extends RateLimitedTestJob
  167. {
  168. public function isAdmin()
  169. {
  170. return false;
  171. }
  172. }
  173. class RateLimitedDontReleaseTestJob extends RateLimitedTestJob
  174. {
  175. public function middleware()
  176. {
  177. return [(new RateLimited('test'))->dontRelease()];
  178. }
  179. }