Viewing File: /home/omtekel/www/wp-content/upgrade/backup/Consequences.tar

ConsequencesLookupTest.php000066600000001700151335014720011745 0ustar00<?php

namespace MediaWiki\Extension\AbuseFilter\Tests\Unit\Consequences;

use MediaWiki\Extension\AbuseFilter\CentralDBManager;
use MediaWiki\Extension\AbuseFilter\Consequences\ConsequencesLookup;
use MediaWiki\Extension\AbuseFilter\Consequences\ConsequencesRegistry;
use MediaWikiUnitTestCase;
use Psr\Log\NullLogger;
use Wikimedia\Rdbms\LBFactory;

/**
 * @group Test
 * @group AbuseFilter
 * @coversDefaultClass \MediaWiki\Extension\AbuseFilter\Consequences\ConsequencesLookup
 * @todo Write unit tests (non-trivial because the class is tied to a DB)
 */
class ConsequencesLookupTest extends MediaWikiUnitTestCase {
	/**
	 * @covers ::__construct
	 */
	public function testConstructor() {
		$this->assertInstanceOf(
			ConsequencesLookup::class,
			new ConsequencesLookup(
				$this->createMock( LBFactory::class ),
				$this->createMock( CentralDBManager::class ),
				$this->createMock( ConsequencesRegistry::class ),
				new NullLogger()
			)
		);
	}
}
ConsequencesRegistryTest.php000066600000011163151335014720012310 0ustar00<?php

namespace MediaWiki\Extension\AbuseFilter\Tests\Unit\Consequences;

use MediaWiki\Extension\AbuseFilter\Consequences\Consequence\Consequence;
use MediaWiki\Extension\AbuseFilter\Consequences\ConsequencesRegistry;
use MediaWiki\Extension\AbuseFilter\Hooks\AbuseFilterHookRunner;
use MediaWikiUnitTestCase;
use ReflectionClass;
use RuntimeException;

/**
 * @group Test
 * @group AbuseFilter
 * @coversDefaultClass \MediaWiki\Extension\AbuseFilter\Consequences\ConsequencesRegistry
 */
class ConsequencesRegistryTest extends MediaWikiUnitTestCase {

	/**
	 * @covers ::__construct
	 */
	public function testConstruct() {
		$hookRunner = $this->createMock( AbuseFilterHookRunner::class );
		$this->assertInstanceOf(
			ConsequencesRegistry::class,
			new ConsequencesRegistry( $hookRunner, [] )
		);
	}

	/**
	 * @covers ::getAllActionNames
	 */
	public function testGetAllActionNames() {
		$configActions = [ 'nothing' => false, 'rickroll' => true ];
		$customActionName = 'spell';
		$hookRunner = $this->createMock( AbuseFilterHookRunner::class );
		$hookRunner->method( 'onAbuseFilterCustomActions' )->willReturnCallback(
			static function ( &$actions ) use ( $customActionName ) {
				$actions[$customActionName] = 'strlen';
			}
		);
		$expected = [ 'nothing', 'rickroll', 'spell' ];
		$registry = new ConsequencesRegistry( $hookRunner, $configActions );
		$this->assertSame( $expected, $registry->getAllActionNames() );
	}

	/**
	 * @covers ::getAllEnabledActionNames
	 */
	public function testGetAllEnabledActionNames() {
		$configActions = [ 'nothing' => false, 'rickroll' => true ];
		$customActionName = 'spell';
		$hookRunner = $this->createMock( AbuseFilterHookRunner::class );
		$hookRunner->method( 'onAbuseFilterCustomActions' )->willReturnCallback(
			static function ( &$actions ) use ( $customActionName ) {
				$actions[$customActionName] = 'strlen';
			}
		);
		$expected = [ 'rickroll', 'spell' ];
		$registry = new ConsequencesRegistry( $hookRunner, $configActions );
		$this->assertSame( $expected, $registry->getAllEnabledActionNames() );
	}

	/**
	 * @covers ::getDangerousActionNames
	 */
	public function testGetDangerousActionNames() {
		// Cheat a bit
		$regReflection = new ReflectionClass( ConsequencesRegistry::class );
		$expected = $regReflection->getConstant( 'DANGEROUS_ACTIONS' );

		$registry = new ConsequencesRegistry( $this->createMock( AbuseFilterHookRunner::class ), [] );
		$this->assertSame( $expected, $registry->getDangerousActionNames() );
	}

	/**
	 * @covers ::getDangerousActionNames
	 */
	public function testGetDangerousActionNames_hook() {
		$extraDangerous = 'rickroll';
		$hookRunner = $this->createMock( AbuseFilterHookRunner::class );
		$hookRunner->method( 'onAbuseFilterGetDangerousActions' )->willReturnCallback(
			static function ( &$array ) use ( $extraDangerous ) {
				$array[] = $extraDangerous;
			}
		);
		$registry = new ConsequencesRegistry( $hookRunner, [] );
		$this->assertContains( $extraDangerous, $registry->getDangerousActionNames() );
	}

	/**
	 * @covers ::getCustomActions
	 * @covers ::validateCustomActions
	 */
	public function testGetCustomActions() {
		$customActionName = 'rickroll';
		$customAction = 'strlen';
		$hookRunner = $this->createMock( AbuseFilterHookRunner::class );
		$hookRunner->method( 'onAbuseFilterCustomActions' )->willReturnCallback(
			static function ( &$actions ) use ( $customActionName, $customAction ) {
				$actions[$customActionName] = $customAction;
			}
		);
		$registry = new ConsequencesRegistry( $hookRunner, [] );
		$this->assertSame( [ $customActionName => $customAction ], $registry->getCustomActions() );
	}

	/**
	 * @covers ::getCustomActions
	 * @covers ::validateCustomActions
	 */
	public function testGetCustomActions_invalidKey() {
		$hookRunner = $this->createMock( AbuseFilterHookRunner::class );
		$hookRunner->method( 'onAbuseFilterCustomActions' )->willReturnCallback(
			function ( &$actions ) {
				$invalidKey = 42;
				$actions[$invalidKey] = $this->createMock( Consequence::class );
			}
		);
		$registry = new ConsequencesRegistry( $hookRunner, [] );
		$this->expectException( RuntimeException::class );
		$registry->getCustomActions();
	}

	/**
	 * @covers ::getCustomActions
	 * @covers ::validateCustomActions
	 */
	public function testGetCustomActions_invalidValue() {
		$hookRunner = $this->createMock( AbuseFilterHookRunner::class );
		$hookRunner->method( 'onAbuseFilterCustomActions' )->willReturnCallback(
			static function ( &$actions ) {
				$invalidValue = 42;
				$actions['myaction'] = $invalidValue;
			}
		);
		$registry = new ConsequencesRegistry( $hookRunner, [] );
		$this->expectException( RuntimeException::class );
		$registry->getCustomActions();
	}
}
Consequence/DisallowTest.php000066600000001703151335014720012151 0ustar00<?php

