wiki.techinc.nl/tests/phpunit/includes/api/query/ApiQuerySiteinfoTest.php
Timo Tijhof c02513c97e phpunit: Fix tests relying on implicit wgScript/wgArticlePath
A number of tests have hardcoded expections that pass only in WMF CI
where Quibble has LocalSettings.php with $wgScript and $wgArticlePath
set a certain way.

We could fix these by adding setMwGlobals() in their tests, as we
often do, but these are so often forgotten that I'd rather we just
add them to TestSetup.php so that it is simply impossible to write a
test that that passes locally for you (if you have the same config)
but not for someone else.

There is a larger project in there somewhere about expanding this
slowly such that we basically only pluck DB-settings and extension
enablement from LocalSettings and otherwise run the tests with the
default settings in PHPUnit. Pretty much by definition, any (other)
setting you have in LocalSettings is irrelevant because it either:
1. has no effect on the test (majority, harmless either way),
2. has a custom default via TestSetup.php (which has precedence over
   LocalSettings.php),
3. is relevant to the code being tested and the test case correctly
   calls setMwGlobals() to ensure a consistent value during test.
4. is relevant to the tested code but has no override, thus only
   passes if you happen to have the "right" value set for it
   (undesirable).

Case 4 is already categorically impossible for the most common config
settings that influence random code because we give them a value
in TestSetup.php. This patch expands that to include $wgScript
and $wgArticlePath. Perhaps in the future we can think about a way
to do this automatically by either re-applying MainConfigSchema
(sans db settings) or by only selectively applying LocalSettings.php
in the first place.

This patch follows-up I072ddf89562fe, which added a test case in
WikitextContentHandlerIntegrationTest.php that assumed "/index.php"
as the value of $wgScript. This passes in WMF CI since Quibble uses
that value, but the tests failed in most local development installs
since those tend to use "/w" instead.

Rather than one-off fixing that one test with overrideConfigValues(),
switch to a more general fixture, since the precise values don't
matter for this test.

Bug: T349087
Bug: T277470
Change-Id: If4304b7ca4a838bd892d4516a0b5c6dfbc30986e
2024-05-05 00:00:01 +00:00

1056 lines
29 KiB
PHP

