Merge "Remove comments literally documenting unit tests being unit tests"
[lhc/web/wiklou.git] / tests / phpunit / includes / api / ApiBaseTest.php
1 <?php
2
3 use Wikimedia\TestingAccessWrapper;
4
5 /**
6 * @group API
7 * @group Database
8 * @group medium
9 *
10 * @covers ApiBase
11 */
12 class ApiBaseTest extends ApiTestCase {
13 /**
14 * This covers a variety of stub methods that return a fixed value.
15 *
16 * @param string|array $method Name of method, or [ name, params... ]
17 * @param string $value Expected value
18 *
19 * @dataProvider provideStubMethods
20 */
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 );
26 }
27
28 public function provideStubMethods() {
29 return [
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' ],
45 ];
46 }
47
48 public function testRequireOnlyOneParameterDefault() {
49 $mock = new MockApi();
50 $mock->requireOnlyOneParameter(
51 [ "filename" => "foo.txt", "enablechunks" => false ],
52 "filename", "enablechunks"
53 );
54 $this->assertTrue( true );
55 }
56
57 /**
58 * @expectedException ApiUsageException
59 */
60 public function testRequireOnlyOneParameterZero() {
61 $mock = new MockApi();
62 $mock->requireOnlyOneParameter(
63 [ "filename" => "foo.txt", "enablechunks" => 0 ],
64 "filename", "enablechunks"
65 );
66 }
67
68 /**
69 * @expectedException ApiUsageException
70 */
71 public function testRequireOnlyOneParameterTrue() {
72 $mock = new MockApi();
73 $mock->requireOnlyOneParameter(
74 [ "filename" => "foo.txt", "enablechunks" => true ],
75 "filename", "enablechunks"
76 );
77 }
78
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 ],
85 "foo", "bar" );
86 }
87
88 public function testRequireMaxOneParameterZero() {
89 $mock = new MockApi();
90 $mock->requireMaxOneParameter(
91 [ 'foo' => 'bar', 'baz' => 'quz' ],
92 'squirrel' );
93 $this->assertTrue( true );
94 }
95
96 public function testRequireMaxOneParameterOne() {
97 $mock = new MockApi();
98 $mock->requireMaxOneParameter(
99 [ 'foo' => 'bar', 'baz' => 'quz' ],
100 'foo', 'squirrel' );
101 $this->assertTrue( true );
102 }
103
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' ],
110 'foo', 'baz' );
111 }
112
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' ],
119 'foo', 'bar' );
120 }
121
122 public function testRequireAtLeastOneParameterOne() {
123 $mock = new MockApi();
124 $mock->requireAtLeastOneParameter(
125 [ 'a' => 'b', 'c' => 'd' ],
126 'foo', 'a' );
127 $this->assertTrue( true );
128 }
129
130 public function testRequireAtLeastOneParameterTwo() {
131 $mock = new MockApi();
132 $mock->requireAtLeastOneParameter(
133 [ 'a' => 'b', 'c' => 'd' ],
134 'a', 'c' );
135 $this->assertTrue( true );
136 }
137
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 ] );
143 }
144
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() );
150 }
151
152 public function testGetTitleOrPageIdInvalidTitle() {
153 $this->setExpectedException( ApiUsageException::class,
154 'Bad title "|".' );
155 $mock = new MockApi();
156 $mock->getTitleOrPageId( [ 'title' => '|' ] );
157 }
158
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' ] );
164 }
165
166 public function testGetTitleOrPageIdPageId() {
167 $page = $this->getExistingTestPage();
168 $result = ( new MockApi() )->getTitleOrPageId(
169 [ 'pageid' => $page->getId() ] );
170 $this->assertInstanceOf( WikiPage::class, $result );
171 $this->assertSame(
172 $page->getTitle()->getPrefixedText(),
173 $result->getTitle()->getPrefixedText()
174 );
175 }
176
177 public function testGetTitleOrPageIdInvalidPageId() {
178 // FIXME: fails under postgres
179 $this->markTestSkippedIfDbType( 'postgres' );
180
181 $this->setExpectedException( ApiUsageException::class,
182 'There is no page with ID 2147483648.' );
183 $mock = new MockApi();
184 $mock->getTitleOrPageId( [ 'pageid' => 2147483648 ] );
185 }
186
187 public function testGetTitleFromTitleOrPageIdBadParams() {
188 $this->setExpectedException( ApiUsageException::class,
189 'The parameters "title" and "pageid" can not be used together.' );
190 $mock = new MockApi();
191 $mock->getTitleFromTitleOrPageId( [ 'title' => 'a', 'pageid' => 7 ] );
192 }
193
194 public function testGetTitleFromTitleOrPageIdTitle() {
195 $mock = new MockApi();
196 $result = $mock->getTitleFromTitleOrPageId( [ 'title' => 'Foo' ] );
197 $this->assertInstanceOf( Title::class, $result );
198 $this->assertSame( 'Foo', $result->getPrefixedText() );
199 }
200
201 public function testGetTitleFromTitleOrPageIdInvalidTitle() {
202 $this->setExpectedException( ApiUsageException::class,
203 'Bad title "|".' );
204 $mock = new MockApi();
205 $mock->getTitleFromTitleOrPageId( [ 'title' => '|' ] );
206 }
207
208 public function testGetTitleFromTitleOrPageIdPageId() {
209 $page = $this->getExistingTestPage();
210 $result = ( new MockApi() )->getTitleFromTitleOrPageId(
211 [ 'pageid' => $page->getId() ] );
212 $this->assertInstanceOf( Title::class, $result );
213 $this->assertSame( $page->getTitle()->getPrefixedText(), $result->getPrefixedText() );
214 }
215
216 public function testGetTitleFromTitleOrPageIdInvalidPageId() {
217 $this->setExpectedException( ApiUsageException::class,
218 'There is no page with ID 298401643.' );
219 $mock = new MockApi();
220 $mock->getTitleFromTitleOrPageId( [ 'pageid' => 298401643 ] );
221 }
222
223 public function testGetParameter() {
224 $mock = $this->getMockBuilder( MockApi::class )
225 ->setMethods( [ 'getAllowedParams' ] )
226 ->getMock();
227 $mock->method( 'getAllowedParams' )->willReturn( [
228 'foo' => [
229 ApiBase::PARAM_TYPE => [ 'value' ],
230 ],
231 'bar' => [
232 ApiBase::PARAM_TYPE => [ 'value' ],
233 ],
234 ] );
235 $wrapper = TestingAccessWrapper::newFromObject( $mock );
236
237 $context = new DerivativeContext( $mock );
238 $context->setRequest( new FauxRequest( [ 'foo' => 'bad', 'bar' => 'value' ] ) );
239 $wrapper->mMainModule = new ApiMain( $context );
240
241 // Even though 'foo' is bad, getParameter( 'bar' ) must not fail
242 $this->assertSame( 'value', $wrapper->getParameter( 'bar' ) );
243
244 // But getParameter( 'foo' ) must throw.
245 try {
246 $wrapper->getParameter( 'foo' );
247 $this->fail( 'Expected exception not thrown' );
248 } catch ( ApiUsageException $ex ) {
249 $this->assertTrue( $this->apiExceptionHasCode( $ex, 'unknown_foo' ) );
250 }
251
252 // And extractRequestParams() must throw too.
253 try {
254 $mock->extractRequestParams();
255 $this->fail( 'Expected exception not thrown' );
256 } catch ( ApiUsageException $ex ) {
257 $this->assertTrue( $this->apiExceptionHasCode( $ex, 'unknown_foo' ) );
258 }
259 }
260
261 /**
262 * @param string|null $input
263 * @param array $paramSettings
264 * @param mixed $expected
265 * @param array $options Key-value pairs:
266 * 'parseLimits': true|false
267 * 'apihighlimits': true|false
268 * 'internalmode': true|false
269 * 'prefix': true|false
270 * @param string[] $warnings
271 */
272 private function doGetParameterFromSettings(
273 $input, $paramSettings, $expected, $warnings, $options = []
274 ) {
275 $mock = new MockApi();
276 $wrapper = TestingAccessWrapper::newFromObject( $mock );
277 if ( $options['prefix'] ) {
278 $wrapper->mModulePrefix = 'my';
279 $paramName = 'Param';
280 } else {
281 $paramName = 'myParam';
282 }
283
284 $context = new DerivativeContext( $mock );
285 $context->setRequest( new FauxRequest(
286 $input !== null ? [ 'myParam' => $input ] : [] ) );
287 $wrapper->mMainModule = new ApiMain( $context );
288
289 $parseLimits = $options['parseLimits'] ?? true;
290
291 if ( !empty( $options['apihighlimits'] ) ) {
292 $context->setUser( self::$users['sysop']->getUser() );
293 }
294
295 if ( isset( $options['internalmode'] ) && !$options['internalmode'] ) {
296 $mainWrapper = TestingAccessWrapper::newFromObject( $wrapper->mMainModule );
297 $mainWrapper->mInternalMode = false;
298 }
299
300 // If we're testing tags, set up some tags
301 if ( isset( $paramSettings[ApiBase::PARAM_TYPE] ) &&
302 $paramSettings[ApiBase::PARAM_TYPE] === 'tags'
303 ) {
304 ChangeTags::defineTag( 'tag1' );
305 ChangeTags::defineTag( 'tag2' );
306 }
307
308 if ( $expected instanceof Exception ) {
309 try {
310 $wrapper->getParameterFromSettings( $paramName, $paramSettings,
311 $parseLimits );
312 $this->fail( 'No exception thrown' );
313 } catch ( Exception $ex ) {
314 $this->assertEquals( $expected, $ex );
315 }
316 } else {
317 $result = $wrapper->getParameterFromSettings( $paramName,
318 $paramSettings, $parseLimits );
319 if ( isset( $paramSettings[ApiBase::PARAM_TYPE] ) &&
320 $paramSettings[ApiBase::PARAM_TYPE] === 'timestamp' &&
321 $expected === 'now'
322 ) {
323 // Allow one second of fuzziness. Make sure the formats are
324 // correct!
325 $this->assertRegExp( '/^\d{14}$/', $result );
326 $this->assertLessThanOrEqual( 1,
327 abs( wfTimestamp( TS_UNIX, $result ) - time() ),
328 "Result $result differs from expected $expected by " .
329 'more than one second' );
330 } else {
331 $this->assertSame( $expected, $result );
332 }
333 $actualWarnings = array_map( function ( $warn ) {
334 return $warn instanceof Message
335 ? array_merge( [ $warn->getKey() ], $warn->getParams() )
336 : $warn;
337 }, $mock->warnings );
338 $this->assertSame( $warnings, $actualWarnings );
339 }
340
341 if ( !empty( $paramSettings[ApiBase::PARAM_SENSITIVE] ) ||
342 ( isset( $paramSettings[ApiBase::PARAM_TYPE] ) &&
343 $paramSettings[ApiBase::PARAM_TYPE] === 'password' )
344 ) {
345 $mainWrapper = TestingAccessWrapper::newFromObject( $wrapper->getMain() );
346 $this->assertSame( [ 'myParam' ],
347 $mainWrapper->getSensitiveParams() );
348 }
349 }
350
351 /**
352 * @dataProvider provideGetParameterFromSettings
353 * @see self::doGetParameterFromSettings()
354 */
355 public function testGetParameterFromSettings_noprefix(
356 $input, $paramSettings, $expected, $warnings, $options = []
357 ) {
358 $options['prefix'] = false;
359 $this->doGetParameterFromSettings( $input, $paramSettings, $expected, $warnings, $options );
360 }
361
362 /**
363 * @dataProvider provideGetParameterFromSettings
364 * @see self::doGetParameterFromSettings()
365 */
366 public function testGetParameterFromSettings_prefix(
367 $input, $paramSettings, $expected, $warnings, $options = []
368 ) {
369 $options['prefix'] = true;
370 $this->doGetParameterFromSettings( $input, $paramSettings, $expected, $warnings, $options );
371 }
372
373 public static function provideGetParameterFromSettings() {
374 $warnings = [
375 [ 'apiwarn-badutf8', 'myParam' ],
376 ];
377
378 $c0 = '';
379 $enc = '';
380 for ( $i = 0; $i < 32; $i++ ) {
381 $c0 .= chr( $i );
382 $enc .= ( $i === 9 || $i === 10 || $i === 13 )
383 ? chr( $i )
384 : '�';
385 }
386
387 $returnArray = [
388 'Basic param' => [ 'bar', null, 'bar', [] ],
389 'Basic param, C0 controls' => [ $c0, null, $enc, $warnings ],
390 'String param' => [ 'bar', '', 'bar', [] ],
391 'String param, defaulted' => [ null, '', '', [] ],
392 'String param, empty' => [ '', 'default', '', [] ],
393 'String param, required, empty' => [
394 '',
395 [ ApiBase::PARAM_DFLT => 'default', ApiBase::PARAM_REQUIRED => true ],
396 ApiUsageException::newWithMessage( null,
397 [ 'apierror-missingparam', 'myParam' ] ),
398 []
399 ],
400 'Multi-valued parameter' => [
401 'a|b|c',
402 [ ApiBase::PARAM_ISMULTI => true ],
403 [ 'a', 'b', 'c' ],
404 []
405 ],
406 'Multi-valued parameter, alternative separator' => [
407 "\x1fa|b\x1fc|d",
408 [ ApiBase::PARAM_ISMULTI => true ],
409 [ 'a|b', 'c|d' ],
410 []
411 ],
412 'Multi-valued parameter, other C0 controls' => [
413 $c0,
414 [ ApiBase::PARAM_ISMULTI => true ],
415 [ $enc ],
416 $warnings
417 ],
418 'Multi-valued parameter, other C0 controls (2)' => [
419 "\x1f" . $c0,
420 [ ApiBase::PARAM_ISMULTI => true ],
421 [ substr( $enc, 0, -3 ), '' ],
422 $warnings
423 ],
424 'Multi-valued parameter with limits' => [
425 'a|b|c',
426 [
427 ApiBase::PARAM_ISMULTI => true,
428 ApiBase::PARAM_ISMULTI_LIMIT1 => 3,
429 ],
430 [ 'a', 'b', 'c' ],
431 [],
432 ],
433 'Multi-valued parameter with exceeded limits' => [
434 'a|b|c',
435 [
436 ApiBase::PARAM_ISMULTI => true,
437 ApiBase::PARAM_ISMULTI_LIMIT1 => 2,
438 ],
439 ApiUsageException::newWithMessage(
440 null, [ 'apierror-toomanyvalues', 'myParam', 2 ], 'too-many-myParam'
441 ),
442 []
443 ],
444 'Multi-valued parameter with exceeded limits for non-bot' => [
445 'a|b|c',
446 [
447 ApiBase::PARAM_ISMULTI => true,
448 ApiBase::PARAM_ISMULTI_LIMIT1 => 2,
449 ApiBase::PARAM_ISMULTI_LIMIT2 => 3,
450 ],
451 ApiUsageException::newWithMessage(
452 null, [ 'apierror-toomanyvalues', 'myParam', 2 ], 'too-many-myParam'
453 ),
454 []
455 ],
456 'Multi-valued parameter with non-exceeded limits for bot' => [
457 'a|b|c',
458 [
459 ApiBase::PARAM_ISMULTI => true,
460 ApiBase::PARAM_ISMULTI_LIMIT1 => 2,
461 ApiBase::PARAM_ISMULTI_LIMIT2 => 3,
462 ],
463 [ 'a', 'b', 'c' ],
464 [],
465 [ 'apihighlimits' => true ],
466 ],
467 'Multi-valued parameter with prohibited duplicates' => [
468 'a|b|a|c',
469 [ ApiBase::PARAM_ISMULTI => true ],
470 // Note that the keys are not sequential! This matches
471 // array_unique, but might be unexpected.
472 [ 0 => 'a', 1 => 'b', 3 => 'c' ],
473 [],
474 ],
475 'Multi-valued parameter with allowed duplicates' => [
476 'a|a',
477 [
478 ApiBase::PARAM_ISMULTI => true,
479 ApiBase::PARAM_ALLOW_DUPLICATES => true,
480 ],
481 [ 'a', 'a' ],
482 [],
483 ],
484 'Empty boolean param' => [
485 '',
486 [ ApiBase::PARAM_TYPE => 'boolean' ],
487 true,
488 [],
489 ],
490 'Boolean param 0' => [
491 '0',
492 [ ApiBase::PARAM_TYPE => 'boolean' ],
493 true,
494 [],
495 ],
496 'Boolean param false' => [
497 'false',
498 [ ApiBase::PARAM_TYPE => 'boolean' ],
499 true,
500 [],
501 ],
502 'Boolean multi-param' => [
503 'true|false',
504 [
505 ApiBase::PARAM_TYPE => 'boolean',
506 ApiBase::PARAM_ISMULTI => true,
507 ],
508 new MWException(
509 'Internal error in ApiBase::getParameterFromSettings: ' .
510 'Multi-values not supported for myParam'
511 ),
512 [],
513 ],
514 'Empty boolean param with non-false default' => [
515 '',
516 [
517 ApiBase::PARAM_TYPE => 'boolean',
518 ApiBase::PARAM_DFLT => true,
519 ],
520 new MWException(
521 'Internal error in ApiBase::getParameterFromSettings: ' .
522 "Boolean param myParam's default is set to '1'. " .
523 'Boolean parameters must default to false.' ),
524 [],
525 ],
526 'Deprecated parameter' => [
527 'foo',
528 [ ApiBase::PARAM_DEPRECATED => true ],
529 'foo',
530 [ [ 'apiwarn-deprecation-parameter', 'myParam' ] ],
531 ],
532 'Deprecated parameter with default, unspecified' => [
533 null,
534 [ ApiBase::PARAM_DEPRECATED => true, ApiBase::PARAM_DFLT => 'foo' ],
535 'foo',
536 [],
537 ],
538 'Deprecated parameter with default, specified' => [
539 'foo',
540 [ ApiBase::PARAM_DEPRECATED => true, ApiBase::PARAM_DFLT => 'foo' ],
541 'foo',
542 [ [ 'apiwarn-deprecation-parameter', 'myParam' ] ],
543 ],
544 'Deprecated parameter value' => [
545 'a',
546 [ ApiBase::PARAM_DEPRECATED_VALUES => [ 'a' => true ] ],
547 'a',
548 [ [ 'apiwarn-deprecation-parameter', 'myParam=a' ] ],
549 ],
550 'Deprecated parameter value as default, unspecified' => [
551 null,
552 [ ApiBase::PARAM_DEPRECATED_VALUES => [ 'a' => true ], ApiBase::PARAM_DFLT => 'a' ],
553 'a',
554 [],
555 ],
556 'Deprecated parameter value as default, specified' => [
557 'a',
558 [ ApiBase::PARAM_DEPRECATED_VALUES => [ 'a' => true ], ApiBase::PARAM_DFLT => 'a' ],
559 'a',
560 [ [ 'apiwarn-deprecation-parameter', 'myParam=a' ] ],
561 ],
562 'Multiple deprecated parameter values' => [
563 'a|b|c|d',
564 [ ApiBase::PARAM_DEPRECATED_VALUES =>
565 [ 'b' => true, 'd' => true ],
566 ApiBase::PARAM_ISMULTI => true ],
567 [ 'a', 'b', 'c', 'd' ],
568 [
569 [ 'apiwarn-deprecation-parameter', 'myParam=b' ],
570 [ 'apiwarn-deprecation-parameter', 'myParam=d' ],
571 ],
572 ],
573 'Deprecated parameter value with custom warning' => [
574 'a',
575 [ ApiBase::PARAM_DEPRECATED_VALUES => [ 'a' => 'my-msg' ] ],
576 'a',
577 [ 'my-msg' ],
578 ],
579 '"*" when wildcard not allowed' => [
580 '*',
581 [ ApiBase::PARAM_ISMULTI => true,
582 ApiBase::PARAM_TYPE => [ 'a', 'b', 'c' ] ],
583 [],
584 [ [ 'apiwarn-unrecognizedvalues', 'myParam',
585 [ 'list' => [ '&#42;' ], 'type' => 'comma' ], 1 ] ],
586 ],
587 'Wildcard "*"' => [
588 '*',
589 [
590 ApiBase::PARAM_ISMULTI => true,
591 ApiBase::PARAM_TYPE => [ 'a', 'b', 'c' ],
592 ApiBase::PARAM_ALL => true,
593 ],
594 [ 'a', 'b', 'c' ],
595 [],
596 ],
597 'Wildcard "*" with multiples not allowed' => [
598 '*',
599 [
600 ApiBase::PARAM_TYPE => [ 'a', 'b', 'c' ],
601 ApiBase::PARAM_ALL => true,
602 ],
603 ApiUsageException::newWithMessage( null,
604 [ 'apierror-unrecognizedvalue', 'myParam', '&#42;' ],
605 'unknown_myParam' ),
606 [],
607 ],
608 'Wildcard "*" with unrestricted type' => [
609 '*',
610 [
611 ApiBase::PARAM_ISMULTI => true,
612 ApiBase::PARAM_ALL => true,
613 ],
614 [ '*' ],
615 [],
616 ],
617 'Wildcard "x"' => [
618 'x',
619 [
620 ApiBase::PARAM_ISMULTI => true,
621 ApiBase::PARAM_TYPE => [ 'a', 'b', 'c' ],
622 ApiBase::PARAM_ALL => 'x',
623 ],
624 [ 'a', 'b', 'c' ],
625 [],
626 ],
627 'Wildcard conflicting with allowed value' => [
628 'a',
629 [
630 ApiBase::PARAM_ISMULTI => true,
631 ApiBase::PARAM_TYPE => [ 'a', 'b', 'c' ],
632 ApiBase::PARAM_ALL => 'a',
633 ],
634 new MWException(
635 'Internal error in ApiBase::getParameterFromSettings: ' .
636 'For param myParam, PARAM_ALL collides with a possible ' .
637 'value' ),
638 [],
639 ],
640 'Namespace with wildcard' => [
641 '*',
642 [
643 ApiBase::PARAM_ISMULTI => true,
644 ApiBase::PARAM_TYPE => 'namespace',
645 ],
646 MWNamespace::getValidNamespaces(),
647 [],
648 ],
649 // PARAM_ALL is ignored with namespace types.
650 'Namespace with wildcard suppressed' => [
651 '*',
652 [
653 ApiBase::PARAM_ISMULTI => true,
654 ApiBase::PARAM_TYPE => 'namespace',
655 ApiBase::PARAM_ALL => false,
656 ],
657 MWNamespace::getValidNamespaces(),
658 [],
659 ],
660 'Namespace with wildcard "x"' => [
661 'x',
662 [
663 ApiBase::PARAM_ISMULTI => true,
664 ApiBase::PARAM_TYPE => 'namespace',
665 ApiBase::PARAM_ALL => 'x',
666 ],
667 [],
668 [ [ 'apiwarn-unrecognizedvalues', 'myParam',
669 [ 'list' => [ 'x' ], 'type' => 'comma' ], 1 ] ],
670 ],
671 'Password' => [
672 'dDy+G?e?txnr.1:(@[Ru',
673 [ ApiBase::PARAM_TYPE => 'password' ],
674 'dDy+G?e?txnr.1:(@[Ru',
675 [],
676 ],
677 'Sensitive field' => [
678 'I am fond of pineapples',
679 [ ApiBase::PARAM_SENSITIVE => true ],
680 'I am fond of pineapples',
681 [],
682 ],
683 'Upload with default' => [
684 '',
685 [
686 ApiBase::PARAM_TYPE => 'upload',
687 ApiBase::PARAM_DFLT => '',
688 ],
689 new MWException(
690 'Internal error in ApiBase::getParameterFromSettings: ' .
691 "File upload param myParam's default is set to ''. " .
692 'File upload parameters may not have a default.' ),
693 [],
694 ],
695 'Multiple upload' => [
696 '',
697 [
698 ApiBase::PARAM_TYPE => 'upload',
699 ApiBase::PARAM_ISMULTI => true,
700 ],
701 new MWException(
702 'Internal error in ApiBase::getParameterFromSettings: ' .
703 'Multi-values not supported for myParam' ),
704 [],
705 ],
706 // @todo Test actual upload
707 'Namespace -1' => [
708 '-1',
709 [ ApiBase::PARAM_TYPE => 'namespace' ],
710 ApiUsageException::newWithMessage( null,
711 [ 'apierror-unrecognizedvalue', 'myParam', '-1' ],
712 'unknown_myParam' ),
713 [],
714 ],
715 'Extra namespace -1' => [
716 '-1',
717 [
718 ApiBase::PARAM_TYPE => 'namespace',
719 ApiBase::PARAM_EXTRA_NAMESPACES => [ '-1' ],
720 ],
721 '-1',
722 [],
723 ],
724 // @todo Test with PARAM_SUBMODULE_MAP unset, need
725 // getModuleManager() to return something real
726 'Nonexistent module' => [
727 'not-a-module-name',
728 [
729 ApiBase::PARAM_TYPE => 'submodule',
730 ApiBase::PARAM_SUBMODULE_MAP =>
731 [ 'foo' => 'foo', 'bar' => 'foo+bar' ],
732 ],
733 ApiUsageException::newWithMessage(
734 null,
735 [
736 'apierror-unrecognizedvalue',
737 'myParam',
738 'not-a-module-name',
739 ],
740 'unknown_myParam'
741 ),
742 [],
743 ],
744 '\\x1f with multiples not allowed' => [
745 "\x1f",
746 [],
747 ApiUsageException::newWithMessage( null,
748 'apierror-badvalue-notmultivalue',
749 'badvalue_notmultivalue' ),
750 [],
751 ],
752 'Integer with unenforced min' => [
753 '-2',
754 [
755 ApiBase::PARAM_TYPE => 'integer',
756 ApiBase::PARAM_MIN => -1,
757 ],
758 -1,
759 [ [ 'apierror-integeroutofrange-belowminimum', 'myParam', -1,
760 -2 ] ],
761 ],
762 'Integer with enforced min' => [
763 '-2',
764 [
765 ApiBase::PARAM_TYPE => 'integer',
766 ApiBase::PARAM_MIN => -1,
767 ApiBase::PARAM_RANGE_ENFORCE => true,
768 ],
769 ApiUsageException::newWithMessage( null,
770 [ 'apierror-integeroutofrange-belowminimum', 'myParam',
771 '-1', '-2' ], 'integeroutofrange',
772 [ 'min' => -1, 'max' => null, 'botMax' => null ] ),
773 [],
774 ],
775 'Integer with unenforced max (internal mode)' => [
776 '8',
777 [
778 ApiBase::PARAM_TYPE => 'integer',
779 ApiBase::PARAM_MAX => 7,
780 ],
781 8,
782 [],
783 ],
784 'Integer with enforced max (internal mode)' => [
785 '8',
786 [
787 ApiBase::PARAM_TYPE => 'integer',
788 ApiBase::PARAM_MAX => 7,
789 ApiBase::PARAM_RANGE_ENFORCE => true,
790 ],
791 8,
792 [],
793 ],
794 'Integer with unenforced max (non-internal mode)' => [
795 '8',
796 [
797 ApiBase::PARAM_TYPE => 'integer',
798 ApiBase::PARAM_MAX => 7,
799 ],
800 7,
801 [ [ 'apierror-integeroutofrange-abovemax', 'myParam', 7, 8 ] ],
802 [ 'internalmode' => false ],
803 ],
804 'Integer with enforced max (non-internal mode)' => [
805 '8',
806 [
807 ApiBase::PARAM_TYPE => 'integer',
808 ApiBase::PARAM_MAX => 7,
809 ApiBase::PARAM_RANGE_ENFORCE => true,
810 ],
811 ApiUsageException::newWithMessage(
812 null,
813 [ 'apierror-integeroutofrange-abovemax', 'myParam', '7', '8' ],
814 'integeroutofrange',
815 [ 'min' => null, 'max' => 7, 'botMax' => 7 ]
816 ),
817 [],
818 [ 'internalmode' => false ],
819 ],
820 'Array of integers' => [
821 '3|12|966|-1',
822 [
823 ApiBase::PARAM_ISMULTI => true,
824 ApiBase::PARAM_TYPE => 'integer',
825 ],
826 [ 3, 12, 966, -1 ],
827 [],
828 ],
829 'Array of integers with unenforced min/max (internal mode)' => [
830 '3|12|966|-1',
831 [
832 ApiBase::PARAM_ISMULTI => true,
833 ApiBase::PARAM_TYPE => 'integer',
834 ApiBase::PARAM_MIN => 0,
835 ApiBase::PARAM_MAX => 100,
836 ],
837 [ 3, 12, 966, 0 ],
838 [ [ 'apierror-integeroutofrange-belowminimum', 'myParam', 0, -1 ] ],
839 ],
840 'Array of integers with enforced min/max (internal mode)' => [
841 '3|12|966|-1',
842 [
843 ApiBase::PARAM_ISMULTI => true,
844 ApiBase::PARAM_TYPE => 'integer',
845 ApiBase::PARAM_MIN => 0,
846 ApiBase::PARAM_MAX => 100,
847 ApiBase::PARAM_RANGE_ENFORCE => true,
848 ],
849 ApiUsageException::newWithMessage(
850 null,
851 [ 'apierror-integeroutofrange-belowminimum', 'myParam', 0, -1 ],
852 'integeroutofrange',
853 [ 'min' => 0, 'max' => 100, 'botMax' => 100 ]
854 ),
855 [],
856 ],
857 'Array of integers with unenforced min/max (non-internal mode)' => [
858 '3|12|966|-1',
859 [
860 ApiBase::PARAM_ISMULTI => true,
861 ApiBase::PARAM_TYPE => 'integer',
862 ApiBase::PARAM_MIN => 0,
863 ApiBase::PARAM_MAX => 100,
864 ],
865 [ 3, 12, 100, 0 ],
866 [
867 [ 'apierror-integeroutofrange-abovemax', 'myParam', 100, 966 ],
868 [ 'apierror-integeroutofrange-belowminimum', 'myParam', 0, -1 ]
869 ],
870 [ 'internalmode' => false ],
871 ],
872 'Array of integers with enforced min/max (non-internal mode)' => [
873 '3|12|966|-1',
874 [
875 ApiBase::PARAM_ISMULTI => true,
876 ApiBase::PARAM_TYPE => 'integer',
877 ApiBase::PARAM_MIN => 0,
878 ApiBase::PARAM_MAX => 100,
879 ApiBase::PARAM_RANGE_ENFORCE => true,
880 ],
881 ApiUsageException::newWithMessage(
882 null,
883 [ 'apierror-integeroutofrange-abovemax', 'myParam', 100, 966 ],
884 'integeroutofrange',
885 [ 'min' => 0, 'max' => 100, 'botMax' => 100 ]
886 ),
887 [],
888 [ 'internalmode' => false ],
889 ],
890 'Limit with parseLimits false' => [
891 '100',
892 [ ApiBase::PARAM_TYPE => 'limit' ],
893 '100',
894 [],
895 [ 'parseLimits' => false ],
896 ],
897 'Limit with no max' => [
898 '100',
899 [
900 ApiBase::PARAM_TYPE => 'limit',
901 ApiBase::PARAM_MAX2 => 10,
902 ApiBase::PARAM_ISMULTI => true,
903 ],
904 new MWException(
905 'Internal error in ApiBase::getParameterFromSettings: ' .
906 'MAX1 or MAX2 are not defined for the limit myParam' ),
907 [],
908 ],
909 'Limit with no max2' => [
910 '100',
911 [
912 ApiBase::PARAM_TYPE => 'limit',
913 ApiBase::PARAM_MAX => 10,
914 ApiBase::PARAM_ISMULTI => true,
915 ],
916 new MWException(
917 'Internal error in ApiBase::getParameterFromSettings: ' .
918 'MAX1 or MAX2 are not defined for the limit myParam' ),
919 [],
920 ],
921 'Limit with multi-value' => [
922 '100',
923 [
924 ApiBase::PARAM_TYPE => 'limit',
925 ApiBase::PARAM_MAX => 10,
926 ApiBase::PARAM_MAX2 => 10,
927 ApiBase::PARAM_ISMULTI => true,
928 ],
929 new MWException(
930 'Internal error in ApiBase::getParameterFromSettings: ' .
931 'Multi-values not supported for myParam' ),
932 [],
933 ],
934 'Valid limit' => [
935 '100',
936 [
937 ApiBase::PARAM_TYPE => 'limit',
938 ApiBase::PARAM_MAX => 100,
939 ApiBase::PARAM_MAX2 => 100,
940 ],
941 100,
942 [],
943 ],
944 'Limit max' => [
945 'max',
946 [
947 ApiBase::PARAM_TYPE => 'limit',
948 ApiBase::PARAM_MAX => 100,
949 ApiBase::PARAM_MAX2 => 101,
950 ],
951 100,
952 [],
953 ],
954 'Limit max for apihighlimits' => [
955 'max',
956 [
957 ApiBase::PARAM_TYPE => 'limit',
958 ApiBase::PARAM_MAX => 100,
959 ApiBase::PARAM_MAX2 => 101,
960 ],
961 101,
962 [],
963 [ 'apihighlimits' => true ],
964 ],
965 'Limit too large (internal mode)' => [
966 '101',
967 [
968 ApiBase::PARAM_TYPE => 'limit',
969 ApiBase::PARAM_MAX => 100,
970 ApiBase::PARAM_MAX2 => 101,
971 ],
972 101,
973 [],
974 ],
975 'Limit okay for apihighlimits (internal mode)' => [
976 '101',
977 [
978 ApiBase::PARAM_TYPE => 'limit',
979 ApiBase::PARAM_MAX => 100,
980 ApiBase::PARAM_MAX2 => 101,
981 ],
982 101,
983 [],
984 [ 'apihighlimits' => true ],
985 ],
986 'Limit too large for apihighlimits (internal mode)' => [
987 '102',
988 [
989 ApiBase::PARAM_TYPE => 'limit',
990 ApiBase::PARAM_MAX => 100,
991 ApiBase::PARAM_MAX2 => 101,
992 ],
993 102,
994 [],
995 [ 'apihighlimits' => true ],
996 ],
997 'Limit too large (non-internal mode)' => [
998 '101',
999 [
1000 ApiBase::PARAM_TYPE => 'limit',
1001 ApiBase::PARAM_MAX => 100,
1002 ApiBase::PARAM_MAX2 => 101,
1003 ],
1004 100,
1005 [ [ 'apierror-integeroutofrange-abovemax', 'myParam', 100, 101 ] ],
1006 [ 'internalmode' => false ],
1007 ],
1008 'Limit okay for apihighlimits (non-internal mode)' => [
1009 '101',
1010 [
1011 ApiBase::PARAM_TYPE => 'limit',
1012 ApiBase::PARAM_MAX => 100,
1013 ApiBase::PARAM_MAX2 => 101,
1014 ],
1015 101,
1016 [],
1017 [ 'internalmode' => false, 'apihighlimits' => true ],
1018 ],
1019 'Limit too large for apihighlimits (non-internal mode)' => [
1020 '102',
1021 [
1022 ApiBase::PARAM_TYPE => 'limit',
1023 ApiBase::PARAM_MAX => 100,
1024 ApiBase::PARAM_MAX2 => 101,
1025 ],
1026 101,
1027 [ [ 'apierror-integeroutofrange-abovebotmax', 'myParam', 101, 102 ] ],
1028 [ 'internalmode' => false, 'apihighlimits' => true ],
1029 ],
1030 'Limit too small' => [
1031 '-2',
1032 [
1033 ApiBase::PARAM_TYPE => 'limit',
1034 ApiBase::PARAM_MIN => -1,
1035 ApiBase::PARAM_MAX => 100,
1036 ApiBase::PARAM_MAX2 => 100,
1037 ],
1038 -1,
1039 [ [ 'apierror-integeroutofrange-belowminimum', 'myParam', -1,
1040 -2 ] ],
1041 ],
1042 'Timestamp' => [
1043 wfTimestamp( TS_UNIX, '20211221122112' ),
1044 [ ApiBase::PARAM_TYPE => 'timestamp' ],
1045 '20211221122112',
1046 [],
1047 ],
1048 'Timestamp 0' => [
1049 '0',
1050 [ ApiBase::PARAM_TYPE => 'timestamp' ],
1051 // Magic keyword
1052 'now',
1053 [ [ 'apiwarn-unclearnowtimestamp', 'myParam', '0' ] ],
1054 ],
1055 'Timestamp empty' => [
1056 '',
1057 [ ApiBase::PARAM_TYPE => 'timestamp' ],
1058 'now',
1059 [ [ 'apiwarn-unclearnowtimestamp', 'myParam', '' ] ],
1060 ],
1061 // wfTimestamp() interprets this as Unix time
1062 'Timestamp 00' => [
1063 '00',
1064 [ ApiBase::PARAM_TYPE => 'timestamp' ],
1065 '19700101000000',
1066 [],
1067 ],
1068 'Timestamp now' => [
1069 'now',
1070 [ ApiBase::PARAM_TYPE => 'timestamp' ],
1071 'now',
1072 [],
1073 ],
1074 'Invalid timestamp' => [
1075 'a potato',
1076 [ ApiBase::PARAM_TYPE => 'timestamp' ],
1077 ApiUsageException::newWithMessage(
1078 null,
1079 [ 'apierror-badtimestamp', 'myParam', 'a potato' ],
1080 'badtimestamp_myParam'
1081 ),
1082 [],
1083 ],
1084 'Timestamp array' => [
1085 '100|101',
1086 [
1087 ApiBase::PARAM_TYPE => 'timestamp',
1088 ApiBase::PARAM_ISMULTI => 1,
1089 ],
1090 [ wfTimestamp( TS_MW, 100 ), wfTimestamp( TS_MW, 101 ) ],
1091 [],
1092 ],
1093 'User' => [
1094 'foo_bar',
1095 [ ApiBase::PARAM_TYPE => 'user' ],
1096 'Foo bar',
1097 [],
1098 ],
1099 'User prefixed with "User:"' => [
1100 'User:foo_bar',
1101 [ ApiBase::PARAM_TYPE => 'user' ],
1102 'Foo bar',
1103 [],
1104 ],
1105 'Invalid username "|"' => [
1106 '|',
1107 [ ApiBase::PARAM_TYPE => 'user' ],
1108 ApiUsageException::newWithMessage( null,
1109 [ 'apierror-baduser', 'myParam', '&#124;' ],
1110 'baduser_myParam' ),
1111 [],
1112 ],
1113 'Invalid username "300.300.300.300"' => [
1114 '300.300.300.300',
1115 [ ApiBase::PARAM_TYPE => 'user' ],
1116 ApiUsageException::newWithMessage( null,
1117 [ 'apierror-baduser', 'myParam', '300.300.300.300' ],
1118 'baduser_myParam' ),
1119 [],
1120 ],
1121 'IP range as username' => [
1122 '10.0.0.0/8',
1123 [ ApiBase::PARAM_TYPE => 'user' ],
1124 '10.0.0.0/8',
1125 [],
1126 ],
1127 'IPv6 as username' => [
1128 '::1',
1129 [ ApiBase::PARAM_TYPE => 'user' ],
1130 '0:0:0:0:0:0:0:1',
1131 [],
1132 ],
1133 'Obsolete cloaked usemod IP address as username' => [
1134 '1.2.3.xxx',
1135 [ ApiBase::PARAM_TYPE => 'user' ],
1136 '1.2.3.xxx',
1137 [],
1138 ],
1139 'Invalid username containing IP address' => [
1140 'This is [not] valid 1.2.3.xxx, ha!',
1141 [ ApiBase::PARAM_TYPE => 'user' ],
1142 ApiUsageException::newWithMessage(
1143 null,
1144 [ 'apierror-baduser', 'myParam', 'This is &#91;not&#93; valid 1.2.3.xxx, ha!' ],
1145 'baduser_myParam'
1146 ),
1147 [],
1148 ],
1149 'External username' => [
1150 'M>Foo bar',
1151 [ ApiBase::PARAM_TYPE => 'user' ],
1152 'M>Foo bar',
1153 [],
1154 ],
1155 'Array of usernames' => [
1156 'foo|bar',
1157 [
1158 ApiBase::PARAM_TYPE => 'user',
1159 ApiBase::PARAM_ISMULTI => true,
1160 ],
1161 [ 'Foo', 'Bar' ],
1162 [],
1163 ],
1164 'tag' => [
1165 'tag1',
1166 [ ApiBase::PARAM_TYPE => 'tags' ],
1167 [ 'tag1' ],
1168 [],
1169 ],
1170 'Array of one tag' => [
1171 'tag1',
1172 [
1173 ApiBase::PARAM_TYPE => 'tags',
1174 ApiBase::PARAM_ISMULTI => true,
1175 ],
1176 [ 'tag1' ],
1177 [],
1178 ],
1179 'Array of tags' => [
1180 'tag1|tag2',
1181 [
1182 ApiBase::PARAM_TYPE => 'tags',
1183 ApiBase::PARAM_ISMULTI => true,
1184 ],
1185 [ 'tag1', 'tag2' ],
1186 [],
1187 ],
1188 'Invalid tag' => [
1189 'invalid tag',
1190 [ ApiBase::PARAM_TYPE => 'tags' ],
1191 new ApiUsageException( null,
1192 Status::newFatal( 'tags-apply-not-allowed-one',
1193 'invalid tag', 1 ) ),
1194 [],
1195 ],
1196 'Unrecognized type' => [
1197 'foo',
1198 [ ApiBase::PARAM_TYPE => 'nonexistenttype' ],
1199 new MWException(
1200 'Internal error in ApiBase::getParameterFromSettings: ' .
1201 "Param myParam's type is unknown - nonexistenttype" ),
1202 [],
1203 ],
1204 'Too many bytes' => [
1205 '1',
1206 [
1207 ApiBase::PARAM_MAX_BYTES => 0,
1208 ApiBase::PARAM_MAX_CHARS => 0,
1209 ],
1210 ApiUsageException::newWithMessage( null,
1211 [ 'apierror-maxbytes', 'myParam', 0 ] ),
1212 [],
1213 ],
1214 'Too many chars' => [
1215 '§§',
1216 [
1217 ApiBase::PARAM_MAX_BYTES => 4,
1218 ApiBase::PARAM_MAX_CHARS => 1,
1219 ],
1220 ApiUsageException::newWithMessage( null,
1221 [ 'apierror-maxchars', 'myParam', 1 ] ),
1222 [],
1223 ],
1224 'Omitted required param' => [
1225 null,
1226 [ ApiBase::PARAM_REQUIRED => true ],
1227 ApiUsageException::newWithMessage( null,
1228 [ 'apierror-missingparam', 'myParam' ] ),
1229 [],
1230 ],
1231 'Empty multi-value' => [
1232 '',
1233 [ ApiBase::PARAM_ISMULTI => true ],
1234 [],
1235 [],
1236 ],
1237 'Multi-value \x1f' => [
1238 "\x1f",
1239 [ ApiBase::PARAM_ISMULTI => true ],
1240 [],
1241 [],
1242 ],
1243 'Allowed non-multi-value with "|"' => [
1244 'a|b',
1245 [ ApiBase::PARAM_TYPE => [ 'a|b' ] ],
1246 'a|b',
1247 [],
1248 ],
1249 'Prohibited multi-value' => [
1250 'a|b',
1251 [ ApiBase::PARAM_TYPE => [ 'a', 'b' ] ],
1252 ApiUsageException::newWithMessage( null,
1253 [
1254 'apierror-multival-only-one-of',
1255 'myParam',
1256 Message::listParam( [ '<kbd>a</kbd>', '<kbd>b</kbd>' ] ),
1257 2
1258 ],
1259 'multival_myParam'
1260 ),
1261 [],
1262 ],
1263 ];
1264
1265 // The following really just test PHP's string-to-int conversion.
1266 $integerTests = [
1267 [ '+1', 1 ],
1268 [ '-1', -1 ],
1269 [ '1.5', 1 ],
1270 [ '-1.5', -1 ],
1271 [ '1abc', 1 ],
1272 [ ' 1', 1 ],
1273 [ "\t1", 1, '\t1' ],
1274 [ "\r1", 1, '\r1' ],
1275 [ "\f1", 0, '\f1', 'badutf-8' ],
1276 [ "\n1", 1, '\n1' ],
1277 [ "\v1", 0, '\v1', 'badutf-8' ],
1278 [ "\e1", 0, '\e1', 'badutf-8' ],
1279 [ "\x001", 0, '\x001', 'badutf-8' ],
1280 ];
1281
1282 foreach ( $integerTests as $test ) {
1283 $desc = $test[2] ?? $test[0];
1284 $warnings = isset( $test[3] ) ?
1285 [ [ 'apiwarn-badutf8', 'myParam' ] ] : [];
1286 $returnArray["\"$desc\" as integer"] = [
1287 $test[0],
1288 [ ApiBase::PARAM_TYPE => 'integer' ],
1289 $test[1],
1290 $warnings,
1291 ];
1292 }
1293
1294 return $returnArray;
1295 }
1296
1297 public function testErrorArrayToStatus() {
1298 $mock = new MockApi();
1299
1300 // Sanity check empty array
1301 $expect = Status::newGood();
1302 $this->assertEquals( $expect, $mock->errorArrayToStatus( [] ) );
1303
1304 // No blocked $user, so no special block handling
1305 $expect = Status::newGood();
1306 $expect->fatal( 'blockedtext' );
1307 $expect->fatal( 'autoblockedtext' );
1308 $expect->fatal( 'systemblockedtext' );
1309 $expect->fatal( 'mainpage' );
1310 $expect->fatal( 'parentheses', 'foobar' );
1311 $this->assertEquals( $expect, $mock->errorArrayToStatus( [
1312 [ 'blockedtext' ],
1313 [ 'autoblockedtext' ],
1314 [ 'systemblockedtext' ],
1315 'mainpage',
1316 [ 'parentheses', 'foobar' ],
1317 ] ) );
1318
1319 // Has a blocked $user, so special block handling
1320 $user = $this->getMutableTestUser()->getUser();
1321 $block = new \Block( [
1322 'address' => $user->getName(),
1323 'user' => $user->getID(),
1324 'by' => $this->getTestSysop()->getUser()->getId(),
1325 'reason' => __METHOD__,
1326 'expiry' => time() + 100500,
1327 ] );
1328 $block->insert();
1329 $blockinfo = [ 'blockinfo' => ApiQueryUserInfo::getBlockInfo( $block ) ];
1330
1331 $expect = Status::newGood();
1332 $expect->fatal( ApiMessage::create( 'apierror-blocked', 'blocked', $blockinfo ) );
1333 $expect->fatal( ApiMessage::create( 'apierror-autoblocked', 'autoblocked', $blockinfo ) );
1334 $expect->fatal( ApiMessage::create( 'apierror-systemblocked', 'blocked', $blockinfo ) );
1335 $expect->fatal( 'mainpage' );
1336 $expect->fatal( 'parentheses', 'foobar' );
1337 $this->assertEquals( $expect, $mock->errorArrayToStatus( [
1338 [ 'blockedtext' ],
1339 [ 'autoblockedtext' ],
1340 [ 'systemblockedtext' ],
1341 'mainpage',
1342 [ 'parentheses', 'foobar' ],
1343 ], $user ) );
1344 }
1345
1346 public function testAddBlockInfoToStatus() {
1347 $mock = new MockApi();
1348
1349 // Sanity check empty array
1350 $expect = Status::newGood();
1351 $test = Status::newGood();
1352 $mock->addBlockInfoToStatus( $test );
1353 $this->assertEquals( $expect, $test );
1354
1355 // No blocked $user, so no special block handling
1356 $expect = Status::newGood();
1357 $expect->fatal( 'blockedtext' );
1358 $expect->fatal( 'autoblockedtext' );
1359 $expect->fatal( 'systemblockedtext' );
1360 $expect->fatal( 'mainpage' );
1361 $expect->fatal( 'parentheses', 'foobar' );
1362 $test = clone $expect;
1363 $mock->addBlockInfoToStatus( $test );
1364 $this->assertEquals( $expect, $test );
1365
1366 // Has a blocked $user, so special block handling
1367 $user = $this->getMutableTestUser()->getUser();
1368 $block = new \Block( [
1369 'address' => $user->getName(),
1370 'user' => $user->getID(),
1371 'by' => $this->getTestSysop()->getUser()->getId(),
1372 'reason' => __METHOD__,
1373 'expiry' => time() + 100500,
1374 ] );
1375 $block->insert();
1376 $blockinfo = [ 'blockinfo' => ApiQueryUserInfo::getBlockInfo( $block ) ];
1377
1378 $expect = Status::newGood();
1379 $expect->fatal( ApiMessage::create( 'apierror-blocked', 'blocked', $blockinfo ) );
1380 $expect->fatal( ApiMessage::create( 'apierror-autoblocked', 'autoblocked', $blockinfo ) );
1381 $expect->fatal( ApiMessage::create( 'apierror-systemblocked', 'blocked', $blockinfo ) );
1382 $expect->fatal( 'mainpage' );
1383 $expect->fatal( 'parentheses', 'foobar' );
1384 $test = Status::newGood();
1385 $test->fatal( 'blockedtext' );
1386 $test->fatal( 'autoblockedtext' );
1387 $test->fatal( 'systemblockedtext' );
1388 $test->fatal( 'mainpage' );
1389 $test->fatal( 'parentheses', 'foobar' );
1390 $mock->addBlockInfoToStatus( $test, $user );
1391 $this->assertEquals( $expect, $test );
1392 }
1393
1394 public function testDieStatus() {
1395 $mock = new MockApi();
1396
1397 $status = StatusValue::newGood();
1398 $status->error( 'foo' );
1399 $status->warning( 'bar' );
1400 try {
1401 $mock->dieStatus( $status );
1402 $this->fail( 'Expected exception not thrown' );
1403 } catch ( ApiUsageException $ex ) {
1404 $this->assertTrue( ApiTestCase::apiExceptionHasCode( $ex, 'foo' ), 'Exception has "foo"' );
1405 $this->assertFalse( ApiTestCase::apiExceptionHasCode( $ex, 'bar' ), 'Exception has "bar"' );
1406 }
1407
1408 $status = StatusValue::newGood();
1409 $status->warning( 'foo' );
1410 $status->warning( 'bar' );
1411 try {
1412 $mock->dieStatus( $status );
1413 $this->fail( 'Expected exception not thrown' );
1414 } catch ( ApiUsageException $ex ) {
1415 $this->assertTrue( ApiTestCase::apiExceptionHasCode( $ex, 'foo' ), 'Exception has "foo"' );
1416 $this->assertTrue( ApiTestCase::apiExceptionHasCode( $ex, 'bar' ), 'Exception has "bar"' );
1417 }
1418
1419 $status = StatusValue::newGood();
1420 $status->setOk( false );
1421 try {
1422 $mock->dieStatus( $status );
1423 $this->fail( 'Expected exception not thrown' );
1424 } catch ( ApiUsageException $ex ) {
1425 $this->assertTrue( ApiTestCase::apiExceptionHasCode( $ex, 'unknownerror-nocode' ),
1426 'Exception has "unknownerror-nocode"' );
1427 }
1428 }
1429
1430 /**
1431 * @covers ApiBase::extractRequestParams
1432 */
1433 public function testExtractRequestParams() {
1434 $request = new FauxRequest( [
1435 'xxexists' => 'exists!',
1436 'xxmulti' => 'a|b|c|d|{bad}',
1437 'xxempty' => '',
1438 'xxtemplate-a' => 'A!',
1439 'xxtemplate-b' => 'B1|B2|B3',
1440 'xxtemplate-c' => '',
1441 'xxrecursivetemplate-b-B1' => 'X',
1442 'xxrecursivetemplate-b-B3' => 'Y',
1443 'xxrecursivetemplate-b-B4' => '?',
1444 'xxemptytemplate-' => 'nope',
1445 'foo' => 'a|b|c',
1446 'xxfoo' => 'a|b|c',
1447 'errorformat' => 'raw',
1448 ] );
1449 $context = new DerivativeContext( RequestContext::getMain() );
1450 $context->setRequest( $request );
1451 $main = new ApiMain( $context );
1452
1453 $mock = $this->getMockBuilder( ApiBase::class )
1454 ->setConstructorArgs( [ $main, 'test', 'xx' ] )
1455 ->setMethods( [ 'getAllowedParams' ] )
1456 ->getMockForAbstractClass();
1457 $mock->method( 'getAllowedParams' )->willReturn( [
1458 'notexists' => null,
1459 'exists' => null,
1460 'multi' => [
1461 ApiBase::PARAM_ISMULTI => true,
1462 ],
1463 'empty' => [
1464 ApiBase::PARAM_ISMULTI => true,
1465 ],
1466 'template-{m}' => [
1467 ApiBase::PARAM_ISMULTI => true,
1468 ApiBase::PARAM_TEMPLATE_VARS => [ 'm' => 'multi' ],
1469 ],
1470 'recursivetemplate-{m}-{t}' => [
1471 ApiBase::PARAM_TEMPLATE_VARS => [ 't' => 'template-{m}', 'm' => 'multi' ],
1472 ],
1473 'emptytemplate-{m}' => [
1474 ApiBase::PARAM_ISMULTI => true,
1475 ApiBase::PARAM_TEMPLATE_VARS => [ 'm' => 'empty' ],
1476 ],
1477 'badtemplate-{e}' => [
1478 ApiBase::PARAM_TEMPLATE_VARS => [ 'e' => 'exists' ],
1479 ],
1480 'badtemplate2-{e}' => [
1481 ApiBase::PARAM_TEMPLATE_VARS => [ 'e' => 'badtemplate2-{e}' ],
1482 ],
1483 'badtemplate3-{x}' => [
1484 ApiBase::PARAM_TEMPLATE_VARS => [ 'x' => 'foo' ],
1485 ],
1486 ] );
1487
1488 $this->assertEquals( [
1489 'notexists' => null,
1490 'exists' => 'exists!',
1491 'multi' => [ 'a', 'b', 'c', 'd', '{bad}' ],
1492 'empty' => [],
1493 'template-a' => [ 'A!' ],
1494 'template-b' => [ 'B1', 'B2', 'B3' ],
1495 'template-c' => [],
1496 'template-d' => null,
1497 'recursivetemplate-a-A!' => null,
1498 'recursivetemplate-b-B1' => 'X',
1499 'recursivetemplate-b-B2' => null,
1500 'recursivetemplate-b-B3' => 'Y',
1501 ], $mock->extractRequestParams() );
1502
1503 $used = TestingAccessWrapper::newFromObject( $main )->getParamsUsed();
1504 sort( $used );
1505 $this->assertEquals( [
1506 'xxempty',
1507 'xxexists',
1508 'xxmulti',
1509 'xxnotexists',
1510 'xxrecursivetemplate-a-A!',
1511 'xxrecursivetemplate-b-B1',
1512 'xxrecursivetemplate-b-B2',
1513 'xxrecursivetemplate-b-B3',
1514 'xxtemplate-a',
1515 'xxtemplate-b',
1516 'xxtemplate-c',
1517 'xxtemplate-d',
1518 ], $used );
1519
1520 $warnings = $mock->getResult()->getResultData( 'warnings', [ 'Strip' => 'all' ] );
1521 $this->assertCount( 1, $warnings );
1522 $this->assertSame( 'ignoring-invalid-templated-value', $warnings[0]['code'] );
1523 }
1524
1525 }