namespace MediaWiki\Extension\AbuseFilter\Tests\Unit\Consequences\Consequence;

use ConsequenceGetMessageTestTrait;
use MediaWiki\Extension\AbuseFilter\Consequences\Consequence\Disallow;
use MediaWiki\Extension\AbuseFilter\Consequences\Parameters;
use MediaWikiUnitTestCase;

/**
 * @coversDefaultClass \MediaWiki\Extension\AbuseFilter\Consequences\Consequence\Disallow
 * @covers ::__construct
 */
class DisallowTest extends MediaWikiUnitTestCase {
	use ConsequenceGetMessageTestTrait;

	/**
	 * @covers ::execute
	 */
	public function testExecute() {
		$disallow = new Disallow( $this->createMock( Parameters::class ), '' );
		$this->assertTrue( $disallow->execute() );
	}

	/**
	 * @covers ::getMessage
	 * @dataProvider provideGetMessageParameters
	 */
	public function testGetMessage( Parameters $params ) {
		$msg = 'some-disallow-message';
		$rangeBlock = new Disallow( $params, $msg );
		$this->doTestGetMessage( $rangeBlock, $params, $msg );
	}
}
Consequence/BlockTest.php000066600000012342151335014720011426 0ustar00<?php

namespace MediaWiki\Extension\AbuseFilter\Tests\Unit\Consequences\Consequence;

use ConsequenceGetMessageTestTrait;
use MediaWiki\Block\BlockUser;
use MediaWiki\Block\BlockUserFactory;
use MediaWiki\Block\DatabaseBlock;
use MediaWiki\Block\DatabaseBlockStore;
use MediaWiki\Extension\AbuseFilter\Consequences\Consequence\Block;
use MediaWiki\Extension\AbuseFilter\Consequences\Parameters;
use MediaWiki\Extension\AbuseFilter\FilterUser;
use MediaWiki\User\UserIdentity;
use MediaWiki\User\UserIdentityValue;
use MediaWikiUnitTestCase;
use MessageLocalizer;
use Psr\Log\NullLogger;
use Status;

/**
 * @coversDefaultClass \MediaWiki\Extension\AbuseFilter\Consequences\Consequence\Block
 * @covers \MediaWiki\Extension\AbuseFilter\Consequences\Consequence\BlockingConsequence
 */
class BlockTest extends MediaWikiUnitTestCase {
	use ConsequenceGetMessageTestTrait;

	private function getMsgLocalizer(): MessageLocalizer {
		$ml = $this->createMock( MessageLocalizer::class );
		$ml->method( 'msg' )->willReturnCallback( function ( $k, $p ) {
			return $this->getMockMessage( $k, $p );
		} );
		return $ml;
	}

	/**
	 * This helper is needed because for reverts we check that the blocker is our filter user, so we want
	 * to always use the same object.
	 * @return FilterUser
	 */
	private function getFilterUser(): FilterUser {
		$filterUser = $this->createMock( FilterUser::class );
		$filterUser->method( 'getUserIdentity' )
			->willReturn( new UserIdentityValue( 2, 'FilterUser' ) );
		return $filterUser;
	}

	public static function provideExecute(): iterable {
		foreach ( [ true, false ] as $result ) {
			$resStr = wfBoolToStr( $result );
			yield "IPv4, $resStr" => [ new UserIdentityValue( 0, '1.2.3.4' ), $result ];
			yield "IPv6, $resStr" => [
				// random IP from https://en.wikipedia.org/w/index.php?title=IPv6&oldid=989727833
				new UserIdentityValue( 0, '2001:0db8:0000:0000:0000:ff00:0042:8329' ),
				$result
			];
			yield "Registered, $resStr" => [ new UserIdentityValue( 3, 'Some random user' ), $result ];
		}
	}

	/**
	 * @dataProvider provideExecute
	 * @covers ::__construct
	 * @covers ::execute
	 */
	public function testExecute( UserIdentity $target, bool $result ) {
		$expiry = '1 day';
		$params = $this->provideGetMessageParameters( $target )->current()[0];
		$blockUser = $this->createMock( BlockUser::class );
		$blockUser->expects( $this->once() )
			->method( 'placeBlockUnsafe' )
			->willReturn( $result ? Status::newGood() : Status::newFatal( 'error' ) );
		$blockUserFactory = $this->createMock( BlockUserFactory::class );
		$blockUserFactory->expects( $this->once() )
			->method( 'newBlockUser' )
			->with(
				$target->getName(),
				$this->anything(),
				$expiry,
				$this->anything(),
				$this->anything()
			)
			->willReturn( $blockUser );

		$block = new Block(
			$params,
			$expiry,
			$preventsTalkEdit = true,
			$blockUserFactory,
			$this->createMock( DatabaseBlockStore::class ),
			static function () {
				return null;
			},
			$this->getFilterUser(),
			$this->getMsgLocalizer(),
			new NullLogger()
		);
		$this->assertSame( $result, $block->execute() );
	}

	/**
	 * @covers ::getMessage
	 * @dataProvider provideGetMessageParameters
	 */
	public function testGetMessage( Parameters $params ) {
		$block = new Block(
			$params,
			'0',
			false,
			$this->createMock( BlockUserFactory::class ),
			$this->createMock( DatabaseBlockStore::class ),
			static function () {
				return null;
			},
			$this->createMock( FilterUser::class ),
			$this->getMsgLocalizer(),
			new NullLogger()
		);
		$this->doTestGetMessage( $block, $params, 'abusefilter-blocked-display' );
	}

	public function provideRevert() {
		yield 'no block to revert' => [ null, null, false ];

		$filterUserIdentity = $this->getFilterUser()->getUserIdentity();

		$notAFBlock = $this->createMock( DatabaseBlock::class );
		$notAFBlock->method( 'getBy' )->willReturn( $filterUserIdentity->getId() + 1 );
		yield 'not blocked by AF user' => [ $notAFBlock, null, false ];

		$blockByFilter = $this->createMock( DatabaseBlock::class );
		$blockByFilter->method( 'getBy' )->willReturn( $filterUserIdentity->getId() );
		$failBlockStore = $this->createMock( DatabaseBlockStore::class );
		$failBlockStore->expects( $this->once() )->method( 'deleteBlock' )->willReturn( false );
		yield 'cannot delete block' => [ $blockByFilter, $failBlockStore, false ];

		$succeedBlockStore = $this->createMock( DatabaseBlockStore::class );
		$succeedBlockStore->expects( $this->once() )->method( 'deleteBlock' )->willReturn( true );
		yield 'succeed' => [ $blockByFilter, $succeedBlockStore, true ];
	}

