3 use Wikimedia\TestingAccessWrapper
;
12 class ApiBaseTest
extends ApiTestCase
{
14 * This covers a variety of stub methods that return a fixed value.
16 * @param string|array $method Name of method, or [ name, params... ]
17 * @param string $value Expected value
19 * @dataProvider provideStubMethods
21 public function testStubMethods( $expected, $method, $args = [] ) {
22 // Some of these are protected
23 $mock = TestingAccessWrapper
::newFromObject( new MockApi() );
24 $result = call_user_func_array( [ $mock, $method ], $args );
25 $this->assertSame( $expected, $result );
28 public function provideStubMethods() {
30 [ null, 'getModuleManager' ],
31 [ null, 'getCustomPrinter' ],
32 [ [], 'getHelpUrls' ],
33 // @todo This is actually overriden by MockApi
34 // [ [], 'getAllowedParams' ],
35 [ true, 'shouldCheckMaxLag' ],
36 [ true, 'isReadMode' ],
37 [ false, 'isWriteMode' ],
38 [ false, 'mustBePosted' ],
39 [ false, 'isDeprecated' ],
40 [ false, 'isInternal' ],
41 [ false, 'needsToken' ],
42 [ null, 'getWebUITokenSalt', [ [] ] ],
43 [ null, 'getConditionalRequestData', [ 'etag' ] ],
44 [ null, 'dynamicParameterDocumentation' ],
48 public function testRequireOnlyOneParameterDefault() {
49 $mock = new MockApi();
50 $mock->requireOnlyOneParameter(
51 [ "filename" => "foo.txt", "enablechunks" => false ],
52 "filename", "enablechunks"
54 $this->assertTrue( true );
58 * @expectedException ApiUsageException
60 public function testRequireOnlyOneParameterZero() {
61 $mock = new MockApi();
62 $mock->requireOnlyOneParameter(
63 [ "filename" => "foo.txt", "enablechunks" => 0 ],
64 "filename", "enablechunks"
69 * @expectedException ApiUsageException
71 public function testRequireOnlyOneParameterTrue() {
72 $mock = new MockApi();
73 $mock->requireOnlyOneParameter(
74 [ "filename" => "foo.txt", "enablechunks" => true ],
75 "filename", "enablechunks"
79 public function testRequireOnlyOneParameterMissing() {
80 $this->setExpectedException( ApiUsageException
::class,
81 'One of the parameters "foo" and "bar" is required.' );
82 $mock = new MockApi();
83 $mock->requireOnlyOneParameter(
84 [ "filename" => "foo.txt", "enablechunks" => false ],
88 public function testRequireMaxOneParameterZero() {
89 $mock = new MockApi();
90 $mock->requireMaxOneParameter(
91 [ 'foo' => 'bar', 'baz' => 'quz' ],
93 $this->assertTrue( true );
96 public function testRequireMaxOneParameterOne() {
97 $mock = new MockApi();
98 $mock->requireMaxOneParameter(
99 [ 'foo' => 'bar', 'baz' => 'quz' ],
101 $this->assertTrue( true );
104 public function testRequireMaxOneParameterTwo() {
105 $this->setExpectedException( ApiUsageException
::class,
106 'The parameters "foo" and "baz" can not be used together.' );
107 $mock = new MockApi();
108 $mock->requireMaxOneParameter(
109 [ 'foo' => 'bar', 'baz' => 'quz' ],
113 public function testRequireAtLeastOneParameterZero() {
114 $this->setExpectedException( ApiUsageException
::class,
115 'At least one of the parameters "foo" and "bar" is required.' );
116 $mock = new MockApi();
117 $mock->requireAtLeastOneParameter(
118 [ 'a' => 'b', 'c' => 'd' ],
122 public function testRequireAtLeastOneParameterOne() {
123 $mock = new MockApi();
124 $mock->requireAtLeastOneParameter(
125 [ 'a' => 'b', 'c' => 'd' ],
127 $this->assertTrue( true );
130 public function testRequireAtLeastOneParameterTwo() {
131 $mock = new MockApi();
132 $mock->requireAtLeastOneParameter(
133 [ 'a' => 'b', 'c' => 'd' ],
135 $this->assertTrue( true );
138 public function testGetTitleOrPageIdBadParams() {
139 $this->setExpectedException( ApiUsageException
::class,
140 'The parameters "title" and "pageid" can not be used together.' );
141 $mock = new MockApi();
142 $mock->getTitleOrPageId( [ 'title' => 'a', 'pageid' => 7 ] );
145 public function testGetTitleOrPageIdTitle() {
146 $mock = new MockApi();
147 $result = $mock->getTitleOrPageId( [ 'title' => 'Foo' ] );
148 $this->assertInstanceOf( WikiPage
::class, $result );
149 $this->assertSame( 'Foo', $result->getTitle()->getPrefixedText() );
152 public function testGetTitleOrPageIdInvalidTitle() {
153 $this->setExpectedException( ApiUsageException
::class,
155 $mock = new MockApi();
156 $mock->getTitleOrPageId( [ 'title' => '|' ] );
159 public function testGetTitleOrPageIdSpecialTitle() {
160 $this->setExpectedException( ApiUsageException
::class,
161 "Namespace doesn't allow actual pages." );
162 $mock = new MockApi();
163 $mock->getTitleOrPageId( [ 'title' => 'Special:RandomPage' ] );
166 public function testGetTitleOrPageIdPageId() {
167 $result = ( new MockApi() )->getTitleOrPageId(
168 [ 'pageid' => Title
::newFromText( 'UTPage' )->getArticleId() ] );
169 $this->assertInstanceOf( WikiPage
::class, $result );
170 $this->assertSame( 'UTPage', $result->getTitle()->getPrefixedText() );
173 public function testGetTitleOrPageIdInvalidPageId() {
174 $this->setExpectedException( ApiUsageException
::class,
175 'There is no page with ID 2147483648.' );
176 $mock = new MockApi();
177 $mock->getTitleOrPageId( [ 'pageid' => 2147483648 ] );
180 public function testGetTitleFromTitleOrPageIdBadParams() {
181 $this->setExpectedException( ApiUsageException
::class,
182 'The parameters "title" and "pageid" can not be used together.' );
183 $mock = new MockApi();
184 $mock->getTitleFromTitleOrPageId( [ 'title' => 'a', 'pageid' => 7 ] );
187 public function testGetTitleFromTitleOrPageIdTitle() {
188 $mock = new MockApi();
189 $result = $mock->getTitleFromTitleOrPageId( [ 'title' => 'Foo' ] );
190 $this->assertInstanceOf( Title
::class, $result );
191 $this->assertSame( 'Foo', $result->getPrefixedText() );
194 public function testGetTitleFromTitleOrPageIdInvalidTitle() {
195 $this->setExpectedException( ApiUsageException
::class,
197 $mock = new MockApi();
198 $mock->getTitleFromTitleOrPageId( [ 'title' => '|' ] );
201 public function testGetTitleFromTitleOrPageIdPageId() {
202 $result = ( new MockApi() )->getTitleFromTitleOrPageId(
203 [ 'pageid' => Title
::newFromText( 'UTPage' )->getArticleId() ] );
204 $this->assertInstanceOf( Title
::class, $result );
205 $this->assertSame( 'UTPage', $result->getPrefixedText() );
208 public function testGetTitleFromTitleOrPageIdInvalidPageId() {
209 $this->setExpectedException( ApiUsageException
::class,
210 'There is no page with ID 298401643.' );
211 $mock = new MockApi();
212 $mock->getTitleFromTitleOrPageId( [ 'pageid' => 298401643 ] );
216 * @dataProvider provideGetParameterFromSettings
217 * @param string|null $input
218 * @param array $paramSettings
219 * @param mixed $expected
220 * @param array $options Key-value pairs:
221 * 'parseLimits': true|false
222 * 'apihighlimits': true|false
223 * 'internalmode': true|false
224 * @param string[] $warnings
226 public function testGetParameterFromSettings(
227 $input, $paramSettings, $expected, $warnings, $options = []
229 $mock = new MockApi();
230 $wrapper = TestingAccessWrapper
::newFromObject( $mock );
232 $context = new DerivativeContext( $mock );
233 $context->setRequest( new FauxRequest(
234 $input !== null ?
[ 'myParam' => $input ] : [] ) );
235 $wrapper->mMainModule
= new ApiMain( $context );
237 $parseLimits = isset( $options['parseLimits'] ) ?
238 $options['parseLimits'] : true;
240 if ( !empty( $options['apihighlimits'] ) ) {
241 $context->setUser( self
::$users['sysop']->getUser() );
244 if ( isset( $options['internalmode'] ) && !$options['internalmode'] ) {
245 $mainWrapper = TestingAccessWrapper
::newFromObject( $wrapper->mMainModule
);
246 $mainWrapper->mInternalMode
= false;
249 // If we're testing tags, set up some tags
250 if ( isset( $paramSettings[ApiBase
::PARAM_TYPE
] ) &&
251 $paramSettings[ApiBase
::PARAM_TYPE
] === 'tags'
253 ChangeTags
::defineTag( 'tag1' );
254 ChangeTags
::defineTag( 'tag2' );
257 if ( $expected instanceof Exception
) {
259 $wrapper->getParameterFromSettings( 'myParam', $paramSettings,
261 $this->fail( 'No exception thrown' );
262 } catch ( Exception
$ex ) {
263 $this->assertEquals( $expected, $ex );
266 $result = $wrapper->getParameterFromSettings( 'myParam',
267 $paramSettings, $parseLimits );
268 if ( isset( $paramSettings[ApiBase
::PARAM_TYPE
] ) &&
269 $paramSettings[ApiBase
::PARAM_TYPE
] === 'timestamp' &&
272 // Allow one second of fuzziness. Make sure the formats are
274 $this->assertRegExp( '/^\d{14}$/', $result );
275 $this->assertLessThanOrEqual( 1,
276 abs( wfTimestamp( TS_UNIX
, $result ) - time() ),
277 "Result $result differs from expected $expected by " .
278 'more than one second' );
280 $this->assertSame( $expected, $result );
282 $actualWarnings = array_map( function ( $warn ) {
283 return $warn instanceof Message
284 ?
array_merge( [ $warn->getKey() ], $warn->getParams() )
286 }, $mock->warnings
);
287 $this->assertSame( $warnings, $actualWarnings );
290 if ( !empty( $paramSettings[ApiBase
::PARAM_SENSITIVE
] ) ||
291 ( isset( $paramSettings[ApiBase
::PARAM_TYPE
] ) &&
292 $paramSettings[ApiBase
::PARAM_TYPE
] === 'password' )
294 $mainWrapper = TestingAccessWrapper
::newFromObject( $wrapper->getMain() );
295 $this->assertSame( [ 'myParam' ],
296 $mainWrapper->getSensitiveParams() );
300 public static function provideGetParameterFromSettings() {
302 [ 'apiwarn-badutf8', 'myParam' ],
307 for ( $i = 0; $i < 32; $i++
) {
309 $enc .= ( $i === 9 ||
$i === 10 ||
$i === 13 )
315 'Basic param' => [ 'bar', null, 'bar', [] ],
316 'Basic param, C0 controls' => [ $c0, null, $enc, $warnings ],
317 'String param' => [ 'bar', '', 'bar', [] ],
318 'String param, defaulted' => [ null, '', '', [] ],
319 'String param, empty' => [ '', 'default', '', [] ],
320 'String param, required, empty' => [
322 [ ApiBase
::PARAM_DFLT
=> 'default', ApiBase
::PARAM_REQUIRED
=> true ],
323 ApiUsageException
::newWithMessage( null,
324 [ 'apierror-missingparam', 'myParam' ] ),
327 'Multi-valued parameter' => [
329 [ ApiBase
::PARAM_ISMULTI
=> true ],
333 'Multi-valued parameter, alternative separator' => [
335 [ ApiBase
::PARAM_ISMULTI
=> true ],
339 'Multi-valued parameter, other C0 controls' => [
341 [ ApiBase
::PARAM_ISMULTI
=> true ],
345 'Multi-valued parameter, other C0 controls (2)' => [
347 [ ApiBase
::PARAM_ISMULTI
=> true ],
348 [ substr( $enc, 0, -3 ), '' ],
351 'Multi-valued parameter with limits' => [
354 ApiBase
::PARAM_ISMULTI
=> true,
355 ApiBase
::PARAM_ISMULTI_LIMIT1
=> 3,
360 'Multi-valued parameter with exceeded limits' => [
363 ApiBase
::PARAM_ISMULTI
=> true,
364 ApiBase
::PARAM_ISMULTI_LIMIT1
=> 2,
367 [ [ 'apiwarn-toomanyvalues', 'myParam', 2 ] ],
369 'Multi-valued parameter with exceeded limits for non-bot' => [
372 ApiBase
::PARAM_ISMULTI
=> true,
373 ApiBase
::PARAM_ISMULTI_LIMIT1
=> 2,
374 ApiBase
::PARAM_ISMULTI_LIMIT2
=> 3,
377 [ [ 'apiwarn-toomanyvalues', 'myParam', 2 ] ],
379 'Multi-valued parameter with non-exceeded limits for bot' => [
382 ApiBase
::PARAM_ISMULTI
=> true,
383 ApiBase
::PARAM_ISMULTI_LIMIT1
=> 2,
384 ApiBase
::PARAM_ISMULTI_LIMIT2
=> 3,
388 [ 'apihighlimits' => true ],
390 'Multi-valued parameter with prohibited duplicates' => [
392 [ ApiBase
::PARAM_ISMULTI
=> true ],
393 // Note that the keys are not sequential! This matches
394 // array_unique, but might be unexpected.
395 [ 0 => 'a', 1 => 'b', 3 => 'c' ],
398 'Multi-valued parameter with allowed duplicates' => [
401 ApiBase
::PARAM_ISMULTI
=> true,
402 ApiBase
::PARAM_ALLOW_DUPLICATES
=> true,
407 'Empty boolean param' => [
409 [ ApiBase
::PARAM_TYPE
=> 'boolean' ],
413 'Boolean param 0' => [
415 [ ApiBase
::PARAM_TYPE
=> 'boolean' ],
419 'Boolean param false' => [
421 [ ApiBase
::PARAM_TYPE
=> 'boolean' ],
425 'Boolean multi-param' => [
428 ApiBase
::PARAM_TYPE
=> 'boolean',
429 ApiBase
::PARAM_ISMULTI
=> true,
432 'Internal error in ApiBase::getParameterFromSettings: ' .
433 'Multi-values not supported for myParam'
437 'Empty boolean param with non-false default' => [
440 ApiBase
::PARAM_TYPE
=> 'boolean',
441 ApiBase
::PARAM_DFLT
=> true,
444 'Internal error in ApiBase::getParameterFromSettings: ' .
445 "Boolean param myParam's default is set to '1'. " .
446 'Boolean parameters must default to false.' ),
449 'Deprecated parameter' => [
451 [ ApiBase
::PARAM_DEPRECATED
=> true ],
453 [ [ 'apiwarn-deprecation-parameter', 'myParam' ] ],
455 'Deprecated parameter value' => [
457 [ ApiBase
::PARAM_DEPRECATED_VALUES
=> [ 'a' => true ] ],
459 [ [ 'apiwarn-deprecation-parameter', 'myParam=a' ] ],
461 'Multiple deprecated parameter values' => [
463 [ ApiBase
::PARAM_DEPRECATED_VALUES
=>
464 [ 'b' => true, 'd' => true ],
465 ApiBase
::PARAM_ISMULTI
=> true ],
466 [ 'a', 'b', 'c', 'd' ],
468 [ 'apiwarn-deprecation-parameter', 'myParam=b' ],
469 [ 'apiwarn-deprecation-parameter', 'myParam=d' ],
472 'Deprecated parameter value with custom warning' => [
474 [ ApiBase
::PARAM_DEPRECATED_VALUES
=> [ 'a' => 'my-msg' ] ],
478 '"*" when wildcard not allowed' => [
480 [ ApiBase
::PARAM_ISMULTI
=> true,
481 ApiBase
::PARAM_TYPE
=> [ 'a', 'b', 'c' ] ],
483 [ [ 'apiwarn-unrecognizedvalues', 'myParam',
484 [ 'list' => [ '*' ], 'type' => 'comma' ], 1 ] ],
489 ApiBase
::PARAM_ISMULTI
=> true,
490 ApiBase
::PARAM_TYPE
=> [ 'a', 'b', 'c' ],
491 ApiBase
::PARAM_ALL
=> true,
496 'Wildcard "*" with multiples not allowed' => [
499 ApiBase
::PARAM_TYPE
=> [ 'a', 'b', 'c' ],
500 ApiBase
::PARAM_ALL
=> true,
502 ApiUsageException
::newWithMessage( null,
503 [ 'apierror-unrecognizedvalue', 'myParam', '*' ],
507 'Wildcard "*" with unrestricted type' => [
510 ApiBase
::PARAM_ISMULTI
=> true,
511 ApiBase
::PARAM_ALL
=> true,
519 ApiBase
::PARAM_ISMULTI
=> true,
520 ApiBase
::PARAM_TYPE
=> [ 'a', 'b', 'c' ],
521 ApiBase
::PARAM_ALL
=> 'x',
526 'Wildcard conflicting with allowed value' => [
529 ApiBase
::PARAM_ISMULTI
=> true,
530 ApiBase
::PARAM_TYPE
=> [ 'a', 'b', 'c' ],
531 ApiBase
::PARAM_ALL
=> 'a',
534 'Internal error in ApiBase::getParameterFromSettings: ' .
535 'For param myParam, PARAM_ALL collides with a possible ' .
539 'Namespace with wildcard' => [
542 ApiBase
::PARAM_ISMULTI
=> true,
543 ApiBase
::PARAM_TYPE
=> 'namespace',
545 MWNamespace
::getValidNamespaces(),
548 // PARAM_ALL is ignored with namespace types.
549 'Namespace with wildcard suppressed' => [
552 ApiBase
::PARAM_ISMULTI
=> true,
553 ApiBase
::PARAM_TYPE
=> 'namespace',
554 ApiBase
::PARAM_ALL
=> false,
556 MWNamespace
::getValidNamespaces(),
559 'Namespace with wildcard "x"' => [
562 ApiBase
::PARAM_ISMULTI
=> true,
563 ApiBase
::PARAM_TYPE
=> 'namespace',
564 ApiBase
::PARAM_ALL
=> 'x',
567 [ [ 'apiwarn-unrecognizedvalues', 'myParam',
568 [ 'list' => [ 'x' ], 'type' => 'comma' ], 1 ] ],
571 'dDy+G?e?txnr.1:(@[Ru',
572 [ ApiBase
::PARAM_TYPE
=> 'password' ],
573 'dDy+G?e?txnr.1:(@[Ru',
576 'Sensitive field' => [
577 'I am fond of pineapples',
578 [ ApiBase
::PARAM_SENSITIVE
=> true ],
579 'I am fond of pineapples',
582 'Upload with default' => [
585 ApiBase
::PARAM_TYPE
=> 'upload',
586 ApiBase
::PARAM_DFLT
=> '',
589 'Internal error in ApiBase::getParameterFromSettings: ' .
590 "File upload param myParam's default is set to ''. " .
591 'File upload parameters may not have a default.' ),
594 'Multiple upload' => [
597 ApiBase
::PARAM_TYPE
=> 'upload',
598 ApiBase
::PARAM_ISMULTI
=> true,
601 'Internal error in ApiBase::getParameterFromSettings: ' .
602 'Multi-values not supported for myParam' ),
605 // @todo Test actual upload
608 [ ApiBase
::PARAM_TYPE
=> 'namespace' ],
609 ApiUsageException
::newWithMessage( null,
610 [ 'apierror-unrecognizedvalue', 'myParam', '-1' ],
614 'Extra namespace -1' => [
617 ApiBase
::PARAM_TYPE
=> 'namespace',
618 ApiBase
::PARAM_EXTRA_NAMESPACES
=> [ '-1' ],
623 // @todo Test with PARAM_SUBMODULE_MAP unset, need
624 // getModuleManager() to return something real
625 'Nonexistent module' => [
628 ApiBase
::PARAM_TYPE
=> 'submodule',
629 ApiBase
::PARAM_SUBMODULE_MAP
=>
630 [ 'foo' => 'foo', 'bar' => 'foo+bar' ],
632 ApiUsageException
::newWithMessage(
635 'apierror-unrecognizedvalue',
643 '\\x1f with multiples not allowed' => [
646 ApiUsageException
::newWithMessage( null,
647 'apierror-badvalue-notmultivalue',
648 'badvalue_notmultivalue' ),
651 'Integer with unenforced min' => [
654 ApiBase
::PARAM_TYPE
=> 'integer',
655 ApiBase
::PARAM_MIN
=> -1,
658 [ [ 'apierror-integeroutofrange-belowminimum', 'myParam', -1,
661 'Integer with enforced min' => [
664 ApiBase
::PARAM_TYPE
=> 'integer',
665 ApiBase
::PARAM_MIN
=> -1,
666 ApiBase
::PARAM_RANGE_ENFORCE
=> true,
668 ApiUsageException
::newWithMessage( null,
669 [ 'apierror-integeroutofrange-belowminimum', 'myParam',
670 '-1', '-2' ], 'integeroutofrange',
671 [ 'min' => -1, 'max' => null, 'botMax' => null ] ),
674 'Integer with unenforced max (internal mode)' => [
677 ApiBase
::PARAM_TYPE
=> 'integer',
678 ApiBase
::PARAM_MAX
=> 7,
683 'Integer with enforced max (internal mode)' => [
686 ApiBase
::PARAM_TYPE
=> 'integer',
687 ApiBase
::PARAM_MAX
=> 7,
688 ApiBase
::PARAM_RANGE_ENFORCE
=> true,
693 'Integer with unenforced max (non-internal mode)' => [
696 ApiBase
::PARAM_TYPE
=> 'integer',
697 ApiBase
::PARAM_MAX
=> 7,
700 [ [ 'apierror-integeroutofrange-abovemax', 'myParam', 7, 8 ] ],
701 [ 'internalmode' => false ],
703 'Integer with enforced max (non-internal mode)' => [
706 ApiBase
::PARAM_TYPE
=> 'integer',
707 ApiBase
::PARAM_MAX
=> 7,
708 ApiBase
::PARAM_RANGE_ENFORCE
=> true,
710 ApiUsageException
::newWithMessage(
712 [ 'apierror-integeroutofrange-abovemax', 'myParam', '7', '8' ],
714 [ 'min' => null, 'max' => 7, 'botMax' => 7 ]
717 [ 'internalmode' => false ],
719 'Array of integers' => [
722 ApiBase
::PARAM_ISMULTI
=> true,
723 ApiBase
::PARAM_TYPE
=> 'integer',
728 'Array of integers with unenforced min/max (internal mode)' => [
731 ApiBase
::PARAM_ISMULTI
=> true,
732 ApiBase
::PARAM_TYPE
=> 'integer',
733 ApiBase
::PARAM_MIN
=> 0,
734 ApiBase
::PARAM_MAX
=> 100,
737 [ [ 'apierror-integeroutofrange-belowminimum', 'myParam', 0, -1 ] ],
739 'Array of integers with enforced min/max (internal mode)' => [
742 ApiBase
::PARAM_ISMULTI
=> true,
743 ApiBase
::PARAM_TYPE
=> 'integer',
744 ApiBase
::PARAM_MIN
=> 0,
745 ApiBase
::PARAM_MAX
=> 100,
746 ApiBase
::PARAM_RANGE_ENFORCE
=> true,
748 ApiUsageException
::newWithMessage(
750 [ 'apierror-integeroutofrange-belowminimum', 'myParam', 0, -1 ],
752 [ 'min' => 0, 'max' => 100, 'botMax' => 100 ]
756 'Array of integers with unenforced min/max (non-internal mode)' => [
759 ApiBase
::PARAM_ISMULTI
=> true,
760 ApiBase
::PARAM_TYPE
=> 'integer',
761 ApiBase
::PARAM_MIN
=> 0,
762 ApiBase
::PARAM_MAX
=> 100,
766 [ 'apierror-integeroutofrange-abovemax', 'myParam', 100, 966 ],
767 [ 'apierror-integeroutofrange-belowminimum', 'myParam', 0, -1 ]
769 [ 'internalmode' => false ],
771 'Array of integers with enforced min/max (non-internal mode)' => [
774 ApiBase
::PARAM_ISMULTI
=> true,
775 ApiBase
::PARAM_TYPE
=> 'integer',
776 ApiBase
::PARAM_MIN
=> 0,
777 ApiBase
::PARAM_MAX
=> 100,
778 ApiBase
::PARAM_RANGE_ENFORCE
=> true,
780 ApiUsageException
::newWithMessage(
782 [ 'apierror-integeroutofrange-abovemax', 'myParam', 100, 966 ],
784 [ 'min' => 0, 'max' => 100, 'botMax' => 100 ]
787 [ 'internalmode' => false ],
789 'Limit with parseLimits false' => [
791 [ ApiBase
::PARAM_TYPE
=> 'limit' ],
794 [ 'parseLimits' => false ],
796 'Limit with no max' => [
799 ApiBase
::PARAM_TYPE
=> 'limit',
800 ApiBase
::PARAM_MAX2
=> 10,
801 ApiBase
::PARAM_ISMULTI
=> true,
804 'Internal error in ApiBase::getParameterFromSettings: ' .
805 'MAX1 or MAX2 are not defined for the limit myParam' ),
808 'Limit with no max2' => [
811 ApiBase
::PARAM_TYPE
=> 'limit',
812 ApiBase
::PARAM_MAX
=> 10,
813 ApiBase
::PARAM_ISMULTI
=> true,
816 'Internal error in ApiBase::getParameterFromSettings: ' .
817 'MAX1 or MAX2 are not defined for the limit myParam' ),
820 'Limit with multi-value' => [
823 ApiBase
::PARAM_TYPE
=> 'limit',
824 ApiBase
::PARAM_MAX
=> 10,
825 ApiBase
::PARAM_MAX2
=> 10,
826 ApiBase
::PARAM_ISMULTI
=> true,
829 'Internal error in ApiBase::getParameterFromSettings: ' .
830 'Multi-values not supported for myParam' ),
836 ApiBase
::PARAM_TYPE
=> 'limit',
837 ApiBase
::PARAM_MAX
=> 100,
838 ApiBase
::PARAM_MAX2
=> 100,
846 ApiBase
::PARAM_TYPE
=> 'limit',
847 ApiBase
::PARAM_MAX
=> 100,
848 ApiBase
::PARAM_MAX2
=> 101,
853 'Limit max for apihighlimits' => [
856 ApiBase
::PARAM_TYPE
=> 'limit',
857 ApiBase
::PARAM_MAX
=> 100,
858 ApiBase
::PARAM_MAX2
=> 101,
862 [ 'apihighlimits' => true ],
864 'Limit too large (internal mode)' => [
867 ApiBase
::PARAM_TYPE
=> 'limit',
868 ApiBase
::PARAM_MAX
=> 100,
869 ApiBase
::PARAM_MAX2
=> 101,
874 'Limit okay for apihighlimits (internal mode)' => [
877 ApiBase
::PARAM_TYPE
=> 'limit',
878 ApiBase
::PARAM_MAX
=> 100,
879 ApiBase
::PARAM_MAX2
=> 101,
883 [ 'apihighlimits' => true ],
885 'Limit too large for apihighlimits (internal mode)' => [
888 ApiBase
::PARAM_TYPE
=> 'limit',
889 ApiBase
::PARAM_MAX
=> 100,
890 ApiBase
::PARAM_MAX2
=> 101,
894 [ 'apihighlimits' => true ],
896 'Limit too large (non-internal mode)' => [
899 ApiBase
::PARAM_TYPE
=> 'limit',
900 ApiBase
::PARAM_MAX
=> 100,
901 ApiBase
::PARAM_MAX2
=> 101,
904 [ [ 'apierror-integeroutofrange-abovemax', 'myParam', 100, 101 ] ],
905 [ 'internalmode' => false ],
907 'Limit okay for apihighlimits (non-internal mode)' => [
910 ApiBase
::PARAM_TYPE
=> 'limit',
911 ApiBase
::PARAM_MAX
=> 100,
912 ApiBase
::PARAM_MAX2
=> 101,
916 [ 'internalmode' => false, 'apihighlimits' => true ],
918 'Limit too large for apihighlimits (non-internal mode)' => [
921 ApiBase
::PARAM_TYPE
=> 'limit',
922 ApiBase
::PARAM_MAX
=> 100,
923 ApiBase
::PARAM_MAX2
=> 101,
926 [ [ 'apierror-integeroutofrange-abovebotmax', 'myParam', 101, 102 ] ],
927 [ 'internalmode' => false, 'apihighlimits' => true ],
929 'Limit too small' => [
932 ApiBase
::PARAM_TYPE
=> 'limit',
933 ApiBase
::PARAM_MIN
=> -1,
934 ApiBase
::PARAM_MAX
=> 100,
935 ApiBase
::PARAM_MAX2
=> 100,
938 [ [ 'apierror-integeroutofrange-belowminimum', 'myParam', -1,
942 wfTimestamp( TS_UNIX
, '20211221122112' ),
943 [ ApiBase
::PARAM_TYPE
=> 'timestamp' ],
949 [ ApiBase
::PARAM_TYPE
=> 'timestamp' ],
952 [ [ 'apiwarn-unclearnowtimestamp', 'myParam', '0' ] ],
954 'Timestamp empty' => [
956 [ ApiBase
::PARAM_TYPE
=> 'timestamp' ],
958 [ [ 'apiwarn-unclearnowtimestamp', 'myParam', '' ] ],
960 // wfTimestamp() interprets this as Unix time
963 [ ApiBase
::PARAM_TYPE
=> 'timestamp' ],
969 [ ApiBase
::PARAM_TYPE
=> 'timestamp' ],
973 'Invalid timestamp' => [
975 [ ApiBase
::PARAM_TYPE
=> 'timestamp' ],
976 ApiUsageException
::newWithMessage(
978 [ 'apierror-badtimestamp', 'myParam', 'a potato' ],
979 'badtimestamp_myParam'
983 'Timestamp array' => [
986 ApiBase
::PARAM_TYPE
=> 'timestamp',
987 ApiBase
::PARAM_ISMULTI
=> 1,
989 [ wfTimestamp( TS_MW
, 100 ), wfTimestamp( TS_MW
, 101 ) ],
994 [ ApiBase
::PARAM_TYPE
=> 'user' ],
998 'Invalid username "|"' => [
1000 [ ApiBase
::PARAM_TYPE
=> 'user' ],
1001 ApiUsageException
::newWithMessage( null,
1002 [ 'apierror-baduser', 'myParam', '|' ],
1003 'baduser_myParam' ),
1006 'Invalid username "300.300.300.300"' => [
1008 [ ApiBase
::PARAM_TYPE
=> 'user' ],
1009 ApiUsageException
::newWithMessage( null,
1010 [ 'apierror-baduser', 'myParam', '300.300.300.300' ],
1011 'baduser_myParam' ),
1014 'IP range as username' => [
1016 [ ApiBase
::PARAM_TYPE
=> 'user' ],
1020 'IPv6 as username' => [
1022 [ ApiBase
::PARAM_TYPE
=> 'user' ],
1026 'Obsolete cloaked usemod IP address as username' => [
1028 [ ApiBase
::PARAM_TYPE
=> 'user' ],
1032 'Invalid username containing IP address' => [
1033 'This is [not] valid 1.2.3.xxx, ha!',
1034 [ ApiBase
::PARAM_TYPE
=> 'user' ],
1035 ApiUsageException
::newWithMessage(
1037 [ 'apierror-baduser', 'myParam', 'This is [not] valid 1.2.3.xxx, ha!' ],
1042 'External username' => [
1044 [ ApiBase
::PARAM_TYPE
=> 'user' ],
1048 'Array of usernames' => [
1051 ApiBase
::PARAM_TYPE
=> 'user',
1052 ApiBase
::PARAM_ISMULTI
=> true,
1059 [ ApiBase
::PARAM_TYPE
=> 'tags' ],
1063 'Array of one tag' => [
1066 ApiBase
::PARAM_TYPE
=> 'tags',
1067 ApiBase
::PARAM_ISMULTI
=> true,
1072 'Array of tags' => [
1075 ApiBase
::PARAM_TYPE
=> 'tags',
1076 ApiBase
::PARAM_ISMULTI
=> true,
1083 [ ApiBase
::PARAM_TYPE
=> 'tags' ],
1084 new ApiUsageException( null,
1085 Status
::newFatal( 'tags-apply-not-allowed-one',
1086 'invalid tag', 1 ) ),
1089 'Unrecognized type' => [
1091 [ ApiBase
::PARAM_TYPE
=> 'nonexistenttype' ],
1093 'Internal error in ApiBase::getParameterFromSettings: ' .
1094 "Param myParam's type is unknown - nonexistenttype" ),
1097 'Too many bytes' => [
1100 ApiBase
::PARAM_MAX_BYTES
=> 0,
1101 ApiBase
::PARAM_MAX_CHARS
=> 0,
1103 ApiUsageException
::newWithMessage( null,
1104 [ 'apierror-maxbytes', 'myParam', 0 ] ),
1107 'Too many chars' => [
1110 ApiBase
::PARAM_MAX_BYTES
=> 4,
1111 ApiBase
::PARAM_MAX_CHARS
=> 1,
1113 ApiUsageException
::newWithMessage( null,
1114 [ 'apierror-maxchars', 'myParam', 1 ] ),
1117 'Omitted required param' => [
1119 [ ApiBase
::PARAM_REQUIRED
=> true ],
1120 ApiUsageException
::newWithMessage( null,
1121 [ 'apierror-missingparam', 'myParam' ] ),
1124 'Empty multi-value' => [
1126 [ ApiBase
::PARAM_ISMULTI
=> true ],
1130 'Multi-value \x1f' => [
1132 [ ApiBase
::PARAM_ISMULTI
=> true ],
1136 'Allowed non-multi-value with "|"' => [
1138 [ ApiBase
::PARAM_TYPE
=> [ 'a|b' ] ],
1142 'Prohibited multi-value' => [
1144 [ ApiBase
::PARAM_TYPE
=> [ 'a', 'b' ] ],
1145 ApiUsageException
::newWithMessage( null,
1147 'apierror-multival-only-one-of',
1149 Message
::listParam( [ '<kbd>a</kbd>', '<kbd>b</kbd>' ] ),
1158 // The following really just test PHP's string-to-int conversion.
1167 [ "\t1", 1, '\t1' ],
1168 [ "\r1", 1, '\r1' ],
1169 [ "\f1", 0, '\f1', 'badutf-8' ],
1170 [ "\n1", 1, '\n1' ],
1171 [ "\v1", 0, '\v1', 'badutf-8' ],
1172 [ "\e1", 0, '\e1', 'badutf-8' ],
1173 [ "\x001", 0, '\x001', 'badutf-8' ],
1176 foreach ( $integerTests as $test ) {
1177 $desc = isset( $test[2] ) ?
$test[2] : $test[0];
1178 $warnings = isset( $test[3] ) ?
1179 [ [ 'apiwarn-badutf8', 'myParam' ] ] : [];
1180 $returnArray["\"$desc\" as integer"] = [
1182 [ ApiBase
::PARAM_TYPE
=> 'integer' ],
1188 return $returnArray;
1191 public function testErrorArrayToStatus() {
1192 $mock = new MockApi();
1194 // Sanity check empty array
1195 $expect = Status
::newGood();
1196 $this->assertEquals( $expect, $mock->errorArrayToStatus( [] ) );
1198 // No blocked $user, so no special block handling
1199 $expect = Status
::newGood();
1200 $expect->fatal( 'blockedtext' );
1201 $expect->fatal( 'autoblockedtext' );
1202 $expect->fatal( 'systemblockedtext' );
1203 $expect->fatal( 'mainpage' );
1204 $expect->fatal( 'parentheses', 'foobar' );
1205 $this->assertEquals( $expect, $mock->errorArrayToStatus( [
1207 [ 'autoblockedtext' ],
1208 [ 'systemblockedtext' ],
1210 [ 'parentheses', 'foobar' ],
1213 // Has a blocked $user, so special block handling
1214 $user = $this->getMutableTestUser()->getUser();
1215 $block = new \
Block( [
1216 'address' => $user->getName(),
1217 'user' => $user->getID(),
1218 'by' => $this->getTestSysop()->getUser()->getId(),
1219 'reason' => __METHOD__
,
1220 'expiry' => time() +
100500,
1223 $blockinfo = [ 'blockinfo' => ApiQueryUserInfo
::getBlockInfo( $block ) ];
1225 $expect = Status
::newGood();
1226 $expect->fatal( ApiMessage
::create( 'apierror-blocked', 'blocked', $blockinfo ) );
1227 $expect->fatal( ApiMessage
::create( 'apierror-autoblocked', 'autoblocked', $blockinfo ) );
1228 $expect->fatal( ApiMessage
::create( 'apierror-systemblocked', 'blocked', $blockinfo ) );
1229 $expect->fatal( 'mainpage' );
1230 $expect->fatal( 'parentheses', 'foobar' );
1231 $this->assertEquals( $expect, $mock->errorArrayToStatus( [
1233 [ 'autoblockedtext' ],
1234 [ 'systemblockedtext' ],
1236 [ 'parentheses', 'foobar' ],
1240 public function testDieStatus() {
1241 $mock = new MockApi();
1243 $status = StatusValue
::newGood();
1244 $status->error( 'foo' );
1245 $status->warning( 'bar' );
1247 $mock->dieStatus( $status );
1248 $this->fail( 'Expected exception not thrown' );
1249 } catch ( ApiUsageException
$ex ) {
1250 $this->assertTrue( ApiTestCase
::apiExceptionHasCode( $ex, 'foo' ), 'Exception has "foo"' );
1251 $this->assertFalse( ApiTestCase
::apiExceptionHasCode( $ex, 'bar' ), 'Exception has "bar"' );
1254 $status = StatusValue
::newGood();
1255 $status->warning( 'foo' );
1256 $status->warning( 'bar' );
1258 $mock->dieStatus( $status );
1259 $this->fail( 'Expected exception not thrown' );
1260 } catch ( ApiUsageException
$ex ) {
1261 $this->assertTrue( ApiTestCase
::apiExceptionHasCode( $ex, 'foo' ), 'Exception has "foo"' );
1262 $this->assertTrue( ApiTestCase
::apiExceptionHasCode( $ex, 'bar' ), 'Exception has "bar"' );
1265 $status = StatusValue
::newGood();
1266 $status->setOk( false );
1268 $mock->dieStatus( $status );
1269 $this->fail( 'Expected exception not thrown' );
1270 } catch ( ApiUsageException
$ex ) {
1271 $this->assertTrue( ApiTestCase
::apiExceptionHasCode( $ex, 'unknownerror-nocode' ),
1272 'Exception has "unknownerror-nocode"' );