Merge "Fix typo"
[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. Defaults to EditPage::AS_OK.
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 = EditPage::AS_OK, $expectedText = null, $message = null
94 ) {
95 if ( is_string( $title ) ) {
96 $ns = $this->getDefaultWikitextNS();
97 $title = Title::newFromText( $title, $ns );
98 }
99
100 if ( is_string( $user ) ) {
101 $user = User::newFromName( $user );
102
103 if ( $user->getId() === 0 ) {
104 $user->addToDatabase();
105 }
106 }
107
108 $page = WikiPage::factory( $title );
109
110 if ( $baseText !== null ) {
111 $content = ContentHandler::makeContent( $baseText, $title );
112 $page->doEditContent( $content, "base text for test" );
113 $this->forceRevisionDate( $page, '20120101000000' );
114
115 //sanity check
116 $page->clear();
117 $currentText = ContentHandler::getContentText( $page->getContent() );
118
119 # EditPage rtrim() the user input, so we alter our expected text
120 # to reflect that.
121 $this->assertEditedTextEquals( $baseText, $currentText );
122 }
123
124 if ( $user == null ) {
125 $user = $GLOBALS['wgUser'];
126 } else {
127 $this->setMwGlobals( 'wgUser', $user );
128 }
129
130 if ( !isset( $edit['wpEditToken'] ) ) {
131 $edit['wpEditToken'] = $user->getEditToken();
132 }
133
134 if ( !isset( $edit['wpEdittime'] ) ) {
135 $edit['wpEdittime'] = $page->exists() ? $page->getTimestamp() : '';
136 }
137
138 if ( !isset( $edit['wpStarttime'] ) ) {
139 $edit['wpStarttime'] = wfTimestampNow();
140 }
141
142 $req = new FauxRequest( $edit, true ); // session ??
143
144 $ep = new EditPage( new Article( $title ) );
145 $ep->setContextTitle( $title );
146 $ep->importFormData( $req );
147
148 $bot = isset( $edit['bot'] ) ? (bool)$edit['bot'] : false;
149
150 // this is where the edit happens!
151 // Note: don't want to use EditPage::AttemptSave, because it messes with $wgOut
152 // and throws exceptions like PermissionsError
153 $status = $ep->internalAttemptSave( $result, $bot );
154
155 if ( $expectedCode !== null ) {
156 // check edit code
157 $this->assertEquals( $expectedCode, $status->value,
158 "Expected result code mismatch. $message" );
159 }
160
161 $page = WikiPage::factory( $title );
162
163 if ( $expectedText !== null ) {
164 // check resulting page text
165 $content = $page->getContent();
166 $text = ContentHandler::getContentText( $content );
167
168 # EditPage rtrim() the user input, so we alter our expected text
169 # to reflect that.
170 $this->assertEditedTextEquals( $expectedText, $text,
171 "Expected article text mismatch. $message" );
172 }
173
174 return $page;
175 }
176
177 /**
178 * @todo split into a dataprovider and test method
179 * @covers EditPage
180 */
181 public function testCreatePage() {
182 $this->assertEdit(
183 'EditPageTest_testCreatePage',
184 null,
185 null,
186 array(
187 'wpTextbox1' => "Hello World!",
188 ),
189 EditPage::AS_SUCCESS_NEW_ARTICLE,
190 "Hello World!",
191 "expected article being created"
192 )->doDeleteArticleReal( 'EditPageTest_testCreatePage' );
193
194 $this->assertEdit(
195 'EditPageTest_testCreatePage',
196 null,
197 null,
198 array(
199 'wpTextbox1' => "",
200 ),
201 EditPage::AS_BLANK_ARTICLE,
202 null,
203 "expected article not being created if empty"
204 );
205
206 $this->assertEdit(
207 'MediaWiki:January',
208 null,
209 'UTSysop',
210 array(
211 'wpTextbox1' => "Not January",
212 ),
213 EditPage::AS_SUCCESS_NEW_ARTICLE,
214 "Not January",
215 "expected MediaWiki: page being created"
216 )->doDeleteArticleReal( 'EditPageTest_testCreatePage' );
217
218 $this->assertEdit(
219 'MediaWiki:EditPageTest_testCreatePage',
220 null,
221 'UTSysop',
222 array(
223 'wpTextbox1' => "",
224 ),
225 EditPage::AS_BLANK_ARTICLE,
226 null,
227 "expected not-registered MediaWiki: page not being created if empty"
228 );
229
230 $this->assertEdit(
231 'MediaWiki:January',
232 null,
233 'UTSysop',
234 array(
235 'wpTextbox1' => "",
236 ),
237 EditPage::AS_SUCCESS_NEW_ARTICLE,
238 "",
239 "expected registered MediaWiki: page being created even if empty"
240 )->doDeleteArticleReal( 'EditPageTest_testCreatePage' );
241
242 $this->assertEdit(
243 'MediaWiki:Ipb-default-expiry',
244 null,
245 'UTSysop',
246 array(
247 'wpTextbox1' => "",
248 ),
249 EditPage::AS_BLANK_ARTICLE,
250 "",
251 "expected registered MediaWiki: page whose default content is empty not being created if empty"
252 );
253
254 $this->assertEdit(
255 'MediaWiki:January',
256 null,
257 'UTSysop',
258 array(
259 'wpTextbox1' => "January",
260 ),
261 EditPage::AS_BLANK_ARTICLE,
262 null,
263 "expected MediaWiki: page not being created if text equals default message"
264 );
265 }
266
267 public function testUpdatePage() {
268 $text = "one";
269 $edit = array(
270 'wpTextbox1' => $text,
271 'wpSummary' => 'first update',
272 );
273
274 $page = $this->assertEdit( 'EditPageTest_testUpdatePage', "zero", null, $edit,
275 EditPage::AS_SUCCESS_UPDATE, $text,
276 "expected successfull update with given text" );
277
278 $this->forceRevisionDate( $page, '20120101000000' );
279
280 $text = "two";
281 $edit = array(
282 'wpTextbox1' => $text,
283 'wpSummary' => 'second update',
284 );
285
286 $this->assertEdit( 'EditPageTest_testUpdatePage', null, null, $edit,
287 EditPage::AS_SUCCESS_UPDATE, $text,
288 "expected successfull update with given text" );
289 }
290
291 public static function provideSectionEdit() {
292 $text = 'Intro
293
294 == one ==
295 first section.
296
297 == two ==
298 second section.
299 ';
300
301 $sectionOne = '== one ==
302 hello
303 ';
304
305 $newSection = '== new section ==
306
307 hello
308 ';
309
310 $textWithNewSectionOne = preg_replace(
311 '/== one ==.*== two ==/ms',
312 "$sectionOne\n== two ==", $text
313 );
314
315 $textWithNewSectionAdded = "$text\n$newSection";
316
317 return array(
318 array( #0
319 $text,
320 '',
321 'hello',
322 'replace all',
323 'hello'
324 ),
325
326 array( #1
327 $text,
328 '1',
329 $sectionOne,
330 'replace first section',
331 $textWithNewSectionOne,
332 ),
333
334 array( #2
335 $text,
336 'new',
337 'hello',
338 'new section',
339 $textWithNewSectionAdded,
340 ),
341 );
342 }
343
344 /**
345 * @dataProvider provideSectionEdit
346 * @covers EditPage
347 */
348 public function testSectionEdit( $base, $section, $text, $summary, $expected ) {
349 $edit = array(
350 'wpTextbox1' => $text,
351 'wpSummary' => $summary,
352 'wpSection' => $section,
353 );
354
355 $this->assertEdit( 'EditPageTest_testSectionEdit', $base, null, $edit,
356 EditPage::AS_SUCCESS_UPDATE, $expected,
357 "expected successfull update of section" );
358 }
359
360 public static function provideAutoMerge() {
361 $tests = array();
362
363 $tests[] = array( #0: plain conflict
364 "Elmo", # base edit user
365 "one\n\ntwo\n\nthree\n",
366 array( #adam's edit
367 'wpStarttime' => 1,
368 'wpTextbox1' => "ONE\n\ntwo\n\nthree\n",
369 ),
370 array( #berta's edit
371 'wpStarttime' => 2,
372 'wpTextbox1' => "(one)\n\ntwo\n\nthree\n",
373 ),
374 EditPage::AS_CONFLICT_DETECTED, # expected code
375 "ONE\n\ntwo\n\nthree\n", # expected text
376 'expected edit conflict', # message
377 );
378
379 $tests[] = array( #1: successful merge
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_SUCCESS_UPDATE, # expected code
391 "ONE\n\ntwo\n\nTHREE\n", # expected text
392 'expected automatic merge', # message
393 );
394
395 $text = "Intro\n\n";
396 $text .= "== first section ==\n\n";
397 $text .= "one\n\ntwo\n\nthree\n\n";
398 $text .= "== second section ==\n\n";
399 $text .= "four\n\nfive\n\nsix\n\n";
400
401 // extract the first section.
402 $section = preg_replace( '/.*(== first section ==.*)== second section ==.*/sm', '$1', $text );
403
404 // generate expected text after merge
405 $expected = str_replace( 'one', 'ONE', str_replace( 'three', 'THREE', $text ) );
406
407 $tests[] = array( #2: merge in section
408 "Elmo", # base edit user
409 $text,
410 array( #adam's edit
411 'wpStarttime' => 1,
412 'wpTextbox1' => str_replace( 'one', 'ONE', $section ),
413 'wpSection' => '1'
414 ),
415 array( #berta's edit
416 'wpStarttime' => 2,
417 'wpTextbox1' => str_replace( 'three', 'THREE', $section ),
418 'wpSection' => '1'
419 ),
420 EditPage::AS_SUCCESS_UPDATE, # expected code
421 $expected, # expected text
422 'expected automatic section merge', # message
423 );
424
425 // see whether it makes a difference who did the base edit
426 $testsWithAdam = array_map( function ( $test ) {
427 $test[0] = 'Adam'; // change base edit user
428 return $test;
429 }, $tests );
430
431 $testsWithBerta = array_map( function ( $test ) {
432 $test[0] = 'Berta'; // change base edit user
433 return $test;
434 }, $tests );
435
436 return array_merge( $tests, $testsWithAdam, $testsWithBerta );
437 }
438
439 /**
440 * @dataProvider provideAutoMerge
441 * @covers EditPage
442 */
443 public function testAutoMerge( $baseUser, $text, $adamsEdit, $bertasEdit,
444 $expectedCode, $expectedText, $message = null
445 ) {
446 $this->checkHasDiff3();
447
448 //create page
449 $ns = $this->getDefaultWikitextNS();
450 $title = Title::newFromText( 'EditPageTest_testAutoMerge', $ns );
451 $page = WikiPage::factory( $title );
452
453 if ( $page->exists() ) {
454 $page->doDeleteArticle( "clean slate for testing" );
455 }
456
457 $baseEdit = array(
458 'wpTextbox1' => $text,
459 );
460
461 $page = $this->assertEdit( 'EditPageTest_testAutoMerge', null,
462 $baseUser, $baseEdit, null, null, __METHOD__ );
463
464 $this->forceRevisionDate( $page, '20120101000000' );
465
466 $edittime = $page->getTimestamp();
467
468 // start timestamps for conflict detection
469 if ( !isset( $adamsEdit['wpStarttime'] ) ) {
470 $adamsEdit['wpStarttime'] = 1;
471 }
472
473 if ( !isset( $bertasEdit['wpStarttime'] ) ) {
474 $bertasEdit['wpStarttime'] = 2;
475 }
476
477 $starttime = wfTimestampNow();
478 $adamsTime = wfTimestamp(
479 TS_MW,
480 (int)wfTimestamp( TS_UNIX, $starttime ) + (int)$adamsEdit['wpStarttime']
481 );
482 $bertasTime = wfTimestamp(
483 TS_MW,
484 (int)wfTimestamp( TS_UNIX, $starttime ) + (int)$bertasEdit['wpStarttime']
485 );
486
487 $adamsEdit['wpStarttime'] = $adamsTime;
488 $bertasEdit['wpStarttime'] = $bertasTime;
489
490 $adamsEdit['wpSummary'] = 'Adam\'s edit';
491 $bertasEdit['wpSummary'] = 'Bertas\'s edit';
492
493 $adamsEdit['wpEdittime'] = $edittime;
494 $bertasEdit['wpEdittime'] = $edittime;
495
496 // first edit
497 $this->assertEdit( 'EditPageTest_testAutoMerge', null, 'Adam', $adamsEdit,
498 EditPage::AS_SUCCESS_UPDATE, null, "expected successfull update" );
499
500 // second edit
501 $this->assertEdit( 'EditPageTest_testAutoMerge', null, 'Berta', $bertasEdit,
502 $expectedCode, $expectedText, $message );
503 }
504 }