Split WikiPageTest into ContentHandler & NoContentHandler tests
[lhc/web/wiklou.git] / tests / phpunit / includes / page / WikiPageDbTestBase.php
1 <?php
2
3 abstract class WikiPageDbTestBase extends MediaWikiLangTestCase {
4
5 private $pagesToDelete;
6
7 public function __construct( $name = null, array $data = [], $dataName = '' ) {
8 parent::__construct( $name, $data, $dataName );
9
10 $this->tablesUsed = array_merge(
11 $this->tablesUsed,
12 [ 'page',
13 'revision',
14 'archive',
15 'ip_changes',
16 'text',
17
18 'recentchanges',
19 'logging',
20
21 'page_props',
22 'pagelinks',
23 'categorylinks',
24 'langlinks',
25 'externallinks',
26 'imagelinks',
27 'templatelinks',
28 'iwlinks' ] );
29 }
30
31 protected function setUp() {
32 parent::setUp();
33 $this->setMwGlobals( 'wgContentHandlerUseDB', $this->getContentHandlerUseDB() );
34 $this->pagesToDelete = [];
35 }
36
37 protected function tearDown() {
38 foreach ( $this->pagesToDelete as $p ) {
39 /* @var $p WikiPage */
40
41 try {
42 if ( $p->exists() ) {
43 $p->doDeleteArticle( "testing done." );
44 }
45 } catch ( MWException $ex ) {
46 // fail silently
47 }
48 }
49 parent::tearDown();
50 }
51
52 abstract protected function getContentHandlerUseDB();
53
54 /**
55 * @param Title|string $title
56 * @param string|null $model
57 * @return WikiPage
58 */
59 private function newPage( $title, $model = null ) {
60 if ( is_string( $title ) ) {
61 $ns = $this->getDefaultWikitextNS();
62 $title = Title::newFromText( $title, $ns );
63 }
64
65 $p = new WikiPage( $title );
66
67 $this->pagesToDelete[] = $p;
68
69 return $p;
70 }
71
72 /**
73 * @param string|Title|WikiPage $page
74 * @param string $text
75 * @param int $model
76 *
77 * @return WikiPage
78 */
79 protected function createPage( $page, $text, $model = null ) {
80 if ( is_string( $page ) || $page instanceof Title ) {
81 $page = $this->newPage( $page, $model );
82 }
83
84 $content = ContentHandler::makeContent( $text, $page->getTitle(), $model );
85 $page->doEditContent( $content, "testing", EDIT_NEW );
86
87 return $page;
88 }
89
90 /**
91 * @covers WikiPage::doEditContent
92 * @covers WikiPage::doModify
93 * @covers WikiPage::doCreate
94 * @covers WikiPage::doEditUpdates
95 */
96 public function testDoEditContent() {
97 $page = $this->newPage( __METHOD__ );
98 $title = $page->getTitle();
99
100 $content = ContentHandler::makeContent(
101 "[[Lorem ipsum]] dolor sit amet, consetetur sadipscing elitr, sed diam "
102 . " nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat.",
103 $title,
104 CONTENT_MODEL_WIKITEXT
105 );
106
107 $page->doEditContent( $content, "[[testing]] 1" );
108
109 $this->assertTrue( $title->getArticleID() > 0, "Title object should have new page id" );
110 $this->assertTrue( $page->getId() > 0, "WikiPage should have new page id" );
111 $this->assertTrue( $title->exists(), "Title object should indicate that the page now exists" );
112 $this->assertTrue( $page->exists(), "WikiPage object should indicate that the page now exists" );
113
114 $id = $page->getId();
115
116 # ------------------------
117 $dbr = wfGetDB( DB_REPLICA );
118 $res = $dbr->select( 'pagelinks', '*', [ 'pl_from' => $id ] );
119 $n = $res->numRows();
120 $res->free();
121
122 $this->assertEquals( 1, $n, 'pagelinks should contain one link from the page' );
123
124 # ------------------------
125 $page = new WikiPage( $title );
126
127 $retrieved = $page->getContent();
128 $this->assertTrue( $content->equals( $retrieved ), 'retrieved content doesn\'t equal original' );
129
130 # ------------------------
131 $content = ContentHandler::makeContent(
132 "At vero eos et accusam et justo duo [[dolores]] et ea rebum. "
133 . "Stet clita kasd [[gubergren]], no sea takimata sanctus est.",
134 $title,
135 CONTENT_MODEL_WIKITEXT
136 );
137
138 $page->doEditContent( $content, "testing 2" );
139
140 # ------------------------
141 $page = new WikiPage( $title );
142
143 $retrieved = $page->getContent();
144 $this->assertTrue( $content->equals( $retrieved ), 'retrieved content doesn\'t equal original' );
145
146 # ------------------------
147 $dbr = wfGetDB( DB_REPLICA );
148 $res = $dbr->select( 'pagelinks', '*', [ 'pl_from' => $id ] );
149 $n = $res->numRows();
150 $res->free();
151
152 $this->assertEquals( 2, $n, 'pagelinks should contain two links from the page' );
153 }
154
155 /**
156 * @covers WikiPage::doDeleteArticle
157 */
158 public function testDoDeleteArticle() {
159 $page = $this->createPage(
160 __METHOD__,
161 "[[original text]] foo",
162 CONTENT_MODEL_WIKITEXT
163 );
164 $id = $page->getId();
165
166 $page->doDeleteArticle( "testing deletion" );
167
168 $this->assertFalse(
169 $page->getTitle()->getArticleID() > 0,
170 "Title object should now have page id 0"
171 );
172 $this->assertFalse( $page->getId() > 0, "WikiPage should now have page id 0" );
173 $this->assertFalse(
174 $page->exists(),
175 "WikiPage::exists should return false after page was deleted"
176 );
177 $this->assertNull(
178 $page->getContent(),
179 "WikiPage::getContent should return null after page was deleted"
180 );
181
182 $t = Title::newFromText( $page->getTitle()->getPrefixedText() );
183 $this->assertFalse(
184 $t->exists(),
185 "Title::exists should return false after page was deleted"
186 );
187
188 // Run the job queue
189 JobQueueGroup::destroySingletons();
190 $jobs = new RunJobs;
191 $jobs->loadParamsAndArgs( null, [ 'quiet' => true ], null );
192 $jobs->execute();
193
194 # ------------------------
195 $dbr = wfGetDB( DB_REPLICA );
196 $res = $dbr->select( 'pagelinks', '*', [ 'pl_from' => $id ] );
197 $n = $res->numRows();
198 $res->free();
199
200 $this->assertEquals( 0, $n, 'pagelinks should contain no more links from the page' );
201 }
202
203 /**
204 * @covers WikiPage::doDeleteUpdates
205 */
206 public function testDoDeleteUpdates() {
207 $page = $this->createPage(
208 __METHOD__,
209 "[[original text]] foo",
210 CONTENT_MODEL_WIKITEXT
211 );
212 $id = $page->getId();
213
214 // Similar to MovePage logic
215 wfGetDB( DB_MASTER )->delete( 'page', [ 'page_id' => $id ], __METHOD__ );
216 $page->doDeleteUpdates( $id );
217
218 // Run the job queue
219 JobQueueGroup::destroySingletons();
220 $jobs = new RunJobs;
221 $jobs->loadParamsAndArgs( null, [ 'quiet' => true ], null );
222 $jobs->execute();
223
224 # ------------------------
225 $dbr = wfGetDB( DB_REPLICA );
226 $res = $dbr->select( 'pagelinks', '*', [ 'pl_from' => $id ] );
227 $n = $res->numRows();
228 $res->free();
229
230 $this->assertEquals( 0, $n, 'pagelinks should contain no more links from the page' );
231 }
232
233 /**
234 * @covers WikiPage::getRevision
235 */
236 public function testGetRevision() {
237 $page = $this->newPage( __METHOD__ );
238
239 $rev = $page->getRevision();
240 $this->assertNull( $rev );
241
242 # -----------------
243 $this->createPage( $page, "some text", CONTENT_MODEL_WIKITEXT );
244
245 $rev = $page->getRevision();
246
247 $this->assertEquals( $page->getLatest(), $rev->getId() );
248 $this->assertEquals( "some text", $rev->getContent()->getNativeData() );
249 }
250
251 /**
252 * @covers WikiPage::getContent
253 */
254 public function testGetContent() {
255 $page = $this->newPage( __METHOD__ );
256
257 $content = $page->getContent();
258 $this->assertNull( $content );
259
260 # -----------------
261 $this->createPage( $page, "some text", CONTENT_MODEL_WIKITEXT );
262
263 $content = $page->getContent();
264 $this->assertEquals( "some text", $content->getNativeData() );
265 }
266
267 /**
268 * @covers WikiPage::exists
269 */
270 public function testExists() {
271 $page = $this->newPage( __METHOD__ );
272 $this->assertFalse( $page->exists() );
273
274 # -----------------
275 $this->createPage( $page, "some text", CONTENT_MODEL_WIKITEXT );
276 $this->assertTrue( $page->exists() );
277
278 $page = new WikiPage( $page->getTitle() );
279 $this->assertTrue( $page->exists() );
280
281 # -----------------
282 $page->doDeleteArticle( "done testing" );
283 $this->assertFalse( $page->exists() );
284
285 $page = new WikiPage( $page->getTitle() );
286 $this->assertFalse( $page->exists() );
287 }
288
289 public function provideHasViewableContent() {
290 return [
291 [ 'WikiPageTest_testHasViewableContent', false, true ],
292 [ 'Special:WikiPageTest_testHasViewableContent', false ],
293 [ 'MediaWiki:WikiPageTest_testHasViewableContent', false ],
294 [ 'Special:Userlogin', true ],
295 [ 'MediaWiki:help', true ],
296 ];
297 }
298
299 /**
300 * @dataProvider provideHasViewableContent
301 * @covers WikiPage::hasViewableContent
302 */
303 public function testHasViewableContent( $title, $viewable, $create = false ) {
304 $page = $this->newPage( $title );
305 $this->assertEquals( $viewable, $page->hasViewableContent() );
306
307 if ( $create ) {
308 $this->createPage( $page, "some text", CONTENT_MODEL_WIKITEXT );
309 $this->assertTrue( $page->hasViewableContent() );
310
311 $page = new WikiPage( $page->getTitle() );
312 $this->assertTrue( $page->hasViewableContent() );
313 }
314 }
315
316 public function provideGetRedirectTarget() {
317 return [
318 [ 'WikiPageTest_testGetRedirectTarget_1', CONTENT_MODEL_WIKITEXT, "hello world", null ],
319 [
320 'WikiPageTest_testGetRedirectTarget_2',
321 CONTENT_MODEL_WIKITEXT,
322 "#REDIRECT [[hello world]]",
323 "Hello world"
324 ],
325 ];
326 }
327
328 /**
329 * @dataProvider provideGetRedirectTarget
330 * @covers WikiPage::getRedirectTarget
331 */
332 public function testGetRedirectTarget( $title, $model, $text, $target ) {
333 $this->setMwGlobals( [
334 'wgCapitalLinks' => true,
335 ] );
336
337 $page = $this->createPage( $title, $text, $model );
338
339 # sanity check, because this test seems to fail for no reason for some people.
340 $c = $page->getContent();
341 $this->assertEquals( 'WikitextContent', get_class( $c ) );
342
343 # now, test the actual redirect
344 $t = $page->getRedirectTarget();
345 $this->assertEquals( $target, is_null( $t ) ? null : $t->getPrefixedText() );
346 }
347
348 /**
349 * @dataProvider provideGetRedirectTarget
350 * @covers WikiPage::isRedirect
351 */
352 public function testIsRedirect( $title, $model, $text, $target ) {
353 $page = $this->createPage( $title, $text, $model );
354 $this->assertEquals( !is_null( $target ), $page->isRedirect() );
355 }
356
357 public function provideIsCountable() {
358 return [
359
360 // any
361 [ 'WikiPageTest_testIsCountable',
362 CONTENT_MODEL_WIKITEXT,
363 '',
364 'any',
365 true
366 ],
367 [ 'WikiPageTest_testIsCountable',
368 CONTENT_MODEL_WIKITEXT,
369 'Foo',
370 'any',
371 true
372 ],
373
374 // comma
375 [ 'WikiPageTest_testIsCountable',
376 CONTENT_MODEL_WIKITEXT,
377 'Foo',
378 'comma',
379 false
380 ],
381 [ 'WikiPageTest_testIsCountable',
382 CONTENT_MODEL_WIKITEXT,
383 'Foo, bar',
384 'comma',
385 true
386 ],
387
388 // link
389 [ 'WikiPageTest_testIsCountable',
390 CONTENT_MODEL_WIKITEXT,
391 'Foo',
392 'link',
393 false
394 ],
395 [ 'WikiPageTest_testIsCountable',
396 CONTENT_MODEL_WIKITEXT,
397 'Foo [[bar]]',
398 'link',
399 true
400 ],
401
402 // redirects
403 [ 'WikiPageTest_testIsCountable',
404 CONTENT_MODEL_WIKITEXT,
405 '#REDIRECT [[bar]]',
406 'any',
407 false
408 ],
409 [ 'WikiPageTest_testIsCountable',
410 CONTENT_MODEL_WIKITEXT,
411 '#REDIRECT [[bar]]',
412 'comma',
413 false
414 ],
415 [ 'WikiPageTest_testIsCountable',
416 CONTENT_MODEL_WIKITEXT,
417 '#REDIRECT [[bar]]',
418 'link',
419 false
420 ],
421
422 // not a content namespace
423 [ 'Talk:WikiPageTest_testIsCountable',
424 CONTENT_MODEL_WIKITEXT,
425 'Foo',
426 'any',
427 false
428 ],
429 [ 'Talk:WikiPageTest_testIsCountable',
430 CONTENT_MODEL_WIKITEXT,
431 'Foo, bar',
432 'comma',
433 false
434 ],
435 [ 'Talk:WikiPageTest_testIsCountable',
436 CONTENT_MODEL_WIKITEXT,
437 'Foo [[bar]]',
438 'link',
439 false
440 ],
441
442 // not a content namespace, different model
443 [ 'MediaWiki:WikiPageTest_testIsCountable.js',
444 null,
445 'Foo',
446 'any',
447 false
448 ],
449 [ 'MediaWiki:WikiPageTest_testIsCountable.js',
450 null,
451 'Foo, bar',
452 'comma',
453 false
454 ],
455 [ 'MediaWiki:WikiPageTest_testIsCountable.js',
456 null,
457 'Foo [[bar]]',
458 'link',
459 false
460 ],
461 ];
462 }
463
464 /**
465 * @dataProvider provideIsCountable
466 * @covers WikiPage::isCountable
467 */
468 public function testIsCountable( $title, $model, $text, $mode, $expected ) {
469 global $wgContentHandlerUseDB;
470
471 $this->setMwGlobals( 'wgArticleCountMethod', $mode );
472
473 $title = Title::newFromText( $title );
474
475 if ( !$wgContentHandlerUseDB
476 && $model
477 && ContentHandler::getDefaultModelFor( $title ) != $model
478 ) {
479 $this->markTestSkipped( "Can not use non-default content model $model for "
480 . $title->getPrefixedDBkey() . " with \$wgContentHandlerUseDB disabled." );
481 }
482
483 $page = $this->createPage( $title, $text, $model );
484
485 $editInfo = $page->prepareContentForEdit( $page->getContent() );
486
487 $v = $page->isCountable();
488 $w = $page->isCountable( $editInfo );
489
490 $this->assertEquals(
491 $expected,
492 $v,
493 "isCountable( null ) returned unexpected value " . var_export( $v, true )
494 . " instead of " . var_export( $expected, true )
495 . " in mode `$mode` for text \"$text\""
496 );
497
498 $this->assertEquals(
499 $expected,
500 $w,
501 "isCountable( \$editInfo ) returned unexpected value " . var_export( $v, true )
502 . " instead of " . var_export( $expected, true )
503 . " in mode `$mode` for text \"$text\""
504 );
505 }
506
507 public function provideGetParserOutput() {
508 return [
509 [
510 CONTENT_MODEL_WIKITEXT,
511 "hello ''world''\n",
512 "<div class=\"mw-parser-output\"><p>hello <i>world</i></p></div>"
513 ],
514 // @todo more...?
515 ];
516 }
517
518 /**
519 * @dataProvider provideGetParserOutput
520 * @covers WikiPage::getParserOutput
521 */
522 public function testGetParserOutput( $model, $text, $expectedHtml ) {
523 $page = $this->createPage( __METHOD__, $text, $model );
524
525 $opt = $page->makeParserOptions( 'canonical' );
526 $po = $page->getParserOutput( $opt );
527 $text = $po->getText();
528
529 $text = trim( preg_replace( '/<!--.*?-->/sm', '', $text ) ); # strip injected comments
530 $text = preg_replace( '!\s*(</p>|</div>)!sm', '\1', $text ); # don't let tidy confuse us
531
532 $this->assertEquals( $expectedHtml, $text );
533
534 return $po;
535 }
536
537 /**
538 * @covers WikiPage::getParserOutput
539 */
540 public function testGetParserOutput_nonexisting() {
541 $page = new WikiPage( Title::newFromText( __METHOD__ ) );
542
543 $opt = new ParserOptions();
544 $po = $page->getParserOutput( $opt );
545
546 $this->assertFalse( $po, "getParserOutput() shall return false for non-existing pages." );
547 }
548
549 /**
550 * @covers WikiPage::getParserOutput
551 */
552 public function testGetParserOutput_badrev() {
553 $page = $this->createPage( __METHOD__, 'dummy', CONTENT_MODEL_WIKITEXT );
554
555 $opt = new ParserOptions();
556 $po = $page->getParserOutput( $opt, $page->getLatest() + 1234 );
557
558 // @todo would be neat to also test deleted revision
559
560 $this->assertFalse( $po, "getParserOutput() shall return false for non-existing revisions." );
561 }
562
563 public static $sections =
564
565 "Intro
566
567 == stuff ==
568 hello world
569
570 == test ==
571 just a test
572
573 == foo ==
574 more stuff
575 ";
576
577 public function dataReplaceSection() {
578 // NOTE: assume the Help namespace to contain wikitext
579 return [
580 [ 'Help:WikiPageTest_testReplaceSection',
581 CONTENT_MODEL_WIKITEXT,
582 self::$sections,
583 "0",
584 "No more",
585 null,
586 trim( preg_replace( '/^Intro/sm', 'No more', self::$sections ) )
587 ],
588 [ 'Help:WikiPageTest_testReplaceSection',
589 CONTENT_MODEL_WIKITEXT,
590 self::$sections,
591 "",
592 "No more",
593 null,
594 "No more"
595 ],
596 [ 'Help:WikiPageTest_testReplaceSection',
597 CONTENT_MODEL_WIKITEXT,
598 self::$sections,
599 "2",
600 "== TEST ==\nmore fun",
601 null,
602 trim( preg_replace( '/^== test ==.*== foo ==/sm',
603 "== TEST ==\nmore fun\n\n== foo ==",
604 self::$sections ) )
605 ],
606 [ 'Help:WikiPageTest_testReplaceSection',
607 CONTENT_MODEL_WIKITEXT,
608 self::$sections,
609 "8",
610 "No more",
611 null,
612 trim( self::$sections )
613 ],
614 [ 'Help:WikiPageTest_testReplaceSection',
615 CONTENT_MODEL_WIKITEXT,
616 self::$sections,
617 "new",
618 "No more",
619 "New",
620 trim( self::$sections ) . "\n\n== New ==\n\nNo more"
621 ],
622 ];
623 }
624
625 /**
626 * @dataProvider dataReplaceSection
627 * @covers WikiPage::replaceSectionContent
628 */
629 public function testReplaceSectionContent( $title, $model, $text, $section,
630 $with, $sectionTitle, $expected
631 ) {
632 $page = $this->createPage( $title, $text, $model );
633
634 $content = ContentHandler::makeContent( $with, $page->getTitle(), $page->getContentModel() );
635 $c = $page->replaceSectionContent( $section, $content, $sectionTitle );
636
637 $this->assertEquals( $expected, is_null( $c ) ? null : trim( $c->getNativeData() ) );
638 }
639
640 /**
641 * @dataProvider dataReplaceSection
642 * @covers WikiPage::replaceSectionAtRev
643 */
644 public function testReplaceSectionAtRev( $title, $model, $text, $section,
645 $with, $sectionTitle, $expected
646 ) {
647 $page = $this->createPage( $title, $text, $model );
648 $baseRevId = $page->getLatest();
649
650 $content = ContentHandler::makeContent( $with, $page->getTitle(), $page->getContentModel() );
651 $c = $page->replaceSectionAtRev( $section, $content, $sectionTitle, $baseRevId );
652
653 $this->assertEquals( $expected, is_null( $c ) ? null : trim( $c->getNativeData() ) );
654 }
655
656 /**
657 * @covers WikiPage::getOldestRevision
658 */
659 public function testGetOldestRevision() {
660 $page = $this->newPage( __METHOD__ );
661 $page->doEditContent(
662 new WikitextContent( 'one' ),
663 "first edit",
664 EDIT_NEW
665 );
666 $rev1 = $page->getRevision();
667
668 $page = new WikiPage( $page->getTitle() );
669 $page->doEditContent(
670 new WikitextContent( 'two' ),
671 "second edit",
672 EDIT_UPDATE
673 );
674
675 $page = new WikiPage( $page->getTitle() );
676 $page->doEditContent(
677 new WikitextContent( 'three' ),
678 "third edit",
679 EDIT_UPDATE
680 );
681
682 // sanity check
683 $this->assertNotEquals(
684 $rev1->getId(),
685 $page->getRevision()->getId(),
686 '$page->getRevision()->getId()'
687 );
688
689 // actual test
690 $this->assertEquals(
691 $rev1->getId(),
692 $page->getOldestRevision()->getId(),
693 '$page->getOldestRevision()->getId()'
694 );
695 }
696
697 /**
698 * @todo FIXME: this is a better rollback test than the one below, but it
699 * keeps failing in jenkins for some reason.
700 */
701 public function broken_testDoRollback() {
702 $admin = $this->getTestSysop()->getUser();
703
704 $text = "one";
705 $page = $this->newPage( __METHOD__ );
706 $page->doEditContent( ContentHandler::makeContent( $text, $page->getTitle() ),
707 "section one", EDIT_NEW, false, $admin );
708
709 $user1 = $this->getTestUser()->getUser();
710 $text .= "\n\ntwo";
711 $page = new WikiPage( $page->getTitle() );
712 $page->doEditContent( ContentHandler::makeContent( $text, $page->getTitle() ),
713 "adding section two", 0, false, $user1 );
714
715 $user2 = $this->getTestUser()->getUser();
716 $text .= "\n\nthree";
717 $page = new WikiPage( $page->getTitle() );
718 $page->doEditContent( ContentHandler::makeContent( $text, $page->getTitle() ),
719 "adding section three", 0, false, $user2 );
720
721 # we are having issues with doRollback spuriously failing. Apparently
722 # the last revision somehow goes missing or not committed under some
723 # circumstances. So, make sure the last revision has the right user name.
724 $dbr = wfGetDB( DB_REPLICA );
725 $this->assertEquals( 3, Revision::countByPageId( $dbr, $page->getId() ) );
726
727 $page = new WikiPage( $page->getTitle() );
728 $rev3 = $page->getRevision();
729 $this->assertEquals( '127.0.2.13', $rev3->getUserText() );
730
731 $rev2 = $rev3->getPrevious();
732 $this->assertEquals( '127.0.1.11', $rev2->getUserText() );
733
734 $rev1 = $rev2->getPrevious();
735 $this->assertEquals( 'Admin', $rev1->getUserText() );
736
737 # now, try the actual rollback
738 $token = $admin->getEditToken(
739 [ $page->getTitle()->getPrefixedText(), $user2->getName() ],
740 null
741 );
742 $errors = $page->doRollback(
743 $user2->getName(),
744 "testing revert",
745 $token,
746 false,
747 $details,
748 $admin
749 );
750
751 if ( $errors ) {
752 $this->fail( "Rollback failed:\n" . print_r( $errors, true )
753 . ";\n" . print_r( $details, true ) );
754 }
755
756 $page = new WikiPage( $page->getTitle() );
757 $this->assertEquals( $rev2->getSha1(), $page->getRevision()->getSha1(),
758 "rollback did not revert to the correct revision" );
759 $this->assertEquals( "one\n\ntwo", $page->getContent()->getNativeData() );
760 }
761
762 /**
763 * @todo FIXME: the above rollback test is better, but it keeps failing in jenkins for some reason.
764 * @covers WikiPage::doRollback
765 */
766 public function testDoRollback() {
767 $admin = $this->getTestSysop()->getUser();
768
769 $text = "one";
770 $page = $this->newPage( __METHOD__ );
771 $page->doEditContent(
772 ContentHandler::makeContent( $text, $page->getTitle(), CONTENT_MODEL_WIKITEXT ),
773 "section one",
774 EDIT_NEW,
775 false,
776 $admin
777 );
778 $rev1 = $page->getRevision();
779
780 $user1 = $this->getTestUser()->getUser();
781 $text .= "\n\ntwo";
782 $page = new WikiPage( $page->getTitle() );
783 $page->doEditContent(
784 ContentHandler::makeContent( $text, $page->getTitle(), CONTENT_MODEL_WIKITEXT ),
785 "adding section two",
786 0,
787 false,
788 $user1
789 );
790
791 # now, try the rollback
792 $token = $admin->getEditToken( 'rollback' );
793 $errors = $page->doRollback(
794 $user1->getName(),
795 "testing revert",
796 $token,
797 false,
798 $details,
799 $admin
800 );
801
802 if ( $errors ) {
803 $this->fail( "Rollback failed:\n" . print_r( $errors, true )
804 . ";\n" . print_r( $details, true ) );
805 }
806
807 $page = new WikiPage( $page->getTitle() );
808 $this->assertEquals( $rev1->getSha1(), $page->getRevision()->getSha1(),
809 "rollback did not revert to the correct revision" );
810 $this->assertEquals( "one", $page->getContent()->getNativeData() );
811 }
812
813 /**
814 * @covers WikiPage::doRollback
815 */
816 public function testDoRollbackFailureSameContent() {
817 $admin = $this->getTestSysop()->getUser();
818
819 $text = "one";
820 $page = $this->newPage( __METHOD__ );
821 $page->doEditContent(
822 ContentHandler::makeContent( $text, $page->getTitle(), CONTENT_MODEL_WIKITEXT ),
823 "section one",
824 EDIT_NEW,
825 false,
826 $admin
827 );
828 $rev1 = $page->getRevision();
829
830 $user1 = $this->getTestUser( [ 'sysop' ] )->getUser();
831 $text .= "\n\ntwo";
832 $page = new WikiPage( $page->getTitle() );
833 $page->doEditContent(
834 ContentHandler::makeContent( $text, $page->getTitle(), CONTENT_MODEL_WIKITEXT ),
835 "adding section two",
836 0,
837 false,
838 $user1
839 );
840
841 # now, do a the rollback from the same user was doing the edit before
842 $resultDetails = [];
843 $token = $user1->getEditToken( 'rollback' );
844 $errors = $page->doRollback(
845 $user1->getName(),
846 "testing revert same user",
847 $token,
848 false,
849 $resultDetails,
850 $admin
851 );
852
853 $this->assertEquals( [], $errors, "Rollback failed same user" );
854
855 # now, try the rollback
856 $resultDetails = [];
857 $token = $admin->getEditToken( 'rollback' );
858 $errors = $page->doRollback(
859 $user1->getName(),
860 "testing revert",
861 $token,
862 false,
863 $resultDetails,
864 $admin
865 );
866
867 $this->assertEquals(
868 [
869 [
870 'alreadyrolled',
871 __METHOD__,
872 $user1->getName(),
873 $admin->getName(),
874 ],
875 ],
876 $errors,
877 "Rollback not failed"
878 );
879
880 $page = new WikiPage( $page->getTitle() );
881 $this->assertEquals( $rev1->getSha1(), $page->getRevision()->getSha1(),
882 "rollback did not revert to the correct revision" );
883 $this->assertEquals( "one", $page->getContent()->getNativeData() );
884 }
885
886 /**
887 * Tests tagging for edits that do rollback action
888 * @covers WikiPage::doRollback
889 */
890 public function testDoRollbackTagging() {
891 if ( !in_array( 'mw-rollback', ChangeTags::getSoftwareTags() ) ) {
892 $this->markTestSkipped( 'Rollback tag deactivated, skipped the test.' );
893 }
894
895 $admin = new User();
896 $admin->setName( 'Administrator' );
897 $admin->addToDatabase();
898
899 $text = 'First line';
900 $page = $this->newPage( 'WikiPageTest_testDoRollbackTagging' );
901 $page->doEditContent(
902 ContentHandler::makeContent( $text, $page->getTitle(), CONTENT_MODEL_WIKITEXT ),
903 'Added first line',
904 EDIT_NEW,
905 false,
906 $admin
907 );
908
909 $secondUser = new User();
910 $secondUser->setName( '92.65.217.32' );
911 $text .= '\n\nSecond line';
912 $page = new WikiPage( $page->getTitle() );
913 $page->doEditContent(
914 ContentHandler::makeContent( $text, $page->getTitle(), CONTENT_MODEL_WIKITEXT ),
915 'Adding second line',
916 0,
917 false,
918 $secondUser
919 );
920
921 // Now, try the rollback
922 $admin->addGroup( 'sysop' ); // Make the test user a sysop
923 $token = $admin->getEditToken( 'rollback' );
924 $errors = $page->doRollback(
925 $secondUser->getName(),
926 'testing rollback',
927 $token,
928 false,
929 $resultDetails,
930 $admin
931 );
932
933 // If doRollback completed without errors
934 if ( $errors === [] ) {
935 $tags = $resultDetails[ 'tags' ];
936 $this->assertContains( 'mw-rollback', $tags );
937 }
938 }
939
940 public function provideGetAutoDeleteReason() {
941 return [
942 [
943 [],
944 false,
945 false
946 ],
947
948 [
949 [
950 [ "first edit", null ],
951 ],
952 "/first edit.*only contributor/",
953 false
954 ],
955
956 [
957 [
958 [ "first edit", null ],
959 [ "second edit", null ],
960 ],
961 "/second edit.*only contributor/",
962 true
963 ],
964
965 [
966 [
967 [ "first edit", "127.0.2.22" ],
968 [ "second edit", "127.0.3.33" ],
969 ],
970 "/second edit/",
971 true
972 ],
973
974 [
975 [
976 [
977 "first edit: "
978 . "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam "
979 . " nonumy eirmod tempor invidunt ut labore et dolore magna "
980 . "aliquyam erat, sed diam voluptua. At vero eos et accusam "
981 . "et justo duo dolores et ea rebum. Stet clita kasd gubergren, "
982 . "no sea takimata sanctus est Lorem ipsum dolor sit amet.'",
983 null
984 ],
985 ],
986 '/first edit:.*\.\.\."/',
987 false
988 ],
989
990 [
991 [
992 [ "first edit", "127.0.2.22" ],
993 [ "", "127.0.3.33" ],
994 ],
995 "/before blanking.*first edit/",
996 true
997 ],
998
999 ];
1000 }
1001
1002 /**
1003 * @dataProvider provideGetAutoDeleteReason
1004 * @covers WikiPage::getAutoDeleteReason
1005 */
1006 public function testGetAutoDeleteReason( $edits, $expectedResult, $expectedHistory ) {
1007 global $wgUser;
1008
1009 // NOTE: assume Help namespace to contain wikitext
1010 $page = $this->newPage( "Help:WikiPageTest_testGetAutoDeleteReason" );
1011
1012 $c = 1;
1013
1014 foreach ( $edits as $edit ) {
1015 $user = new User();
1016
1017 if ( !empty( $edit[1] ) ) {
1018 $user->setName( $edit[1] );
1019 } else {
1020 $user = $wgUser;
1021 }
1022
1023 $content = ContentHandler::makeContent( $edit[0], $page->getTitle(), $page->getContentModel() );
1024
1025 $page->doEditContent( $content, "test edit $c", $c < 2 ? EDIT_NEW : 0, false, $user );
1026
1027 $c += 1;
1028 }
1029
1030 $reason = $page->getAutoDeleteReason( $hasHistory );
1031
1032 if ( is_bool( $expectedResult ) || is_null( $expectedResult ) ) {
1033 $this->assertEquals( $expectedResult, $reason );
1034 } else {
1035 $this->assertTrue( (bool)preg_match( $expectedResult, $reason ),
1036 "Autosummary didn't match expected pattern $expectedResult: $reason" );
1037 }
1038
1039 $this->assertEquals( $expectedHistory, $hasHistory,
1040 "expected \$hasHistory to be " . var_export( $expectedHistory, true ) );
1041
1042 $page->doDeleteArticle( "done" );
1043 }
1044
1045 public function providePreSaveTransform() {
1046 return [
1047 [ 'hello this is ~~~',
1048 "hello this is [[Special:Contributions/127.0.0.1|127.0.0.1]]",
1049 ],
1050 [ 'hello \'\'this\'\' is <nowiki>~~~</nowiki>',
1051 'hello \'\'this\'\' is <nowiki>~~~</nowiki>',
1052 ],
1053 ];
1054 }
1055
1056 /**
1057 * @covers WikiPage::factory
1058 */
1059 public function testWikiPageFactory() {
1060 $title = Title::makeTitle( NS_FILE, 'Someimage.png' );
1061 $page = WikiPage::factory( $title );
1062 $this->assertEquals( 'WikiFilePage', get_class( $page ) );
1063
1064 $title = Title::makeTitle( NS_CATEGORY, 'SomeCategory' );
1065 $page = WikiPage::factory( $title );
1066 $this->assertEquals( 'WikiCategoryPage', get_class( $page ) );
1067
1068 $title = Title::makeTitle( NS_MAIN, 'SomePage' );
1069 $page = WikiPage::factory( $title );
1070 $this->assertEquals( 'WikiPage', get_class( $page ) );
1071 }
1072
1073 /**
1074 * @dataProvider provideCommentMigrationOnDeletion
1075 *
1076 * @param int $writeStage
1077 * @param int $readStage
1078 */
1079 public function testCommentMigrationOnDeletion( $writeStage, $readStage ) {
1080 $this->setMwGlobals( 'wgCommentTableSchemaMigrationStage', $writeStage );
1081 $dbr = wfGetDB( DB_REPLICA );
1082
1083 $page = $this->createPage(
1084 __METHOD__,
1085 "foo",
1086 CONTENT_MODEL_WIKITEXT
1087 );
1088 $revid = $page->getLatest();
1089 if ( $writeStage > MIGRATION_OLD ) {
1090 $comment_id = $dbr->selectField(
1091 'revision_comment_temp',
1092 'revcomment_comment_id',
1093 [ 'revcomment_rev' => $revid ],
1094 __METHOD__
1095 );
1096 }
1097
1098 $this->setMwGlobals( 'wgCommentTableSchemaMigrationStage', $readStage );
1099
1100 $page->doDeleteArticle( "testing deletion" );
1101
1102 if ( $readStage > MIGRATION_OLD ) {
1103 // Didn't leave behind any 'revision_comment_temp' rows
1104 $n = $dbr->selectField(
1105 'revision_comment_temp', 'COUNT(*)', [ 'revcomment_rev' => $revid ], __METHOD__
1106 );
1107 $this->assertEquals( 0, $n, 'no entry in revision_comment_temp after deletion' );
1108
1109 // Copied or upgraded the comment_id, as applicable
1110 $ar_comment_id = $dbr->selectField(
1111 'archive',
1112 'ar_comment_id',
1113 [ 'ar_rev_id' => $revid ],
1114 __METHOD__
1115 );
1116 if ( $writeStage > MIGRATION_OLD ) {
1117 $this->assertSame( $comment_id, $ar_comment_id );
1118 } else {
1119 $this->assertNotEquals( 0, $ar_comment_id );
1120 }
1121 }
1122
1123 // Copied rev_comment, if applicable
1124 if ( $readStage <= MIGRATION_WRITE_BOTH && $writeStage <= MIGRATION_WRITE_BOTH ) {
1125 $ar_comment = $dbr->selectField(
1126 'archive',
1127 'ar_comment',
1128 [ 'ar_rev_id' => $revid ],
1129 __METHOD__
1130 );
1131 $this->assertSame( 'testing', $ar_comment );
1132 }
1133 }
1134
1135 public function provideCommentMigrationOnDeletion() {
1136 return [
1137 [ MIGRATION_OLD, MIGRATION_OLD ],
1138 [ MIGRATION_OLD, MIGRATION_WRITE_BOTH ],
1139 [ MIGRATION_OLD, MIGRATION_WRITE_NEW ],
1140 [ MIGRATION_WRITE_BOTH, MIGRATION_OLD ],
1141 [ MIGRATION_WRITE_BOTH, MIGRATION_WRITE_BOTH ],
1142 [ MIGRATION_WRITE_BOTH, MIGRATION_WRITE_NEW ],
1143 [ MIGRATION_WRITE_BOTH, MIGRATION_NEW ],
1144 [ MIGRATION_WRITE_NEW, MIGRATION_WRITE_BOTH ],
1145 [ MIGRATION_WRITE_NEW, MIGRATION_WRITE_NEW ],
1146 [ MIGRATION_WRITE_NEW, MIGRATION_NEW ],
1147 [ MIGRATION_NEW, MIGRATION_WRITE_BOTH ],
1148 [ MIGRATION_NEW, MIGRATION_WRITE_NEW ],
1149 [ MIGRATION_NEW, MIGRATION_NEW ],
1150 ];
1151 }
1152
1153 }