123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719 |
- <?php
- use MediaWiki\MediaWikiServices;
- /**
- * @group Editing
- *
- * @group Database
- * ^--- tell jenkins this test needs the database
- *
- * @group medium
- * ^--- tell phpunit that these test cases may take longer than 2 seconds.
- */
- class EditPageTest extends MediaWikiLangTestCase {
- protected function setUp() {
- parent::setUp();
- $contLang = MediaWikiServices::getInstance()->getContentLanguage();
- $this->setContentLang( $contLang );
- $this->setMwGlobals( [
- 'wgExtraNamespaces' => [
- 12312 => 'Dummy',
- 12313 => 'Dummy_talk',
- ],
- 'wgNamespaceContentModels' => [ 12312 => 'testing' ],
- ] );
- $this->mergeMwGlobalArrayValue(
- 'wgContentHandlers',
- [ 'testing' => 'DummyContentHandlerForTesting' ]
- );
- }
- /**
- * @dataProvider provideExtractSectionTitle
- * @covers EditPage::extractSectionTitle
- */
- public function testExtractSectionTitle( $section, $title ) {
- $extracted = EditPage::extractSectionTitle( $section );
- $this->assertEquals( $title, $extracted );
- }
- public static function provideExtractSectionTitle() {
- return [
- [
- "== Test ==\n\nJust a test section.",
- "Test"
- ],
- [
- "An initial section, no header.",
- false
- ],
- [
- "An initial section with a fake heder (T34617)\n\n== Test == ??\nwtf",
- false
- ],
- [
- "== Section ==\nfollowed by a fake == Non-section == ??\nnoooo",
- "Section"
- ],
- [
- "== Section== \t\r\n followed by whitespace (T37051)",
- 'Section',
- ],
- ];
- }
- protected function forceRevisionDate( WikiPage $page, $timestamp ) {
- $dbw = wfGetDB( DB_MASTER );
- $dbw->update( 'revision',
- [ 'rev_timestamp' => $dbw->timestamp( $timestamp ) ],
- [ 'rev_id' => $page->getLatest() ] );
- $page->clear();
- }
- /**
- * User input text is passed to rtrim() by edit page. This is a simple
- * wrapper around assertEquals() which calls rrtrim() to normalize the
- * expected and actual texts.
- * @param string $expected
- * @param string $actual
- * @param string $msg
- */
- protected function assertEditedTextEquals( $expected, $actual, $msg = '' ) {
- $this->assertEquals( rtrim( $expected ), rtrim( $actual ), $msg );
- }
- /**
- * Performs an edit and checks the result.
- *
- * @param string|Title $title The title of the page to edit
- * @param string|null $baseText Some text to create the page with before attempting the edit.
- * @param User|string|null $user The user to perform the edit as.
- * @param array $edit An array of request parameters used to define the edit to perform.
- * Some well known fields are:
- * * wpTextbox1: the text to submit
- * * wpSummary: the edit summary
- * * wpEditToken: the edit token (will be inserted if not provided)
- * * wpEdittime: timestamp of the edit's base revision (will be inserted
- * if not provided)
- * * wpStarttime: timestamp when the edit started (will be inserted if not provided)
- * * wpSectionTitle: the section to edit
- * * wpMinorEdit: mark as minor edit
- * * wpWatchthis: whether to watch the page
- * @param int|null $expectedCode The expected result code (EditPage::AS_XXX constants).
- * Set to null to skip the check.
- * @param string|null $expectedText The text expected to be on the page after the edit.
- * Set to null to skip the check.
- * @param string|null $message An optional message to show along with any error message.
- *
- * @return WikiPage The page that was just edited, useful for getting the edit's rev_id, etc.
- */
- protected function assertEdit( $title, $baseText, $user = null, array $edit,
- $expectedCode = null, $expectedText = null, $message = null
- ) {
- if ( is_string( $title ) ) {
- $ns = $this->getDefaultWikitextNS();
- $title = Title::newFromText( $title, $ns );
- }
- $this->assertNotNull( $title );
- if ( is_string( $user ) ) {
- $user = User::newFromName( $user );
- if ( $user->getId() === 0 ) {
- $user->addToDatabase();
- }
- }
- $page = WikiPage::factory( $title );
- if ( $baseText !== null ) {
- $content = ContentHandler::makeContent( $baseText, $title );
- $page->doEditContent( $content, "base text for test" );
- $this->forceRevisionDate( $page, '20120101000000' );
- // sanity check
- $page->clear();
- $currentText = ContentHandler::getContentText( $page->getContent() );
- # EditPage rtrim() the user input, so we alter our expected text
- # to reflect that.
- $this->assertEditedTextEquals( $baseText, $currentText );
- }
- if ( $user == null ) {
- $user = $GLOBALS['wgUser'];
- } else {
- $this->setMwGlobals( 'wgUser', $user );
- }
- if ( !isset( $edit['wpEditToken'] ) ) {
- $edit['wpEditToken'] = $user->getEditToken();
- }
- if ( !isset( $edit['wpEdittime'] ) ) {
- $edit['wpEdittime'] = $page->exists() ? $page->getTimestamp() : '';
- }
- if ( !isset( $edit['wpStarttime'] ) ) {
- $edit['wpStarttime'] = wfTimestampNow();
- }
- if ( !isset( $edit['wpUnicodeCheck'] ) ) {
- $edit['wpUnicodeCheck'] = EditPage::UNICODE_CHECK;
- }
- $req = new FauxRequest( $edit, true ); // session ??
- $article = new Article( $title );
- $article->getContext()->setTitle( $title );
- $ep = new EditPage( $article );
- $ep->setContextTitle( $title );
- $ep->importFormData( $req );
- $bot = isset( $edit['bot'] ) ? (bool)$edit['bot'] : false;
- // this is where the edit happens!
- // Note: don't want to use EditPage::AttemptSave, because it messes with $wgOut
- // and throws exceptions like PermissionsError
- $status = $ep->internalAttemptSave( $result, $bot );
- if ( $expectedCode !== null ) {
- // check edit code
- $this->assertEquals( $expectedCode, $status->value,
- "Expected result code mismatch. $message" );
- }
- $page = WikiPage::factory( $title );
- if ( $expectedText !== null ) {
- // check resulting page text
- $content = $page->getContent();
- $text = ContentHandler::getContentText( $content );
- # EditPage rtrim() the user input, so we alter our expected text
- # to reflect that.
- $this->assertEditedTextEquals( $expectedText, $text,
- "Expected article text mismatch. $message" );
- }
- return $page;
- }
- public static function provideCreatePages() {
- return [
- [ 'expected article being created',
- 'EditPageTest_testCreatePage',
- null,
- 'Hello World!',
- EditPage::AS_SUCCESS_NEW_ARTICLE,
- 'Hello World!'
- ],
- [ 'expected article not being created if empty',
- 'EditPageTest_testCreatePage',
- null,
- '',
- EditPage::AS_BLANK_ARTICLE,
- null
- ],
- [ 'expected MediaWiki: page being created',
- 'MediaWiki:January',
- 'UTSysop',
- 'Not January',
- EditPage::AS_SUCCESS_NEW_ARTICLE,
- 'Not January'
- ],
- [ 'expected not-registered MediaWiki: page not being created if empty',
- 'MediaWiki:EditPageTest_testCreatePage',
- 'UTSysop',
- '',
- EditPage::AS_BLANK_ARTICLE,
- null
- ],
- [ 'expected registered MediaWiki: page being created even if empty',
- 'MediaWiki:January',
- 'UTSysop',
- '',
- EditPage::AS_SUCCESS_NEW_ARTICLE,
- ''
- ],
- [ 'expected registered MediaWiki: page whose default content is empty'
- . ' not being created if empty',
- 'MediaWiki:Ipb-default-expiry',
- 'UTSysop',
- '',
- EditPage::AS_BLANK_ARTICLE,
- ''
- ],
- [ 'expected MediaWiki: page not being created if text equals default message',
- 'MediaWiki:January',
- 'UTSysop',
- 'January',
- EditPage::AS_BLANK_ARTICLE,
- null
- ],
- [ 'expected empty article being created',
- 'EditPageTest_testCreatePage',
- null,
- '',
- EditPage::AS_SUCCESS_NEW_ARTICLE,
- '',
- true
- ],
- ];
- }
- /**
- * @dataProvider provideCreatePages
- * @covers EditPage
- */
- public function testCreatePage(
- $desc, $pageTitle, $user, $editText, $expectedCode, $expectedText, $ignoreBlank = false
- ) {
- $checkId = null;
- $this->setMwGlobals( 'wgHooks', [
- 'PageContentInsertComplete' => [ function (
- WikiPage &$page, User &$user, Content $content,
- $summary, $minor, $u1, $u2, &$flags, Revision $revision
- ) {
- // types/refs checked
- } ],
- 'PageContentSaveComplete' => [ function (
- WikiPage &$page, User &$user, Content $content,
- $summary, $minor, $u1, $u2, &$flags, Revision $revision,
- Status &$status, $baseRevId
- ) use ( &$checkId ) {
- $checkId = $status->value['revision']->getId();
- // types/refs checked
- } ],
- ] );
- $edit = [ 'wpTextbox1' => $editText ];
- if ( $ignoreBlank ) {
- $edit['wpIgnoreBlankArticle'] = 1;
- }
- $page = $this->assertEdit( $pageTitle, null, $user, $edit, $expectedCode, $expectedText, $desc );
- if ( $expectedCode != EditPage::AS_BLANK_ARTICLE ) {
- $latest = $page->getLatest();
- $page->doDeleteArticleReal( $pageTitle );
- $this->assertGreaterThan( 0, $latest, "Page revision ID updated in object" );
- $this->assertEquals( $latest, $checkId, "Revision in Status for hook" );
- }
- }
- /**
- * @dataProvider provideCreatePages
- * @covers EditPage
- */
- public function testCreatePageTrx(
- $desc, $pageTitle, $user, $editText, $expectedCode, $expectedText, $ignoreBlank = false
- ) {
- $checkIds = [];
- $this->setMwGlobals( 'wgHooks', [
- 'PageContentInsertComplete' => [ function (
- WikiPage &$page, User &$user, Content $content,
- $summary, $minor, $u1, $u2, &$flags, Revision $revision
- ) {
- // types/refs checked
- } ],
- 'PageContentSaveComplete' => [ function (
- WikiPage &$page, User &$user, Content $content,
- $summary, $minor, $u1, $u2, &$flags, Revision $revision,
- Status &$status, $baseRevId
- ) use ( &$checkIds ) {
- $checkIds[] = $status->value['revision']->getId();
- // types/refs checked
- } ],
- ] );
- wfGetDB( DB_MASTER )->begin( __METHOD__ );
- $edit = [ 'wpTextbox1' => $editText ];
- if ( $ignoreBlank ) {
- $edit['wpIgnoreBlankArticle'] = 1;
- }
- $page = $this->assertEdit(
- $pageTitle, null, $user, $edit, $expectedCode, $expectedText, $desc );
- $pageTitle2 = (string)$pageTitle . '/x';
- $page2 = $this->assertEdit(
- $pageTitle2, null, $user, $edit, $expectedCode, $expectedText, $desc );
- wfGetDB( DB_MASTER )->commit( __METHOD__ );
- $this->assertEquals( 0, DeferredUpdates::pendingUpdatesCount(), 'No deferred updates' );
- if ( $expectedCode != EditPage::AS_BLANK_ARTICLE ) {
- $latest = $page->getLatest();
- $page->doDeleteArticleReal( $pageTitle );
- $this->assertGreaterThan( 0, $latest, "Page #1 revision ID updated in object" );
- $this->assertEquals( $latest, $checkIds[0], "Revision #1 in Status for hook" );
- $latest2 = $page2->getLatest();
- $page2->doDeleteArticleReal( $pageTitle2 );
- $this->assertGreaterThan( 0, $latest2, "Page #2 revision ID updated in object" );
- $this->assertEquals( $latest2, $checkIds[1], "Revision #2 in Status for hook" );
- }
- }
- public function testUpdatePage() {
- $checkIds = [];
- $this->setMwGlobals( 'wgHooks', [
- 'PageContentInsertComplete' => [ function (
- WikiPage &$page, User &$user, Content $content,
- $summary, $minor, $u1, $u2, &$flags, Revision $revision
- ) {
- // types/refs checked
- } ],
- 'PageContentSaveComplete' => [ function (
- WikiPage &$page, User &$user, Content $content,
- $summary, $minor, $u1, $u2, &$flags, Revision $revision,
- Status &$status, $baseRevId
- ) use ( &$checkIds ) {
- $checkIds[] = $status->value['revision']->getId();
- // types/refs checked
- } ],
- ] );
- $text = "one";
- $edit = [
- 'wpTextbox1' => $text,
- 'wpSummary' => 'first update',
- ];
- $page = $this->assertEdit( 'EditPageTest_testUpdatePage', "zero", null, $edit,
- EditPage::AS_SUCCESS_UPDATE, $text,
- "expected successful update with given text" );
- $this->assertGreaterThan( 0, $checkIds[0], "First event rev ID set" );
- $this->forceRevisionDate( $page, '20120101000000' );
- $text = "two";
- $edit = [
- 'wpTextbox1' => $text,
- 'wpSummary' => 'second update',
- ];
- $this->assertEdit( 'EditPageTest_testUpdatePage', null, null, $edit,
- EditPage::AS_SUCCESS_UPDATE, $text,
- "expected successful update with given text" );
- $this->assertGreaterThan( 0, $checkIds[1], "Second edit hook rev ID set" );
- $this->assertGreaterThan( $checkIds[0], $checkIds[1], "Second event rev ID is higher" );
- }
- public function testUpdatePageTrx() {
- $text = "one";
- $edit = [
- 'wpTextbox1' => $text,
- 'wpSummary' => 'first update',
- ];
- $page = $this->assertEdit( 'EditPageTest_testTrxUpdatePage', "zero", null, $edit,
- EditPage::AS_SUCCESS_UPDATE, $text,
- "expected successful update with given text" );
- $this->forceRevisionDate( $page, '20120101000000' );
- $checkIds = [];
- $this->setMwGlobals( 'wgHooks', [
- 'PageContentSaveComplete' => [ function (
- WikiPage &$page, User &$user, Content $content,
- $summary, $minor, $u1, $u2, &$flags, Revision $revision,
- Status &$status, $baseRevId
- ) use ( &$checkIds ) {
- $checkIds[] = $status->value['revision']->getId();
- // types/refs checked
- } ],
- ] );
- wfGetDB( DB_MASTER )->begin( __METHOD__ );
- $text = "two";
- $edit = [
- 'wpTextbox1' => $text,
- 'wpSummary' => 'second update',
- ];
- $this->assertEdit( 'EditPageTest_testTrxUpdatePage', null, null, $edit,
- EditPage::AS_SUCCESS_UPDATE, $text,
- "expected successful update with given text" );
- $text = "three";
- $edit = [
- 'wpTextbox1' => $text,
- 'wpSummary' => 'third update',
- ];
- $this->assertEdit( 'EditPageTest_testTrxUpdatePage', null, null, $edit,
- EditPage::AS_SUCCESS_UPDATE, $text,
- "expected successful update with given text" );
- wfGetDB( DB_MASTER )->commit( __METHOD__ );
- $this->assertGreaterThan( 0, $checkIds[0], "First event rev ID set" );
- $this->assertGreaterThan( 0, $checkIds[1], "Second edit hook rev ID set" );
- $this->assertGreaterThan( $checkIds[0], $checkIds[1], "Second event rev ID is higher" );
- }
- public static function provideSectionEdit() {
- $text = 'Intro
- == one ==
- first section.
- == two ==
- second section.
- ';
- $sectionOne = '== one ==
- hello
- ';
- $newSection = '== new section ==
- hello
- ';
- $textWithNewSectionOne = preg_replace(
- '/== one ==.*== two ==/ms',
- "$sectionOne\n== two ==", $text
- );
- $textWithNewSectionAdded = "$text\n$newSection";
- return [
- [ # 0
- $text,
- '',
- 'hello',
- 'replace all',
- 'hello'
- ],
- [ # 1
- $text,
- '1',
- $sectionOne,
- 'replace first section',
- $textWithNewSectionOne,
- ],
- [ # 2
- $text,
- 'new',
- 'hello',
- 'new section',
- $textWithNewSectionAdded,
- ],
- ];
- }
- /**
- * @dataProvider provideSectionEdit
- * @covers EditPage
- */
- public function testSectionEdit( $base, $section, $text, $summary, $expected ) {
- $edit = [
- 'wpTextbox1' => $text,
- 'wpSummary' => $summary,
- 'wpSection' => $section,
- ];
- $this->assertEdit( 'EditPageTest_testSectionEdit', $base, null, $edit,
- EditPage::AS_SUCCESS_UPDATE, $expected,
- "expected successful update of section" );
- }
- public static function provideAutoMerge() {
- $tests = [];
- $tests[] = [ # 0: plain conflict
- "Elmo", # base edit user
- "one\n\ntwo\n\nthree\n",
- [ # adam's edit
- 'wpStarttime' => 1,
- 'wpTextbox1' => "ONE\n\ntwo\n\nthree\n",
- ],
- [ # berta's edit
- 'wpStarttime' => 2,
- 'wpTextbox1' => "(one)\n\ntwo\n\nthree\n",
- ],
- EditPage::AS_CONFLICT_DETECTED, # expected code
- "ONE\n\ntwo\n\nthree\n", # expected text
- 'expected edit conflict', # message
- ];
- $tests[] = [ # 1: successful merge
- "Elmo", # base edit user
- "one\n\ntwo\n\nthree\n",
- [ # adam's edit
- 'wpStarttime' => 1,
- 'wpTextbox1' => "ONE\n\ntwo\n\nthree\n",
- ],
- [ # berta's edit
- 'wpStarttime' => 2,
- 'wpTextbox1' => "one\n\ntwo\n\nTHREE\n",
- ],
- EditPage::AS_SUCCESS_UPDATE, # expected code
- "ONE\n\ntwo\n\nTHREE\n", # expected text
- 'expected automatic merge', # message
- ];
- $text = "Intro\n\n";
- $text .= "== first section ==\n\n";
- $text .= "one\n\ntwo\n\nthree\n\n";
- $text .= "== second section ==\n\n";
- $text .= "four\n\nfive\n\nsix\n\n";
- // extract the first section.
- $section = preg_replace( '/.*(== first section ==.*)== second section ==.*/sm', '$1', $text );
- // generate expected text after merge
- $expected = str_replace( 'one', 'ONE', str_replace( 'three', 'THREE', $text ) );
- $tests[] = [ # 2: merge in section
- "Elmo", # base edit user
- $text,
- [ # adam's edit
- 'wpStarttime' => 1,
- 'wpTextbox1' => str_replace( 'one', 'ONE', $section ),
- 'wpSection' => '1'
- ],
- [ # berta's edit
- 'wpStarttime' => 2,
- 'wpTextbox1' => str_replace( 'three', 'THREE', $section ),
- 'wpSection' => '1'
- ],
- EditPage::AS_SUCCESS_UPDATE, # expected code
- $expected, # expected text
- 'expected automatic section merge', # message
- ];
- // see whether it makes a difference who did the base edit
- $testsWithAdam = array_map( function ( $test ) {
- $test[0] = 'Adam'; // change base edit user
- return $test;
- }, $tests );
- $testsWithBerta = array_map( function ( $test ) {
- $test[0] = 'Berta'; // change base edit user
- return $test;
- }, $tests );
- return array_merge( $tests, $testsWithAdam, $testsWithBerta );
- }
- /**
- * @dataProvider provideAutoMerge
- * @covers EditPage
- */
- public function testAutoMerge( $baseUser, $text, $adamsEdit, $bertasEdit,
- $expectedCode, $expectedText, $message = null
- ) {
- $this->markTestSkippedIfNoDiff3();
- // create page
- $ns = $this->getDefaultWikitextNS();
- $title = Title::newFromText( 'EditPageTest_testAutoMerge', $ns );
- $page = WikiPage::factory( $title );
- if ( $page->exists() ) {
- $page->doDeleteArticle( "clean slate for testing" );
- }
- $baseEdit = [
- 'wpTextbox1' => $text,
- ];
- $page = $this->assertEdit( 'EditPageTest_testAutoMerge', null,
- $baseUser, $baseEdit, null, null, __METHOD__ );
- $this->forceRevisionDate( $page, '20120101000000' );
- $edittime = $page->getTimestamp();
- // start timestamps for conflict detection
- if ( !isset( $adamsEdit['wpStarttime'] ) ) {
- $adamsEdit['wpStarttime'] = 1;
- }
- if ( !isset( $bertasEdit['wpStarttime'] ) ) {
- $bertasEdit['wpStarttime'] = 2;
- }
- $starttime = wfTimestampNow();
- $adamsTime = wfTimestamp(
- TS_MW,
- (int)wfTimestamp( TS_UNIX, $starttime ) + (int)$adamsEdit['wpStarttime']
- );
- $bertasTime = wfTimestamp(
- TS_MW,
- (int)wfTimestamp( TS_UNIX, $starttime ) + (int)$bertasEdit['wpStarttime']
- );
- $adamsEdit['wpStarttime'] = $adamsTime;
- $bertasEdit['wpStarttime'] = $bertasTime;
- $adamsEdit['wpSummary'] = 'Adam\'s edit';
- $bertasEdit['wpSummary'] = 'Bertas\'s edit';
- $adamsEdit['wpEdittime'] = $edittime;
- $bertasEdit['wpEdittime'] = $edittime;
- // first edit
- $this->assertEdit( 'EditPageTest_testAutoMerge', null, 'Adam', $adamsEdit,
- EditPage::AS_SUCCESS_UPDATE, null, "expected successful update" );
- // second edit
- $this->assertEdit( 'EditPageTest_testAutoMerge', null, 'Berta', $bertasEdit,
- $expectedCode, $expectedText, $message );
- }
- /**
- * @depends testAutoMerge
- */
- public function testCheckDirectEditingDisallowed_forNonTextContent() {
- $title = Title::newFromText( 'Dummy:NonTextPageForEditPage' );
- $page = WikiPage::factory( $title );
- $article = new Article( $title );
- $article->getContext()->setTitle( $title );
- $ep = new EditPage( $article );
- $ep->setContextTitle( $title );
- $user = $GLOBALS['wgUser'];
- $edit = [
- 'wpTextbox1' => serialize( 'non-text content' ),
- 'wpEditToken' => $user->getEditToken(),
- 'wpEdittime' => '',
- 'wpStarttime' => wfTimestampNow(),
- 'wpUnicodeCheck' => EditPage::UNICODE_CHECK,
- ];
- $req = new FauxRequest( $edit, true );
- $ep->importFormData( $req );
- $this->setExpectedException(
- MWException::class,
- 'This content model is not supported: testing'
- );
- $ep->internalAttemptSave( $result, false );
- }
- }
|