Merge "API: Handle "special" options in action=options"
[lhc/web/wiklou.git] / tests / phpunit / includes / api / ApiEditPageTest.php
1 <?php
2
3 /**
4 * Tests for MediaWiki api.php?action=edit.
5 *
6 * @author Daniel Kinzler
7 *
8 * @group API
9 * @group Database
10 * @group medium
11 *
12 * @covers ApiEditPage
13 */
14 class ApiEditPageTest extends ApiTestCase {
15
16 protected function setUp() {
17 global $wgExtraNamespaces, $wgNamespaceContentModels, $wgContentHandlers, $wgContLang;
18
19 parent::setUp();
20
21 $wgExtraNamespaces[12312] = 'Dummy';
22 $wgExtraNamespaces[12313] = 'Dummy_talk';
23
24 $wgNamespaceContentModels[12312] = "testing";
25 $wgContentHandlers["testing"] = 'DummyContentHandlerForTesting';
26
27 MWNamespace::getCanonicalNamespaces( true ); # reset namespace cache
28 $wgContLang->resetNamespaces(); # reset namespace cache
29
30 $this->doLogin();
31 }
32
33 protected function tearDown() {
34 global $wgExtraNamespaces, $wgNamespaceContentModels, $wgContentHandlers, $wgContLang;
35
36 unset( $wgExtraNamespaces[12312] );
37 unset( $wgExtraNamespaces[12313] );
38
39 unset( $wgNamespaceContentModels[12312] );
40 unset( $wgContentHandlers["testing"] );
41
42 MWNamespace::getCanonicalNamespaces( true ); # reset namespace cache
43 $wgContLang->resetNamespaces(); # reset namespace cache
44
45 parent::tearDown();
46 }
47
48 public function testEdit() {
49 $name = 'Help:ApiEditPageTest_testEdit'; // assume Help namespace to default to wikitext
50
51 // -- test new page --------------------------------------------
52 $apiResult = $this->doApiRequestWithToken( array(
53 'action' => 'edit',
54 'title' => $name,
55 'text' => 'some text',
56 ) );
57 $apiResult = $apiResult[0];
58
59 // Validate API result data
60 $this->assertArrayHasKey( 'edit', $apiResult );
61 $this->assertArrayHasKey( 'result', $apiResult['edit'] );
62 $this->assertEquals( 'Success', $apiResult['edit']['result'] );
63
64 $this->assertArrayHasKey( 'new', $apiResult['edit'] );
65 $this->assertArrayNotHasKey( 'nochange', $apiResult['edit'] );
66
67 $this->assertArrayHasKey( 'pageid', $apiResult['edit'] );
68
69 // -- test existing page, no change ----------------------------
70 $data = $this->doApiRequestWithToken( array(
71 'action' => 'edit',
72 'title' => $name,
73 'text' => 'some text',
74 ) );
75
76 $this->assertEquals( 'Success', $data[0]['edit']['result'] );
77
78 $this->assertArrayNotHasKey( 'new', $data[0]['edit'] );
79 $this->assertArrayHasKey( 'nochange', $data[0]['edit'] );
80
81 // -- test existing page, with change --------------------------
82 $data = $this->doApiRequestWithToken( array(
83 'action' => 'edit',
84 'title' => $name,
85 'text' => 'different text'
86 ) );
87
88 $this->assertEquals( 'Success', $data[0]['edit']['result'] );
89
90 $this->assertArrayNotHasKey( 'new', $data[0]['edit'] );
91 $this->assertArrayNotHasKey( 'nochange', $data[0]['edit'] );
92
93 $this->assertArrayHasKey( 'oldrevid', $data[0]['edit'] );
94 $this->assertArrayHasKey( 'newrevid', $data[0]['edit'] );
95 $this->assertNotEquals(
96 $data[0]['edit']['newrevid'],
97 $data[0]['edit']['oldrevid'],
98 "revision id should change after edit"
99 );
100 }
101
102 public function testNonTextEdit() {
103 $name = 'Dummy:ApiEditPageTest_testNonTextEdit';
104 $data = serialize( 'some bla bla text' );
105
106 // -- test new page --------------------------------------------
107 $apiResult = $this->doApiRequestWithToken( array(
108 'action' => 'edit',
109 'title' => $name,
110 'text' => $data, ) );
111 $apiResult = $apiResult[0];
112
113 // Validate API result data
114 $this->assertArrayHasKey( 'edit', $apiResult );
115 $this->assertArrayHasKey( 'result', $apiResult['edit'] );
116 $this->assertEquals( 'Success', $apiResult['edit']['result'] );
117
118 $this->assertArrayHasKey( 'new', $apiResult['edit'] );
119 $this->assertArrayNotHasKey( 'nochange', $apiResult['edit'] );
120
121 $this->assertArrayHasKey( 'pageid', $apiResult['edit'] );
122
123 // validate resulting revision
124 $page = WikiPage::factory( Title::newFromText( $name ) );
125 $this->assertEquals( "testing", $page->getContentModel() );
126 $this->assertEquals( $data, $page->getContent()->serialize() );
127 }
128
129 /**
130 * @return array
131 */
132 public static function provideEditAppend() {
133 return array(
134 array( #0: append
135 'foo', 'append', 'bar', "foobar"
136 ),
137 array( #1: prepend
138 'foo', 'prepend', 'bar', "barfoo"
139 ),
140 array( #2: append to empty page
141 '', 'append', 'foo', "foo"
142 ),
143 array( #3: prepend to empty page
144 '', 'prepend', 'foo', "foo"
145 ),
146 array( #4: append to non-existing page
147 null, 'append', 'foo', "foo"
148 ),
149 array( #5: prepend to non-existing page
150 null, 'prepend', 'foo', "foo"
151 ),
152 );
153 }
154
155 /**
156 * @dataProvider provideEditAppend
157 */
158 public function testEditAppend( $text, $op, $append, $expected ) {
159 static $count = 0;
160 $count++;
161
162 // assume NS_HELP defaults to wikitext
163 $name = "Help:ApiEditPageTest_testEditAppend_$count";
164
165 // -- create page (or not) -----------------------------------------
166 if ( $text !== null ) {
167 if ( $text === '' ) {
168 // can't create an empty page, so create it with some content
169 $this->doApiRequestWithToken( array(
170 'action' => 'edit',
171 'title' => $name,
172 'text' => '(dummy)', ) );
173 }
174
175 list( $re ) = $this->doApiRequestWithToken( array(
176 'action' => 'edit',
177 'title' => $name,
178 'text' => $text, ) );
179
180 $this->assertEquals( 'Success', $re['edit']['result'] ); // sanity
181 }
182
183 // -- try append/prepend --------------------------------------------
184 list( $re ) = $this->doApiRequestWithToken( array(
185 'action' => 'edit',
186 'title' => $name,
187 $op . 'text' => $append, ) );
188
189 $this->assertEquals( 'Success', $re['edit']['result'] );
190
191 // -- validate -----------------------------------------------------
192 $page = new WikiPage( Title::newFromText( $name ) );
193 $content = $page->getContent();
194 $this->assertNotNull( $content, 'Page should have been created' );
195
196 $text = $content->getNativeData();
197
198 $this->assertEquals( $expected, $text );
199 }
200
201 /**
202 * Test editing of sections
203 */
204 public function testEditSection() {
205 $name = 'Help:ApiEditPageTest_testEditSection';
206 $page = WikiPage::factory( Title::newFromText( $name ) );
207 $text = "==section 1==\ncontent 1\n==section 2==\ncontent2";
208 // Preload the page with some text
209 $page->doEditContent( ContentHandler::makeContent( $text, $page->getTitle() ), 'summary' );
210
211 list( $re ) = $this->doApiRequestWithToken( array(
212 'action' => 'edit',
213 'title' => $name,
214 'section' => '1',
215 'text' => "==section 1==\nnew content 1",
216 ) );
217 $this->assertEquals( 'Success', $re['edit']['result'] );
218 $newtext = WikiPage::factory( Title::newFromText( $name ) )->getContent( Revision::RAW )->getNativeData();
219 $this->assertEquals( $newtext, "==section 1==\nnew content 1\n\n==section 2==\ncontent2" );
220
221 // Test that we raise a 'nosuchsection' error
222 try {
223 $this->doApiRequestWithToken( array(
224 'action' => 'edit',
225 'title' => $name,
226 'section' => '9999',
227 'text' => 'text',
228 ) );
229 $this->fail( "Should have raised a UsageException" );
230 } catch ( UsageException $e ) {
231 $this->assertEquals( $e->getCodeString(), 'nosuchsection' );
232 }
233 }
234
235 /**
236 * Test action=edit&section=new
237 * Run it twice so we test adding a new section on a
238 * page that doesn't exist (bug 52830) and one that
239 * does exist
240 */
241 public function testEditNewSection() {
242 $name = 'Help:ApiEditPageTest_testEditNewSection';
243
244 // Test on a page that does not already exist
245 $this->assertFalse( Title::newFromText( $name )->exists() );
246 list( $re ) = $this->doApiRequestWithToken( array(
247 'action' => 'edit',
248 'title' => $name,
249 'section' => 'new',
250 'text' => 'test',
251 'summary' => 'header',
252 ));
253
254 $this->assertEquals( 'Success', $re['edit']['result'] );
255 // Check the page text is correct
256 $text = WikiPage::factory( Title::newFromText( $name ) )->getContent( Revision::RAW )->getNativeData();
257 $this->assertEquals( $text, "== header ==\n\ntest" );
258
259 // Now on one that does
260 $this->assertTrue( Title::newFromText( $name )->exists() );
261 list( $re2 ) = $this->doApiRequestWithToken( array(
262 'action' => 'edit',
263 'title' => $name,
264 'section' => 'new',
265 'text' => 'test',
266 'summary' => 'header',
267 ));
268
269 $this->assertEquals( 'Success', $re2['edit']['result'] );
270 $text = WikiPage::factory( Title::newFromText( $name ) )->getContent( Revision::RAW )->getNativeData();
271 $this->assertEquals( $text, "== header ==\n\ntest\n\n== header ==\n\ntest" );
272 }
273
274 public function testEditConflict() {
275 static $count = 0;
276 $count++;
277
278 // assume NS_HELP defaults to wikitext
279 $name = "Help:ApiEditPageTest_testEditConflict_$count";
280 $title = Title::newFromText( $name );
281
282 $page = WikiPage::factory( $title );
283
284 // base edit
285 $page->doEditContent( new WikitextContent( "Foo" ),
286 "testing 1", EDIT_NEW, false, self::$users['sysop']->user );
287 $this->forceRevisionDate( $page, '20120101000000' );
288 $baseTime = $page->getRevision()->getTimestamp();
289
290 // conflicting edit
291 $page->doEditContent( new WikitextContent( "Foo bar" ),
292 "testing 2", EDIT_UPDATE, $page->getLatest(), self::$users['uploader']->user );
293 $this->forceRevisionDate( $page, '20120101020202' );
294
295 // try to save edit, expect conflict
296 try {
297 $this->doApiRequestWithToken( array(
298 'action' => 'edit',
299 'title' => $name,
300 'text' => 'nix bar!',
301 'basetimestamp' => $baseTime,
302 ), null, self::$users['sysop']->user );
303
304 $this->fail( 'edit conflict expected' );
305 } catch ( UsageException $ex ) {
306 $this->assertEquals( 'editconflict', $ex->getCodeString() );
307 }
308 }
309
310 public function testEditConflict_redirect() {
311 static $count = 0;
312 $count++;
313
314 // assume NS_HELP defaults to wikitext
315 $name = "Help:ApiEditPageTest_testEditConflict_redirect_$count";
316 $title = Title::newFromText( $name );
317 $page = WikiPage::factory( $title );
318
319 $rname = "Help:ApiEditPageTest_testEditConflict_redirect_r$count";
320 $rtitle = Title::newFromText( $rname );
321 $rpage = WikiPage::factory( $rtitle );
322
323 // base edit for content
324 $page->doEditContent( new WikitextContent( "Foo" ),
325 "testing 1", EDIT_NEW, false, self::$users['sysop']->user );
326 $this->forceRevisionDate( $page, '20120101000000' );
327 $baseTime = $page->getRevision()->getTimestamp();
328
329 // base edit for redirect
330 $rpage->doEditContent( new WikitextContent( "#REDIRECT [[$name]]" ),
331 "testing 1", EDIT_NEW, false, self::$users['sysop']->user );
332 $this->forceRevisionDate( $rpage, '20120101000000' );
333
334 // conflicting edit to redirect
335 $rpage->doEditContent( new WikitextContent( "#REDIRECT [[$name]]\n\n[[Category:Test]]" ),
336 "testing 2", EDIT_UPDATE, $page->getLatest(), self::$users['uploader']->user );
337 $this->forceRevisionDate( $rpage, '20120101020202' );
338
339 // try to save edit; should work, because we follow the redirect
340 list( $re, , ) = $this->doApiRequestWithToken( array(
341 'action' => 'edit',
342 'title' => $rname,
343 'text' => 'nix bar!',
344 'basetimestamp' => $baseTime,
345 'redirect' => true,
346 ), null, self::$users['sysop']->user );
347
348 $this->assertEquals( 'Success', $re['edit']['result'],
349 "no edit conflict expected when following redirect" );
350
351 // try again, without following the redirect. Should fail.
352 try {
353 $this->doApiRequestWithToken( array(
354 'action' => 'edit',
355 'title' => $rname,
356 'text' => 'nix bar!',
357 'basetimestamp' => $baseTime,
358 ), null, self::$users['sysop']->user );
359
360 $this->fail( 'edit conflict expected' );
361 } catch ( UsageException $ex ) {
362 $this->assertEquals( 'editconflict', $ex->getCodeString() );
363 }
364 }
365
366 public function testEditConflict_bug41990() {
367 static $count = 0;
368 $count++;
369
370 /*
371 * bug 41990: if the target page has a newer revision than the redirect, then editing the
372 * redirect while specifying 'redirect' and *not* specifying 'basetimestamp' erroneously
373 * caused an edit conflict to be detected.
374 */
375
376 // assume NS_HELP defaults to wikitext
377 $name = "Help:ApiEditPageTest_testEditConflict_redirect_bug41990_$count";
378 $title = Title::newFromText( $name );
379 $page = WikiPage::factory( $title );
380
381 $rname = "Help:ApiEditPageTest_testEditConflict_redirect_bug41990_r$count";
382 $rtitle = Title::newFromText( $rname );
383 $rpage = WikiPage::factory( $rtitle );
384
385 // base edit for content
386 $page->doEditContent( new WikitextContent( "Foo" ),
387 "testing 1", EDIT_NEW, false, self::$users['sysop']->user );
388 $this->forceRevisionDate( $page, '20120101000000' );
389
390 // base edit for redirect
391 $rpage->doEditContent( new WikitextContent( "#REDIRECT [[$name]]" ),
392 "testing 1", EDIT_NEW, false, self::$users['sysop']->user );
393 $this->forceRevisionDate( $rpage, '20120101000000' );
394 $baseTime = $rpage->getRevision()->getTimestamp();
395
396 // new edit to content
397 $page->doEditContent( new WikitextContent( "Foo bar" ),
398 "testing 2", EDIT_UPDATE, $page->getLatest(), self::$users['uploader']->user );
399 $this->forceRevisionDate( $rpage, '20120101020202' );
400
401 // try to save edit; should work, following the redirect.
402 list( $re, , ) = $this->doApiRequestWithToken( array(
403 'action' => 'edit',
404 'title' => $rname,
405 'text' => 'nix bar!',
406 'redirect' => true,
407 ), null, self::$users['sysop']->user );
408
409 $this->assertEquals( 'Success', $re['edit']['result'],
410 "no edit conflict expected here" );
411 }
412
413 /**
414 * @param WikiPage $page
415 * @param string|int $timestamp
416 */
417 protected function forceRevisionDate( WikiPage $page, $timestamp ) {
418 $dbw = wfGetDB( DB_MASTER );
419
420 $dbw->update( 'revision',
421 array( 'rev_timestamp' => $dbw->timestamp( $timestamp ) ),
422 array( 'rev_id' => $page->getLatest() ) );
423
424 $page->clear();
425 }
426 }