mediawiki-extensions-Nuke/tests/phpunit/integration/SpecialNukeTest.php
Chlod Alejandro def1219fda
Add more details to post-delete page
This adds the following to the post-delete page:
* Number of pages queued for deletion (or deleted immediately)
* Username, talk page link, and Special:Block link for the target
  (if the target is a user)
* Number of pages not queued for deletion (i.e. skipped)
* List of pages not queued for deletion

This adds relevant tests, and also modifies the "uploadTestFile"
utility function, which now randomizes file name to ensure that
files uploaded by users regardless of title are also deleted.

Bug: T364223
Bug: T364225
Change-Id: I60cf1a1c54cdaa3ae4570b4d2bef7e7cf39b47e5
2024-11-20 23:13:56 +08:00

1026 lines
33 KiB
PHP

<?php
namespace MediaWiki\Extension\Nuke\Test\Integration;
use ErrorPageError;
use MediaWiki\Context\RequestContext;
use MediaWiki\Extension\Nuke\SpecialNuke;
use MediaWiki\Permissions\UltimateAuthority;
use MediaWiki\Request\FauxRequest;
use MediaWiki\Tests\User\TempUser\TempUserTestTrait;
use MediaWiki\Title\Title;
use MediaWiki\User\User;
use PermissionsError;
use SpecialPageTestBase;
use UploadFromFile;
use UserBlockedError;
/**
* @group Database
* @covers \MediaWiki\Extension\Nuke\SpecialNuke
*/
class SpecialNukeTest extends SpecialPageTestBase {
use TempUserTestTrait;
protected function newSpecialPage(): SpecialNuke {
$services = $this->getServiceContainer();
return new SpecialNuke(
$services->getJobQueueGroup(),
$services->getDBLoadBalancerFactory(),
$services->getPermissionManager(),
$services->getRepoGroup(),
$services->getUserFactory(),
$services->getUserOptionsLookup(),
$services->getUserNamePrefixSearch(),
$services->getUserNameUtils(),
$services->getNamespaceInfo(),
$services->getContentLanguage(),
$services->getService( 'NukeIPLookup' )
);
}
/**
* Ensure that the prompt prevents a user blocked from deleting
* pages from accessing the form.
*
* @return void
*/
public function testBlocked() {
$user = $this->getTestSysop()->getUser();
$performer = new UltimateAuthority( $user );
// Self-blocks should still prevent the form from being shown
$this->getServiceContainer()
->getBlockUserFactory()
->newBlockUser( $user, $performer, 'infinity', 'SpecialNukeTest::testBlocked' )
->placeBlockUnsafe();
$this->expectException( UserBlockedError::class );
$this->executeSpecialPage( '', null, 'qqx', $performer );
$this->getServiceContainer()
->getUnblockUserFactory()
->newUnblockUser( $user, $performer, 'SpecialNukeTest::testBlocked' )
->unblockUnsafe();
}
public function testProtectedPage() {
$pages = [];
$pages[] = $this->insertPage( 'Page123', 'Test', NS_MAIN )[ 'title' ];
$pages[] = $this->insertPage( 'Page456', 'Test', NS_MAIN )[ 'title' ];
$pages[] = $this->insertPage( 'Page789', 'Test', NS_MAIN )[ 'title' ];
$this->overrideConfigValues( [
"GroupPermissions" => [
"testgroup" => [
"nuke" => true,
"delete" => true
]
]
] );
$services = $this->getServiceContainer();
$page = $services->getWikiPageFactory()
->newFromTitle( $pages[2] );
$restrictions = [];
foreach ( $services->getRestrictionStore()->listApplicableRestrictionTypes( $page ) as $type ) {
$restrictions[$type] = "sysop";
}
$cascade = false;
$page->doUpdateRestrictions(
$restrictions,
[],
$cascade,
"test",
$this->getTestSysop()->getUser()
);
$testUser = $this->getTestUser( [ "testgroup" ] );
$request = new FauxRequest( [
'action' => 'delete',
'wpDeleteReasonList' => 'other',
'wpReason' => 'Reason',
'pages' => $pages,
'wpFormIdentifier' => 'nukelist',
'wpEditToken' => $testUser->getUser()->getEditToken(),
], true );
$this->expectException( PermissionsError::class );
$this->executeSpecialPage( '', $request, 'qqx', $testUser->getAuthority() );
foreach ( $pages as $checkedPage ) {
$this->assertTrue( $checkedPage->exists() );
}
}
public function testPrompt() {
$admin = $this->getTestSysop()->getUser();
$this->disableAutoCreateTempUser();
$performer = new UltimateAuthority( $admin );
[ $html ] = $this->executeSpecialPage( '', null, 'qqx', $performer );
$this->assertStringContainsString( '(nuke-summary)', $html );
$this->assertStringContainsString( '(nuke-tools)', $html );
}
/**
* Ensure that the prompt prevents a nuke user without the checkuser-temporary-account permission
* from performing CheckUser IP lookups
*
* @return void
*/
public function testPromptCheckUserNoPermission() {
$this->expectException( PermissionsError::class );
$this->markTestSkippedIfExtensionNotLoaded( 'CheckUser' );
$this->enableAutoCreateTempUser();
$ip = '1.2.3.4';
$adminUser = $this->getTestSysop()->getUser();
$permissionManager = $this->getServiceContainer()->getPermissionManager();
$permissions = $permissionManager->getUserPermissions( $adminUser );
$permissions = array_diff( $permissions, [ 'checkuser-temporary-account' ] );
$permissionManager->overrideUserRightsForTesting( $adminUser,
$permissions
);
$performer = new UltimateAuthority( $adminUser );
$request = new FauxRequest( [
'target' => $ip,
'action' => 'submit',
], true );
[ $html ] = $this->executeSpecialPage( '', $request, 'qqx', $performer );
}
/**
* Ensure that the prompt prevents a nuke user who hasn't accepted the agreement
* from performing CheckUser IP lookups
*
* @return void
*/
public function testPromptCheckUserNoPreference() {
$this->expectException( ErrorPageError::class );
$this->expectExceptionMessage(
'To view temporary account contributions for an IP, please accept' .
' the agreement in [[Special:Preferences|your preferences]].'
);
$this->markTestSkippedIfExtensionNotLoaded( 'CheckUser' );
$this->enableAutoCreateTempUser();
$ip = '1.2.3.4';
$this->overrideConfigValues( [
'GroupPermissions' => [
'testgroup' => [
'nuke' => true,
'checkuser-temporary-account' => true
]
]
] );
$adminUser = $this->getTestUser( [ 'testgroup' ] )->getUser();
$request = new FauxRequest( [
'target' => $ip,
'action' => 'submit',
], true );
$adminPerformer = new UltimateAuthority( $adminUser );
[ $html ] = $this->executeSpecialPage( '', $request, 'qqx', $adminPerformer );
}
/**
* Ensure that the prompt displays the correct messages when
* temp accounts and CheckUser are enabled
*
* @return void
*/
public function testPromptWithCheckUser() {
$this->markTestSkippedIfExtensionNotLoaded( 'CheckUser' );
$admin = $this->getTestSysop()->getUser();
$this->enableAutoCreateTempUser();
$performer = new UltimateAuthority( $admin );
[ $html ] = $this->executeSpecialPage( '', null, 'qqx', $performer );
$this->assertStringContainsString( '(nuke-summary)', $html );
$this->assertStringContainsString( '(nuke-tools-tempaccount)', $html );
}
public function testPromptTarget() {
$testUser = $this->getTestUser();
$performer = $testUser->getAuthority();
$this->editPage( 'Target1', 'test', "", NS_MAIN, $performer );
$this->editPage( 'Target2', 'test', "", NS_MAIN, $performer );
$adminUser = $this->getTestSysop()->getUser();
$request = new FauxRequest( [
'target' => $testUser->getUser()->getName()
] );
$adminPerformer = new UltimateAuthority( $adminUser );
[ $html ] = $this->executeSpecialPage( '', $request, 'qqx', $adminPerformer );
$this->assertStringContainsString( 'Target1', $html );
$this->assertStringContainsString( 'Target2', $html );
}
/**
* Ensure that the prompt works with anon IP searches when
* temp accounts are disabled
*
* @return void
*/
public function testPromptTargetAnonUser() {
$this->disableAutoCreateTempUser( [ 'known' => false ] );
$ip = '127.0.0.1';
$testUser = $this->getServiceContainer()->getUserFactory()->newAnonymous( $ip );
$performer = new UltimateAuthority( $testUser );
$this->editPage( 'Target1', 'test', "", NS_MAIN, $performer );
$this->editPage( 'Target2', 'test', "", NS_MAIN, $performer );
$adminUser = $this->getTestSysop()->getUser();
$request = new FauxRequest( [
'target' => $testUser->getUser()->getName()
] );
$adminPerformer = new UltimateAuthority( $adminUser );
[ $html ] = $this->executeSpecialPage( '', $request, 'qqx', $adminPerformer );
$this->assertStringContainsString( '(nuke-list:', $html );
$this->assertStringContainsString( 'Target1', $html );
$this->assertStringContainsString( 'Target2', $html );
$this->assertEquals( 2, substr_count( $html, '(nuke-editby: 127.0.0.1)' ) );
}
/**
* Ensure that the prompt returns temp accounts from IP lookups when
* temp accounts and CheckUser are enabled
*
* @return void
*/
public function testPromptTargetCheckUser() {
$this->markTestSkippedIfExtensionNotLoaded( 'CheckUser' );
$this->enableAutoCreateTempUser();
$ip = '1.2.3.4';
RequestContext::getMain()->getRequest()->setIP( $ip );
$testUser = $this->getServiceContainer()->getTempUserCreator()
->create( null, new FauxRequest() )->getUser();
$this->editPage( 'Target1', 'test', "", NS_MAIN, $testUser );
$this->editPage( 'Target2', 'test', "", NS_MAIN, $testUser );
$adminUser = $this->getTestSysop()->getUser();
$permissionManager = $this->getServiceContainer()->getPermissionManager();
$permissionManager->overrideUserRightsForTesting( $adminUser,
array_merge(
$permissionManager->getUserPermissions( $adminUser ),
[ 'checkuser-temporary-account-no-preference' ]
) );
$request = new FauxRequest( [
'target' => $ip,
'action' => 'submit',
], true );
$adminPerformer = new UltimateAuthority( $adminUser );
[ $html ] = $this->executeSpecialPage( '', $request, 'qqx', $adminPerformer );
$this->assertEquals( 2, substr_count( $html, '(nuke-editby: ~2024-1)' ) );
$this->assertStringContainsString( '(nuke-list-tempaccount:', $html );
$this->assertStringContainsString( 'Target1', $html );
$this->assertStringContainsString( 'Target2', $html );
}
/**
* Ensure that the prompt returns temp accounts and IP accounts from IP lookups when
* temp accounts and CheckUser are enabled and Anonymous IP accounts exist
*
* @return void
*/
public function testPromptTargetCheckUserMixed() {
$this->markTestSkippedIfExtensionNotLoaded( 'CheckUser' );
$this->disableAutoCreateTempUser( [ 'known' => false ] );
$ip = '1.2.3.4';
$testUser = $this->getServiceContainer()->getUserFactory()->newAnonymous( $ip );
$performer = new UltimateAuthority( $testUser );
// create a page as an anonymous IP user
$this->editPage( 'Target1', 'test', "", NS_MAIN, $performer );
$this->enableAutoCreateTempUser();
RequestContext::getMain()->getRequest()->setIP( $ip );
$testUser = $this->getServiceContainer()->getTempUserCreator()
->create( null, new FauxRequest() )->getUser();
$performer = new UltimateAuthority( $testUser );
// create a page as a temp user
$this->editPage( 'Target2', 'test', "", NS_MAIN, $performer );
$adminUser = $this->getTestSysop()->getUser();
$permissionManager = $this->getServiceContainer()->getPermissionManager();
$permissionManager->overrideUserRightsForTesting( $adminUser,
array_merge(
$permissionManager->getUserPermissions( $adminUser ),
[ 'checkuser-temporary-account-no-preference' ]
) );
$request = new FauxRequest( [
'target' => $ip,
'action' => 'submit',
], true );
$adminPerformer = new UltimateAuthority( $adminUser );
[ $html ] = $this->executeSpecialPage( '', $request, 'qqx', $adminPerformer );
$this->assertSame( 1, substr_count( $html, ' (nuke-editby: ~2024-1)' ) );
$this->assertSame( 1, substr_count( $html, ' (nuke-editby: 1.2.3.4)' ) );
// They should all show up together
$this->assertStringContainsString( '(nuke-list-tempaccount:', $html );
$this->assertStringContainsString( 'Target1', $html );
$this->assertStringContainsString( 'Target2', $html );
}
public function testListNoPagesGlobal() {
$admin = $this->getTestSysop()->getUser();
$request = new FauxRequest( [
'action' => 'submit',
'pattern' => 'ThisPageShouldNotExist-' . rand()
], true );
$performer = new UltimateAuthority( $admin );
[ $html ] = $this->executeSpecialPage( '', $request, 'qqx', $performer );
$this->assertStringContainsString( '(nuke-nopages-global)', $html );
$this->assertStringNotContainsString( '(nuke-nopages)', $html );
}
public function testListNoPagesUser() {
$admin = $this->getTestSysop()->getUser();
$request = new FauxRequest( [
'action' => 'submit',
'target' => 'ThisPageShouldNotExist-' . rand()
], true );
$performer = new UltimateAuthority( $admin );
[ $html ] = $this->executeSpecialPage( '', $request, 'qqx', $performer );
$this->assertStringNotContainsString( '(nuke-nopages-global)', $html );
$this->assertStringContainsString( 'nuke-nopages', $html );
}
public function testListNamespace() {
$this->editPage( 'NukeUserPageTarget', 'test', '', NS_USER );
$admin = $this->getTestSysop()->getUser();
$request = new FauxRequest( [
'action' => 'submit',
'pattern' => 'NukeUserPageTarget',
'namespace' => NS_USER
], true );
$performer = new UltimateAuthority( $admin );
[ $html ] = $this->executeSpecialPage( '', $request, 'qqx', $performer );
$expectedTitle = Title::makeTitle( NS_USER, 'NukeUserPageTarget' )
->getPrefixedText();
$this->assertStringContainsString( $expectedTitle, $html );
}
public function testListTalk() {
$this->editPage( 'NukeTalkPageTarget', 'test', '', NS_TALK );
$admin = $this->getTestSysop()->getUser();
$request = new FauxRequest( [
'action' => 'submit',
'pattern' => 'NukeTalkPageTarget'
], true );
$performer = new UltimateAuthority( $admin );
[ $html ] = $this->executeSpecialPage( '', $request, 'qqx', $performer );
$expectedTitle = Title::makeTitle( NS_TALK, 'NukeTalkPageTarget' )
->getPrefixedText();
$this->assertStringContainsString( $expectedTitle, $html );
}
public function testListCapitalizedNamespace() {
$this->overrideConfigValues( [
'CapitalLinks' => false,
'CapitalLinkOverrides' => []
] );
$this->editPage( 'uncapsTarget', 'test' );
$this->editPage( 'UncapsTarget', 'test' );
$admin = $this->getTestSysop()->getUser();
$expectedTitle = Title::makeTitle( NS_MAIN, 'uncapsTarget' )
->getPrefixedText();
$shouldMatch = [
"%ncapsTarget",
"u%",
"uncapsTarget"
];
$shouldNotMatch = [
"UncapsTarget"
];
foreach ( $shouldMatch as $match ) {
$request = new FauxRequest( [
'action' => 'submit',
'pattern' => 'uncapsTarget'
], true );
$performer = new UltimateAuthority( $admin );
[ $html ] = $this->executeSpecialPage( '', $request, 'qqx', $performer );
$this->assertStringContainsString( $expectedTitle, $html, "match: $match" );
foreach ( $shouldNotMatch as $noMatch ) {
$this->assertStringNotContainsString( $noMatch, $html );
}
}
}
public function testListCapitalizedNamespaceOverrides() {
$overriddenNamespaces = [
NS_PROJECT => true,
NS_MEDIAWIKI => true,
NS_USER => true,
];
$this->overrideConfigValues( [
'CapitalLinks' => false,
'CapitalLinkOverrides' => $overriddenNamespaces
] );
$overriddenNamespaces[ NS_MAIN ] = false;
foreach ( $overriddenNamespaces as $ns => $override ) {
$this->editPage( "UncapsTarget" . $ns, 'test', '', $ns );
// If capital links for this ns is `true`, then the existing page should be edited.
$this->editPage( "uncapsTarget" . $ns, 'test2', '', $ns );
}
$admin = $this->getTestSysop()->getUser();
$request = new FauxRequest( [
'action' => 'submit',
'pattern' => 'u%'
], true );
$performer = new UltimateAuthority( $admin );
[ $html ] = $this->executeSpecialPage( '', $request, 'qqx', $performer );
$this->assertStringContainsString( "uncapsTarget" . NS_MAIN, $html );
// Check that the overridden namespaces are included in the search
$this->assertStringContainsString( "UncapsTarget" . NS_PROJECT, $html );
$this->assertStringContainsString( "UncapsTarget" . NS_MEDIAWIKI, $html );
$this->assertStringContainsString( "UncapsTarget" . NS_USER, $html );
$this->assertStringNotContainsString( "UncapsTarget" . NS_MAIN, $html );
// Check that the non-overridden namespaces are not included in the search
$this->assertStringNotContainsString( "uncapsTarget" . NS_PROJECT, $html );
$this->assertStringNotContainsString( "uncapsTarget" . NS_MEDIAWIKI, $html );
$this->assertStringNotContainsString( "uncapsTarget" . NS_USER, $html );
}
/**
* Check if Nuke still works with languages with complicated capitalization
* or no capitalization.
*
* Since the first letter of each title should be capitalized, searching for the
* lowercase version should still yield the uppercase version, or the same character
* if casing does not exist for the language.
*
* @return void
*/
public function testListCapitalized() {
/** @noinspection SpellCheckingInspection */
$testedLanguages = [
"ee" => [
"Ülemiste",
"ülemiste"
],
"zh" => "你好",
"ja" => "にほんご",
];
foreach ( $testedLanguages as $lang => $testData ) {
[ $created, $wanted ] = is_array( $testData ) ?
$testData : [ $testData, $testData ];
$this->overrideConfigValue( 'LanguageCode', $lang );
$this->editPage( $created, 'test' );
$admin = $this->getTestSysop()->getUser();
$request = new FauxRequest( [
'action' => 'submit',
'pattern' => $wanted
], true );
$performer = new UltimateAuthority( $admin );
[ $html ] = $this->executeSpecialPage( '', $request, 'qqx', $performer );
$this->assertStringContainsString( $created, $html );
}
}
public function testListLimit() {
$this->editPage( 'Page1', 'test' );
$this->editPage( 'Page2', 'test' );
$this->editPage( 'Page3', 'test' );
$admin = $this->getTestSysop()->getUser();
$request = new FauxRequest( [
'action' => 'submit',
'pattern' => 'Page%',
'limit' => 2
], true );
$performer = new UltimateAuthority( $admin );
[ $html ] = $this->executeSpecialPage( '', $request, 'qqx', $performer );
$this->assertEquals( 2, substr_count( $html, '<li>' ) );
}
public function testListLimitWithHooks() {
$this->editPage( 'Page1', 'test' );
$this->editPage( 'Page2', 'test' );
$this->editPage( 'Page3', 'test' );
$this->setTemporaryHook( "NukeGetNewPages", static function ( $_1, $_2, $_3, $_4, &$pages ) {
$pages[] = "Page3";
return true;
} );
$admin = $this->getTestSysop()->getUser();
$request = new FauxRequest( [
'action' => 'submit',
'pattern' => 'Page%',
'limit' => 2
], true );
$performer = new UltimateAuthority( $admin );
[ $html ] = $this->executeSpecialPage( '', $request, 'qqx', $performer );
$this->assertEquals( 2, substr_count( $html, '<li>' ) );
}
public function testListMaxAge() {
// 1 day
$maxAge = 86400;
$this->overrideConfigValues( [
'NukeMaxAge' => $maxAge,
'RCMaxAge' => $maxAge
] );
// Will never show up. If it does, the max age isn't being applied at all.
$page1Status = $this->editPage( 'Page1', 'test' );
// Will show up conditionally (see below).
$page2Status = $this->editPage( 'Page2', 'test' );
// Will always show up.
$this->editPage( 'Page3', 'test' );
// Prepare database connection and update query builder
$dbw = $this->getServiceContainer()->getConnectionProvider()->getPrimaryDatabase();
$uqb = $dbw->newUpdateQueryBuilder()
->update( 'revision' )
->caller( __METHOD__ );
// Manually change the rev_timestamp of Page1's creation in the database.
( clone $uqb )
->set( [ 'rev_timestamp' => $dbw->timestamp( time() - ( $maxAge * 3 ) ) ] )
->where( [
'rev_id' => $page1Status->getNewRevision()->getId()
] )->execute();
// Manually change the rev_timestamp of Page2's creation in the database.
( clone $uqb )
->set( [ 'rev_timestamp' => $dbw->timestamp( time() - $maxAge - 60 ) ] )
->where( [
'rev_id' => $page2Status->getNewRevision()->getId()
] )->execute();
$admin = $this->getTestSysop()->getUser();
$request = new FauxRequest( [
'action' => 'submit',
'pattern' => 'Page%',
'limit' => 2
], true );
$performer = new UltimateAuthority( $admin );
[ $html ] = $this->executeSpecialPage( '', $request, 'qqx', $performer );
$this->assertStringNotContainsString( 'Page1', $html );
$this->assertStringNotContainsString( 'Page2', $html );
$this->assertStringContainsString( 'Page3', $html );
// Now check if resetting $wgNukeMaxAge will show Page2.
// This should follow $wgRCMaxAge, which will include the page.
$this->overrideConfigValues( [
'RCMaxAge' => $maxAge * 2,
'NukeMaxAge' => 0
] );
[ $html ] = $this->executeSpecialPage( '', $request, 'qqx', $performer );
$this->assertStringNotContainsString( 'Page1', $html );
$this->assertStringContainsString( 'Page2', $html );
$this->assertStringContainsString( 'Page3', $html );
// Now check if expanding just $wgNukeMaxAge will show Page2.
$this->overrideConfigValues( [
'RCMaxAge' => $maxAge,
'NukeMaxAge' => $maxAge * 2
] );
[ $html ] = $this->executeSpecialPage( '', $request, 'qqx', $performer );
$this->assertStringNotContainsString( 'Page1', $html );
$this->assertStringContainsString( 'Page2', $html );
$this->assertStringContainsString( 'Page3', $html );
}
public function testExecutePattern() {
// Test that matching wildcards works, and that escaping wildcards works as documented
// at https://www.mediawiki.org/wiki/Help:Extension:Nuke
$this->editPage( '%PositiveNukeTest123', 'test' );
$this->editPage( 'NegativeNukeTest123', 'test' );
$admin = $this->getTestSysop()->getUser();
$request = new FauxRequest( [
'action' => 'submit',
'pattern' => '\\%PositiveNukeTest%',
'wpFormIdentifier' => 'massdelete',
'wpEditToken' => $admin->getEditToken(),
], true );
$performer = new UltimateAuthority( $admin );
[ $html ] = $this->executeSpecialPage( '', $request, 'qqx', $performer );
$this->assertStringContainsString( 'PositiveNukeTest123', $html );
$this->assertStringNotContainsString( 'NegativeNukeTest123', $html );
}
public function testListFiles() {
$testFileName = $this->uploadTestFile()['title']->getPrefixedText();
$admin = $this->getTestSysop()->getUser();
$request = new FauxRequest( [
'action' => 'submit',
'namespace' => NS_FILE
], true );
$performer = new UltimateAuthority( $admin );
[ $html ] = $this->executeSpecialPage( '', $request, 'qqx', $performer );
// The title should be in the list
$this->assertStringContainsString( $testFileName, $html );
// There should also be an image preview
$this->assertStringContainsString( "<img src", $html );
}
public function testUserPages() {
$user = $this->getTestUser()->getUser();
$this->insertPage( 'Page123', 'Test', NS_MAIN, $user );
$this->insertPage( 'Paging456', 'Test', NS_MAIN, $user );
$this->insertPage( 'Should not show', 'No show' );
$admin = $this->getTestSysop()->getUser();
$request = new FauxRequest( [
'action' => 'submit',
'target' => $user->getName(),
'wpFormIdentifier' => 'massdelete',
'wpEditToken' => $admin->getEditToken(),
], true );
$performer = new UltimateAuthority( $admin );
[ $html ] = $this->executeSpecialPage( '', $request, 'qqx', $performer );
$this->assertStringContainsString( 'Page123', $html );
$this->assertStringContainsString( 'Paging456', $html );
$this->assertStringNotContainsString( 'Should not show', $html );
}
public function testNamespaces() {
$this->insertPage( 'Page123', 'Test', NS_MAIN );
$this->insertPage( 'Paging456', 'Test', NS_MAIN );
$this->insertPage( 'Should not show', 'No show', NS_TALK );
$admin = $this->getTestSysop()->getUser();
$request = new FauxRequest( [
'action' => 'submit',
'namespace' => NS_MAIN,
'wpFormIdentifier' => 'massdelete',
'wpEditToken' => $admin->getEditToken(),
], true );
$performer = new UltimateAuthority( $admin );
[ $html ] = $this->executeSpecialPage( '', $request, 'qqx', $performer );
$this->assertStringContainsString( 'Page123', $html );
$this->assertStringContainsString( 'Paging456', $html );
$this->assertStringNotContainsString( 'Should not show', $html );
}
public function testDelete() {
$pages = [];
$pages[] = $this->insertPage( 'Page123', 'Test', NS_MAIN )[ 'title' ];
$pages[] = $this->insertPage( 'Paging456', 'Test', NS_MAIN )[ 'title' ];
$admin = $this->getTestSysop()->getUser();
$fauxReason = "Reason " . wfRandomString();
$request = new FauxRequest( [
'action' => 'delete',
'wpDeleteReasonList' => 'Vandalism',
'wpReason' => $fauxReason,
'pages' => $pages,
'wpFormIdentifier' => 'nukelist',
'wpEditToken' => $admin->getEditToken(),
], true );
$performer = new UltimateAuthority( $admin );
[ $html ] = $this->executeSpecialPage( '', $request, 'qqx', $performer );
$deleteCount = count( $pages );
$this->assertStringContainsString( "(nuke-delete-summary: $deleteCount)", $html );
$this->assertStringContainsString( '(nuke-deletion-queued: Page123)', $html );
$this->assertStringContainsString( '(nuke-deletion-queued: Paging456)', $html );
// Pre-check to confirm that the page hasn't been deleted yet
// "mw-tag-marker-nuke" is the CSS class for the "Nuke" tag's <span> on Special:Log.
$this->assertStringNotContainsString( 'mw-tag-marker-nuke', $this->getDeleteLogHtml() );
// Ensure all delete jobs are run
$this->runJobs();
$deleteLogHtml = $this->getDeleteLogHtml();
$this->assertStringContainsString( 'Vandalism', $deleteLogHtml );
$this->assertStringContainsString( 'mw-tag-marker-nuke', $deleteLogHtml );
$this->assertStringContainsString( $fauxReason, $deleteLogHtml );
}
public function testDeleteTarget() {
$pages = [];
$testUser = $this->getTestUser( "user" )->getUser();
$testUserName = $testUser->getName();
$pages[] = $this->uploadTestFile( $testUser )[ 'title' ];
$pages[] = $this->insertPage( 'Page123', 'Test', NS_MAIN, $testUser )[ 'title' ];
$pages[] = $this->insertPage( 'Paging456', 'Test', NS_MAIN, $testUser )[ 'title' ];
$admin = $this->getTestSysop()->getUser();
$fauxReason = "Reason " . wfRandomString();
$request = new FauxRequest( [
'action' => 'delete',
'wpDeleteReasonList' => 'Vandalism',
'wpReason' => $fauxReason,
'target' => $testUserName,
'pages' => $pages,
'wpFormIdentifier' => 'nukelist',
'wpEditToken' => $admin->getEditToken(),
], true );
$performer = new UltimateAuthority( $admin );
[ $html ] = $this->executeSpecialPage( '', $request, 'qqx', $performer );
$deleteCount = count( $pages );
$this->assertStringContainsString(
"(nuke-delete-summary-user: $deleteCount, $testUserName)",
$html
);
}
public function testDeleteTargetAnon() {
$pages = [];
$testUser = $this->getTestUser( "user" )->getUser();
$testUserName = $testUser->getName();
$pages[] = $this->uploadTestFile( $testUser )[ 'title' ];
$pages[] = $this->insertPage( 'Page123', 'Test', NS_MAIN, $testUser )[ 'title' ];
$pages[] = $this->insertPage( 'Paging456', 'Test', NS_MAIN, $testUser )[ 'title' ];
$admin = $this->getTestSysop()->getUser();
$fauxReason = "Reason " . wfRandomString();
$request = new FauxRequest( [
'action' => 'delete',
'wpDeleteReasonList' => 'Vandalism',
'wpReason' => $fauxReason,
'pages' => $pages,
'wpFormIdentifier' => 'nukelist',
'wpEditToken' => $admin->getEditToken(),
], true );
$performer = new UltimateAuthority( $admin );
[ $html ] = $this->executeSpecialPage( '', $request, 'qqx', $performer );
$deleteCount = count( $pages );
$this->assertStringContainsString(
"(nuke-delete-summary: $deleteCount)",
$html
);
}
public function testDeleteSkipped() {
$pages = [];
$pages[] = $this->insertPage( 'Page123', 'Test', NS_MAIN )[ 'title' ];
$pages[] = $this->insertPage( 'Paging 456', 'Test', NS_MAIN )[ 'title' ];
$skippedPage = $this->insertPage( 'Page 789', 'Test', NS_MAIN )['title'];
$admin = $this->getTestSysop()->getUser();
$request = new FauxRequest( [
'action' => 'delete',
'wpDeleteReasonList' => 'Vandalism',
'originalPageList' => implode( '|', $pages ),
'pages' => [ $skippedPage->getPrefixedDBkey() ],
'wpFormIdentifier' => 'nukelist',
'wpEditToken' => $admin->getEditToken(),
], true );
$performer = new UltimateAuthority( $admin );
[ $html ] = $this->executeSpecialPage( '', $request, 'qqx', $performer );
$skippedCount = count( $pages );
$this->assertStringContainsString( '(nuke-delete-summary: 1)', $html );
$this->assertStringContainsString( "(nuke-skipped-summary: $skippedCount)", $html );
// Ensure all delete jobs are run
$this->runJobs();
// Make sure that those pages are not in the deletion log.
$deleteLogHtml = $this->getDeleteLogHtml();
foreach ( $pages as $title ) {
$this->assertStringNotContainsString( $title->getPrefixedText(), $deleteLogHtml );
}
$this->assertStringContainsString( $skippedPage->getPrefixedText(), $deleteLogHtml );
}
public function testDeleteDropdownReason() {
$pages = [];
$pages[] = $this->insertPage( 'Page123', 'Test', NS_MAIN )[ 'title' ];
$pages[] = $this->insertPage( 'Paging456', 'Test', NS_MAIN )[ 'title' ];
$admin = $this->getTestSysop()->getUser();
$request = new FauxRequest( [
'action' => 'delete',
'wpDeleteReasonList' => 'Vandalism',
'wpReason' => "",
'pages' => $pages,
'wpFormIdentifier' => 'nukelist',
'wpEditToken' => $admin->getEditToken(),
], true );
$performer = new UltimateAuthority( $admin );
$this->executeSpecialPage( '', $request, 'qqx', $performer );
// Ensure all jobs are run
$this->runJobs();
// Check logs
$this->assertStringContainsString( "Vandalism", $this->getDeleteLogHtml() );
}
public function testDeleteCustomReason() {
$pages = [];
$pages[] = $this->insertPage( 'Page123', 'Test', NS_MAIN )[ 'title' ];
$pages[] = $this->insertPage( 'Paging456', 'Test', NS_MAIN )[ 'title' ];
$admin = $this->getTestSysop()->getUser();
$fauxReason = "Reason " . wfRandomString();
$request = new FauxRequest( [
'action' => 'delete',
'wpDeleteReasonList' => 'other',
'wpReason' => $fauxReason,
'pages' => $pages,
'wpFormIdentifier' => 'nukelist',
'wpEditToken' => $admin->getEditToken(),
], true );
$performer = new UltimateAuthority( $admin );
$this->executeSpecialPage( '', $request, 'qqx', $performer );
// Ensure all jobs are run
$this->runJobs();
// Check logs
$this->assertStringContainsString( $fauxReason, $this->getDeleteLogHtml() );
}
public function testDeleteFiles() {
$testFileName = $this->uploadTestFile()['title']->getPrefixedText();
$admin = $this->getTestSysop()->getUser();
$request = new FauxRequest( [
'action' => 'delete',
'wpDeleteReasonList' => 'Reason',
'wpReason' => 'Reason',
'pages' => [ $testFileName ],
'wpFormIdentifier' => 'nukelist',
'wpEditToken' => $admin->getEditToken(),
], true );
$performer = new UltimateAuthority( $admin );
[ $html ] = $this->executeSpecialPage( '', $request, 'qqx', $performer );
// Files are deleted instantly
$this->assertStringContainsString( "nuke-deleted", $html );
$this->assertStringContainsString( $testFileName, $html );
}
public function testDeleteHook() {
$wasCalled = false;
$pages = [];
$pages[] = $this->insertPage( 'Bad article', 'test', NS_MAIN )['title'];
$pages[] = $this->insertPage( 'DO NOT DELETE', 'test', NS_MAIN )['title'];
$pages[] = $this->insertPage( 'Bad article 2', 'test', NS_MAIN )['title'];
$this->setTemporaryHook(
"NukeDeletePage",
static function ( Title $title, $reason, &$deletionResult ) use ( &$wasCalled ) {
$wasCalled = true;
$deletionResult = $title->getPrefixedText() !== "DO NOT DELETE";
return $title->getPrefixedText() === "Bad article 2";
}
);
$admin = $this->getTestSysop()->getUser();
$request = new FauxRequest( [
'action' => 'delete',
'wpDeleteReasonList' => 'Reason',
'wpReason' => 'Reason',
'pages' => $pages,
'wpFormIdentifier' => 'nukelist',
'wpEditToken' => $admin->getRequest()->getSession()->getToken(),
], true );
$performer = new UltimateAuthority( $admin );
[ $html ] = $this->executeSpecialPage( '', $request, 'qqx', $performer );
$this->assertTrue( $wasCalled );
$this->assertStringContainsString( '(nuke-deleted: Bad article)', $html );
$this->assertStringContainsString( '(nuke-not-deleted: DO NOT DELETE)', $html );
$this->assertStringContainsString( '(nuke-deletion-queued: Bad article 2)', $html );
}
public function testSearchSubpages() {
$user = $this->getTestUser()->getUser();
$username = $user->getName();
$truncatedUsername = substr( $username, 0, 2 );
$searchResults1 = $this->newSpecialPage()
->prefixSearchSubpages( $truncatedUsername, 10, 0 );
$this->assertArrayContains( [ $username ], $searchResults1 );
$searchResults2 = $this->newSpecialPage()
->prefixSearchSubpages( "", 10, 0 );
$this->assertCount( 0, $searchResults2 );
}
/**
* Upload a test file.
*
* @param ?User|null $user
* @return array Title object and page id
*/
private function uploadTestFile( ?User $user = null ): array {
$exampleFilePath = realpath( __DIR__ . "/../../assets/Example.png" );
$tempFilePath = $this->getNewTempFile();
copy( $exampleFilePath, $tempFilePath );
$title = Title::makeTitle( NS_FILE, "Example " . rand() . ".png" );
$request = new FauxRequest( [], true );
$request->setUpload( 'wpUploadFile', [
'name' => $title->getText(),
'type' => 'image/png',
'tmp_name' => $tempFilePath,
'size' => filesize( $tempFilePath ),
'error' => UPLOAD_ERR_OK
] );
$upload = UploadFromFile::createFromRequest( $request );
$uploadStatus = $upload->performUpload(
"test",
false,
false,
$user ?? $this->getTestUser( "user" )->getUser()
);
$this->assertTrue( $uploadStatus->isOK() );
$this->getServiceContainer()->getJobRunner()->run( [] );
return [
'title' => $title,
'id' => $title->getId()
];
}
private function getDeleteLogHtml(): string {
$services = $this->getServiceContainer();
// TODO: Make this use qqx so tests can be checked against system message keys.
$specialLog = $services->getSpecialPageFactory()->getPage( 'Log' );
$specialLog->execute( "delete" );
return $specialLog->getOutput()->getHTML();
}
}