Import: Handle uploads with sha1 starting with 0 properly
[mediawiki.git] / tests / phpunit / includes / EditPageTest.php
blob51f00832bab0aca85fc7982b9d7c694580f3edb0
1 <?php
3 /**
4 * @group Editing
6 * @group Database
7 * ^--- tell jenkins this test needs the database
9 * @group medium
10 * ^--- tell phpunit that these test cases may take longer than 2 seconds.
12 class EditPageTest extends MediaWikiLangTestCase {
14 protected function setUp() {
15 global $wgExtraNamespaces, $wgNamespaceContentModels, $wgContentHandlers, $wgContLang;
17 parent::setUp();
19 $this->setMwGlobals( array(
20 'wgExtraNamespaces' => $wgExtraNamespaces,
21 'wgNamespaceContentModels' => $wgNamespaceContentModels,
22 'wgContentHandlers' => $wgContentHandlers,
23 'wgContLang' => $wgContLang,
24 ) );
26 $wgExtraNamespaces[12312] = 'Dummy';
27 $wgExtraNamespaces[12313] = 'Dummy_talk';
29 $wgNamespaceContentModels[12312] = "testing";
30 $wgContentHandlers["testing"] = 'DummyContentHandlerForTesting';
32 MWNamespace::getCanonicalNamespaces( true ); # reset namespace cache
33 $wgContLang->resetNamespaces(); # reset namespace cache
36 /**
37 * @dataProvider provideExtractSectionTitle
38 * @covers EditPage::extractSectionTitle
40 public function testExtractSectionTitle( $section, $title ) {
41 $extracted = EditPage::extractSectionTitle( $section );
42 $this->assertEquals( $title, $extracted );
45 public static function provideExtractSectionTitle() {
46 return array(
47 array(
48 "== Test ==\n\nJust a test section.",
49 "Test"
51 array(
52 "An initial section, no header.",
53 false
55 array(
56 "An initial section with a fake heder (bug 32617)\n\n== Test == ??\nwtf",
57 false
59 array(
60 "== Section ==\nfollowed by a fake == Non-section == ??\nnoooo",
61 "Section"
63 array(
64 "== Section== \t\r\n followed by whitespace (bug 35051)",
65 'Section',
70 protected function forceRevisionDate( WikiPage $page, $timestamp ) {
71 $dbw = wfGetDB( DB_MASTER );
73 $dbw->update( 'revision',
74 array( 'rev_timestamp' => $dbw->timestamp( $timestamp ) ),
75 array( 'rev_id' => $page->getLatest() ) );
77 $page->clear();
80 /**
81 * User input text is passed to rtrim() by edit page. This is a simple
82 * wrapper around assertEquals() which calls rrtrim() to normalize the
83 * expected and actual texts.
84 * @param string $expected
85 * @param string $actual
86 * @param string $msg
88 protected function assertEditedTextEquals( $expected, $actual, $msg = '' ) {
89 return $this->assertEquals( rtrim( $expected ), rtrim( $actual ), $msg );
92 /**
93 * Performs an edit and checks the result.
95 * @param string|Title $title The title of the page to edit
96 * @param string|null $baseText Some text to create the page with before attempting the edit.
97 * @param User|string|null $user The user to perform the edit as.
98 * @param array $edit An array of request parameters used to define the edit to perform.
99 * Some well known fields are:
100 * * wpTextbox1: the text to submit
101 * * wpSummary: the edit summary
102 * * wpEditToken: the edit token (will be inserted if not provided)
103 * * wpEdittime: timestamp of the edit's base revision (will be inserted
104 * if not provided)
105 * * wpStarttime: timestamp when the edit started (will be inserted if not provided)
106 * * wpSectionTitle: the section to edit
107 * * wpMinorEdit: mark as minor edit
108 * * wpWatchthis: whether to watch the page
109 * @param int|null $expectedCode The expected result code (EditPage::AS_XXX constants).
110 * Set to null to skip the check.
111 * @param string|null $expectedText The text expected to be on the page after the edit.
112 * Set to null to skip the check.
113 * @param string|null $message An optional message to show along with any error message.
115 * @return WikiPage The page that was just edited, useful for getting the edit's rev_id, etc.
117 protected function assertEdit( $title, $baseText, $user = null, array $edit,
118 $expectedCode = null, $expectedText = null, $message = null
120 if ( is_string( $title ) ) {
121 $ns = $this->getDefaultWikitextNS();
122 $title = Title::newFromText( $title, $ns );
124 $this->assertNotNull( $title );
126 if ( is_string( $user ) ) {
127 $user = User::newFromName( $user );
129 if ( $user->getId() === 0 ) {
130 $user->addToDatabase();
134 $page = WikiPage::factory( $title );
136 if ( $baseText !== null ) {
137 $content = ContentHandler::makeContent( $baseText, $title );
138 $page->doEditContent( $content, "base text for test" );
139 $this->forceRevisionDate( $page, '20120101000000' );
141 // sanity check
142 $page->clear();
143 $currentText = ContentHandler::getContentText( $page->getContent() );
145 # EditPage rtrim() the user input, so we alter our expected text
146 # to reflect that.
147 $this->assertEditedTextEquals( $baseText, $currentText );
150 if ( $user == null ) {
151 $user = $GLOBALS['wgUser'];
152 } else {
153 $this->setMwGlobals( 'wgUser', $user );
156 if ( !isset( $edit['wpEditToken'] ) ) {
157 $edit['wpEditToken'] = $user->getEditToken();
160 if ( !isset( $edit['wpEdittime'] ) ) {
161 $edit['wpEdittime'] = $page->exists() ? $page->getTimestamp() : '';
164 if ( !isset( $edit['wpStarttime'] ) ) {
165 $edit['wpStarttime'] = wfTimestampNow();
168 $req = new FauxRequest( $edit, true ); // session ??
170 $article = new Article( $title );
171 $article->getContext()->setTitle( $title );
172 $ep = new EditPage( $article );
173 $ep->setContextTitle( $title );
174 $ep->importFormData( $req );
176 $bot = isset( $edit['bot'] ) ? (bool)$edit['bot'] : false;
178 // this is where the edit happens!
179 // Note: don't want to use EditPage::AttemptSave, because it messes with $wgOut
180 // and throws exceptions like PermissionsError
181 $status = $ep->internalAttemptSave( $result, $bot );
183 if ( $expectedCode !== null ) {
184 // check edit code
185 $this->assertEquals( $expectedCode, $status->value,
186 "Expected result code mismatch. $message" );
189 $page = WikiPage::factory( $title );
191 if ( $expectedText !== null ) {
192 // check resulting page text
193 $content = $page->getContent();
194 $text = ContentHandler::getContentText( $content );
196 # EditPage rtrim() the user input, so we alter our expected text
197 # to reflect that.
198 $this->assertEditedTextEquals( $expectedText, $text,
199 "Expected article text mismatch. $message" );
202 return $page;
205 public static function provideCreatePages() {
206 return array(
207 array( 'expected article being created',
208 'EditPageTest_testCreatePage',
209 null,
210 'Hello World!',
211 EditPage::AS_SUCCESS_NEW_ARTICLE,
212 'Hello World!'
214 array( 'expected article not being created if empty',
215 'EditPageTest_testCreatePage',
216 null,
218 EditPage::AS_BLANK_ARTICLE,
219 null
221 array( 'expected MediaWiki: page being created',
222 'MediaWiki:January',
223 'UTSysop',
224 'Not January',
225 EditPage::AS_SUCCESS_NEW_ARTICLE,
226 'Not January'
228 array( 'expected not-registered MediaWiki: page not being created if empty',
229 'MediaWiki:EditPageTest_testCreatePage',
230 'UTSysop',
232 EditPage::AS_BLANK_ARTICLE,
233 null
235 array( 'expected registered MediaWiki: page being created even if empty',
236 'MediaWiki:January',
237 'UTSysop',
239 EditPage::AS_SUCCESS_NEW_ARTICLE,
242 array( 'expected registered MediaWiki: page whose default content is empty'
243 . ' not being created if empty',
244 'MediaWiki:Ipb-default-expiry',
245 'UTSysop',
247 EditPage::AS_BLANK_ARTICLE,
250 array( 'expected MediaWiki: page not being created if text equals default message',
251 'MediaWiki:January',
252 'UTSysop',
253 'January',
254 EditPage::AS_BLANK_ARTICLE,
255 null
257 array( 'expected empty article being created',
258 'EditPageTest_testCreatePage',
259 null,
261 EditPage::AS_SUCCESS_NEW_ARTICLE,
263 true
269 * @dataProvider provideCreatePages
270 * @covers EditPage
272 public function testCreatePage(
273 $desc, $pageTitle, $user, $editText, $expectedCode, $expectedText, $ignoreBlank = false
275 $edit = array( 'wpTextbox1' => $editText );
276 if ( $ignoreBlank ) {
277 $edit['wpIgnoreBlankArticle'] = 1;
280 $page = $this->assertEdit( $pageTitle, null, $user, $edit, $expectedCode, $expectedText, $desc );
282 if ( $expectedCode != EditPage::AS_BLANK_ARTICLE ) {
283 $page->doDeleteArticleReal( $pageTitle );
287 public function testUpdatePage() {
288 $text = "one";
289 $edit = array(
290 'wpTextbox1' => $text,
291 'wpSummary' => 'first update',
294 $page = $this->assertEdit( 'EditPageTest_testUpdatePage', "zero", null, $edit,
295 EditPage::AS_SUCCESS_UPDATE, $text,
296 "expected successfull update with given text" );
298 $this->forceRevisionDate( $page, '20120101000000' );
300 $text = "two";
301 $edit = array(
302 'wpTextbox1' => $text,
303 'wpSummary' => 'second update',
306 $this->assertEdit( 'EditPageTest_testUpdatePage', null, null, $edit,
307 EditPage::AS_SUCCESS_UPDATE, $text,
308 "expected successfull update with given text" );
311 public static function provideSectionEdit() {
312 $text = 'Intro
314 == one ==
315 first section.
317 == two ==
318 second section.
321 $sectionOne = '== one ==
322 hello
325 $newSection = '== new section ==
327 hello
330 $textWithNewSectionOne = preg_replace(
331 '/== one ==.*== two ==/ms',
332 "$sectionOne\n== two ==", $text
335 $textWithNewSectionAdded = "$text\n$newSection";
337 return array(
338 array( # 0
339 $text,
341 'hello',
342 'replace all',
343 'hello'
346 array( # 1
347 $text,
348 '1',
349 $sectionOne,
350 'replace first section',
351 $textWithNewSectionOne,
354 array( # 2
355 $text,
356 'new',
357 'hello',
358 'new section',
359 $textWithNewSectionAdded,
365 * @dataProvider provideSectionEdit
366 * @covers EditPage
368 public function testSectionEdit( $base, $section, $text, $summary, $expected ) {
369 $edit = array(
370 'wpTextbox1' => $text,
371 'wpSummary' => $summary,
372 'wpSection' => $section,
375 $this->assertEdit( 'EditPageTest_testSectionEdit', $base, null, $edit,
376 EditPage::AS_SUCCESS_UPDATE, $expected,
377 "expected successfull update of section" );
380 public static function provideAutoMerge() {
381 $tests = array();
383 $tests[] = array( # 0: plain conflict
384 "Elmo", # base edit user
385 "one\n\ntwo\n\nthree\n",
386 array( # adam's edit
387 'wpStarttime' => 1,
388 'wpTextbox1' => "ONE\n\ntwo\n\nthree\n",
390 array( # berta's edit
391 'wpStarttime' => 2,
392 'wpTextbox1' => "(one)\n\ntwo\n\nthree\n",
394 EditPage::AS_CONFLICT_DETECTED, # expected code
395 "ONE\n\ntwo\n\nthree\n", # expected text
396 'expected edit conflict', # message
399 $tests[] = array( # 1: successful merge
400 "Elmo", # base edit user
401 "one\n\ntwo\n\nthree\n",
402 array( # adam's edit
403 'wpStarttime' => 1,
404 'wpTextbox1' => "ONE\n\ntwo\n\nthree\n",
406 array( # berta's edit
407 'wpStarttime' => 2,
408 'wpTextbox1' => "one\n\ntwo\n\nTHREE\n",
410 EditPage::AS_SUCCESS_UPDATE, # expected code
411 "ONE\n\ntwo\n\nTHREE\n", # expected text
412 'expected automatic merge', # message
415 $text = "Intro\n\n";
416 $text .= "== first section ==\n\n";
417 $text .= "one\n\ntwo\n\nthree\n\n";
418 $text .= "== second section ==\n\n";
419 $text .= "four\n\nfive\n\nsix\n\n";
421 // extract the first section.
422 $section = preg_replace( '/.*(== first section ==.*)== second section ==.*/sm', '$1', $text );
424 // generate expected text after merge
425 $expected = str_replace( 'one', 'ONE', str_replace( 'three', 'THREE', $text ) );
427 $tests[] = array( # 2: merge in section
428 "Elmo", # base edit user
429 $text,
430 array( # adam's edit
431 'wpStarttime' => 1,
432 'wpTextbox1' => str_replace( 'one', 'ONE', $section ),
433 'wpSection' => '1'
435 array( # berta's edit
436 'wpStarttime' => 2,
437 'wpTextbox1' => str_replace( 'three', 'THREE', $section ),
438 'wpSection' => '1'
440 EditPage::AS_SUCCESS_UPDATE, # expected code
441 $expected, # expected text
442 'expected automatic section merge', # message
445 // see whether it makes a difference who did the base edit
446 $testsWithAdam = array_map( function ( $test ) {
447 $test[0] = 'Adam'; // change base edit user
448 return $test;
449 }, $tests );
451 $testsWithBerta = array_map( function ( $test ) {
452 $test[0] = 'Berta'; // change base edit user
453 return $test;
454 }, $tests );
456 return array_merge( $tests, $testsWithAdam, $testsWithBerta );
460 * @dataProvider provideAutoMerge
461 * @covers EditPage
463 public function testAutoMerge( $baseUser, $text, $adamsEdit, $bertasEdit,
464 $expectedCode, $expectedText, $message = null
466 $this->checkHasDiff3();
468 // create page
469 $ns = $this->getDefaultWikitextNS();
470 $title = Title::newFromText( 'EditPageTest_testAutoMerge', $ns );
471 $page = WikiPage::factory( $title );
473 if ( $page->exists() ) {
474 $page->doDeleteArticle( "clean slate for testing" );
477 $baseEdit = array(
478 'wpTextbox1' => $text,
481 $page = $this->assertEdit( 'EditPageTest_testAutoMerge', null,
482 $baseUser, $baseEdit, null, null, __METHOD__ );
484 $this->forceRevisionDate( $page, '20120101000000' );
486 $edittime = $page->getTimestamp();
488 // start timestamps for conflict detection
489 if ( !isset( $adamsEdit['wpStarttime'] ) ) {
490 $adamsEdit['wpStarttime'] = 1;
493 if ( !isset( $bertasEdit['wpStarttime'] ) ) {
494 $bertasEdit['wpStarttime'] = 2;
497 $starttime = wfTimestampNow();
498 $adamsTime = wfTimestamp(
499 TS_MW,
500 (int)wfTimestamp( TS_UNIX, $starttime ) + (int)$adamsEdit['wpStarttime']
502 $bertasTime = wfTimestamp(
503 TS_MW,
504 (int)wfTimestamp( TS_UNIX, $starttime ) + (int)$bertasEdit['wpStarttime']
507 $adamsEdit['wpStarttime'] = $adamsTime;
508 $bertasEdit['wpStarttime'] = $bertasTime;
510 $adamsEdit['wpSummary'] = 'Adam\'s edit';
511 $bertasEdit['wpSummary'] = 'Bertas\'s edit';
513 $adamsEdit['wpEdittime'] = $edittime;
514 $bertasEdit['wpEdittime'] = $edittime;
516 // first edit
517 $this->assertEdit( 'EditPageTest_testAutoMerge', null, 'Adam', $adamsEdit,
518 EditPage::AS_SUCCESS_UPDATE, null, "expected successfull update" );
520 // second edit
521 $this->assertEdit( 'EditPageTest_testAutoMerge', null, 'Berta', $bertasEdit,
522 $expectedCode, $expectedText, $message );
526 * @depends testAutoMerge
528 public function testCheckDirectEditingDisallowed_forNonTextContent() {
529 $title = Title::newFromText( 'Dummy:NonTextPageForEditPage' );
530 $page = WikiPage::factory( $title );
532 $article = new Article( $title );
533 $article->getContext()->setTitle( $title );
534 $ep = new EditPage( $article );
535 $ep->setContextTitle( $title );
537 $user = $GLOBALS['wgUser'];
539 $edit = array(
540 'wpTextbox1' => serialize( 'non-text content' ),
541 'wpEditToken' => $user->getEditToken(),
542 'wpEdittime' => '',
543 'wpStarttime' => wfTimestampNow()
546 $req = new FauxRequest( $edit, true );
547 $ep->importFormData( $req );
549 $this->setExpectedException(
550 'MWException',
551 'This content model is not supported: testing'
554 $ep->internalAttemptSave( $result, false );