mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/AbuseFilter.git
synced 2024-11-23 21:53:35 +00:00
Use independent stats for emergency disable
Bug: T264629 Change-Id: I64b611243b6a4c136b82b09f2ccf588d1c3e3426
This commit is contained in:
parent
550a0936fc
commit
b8ac52c51c
|
@ -99,6 +99,13 @@ class AbuseFilterServices {
|
|||
return MediaWikiServices::getInstance()->getService( FilterLookup::SERVICE_NAME );
|
||||
}
|
||||
|
||||
/**
|
||||
* @return EmergencyCache
|
||||
*/
|
||||
public static function getEmergencyCache() : EmergencyCache {
|
||||
return MediaWikiServices::getInstance()->getService( EmergencyCache::SERVICE_NAME );
|
||||
}
|
||||
|
||||
/**
|
||||
* @return EmergencyWatcher
|
||||
*/
|
||||
|
|
140
includes/EmergencyCache.php
Normal file
140
includes/EmergencyCache.php
Normal file
|
@ -0,0 +1,140 @@
|
|||
<?php
|
||||
|
||||
namespace MediaWiki\Extension\AbuseFilter;
|
||||
|
||||
use BagOStuff;
|
||||
|
||||
/**
|
||||
* Helper class for EmergencyWatcher. Wrapper around cache which tracks hits of recently
|
||||
* modified filters.
|
||||
*/
|
||||
class EmergencyCache {
|
||||
|
||||
public const SERVICE_NAME = 'AbuseFilterEmergencyCache';
|
||||
|
||||
/** @var BagOStuff */
|
||||
private $stash;
|
||||
|
||||
/** @var int[] */
|
||||
private $ttlPerGroup;
|
||||
|
||||
/**
|
||||
* @param BagOStuff $stash
|
||||
* @param int[] $ttlPerGroup
|
||||
*/
|
||||
public function __construct( BagOStuff $stash, array $ttlPerGroup ) {
|
||||
$this->stash = $stash;
|
||||
$this->ttlPerGroup = $ttlPerGroup;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get recently modified filters in the group. Thanks to this, performance can be improved,
|
||||
* because only a small subset of filters will need an update.
|
||||
*
|
||||
* @param string $group
|
||||
* @return int[]
|
||||
*/
|
||||
public function getFiltersToCheckInGroup( string $group ) : array {
|
||||
$filterToExpiry = $this->stash->get( $this->createGroupKey( $group ) );
|
||||
if ( $filterToExpiry === false ) {
|
||||
return [];
|
||||
}
|
||||
$time = (int)round( $this->stash->getCurrentTime() );
|
||||
return array_keys( array_filter(
|
||||
$filterToExpiry,
|
||||
static function ( $exp ) use ( $time ) {
|
||||
return $exp > $time;
|
||||
}
|
||||
) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new entry in cache for a filter and update the entry for the group.
|
||||
* This method is usually called after the filter has been updated.
|
||||
*
|
||||
* @param int $filter
|
||||
* @param string $group
|
||||
* @return bool
|
||||
*/
|
||||
public function setNewForFilter( int $filter, string $group ) : bool {
|
||||
$ttl = $this->ttlPerGroup[$group] ?? $this->ttlPerGroup['default'];
|
||||
$expiry = (int)round( $this->stash->getCurrentTime() + $ttl );
|
||||
$this->stash->merge(
|
||||
$this->createGroupKey( $group ),
|
||||
function ( $cache, $key, $value ) use ( $filter, $expiry ) {
|
||||
if ( $value === false ) {
|
||||
$value = [];
|
||||
}
|
||||
// note that some filters may have already had their keys expired
|
||||
// we are currently filtering them out in getFiltersToCheckInGroup
|
||||
// but if necessary, it can be done here
|
||||
$value[$filter] = $expiry;
|
||||
return $value;
|
||||
},
|
||||
$expiry
|
||||
);
|
||||
return $this->stash->set(
|
||||
$this->createFilterKey( $filter ),
|
||||
[ 'total' => 0, 'matches' => 0, 'expiry' => $expiry ],
|
||||
$expiry
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Increase the filter's 'total' value by one and possibly also the 'matched' value.
|
||||
*
|
||||
* @param int $filter
|
||||
* @param bool $matched Whether the filter matched the action
|
||||
* @return bool
|
||||
*/
|
||||
public function incrementForFilter( int $filter, bool $matched ) : bool {
|
||||
return $this->stash->merge(
|
||||
$this->createFilterKey( $filter ),
|
||||
static function ( $cache, $key, $value, &$expiry ) use ( $matched ) {
|
||||
if ( $value === false ) {
|
||||
return false;
|
||||
}
|
||||
$value['total']++;
|
||||
if ( $matched ) {
|
||||
$value['matches']++;
|
||||
}
|
||||
// enforce the prior TTL
|
||||
$expiry = $value['expiry'];
|
||||
return $value;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the cache entry for the filter. Returns false when the key has already expired.
|
||||
* Otherwise it returns the entry formatted as [ 'total' => number of actions,
|
||||
* 'matches' => number of hits ] (since the last filter modification).
|
||||
*
|
||||
* @param int $filter
|
||||
* @return array|false
|
||||
*/
|
||||
public function getForFilter( int $filter ) {
|
||||
$value = $this->stash->get( $this->createFilterKey( $filter ) );
|
||||
if ( $value !== false ) {
|
||||
unset( $value['expiry'] );
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $group
|
||||
* @return string
|
||||
*/
|
||||
private function createGroupKey( string $group ) : string {
|
||||
return $this->stash->makeKey( 'abusefilter', 'emergency', 'group', $group );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $filter
|
||||
* @return string
|
||||
*/
|
||||
private function createFilterKey( int $filter ) : string {
|
||||
return $this->stash->makeKey( 'abusefilter', 'emergency', 'filter', $filter );
|
||||
}
|
||||
|
||||
}
|
|
@ -3,6 +3,7 @@
|
|||
namespace MediaWiki\Extension\AbuseFilter;
|
||||
|
||||
use BadMethodCallException;
|
||||
use DeferredUpdates;
|
||||
use IBufferingStatsdDataFactory;
|
||||
use InvalidArgumentException;
|
||||
use MediaWiki\Config\ServiceOptions;
|
||||
|
@ -51,6 +52,8 @@ class FilterRunner {
|
|||
private $consExecutorFactory;
|
||||
/** @var AbuseLoggerFactory */
|
||||
private $abuseLoggerFactory;
|
||||
/** @var EmergencyCache */
|
||||
private $emergencyCache;
|
||||
/** @var Watcher[] */
|
||||
private $watchers;
|
||||
/** @var EditStashCache */
|
||||
|
@ -103,6 +106,7 @@ class FilterRunner {
|
|||
* @param AbuseLoggerFactory $abuseLoggerFactory
|
||||
* @param VariablesManager $varManager
|
||||
* @param VariableGeneratorFactory $varGeneratorFactory
|
||||
* @param EmergencyCache $emergencyCache
|
||||
* @param Watcher[] $watchers
|
||||
* @param EditStashCache $stashCache
|
||||
* @param LoggerInterface $logger
|
||||
|
@ -124,6 +128,7 @@ class FilterRunner {
|
|||
AbuseLoggerFactory $abuseLoggerFactory,
|
||||
VariablesManager $varManager,
|
||||
VariableGeneratorFactory $varGeneratorFactory,
|
||||
EmergencyCache $emergencyCache,
|
||||
array $watchers,
|
||||
EditStashCache $stashCache,
|
||||
LoggerInterface $logger,
|
||||
|
@ -143,6 +148,7 @@ class FilterRunner {
|
|||
$this->abuseLoggerFactory = $abuseLoggerFactory;
|
||||
$this->varManager = $varManager;
|
||||
$this->varGeneratorFactory = $varGeneratorFactory;
|
||||
$this->emergencyCache = $emergencyCache;
|
||||
$this->watchers = $watchers;
|
||||
$this->stashCache = $stashCache;
|
||||
$this->logger = $logger;
|
||||
|
@ -226,6 +232,14 @@ class FilterRunner {
|
|||
}
|
||||
|
||||
$this->profileExecution( $runnerData );
|
||||
// hack until DI for DeferredUpdates is possible (T265749)
|
||||
if ( defined( 'MW_PHPUNIT_TEST' ) ) {
|
||||
$this->updateEmergencyCache( $runnerData->getMatchesMap() );
|
||||
} else {
|
||||
DeferredUpdates::addCallableUpdate( function () use ( $runnerData ) {
|
||||
$this->updateEmergencyCache( $runnerData->getMatchesMap() );
|
||||
} );
|
||||
}
|
||||
|
||||
// Tag the action if the condition limit was hit
|
||||
if ( $runnerData->getTotalConditions() > $this->options->get( 'AbuseFilterConditionLimit' ) ) {
|
||||
|
@ -395,6 +409,18 @@ class FilterRunner {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool[] $matches
|
||||
*/
|
||||
protected function updateEmergencyCache( array $matches ) : void {
|
||||
$filters = $this->emergencyCache->getFiltersToCheckInGroup( $this->group );
|
||||
foreach ( $filters as $filter ) {
|
||||
if ( array_key_exists( "$filter", $matches ) ) {
|
||||
$this->emergencyCache->incrementForFilter( $filter, $matches["$filter"] );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
|
|
|
@ -40,6 +40,8 @@ class FilterRunnerFactory {
|
|||
private $varManager;
|
||||
/** @var VariableGeneratorFactory */
|
||||
private $varGeneratorFactory;
|
||||
/** @var EmergencyCache */
|
||||
private $emergencyCache;
|
||||
/** @var UpdateHitCountWatcher */
|
||||
private $updateHitCountWatcher;
|
||||
/** @var EmergencyWatcher */
|
||||
|
@ -65,6 +67,7 @@ class FilterRunnerFactory {
|
|||
* @param AbuseLoggerFactory $abuseLoggerFactory
|
||||
* @param VariablesManager $varManager
|
||||
* @param VariableGeneratorFactory $varGeneratorFactory
|
||||
* @param EmergencyCache $emergencyCache
|
||||
* @param UpdateHitCountWatcher $updateHitCountWatcher
|
||||
* @param EmergencyWatcher $emergencyWatcher
|
||||
* @param BagOStuff $localCache
|
||||
|
@ -83,6 +86,7 @@ class FilterRunnerFactory {
|
|||
AbuseLoggerFactory $abuseLoggerFactory,
|
||||
VariablesManager $varManager,
|
||||
VariableGeneratorFactory $varGeneratorFactory,
|
||||
EmergencyCache $emergencyCache,
|
||||
UpdateHitCountWatcher $updateHitCountWatcher,
|
||||
EmergencyWatcher $emergencyWatcher,
|
||||
BagOStuff $localCache,
|
||||
|
@ -100,6 +104,7 @@ class FilterRunnerFactory {
|
|||
$this->abuseLoggerFactory = $abuseLoggerFactory;
|
||||
$this->varManager = $varManager;
|
||||
$this->varGeneratorFactory = $varGeneratorFactory;
|
||||
$this->emergencyCache = $emergencyCache;
|
||||
$this->updateHitCountWatcher = $updateHitCountWatcher;
|
||||
$this->emergencyWatcher = $emergencyWatcher;
|
||||
$this->localCache = $localCache;
|
||||
|
@ -134,6 +139,7 @@ class FilterRunnerFactory {
|
|||
$this->abuseLoggerFactory,
|
||||
$this->varManager,
|
||||
$this->varGeneratorFactory,
|
||||
$this->emergencyCache,
|
||||
$watchers,
|
||||
new EditStashCache(
|
||||
$this->localCache,
|
||||
|
|
|
@ -39,6 +39,9 @@ class FilterStore {
|
|||
/** @var FilterCompare */
|
||||
private $filterCompare;
|
||||
|
||||
/** @var EmergencyCache */
|
||||
private $emergencyCache;
|
||||
|
||||
/**
|
||||
* @param ConsequencesRegistry $consequencesRegistry
|
||||
* @param ILoadBalancer $loadBalancer
|
||||
|
@ -47,6 +50,7 @@ class FilterStore {
|
|||
* @param ChangeTagsManager $tagsManager
|
||||
* @param FilterValidator $filterValidator
|
||||
* @param FilterCompare $filterCompare
|
||||
* @param EmergencyCache $emergencyCache
|
||||
*/
|
||||
public function __construct(
|
||||
ConsequencesRegistry $consequencesRegistry,
|
||||
|
@ -55,7 +59,8 @@ class FilterStore {
|
|||
FilterLookup $filterLookup,
|
||||
ChangeTagsManager $tagsManager,
|
||||
FilterValidator $filterValidator,
|
||||
FilterCompare $filterCompare
|
||||
FilterCompare $filterCompare,
|
||||
EmergencyCache $emergencyCache
|
||||
) {
|
||||
$this->consequencesRegistry = $consequencesRegistry;
|
||||
$this->loadBalancer = $loadBalancer;
|
||||
|
@ -64,6 +69,7 @@ class FilterStore {
|
|||
$this->tagsManager = $tagsManager;
|
||||
$this->filterValidator = $filterValidator;
|
||||
$this->filterCompare = $filterCompare;
|
||||
$this->emergencyCache = $emergencyCache;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -213,13 +219,15 @@ class FilterStore {
|
|||
|
||||
$dbw->endAtomic( __METHOD__ );
|
||||
|
||||
// TODO: isset() shouldn't be necessary,
|
||||
// invalid values should also be discarded upstream
|
||||
$group = 'default';
|
||||
if ( isset( $newRow['af_group'] ) && $newRow['af_group'] !== '' ) {
|
||||
$group = $newRow['af_group'];
|
||||
}
|
||||
|
||||
// Invalidate cache if this was a global rule
|
||||
if ( $wasGlobal || $newRow['af_global'] ) {
|
||||
$group = 'default';
|
||||
if ( isset( $newRow['af_group'] ) && $newRow['af_group'] !== '' ) {
|
||||
$group = $newRow['af_group'];
|
||||
}
|
||||
|
||||
$this->filterLookup->purgeGroupWANCache( $group );
|
||||
}
|
||||
|
||||
|
@ -241,6 +249,9 @@ class FilterStore {
|
|||
}
|
||||
|
||||
$this->filterProfiler->resetFilterProfile( $newID );
|
||||
if ( $newRow['af_enabled'] ) {
|
||||
$this->emergencyCache->setNewForFilter( $newID, $group );
|
||||
}
|
||||
return [ $newID, $historyID ];
|
||||
}
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ use MediaWiki\Extension\AbuseFilter\Consequences\ConsequencesRegistry;
|
|||
use MediaWiki\Extension\AbuseFilter\EchoNotifier;
|
||||
use MediaWiki\Extension\AbuseFilter\EditBox\EditBoxBuilderFactory;
|
||||
use MediaWiki\Extension\AbuseFilter\EditRevUpdater;
|
||||
use MediaWiki\Extension\AbuseFilter\EmergencyCache;
|
||||
use MediaWiki\Extension\AbuseFilter\FilterCompare;
|
||||
use MediaWiki\Extension\AbuseFilter\FilterImporter;
|
||||
use MediaWiki\Extension\AbuseFilter\FilterLookup;
|
||||
|
@ -127,9 +128,15 @@ return [
|
|||
$services->get( CentralDBManager::SERVICE_NAME )
|
||||
);
|
||||
},
|
||||
EmergencyCache::SERVICE_NAME => function ( MediaWikiServices $services ): EmergencyCache {
|
||||
return new EmergencyCache(
|
||||
$services->getMainObjectStash(),
|
||||
$services->getMainConfig()->get( 'AbuseFilterEmergencyDisableAge' )
|
||||
);
|
||||
},
|
||||
EmergencyWatcher::SERVICE_NAME => function ( MediaWikiServices $services ): EmergencyWatcher {
|
||||
return new EmergencyWatcher(
|
||||
$services->getService( FilterProfiler::SERVICE_NAME ),
|
||||
$services->getService( EmergencyCache::SERVICE_NAME ),
|
||||
$services->getDBLoadBalancer(),
|
||||
$services->getService( FilterLookup::SERVICE_NAME ),
|
||||
$services->getService( EchoNotifier::SERVICE_NAME ),
|
||||
|
@ -176,7 +183,8 @@ return [
|
|||
$services->get( FilterLookup::SERVICE_NAME ),
|
||||
$services->get( ChangeTagsManager::SERVICE_NAME ),
|
||||
$services->get( FilterValidator::SERVICE_NAME ),
|
||||
$services->get( FilterCompare::SERVICE_NAME )
|
||||
$services->get( FilterCompare::SERVICE_NAME ),
|
||||
$services->get( EmergencyCache::SERVICE_NAME )
|
||||
);
|
||||
},
|
||||
ConsequencesFactory::SERVICE_NAME => function ( MediaWikiServices $services ): ConsequencesFactory {
|
||||
|
@ -277,6 +285,7 @@ return [
|
|||
$services->get( AbuseLoggerFactory::SERVICE_NAME ),
|
||||
$services->get( VariablesManager::SERVICE_NAME ),
|
||||
$services->get( VariableGeneratorFactory::SERVICE_NAME ),
|
||||
$services->get( EmergencyCache::SERVICE_NAME ),
|
||||
$services->get( UpdateHitCountWatcher::SERVICE_NAME ),
|
||||
$services->get( EmergencyWatcher::SERVICE_NAME ),
|
||||
ObjectCache::getLocalClusterInstance(),
|
||||
|
|
|
@ -7,8 +7,8 @@ use DeferredUpdates;
|
|||
use InvalidArgumentException;
|
||||
use MediaWiki\Config\ServiceOptions;
|
||||
use MediaWiki\Extension\AbuseFilter\EchoNotifier;
|
||||
use MediaWiki\Extension\AbuseFilter\EmergencyCache;
|
||||
use MediaWiki\Extension\AbuseFilter\FilterLookup;
|
||||
use MediaWiki\Extension\AbuseFilter\FilterProfiler;
|
||||
use Wikimedia\Rdbms\IDatabase;
|
||||
use Wikimedia\Rdbms\ILoadBalancer;
|
||||
|
||||
|
@ -27,8 +27,8 @@ class EmergencyWatcher implements Watcher {
|
|||
'AbuseFilterEmergencyDisableThreshold',
|
||||
];
|
||||
|
||||
/** @var FilterProfiler */
|
||||
private $profiler;
|
||||
/** @var EmergencyCache */
|
||||
private $cache;
|
||||
|
||||
/** @var ILoadBalancer */
|
||||
private $loadBalancer;
|
||||
|
@ -43,21 +43,21 @@ class EmergencyWatcher implements Watcher {
|
|||
private $options;
|
||||
|
||||
/**
|
||||
* @param FilterProfiler $profiler
|
||||
* @param EmergencyCache $cache
|
||||
* @param ILoadBalancer $loadBalancer
|
||||
* @param FilterLookup $filterLookup
|
||||
* @param EchoNotifier $notifier
|
||||
* @param ServiceOptions $options
|
||||
*/
|
||||
public function __construct(
|
||||
FilterProfiler $profiler,
|
||||
EmergencyCache $cache,
|
||||
ILoadBalancer $loadBalancer,
|
||||
FilterLookup $filterLookup,
|
||||
EchoNotifier $notifier,
|
||||
ServiceOptions $options
|
||||
) {
|
||||
$options->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS );
|
||||
$this->profiler = $profiler;
|
||||
$this->cache = $cache;
|
||||
$this->loadBalancer = $loadBalancer;
|
||||
$this->filterLookup = $filterLookup;
|
||||
$this->notifier = $notifier;
|
||||
|
@ -73,12 +73,11 @@ class EmergencyWatcher implements Watcher {
|
|||
* @return int[] Array of filters to be throttled
|
||||
*/
|
||||
public function getFiltersToThrottle( array $filters, string $group ) : array {
|
||||
$groupProfile = $this->profiler->getGroupProfile( $group );
|
||||
|
||||
// @ToDo this is an amount between 1 and AbuseFilterProfileActionsCap, which means that the
|
||||
// reliability of this number may strongly vary. We should instead use a fixed one.
|
||||
$totalActions = $groupProfile['total'];
|
||||
if ( $totalActions === 0 ) {
|
||||
$filters = array_intersect(
|
||||
$filters,
|
||||
$this->cache->getFiltersToCheckInGroup( $group )
|
||||
);
|
||||
if ( $filters === [] ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
|
@ -91,6 +90,8 @@ class EmergencyWatcher implements Watcher {
|
|||
$throttleFilters = [];
|
||||
foreach ( $filters as $filter ) {
|
||||
$filterObj = $this->filterLookup->getFilter( $filter, false );
|
||||
// TODO: consider removing the filter from the group key
|
||||
// after throttling
|
||||
if ( $filterObj->isThrottled() ) {
|
||||
continue;
|
||||
}
|
||||
|
@ -99,13 +100,20 @@ class EmergencyWatcher implements Watcher {
|
|||
$exemptTime = $filterAge + $maxAge;
|
||||
|
||||
// Optimize for the common case when filters are well-established
|
||||
// This check somewhat duplicates the role of cache entry's TTL
|
||||
// and could as well be removed
|
||||
if ( $exemptTime <= $time ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// TODO: this value might be stale, there is no guarantee the match
|
||||
// has actually been recorded now
|
||||
$matchCount = $this->profiler->getFilterProfile( $filter )['matches'];
|
||||
$cacheValue = $this->cache->getForFilter( $filter );
|
||||
if ( $cacheValue === false ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
[ 'total' => $totalActions, 'matches' => $matchCount ] = $cacheValue;
|
||||
|
||||
if ( $matchCount > $hitCountLimit && ( $matchCount / $totalActions ) > $threshold ) {
|
||||
// More than AbuseFilterEmergencyDisableCount matches, constituting more than
|
||||
|
|
107
tests/phpunit/unit/EmergencyCacheTest.php
Normal file
107
tests/phpunit/unit/EmergencyCacheTest.php
Normal file
|
@ -0,0 +1,107 @@
|
|||
<?php
|
||||
|
||||
namespace MediaWiki\Extension\AbuseFilter\Tests\Unit;
|
||||
|
||||
use HashBagOStuff;
|
||||
use MediaWiki\Extension\AbuseFilter\EmergencyCache;
|
||||
use MediaWikiUnitTestCase;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \MediaWiki\Extension\AbuseFilter\EmergencyCache
|
||||
*/
|
||||
class EmergencyCacheTest extends MediaWikiUnitTestCase {
|
||||
|
||||
/**
|
||||
* @covers ::__construct
|
||||
* @covers ::getFiltersToCheckInGroup
|
||||
* @covers ::getForFilter
|
||||
*/
|
||||
public function testEmptyCache() {
|
||||
$cache = new EmergencyCache( new HashBagOStuff(), [ 'default' => 86400 ] );
|
||||
$this->assertSame( [], $cache->getFiltersToCheckInGroup( 'default' ) );
|
||||
$this->assertFalse( $cache->getForFilter( 1 ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::setNewForFilter
|
||||
* @covers ::getFiltersToCheckInGroup
|
||||
* @covers ::getForFilter
|
||||
* @covers ::createGroupKey
|
||||
* @covers ::createFilterKey
|
||||
*/
|
||||
public function testSetNewForFilter() {
|
||||
$time = microtime( true );
|
||||
$stash = new HashBagOStuff();
|
||||
$stash->setMockTime( $time );
|
||||
$cache = new EmergencyCache( $stash, [ 'default' => 86400, 'other' => 3600 ] );
|
||||
$cache->setNewForFilter( 2, 'other' );
|
||||
$this->assertSame(
|
||||
[ 'total' => 0, 'matches' => 0 ],
|
||||
$cache->getForFilter( 2 )
|
||||
);
|
||||
$this->assertSame(
|
||||
[ 2 ],
|
||||
$cache->getFiltersToCheckInGroup( 'other' )
|
||||
);
|
||||
$this->assertSame(
|
||||
[],
|
||||
$cache->getFiltersToCheckInGroup( 'default' )
|
||||
);
|
||||
|
||||
$time += 3599;
|
||||
$this->assertNotFalse( $cache->getForFilter( 2 ) );
|
||||
$time += 2;
|
||||
$this->assertFalse( $cache->getForFilter( 2 ) );
|
||||
$this->assertSame( [], $cache->getFiltersToCheckInGroup( 'other' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::incrementForFilter
|
||||
* @covers ::getForFilter
|
||||
* @covers ::setNewForFilter
|
||||
*/
|
||||
public function testIncrementForFilter() {
|
||||
$time = microtime( true );
|
||||
$stash = new HashBagOStuff();
|
||||
$stash->setMockTime( $time );
|
||||
$cache = new EmergencyCache( $stash, [ 'default' => 86400 ] );
|
||||
$cache->setNewForFilter( 1, 'default' );
|
||||
$cache->incrementForFilter( 1, false );
|
||||
$this->assertSame(
|
||||
[ 'total' => 1, 'matches' => 0 ],
|
||||
$cache->getForFilter( 1 )
|
||||
);
|
||||
$cache->incrementForFilter( 1, true );
|
||||
$this->assertSame(
|
||||
[ 'total' => 2, 'matches' => 1 ],
|
||||
$cache->getForFilter( 1 )
|
||||
);
|
||||
|
||||
$time += 86401;
|
||||
$cache->incrementForFilter( 1, true );
|
||||
$this->assertFalse( $cache->getForFilter( 1 ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::getFiltersToCheckInGroup
|
||||
*/
|
||||
public function testGetFiltersToCheckInGroup() {
|
||||
$time = microtime( true );
|
||||
$stash = new HashBagOStuff();
|
||||
$stash->setMockTime( $time );
|
||||
$cache = new EmergencyCache( $stash, [ 'default' => 3600 ] );
|
||||
$cache->setNewForFilter( 1, 'default' );
|
||||
$time += 1000;
|
||||
$cache->setNewForFilter( 2, 'default' );
|
||||
$this->assertArrayEquals(
|
||||
[ 1, 2 ],
|
||||
$cache->getFiltersToCheckInGroup( 'default' )
|
||||
);
|
||||
$time += 2601;
|
||||
$this->assertArrayEquals(
|
||||
[ 2 ],
|
||||
$cache->getFiltersToCheckInGroup( 'default' )
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -8,6 +8,7 @@ use MediaWiki\Config\ServiceOptions;
|
|||
use MediaWiki\Extension\AbuseFilter\AbuseLoggerFactory;
|
||||
use MediaWiki\Extension\AbuseFilter\ChangeTags\ChangeTagger;
|
||||
use MediaWiki\Extension\AbuseFilter\Consequences\ConsequencesExecutorFactory;
|
||||
use MediaWiki\Extension\AbuseFilter\EmergencyCache;
|
||||
use MediaWiki\Extension\AbuseFilter\FilterLookup;
|
||||
use MediaWiki\Extension\AbuseFilter\FilterProfiler;
|
||||
use MediaWiki\Extension\AbuseFilter\FilterRunner;
|
||||
|
@ -54,6 +55,7 @@ class FilterRunnerFactoryTest extends MediaWikiUnitTestCase {
|
|||
$this->createMock( AbuseLoggerFactory::class ),
|
||||
$this->createMock( VariablesManager::class ),
|
||||
$this->createMock( VariableGeneratorFactory::class ),
|
||||
$this->createMock( EmergencyCache::class ),
|
||||
$this->createMock( UpdateHitCountWatcher::class ),
|
||||
$this->createMock( EmergencyWatcher::class ),
|
||||
new HashBagOStuff(),
|
||||
|
|
|
@ -9,6 +9,7 @@ use MediaWiki\Extension\AbuseFilter\AbuseLoggerFactory;
|
|||
use MediaWiki\Extension\AbuseFilter\ChangeTags\ChangeTagger;
|
||||
use MediaWiki\Extension\AbuseFilter\Consequences\ConsequencesExecutorFactory;
|
||||
use MediaWiki\Extension\AbuseFilter\EditStashCache;
|
||||
use MediaWiki\Extension\AbuseFilter\EmergencyCache;
|
||||
use MediaWiki\Extension\AbuseFilter\FilterLookup;
|
||||
use MediaWiki\Extension\AbuseFilter\FilterProfiler;
|
||||
use MediaWiki\Extension\AbuseFilter\FilterRunner;
|
||||
|
@ -67,6 +68,7 @@ class FilterRunnerTest extends MediaWikiUnitTestCase {
|
|||
$this->createMock( AbuseLoggerFactory::class ),
|
||||
$this->createMock( VariablesManager::class ),
|
||||
$this->createMock( VariableGeneratorFactory::class ),
|
||||
$this->createMock( EmergencyCache::class ),
|
||||
[],
|
||||
$cache,
|
||||
new NullLogger(),
|
||||
|
|
|
@ -4,6 +4,7 @@ namespace MediaWiki\Extension\AbuseFilter\Tests\Unit;
|
|||
|
||||
use MediaWiki\Extension\AbuseFilter\ChangeTags\ChangeTagsManager;
|
||||
use MediaWiki\Extension\AbuseFilter\Consequences\ConsequencesRegistry;
|
||||
use MediaWiki\Extension\AbuseFilter\EmergencyCache;
|
||||
use MediaWiki\Extension\AbuseFilter\FilterCompare;
|
||||
use MediaWiki\Extension\AbuseFilter\FilterLookup;
|
||||
use MediaWiki\Extension\AbuseFilter\FilterProfiler;
|
||||
|
@ -32,7 +33,8 @@ class FilterStoreTest extends MediaWikiUnitTestCase {
|
|||
$this->createMock( FilterLookup::class ),
|
||||
$this->createMock( ChangeTagsManager::class ),
|
||||
$this->createMock( FilterValidator::class ),
|
||||
$this->createMock( FilterCompare::class )
|
||||
$this->createMock( FilterCompare::class ),
|
||||
$this->createMock( EmergencyCache::class )
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -4,9 +4,9 @@ namespace MediaWiki\Extension\AbuseFilter\Tests\Unit\Watcher;
|
|||
|
||||
use MediaWiki\Config\ServiceOptions;
|
||||
use MediaWiki\Extension\AbuseFilter\EchoNotifier;
|
||||
use MediaWiki\Extension\AbuseFilter\EmergencyCache;
|
||||
use MediaWiki\Extension\AbuseFilter\Filter\ExistingFilter;
|
||||
use MediaWiki\Extension\AbuseFilter\FilterLookup;
|
||||
use MediaWiki\Extension\AbuseFilter\FilterProfiler;
|
||||
use MediaWiki\Extension\AbuseFilter\Watcher\EmergencyWatcher;
|
||||
use MediaWikiUnitTestCase;
|
||||
use MWTimestamp;
|
||||
|
@ -36,15 +36,15 @@ class EmergencyWatcherTest extends MediaWikiUnitTestCase {
|
|||
);
|
||||
}
|
||||
|
||||
private function getFilterProfiler( array $profilerData ) : FilterProfiler {
|
||||
$profiler = $this->createMock( FilterProfiler::class );
|
||||
$profiler->method( 'getGroupProfile' )
|
||||
->willReturnCallback( function ( $group ) use ( $profilerData ) {
|
||||
return [ 'total' => $profilerData['total'][$group] ];
|
||||
} );
|
||||
$profiler->method( 'getFilterProfile' )
|
||||
->willReturn( [ 'matches' => $profilerData['matches'] ] );
|
||||
return $profiler;
|
||||
private function getEmergencyCache( array $cacheData, string $group ) : EmergencyCache {
|
||||
$cache = $this->createMock( EmergencyCache::class );
|
||||
$cache->method( 'getForFilter' )
|
||||
->with( 1 )
|
||||
->willReturn( $cacheData );
|
||||
$cache->method( 'getFiltersToCheckInGroup' )
|
||||
->with( $group )
|
||||
->willReturn( [ 1 ] );
|
||||
return $cache;
|
||||
}
|
||||
|
||||
private function getFilterLookup( array $filterData ) : FilterLookup {
|
||||
|
@ -67,10 +67,8 @@ class EmergencyWatcherTest extends MediaWikiUnitTestCase {
|
|||
/* filterData */ [
|
||||
'timestamp' => '20201016000000'
|
||||
],
|
||||
/* profilerData */ [
|
||||
'total' => [
|
||||
'default' => 100,
|
||||
],
|
||||
/* cacheData */ [
|
||||
'total' => 100,
|
||||
'matches' => 10
|
||||
],
|
||||
/* willThrottle */ true
|
||||
|
@ -80,11 +78,8 @@ class EmergencyWatcherTest extends MediaWikiUnitTestCase {
|
|||
/* filterData */ [
|
||||
'timestamp' => '20201016000000'
|
||||
],
|
||||
/* profilerData */ [
|
||||
'total' => [
|
||||
'default' => 200,
|
||||
'other' => 100
|
||||
],
|
||||
/* cacheData */ [
|
||||
'total' => 100,
|
||||
'matches' => 5
|
||||
],
|
||||
/* willThrottle */ true,
|
||||
|
@ -96,10 +91,8 @@ class EmergencyWatcherTest extends MediaWikiUnitTestCase {
|
|||
'timestamp' => '20201016000000',
|
||||
'throttled' => true,
|
||||
],
|
||||
/* profilerData */ [
|
||||
'total' => [
|
||||
'default' => 100,
|
||||
],
|
||||
/* cacheData */ [
|
||||
'total' => 100,
|
||||
'matches' => 10
|
||||
],
|
||||
/* willThrottle */ false
|
||||
|
@ -109,10 +102,8 @@ class EmergencyWatcherTest extends MediaWikiUnitTestCase {
|
|||
/* filterData */ [
|
||||
'timestamp' => '20201016000000'
|
||||
],
|
||||
/* profilerData */ [
|
||||
'total' => [
|
||||
'default' => 5,
|
||||
],
|
||||
/* cacheData */ [
|
||||
'total' => 5,
|
||||
'matches' => 2
|
||||
],
|
||||
/* willThrottle */ false
|
||||
|
@ -122,10 +113,8 @@ class EmergencyWatcherTest extends MediaWikiUnitTestCase {
|
|||
/* filterData */ [
|
||||
'timestamp' => '20201016000000'
|
||||
],
|
||||
/* profilerData */ [
|
||||
'total' => [
|
||||
'default' => 100,
|
||||
],
|
||||
/* cacheData */ [
|
||||
'total' => 100,
|
||||
'matches' => 5
|
||||
],
|
||||
/* willThrottle */ false
|
||||
|
@ -135,10 +124,8 @@ class EmergencyWatcherTest extends MediaWikiUnitTestCase {
|
|||
/* filterData */ [
|
||||
'timestamp' => '20201016000000'
|
||||
],
|
||||
/* profilerData */ [
|
||||
'total' => [
|
||||
'default' => 1000,
|
||||
],
|
||||
/* cacheData */ [
|
||||
'total' => 1000,
|
||||
'matches' => 100
|
||||
],
|
||||
/* willThrottle */ false
|
||||
|
@ -148,10 +135,8 @@ class EmergencyWatcherTest extends MediaWikiUnitTestCase {
|
|||
/* filterData */ [
|
||||
'timestamp' => '20201016000000'
|
||||
],
|
||||
/* profilerData */ [
|
||||
'total' => [
|
||||
'default' => 0,
|
||||
],
|
||||
/* cacheData */ [
|
||||
'total' => 0,
|
||||
'matches' => 0
|
||||
],
|
||||
/* willThrottle */ false
|
||||
|
@ -167,13 +152,13 @@ class EmergencyWatcherTest extends MediaWikiUnitTestCase {
|
|||
public function testGetFiltersToThrottle(
|
||||
string $timestamp,
|
||||
array $filterData,
|
||||
array $profilerData,
|
||||
array $cacheData,
|
||||
bool $willThrottle,
|
||||
string $group = 'default'
|
||||
) {
|
||||
MWTimestamp::setFakeTime( $timestamp );
|
||||
$watcher = new EmergencyWatcher(
|
||||
$this->getFilterProfiler( $profilerData ),
|
||||
$this->getEmergencyCache( $cacheData, $group ),
|
||||
$this->createMock( ILoadBalancer::class ),
|
||||
$this->getFilterLookup( $filterData ),
|
||||
$this->createMock( EchoNotifier::class ),
|
||||
|
@ -194,7 +179,7 @@ class EmergencyWatcherTest extends MediaWikiUnitTestCase {
|
|||
*/
|
||||
public function testConstruct() {
|
||||
$watcher = new EmergencyWatcher(
|
||||
$this->createMock( FilterProfiler::class ),
|
||||
$this->createMock( EmergencyCache::class ),
|
||||
$this->createMock( ILoadBalancer::class ),
|
||||
$this->createMock( FilterLookup::class ),
|
||||
$this->createMock( EchoNotifier::class ),
|
||||
|
|
Loading…
Reference in a new issue