	/**
	 * @covers ::revert
	 * @dataProvider provideRevert
	 */
	public function testRevert( ?DatabaseBlock $block, ?DatabaseBlockStore $blockStore, bool $expected ) {
		$params = $this->createMock( Parameters::class );
		$params->method( 'getUser' )->willReturn( new UserIdentityValue( 1, 'Foobar' ) );
		$block = new Block(
			$params,
			'0',
			false,
			$this->createMock( BlockUserFactory::class ),
			$blockStore ?? $this->createMock( DatabaseBlockStore::class ),
			static function () use ( $block ) {
				return $block;
			},
			$this->getFilterUser(),
			$this->getMsgLocalizer(),
			new NullLogger()
		);
		$this->assertSame( $expected, $block->revert( $this->createMock( UserIdentity::class ), '' ) );
	}
}
Consequence/BlockAutopromoteTest.php000066600000007376151335014720013700 0ustar00<?php

namespace MediaWiki\Extension\AbuseFilter\Tests\Unit\Consequences\Consequence;

use ConsequenceGetMessageTestTrait;
use MediaWiki\Extension\AbuseFilter\BlockAutopromoteStore;
use MediaWiki\Extension\AbuseFilter\Consequences\Consequence\BlockAutopromote;
use MediaWiki\Extension\AbuseFilter\Consequences\Parameters;
use MediaWiki\User\UserIdentityUtils;
use MediaWiki\User\UserIdentityValue;
use MediaWikiUnitTestCase;
use MessageLocalizer;

/**
 * @coversDefaultClass \MediaWiki\Extension\AbuseFilter\Consequences\Consequence\BlockAutopromote
 * @covers ::__construct
 */
class BlockAutopromoteTest extends MediaWikiUnitTestCase {
	use ConsequenceGetMessageTestTrait;

	private function getMsgLocalizer(): MessageLocalizer {
		$ml = $this->createMock( MessageLocalizer::class );
		$ml->method( 'msg' )->willReturnCallback( function ( $k, $p ) {
			return $this->getMockMessage( $k, $p );
		} );
		return $ml;
	}

	/**
	 * @covers ::execute
	 */
	public function testExecute_anonymous() {
		$user = new UserIdentityValue( 0, 'Anonymous user' );
		$params = $this->provideGetMessageParameters( $user )->current()[0];
		$blockAutopromoteStore = $this->createMock( BlockAutopromoteStore::class );
		$blockAutopromoteStore->expects( $this->never() )
			->method( 'blockAutoPromote' );
		$userIdentityUtils = $this->createMock( UserIdentityUtils::class );
		$blockAutopromote = new BlockAutopromote(
			$params,
			5 * 86400,
			$blockAutopromoteStore,
			$this->getMsgLocalizer(),
			$userIdentityUtils
		);
		$this->assertFalse( $blockAutopromote->execute() );
	}

	/**
	 * @covers ::execute
	 * @dataProvider provideExecute
	 */
	public function testExecute( bool $success ) {
		$target = new UserIdentityValue( 1, 'A new user' );
		$userIdentityUtils = $this->createMock( UserIdentityUtils::class );
		$userIdentityUtils->method( 'isNamed' )->willReturn( true );
		$params = $this->provideGetMessageParameters( $target )->current()[0];
		$duration = 5 * 86400;
		$blockAutopromoteStore = $this->createMock( BlockAutopromoteStore::class );
		$blockAutopromoteStore->expects( $this->once() )
			->method( 'blockAutoPromote' )
			->with( $target, $this->anything(), $duration )
			->willReturn( $success );
		$blockAutopromote = new BlockAutopromote(
			$params,
			$duration,
			$blockAutopromoteStore,
			$this->getMsgLocalizer(),
			$userIdentityUtils
		);
		$this->assertSame( $success, $blockAutopromote->execute() );
	}

	public static function provideExecute(): array {
		return [
			[ true ],
			[ false ]
		];
	}

	/**
	 * @covers ::revert
	 * @dataProvider provideExecute
	 */
	public function testRevert( bool $success ) {
		$target = new UserIdentityValue( 1, 'A new user' );
		$performer = new UserIdentityValue( 2, 'Reverting user' );
		$params = $this->provideGetMessageParameters( $target )->current()[0];
		$blockAutopromoteStore = $this->createMock( BlockAutopromoteStore::class );
		$blockAutopromoteStore->expects( $this->once() )
			->method( 'unblockAutoPromote' )
			->with( $target, $performer, $this->anything() )
			->willReturn( $success );
		$userIdentityUtils = $this->createMock( UserIdentityUtils::class );
		$blockAutopromote = new BlockAutopromote(
			$params,
			0,
			$blockAutopromoteStore,
			$this->getMsgLocalizer(),
			$userIdentityUtils
		);
		$this->assertSame( $success, $blockAutopromote->revert( $performer, 'reason' ) );
	}

	/**
	 * @covers ::getMessage
	 * @dataProvider provideGetMessageParameters
	 */
	public function testGetMessage( Parameters $params ) {
		$userIdentityUtils = $this->createMock( UserIdentityUtils::class );
		$rangeBlock = new BlockAutopromote(
			$params,
			83,
			$this->createMock( BlockAutopromoteStore::class ),
			$this->getMsgLocalizer(),
			$userIdentityUtils
		);
		$this->doTestGetMessage( $rangeBlock, $params, 'abusefilter-autopromote-blocked' );
	}
}
Consequence/ThrottleTest.php000066600000014255151335014730012207 0ustar00<?php

namespace MediaWiki\Extension\AbuseFilter\Tests\Unit\Consequences\Consequence;

use BagOStuff;
use Generator;
use HashBagOStuff;
use InvalidArgumentException;
use MediaWiki\Extension\AbuseFilter\ActionSpecifier;
use MediaWiki\Extension\AbuseFilter\Consequences\Consequence\Throttle;
use MediaWiki\Extension\AbuseFilter\Consequences\ConsequenceNotPrecheckedException;
use MediaWiki\Extension\AbuseFilter\Consequences\Parameters;
use MediaWiki\Extension\AbuseFilter\Filter\ExistingFilter;
use MediaWiki\Linker\LinkTarget;
use MediaWiki\Title\Title;
use MediaWiki\User\UserEditTracker;
use MediaWiki\User\UserFactory;
use MediaWiki\User\UserIdentity;
use MediaWiki\User\UserIdentityValue;
use MediaWikiUnitTestCase;
use PHPUnit\Framework\MockObject\MockObject;
use Psr\Log\NullLogger;
use Wikimedia\TestingAccessWrapper;

/**
 * @coversDefaultClass \MediaWiki\Extension\AbuseFilter\Consequences\Consequence\Throttle
 * @covers ::__construct
 */
class ThrottleTest extends MediaWikiUnitTestCase {

