Add integration tests (Phase 2: DatabaseExtended, ShareLinkExtended, RateLimitExtended) and controller validation tests (Phase 3: ThesisCreate, ThesisEdit, AutofocusField)

This commit is contained in:
Pontoporeia
2026-05-20 01:51:41 +02:00
parent 7a4d0fafb2
commit 93625d09b5
10 changed files with 1543 additions and 8 deletions

View File

@@ -0,0 +1,156 @@
<?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);
}
}