3 namespace MediaWiki\Tests\Api
;
5 use MediaWiki\Api\ApiUsageException
;
6 use MediaWiki\Block\DatabaseBlock
;
7 use MediaWiki\CommentStore\CommentStoreComment
;
8 use MediaWiki\Content\JavaScriptContent
;
9 use MediaWiki\Content\WikitextContent
;
10 use MediaWiki\Context\RequestContext
;
11 use MediaWiki\MainConfigNames
;
12 use MediaWiki\Revision\RevisionRecord
;
13 use MediaWiki\Status\Status
;
14 use MediaWiki\Tests\User\TempUser\TempUserTestTrait
;
15 use MediaWiki\Title\Title
;
16 use MediaWiki\Title\TitleValue
;
17 use MediaWiki\User\User
;
18 use MediaWiki\Utils\MWTimestamp
;
20 use Wikimedia\Rdbms\IDBAccessObject
;
24 * Tests for MediaWiki api.php?action=edit.
26 * @author Daniel Kinzler
32 * @covers \MediaWiki\Api\ApiEditPage
34 class ApiEditPageTest
extends ApiTestCase
{
36 use TempUserTestTrait
;
38 protected function setUp(): void
{
41 $this->overrideConfigValues( [
42 MainConfigNames
::ExtraNamespaces
=> [
44 12313 => 'Dummy_talk',
45 12314 => 'DummyNonText',
46 12315 => 'DummyNonText_talk',
48 MainConfigNames
::NamespaceContentModels
=> [
50 12314 => 'testing-nontext',
52 MainConfigNames
::WatchlistExpiry
=> true,
53 MainConfigNames
::WatchlistExpiryMaxDuration
=> '6 months',
55 $this->mergeMwGlobalArrayValue( 'wgContentHandlers', [
56 'testing' => 'DummyContentHandlerForTesting',
57 'testing-nontext' => 'DummyNonTextContentHandler',
58 'testing-serialize-error' => 'DummySerializeErrorContentHandler',
62 public function testEdit() {
63 $name = 'Help:ApiEditPageTest_testEdit'; // assume Help namespace to default to wikitext
65 // -- test new page --------------------------------------------
66 $apiResult = $this->doApiRequestWithToken( [
69 'text' => 'some text',
71 $apiResult = $apiResult[0];
73 // Validate API result data
74 $this->assertArrayHasKey( 'edit', $apiResult );
75 $this->assertArrayHasKey( 'result', $apiResult['edit'] );
76 $this->assertSame( 'Success', $apiResult['edit']['result'] );
78 $this->assertArrayHasKey( 'new', $apiResult['edit'] );
79 $this->assertArrayNotHasKey( 'nochange', $apiResult['edit'] );
81 $this->assertArrayHasKey( 'pageid', $apiResult['edit'] );
83 // -- test existing page, no change ----------------------------
84 $data = $this->doApiRequestWithToken( [
87 'text' => 'some text',
90 $this->assertSame( 'Success', $data[0]['edit']['result'] );
92 $this->assertArrayNotHasKey( 'new', $data[0]['edit'] );
93 $this->assertArrayHasKey( 'nochange', $data[0]['edit'] );
95 // -- test existing page, with change --------------------------
96 $data = $this->doApiRequestWithToken( [
99 'text' => 'different text'
102 $this->assertSame( 'Success', $data[0]['edit']['result'] );
104 $this->assertArrayNotHasKey( 'new', $data[0]['edit'] );
105 $this->assertArrayNotHasKey( 'nochange', $data[0]['edit'] );
107 $this->assertArrayHasKey( 'oldrevid', $data[0]['edit'] );
108 $this->assertArrayHasKey( 'newrevid', $data[0]['edit'] );
109 $this->assertNotEquals(
110 $data[0]['edit']['newrevid'],
111 $data[0]['edit']['oldrevid'],
112 "revision id should change after edit"
119 public static function provideEditAppend() {
122 'foo', 'append', 'bar', "foobar"
125 'foo', 'prepend', 'bar', "barfoo"
127 [ # 2: append to empty page
128 '', 'append', 'foo', "foo"
130 [ # 3: prepend to empty page
131 '', 'prepend', 'foo', "foo"
133 [ # 4: append to non-existing page
134 null, 'append', 'foo', "foo"
136 [ # 5: prepend to non-existing page
137 null, 'prepend', 'foo', "foo"
143 * @dataProvider provideEditAppend
145 public function testEditAppend( $text, $op, $append, $expected ) {
149 // assume NS_HELP defaults to wikitext
150 $title = Title
::makeTitle( NS_HELP
, "ApiEditPageTest_testEditAppend_$count" );
152 // -- create page (or not) -----------------------------------------
153 if ( $text !== null ) {
154 [ $re ] = $this->doApiRequestWithToken( [
156 'title' => $title->getPrefixedText(),
157 'text' => $text, ] );
159 $this->assertSame( 'Success', $re['edit']['result'] );
162 // -- try append/prepend --------------------------------------------
163 [ $re ] = $this->doApiRequestWithToken( [
165 'title' => $title->getPrefixedText(),
166 $op . 'text' => $append, ] );
168 $this->assertSame( 'Success', $re['edit']['result'] );
170 // -- validate -----------------------------------------------------
171 $page = $this->getServiceContainer()->getWikiPageFactory()->newFromTitle( $title );
172 $content = $page->getContent();
173 $this->assertNotNull( $content, 'Page should have been created' );
175 $text = $content->getText();
177 $this->assertSame( $expected, $text );
181 * Test editing of sections
183 public function testEditSection() {
184 $title = Title
::makeTitle( NS_HELP
, 'ApiEditPageTest_testEditSection' );
185 $wikiPageFactory = $this->getServiceContainer()->getWikiPageFactory();
186 $page = $wikiPageFactory->newFromTitle( $title );
187 $text = "==section 1==\ncontent 1\n==section 2==\ncontent2";
188 // Preload the page with some text
189 $page->doUserEditContent(
190 $page->getContentHandler()->unserializeContent( $text ),
191 $this->getTestSysop()->getAuthority(),
195 [ $re ] = $this->doApiRequestWithToken( [
197 'title' => $title->getPrefixedText(),
199 'text' => "==section 1==\nnew content 1",
201 $this->assertSame( 'Success', $re['edit']['result'] );
202 $newtext = $wikiPageFactory->newFromTitle( $title )
203 ->getContent( RevisionRecord
::RAW
)
205 $this->assertSame( "==section 1==\nnew content 1\n\n==section 2==\ncontent2", $newtext );
207 // Test that we raise a 'nosuchsection' error
209 $this->doApiRequestWithToken( [
211 'title' => $title->getPrefixedText(),
215 $this->fail( "Should have raised an ApiUsageException" );
216 } catch ( ApiUsageException
$e ) {
217 $this->assertApiErrorCode( 'nosuchsection', $e );
222 * Test action=edit§ion=new
223 * Run it twice so we test adding a new section on a
224 * page that doesn't exist (T54830) and one that
227 public function testEditNewSection() {
228 $title = Title
::makeTitle( NS_HELP
, 'ApiEditPageTest_testEditNewSection' );
229 $wikiPageFactory = $this->getServiceContainer()->getWikiPageFactory();
231 // Test on a page that does not already exist
232 $this->assertFalse( $title->exists() );
233 [ $re ] = $this->doApiRequestWithToken( [
235 'title' => $title->getPrefixedText(),
238 'summary' => 'header',
241 $this->assertSame( 'Success', $re['edit']['result'] );
242 // Check the page text is correct
243 $text = $wikiPageFactory->newFromTitle( $title )
244 ->getContent( RevisionRecord
::RAW
)
246 $this->assertSame( "== header ==\n\ntest", $text );
248 // Now on one that does
249 $this->assertTrue( $title->exists( IDBAccessObject
::READ_LATEST
) );
250 [ $re2 ] = $this->doApiRequestWithToken( [
252 'title' => $title->getPrefixedText(),
255 'summary' => 'header',
258 $this->assertSame( 'Success', $re2['edit']['result'] );
259 $text = $wikiPageFactory->newFromTitle( $title )
260 ->getContent( RevisionRecord
::RAW
)
262 $this->assertSame( "== header ==\n\ntest\n\n== header ==\n\ntest", $text );
266 * Test action=edit§ion=new with different combinations of summary and sectiontitle.
268 * @dataProvider provideEditNewSectionSummarySectiontitle
270 public function testEditNewSectionSummarySectiontitle(
278 $title = Title
::makeTitle( NS_HELP
, 'ApiEditPageTest_testEditNewSectionSummarySectiontitle' . $count );
280 // Test edit 1 (new page)
281 $this->doApiRequestWithToken( [
283 'title' => $title->getPrefixedText(),
286 'sectiontitle' => $sectiontitle,
287 'summary' => $summary,
290 $wikiPageFactory = $this->getServiceContainer()->getWikiPageFactory();
291 $wikiPage = $wikiPageFactory->newFromTitle( $title );
293 // Check the page text is correct
294 $savedText = $wikiPage->getContent( RevisionRecord
::RAW
)->getText();
295 $this->assertSame( $expectedText, $savedText, 'Correct text saved (new page)' );
297 // Check that the edit summary is correct
298 // (when not provided or empty, there is an autogenerated summary for page creation)
299 $savedSummary = $wikiPage->getRevisionRecord()->getComment( RevisionRecord
::RAW
)->text
;
300 $expectedSummaryNew = $expectedSummary ?
: wfMessage( 'autosumm-new' )->rawParams( $expectedText )
301 ->inContentLanguage()->text();
302 $this->assertSame( $expectedSummaryNew, $savedSummary, 'Correct summary saved (new page)' );
305 $this->editPage( $wikiPage, '' );
307 // Test edit 2 (existing page)
308 $this->doApiRequestWithToken( [
310 'title' => $title->getPrefixedText(),
313 'sectiontitle' => $sectiontitle,
314 'summary' => $summary,
317 $wikiPage = $wikiPageFactory->newFromTitle( $title );
319 // Check the page text is correct
320 $savedText = $wikiPage->getContent( RevisionRecord
::RAW
)->getText();
321 $this->assertSame( $expectedText, $savedText, 'Correct text saved (existing page)' );
323 // Check that the edit summary is correct
324 $savedSummary = $wikiPage->getRevisionRecord()->getComment( RevisionRecord
::RAW
)->text
;
325 $this->assertSame( $expectedSummary, $savedSummary, 'Correct summary saved (existing page)' );
328 public static function provideEditNewSectionSummarySectiontitle() {
329 $sectiontitleCases = [
332 'set' => 'sectiontitle',
343 "== summary ==\n\ntext",
347 "== sectiontitle ==\n\ntext",
348 "== sectiontitle ==\n\ntext",
349 "== sectiontitle ==\n\ntext",
352 $expectedSummaries = [
355 '/* summary */ new section',
359 '/* sectiontitle */ new section',
360 '/* sectiontitle */ new section',
365 foreach ( $sectiontitleCases as $sectiontitleDesc => $sectiontitle ) {
366 foreach ( $summaryCases as $summaryDesc => $summary ) {
367 $message = "sectiontitle $sectiontitleDesc, summary $summaryDesc";
372 $expectedSummaries[$i],
380 * Ensure we can edit through a redirect, if adding a section
382 public function testEdit_redirect() {
386 // assume NS_HELP defaults to wikitext
387 $title = Title
::makeTitle( NS_HELP
, "ApiEditPageTest_testEdit_redirect_$count" );
388 $wikiPageFactory = $this->getServiceContainer()->getWikiPageFactory();
389 $page = $this->getExistingTestPage( $title );
390 $this->forceRevisionDate( $page, '20120101000000' );
392 $rtitle = Title
::makeTitle( NS_HELP
, "ApiEditPageTest_testEdit_redirect_r$count" );
393 $rpage = $wikiPageFactory->newFromTitle( $rtitle );
395 $baseTime = $page->getRevisionRecord()->getTimestamp();
397 // base edit for redirect
398 $rpage->doUserEditContent(
399 new WikitextContent( "#REDIRECT [[{$title->getPrefixedText()}]]" ),
400 $this->getTestSysop()->getUser(),
404 $this->forceRevisionDate( $rpage, '20120101000000' );
406 // conflicting edit to redirect
407 $rpage->doUserEditContent(
408 new WikitextContent( "#REDIRECT [[{$title->getPrefixedText()}]]\n\n[[Category:Test]]" ),
409 $this->getTestUser()->getUser(),
413 $this->forceRevisionDate( $rpage, '20120101020202' );
415 // try to save edit, following the redirect
416 [ $re, , ] = $this->doApiRequestWithToken( [
418 'title' => $rtitle->getPrefixedText(),
419 'text' => 'nix bar!',
420 'basetimestamp' => $baseTime,
425 $this->assertSame( 'Success', $re['edit']['result'],
426 "no problems expected when following redirect" );
430 * Ensure we cannot edit through a redirect, if attempting to overwrite content
432 public function testEdit_redirectText() {
436 // assume NS_HELP defaults to wikitext
437 $title = Title
::makeTitle( NS_HELP
, "ApiEditPageTest_testEdit_redirectText_$count" );
438 $wikiPageFactory = $this->getServiceContainer()->getWikiPageFactory();
439 $page = $this->getExistingTestPage( $title );
440 $this->forceRevisionDate( $page, '20120101000000' );
441 $baseTime = $page->getRevisionRecord()->getTimestamp();
443 $rtitle = Title
::makeTitle( NS_HELP
, "ApiEditPageTest_testEdit_redirectText_r$count" );
444 $rpage = $wikiPageFactory->newFromTitle( $rtitle );
446 // base edit for redirect
447 $rpage->doUserEditContent(
448 new WikitextContent( "#REDIRECT [[{$title->getPrefixedText()}]]" ),
449 $this->getTestSysop()->getUser(),
453 $this->forceRevisionDate( $rpage, '20120101000000' );
455 // conflicting edit to redirect
456 $rpage->doUserEditContent(
457 new WikitextContent( "#REDIRECT [[{$title->getPrefixedText()}]]\n\n[[Category:Test]]" ),
458 $this->getTestUser()->getUser(),
462 $this->forceRevisionDate( $rpage, '20120101020202' );
464 // try to save edit, following the redirect but without creating a section
466 $this->doApiRequestWithToken( [
468 'title' => $rtitle->getPrefixedText(),
469 'text' => 'nix bar!',
470 'basetimestamp' => $baseTime,
474 $this->fail( 'redirect-appendonly error expected' );
475 } catch ( ApiUsageException
$ex ) {
476 $this->assertApiErrorCode( 'redirect-appendonly', $ex );
480 public function testEditConflict_revid() {
484 // assume NS_HELP defaults to wikitext
485 $title = Title
::makeTitle( NS_HELP
, "ApiEditPageTest_testEditConflict_$count" );
487 $page = $this->getServiceContainer()->getWikiPageFactory()->newFromTitle( $title );
490 $page->doUserEditContent(
491 new WikitextContent( "Foo" ),
492 $this->getTestSysop()->getUser(),
496 $this->forceRevisionDate( $page, '20120101000000' );
497 $baseId = $page->getRevisionRecord()->getId();
500 $page->doUserEditContent(
501 new WikitextContent( "Foo bar" ),
502 $this->getTestUser()->getUser(),
506 $this->forceRevisionDate( $page, '20120101020202' );
508 // try to save edit, expect conflict
510 $this->doApiRequestWithToken( [
512 'title' => $title->getPrefixedText(),
513 'text' => 'nix bar!',
514 'baserevid' => $baseId,
515 ], null, $this->getTestSysop()->getUser() );
517 $this->fail( 'edit conflict expected' );
518 } catch ( ApiUsageException
$ex ) {
519 $this->assertApiErrorCode( 'editconflict', $ex );
523 public function testEditConflict_timestamp() {
527 // assume NS_HELP defaults to wikitext
528 $title = Title
::makeTitle( NS_HELP
, "ApiEditPageTest_testEditConflict_$count" );
530 $page = $this->getServiceContainer()->getWikiPageFactory()->newFromTitle( $title );
533 $page->doUserEditContent(
534 new WikitextContent( "Foo" ),
535 $this->getTestSysop()->getUser(),
539 $this->forceRevisionDate( $page, '20120101000000' );
540 $baseTime = $page->getRevisionRecord()->getTimestamp();
543 $page->doUserEditContent(
544 new WikitextContent( "Foo bar" ),
545 $this->getTestUser()->getUser(),
549 $this->forceRevisionDate( $page, '20120101020202' );
551 // try to save edit, expect conflict
553 $this->doApiRequestWithToken( [
555 'title' => $title->getPrefixedText(),
556 'text' => 'nix bar!',
557 'basetimestamp' => $baseTime,
560 $this->fail( 'edit conflict expected' );
561 } catch ( ApiUsageException
$ex ) {
562 $this->assertApiErrorCode( 'editconflict', $ex );
567 * Ensure that editing using section=new will prevent simple conflicts
569 public function testEditConflict_newSection() {
573 // assume NS_HELP defaults to wikitext
574 $title = Title
::makeTitle( NS_HELP
, "ApiEditPageTest_testEditConflict_newSection_$count" );
576 $page = $this->getServiceContainer()->getWikiPageFactory()->newFromTitle( $title );
579 $page->doUserEditContent(
580 new WikitextContent( "Foo" ),
581 $this->getTestSysop()->getUser(),
585 $this->forceRevisionDate( $page, '20120101000000' );
586 $baseTime = $page->getRevisionRecord()->getTimestamp();
589 $page->doUserEditContent(
590 new WikitextContent( "Foo bar" ),
591 $this->getTestUser()->getUser(),
595 $this->forceRevisionDate( $page, '20120101020202' );
597 // try to save edit, expect no conflict
598 [ $re, , ] = $this->doApiRequestWithToken( [
600 'title' => $title->getPrefixedText(),
601 'text' => 'nix bar!',
602 'basetimestamp' => $baseTime,
606 $this->assertSame( 'Success', $re['edit']['result'],
607 "no edit conflict expected here" );
610 public function testEditConflict_T43990() {
615 * T43990: if the target page has a newer revision than the redirect, then editing the
616 * redirect while specifying 'redirect' and *not* specifying 'basetimestamp' erroneously
617 * caused an edit conflict to be detected.
620 // assume NS_HELP defaults to wikitext
621 $title = Title
::makeTitle( NS_HELP
, "ApiEditPageTest_testEditConflict_redirect_T43990_$count" );
622 $wikiPageFactory = $this->getServiceContainer()->getWikiPageFactory();
623 $page = $this->getExistingTestPage( $title );
624 $this->forceRevisionDate( $page, '20120101000000' );
626 $rtitle = Title
::makeTitle( NS_HELP
, "ApiEditPageTest_testEditConflict_redirect_T43990_r$count" );
627 $rpage = $wikiPageFactory->newFromTitle( $rtitle );
629 // base edit for redirect
630 $rpage->doUserEditContent(
631 new WikitextContent( "#REDIRECT [[{$title->getPrefixedText()}]]" ),
632 $this->getTestSysop()->getUser(),
636 $this->forceRevisionDate( $rpage, '20120101000000' );
638 // new edit to content
639 $page->doUserEditContent(
640 new WikitextContent( "Foo bar" ),
641 $this->getTestUser()->getUser(),
645 $this->forceRevisionDate( $rpage, '20120101020202' );
647 // try to save edit; should work, following the redirect.
648 [ $re, , ] = $this->doApiRequestWithToken( [
650 'title' => $rtitle->getPrefixedText(),
651 'text' => 'nix bar!',
656 $this->assertSame( 'Success', $re['edit']['result'],
657 "no edit conflict expected here" );
661 * @param WikiPage $page
662 * @param string|int $timestamp
664 protected function forceRevisionDate( WikiPage
$page, $timestamp ) {
665 $dbw = $this->getDb();
667 $dbw->newUpdateQueryBuilder()
668 ->update( 'revision' )
669 ->set( [ 'rev_timestamp' => $dbw->timestamp( $timestamp ) ] )
670 ->where( [ 'rev_id' => $page->getLatest() ] )
671 ->caller( __METHOD__
)->execute();
676 public function testCheckDirectApiEditingDisallowed_forNonTextContent() {
677 $this->expectApiErrorCode( 'no-direct-editing' );
679 $this->doApiRequestWithToken( [
681 'title' => 'Dummy:ApiEditPageTest_nonTextPageEdit',
682 'text' => '{"animals":["kittens!"]}'
686 public function testSupportsDirectApiEditing_withContentHandlerOverride() {
687 $name = 'DummyNonText:ApiEditPageTest_testNonTextEdit';
688 $data = 'some bla bla text';
690 $result = $this->doApiRequestWithToken( [
696 $apiResult = $result[0];
698 // Validate API result data
699 $this->assertArrayHasKey( 'edit', $apiResult );
700 $this->assertArrayHasKey( 'result', $apiResult['edit'] );
701 $this->assertSame( 'Success', $apiResult['edit']['result'] );
703 $this->assertArrayHasKey( 'new', $apiResult['edit'] );
704 $this->assertArrayNotHasKey( 'nochange', $apiResult['edit'] );
706 $this->assertArrayHasKey( 'pageid', $apiResult['edit'] );
708 // validate resulting revision
709 $page = $this->getServiceContainer()->getWikiPageFactory()->newFromTitle( Title
::newFromText( $name ) );
710 $this->assertSame( "testing-nontext", $page->getContentModel() );
711 $this->assertSame( $data, $page->getContent()->serialize() );
715 * This test verifies that after changing the content model
716 * of a page, undoing that edit via the API will also
717 * undo the content model change.
719 public function testUndoAfterContentModelChange() {
720 $name = 'Help:' . __FUNCTION__
;
721 $sysop = $this->getTestSysop()->getUser();
722 $otherUser = $this->getTestUser()->getUser();
724 $apiResult = $this->doApiRequestWithToken( [
727 'text' => 'some text',
728 ], null, $sysop )[0];
731 $this->assertArrayHasKey( 'edit', $apiResult );
732 $this->assertArrayHasKey( 'result', $apiResult['edit'] );
733 $this->assertSame( 'Success', $apiResult['edit']['result'] );
734 $this->assertArrayHasKey( 'contentmodel', $apiResult['edit'] );
735 // Content model is wikitext
736 $this->assertSame( 'wikitext', $apiResult['edit']['contentmodel'] );
738 // Convert the page to JSON
739 $apiResult = $this->doApiRequestWithToken( [
743 'contentmodel' => 'json',
744 ], null, $otherUser )[0];
747 $this->assertArrayHasKey( 'edit', $apiResult );
748 $this->assertArrayHasKey( 'result', $apiResult['edit'] );
749 $this->assertSame( 'Success', $apiResult['edit']['result'] );
750 $this->assertArrayHasKey( 'contentmodel', $apiResult['edit'] );
751 $this->assertSame( 'json', $apiResult['edit']['contentmodel'] );
753 $apiResult = $this->doApiRequestWithToken( [
756 'undo' => $apiResult['edit']['newrevid']
757 ], null, $sysop )[0];
760 $this->assertArrayHasKey( 'edit', $apiResult );
761 $this->assertArrayHasKey( 'result', $apiResult['edit'] );
762 $this->assertSame( 'Success', $apiResult['edit']['result'] );
763 $this->assertArrayHasKey( 'contentmodel', $apiResult['edit'] );
764 // Check that the contentmodel is back to wikitext now.
765 $this->assertSame( 'wikitext', $apiResult['edit']['contentmodel'] );
768 // The tests below are mostly not commented because they do exactly what
769 // you'd expect from the name.
771 public function testCorrectContentFormat() {
772 $title = Title
::makeTitle( NS_HELP
, 'TestCorrectContentFormat' );
774 $this->doApiRequestWithToken( [
776 'title' => $title->getPrefixedText(),
777 'text' => 'some text',
778 'contentmodel' => 'wikitext',
779 'contentformat' => 'text/x-wiki',
782 $this->assertTrue( $title->exists( IDBAccessObject
::READ_LATEST
) );
785 public function testUnsupportedContentFormat() {
786 $title = Title
::makeTitle( NS_HELP
, 'TestUnsupportedContentFormat' );
788 $this->expectApiErrorCode( 'badvalue' );
791 $this->doApiRequestWithToken( [
793 'title' => $title->getPrefixedText(),
794 'text' => 'some text',
795 'contentformat' => 'nonexistent format',
798 $this->assertFalse( $title->exists( IDBAccessObject
::READ_LATEST
) );
802 public function testMismatchedContentFormat() {
803 $title = Title
::makeTitle( NS_HELP
, 'TestMismatchedContentFormat' );
805 $this->expectApiErrorCode( 'badformat' );
808 $this->doApiRequestWithToken( [
810 'title' => $title->getPrefixedText(),
811 'text' => 'some text',
812 'contentmodel' => 'wikitext',
813 'contentformat' => 'text/plain',
816 $this->assertFalse( $title->exists( IDBAccessObject
::READ_LATEST
) );
820 public function testUndoToInvalidRev() {
821 $title = Title
::makeTitle( NS_HELP
, 'TestUndoToInvalidRev' );
823 $revId = $this->editPage( $title, 'Some text' )->getNewRevision()
827 $this->expectApiErrorCode( 'nosuchrevid' );
829 $this->doApiRequestWithToken( [
831 'title' => $title->getPrefixedText(),
837 * Tests what happens if the undo parameter is a valid revision, but
838 * the undoafter parameter doesn't refer to a revision that exists in the
841 public function testUndoAfterToInvalidRev() {
842 // We can't just pick a large number for undoafter (as in
843 // testUndoToInvalidRev above), because then MediaWiki will helpfully
844 // assume we switched around undo and undoafter and we'll test the code
845 // path for undo being invalid, not undoafter. So instead we delete
846 // the revision from the database. In real life this case could come
847 // up if a revision number was skipped, e.g., if two transactions try
848 // to insert new revision rows at once and the first one to succeed
850 $page = $this->getServiceContainer()->getWikiPageFactory()
851 ->newFromLinkTarget( new TitleValue( NS_HELP
, 'TestUndoAfterToInvalidRev' ) );
853 $revId1 = $this->editPage( $page, '1' )->getNewRevision()->getId();
854 $revId2 = $this->editPage( $page, '2' )->getNewRevision()->getId();
855 $revId3 = $this->editPage( $page, '3' )->getNewRevision()->getId();
857 // Make the middle revision disappear
858 $dbw = $this->getDb();
859 $dbw->newDeleteQueryBuilder()
860 ->deleteFrom( 'revision' )
861 ->where( [ 'rev_id' => $revId2 ] )
862 ->caller( __METHOD__
)->execute();
863 $dbw->newUpdateQueryBuilder()
864 ->update( 'revision' )
865 ->set( [ 'rev_parent_id' => $revId1 ] )
866 ->where( [ 'rev_id' => $revId3 ] )
867 ->caller( __METHOD__
)->execute();
869 $this->expectApiErrorCode( 'nosuchrevid' );
871 $this->doApiRequestWithToken( [
873 'title' => $page->getTitle()->getPrefixedText(),
875 'undoafter' => $revId2,
880 * Tests what happens if the undo parameter is a valid revision, but
881 * undoafter is hidden (rev_deleted).
883 public function testUndoAfterToHiddenRev() {
884 $page = $this->getServiceContainer()->getWikiPageFactory()
885 ->newFromLinkTarget( new TitleValue( NS_HELP
, 'TestUndoAfterToHiddenRev' ) );
886 $titleObj = $page->getTitle();
888 $this->editPage( $page, '0' );
890 $revId1 = $this->editPage( $page, '1' )->getNewRevision()->getId();
892 $revId2 = $this->editPage( $page, '2' )->getNewRevision()->getId();
894 // Hide the middle revision
895 $list = RevisionDeleter
::createList( 'revision',
896 RequestContext
::getMain(), $titleObj, [ $revId1 ] );
897 // Set a user for modifying the visibility, this is needed because
898 // setVisibility generates a log, which cannot be an anonymous user actor
899 // when temporary accounts are enabled.
900 RequestContext
::getMain()->setUser( $this->getTestUser()->getUser() );
901 $list->setVisibility( [
902 'value' => [ RevisionRecord
::DELETED_TEXT
=> 1 ],
903 'comment' => 'Bye-bye',
906 $this->expectApiErrorCode( 'nosuchrevid' );
908 $this->doApiRequestWithToken( [
910 'title' => $titleObj->getPrefixedText(),
912 'undoafter' => $revId1,
917 * Test undo when a revision with a higher id has an earlier timestamp.
918 * This can happen if importing an old revision.
920 public function testUndoWithSwappedRevisions() {
921 $this->markTestSkippedIfNoDiff3();
923 $page = $this->getServiceContainer()->getWikiPageFactory()
924 ->newFromLinkTarget( new TitleValue( NS_HELP
, 'TestUndoWithSwappedRevisions' ) );
925 $this->editPage( $page, '0' );
927 $revId2 = $this->editPage( $page, '2' )->getNewRevision()->getId();
929 $revId1 = $this->editPage( $page, '1' )->getNewRevision()->getId();
931 // Now monkey with the timestamp
932 $dbw = $this->getDb();
933 $dbw->newUpdateQueryBuilder()
934 ->update( 'revision' )
935 ->set( [ 'rev_timestamp' => $dbw->timestamp( time() - 86400 ) ] )
936 ->where( [ 'rev_id' => $revId1 ] )
937 ->caller( __METHOD__
)->execute();
939 $this->doApiRequestWithToken( [
941 'title' => $page->getTitle()->getPrefixedText(),
943 'undoafter' => $revId1,
946 $page->loadPageData( IDBAccessObject
::READ_LATEST
);
947 $this->assertSame( '1', $page->getContent()->getText() );
950 public function testUndoWithConflicts() {
951 $this->expectApiErrorCode( 'undofailure' );
953 $page = $this->getServiceContainer()->getWikiPageFactory()
954 ->newFromLinkTarget( new TitleValue( NS_HELP
, 'TestUndoWithConflicts' ) );
955 $this->editPage( $page, '1' );
957 $revId = $this->editPage( $page, '2' )->getNewRevision()->getId();
959 $this->editPage( $page, '3' );
961 $this->doApiRequestWithToken( [
963 'title' => $page->getTitle()->getPrefixedText(),
967 $page->loadPageData( IDBAccessObject
::READ_LATEST
);
968 $this->assertSame( '3', $page->getContent()->getText() );
971 public function testReversedUndoAfter() {
972 $this->markTestSkippedIfNoDiff3();
974 $page = $this->getServiceContainer()->getWikiPageFactory()
975 ->newFromLinkTarget( new TitleValue( NS_HELP
, 'TestReversedUndoAfter' ) );
976 $this->editPage( $page, '0' );
977 $revId1 = $this->editPage( $page, '1' )->getNewRevision()->getId();
978 $revId2 = $this->editPage( $page, '2' )->getNewRevision()->getId();
980 $this->doApiRequestWithToken( [
982 'title' => $page->getTitle()->getPrefixedText(),
984 'undoafter' => $revId2,
987 $page->loadPageData( IDBAccessObject
::READ_LATEST
);
988 $this->assertSame( '2', $page->getContent()->getText() );
991 public function testUndoToRevFromDifferentPage() {
992 $title1 = Title
::makeTitle( NS_HELP
, 'TestUndoToRevFromDifferentPage-1' );
993 $this->editPage( $title1, 'Some text' );
994 $revId = $this->editPage( $title1, 'Some more text' )
995 ->getNewRevision()->getId();
997 $title2 = Title
::makeTitle( NS_HELP
, 'TestUndoToRevFromDifferentPage-2' );
998 $this->editPage( $title2, 'Some text' );
1000 $this->expectApiErrorCode( 'revwrongpage' );
1002 $this->doApiRequestWithToken( [
1004 'title' => $title2->getPrefixedText(),
1009 public function testUndoAfterToRevFromDifferentPage() {
1010 $title1 = Title
::makeTitle( NS_HELP
, 'TestUndoAfterToRevFromDifferentPage-1' );
1011 $revId1 = $this->editPage( $title1, 'Some text' )
1012 ->getNewRevision()->getId();
1014 $title2 = Title
::makeTitle( NS_HELP
, 'TestUndoAfterToRevFromDifferentPage-2' );
1015 $revId2 = $this->editPage( $title2, 'Some text' )
1016 ->getNewRevision()->getId();
1018 $this->expectApiErrorCode( 'revwrongpage' );
1020 $this->doApiRequestWithToken( [
1022 'title' => $title2->getPrefixedText(),
1024 'undoafter' => $revId1,
1028 public function testMd5Text() {
1029 $title = Title
::makeTitle( NS_HELP
, 'TestMd5Text' );
1031 $this->assertFalse( $title->exists( IDBAccessObject
::READ_LATEST
) );
1033 $this->doApiRequestWithToken( [
1035 'title' => $title->getPrefixedText(),
1036 'text' => 'Some text',
1037 'md5' => md5( 'Some text' ),
1040 $this->assertTrue( $title->exists( IDBAccessObject
::READ_LATEST
) );
1043 public function testMd5PrependText() {
1044 $title = Title
::makeTitle( NS_HELP
, 'TestMd5PrependText' );
1046 $this->editPage( $title, 'Some text' );
1048 $this->doApiRequestWithToken( [
1050 'title' => $title->getPrefixedText(),
1051 'prependtext' => 'Alert: ',
1052 'md5' => md5( 'Alert: ' ),
1055 $text = $this->getServiceContainer()->getWikiPageFactory()->newFromTitle( $title )
1056 ->getContent()->getText();
1057 $this->assertSame( 'Alert: Some text', $text );
1060 public function testMd5AppendText() {
1061 $title = Title
::makeTitle( NS_HELP
, 'TestMd5AppendText' );
1063 $this->editPage( $title, 'Some text' );
1065 $this->doApiRequestWithToken( [
1067 'title' => $title->getPrefixedText(),
1068 'appendtext' => ' is nice',
1069 'md5' => md5( ' is nice' ),
1072 $text = $this->getServiceContainer()->getWikiPageFactory()->newFromTitle( $title )
1073 ->getContent()->getText();
1074 $this->assertSame( 'Some text is nice', $text );
1077 public function testMd5PrependAndAppendText() {
1078 $title = Title
::makeTitle( NS_HELP
, 'TestMd5PrependAndAppendText' );
1080 $this->editPage( $title, 'Some text' );
1082 $this->doApiRequestWithToken( [
1084 'title' => $title->getPrefixedText(),
1085 'prependtext' => 'Alert: ',
1086 'appendtext' => ' is nice',
1087 'md5' => md5( 'Alert: is nice' ),
1090 $text = $this->getServiceContainer()->getWikiPageFactory()->newFromTitle( $title )
1091 ->getContent()->getText();
1092 $this->assertSame( 'Alert: Some text is nice', $text );
1095 public function testIncorrectMd5Text() {
1096 $name = 'Help:' . ucfirst( __FUNCTION__
);
1098 $this->expectApiErrorCode( 'badmd5' );
1100 $this->doApiRequestWithToken( [
1103 'text' => 'Some text',
1108 public function testIncorrectMd5PrependText() {
1109 $name = 'Help:' . ucfirst( __FUNCTION__
);
1111 $this->expectApiErrorCode( 'badmd5' );
1113 $this->doApiRequestWithToken( [
1116 'prependtext' => 'Some ',
1117 'appendtext' => 'text',
1118 'md5' => md5( 'Some ' ),
1122 public function testIncorrectMd5AppendText() {
1123 $name = 'Help:' . ucfirst( __FUNCTION__
);
1125 $this->expectApiErrorCode( 'badmd5' );
1127 $this->doApiRequestWithToken( [
1130 'prependtext' => 'Some ',
1131 'appendtext' => 'text',
1132 'md5' => md5( 'text' ),
1136 public function testCreateOnly() {
1137 $title = Title
::makeTitle( NS_HELP
, 'TestCreateOnly' );
1139 $this->expectApiErrorCode( 'articleexists' );
1141 $this->editPage( $title, 'Some text' );
1142 $this->assertTrue( $title->exists( IDBAccessObject
::READ_LATEST
) );
1145 $this->doApiRequestWithToken( [
1147 'title' => $title->getPrefixedText(),
1148 'text' => 'Some more text',
1152 // Validate that content was not changed
1153 $text = $this->getServiceContainer()->getWikiPageFactory()->newFromTitle( $title )
1154 ->getContent()->getText();
1156 $this->assertSame( 'Some text', $text );
1160 public function testNoCreate() {
1161 $title = Title
::makeTitle( NS_HELP
, 'TestNoCreate' );
1163 $this->expectApiErrorCode( 'missingtitle' );
1165 $this->assertFalse( $title->exists( IDBAccessObject
::READ_LATEST
) );
1168 $this->doApiRequestWithToken( [
1170 'title' => $title->getPrefixedText(),
1171 'text' => 'Some text',
1175 $this->assertFalse( $title->exists( IDBAccessObject
::READ_LATEST
) );
1180 * Appending/prepending is currently only supported for TextContent. We
1181 * test this right now, and when support is added this test should be
1182 * replaced by tests that the support is correct.
1184 public function testAppendWithNonTextContentHandler() {
1185 $name = 'MediaWiki:' . ucfirst( __FUNCTION__
);
1187 $this->expectApiErrorCode( 'appendnotsupported' );
1189 $this->setTemporaryHook( 'ContentHandlerDefaultModelFor',
1190 static function ( Title
$title, &$model ) use ( $name ) {
1191 if ( $title->getPrefixedText() === $name ) {
1192 $model = 'testing-nontext';
1198 $this->doApiRequestWithToken( [
1201 'appendtext' => 'Some text',
1205 public function testAppendInMediaWikiNamespace() {
1206 $title = Title
::makeTitle( NS_MEDIAWIKI
, 'TestAppendInMediaWikiNamespace' );
1208 $this->assertFalse( $title->exists( IDBAccessObject
::READ_LATEST
) );
1210 $this->doApiRequestWithToken( [
1212 'title' => $title->getPrefixedText(),
1213 'appendtext' => 'Some text',
1216 $this->assertTrue( $title->exists( IDBAccessObject
::READ_LATEST
) );
1219 public function testAppendInMediaWikiNamespaceWithSerializationError() {
1220 $name = 'MediaWiki:' . ucfirst( __FUNCTION__
);
1222 $this->expectApiErrorCode( 'parseerror' );
1224 $this->setTemporaryHook( 'ContentHandlerDefaultModelFor',
1225 static function ( Title
$title, &$model ) use ( $name ) {
1226 if ( $title->getPrefixedText() === $name ) {
1227 $model = 'testing-serialize-error';
1233 $this->doApiRequestWithToken( [
1236 'appendtext' => 'Some text',
1240 public function testAppendNewSection() {
1241 $title = Title
::makeTitle( NS_HELP
, 'TestAppendNewSection' );
1243 $this->editPage( $title, 'Initial content' );
1245 $this->doApiRequestWithToken( [
1247 'title' => $title->getPrefixedText(),
1248 'appendtext' => '== New section ==',
1252 $text = $this->getServiceContainer()->getWikiPageFactory()->newFromTitle( $title )
1253 ->getContent()->getText();
1255 $this->assertSame( "Initial content\n\n== New section ==", $text );
1258 public function testAppendNewSectionWithInvalidContentModel() {
1259 $title = Title
::makeTitle( NS_HELP
, 'TestAppendNewSectionWithInvalidContentModel' );
1261 $this->expectApiErrorCode( 'sectionsnotsupported' );
1263 $this->editPage( $title, 'Initial content' );
1265 $this->doApiRequestWithToken( [
1267 'title' => $title->getPrefixedText(),
1268 'appendtext' => '== New section ==',
1270 'contentmodel' => 'text',
1274 public function testAppendNewSectionWithTitle() {
1275 $title = Title
::makeTitle( NS_HELP
, 'TestAppendNewSectionWithTitle' );
1277 $this->editPage( $title, 'Initial content' );
1279 $this->doApiRequestWithToken( [
1281 'title' => $title->getPrefixedText(),
1282 'sectiontitle' => 'My section',
1283 'appendtext' => 'More content',
1287 $page = $this->getServiceContainer()->getWikiPageFactory()->newFromTitle( $title );
1289 $this->assertSame( "Initial content\n\n== My section ==\n\nMore content",
1290 $page->getContent()->getText() );
1291 $comment = $page->getRevisionRecord()->getComment();
1292 $this->assertInstanceOf( CommentStoreComment
::class, $comment );
1293 $this->assertSame( '/* My section */ new section', $comment->text
);
1296 public function testAppendNewSectionWithSummary() {
1297 $title = Title
::makeTitle( NS_HELP
, 'TestAppendNewSectionWithSummary' );
1299 $this->editPage( $title, 'Initial content' );
1301 $this->doApiRequestWithToken( [
1303 'title' => $title->getPrefixedText(),
1304 'appendtext' => 'More content',
1306 'summary' => 'Add new section',
1309 $page = $this->getServiceContainer()->getWikiPageFactory()->newFromTitle( $title );
1311 $this->assertSame( "Initial content\n\n== Add new section ==\n\nMore content",
1312 $page->getContent()->getText() );
1313 // EditPage actually assumes the summary is the section name here
1314 $comment = $page->getRevisionRecord()->getComment();
1315 $this->assertInstanceOf( CommentStoreComment
::class, $comment );
1316 $this->assertSame( '/* Add new section */ new section', $comment->text
);
1319 public function testAppendNewSectionWithTitleAndSummary() {
1320 $title = Title
::makeTitle( NS_HELP
, 'TestAppendNewSectionWithTitleAndSummary' );
1322 $this->editPage( $title, 'Initial content' );
1324 $this->doApiRequestWithToken( [
1326 'title' => $title->getPrefixedText(),
1327 'sectiontitle' => 'My section',
1328 'appendtext' => 'More content',
1330 'summary' => 'Add new section',
1333 $page = $this->getServiceContainer()->getWikiPageFactory()->newFromTitle( $title );
1335 $this->assertSame( "Initial content\n\n== My section ==\n\nMore content",
1336 $page->getContent()->getText() );
1337 $comment = $page->getRevisionRecord()->getComment();
1338 $this->assertInstanceOf( CommentStoreComment
::class, $comment );
1339 $this->assertSame( 'Add new section', $comment->text
);
1342 public function testAppendToSection() {
1343 $title = Title
::makeTitle( NS_HELP
, 'TestAppendToSection' );
1345 $this->editPage( $title, "== Section 1 ==\n\nContent\n\n" .
1346 "== Section 2 ==\n\nFascinating!" );
1348 $this->doApiRequestWithToken( [
1350 'title' => $title->getPrefixedText(),
1351 'appendtext' => ' and more content',
1355 $text = $this->getServiceContainer()->getWikiPageFactory()->newFromTitle( $title )
1356 ->getContent()->getText();
1358 $this->assertSame( "== Section 1 ==\n\nContent and more content\n\n" .
1359 "== Section 2 ==\n\nFascinating!", $text );
1362 public function testAppendToFirstSection() {
1363 $title = Title
::makeTitle( NS_HELP
, 'TestAppendToFirstSection' );
1365 $this->editPage( $title, "Content\n\n== Section 1 ==\n\nFascinating!" );
1367 $this->doApiRequestWithToken( [
1369 'title' => $title->getPrefixedText(),
1370 'appendtext' => ' and more content',
1374 $text = $this->getServiceContainer()->getWikiPageFactory()->newFromTitle( $title )
1375 ->getContent()->getText();
1377 $this->assertSame( "Content and more content\n\n== Section 1 ==\n\n" .
1378 "Fascinating!", $text );
1381 public function testAppendToNonexistentSection() {
1382 $title = Title
::makeTitle( NS_HELP
, 'TestAppendToNonexistentSection' );
1384 $this->expectApiErrorCode( 'nosuchsection' );
1386 $this->editPage( $title, 'Content' );
1389 $this->doApiRequestWithToken( [
1391 'title' => $title->getPrefixedText(),
1392 'appendtext' => ' and more content',
1396 $text = $this->getServiceContainer()->getWikiPageFactory()->newFromTitle( $title )
1397 ->getContent()->getText();
1399 $this->assertSame( 'Content', $text );
1403 public function testEditMalformedSection() {
1404 $title = Title
::makeTitle( NS_HELP
, 'TestEditMalformedSection' );
1406 $this->expectApiErrorCode( 'invalidsection' );
1407 $this->editPage( $title, 'Content' );
1410 $this->doApiRequestWithToken( [
1412 'title' => $title->getPrefixedText(),
1413 'text' => 'Different content',
1414 'section' => 'It is unlikely that this is valid',
1417 $text = $this->getServiceContainer()->getWikiPageFactory()->newFromTitle( $title )
1418 ->getContent()->getText();
1420 $this->assertSame( 'Content', $text );
1424 public function testEditWithStartTimestamp() {
1425 $title = Title
::makeTitle( NS_HELP
, 'TestEditWithStartTimestamp' );
1426 $this->expectApiErrorCode( 'pagedeleted' );
1428 $startTime = MWTimestamp
::convert( TS_MW
, time() - 1 );
1430 $this->editPage( $title, 'Some text' );
1432 $pageObj = $this->getServiceContainer()->getWikiPageFactory()->newFromTitle( $title );
1433 $this->deletePage( $pageObj );
1435 $this->assertFalse( $pageObj->exists() );
1438 $this->doApiRequestWithToken( [
1440 'title' => $title->getPrefixedText(),
1441 'text' => 'Different text',
1442 'starttimestamp' => $startTime,
1445 $this->assertFalse( $pageObj->exists() );
1449 public function testEditMinor() {
1450 $title = Title
::makeTitle( NS_HELP
, 'TestEditMinor' );
1452 $this->editPage( $title, 'Some text' );
1454 $this->doApiRequestWithToken( [
1456 'title' => $title->getPrefixedText(),
1457 'text' => 'Different text',
1461 $revisionStore = $this->getServiceContainer()->getRevisionStore();
1462 $revision = $revisionStore->getRevisionByTitle( $title );
1463 $this->assertTrue( $revision->isMinor() );
1466 public function testEditRecreate() {
1467 $title = Title
::makeTitle( NS_HELP
, 'TestEditRecreate' );
1469 $startTime = MWTimestamp
::convert( TS_MW
, time() - 1 );
1471 $this->editPage( $title, 'Some text' );
1473 $pageObj = $this->getServiceContainer()->getWikiPageFactory()->newFromTitle( $title );
1474 $this->deletePage( $pageObj );
1476 $this->assertFalse( $pageObj->exists() );
1478 $this->doApiRequestWithToken( [
1480 'title' => $title->getPrefixedText(),
1481 'text' => 'Different text',
1482 'starttimestamp' => $startTime,
1486 $this->assertTrue( $title->exists( IDBAccessObject
::READ_LATEST
) );
1489 public function testEditWatch() {
1490 $title = Title
::makeTitle( NS_HELP
, 'TestEditWatch' );
1491 $user = $this->getTestSysop()->getUser();
1492 $watchlistManager = $this->getServiceContainer()->getWatchlistManager();
1494 $this->doApiRequestWithToken( [
1496 'title' => $title->getPrefixedText(),
1497 'text' => 'Some text',
1499 'watchlistexpiry' => '99990123000000',
1502 $this->assertTrue( $title->exists( IDBAccessObject
::READ_LATEST
) );
1503 $this->assertTrue( $watchlistManager->isWatched( $user, $title ) );
1504 $this->assertTrue( $watchlistManager->isTempWatched( $user, $title ) );
1507 public function testEditUnwatch() {
1508 $title = Title
::makeTitle( NS_HELP
, 'TestEditUnwatch' );
1509 $user = $this->getTestSysop()->getUser();
1511 $watchlistManager = $this->getServiceContainer()->getWatchlistManager();
1512 $watchlistManager->addWatch( $user, $title );
1514 $this->assertFalse( $title->exists() );
1515 $this->assertTrue( $watchlistManager->isWatched( $user, $title ) );
1517 $this->doApiRequestWithToken( [
1519 'title' => $title->getPrefixedText(),
1520 'text' => 'Some text',
1524 $this->assertTrue( $title->exists( IDBAccessObject
::READ_LATEST
) );
1525 $this->assertFalse( $watchlistManager->isWatched( $user, $title ) );
1528 public function testEditWithTag() {
1529 $name = 'Help:' . ucfirst( __FUNCTION__
);
1531 $this->getServiceContainer()->getChangeTagsStore()->defineTag( 'custom tag' );
1533 $revId = $this->doApiRequestWithToken( [
1536 'text' => 'Some text',
1537 'tags' => 'custom tag',
1538 ] )[0]['edit']['newrevid'];
1540 $this->assertSame( 'custom tag', $this->getDb()->newSelectQueryBuilder()
1541 ->select( 'ctd_name' )
1542 ->from( 'change_tag' )
1543 ->join( 'change_tag_def', null, 'ctd_id = ct_tag_id' )
1544 ->where( [ 'ct_rev_id' => $revId ] )
1545 ->caller( __METHOD__
)->fetchField() );
1548 public function testEditWithoutTagPermission() {
1549 $title = Title
::makeTitle( NS_HELP
, 'TestEditWithoutTagPermission' );
1551 $this->expectApiErrorCode( 'tags-apply-no-permission' );
1553 $this->assertFalse( $title->exists( IDBAccessObject
::READ_LATEST
) );
1555 $this->getServiceContainer()->getChangeTagsStore()->defineTag( 'custom tag' );
1556 $this->overrideConfigValue(
1557 MainConfigNames
::RevokePermissions
,
1558 [ 'user' => [ 'applychangetags' => true ] ]
1562 $this->doApiRequestWithToken( [
1564 'title' => $title->getPrefixedText(),
1565 'text' => 'Some text',
1566 'tags' => 'custom tag',
1569 $this->assertFalse( $title->exists( IDBAccessObject
::READ_LATEST
) );
1573 public function testEditAbortedByEditPageHookWithResult() {
1574 $title = Title
::makeTitle( NS_HELP
, 'TestEditAbortedByEditPageHookWithResult' );
1576 $this->setTemporaryHook( 'EditFilterMergedContent',
1577 static function ( $unused1, $unused2, Status
$status ) {
1578 $status->statusData
= [ 'msg' => 'A message for you!' ];
1582 $res = $this->doApiRequestWithToken( [
1584 'title' => $title->getPrefixedText(),
1585 'text' => 'Some text',
1588 $this->assertFalse( $title->exists( IDBAccessObject
::READ_LATEST
) );
1589 $this->assertSame( [ 'edit' => [ 'msg' => 'A message for you!',
1590 'result' => 'Failure' ] ], $res[0] );
1593 public function testEditAbortedByEditPageHookWithNoResult() {
1594 $title = Title
::makeTitle( NS_HELP
, 'TestEditAbortedByEditPageHookWithNoResult' );
1596 $this->expectApiErrorCode( 'hookaborted' );
1598 $this->setTemporaryHook( 'EditFilterMergedContent',
1599 static function () {
1605 $this->doApiRequestWithToken( [
1607 'title' => $title->getPrefixedText(),
1608 'text' => 'Some text',
1611 $this->assertFalse( $title->exists( IDBAccessObject
::READ_LATEST
) );
1615 public function testEditWhileBlocked() {
1616 $name = 'Help:' . ucfirst( __FUNCTION__
);
1618 $blockStore = $this->getServiceContainer()->getDatabaseBlockStore();
1619 $this->assertNull( $blockStore->newFromTarget( '127.0.0.1' ) );
1621 $user = $this->getTestSysop()->getUser();
1622 $block = new DatabaseBlock( [
1623 'address' => $user->getName(),
1625 'reason' => 'Capriciousness',
1626 'timestamp' => '19370101000000',
1627 'expiry' => 'infinity',
1628 'enableAutoblock' => true,
1630 $blockStore->insertBlock( $block );
1633 $this->doApiRequestWithToken( [
1636 'text' => 'Some text',
1638 $this->fail( 'Expected exception not thrown' );
1639 } catch ( ApiUsageException
$ex ) {
1640 $this->assertApiErrorCode( 'blocked', $ex );
1641 $this->assertNotNull( $blockStore->newFromTarget( '127.0.0.1' ), 'Autoblock spread' );
1645 public function testEditWhileReadOnly() {
1646 $name = 'Help:' . ucfirst( __FUNCTION__
);
1648 // Create the test user before making the DB readonly
1649 $user = $this->getTestSysop()->getUser();
1650 $this->expectApiErrorCode( 'readonly' );
1652 $svc = $this->getServiceContainer()->getReadOnlyMode();
1653 $svc->setReason( "Read-only for testing" );
1656 $this->doApiRequestWithToken( [
1659 'text' => 'Some text',
1662 $svc->setReason( false );
1666 public function testCreateImageRedirectAnon() {
1667 $this->disableAutoCreateTempUser();
1668 $name = 'File:' . ucfirst( __FUNCTION__
);
1670 $this->expectApiErrorCode( 'noimageredirect-anon' );
1672 $this->doApiRequestWithToken( [
1675 'text' => '#REDIRECT [[File:Other file.png]]',
1676 ], null, new User() );
1679 public function testCreateImageRedirectLoggedIn() {
1680 $name = 'File:' . ucfirst( __FUNCTION__
);
1682 $this->expectApiErrorCode( 'noimageredirect' );
1684 $this->overrideConfigValue(
1685 MainConfigNames
::RevokePermissions
,
1686 [ 'user' => [ 'upload' => true ] ]
1689 $this->doApiRequestWithToken( [
1692 'text' => '#REDIRECT [[File:Other file.png]]',
1696 public function testTooBigEdit() {
1697 $name = 'Help:' . ucfirst( __FUNCTION__
);
1699 $this->expectApiErrorCode( 'contenttoobig' );
1701 $this->overrideConfigValue( MainConfigNames
::MaxArticleSize
, 1 );
1703 $text = str_repeat( '!', 1025 );
1705 $this->doApiRequestWithToken( [
1712 public function testProhibitedAnonymousEdit() {
1713 $name = 'Help:' . ucfirst( __FUNCTION__
);
1715 $this->expectApiErrorCode( 'permissiondenied' );
1717 $this->overrideConfigValue(
1718 MainConfigNames
::RevokePermissions
,
1719 [ '*' => [ 'edit' => true ] ]
1722 $this->doApiRequestWithToken( [
1725 'text' => 'Some text',
1726 ], null, new User() );
1729 public function testProhibitedChangeContentModel() {
1730 $name = 'Help:' . ucfirst( __FUNCTION__
);
1732 $this->expectApiErrorCode( 'cantchangecontentmodel' );
1734 $this->overrideConfigValue(
1735 MainConfigNames
::RevokePermissions
,
1736 [ 'user' => [ 'editcontentmodel' => true ] ]
1739 $this->doApiRequestWithToken( [
1742 'text' => 'Some text',
1743 'contentmodel' => 'json',
1747 public function testMidEditContentModelMismatch() {
1748 $title = Title
::makeTitle( NS_HELP
, 'TestMidEditContentModelMismatch' );
1750 $page = $this->getServiceContainer()->getWikiPageFactory()->newFromTitle( $title );
1752 // base edit, currently in Wikitext
1753 $page->doUserEditContent(
1754 new WikitextContent( "Foo" ),
1755 $this->getTestSysop()->getUser(),
1759 $this->forceRevisionDate( $page, '20120101000000' );
1760 $baseId = $page->getRevisionRecord()->getId();
1762 // Attempt edit in Javascript. This may happen, for instance, if we
1763 // started editing the base content while it was in Javascript and
1764 // before we save it was changed to Wikitext (base edit model).
1765 $page->doUserEditContent(
1766 new JavaScriptContent( "Bar" ),
1767 $this->getTestUser()->getUser(),
1771 $this->forceRevisionDate( $page, '20120101020202' );
1773 // ContentHandler may throw exception if we attempt saving the above, so we will
1774 // handle that with contentmodel-mismatch error. Test this is the case.
1776 $this->doApiRequestWithToken( [
1778 'title' => $title->getPrefixedText(),
1779 'text' => 'different content models!',
1780 'baserevid' => $baseId,
1782 $this->fail( "Should have raised an ApiUsageException" );
1783 } catch ( ApiUsageException
$e ) {
1784 $this->assertApiErrorCode( 'contentmodel-mismatch', $e );