	private function getThrottle(
		array $throttleParams = [],
		BagOStuff $cache = null,
		bool $globalFilter = false,
		UserIdentity $user = null,
		Title $title = null,
		UserEditTracker $editTracker = null,
		string $ip = null
	) {
		$specifier = new ActionSpecifier(
			'some-action',
			$title ?? $this->createMock( LinkTarget::class ),
			$user ?? $this->createMock( UserIdentity::class ),
			$ip ?? '1.2.3.4',
			null
		);
		$params = new Parameters(
			$this->createMock( ExistingFilter::class ),
			$globalFilter,
			$specifier
		);
		return new Throttle(
			$params,
			$throttleParams + [ 'groups' => [ 'user' ], 'count' => 3, 'period' => 60, 'id' => 1 ],
			$cache ?? new HashBagOStuff(),
			$editTracker ?? $this->createMock( UserEditTracker::class ),
			$this->createMock( UserFactory::class ),
			new NullLogger(),
			false,
			$globalFilter ? 'foo-db' : null
		);
	}

	/**
	 * @covers ::execute
	 */
	public function testExecute_notPrechecked() {
		$throttle = $this->getThrottle();
		$this->expectException( ConsequenceNotPrecheckedException::class );
		$throttle->execute();
	}

	public function provideThrottle() {
		foreach ( [ false, true ] as $global ) {
			$globalStr = $global ? 'global' : 'local';
			yield "no groups, $globalStr" => [ $this->getThrottle( [ 'groups' => [] ], null, $global ), true ];

			$cache = $this->getMockBuilder( HashBagOStuff::class )->onlyMethods( [ 'incrWithInit' ] )->getMock();
			yield "no cache value set, $globalStr" => [ $this->getThrottle( [], $cache, $global ), true, $cache ];

			$groups = [ 'ip', 'user', 'range', 'creationdate', 'editcount', 'site', 'page' ];
			foreach ( $groups as $group ) {
				$throttle = $this->getThrottle( [ 'groups' => [ $group ], 'count' => 0 ], null, $global );
				/** @var Throttle $throttleWr */
				$throttleWr = TestingAccessWrapper::newFromObject( $throttle );
				$throttleWr->setThrottled( $group );
				yield "$group set, $globalStr" => [ $throttle, false ];
			}
		}
	}

	/**
	 * @covers ::shouldDisableOtherConsequences
	 * @covers ::isThrottled
	 * @covers ::throttleKey
	 * @covers ::throttleIdentifier
	 * @dataProvider provideThrottle
	 */
	public function testShouldDisableOtherConsequences( Throttle $throttle, bool $shouldDisable ) {
		$this->assertSame( $shouldDisable, $throttle->shouldDisableOtherConsequences() );
	}

	/**
	 * @covers ::execute
	 * @covers ::setThrottled
	 * @covers ::throttleKey
	 * @covers ::throttleIdentifier
	 * @dataProvider provideThrottle
	 */
	public function testExecute( Throttle $throttle, bool $shouldDisable, MockObject $cache = null ) {
		if ( $cache ) {
			/** @var Throttle $wrapper */
			$wrapper = TestingAccessWrapper::newFromObject( $throttle );
			$groupCount = count( $wrapper->throttleParams['groups'] );
			$cache->expects( $this->exactly( $groupCount ) )->method( 'incrWithInit' );
		}
		$throttle->shouldDisableOtherConsequences();
		$this->assertSame( $shouldDisable, $throttle->execute() );
	}

	/**
	 * @covers ::throttleIdentifier
	 * @dataProvider provideThrottleDataForIdentifiers
	 */
	public function testThrottleIdentifier(
		string $type,
		?string $expected,
		string $ip,
		Title $title,
		UserIdentity $user,
		UserEditTracker $editTracker = null
	) {
		$throttle = $this->getThrottle( [], null, false, $user, $title, $editTracker, $ip );
		/** @var Throttle $throttleWrapper */
		$throttleWrapper = TestingAccessWrapper::newFromObject( $throttle );

		if ( $expected === null ) {
			$this->expectException( InvalidArgumentException::class );
		}

		$this->assertSame( $expected, $throttleWrapper->throttleIdentifier( $type ) );
	}

	public function provideThrottleDataForIdentifiers(): Generator {
		$pageName = 'AbuseFilter test throttle identifiers';
		$title = $this->createMock( Title::class );
		$title->method( 'getPrefixedText' )->willReturn( $pageName );
		$ip = '42.42.42.42';
		$anon = new UserIdentityValue( 0, $ip );

		yield 'IP, simple' => [ 'ip', "ip-$ip", $ip, $title, $anon ];
		yield 'user, anonymous' => [ 'user', 'user-0', $ip, $title, $anon ];

		$userID = 123;
		$user = new UserIdentityValue( $userID, 'Username' );
		yield 'user, registered' => [ 'user', "user-$userID", $ip, $title, $user ];

		$editcount = 5;
		$uet = $this->createMock( UserEditTracker::class );
		$uet->method( 'getUserEditCount' )->with( $user )->willReturn( $editcount );
		yield 'editcount, simple' => [ 'editcount', "editcount-$editcount", $ip, $title, $user, $uet ];

		yield 'page, simple' => [ 'page', "page-$pageName", $ip, $title, $user ];

		yield 'site, simple' => [ 'site', 'site-1', $ip, $title, $user ];

		yield 'non-existing throttle type' => [ 'foo', null, $ip, $title, $user ];

		$testingIPs = [
			'123.123.123.123' => '123.123.0.0/16',
			'100.0.0.0' => '100.0.0.0/16',
			'255.255.0.0' => '255.255.0.0/16',
			'1.2.3.4' => '1.2.0.0/16',
			'2001:0db8:0000:0000:0000:0000:1428:57ab' => '2001:DB8:0:0:0:0:0:0/64',
			'2001:0db8::1428:57ab' => '2001:DB8:0:0:0:0:0:0/64',
			'2001:0dff:ffff:ffff:ffff:ffff:ffff:ffff' => '2001:DFF:FFFF:FFFF:0:0:0:0/64',
			'2001:db8::' => '2001:DB8:0:0:0:0:0:0/64',
			'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff' => 'FFFF:FFFF:FFFF:FFFF:0:0:0:0/64'
		];
		foreach ( $testingIPs as $testIP => $expected ) {
			yield "range, $testIP" => [ 'range', "range-$expected", $testIP, $title, $user ];
		}
	}
}
Consequence/BuiltinPrioritiesTest.php000066600000002370151335014730014055 0ustar00<?php

namespace MediaWiki\Extension\AbuseFilter\Tests\Unit\Consequences\Consequence;

use BagOStuff;
use MediaWiki\Extension\AbuseFilter\Consequences\Consequence\Throttle;
use MediaWiki\Extension\AbuseFilter\Consequences\Consequence\Warn;
use MediaWiki\Extension\AbuseFilter\Consequences\Parameters;
use MediaWiki\Session\Session;
use MediaWiki\User\UserEditTracker;
use MediaWiki\User\UserFactory;
use MediaWikiUnitTestCase;
use Psr\Log\NullLogger;

