Merge "Remove two unused OutputPage methods"
[lhc/web/wiklou.git] / tests / phpunit / includes / EditPageTest.php
1 <?php
2
3 /**
4 * @group Editing
5 *
6 * @group Database
7 * ^--- tell jenkins this test needs the database
8 *
9 * @group medium
10 * ^--- tell phpunit that these test cases may take longer than 2 seconds.
11 */
12 class EditPageTest extends MediaWikiLangTestCase {
13
14 protected function setUp() {
15 global $wgExtraNamespaces, $wgNamespaceContentModels, $wgContentHandlers, $wgContLang;
16
17 parent::setUp();
18
19 $this->setContentLang( $wgContLang );
20
21 $this->setMwGlobals( [
22 'wgExtraNamespaces' => $wgExtraNamespaces,
23 'wgNamespaceContentModels' => $wgNamespaceContentModels,
24 'wgContentHandlers' => $wgContentHandlers,
25 ] );
26
27 $wgExtraNamespaces[12312] = 'Dummy';
28 $wgExtraNamespaces[12313] = 'Dummy_talk';
29
30 $wgNamespaceContentModels[12312] = "testing";
31 $wgContentHandlers["testing"] = 'DummyContentHandlerForTesting';
32
33 MWNamespace::clearCaches();
34 $wgContLang->resetNamespaces(); # reset namespace cache
35 }
36
37 protected function tearDown() {
38 global $wgContLang;
39
40 MWNamespace::clearCaches();
41 $wgContLang->resetNamespaces(); # reset namespace cache
42 parent::tearDown();
43 }
44
45 /**
46 * @dataProvider provideExtractSectionTitle
47 * @covers EditPage::extractSectionTitle
48 */
49 public function testExtractSectionTitle( $section, $title ) {
50 $extracted = EditPage::extractSectionTitle( $section );
51 $this->assertEquals( $title, $extracted );
52 }
53
54 public static function provideExtractSectionTitle() {
55 return [
56 [
57 "== Test ==\n\nJust a test section.",
58 "Test"
59 ],
60 [
61 "An initial section, no header.",
62 false
63 ],
64 [
65 "An initial section with a fake heder (T34617)\n\n== Test == ??\nwtf",
66 false
67 ],
68 [
69 "== Section ==\nfollowed by a fake == Non-section == ??\nnoooo",
70 "Section"
71 ],
72 [
73 "== Section== \t\r\n followed by whitespace (T37051)",
74 'Section',
75 ],
76 ];
77 }
78
79 protected function forceRevisionDate( WikiPage $page, $timestamp ) {
80 $dbw = wfGetDB( DB_MASTER );
81
82 $dbw->update( 'revision',
83 [ 'rev_timestamp' => $dbw->timestamp( $timestamp ) ],
84 [ 'rev_id' => $page->getLatest() ] );
85
86 $page->clear();
87 }
88
89 /**
90 * User input text is passed to rtrim() by edit page. This is a simple
91 * wrapper around assertEquals() which calls rrtrim() to normalize the
92 * expected and actual texts.
93 * @param string $expected
94 * @param string $actual
95 * @param string $msg
96 */
97 protected function assertEditedTextEquals( $expected, $actual, $msg = '' ) {
98 $this->assertEquals( rtrim( $expected ), rtrim( $actual ), $msg );
99 }
100
101 /**
102 * Performs an edit and checks the result.
103 *
104 * @param string|Title $title The title of the page to edit
105 * @param string|null $baseText Some text to create the page with before attempting the edit.
106 * @param User|string|null $user The user to perform the edit as.
107 * @param array $edit An array of request parameters used to define the edit to perform.
108 * Some well known fields are:
109 * * wpTextbox1: the text to submit
110 * * wpSummary: the edit summary
111 * * wpEditToken: the edit token (will be inserted if not provided)
112 * * wpEdittime: timestamp of the edit's base revision (will be inserted
113 * if not provided)
114 * * wpStarttime: timestamp when the edit started (will be inserted if not provided)
115 * * wpSectionTitle: the section to edit
116 * * wpMinorEdit: mark as minor edit
117 * * wpWatchthis: whether to watch the page
118 * @param int|null $expectedCode The expected result code (EditPage::AS_XXX constants).
119 * Set to null to skip the check.
120 * @param string|null $expectedText The text expected to be on the page after the edit.
121 * Set to null to skip the check.
122 * @param string|null $message An optional message to show along with any error message.
123 *
124 * @return WikiPage The page that was just edited, useful for getting the edit's rev_id, etc.
125 */
126 protected function assertEdit( $title, $baseText, $user = null, array $edit,
127 $expectedCode = null, $expectedText = null, $message = null
128 ) {
129 if ( is_string( $title ) ) {
130 $ns = $this->getDefaultWikitextNS();
131 $title = Title::newFromText( $title, $ns );
132 }
133 $this->assertNotNull( $title );
134
135 if ( is_string( $user ) ) {
136 $user = User::newFromName( $user );
137
138 if ( $user->getId() === 0 ) {
139 $user->addToDatabase();
140 }
141 }
142
143 $page = WikiPage::factory( $title );
144
145 if ( $baseText !== null ) {
146 $content = ContentHandler::makeContent( $baseText, $title );
147 $page->doEditContent( $content, "base text for test" );
148 $this->forceRevisionDate( $page, '20120101000000' );
149
150 // sanity check
151 $page->clear();
152 $currentText = ContentHandler::getContentText( $page->getContent() );
153
154 # EditPage rtrim() the user input, so we alter our expected text
155 # to reflect that.
156 $this->assertEditedTextEquals( $baseText, $currentText );
157 }
158
159 if ( $user == null ) {
160 $user = $GLOBALS['wgUser'];
161 } else {
162 $this->setMwGlobals( 'wgUser', $user );
163 }
164
165 if ( !isset( $edit['wpEditToken'] ) ) {
166 $edit['wpEditToken'] = $user->getEditToken();
167 }
168
169 if ( !isset( $edit['wpEdittime'] ) ) {
170 $edit['wpEdittime'] = $page->exists() ? $page->getTimestamp() : '';
171 }
172
173 if ( !isset( $edit['wpStarttime'] ) ) {
174 $edit['wpStarttime'] = wfTimestampNow();
175 }
176
177 if ( !isset( $edit['wpUnicodeCheck'] ) ) {
178 $edit['wpUnicodeCheck'] = EditPage::UNICODE_CHECK;
179 }
180
181 $req = new FauxRequest( $edit, true ); // session ??
182
183 $article = new Article( $title );
184 $article->getContext()->setTitle( $title );
185 $ep = new EditPage( $article );
186 $ep->setContextTitle( $title );
187 $ep->importFormData( $req );
188
189 $bot = isset( $edit['bot'] ) ? (bool)$edit['bot'] : false;
190
191 // this is where the edit happens!
192 // Note: don't want to use EditPage::AttemptSave, because it messes with $wgOut
193 // and throws exceptions like PermissionsError
194 $status = $ep->internalAttemptSave( $result, $bot );
195
196 if ( $expectedCode !== null ) {
197 // check edit code
198 $this->assertEquals( $expectedCode, $status->value,
199 "Expected result code mismatch. $message" );
200 }
201
202 $page = WikiPage::factory( $title );
203
204 if ( $expectedText !== null ) {
205 // check resulting page text
206 $content = $page->getContent();
207 $text = ContentHandler::getContentText( $content );
208
209 # EditPage rtrim() the user input, so we alter our expected text
210 # to reflect that.
211 $this->assertEditedTextEquals( $expectedText, $text,
212 "Expected article text mismatch. $message" );
213 }
214
215 return $page;
216 }
217
218 public static function provideCreatePages() {
219 return [
220 [ 'expected article being created',
221 'EditPageTest_testCreatePage',
222 null,
223 'Hello World!',
224 EditPage::AS_SUCCESS_NEW_ARTICLE,
225 'Hello World!'
226 ],
227 [ 'expected article not being created if empty',
228 'EditPageTest_testCreatePage',
229 null,
230 '',
231 EditPage::AS_BLANK_ARTICLE,
232 null
233 ],
234 [ 'expected MediaWiki: page being created',
235 'MediaWiki:January',
236 'UTSysop',
237 'Not January',
238 EditPage::AS_SUCCESS_NEW_ARTICLE,
239 'Not January'
240 ],
241 [ 'expected not-registered MediaWiki: page not being created if empty',
242 'MediaWiki:EditPageTest_testCreatePage',
243 'UTSysop',
244 '',
245 EditPage::AS_BLANK_ARTICLE,
246 null
247 ],
248 [ 'expected registered MediaWiki: page being created even if empty',
249 'MediaWiki:January',
250 'UTSysop',
251 '',
252 EditPage::AS_SUCCESS_NEW_ARTICLE,
253 ''
254 ],
255 [ 'expected registered MediaWiki: page whose default content is empty'
256 . ' not being created if empty',
257 'MediaWiki:Ipb-default-expiry',
258 'UTSysop',
259 '',
260 EditPage::AS_BLANK_ARTICLE,
261 ''
262 ],
263 [ 'expected MediaWiki: page not being created if text equals default message',
264 'MediaWiki:January',
265 'UTSysop',
266 'January',
267 EditPage::AS_BLANK_ARTICLE,
268 null
269 ],
270 [ 'expected empty article being created',
271 'EditPageTest_testCreatePage',
272 null,
273 '',
274 EditPage::AS_SUCCESS_NEW_ARTICLE,
275 '',
276 true
277 ],
278 ];
279 }
280
281 /**
282 * @dataProvider provideCreatePages
283 * @covers EditPage
284 */
285 public function testCreatePage(
286 $desc, $pageTitle, $user, $editText, $expectedCode, $expectedText, $ignoreBlank = false
287 ) {
288 $checkId = null;
289
290 $this->setMwGlobals( 'wgHooks', [
291 'PageContentInsertComplete' => [ function (
292 WikiPage &$page, User &$user, Content $content,
293 $summary, $minor, $u1, $u2, &$flags, Revision $revision
294 ) {
295 // types/refs checked
296 } ],
297 'PageContentSaveComplete' => [ function (
298 WikiPage &$page, User &$user, Content $content,
299 $summary, $minor, $u1, $u2, &$flags, Revision $revision,
300 Status &$status, $baseRevId
301 ) use ( &$checkId ) {
302 $checkId = $status->value['revision']->getId();
303 // types/refs checked
304 } ],
305 ] );
306
307 $edit = [ 'wpTextbox1' => $editText ];
308 if ( $ignoreBlank ) {
309 $edit['wpIgnoreBlankArticle'] = 1;
310 }
311
312 $page = $this->assertEdit( $pageTitle, null, $user, $edit, $expectedCode, $expectedText, $desc );
313
314 if ( $expectedCode != EditPage::AS_BLANK_ARTICLE ) {
315 $latest = $page->getLatest();
316 $page->doDeleteArticleReal( $pageTitle );
317
318 $this->assertGreaterThan( 0, $latest, "Page revision ID updated in object" );
319 $this->assertEquals( $latest, $checkId, "Revision in Status for hook" );
320 }
321 }
322
323 /**
324 * @dataProvider provideCreatePages
325 * @covers EditPage
326 */
327 public function testCreatePageTrx(
328 $desc, $pageTitle, $user, $editText, $expectedCode, $expectedText, $ignoreBlank = false
329 ) {
330 $checkIds = [];
331 $this->setMwGlobals( 'wgHooks', [
332 'PageContentInsertComplete' => [ function (
333 WikiPage &$page, User &$user, Content $content,
334 $summary, $minor, $u1, $u2, &$flags, Revision $revision
335 ) {
336 // types/refs checked
337 } ],
338 'PageContentSaveComplete' => [ function (
339 WikiPage &$page, User &$user, Content $content,
340 $summary, $minor, $u1, $u2, &$flags, Revision $revision,
341 Status &$status, $baseRevId
342 ) use ( &$checkIds ) {
343 $checkIds[] = $status->value['revision']->getId();
344 // types/refs checked
345 } ],
346 ] );
347
348 wfGetDB( DB_MASTER )->begin( __METHOD__ );
349
350 $edit = [ 'wpTextbox1' => $editText ];
351 if ( $ignoreBlank ) {
352 $edit['wpIgnoreBlankArticle'] = 1;
353 }
354
355 $page = $this->assertEdit(
356 $pageTitle, null, $user, $edit, $expectedCode, $expectedText, $desc );
357
358 $pageTitle2 = (string)$pageTitle . '/x';
359 $page2 = $this->assertEdit(
360 $pageTitle2, null, $user, $edit, $expectedCode, $expectedText, $desc );
361
362 wfGetDB( DB_MASTER )->commit( __METHOD__ );
363
364 $this->assertEquals( 0, DeferredUpdates::pendingUpdatesCount(), 'No deferred updates' );
365
366 if ( $expectedCode != EditPage::AS_BLANK_ARTICLE ) {
367 $latest = $page->getLatest();
368 $page->doDeleteArticleReal( $pageTitle );
369
370 $this->assertGreaterThan( 0, $latest, "Page #1 revision ID updated in object" );
371 $this->assertEquals( $latest, $checkIds[0], "Revision #1 in Status for hook" );
372
373 $latest2 = $page2->getLatest();
374 $page2->doDeleteArticleReal( $pageTitle2 );
375
376 $this->assertGreaterThan( 0, $latest2, "Page #2 revision ID updated in object" );
377 $this->assertEquals( $latest2, $checkIds[1], "Revision #2 in Status for hook" );
378 }
379 }
380
381 public function testUpdatePage() {
382 $checkIds = [];
383
384 $this->setMwGlobals( 'wgHooks', [
385 'PageContentInsertComplete' => [ function (
386 WikiPage &$page, User &$user, Content $content,
387 $summary, $minor, $u1, $u2, &$flags, Revision $revision
388 ) {
389 // types/refs checked
390 } ],
391 'PageContentSaveComplete' => [ function (
392 WikiPage &$page, User &$user, Content $content,
393 $summary, $minor, $u1, $u2, &$flags, Revision $revision,
394 Status &$status, $baseRevId
395 ) use ( &$checkIds ) {
396 $checkIds[] = $status->value['revision']->getId();
397 // types/refs checked
398 } ],
399 ] );
400
401 $text = "one";
402 $edit = [
403 'wpTextbox1' => $text,
404 'wpSummary' => 'first update',
405 ];
406
407 $page = $this->assertEdit( 'EditPageTest_testUpdatePage', "zero", null, $edit,
408 EditPage::AS_SUCCESS_UPDATE, $text,
409 "expected successfull update with given text" );
410 $this->assertGreaterThan( 0, $checkIds[0], "First event rev ID set" );
411
412 $this->forceRevisionDate( $page, '20120101000000' );
413
414 $text = "two";
415 $edit = [
416 'wpTextbox1' => $text,
417 'wpSummary' => 'second update',
418 ];
419
420 $this->assertEdit( 'EditPageTest_testUpdatePage', null, null, $edit,
421 EditPage::AS_SUCCESS_UPDATE, $text,
422 "expected successfull update with given text" );
423 $this->assertGreaterThan( 0, $checkIds[1], "Second edit hook rev ID set" );
424 $this->assertGreaterThan( $checkIds[0], $checkIds[1], "Second event rev ID is higher" );
425 }
426
427 public function testUpdatePageTrx() {
428 $text = "one";
429 $edit = [
430 'wpTextbox1' => $text,
431 'wpSummary' => 'first update',
432 ];
433
434 $page = $this->assertEdit( 'EditPageTest_testTrxUpdatePage', "zero", null, $edit,
435 EditPage::AS_SUCCESS_UPDATE, $text,
436 "expected successfull update with given text" );
437
438 $this->forceRevisionDate( $page, '20120101000000' );
439
440 $checkIds = [];
441 $this->setMwGlobals( 'wgHooks', [
442 'PageContentSaveComplete' => [ function (
443 WikiPage &$page, User &$user, Content $content,
444 $summary, $minor, $u1, $u2, &$flags, Revision $revision,
445 Status &$status, $baseRevId
446 ) use ( &$checkIds ) {
447 $checkIds[] = $status->value['revision']->getId();
448 // types/refs checked
449 } ],
450 ] );
451
452 wfGetDB( DB_MASTER )->begin( __METHOD__ );
453
454 $text = "two";
455 $edit = [
456 'wpTextbox1' => $text,
457 'wpSummary' => 'second update',
458 ];
459
460 $this->assertEdit( 'EditPageTest_testTrxUpdatePage', null, null, $edit,
461 EditPage::AS_SUCCESS_UPDATE, $text,
462 "expected successfull update with given text" );
463
464 $text = "three";
465 $edit = [
466 'wpTextbox1' => $text,
467 'wpSummary' => 'third update',
468 ];
469
470 $this->assertEdit( 'EditPageTest_testTrxUpdatePage', null, null, $edit,
471 EditPage::AS_SUCCESS_UPDATE, $text,
472 "expected successfull update with given text" );
473
474 wfGetDB( DB_MASTER )->commit( __METHOD__ );
475
476 $this->assertGreaterThan( 0, $checkIds[0], "First event rev ID set" );
477 $this->assertGreaterThan( 0, $checkIds[1], "Second edit hook rev ID set" );
478 $this->assertGreaterThan( $checkIds[0], $checkIds[1], "Second event rev ID is higher" );
479 }
480
481 public static function provideSectionEdit() {
482 $text = 'Intro
483
484 == one ==
485 first section.
486
487 == two ==
488 second section.
489 ';
490
491 $sectionOne = '== one ==
492 hello
493 ';
494
495 $newSection = '== new section ==
496
497 hello
498 ';
499
500 $textWithNewSectionOne = preg_replace(
501 '/== one ==.*== two ==/ms',
502 "$sectionOne\n== two ==", $text
503 );
504
505 $textWithNewSectionAdded = "$text\n$newSection";
506
507 return [
508 [ # 0
509 $text,
510 '',
511 'hello',
512 'replace all',
513 'hello'
514 ],
515
516 [ # 1
517 $text,
518 '1',
519 $sectionOne,
520 'replace first section',
521 $textWithNewSectionOne,
522 ],
523
524 [ # 2
525 $text,
526 'new',
527 'hello',
528 'new section',
529 $textWithNewSectionAdded,
530 ],
531 ];
532 }
533
534 /**
535 * @dataProvider provideSectionEdit
536 * @covers EditPage
537 */
538 public function testSectionEdit( $base, $section, $text, $summary, $expected ) {
539 $edit = [
540 'wpTextbox1' => $text,
541 'wpSummary' => $summary,
542 'wpSection' => $section,
543 ];
544
545 $this->assertEdit( 'EditPageTest_testSectionEdit', $base, null, $edit,
546 EditPage::AS_SUCCESS_UPDATE, $expected,
547 "expected successfull update of section" );
548 }
549
550 public static function provideAutoMerge() {
551 $tests = [];
552
553 $tests[] = [ # 0: plain conflict
554 "Elmo", # base edit user
555 "one\n\ntwo\n\nthree\n",
556 [ # adam's edit
557 'wpStarttime' => 1,
558 'wpTextbox1' => "ONE\n\ntwo\n\nthree\n",
559 ],
560 [ # berta's edit
561 'wpStarttime' => 2,
562 'wpTextbox1' => "(one)\n\ntwo\n\nthree\n",
563 ],
564 EditPage::AS_CONFLICT_DETECTED, # expected code
565 "ONE\n\ntwo\n\nthree\n", # expected text
566 'expected edit conflict', # message
567 ];
568
569 $tests[] = [ # 1: successful merge
570 "Elmo", # base edit user
571 "one\n\ntwo\n\nthree\n",
572 [ # adam's edit
573 'wpStarttime' => 1,
574 'wpTextbox1' => "ONE\n\ntwo\n\nthree\n",
575 ],
576 [ # berta's edit
577 'wpStarttime' => 2,
578 'wpTextbox1' => "one\n\ntwo\n\nTHREE\n",
579 ],
580 EditPage::AS_SUCCESS_UPDATE, # expected code
581 "ONE\n\ntwo\n\nTHREE\n", # expected text
582 'expected automatic merge', # message
583 ];
584
585 $text = "Intro\n\n";
586 $text .= "== first section ==\n\n";
587 $text .= "one\n\ntwo\n\nthree\n\n";
588 $text .= "== second section ==\n\n";
589 $text .= "four\n\nfive\n\nsix\n\n";
590
591 // extract the first section.
592 $section = preg_replace( '/.*(== first section ==.*)== second section ==.*/sm', '$1', $text );
593
594 // generate expected text after merge
595 $expected = str_replace( 'one', 'ONE', str_replace( 'three', 'THREE', $text ) );
596
597 $tests[] = [ # 2: merge in section
598 "Elmo", # base edit user
599 $text,
600 [ # adam's edit
601 'wpStarttime' => 1,
602 'wpTextbox1' => str_replace( 'one', 'ONE', $section ),
603 'wpSection' => '1'
604 ],
605 [ # berta's edit
606 'wpStarttime' => 2,
607 'wpTextbox1' => str_replace( 'three', 'THREE', $section ),
608 'wpSection' => '1'
609 ],
610 EditPage::AS_SUCCESS_UPDATE, # expected code
611 $expected, # expected text
612 'expected automatic section merge', # message
613 ];
614
615 // see whether it makes a difference who did the base edit
616 $testsWithAdam = array_map( function ( $test ) {
617 $test[0] = 'Adam'; // change base edit user
618 return $test;
619 }, $tests );
620
621 $testsWithBerta = array_map( function ( $test ) {
622 $test[0] = 'Berta'; // change base edit user
623 return $test;
624 }, $tests );
625
626 return array_merge( $tests, $testsWithAdam, $testsWithBerta );
627 }
628
629 /**
630 * @dataProvider provideAutoMerge
631 * @covers EditPage
632 */
633 public function testAutoMerge( $baseUser, $text, $adamsEdit, $bertasEdit,
634 $expectedCode, $expectedText, $message = null
635 ) {
636 $this->markTestSkippedIfNoDiff3();
637
638 // create page
639 $ns = $this->getDefaultWikitextNS();
640 $title = Title::newFromText( 'EditPageTest_testAutoMerge', $ns );
641 $page = WikiPage::factory( $title );
642
643 if ( $page->exists() ) {
644 $page->doDeleteArticle( "clean slate for testing" );
645 }
646
647 $baseEdit = [
648 'wpTextbox1' => $text,
649 ];
650
651 $page = $this->assertEdit( 'EditPageTest_testAutoMerge', null,
652 $baseUser, $baseEdit, null, null, __METHOD__ );
653
654 $this->forceRevisionDate( $page, '20120101000000' );
655
656 $edittime = $page->getTimestamp();
657
658 // start timestamps for conflict detection
659 if ( !isset( $adamsEdit['wpStarttime'] ) ) {
660 $adamsEdit['wpStarttime'] = 1;
661 }
662
663 if ( !isset( $bertasEdit['wpStarttime'] ) ) {
664 $bertasEdit['wpStarttime'] = 2;
665 }
666
667 $starttime = wfTimestampNow();
668 $adamsTime = wfTimestamp(
669 TS_MW,
670 (int)wfTimestamp( TS_UNIX, $starttime ) + (int)$adamsEdit['wpStarttime']
671 );
672 $bertasTime = wfTimestamp(
673 TS_MW,
674 (int)wfTimestamp( TS_UNIX, $starttime ) + (int)$bertasEdit['wpStarttime']
675 );
676
677 $adamsEdit['wpStarttime'] = $adamsTime;
678 $bertasEdit['wpStarttime'] = $bertasTime;
679
680 $adamsEdit['wpSummary'] = 'Adam\'s edit';
681 $bertasEdit['wpSummary'] = 'Bertas\'s edit';
682
683 $adamsEdit['wpEdittime'] = $edittime;
684 $bertasEdit['wpEdittime'] = $edittime;
685
686 // first edit
687 $this->assertEdit( 'EditPageTest_testAutoMerge', null, 'Adam', $adamsEdit,
688 EditPage::AS_SUCCESS_UPDATE, null, "expected successfull update" );
689
690 // second edit
691 $this->assertEdit( 'EditPageTest_testAutoMerge', null, 'Berta', $bertasEdit,
692 $expectedCode, $expectedText, $message );
693 }
694
695 /**
696 * @depends testAutoMerge
697 */
698 public function testCheckDirectEditingDisallowed_forNonTextContent() {
699 $title = Title::newFromText( 'Dummy:NonTextPageForEditPage' );
700 $page = WikiPage::factory( $title );
701
702 $article = new Article( $title );
703 $article->getContext()->setTitle( $title );
704 $ep = new EditPage( $article );
705 $ep->setContextTitle( $title );
706
707 $user = $GLOBALS['wgUser'];
708
709 $edit = [
710 'wpTextbox1' => serialize( 'non-text content' ),
711 'wpEditToken' => $user->getEditToken(),
712 'wpEdittime' => '',
713 'wpStarttime' => wfTimestampNow(),
714 'wpUnicodeCheck' => EditPage::UNICODE_CHECK,
715 ];
716
717 $req = new FauxRequest( $edit, true );
718 $ep->importFormData( $req );
719
720 $this->setExpectedException(
721 MWException::class,
722 'This content model is not supported: testing'
723 );
724
725 $ep->internalAttemptSave( $result, false );
726 }
727
728 }