Skip to content

Commit c803fe1

Browse files
test: Add unit tests for RateLimitMiddleware
1 parent f37c59f commit c803fe1

File tree

1 file changed

+96
-0
lines changed

1 file changed

+96
-0
lines changed
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
<?php
2+
3+
namespace Tests\Unit\Middleware;
4+
5+
use Fyennyi\AsyncCache\CacheOptions;
6+
use Fyennyi\AsyncCache\Core\CacheContext;
7+
use Fyennyi\AsyncCache\Enum\CacheStatus;
8+
use Fyennyi\AsyncCache\Middleware\RateLimitMiddleware;
9+
use Fyennyi\AsyncCache\Model\CachedItem;
10+
use Fyennyi\AsyncCache\Exception\RateLimitException;
11+
use PHPUnit\Framework\MockObject\MockObject;
12+
use PHPUnit\Framework\TestCase;
13+
use Psr\EventDispatcher\EventDispatcherInterface;
14+
use Psr\Log\LoggerInterface;
15+
use Symfony\Component\Clock\MockClock;
16+
use Symfony\Component\RateLimiter\LimiterInterface;
17+
use Symfony\Component\RateLimiter\RateLimit;
18+
use Symfony\Component\RateLimiter\RateLimiterFactoryInterface;
19+
use function React\Async\await;
20+
use function React\Promise\resolve;
21+
22+
class RateLimitMiddlewareTest extends TestCase
23+
{
24+
private MockObject|LoggerInterface $logger;
25+
private MockObject|EventDispatcherInterface $dispatcher;
26+
private MockObject|RateLimiterFactoryInterface $factory;
27+
private MockObject|LimiterInterface $limiter;
28+
private MockClock $clock;
29+
private RateLimitMiddleware $middleware;
30+
31+
protected function setUp() : void
32+
{
33+
$this->logger = $this->createMock(LoggerInterface::class);
34+
$this->dispatcher = $this->createMock(EventDispatcherInterface::class);
35+
$this->factory = $this->createMock(RateLimiterFactoryInterface::class);
36+
$this->limiter = $this->createMock(LimiterInterface::class);
37+
$this->clock = new MockClock();
38+
$this->middleware = new RateLimitMiddleware($this->factory, $this->logger, $this->dispatcher);
39+
}
40+
41+
public function testBypassesIfNoLimiterOrNoKey() : void
42+
{
43+
$context = new CacheContext('k', fn () => null, new CacheOptions(), $this->clock);
44+
$next = fn () => resolve('ok');
45+
46+
$this->assertSame('ok', await($this->middleware->handle($context, $next)));
47+
48+
$middlewareNoLimiter = new RateLimitMiddleware(null, $this->logger);
49+
$this->assertSame('ok', await($middlewareNoLimiter->handle($context, $next)));
50+
}
51+
52+
public function testCallsNextIfAccepted() : void
53+
{
54+
$key = 'api_limit';
55+
$context = new CacheContext('k', fn () => null, new CacheOptions(rate_limit_key: $key), $this->clock);
56+
$next = fn () => resolve('ok');
57+
58+
$rateLimit = new RateLimit(10, new \DateTimeImmutable(), true, 10);
59+
60+
$this->factory->expects($this->once())->method('create')->with($key)->willReturn($this->limiter);
61+
$this->limiter->expects($this->once())->method('consume')->willReturn($rateLimit);
62+
63+
$this->assertSame('ok', await($this->middleware->handle($context, $next)));
64+
}
65+
66+
public function testThrowsExceptionIfExceededAndNoStale() : void
67+
{
68+
$key = 'api_limit';
69+
$context = new CacheContext('k', fn () => null, new CacheOptions(rate_limit_key: $key, serve_stale_if_limited: false), $this->clock);
70+
$next = fn () => resolve('ok');
71+
72+
$rateLimit = new RateLimit(0, new \DateTimeImmutable(), false, 10);
73+
74+
$this->factory->method('create')->willReturn($this->limiter);
75+
$this->limiter->method('consume')->willReturn($rateLimit);
76+
77+
$this->expectException(RateLimitException::class);
78+
await($this->middleware->handle($context, $next));
79+
}
80+
81+
public function testReturnsStaleIfExceededAndConfigured() : void
82+
{
83+
$key = 'api_limit';
84+
$item = new CachedItem('stale_data', $this->clock->now()->getTimestamp() - 10);
85+
$context = new CacheContext('k', fn () => null, new CacheOptions(rate_limit_key: $key, serve_stale_if_limited: true), $this->clock);
86+
$context->stale_item = $item;
87+
$next = fn () => resolve('should_not_be_called');
88+
89+
$rateLimit = new RateLimit(0, new \DateTimeImmutable(), false, 10);
90+
91+
$this->factory->method('create')->willReturn($this->limiter);
92+
$this->limiter->method('consume')->willReturn($rateLimit);
93+
94+
$this->assertSame('stale_data', await($this->middleware->handle($context, $next)));
95+
}
96+
}

0 commit comments

Comments
 (0)