/**
 * Test for priorities of builtin ConsequencesDisablerConsequence classes
 */
class BuiltinPrioritiesTest extends MediaWikiUnitTestCase {

	/**
	 * @covers \MediaWiki\Extension\AbuseFilter\Consequences\Consequence\Throttle::getSort
	 * @covers \MediaWiki\Extension\AbuseFilter\Consequences\Consequence\Warn::getSort
	 */
	public function testThrottleMoreImportantThanWarn() {
		$throttle = new Throttle(
			$this->createMock( Parameters::class ),
			[],
			$this->createMock( BagOStuff::class ),
			$this->createMock( UserEditTracker::class ),
			$this->createMock( UserFactory::class ),
			new NullLogger(),
			'',
			false
		);
		$warn = new Warn(
			$this->createMock( Parameters::class ),
			'',
			$this->createMock( Session::class )
		);
		$this->assertLessThan( $warn->getSort(), $throttle->getSort() );
	}
}
Consequence/WarnTest.php000066600000006631151335014730011310 0ustar00<?php

namespace MediaWiki\Extension\AbuseFilter\Tests\Unit\Consequences\Consequence;

use ConsequenceGetMessageTestTrait;
use MediaWiki\Extension\AbuseFilter\ActionSpecifier;
use MediaWiki\Extension\AbuseFilter\Consequences\Consequence\Warn;
use MediaWiki\Extension\AbuseFilter\Consequences\ConsequenceNotPrecheckedException;
use MediaWiki\Extension\AbuseFilter\Consequences\Parameters;
use MediaWiki\Extension\AbuseFilter\Filter\ExistingFilter;
use MediaWiki\Session\Session;
use MediaWiki\User\UserIdentityValue;
use MediaWikiUnitTestCase;
use PHPUnit\Framework\MockObject\MockObject;
use TitleValue;
use Wikimedia\TestingAccessWrapper;

/**
 * @coversDefaultClass \MediaWiki\Extension\AbuseFilter\Consequences\Consequence\Warn
 * @covers ::__construct
 */
class WarnTest extends MediaWikiUnitTestCase {
	use ConsequenceGetMessageTestTrait;

	private function getWarn( Parameters $params = null ): Warn {
		return new Warn(
			$params ?? $this->createMock( Parameters::class ),
			'foo-bar-message',
			$this->createMock( Session::class )
		);
	}

	private function getParamsAndWarnKey(): array {
		$filter = $this->createMock( ExistingFilter::class );
		$filter->method( 'getID' )->willReturn( 42 );
		$params = new Parameters(
			$filter,
			false,
			new ActionSpecifier(
				'edit',
				new TitleValue( NS_HELP, 'Some title' ),
				new UserIdentityValue( 1, 'Warned user' ),
				'1.2.3.4',
				null
			)
		);
		/** @var Warn $warnWrap */
		$warnWrap = TestingAccessWrapper::newFromObject( $this->getWarn( $params ) );
		return [ $params, $warnWrap->getWarnKey() ];
	}

	/**
	 * @covers ::execute
	 */
	public function testExecute_notPrechecked() {
		$warn = $this->getWarn();
		$this->expectException( ConsequenceNotPrecheckedException::class );
		$warn->execute();
	}

	public function provideWarnsAndSuccess() {
		$mockSession = $this->createMock( Session::class );
		$noKeyWarn = new Warn(
			$this->getParamsAndWarnKey()[0],
			'some-msg',
			$mockSession
		);
		yield 'should warn' => [ $noKeyWarn, true, $mockSession ];

		[ $params, $key ] = $this->getParamsAndWarnKey();
		$keySession = $this->createMock( Session::class );
		$keySession->method( 'offsetExists' )->with( $key )->willReturn( true );
		$keySession->method( 'offsetGet' )->with( $key )->willReturn( true );

		$keyWarn = new Warn(
			$params,
			'some-msg',
			$keySession
		);
		yield 'already warned' => [ $keyWarn, false, $keySession ];
	}

	/**
	 * @covers ::shouldDisableOtherConsequences
	 * @covers ::shouldBeWarned
	 * @covers ::getWarnKey
	 * @dataProvider provideWarnsAndSuccess
	 */
	public function testShouldDisableOtherConsequences( Warn $warn, bool $shouldDisable ) {
		$this->assertSame( $shouldDisable, $warn->shouldDisableOtherConsequences() );
	}

	/**
	 * @covers ::execute
	 * @covers ::setWarn
	 * @covers ::getWarnKey
	 * @dataProvider provideWarnsAndSuccess
	 */
	public function testExecute( Warn $warn, bool $shouldDisable, MockObject $session ) {
		$session->expects( $this->once() )
			->method( 'offsetSet' )
			->with( $this->anything(), $shouldDisable );
		$warn->shouldDisableOtherConsequences();
		$this->assertSame( $shouldDisable, $warn->execute() );
	}

	/**
	 * @covers ::getMessage
	 * @dataProvider provideGetMessageParameters
	 */
	public function testGetMessage( Parameters $params ) {
		$msg = 'some-warning-message';
		$rangeBlock = new Warn( $params, $msg, $this->createMock( Session::class ) );
		$this->doTestGetMessage( $rangeBlock, $params, $msg );
	}
}
Consequence/RangeBlockTest.php000066600000007157151335014730012414 0ustar00<?php

namespace MediaWiki\Extension\AbuseFilter\Tests\Unit\Consequences\Consequence;

use ConsequenceGetMessageTestTrait;
use MediaWiki\Block\BlockUser;
use MediaWiki\Block\BlockUserFactory;
use MediaWiki\Extension\AbuseFilter\ActionSpecifier;
use MediaWiki\Extension\AbuseFilter\Consequences\Consequence\RangeBlock;
use MediaWiki\Extension\AbuseFilter\Consequences\Parameters;
use MediaWiki\Extension\AbuseFilter\Filter\ExistingFilter;
use MediaWiki\Extension\AbuseFilter\FilterUser;
use MediaWikiUnitTestCase;
use MessageLocalizer;
use Psr\Log\NullLogger;
use Status;

/**
 * @coversDefaultClass \MediaWiki\Extension\AbuseFilter\Consequences\Consequence\RangeBlock
 * @covers \MediaWiki\Extension\AbuseFilter\Consequences\Consequence\BlockingConsequence
 */
class RangeBlockTest extends MediaWikiUnitTestCase {
	use ConsequenceGetMessageTestTrait;

	private const CIDR_LIMIT = [
		'IPv4' => 16,
		'IPv6' => 19,
	];

	private function getMsgLocalizer(): MessageLocalizer {
		$ml = $this->createMock( MessageLocalizer::class );
		$ml->method( 'msg' )->willReturnCallback( function ( $k, $p ) {
			return $this->getMockMessage( $k, $p );
		} );
		return $ml;
	}

