Merge "Change 'editfont' default preference to 'monospace'"
[lhc/web/wiklou.git] / tests / phpunit / includes / api / ApiErrorFormatterTest.php
1 <?php
2
3 use Wikimedia\TestingAccessWrapper;
4
5 /**
6 * @group API
7 */
8 class ApiErrorFormatterTest extends MediaWikiLangTestCase {
9
10 /**
11 * @covers ApiErrorFormatter
12 */
13 public function testErrorFormatterBasics() {
14 $result = new ApiResult( 8388608 );
15 $formatter = new ApiErrorFormatter( $result, Language::factory( 'de' ), 'wikitext', false );
16 $this->assertSame( 'de', $formatter->getLanguage()->getCode() );
17
18 $formatter->addMessagesFromStatus( null, Status::newGood() );
19 $this->assertSame(
20 [ ApiResult::META_TYPE => 'assoc' ],
21 $result->getResultData()
22 );
23
24 $this->assertSame( [], $formatter->arrayFromStatus( Status::newGood() ) );
25
26 $wrappedFormatter = TestingAccessWrapper::newFromObject( $formatter );
27 $this->assertSame(
28 'Blah "kbd" <X> 😊',
29 $wrappedFormatter->stripMarkup( 'Blah <kbd>kbd</kbd> <b>&lt;X&gt;</b> &#x1f60a;' ),
30 'stripMarkup'
31 );
32 }
33
34 /**
35 * @covers ApiErrorFormatter
36 * @dataProvider provideErrorFormatter
37 */
38 public function testErrorFormatter( $format, $lang, $useDB,
39 $expect1, $expect2, $expect3
40 ) {
41 $result = new ApiResult( 8388608 );
42 $formatter = new ApiErrorFormatter( $result, Language::factory( $lang ), $format, $useDB );
43
44 // Add default type
45 $expect1[ApiResult::META_TYPE] = 'assoc';
46 $expect2[ApiResult::META_TYPE] = 'assoc';
47 $expect3[ApiResult::META_TYPE] = 'assoc';
48
49 $formatter->addWarning( 'string', 'mainpage' );
50 $formatter->addError( 'err', 'mainpage' );
51 $this->assertEquals( $expect1, $result->getResultData(), 'Simple test' );
52
53 $result->reset();
54 $formatter->addWarning( 'foo', 'mainpage' );
55 $formatter->addWarning( 'foo', 'mainpage' );
56 $formatter->addWarning( 'foo', [ 'parentheses', 'foobar' ] );
57 $msg1 = wfMessage( 'mainpage' );
58 $formatter->addWarning( 'message', $msg1 );
59 $msg2 = new ApiMessage( 'mainpage', 'overriddenCode', [ 'overriddenData' => true ] );
60 $formatter->addWarning( 'messageWithData', $msg2 );
61 $formatter->addError( 'errWithData', $msg2 );
62 $this->assertSame( $expect2, $result->getResultData(), 'Complex test' );
63
64 $this->assertEquals(
65 $this->removeModuleTag( $expect2['warnings'][2] ),
66 $formatter->formatMessage( $msg1 ),
67 'formatMessage test 1'
68 );
69 $this->assertEquals(
70 $this->removeModuleTag( $expect2['warnings'][3] ),
71 $formatter->formatMessage( $msg2 ),
72 'formatMessage test 2'
73 );
74
75 $result->reset();
76 $status = Status::newGood();
77 $status->warning( 'mainpage' );
78 $status->warning( 'parentheses', 'foobar' );
79 $status->warning( $msg1 );
80 $status->warning( $msg2 );
81 $status->error( 'mainpage' );
82 $status->error( 'parentheses', 'foobar' );
83 $formatter->addMessagesFromStatus( 'status', $status );
84 $this->assertSame( $expect3, $result->getResultData(), 'Status test' );
85
86 $this->assertSame(
87 array_map( [ $this, 'removeModuleTag' ], $expect3['errors'] ),
88 $formatter->arrayFromStatus( $status, 'error' ),
89 'arrayFromStatus test for error'
90 );
91 $this->assertSame(
92 array_map( [ $this, 'removeModuleTag' ], $expect3['warnings'] ),
93 $formatter->arrayFromStatus( $status, 'warning' ),
94 'arrayFromStatus test for warning'
95 );
96 }
97
98 private function removeModuleTag( $s ) {
99 if ( is_array( $s ) ) {
100 unset( $s['module'] );
101 }
102 return $s;
103 }
104
105 public static function provideErrorFormatter() {
106 $mainpageText = wfMessage( 'mainpage' )->inLanguage( 'de' )->useDatabase( false )->text();
107 $parensText = wfMessage( 'parentheses', 'foobar' )->inLanguage( 'de' )
108 ->useDatabase( false )->text();
109 $mainpageHTML = wfMessage( 'mainpage' )->inLanguage( 'en' )->parse();
110 $parensHTML = wfMessage( 'parentheses', 'foobar' )->inLanguage( 'en' )->parse();
111 $C = ApiResult::META_CONTENT;
112 $I = ApiResult::META_INDEXED_TAG_NAME;
113 $overriddenData = [ 'overriddenData' => true, ApiResult::META_TYPE => 'assoc' ];
114
115 return [
116 $tmp = [ 'wikitext', 'de', false,
117 [
118 'errors' => [
119 [ 'code' => 'mainpage', 'text' => $mainpageText, 'module' => 'err', $C => 'text' ],
120 $I => 'error',
121 ],
122 'warnings' => [
123 [ 'code' => 'mainpage', 'text' => $mainpageText, 'module' => 'string', $C => 'text' ],
124 $I => 'warning',
125 ],
126 ],
127 [
128 'errors' => [
129 [ 'code' => 'overriddenCode', 'text' => $mainpageText,
130 'data' => $overriddenData, 'module' => 'errWithData', $C => 'text' ],
131 $I => 'error',
132 ],
133 'warnings' => [
134 [ 'code' => 'mainpage', 'text' => $mainpageText, 'module' => 'foo', $C => 'text' ],
135 [ 'code' => 'parentheses', 'text' => $parensText, 'module' => 'foo', $C => 'text' ],
136 [ 'code' => 'mainpage', 'text' => $mainpageText, 'module' => 'message', $C => 'text' ],
137 [ 'code' => 'overriddenCode', 'text' => $mainpageText,
138 'data' => $overriddenData, 'module' => 'messageWithData', $C => 'text' ],
139 $I => 'warning',
140 ],
141 ],
142 [
143 'errors' => [
144 [ 'code' => 'mainpage', 'text' => $mainpageText, 'module' => 'status', $C => 'text' ],
145 [ 'code' => 'parentheses', 'text' => $parensText, 'module' => 'status', $C => 'text' ],
146 $I => 'error',
147 ],
148 'warnings' => [
149 [ 'code' => 'mainpage', 'text' => $mainpageText, 'module' => 'status', $C => 'text' ],
150 [ 'code' => 'parentheses', 'text' => $parensText, 'module' => 'status', $C => 'text' ],
151 [ 'code' => 'overriddenCode', 'text' => $mainpageText,
152 'data' => $overriddenData, 'module' => 'status', $C => 'text' ],
153 $I => 'warning',
154 ],
155 ],
156 ],
157 [ 'plaintext' ] + $tmp, // For these messages, plaintext and wikitext are the same
158 [ 'html', 'en', true,
159 [
160 'errors' => [
161 [ 'code' => 'mainpage', 'html' => $mainpageHTML, 'module' => 'err', $C => 'html' ],
162 $I => 'error',
163 ],
164 'warnings' => [
165 [ 'code' => 'mainpage', 'html' => $mainpageHTML, 'module' => 'string', $C => 'html' ],
166 $I => 'warning',
167 ],
168 ],
169 [
170 'errors' => [
171 [ 'code' => 'overriddenCode', 'html' => $mainpageHTML,
172 'data' => $overriddenData, 'module' => 'errWithData', $C => 'html' ],
173 $I => 'error',
174 ],
175 'warnings' => [
176 [ 'code' => 'mainpage', 'html' => $mainpageHTML, 'module' => 'foo', $C => 'html' ],
177 [ 'code' => 'parentheses', 'html' => $parensHTML, 'module' => 'foo', $C => 'html' ],
178 [ 'code' => 'mainpage', 'html' => $mainpageHTML, 'module' => 'message', $C => 'html' ],
179 [ 'code' => 'overriddenCode', 'html' => $mainpageHTML,
180 'data' => $overriddenData, 'module' => 'messageWithData', $C => 'html' ],
181 $I => 'warning',
182 ],
183 ],
184 [
185 'errors' => [
186 [ 'code' => 'mainpage', 'html' => $mainpageHTML, 'module' => 'status', $C => 'html' ],
187 [ 'code' => 'parentheses', 'html' => $parensHTML, 'module' => 'status', $C => 'html' ],
188 $I => 'error',
189 ],
190 'warnings' => [
191 [ 'code' => 'mainpage', 'html' => $mainpageHTML, 'module' => 'status', $C => 'html' ],
192 [ 'code' => 'parentheses', 'html' => $parensHTML, 'module' => 'status', $C => 'html' ],
193 [ 'code' => 'overriddenCode', 'html' => $mainpageHTML,
194 'data' => $overriddenData, 'module' => 'status', $C => 'html' ],
195 $I => 'warning',
196 ],
197 ],
198 ],
199 [ 'raw', 'fr', true,
200 [
201 'errors' => [
202 [
203 'code' => 'mainpage',
204 'key' => 'mainpage',
205 'params' => [ $I => 'param' ],
206 'module' => 'err',
207 ],
208 $I => 'error',
209 ],
210 'warnings' => [
211 [
212 'code' => 'mainpage',
213 'key' => 'mainpage',
214 'params' => [ $I => 'param' ],
215 'module' => 'string',
216 ],
217 $I => 'warning',
218 ],
219 ],
220 [
221 'errors' => [
222 [
223 'code' => 'overriddenCode',
224 'key' => 'mainpage',
225 'params' => [ $I => 'param' ],
226 'data' => $overriddenData,
227 'module' => 'errWithData',
228 ],
229 $I => 'error',
230 ],
231 'warnings' => [
232 [
233 'code' => 'mainpage',
234 'key' => 'mainpage',
235 'params' => [ $I => 'param' ],
236 'module' => 'foo',
237 ],
238 [
239 'code' => 'parentheses',
240 'key' => 'parentheses',
241 'params' => [ 'foobar', $I => 'param' ],
242 'module' => 'foo',
243 ],
244 [
245 'code' => 'mainpage',
246 'key' => 'mainpage',
247 'params' => [ $I => 'param' ],
248 'module' => 'message',
249 ],
250 [
251 'code' => 'overriddenCode',
252 'key' => 'mainpage',
253 'params' => [ $I => 'param' ],
254 'data' => $overriddenData,
255 'module' => 'messageWithData',
256 ],
257 $I => 'warning',
258 ],
259 ],
260 [
261 'errors' => [
262 [
263 'code' => 'mainpage',
264 'key' => 'mainpage',
265 'params' => [ $I => 'param' ],
266 'module' => 'status',
267 ],
268 [
269 'code' => 'parentheses',
270 'key' => 'parentheses',
271 'params' => [ 'foobar', $I => 'param' ],
272 'module' => 'status',
273 ],
274 $I => 'error',
275 ],
276 'warnings' => [
277 [
278 'code' => 'mainpage',
279 'key' => 'mainpage',
280 'params' => [ $I => 'param' ],
281 'module' => 'status',
282 ],
283 [
284 'code' => 'parentheses',
285 'key' => 'parentheses',
286 'params' => [ 'foobar', $I => 'param' ],
287 'module' => 'status',
288 ],
289 [
290 'code' => 'overriddenCode',
291 'key' => 'mainpage',
292 'params' => [ $I => 'param' ],
293 'data' => $overriddenData,
294 'module' => 'status',
295 ],
296 $I => 'warning',
297 ],
298 ],
299 ],
300 [ 'none', 'fr', true,
301 [
302 'errors' => [
303 [ 'code' => 'mainpage', 'module' => 'err' ],
304 $I => 'error',
305 ],
306 'warnings' => [
307 [ 'code' => 'mainpage', 'module' => 'string' ],
308 $I => 'warning',
309 ],
310 ],
311 [
312 'errors' => [
313 [ 'code' => 'overriddenCode', 'data' => $overriddenData,
314 'module' => 'errWithData' ],
315 $I => 'error',
316 ],
317 'warnings' => [
318 [ 'code' => 'mainpage', 'module' => 'foo' ],
319 [ 'code' => 'parentheses', 'module' => 'foo' ],
320 [ 'code' => 'mainpage', 'module' => 'message' ],
321 [ 'code' => 'overriddenCode', 'data' => $overriddenData,
322 'module' => 'messageWithData' ],
323 $I => 'warning',
324 ],
325 ],
326 [
327 'errors' => [
328 [ 'code' => 'mainpage', 'module' => 'status' ],
329 [ 'code' => 'parentheses', 'module' => 'status' ],
330 $I => 'error',
331 ],
332 'warnings' => [
333 [ 'code' => 'mainpage', 'module' => 'status' ],
334 [ 'code' => 'parentheses', 'module' => 'status' ],
335 [ 'code' => 'overriddenCode', 'data' => $overriddenData, 'module' => 'status' ],
336 $I => 'warning',
337 ],
338 ],
339 ],
340 ];
341 }
342
343 /**
344 * @covers ApiErrorFormatter_BackCompat
345 */
346 public function testErrorFormatterBC() {
347 $mainpagePlain = wfMessage( 'mainpage' )->useDatabase( false )->plain();
348 $parensPlain = wfMessage( 'parentheses', 'foobar' )->useDatabase( false )->plain();
349
350 $result = new ApiResult( 8388608 );
351 $formatter = new ApiErrorFormatter_BackCompat( $result );
352
353 $this->assertSame( 'en', $formatter->getLanguage()->getCode() );
354
355 $this->assertSame( [], $formatter->arrayFromStatus( Status::newGood() ) );
356
357 $formatter->addWarning( 'string', 'mainpage' );
358 $formatter->addWarning( 'raw',
359 new RawMessage( 'Blah <kbd>kbd</kbd> <b>&lt;X&gt;</b> &#x1f61e;' )
360 );
361 $formatter->addError( 'err', 'mainpage' );
362 $this->assertSame( [
363 'error' => [
364 'code' => 'mainpage',
365 'info' => $mainpagePlain,
366 ],
367 'warnings' => [
368 'raw' => [
369 'warnings' => 'Blah "kbd" <X> 😞',
370 ApiResult::META_CONTENT => 'warnings',
371 ],
372 'string' => [
373 'warnings' => $mainpagePlain,
374 ApiResult::META_CONTENT => 'warnings',
375 ],
376 ],
377 ApiResult::META_TYPE => 'assoc',
378 ], $result->getResultData(), 'Simple test' );
379
380 $result->reset();
381 $formatter->addWarning( 'foo', 'mainpage' );
382 $formatter->addWarning( 'foo', 'mainpage' );
383 $formatter->addWarning( 'xxx+foo', [ 'parentheses', 'foobar' ] );
384 $msg1 = wfMessage( 'mainpage' );
385 $formatter->addWarning( 'message', $msg1 );
386 $msg2 = new ApiMessage( 'mainpage', 'overriddenCode', [ 'overriddenData' => true ] );
387 $formatter->addWarning( 'messageWithData', $msg2 );
388 $formatter->addError( 'errWithData', $msg2 );
389 $formatter->addWarning( null, 'mainpage' );
390 $this->assertSame( [
391 'error' => [
392 'code' => 'overriddenCode',
393 'info' => $mainpagePlain,
394 'overriddenData' => true,
395 ],
396 'warnings' => [
397 'unknown' => [
398 'warnings' => $mainpagePlain,
399 ApiResult::META_CONTENT => 'warnings',
400 ],
401 'messageWithData' => [
402 'warnings' => $mainpagePlain,
403 ApiResult::META_CONTENT => 'warnings',
404 ],
405 'message' => [
406 'warnings' => $mainpagePlain,
407 ApiResult::META_CONTENT => 'warnings',
408 ],
409 'foo' => [
410 'warnings' => "$mainpagePlain\n$parensPlain",
411 ApiResult::META_CONTENT => 'warnings',
412 ],
413 ],
414 ApiResult::META_TYPE => 'assoc',
415 ], $result->getResultData(), 'Complex test' );
416
417 $this->assertSame(
418 [
419 'code' => 'mainpage',
420 'info' => 'Main Page',
421 ],
422 $formatter->formatMessage( $msg1 )
423 );
424 $this->assertSame(
425 [
426 'code' => 'overriddenCode',
427 'info' => 'Main Page',
428 'overriddenData' => true,
429 ],
430 $formatter->formatMessage( $msg2 )
431 );
432
433 $result->reset();
434 $status = Status::newGood();
435 $status->warning( 'mainpage' );
436 $status->warning( 'parentheses', 'foobar' );
437 $status->warning( $msg1 );
438 $status->warning( $msg2 );
439 $status->error( 'mainpage' );
440 $status->error( 'parentheses', 'foobar' );
441 $formatter->addMessagesFromStatus( 'status', $status );
442 $this->assertSame( [
443 'error' => [
444 'code' => 'mainpage',
445 'info' => $mainpagePlain,
446 ],
447 'warnings' => [
448 'status' => [
449 'warnings' => "$mainpagePlain\n$parensPlain",
450 ApiResult::META_CONTENT => 'warnings',
451 ],
452 ],
453 ApiResult::META_TYPE => 'assoc',
454 ], $result->getResultData(), 'Status test' );
455
456 $I = ApiResult::META_INDEXED_TAG_NAME;
457 $this->assertSame(
458 [
459 [
460 'message' => 'mainpage',
461 'params' => [ $I => 'param' ],
462 'code' => 'mainpage',
463 'type' => 'error',
464 ],
465 [
466 'message' => 'parentheses',
467 'params' => [ 'foobar', $I => 'param' ],
468 'code' => 'parentheses',
469 'type' => 'error',
470 ],
471 $I => 'error',
472 ],
473 $formatter->arrayFromStatus( $status, 'error' ),
474 'arrayFromStatus test for error'
475 );
476 $this->assertSame(
477 [
478 [
479 'message' => 'mainpage',
480 'params' => [ $I => 'param' ],
481 'code' => 'mainpage',
482 'type' => 'warning',
483 ],
484 [
485 'message' => 'parentheses',
486 'params' => [ 'foobar', $I => 'param' ],
487 'code' => 'parentheses',
488 'type' => 'warning',
489 ],
490 [
491 'message' => 'mainpage',
492 'params' => [ $I => 'param' ],
493 'code' => 'mainpage',
494 'type' => 'warning',
495 ],
496 [
497 'message' => 'mainpage',
498 'params' => [ $I => 'param' ],
499 'code' => 'overriddenCode',
500 'type' => 'warning',
501 ],
502 $I => 'warning',
503 ],
504 $formatter->arrayFromStatus( $status, 'warning' ),
505 'arrayFromStatus test for warning'
506 );
507
508 $result->reset();
509 $result->addValue( null, 'error', [ 'bogus' ] );
510 $formatter->addError( 'err', 'mainpage' );
511 $this->assertSame( [
512 'error' => [
513 'code' => 'mainpage',
514 'info' => $mainpagePlain,
515 ],
516 ApiResult::META_TYPE => 'assoc',
517 ], $result->getResultData(), 'Overwrites bogus "error" value with real error' );
518 }
519
520 /**
521 * @dataProvider provideGetMessageFromException
522 * @covers ApiErrorFormatter::getMessageFromException
523 * @covers ApiErrorFormatter::formatException
524 * @param Exception $exception
525 * @param array $options
526 * @param array $expect
527 */
528 public function testGetMessageFromException( $exception, $options, $expect ) {
529 if ( $exception instanceof UsageException ) {
530 $this->hideDeprecated( 'UsageException::getMessageArray' );
531 }
532
533 $result = new ApiResult( 8388608 );
534 $formatter = new ApiErrorFormatter( $result, Language::factory( 'en' ), 'html', false );
535
536 $msg = $formatter->getMessageFromException( $exception, $options );
537 $this->assertInstanceOf( Message::class, $msg );
538 $this->assertInstanceOf( IApiMessage::class, $msg );
539 $this->assertSame( $expect, [
540 'text' => $msg->parse(),
541 'code' => $msg->getApiCode(),
542 'data' => $msg->getApiData(),
543 ] );
544
545 $expectFormatted = $formatter->formatMessage( $msg );
546 $formatted = $formatter->formatException( $exception, $options );
547 $this->assertSame( $expectFormatted, $formatted );
548 }
549
550 /**
551 * @dataProvider provideGetMessageFromException
552 * @covers ApiErrorFormatter_BackCompat::formatException
553 * @param Exception $exception
554 * @param array $options
555 * @param array $expect
556 */
557 public function testGetMessageFromException_BC( $exception, $options, $expect ) {
558 $result = new ApiResult( 8388608 );
559 $formatter = new ApiErrorFormatter_BackCompat( $result );
560
561 $msg = $formatter->getMessageFromException( $exception, $options );
562 $this->assertInstanceOf( Message::class, $msg );
563 $this->assertInstanceOf( IApiMessage::class, $msg );
564 $this->assertSame( $expect, [
565 'text' => $msg->parse(),
566 'code' => $msg->getApiCode(),
567 'data' => $msg->getApiData(),
568 ] );
569
570 $expectFormatted = $formatter->formatMessage( $msg );
571 $formatted = $formatter->formatException( $exception, $options );
572 $this->assertSame( $expectFormatted, $formatted );
573 $formatted = $formatter->formatException( $exception, $options + [ 'bc' => true ] );
574 $this->assertSame( $expectFormatted['info'], $formatted );
575 }
576
577 public static function provideGetMessageFromException() {
578 MediaWiki\suppressWarnings();
579 $usageException = new UsageException(
580 '<b>Something broke!</b>', 'ue-code', 0, [ 'xxx' => 'yyy', 'baz' => 23 ]
581 );
582 MediaWiki\restoreWarnings();
583
584 return [
585 'Normal exception' => [
586 new RuntimeException( '<b>Something broke!</b>' ),
587 [],
588 [
589 'text' => '&#60;b&#62;Something broke!&#60;/b&#62;',
590 'code' => 'internal_api_error_RuntimeException',
591 'data' => [],
592 ]
593 ],
594 'Normal exception, wrapped' => [
595 new RuntimeException( '<b>Something broke!</b>' ),
596 [ 'wrap' => 'parentheses', 'code' => 'some-code', 'data' => [ 'foo' => 'bar', 'baz' => 42 ] ],
597 [
598 'text' => '(&#60;b&#62;Something broke!&#60;/b&#62;)',
599 'code' => 'some-code',
600 'data' => [ 'foo' => 'bar', 'baz' => 42 ],
601 ]
602 ],
603 'UsageException' => [
604 $usageException,
605 [],
606 [
607 'text' => '&#60;b&#62;Something broke!&#60;/b&#62;',
608 'code' => 'ue-code',
609 'data' => [ 'xxx' => 'yyy', 'baz' => 23 ],
610 ]
611 ],
612 'UsageException, wrapped' => [
613 $usageException,
614 [ 'wrap' => 'parentheses', 'code' => 'some-code', 'data' => [ 'foo' => 'bar', 'baz' => 42 ] ],
615 [
616 'text' => '(&#60;b&#62;Something broke!&#60;/b&#62;)',
617 'code' => 'some-code',
618 'data' => [ 'xxx' => 'yyy', 'baz' => 42, 'foo' => 'bar' ],
619 ]
620 ],
621 'LocalizedException' => [
622 new LocalizedException( [ 'returnto', '<b>FooBar</b>' ] ),
623 [],
624 [
625 'text' => 'Return to <b>FooBar</b>.',
626 'code' => 'returnto',
627 'data' => [],
628 ]
629 ],
630 'LocalizedException, wrapped' => [
631 new LocalizedException( [ 'returnto', '<b>FooBar</b>' ] ),
632 [ 'wrap' => 'parentheses', 'code' => 'some-code', 'data' => [ 'foo' => 'bar', 'baz' => 42 ] ],
633 [
634 'text' => 'Return to <b>FooBar</b>.',
635 'code' => 'some-code',
636 'data' => [ 'foo' => 'bar', 'baz' => 42 ],
637 ]
638 ],
639 ];
640 }
641
642 }