4 * Tests for MediaWiki api.php?action=edit.
6 * @author Daniel Kinzler
14 class ApiEditPageTest extends ApiTestCase {
16 protected function setUp() {
17 global $wgExtraNamespaces, $wgNamespaceContentModels, $wgContentHandlers, $wgContLang;
21 $this->setMwGlobals( [
22 'wgExtraNamespaces' => $wgExtraNamespaces,
23 'wgNamespaceContentModels' => $wgNamespaceContentModels,
24 'wgContentHandlers' => $wgContentHandlers,
25 'wgContLang' => $wgContLang,
28 $wgExtraNamespaces[12312] = 'Dummy';
29 $wgExtraNamespaces[12313] = 'Dummy_talk';
30 $wgExtraNamespaces[12314] = 'DummyNonText';
31 $wgExtraNamespaces[12315] = 'DummyNonText_talk';
33 $wgNamespaceContentModels[12312] = "testing";
34 $wgNamespaceContentModels[12314] = "testing-nontext";
36 $wgContentHandlers["testing"] = 'DummyContentHandlerForTesting';
37 $wgContentHandlers["testing-nontext"] = 'DummyNonTextContentHandler';
39 MWNamespace::getCanonicalNamespaces( true ); # reset namespace cache
40 $wgContLang->resetNamespaces(); # reset namespace cache
45 protected function tearDown() {
46 MWNamespace::getCanonicalNamespaces( true ); # reset namespace cache
50 public function testEdit() {
51 $name = 'Help:ApiEditPageTest_testEdit'; // assume Help namespace to default to wikitext
53 // -- test new page --------------------------------------------
54 $apiResult = $this->doApiRequestWithToken( [
57 'text' => 'some text',
59 $apiResult = $apiResult[0];
61 // Validate API result data
62 $this->assertArrayHasKey( 'edit', $apiResult );
63 $this->assertArrayHasKey( 'result', $apiResult['edit'] );
64 $this->assertEquals( 'Success', $apiResult['edit']['result'] );
66 $this->assertArrayHasKey( 'new', $apiResult['edit'] );
67 $this->assertArrayNotHasKey( 'nochange', $apiResult['edit'] );
69 $this->assertArrayHasKey( 'pageid', $apiResult['edit'] );
71 // -- test existing page, no change ----------------------------
72 $data = $this->doApiRequestWithToken( [
75 'text' => 'some text',
78 $this->assertEquals( 'Success', $data[0]['edit']['result'] );
80 $this->assertArrayNotHasKey( 'new', $data[0]['edit'] );
81 $this->assertArrayHasKey( 'nochange', $data[0]['edit'] );
83 // -- test existing page, with change --------------------------
84 $data = $this->doApiRequestWithToken( [
87 'text' => 'different text'
90 $this->assertEquals( 'Success', $data[0]['edit']['result'] );
92 $this->assertArrayNotHasKey( 'new', $data[0]['edit'] );
93 $this->assertArrayNotHasKey( 'nochange', $data[0]['edit'] );
95 $this->assertArrayHasKey( 'oldrevid', $data[0]['edit'] );
96 $this->assertArrayHasKey( 'newrevid', $data[0]['edit'] );
97 $this->assertNotEquals(
98 $data[0]['edit']['newrevid'],
99 $data[0]['edit']['oldrevid'],
100 "revision id should change after edit"
107 public static function provideEditAppend() {
110 'foo', 'append', 'bar', "foobar"
113 'foo', 'prepend', 'bar', "barfoo"
115 [ # 2: append to empty page
116 '', 'append', 'foo', "foo"
118 [ # 3: prepend to empty page
119 '', 'prepend', 'foo', "foo"
121 [ # 4: append to non-existing page
122 null, 'append', 'foo', "foo"
124 [ # 5: prepend to non-existing page
125 null, 'prepend', 'foo', "foo"
131 * @dataProvider provideEditAppend
133 public function testEditAppend( $text, $op, $append, $expected ) {
137 // assume NS_HELP defaults to wikitext
138 $name = "Help:ApiEditPageTest_testEditAppend_$count";
140 // -- create page (or not) -----------------------------------------
141 if ( $text !== null ) {
142 list( $re ) = $this->doApiRequestWithToken( [
145 'text' => $text, ] );
147 $this->assertEquals( 'Success', $re['edit']['result'] ); // sanity
150 // -- try append/prepend --------------------------------------------
151 list( $re ) = $this->doApiRequestWithToken( [
154 $op . 'text' => $append, ] );
156 $this->assertEquals( 'Success', $re['edit']['result'] );
158 // -- validate -----------------------------------------------------
159 $page = new WikiPage( Title::newFromText( $name ) );
160 $content = $page->getContent();
161 $this->assertNotNull( $content, 'Page should have been created' );
163 $text = $content->getNativeData();
165 $this->assertEquals( $expected, $text );
169 * Test editing of sections
171 public function testEditSection() {
172 $name = 'Help:ApiEditPageTest_testEditSection';
173 $page = WikiPage::factory( Title::newFromText( $name ) );
174 $text = "==section 1==\ncontent 1\n==section 2==\ncontent2";
175 // Preload the page with some text
176 $page->doEditContent( ContentHandler::makeContent( $text, $page->getTitle() ), 'summary' );
178 list( $re ) = $this->doApiRequestWithToken( [
182 'text' => "==section 1==\nnew content 1",
184 $this->assertEquals( 'Success', $re['edit']['result'] );
185 $newtext = WikiPage::factory( Title::newFromText( $name ) )
186 ->getContent( Revision::RAW )
188 $this->assertEquals( "==section 1==\nnew content 1\n\n==section 2==\ncontent2", $newtext );
190 // Test that we raise a 'nosuchsection' error
192 $this->doApiRequestWithToken( [
198 $this->fail( "Should have raised an ApiUsageException" );
199 } catch ( ApiUsageException $e ) {
200 $this->assertTrue( self::apiExceptionHasCode( $e, 'nosuchsection' ) );
205 * Test action=edit§ion=new
206 * Run it twice so we test adding a new section on a
207 * page that doesn't exist (T54830) and one that
210 public function testEditNewSection() {
211 $name = 'Help:ApiEditPageTest_testEditNewSection';
213 // Test on a page that does not already exist
214 $this->assertFalse( Title::newFromText( $name )->exists() );
215 list( $re ) = $this->doApiRequestWithToken( [
220 'summary' => 'header',
223 $this->assertEquals( 'Success', $re['edit']['result'] );
224 // Check the page text is correct
225 $text = WikiPage::factory( Title::newFromText( $name ) )
226 ->getContent( Revision::RAW )
228 $this->assertEquals( "== header ==\n\ntest", $text );
230 // Now on one that does
231 $this->assertTrue( Title::newFromText( $name )->exists() );
232 list( $re2 ) = $this->doApiRequestWithToken( [
237 'summary' => 'header',
240 $this->assertEquals( 'Success', $re2['edit']['result'] );
241 $text = WikiPage::factory( Title::newFromText( $name ) )
242 ->getContent( Revision::RAW )
244 $this->assertEquals( "== header ==\n\ntest\n\n== header ==\n\ntest", $text );
248 * Ensure we can edit through a redirect, if adding a section
250 public function testEdit_redirect() {
254 // assume NS_HELP defaults to wikitext
255 $name = "Help:ApiEditPageTest_testEdit_redirect_$count";
256 $title = Title::newFromText( $name );
257 $page = WikiPage::factory( $title );
259 $rname = "Help:ApiEditPageTest_testEdit_redirect_r$count";
260 $rtitle = Title::newFromText( $rname );
261 $rpage = WikiPage::factory( $rtitle );
263 // base edit for content
264 $page->doEditContent( new WikitextContent( "Foo" ),
265 "testing 1", EDIT_NEW, false, self::$users['sysop']->getUser() );
266 $this->forceRevisionDate( $page, '20120101000000' );
267 $baseTime = $page->getRevision()->getTimestamp();
269 // base edit for redirect
270 $rpage->doEditContent( new WikitextContent( "#REDIRECT [[$name]]" ),
271 "testing 1", EDIT_NEW, false, self::$users['sysop']->getUser() );
272 $this->forceRevisionDate( $rpage, '20120101000000' );
274 // conflicting edit to redirect
275 $rpage->doEditContent( new WikitextContent( "#REDIRECT [[$name]]\n\n[[Category:Test]]" ),
276 "testing 2", EDIT_UPDATE, $page->getLatest(), self::$users['uploader']->getUser() );
277 $this->forceRevisionDate( $rpage, '20120101020202' );
279 // try to save edit, following the redirect
280 list( $re, , ) = $this->doApiRequestWithToken( [
283 'text' => 'nix bar!',
284 'basetimestamp' => $baseTime,
287 ], null, self::$users['sysop']->getUser() );
289 $this->assertEquals( 'Success', $re['edit']['result'],
290 "no problems expected when following redirect" );
294 * Ensure we cannot edit through a redirect, if attempting to overwrite content
296 public function testEdit_redirectText() {
300 // assume NS_HELP defaults to wikitext
301 $name = "Help:ApiEditPageTest_testEdit_redirectText_$count";
302 $title = Title::newFromText( $name );
303 $page = WikiPage::factory( $title );
305 $rname = "Help:ApiEditPageTest_testEdit_redirectText_r$count";
306 $rtitle = Title::newFromText( $rname );
307 $rpage = WikiPage::factory( $rtitle );
309 // base edit for content
310 $page->doEditContent( new WikitextContent( "Foo" ),
311 "testing 1", EDIT_NEW, false, self::$users['sysop']->getUser() );
312 $this->forceRevisionDate( $page, '20120101000000' );
313 $baseTime = $page->getRevision()->getTimestamp();
315 // base edit for redirect
316 $rpage->doEditContent( new WikitextContent( "#REDIRECT [[$name]]" ),
317 "testing 1", EDIT_NEW, false, self::$users['sysop']->getUser() );
318 $this->forceRevisionDate( $rpage, '20120101000000' );
320 // conflicting edit to redirect
321 $rpage->doEditContent( new WikitextContent( "#REDIRECT [[$name]]\n\n[[Category:Test]]" ),
322 "testing 2", EDIT_UPDATE, $page->getLatest(), self::$users['uploader']->getUser() );
323 $this->forceRevisionDate( $rpage, '20120101020202' );
325 // try to save edit, following the redirect but without creating a section
327 $this->doApiRequestWithToken( [
330 'text' => 'nix bar!',
331 'basetimestamp' => $baseTime,
333 ], null, self::$users['sysop']->getUser() );
335 $this->fail( 'redirect-appendonly error expected' );
336 } catch ( ApiUsageException $ex ) {
337 $this->assertTrue( self::apiExceptionHasCode( $ex, 'redirect-appendonly' ) );
341 public function testEditConflict() {
345 // assume NS_HELP defaults to wikitext
346 $name = "Help:ApiEditPageTest_testEditConflict_$count";
347 $title = Title::newFromText( $name );
349 $page = WikiPage::factory( $title );
352 $page->doEditContent( new WikitextContent( "Foo" ),
353 "testing 1", EDIT_NEW, false, self::$users['sysop']->getUser() );
354 $this->forceRevisionDate( $page, '20120101000000' );
355 $baseTime = $page->getRevision()->getTimestamp();
358 $page->doEditContent( new WikitextContent( "Foo bar" ),
359 "testing 2", EDIT_UPDATE, $page->getLatest(), self::$users['uploader']->getUser() );
360 $this->forceRevisionDate( $page, '20120101020202' );
362 // try to save edit, expect conflict
364 $this->doApiRequestWithToken( [
367 'text' => 'nix bar!',
368 'basetimestamp' => $baseTime,
369 ], null, self::$users['sysop']->getUser() );
371 $this->fail( 'edit conflict expected' );
372 } catch ( ApiUsageException $ex ) {
373 $this->assertTrue( self::apiExceptionHasCode( $ex, 'editconflict' ) );
378 * Ensure that editing using section=new will prevent simple conflicts
380 public function testEditConflict_newSection() {
384 // assume NS_HELP defaults to wikitext
385 $name = "Help:ApiEditPageTest_testEditConflict_newSection_$count";
386 $title = Title::newFromText( $name );
388 $page = WikiPage::factory( $title );
391 $page->doEditContent( new WikitextContent( "Foo" ),
392 "testing 1", EDIT_NEW, false, self::$users['sysop']->getUser() );
393 $this->forceRevisionDate( $page, '20120101000000' );
394 $baseTime = $page->getRevision()->getTimestamp();
397 $page->doEditContent( new WikitextContent( "Foo bar" ),
398 "testing 2", EDIT_UPDATE, $page->getLatest(), self::$users['uploader']->getUser() );
399 $this->forceRevisionDate( $page, '20120101020202' );
401 // try to save edit, expect no conflict
402 list( $re, , ) = $this->doApiRequestWithToken( [
405 'text' => 'nix bar!',
406 'basetimestamp' => $baseTime,
408 ], null, self::$users['sysop']->getUser() );
410 $this->assertEquals( 'Success', $re['edit']['result'],
411 "no edit conflict expected here" );
414 public function testEditConflict_bug41990() {
419 * T43990: if the target page has a newer revision than the redirect, then editing the
420 * redirect while specifying 'redirect' and *not* specifying 'basetimestamp' erroneously
421 * caused an edit conflict to be detected.
424 // assume NS_HELP defaults to wikitext
425 $name = "Help:ApiEditPageTest_testEditConflict_redirect_bug41990_$count";
426 $title = Title::newFromText( $name );
427 $page = WikiPage::factory( $title );
429 $rname = "Help:ApiEditPageTest_testEditConflict_redirect_bug41990_r$count";
430 $rtitle = Title::newFromText( $rname );
431 $rpage = WikiPage::factory( $rtitle );
433 // base edit for content
434 $page->doEditContent( new WikitextContent( "Foo" ),
435 "testing 1", EDIT_NEW, false, self::$users['sysop']->getUser() );
436 $this->forceRevisionDate( $page, '20120101000000' );
438 // base edit for redirect
439 $rpage->doEditContent( new WikitextContent( "#REDIRECT [[$name]]" ),
440 "testing 1", EDIT_NEW, false, self::$users['sysop']->getUser() );
441 $this->forceRevisionDate( $rpage, '20120101000000' );
443 // new edit to content
444 $page->doEditContent( new WikitextContent( "Foo bar" ),
445 "testing 2", EDIT_UPDATE, $page->getLatest(), self::$users['uploader']->getUser() );
446 $this->forceRevisionDate( $rpage, '20120101020202' );
448 // try to save edit; should work, following the redirect.
449 list( $re, , ) = $this->doApiRequestWithToken( [
452 'text' => 'nix bar!',
455 ], null, self::$users['sysop']->getUser() );
457 $this->assertEquals( 'Success', $re['edit']['result'],
458 "no edit conflict expected here" );
462 * @param WikiPage $page
463 * @param string|int $timestamp
465 protected function forceRevisionDate( WikiPage $page, $timestamp ) {
466 $dbw = wfGetDB( DB_MASTER );
468 $dbw->update( 'revision',
469 [ 'rev_timestamp' => $dbw->timestamp( $timestamp ) ],
470 [ 'rev_id' => $page->getLatest() ] );
475 public function testCheckDirectApiEditingDisallowed_forNonTextContent() {
476 $this->setExpectedException(
478 'Direct editing via API is not supported for content model ' .
479 'testing used by Dummy:ApiEditPageTest_nonTextPageEdit'
482 $this->doApiRequestWithToken( [
484 'title' => 'Dummy:ApiEditPageTest_nonTextPageEdit',
485 'text' => '{"animals":["kittens!"]}'
489 public function testSupportsDirectApiEditing_withContentHandlerOverride() {
490 $name = 'DummyNonText:ApiEditPageTest_testNonTextEdit';
491 $data = serialize( 'some bla bla text' );
493 $result = $this->doApiRequestWithToken( [
499 $apiResult = $result[0];
501 // Validate API result data
502 $this->assertArrayHasKey( 'edit', $apiResult );
503 $this->assertArrayHasKey( 'result', $apiResult['edit'] );
504 $this->assertEquals( 'Success', $apiResult['edit']['result'] );
506 $this->assertArrayHasKey( 'new', $apiResult['edit'] );
507 $this->assertArrayNotHasKey( 'nochange', $apiResult['edit'] );
509 $this->assertArrayHasKey( 'pageid', $apiResult['edit'] );
511 // validate resulting revision
512 $page = WikiPage::factory( Title::newFromText( $name ) );
513 $this->assertEquals( "testing-nontext", $page->getContentModel() );
514 $this->assertEquals( $data, $page->getContent()->serialize() );
518 * This test verifies that after changing the content model
519 * of a page, undoing that edit via the API will also
520 * undo the content model change.
522 public function testUndoAfterContentModelChange() {
523 $name = 'Help:' . __FUNCTION__;
524 $uploader = self::$users['uploader']->getUser();
525 $sysop = self::$users['sysop']->getUser();
526 $apiResult = $this->doApiRequestWithToken( [
529 'text' => 'some text',
530 ], null, $sysop )[0];
533 $this->assertArrayHasKey( 'edit', $apiResult );
534 $this->assertArrayHasKey( 'result', $apiResult['edit'] );
535 $this->assertEquals( 'Success', $apiResult['edit']['result'] );
536 $this->assertArrayHasKey( 'contentmodel', $apiResult['edit'] );
537 // Content model is wikitext
538 $this->assertEquals( 'wikitext', $apiResult['edit']['contentmodel'] );
540 // Convert the page to JSON
541 $apiResult = $this->doApiRequestWithToken( [
545 'contentmodel' => 'json',
546 ], null, $uploader )[0];
549 $this->assertArrayHasKey( 'edit', $apiResult );
550 $this->assertArrayHasKey( 'result', $apiResult['edit'] );
551 $this->assertEquals( 'Success', $apiResult['edit']['result'] );
552 $this->assertArrayHasKey( 'contentmodel', $apiResult['edit'] );
553 $this->assertEquals( 'json', $apiResult['edit']['contentmodel'] );
555 $apiResult = $this->doApiRequestWithToken( [
558 'undo' => $apiResult['edit']['newrevid']
559 ], null, $sysop )[0];
562 $this->assertArrayHasKey( 'edit', $apiResult );
563 $this->assertArrayHasKey( 'result', $apiResult['edit'] );
564 $this->assertEquals( 'Success', $apiResult['edit']['result'] );
565 $this->assertArrayHasKey( 'contentmodel', $apiResult['edit'] );
566 // Check that the contentmodel is back to wikitext now.
567 $this->assertEquals( 'wikitext', $apiResult['edit']['contentmodel'] );