	public static function provideExecute(): iterable {
		yield 'IPv4 range block' => [
			'1.2.3.4',
			[
				'IPv4' => 16,
				'IPv6' => 18,
			],
			'1.2.0.0/16',
			true
		];
		yield 'IPv6 range block' => [
			// random IP from https://en.wikipedia.org/w/index.php?title=IPv6&oldid=989727833
			'2001:0db8:0000:0000:0000:ff00:0042:8329',
			[
				'IPv4' => 15,
				'IPv6' => 19,
			],
			'2001:0:0:0:0:0:0:0/19',
			true
		];
		yield 'IPv4 range block constrained by core limits' => [
			'1.2.3.4',
			[
				'IPv4' => 15,
				'IPv6' => 19,
			],
			'1.2.0.0/16',
			true
		];
		yield 'IPv6 range block constrained by core limits' => [
			'2001:0db8:0000:0000:0000:ff00:0042:8329',
			[
				'IPv4' => 16,
				'IPv6' => 18,
			],
			'2001:0:0:0:0:0:0:0/19',
			true
		];
		yield 'failure' => [
			'1.2.3.4',
			self::CIDR_LIMIT,
			'1.2.0.0/16',
			false
		];
	}

	/**
	 * @dataProvider provideExecute
	 * @covers ::__construct
	 * @covers ::execute
	 */
	public function testExecute(
		string $requestIP, array $rangeBlockSize, string $target, bool $result
	) {
		$specifier = $this->createMock( ActionSpecifier::class );
		$specifier->method( 'getIP' )->willReturn( $requestIP );
		$params = new Parameters(
			$this->createMock( ExistingFilter::class ),
			false,
			$specifier
		);
		$blockUser = $this->createMock( BlockUser::class );
		$blockUser->expects( $this->once() )
			->method( 'placeBlockUnsafe' )
			->willReturn( $result ? Status::newGood() : Status::newFatal( 'error' ) );
		$blockUserFactory = $this->createMock( BlockUserFactory::class );
		$blockUserFactory->expects( $this->once() )
			->method( 'newBlockUser' )
			->with(
				$target,
				$this->anything(),
				'1 week',
				$this->anything(),
				$this->anything()
			)
			->willReturn( $blockUser );

		$rangeBlock = new RangeBlock(
			$params,
			'1 week',
			$blockUserFactory,
			$this->createMock( FilterUser::class ),
			$this->getMsgLocalizer(),
			new NullLogger(),
			$rangeBlockSize,
			self::CIDR_LIMIT
		);
		$this->assertSame( $result, $rangeBlock->execute() );
	}

	/**
	 * @covers ::getMessage
	 * @dataProvider provideGetMessageParameters
	 */
	public function testGetMessage( Parameters $params ) {
		$rangeBlock = new RangeBlock(
			$params,
			'0',
			$this->createMock( BlockUserFactory::class ),
			$this->createMock( FilterUser::class ),
			$this->getMsgLocalizer(),
			new NullLogger(),
			[ 'IPv6' => 24, 'IPv4' => 24 ],
			self::CIDR_LIMIT
		);
		$this->doTestGetMessage( $rangeBlock, $params, 'abusefilter-blocked-display' );
	}
}
Consequence/TagTest.php000066600000002152151335014730011106 0ustar00<?php

namespace MediaWiki\Extension\AbuseFilter\Tests\Unit\Consequences\Consequence;

use MediaWiki\Extension\AbuseFilter\ActionSpecifier;
use MediaWiki\Extension\AbuseFilter\ChangeTags\ChangeTagger;
use MediaWiki\Extension\AbuseFilter\Consequences\Consequence\Tag;
use MediaWiki\Extension\AbuseFilter\Consequences\Parameters;
use MediaWikiUnitTestCase;

/**
 * @coversDefaultClass \MediaWiki\Extension\AbuseFilter\Consequences\Consequence\Tag
 * @covers ::__construct
 */
class TagTest extends MediaWikiUnitTestCase {

	/**
	 * @covers ::execute
	 */
	public function testExecute() {
		$tagsToAdd = [ 'tag1', 'tag2' ];
		$specifier = $this->createMock( ActionSpecifier::class );
		$params = $this->createMock( Parameters::class );
		$params->expects( $this->once() )->method( 'getActionSpecifier' )
			->willReturn( $specifier );
		$tagger = $this->createMock( ChangeTagger::class );
		$tagger->expects( $this->once() )->method( 'addTags' )
			->with(
				$this->identicalTo( $specifier ),
				$this->identicalTo( $tagsToAdd )
			);
		$tag = new Tag( $params, $tagsToAdd, $tagger );
		$this->assertTrue( $tag->execute() );
	}
}
ConsequencesExecutorFactoryTest.php000066600000002706151335014730013632 0ustar00<?php

namespace MediaWiki\Extension\AbuseFilter\Tests\Unit\Consequences;

use MediaWiki\Config\ServiceOptions;
use MediaWiki\Extension\AbuseFilter\ActionSpecifier;
use MediaWiki\Extension\AbuseFilter\Consequences\ConsequencesExecutorFactory;
use MediaWiki\Extension\AbuseFilter\Consequences\ConsequencesFactory;
use MediaWiki\Extension\AbuseFilter\Consequences\ConsequencesLookup;
use MediaWiki\Extension\AbuseFilter\Consequences\ConsequencesRegistry;
use MediaWiki\Extension\AbuseFilter\FilterLookup;
use MediaWiki\Extension\AbuseFilter\Variables\VariableHolder;
use MediaWiki\User\UserIdentityUtils;
use MediaWikiUnitTestCase;
use Psr\Log\NullLogger;

/**
 * @group Test
 * @group AbuseFilter
 * @coversDefaultClass \MediaWiki\Extension\AbuseFilter\Consequences\ConsequencesExecutorFactory
 */
class ConsequencesExecutorFactoryTest extends MediaWikiUnitTestCase {

	/**
	 * @covers ::__construct
	 * @covers ::newExecutor
	 */
	public function testNewExecutor() {
		$factory = new ConsequencesExecutorFactory(
			$this->createMock( ConsequencesLookup::class ),
			$this->createMock( ConsequencesFactory::class ),
			$this->createMock( ConsequencesRegistry::class ),
			$this->createMock( FilterLookup::class ),
			new NullLogger(),
			$this->createMock( UserIdentityUtils::class ),
			$this->createMock( ServiceOptions::class )
		);
		$factory->newExecutor(
			$this->createMock( ActionSpecifier::class ),
			new VariableHolder()
		);
		$this->addToAssertionCount( 1 );
	}
}
ConsequencesFactoryTest.php000066600000007015151335014730012111 0ustar00<?php

namespace MediaWiki\Extension\AbuseFilter\Tests\Unit\Consequences;

