Skip to content

Commit caeef85

Browse files
committed
Bug fixes and tests
1 parent 7c3212a commit caeef85

File tree

4 files changed

+164
-4
lines changed

4 files changed

+164
-4
lines changed

src/HeaderRateLimiter.php

+3-2
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,12 @@ final class HeaderRateLimiter implements RateLimiterInterface
3434

3535
public function __construct(
3636
CacheInterface $cache,
37+
string $cacheKey = self::CACHE_KEY,
3738
string $remainingHeader = 'X-RateLimit-Remaining',
3839
string $expiresAfterHeader = 'Retry-After'
3940
) {
4041
$this->cache = $cache;
41-
$this->cacheKey = self::CACHE_KEY;
42+
$this->cacheKey = $cacheKey;
4243
$this->remainingHeader = $remainingHeader;
4344
$this->expiresAfterHeader = $expiresAfterHeader;
4445
}
@@ -51,7 +52,7 @@ public function tooManyAttempts(RequestInterface $request): bool
5152
return false;
5253
}
5354

54-
return (int) $remaining === 0;
55+
return (int) $remaining <= 0;
5556
}
5657

5758
public function hit(RequestInterface $request, ResponseInterface $response): void

src/RateLimiter.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ public function tooManyAttempts(RequestInterface $request): bool
3838
continue;
3939
}
4040

41-
if ($remaining === 0) {
41+
if ($remaining <= 0) {
4242
return true;
4343
}
4444
}
@@ -56,7 +56,7 @@ public function hit(RequestInterface $request, ResponseInterface $response): voi
5656
$remaining = $limit->maxAttempts();
5757
}
5858

59-
$this->cache->set($key, --$remaining);
59+
$this->cache->set($key, --$remaining, $limit->expiresAfter());
6060
}
6161
}
6262
}

tests/MiddlewareTest.php

+94
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Fansipan\RateLimiter\Tests;
6+
7+
use Fansipan\ConnectorConfigurator;
8+
use Fansipan\GenericConnector;
9+
use Fansipan\Mock\MockClient;
10+
use Fansipan\Mock\MockResponse;
11+
use Fansipan\RateLimiter\Exception\RateLimitReachedException;
12+
use Fansipan\RateLimiter\HeaderRateLimiter;
13+
use Fansipan\RateLimiter\Limit;
14+
use Fansipan\RateLimiter\RateLimiter;
15+
use Fansipan\RateLimiter\ThrottleRequests;
16+
use PHPUnit\Framework\TestCase;
17+
use Symfony\Component\Cache\Adapter\ArrayAdapter;
18+
use Symfony\Component\Cache\Psr16Cache;
19+
20+
final class MiddlewareTest extends TestCase
21+
{
22+
/**
23+
* @var \Psr\SimpleCache\CacheInterface
24+
*/
25+
private $cache;
26+
27+
/**
28+
* @var GenericConnector
29+
*/
30+
private $connector;
31+
32+
protected function setUp(): void
33+
{
34+
parent::setUp();
35+
36+
$this->cache = new Psr16Cache(new ArrayAdapter());
37+
$this->connector = new GenericConnector();
38+
}
39+
40+
public function test_throttle_requests_middleware_with_rate_limiter(): void
41+
{
42+
$limiter = new RateLimiter($this->cache, [
43+
Limit::allow(2),
44+
]);
45+
46+
$connector = (new ConnectorConfigurator())
47+
->middleware(new ThrottleRequests($limiter), 'rate_limiter')
48+
->configure($this->connector->withClient(new MockClient()));
49+
50+
$this->assertCount(1, $connector->middleware());
51+
52+
$response = $connector->send(new DummyRequest());
53+
54+
$this->assertTrue($response->ok());
55+
56+
$connector->send(new DummyRequest());
57+
58+
$this->expectException(RateLimitReachedException::class);
59+
60+
$connector->send(new DummyRequest());
61+
}
62+
63+
public function test_throttle_requests_middleware_with_header_rate_limiter(): void
64+
{
65+
$limiter = new HeaderRateLimiter($this->cache);
66+
$response = MockResponse::create('');
67+
68+
$responses = static function (int $remaining) use ($response) {
69+
for ($i = --$remaining; $i >= 0; --$i) {
70+
yield $response->withAddedHeader('X-RateLimit-Remaining', $i);
71+
}
72+
};
73+
74+
$client = new MockClient($responses(2));
75+
76+
$connector = (new ConnectorConfigurator())
77+
->middleware(new ThrottleRequests($limiter), 'rate_limiter')
78+
->configure($this->connector->withClient($client));
79+
80+
$this->assertCount(1, $connector->middleware());
81+
82+
$response = $connector->send(new DummyRequest());
83+
84+
$this->assertTrue($response->ok());
85+
$this->assertSame(1, (int) $response->header('X-RateLimit-Remaining'));
86+
87+
$connector->send(new DummyRequest());
88+
89+
$this->expectException(RateLimitReachedException::class);
90+
91+
$connector->send(new DummyRequest());
92+
dump($this->cache);
93+
}
94+
}

tests/RateLimiterTest.php

+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Fansipan\RateLimiter\Tests;
6+
7+
use Fansipan\Mock\MockResponse;
8+
use Fansipan\RateLimiter\HeaderRateLimiter;
9+
use Fansipan\RateLimiter\Limit;
10+
use Fansipan\RateLimiter\RateLimiter;
11+
use Http\Discovery\Psr17FactoryDiscovery;
12+
use PHPUnit\Framework\TestCase;
13+
use Symfony\Component\Cache\Adapter\ArrayAdapter;
14+
use Symfony\Component\Cache\Psr16Cache;
15+
16+
final class RateLimiterTest extends TestCase
17+
{
18+
/**
19+
* @var \Psr\SimpleCache\CacheInterface
20+
*/
21+
private $cache;
22+
23+
protected function setUp(): void
24+
{
25+
parent::setUp();
26+
27+
$this->cache = new Psr16Cache(new ArrayAdapter());
28+
}
29+
30+
public function test_rate_limiter(): void
31+
{
32+
$limiter = new RateLimiter($this->cache, [
33+
Limit::allow(1),
34+
]);
35+
36+
$request = Psr17FactoryDiscovery::findRequestFactory()->createRequest('GET', 'http://localhost');
37+
38+
$this->assertFalse($limiter->tooManyAttempts($request));
39+
40+
$limiter->hit($request, MockResponse::create(''));
41+
42+
$this->assertTrue($limiter->tooManyAttempts($request));
43+
}
44+
45+
public function test_header_rate_limit(): void
46+
{
47+
$limiter = new HeaderRateLimiter($this->cache);
48+
49+
$request = Psr17FactoryDiscovery::findRequestFactory()->createRequest('GET', 'http://localhost');
50+
51+
$this->assertFalse($limiter->tooManyAttempts($request));
52+
53+
$limiter->hit($request, MockResponse::create('', 200, [
54+
'X-RateLimit-Remaining' => 1,
55+
]));
56+
57+
$this->assertFalse($limiter->tooManyAttempts($request));
58+
59+
$limiter->hit($request, MockResponse::create('', 200, [
60+
'X-RateLimit-Remaining' => 0,
61+
]));
62+
63+
$this->assertTrue($limiter->tooManyAttempts($request));
64+
}
65+
}

0 commit comments

Comments
 (0)