Move ResultWrapper subclasses to Rdbms
[mediawiki.git] / tests / phpunit / includes / api / ApiEditPageTest.php
blobe0911532818053d204be65dc11f77dc308c7bef2
1 <?php
3 /**
4 * Tests for MediaWiki api.php?action=edit.
6 * @author Daniel Kinzler
8 * @group API
9 * @group Database
10 * @group medium
12 * @covers ApiEditPage
14 class ApiEditPageTest extends ApiTestCase {
16 protected function setUp() {
17 global $wgExtraNamespaces, $wgNamespaceContentModels, $wgContentHandlers, $wgContLang;
19 parent::setUp();
21 $this->setMwGlobals( [
22 'wgExtraNamespaces' => $wgExtraNamespaces,
23 'wgNamespaceContentModels' => $wgNamespaceContentModels,
24 'wgContentHandlers' => $wgContentHandlers,
25 'wgContLang' => $wgContLang,
26 ] );
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
42 $this->doLogin();
45 protected function tearDown() {
46 MWNamespace::getCanonicalNamespaces( true ); # reset namespace cache
47 parent::tearDown();
50 public function testEdit() {
51 $name = 'Help:ApiEditPageTest_testEdit'; // assume Help namespace to default to wikitext
53 // -- test new page --------------------------------------------
54 $apiResult = $this->doApiRequestWithToken( [
55 'action' => 'edit',
56 'title' => $name,
57 'text' => 'some text',
58 ] );
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( [
73 'action' => 'edit',
74 'title' => $name,
75 'text' => 'some text',
76 ] );
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( [
85 'action' => 'edit',
86 'title' => $name,
87 'text' => 'different text'
88 ] );
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"
105 * @return array
107 public static function provideEditAppend() {
108 return [
109 [ # 0: append
110 'foo', 'append', 'bar', "foobar"
112 [ # 1: prepend
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 ) {
134 static $count = 0;
135 $count++;
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( [
143 'action' => 'edit',
144 'title' => $name,
145 'text' => $text, ] );
147 $this->assertEquals( 'Success', $re['edit']['result'] ); // sanity
150 // -- try append/prepend --------------------------------------------
151 list( $re ) = $this->doApiRequestWithToken( [
152 'action' => 'edit',
153 'title' => $name,
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( [
179 'action' => 'edit',
180 'title' => $name,
181 'section' => '1',
182 'text' => "==section 1==\nnew content 1",
183 ] );
184 $this->assertEquals( 'Success', $re['edit']['result'] );
185 $newtext = WikiPage::factory( Title::newFromText( $name ) )
186 ->getContent( Revision::RAW )
187 ->getNativeData();
188 $this->assertEquals( "==section 1==\nnew content 1\n\n==section 2==\ncontent2", $newtext );
190 // Test that we raise a 'nosuchsection' error
191 try {
192 $this->doApiRequestWithToken( [
193 'action' => 'edit',
194 'title' => $name,
195 'section' => '9999',
196 'text' => 'text',
197 ] );
198 $this->fail( "Should have raised an ApiUsageException" );
199 } catch ( ApiUsageException $e ) {
200 $this->assertTrue( self::apiExceptionHasCode( $e, 'nosuchsection' ) );
205 * Test action=edit&section=new
206 * Run it twice so we test adding a new section on a
207 * page that doesn't exist (T54830) and one that
208 * does exist
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( [
216 'action' => 'edit',
217 'title' => $name,
218 'section' => 'new',
219 'text' => 'test',
220 'summary' => 'header',
221 ] );
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 )
227 ->getNativeData();
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( [
233 'action' => 'edit',
234 'title' => $name,
235 'section' => 'new',
236 'text' => 'test',
237 'summary' => 'header',
238 ] );
240 $this->assertEquals( 'Success', $re2['edit']['result'] );
241 $text = WikiPage::factory( Title::newFromText( $name ) )
242 ->getContent( Revision::RAW )
243 ->getNativeData();
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() {
251 static $count = 0;
252 $count++;
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( [
281 'action' => 'edit',
282 'title' => $rname,
283 'text' => 'nix bar!',
284 'basetimestamp' => $baseTime,
285 'section' => 'new',
286 'redirect' => true,
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() {
297 static $count = 0;
298 $count++;
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
326 try {
327 $this->doApiRequestWithToken( [
328 'action' => 'edit',
329 'title' => $rname,
330 'text' => 'nix bar!',
331 'basetimestamp' => $baseTime,
332 'redirect' => true,
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() {
342 static $count = 0;
343 $count++;
345 // assume NS_HELP defaults to wikitext
346 $name = "Help:ApiEditPageTest_testEditConflict_$count";
347 $title = Title::newFromText( $name );
349 $page = WikiPage::factory( $title );
351 // base edit
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();
357 // conflicting edit
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
363 try {
364 $this->doApiRequestWithToken( [
365 'action' => 'edit',
366 'title' => $name,
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() {
381 static $count = 0;
382 $count++;
384 // assume NS_HELP defaults to wikitext
385 $name = "Help:ApiEditPageTest_testEditConflict_newSection_$count";
386 $title = Title::newFromText( $name );
388 $page = WikiPage::factory( $title );
390 // base edit
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();
396 // conflicting edit
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( [
403 'action' => 'edit',
404 'title' => $name,
405 'text' => 'nix bar!',
406 'basetimestamp' => $baseTime,
407 'section' => 'new',
408 ], null, self::$users['sysop']->getUser() );
410 $this->assertEquals( 'Success', $re['edit']['result'],
411 "no edit conflict expected here" );
414 public function testEditConflict_bug41990() {
415 static $count = 0;
416 $count++;
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( [
450 'action' => 'edit',
451 'title' => $rname,
452 'text' => 'nix bar!',
453 'section' => 'new',
454 'redirect' => true,
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() ] );
472 $page->clear();
475 public function testCheckDirectApiEditingDisallowed_forNonTextContent() {
476 $this->setExpectedException(
477 'ApiUsageException',
478 'Direct editing via API is not supported for content model ' .
479 'testing used by Dummy:ApiEditPageTest_nonTextPageEdit'
482 $this->doApiRequestWithToken( [
483 'action' => 'edit',
484 'title' => 'Dummy:ApiEditPageTest_nonTextPageEdit',
485 'text' => '{"animals":["kittens!"]}'
486 ] );
489 public function testSupportsDirectApiEditing_withContentHandlerOverride() {
490 $name = 'DummyNonText:ApiEditPageTest_testNonTextEdit';
491 $data = serialize( 'some bla bla text' );
493 $result = $this->doApiRequestWithToken( [
494 'action' => 'edit',
495 'title' => $name,
496 'text' => $data,
497 ] );
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( [
527 'action' => 'edit',
528 'title' => $name,
529 'text' => 'some text',
530 ], null, $sysop )[0];
532 // Check success
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( [
542 'action' => 'edit',
543 'title' => $name,
544 'text' => '{}',
545 'contentmodel' => 'json',
546 ], null, $uploader )[0];
548 // Check success
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( [
556 'action' => 'edit',
557 'title' => $name,
558 'undo' => $apiResult['edit']['newrevid']
559 ], null, $sysop )[0];
561 // Check success
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'] );