6537364994281b614cf786090a1d8b059ce85d8c
[lhc/web/wiklou.git] / tests / phpunit / includes / EditPageTest.php
1 <?php
2
3 /**
4 * @group Editing
5 *
6 * @group Database
7 * ^--- tell jenkins this test needs the database
8 *
9 * @group medium
10 * ^--- tell phpunit that these test cases may take longer than 2 seconds.
11 */
12 class EditPageTest extends MediaWikiLangTestCase {
13
14 /**
15 * @dataProvider provideExtractSectionTitle
16 * @covers EditPage::extractSectionTitle
17 */
18 public function testExtractSectionTitle( $section, $title ) {
19 $extracted = EditPage::extractSectionTitle( $section );
20 $this->assertEquals( $title, $extracted );
21 }
22
23 public static function provideExtractSectionTitle() {
24 return array(
25 array(
26 "== Test ==\n\nJust a test section.",
27 "Test"
28 ),
29 array(
30 "An initial section, no header.",
31 false
32 ),
33 array(
34 "An initial section with a fake heder (bug 32617)\n\n== Test == ??\nwtf",
35 false
36 ),
37 array(
38 "== Section ==\nfollowed by a fake == Non-section == ??\nnoooo",
39 "Section"
40 ),
41 array(
42 "== Section== \t\r\n followed by whitespace (bug 35051)",
43 'Section',
44 ),
45 );
46 }
47
48 protected function forceRevisionDate( WikiPage $page, $timestamp ) {
49 $dbw = wfGetDB( DB_MASTER );
50
51 $dbw->update( 'revision',
52 array( 'rev_timestamp' => $dbw->timestamp( $timestamp ) ),
53 array( 'rev_id' => $page->getLatest() ) );
54
55 $page->clear();
56 }
57
58 /**
59 * User input text is passed to rtrim() by edit page. This is a simple
60 * wrapper around assertEquals() which calls rrtrim() to normalize the
61 * expected and actual texts.
62 */
63 protected function assertEditedTextEquals( $expected, $actual, $msg = '' ) {
64 return $this->assertEquals( rtrim( $expected ), rtrim( $actual ), $msg );
65 }
66
67 /**
68 * Performs an edit and checks the result.
69 *
70 * @param string|Title $title The title of the page to edit
71 * @param string|null $baseText Some text to create the page with before attempting the edit.
72 * @param User|string|null $user The user to perform the edit as.
73 * @param array $edit An array of request parameters used to define the edit to perform.
74 * Some well known fields are:
75 * * wpTextbox1: the text to submit
76 * * wpSummary: the edit summary
77 * * wpEditToken: the edit token (will be inserted if not provided)
78 * * wpEdittime: timestamp of the edit's base revision (will be inserted
79 * if not provided)
80 * * wpStarttime: timestamp when the edit started (will be inserted if not provided)
81 * * wpSectionTitle: the section to edit
82 * * wpMinorEdit: mark as minor edit
83 * * wpWatchthis: whether to watch the page
84 * @param int|null $expectedCode The expected result code (EditPage::AS_XXX constants).
85 * Set to null to skip the check.
86 * @param string|null $expectedText The text expected to be on the page after the edit.
87 * Set to null to skip the check.
88 * @param string|null $message An optional message to show along with any error message.
89 *
90 * @return WikiPage The page that was just edited, useful for getting the edit's rev_id, etc.
91 */
92 protected function assertEdit( $title, $baseText, $user = null, array $edit,
93 $expectedCode = null, $expectedText = null, $message = null
94 ) {
95 if ( is_string( $title ) ) {
96 $ns = $this->getDefaultWikitextNS();
97 $title = Title::newFromText( $title, $ns );
98 }
99 $this->assertNotNull( $title );
100
101 if ( is_string( $user ) ) {
102 $user = User::newFromName( $user );
103
104 if ( $user->getId() === 0 ) {
105 $user->addToDatabase();
106 }
107 }
108
109 $page = WikiPage::factory( $title );
110
111 if ( $baseText !== null ) {
112 $content = ContentHandler::makeContent( $baseText, $title );
113 $page->doEditContent( $content, "base text for test" );
114 $this->forceRevisionDate( $page, '20120101000000' );
115
116 //sanity check
117 $page->clear();
118 $currentText = ContentHandler::getContentText( $page->getContent() );
119
120 # EditPage rtrim() the user input, so we alter our expected text
121 # to reflect that.
122 $this->assertEditedTextEquals( $baseText, $currentText );
123 }
124
125 if ( $user == null ) {
126 $user = $GLOBALS['wgUser'];
127 } else {
128 $this->setMwGlobals( 'wgUser', $user );
129 }
130
131 if ( !isset( $edit['wpEditToken'] ) ) {
132 $edit['wpEditToken'] = $user->getEditToken();
133 }
134
135 if ( !isset( $edit['wpEdittime'] ) ) {
136 $edit['wpEdittime'] = $page->exists() ? $page->getTimestamp() : '';
137 }
138
139 if ( !isset( $edit['wpStarttime'] ) ) {
140 $edit['wpStarttime'] = wfTimestampNow();
141 }
142
143 $req = new FauxRequest( $edit, true ); // session ??
144
145 $article = new Article( $title );
146 $article->getContext()->setTitle( $title );
147 $ep = new EditPage( $article );
148 $ep->setContextTitle( $title );
149 $ep->importFormData( $req );
150
151 $bot = isset( $edit['bot'] ) ? (bool)$edit['bot'] : false;
152
153 // this is where the edit happens!
154 // Note: don't want to use EditPage::AttemptSave, because it messes with $wgOut
155 // and throws exceptions like PermissionsError
156 $status = $ep->internalAttemptSave( $result, $bot );
157
158 if ( $expectedCode !== null ) {
159 // check edit code
160 $this->assertEquals( $expectedCode, $status->value,
161 "Expected result code mismatch. $message" );
162 }
163
164 $page = WikiPage::factory( $title );
165
166 if ( $expectedText !== null ) {
167 // check resulting page text
168 $content = $page->getContent();
169 $text = ContentHandler::getContentText( $content );
170
171 # EditPage rtrim() the user input, so we alter our expected text
172 # to reflect that.
173 $this->assertEditedTextEquals( $expectedText, $text,
174 "Expected article text mismatch. $message" );
175 }
176
177 return $page;
178 }
179
180 /**
181 * @todo split into a dataprovider and test method
182 * @covers EditPage
183 */
184 public function testCreatePage() {
185 $this->assertEdit(
186 'EditPageTest_testCreatePage',
187 null,
188 null,
189 array(
190 'wpTextbox1' => "Hello World!",
191 ),
192 EditPage::AS_SUCCESS_NEW_ARTICLE,
193 "Hello World!",
194 "expected article being created"
195 )->doDeleteArticleReal( 'EditPageTest_testCreatePage' );
196
197 $this->assertEdit(
198 'EditPageTest_testCreatePage',
199 null,
200 null,
201 array(
202 'wpTextbox1' => "",
203 ),
204 EditPage::AS_BLANK_ARTICLE,
205 null,
206 "expected article not being created if empty"
207 );
208
209 $this->assertEdit(
210 'MediaWiki:January',
211 null,
212 'UTSysop',
213 array(
214 'wpTextbox1' => "Not January",
215 ),
216 EditPage::AS_SUCCESS_NEW_ARTICLE,
217 "Not January",
218 "expected MediaWiki: page being created"
219 )->doDeleteArticleReal( 'EditPageTest_testCreatePage' );
220
221 $this->assertEdit(
222 'MediaWiki:EditPageTest_testCreatePage',
223 null,
224 'UTSysop',
225 array(
226 'wpTextbox1' => "",
227 ),
228 EditPage::AS_BLANK_ARTICLE,
229 null,
230 "expected not-registered MediaWiki: page not being created if empty"
231 );
232
233 $this->assertEdit(
234 'MediaWiki:January',
235 null,
236 'UTSysop',
237 array(
238 'wpTextbox1' => "",
239 ),
240 EditPage::AS_SUCCESS_NEW_ARTICLE,
241 "",
242 "expected registered MediaWiki: page being created even if empty"
243 )->doDeleteArticleReal( 'EditPageTest_testCreatePage' );
244
245 $this->assertEdit(
246 'MediaWiki:Ipb-default-expiry',
247 null,
248 'UTSysop',
249 array(
250 'wpTextbox1' => "",
251 ),
252 EditPage::AS_BLANK_ARTICLE,
253 "",
254 "expected registered MediaWiki: page whose default content is empty not being created if empty"
255 );
256
257 $this->assertEdit(
258 'MediaWiki:January',
259 null,
260 'UTSysop',
261 array(
262 'wpTextbox1' => "January",
263 ),
264 EditPage::AS_BLANK_ARTICLE,
265 null,
266 "expected MediaWiki: page not being created if text equals default message"
267 );
268
269 $this->assertEdit(
270 'EditPageTest_testCreatePage',
271 null,
272 null,
273 array(
274 'wpTextbox1' => "",
275 'wpIgnoreBlankArticle' => 1,
276 ),
277 EditPage::AS_SUCCESS_NEW_ARTICLE,
278 "",
279 "expected empty article being created"
280 )->doDeleteArticleReal( 'EditPageTest_testCreatePage' );
281 }
282
283 public function testUpdatePage() {
284 $text = "one";
285 $edit = array(
286 'wpTextbox1' => $text,
287 'wpSummary' => 'first update',
288 );
289
290 $page = $this->assertEdit( 'EditPageTest_testUpdatePage', "zero", null, $edit,
291 EditPage::AS_SUCCESS_UPDATE, $text,
292 "expected successfull update with given text" );
293
294 $this->forceRevisionDate( $page, '20120101000000' );
295
296 $text = "two";
297 $edit = array(
298 'wpTextbox1' => $text,
299 'wpSummary' => 'second update',
300 );
301
302 $this->assertEdit( 'EditPageTest_testUpdatePage', null, null, $edit,
303 EditPage::AS_SUCCESS_UPDATE, $text,
304 "expected successfull update with given text" );
305 }
306
307 public static function provideSectionEdit() {
308 $text = 'Intro
309
310 == one ==
311 first section.
312
313 == two ==
314 second section.
315 ';
316
317 $sectionOne = '== one ==
318 hello
319 ';
320
321 $newSection = '== new section ==
322
323 hello
324 ';
325
326 $textWithNewSectionOne = preg_replace(
327 '/== one ==.*== two ==/ms',
328 "$sectionOne\n== two ==", $text
329 );
330
331 $textWithNewSectionAdded = "$text\n$newSection";
332
333 return array(
334 array( #0
335 $text,
336 '',
337 'hello',
338 'replace all',
339 'hello'
340 ),
341
342 array( #1
343 $text,
344 '1',
345 $sectionOne,
346 'replace first section',
347 $textWithNewSectionOne,
348 ),
349
350 array( #2
351 $text,
352 'new',
353 'hello',
354 'new section',
355 $textWithNewSectionAdded,
356 ),
357 );
358 }
359
360 /**
361 * @dataProvider provideSectionEdit
362 * @covers EditPage
363 */
364 public function testSectionEdit( $base, $section, $text, $summary, $expected ) {
365 $edit = array(
366 'wpTextbox1' => $text,
367 'wpSummary' => $summary,
368 'wpSection' => $section,
369 );
370
371 $this->assertEdit( 'EditPageTest_testSectionEdit', $base, null, $edit,
372 EditPage::AS_SUCCESS_UPDATE, $expected,
373 "expected successfull update of section" );
374 }
375
376 public static function provideAutoMerge() {
377 $tests = array();
378
379 $tests[] = array( #0: plain conflict
380 "Elmo", # base edit user
381 "one\n\ntwo\n\nthree\n",
382 array( #adam's edit
383 'wpStarttime' => 1,
384 'wpTextbox1' => "ONE\n\ntwo\n\nthree\n",
385 ),
386 array( #berta's edit
387 'wpStarttime' => 2,
388 'wpTextbox1' => "(one)\n\ntwo\n\nthree\n",
389 ),
390 EditPage::AS_CONFLICT_DETECTED, # expected code
391 "ONE\n\ntwo\n\nthree\n", # expected text
392 'expected edit conflict', # message
393 );
394
395 $tests[] = array( #1: successful merge
396 "Elmo", # base edit user
397 "one\n\ntwo\n\nthree\n",
398 array( #adam's edit
399 'wpStarttime' => 1,
400 'wpTextbox1' => "ONE\n\ntwo\n\nthree\n",
401 ),
402 array( #berta's edit
403 'wpStarttime' => 2,
404 'wpTextbox1' => "one\n\ntwo\n\nTHREE\n",
405 ),
406 EditPage::AS_SUCCESS_UPDATE, # expected code
407 "ONE\n\ntwo\n\nTHREE\n", # expected text
408 'expected automatic merge', # message
409 );
410
411 $text = "Intro\n\n";
412 $text .= "== first section ==\n\n";
413 $text .= "one\n\ntwo\n\nthree\n\n";
414 $text .= "== second section ==\n\n";
415 $text .= "four\n\nfive\n\nsix\n\n";
416
417 // extract the first section.
418 $section = preg_replace( '/.*(== first section ==.*)== second section ==.*/sm', '$1', $text );
419
420 // generate expected text after merge
421 $expected = str_replace( 'one', 'ONE', str_replace( 'three', 'THREE', $text ) );
422
423 $tests[] = array( #2: merge in section
424 "Elmo", # base edit user
425 $text,
426 array( #adam's edit
427 'wpStarttime' => 1,
428 'wpTextbox1' => str_replace( 'one', 'ONE', $section ),
429 'wpSection' => '1'
430 ),
431 array( #berta's edit
432 'wpStarttime' => 2,
433 'wpTextbox1' => str_replace( 'three', 'THREE', $section ),
434 'wpSection' => '1'
435 ),
436 EditPage::AS_SUCCESS_UPDATE, # expected code
437 $expected, # expected text
438 'expected automatic section merge', # message
439 );
440
441 // see whether it makes a difference who did the base edit
442 $testsWithAdam = array_map( function ( $test ) {
443 $test[0] = 'Adam'; // change base edit user
444 return $test;
445 }, $tests );
446
447 $testsWithBerta = array_map( function ( $test ) {
448 $test[0] = 'Berta'; // change base edit user
449 return $test;
450 }, $tests );
451
452 return array_merge( $tests, $testsWithAdam, $testsWithBerta );
453 }
454
455 /**
456 * @dataProvider provideAutoMerge
457 * @covers EditPage
458 */
459 public function testAutoMerge( $baseUser, $text, $adamsEdit, $bertasEdit,
460 $expectedCode, $expectedText, $message = null
461 ) {
462 $this->checkHasDiff3();
463
464 //create page
465 $ns = $this->getDefaultWikitextNS();
466 $title = Title::newFromText( 'EditPageTest_testAutoMerge', $ns );
467 $page = WikiPage::factory( $title );
468
469 if ( $page->exists() ) {
470 $page->doDeleteArticle( "clean slate for testing" );
471 }
472
473 $baseEdit = array(
474 'wpTextbox1' => $text,
475 );
476
477 $page = $this->assertEdit( 'EditPageTest_testAutoMerge', null,
478 $baseUser, $baseEdit, null, null, __METHOD__ );
479
480 $this->forceRevisionDate( $page, '20120101000000' );
481
482 $edittime = $page->getTimestamp();
483
484 // start timestamps for conflict detection
485 if ( !isset( $adamsEdit['wpStarttime'] ) ) {
486 $adamsEdit['wpStarttime'] = 1;
487 }
488
489 if ( !isset( $bertasEdit['wpStarttime'] ) ) {
490 $bertasEdit['wpStarttime'] = 2;
491 }
492
493 $starttime = wfTimestampNow();
494 $adamsTime = wfTimestamp(
495 TS_MW,
496 (int)wfTimestamp( TS_UNIX, $starttime ) + (int)$adamsEdit['wpStarttime']
497 );
498 $bertasTime = wfTimestamp(
499 TS_MW,
500 (int)wfTimestamp( TS_UNIX, $starttime ) + (int)$bertasEdit['wpStarttime']
501 );
502
503 $adamsEdit['wpStarttime'] = $adamsTime;
504 $bertasEdit['wpStarttime'] = $bertasTime;
505
506 $adamsEdit['wpSummary'] = 'Adam\'s edit';
507 $bertasEdit['wpSummary'] = 'Bertas\'s edit';
508
509 $adamsEdit['wpEdittime'] = $edittime;
510 $bertasEdit['wpEdittime'] = $edittime;
511
512 // first edit
513 $this->assertEdit( 'EditPageTest_testAutoMerge', null, 'Adam', $adamsEdit,
514 EditPage::AS_SUCCESS_UPDATE, null, "expected successfull update" );
515
516 // second edit
517 $this->assertEdit( 'EditPageTest_testAutoMerge', null, 'Berta', $bertasEdit,
518 $expectedCode, $expectedText, $message );
519 }
520 }