3 namespace MediaWiki\Tests\Api\Query
;
5 use MediaWiki\Api\ApiMain
;
6 use MediaWiki\Api\ApiModuleManager
;
7 use MediaWiki\Api\ApiQuery
;
8 use MediaWiki\Api\ApiUsageException
;
9 use MediaWiki\MainConfigNames
;
10 use MediaWiki\Request\FauxRequest
;
11 use MediaWiki\Tests\Api\ApiTestCase
;
12 use MediaWiki\Tests\Api\MockApiQueryBase
;
13 use MediaWiki\Tests\Unit\DummyServicesTrait
;
14 use MediaWiki\Title\Title
;
15 use Wikimedia\TestingAccessWrapper
;
21 * @covers \MediaWiki\Api\ApiQuery
23 class ApiQueryTest
extends ApiTestCase
{
24 use DummyServicesTrait
;
26 protected function setUp(): void
{
29 // Setup apiquerytestiw: as interwiki prefix
30 $interwikiLookup = $this->getDummyInterwikiLookup( [
31 [ 'iw_prefix' => 'apiquerytestiw', 'iw_url' => 'wikipedia' ],
33 $this->setService( 'InterwikiLookup', $interwikiLookup );
36 public function testTitlesGetNormalized() {
37 $this->overrideConfigValues( [
38 MainConfigNames
::CapitalLinks
=> true,
39 MainConfigNames
::MetaNamespace
=> 'TestWiki',
42 $data = $this->doApiRequest( [
44 'titles' => 'Project:articleA|article_B' ] );
46 $this->assertArrayHasKey( 'query', $data[0] );
47 $this->assertArrayHasKey( 'normalized', $data[0]['query'] );
51 'fromencoded' => false,
52 'from' => 'Project:articleA',
53 'to' => 'TestWiki:ArticleA',
55 $data[0]['query']['normalized'][0]
60 'fromencoded' => false,
61 'from' => 'article_B',
64 $data[0]['query']['normalized'][1]
68 public function testTitlesAreRejectedIfInvalid() {
70 while ( !$title || Title
::newFromText( $title )->exists() ) {
71 $title = md5( mt_rand( 0, 100_000
) );
74 $data = $this->doApiRequest( [
76 'titles' => $title . '|Talk:' ] );
78 $this->assertArrayHasKey( 'query', $data[0] );
79 $this->assertArrayHasKey( 'pages', $data[0]['query'] );
80 $this->assertCount( 2, $data[0]['query']['pages'] );
82 $this->assertArrayHasKey( -2, $data[0]['query']['pages'] );
83 $this->assertArrayHasKey( -1, $data[0]['query']['pages'] );
85 $this->assertArrayHasKey( 'missing', $data[0]['query']['pages'][-2] );
86 $this->assertArrayHasKey( 'invalid', $data[0]['query']['pages'][-1] );
89 public function testTitlesWithWhitespaces() {
90 $data = $this->doApiRequest( [
95 $this->assertArrayHasKey( 'query', $data[0] );
96 $this->assertArrayHasKey( 'pages', $data[0]['query'] );
97 $this->assertCount( 1, $data[0]['query']['pages'] );
98 $this->assertArrayHasKey( -1, $data[0]['query']['pages'] );
99 $this->assertArrayHasKey( 'invalid', $data[0]['query']['pages'][-1] );
103 * Test the ApiBase::titlePartToKey function
105 * @param string $titlePart
106 * @param int $namespace
107 * @param string $expected
108 * @param string $expectException
109 * @dataProvider provideTestTitlePartToKey
111 public function testTitlePartToKey( $titlePart, $namespace, $expected, $expectException ) {
112 $this->overrideConfigValue( MainConfigNames
::CapitalLinks
, true );
114 $api = new MockApiQueryBase();
115 $exceptionCaught = false;
117 $this->assertEquals( $expected, $api->titlePartToKey( $titlePart, $namespace ) );
118 } catch ( ApiUsageException
$e ) {
119 $exceptionCaught = true;
121 $this->assertEquals( $expectException, $exceptionCaught,
122 'ApiUsageException thrown by titlePartToKey' );
125 public static function provideTestTitlePartToKey() {
127 [ 'a b c', NS_MAIN
, 'A_b_c', false ],
128 [ 'x', NS_MAIN
, 'X', false ],
129 [ 'y ', NS_MAIN
, 'Y_', false ],
130 [ 'template:foo', NS_CATEGORY
, 'Template:foo', false ],
131 [ 'apiquerytestiw:foo', NS_CATEGORY
, 'Apiquerytestiw:foo', false ],
132 [ "\xF7", NS_MAIN
, null, true ],
133 [ 'template:foo', NS_MAIN
, null, true ],
134 [ 'apiquerytestiw:foo', NS_MAIN
, null, true ],
139 * Test if all classes in the query module manager exists
141 public function testClassNamesInModuleManager() {
143 new FauxRequest( [ 'action' => 'query', 'meta' => 'siteinfo' ] )
145 $queryApi = $api->getModuleManager()->getModule( 'query' );
146 $modules = $queryApi->getModuleManager()->getNamesWithClasses();
148 foreach ( $modules as $name => $class ) {
150 class_exists( $class ),
151 'Class ' . $class . ' for api module ' . $name . ' does not exist (with exact case)'
156 public function testShouldNotExportPagesThatUserCanNotRead() {
157 $title = Title
::makeTitle( NS_MAIN
, 'Test article' );
158 $this->insertPage( $title );
160 $this->setTemporaryHook( 'getUserPermissionsErrors',
161 static function ( Title
$page, &$user, $action, &$result ) use ( $title ) {
162 if ( $page->equals( $title ) && $action === 'read' ) {
168 $data = $this->doApiRequest( [
170 'titles' => $title->getPrefixedText(),
174 $this->assertArrayHasKey( 'query', $data[0] );
175 $this->assertArrayHasKey( 'export', $data[0]['query'] );
176 // This response field contains an XML document even if no pages were exported
177 $this->assertStringNotContainsString( $title->getPrefixedText(), $data[0]['query']['export'] );
180 public function testIsReadMode() {
182 new FauxRequest( [ 'action' => 'query', 'meta' => 'tokens', 'type' => 'login' ] )
184 $queryApi = $api->getModuleManager()->getModule( 'query' );
185 $this->assertFalse( $queryApi->isReadMode(),
186 'isReadMode() => false when meta=tokens is the only module' );
188 $api = new ApiMain( new FauxRequest( [
189 'action' => 'query', 'meta' => 'tokens', 'type' => 'login', 'rawcontinue' => 1,
193 $queryApi = $api->getModuleManager()->getModule( 'query' );
194 $this->assertFalse( $queryApi->isReadMode(),
195 'rawcontinue and indexpageids are also allowed' );
198 new FauxRequest( [ 'action' => 'query', 'meta' => 'tokens|siteinfo', 'type' => 'login' ] )
200 $queryApi = $api->getModuleManager()->getModule( 'query' );
201 $this->assertTrue( $queryApi->isReadMode(),
202 'isReadMode() => true when other meta modules are present' );
204 $api = new ApiMain( new FauxRequest( [
205 'action' => 'query', 'meta' => 'tokens', 'type' => 'login', 'list' => 'allpages'
207 $queryApi = $api->getModuleManager()->getModule( 'query' );
208 $this->assertTrue( $queryApi->isReadMode(),
209 'isReadMode() => true when other modules are present' );
211 $api = new ApiMain( new FauxRequest( [
212 'action' => 'query', 'meta' => 'tokens', 'type' => 'login', 'titles' => 'Foo'
214 $queryApi = $api->getModuleManager()->getModule( 'query' );
215 $this->assertTrue( $queryApi->isReadMode(),
216 'isReadMode() => true when other ApiQuery parameters are present' );
218 $api = new ApiMain( new FauxRequest( [ 'action' => 'query' ] ) );
219 $queryApi = $api->getModuleManager()->getModule( 'query' );
220 $this->assertTrue( $queryApi->isReadMode(),
221 'isReadMode() => true when no modules are requested' );
224 /** @dataProvider provideIsWriteMode */
225 public function testIsWriteMode( $queryParams, $expected ) {
226 $api = new ApiMain( new FauxRequest( array_merge( [ 'action' => 'query' ], $queryParams ) ) );
227 $queryApi = $api->getModuleManager()->getModule( 'query' );
230 $queryApi->isWriteMode(),
231 '::isWriteMode did not return the expected value.'
235 public static function provideIsWriteMode() {
237 'No modules specified' => [ [], false ],
238 'Only meta=tokens' => [ [ 'meta' => 'tokens', 'type' => 'login' ], false ],
242 public function testIsWriteModeForMockedModule() {
243 $queryApi = $this->getMockBuilder( ApiQuery
::class )
244 ->disableOriginalConstructor()
245 ->onlyMethods( [ 'extractRequestParams' ] )
247 // We need to mock ::extractRequestParams because we mock the module manager
248 // and using the original implementation results in a test failure.
249 $queryApi->method( 'extractRequestParams' )->willReturn( [
250 'list' => [ 'mocked-module' ]
252 // Mock $queryApi->mModuleMgr to return always return a mock module that returns true from ::isWriteMode
253 $mockModuleManager = $this->createMock( ApiModuleManager
::class );
254 $mockModuleManager->method( 'getModule' )->willReturnCallback( function ( $name ) {
255 $module = $this->createMock( MockApiQueryBase
::class );
256 $module->method( 'isWriteMode' )
257 ->willReturn( true );
260 $queryApi = TestingAccessWrapper
::newFromObject( $queryApi );
261 $queryApi->mModuleMgr
= $mockModuleManager;
262 $this->assertTrue( $queryApi->isWriteMode(), '::isWriteMode did not return the expected value.' );