wiki.techinc.nl/tests/phpunit/unit/includes/libs/WRStats/WRStatsRateLimiterTest.php
Tim Starling cc9af99364 WRStats: Add rate limiter support
Bug: T261744
Change-Id: If3e66491306f22650b31f22ccd60f7d9468316c7
2022-07-15 04:21:33 +00:00

148 lines
5.1 KiB
PHP

<?php
namespace Wikimedia\WRStats;
use PHPUnit\Framework\TestCase;
/**
* @covers \Wikimedia\WRStats\WRStatsRateLimiter
* @covers \Wikimedia\WRStats\LimitOperationResult
* @covers \Wikimedia\WRStats\LimitBatch
* @covers \Wikimedia\WRStats\LimitBatchResult
* @covers \Wikimedia\WRStats\LimitCondition
* @covers \Wikimedia\WRStats\LimitOperation
*/
class WRStatsRateLimiterTest extends TestCase {
public function testTryIncrBatch() {
$store = new ArrayStatsStore;
$conds = [
'cond1' => new LimitCondition( 3, 60 ),
];
$rateLimiter = new WRStatsRateLimiter( $store, $conds );
$rateLimiter->setCurrentTime( 1000 );
$actions = [ new LimitOperation( 'cond1' ) ];
$batchResult = $rateLimiter->tryIncrBatch( $actions );
$this->assertTrue( $batchResult->isAllowed() );
$this->assertSame( "LimitActionResult{1/3}",
$batchResult->getAllResults()[0]->dump() );
$this->assertTrue( $rateLimiter->tryIncrBatch( $actions )->isAllowed() );
$this->assertTrue( $rateLimiter->tryIncrBatch( $actions )->isAllowed() );
$batchResult = $rateLimiter->tryIncrBatch( $actions );
$this->assertFalse( $batchResult->isAllowed() );
$this->assertCount( 0, $batchResult->getPassedResults() );
$failed = $batchResult->getFailedResults();
$this->assertCount( 1, $failed );
$this->assertSame( "LimitActionResult{4/3}", $failed[0]->dump() );
$this->assertSame( 3, $failed[0]->prevTotal );
$this->assertSame( 4, $failed[0]->newTotal );
}
public function testMultiEntity() {
$store = new ArrayStatsStore;
$conds = [
'cond1' => new LimitCondition( 1, 60 ),
];
$entity1 = new LocalEntityKey( [ 'user_id', 1 ] );
$entity2 = new LocalEntityKey( [ 'user_id', 2 ] );
$rateLimiter = new WRStatsRateLimiter( $store, $conds );
$rateLimiter->setCurrentTime( 1000 );
$this->assertTrue(
$rateLimiter->tryIncrBatch( [
new LimitOperation( 'cond1', $entity1 )
] )->isAllowed()
);
$this->assertTrue(
$rateLimiter->tryIncrBatch( [
new LimitOperation( 'cond1', $entity2 )
] )->isAllowed()
);
$rateLimiter->incr( 'cond1', $entity1, 10 );
$rateLimiter->incr( 'cond1', $entity2, 20 );
$res = $rateLimiter->tryIncr( 'cond1', $entity1 );
$this->assertSame( 'LimitActionResult{12/1}', $res->dump() );
$res = $rateLimiter->tryIncr( 'cond1', $entity2 );
$this->assertSame( 'LimitActionResult{22/1}', $res->dump() );
}
public function testMultiEntityBatch() {
$store = new ArrayStatsStore;
$conds = [
'id' => new LimitCondition( 2, 60 ),
'ip' => new LimitCondition( 3, 60 )
];
$rateLimiter = new WRStatsRateLimiter( $store, $conds );
$rateLimiter->setCurrentTime( 1000 );
// Increment both metrics to 1
$res = $rateLimiter->createBatch()
->localOp( 'id', 1 )
->globalOp( 'ip', '127.0.0.1' )
->tryIncr();
$this->assertTrue( $res->isAllowed() );
$this->assertSame( 'LimitActionResult{1/2}',
$res->getAllResults()['id']->dump() );
$this->assertSame( 'LimitActionResult{1/3}',
$res->getAllResults()['ip']->dump() );
// Increment to 2, testing peek/incr
$batch = $rateLimiter->createBatch()
->localOp( 'id', 1 )
->globalOp( 'ip', '127.0.0.1' );
$this->assertTrue( $batch->peek()->isAllowed() );
$batch->incr();
// Increment to 3, in which the id metric is expected to fail
$res = $rateLimiter->createBatch()
->localOp( 'id', 1 )
->globalOp( 'ip', '127.0.0.1' )
->tryIncr();
$this->assertFalse( $res->isAllowed() );
$this->assertCount( 1, $res->getPassedResults() );
$failed = $res->getFailedResults();
$this->assertCount( 1, $failed );
$all = $res->getAllResults();
$this->assertSame( 'LimitActionResult{3/2}', $all['id']->dump() );
$this->assertSame( 'LimitActionResult{3/3}', $all['ip']->dump() );
}
public function testTimeDependent() {
$store = new ArrayStatsStore;
$conds = [
'cond1' => new LimitCondition( 3, 100 ),
];
$rateLimiter = new WRStatsRateLimiter( $store, $conds, 'WRLimit',
[ 'bucketCount' => 10 ] );
$rateLimiter->setCurrentTime( 1000 );
$this->assertTrue( $rateLimiter->tryIncr( 'cond1' )->isAllowed() );
$rateLimiter->setCurrentTime( 1010 );
$this->assertTrue( $rateLimiter->tryIncr( 'cond1' )->isAllowed() );
$rateLimiter->setCurrentTime( 1020 );
$this->assertTrue( $rateLimiter->tryIncr( 'cond1' )->isAllowed() );
$rateLimiter->setCurrentTime( 1090 );
$this->assertSame( 'LimitActionResult{4/3}',
$rateLimiter->peek( 'cond1' )->dump() );
$rateLimiter->setCurrentTime( 1100 );
// The range goes back to 1000 so the count is still 3
$this->assertSame( 3,
$rateLimiter->peek( 'cond1' )->prevTotal );
$rateLimiter->setCurrentTime( 1103 );
// The range goes back to 1000 so the count should be 2.7, rounded up to 3
$this->assertSame( 3,
$rateLimiter->peek( 'cond1' )->prevTotal );
$rateLimiter->setCurrentTime( 1109 );
// The range goes back to 1009 so the first bucket is interpolated and
// rounded down to zero, so the count is 2 and there is room for
// another action
$this->assertTrue( $rateLimiter->peek( 'cond1' )->isAllowed() );
// But not room for 2 actions
$this->assertSame( 'LimitActionResult{4/3}',
$rateLimiter->tryIncr( 'cond1', null, 2 )->dump() );
}
}