summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'AbuseFilter/tests/phpunit/AbuseFilterDBTest.php')
-rw-r--r--AbuseFilter/tests/phpunit/AbuseFilterDBTest.php362
1 files changed, 362 insertions, 0 deletions
diff --git a/AbuseFilter/tests/phpunit/AbuseFilterDBTest.php b/AbuseFilter/tests/phpunit/AbuseFilterDBTest.php
new file mode 100644
index 00000000..c0358109
--- /dev/null
+++ b/AbuseFilter/tests/phpunit/AbuseFilterDBTest.php
@@ -0,0 +1,362 @@
+<?php
+
+use MediaWiki\Revision\MutableRevisionRecord;
+use MediaWiki\Revision\RevisionRecord;
+use MediaWiki\Revision\SlotRecord;
+use PHPUnit\Framework\MockObject\MockObject;
+
+/**
+ * Generic tests for utility functions in AbuseFilter that require DB access
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ *
+ * @license GPL-2.0-or-later
+ */
+
+/**
+ * @group Test
+ * @group AbuseFilter
+ * @group AbuseFilterGeneric
+ * @group Database
+ */
+class AbuseFilterDBTest extends MediaWikiTestCase {
+ /**
+ * @var array These tables will be deleted in parent::tearDown.
+ * We need it to happen to make tests on fresh pages.
+ */
+ protected $tablesUsed = [
+ 'abuse_filter',
+ 'abuse_filter_history',
+ 'abuse_filter_log'
+ ];
+
+ /**
+ * Test storing and loading the var dump. See also AbuseFilterConsequencesTest::testVarDump
+ *
+ * @param array $variables Map of [ name => value ] to build an AbuseFilterVariableHolder with
+ * @param ?array $expectedValues Null to use $variables
+ * @covers AbuseFilter::storeVarDump
+ * @covers AbuseFilter::loadVarDump
+ * @covers AbuseFilterVariableHolder::dumpAllVars
+ * @dataProvider provideVariables
+ */
+ public function testVarDump( array $variables, array $expectedValues = null ) {
+ global $wgCompressRevisions, $wgDefaultExternalStore;
+
+ $holder = AbuseFilterVariableHolder::newFromArray( $variables );
+
+ $insertID = AbuseFilter::storeVarDump( $holder );
+
+ $flags = $this->db->selectField(
+ 'text',
+ 'old_flags',
+ '',
+ __METHOD__,
+ [ 'ORDER BY' => 'old_id DESC' ]
+ );
+ $this->assertNotFalse( $flags, 'The var dump has not been saved.' );
+ $flags = $flags === '' ? [] : explode( ',', $flags );
+
+ $expectedFlags = [ 'utf-8' ];
+ if ( $wgCompressRevisions ) {
+ $expectedFlags[] = 'gzip';
+ }
+ if ( $wgDefaultExternalStore ) {
+ $expectedFlags[] = 'external';
+ }
+
+ $this->assertEquals( $expectedFlags, $flags, 'The var dump does not have the correct flags' );
+
+ $dump = AbuseFilter::loadVarDump( "stored-text:$insertID" );
+ $expected = $expectedValues ? AbuseFilterVariableHolder::newFromArray( $expectedValues ) : $holder;
+ $this->assertEquals( $expected, $dump, 'The var dump is not saved correctly' );
+ }
+
+ /**
+ * Data provider for testVarDump
+ *
+ * @return array
+ */
+ public function provideVariables() {
+ return [
+ 'Only basic variables' => [
+ [
+ 'action' => 'edit',
+ 'old_wikitext' => 'Old text',
+ 'new_wikitext' => 'New text'
+ ]
+ ],
+ 'Normal case' => [
+ [
+ 'action' => 'edit',
+ 'old_wikitext' => 'Old text',
+ 'new_wikitext' => 'New text',
+ 'user_editcount' => 15,
+ 'added_lines' => [ 'Foo', '', 'Bar' ]
+ ]
+ ],
+ 'Deprecated variables' => [
+ [
+ 'action' => 'edit',
+ 'old_wikitext' => 'Old text',
+ 'new_wikitext' => 'New text',
+ 'article_articleid' => 11745,
+ 'article_first_contributor' => 'Good guy'
+ ],
+ [
+ 'action' => 'edit',
+ 'old_wikitext' => 'Old text',
+ 'new_wikitext' => 'New text',
+ 'page_id' => 11745,
+ 'page_first_contributor' => 'Good guy'
+ ]
+ ],
+ 'Move action' => [
+ [
+ 'action' => 'move',
+ 'old_wikitext' => 'Old text',
+ 'new_wikitext' => 'New text',
+ 'all_links' => [ 'https://en.wikipedia.org' ],
+ 'moved_to_id' => 156,
+ 'moved_to_prefixedtitle' => 'MediaWiki:Foobar.js',
+ 'new_content_model' => CONTENT_MODEL_JAVASCRIPT
+ ]
+ ],
+ 'Delete action' => [
+ [
+ 'old_wikitext' => 'Old text',
+ 'new_wikitext' => 'New text',
+ 'timestamp' => 1546000295,
+ 'action' => 'delete',
+ 'page_namespace' => 114
+ ]
+ ],
+ 'Disabled vars' => [
+ [
+ 'action' => 'edit',
+ 'old_wikitext' => 'Old text',
+ 'new_wikitext' => 'New text',
+ 'old_html' => 'Foo <small>bar</small> <s>lol</s>.',
+ 'old_text' => 'Foobar'
+ ]
+ ],
+ 'Account creation' => [
+ [
+ 'action' => 'createaccount',
+ 'accountname' => 'XXX'
+ ]
+ ]
+ ];
+ }
+
+ /**
+ * @param RevisionRecord|null $rev The revision being converted
+ * @param bool $sysop Whether the user should be a sysop (i.e. able to see deleted stuff)
+ * @param string $expected The expected textual representation of the Revision
+ * @covers AbuseFilter::revisionToString
+ * @dataProvider provideRevisionToString
+ * @todo This should be a unit test...
+ */
+ public function testRevisionToString( ?RevisionRecord $rev, bool $sysop, string $expected ) {
+ /** @var MockObject|User $user */
+ $user = $this->getMockBuilder( User::class )
+ ->setMethods( [ 'getEffectiveGroups' ] )
+ ->getMock();
+ if ( $sysop ) {
+ $user->expects( $this->atLeastOnce() )
+ ->method( 'getEffectiveGroups' )
+ ->willReturn( [ 'user', 'sysop' ] );
+ } else {
+ $user->expects( $this->any() )
+ ->method( 'getEffectiveGroups' )
+ ->willReturn( [ 'user' ] );
+ }
+
+ $user->clearInstanceCache();
+
+ $actual = AbuseFilter::revisionToString( $rev, $user );
+ $this->assertSame( $expected, $actual );
+ }
+
+ /**
+ * Data provider for testRevisionToString
+ *
+ * @return Generator|array
+ */
+ public function provideRevisionToString() {
+ yield 'no revision' => [ null, false, '' ];
+
+ $title = Title::newFromText( __METHOD__ );
+ $revRec = new MutableRevisionRecord( $title );
+ $revRec->setContent( SlotRecord::MAIN, new TextContent( 'Main slot text.' ) );
+
+ yield 'RevisionRecord instance' => [
+ $revRec,
+ false,
+ 'Main slot text.'
+ ];
+
+ $revRec = new MutableRevisionRecord( $title );
+ $revRec->setContent( SlotRecord::MAIN, new TextContent( 'Main slot text.' ) );
+ $revRec->setContent( 'aux', new TextContent( 'Aux slot content.' ) );
+ yield 'Multi-slot' => [
+ $revRec,
+ false,
+ "Main slot text.\n\nAux slot content."
+ ];
+
+ $revRec = new MutableRevisionRecord( $title );
+ $revRec->setContent( SlotRecord::MAIN, new TextContent( 'Main slot text.' ) );
+ $revRec->setVisibility( RevisionRecord::DELETED_TEXT );
+ yield 'Suppressed revision, unprivileged' => [
+ $revRec,
+ false,
+ ''
+ ];
+
+ yield 'Suppressed revision, privileged' => [
+ $revRec,
+ true,
+ 'Main slot text.'
+ ];
+ }
+
+ /**
+ * Check that our tag validation is working properly. Note that we only need one test
+ * for each called function. Consistency within ChangeTags functions should be
+ * assured by tests in core. The test for canAddTagsAccompanyingChange and canCreateTag
+ * are missing because they won't actually fail, never. Resolving T173917 would
+ * greatly improve the situation and could help writing better tests.
+ *
+ * @param string $tag The tag to validate
+ * @param string|null $error The expected error message. Null if validations should pass
+ * @covers AbuseFilter::isAllowedTag
+ * @dataProvider provideTags
+ */
+ public function testIsAllowedTag( $tag, $error ) {
+ $status = AbuseFilter::isAllowedTag( $tag );
+
+ if ( !$status->isGood() ) {
+ $actualError = $status->getErrors();
+ $actualError = $actualError[0]['message'];
+ } else {
+ $actualError = null;
+ if ( $error !== null ) {
+ $this->fail( "Tag validation returned a valid status instead of the expected '$error' error." );
+ }
+ }
+
+ $this->assertSame(
+ $error,
+ $actualError,
+ "Expected message '$error', got '$actualError' while validating the tag '$tag'."
+ );
+ }
+
+ /**
+ * Data provider for testIsAllowedTag
+ * @return array
+ */
+ public function provideTags() {
+ return [
+ [ 'a|b', 'tags-create-invalid-chars' ],
+ [ 'mw-undo', 'abusefilter-edit-bad-tags' ],
+ [ 'abusefilter-condition-limit', 'abusefilter-tag-reserved' ],
+ [ 'my_tag', null ],
+ ];
+ }
+
+ /**
+ * Test for the wiki_name variable.
+ *
+ * @covers AbuseFilter::generateGenericVars
+ * @covers AFComputedVariable::compute
+ */
+ public function testWikiNameVar() {
+ $name = 'foo';
+ $prefix = 'bar';
+ $this->setMwGlobals( [
+ 'wgDBname' => $name,
+ 'wgDBprefix' => $prefix
+ ] );
+
+ $vars = new AbuseFilterVariableHolder();
+ $vars->setLazyLoadVar( 'wiki_name', 'get-wiki-name', [] );
+ $this->assertSame(
+ "$name-$prefix",
+ $vars->getVar( 'wiki_name' )->toNative()
+ );
+ }
+
+ /**
+ * Test for the wiki_language variable.
+ *
+ * @covers AbuseFilter::generateGenericVars
+ * @covers AFComputedVariable::compute
+ */
+ public function testWikiLanguageVar() {
+ $fakeCode = 'foobar';
+ $fakeLang = $this->getMockBuilder( Language::class )
+ ->setMethods( [ 'getCode' ] )
+ ->getMock();
+ $fakeLang->method( 'getCode' )->willReturn( $fakeCode );
+ $this->setService( 'ContentLanguage', $fakeLang );
+
+ $vars = new AbuseFilterVariableHolder();
+ $vars->setLazyLoadVar( 'wiki_language', 'get-wiki-language', [] );
+ $this->assertSame(
+ $fakeCode,
+ $vars->getVar( 'wiki_language' )->toNative()
+ );
+ }
+
+ /**
+ * @param RevisionRecord $revRec
+ * @param bool $privileged
+ * @param bool $expected
+ * @dataProvider provideUserCanViewRev
+ * @covers AbuseFilter::userCanViewRev
+ */
+ public function testUserCanViewRev( RevisionRecord $revRec, bool $privileged, bool $expected ) {
+ $user = $privileged
+ ? $this->getTestUser( 'suppress' )->getUser()
+ : $this->getTestUser()->getUser();
+ $this->assertSame( $expected, AbuseFilter::userCanViewRev( $revRec, $user ) );
+ }
+
+ /**
+ * @return Generator|array
+ */
+ public function provideUserCanViewRev() {
+ $title = Title::newFromText( __METHOD__ );
+
+ $visible = new MutableRevisionRecord( $title );
+ yield 'Visible, not privileged' => [ $visible, false, true ];
+ yield 'Visible, privileged' => [ $visible, true, true ];
+
+ $userSup = new MutableRevisionRecord( $title );
+ $userSup->setVisibility( RevisionRecord::SUPPRESSED_USER );
+ yield 'User suppressed, not privileged' => [ $userSup, false, false ];
+ yield 'User suppressed, privileged' => [ $userSup, true, true ];
+
+ $allSupp = new MutableRevisionRecord( $title );
+ $allSupp->setVisibility( RevisionRecord::SUPPRESSED_ALL );
+ yield 'All suppressed, not privileged' => [ $allSupp, false, false ];
+ yield 'All suppressed, privileged' => [ $allSupp, true, true ];
+ }
+}