<?php
namespace MediaWiki\Tests\Api\Query;
use ExtensionRegistry;
use LanguageCode;
use LanguageConverter;
use MediaWiki\MainConfigNames;
use MediaWiki\MainConfigSchema;
use MediaWiki\Message\Message;
use MediaWiki\SiteStats\SiteStats;
use MediaWiki\Tests\Api\ApiTestCase;
use MediaWiki\Tests\User\TempUser\TempUserTestTrait;
use MediaWiki\Title\Title;
use Skin;
use Wikimedia\Composer\ComposerInstalled;
use Wikimedia\Rdbms\LoadBalancer;
use Wikimedia\TestingAccessWrapper;
/**
* @group API
* @group medium
* @group Database
*
* @covers \ApiQuerySiteinfo
*/
class ApiQuerySiteinfoTest extends ApiTestCase {
use TempUserTestTrait;
private $originalRegistryLoaded = null;
protected function tearDown(): void {
if ( $this->originalRegistryLoaded !== null ) {
$reg = TestingAccessWrapper::newFromObject( ExtensionRegistry::getInstance() );
$reg->loaded = $this->originalRegistryLoaded;
$this->originalRegistryLoaded = null;
}
parent::tearDown();
}
// We don't try to test every single thing for every category, just a sample
protected function doQuery( $siprop = null, $extraParams = [] ) {
$params = [ 'action' => 'query', 'meta' => 'siteinfo' ];
if ( $siprop !== null ) {
$params['siprop'] = $siprop;
}
$params = array_merge( $params, $extraParams );
$res = $this->doApiRequest( $params );
$this->assertArrayNotHasKey( 'warnings', $res[0] );
$this->assertCount( 1, $res[0]['query'] );
return $res[0]['query'][$siprop === null ? 'general' : $siprop];
}
public function testGeneral() {
$this->overrideConfigValues( [
MainConfigNames::AllowExternalImagesFrom => '//localhost/',
MainConfigNames::MainPageIsDomainRoot => true,
] );
$data = $this->doQuery();
$this->assertSame( Title::newMainPage()->getPrefixedText(), $data['mainpage'] );
$this->assertSame( PHP_VERSION, $data['phpversion'] );
$this->assertSame( [ '//localhost/' ], $data['externalimages'] );
$this->assertTrue( $data['mainpageisdomainroot'] );
}
public function testLinkPrefixCharset() {
$contLang = $this->getServiceContainer()->getLanguageFactory()->getLanguage( 'ar' );
$this->setContentLang( $contLang );
$this->assertTrue( $contLang->linkPrefixExtension() );
$data = $this->doQuery();
$this->assertSame( $contLang->linkPrefixCharset(), $data['linkprefixcharset'] );
}
public function testVariants() {
$contLang = $this->getServiceContainer()->getLanguageFactory()->getLanguage( 'zh' );
$converter = $this->getServiceContainer()->getLanguageConverterFactory()->getLanguageConverter( $contLang );
$this->setContentLang( $contLang );
$this->assertTrue( $converter->hasVariants() );
$data = $this->doQuery();
$expected = array_map(
static function ( $code ) use ( $contLang ) {
return [ 'code' => $code, 'name' => $contLang->getVariantname( $code ) ];
},
$converter->getVariants()
);
$this->assertSame( $expected, $data['variants'] );
}
public function testReadOnly() {
// Create the test user before making the DB readonly
$this->getTestSysop()->getUser();
$svc = $this->getServiceContainer()->getReadOnlyMode();
$svc->setReason( 'Need more donations' );
try {
$data = $this->doQuery();
} finally {
$svc->setReason( false );
}
$this->assertTrue( $data['readonly'] );
$this->assertSame( 'Need more donations', $data['readonlyreason'] );
}
public function testNamespacesBasic() {
$this->assertSame(
array_keys( $this->getServiceContainer()->getContentLanguage()->getFormattedNamespaces() ),
array_keys( $this->doQuery( 'namespaces' ) )
);
}
public function testNamespacesExtraNS() {
$this->overrideConfigValue( MainConfigNames::ExtraNamespaces, [ '138' => 'Testing' ] );
$this->assertSame(
array_keys( $this->getServiceContainer()->getContentLanguage()->getFormattedNamespaces() ),
array_keys( $this->doQuery( 'namespaces' ) )
);
}
public function testNamespacesProtection() {
$this->overrideConfigValue(
MainConfigNames::NamespaceProtection,
[
'0' => '',
'2' => [ '' ],
'4' => 'editsemiprotected',
'8' => [
'editinterface',
'noratelimit'
],
'14' => [
'move-categorypages',
''
]
]
);
$data = $this->doQuery( 'namespaces' );
$this->assertArrayNotHasKey( 'namespaceprotection', $data['0'] );
$this->assertArrayNotHasKey( 'namespaceprotection', $data['2'] );
$this->assertSame( 'editsemiprotected', $data['4']['namespaceprotection'] );
$this->assertSame( 'editinterface|noratelimit', $data['8']['namespaceprotection'] );
$this->assertSame( 'move-categorypages', $data['14']['namespaceprotection'] );
}
public function testNamespaceAliases() {
// XXX: why does this fail when the en-x-piglatin variant is enabled?
$this->overrideConfigValue( MainConfigNames::UsePigLatinVariant, false );
$expected = $this->getServiceContainer()->getContentLanguage()->getNamespaceAliases();
$expected = array_map(
static function ( $key, $val ) {
return [ 'id' => $val, 'alias' => strtr( $key, '_', ' ' ) ];
},
array_keys( $expected ),
$expected
);
$this->assertSame( $expected, $this->doQuery( 'namespacealiases' ) );
}
public function testSpecialPageAliases() {
$this->assertSameSize(
$this->getServiceContainer()->getSpecialPageFactory()->getNames(),
$this->doQuery( 'specialpagealiases' )
);
}
public function testMagicWords() {
$this->assertSameSize(
$this->getServiceContainer()->getContentLanguage()->getMagicWords(),
$this->doQuery( 'magicwords' )
);
}
/**
* @dataProvider interwikiMapProvider
*/
public function testInterwikiMap( $filter ) {
$this->overrideConfigValues( [
MainConfigNames::ExtraInterlanguageLinkPrefixes => [ 'self' ],
MainConfigNames::ExtraLanguageNames => [ 'self' => 'Recursion' ],
MainConfigNames::LocalInterwikis => [ 'self' ],
MainConfigNames::Server => 'https://local.example',
MainConfigNames::ScriptPath => '/w',
] );
$this->getDb()->newInsertQueryBuilder()
->insertInto( 'interwiki' )
->ignore()
->row( [
'iw_prefix' => 'self',
'iw_url' => 'https://local.example/w/index.php?title=$1',
'iw_api' => 'https://local.example/w/api.php',
'iw_wikiid' => 'somedbname',
'iw_local' => true,
'iw_trans' => true,
] )
->row( [
'iw_prefix' => 'foreign',
'iw_url' => '//foreign.example/wiki/$1',
'iw_api' => '',
'iw_wikiid' => '',
'iw_local' => false,
'iw_trans' => false,
] )
->caller( __METHOD__ )
->execute();
$this->getServiceContainer()->getMessageCache()->enable();
$this->editPage( 'MediaWiki:Interlanguage-link-self', 'Self!' );
$this->editPage( 'MediaWiki:Interlanguage-link-sitename-self', 'Circular logic' );
$expected = [];
if ( $filter === null || $filter === '!local' ) {
$expected[] = [
'prefix' => 'foreign',
'url' => 'http://foreign.example/wiki/$1',
'protorel' => true,
];
}
if ( $filter === null || $filter === 'local' ) {
$expected[] = [
'prefix' => 'self',
'local' => true,
'trans' => true,
'language' => 'Recursion',
'bcp47' => 'self',
'localinterwiki' => true,
'extralanglink' => true,
'code' => 'self',
'linktext' => 'Self!',
'sitename' => 'Circular logic',
'url' => 'https://local.example/w/index.php?title=$1',
'protorel' => false,
'wikiid' => 'somedbname',
'api' => 'https://local.example/w/api.php',
];
}
$data = $this->doQuery( 'interwikimap',
$filter === null ? [] : [ 'sifilteriw' => $filter ] );
$this->assertSame( $expected, $data );
}
public static function interwikiMapProvider() {
return [ [ 'local' ], [ '!local' ], [ null ] ];
}
/**
* @dataProvider dbReplLagProvider
*/
public function testDbReplLagInfo( $showHostnames, $includeAll ) {
if ( !$showHostnames && $includeAll ) {
$this->expectApiErrorCode( 'includeAllDenied' );
}
// Force creation of the test user before mocking the database.
$this->getTestSysop()->getUser();
$mockLB = $this->createNoOpMock( LoadBalancer::class, [ 'getMaxLag', 'getLagTimes',
'getServerName', 'getLocalDomainID' ] );
$mockLB->method( 'getMaxLag' )->willReturn( [ null, 7, 1 ] );
$mockLB->method( 'getLagTimes' )->willReturn( [ 5, 7 ] );
$mockLB->method( 'getServerName' )->willReturnMap( [
[ 0, 'apple' ],
[ 1, 'carrot' ]
] );
$mockLB->method( 'getLocalDomainID' )->willReturn( 'testdomain' );
$this->setService( 'DBLoadBalancer', $mockLB );
$this->overrideConfigValue( MainConfigNames::ShowHostnames, $showHostnames );
$expected = [];
if ( $includeAll ) {
$expected[] = [ 'host' => $showHostnames ? 'apple' : '', 'lag' => 5 ];
}
$expected[] = [ 'host' => $showHostnames ? 'carrot' : '', 'lag' => 7 ];
$data = $this->doQuery( 'dbrepllag', $includeAll ? [ 'sishowalldb' => '' ] : [] );
$this->assertSame( $expected, $data );
}
public static function dbReplLagProvider() {
return [
'no hostnames, no showalldb' => [ false, false ],
'no hostnames, showalldb' => [ false, true ],
'hostnames, no showalldb' => [ true, false ],
'hostnames, showalldb' => [ true, true ]
];
}
public function testStatistics() {
$this->setTemporaryHook( 'APIQuerySiteInfoStatisticsInfo',
static function ( &$data ) {
$data['addedstats'] = 42;
}
);
$expected = [
'pages' => intval( SiteStats::pages() ),
'articles' => intval( SiteStats::articles() ),
'edits' => intval( SiteStats::edits() ),
'images' => intval( SiteStats::images() ),
'users' => intval( SiteStats::users() ),
'activeusers' => intval( SiteStats::activeUsers() ),
'admins' => intval( SiteStats::numberingroup( 'sysop' ) ),
'jobs' => intval( SiteStats::jobs() ),
'addedstats' => 42,
];
$this->assertSame( $expected, $this->doQuery( 'statistics' ) );
}
/**
* @dataProvider groupsProvider
*/
public function testUserGroups( $numInGroup ) {
global $wgGroupPermissions, $wgAutopromote;
$this->setGroupPermissions( 'viscount', 'perambulate', 'yes' );
$this->setGroupPermissions( 'viscount', 'legislate', '0' );
$this->overrideConfigValues( [
MainConfigNames::AddGroups => [ 'viscount' => true, 'bot' => [] ],
MainConfigNames::RemoveGroups => [ 'viscount' => [ 'sysop' ], 'bot' => [ '*', 'earl' ] ],
MainConfigNames::GroupsAddToSelf => [ 'bot' => [ 'bureaucrat', 'sysop' ] ],
MainConfigNames::GroupsRemoveFromSelf => [ 'bot' => [ 'bot' ] ],
] );
$data = $this->doQuery( 'usergroups', $numInGroup ? [ 'sinumberingroup' => '' ] : [] );
$names = array_column( $data, 'name' );
$this->assertSame( array_keys( $wgGroupPermissions ), $names );
$userAllGroups = $this->getServiceContainer()->getUserGroupManager()->listAllGroups();
foreach ( $data as $val ) {
if ( !$numInGroup ) {
$expectedSize = null;
} elseif ( $val['name'] === 'user' ) {
$expectedSize = SiteStats::users();
} elseif ( $val['name'] === '*' || isset( $wgAutopromote[$val['name']] ) ) {
$expectedSize = null;
} else {
$expectedSize = SiteStats::numberingroup( $val['name'] );
}
if ( $expectedSize === null ) {
$this->assertArrayNotHasKey( 'number', $val );
} else {
$this->assertSame( $expectedSize, $val['number'] );
}
if ( $val['name'] === 'viscount' ) {
$this->assertSame( [ 'perambulate' ], $val['rights'] );
$this->assertSame( $userAllGroups, $val['add'] );
} elseif ( $val['name'] === 'bot' ) {
$this->assertArrayNotHasKey( 'add', $val );
$this->assertArrayNotHasKey( 'remove', $val );
$this->assertSame( [ 'bureaucrat', 'sysop' ], $val['add-self'] );
$this->assertSame( [ 'bot' ], $val['remove-self'] );
}
}
}
public function testAutoCreateTempUser() {
$this->disableAutoCreateTempUser();
$this->assertSame(
[ 'enabled' => false ],
$this->doQuery( 'autocreatetempuser' ),
'When disabled, no other properties are present'
);
$this->enableAutoCreateTempUser( [
'reservedPattern' => null,
] );
$this->assertArrayEquals(
[
'enabled' => true,
'actions' => [ 'edit' ],
'genPattern' => '~$1',
'matchPattern' => '~$1',
'serialProvider' => [ 'type' => 'local', 'useYear' => true ],
'serialMapping' => [ 'type' => 'plain-numeric' ],
],
$this->doQuery( 'autocreatetempuser' ),
false,
true,
'When enabled, some properties are filled in or cleaned up'
);
}
public function testFileExtensions() {
// Add duplicate
$this->overrideConfigValue( MainConfigNames::FileExtensions, [ 'png', 'gif', 'jpg', 'png' ] );
$expected = [ [ 'ext' => 'png' ], [ 'ext' => 'gif' ], [ 'ext' => 'jpg' ] ];
$this->assertSame( $expected, $this->doQuery( 'fileextensions' ) );
}
public static function groupsProvider() {
return [
'numingroup' => [ true ],
'nonumingroup' => [ false ],
];
}
public function testInstalledLibraries() {
// @todo Test no installed.json? Moving installed.json to a different name temporarily
// seems a bit scary, but I don't see any other way to do it.
//
// @todo Install extensions/skins somehow so that we can test they're filtered out
global $IP;
$path = "$IP/vendor/composer/installed.json";
if ( !is_file( $path ) ) {
$this->markTestSkipped( 'No installed libraries' );
}
$expected = ( new ComposerInstalled( $path ) )->getInstalledDependencies();
$expected = array_filter( $expected,
static function ( $info ) {
return !str_starts_with( $info['type'], 'mediawiki-' );
}
);
$expected = array_map(
static function ( $name, $info ) {
return [ 'name' => $name, 'version' => $info['version'] ];
},
array_keys( $expected ),
array_values( $expected )
);
$this->assertSame( $expected, $this->doQuery( 'libraries' ) );
}
public function testExtensions() {
$tmpdir = $this->getNewTempDirectory();
touch( "$tmpdir/ErsatzExtension.php" );
touch( "$tmpdir/LICENSE" );
touch( "$tmpdir/AUTHORS.txt" );
$val = [
'path' => "$tmpdir/ErsatzExtension.php",
'name' => 'Ersatz Extension',
'namemsg' => 'ersatz-extension-name',
'author' => 'John Smith',
'version' => '0.0.2',
'url' => 'https://www.example.com/software/ersatz-extension',
'description' => 'An extension that is not what it seems.',
'descriptionmsg' => 'ersatz-extension-desc',
'license-name' => 'PD',
];
$this->overrideConfigValue( MainConfigNames::ExtensionCredits, [ 'api' => [
$val,
[
'author' => [ 'John Smith', 'John Smith Jr.', '...' ],
'descriptionmsg' => [ 'another-extension-desc', 'param' ] ],
] ] );
// Make the main registry empty
// TODO: Make ExtensionRegistry an injected service?
$reg = TestingAccessWrapper::newFromObject( ExtensionRegistry::getInstance() );
$this->originalRegistryLoaded = $reg->loaded;
$reg->loaded = [];
$data = $this->doQuery( 'extensions' );
$this->assertCount( 2, $data );
$this->assertSame( 'api', $data[0]['type'] );
$sharedKeys = [ 'name', 'namemsg', 'description', 'descriptionmsg', 'author', 'url',
'version', 'license-name' ];
foreach ( $sharedKeys as $key ) {
$this->assertSame( $val[$key], $data[0][$key] );
}
// @todo Test git info
$this->assertSame(
Title::makeTitle( NS_SPECIAL, 'Version/License/Ersatz Extension' )->getLinkURL(),
$data[0]['license']
);
$this->assertSame(
Title::makeTitle( NS_SPECIAL, 'Version/Credits/Ersatz Extension' )->getLinkURL(),
$data[0]['credits']
);
$this->assertSame( 'another-extension-desc', $data[1]['descriptionmsg'] );
$this->assertSame( [ 'param' ], $data[1]['descriptionmsgparams'] );
$this->assertSame( 'John Smith, John Smith Jr., ...', $data[1]['author'] );
}
/**
* @dataProvider rightsInfoProvider
*/
public function testRightsInfo( $page, $url, $text, $expectedUrl, $expectedText ) {
$this->overrideConfigValues( [
MainConfigNames::Server => 'https://local.example',
MainConfigNames::RightsPage => $page,
MainConfigNames::RightsUrl => $url,
MainConfigNames::RightsText => $text,
] );
$this->assertSame(
[ 'url' => $expectedUrl, 'text' => $expectedText ],
$this->doQuery( 'rightsinfo' )
);
// The installer sets these options to empty string if not specified otherwise,
// test that this behaves the same as null.
$this->overrideConfigValues( [
MainConfigNames::RightsPage => $page ?? '',
MainConfigNames::RightsUrl => $url ?? '',
MainConfigNames::RightsText => $text ?? '',
] );
$this->assertSame(
[ 'url' => $expectedUrl, 'text' => $expectedText ],
$this->doQuery( 'rightsinfo' ),
'empty string behaves the same as null'
);
}
public static function rightsInfoProvider() {
$licenseTitleUrl = 'https://local.example/wiki/License';
$licenseUrl = 'http://license.example/';
return [
'No rights info' => [ null, null, null, '', '' ],
'Only page' => [ 'License', null, null, $licenseTitleUrl, 'License' ],
'Only URL' => [ null, $licenseUrl, null, $licenseUrl, '' ],
'Only text' => [ null, null, '!!!', '', '!!!' ],
// URL is ignored if page is specified
'Page and URL' => [ 'License', $licenseUrl, null, $licenseTitleUrl, 'License' ],
'URL and text' => [ null, $licenseUrl, '!!!', $licenseUrl, '!!!' ],
'Page and text' => [ 'License', null, '!!!', $licenseTitleUrl, '!!!' ],
'Page and URL and text' => [ 'License', $licenseUrl, '!!!', $licenseTitleUrl, '!!!' ],
'Pagename "0"' => [ '0', null, null, 'https://local.example/wiki/0', '0' ],
'URL "0"' => [ null, '0', null, '0', '' ],
'Text "0"' => [ null, null, '0', '', '0' ],
];
}
public function testRestrictions() {
global $wgRestrictionTypes, $wgRestrictionLevels, $wgCascadingRestrictionLevels,
$wgSemiprotectedRestrictionLevels;
$this->assertSame( [
'types' => $wgRestrictionTypes,
'levels' => $wgRestrictionLevels,
'cascadinglevels' => $wgCascadingRestrictionLevels,
'semiprotectedlevels' => $wgSemiprotectedRestrictionLevels,
], $this->doQuery( 'restrictions' ) );
}
/**
* @dataProvider languagesProvider
*/
public function testLanguages( $langCode ) {
$expected = $this->getServiceContainer()
->getLanguageNameUtils()
->getLanguageNames( (string)$langCode );
$expected = array_map(
static function ( $code, $name ) {
return [
'code' => $code,
'bcp47' => LanguageCode::bcp47( $code ),
'name' => $name
];
},
array_keys( $expected ),
array_values( $expected )
);
$data = $this->doQuery( 'languages',
$langCode !== null ? [ 'siinlanguagecode' => $langCode ] : [] );
$this->assertSame( $expected, $data );
}
public static function languagesProvider() {
return [ [ null ], [ 'fr' ] ];
}
public function testLanguageVariants() {
$expectedKeys = array_filter( LanguageConverter::$languagesWithVariants,
function ( $langCode ) {
$lang = $this->getServiceContainer()->getLanguageFactory()
->getLanguage( $langCode );
$converter = $this->getServiceContainer()->getLanguageConverterFactory()
->getLanguageConverter( $lang );
return $converter->hasVariants();
}
);
sort( $expectedKeys );
$this->assertSame( $expectedKeys, array_keys( $this->doQuery( 'languagevariants' ) ) );
}
public function testLanguageVariantsDisabled() {
$this->overrideConfigValue( MainConfigNames::DisableLangConversion, true );
$this->assertSame( [], $this->doQuery( 'languagevariants' ) );
}
/**
* @todo Test a skin with a description that's known to be different in a different language.
* Vector will do, but it's not installed by default.
*
* @todo Test that an invalid language code doesn't actually try reading any messages
*
* @dataProvider skinsProvider
*/
public function testSkins( $code ) {
$data = $this->doQuery( 'skins', $code !== null ? [ 'siinlanguagecode' => $code ] : [] );
$services = $this->getServiceContainer();
$skinFactory = $services->getSkinFactory();
$skinNames = $skinFactory->getInstalledSkins();
$expectedAllowed = $skinFactory->getAllowedSkins();
$expectedDefault = Skin::normalizeKey( 'default' );
$languageNameUtils = $services->getLanguageNameUtils();
$i = 0;
foreach ( $skinNames as $name => $displayName ) {
$this->assertSame( $name, $data[$i]['code'] );
$msg = wfMessage( "skinname-$name" );
if ( $code && $languageNameUtils->isValidCode( $code ) ) {
$msg->inLanguage( $code );
} else {
$msg->inContentLanguage();
}
if ( $msg->exists() ) {
$displayName = $msg->text();
}
$this->assertSame( $displayName, $data[$i]['name'] );
if ( !isset( $expectedAllowed[$name] ) ) {
$this->assertTrue( $data[$i]['unusable'], "$name must be unusable" );
}
if ( $name === $expectedDefault ) {
$this->assertTrue( $data[$i]['default'], "$expectedDefault must be default" );
}
$i++;
}
}
public function skinsProvider() {
return [
'No language specified' => [ null ],
'Czech' => [ 'cs' ],
'Invalid language' => [ '/invalid/' ],
];
}
public function testExtensionTags() {
$expected = array_map(
static function ( $tag ) {
return "<$tag>";
},
$this->getServiceContainer()->getParser()->getTags()
);
$this->assertSame( $expected, $this->doQuery( 'extensiontags' ) );
}
public function testFunctionHooks() {
$this->assertSame( $this->getServiceContainer()->getParser()->getFunctionHooks(),
$this->doQuery( 'functionhooks' ) );
}
public function testVariables() {
$this->assertSame(
$this->getServiceContainer()->getMagicWordFactory()->getVariableIDs(),
$this->doQuery( 'variables' )
);
}
public function testProtocols() {
$urlProtocol = MainConfigSchema::getDefaultValue(
MainConfigNames::UrlProtocols
);
$this->assertSame( $urlProtocol, $this->doQuery( 'protocols' ) );
}
public function testDefaultOptions() {
$this->assertSame(
$this->getServiceContainer()->getUserOptionsLookup()->getDefaultOptions(),
$this->doQuery( 'defaultoptions' )
);
}
public function testUploadDialog() {
global $wgUploadDialog;
$this->assertSame( $wgUploadDialog, $this->doQuery( 'uploaddialog' ) );
}
public function testGetHooks() {
// Make sure there's something to report on
$this->setTemporaryHook( 'somehook',
static function () {
}
);
$hookContainer = $this->getServiceContainer()->getHookContainer();
$expectedNames = $hookContainer->getHookNames();
$actualNames = array_column( $this->doQuery( 'showhooks' ), 'name' );
$this->assertArrayEquals( $expectedNames, $actualNames );
}
public function testContinuation() {
// Use $wgUrlProtocols as easy example for forging the
// size of the API response
$protocol = 'foo://';
$size = strlen( $protocol );
$protocols = [ $protocol ];
$this->overrideConfigValues( [
MainConfigNames::UrlProtocols => [ $protocol ],
MainConfigNames::APIMaxResultSize => $size,
] );
$res = $this->doApiRequest( [
'action' => 'query',
'meta' => 'siteinfo',
'siprop' => 'protocols|languages',
] );
$this->assertSame(
wfMessage( 'apiwarn-truncatedresult', Message::numParam( $size ) )
->text(),
$res[0]['warnings']['result']['warnings']
);
$this->assertSame( $protocols, $res[0]['query']['protocols'] );
$this->assertArrayNotHasKey( 'languages', $res[0] );
$this->assertTrue( $res[0]['batchcomplete'], 'batchcomplete should be true' );
$this->assertSame( [ 'siprop' => 'languages', 'continue' => '-||' ], $res[0]['continue'] );
}
/**
* @dataProvider provideAutopromote
*/
public function testAutopromote( $config, $expected ) {
$this->overrideConfigValues( [
MainConfigNames::Autopromote => $config,
MainConfigNames::AutoConfirmCount => 10,
MainConfigNames::AutoConfirmAge => 345600,
] );
$this->assertSame( $expected, $this->doQuery( 'autopromote' ) );
}
public static function provideAutopromote() {
yield 'simple' => [
[
// edit count >= 10 and age >= 4 days
'simple' => [ '&',
[ APCOND_EDITCOUNT, 10 ],
[ APCOND_AGE, 345600 ],
],
],
[
'simple' => [
'operand' => '&',
0 => [
'condname' => 'APCOND_EDITCOUNT',
'params' => [ 10 ]
],
1 => [
'condname' => 'APCOND_AGE',
'params' => [ 345600 ]
]
]
]
];
// Test case to check default of null is replaced with value of appropriate $wg
yield 'simple-use-wg' => [
[
'simple-use-wg' => [ '&',
[ APCOND_EDITCOUNT, null ],
[ APCOND_AGE, null ],
],
],
[
'simple-use-wg' => [
'operand' => '&',
0 => [
'condname' => 'APCOND_EDITCOUNT',
'params' => [ 10 ]
],
1 => [
'condname' => 'APCOND_AGE',
'params' => [ 345600 ]
]
]
]
];
yield 'trivial' => [
[
'trivial' => APCOND_EMAILCONFIRMED,
],
[
'trivial' => [
0 => [
'condname' => 'APCOND_EMAILCONFIRMED',
'params' => []
]
],
]
];
yield 'multiple-copies-of-condition' => [
[
// In both groups 'foo' and 'bar', or in group 'baz'
'multiple-copies-of-condition' => [ '|',
[ APCOND_INGROUPS, 'foo', 'bar' ],
[ APCOND_INGROUPS, 'baz' ],
],
],
[
'multiple-copies-of-condition' => [
'operand' => '|',
0 => [
'condname' => 'APCOND_INGROUPS',
'params' => [ 'foo', 'bar' ]
],
1 => [
'condname' => 'APCOND_INGROUPS',
'params' => [ 'baz' ]
]
]
]
];
yield 'complicated' => [
[
// confirmed email, or their edit count >= 100 and either they
// created their account a year ago or it has been 10 days since
// their first edit, or if they're in both groups 'group1' and
// 'group2'. Except for users in the 'bad' group.
'complicated' => [ '&',
[ '|',
APCOND_EMAILCONFIRMED,
[ '&',
[ APCOND_EDITCOUNT, 100 ],
[ '|',
[ APCOND_AGE, 525600 * 60 ],
[ APCOND_AGE_FROM_EDIT, 864000 ],
],
],
[ APCOND_INGROUPS, 'group1', 'group2' ],
],
[ '!', [ APCOND_INGROUPS, 'bad' ] ],
],
],
[
'complicated' => [
'operand' => '&',
0 => [
'operand' => '|',
0 => [
'condname' => 'APCOND_EMAILCONFIRMED',
'params' => []
],
1 => [
'operand' => '&',
0 => [
'condname' => 'APCOND_EDITCOUNT',
'params' => [ 100 ]
],
1 => [
'operand' => '|',
0 => [
'condname' => 'APCOND_AGE',
'params' => [ 31536000 ]
],
1 => [
'condname' => 'APCOND_AGE_FROM_EDIT',
'params' => [ 864000 ]
]
]
],
2 => [
'condname' => 'APCOND_INGROUPS',
'params' => [ 'group1', 'group2' ]
]
],
1 => [
'operand' => '!',
0 => [
'condname' => 'APCOND_INGROUPS',
'params' => [ 'bad' ]
]
]
]
]
];
// Find an undefined APCOND integer
$constants = [];
foreach ( get_defined_constants() as $k => $v ) {
if ( strpos( $k, 'APCOND_' ) !== false ) {
$constants[$v] = $k;
}
}
$bogusCond = 9000;
while ( isset( $constants[$bogusCond] ) ) {
$bogusCond++;
}
$bogusCond2 = $bogusCond + 1;
while ( isset( $constants[$bogusCond2] ) ) {
$bogusCond2++;
}
yield 'bad-cond-1' => [
[
// unknown APCOND constant. Might be handled by an extension that
// didn't define a constant with the expected name.
'bad-cond' => 'bogus',
],
[
'bad-cond' => [
'bogus'
],
]
];
yield 'bad-cond-2' => [
[
'bad-cond' => $bogusCond,
],
[
'bad-cond' => [
0 => [
'condname' => false,
'params' => []
]
],
]
];
yield 'bad-cond-3' => [
[
'bad-cond' => [ 'bogus', 'bogus?', APCOND_EMAILCONFIRMED, $bogusCond, $bogusCond2 ],
],
[
'bad-cond' => [
'bogus',
'bogus?',
APCOND_EMAILCONFIRMED,
$bogusCond,
$bogusCond2
],
]
];
yield 'bad-cond-4' => [
[
'bad-cond' => [ '&',
'bogus1',
'bogus2',
APCOND_EMAILCONFIRMED,
$bogusCond,
$bogusCond2,
],
],
[
'bad-cond' => [
'operand' => '&',
0 => 'bogus1',
1 => 'bogus2',
2 => [
'condname' => 'APCOND_EMAILCONFIRMED',
'params' => []
],
3 => [
'condname' => false,
'params' => []
],
4 => [
'condname' => false,
'params' => []
]
]
]
];
}
public function testAutopromoteOnceDefault() {
// PHP doesn't like empty nested arrays nested in arrays...
$value = [
'onEdit' => [],
'onView' => [],
];
$this->testAutopromoteOnce( $value, $value );
}
/**
* @dataProvider provideAutopromoteOnce
*/
public function testAutopromoteOnce( $config, $expected ) {
$this->overrideConfigValues( [
MainConfigNames::AutopromoteOnce => $config,
MainConfigNames::AutoConfirmCount => 10,
MainConfigNames::AutoConfirmAge => 345600,
] );
$this->assertSame( $expected, $this->doQuery( 'autopromoteonce' ) );
}
public static function provideAutopromoteOnce() {
yield 'simple' => [
[
'onEdit' => [
// edit count >= 10 and age >= 4 days
'simple' => [ '&',
[ APCOND_EDITCOUNT, 10 ],
[ APCOND_AGE, 345600 ],
],
],
],
[
'onEdit' => [
'simple' => [
'operand' => '&',
0 => [
'condname' => 'APCOND_EDITCOUNT',
'params' => [ 10 ]
],
1 => [
'condname' => 'APCOND_AGE',
'params' => [ 345600 ]
]
]
]
]
];
}
}