3 namespace MediaWiki\Tests\Api
;
6 use LocalizedException
;
7 use MediaWiki\Api\ApiErrorFormatter
;
8 use MediaWiki\Api\ApiErrorFormatter_BackCompat
;
9 use MediaWiki\Api\ApiMessage
;
10 use MediaWiki\Api\ApiResult
;
11 use MediaWiki\Api\IApiMessage
;
12 use MediaWiki\Language\RawMessage
;
13 use MediaWiki\Message\Message
;
14 use MediaWiki\Status\Status
;
15 use MediaWikiLangTestCase
;
17 use Wikimedia\TestingAccessWrapper
;
22 class ApiErrorFormatterTest
extends MediaWikiLangTestCase
{
25 * @covers \MediaWiki\Api\ApiErrorFormatter
27 public function testErrorFormatterBasics() {
28 $result = new ApiResult( 8_388_608
);
29 $formatter = new ApiErrorFormatter( $result,
30 $this->getServiceContainer()->getLanguageFactory()->getLanguage( 'de' ), 'wikitext',
32 $this->assertSame( 'de', $formatter->getLanguage()->getCode() );
33 $this->assertSame( 'wikitext', $formatter->getFormat() );
35 $formatter->addMessagesFromStatus( null, Status
::newGood() );
37 [ ApiResult
::META_TYPE
=> 'assoc' ],
38 $result->getResultData()
41 $this->assertSame( [], $formatter->arrayFromStatus( Status
::newGood() ) );
43 $wrappedFormatter = TestingAccessWrapper
::newFromObject( $formatter );
45 'Blah "kbd" <X> 😊',
46 $wrappedFormatter->stripMarkup( 'Blah <kbd>kbd</kbd> <b><X></b> 😊' ),
52 * @covers \MediaWiki\Api\ApiErrorFormatter
53 * @covers \MediaWiki\Api\ApiErrorFormatter_BackCompat
55 public function testNewWithFormat() {
56 $result = new ApiResult( 8_388_608
);
57 $formatter = new ApiErrorFormatter( $result,
58 $this->getServiceContainer()->getLanguageFactory()->getLanguage( 'de' ), 'wikitext',
60 $formatter2 = $formatter->newWithFormat( 'html' );
62 $this->assertSame( $formatter->getLanguage(), $formatter2->getLanguage() );
63 $this->assertSame( 'html', $formatter2->getFormat() );
65 $formatter3 = new ApiErrorFormatter_BackCompat( $result );
66 $formatter4 = $formatter3->newWithFormat( 'html' );
67 $this->assertNotInstanceOf( ApiErrorFormatter_BackCompat
::class, $formatter4 );
68 $this->assertSame( $formatter3->getLanguage(), $formatter4->getLanguage() );
69 $this->assertSame( 'html', $formatter4->getFormat() );
73 * @covers \MediaWiki\Api\ApiErrorFormatter
74 * @dataProvider provideErrorFormatter
76 public function testErrorFormatter( $format, $lang, $useDB,
77 $expect1, $expect2, $expect3
79 $result = new ApiResult( 8_388_608
);
80 $formatter = new ApiErrorFormatter( $result,
81 $this->getServiceContainer()->getLanguageFactory()->getLanguage( $lang ), $format,
85 $expect1[ApiResult
::META_TYPE
] = 'assoc';
86 $expect2[ApiResult
::META_TYPE
] = 'assoc';
87 $expect3[ApiResult
::META_TYPE
] = 'assoc';
89 $formatter->addWarning( 'string', 'mainpage' );
90 $formatter->addError( 'err', 'aboutpage' );
91 $this->assertEquals( $expect1, $result->getResultData(), 'Simple test' );
94 $formatter->addWarning( 'foo', 'mainpage' );
95 $formatter->addWarning( 'foo', 'mainpage' );
96 $formatter->addWarning( 'foo', [ 'parentheses', 'foobar' ] );
97 $msg1 = wfMessage( 'copyright' );
98 $formatter->addWarning( 'message', $msg1 );
99 $msg2 = new ApiMessage( 'disclaimers', 'overriddenCode', [ 'overriddenData' => true ] );
100 $formatter->addWarning( 'messageWithData', $msg2 );
101 $msg3 = new ApiMessage( 'edithelp', 'overriddenCode', [ 'overriddenData' => true ] );
102 $formatter->addError( 'errWithData', $msg3 );
103 $this->assertSame( $expect2, $result->getResultData(), 'Complex test' );
106 $this->removeModuleTag( $expect2['warnings'][2] ),
107 $formatter->formatMessage( $msg1 ),
108 'formatMessage test 1'
111 $this->removeModuleTag( $expect2['warnings'][3] ),
112 $formatter->formatMessage( $msg2 ),
113 'formatMessage test 2'
117 $status = Status
::newGood();
118 $status->warning( 'mainpage' );
119 $status->warning( 'parentheses', 'foobar' );
120 $status->warning( $msg1 );
121 $status->warning( $msg2 );
122 $status->error( 'aboutpage' );
123 $status->error( 'brackets', 'foobar' );
124 $formatter->addMessagesFromStatus( 'status', $status );
125 $this->assertSame( $expect3, $result->getResultData(), 'Status test' );
128 array_map( [ $this, 'removeModuleTag' ], $expect3['errors'] ),
129 $formatter->arrayFromStatus( $status, 'error' ),
130 'arrayFromStatus test for error'
133 array_map( [ $this, 'removeModuleTag' ], $expect3['warnings'] ),
134 $formatter->arrayFromStatus( $status, 'warning' ),
135 'arrayFromStatus test for warning'
139 private function removeModuleTag( $s ) {
140 if ( is_array( $s ) ) {
141 unset( $s['module'] );
146 private static function text( $msg ) {
147 return $msg->inLanguage( 'de' )->useDatabase( false )->text();
150 private static function html( $msg ) {
151 return $msg->inLanguage( 'en' )->parse();
154 public static function provideErrorFormatter() {
155 $aboutpage = wfMessage( 'aboutpage' );
156 $mainpage = wfMessage( 'mainpage' );
157 $parens = wfMessage( 'parentheses', 'foobar' );
158 $brackets = wfMessage( 'brackets', 'foobar' );
159 $copyright = wfMessage( 'copyright' );
160 $disclaimers = wfMessage( 'disclaimers' );
161 $edithelp = wfMessage( 'edithelp' );
163 $C = ApiResult
::META_CONTENT
;
164 $I = ApiResult
::META_INDEXED_TAG_NAME
;
165 $overriddenData = [ 'overriddenData' => true, ApiResult
::META_TYPE
=> 'assoc' ];
168 'zero' => $tmp = [ 'wikitext', 'de', false,
171 [ 'code' => 'aboutpage', 'text' => self
::text( $aboutpage ), 'module' => 'err', $C => 'text' ],
175 [ 'code' => 'mainpage', 'text' => self
::text( $mainpage ), 'module' => 'string', $C => 'text' ],
181 [ 'code' => 'overriddenCode', 'text' => self
::text( $edithelp ),
182 'data' => $overriddenData, 'module' => 'errWithData', $C => 'text' ],
186 [ 'code' => 'mainpage', 'text' => self
::text( $mainpage ), 'module' => 'foo', $C => 'text' ],
187 [ 'code' => 'parentheses', 'text' => self
::text( $parens ), 'module' => 'foo', $C => 'text' ],
188 [ 'code' => 'copyright', 'text' => self
::text( $copyright ),
189 'module' => 'message', $C => 'text' ],
190 [ 'code' => 'overriddenCode', 'text' => self
::text( $disclaimers ),
191 'data' => $overriddenData, 'module' => 'messageWithData', $C => 'text' ],
197 [ 'code' => 'aboutpage', 'text' => self
::text( $aboutpage ),
198 'module' => 'status', $C => 'text' ],
199 [ 'code' => 'brackets', 'text' => self
::text( $brackets ), 'module' => 'status', $C => 'text' ],
203 [ 'code' => 'mainpage', 'text' => self
::text( $mainpage ), 'module' => 'status', $C => 'text' ],
204 [ 'code' => 'parentheses', 'text' => self
::text( $parens ),
205 'module' => 'status', $C => 'text' ],
206 [ 'code' => 'copyright', 'text' => self
::text( $copyright ),
207 'module' => 'status', $C => 'text' ],
208 [ 'code' => 'overriddenCode', 'text' => self
::text( $disclaimers ),
209 'data' => $overriddenData, 'module' => 'status', $C => 'text' ],
214 'one' => [ 'plaintext' ] +
$tmp, // For these messages, plaintext and wikitext are the same
215 'two' => [ 'html', 'en', true,
218 [ 'code' => 'aboutpage', 'html' => self
::html( $aboutpage ), 'module' => 'err', $C => 'html' ],
222 [ 'code' => 'mainpage', 'html' => self
::html( $mainpage ), 'module' => 'string', $C => 'html' ],
228 [ 'code' => 'overriddenCode', 'html' => self
::html( $edithelp ),
229 'data' => $overriddenData, 'module' => 'errWithData', $C => 'html' ],
233 [ 'code' => 'mainpage', 'html' => self
::html( $mainpage ), 'module' => 'foo', $C => 'html' ],
234 [ 'code' => 'parentheses', 'html' => self
::html( $parens ), 'module' => 'foo', $C => 'html' ],
235 [ 'code' => 'copyright', 'html' => self
::html( $copyright ),
236 'module' => 'message', $C => 'html' ],
237 [ 'code' => 'overriddenCode', 'html' => self
::html( $disclaimers ),
238 'data' => $overriddenData, 'module' => 'messageWithData', $C => 'html' ],
244 [ 'code' => 'aboutpage', 'html' => self
::html( $aboutpage ),
245 'module' => 'status', $C => 'html' ],
246 [ 'code' => 'brackets', 'html' => self
::html( $brackets ), 'module' => 'status', $C => 'html' ],
250 [ 'code' => 'mainpage', 'html' => self
::html( $mainpage ), 'module' => 'status', $C => 'html' ],
251 [ 'code' => 'parentheses', 'html' => self
::html( $parens ),
252 'module' => 'status', $C => 'html' ],
253 [ 'code' => 'copyright', 'html' => self
::html( $copyright ),
254 'module' => 'status', $C => 'html' ],
255 [ 'code' => 'overriddenCode', 'html' => self
::html( $disclaimers ),
256 'data' => $overriddenData, 'module' => 'status', $C => 'html' ],
261 'three' => [ 'raw', 'fr', true,
265 'code' => 'aboutpage',
266 'key' => 'aboutpage',
267 'params' => [ $I => 'param' ],
274 'code' => 'mainpage',
276 'params' => [ $I => 'param' ],
277 'module' => 'string',
285 'code' => 'overriddenCode',
287 'params' => [ $I => 'param' ],
288 'data' => $overriddenData,
289 'module' => 'errWithData',
295 'code' => 'mainpage',
297 'params' => [ $I => 'param' ],
301 'code' => 'parentheses',
302 'key' => 'parentheses',
303 'params' => [ 'foobar', $I => 'param' ],
307 'code' => 'copyright',
308 'key' => 'copyright',
309 'params' => [ $I => 'param' ],
310 'module' => 'message',
313 'code' => 'overriddenCode',
314 'key' => 'disclaimers',
315 'params' => [ $I => 'param' ],
316 'data' => $overriddenData,
317 'module' => 'messageWithData',
325 'code' => 'aboutpage',
326 'key' => 'aboutpage',
327 'params' => [ $I => 'param' ],
328 'module' => 'status',
331 'code' => 'brackets',
333 'params' => [ 'foobar', $I => 'param' ],
334 'module' => 'status',
340 'code' => 'mainpage',
342 'params' => [ $I => 'param' ],
343 'module' => 'status',
346 'code' => 'parentheses',
347 'key' => 'parentheses',
348 'params' => [ 'foobar', $I => 'param' ],
349 'module' => 'status',
352 'code' => 'copyright',
353 'key' => 'copyright',
354 'params' => [ $I => 'param' ],
355 'module' => 'status',
358 'code' => 'overriddenCode',
359 'key' => 'disclaimers',
360 'params' => [ $I => 'param' ],
361 'data' => $overriddenData,
362 'module' => 'status',
368 'four' => [ 'none', 'fr', true,
371 [ 'code' => 'aboutpage', 'module' => 'err' ],
375 [ 'code' => 'mainpage', 'module' => 'string' ],
381 [ 'code' => 'overriddenCode', 'data' => $overriddenData,
382 'module' => 'errWithData' ],
386 [ 'code' => 'mainpage', 'module' => 'foo' ],
387 [ 'code' => 'parentheses', 'module' => 'foo' ],
388 [ 'code' => 'copyright', 'module' => 'message' ],
389 [ 'code' => 'overriddenCode', 'data' => $overriddenData,
390 'module' => 'messageWithData' ],
396 [ 'code' => 'aboutpage', 'module' => 'status' ],
397 [ 'code' => 'brackets', 'module' => 'status' ],
401 [ 'code' => 'mainpage', 'module' => 'status' ],
402 [ 'code' => 'parentheses', 'module' => 'status' ],
403 [ 'code' => 'copyright', 'module' => 'status' ],
404 [ 'code' => 'overriddenCode', 'data' => $overriddenData, 'module' => 'status' ],
413 * @covers \MediaWiki\Api\ApiErrorFormatter_BackCompat
415 public function testErrorFormatterBC() {
416 $aboutpage = wfMessage( 'aboutpage' );
417 $mainpage = wfMessage( 'mainpage' );
418 $parens = wfMessage( 'parentheses', 'foobar' );
419 $copyright = wfMessage( 'copyright' );
420 $disclaimers = wfMessage( 'disclaimers' );
421 $edithelp = wfMessage( 'edithelp' );
423 $result = new ApiResult( 8_388_608
);
424 $formatter = new ApiErrorFormatter_BackCompat( $result );
426 $this->assertSame( 'en', $formatter->getLanguage()->getCode() );
427 $this->assertSame( 'bc', $formatter->getFormat() );
429 $this->assertSame( [], $formatter->arrayFromStatus( Status
::newGood() ) );
431 $formatter->addWarning( 'string', 'mainpage' );
432 $formatter->addWarning( 'raw',
433 new RawMessage( 'Blah <kbd>kbd</kbd> <b><X></b> 😞' )
435 $formatter->addError( 'err', 'aboutpage' );
438 'code' => 'aboutpage',
439 'info' => $aboutpage->useDatabase( false )->plain(),
443 'warnings' => 'Blah "kbd" <X> 😞',
444 ApiResult
::META_CONTENT
=> 'warnings',
447 'warnings' => $mainpage->useDatabase( false )->plain(),
448 ApiResult
::META_CONTENT
=> 'warnings',
451 ApiResult
::META_TYPE
=> 'assoc',
452 ], $result->getResultData(), 'Simple test' );
455 $formatter->addWarning( 'foo', 'mainpage' );
456 $formatter->addWarning( 'foo', 'mainpage' );
457 $formatter->addWarning( 'xxx+foo', [ 'parentheses', 'foobar' ] );
458 $msg1 = wfMessage( 'copyright' );
459 $formatter->addWarning( 'message', $msg1 );
460 $msg2 = new ApiMessage( 'disclaimers', 'overriddenCode', [ 'overriddenData' => true ] );
461 $formatter->addWarning( 'messageWithData', $msg2 );
462 $msg3 = new ApiMessage( 'edithelp', 'overriddenCode', [ 'overriddenData' => true ] );
463 $formatter->addError( 'errWithData', $msg3 );
464 $formatter->addWarning( null, 'mainpage' );
467 'code' => 'overriddenCode',
468 'info' => $edithelp->useDatabase( false )->plain(),
469 'overriddenData' => true,
473 'warnings' => $mainpage->useDatabase( false )->plain(),
474 ApiResult
::META_CONTENT
=> 'warnings',
476 'messageWithData' => [
477 'warnings' => $disclaimers->useDatabase( false )->plain(),
478 ApiResult
::META_CONTENT
=> 'warnings',
481 'warnings' => $copyright->useDatabase( false )->plain(),
482 ApiResult
::META_CONTENT
=> 'warnings',
485 'warnings' => $mainpage->useDatabase( false )->plain()
486 . "\n" . $parens->useDatabase( false )->plain(),
487 ApiResult
::META_CONTENT
=> 'warnings',
490 ApiResult
::META_TYPE
=> 'assoc',
491 ], $result->getResultData(), 'Complex test' );
495 'code' => 'copyright',
496 'info' => $copyright->useDatabase( false )->plain(),
498 $formatter->formatMessage( $msg1 )
502 'code' => 'overriddenCode',
503 'info' => $disclaimers->useDatabase( false )->plain(),
504 'overriddenData' => true,
506 $formatter->formatMessage( $msg2 )
510 'code' => 'overriddenCode',
511 'info' => $edithelp->useDatabase( false )->plain(),
512 'overriddenData' => true,
514 $formatter->formatMessage( $msg3 )
518 $status = Status
::newGood();
519 $status->warning( 'mainpage' );
520 $status->warning( 'parentheses', 'foobar' );
521 $status->warning( $msg1 );
522 $status->warning( $msg2 );
523 $status->error( 'aboutpage' );
524 $status->error( 'brackets', 'foobar' );
525 $formatter->addMessagesFromStatus( 'status', $status );
528 'code' => 'aboutpage',
529 'info' => $aboutpage->useDatabase( false )->plain(),
533 'warnings' => $mainpage->useDatabase( false )->plain()
534 . "\n" . $parens->useDatabase( false )->plain()
535 . "\n" . $copyright->useDatabase( false )->plain()
536 . "\n" . $disclaimers->useDatabase( false )->plain(),
537 ApiResult
::META_CONTENT
=> 'warnings',
540 ApiResult
::META_TYPE
=> 'assoc',
541 ], $result->getResultData(), 'Status test' );
543 $I = ApiResult
::META_INDEXED_TAG_NAME
;
547 'message' => 'aboutpage',
548 'params' => [ $I => 'param' ],
549 'code' => 'aboutpage',
553 'message' => 'brackets',
554 'params' => [ 'foobar', $I => 'param' ],
555 'code' => 'brackets',
560 $formatter->arrayFromStatus( $status, 'error' ),
561 'arrayFromStatus test for error'
566 'message' => 'mainpage',
567 'params' => [ $I => 'param' ],
568 'code' => 'mainpage',
572 'message' => 'parentheses',
573 'params' => [ 'foobar', $I => 'param' ],
574 'code' => 'parentheses',
578 'message' => 'copyright',
579 'params' => [ $I => 'param' ],
580 'code' => 'copyright',
584 'message' => 'disclaimers',
585 'params' => [ $I => 'param' ],
586 'code' => 'overriddenCode',
591 $formatter->arrayFromStatus( $status, 'warning' ),
592 'arrayFromStatus test for warning'
596 $result->addValue( null, 'error', [ 'bogus' ] );
597 $formatter->addError( 'err', 'aboutpage' );
600 'code' => 'aboutpage',
601 'info' => $aboutpage->useDatabase( false )->plain(),
603 ApiResult
::META_TYPE
=> 'assoc',
604 ], $result->getResultData(), 'Overwrites bogus "error" value with real error' );
608 * @dataProvider provideGetMessageFromException
609 * @covers \MediaWiki\Api\ApiErrorFormatter::getMessageFromException
610 * @covers \MediaWiki\Api\ApiErrorFormatter::formatException
611 * @param Exception $exception
612 * @param array $options
613 * @param array $expect
615 public function testGetMessageFromException( $exception, $options, $expect ) {
616 $result = new ApiResult( 8_388_608
);
617 $formatter = new ApiErrorFormatter( $result,
618 $this->getServiceContainer()->getLanguageFactory()->getLanguage( 'en' ), 'html',
621 $msg = $formatter->getMessageFromException( $exception, $options );
622 $this->assertInstanceOf( Message
::class, $msg );
623 $this->assertInstanceOf( IApiMessage
::class, $msg );
624 $this->assertSame( $expect, [
625 'text' => $msg->parse(),
626 'code' => $msg->getApiCode(),
627 'data' => $msg->getApiData(),
630 $expectFormatted = $formatter->formatMessage( $msg );
631 $formatted = $formatter->formatException( $exception, $options );
632 $this->assertSame( $expectFormatted, $formatted );
636 * @dataProvider provideGetMessageFromException
637 * @covers \MediaWiki\Api\ApiErrorFormatter_BackCompat::formatException
638 * @param Exception $exception
639 * @param array $options
640 * @param array $expect
642 public function testGetMessageFromException_BC( $exception, $options, $expect ) {
643 $result = new ApiResult( 8_388_608
);
644 $formatter = new ApiErrorFormatter_BackCompat( $result );
646 $msg = $formatter->getMessageFromException( $exception, $options );
647 $this->assertInstanceOf( Message
::class, $msg );
648 $this->assertInstanceOf( IApiMessage
::class, $msg );
649 $this->assertSame( $expect, [
650 'text' => $msg->parse(),
651 'code' => $msg->getApiCode(),
652 'data' => $msg->getApiData(),
655 $expectFormatted = $formatter->formatMessage( $msg );
656 $formatted = $formatter->formatException( $exception, $options );
657 $this->assertSame( $expectFormatted, $formatted );
658 $formatted = $formatter->formatException( $exception, $options +
[ 'bc' => true ] );
659 $this->assertSame( $expectFormatted['info'], $formatted );
662 public static function provideGetMessageFromException() {
664 'Normal exception' => [
665 new RuntimeException( '<b>Something broke!</b>' ),
668 'text' => '<b>Something broke!</b>',
669 'code' => 'internal_api_error_RuntimeException',
671 'errorclass' => 'RuntimeException',
675 'Normal exception, wrapped' => [
676 new RuntimeException( '<b>Something broke!</b>' ),
677 [ 'wrap' => 'parentheses', 'code' => 'some-code', 'data' => [ 'foo' => 'bar', 'baz' => 42 ] ],
679 'text' => '(<b>Something broke!</b>)',
680 'code' => 'some-code',
681 'data' => [ 'foo' => 'bar', 'baz' => 42 ],
684 'LocalizedException' => [
685 new LocalizedException( [ 'returnto', '<b>FooBar</b>' ] ),
688 'text' => 'Return to <b>FooBar</b>.',
689 'code' => 'returnto',
693 'LocalizedException, wrapped' => [
694 new LocalizedException( [ 'returnto', '<b>FooBar</b>' ] ),
695 [ 'wrap' => 'parentheses', 'code' => 'some-code', 'data' => [ 'foo' => 'bar', 'baz' => 42 ] ],
697 'text' => 'Return to <b>FooBar</b>.',
698 'code' => 'some-code',
699 'data' => [ 'foo' => 'bar', 'baz' => 42 ],
706 * @covers \MediaWiki\Api\ApiErrorFormatter::addMessagesFromStatus
707 * @covers \MediaWiki\Api\ApiErrorFormatter::addWarningOrError
708 * @covers \MediaWiki\Api\ApiErrorFormatter::formatMessageInternal
710 public function testAddMessagesFromStatus_filter() {
711 $result = new ApiResult( 8_388_608
);
712 $formatter = new ApiErrorFormatter( $result,
713 $this->getServiceContainer()->getLanguageFactory()->getLanguage( 'qqx' ),
714 'plaintext', false );
716 $status = Status
::newGood();
717 $status->warning( 'mainpage' );
718 $status->warning( 'parentheses', 'foobar' );
719 $status->warning( wfMessage( 'mainpage' ) );
720 $status->error( 'mainpage' );
721 $status->error( 'parentheses', 'foobaz' );
722 $formatter->addMessagesFromStatus( 'status', $status, [ 'warning', 'error' ], [ 'mainpage' ] );
726 'code' => 'parentheses',
727 'text' => '(parentheses: foobaz)',
728 'module' => 'status',
729 ApiResult
::META_CONTENT
=> 'text',
731 ApiResult
::META_INDEXED_TAG_NAME
=> 'error',
735 'code' => 'parentheses',
736 'text' => '(parentheses: foobar)',
737 'module' => 'status',
738 ApiResult
::META_CONTENT
=> 'text',
740 ApiResult
::META_INDEXED_TAG_NAME
=> 'warning',
742 ApiResult
::META_TYPE
=> 'assoc',
743 ], $result->getResultData() );
747 * @dataProvider provideIsValidApiCode
748 * @covers \MediaWiki\Api\ApiErrorFormatter::isValidApiCode
749 * @param string $code
750 * @param bool $expect
752 public function testIsValidApiCode( $code, $expect ) {
753 $this->assertSame( $expect, ApiErrorFormatter
::isValidApiCode( $code ) );
756 public static function provideIsValidApiCode() {
758 [ 'foo-bar_Baz123', true ],
759 [ 'foo bar', false ],
760 [ 'foo\\bar', false ],
761 [ 'internal_api_error_foo\\bar baz', true ],