File: /home/xedaptot/ai.naniguide.com/tests/Unit/RateTrackerTest.php
<?php
use App\Library\RateTracker;
use App\Library\RateLimit;
use Carbon\Carbon;
test('Rollback just works', function () {
$file = '/tmp/test-rate-'.uniqid();
$tracker = new RateTracker($file);
$now = Carbon::now();
$oneMinuteAgo = $now->clone()->add('1 minute ago');
// 4 times
$tracker->count($oneMinuteAgo);
$tracker->count($oneMinuteAgo);
$tracker->count($now);
$tracker->count($now);
/*
* After 4 counts, the lock file should look like this
*
* 202307300013:2
* 202307300014:2
*
*/
$used = $tracker->getCreditsUsed($oneMinuteAgo, $now);
$linesCount = sizeof(file($tracker->getLockFilePath()));
expect($used)->toBe(4);
expect($linesCount)->toBe(2);
// Rollback 1
$tracker->rollback();
/*
* After rollback 1, the lock file should look like this
*
* 202307300013:2
* 202307300014:1
*
*/
$used = $tracker->getCreditsUsed($oneMinuteAgo, $now);
$linesCount = sizeof(file($tracker->getLockFilePath()));
expect($used)->toBe(3);
expect($linesCount)->toBe(2);
// Rollback 2
$tracker->rollback();
/*
* After rollback 2, the lock file should look like this
*
* 202307300013:2
*
*/
$used = $tracker->getCreditsUsed($oneMinuteAgo, $now);
$linesCount = sizeof(file($tracker->getLockFilePath()));
expect($used)->toBe(2);
expect($linesCount)->toBe(1);
});
test('Clean up tracker', function () {
$file = '/tmp/test-rate';
$tracker = new RateTracker($file);
$tracker->cleanup();
$this->assertEquals($tracker->getCreditsUsed(), 0);
$now = now();
$twoMinuteAgo = $now->copy()->add('2 minute ago');
$oneMinuteAgo = $now->copy()->add('1 minute ago');
// 2 time
$tracker->count($twoMinuteAgo);
$tracker->count($twoMinuteAgo);
// 3 times
$tracker->count($oneMinuteAgo);
$tracker->count($oneMinuteAgo);
$tracker->count($oneMinuteAgo);
// two: 2
// one: 3
$this->assertEquals($tracker->getCreditsUsed(), 5);
// 1 time
$tracker->count($now);
// two: 2
// one: 3
// now: 1
$this->assertEquals($tracker->getCreditsUsed(), 6);
// Clean up
$tracker->cleanup('2 minute'); // keep records during the last 2 minute, including 'two', 'one' and 'now'
$this->assertEquals($tracker->getCreditsUsed(), 6);
$tracker->cleanup('1 minute'); // keep records during the last 1 minute, including 'one' and 'now'
$this->assertEquals($tracker->getCreditsUsed(), 4);
});
test('Rollback truncates a shorter rewritten record from 10 to 9', function () {
$file = '/tmp/test-rate-'.uniqid();
$tracker = new RateTracker($file);
$now = Carbon::create(2026, 4, 21, 12, 30, 0);
for ($i = 0; $i < 10; $i++) {
$tracker->count($now);
}
$tracker->rollback();
expect(trim(file_get_contents($tracker->getLockFilePath())))->toBe('202604211230:9');
expect($tracker->getCreditsUsed($now->copy()->subMinute(), $now))->toBe(9);
});
test('Rollback truncates a shorter rewritten record from 100 to 99', function () {
$file = '/tmp/test-rate-'.uniqid();
$tracker = new RateTracker($file);
$now = Carbon::create(2026, 4, 21, 12, 30, 0);
for ($i = 0; $i < 100; $i++) {
$tracker->count($now);
}
$tracker->rollback();
expect(trim(file_get_contents($tracker->getLockFilePath())))->toBe('202604211230:99');
expect($tracker->getCreditsUsed($now->copy()->subMinute(), $now))->toBe(99);
});
test('Test checks limits without mutating the tracker file', function () {
$file = '/tmp/test-rate-'.uniqid();
$now = Carbon::create(2026, 4, 21, 12, 30, 0);
$tracker = new RateTracker($file, [
new RateLimit(5, 1, 'minute', '5 per minute'),
]);
for ($i = 0; $i < 3; $i++) {
$tracker->count($now);
}
$before = file_get_contents($tracker->getLockFilePath());
$tracker->test($now);
expect(file_get_contents($tracker->getLockFilePath()))->toBe($before);
expect(trim($before))->toBe('202604211230:3');
});