mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/AbuseFilter.git
synced 2024-11-23 21:53:35 +00:00
Expand SearchFilters.php to search by consequence
Why: * SearchFilters.php allows the caller to search by a regex that is applied to the pattern. * This script can be expanded to allow callers to specify what consequence should be associated with the filters that are outputted. What: * Add a 'consequence' option to the SearchFilters.php maintenance script, which is applied through a LIKE query on the af_actions column. ** This can be specified with or without the pattern option. ** Instead of making pattern required, the script now requires that one of consequence or pattern is provided. * Expand the tests for the script for this new code, along with using the new ::expectCallToFatalError method to be able to test previously untestable code. Bug: T373148 Change-Id: I1b507d8f9dc1f4cf91ee4f83ccde745eb6d46d6d
This commit is contained in:
parent
86d4fed611
commit
7ecc204050
|
@ -3,6 +3,9 @@
|
|||
namespace MediaWiki\Extension\AbuseFilter\Maintenance;
|
||||
|
||||
use Maintenance;
|
||||
use MediaWiki\MainConfigNames;
|
||||
use Wikimedia\Rdbms\IExpression;
|
||||
use Wikimedia\Rdbms\LikeValue;
|
||||
|
||||
// @codeCoverageIgnoreStart
|
||||
$IP = getenv( 'MW_INSTALL_PATH' );
|
||||
|
@ -15,8 +18,11 @@ require_once "$IP/maintenance/Maintenance.php";
|
|||
class SearchFilters extends Maintenance {
|
||||
public function __construct() {
|
||||
parent::__construct();
|
||||
$this->addDescription( 'Find all filters matching a regular expression pattern' );
|
||||
$this->addOption( 'pattern', 'Regular expression pattern', true, true );
|
||||
$this->addDescription(
|
||||
'Find all filters matching a regular expression pattern and/or that have given consequence'
|
||||
);
|
||||
$this->addOption( 'pattern', 'Regular expression pattern', false, true );
|
||||
$this->addOption( 'consequence', 'The consequence that the filter should have', false, true );
|
||||
|
||||
$this->requireExtension( 'Abuse Filter' );
|
||||
}
|
||||
|
@ -25,22 +31,17 @@ class SearchFilters extends Maintenance {
|
|||
* @see Maintenance:execute()
|
||||
*/
|
||||
public function execute() {
|
||||
global $wgConf, $wgDBtype;
|
||||
global $wgConf;
|
||||
|
||||
if ( $wgDBtype !== 'mysql' ) {
|
||||
// Code using exit() cannot be tested (T272241)
|
||||
// @codeCoverageIgnoreStart
|
||||
if ( $this->getConfig()->get( MainConfigNames::DBtype ) !== 'mysql' ) {
|
||||
$this->fatalError( 'This maintenance script only works with MySQL databases' );
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
|
||||
if ( !$this->getOption( 'pattern' ) && !$this->getOption( 'consequence' ) ) {
|
||||
$this->fatalError( 'One of --consequence or --pattern should be specified.' );
|
||||
}
|
||||
|
||||
$this->output( "wiki\tfilter\n" );
|
||||
if ( $this->getOption( 'pattern' ) === '' ) {
|
||||
// Code using exit() cannot be tested (T272241)
|
||||
// @codeCoverageIgnoreStart
|
||||
$this->fatalError( 'Pattern cannot be empty' );
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
|
||||
if ( count( $wgConf->wikis ) > 0 ) {
|
||||
foreach ( $wgConf->wikis as $dbname ) {
|
||||
|
@ -57,16 +58,23 @@ class SearchFilters extends Maintenance {
|
|||
private function getMatchingFilters( $dbname = false ) {
|
||||
$dbr = $this->getDB( DB_REPLICA, [], $dbname );
|
||||
$pattern = $dbr->addQuotes( $this->getOption( 'pattern' ) );
|
||||
$consequence = $this->getOption( 'consequence' );
|
||||
|
||||
if ( $dbr->tableExists( 'abuse_filter' ) ) {
|
||||
$rows = $dbr->newSelectQueryBuilder()
|
||||
$queryBuilder = $dbr->newSelectQueryBuilder()
|
||||
->select( [ 'dbname' => 'DATABASE()', 'af_id' ] )
|
||||
->from( 'abuse_filter' )
|
||||
->where( [
|
||||
"af_pattern RLIKE $pattern"
|
||||
] )
|
||||
->caller( __METHOD__ )
|
||||
->fetchResultSet();
|
||||
->from( 'abuse_filter' );
|
||||
if ( $pattern ) {
|
||||
$queryBuilder->where( "af_pattern RLIKE $pattern" );
|
||||
}
|
||||
if ( $consequence ) {
|
||||
$queryBuilder->where( $dbr->expr(
|
||||
'af_actions',
|
||||
IExpression::LIKE,
|
||||
new LikeValue( $dbr->anyString(), $consequence, $dbr->anyString() )
|
||||
) );
|
||||
}
|
||||
$rows = $queryBuilder->caller( __METHOD__ )->fetchResultSet();
|
||||
|
||||
foreach ( $rows as $row ) {
|
||||
$this->output( $row->dbname . "\t" . $row->af_id . "\n" );
|
||||
|
|
|
@ -5,6 +5,7 @@ namespace MediaWiki\Extension\AbuseFilter\Tests\Integration;
|
|||
use Generator;
|
||||
use MediaWiki\Extension\AbuseFilter\Filter\Flags;
|
||||
use MediaWiki\Extension\AbuseFilter\Maintenance\SearchFilters;
|
||||
use MediaWiki\MainConfigNames;
|
||||
use MediaWiki\Tests\Maintenance\MaintenanceBaseTestCase;
|
||||
|
||||
/**
|
||||
|
@ -14,17 +15,6 @@ use MediaWiki\Tests\Maintenance\MaintenanceBaseTestCase;
|
|||
* @covers \MediaWiki\Extension\AbuseFilter\Maintenance\SearchFilters
|
||||
*/
|
||||
class SearchFiltersTest extends MaintenanceBaseTestCase {
|
||||
|
||||
protected function setUp(): void {
|
||||
global $wgDBtype;
|
||||
|
||||
parent::setUp();
|
||||
|
||||
if ( $wgDBtype !== 'mysql' ) {
|
||||
$this->markTestSkipped( 'The script only works on MySQL' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
|
@ -46,15 +36,16 @@ class SearchFiltersTest extends MaintenanceBaseTestCase {
|
|||
'af_hit_count' => 0,
|
||||
'af_throttled' => 0,
|
||||
'af_deleted' => 0,
|
||||
'af_actions' => '',
|
||||
'af_global' => 0,
|
||||
'af_group' => 'default'
|
||||
];
|
||||
$rows = [
|
||||
[ 'af_id' => 1, 'af_pattern' => '' ] + $defaultRow,
|
||||
[ 'af_id' => 2, 'af_pattern' => 'rmspecials(page_title) === "foo"' ] + $defaultRow,
|
||||
[ 'af_id' => 3, 'af_pattern' => 'user_editcount % 3 !== 1' ] + $defaultRow,
|
||||
[ 'af_id' => 4, 'af_pattern' => 'rmspecials(added_lines_pst) !== ""' ] + $defaultRow
|
||||
[ 'af_id' => 1, 'af_pattern' => '', 'af_actions' => '' ] + $defaultRow,
|
||||
[ 'af_id' => 2, 'af_pattern' => 'rmspecials(page_title) === "foo"', 'af_actions' => 'warn' ] + $defaultRow,
|
||||
[ 'af_id' => 3, 'af_pattern' => 'user_editcount % 3 !== 1', 'af_actions' => 'warn,block' ] + $defaultRow,
|
||||
[
|
||||
'af_id' => 4, 'af_pattern' => 'rmspecials(added_lines_pst) !== ""', 'af_actions' => 'block',
|
||||
] + $defaultRow
|
||||
];
|
||||
$this->getDb()->newInsertQueryBuilder()
|
||||
->insertInto( 'abuse_filter' )
|
||||
|
@ -63,6 +54,29 @@ class SearchFiltersTest extends MaintenanceBaseTestCase {
|
|||
->execute();
|
||||
}
|
||||
|
||||
/** @dataProvider provideNonMySQLDatabaseTypes */
|
||||
public function testExecuteForNonMySQLDatabaseType( $dbType ) {
|
||||
$this->expectCallToFatalError();
|
||||
$this->expectOutputString( "This maintenance script only works with MySQL databases\n" );
|
||||
$this->overrideConfigValue( MainConfigNames::DBtype, $dbType );
|
||||
$this->maintenance->execute();
|
||||
}
|
||||
|
||||
public static function provideNonMySQLDatabaseTypes() {
|
||||
return [
|
||||
'PostgresSQL' => [ 'postgres' ],
|
||||
'SQLite' => [ 'sqlite' ],
|
||||
];
|
||||
}
|
||||
|
||||
public function testExecuteWhenNeitherPatternOrConsequenceProvided() {
|
||||
// It is safe to mock the DB type here, as the script should exit before any queries are made
|
||||
$this->overrideConfigValue( MainConfigNames::DBtype, 'mysql' );
|
||||
$this->expectCallToFatalError();
|
||||
$this->expectOutputString( "One of --consequence or --pattern should be specified.\n" );
|
||||
$this->maintenance->execute();
|
||||
}
|
||||
|
||||
private function getExpectedOutput( array $ids, bool $withHeader = true ): string {
|
||||
global $wgDBname;
|
||||
$expected = $withHeader ? "wiki\tfilter\n" : '';
|
||||
|
@ -73,32 +87,41 @@ class SearchFiltersTest extends MaintenanceBaseTestCase {
|
|||
}
|
||||
|
||||
public static function provideSearches(): Generator {
|
||||
yield 'single filter' => [ 'page_title', [ 2 ] ];
|
||||
yield 'multiple filters' => [ 'rmspecials', [ 2, 4 ] ];
|
||||
yield 'regex' => [ '[a-z]\(', [ 2, 4 ] ];
|
||||
yield 'single filter for pattern search' => [ 'page_title', '', [ 2 ] ];
|
||||
yield 'multiple filters for pattern search' => [ 'rmspecials', '', [ 2, 4 ] ];
|
||||
yield 'single filter when consequence specified' => [ 'rmspecials', 'block', [ 4 ] ];
|
||||
yield 'regex for pattern' => [ '[a-z]\(', '', [ 2, 4 ] ];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $pattern
|
||||
* @param string $consequence
|
||||
* @param array $expectedIDs
|
||||
* @dataProvider provideSearches
|
||||
*/
|
||||
public function testExecute_singleWiki( string $pattern, array $expectedIDs ) {
|
||||
public function testExecute_singleWiki( string $pattern, string $consequence, array $expectedIDs ) {
|
||||
if ( $this->getDb()->getType() !== 'mysql' ) {
|
||||
$this->markTestSkipped( 'The script only works on MySQL' );
|
||||
}
|
||||
$this->setMwGlobals( [ 'wgConf' => (object)[ 'wikis' => [] ] ] );
|
||||
$this->maintenance->loadParamsAndArgs( null, [ 'pattern' => $pattern ] );
|
||||
$this->maintenance->loadParamsAndArgs( null, [ 'pattern' => $pattern, 'consequence' => $consequence ] );
|
||||
$this->expectOutputString( $this->getExpectedOutput( $expectedIDs ) );
|
||||
$this->maintenance->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $pattern
|
||||
* @param string $consequence
|
||||
* @param array $expectedIDs
|
||||
* @dataProvider provideSearches
|
||||
*/
|
||||
public function testExecute_multipleWikis( string $pattern, array $expectedIDs ) {
|
||||
public function testExecute_multipleWikis( string $pattern, string $consequence, array $expectedIDs ) {
|
||||
if ( $this->getDb()->getType() !== 'mysql' ) {
|
||||
$this->markTestSkipped( 'The script only works on MySQL' );
|
||||
}
|
||||
global $wgDBname;
|
||||
$this->setMwGlobals( [ 'wgConf' => (object)[ 'wikis' => [ $wgDBname, $wgDBname ] ] ] );
|
||||
$this->maintenance->loadParamsAndArgs( null, [ 'pattern' => $pattern ] );
|
||||
$this->maintenance->loadParamsAndArgs( null, [ 'pattern' => $pattern, 'consequence' => $consequence ] );
|
||||
$expectedText = $this->getExpectedOutput( $expectedIDs ) . $this->getExpectedOutput( $expectedIDs, false );
|
||||
$this->expectOutputString( $expectedText );
|
||||
$this->maintenance->execute();
|
||||
|
|
Loading…
Reference in a new issue