use HashBagOStuff;
use MediaWiki\Block\BlockUserFactory;
use MediaWiki\Block\DatabaseBlockStore;
use MediaWiki\Config\ServiceOptions;
use MediaWiki\Extension\AbuseFilter\BlockAutopromoteStore;
use MediaWiki\Extension\AbuseFilter\ChangeTags\ChangeTagger;
use MediaWiki\Extension\AbuseFilter\Consequences\ConsequencesFactory;
use MediaWiki\Extension\AbuseFilter\Consequences\Parameters;
use MediaWiki\Extension\AbuseFilter\FilterUser;
use MediaWiki\Extension\AbuseFilter\Variables\VariableHolder;
use MediaWiki\Session\Session;
use MediaWiki\User\UserEditTracker;
use MediaWiki\User\UserFactory;
use MediaWiki\User\UserGroupManager;
use MediaWiki\User\UserIdentityUtils;
use MediaWikiUnitTestCase;
use MessageLocalizer;
use Psr\Log\NullLogger;

/**
 * @group Test
 * @group AbuseFilter
 * @coversDefaultClass \MediaWiki\Extension\AbuseFilter\Consequences\ConsequencesFactory
 * @covers ::__construct
 */
class ConsequencesFactoryTest extends MediaWikiUnitTestCase {

	private function getFactory(): ConsequencesFactory {
		$opts = new ServiceOptions(
			ConsequencesFactory::CONSTRUCTOR_OPTIONS,
			[
				'AbuseFilterCentralDB' => false,
				'AbuseFilterIsCentral' => false,
				'AbuseFilterRangeBlockSize' => [],
				'BlockCIDRLimit' => [],
			]
		);

		$consequencesFactory = new ConsequencesFactory(
			$opts,
			new NullLogger(),
			$this->createMock( BlockUserFactory::class ),
			$this->createMock( DatabaseBlockStore::class ),
			$this->createMock( UserGroupManager::class ),
			new HashBagOStuff(),
			$this->createMock( ChangeTagger::class ),
			$this->createMock( BlockAutopromoteStore::class ),
			$this->createMock( FilterUser::class ),
			$this->createMock( MessageLocalizer::class ),
			$this->createMock( UserEditTracker::class ),
			$this->createMock( UserFactory::class ),
			$this->createMock( UserIdentityUtils::class )
		);
		$consequencesFactory->setSession( $this->createMock( Session::class ) );

		return $consequencesFactory;
	}

	/**
	 * @covers ::newBlock
	 */
	public function testNewBlock() {
		$this->getFactory()->newBlock( $this->createMock( Parameters::class ), '', false );
		$this->addToAssertionCount( 1 );
	}

	/**
	 * @covers ::newRangeBlock
	 */
	public function testNewRangeBlock() {
		$this->getFactory()->newRangeBlock( $this->createMock( Parameters::class ), '' );
		$this->addToAssertionCount( 1 );
	}

	/**
	 * @covers ::newDegroup
	 */
	public function testNewDegroup() {
		$this->getFactory()->newDegroup( $this->createMock( Parameters::class ), new VariableHolder() );
		$this->addToAssertionCount( 1 );
	}

	/**
	 * @covers ::newBlockAutopromote
	 */
	public function testNewBlockAutopromote() {
		$this->getFactory()->newBlockAutopromote( $this->createMock( Parameters::class ), 42 );
		$this->addToAssertionCount( 1 );
	}

	/**
	 * @covers ::newThrottle
	 */
	public function testNewThrottle() {
		$this->getFactory()->newThrottle( $this->createMock( Parameters::class ), [] );
		$this->addToAssertionCount( 1 );
	}

	/**
	 * @covers ::newWarn
	 */
	public function testNewWarn() {
		$this->getFactory()->newWarn( $this->createMock( Parameters::class ), '' );
		$this->addToAssertionCount( 1 );
	}

	/**
	 * @covers ::newDisallow
	 */
	public function testNewDisallow() {
		$this->getFactory()->newDisallow( $this->createMock( Parameters::class ), '' );
		$this->addToAssertionCount( 1 );
	}

	/**
	 * @covers ::newTag
	 */
	public function testNewTag() {
		$this->getFactory()->newTag( $this->createMock( Parameters::class ), [] );
		$this->addToAssertionCount( 1 );
	}
}
ParametersTest.php000066600000002734151335014730010234 0ustar00<?php

namespace MediaWiki\Extension\AbuseFilter\Tests\Unit\Consequences;

use MediaWiki\Extension\AbuseFilter\ActionSpecifier;
use MediaWiki\Extension\AbuseFilter\Consequences\Parameters;
use MediaWiki\Extension\AbuseFilter\Filter\ExistingFilter;
use MediaWiki\Linker\LinkTarget;
use MediaWiki\User\UserIdentity;
use MediaWikiUnitTestCase;

/**
 * @group Test
 * @group AbuseFilter
 * @coversDefaultClass \MediaWiki\Extension\AbuseFilter\Consequences\Parameters
 */
class ParametersTest extends MediaWikiUnitTestCase {
	/**
	 * @covers ::__construct
	 * @covers ::getFilter
	 * @covers ::getIsGlobalFilter
	 * @covers ::getActionSpecifier
	 * @covers ::getUser
	 * @covers ::getTarget
	 * @covers ::getAction
	 */
	public function testGetters() {
		$filter = $this->createMock( ExistingFilter::class );
		$global = true;
		$user = $this->createMock( UserIdentity::class );
		$target = $this->createMock( LinkTarget::class );
		$action = 'some-action';
		$specifier = new ActionSpecifier( $action, $target, $user, '1.2.3.4', null );
		$params = new Parameters( $filter, $global, $specifier );

		$this->assertSame( $filter, $params->getFilter(), 'filter' );
		$this->assertSame( $global, $params->getIsGlobalFilter(), 'global' );
		$this->assertSame( $specifier, $params->getActionSpecifier(), 'specifier' );
		$this->assertSame( $user, $params->getUser(), 'user' );
		$this->assertSame( $target, $params->getTarget(), 'target' );
		$this->assertSame( $action, $params->getAction(), 'action' );
	}
}
ConsequencesExecutorTest.php000066600000015071151335014730012301 0ustar00<?php

namespace MediaWiki\Extension\AbuseFilter\Tests\Unit\Consequences;

use MediaWiki\Config\ServiceOptions;
use MediaWiki\Extension\AbuseFilter\ActionSpecifier;
use MediaWiki\Extension\AbuseFilter\Consequences\Consequence\Block;
use MediaWiki\Extension\AbuseFilter\Consequences\Consequence\Throttle;
use MediaWiki\Extension\AbuseFilter\Consequences\Consequence\Warn;
use MediaWiki\Extension\AbuseFilter\Consequences\ConsequencesExecutor;
use MediaWiki\Extension\AbuseFilter\Consequences\ConsequencesFactory;
use MediaWiki\Extension\AbuseFilter\Consequences\ConsequencesLookup;
use MediaWiki\Extension\AbuseFilter\Consequences\ConsequencesRegistry;
use MediaWiki\Extension\AbuseFilter\Consequences\Parameters;
use MediaWiki\Extension\AbuseFilter\FilterLookup;
use MediaWiki\Extension\AbuseFilter\Variables\VariableHolder;
use MediaWiki\Linker\LinkTarget;
use MediaWiki\User\UserIdentity;
use MediaWiki\User\UserIdentityUtils;
use MediaWikiUnitTestCase;
use PHPUnit\Framework\MockObject\MockObject;
use Psr\Log\NullLogger;
use Wikimedia\TestingAccessWrapper;

