3 use MediaWiki\MainConfigNames
;
4 use MediaWiki\Permissions\Authority
;
5 use MediaWiki\Permissions\UltimateAuthority
;
6 use MediaWiki\Request\FauxRequest
;
7 use MediaWiki\Specials\SpecialContributions
;
8 use MediaWiki\Tests\User\TempUser\TempUserTestTrait
;
9 use MediaWiki\User\User
;
10 use MediaWiki\User\UserFactory
;
11 use Wikimedia\Parsoid\Utils\DOMCompat
;
12 use Wikimedia\Parsoid\Utils\DOMUtils
;
17 * @covers \MediaWiki\Specials\SpecialContributions
18 * @covers \MediaWiki\SpecialPage\ContributionsSpecialPage
20 class SpecialContributionsTest
extends SpecialPageTestBase
{
21 use TempUserTestTrait
;
23 private const PAGE_NAME
= __CLASS__
. 'BlaBlaTest';
25 private static $admin;
28 private static int $useModWikiIPRevId;
30 public function addDBDataOnce() {
31 $this->overrideConfigValue(
32 MainConfigNames
::RangeContributionsCIDRLimit
,
38 $this->setTemporaryHook(
39 'SpecialContributionsBeforeMainOutput',
43 self
::$admin = new UltimateAuthority( $this->getTestSysop()->getUser() );
46 self
::PAGE_NAME
, 'Test Content', 'test', NS_MAIN
, self
::$admin
48 'Edit failed for admin'
51 self
::$user = $this->getTestUser()->getUser();
54 'Test', 'Test Content', 'test', NS_MAIN
, self
::$user
56 'Edit failed for user'
59 $this->disableAutoCreateTempUser();
60 $useModWikiIP = $this->getServiceContainer()->getUserFactory()
61 ->newFromName( '1.2.3.xxx', UserFactory
::RIGOR_NONE
);
62 $useModWikiIPEditStatus = $this->editPage( 'Test1234', 'Test Content', 'test', NS_MAIN
, $useModWikiIP );
63 $this->assertStatusGood( $useModWikiIPEditStatus, 'Edit failed for IP in usemod format' );
64 static::$useModWikiIPRevId = $useModWikiIPEditStatus->getNewRevision()->getId();
66 $blockStatus = $this->getServiceContainer()->getBlockUserFactory()
68 self
::$user->getName(),
72 [ 'isHideUser' => true ],
75 $this->assertStatusGood( $blockStatus, 'Block was not placed' );
78 public function testExecuteEmptyTarget() {
79 [ $html ] = $this->executeSpecialPage();
80 // This 'topOnly' filter should always be added to Special:Contributions
81 $this->assertStringContainsString( 'topOnly', $html );
82 $this->assertStringNotContainsString( 'mw-pager-body', $html );
85 public function testExecuteHiddenTarget() {
86 [ $html ] = $this->executeSpecialPage(
87 self
::$user->getName()
89 $this->assertStringNotContainsString( 'mw-pager-body', $html );
92 public function testExecuteHiddenTargetWithPermissions() {
93 [ $html ] = $this->executeSpecialPage(
94 self
::$user->getName(),
97 // This is necessary because permission checks aren't actually
98 // done on the UlitmateAuthority that is self::$admin. Instead,
99 // they are done on a UserAuthority. See the TODO comment in
100 // User::getThisAsAuthority for more details.
101 $this->getTestUser( [
107 $this->assertStringContainsString( 'mw-pager-body', $html );
110 public function testExecuteInvalidNamespace() {
111 [ $html ] = $this->executeSpecialPage(
117 $this->assertStringNotContainsString( 'mw-pager-body', $html );
120 /** @dataProvider provideExecuteNoResultsForIPTarget */
121 public function testExecuteNoResultsForIPTarget( $temporaryAccountsEnabled, $expectedPageTitleMessageKey ) {
122 if ( $temporaryAccountsEnabled ) {
123 $this->enableAutoCreateTempUser();
125 $this->disableAutoCreateTempUser();
127 [ $html ] = $this->executeSpecialPage( '4.3.2.1', null, null, null, true );
128 $specialPageDocument = DOMUtils
::parseHTML( $html );
129 $contentHtml = DOMCompat
::querySelector( $specialPageDocument, '.mw-content-container' )->nodeValue
;
130 $this->assertStringNotContainsString( 'mw-pager-body', $contentHtml );
131 $this->assertStringContainsString( "($expectedPageTitleMessageKey: 4.3.2.1", $contentHtml );
134 public static function provideExecuteNoResultsForIPTarget() {
136 'Temporary accounts not enabled' => [ false, 'contributions-title' ],
137 'Temporary accounts enabled' => [
138 true, 'contributions-title-for-ip-when-temporary-accounts-enabled',
143 public function testExecuteForUseModWikiIP() {
144 // Regression test for T370413
145 [ $html ] = $this->executeSpecialPage( '1.2.3.xxx' );
146 $contributionsList = DOMCompat
::querySelectorAll( DOMUtils
::parseHTML( $html ), '.mw-contributions-list' );
148 1, $contributionsList, 'Should have the contributions list as 1.2.3.xxx has made one edit'
150 $matchingLines = DOMCompat
::querySelectorAll(
151 $contributionsList[0], '[data-mw-revid="' . static::$useModWikiIPRevId . '"]'
153 $this->assertCount( 1, $matchingLines, "The edit made by the usemod IP is missing" );
157 * @dataProvider provideTestExecuteRange
159 public function testExecuteRange( $username, $shouldShowLinks ) {
160 [ $html ] = $this->executeSpecialPage( $username, null, 'qqx', self
::$admin, true );
162 if ( $shouldShowLinks ) {
163 $this->assertStringContainsString( 'blocklink', $html );
165 $this->assertStringNotContainsString( 'blocklink', $html );
166 $this->assertStringContainsString( 'sp-contributions-outofrange', $html );
171 * @dataProvider provideTestExecuteNonRange
173 public function testExecuteNonRange( $username, $shouldShowLinks ) {
174 [ $html ] = $this->executeSpecialPage( $username, null, 'qqx', self
::$admin, true );
176 if ( $shouldShowLinks ) {
177 $this->assertStringContainsString( 'blocklink', $html );
179 $this->assertStringNotContainsString( 'blocklink', $html );
183 public static function provideTestExecuteRange() {
184 yield
'Queryable IPv4 range should have blocklink for admin'
185 => [ '24.237.208.166/30', true ];
186 yield
'Queryable IPv6 range should have blocklink for admin'
187 => [ '2001:DB8:0:0:0:0:0:01/43', true ];
188 yield
'Unqueryable IPv4 range should not have blocklink for admin'
189 => [ '212.35.31.121/14', false ];
190 yield
'Unqueryable IPv6 range should not have blocklink for admin'
191 => [ '2000::/24', false ];
194 public static function provideTestExecuteNonRange() {
195 yield
'Valid IPv4 should have blocklink for admin' => [ '124.24.52.13', true ];
196 yield
'Valid IPv6 should have blocklink for admin' => [ '2001:db8::', true ];
197 yield
'Local user should have blocklink for admin' => [ 'UTSysop', true ];
198 yield
'Invalid IP should not have blocklink for admin' => [ '24.237.222208.166', false ];
199 yield
'External user should not have blocklink for admin' => [ 'imported>UTSysop', false ];
200 yield
'Nonexistent user should not have blocklink for admin' => [ __CLASS__
, false ];
203 public static function provideYearMonthParams() {
204 yield
'Current year/month' => [
205 'year' => date( 'Y' ),
206 'month' => date( 'm' ),
209 yield
'Old year/moth' => [
215 'year' => '123garbage123',
216 'month' => date( 'm' ),
222 * @dataProvider provideYearMonthParams
224 public function testYearMonthParams( string $year, string $month, bool $expect ) {
225 [ $html ] = $this->executeSpecialPage(
226 self
::$admin->getUser()->getName(),
232 $this->assertStringContainsString( self
::PAGE_NAME
, $html );
234 $this->assertStringNotContainsString( self
::PAGE_NAME
, $html );
238 public function testBotParam() {
239 [ $html ] = $this->executeSpecialPage(
247 $this->assertStringContainsString( 'bot', $html );
250 public function testFeedFormat() {
251 $specialPage = $this->newSpecialPage();
252 [ $html ] = ( new SpecialPageExecutor() )->executeSpecialPage(
261 'deletedOnly' => true,
262 'tagfilter' => 'mw-reverted',
267 $url = $specialPage->getOutput()->getRedirect();
268 $this->assertStringContainsString( 'namespace', $url );
269 $this->assertStringContainsString( 'toponly', $url );
270 $this->assertStringContainsString( 'newonly', $url );
271 $this->assertStringContainsString( 'hideminor', $url );
272 $this->assertStringContainsString( 'deletedonly', $url );
273 $this->assertStringContainsString( 'tagfilter', $url );
274 $this->assertStringContainsString( 'year', $url );
275 $this->assertStringContainsString( 'month', $url );
279 * @dataProvider providePrefixSearchSubpages
281 public function testPrefixSearchSubpages( $search, $expected ) {
282 $specialPage = $this->newSpecialPage();
285 $specialPage->prefixSearchSubpages( $search, 10, 0 )
289 public static function providePrefixSearchSubpages() {
291 'Invalid prefix' => [ '/', 0 ],
292 'Valid prefix' => [ 'U', 1 ],
296 protected function newSpecialPage(): SpecialContributions
{
297 $services = $this->getServiceContainer();
299 return new SpecialContributions(
300 $services->getLinkBatchFactory(),
301 $services->getPermissionManager(),
302 $services->getConnectionProvider(),
303 $services->getRevisionStore(),
304 $services->getNamespaceInfo(),
305 $services->getUserNameUtils(),
306 $services->getUserNamePrefixSearch(),
307 $services->getUserOptionsLookup(),
308 $services->getCommentFormatter(),
309 $services->getUserFactory(),
310 $services->getUserIdentityLookup(),
311 $services->getDatabaseBlockStore(),
312 $services->getTempUserConfig()