mirror of
https://codeberg.org/PostERG/xamxam.git
synced 2026-06-25 08:09:18 +02:00
157 lines
5.0 KiB
PHP
157 lines
5.0 KiB
PHP
<?php
|
|
|
|
use PHPUnit\Framework\TestCase;
|
|
|
|
/**
|
|
* RateLimitExtendedTest — Integration tests for RateLimit using a temp directory.
|
|
*/
|
|
class RateLimitExtendedTest extends TestCase
|
|
{
|
|
private string $tmpDir;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
$this->tmpDir = sys_get_temp_dir() . '/xamxam_ratelimit_test_' . uniqid();
|
|
mkdir($this->tmpDir, 0755, true);
|
|
}
|
|
|
|
protected function tearDown(): void
|
|
{
|
|
$files = glob($this->tmpDir . '/*.json');
|
|
foreach ($files as $file) {
|
|
unlink($file);
|
|
}
|
|
rmdir($this->tmpDir);
|
|
}
|
|
|
|
private function newRateLimit(int $max = 5, int $window = 60): RateLimit
|
|
{
|
|
return new RateLimit($max, $window, $this->tmpDir);
|
|
}
|
|
|
|
// ── checkKey: per-key limits, not global ──────────────────────────────────
|
|
|
|
public function testCheckKeyCountsPerKey(): void
|
|
{
|
|
$rl = $this->newRateLimit(2, 60);
|
|
|
|
$this->assertTrue($rl->checkKey('key-a'));
|
|
$this->assertTrue($rl->checkKey('key-b'));
|
|
$this->assertTrue($rl->checkKey('key-a')); // second hit for key-a, still allowed
|
|
|
|
// key-a is now at limit (2)
|
|
$this->assertFalse($rl->checkKey('key-a'));
|
|
|
|
// key-b still has room
|
|
$this->assertTrue($rl->checkKey('key-b'));
|
|
$this->assertFalse($rl->checkKey('key-b'));
|
|
}
|
|
|
|
public function testCheckKeyDoesNotAffectDefaultCheck(): void
|
|
{
|
|
$rl = $this->newRateLimit(3, 60);
|
|
|
|
$rl->checkKey('separate-key');
|
|
$rl->checkKey('separate-key');
|
|
$rl->checkKey('separate-key');
|
|
$rl->checkKey('separate-key'); // exhausted for this key
|
|
|
|
// Default check (uses REMOTE_ADDR) should be unaffected by key-based tracking
|
|
$ipKey = md5($_SERVER['REMOTE_ADDR'] ?? 'unknown');
|
|
$ipFile = $this->tmpDir . '/' . $ipKey . '.json';
|
|
$this->assertFileDoesNotExist($ipFile);
|
|
}
|
|
|
|
// ── getRemaining: tied to client IP (REMOTE_ADDR) ─────────────────────────
|
|
|
|
public function testGetRemainingStartsAtMax(): void
|
|
{
|
|
$rl = $this->newRateLimit(5, 60);
|
|
|
|
$remaining = $rl->getRemaining();
|
|
$this->assertSame(5, $remaining);
|
|
}
|
|
|
|
public function testCheckDecrementsRemainingForSameIp(): void
|
|
{
|
|
$rl = $this->newRateLimit(5, 60);
|
|
|
|
// check() uses IP-based identifier. We need to use check() directly,
|
|
// not checkKey(), for getRemaining() to reflect usage.
|
|
$rl->check(); // hit 1
|
|
$this->assertSame(4, $rl->getRemaining());
|
|
|
|
$rl->check(); // hit 2
|
|
$this->assertSame(3, $rl->getRemaining());
|
|
}
|
|
|
|
public function testCheckAndCheckKeyAreIndependent(): void
|
|
{
|
|
$rl = $this->newRateLimit(5, 60);
|
|
|
|
// checkKey hits don't affect IP-based remaining
|
|
$rl->checkKey('some-key');
|
|
$rl->checkKey('some-key');
|
|
|
|
$this->assertSame(5, $rl->getRemaining());
|
|
}
|
|
|
|
// ── Consistent client identifier ─────────────────────────────────────────
|
|
|
|
public function testMultipleChecksFromSameClient(): void
|
|
{
|
|
$rl = $this->newRateLimit(1, 60);
|
|
|
|
// First check passes, second from same IP fails
|
|
$this->assertTrue($rl->check());
|
|
$this->assertFalse($rl->check());
|
|
}
|
|
|
|
public function testGetRemainingReturnsZeroAfterExhaustion(): void
|
|
{
|
|
$rl = $this->newRateLimit(1, 60);
|
|
|
|
$rl->check();
|
|
$this->assertSame(0, $rl->getRemaining());
|
|
}
|
|
|
|
// ── reset time ────────────────────────────────────────────────────────────
|
|
|
|
public function testGetResetTimeReturnsZeroWhenNoData(): void
|
|
{
|
|
$rl = $this->newRateLimit(5, 60);
|
|
$this->assertSame(0, $rl->getResetTime());
|
|
}
|
|
|
|
public function testGetResetTimePositiveAfterHits(): void
|
|
{
|
|
$rl = $this->newRateLimit(5, 60);
|
|
$rl->check(); // use IP-based check so file is written
|
|
|
|
$reset = $rl->getResetTime();
|
|
$this->assertGreaterThan(0, $reset);
|
|
$this->assertLessThanOrEqual(60, $reset);
|
|
}
|
|
|
|
// ── cleanup ───────────────────────────────────────────────────────────────
|
|
|
|
public function testCleanupRemovesOldFiles(): void
|
|
{
|
|
$rl = $this->newRateLimit(5, 60);
|
|
$rl->checkKey('cleanup-test');
|
|
|
|
// Touch the cache file to make it old
|
|
$files = glob($this->tmpDir . '/*.json');
|
|
$this->assertNotEmpty($files);
|
|
foreach ($files as $file) {
|
|
touch($file, time() - 90000); // 25 hours ago
|
|
}
|
|
|
|
$rl->cleanup();
|
|
|
|
// The old file should now be gone
|
|
$filesAfter = glob($this->tmpDir . '/*.json');
|
|
$this->assertEmpty($filesAfter);
|
|
}
|
|
}
|