3 namespace MediaWiki\Tests\Api\Query
;
5 use MediaWiki\Language\LanguageCode
;
6 use MediaWiki\Language\LanguageConverter
;
7 use MediaWiki\MainConfigNames
;
8 use MediaWiki\MainConfigSchema
;
9 use MediaWiki\Message\Message
;
10 use MediaWiki\Registration\ExtensionRegistry
;
11 use MediaWiki\SiteStats\SiteStats
;
12 use MediaWiki\Tests\Api\ApiTestCase
;
13 use MediaWiki\Tests\User\TempUser\TempUserTestTrait
;
14 use MediaWiki\Title\Title
;
16 use Wikimedia\Composer\ComposerInstalled
;
17 use Wikimedia\Rdbms\LoadBalancer
;
18 use Wikimedia\TestingAccessWrapper
;
25 * @covers \MediaWiki\Api\ApiQuerySiteinfo
27 class ApiQuerySiteinfoTest
extends ApiTestCase
{
28 use TempUserTestTrait
;
30 /** @var array[]|null */
31 private $originalRegistryLoaded = null;
33 protected function tearDown(): void
{
34 if ( $this->originalRegistryLoaded
!== null ) {
35 $reg = TestingAccessWrapper
::newFromObject( ExtensionRegistry
::getInstance() );
36 $reg->loaded
= $this->originalRegistryLoaded
;
37 $this->originalRegistryLoaded
= null;
42 // We don't try to test every single thing for every category, just a sample
43 protected function doQuery( $siprop = null, $extraParams = [] ) {
44 $params = [ 'action' => 'query', 'meta' => 'siteinfo' ];
45 if ( $siprop !== null ) {
46 $params['siprop'] = $siprop;
48 $params = array_merge( $params, $extraParams );
50 $res = $this->doApiRequest( $params );
52 $this->assertArrayNotHasKey( 'warnings', $res[0] );
53 $this->assertCount( 1, $res[0]['query'] );
55 return $res[0]['query'][$siprop === null ?
'general' : $siprop];
58 public function testGeneral() {
59 $this->overrideConfigValues( [
60 MainConfigNames
::AllowExternalImagesFrom
=> '//localhost/',
61 MainConfigNames
::MainPageIsDomainRoot
=> true,
64 $data = $this->doQuery();
66 $this->assertSame( Title
::newMainPage()->getPrefixedText(), $data['mainpage'] );
67 $this->assertSame( PHP_VERSION
, $data['phpversion'] );
68 $this->assertSame( [ '//localhost/' ], $data['externalimages'] );
69 $this->assertTrue( $data['mainpageisdomainroot'] );
72 public function testLinkPrefixCharset() {
73 $contLang = $this->getServiceContainer()->getLanguageFactory()->getLanguage( 'ar' );
74 $this->setContentLang( $contLang );
75 $this->assertTrue( $contLang->linkPrefixExtension() );
77 $data = $this->doQuery();
79 $this->assertSame( $contLang->linkPrefixCharset(), $data['linkprefixcharset'] );
82 public function testVariants() {
83 $contLang = $this->getServiceContainer()->getLanguageFactory()->getLanguage( 'zh' );
84 $converter = $this->getServiceContainer()->getLanguageConverterFactory()->getLanguageConverter( $contLang );
85 $this->setContentLang( $contLang );
86 $this->assertTrue( $converter->hasVariants() );
88 $data = $this->doQuery();
90 $expected = array_map(
91 static function ( $code ) use ( $contLang ) {
92 return [ 'code' => $code, 'name' => $contLang->getVariantname( $code ) ];
94 $converter->getVariants()
97 $this->assertSame( $expected, $data['variants'] );
100 public function testReadOnly() {
101 // Create the test user before making the DB readonly
102 $this->getTestSysop()->getUser();
103 $svc = $this->getServiceContainer()->getReadOnlyMode();
104 $svc->setReason( 'Need more donations' );
106 $data = $this->doQuery();
108 $svc->setReason( false );
111 $this->assertTrue( $data['readonly'] );
112 $this->assertSame( 'Need more donations', $data['readonlyreason'] );
115 public function testNamespacesBasic() {
117 array_keys( $this->getServiceContainer()->getContentLanguage()->getFormattedNamespaces() ),
118 array_keys( $this->doQuery( 'namespaces' ) )
122 public function testNamespacesExtraNS() {
123 $this->overrideConfigValue( MainConfigNames
::ExtraNamespaces
, [ '138' => 'Testing' ] );
125 array_keys( $this->getServiceContainer()->getContentLanguage()->getFormattedNamespaces() ),
126 array_keys( $this->doQuery( 'namespaces' ) )
130 public function testNamespacesProtection() {
131 $this->overrideConfigValue(
132 MainConfigNames
::NamespaceProtection
,
136 '4' => 'editsemiprotected',
142 'move-categorypages',
147 $data = $this->doQuery( 'namespaces' );
148 $this->assertArrayNotHasKey( 'namespaceprotection', $data['0'] );
149 $this->assertArrayNotHasKey( 'namespaceprotection', $data['2'] );
150 $this->assertSame( 'editsemiprotected', $data['4']['namespaceprotection'] );
151 $this->assertSame( 'editinterface|noratelimit', $data['8']['namespaceprotection'] );
152 $this->assertSame( 'move-categorypages', $data['14']['namespaceprotection'] );
155 public function testNamespaceAliases() {
156 // XXX: why does this fail when the en-x-piglatin variant is enabled?
157 $this->overrideConfigValue( MainConfigNames
::UsePigLatinVariant
, false );
159 $expected = $this->getServiceContainer()->getContentLanguage()->getNamespaceAliases();
160 $expected = array_map(
161 static function ( $key, $val ) {
162 return [ 'id' => $val, 'alias' => strtr( $key, '_', ' ' ) ];
164 array_keys( $expected ),
168 $this->assertSame( $expected, $this->doQuery( 'namespacealiases' ) );
171 public function testSpecialPageAliases() {
172 $this->assertSameSize(
173 $this->getServiceContainer()->getSpecialPageFactory()->getNames(),
174 $this->doQuery( 'specialpagealiases' )
178 public function testMagicWords() {
179 $this->assertSameSize(
180 $this->getServiceContainer()->getContentLanguage()->getMagicWords(),
181 $this->doQuery( 'magicwords' )
186 * @dataProvider interwikiMapProvider
188 public function testInterwikiMap( $filter ) {
189 $this->overrideConfigValues( [
190 MainConfigNames
::ExtraInterlanguageLinkPrefixes
=> [ 'self' ],
191 MainConfigNames
::ExtraLanguageNames
=> [ 'self' => 'Recursion' ],
192 MainConfigNames
::LocalInterwikis
=> [ 'self' ],
193 MainConfigNames
::Server
=> 'https://local.example',
194 MainConfigNames
::ScriptPath
=> '/w',
197 $this->getDb()->newInsertQueryBuilder()
198 ->insertInto( 'interwiki' )
201 'iw_prefix' => 'self',
202 'iw_url' => 'https://local.example/w/index.php?title=$1',
203 'iw_api' => 'https://local.example/w/api.php',
204 'iw_wikiid' => 'somedbname',
209 'iw_prefix' => 'foreign',
210 'iw_url' => '//foreign.example/wiki/$1',
216 ->caller( __METHOD__
)
219 $this->getServiceContainer()->getMessageCache()->enable();
221 $this->editPage( 'MediaWiki:Interlanguage-link-self', 'Self!' );
222 $this->editPage( 'MediaWiki:Interlanguage-link-sitename-self', 'Circular logic' );
226 if ( $filter === null ||
$filter === '!local' ) {
228 'prefix' => 'foreign',
229 'url' => 'http://foreign.example/wiki/$1',
233 if ( $filter === null ||
$filter === 'local' ) {
238 'language' => 'Recursion',
240 'localinterwiki' => true,
241 'extralanglink' => true,
243 'linktext' => 'Self!',
244 'sitename' => 'Circular logic',
245 'url' => 'https://local.example/w/index.php?title=$1',
247 'wikiid' => 'somedbname',
248 'api' => 'https://local.example/w/api.php',
252 $data = $this->doQuery( 'interwikimap',
253 $filter === null ?
[] : [ 'sifilteriw' => $filter ] );
255 $this->assertSame( $expected, $data );
258 public static function interwikiMapProvider() {
259 return [ [ 'local' ], [ '!local' ], [ null ] ];
263 * @dataProvider dbReplLagProvider
265 public function testDbReplLagInfo( $showHostnames, $includeAll ) {
266 if ( !$showHostnames && $includeAll ) {
267 $this->expectApiErrorCode( 'includeAllDenied' );
270 // Force creation of the test user before mocking the database.
271 $this->getTestSysop()->getUser();
273 $mockLB = $this->createNoOpMock( LoadBalancer
::class, [ 'getMaxLag', 'getLagTimes',
274 'getServerName', 'getLocalDomainID' ] );
275 $mockLB->method( 'getMaxLag' )->willReturn( [ null, 7, 1 ] );
276 $mockLB->method( 'getLagTimes' )->willReturn( [ 5, 7 ] );
277 $mockLB->method( 'getServerName' )->willReturnMap( [
281 $mockLB->method( 'getLocalDomainID' )->willReturn( 'testdomain' );
282 $this->setService( 'DBLoadBalancer', $mockLB );
284 $this->overrideConfigValue( MainConfigNames
::ShowHostnames
, $showHostnames );
288 $expected[] = [ 'host' => $showHostnames ?
'apple' : '', 'lag' => 5 ];
290 $expected[] = [ 'host' => $showHostnames ?
'carrot' : '', 'lag' => 7 ];
292 $data = $this->doQuery( 'dbrepllag', $includeAll ?
[ 'sishowalldb' => '' ] : [] );
294 $this->assertSame( $expected, $data );
297 public static function dbReplLagProvider() {
299 'no hostnames, no showalldb' => [ false, false ],
300 'no hostnames, showalldb' => [ false, true ],
301 'hostnames, no showalldb' => [ true, false ],
302 'hostnames, showalldb' => [ true, true ]
306 public function testStatistics() {
307 $this->setTemporaryHook( 'APIQuerySiteInfoStatisticsInfo',
308 static function ( &$data ) {
309 $data['addedstats'] = 42;
314 'pages' => intval( SiteStats
::pages() ),
315 'articles' => intval( SiteStats
::articles() ),
316 'edits' => intval( SiteStats
::edits() ),
317 'images' => intval( SiteStats
::images() ),
318 'users' => intval( SiteStats
::users() ),
319 'activeusers' => intval( SiteStats
::activeUsers() ),
320 'admins' => intval( SiteStats
::numberingroup( 'sysop' ) ),
321 'jobs' => intval( SiteStats
::jobs() ),
325 $this->assertSame( $expected, $this->doQuery( 'statistics' ) );
329 * @dataProvider groupsProvider
331 public function testUserGroups( $numInGroup ) {
332 global $wgGroupPermissions, $wgAutopromote;
334 $this->setGroupPermissions( [
336 'perambulate' => true,
337 'legislate' => false,
340 $this->overrideConfigValues( [
341 MainConfigNames
::AddGroups
=> [ 'viscount' => true, 'bot' => [] ],
342 MainConfigNames
::RemoveGroups
=> [ 'viscount' => [ 'sysop' ], 'bot' => [ '*', 'earl' ] ],
343 MainConfigNames
::GroupsAddToSelf
=> [ 'bot' => [ 'bureaucrat', 'sysop' ] ],
344 MainConfigNames
::GroupsRemoveFromSelf
=> [ 'bot' => [ 'bot' ] ],
347 $data = $this->doQuery( 'usergroups', $numInGroup ?
[ 'sinumberingroup' => '' ] : [] );
349 $names = array_column( $data, 'name' );
351 $this->assertSame( array_keys( $wgGroupPermissions ), $names );
352 $userAllGroups = $this->getServiceContainer()->getUserGroupManager()->listAllGroups();
354 foreach ( $data as $val ) {
355 if ( !$numInGroup ) {
356 $expectedSize = null;
357 } elseif ( $val['name'] === 'user' ) {
358 $expectedSize = SiteStats
::users();
359 } elseif ( $val['name'] === '*' ||
isset( $wgAutopromote[$val['name']] ) ) {
360 $expectedSize = null;
362 $expectedSize = SiteStats
::numberingroup( $val['name'] );
365 if ( $expectedSize === null ) {
366 $this->assertArrayNotHasKey( 'number', $val );
368 $this->assertSame( $expectedSize, $val['number'] );
371 if ( $val['name'] === 'viscount' ) {
372 $this->assertSame( [ 'perambulate' ], $val['rights'] );
373 $this->assertSame( $userAllGroups, $val['add'] );
374 } elseif ( $val['name'] === 'bot' ) {
375 $this->assertArrayNotHasKey( 'add', $val );
376 $this->assertArrayNotHasKey( 'remove', $val );
377 $this->assertSame( [ 'bureaucrat', 'sysop' ], $val['add-self'] );
378 $this->assertSame( [ 'bot' ], $val['remove-self'] );
383 public function testAutoCreateTempUser() {
384 $this->disableAutoCreateTempUser( [ 'reservedPattern' => null ] );
386 [ 'enabled' => false ],
387 $this->doQuery( 'autocreatetempuser' ),
388 'When disabled, no other properties are present'
391 $this->enableAutoCreateTempUser( [
392 'reservedPattern' => null,
394 $this->assertArrayEquals(
397 'matchPatterns' => [ '~$1' ],
399 $this->doQuery( 'autocreatetempuser' ),
402 'When enabled, some properties are filled in or cleaned up'
406 public function testFileExtensions() {
408 $this->overrideConfigValue( MainConfigNames
::FileExtensions
, [ 'png', 'gif', 'jpg', 'png' ] );
410 $expected = [ [ 'ext' => 'png' ], [ 'ext' => 'gif' ], [ 'ext' => 'jpg' ] ];
412 $this->assertSame( $expected, $this->doQuery( 'fileextensions' ) );
415 public static function groupsProvider() {
417 'numingroup' => [ true ],
418 'nonumingroup' => [ false ],
422 public function testInstalledLibraries() {
423 // @todo Test no installed.json? Moving installed.json to a different name temporarily
424 // seems a bit scary, but I don't see any other way to do it.
426 // @todo Install extensions/skins somehow so that we can test they're filtered out
429 $path = "$IP/vendor/composer/installed.json";
430 if ( !is_file( $path ) ) {
431 $this->markTestSkipped( 'No installed libraries' );
434 $expected = ( new ComposerInstalled( $path ) )->getInstalledDependencies();
436 $expected = array_filter( $expected,
437 static function ( $info ) {
438 return !str_starts_with( $info['type'], 'mediawiki-' );
442 $expected = array_map(
443 static function ( $name, $info ) {
444 return [ 'name' => $name, 'version' => $info['version'] ];
446 array_keys( $expected ),
447 array_values( $expected )
450 $this->assertSame( $expected, $this->doQuery( 'libraries' ) );
453 public function testExtensions() {
454 $tmpdir = $this->getNewTempDirectory();
455 touch( "$tmpdir/ErsatzExtension.php" );
456 touch( "$tmpdir/LICENSE" );
457 touch( "$tmpdir/AUTHORS.txt" );
460 'path' => "$tmpdir/ErsatzExtension.php",
461 'name' => 'Ersatz Extension',
462 'namemsg' => 'ersatz-extension-name',
463 'author' => 'John Smith',
464 'version' => '0.0.2',
465 'url' => 'https://www.example.com/software/ersatz-extension',
466 'description' => 'An extension that is not what it seems.',
467 'descriptionmsg' => 'ersatz-extension-desc',
468 'license-name' => 'PD',
471 $this->overrideConfigValue( MainConfigNames
::ExtensionCredits
, [ 'api' => [
474 'author' => [ 'John Smith', 'John Smith Jr.', '...' ],
475 'descriptionmsg' => [ 'another-extension-desc', 'param' ] ],
477 // Make the main registry empty
478 // TODO: Make ExtensionRegistry an injected service?
479 $reg = TestingAccessWrapper
::newFromObject( ExtensionRegistry
::getInstance() );
480 $this->originalRegistryLoaded
= $reg->loaded
;
483 $data = $this->doQuery( 'extensions' );
485 $this->assertCount( 2, $data );
487 $this->assertSame( 'api', $data[0]['type'] );
489 $sharedKeys = [ 'name', 'namemsg', 'description', 'descriptionmsg', 'author', 'url',
490 'version', 'license-name' ];
491 foreach ( $sharedKeys as $key ) {
492 $this->assertSame( $val[$key], $data[0][$key] );
495 // @todo Test git info
498 Title
::makeTitle( NS_SPECIAL
, 'Version/License/Ersatz Extension' )->getLinkURL(),
503 Title
::makeTitle( NS_SPECIAL
, 'Version/Credits/Ersatz Extension' )->getLinkURL(),
507 $this->assertSame( 'another-extension-desc', $data[1]['descriptionmsg'] );
508 $this->assertSame( [ 'param' ], $data[1]['descriptionmsgparams'] );
509 $this->assertSame( 'John Smith, John Smith Jr., ...', $data[1]['author'] );
513 * @dataProvider rightsInfoProvider
515 public function testRightsInfo( $page, $url, $text, $expectedUrl, $expectedText ) {
516 $this->overrideConfigValues( [
517 MainConfigNames
::Server
=> 'https://local.example',
518 MainConfigNames
::RightsPage
=> $page,
519 MainConfigNames
::RightsUrl
=> $url,
520 MainConfigNames
::RightsText
=> $text,
523 [ 'url' => $expectedUrl, 'text' => $expectedText ],
524 $this->doQuery( 'rightsinfo' )
527 // The installer sets these options to empty string if not specified otherwise,
528 // test that this behaves the same as null.
529 $this->overrideConfigValues( [
530 MainConfigNames
::RightsPage
=> $page ??
'',
531 MainConfigNames
::RightsUrl
=> $url ??
'',
532 MainConfigNames
::RightsText
=> $text ??
'',
535 [ 'url' => $expectedUrl, 'text' => $expectedText ],
536 $this->doQuery( 'rightsinfo' ),
537 'empty string behaves the same as null'
541 public static function rightsInfoProvider() {
542 $licenseTitleUrl = 'https://local.example/wiki/License';
543 $licenseUrl = 'http://license.example/';
546 'No rights info' => [ null, null, null, '', '' ],
547 'Only page' => [ 'License', null, null, $licenseTitleUrl, 'License' ],
548 'Only URL' => [ null, $licenseUrl, null, $licenseUrl, '' ],
549 'Only text' => [ null, null, '!!!', '', '!!!' ],
550 // URL is ignored if page is specified
551 'Page and URL' => [ 'License', $licenseUrl, null, $licenseTitleUrl, 'License' ],
552 'URL and text' => [ null, $licenseUrl, '!!!', $licenseUrl, '!!!' ],
553 'Page and text' => [ 'License', null, '!!!', $licenseTitleUrl, '!!!' ],
554 'Page and URL and text' => [ 'License', $licenseUrl, '!!!', $licenseTitleUrl, '!!!' ],
555 'Pagename "0"' => [ '0', null, null, 'https://local.example/wiki/0', '0' ],
556 'URL "0"' => [ null, '0', null, '0', '' ],
557 'Text "0"' => [ null, null, '0', '', '0' ],
561 public function testRestrictions() {
562 global $wgRestrictionTypes, $wgRestrictionLevels, $wgCascadingRestrictionLevels,
563 $wgSemiprotectedRestrictionLevels;
566 'types' => $wgRestrictionTypes,
567 'levels' => $wgRestrictionLevels,
568 'cascadinglevels' => $wgCascadingRestrictionLevels,
569 'semiprotectedlevels' => $wgSemiprotectedRestrictionLevels,
570 ], $this->doQuery( 'restrictions' ) );
574 * @dataProvider languagesProvider
576 public function testLanguages( $langCode ) {
577 $expected = $this->getServiceContainer()
578 ->getLanguageNameUtils()
579 ->getLanguageNames( (string)$langCode );
581 $expected = array_map(
582 static function ( $code, $name ) {
585 'bcp47' => LanguageCode
::bcp47( $code ),
589 array_keys( $expected ),
590 array_values( $expected )
593 $data = $this->doQuery( 'languages',
594 $langCode !== null ?
[ 'siinlanguagecode' => $langCode ] : [] );
596 $this->assertSame( $expected, $data );
599 public static function languagesProvider() {
600 return [ [ null ], [ 'fr' ] ];
603 public function testLanguageVariants() {
604 $expectedKeys = array_filter( LanguageConverter
::$languagesWithVariants,
605 function ( $langCode ) {
606 $lang = $this->getServiceContainer()->getLanguageFactory()
607 ->getLanguage( $langCode );
608 $converter = $this->getServiceContainer()->getLanguageConverterFactory()
609 ->getLanguageConverter( $lang );
610 return $converter->hasVariants();
613 sort( $expectedKeys );
615 $this->assertSame( $expectedKeys, array_keys( $this->doQuery( 'languagevariants' ) ) );
618 public function testLanguageVariantsDisabled() {
619 $this->overrideConfigValue( MainConfigNames
::DisableLangConversion
, true );
621 $this->assertSame( [], $this->doQuery( 'languagevariants' ) );
625 * @todo Test a skin with a description that's known to be different in a different language.
626 * Vector will do, but it's not installed by default.
628 * @todo Test that an invalid language code doesn't actually try reading any messages
630 * @dataProvider skinsProvider
632 public function testSkins( $code ) {
633 $data = $this->doQuery( 'skins', $code !== null ?
[ 'siinlanguagecode' => $code ] : [] );
634 $services = $this->getServiceContainer();
635 $skinFactory = $services->getSkinFactory();
636 $skinNames = $skinFactory->getInstalledSkins();
637 $expectedAllowed = $skinFactory->getAllowedSkins();
638 $expectedDefault = Skin
::normalizeKey( 'default' );
639 $languageNameUtils = $services->getLanguageNameUtils();
642 foreach ( $skinNames as $name => $displayName ) {
643 $this->assertSame( $name, $data[$i]['code'] );
645 $msg = wfMessage( "skinname-$name" );
646 if ( $code && $languageNameUtils->isValidCode( $code ) ) {
647 $msg->inLanguage( $code );
649 $msg->inContentLanguage();
651 if ( $msg->exists() ) {
652 $displayName = $msg->text();
654 $this->assertSame( $displayName, $data[$i]['name'] );
656 if ( !isset( $expectedAllowed[$name] ) ) {
657 $this->assertTrue( $data[$i]['unusable'], "$name must be unusable" );
659 if ( $name === $expectedDefault ) {
660 $this->assertTrue( $data[$i]['default'], "$expectedDefault must be default" );
666 public function skinsProvider() {
668 'No language specified' => [ null ],
670 'Invalid language' => [ '/invalid/' ],
674 public function testExtensionTags() {
675 $expected = array_map(
676 static function ( $tag ) {
679 $this->getServiceContainer()->getParser()->getTags()
682 $this->assertSame( $expected, $this->doQuery( 'extensiontags' ) );
685 public function testFunctionHooks() {
686 $this->assertSame( $this->getServiceContainer()->getParser()->getFunctionHooks(),
687 $this->doQuery( 'functionhooks' ) );
690 public function testVariables() {
692 $this->getServiceContainer()->getMagicWordFactory()->getVariableIDs(),
693 $this->doQuery( 'variables' )
697 public function testProtocols() {
698 $urlProtocol = MainConfigSchema
::getDefaultValue(
699 MainConfigNames
::UrlProtocols
701 $this->assertSame( $urlProtocol, $this->doQuery( 'protocols' ) );
704 public function testDefaultOptions() {
706 $this->getServiceContainer()->getUserOptionsLookup()->getDefaultOptions(),
707 $this->doQuery( 'defaultoptions' )
711 public function testUploadDialog() {
712 global $wgUploadDialog;
714 $this->assertSame( $wgUploadDialog, $this->doQuery( 'uploaddialog' ) );
717 public function testGetHooks() {
718 // Make sure there's something to report on
719 $this->setTemporaryHook( 'somehook',
724 $hookContainer = $this->getServiceContainer()->getHookContainer();
725 $expectedNames = $hookContainer->getHookNames();
726 $actualNames = array_column( $this->doQuery( 'showhooks' ), 'name' );
728 $this->assertArrayEquals( $expectedNames, $actualNames );
731 public function testContinuation() {
732 // Use $wgUrlProtocols as easy example for forging the
733 // size of the API response
734 $protocol = 'foo://';
735 $size = strlen( $protocol );
736 $protocols = [ $protocol ];
738 $this->overrideConfigValues( [
739 MainConfigNames
::UrlProtocols
=> [ $protocol ],
740 MainConfigNames
::APIMaxResultSize
=> $size,
743 $res = $this->doApiRequest( [
745 'meta' => 'siteinfo',
746 'siprop' => 'protocols|languages',
750 wfMessage( 'apiwarn-truncatedresult', Message
::numParam( $size ) )
752 $res[0]['warnings']['result']['warnings']
755 $this->assertSame( $protocols, $res[0]['query']['protocols'] );
756 $this->assertArrayNotHasKey( 'languages', $res[0] );
757 $this->assertTrue( $res[0]['batchcomplete'], 'batchcomplete should be true' );
758 $this->assertSame( [ 'siprop' => 'languages', 'continue' => '-||' ], $res[0]['continue'] );
762 * @dataProvider provideAutopromote
764 public function testAutopromote( $config, $expected ) {
765 $this->overrideConfigValues( [
766 MainConfigNames
::Autopromote
=> $config,
767 MainConfigNames
::AutoConfirmCount
=> 10,
768 MainConfigNames
::AutoConfirmAge
=> 345600,
770 $this->assertSame( $expected, $this->doQuery( 'autopromote' ) );
773 public static function provideAutopromote() {
776 // edit count >= 10 and age >= 4 days
778 [ APCOND_EDITCOUNT
, 10 ],
779 [ APCOND_AGE
, 345600 ],
786 'condname' => 'APCOND_EDITCOUNT',
790 'condname' => 'APCOND_AGE',
791 'params' => [ 345600 ]
797 // Test case to check default of null is replaced with value of appropriate $wg
798 yield
'simple-use-wg' => [
800 'simple-use-wg' => [ '&',
801 [ APCOND_EDITCOUNT
, null ],
802 [ APCOND_AGE
, null ],
809 'condname' => 'APCOND_EDITCOUNT',
813 'condname' => 'APCOND_AGE',
814 'params' => [ 345600 ]
822 'trivial' => APCOND_EMAILCONFIRMED
,
827 'condname' => 'APCOND_EMAILCONFIRMED',
834 yield
'multiple-copies-of-condition' => [
836 // In both groups 'foo' and 'bar', or in group 'baz'
837 'multiple-copies-of-condition' => [ '|',
838 [ APCOND_INGROUPS
, 'foo', 'bar' ],
839 [ APCOND_INGROUPS
, 'baz' ],
843 'multiple-copies-of-condition' => [
846 'condname' => 'APCOND_INGROUPS',
847 'params' => [ 'foo', 'bar' ]
850 'condname' => 'APCOND_INGROUPS',
851 'params' => [ 'baz' ]
857 yield
'complicated' => [
859 // confirmed email, or their edit count >= 100 and either they
860 // created their account a year ago or it has been 10 days since
861 // their first edit, or if they're in both groups 'group1' and
862 // 'group2'. Except for users in the 'bad' group.
863 'complicated' => [ '&',
865 APCOND_EMAILCONFIRMED
,
867 [ APCOND_EDITCOUNT
, 100 ],
869 [ APCOND_AGE
, 525600 * 60 ],
870 [ APCOND_AGE_FROM_EDIT
, 864000 ],
873 [ APCOND_INGROUPS
, 'group1', 'group2' ],
875 [ '!', [ APCOND_INGROUPS
, 'bad' ] ],
884 'condname' => 'APCOND_EMAILCONFIRMED',
890 'condname' => 'APCOND_EDITCOUNT',
896 'condname' => 'APCOND_AGE',
897 'params' => [ 31536000 ]
900 'condname' => 'APCOND_AGE_FROM_EDIT',
901 'params' => [ 864000 ]
906 'condname' => 'APCOND_INGROUPS',
907 'params' => [ 'group1', 'group2' ]
913 'condname' => 'APCOND_INGROUPS',
914 'params' => [ 'bad' ]
921 // Find an undefined APCOND integer
923 foreach ( get_defined_constants() as $k => $v ) {
924 if ( strpos( $k, 'APCOND_' ) !== false ) {
929 while ( isset( $constants[$bogusCond] ) ) {
932 $bogusCond2 = $bogusCond +
1;
933 while ( isset( $constants[$bogusCond2] ) ) {
937 yield
'bad-cond-1' => [
939 // unknown APCOND constant. Might be handled by an extension that
940 // didn't define a constant with the expected name.
941 'bad-cond' => 'bogus',
949 yield
'bad-cond-2' => [
951 'bad-cond' => $bogusCond,
962 yield
'bad-cond-3' => [
964 'bad-cond' => [ 'bogus', 'bogus?', APCOND_EMAILCONFIRMED
, $bogusCond, $bogusCond2 ],
970 APCOND_EMAILCONFIRMED
,
976 yield
'bad-cond-4' => [
981 APCOND_EMAILCONFIRMED
,
992 'condname' => 'APCOND_EMAILCONFIRMED',
1000 'condname' => false,
1008 public function testAutopromoteOnceDefault() {
1009 // PHP doesn't like empty nested arrays nested in arrays...
1014 $this->testAutopromoteOnce( $value, $value );
1018 * @dataProvider provideAutopromoteOnce
1020 public function testAutopromoteOnce( $config, $expected ) {
1021 $this->overrideConfigValues( [
1022 MainConfigNames
::AutopromoteOnce
=> $config,
1023 MainConfigNames
::AutoConfirmCount
=> 10,
1024 MainConfigNames
::AutoConfirmAge
=> 345600,
1026 $this->assertSame( $expected, $this->doQuery( 'autopromoteonce' ) );
1029 public static function provideAutopromoteOnce() {
1033 // edit count >= 10 and age >= 4 days
1035 [ APCOND_EDITCOUNT
, 10 ],
1036 [ APCOND_AGE
, 345600 ],
1045 'condname' => 'APCOND_EDITCOUNT',
1049 'condname' => 'APCOND_AGE',
1050 'params' => [ 345600 ]