wiki.techinc.nl/tests/phpunit/includes/api/ApiEditPageTest.php
Ebrahim Byagowi 4c270a72ac Add namespace to WikitextContent
It adds MediaWiki\Content namespace to WikitextContent
and two classes related.

Change-Id: Ib74e4c5b3edac6aa0e35d3b2093ce1d0b794cb6d
2024-08-06 17:42:51 +03:30

1787 lines
52 KiB
PHP

<?php
namespace MediaWiki\Tests\Api;
use ApiUsageException;
use IDBAccessObject;
use MediaWiki\Block\DatabaseBlock;
use MediaWiki\CommentStore\CommentStoreComment;
use MediaWiki\Content\JavaScriptContent;
use MediaWiki\Content\WikitextContent;
use MediaWiki\Context\RequestContext;
use MediaWiki\MainConfigNames;
use MediaWiki\Revision\RevisionRecord;
use MediaWiki\Status\Status;
use MediaWiki\Tests\User\TempUser\TempUserTestTrait;
use MediaWiki\Title\Title;
use MediaWiki\Title\TitleValue;
use MediaWiki\User\User;
use MediaWiki\Utils\MWTimestamp;
use RevisionDeleter;
use WikiPage;
/**
* Tests for MediaWiki api.php?action=edit.
*
* @author Daniel Kinzler
*
* @group API
* @group Database
* @group medium
*
* @covers \ApiEditPage
*/
class ApiEditPageTest extends ApiTestCase {
use TempUserTestTrait;
protected function setUp(): void {
parent::setUp();
$this->overrideConfigValues( [
MainConfigNames::ExtraNamespaces => [
12312 => 'Dummy',
12313 => 'Dummy_talk',
12314 => 'DummyNonText',
12315 => 'DummyNonText_talk',
],
MainConfigNames::NamespaceContentModels => [
12312 => 'testing',
12314 => 'testing-nontext',
],
MainConfigNames::WatchlistExpiry => true,
MainConfigNames::WatchlistExpiryMaxDuration => '6 months',
] );
$this->mergeMwGlobalArrayValue( 'wgContentHandlers', [
'testing' => 'DummyContentHandlerForTesting',
'testing-nontext' => 'DummyNonTextContentHandler',
'testing-serialize-error' => 'DummySerializeErrorContentHandler',
] );
}
public function testEdit() {
$name = 'Help:ApiEditPageTest_testEdit'; // assume Help namespace to default to wikitext
// -- test new page --------------------------------------------
$apiResult = $this->doApiRequestWithToken( [
'action' => 'edit',
'title' => $name,
'text' => 'some text',
] );
$apiResult = $apiResult[0];
// Validate API result data
$this->assertArrayHasKey( 'edit', $apiResult );
$this->assertArrayHasKey( 'result', $apiResult['edit'] );
$this->assertSame( 'Success', $apiResult['edit']['result'] );
$this->assertArrayHasKey( 'new', $apiResult['edit'] );
$this->assertArrayNotHasKey( 'nochange', $apiResult['edit'] );
$this->assertArrayHasKey( 'pageid', $apiResult['edit'] );
// -- test existing page, no change ----------------------------
$data = $this->doApiRequestWithToken( [
'action' => 'edit',
'title' => $name,
'text' => 'some text',
] );
$this->assertSame( 'Success', $data[0]['edit']['result'] );
$this->assertArrayNotHasKey( 'new', $data[0]['edit'] );
$this->assertArrayHasKey( 'nochange', $data[0]['edit'] );
// -- test existing page, with change --------------------------
$data = $this->doApiRequestWithToken( [
'action' => 'edit',
'title' => $name,
'text' => 'different text'
] );
$this->assertSame( 'Success', $data[0]['edit']['result'] );
$this->assertArrayNotHasKey( 'new', $data[0]['edit'] );
$this->assertArrayNotHasKey( 'nochange', $data[0]['edit'] );
$this->assertArrayHasKey( 'oldrevid', $data[0]['edit'] );
$this->assertArrayHasKey( 'newrevid', $data[0]['edit'] );
$this->assertNotEquals(
$data[0]['edit']['newrevid'],
$data[0]['edit']['oldrevid'],
"revision id should change after edit"
);
}
/**
* @return array
*/
public static function provideEditAppend() {
return [
[ # 0: append
'foo', 'append', 'bar', "foobar"
],
[ # 1: prepend
'foo', 'prepend', 'bar', "barfoo"
],
[ # 2: append to empty page
'', 'append', 'foo', "foo"
],
[ # 3: prepend to empty page
'', 'prepend', 'foo', "foo"
],
[ # 4: append to non-existing page
null, 'append', 'foo', "foo"
],
[ # 5: prepend to non-existing page
null, 'prepend', 'foo', "foo"
],
];
}
/**
* @dataProvider provideEditAppend
*/
public function testEditAppend( $text, $op, $append, $expected ) {
static $count = 0;
$count++;
// assume NS_HELP defaults to wikitext
$title = Title::makeTitle( NS_HELP, "ApiEditPageTest_testEditAppend_$count" );
// -- create page (or not) -----------------------------------------
if ( $text !== null ) {
[ $re ] = $this->doApiRequestWithToken( [
'action' => 'edit',
'title' => $title->getPrefixedText(),
'text' => $text, ] );
$this->assertSame( 'Success', $re['edit']['result'] );
}
// -- try append/prepend --------------------------------------------
[ $re ] = $this->doApiRequestWithToken( [
'action' => 'edit',
'title' => $title->getPrefixedText(),
$op . 'text' => $append, ] );
$this->assertSame( 'Success', $re['edit']['result'] );
// -- validate -----------------------------------------------------
$page = $this->getServiceContainer()->getWikiPageFactory()->newFromTitle( $title );
$content = $page->getContent();
$this->assertNotNull( $content, 'Page should have been created' );
$text = $content->getText();
$this->assertSame( $expected, $text );
}
/**
* Test editing of sections
*/
public function testEditSection() {
$title = Title::makeTitle( NS_HELP, 'ApiEditPageTest_testEditSection' );
$wikiPageFactory = $this->getServiceContainer()->getWikiPageFactory();
$page = $wikiPageFactory->newFromTitle( $title );
$text = "==section 1==\ncontent 1\n==section 2==\ncontent2";
// Preload the page with some text
$page->doUserEditContent(
$page->getContentHandler()->unserializeContent( $text ),
$this->getTestSysop()->getAuthority(),
'summary'
);
[ $re ] = $this->doApiRequestWithToken( [
'action' => 'edit',
'title' => $title->getPrefixedText(),
'section' => '1',
'text' => "==section 1==\nnew content 1",
] );
$this->assertSame( 'Success', $re['edit']['result'] );
$newtext = $wikiPageFactory->newFromTitle( $title )
->getContent( RevisionRecord::RAW )
->getText();
$this->assertSame( "==section 1==\nnew content 1\n\n==section 2==\ncontent2", $newtext );
// Test that we raise a 'nosuchsection' error
try {
$this->doApiRequestWithToken( [
'action' => 'edit',
'title' => $title->getPrefixedText(),
'section' => '9999',
'text' => 'text',
] );
$this->fail( "Should have raised an ApiUsageException" );
} catch ( ApiUsageException $e ) {
$this->assertApiErrorCode( 'nosuchsection', $e );
}
}
/**
* Test action=edit&section=new
* Run it twice so we test adding a new section on a
* page that doesn't exist (T54830) and one that
* does exist
*/
public function testEditNewSection() {
$title = Title::makeTitle( NS_HELP, 'ApiEditPageTest_testEditNewSection' );
$wikiPageFactory = $this->getServiceContainer()->getWikiPageFactory();
// Test on a page that does not already exist
$this->assertFalse( $title->exists() );
[ $re ] = $this->doApiRequestWithToken( [
'action' => 'edit',
'title' => $title->getPrefixedText(),
'section' => 'new',
'text' => 'test',
'summary' => 'header',
] );
$this->assertSame( 'Success', $re['edit']['result'] );
// Check the page text is correct
$text = $wikiPageFactory->newFromTitle( $title )
->getContent( RevisionRecord::RAW )
->getText();
$this->assertSame( "== header ==\n\ntest", $text );
// Now on one that does
$this->assertTrue( $title->exists( IDBAccessObject::READ_LATEST ) );
[ $re2 ] = $this->doApiRequestWithToken( [
'action' => 'edit',
'title' => $title->getPrefixedText(),
'section' => 'new',
'text' => 'test',
'summary' => 'header',
] );
$this->assertSame( 'Success', $re2['edit']['result'] );
$text = $wikiPageFactory->newFromTitle( $title )
->getContent( RevisionRecord::RAW )
->getText();
$this->assertSame( "== header ==\n\ntest\n\n== header ==\n\ntest", $text );
}
/**
* Test action=edit&section=new with different combinations of summary and sectiontitle.
*
* @dataProvider provideEditNewSectionSummarySectiontitle
*/
public function testEditNewSectionSummarySectiontitle(
$sectiontitle,
$summary,
$expectedText,
$expectedSummary
) {
static $count = 0;
$count++;
$title = Title::makeTitle( NS_HELP, 'ApiEditPageTest_testEditNewSectionSummarySectiontitle' . $count );
// Test edit 1 (new page)
$this->doApiRequestWithToken( [
'action' => 'edit',
'title' => $title->getPrefixedText(),
'section' => 'new',
'text' => 'text',
'sectiontitle' => $sectiontitle,
'summary' => $summary,
] );
$wikiPageFactory = $this->getServiceContainer()->getWikiPageFactory();
$wikiPage = $wikiPageFactory->newFromTitle( $title );
// Check the page text is correct
$savedText = $wikiPage->getContent( RevisionRecord::RAW )->getText();
$this->assertSame( $expectedText, $savedText, 'Correct text saved (new page)' );
// Check that the edit summary is correct
// (when not provided or empty, there is an autogenerated summary for page creation)
$savedSummary = $wikiPage->getRevisionRecord()->getComment( RevisionRecord::RAW )->text;
$expectedSummaryNew = $expectedSummary ?: wfMessage( 'autosumm-new' )->rawParams( $expectedText )
->inContentLanguage()->text();
$this->assertSame( $expectedSummaryNew, $savedSummary, 'Correct summary saved (new page)' );
// Clear the page
$this->editPage( $wikiPage, '' );
// Test edit 2 (existing page)
$this->doApiRequestWithToken( [
'action' => 'edit',
'title' => $title->getPrefixedText(),
'section' => 'new',
'text' => 'text',
'sectiontitle' => $sectiontitle,
'summary' => $summary,
] );
$wikiPage = $wikiPageFactory->newFromTitle( $title );
// Check the page text is correct
$savedText = $wikiPage->getContent( RevisionRecord::RAW )->getText();
$this->assertSame( $expectedText, $savedText, 'Correct text saved (existing page)' );
// Check that the edit summary is correct
$savedSummary = $wikiPage->getRevisionRecord()->getComment( RevisionRecord::RAW )->text;
$this->assertSame( $expectedSummary, $savedSummary, 'Correct summary saved (existing page)' );
}
public static function provideEditNewSectionSummarySectiontitle() {
$sectiontitleCases = [
'unset' => null,
'empty' => '',
'set' => 'sectiontitle',
];
$summaryCases = [
'unset' => null,
'empty' => '',
'set' => 'summary',
];
$expectedTexts = [
"text",
"text",
"== summary ==\n\ntext",
"text",
"text",
"text",
"== sectiontitle ==\n\ntext",
"== sectiontitle ==\n\ntext",
"== sectiontitle ==\n\ntext",
];
$expectedSummaries = [
'',
'',
'/* summary */ new section',
'',
'',
'summary',
'/* sectiontitle */ new section',
'/* sectiontitle */ new section',
'summary',
];
$i = 0;
foreach ( $sectiontitleCases as $sectiontitleDesc => $sectiontitle ) {
foreach ( $summaryCases as $summaryDesc => $summary ) {
$message = "sectiontitle $sectiontitleDesc, summary $summaryDesc";
yield $message => [
$sectiontitle,
$summary,
$expectedTexts[$i],
$expectedSummaries[$i],
];
$i++;
}
}
}
/**
* Ensure we can edit through a redirect, if adding a section
*/
public function testEdit_redirect() {
static $count = 0;
$count++;
// assume NS_HELP defaults to wikitext
$title = Title::makeTitle( NS_HELP, "ApiEditPageTest_testEdit_redirect_$count" );
$wikiPageFactory = $this->getServiceContainer()->getWikiPageFactory();
$page = $this->getExistingTestPage( $title );
$this->forceRevisionDate( $page, '20120101000000' );
$rtitle = Title::makeTitle( NS_HELP, "ApiEditPageTest_testEdit_redirect_r$count" );
$rpage = $wikiPageFactory->newFromTitle( $rtitle );
$baseTime = $page->getRevisionRecord()->getTimestamp();
// base edit for redirect
$rpage->doUserEditContent(
new WikitextContent( "#REDIRECT [[{$title->getPrefixedText()}]]" ),
$this->getTestSysop()->getUser(),
"testing 1",
EDIT_NEW
);
$this->forceRevisionDate( $rpage, '20120101000000' );
// conflicting edit to redirect
$rpage->doUserEditContent(
new WikitextContent( "#REDIRECT [[{$title->getPrefixedText()}]]\n\n[[Category:Test]]" ),
$this->getTestUser()->getUser(),
"testing 2",
EDIT_UPDATE
);
$this->forceRevisionDate( $rpage, '20120101020202' );
// try to save edit, following the redirect
[ $re, , ] = $this->doApiRequestWithToken( [
'action' => 'edit',
'title' => $rtitle->getPrefixedText(),
'text' => 'nix bar!',
'basetimestamp' => $baseTime,
'section' => 'new',
'redirect' => true,
] );
$this->assertSame( 'Success', $re['edit']['result'],
"no problems expected when following redirect" );
}
/**
* Ensure we cannot edit through a redirect, if attempting to overwrite content
*/
public function testEdit_redirectText() {
static $count = 0;
$count++;
// assume NS_HELP defaults to wikitext
$title = Title::makeTitle( NS_HELP, "ApiEditPageTest_testEdit_redirectText_$count" );
$wikiPageFactory = $this->getServiceContainer()->getWikiPageFactory();
$page = $this->getExistingTestPage( $title );
$this->forceRevisionDate( $page, '20120101000000' );
$baseTime = $page->getRevisionRecord()->getTimestamp();
$rtitle = Title::makeTitle( NS_HELP, "ApiEditPageTest_testEdit_redirectText_r$count" );
$rpage = $wikiPageFactory->newFromTitle( $rtitle );
// base edit for redirect
$rpage->doUserEditContent(
new WikitextContent( "#REDIRECT [[{$title->getPrefixedText()}]]" ),
$this->getTestSysop()->getUser(),
"testing 1",
EDIT_NEW
);
$this->forceRevisionDate( $rpage, '20120101000000' );
// conflicting edit to redirect
$rpage->doUserEditContent(
new WikitextContent( "#REDIRECT [[{$title->getPrefixedText()}]]\n\n[[Category:Test]]" ),
$this->getTestUser()->getUser(),
"testing 2",
EDIT_UPDATE
);
$this->forceRevisionDate( $rpage, '20120101020202' );
// try to save edit, following the redirect but without creating a section
try {
$this->doApiRequestWithToken( [
'action' => 'edit',
'title' => $rtitle->getPrefixedText(),
'text' => 'nix bar!',
'basetimestamp' => $baseTime,
'redirect' => true,
] );
$this->fail( 'redirect-appendonly error expected' );
} catch ( ApiUsageException $ex ) {
$this->assertApiErrorCode( 'redirect-appendonly', $ex );
}
}
public function testEditConflict_revid() {
static $count = 0;
$count++;
// assume NS_HELP defaults to wikitext
$title = Title::makeTitle( NS_HELP, "ApiEditPageTest_testEditConflict_$count" );
$page = $this->getServiceContainer()->getWikiPageFactory()->newFromTitle( $title );
// base edit
$page->doUserEditContent(
new WikitextContent( "Foo" ),
$this->getTestSysop()->getUser(),
"testing 1",
EDIT_NEW
);
$this->forceRevisionDate( $page, '20120101000000' );
$baseId = $page->getRevisionRecord()->getId();
// conflicting edit
$page->doUserEditContent(
new WikitextContent( "Foo bar" ),
$this->getTestUser()->getUser(),
"testing 2",
EDIT_UPDATE
);
$this->forceRevisionDate( $page, '20120101020202' );
// try to save edit, expect conflict
try {
$this->doApiRequestWithToken( [
'action' => 'edit',
'title' => $title->getPrefixedText(),
'text' => 'nix bar!',
'baserevid' => $baseId,
], null, $this->getTestSysop()->getUser() );
$this->fail( 'edit conflict expected' );
} catch ( ApiUsageException $ex ) {
$this->assertApiErrorCode( 'editconflict', $ex );
}
}
public function testEditConflict_timestamp() {
static $count = 0;
$count++;
// assume NS_HELP defaults to wikitext
$title = Title::makeTitle( NS_HELP, "ApiEditPageTest_testEditConflict_$count" );
$page = $this->getServiceContainer()->getWikiPageFactory()->newFromTitle( $title );
// base edit
$page->doUserEditContent(
new WikitextContent( "Foo" ),
$this->getTestSysop()->getUser(),
"testing 1",
EDIT_NEW
);
$this->forceRevisionDate( $page, '20120101000000' );
$baseTime = $page->getRevisionRecord()->getTimestamp();
// conflicting edit
$page->doUserEditContent(
new WikitextContent( "Foo bar" ),
$this->getTestUser()->getUser(),
"testing 2",
EDIT_UPDATE
);
$this->forceRevisionDate( $page, '20120101020202' );
// try to save edit, expect conflict
try {
$this->doApiRequestWithToken( [
'action' => 'edit',
'title' => $title->getPrefixedText(),
'text' => 'nix bar!',
'basetimestamp' => $baseTime,
] );
$this->fail( 'edit conflict expected' );
} catch ( ApiUsageException $ex ) {
$this->assertApiErrorCode( 'editconflict', $ex );
}
}
/**
* Ensure that editing using section=new will prevent simple conflicts
*/
public function testEditConflict_newSection() {
static $count = 0;
$count++;
// assume NS_HELP defaults to wikitext
$title = Title::makeTitle( NS_HELP, "ApiEditPageTest_testEditConflict_newSection_$count" );
$page = $this->getServiceContainer()->getWikiPageFactory()->newFromTitle( $title );
// base edit
$page->doUserEditContent(
new WikitextContent( "Foo" ),
$this->getTestSysop()->getUser(),
"testing 1",
EDIT_NEW
);
$this->forceRevisionDate( $page, '20120101000000' );
$baseTime = $page->getRevisionRecord()->getTimestamp();
// conflicting edit
$page->doUserEditContent(
new WikitextContent( "Foo bar" ),
$this->getTestUser()->getUser(),
"testing 2",
EDIT_UPDATE
);
$this->forceRevisionDate( $page, '20120101020202' );
// try to save edit, expect no conflict
[ $re, , ] = $this->doApiRequestWithToken( [
'action' => 'edit',
'title' => $title->getPrefixedText(),
'text' => 'nix bar!',
'basetimestamp' => $baseTime,
'section' => 'new',
] );
$this->assertSame( 'Success', $re['edit']['result'],
"no edit conflict expected here" );
}
public function testEditConflict_T43990() {
static $count = 0;
$count++;
/*
* T43990: if the target page has a newer revision than the redirect, then editing the
* redirect while specifying 'redirect' and *not* specifying 'basetimestamp' erroneously
* caused an edit conflict to be detected.
*/
// assume NS_HELP defaults to wikitext
$title = Title::makeTitle( NS_HELP, "ApiEditPageTest_testEditConflict_redirect_T43990_$count" );
$wikiPageFactory = $this->getServiceContainer()->getWikiPageFactory();
$page = $this->getExistingTestPage( $title );
$this->forceRevisionDate( $page, '20120101000000' );
$rtitle = Title::makeTitle( NS_HELP, "ApiEditPageTest_testEditConflict_redirect_T43990_r$count" );
$rpage = $wikiPageFactory->newFromTitle( $rtitle );
// base edit for redirect
$rpage->doUserEditContent(
new WikitextContent( "#REDIRECT [[{$title->getPrefixedText()}]]" ),
$this->getTestSysop()->getUser(),
"testing 1",
EDIT_NEW
);
$this->forceRevisionDate( $rpage, '20120101000000' );
// new edit to content
$page->doUserEditContent(
new WikitextContent( "Foo bar" ),
$this->getTestUser()->getUser(),
"testing 2",
EDIT_UPDATE
);
$this->forceRevisionDate( $rpage, '20120101020202' );
// try to save edit; should work, following the redirect.
[ $re, , ] = $this->doApiRequestWithToken( [
'action' => 'edit',
'title' => $rtitle->getPrefixedText(),
'text' => 'nix bar!',
'section' => 'new',
'redirect' => true,
] );
$this->assertSame( 'Success', $re['edit']['result'],
"no edit conflict expected here" );
}
/**
* @param WikiPage $page
* @param string|int $timestamp
*/
protected function forceRevisionDate( WikiPage $page, $timestamp ) {
$dbw = $this->getDb();
$dbw->newUpdateQueryBuilder()
->update( 'revision' )
->set( [ 'rev_timestamp' => $dbw->timestamp( $timestamp ) ] )
->where( [ 'rev_id' => $page->getLatest() ] )
->caller( __METHOD__ )->execute();
$page->clear();
}
public function testCheckDirectApiEditingDisallowed_forNonTextContent() {
$this->expectApiErrorCode( 'no-direct-editing' );
$this->doApiRequestWithToken( [
'action' => 'edit',
'title' => 'Dummy:ApiEditPageTest_nonTextPageEdit',
'text' => '{"animals":["kittens!"]}'
] );
}
public function testSupportsDirectApiEditing_withContentHandlerOverride() {
$name = 'DummyNonText:ApiEditPageTest_testNonTextEdit';
$data = 'some bla bla text';
$result = $this->doApiRequestWithToken( [
'action' => 'edit',
'title' => $name,
'text' => $data,
] );
$apiResult = $result[0];
// Validate API result data
$this->assertArrayHasKey( 'edit', $apiResult );
$this->assertArrayHasKey( 'result', $apiResult['edit'] );
$this->assertSame( 'Success', $apiResult['edit']['result'] );
$this->assertArrayHasKey( 'new', $apiResult['edit'] );
$this->assertArrayNotHasKey( 'nochange', $apiResult['edit'] );
$this->assertArrayHasKey( 'pageid', $apiResult['edit'] );
// validate resulting revision
$page = $this->getServiceContainer()->getWikiPageFactory()->newFromTitle( Title::newFromText( $name ) );
$this->assertSame( "testing-nontext", $page->getContentModel() );
$this->assertSame( $data, $page->getContent()->serialize() );
}
/**
* This test verifies that after changing the content model
* of a page, undoing that edit via the API will also
* undo the content model change.
*/
public function testUndoAfterContentModelChange() {
$name = 'Help:' . __FUNCTION__;
$sysop = $this->getTestSysop()->getUser();
$otherUser = $this->getTestUser()->getUser();
$apiResult = $this->doApiRequestWithToken( [
'action' => 'edit',
'title' => $name,
'text' => 'some text',
], null, $sysop )[0];
// Check success
$this->assertArrayHasKey( 'edit', $apiResult );
$this->assertArrayHasKey( 'result', $apiResult['edit'] );
$this->assertSame( 'Success', $apiResult['edit']['result'] );
$this->assertArrayHasKey( 'contentmodel', $apiResult['edit'] );
// Content model is wikitext
$this->assertSame( 'wikitext', $apiResult['edit']['contentmodel'] );
// Convert the page to JSON
$apiResult = $this->doApiRequestWithToken( [
'action' => 'edit',
'title' => $name,
'text' => '{}',
'contentmodel' => 'json',
], null, $otherUser )[0];
// Check success
$this->assertArrayHasKey( 'edit', $apiResult );
$this->assertArrayHasKey( 'result', $apiResult['edit'] );
$this->assertSame( 'Success', $apiResult['edit']['result'] );
$this->assertArrayHasKey( 'contentmodel', $apiResult['edit'] );
$this->assertSame( 'json', $apiResult['edit']['contentmodel'] );
$apiResult = $this->doApiRequestWithToken( [
'action' => 'edit',
'title' => $name,
'undo' => $apiResult['edit']['newrevid']
], null, $sysop )[0];
// Check success
$this->assertArrayHasKey( 'edit', $apiResult );
$this->assertArrayHasKey( 'result', $apiResult['edit'] );
$this->assertSame( 'Success', $apiResult['edit']['result'] );
$this->assertArrayHasKey( 'contentmodel', $apiResult['edit'] );
// Check that the contentmodel is back to wikitext now.
$this->assertSame( 'wikitext', $apiResult['edit']['contentmodel'] );
}
// The tests below are mostly not commented because they do exactly what
// you'd expect from the name.
public function testCorrectContentFormat() {
$title = Title::makeTitle( NS_HELP, 'TestCorrectContentFormat' );
$this->doApiRequestWithToken( [
'action' => 'edit',
'title' => $title->getPrefixedText(),
'text' => 'some text',
'contentmodel' => 'wikitext',
'contentformat' => 'text/x-wiki',
] );
$this->assertTrue( $title->exists( IDBAccessObject::READ_LATEST ) );
}
public function testUnsupportedContentFormat() {
$title = Title::makeTitle( NS_HELP, 'TestUnsupportedContentFormat' );
$this->expectApiErrorCode( 'badvalue' );
try {
$this->doApiRequestWithToken( [
'action' => 'edit',
'title' => $title->getPrefixedText(),
'text' => 'some text',
'contentformat' => 'nonexistent format',
] );
} finally {
$this->assertFalse( $title->exists( IDBAccessObject::READ_LATEST ) );
}
}
public function testMismatchedContentFormat() {
$title = Title::makeTitle( NS_HELP, 'TestMismatchedContentFormat' );
$this->expectApiErrorCode( 'badformat' );
try {
$this->doApiRequestWithToken( [
'action' => 'edit',
'title' => $title->getPrefixedText(),
'text' => 'some text',
'contentmodel' => 'wikitext',
'contentformat' => 'text/plain',
] );
} finally {
$this->assertFalse( $title->exists( IDBAccessObject::READ_LATEST ) );
}
}
public function testUndoToInvalidRev() {
$title = Title::makeTitle( NS_HELP, 'TestUndoToInvalidRev' );
$revId = $this->editPage( $title, 'Some text' )->getNewRevision()
->getId();
$revId++;
$this->expectApiErrorCode( 'nosuchrevid' );
$this->doApiRequestWithToken( [
'action' => 'edit',
'title' => $title->getPrefixedText(),
'undo' => $revId,
] );
}
/**
* Tests what happens if the undo parameter is a valid revision, but
* the undoafter parameter doesn't refer to a revision that exists in the
* database.
*/
public function testUndoAfterToInvalidRev() {
// We can't just pick a large number for undoafter (as in
// testUndoToInvalidRev above), because then MediaWiki will helpfully
// assume we switched around undo and undoafter and we'll test the code
// path for undo being invalid, not undoafter. So instead we delete
// the revision from the database. In real life this case could come
// up if a revision number was skipped, e.g., if two transactions try
// to insert new revision rows at once and the first one to succeed
// gets rolled back.
$page = $this->getServiceContainer()->getWikiPageFactory()
->newFromLinkTarget( new TitleValue( NS_HELP, 'TestUndoAfterToInvalidRev' ) );
$revId1 = $this->editPage( $page, '1' )->getNewRevision()->getId();
$revId2 = $this->editPage( $page, '2' )->getNewRevision()->getId();
$revId3 = $this->editPage( $page, '3' )->getNewRevision()->getId();
// Make the middle revision disappear
$dbw = $this->getDb();
$dbw->newDeleteQueryBuilder()
->deleteFrom( 'revision' )
->where( [ 'rev_id' => $revId2 ] )
->caller( __METHOD__ )->execute();
$dbw->newUpdateQueryBuilder()
->update( 'revision' )
->set( [ 'rev_parent_id' => $revId1 ] )
->where( [ 'rev_id' => $revId3 ] )
->caller( __METHOD__ )->execute();
$this->expectApiErrorCode( 'nosuchrevid' );
$this->doApiRequestWithToken( [
'action' => 'edit',
'title' => $page->getTitle()->getPrefixedText(),
'undo' => $revId3,
'undoafter' => $revId2,
] );
}
/**
* Tests what happens if the undo parameter is a valid revision, but
* undoafter is hidden (rev_deleted).
*/
public function testUndoAfterToHiddenRev() {
$page = $this->getServiceContainer()->getWikiPageFactory()
->newFromLinkTarget( new TitleValue( NS_HELP, 'TestUndoAfterToHiddenRev' ) );
$titleObj = $page->getTitle();
$this->editPage( $page, '0' );
$revId1 = $this->editPage( $page, '1' )->getNewRevision()->getId();
$revId2 = $this->editPage( $page, '2' )->getNewRevision()->getId();
// Hide the middle revision
$list = RevisionDeleter::createList( 'revision',
RequestContext::getMain(), $titleObj, [ $revId1 ] );
// Set a user for modifying the visibility, this is needed because
// setVisibility generates a log, which cannot be an anonymous user actor
// when temporary accounts are enabled.
RequestContext::getMain()->setUser( $this->getTestUser()->getUser() );
$list->setVisibility( [
'value' => [ RevisionRecord::DELETED_TEXT => 1 ],
'comment' => 'Bye-bye',
] );
$this->expectApiErrorCode( 'nosuchrevid' );
$this->doApiRequestWithToken( [
'action' => 'edit',
'title' => $titleObj->getPrefixedText(),
'undo' => $revId2,
'undoafter' => $revId1,
] );
}
/**
* Test undo when a revision with a higher id has an earlier timestamp.
* This can happen if importing an old revision.
*/
public function testUndoWithSwappedRevisions() {
$this->markTestSkippedIfNoDiff3();
$page = $this->getServiceContainer()->getWikiPageFactory()
->newFromLinkTarget( new TitleValue( NS_HELP, 'TestUndoWithSwappedRevisions' ) );
$this->editPage( $page, '0' );
$revId2 = $this->editPage( $page, '2' )->getNewRevision()->getId();
$revId1 = $this->editPage( $page, '1' )->getNewRevision()->getId();
// Now monkey with the timestamp
$dbw = $this->getDb();
$dbw->newUpdateQueryBuilder()
->update( 'revision' )
->set( [ 'rev_timestamp' => $dbw->timestamp( time() - 86400 ) ] )
->where( [ 'rev_id' => $revId1 ] )
->caller( __METHOD__ )->execute();
$this->doApiRequestWithToken( [
'action' => 'edit',
'title' => $page->getTitle()->getPrefixedText(),
'undo' => $revId2,
'undoafter' => $revId1,
] );
$page->loadPageData( IDBAccessObject::READ_LATEST );
$this->assertSame( '1', $page->getContent()->getText() );
}
public function testUndoWithConflicts() {
$this->expectApiErrorCode( 'undofailure' );
$page = $this->getServiceContainer()->getWikiPageFactory()
->newFromLinkTarget( new TitleValue( NS_HELP, 'TestUndoWithConflicts' ) );
$this->editPage( $page, '1' );
$revId = $this->editPage( $page, '2' )->getNewRevision()->getId();
$this->editPage( $page, '3' );
$this->doApiRequestWithToken( [
'action' => 'edit',
'title' => $page->getTitle()->getPrefixedText(),
'undo' => $revId,
] );
$page->loadPageData( IDBAccessObject::READ_LATEST );
$this->assertSame( '3', $page->getContent()->getText() );
}
public function testReversedUndoAfter() {
$this->markTestSkippedIfNoDiff3();
$page = $this->getServiceContainer()->getWikiPageFactory()
->newFromLinkTarget( new TitleValue( NS_HELP, 'TestReversedUndoAfter' ) );
$this->editPage( $page, '0' );
$revId1 = $this->editPage( $page, '1' )->getNewRevision()->getId();
$revId2 = $this->editPage( $page, '2' )->getNewRevision()->getId();
$this->doApiRequestWithToken( [
'action' => 'edit',
'title' => $page->getTitle()->getPrefixedText(),
'undo' => $revId1,
'undoafter' => $revId2,
] );
$page->loadPageData( IDBAccessObject::READ_LATEST );
$this->assertSame( '2', $page->getContent()->getText() );
}
public function testUndoToRevFromDifferentPage() {
$title1 = Title::makeTitle( NS_HELP, 'TestUndoToRevFromDifferentPage-1' );
$this->editPage( $title1, 'Some text' );
$revId = $this->editPage( $title1, 'Some more text' )
->getNewRevision()->getId();
$title2 = Title::makeTitle( NS_HELP, 'TestUndoToRevFromDifferentPage-2' );
$this->editPage( $title2, 'Some text' );
$this->expectApiErrorCode( 'revwrongpage' );
$this->doApiRequestWithToken( [
'action' => 'edit',
'title' => $title2->getPrefixedText(),
'undo' => $revId,
] );
}
public function testUndoAfterToRevFromDifferentPage() {
$title1 = Title::makeTitle( NS_HELP, 'TestUndoAfterToRevFromDifferentPage-1' );
$revId1 = $this->editPage( $title1, 'Some text' )
->getNewRevision()->getId();
$title2 = Title::makeTitle( NS_HELP, 'TestUndoAfterToRevFromDifferentPage-2' );
$revId2 = $this->editPage( $title2, 'Some text' )
->getNewRevision()->getId();
$this->expectApiErrorCode( 'revwrongpage' );
$this->doApiRequestWithToken( [
'action' => 'edit',
'title' => $title2->getPrefixedText(),
'undo' => $revId2,
'undoafter' => $revId1,
] );
}
public function testMd5Text() {
$title = Title::makeTitle( NS_HELP, 'TestMd5Text' );
$this->assertFalse( $title->exists( IDBAccessObject::READ_LATEST ) );
$this->doApiRequestWithToken( [
'action' => 'edit',
'title' => $title->getPrefixedText(),
'text' => 'Some text',
'md5' => md5( 'Some text' ),
] );
$this->assertTrue( $title->exists( IDBAccessObject::READ_LATEST ) );
}
public function testMd5PrependText() {
$title = Title::makeTitle( NS_HELP, 'TestMd5PrependText' );
$this->editPage( $title, 'Some text' );
$this->doApiRequestWithToken( [
'action' => 'edit',
'title' => $title->getPrefixedText(),
'prependtext' => 'Alert: ',
'md5' => md5( 'Alert: ' ),
] );
$text = $this->getServiceContainer()->getWikiPageFactory()->newFromTitle( $title )
->getContent()->getText();
$this->assertSame( 'Alert: Some text', $text );
}
public function testMd5AppendText() {
$title = Title::makeTitle( NS_HELP, 'TestMd5AppendText' );
$this->editPage( $title, 'Some text' );
$this->doApiRequestWithToken( [
'action' => 'edit',
'title' => $title->getPrefixedText(),
'appendtext' => ' is nice',
'md5' => md5( ' is nice' ),
] );
$text = $this->getServiceContainer()->getWikiPageFactory()->newFromTitle( $title )
->getContent()->getText();
$this->assertSame( 'Some text is nice', $text );
}
public function testMd5PrependAndAppendText() {
$title = Title::makeTitle( NS_HELP, 'TestMd5PrependAndAppendText' );
$this->editPage( $title, 'Some text' );
$this->doApiRequestWithToken( [
'action' => 'edit',
'title' => $title->getPrefixedText(),
'prependtext' => 'Alert: ',
'appendtext' => ' is nice',
'md5' => md5( 'Alert: is nice' ),
] );
$text = $this->getServiceContainer()->getWikiPageFactory()->newFromTitle( $title )
->getContent()->getText();
$this->assertSame( 'Alert: Some text is nice', $text );
}
public function testIncorrectMd5Text() {
$name = 'Help:' . ucfirst( __FUNCTION__ );
$this->expectApiErrorCode( 'badmd5' );
$this->doApiRequestWithToken( [
'action' => 'edit',
'title' => $name,
'text' => 'Some text',
'md5' => md5( '' ),
] );
}
public function testIncorrectMd5PrependText() {
$name = 'Help:' . ucfirst( __FUNCTION__ );
$this->expectApiErrorCode( 'badmd5' );
$this->doApiRequestWithToken( [
'action' => 'edit',
'title' => $name,
'prependtext' => 'Some ',
'appendtext' => 'text',
'md5' => md5( 'Some ' ),
] );
}
public function testIncorrectMd5AppendText() {
$name = 'Help:' . ucfirst( __FUNCTION__ );
$this->expectApiErrorCode( 'badmd5' );
$this->doApiRequestWithToken( [
'action' => 'edit',
'title' => $name,
'prependtext' => 'Some ',
'appendtext' => 'text',
'md5' => md5( 'text' ),
] );
}
public function testCreateOnly() {
$title = Title::makeTitle( NS_HELP, 'TestCreateOnly' );
$this->expectApiErrorCode( 'articleexists' );
$this->editPage( $title, 'Some text' );
$this->assertTrue( $title->exists( IDBAccessObject::READ_LATEST ) );
try {
$this->doApiRequestWithToken( [
'action' => 'edit',
'title' => $title->getPrefixedText(),
'text' => 'Some more text',
'createonly' => '',
] );
} finally {
// Validate that content was not changed
$text = $this->getServiceContainer()->getWikiPageFactory()->newFromTitle( $title )
->getContent()->getText();
$this->assertSame( 'Some text', $text );
}
}
public function testNoCreate() {
$title = Title::makeTitle( NS_HELP, 'TestNoCreate' );
$this->expectApiErrorCode( 'missingtitle' );
$this->assertFalse( $title->exists( IDBAccessObject::READ_LATEST ) );
try {
$this->doApiRequestWithToken( [
'action' => 'edit',
'title' => $title->getPrefixedText(),
'text' => 'Some text',
'nocreate' => '',
] );
} finally {
$this->assertFalse( $title->exists( IDBAccessObject::READ_LATEST ) );
}
}
/**
* Appending/prepending is currently only supported for TextContent. We
* test this right now, and when support is added this test should be
* replaced by tests that the support is correct.
*/
public function testAppendWithNonTextContentHandler() {
$name = 'MediaWiki:' . ucfirst( __FUNCTION__ );
$this->expectApiErrorCode( 'appendnotsupported' );
$this->setTemporaryHook( 'ContentHandlerDefaultModelFor',
static function ( Title $title, &$model ) use ( $name ) {
if ( $title->getPrefixedText() === $name ) {
$model = 'testing-nontext';
}
return true;
}
);
$this->doApiRequestWithToken( [
'action' => 'edit',
'title' => $name,
'appendtext' => 'Some text',
] );
}
public function testAppendInMediaWikiNamespace() {
$title = Title::makeTitle( NS_MEDIAWIKI, 'TestAppendInMediaWikiNamespace' );
$this->assertFalse( $title->exists( IDBAccessObject::READ_LATEST ) );
$this->doApiRequestWithToken( [
'action' => 'edit',
'title' => $title->getPrefixedText(),
'appendtext' => 'Some text',
] );
$this->assertTrue( $title->exists( IDBAccessObject::READ_LATEST ) );
}
public function testAppendInMediaWikiNamespaceWithSerializationError() {
$name = 'MediaWiki:' . ucfirst( __FUNCTION__ );
$this->expectApiErrorCode( 'parseerror' );
$this->setTemporaryHook( 'ContentHandlerDefaultModelFor',
static function ( Title $title, &$model ) use ( $name ) {
if ( $title->getPrefixedText() === $name ) {
$model = 'testing-serialize-error';
}
return true;
}
);
$this->doApiRequestWithToken( [
'action' => 'edit',
'title' => $name,
'appendtext' => 'Some text',
] );
}
public function testAppendNewSection() {
$title = Title::makeTitle( NS_HELP, 'TestAppendNewSection' );
$this->editPage( $title, 'Initial content' );
$this->doApiRequestWithToken( [
'action' => 'edit',
'title' => $title->getPrefixedText(),
'appendtext' => '== New section ==',
'section' => 'new',
] );
$text = $this->getServiceContainer()->getWikiPageFactory()->newFromTitle( $title )
->getContent()->getText();
$this->assertSame( "Initial content\n\n== New section ==", $text );
}
public function testAppendNewSectionWithInvalidContentModel() {
$title = Title::makeTitle( NS_HELP, 'TestAppendNewSectionWithInvalidContentModel' );
$this->expectApiErrorCode( 'sectionsnotsupported' );
$this->editPage( $title, 'Initial content' );
$this->doApiRequestWithToken( [
'action' => 'edit',
'title' => $title->getPrefixedText(),
'appendtext' => '== New section ==',
'section' => 'new',
'contentmodel' => 'text',
] );
}
public function testAppendNewSectionWithTitle() {
$title = Title::makeTitle( NS_HELP, 'TestAppendNewSectionWithTitle' );
$this->editPage( $title, 'Initial content' );
$this->doApiRequestWithToken( [
'action' => 'edit',
'title' => $title->getPrefixedText(),
'sectiontitle' => 'My section',
'appendtext' => 'More content',
'section' => 'new',
] );
$page = $this->getServiceContainer()->getWikiPageFactory()->newFromTitle( $title );
$this->assertSame( "Initial content\n\n== My section ==\n\nMore content",
$page->getContent()->getText() );
$comment = $page->getRevisionRecord()->getComment();
$this->assertInstanceOf( CommentStoreComment::class, $comment );
$this->assertSame( '/* My section */ new section', $comment->text );
}
public function testAppendNewSectionWithSummary() {
$title = Title::makeTitle( NS_HELP, 'TestAppendNewSectionWithSummary' );
$this->editPage( $title, 'Initial content' );
$this->doApiRequestWithToken( [
'action' => 'edit',
'title' => $title->getPrefixedText(),
'appendtext' => 'More content',
'section' => 'new',
'summary' => 'Add new section',
] );
$page = $this->getServiceContainer()->getWikiPageFactory()->newFromTitle( $title );
$this->assertSame( "Initial content\n\n== Add new section ==\n\nMore content",
$page->getContent()->getText() );
// EditPage actually assumes the summary is the section name here
$comment = $page->getRevisionRecord()->getComment();
$this->assertInstanceOf( CommentStoreComment::class, $comment );
$this->assertSame( '/* Add new section */ new section', $comment->text );
}
public function testAppendNewSectionWithTitleAndSummary() {
$title = Title::makeTitle( NS_HELP, 'TestAppendNewSectionWithTitleAndSummary' );
$this->editPage( $title, 'Initial content' );
$this->doApiRequestWithToken( [
'action' => 'edit',
'title' => $title->getPrefixedText(),
'sectiontitle' => 'My section',
'appendtext' => 'More content',
'section' => 'new',
'summary' => 'Add new section',
] );
$page = $this->getServiceContainer()->getWikiPageFactory()->newFromTitle( $title );
$this->assertSame( "Initial content\n\n== My section ==\n\nMore content",
$page->getContent()->getText() );
$comment = $page->getRevisionRecord()->getComment();
$this->assertInstanceOf( CommentStoreComment::class, $comment );
$this->assertSame( 'Add new section', $comment->text );
}
public function testAppendToSection() {
$title = Title::makeTitle( NS_HELP, 'TestAppendToSection' );
$this->editPage( $title, "== Section 1 ==\n\nContent\n\n" .
"== Section 2 ==\n\nFascinating!" );
$this->doApiRequestWithToken( [
'action' => 'edit',
'title' => $title->getPrefixedText(),
'appendtext' => ' and more content',
'section' => '1',
] );
$text = $this->getServiceContainer()->getWikiPageFactory()->newFromTitle( $title )
->getContent()->getText();
$this->assertSame( "== Section 1 ==\n\nContent and more content\n\n" .
"== Section 2 ==\n\nFascinating!", $text );
}
public function testAppendToFirstSection() {
$title = Title::makeTitle( NS_HELP, 'TestAppendToFirstSection' );
$this->editPage( $title, "Content\n\n== Section 1 ==\n\nFascinating!" );
$this->doApiRequestWithToken( [
'action' => 'edit',
'title' => $title->getPrefixedText(),
'appendtext' => ' and more content',
'section' => '0',
] );
$text = $this->getServiceContainer()->getWikiPageFactory()->newFromTitle( $title )
->getContent()->getText();
$this->assertSame( "Content and more content\n\n== Section 1 ==\n\n" .
"Fascinating!", $text );
}
public function testAppendToNonexistentSection() {
$title = Title::makeTitle( NS_HELP, 'TestAppendToNonexistentSection' );
$this->expectApiErrorCode( 'nosuchsection' );
$this->editPage( $title, 'Content' );
try {
$this->doApiRequestWithToken( [
'action' => 'edit',
'title' => $title->getPrefixedText(),
'appendtext' => ' and more content',
'section' => '1',
] );
} finally {
$text = $this->getServiceContainer()->getWikiPageFactory()->newFromTitle( $title )
->getContent()->getText();
$this->assertSame( 'Content', $text );
}
}
public function testEditMalformedSection() {
$title = Title::makeTitle( NS_HELP, 'TestEditMalformedSection' );
$this->expectApiErrorCode( 'invalidsection' );
$this->editPage( $title, 'Content' );
try {
$this->doApiRequestWithToken( [
'action' => 'edit',
'title' => $title->getPrefixedText(),
'text' => 'Different content',
'section' => 'It is unlikely that this is valid',
] );
} finally {
$text = $this->getServiceContainer()->getWikiPageFactory()->newFromTitle( $title )
->getContent()->getText();
$this->assertSame( 'Content', $text );
}
}
public function testEditWithStartTimestamp() {
$title = Title::makeTitle( NS_HELP, 'TestEditWithStartTimestamp' );
$this->expectApiErrorCode( 'pagedeleted' );
$startTime = MWTimestamp::convert( TS_MW, time() - 1 );
$this->editPage( $title, 'Some text' );
$pageObj = $this->getServiceContainer()->getWikiPageFactory()->newFromTitle( $title );
$this->deletePage( $pageObj );
$this->assertFalse( $pageObj->exists() );
try {
$this->doApiRequestWithToken( [
'action' => 'edit',
'title' => $title->getPrefixedText(),
'text' => 'Different text',
'starttimestamp' => $startTime,
] );
} finally {
$this->assertFalse( $pageObj->exists() );
}
}
public function testEditMinor() {
$title = Title::makeTitle( NS_HELP, 'TestEditMinor' );
$this->editPage( $title, 'Some text' );
$this->doApiRequestWithToken( [
'action' => 'edit',
'title' => $title->getPrefixedText(),
'text' => 'Different text',
'minor' => '',
] );
$revisionStore = $this->getServiceContainer()->getRevisionStore();
$revision = $revisionStore->getRevisionByTitle( $title );
$this->assertTrue( $revision->isMinor() );
}
public function testEditRecreate() {
$title = Title::makeTitle( NS_HELP, 'TestEditRecreate' );
$startTime = MWTimestamp::convert( TS_MW, time() - 1 );
$this->editPage( $title, 'Some text' );
$pageObj = $this->getServiceContainer()->getWikiPageFactory()->newFromTitle( $title );
$this->deletePage( $pageObj );
$this->assertFalse( $pageObj->exists() );
$this->doApiRequestWithToken( [
'action' => 'edit',
'title' => $title->getPrefixedText(),
'text' => 'Different text',
'starttimestamp' => $startTime,
'recreate' => '',
] );
$this->assertTrue( $title->exists( IDBAccessObject::READ_LATEST ) );
}
public function testEditWatch() {
$title = Title::makeTitle( NS_HELP, 'TestEditWatch' );
$user = $this->getTestSysop()->getUser();
$watchlistManager = $this->getServiceContainer()->getWatchlistManager();
$this->doApiRequestWithToken( [
'action' => 'edit',
'title' => $title->getPrefixedText(),
'text' => 'Some text',
'watch' => '',
'watchlistexpiry' => '99990123000000',
] );
$this->assertTrue( $title->exists( IDBAccessObject::READ_LATEST ) );
$this->assertTrue( $watchlistManager->isWatched( $user, $title ) );
$this->assertTrue( $watchlistManager->isTempWatched( $user, $title ) );
}
public function testEditUnwatch() {
$title = Title::makeTitle( NS_HELP, 'TestEditUnwatch' );
$user = $this->getTestSysop()->getUser();
$watchlistManager = $this->getServiceContainer()->getWatchlistManager();
$watchlistManager->addWatch( $user, $title );
$this->assertFalse( $title->exists() );
$this->assertTrue( $watchlistManager->isWatched( $user, $title ) );
$this->doApiRequestWithToken( [
'action' => 'edit',
'title' => $title->getPrefixedText(),
'text' => 'Some text',
'unwatch' => '',
] );
$this->assertTrue( $title->exists( IDBAccessObject::READ_LATEST ) );
$this->assertFalse( $watchlistManager->isWatched( $user, $title ) );
}
public function testEditWithTag() {
$name = 'Help:' . ucfirst( __FUNCTION__ );
$this->getServiceContainer()->getChangeTagsStore()->defineTag( 'custom tag' );
$revId = $this->doApiRequestWithToken( [
'action' => 'edit',
'title' => $name,
'text' => 'Some text',
'tags' => 'custom tag',
] )[0]['edit']['newrevid'];
$this->assertSame( 'custom tag', $this->getDb()->newSelectQueryBuilder()
->select( 'ctd_name' )
->from( 'change_tag' )
->join( 'change_tag_def', null, 'ctd_id = ct_tag_id' )
->where( [ 'ct_rev_id' => $revId ] )
->caller( __METHOD__ )->fetchField() );
}
public function testEditWithoutTagPermission() {
$title = Title::makeTitle( NS_HELP, 'TestEditWithoutTagPermission' );
$this->expectApiErrorCode( 'tags-apply-no-permission' );
$this->assertFalse( $title->exists( IDBAccessObject::READ_LATEST ) );
$this->getServiceContainer()->getChangeTagsStore()->defineTag( 'custom tag' );
$this->overrideConfigValue(
MainConfigNames::RevokePermissions,
[ 'user' => [ 'applychangetags' => true ] ]
);
try {
$this->doApiRequestWithToken( [
'action' => 'edit',
'title' => $title->getPrefixedText(),
'text' => 'Some text',
'tags' => 'custom tag',
] );
} finally {
$this->assertFalse( $title->exists( IDBAccessObject::READ_LATEST ) );
}
}
public function testEditAbortedByEditPageHookWithResult() {
$title = Title::makeTitle( NS_HELP, 'TestEditAbortedByEditPageHookWithResult' );
$this->setTemporaryHook( 'EditFilterMergedContent',
static function ( $unused1, $unused2, Status $status ) {
$status->statusData = [ 'msg' => 'A message for you!' ];
return false;
} );
$res = $this->doApiRequestWithToken( [
'action' => 'edit',
'title' => $title->getPrefixedText(),
'text' => 'Some text',
] );
$this->assertFalse( $title->exists( IDBAccessObject::READ_LATEST ) );
$this->assertSame( [ 'edit' => [ 'msg' => 'A message for you!',
'result' => 'Failure' ] ], $res[0] );
}
public function testEditAbortedByEditPageHookWithNoResult() {
$title = Title::makeTitle( NS_HELP, 'TestEditAbortedByEditPageHookWithNoResult' );
$this->expectApiErrorCode( 'hookaborted' );
$this->setTemporaryHook( 'EditFilterMergedContent',
static function () {
return false;
}
);
try {
$this->doApiRequestWithToken( [
'action' => 'edit',
'title' => $title->getPrefixedText(),
'text' => 'Some text',
] );
} finally {
$this->assertFalse( $title->exists( IDBAccessObject::READ_LATEST ) );
}
}
public function testEditWhileBlocked() {
$name = 'Help:' . ucfirst( __FUNCTION__ );
$blockStore = $this->getServiceContainer()->getDatabaseBlockStore();
$this->assertNull( $blockStore->newFromTarget( '127.0.0.1' ) );
$user = $this->getTestSysop()->getUser();
$block = new DatabaseBlock( [
'address' => $user->getName(),
'by' => $user,
'reason' => 'Capriciousness',
'timestamp' => '19370101000000',
'expiry' => 'infinity',
'enableAutoblock' => true,
] );
$blockStore->insertBlock( $block );
try {
$this->doApiRequestWithToken( [
'action' => 'edit',
'title' => $name,
'text' => 'Some text',
] );
$this->fail( 'Expected exception not thrown' );
} catch ( ApiUsageException $ex ) {
$this->assertApiErrorCode( 'blocked', $ex );
$this->assertNotNull( $blockStore->newFromTarget( '127.0.0.1' ), 'Autoblock spread' );
}
}
public function testEditWhileReadOnly() {
$name = 'Help:' . ucfirst( __FUNCTION__ );
// Create the test user before making the DB readonly
$user = $this->getTestSysop()->getUser();
$this->expectApiErrorCode( 'readonly' );
$svc = $this->getServiceContainer()->getReadOnlyMode();
$svc->setReason( "Read-only for testing" );
try {
$this->doApiRequestWithToken( [
'action' => 'edit',
'title' => $name,
'text' => 'Some text',
], null, $user );
} finally {
$svc->setReason( false );
}
}
public function testCreateImageRedirectAnon() {
$this->disableAutoCreateTempUser();
$name = 'File:' . ucfirst( __FUNCTION__ );
$this->expectApiErrorCode( 'noimageredirect-anon' );
$this->doApiRequestWithToken( [
'action' => 'edit',
'title' => $name,
'text' => '#REDIRECT [[File:Other file.png]]',
], null, new User() );
}
public function testCreateImageRedirectLoggedIn() {
$name = 'File:' . ucfirst( __FUNCTION__ );
$this->expectApiErrorCode( 'noimageredirect' );
$this->overrideConfigValue(
MainConfigNames::RevokePermissions,
[ 'user' => [ 'upload' => true ] ]
);
$this->doApiRequestWithToken( [
'action' => 'edit',
'title' => $name,
'text' => '#REDIRECT [[File:Other file.png]]',
] );
}
public function testTooBigEdit() {
$name = 'Help:' . ucfirst( __FUNCTION__ );
$this->expectApiErrorCode( 'contenttoobig' );
$this->overrideConfigValue( MainConfigNames::MaxArticleSize, 1 );
$text = str_repeat( '!', 1025 );
$this->doApiRequestWithToken( [
'action' => 'edit',
'title' => $name,
'text' => $text,
] );
}
public function testProhibitedAnonymousEdit() {
$name = 'Help:' . ucfirst( __FUNCTION__ );
$this->expectApiErrorCode( 'permissiondenied' );
$this->overrideConfigValue(
MainConfigNames::RevokePermissions,
[ '*' => [ 'edit' => true ] ]
);
$this->doApiRequestWithToken( [
'action' => 'edit',
'title' => $name,
'text' => 'Some text',
], null, new User() );
}
public function testProhibitedChangeContentModel() {
$name = 'Help:' . ucfirst( __FUNCTION__ );
$this->expectApiErrorCode( 'cantchangecontentmodel' );
$this->overrideConfigValue(
MainConfigNames::RevokePermissions,
[ 'user' => [ 'editcontentmodel' => true ] ]
);
$this->doApiRequestWithToken( [
'action' => 'edit',
'title' => $name,
'text' => 'Some text',
'contentmodel' => 'json',
] );
}
public function testMidEditContentModelMismatch() {
$title = Title::makeTitle( NS_HELP, 'TestMidEditContentModelMismatch' );
$page = $this->getServiceContainer()->getWikiPageFactory()->newFromTitle( $title );
// base edit, currently in Wikitext
$page->doUserEditContent(
new WikitextContent( "Foo" ),
$this->getTestSysop()->getUser(),
"testing 1",
EDIT_NEW
);
$this->forceRevisionDate( $page, '20120101000000' );
$baseId = $page->getRevisionRecord()->getId();
// Attempt edit in Javascript. This may happen, for instance, if we
// started editing the base content while it was in Javascript and
// before we save it was changed to Wikitext (base edit model).
$page->doUserEditContent(
new JavaScriptContent( "Bar" ),
$this->getTestUser()->getUser(),
"testing 2",
EDIT_UPDATE
);
$this->forceRevisionDate( $page, '20120101020202' );
// ContentHandler may throw exception if we attempt saving the above, so we will
// handle that with contentmodel-mismatch error. Test this is the case.
try {
$this->doApiRequestWithToken( [
'action' => 'edit',
'title' => $title->getPrefixedText(),
'text' => 'different content models!',
'baserevid' => $baseId,
] );
$this->fail( "Should have raised an ApiUsageException" );
} catch ( ApiUsageException $e ) {
$this->assertApiErrorCode( 'contentmodel-mismatch', $e );
}
}
}