mediawiki.api: Adopt async-await and assert.rejects() in various tests
[mediawiki.git] / tests / phpunit / includes / api / ApiBaseTest.php
blobbb37a821a9ea7b7e423d46dc1d869c0ca8546c9a
1 <?php
3 namespace MediaWiki\Tests\Api;
5 use DomainException;
6 use Exception;
7 use MediaWiki\Api\ApiBase;
8 use MediaWiki\Api\ApiBlockInfoTrait;
9 use MediaWiki\Api\ApiMain;
10 use MediaWiki\Api\ApiMessage;
11 use MediaWiki\Api\ApiUsageException;
12 use MediaWiki\Api\Validator\SubmoduleDef;
13 use MediaWiki\Block\DatabaseBlock;
14 use MediaWiki\Context\DerivativeContext;
15 use MediaWiki\Context\RequestContext;
16 use MediaWiki\MediaWikiServices;
17 use MediaWiki\Message\Message;
18 use MediaWiki\ParamValidator\TypeDef\NamespaceDef;
19 use MediaWiki\Permissions\PermissionStatus;
20 use MediaWiki\Request\FauxRequest;
21 use MediaWiki\Status\Status;
22 use MediaWiki\Title\Title;
23 use MWException;
24 use StatusValue;
25 use Wikimedia\Message\MessageSpecifier;
26 use Wikimedia\ParamValidator\ParamValidator;
27 use Wikimedia\ParamValidator\TypeDef\EnumDef;
28 use Wikimedia\ParamValidator\TypeDef\IntegerDef;
29 use Wikimedia\ParamValidator\TypeDef\StringDef;
30 use Wikimedia\TestingAccessWrapper;
31 use WikiPage;
33 /**
34 * @group API
35 * @group Database
36 * @group medium
38 * @covers \MediaWiki\Api\ApiBase
40 class ApiBaseTest extends ApiTestCase {
42 protected function setUp(): void {
43 parent::setUp();
44 $this->setGroupPermissions( [
45 '*' => [
46 'read' => true,
47 'edit' => true,
48 'apihighlimits' => false,
50 ] );
53 /**
54 * This covers a variety of stub methods that return a fixed value.
56 * @dataProvider provideStubMethods
58 public function testStubMethods( $expected, $method, $args = [] ) {
59 // Some of these are protected
60 $mock = TestingAccessWrapper::newFromObject( new MockApi() );
61 $result = $mock->$method( ...$args );
62 $this->assertSame( $expected, $result );
65 public static function provideStubMethods() {
66 return [
67 [ null, 'getModuleManager' ],
68 [ null, 'getCustomPrinter' ],
69 [ [], 'getHelpUrls' ],
70 // @todo This is actually overridden by MockApi
71 // [ [], 'getAllowedParams' ],
72 [ true, 'shouldCheckMaxLag' ],
73 [ true, 'isReadMode' ],
74 [ false, 'isWriteMode' ],
75 [ false, 'mustBePosted' ],
76 [ false, 'isDeprecated' ],
77 [ false, 'isInternal' ],
78 [ false, 'needsToken' ],
79 [ null, 'getWebUITokenSalt', [ [] ] ],
80 [ null, 'getConditionalRequestData', [ 'etag' ] ],
81 [ null, 'dynamicParameterDocumentation' ],
85 public function testRequireOnlyOneParameterDefault() {
86 $mock = new MockApi();
87 $mock->requireOnlyOneParameter(
88 [ "filename" => "foo.txt", "enablechunks" => false ],
89 "filename", "enablechunks"
91 $this->assertTrue( true );
94 public function testRequireOnlyOneParameterZero() {
95 $mock = new MockApi();
96 $this->expectException( ApiUsageException::class );
97 $mock->requireOnlyOneParameter(
98 [ "filename" => "foo.txt", "enablechunks" => 0 ],
99 "filename", "enablechunks"
103 public function testRequireOnlyOneParameterTrue() {
104 $mock = new MockApi();
105 $this->expectException( ApiUsageException::class );
106 $mock->requireOnlyOneParameter(
107 [ "filename" => "foo.txt", "enablechunks" => true ],
108 "filename", "enablechunks"
112 public function testRequireOnlyOneParameterMissing() {
113 $this->expectApiErrorCode( 'missingparam' );
114 $mock = new MockApi();
115 $mock->requireOnlyOneParameter(
116 [ "filename" => "foo.txt", "enablechunks" => false ],
117 "foo", "bar" );
120 public function testRequireMaxOneParameterZero() {
121 $mock = new MockApi();
122 $mock->requireMaxOneParameter(
123 [ 'foo' => 'bar', 'baz' => 'quz' ],
124 'squirrel' );
125 $this->assertTrue( true );
128 public function testRequireMaxOneParameterOne() {
129 $mock = new MockApi();
130 $mock->requireMaxOneParameter(
131 [ 'foo' => 'bar', 'baz' => 'quz' ],
132 'foo', 'squirrel' );
133 $this->assertTrue( true );
136 public function testRequireMaxOneParameterTwo() {
137 $this->expectApiErrorCode( 'invalidparammix' );
138 $mock = new MockApi();
139 $mock->requireMaxOneParameter(
140 [ 'foo' => 'bar', 'baz' => 'quz' ],
141 'foo', 'baz' );
144 public function testRequireAtLeastOneParameterZero() {
145 $this->expectApiErrorCode( 'missingparam' );
146 $mock = new MockApi();
147 $mock->requireAtLeastOneParameter(
148 [ 'a' => 'b', 'c' => 'd' ],
149 'foo', 'bar' );
152 public function testRequireAtLeastOneParameterOne() {
153 $mock = new MockApi();
154 $mock->requireAtLeastOneParameter(
155 [ 'a' => 'b', 'c' => 'd' ],
156 'foo', 'a' );
157 $this->assertTrue( true );
160 public function testRequireAtLeastOneParameterTwo() {
161 $mock = new MockApi();
162 $mock->requireAtLeastOneParameter(
163 [ 'a' => 'b', 'c' => 'd' ],
164 'a', 'c' );
165 $this->assertTrue( true );
168 public function testGetTitleOrPageIdBadParams() {
169 $this->expectApiErrorCode( 'invalidparammix' );
170 $mock = new MockApi();
171 $mock->getTitleOrPageId( [ 'title' => 'a', 'pageid' => 7 ] );
174 public function testGetTitleOrPageIdTitle() {
175 $mock = new MockApi();
176 $result = $mock->getTitleOrPageId( [ 'title' => 'Foo' ] );
177 $this->assertInstanceOf( WikiPage::class, $result );
178 $this->assertSame( 'Foo', $result->getTitle()->getPrefixedText() );
181 public function testGetTitleOrPageIdInvalidTitle() {
182 $this->expectApiErrorCode( 'invalidtitle' );
183 $mock = new MockApi();
184 $mock->getTitleOrPageId( [ 'title' => '|' ] );
187 public function testGetTitleOrPageIdSpecialTitle() {
188 $this->expectApiErrorCode( 'pagecannotexist' );
189 $mock = new MockApi();
190 $mock->getTitleOrPageId( [ 'title' => 'Special:RandomPage' ] );
193 public function testGetTitleOrPageIdPageId() {
194 $page = $this->getExistingTestPage();
195 $result = ( new MockApi() )->getTitleOrPageId(
196 [ 'pageid' => $page->getId() ] );
197 $this->assertInstanceOf( WikiPage::class, $result );
198 $this->assertSame(
199 $page->getTitle()->getPrefixedText(),
200 $result->getTitle()->getPrefixedText()
204 public function testGetTitleOrPageIdInvalidPageId() {
205 $this->expectApiErrorCode( 'nosuchpageid' );
206 $mock = new MockApi();
207 $mock->getTitleOrPageId( [ 'pageid' => 2147483648 ] );
210 public function testGetTitleFromTitleOrPageIdBadParams() {
211 $this->expectApiErrorCode( 'invalidparammix' );
212 $mock = new MockApi();
213 $mock->getTitleFromTitleOrPageId( [ 'title' => 'a', 'pageid' => 7 ] );
216 public function testGetTitleFromTitleOrPageIdTitle() {
217 $mock = new MockApi();
218 $result = $mock->getTitleFromTitleOrPageId( [ 'title' => 'Foo' ] );
219 $this->assertInstanceOf( Title::class, $result );
220 $this->assertSame( 'Foo', $result->getPrefixedText() );
223 public function testGetTitleFromTitleOrPageIdInvalidTitle() {
224 $this->expectApiErrorCode( 'invalidtitle' );
225 $mock = new MockApi();
226 $mock->getTitleFromTitleOrPageId( [ 'title' => '|' ] );
229 public function testGetTitleFromTitleOrPageIdPageId() {
230 $page = $this->getExistingTestPage();
231 $result = ( new MockApi() )->getTitleFromTitleOrPageId(
232 [ 'pageid' => $page->getId() ] );
233 $this->assertInstanceOf( Title::class, $result );
234 $this->assertSame( $page->getTitle()->getPrefixedText(), $result->getPrefixedText() );
237 public function testGetTitleFromTitleOrPageIdInvalidPageId() {
238 $this->expectApiErrorCode( 'nosuchpageid' );
239 $mock = new MockApi();
240 $mock->getTitleFromTitleOrPageId( [ 'pageid' => 298401643 ] );
243 public function testGetParameter() {
244 $mock = $this->getMockBuilder( MockApi::class )
245 ->onlyMethods( [ 'getAllowedParams' ] )
246 ->getMock();
247 $mock->method( 'getAllowedParams' )->willReturn( [
248 'foo' => [
249 ParamValidator::PARAM_TYPE => [ 'value' ],
251 'bar' => [
252 ParamValidator::PARAM_TYPE => [ 'value' ],
254 ] );
255 $wrapper = TestingAccessWrapper::newFromObject( $mock );
257 $context = new DerivativeContext( $mock );
258 $context->setRequest( new FauxRequest( [ 'foo' => 'bad', 'bar' => 'value' ] ) );
259 $wrapper->mMainModule = new ApiMain( $context );
261 // Even though 'foo' is bad, getParameter( 'bar' ) must not fail
262 $this->assertSame( 'value', $wrapper->getParameter( 'bar' ) );
264 // But getParameter( 'foo' ) must throw.
265 try {
266 $wrapper->getParameter( 'foo' );
267 $this->fail( 'Expected exception not thrown' );
268 } catch ( ApiUsageException $ex ) {
269 $this->assertApiErrorCode( 'badvalue', $ex );
272 // And extractRequestParams() must throw too.
273 try {
274 $mock->extractRequestParams();
275 $this->fail( 'Expected exception not thrown' );
276 } catch ( ApiUsageException $ex ) {
277 $this->assertApiErrorCode( 'badvalue', $ex );
282 * @param string|null $input
283 * @param array $paramSettings
284 * @param mixed $expected
285 * @param string[] $warnings
286 * @param array $options Key-value pairs:
287 * 'parseLimits': true|false
288 * 'apihighlimits': true|false
289 * 'prefix': true|false
291 private function doGetParameterFromSettings(
292 $input, $paramSettings, $expected, $warnings, $options = []
294 $mock = new MockApi();
295 $wrapper = TestingAccessWrapper::newFromObject( $mock );
296 if ( $options['prefix'] ) {
297 $wrapper->mModulePrefix = 'my';
298 $paramName = 'Param';
299 } else {
300 $paramName = 'myParam';
303 $context = new DerivativeContext( $mock );
304 $context->setRequest( new FauxRequest(
305 $input !== null ? [ 'myParam' => $input ] : [] ) );
306 $wrapper->mMainModule = new ApiMain( $context );
308 $parseLimits = $options['parseLimits'] ?? true;
310 if ( !empty( $options['apihighlimits'] ) ) {
311 $context->setUser( $this->getTestSysop()->getUser() );
314 // If we're testing tags, set up some tags
315 if ( isset( $paramSettings[ParamValidator::PARAM_TYPE] ) &&
316 $paramSettings[ParamValidator::PARAM_TYPE] === 'tags'
318 $changeTagStore = $this->getServiceContainer()->getChangeTagsStore();
319 $changeTagStore->defineTag( 'tag1' );
320 $changeTagStore->defineTag( 'tag2' );
323 if ( $expected instanceof Exception ) {
324 try {
325 $wrapper->getParameterFromSettings( $paramName, $paramSettings,
326 $parseLimits );
327 $this->fail( 'No exception thrown' );
328 } catch ( Exception $ex ) {
329 $this->assertInstanceOf( get_class( $expected ), $ex );
330 if ( $ex instanceof ApiUsageException ) {
331 $this->assertEquals( $expected->getModulePath(), $ex->getModulePath() );
332 $this->assertEquals( $expected->getStatusValue(), $ex->getStatusValue() );
333 } else {
334 $this->assertEquals( $expected->getMessage(), $ex->getMessage() );
335 $this->assertEquals( $expected->getCode(), $ex->getCode() );
338 } else {
339 $result = $wrapper->getParameterFromSettings( $paramName,
340 $paramSettings, $parseLimits );
341 if ( isset( $paramSettings[ParamValidator::PARAM_TYPE] ) &&
342 $paramSettings[ParamValidator::PARAM_TYPE] === 'timestamp' &&
343 $expected === 'now'
345 // Allow one second of fuzziness. Make sure the formats are
346 // correct!
347 $this->assertMatchesRegularExpression( '/^\d{14}$/', $result );
348 $this->assertLessThanOrEqual( 1,
349 abs( wfTimestamp( TS_UNIX, $result ) - time() ),
350 "Result $result differs from expected $expected by " .
351 'more than one second' );
352 } else {
353 $this->assertSame( $expected, $result );
355 $actualWarnings = array_map( static function ( $warn ) {
356 return $warn instanceof MessageSpecifier
357 ? [ $warn->getKey(), ...$warn->getParams() ]
358 : $warn;
359 }, $mock->warnings );
360 $this->assertEquals( $warnings, $actualWarnings );
363 if ( !empty( $paramSettings[ParamValidator::PARAM_SENSITIVE] ) ||
364 ( isset( $paramSettings[ParamValidator::PARAM_TYPE] ) &&
365 $paramSettings[ParamValidator::PARAM_TYPE] === 'password' )
367 $mainWrapper = TestingAccessWrapper::newFromObject( $wrapper->getMain() );
368 $this->assertSame( [ 'myParam' ],
369 $mainWrapper->getSensitiveParams() );
374 * @dataProvider provideGetParameterFromSettings
375 * @see self::doGetParameterFromSettings()
377 public function testGetParameterFromSettings_noprefix(
378 $input, $paramSettings, $expected, $warnings, $options = []
380 $options['prefix'] = false;
381 $this->doGetParameterFromSettings( $input, $paramSettings, $expected, $warnings, $options );
385 * @dataProvider provideGetParameterFromSettings
386 * @see self::doGetParameterFromSettings()
388 public function testGetParameterFromSettings_prefix(
389 $input, $paramSettings, $expected, $warnings, $options = []
391 $options['prefix'] = true;
392 $this->doGetParameterFromSettings( $input, $paramSettings, $expected, $warnings, $options );
395 public static function provideGetParameterFromSettings() {
396 $warnings = [
397 [ 'apiwarn-badutf8', 'myParam' ],
400 $c0 = '';
401 $enc = '';
402 for ( $i = 0; $i < 32; $i++ ) {
403 $c0 .= chr( $i );
404 $enc .= ( $i === 9 || $i === 10 || $i === 13 )
405 ? chr( $i )
406 : '�';
409 $namespaces = MediaWikiServices::getInstance()->getNamespaceInfo()->getValidNamespaces();
411 $returnArray = [
412 'Basic param' => [ 'bar', null, 'bar', [] ],
413 'Basic param, C0 controls' => [ $c0, null, $enc, $warnings ],
414 'String param' => [ 'bar', '', 'bar', [] ],
415 'String param, defaulted' => [ null, '', '', [] ],
416 'String param, empty' => [ '', 'default', '', [] ],
417 'String param, required, empty' => [
419 [ ParamValidator::PARAM_DEFAULT => 'default', ParamValidator::PARAM_REQUIRED => true ],
420 ApiUsageException::newWithMessage( null, [
421 'paramvalidator-missingparam',
422 Message::plaintextParam( 'myParam' ),
423 Message::plaintextParam( '' ),
424 ], 'missingparam' ),
427 'Multi-valued parameter' => [
428 'a|b|c',
429 [ ParamValidator::PARAM_ISMULTI => true ],
430 [ 'a', 'b', 'c' ],
433 'Multi-valued parameter, alternative separator' => [
434 "\x1fa|b\x1fc|d",
435 [ ParamValidator::PARAM_ISMULTI => true ],
436 [ 'a|b', 'c|d' ],
439 'Multi-valued parameter, other C0 controls' => [
440 $c0,
441 [ ParamValidator::PARAM_ISMULTI => true ],
442 [ $enc ],
443 $warnings
445 'Multi-valued parameter, other C0 controls (2)' => [
446 "\x1f" . $c0,
447 [ ParamValidator::PARAM_ISMULTI => true ],
448 [ substr( $enc, 0, -3 ), '' ],
449 $warnings
451 'Multi-valued parameter with limits' => [
452 'a|b|c',
454 ParamValidator::PARAM_ISMULTI => true,
455 ParamValidator::PARAM_ISMULTI_LIMIT1 => 3,
457 [ 'a', 'b', 'c' ],
460 'Multi-valued parameter with exceeded limits' => [
461 'a|b|c',
463 ParamValidator::PARAM_ISMULTI => true,
464 ParamValidator::PARAM_ISMULTI_LIMIT1 => 2,
466 ApiUsageException::newWithMessage( null, [
467 'paramvalidator-toomanyvalues',
468 Message::plaintextParam( 'myParam' ),
469 Message::numParam( 2 ),
470 ], 'toomanyvalues', [
471 'parameter' => 'myParam',
472 'limit' => 2,
473 'lowlimit' => 2,
474 'highlimit' => 500,
475 ] ),
478 'Multi-valued parameter with exceeded limits for non-bot' => [
479 'a|b|c',
481 ParamValidator::PARAM_ISMULTI => true,
482 ParamValidator::PARAM_ISMULTI_LIMIT1 => 2,
483 ParamValidator::PARAM_ISMULTI_LIMIT2 => 3,
485 ApiUsageException::newWithMessage( null, [
486 'paramvalidator-toomanyvalues',
487 Message::plaintextParam( 'myParam' ),
488 Message::numParam( 2 ),
489 ], 'toomanyvalues', [
490 'parameter' => 'myParam',
491 'limit' => 2,
492 'lowlimit' => 2,
493 'highlimit' => 3,
494 ] ),
497 'Multi-valued parameter with non-exceeded limits for bot' => [
498 'a|b|c',
500 ParamValidator::PARAM_ISMULTI => true,
501 ParamValidator::PARAM_ISMULTI_LIMIT1 => 2,
502 ParamValidator::PARAM_ISMULTI_LIMIT2 => 3,
504 [ 'a', 'b', 'c' ],
506 [ 'apihighlimits' => true ],
508 'Multi-valued parameter with prohibited duplicates' => [
509 'a|b|a|c',
510 [ ParamValidator::PARAM_ISMULTI => true ],
511 [ 'a', 'b', 'c' ],
514 'Multi-valued parameter with allowed duplicates' => [
515 'a|a',
517 ParamValidator::PARAM_ISMULTI => true,
518 ParamValidator::PARAM_ALLOW_DUPLICATES => true,
520 [ 'a', 'a' ],
523 'Empty boolean param' => [
525 [ ParamValidator::PARAM_TYPE => 'boolean' ],
526 true,
529 'Boolean param 0' => [
530 '0',
531 [ ParamValidator::PARAM_TYPE => 'boolean' ],
532 true,
535 'Boolean param false' => [
536 'false',
537 [ ParamValidator::PARAM_TYPE => 'boolean' ],
538 true,
541 'Deprecated parameter' => [
542 'foo',
543 [ ParamValidator::PARAM_DEPRECATED => true ],
544 'foo',
546 'paramvalidator-param-deprecated',
547 Message::plaintextParam( 'myParam' ),
548 Message::plaintextParam( 'foo' )
549 ] ],
551 'Deprecated parameter with default, unspecified' => [
552 null,
553 [ ParamValidator::PARAM_DEPRECATED => true, ParamValidator::PARAM_DEFAULT => 'foo' ],
554 'foo',
557 'Deprecated parameter with default, specified' => [
558 'foo',
559 [ ParamValidator::PARAM_DEPRECATED => true, ParamValidator::PARAM_DEFAULT => 'foo' ],
560 'foo',
562 'paramvalidator-param-deprecated',
563 Message::plaintextParam( 'myParam' ),
564 Message::plaintextParam( 'foo' )
565 ] ],
567 'Deprecated parameter value' => [
568 'a',
569 [ ParamValidator::PARAM_TYPE => [ 'a' ], EnumDef::PARAM_DEPRECATED_VALUES => [ 'a' => true ] ],
570 'a',
572 'paramvalidator-deprecated-value',
573 Message::plaintextParam( 'myParam' ),
574 Message::plaintextParam( 'a' )
575 ] ],
577 'Deprecated parameter value as default, unspecified' => [
578 null,
580 ParamValidator::PARAM_TYPE => [ 'a' ],
581 EnumDef::PARAM_DEPRECATED_VALUES => [ 'a' => true ],
582 ParamValidator::PARAM_DEFAULT => 'a'
584 'a',
587 'Deprecated parameter value as default, specified' => [
588 'a',
590 ParamValidator::PARAM_TYPE => [ 'a' ],
591 EnumDef::PARAM_DEPRECATED_VALUES => [ 'a' => true ],
592 ParamValidator::PARAM_DEFAULT => 'a'
594 'a',
596 'paramvalidator-deprecated-value',
597 Message::plaintextParam( 'myParam' ),
598 Message::plaintextParam( 'a' )
599 ] ],
601 'Multiple deprecated parameter values' => [
602 'a|b|c|d',
604 ParamValidator::PARAM_TYPE => [ 'a', 'b', 'c', 'd' ],
605 EnumDef::PARAM_DEPRECATED_VALUES => [ 'b' => true, 'd' => true ],
606 ParamValidator::PARAM_ISMULTI => true,
608 [ 'a', 'b', 'c', 'd' ],
611 'paramvalidator-deprecated-value',
612 Message::plaintextParam( 'myParam' ),
613 Message::plaintextParam( 'b' )
616 'paramvalidator-deprecated-value',
617 Message::plaintextParam( 'myParam' ),
618 Message::plaintextParam( 'd' )
622 'Deprecated parameter value with custom warning' => [
623 'a',
624 [ ParamValidator::PARAM_TYPE => [ 'a' ], EnumDef::PARAM_DEPRECATED_VALUES => [ 'a' => 'my-msg' ] ],
625 'a',
626 [ [ 'my-msg' ] ],
628 '"*" when wildcard not allowed' => [
629 '*',
631 ParamValidator::PARAM_ISMULTI => true,
632 ParamValidator::PARAM_TYPE => [ 'a', 'b', 'c' ],
636 'paramvalidator-unrecognizedvalues',
637 Message::plaintextParam( 'myParam' ),
638 Message::plaintextParam( '*' ),
639 Message::listParam( [ Message::plaintextParam( '*' ) ], 'comma' ),
640 Message::numParam( 1 ),
641 ] ],
643 'Wildcard "*"' => [
644 '*',
646 ParamValidator::PARAM_ISMULTI => true,
647 ParamValidator::PARAM_TYPE => [ 'a', 'b', 'c' ],
648 ParamValidator::PARAM_ALL => true,
650 [ 'a', 'b', 'c' ],
653 'Wildcard "*" with multiples not allowed' => [
654 '*',
656 ParamValidator::PARAM_TYPE => [ 'a', 'b', 'c' ],
657 ParamValidator::PARAM_ALL => true,
659 ApiUsageException::newWithMessage( null, [
660 'paramvalidator-badvalue-enumnotmulti',
661 Message::plaintextParam( 'myParam' ),
662 Message::plaintextParam( '*' ),
663 Message::listParam( [
664 Message::plaintextParam( 'a' ),
665 Message::plaintextParam( 'b' ),
666 Message::plaintextParam( 'c' ),
667 ] ),
668 Message::numParam( 3 ),
669 ], 'badvalue' ),
672 'Wildcard "*" with unrestricted type' => [
673 '*',
675 ParamValidator::PARAM_ISMULTI => true,
676 ParamValidator::PARAM_ALL => true,
678 [ '*' ],
681 'Wildcard "x"' => [
682 'x',
684 ParamValidator::PARAM_ISMULTI => true,
685 ParamValidator::PARAM_TYPE => [ 'a', 'b', 'c' ],
686 ParamValidator::PARAM_ALL => 'x',
688 [ 'a', 'b', 'c' ],
691 'Namespace with wildcard' => [
692 '*',
694 ParamValidator::PARAM_ISMULTI => true,
695 ParamValidator::PARAM_TYPE => 'namespace',
697 $namespaces,
700 // PARAM_ALL is ignored with namespace types.
701 'Namespace with wildcard suppressed' => [
702 '*',
704 ParamValidator::PARAM_ISMULTI => true,
705 ParamValidator::PARAM_TYPE => 'namespace',
706 ParamValidator::PARAM_ALL => false,
708 $namespaces,
711 'Namespace with wildcard "x"' => [
712 'x',
714 ParamValidator::PARAM_ISMULTI => true,
715 ParamValidator::PARAM_TYPE => 'namespace',
716 ParamValidator::PARAM_ALL => 'x',
720 'paramvalidator-unrecognizedvalues',
721 Message::plaintextParam( 'myParam' ),
722 Message::plaintextParam( 'x' ),
723 Message::listParam( [ Message::plaintextParam( 'x' ) ], 'comma' ),
724 Message::numParam( 1 ),
725 ] ],
727 'Password' => [
728 'dDy+G?e?txnr.1:(@Ru',
729 [ ParamValidator::PARAM_TYPE => 'password' ],
730 'dDy+G?e?txnr.1:(@Ru',
733 'Sensitive field' => [
734 'I am fond of pineapples',
735 [ ParamValidator::PARAM_SENSITIVE => true ],
736 'I am fond of pineapples',
739 // @todo Test actual upload
740 'Namespace -1' => [
741 '-1',
742 [ ParamValidator::PARAM_TYPE => 'namespace' ],
743 ApiUsageException::newWithMessage( null, [
744 'paramvalidator-badvalue-enumnotmulti',
745 Message::plaintextParam( 'myParam' ),
746 Message::plaintextParam( '-1' ),
747 Message::listParam( array_map( [ Message::class, 'plaintextParam' ], $namespaces ) ),
748 Message::numParam( count( $namespaces ) ),
749 ], 'badvalue' ),
752 'Extra namespace -1' => [
753 '-1',
755 ParamValidator::PARAM_TYPE => 'namespace',
756 NamespaceDef::PARAM_EXTRA_NAMESPACES => [ -1 ],
761 // @todo Test with PARAM_SUBMODULE_MAP unset, need
762 // getModuleManager() to return something real
763 'Nonexistent module' => [
764 'not-a-module-name',
766 ParamValidator::PARAM_TYPE => 'submodule',
767 SubmoduleDef::PARAM_SUBMODULE_MAP =>
768 [ 'foo' => 'foo', 'bar' => 'foo+bar' ],
770 ApiUsageException::newWithMessage( null, [
771 'paramvalidator-badvalue-enumnotmulti',
772 Message::plaintextParam( 'myParam' ),
773 Message::plaintextParam( 'not-a-module-name' ),
774 Message::listParam( [
775 Message::plaintextParam( 'foo' ),
776 Message::plaintextParam( 'bar' ),
777 ] ),
778 Message::numParam( 2 ),
779 ], 'badvalue' ),
782 '\\x1f with multiples not allowed' => [
783 "\x1f",
785 ApiUsageException::newWithMessage( null, [
786 'paramvalidator-notmulti',
787 Message::plaintextParam( 'myParam' ),
788 Message::plaintextParam( "\x1f" ),
789 ], 'badvalue' ),
792 'Integer with unenforced min' => [
793 '-2',
795 ParamValidator::PARAM_TYPE => 'integer',
796 IntegerDef::PARAM_MIN => -1,
800 'paramvalidator-outofrange-min',
801 Message::plaintextParam( 'myParam' ),
802 Message::plaintextParam( '-2' ),
803 Message::numParam( -1 ),
804 Message::numParam( '' ),
805 ] ],
807 'Integer with enforced min' => [
808 '-2',
810 ParamValidator::PARAM_TYPE => 'integer',
811 IntegerDef::PARAM_MIN => -1,
812 ApiBase::PARAM_RANGE_ENFORCE => true,
814 ApiUsageException::newWithMessage( null, [
815 'paramvalidator-outofrange-min',
816 Message::plaintextParam( 'myParam' ),
817 Message::plaintextParam( '-2' ),
818 Message::numParam( -1 ),
819 Message::numParam( '' ),
820 ], 'outofrange', [ 'min' => -1, 'curmax' => null, 'max' => null, 'highmax' => null ] ),
823 'Integer with unenforced max' => [
824 '8',
826 ParamValidator::PARAM_TYPE => 'integer',
827 IntegerDef::PARAM_MAX => 7,
831 'paramvalidator-outofrange-max',
832 Message::plaintextParam( 'myParam' ),
833 Message::plaintextParam( '8' ),
834 Message::numParam( '' ),
835 Message::numParam( 7 ),
836 ] ],
838 'Integer with enforced max' => [
839 '8',
841 ParamValidator::PARAM_TYPE => 'integer',
842 IntegerDef::PARAM_MAX => 7,
843 ApiBase::PARAM_RANGE_ENFORCE => true,
845 ApiUsageException::newWithMessage( null, [
846 'paramvalidator-outofrange-max',
847 Message::plaintextParam( 'myParam' ),
848 Message::plaintextParam( '8' ),
849 Message::numParam( '' ),
850 Message::numParam( 7 ),
851 ], 'outofrange', [ 'min' => null, 'curmax' => 7, 'max' => 7, 'highmax' => 7 ] ),
854 'Array of integers' => [
855 '3|12|966|-1',
857 ParamValidator::PARAM_ISMULTI => true,
858 ParamValidator::PARAM_TYPE => 'integer',
860 [ 3, 12, 966, -1 ],
863 'Array of integers with unenforced min/max' => [
864 '3|12|966|-1',
866 ParamValidator::PARAM_ISMULTI => true,
867 ParamValidator::PARAM_TYPE => 'integer',
868 IntegerDef::PARAM_MIN => 0,
869 IntegerDef::PARAM_MAX => 100,
871 [ 3, 12, 100, 0 ],
874 'paramvalidator-outofrange-minmax',
875 Message::plaintextParam( 'myParam' ),
876 Message::plaintextParam( '966' ),
877 Message::numParam( 0 ),
878 Message::numParam( 100 ),
881 'paramvalidator-outofrange-minmax',
882 Message::plaintextParam( 'myParam' ),
883 Message::plaintextParam( '-1' ),
884 Message::numParam( 0 ),
885 Message::numParam( 100 ),
889 'Array of integers with enforced min/max' => [
890 '3|12|966|-1',
892 ParamValidator::PARAM_ISMULTI => true,
893 ParamValidator::PARAM_TYPE => 'integer',
894 IntegerDef::PARAM_MIN => 0,
895 IntegerDef::PARAM_MAX => 100,
896 ApiBase::PARAM_RANGE_ENFORCE => true,
898 ApiUsageException::newWithMessage( null, [
899 'paramvalidator-outofrange-minmax',
900 Message::plaintextParam( 'myParam' ),
901 Message::plaintextParam( '966' ),
902 Message::numParam( 0 ),
903 Message::numParam( 100 ),
904 ], 'outofrange', [ 'min' => 0, 'curmax' => 100, 'max' => 100, 'highmax' => 100 ] ),
907 'Limit with parseLimits false (numeric)' => [
908 '100',
909 [ ParamValidator::PARAM_TYPE => 'limit' ],
910 100,
912 [ 'parseLimits' => false ],
914 'Limit with parseLimits false (max)' => [
915 'max',
916 [ ParamValidator::PARAM_TYPE => 'limit' ],
917 'max',
919 [ 'parseLimits' => false ],
921 'Limit with parseLimits false (invalid)' => [
922 'kitten',
923 [ ParamValidator::PARAM_TYPE => 'limit' ],
924 ApiUsageException::newWithMessage( null, [
925 'paramvalidator-badinteger',
926 Message::plaintextParam( 'myParam' ),
927 Message::plaintextParam( 'kitten' ),
928 ], 'badinteger' ),
930 [ 'parseLimits' => false ],
932 'Limit with no max, supplied "max"' => [
933 'max',
935 ParamValidator::PARAM_TYPE => 'limit',
937 PHP_INT_MAX,
940 'Valid limit' => [
941 '100',
943 ParamValidator::PARAM_TYPE => 'limit',
944 IntegerDef::PARAM_MAX => 100,
945 IntegerDef::PARAM_MAX2 => 100,
947 100,
950 'Limit max' => [
951 'max',
953 ParamValidator::PARAM_TYPE => 'limit',
954 IntegerDef::PARAM_MAX => 100,
955 IntegerDef::PARAM_MAX2 => 101,
957 100,
960 'Limit max for apihighlimits' => [
961 'max',
963 ParamValidator::PARAM_TYPE => 'limit',
964 IntegerDef::PARAM_MAX => 100,
965 IntegerDef::PARAM_MAX2 => 101,
967 101,
969 [ 'apihighlimits' => true ],
971 'Limit too large' => [
972 '101',
974 ParamValidator::PARAM_TYPE => 'limit',
975 IntegerDef::PARAM_MAX => 100,
976 IntegerDef::PARAM_MAX2 => 101,
978 100,
980 'paramvalidator-outofrange-minmax',
981 Message::plaintextParam( 'myParam' ),
982 Message::plaintextParam( '101' ),
983 Message::numParam( 0 ),
984 Message::numParam( 100 ),
985 ] ],
987 'Limit okay for apihighlimits' => [
988 '101',
990 ParamValidator::PARAM_TYPE => 'limit',
991 IntegerDef::PARAM_MAX => 100,
992 IntegerDef::PARAM_MAX2 => 101,
994 101,
996 [ 'apihighlimits' => true ],
998 'Limit too large for apihighlimits (non-internal mode)' => [
999 '102',
1001 ParamValidator::PARAM_TYPE => 'limit',
1002 IntegerDef::PARAM_MAX => 100,
1003 IntegerDef::PARAM_MAX2 => 101,
1005 101,
1007 'paramvalidator-outofrange-minmax',
1008 Message::plaintextParam( 'myParam' ),
1009 Message::plaintextParam( '102' ),
1010 Message::numParam( 0 ),
1011 Message::numParam( 101 ),
1012 ] ],
1013 [ 'apihighlimits' => true ],
1015 'Limit too small' => [
1016 '-2',
1018 ParamValidator::PARAM_TYPE => 'limit',
1019 IntegerDef::PARAM_MIN => -1,
1020 IntegerDef::PARAM_MAX => 100,
1021 IntegerDef::PARAM_MAX2 => 100,
1025 'paramvalidator-outofrange-minmax',
1026 Message::plaintextParam( 'myParam' ),
1027 Message::plaintextParam( '-2' ),
1028 Message::numParam( -1 ),
1029 Message::numParam( 100 ),
1030 ] ],
1032 'Timestamp' => [
1033 wfTimestamp( TS_UNIX, '20211221122112' ),
1034 [ ParamValidator::PARAM_TYPE => 'timestamp' ],
1035 '20211221122112',
1038 'Timestamp 0' => [
1039 '0',
1040 [ ParamValidator::PARAM_TYPE => 'timestamp' ],
1041 // Magic keyword
1042 'now',
1044 'paramvalidator-unclearnowtimestamp',
1045 Message::plaintextParam( 'myParam' ),
1046 Message::plaintextParam( '0' ),
1047 ] ],
1049 'Timestamp empty' => [
1051 [ ParamValidator::PARAM_TYPE => 'timestamp' ],
1052 'now',
1054 'paramvalidator-unclearnowtimestamp',
1055 Message::plaintextParam( 'myParam' ),
1056 Message::plaintextParam( '' ),
1057 ] ],
1059 // wfTimestamp() interprets this as Unix time
1060 'Timestamp 00' => [
1061 '00',
1062 [ ParamValidator::PARAM_TYPE => 'timestamp' ],
1063 '19700101000000',
1066 'Timestamp now' => [
1067 'now',
1068 [ ParamValidator::PARAM_TYPE => 'timestamp' ],
1069 'now',
1072 'Invalid timestamp' => [
1073 'a potato',
1074 [ ParamValidator::PARAM_TYPE => 'timestamp' ],
1075 ApiUsageException::newWithMessage( null, [
1076 'paramvalidator-badtimestamp',
1077 Message::plaintextParam( 'myParam' ),
1078 Message::plaintextParam( 'a potato' ),
1079 ], 'badtimestamp' ),
1082 'Timestamp array' => [
1083 '100|101',
1085 ParamValidator::PARAM_TYPE => 'timestamp',
1086 ParamValidator::PARAM_ISMULTI => 1,
1088 [ wfTimestamp( TS_MW, 100 ), wfTimestamp( TS_MW, 101 ) ],
1091 'Expiry array' => [
1092 '99990123123456|8888-01-23 12:34:56|indefinite',
1094 ParamValidator::PARAM_TYPE => 'expiry',
1095 ParamValidator::PARAM_ISMULTI => 1,
1097 [ '9999-01-23T12:34:56Z', '8888-01-23T12:34:56Z', 'infinity' ],
1100 'User' => [
1101 'foo_bar',
1102 [ ParamValidator::PARAM_TYPE => 'user' ],
1103 'Foo bar',
1106 'User prefixed with "User:"' => [
1107 'User:foo_bar',
1108 [ ParamValidator::PARAM_TYPE => 'user' ],
1109 'Foo bar',
1112 'Invalid username "|"' => [
1113 '|',
1114 [ ParamValidator::PARAM_TYPE => 'user' ],
1115 ApiUsageException::newWithMessage( null, [
1116 'paramvalidator-baduser',
1117 Message::plaintextParam( 'myParam' ),
1118 Message::plaintextParam( '|' ),
1119 ], 'baduser' ),
1122 'Invalid username "300.300.300.300"' => [
1123 '300.300.300.300',
1124 [ ParamValidator::PARAM_TYPE => 'user' ],
1125 ApiUsageException::newWithMessage( null, [
1126 'paramvalidator-baduser',
1127 Message::plaintextParam( 'myParam' ),
1128 Message::plaintextParam( '300.300.300.300' ),
1129 ], 'baduser' ),
1132 'IP range as username' => [
1133 '10.0.0.0/8',
1134 [ ParamValidator::PARAM_TYPE => 'user' ],
1135 '10.0.0.0/8',
1138 'IPv6 as username' => [
1139 '::1',
1140 [ ParamValidator::PARAM_TYPE => 'user' ],
1141 '0:0:0:0:0:0:0:1',
1144 'Obsolete cloaked usemod IP address as username' => [
1145 '1.2.3.xxx',
1146 [ ParamValidator::PARAM_TYPE => 'user' ],
1147 '1.2.3.xxx',
1150 'Invalid username containing IP address' => [
1151 'This is [not] valid 1.2.3.xxx, ha!',
1152 [ ParamValidator::PARAM_TYPE => 'user' ],
1153 ApiUsageException::newWithMessage( null, [
1154 'paramvalidator-baduser',
1155 Message::plaintextParam( 'myParam' ),
1156 Message::plaintextParam( 'This is [not] valid 1.2.3.xxx, ha!' ),
1157 ], 'baduser' ),
1160 'External username' => [
1161 'M>Foo bar',
1162 [ ParamValidator::PARAM_TYPE => 'user' ],
1163 'M>Foo bar',
1166 'Array of usernames' => [
1167 'foo|bar',
1169 ParamValidator::PARAM_TYPE => 'user',
1170 ParamValidator::PARAM_ISMULTI => true,
1172 [ 'Foo', 'Bar' ],
1175 'tag' => [
1176 'tag1',
1177 [ ParamValidator::PARAM_TYPE => 'tags' ],
1178 [ 'tag1' ],
1181 'Array of one tag' => [
1182 'tag1',
1184 ParamValidator::PARAM_TYPE => 'tags',
1185 ParamValidator::PARAM_ISMULTI => true,
1187 [ 'tag1' ],
1190 'Array of tags' => [
1191 'tag1|tag2',
1193 ParamValidator::PARAM_TYPE => 'tags',
1194 ParamValidator::PARAM_ISMULTI => true,
1196 [ 'tag1', 'tag2' ],
1199 'Invalid tag' => [
1200 'invalid tag',
1201 [ ParamValidator::PARAM_TYPE => 'tags' ],
1202 ApiUsageException::newWithMessage(
1203 null,
1204 [ 'tags-apply-not-allowed-one', 'invalid tag', 1 ],
1205 'badtags',
1206 [ 'disallowedtags' => [ 'invalid tag' ] ]
1210 'Unrecognized type' => [
1211 'foo',
1212 [ ParamValidator::PARAM_TYPE => 'nonexistenttype' ],
1213 new DomainException( "Param myParam's type is unknown - nonexistenttype" ),
1216 'Too many bytes' => [
1217 '1',
1219 StringDef::PARAM_MAX_BYTES => 0,
1220 StringDef::PARAM_MAX_CHARS => 0,
1222 ApiUsageException::newWithMessage( null, [
1223 'paramvalidator-maxbytes',
1224 Message::plaintextParam( 'myParam' ),
1225 Message::plaintextParam( '1' ),
1226 Message::numParam( 0 ),
1227 Message::numParam( 1 ),
1228 ], 'maxbytes', [ 'maxbytes' => 0, 'maxchars' => 0 ] ),
1231 'Too many chars' => [
1232 '§§',
1234 StringDef::PARAM_MAX_BYTES => 4,
1235 StringDef::PARAM_MAX_CHARS => 1,
1237 ApiUsageException::newWithMessage( null, [
1238 'paramvalidator-maxchars',
1239 Message::plaintextParam( 'myParam' ),
1240 Message::plaintextParam( '§§' ),
1241 Message::numParam( 1 ),
1242 Message::numParam( 2 ),
1243 ], 'maxchars', [ 'maxbytes' => 4, 'maxchars' => 1 ] ),
1246 'Omitted required param' => [
1247 null,
1248 [ ParamValidator::PARAM_REQUIRED => true ],
1249 ApiUsageException::newWithMessage( null, [
1250 'paramvalidator-missingparam',
1251 Message::plaintextParam( 'myParam' )
1252 ], 'missingparam' ),
1255 'Empty multi-value' => [
1257 [ ParamValidator::PARAM_ISMULTI => true ],
1261 'Multi-value \x1f' => [
1262 "\x1f",
1263 [ ParamValidator::PARAM_ISMULTI => true ],
1267 'Allowed non-multi-value with "|"' => [
1268 'a|b',
1269 [ ParamValidator::PARAM_TYPE => [ 'a|b' ] ],
1270 'a|b',
1273 'Prohibited multi-value' => [
1274 'a|b',
1275 [ ParamValidator::PARAM_TYPE => [ 'a', 'b' ] ],
1276 ApiUsageException::newWithMessage( null, [
1277 'paramvalidator-badvalue-enumnotmulti',
1278 Message::plaintextParam( 'myParam' ),
1279 Message::plaintextParam( 'a|b' ),
1280 Message::listParam( [ Message::plaintextParam( 'a' ), Message::plaintextParam( 'b' ) ] ),
1281 Message::numParam( 2 ),
1282 ], 'badvalue' ),
1287 $integerTests = [
1288 [ '+1', 1 ],
1289 [ '-1', -1 ],
1290 [ '1.5', null ],
1291 [ '-1.5', null ],
1292 [ '1abc', null ],
1293 [ ' 1', null ],
1294 [ "\t1", null, '\t1' ],
1295 [ "\r1", null, '\r1' ],
1296 [ "\f1", null, '\f1', 'badutf-8' ],
1297 [ "\n1", null, '\n1' ],
1298 [ "\v1", null, '\v1', 'badutf-8' ],
1299 [ "\e1", null, '\e1', 'badutf-8' ],
1300 [ "\x001", null, '\x001', 'badutf-8' ],
1303 foreach ( $integerTests as $test ) {
1304 $desc = $test[2] ?? $test[0];
1305 $warnings = isset( $test[3] ) ?
1306 [ [ 'apiwarn-badutf8', 'myParam' ] ] : [];
1307 $returnArray["\"$desc\" as integer"] = [
1308 $test[0],
1309 [ ParamValidator::PARAM_TYPE => 'integer' ],
1310 $test[1] ?? ApiUsageException::newWithMessage( null, [
1311 'paramvalidator-badinteger',
1312 Message::plaintextParam( 'myParam' ),
1313 Message::plaintextParam( preg_replace( "/[\f\v\e\\0]/", '�', $test[0] ) ),
1314 ], 'badinteger' ),
1315 $warnings,
1319 return $returnArray;
1323 * @dataProvider provideGetFinalParamDescription
1325 public function testGetFinalParamDescription( $paramSettings, $expectedMessages ) {
1326 $mock = $this->getMockBuilder( MockApi::class )
1327 ->onlyMethods( [ 'getAllowedParams', 'getModulePath' ] )
1328 ->getMock();
1329 $mock->method( 'getAllowedParams' )->willReturn( [
1330 'param' => $paramSettings,
1331 ] );
1332 $mock->method( 'getModulePath' )->willReturn( 'test' );
1333 if ( $expectedMessages instanceof Exception ) {
1334 $this->expectExceptionObject( $expectedMessages );
1336 $paramDescription = $mock->getFinalParamDescription();
1337 $this->assertArrayHasKey( 'param', $paramDescription );
1338 $messages = $paramDescription['param'];
1339 $messageKeys = array_map( static fn ( MessageSpecifier $m ) => $m->getKey(), $messages );
1340 $this->assertSame( $expectedMessages, $messageKeys );
1343 public static function provideGetFinalParamDescription() {
1344 return [
1345 'default message' => [
1346 'settings' => [],
1347 'messages' => [ 'apihelp-test-param-param' ],
1349 'custom message' => [
1350 'settings' => [ ApiBase::PARAM_HELP_MSG => 'foo' ],
1351 'messages' => [ 'foo' ],
1353 'default per-value message' => [
1354 'settings' => [
1355 ParamValidator::PARAM_TYPE => [ 'a', 'b' ],
1356 ApiBase::PARAM_HELP_MSG_PER_VALUE => [],
1358 'messages' => [
1359 'apihelp-test-param-param',
1360 'apihelp-test-paramvalue-param-a',
1361 'apihelp-test-paramvalue-param-b',
1364 'custom per-value message' => [
1365 'settings' => [
1366 ParamValidator::PARAM_TYPE => [ 'a', 'b' ],
1367 ApiBase::PARAM_HELP_MSG_PER_VALUE => [
1368 'a' => 'foo',
1369 'b' => 'bar',
1372 'messages' => [
1373 'apihelp-test-param-param',
1374 'foo',
1375 'bar',
1378 'custom per-value message for strings' => [
1379 'settings' => [
1380 ParamValidator::PARAM_TYPE => 'string',
1381 ParamValidator::PARAM_ISMULTI => true,
1382 ApiBase::PARAM_HELP_MSG_PER_VALUE => [
1383 'a' => 'foo',
1384 'b' => 'bar',
1387 'messages' => [
1388 'apihelp-test-param-param',
1389 'foo',
1390 'bar',
1393 'must be multi-valued for per-value message' => [
1394 'settings' => [
1395 ParamValidator::PARAM_TYPE => 'string',
1396 ApiBase::PARAM_HELP_MSG_PER_VALUE => [],
1398 'messages' => new MWException(
1399 'Internal error in ' . ApiBase::class . '::getFinalParamDescription: '
1400 . 'ApiBase::PARAM_HELP_MSG_PER_VALUE may only be used when '
1401 . "ParamValidator::PARAM_TYPE is an array or it is 'string' "
1402 . 'and ParamValidator::PARAM_ISMULTI is true'
1408 public function testAddBlockInfoToStatus() {
1409 $mock = new MockApi();
1411 $msg = new Message( 'mainpage' );
1413 // Check empty array
1414 $expect = Status::newGood();
1415 $test = Status::newGood();
1416 $mock->addBlockInfoToStatus( $test );
1417 $this->assertEquals( $expect, $test );
1419 // No blocked $user, so no special block handling
1420 $expect = Status::newGood();
1421 $expect->fatal( 'blockedtext' );
1422 $expect->fatal( 'autoblockedtext' );
1423 $expect->fatal( 'systemblockedtext' );
1424 $expect->fatal( 'mainpage' );
1425 $expect->fatal( $msg );
1426 $expect->fatal( 'parentheses', 'foobar' );
1427 $test = clone $expect;
1428 $mock->addBlockInfoToStatus( $test );
1429 $this->assertEquals( $expect, $test );
1431 // Has a blocked $user, so special block handling
1432 $user = $this->getMutableTestUser()->getUser();
1433 $block = new DatabaseBlock( [
1434 'address' => $user,
1435 'by' => $this->getTestSysop()->getUser(),
1436 'reason' => __METHOD__,
1437 'expiry' => time() + 100500,
1438 ] );
1439 $this->getServiceContainer()->getDatabaseBlockStore()->insertBlock( $block );
1441 $mockTrait = $this->getMockForTrait( ApiBlockInfoTrait::class );
1442 $language = $this->getServiceContainer()->getLanguageFactory()->getLanguage( 'en' );
1443 $mockTrait->method( 'getLanguage' )->willReturn( $language );
1444 $userInfoTrait = TestingAccessWrapper::newFromObject( $mockTrait );
1445 $blockinfo = [ 'blockinfo' => $userInfoTrait->getBlockDetails( $block ) ];
1447 $expect = Status::newGood();
1448 $expect->fatal( ApiMessage::create( 'blockedtext', 'blocked', $blockinfo ) );
1449 // This would normally use the 'autoblocked' code, but the codes are computed from $blockinfo
1450 // now rather than the message, and we're not faking it well enough
1451 $expect->fatal( ApiMessage::create( 'autoblockedtext', 'blocked', $blockinfo ) );
1452 $expect->fatal( ApiMessage::create( 'systemblockedtext', 'blocked', $blockinfo ) );
1453 $expect->fatal( 'mainpage' );
1454 $expect->fatal( $msg );
1455 $expect->fatal( 'parentheses', 'foobar' );
1456 $test = Status::newGood();
1457 $test->fatal( 'blockedtext' );
1458 $test->fatal( 'autoblockedtext' );
1459 $test->fatal( 'systemblockedtext' );
1460 $test->fatal( 'mainpage' );
1461 $test->fatal( $msg );
1462 $test->fatal( 'parentheses', 'foobar' );
1463 $mock->addBlockInfoToStatus( $test, $user );
1464 $this->assertEquals( $expect, $test );
1467 public static function provideDieStatus() {
1468 $status = StatusValue::newGood();
1469 $status->error( 'foo' );
1470 $status->warning( 'bar' );
1471 yield [ $status, [ 'foo' => true, 'bar' => false ] ];
1473 $status = StatusValue::newGood();
1474 $status->warning( 'foo' );
1475 $status->warning( 'bar' );
1476 yield [ $status, [ 'foo' => true, 'bar' => true ] ];
1478 $status = StatusValue::newGood();
1479 $status->setOK( false );
1480 yield [ $status, [ 'unknownerror-nocode' => true ] ];
1482 $status = PermissionStatus::newEmpty();
1483 $status->setRateLimitExceeded();
1484 yield [ $status, [ 'ratelimited' => true ] ];
1486 $status = StatusValue::newFatal( 'actionthrottledtext' );
1487 yield [ $status, [ 'ratelimited' => true ] ];
1489 $status = StatusValue::newFatal( 'actionthrottled' );
1490 yield [ $status, [ 'ratelimited' => true ] ];
1492 $status = StatusValue::newFatal( 'blockedtext' );
1493 yield [ $status, [ 'blocked' => true ] ];
1495 $status = StatusValue::newFatal( 'autoblockedtext' );
1496 yield [ $status, [ 'autoblocked' => true ] ];
1500 * @dataProvider provideDieStatus
1502 * @param StatusValue $status
1503 * @param array $expected
1505 public function testDieStatus( $status, $expected ) {
1506 $mock = new MockApi();
1508 try {
1509 $mock->dieStatus( $status );
1510 $this->fail( 'Expected exception not thrown' );
1511 } catch ( ApiUsageException $ex ) {
1512 foreach ( $expected as $key => $has ) {
1513 $this->assertSame( $has, ApiTestCase::apiExceptionHasCode( $ex, $key ), "Exception has '$key'" );
1519 * @covers \MediaWiki\Api\ApiBase::extractRequestParams
1521 public function testExtractRequestParams() {
1522 $request = new FauxRequest( [
1523 'xxexists' => 'exists!',
1524 'xxmulti' => 'a|b|c|d|{bad}',
1525 'xxempty' => '',
1526 'xxtemplate-a' => 'A!',
1527 'xxtemplate-b' => 'B1|B2|B3',
1528 'xxtemplate-c' => '',
1529 'xxrecursivetemplate-b-B1' => 'X',
1530 'xxrecursivetemplate-b-B3' => 'Y',
1531 'xxrecursivetemplate-b-B4' => '?',
1532 'xxemptytemplate-' => 'nope',
1533 'foo' => 'a|b|c',
1534 'xxfoo' => 'a|b|c',
1535 'errorformat' => 'raw',
1536 ] );
1537 $context = new DerivativeContext( RequestContext::getMain() );
1538 $context->setRequest( $request );
1539 $main = new ApiMain( $context );
1541 $mock = $this->getMockBuilder( ApiBase::class )
1542 ->setConstructorArgs( [ $main, 'test', 'xx' ] )
1543 ->onlyMethods( [ 'getAllowedParams' ] )
1544 ->getMockForAbstractClass();
1545 $mock->method( 'getAllowedParams' )->willReturn( [
1546 'notexists' => null,
1547 'exists' => null,
1548 'multi' => [
1549 ParamValidator::PARAM_ISMULTI => true,
1551 'empty' => [
1552 ParamValidator::PARAM_ISMULTI => true,
1554 'template-{m}' => [
1555 ParamValidator::PARAM_ISMULTI => true,
1556 ApiBase::PARAM_TEMPLATE_VARS => [ 'm' => 'multi' ],
1558 'recursivetemplate-{m}-{t}' => [
1559 ApiBase::PARAM_TEMPLATE_VARS => [ 't' => 'template-{m}', 'm' => 'multi' ],
1561 'emptytemplate-{m}' => [
1562 ParamValidator::PARAM_ISMULTI => true,
1563 ApiBase::PARAM_TEMPLATE_VARS => [ 'm' => 'empty' ],
1565 'badtemplate-{e}' => [
1566 ApiBase::PARAM_TEMPLATE_VARS => [ 'e' => 'exists' ],
1568 'badtemplate2-{e}' => [
1569 ApiBase::PARAM_TEMPLATE_VARS => [ 'e' => 'badtemplate2-{e}' ],
1571 'badtemplate3-{x}' => [
1572 ApiBase::PARAM_TEMPLATE_VARS => [ 'x' => 'foo' ],
1574 ] );
1576 $this->assertEquals( [
1577 'notexists' => null,
1578 'exists' => 'exists!',
1579 'multi' => [ 'a', 'b', 'c', 'd', '{bad}' ],
1580 'empty' => [],
1581 'template-a' => [ 'A!' ],
1582 'template-b' => [ 'B1', 'B2', 'B3' ],
1583 'template-c' => [],
1584 'template-d' => null,
1585 'recursivetemplate-a-A!' => null,
1586 'recursivetemplate-b-B1' => 'X',
1587 'recursivetemplate-b-B2' => null,
1588 'recursivetemplate-b-B3' => 'Y',
1589 ], $mock->extractRequestParams() );
1591 $used = TestingAccessWrapper::newFromObject( $main )->getParamsUsed();
1592 sort( $used );
1593 $this->assertEquals( [
1594 'xxempty',
1595 'xxexists',
1596 'xxmulti',
1597 'xxnotexists',
1598 'xxrecursivetemplate-a-A!',
1599 'xxrecursivetemplate-b-B1',
1600 'xxrecursivetemplate-b-B2',
1601 'xxrecursivetemplate-b-B3',
1602 'xxtemplate-a',
1603 'xxtemplate-b',
1604 'xxtemplate-c',
1605 'xxtemplate-d',
1606 ], $used );
1608 $warnings = $mock->getResult()->getResultData( 'warnings', [ 'Strip' => 'all' ] );
1609 $this->assertCount( 1, $warnings );
1610 $this->assertSame( 'ignoring-invalid-templated-value', $warnings[0]['code'] );