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