/**
 * @group Test
 * @group AbuseFilter
 * @coversDefaultClass \MediaWiki\Extension\AbuseFilter\Consequences\ConsequencesExecutor
 */
class ConsequencesExecutorTest extends MediaWikiUnitTestCase {
	/**
	 * Returns a ConsequencesFactory where:
	 *  - all the ConsequenceDisablerConsequence's created will disable other consequences.
	 *  - the block expiry is set as normal
	 * @return ConsequencesFactory|MockObject
	 */
	private function getConsequencesFactory() {
		$consFactory = $this->createMock( ConsequencesFactory::class );
		$warn = $this->createMock( Warn::class );
		$warn->method( 'shouldDisableOtherConsequences' )->willReturn( true );
		$consFactory->method( 'newWarn' )->willReturn( $warn );
		$throttle = $this->createMock( Throttle::class );
		$throttle->method( 'shouldDisableOtherConsequences' )->willReturn( true );
		$consFactory->method( 'newThrottle' )->willReturn( $throttle );
		$consFactory->method( 'newBlock' )->willReturnCallback(
			function ( Parameters $params, string $expiry, bool $preventsTalk ): Block {
				$block = $this->createMock( Block::class );
				$block->method( 'getExpiry' )->willReturn( $expiry );
				return $block;
			}
		);
		return $consFactory;
	}

	private function getConsExecutor( array $consequences ): ConsequencesExecutor {
		$locallyDisabledActions = [
			'flag' => false,
			'throttle' => false,
			'warn' => false,
			'disallow' => false,
			'blockautopromote' => true,
			'block' => true,
			'rangeblock' => true,
			'degroup' => true,
			'tag' => false
		];
		$options = new ServiceOptions(
			ConsequencesExecutor::CONSTRUCTOR_OPTIONS,
			[
				'AbuseFilterLocallyDisabledGlobalActions' => $locallyDisabledActions,
				'AbuseFilterBlockDuration' => '24 hours',
				'AbuseFilterAnonBlockDuration' => '24 hours',
				'AbuseFilterBlockAutopromoteDuration' => '5 days',
			]
		);
		$consRegistry = $this->createMock( ConsequencesRegistry::class );
		$dangerousActions = TestingAccessWrapper::constant( ConsequencesRegistry::class, 'DANGEROUS_ACTIONS' );
		$consRegistry->method( 'getDangerousActionNames' )->willReturn( $dangerousActions );
		$consLookup = $this->createMock( ConsequencesLookup::class );
		$consLookup->expects( $this->atLeastOnce() )
			->method( 'getConsequencesForFilters' )
			->with( array_keys( $consequences ) )
			->willReturn( $consequences );

		return new ConsequencesExecutor(
			$consLookup,
			$this->getConsequencesFactory(),
			$consRegistry,
			$this->createMock( FilterLookup::class ),
			new NullLogger,
			$this->createMock( UserIdentityUtils::class ),
			$options,
			new ActionSpecifier(
				'edit',
				$this->createMock( LinkTarget::class ),
				$this->createMock( UserIdentity::class ),
				'1.2.3.4',
				null
			),
			new VariableHolder
		);
	}

	/**
	 * @param array $rawConsequences A raw, unfiltered list of consequences
	 * @param array $expectedKeys
	 *
	 * @covers ::getActualConsequencesToExecute
	 * @covers ::replaceLegacyParameters
	 * @covers ::specializeParameters
	 * @covers ::removeForbiddenConsequences
	 * @covers ::replaceArraysWithConsequences
	 * @covers ::applyConsequenceDisablers
	 * @covers ::deduplicateConsequences
	 * @covers ::removeRedundantConsequences
	 * @dataProvider provideConsequences
	 */
	public function testGetActualConsequencesToExecute(
		array $rawConsequences,
		array $expectedKeys
	): void {
		$executor = $this->getConsExecutor( $rawConsequences );
		$actual = $executor->getActualConsequencesToExecute( array_keys( $rawConsequences ) );

		$actualKeys = [];
		foreach ( $actual as $filter => $actions ) {
			$actualKeys[$filter] = array_keys( $actions );
		}

		$this->assertEquals( $expectedKeys, $actualKeys );
	}

	/**
	 * @return array
	 */
	public static function provideConsequences(): array {
		return [
			'warn and throttle exclude other actions' => [
				[
					2 => [
						'warn' => [
							'abusefilter-warning'
						],
						'tag' => [
							'some tag'
						]
					],
					13 => [
						'throttle' => [
							'13',
							'14,15',
							'user'
						],
						'disallow' => []
					],
					168 => [
						'degroup' => []
					]
				],
				[
					2 => [ 'warn' ],
					13 => [ 'throttle' ],
					168 => [ 'degroup' ]
				],
			],
			'warn excludes other actions, block excludes disallow' => [
				[
					3 => [
						'tag' => [
							'some tag'
						]
					],
					'global-2' => [
						'warn' => [
							'abusefilter-beautiful-warning'
						],
						'degroup' => []
					],
					4 => [
						'disallow' => [],
						'block' => [
							'blocktalk',
							'15 minutes',
							'indefinite'
						]
					]
				],
				[
					3 => [ 'tag' ],
					'global-2' => [ 'warn' ],
					4 => [ 'block' ]
				],
			],
			'some global actions are disabled locally, the longest block is chosen' => [
				[
					'global-1' => [
						'blockautopromote' => [],
						'block' => [
							'blocktalk',
							'indefinite',
							'indefinite'
						]
					],
					1 => [
						'block' => [
							'blocktalk',
							'4 hours',
							'4 hours'
						]
					],
					2 => [
						'degroup' => [],
						'block' => [
							'blocktalk',
							'infinity',
							'never'
						]
					]
				],
				[
					'global-1' => [],
					1 => [],
					2 => [ 'degroup', 'block' ]
				],
			],
			'do not use a block that will be skipped as the longer one' => [
				[
					1 => [
						'warn' => [
							'abusefilter-warning'
						],
						'block' => [
							'blocktalk',
							'4 hours',
							'4 hours'
						]
					],
					2 => [
						'block' => [
							'blocktalk',
							'2 hours',
							'2 hours'
						]
					]
				],
				[
					1 => [ 'warn' ],
					2 => [ 'block' ]
				],
			],
		];
	}
}
Back to Directory File Manager