Merge "Fix 'Tags' padding to keep it farther from the edge and document the source...
[lhc/web/wiklou.git] / tests / phpunit / includes / Storage / DerivedPageDataUpdaterTest.php
1 <?php
2
3 namespace MediaWiki\Tests\Storage;
4
5 use CommentStoreComment;
6 use Content;
7 use LinksUpdate;
8 use MediaWiki\MediaWikiServices;
9 use MediaWiki\Storage\DerivedPageDataUpdater;
10 use MediaWiki\Storage\MutableRevisionRecord;
11 use MediaWiki\Storage\MutableRevisionSlots;
12 use MediaWiki\Storage\RevisionRecord;
13 use MediaWiki\Storage\RevisionSlotsUpdate;
14 use MediaWiki\Storage\SlotRecord;
15 use MediaWikiTestCase;
16 use Title;
17 use User;
18 use Wikimedia\TestingAccessWrapper;
19 use WikiPage;
20 use WikitextContent;
21
22 /**
23 * @group Database
24 *
25 * @covers MediaWiki\Storage\DerivedPageDataUpdater
26 */
27 class DerivedPageDataUpdaterTest extends MediaWikiTestCase {
28
29 /**
30 * @param string $title
31 *
32 * @return Title
33 */
34 private function getTitle( $title ) {
35 return Title::makeTitleSafe( $this->getDefaultWikitextNS(), $title );
36 }
37
38 /**
39 * @param string|Title $title
40 *
41 * @return WikiPage
42 */
43 private function getPage( $title ) {
44 $title = ( $title instanceof Title ) ? $title : $this->getTitle( $title );
45
46 return WikiPage::factory( $title );
47 }
48
49 /**
50 * @param string|Title|WikiPage $page
51 *
52 * @return DerivedPageDataUpdater
53 */
54 private function getDerivedPageDataUpdater( $page, RevisionRecord $rec = null ) {
55 if ( is_string( $page ) || $page instanceof Title ) {
56 $page = $this->getPage( $page );
57 }
58
59 $page = TestingAccessWrapper::newFromObject( $page );
60 return $page->getDerivedDataUpdater( null, $rec );
61 }
62
63 /**
64 * Creates a revision in the database.
65 *
66 * @param WikiPage $page
67 * @param $summary
68 * @param null|string|Content $content
69 *
70 * @return RevisionRecord|null
71 */
72 private function createRevision( WikiPage $page, $summary, $content = null ) {
73 $user = $this->getTestUser()->getUser();
74 $comment = CommentStoreComment::newUnsavedComment( $summary );
75
76 if ( !$content instanceof Content ) {
77 $content = new WikitextContent( $content === null ? $summary : $content );
78 }
79
80 $this->getDerivedPageDataUpdater( $page ); // flush cached instance before.
81
82 $updater = $page->newPageUpdater( $user );
83 $updater->setContent( 'main', $content );
84 $rev = $updater->saveRevision( $comment );
85
86 $this->getDerivedPageDataUpdater( $page ); // flush cached instance after.
87 return $rev;
88 }
89
90 // TODO: test setArticleCountMethod() and isCountable();
91 // TODO: test isRedirect() and wasRedirect()
92
93 /**
94 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::getCanonicalParserOptions()
95 */
96 public function testGetCanonicalParserOptions() {
97 global $wgContLang;
98
99 $user = $this->getTestUser()->getUser();
100 $page = $this->getPage( __METHOD__ );
101
102 $parentRev = $this->createRevision( $page, 'first' );
103
104 $mainContent = new WikitextContent( 'Lorem ipsum' );
105
106 $update = new RevisionSlotsUpdate();
107 $update->modifyContent( 'main', $mainContent );
108 $updater = $this->getDerivedPageDataUpdater( $page );
109 $updater->prepareContent( $user, $update, false );
110
111 $options1 = $updater->getCanonicalParserOptions();
112 $this->assertSame( $wgContLang, $options1->getUserLangObj() );
113
114 $speculativeId = call_user_func( $options1->getSpeculativeRevIdCallback(), $page->getTitle() );
115 $this->assertSame( $parentRev->getId() + 1, $speculativeId );
116
117 $rev = $this->makeRevision(
118 $page->getTitle(),
119 $update,
120 $user,
121 $parentRev->getId() + 7,
122 $parentRev->getId()
123 );
124 $updater->prepareUpdate( $rev );
125
126 $options2 = $updater->getCanonicalParserOptions();
127 $this->assertNotSame( $options1, $options2 );
128
129 $currentRev = call_user_func( $options2->getCurrentRevisionCallback(), $page->getTitle() );
130 $this->assertSame( $rev->getId(), $currentRev->getId() );
131 }
132
133 /**
134 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::grabCurrentRevision()
135 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::pageExisted()
136 */
137 public function testGrabCurrentRevision() {
138 $page = $this->getPage( __METHOD__ );
139
140 $updater0 = $this->getDerivedPageDataUpdater( $page );
141 $this->assertNull( $updater0->grabCurrentRevision() );
142 $this->assertFalse( $updater0->pageExisted() );
143
144 $rev1 = $this->createRevision( $page, 'first' );
145 $updater1 = $this->getDerivedPageDataUpdater( $page );
146 $this->assertSame( $rev1->getId(), $updater1->grabCurrentRevision()->getId() );
147 $this->assertFalse( $updater0->pageExisted() );
148 $this->assertTrue( $updater1->pageExisted() );
149
150 $rev2 = $this->createRevision( $page, 'second' );
151 $updater2 = $this->getDerivedPageDataUpdater( $page );
152 $this->assertSame( $rev1->getId(), $updater1->grabCurrentRevision()->getId() );
153 $this->assertSame( $rev2->getId(), $updater2->grabCurrentRevision()->getId() );
154 }
155
156 /**
157 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::prepareContent()
158 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::isContentPrepared()
159 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::pageExisted()
160 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::isCreation()
161 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::isChange()
162 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::getSlots()
163 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::getRawSlot()
164 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::getRawContent()
165 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::getModifiedSlotRoles()
166 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::getTouchedSlotRoles()
167 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::getSlotParserOutput()
168 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::getCanonicalParserOutput()
169 */
170 public function testPrepareContent() {
171 $user = $this->getTestUser()->getUser();
172 $updater = $this->getDerivedPageDataUpdater( __METHOD__ );
173
174 $this->assertFalse( $updater->isContentPrepared() );
175
176 // TODO: test stash
177 // TODO: MCR: Test multiple slots. Test slot removal.
178 $mainContent = new WikitextContent( 'first [[main]] ~~~' );
179 $auxContent = new WikitextContent( 'inherited ~~~ content' );
180 $auxSlot = SlotRecord::newSaved(
181 10, 7, 'tt:7',
182 SlotRecord::newUnsaved( 'aux', $auxContent )
183 );
184
185 $update = new RevisionSlotsUpdate();
186 $update->modifyContent( 'main', $mainContent );
187 $update->modifySlot( SlotRecord::newInherited( $auxSlot ) );
188 // TODO: MCR: test removing slots!
189
190 $updater->prepareContent( $user, $update, false );
191
192 // second be ok to call again with the same params
193 $updater->prepareContent( $user, $update, false );
194
195 $this->assertNull( $updater->grabCurrentRevision() );
196 $this->assertTrue( $updater->isContentPrepared() );
197 $this->assertFalse( $updater->isUpdatePrepared() );
198 $this->assertFalse( $updater->pageExisted() );
199 $this->assertTrue( $updater->isCreation() );
200 $this->assertTrue( $updater->isChange() );
201 $this->assertTrue( $updater->isContentPublic() );
202
203 $this->assertEquals( [ 'main', 'aux' ], $updater->getSlots()->getSlotRoles() );
204 $this->assertEquals( [ 'main' ], array_keys( $updater->getSlots()->getOriginalSlots() ) );
205 $this->assertEquals( [ 'aux' ], array_keys( $updater->getSlots()->getInheritedSlots() ) );
206 $this->assertEquals( [ 'main', 'aux' ], $updater->getModifiedSlotRoles() );
207 $this->assertEquals( [ 'main', 'aux' ], $updater->getTouchedSlotRoles() );
208
209 $mainSlot = $updater->getRawSlot( 'main' );
210 $this->assertInstanceOf( SlotRecord::class, $mainSlot );
211 $this->assertNotContains( '~~~', $mainSlot->getContent()->serialize(), 'PST should apply.' );
212 $this->assertContains( $user->getName(), $mainSlot->getContent()->serialize() );
213
214 $auxSlot = $updater->getRawSlot( 'aux' );
215 $this->assertInstanceOf( SlotRecord::class, $auxSlot );
216 $this->assertContains( '~~~', $auxSlot->getContent()->serialize(), 'No PST should apply.' );
217
218 $mainOutput = $updater->getCanonicalParserOutput();
219 $this->assertContains( 'first', $mainOutput->getText() );
220 $this->assertContains( '<a ', $mainOutput->getText() );
221 $this->assertNotEmpty( $mainOutput->getLinks() );
222
223 $canonicalOutput = $updater->getCanonicalParserOutput();
224 $this->assertContains( 'first', $canonicalOutput->getText() );
225 $this->assertContains( '<a ', $canonicalOutput->getText() );
226 $this->assertNotEmpty( $canonicalOutput->getLinks() );
227 }
228
229 /**
230 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::prepareContent()
231 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::pageExisted()
232 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::isCreation()
233 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::isChange()
234 */
235 public function testPrepareContentInherit() {
236 $user = $this->getTestUser()->getUser();
237 $page = $this->getPage( __METHOD__ );
238
239 $mainContent1 = new WikitextContent( 'first [[main]] ~~~' );
240 $mainContent2 = new WikitextContent( 'second' );
241
242 $this->createRevision( $page, 'first', $mainContent1 );
243
244 $update = new RevisionSlotsUpdate();
245 $update->modifyContent( 'main', $mainContent1 );
246 $updater1 = $this->getDerivedPageDataUpdater( $page );
247 $updater1->prepareContent( $user, $update, false );
248
249 $this->assertNotNull( $updater1->grabCurrentRevision() );
250 $this->assertTrue( $updater1->isContentPrepared() );
251 $this->assertTrue( $updater1->pageExisted() );
252 $this->assertFalse( $updater1->isCreation() );
253 $this->assertFalse( $updater1->isChange() );
254
255 // TODO: MCR: test inheritance from parent
256 $update = new RevisionSlotsUpdate();
257 $update->modifyContent( 'main', $mainContent2 );
258 $updater2 = $this->getDerivedPageDataUpdater( $page );
259 $updater2->prepareContent( $user, $update, false );
260
261 $this->assertFalse( $updater2->isCreation() );
262 $this->assertTrue( $updater2->isChange() );
263 }
264
265 // TODO: test failure of prepareContent() when called again...
266 // - with different user
267 // - with different update
268 // - after calling prepareUpdate()
269
270 /**
271 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::prepareUpdate()
272 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::isUpdatePrepared()
273 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::isCreation()
274 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::getSlots()
275 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::getRawSlot()
276 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::getRawContent()
277 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::getModifiedSlotRoles()
278 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::getTouchedSlotRoles()
279 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::getSlotParserOutput()
280 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::getCanonicalParserOutput()
281 */
282 public function testPrepareUpdate() {
283 $page = $this->getPage( __METHOD__ );
284
285 $mainContent1 = new WikitextContent( 'first [[main]] ~~~' );
286 $rev1 = $this->createRevision( $page, 'first', $mainContent1 );
287 $updater1 = $this->getDerivedPageDataUpdater( $page, $rev1 );
288
289 $options = []; // TODO: test *all* the options...
290 $updater1->prepareUpdate( $rev1, $options );
291
292 $this->assertTrue( $updater1->isUpdatePrepared() );
293 $this->assertTrue( $updater1->isContentPrepared() );
294 $this->assertTrue( $updater1->isCreation() );
295 $this->assertTrue( $updater1->isChange() );
296 $this->assertTrue( $updater1->isContentPublic() );
297
298 $this->assertEquals( [ 'main' ], $updater1->getSlots()->getSlotRoles() );
299 $this->assertEquals( [ 'main' ], array_keys( $updater1->getSlots()->getOriginalSlots() ) );
300 $this->assertEquals( [], array_keys( $updater1->getSlots()->getInheritedSlots() ) );
301 $this->assertEquals( [ 'main' ], $updater1->getModifiedSlotRoles() );
302 $this->assertEquals( [ 'main' ], $updater1->getTouchedSlotRoles() );
303
304 // TODO: MCR: test multiple slots, test slot removal!
305
306 $this->assertInstanceOf( SlotRecord::class, $updater1->getRawSlot( 'main' ) );
307 $this->assertNotContains( '~~~~', $updater1->getRawContent( 'main' )->serialize() );
308
309 $mainOutput = $updater1->getCanonicalParserOutput();
310 $this->assertContains( 'first', $mainOutput->getText() );
311 $this->assertContains( '<a ', $mainOutput->getText() );
312 $this->assertNotEmpty( $mainOutput->getLinks() );
313
314 $canonicalOutput = $updater1->getCanonicalParserOutput();
315 $this->assertContains( 'first', $canonicalOutput->getText() );
316 $this->assertContains( '<a ', $canonicalOutput->getText() );
317 $this->assertNotEmpty( $canonicalOutput->getLinks() );
318
319 $mainContent2 = new WikitextContent( 'second' );
320 $rev2 = $this->createRevision( $page, 'second', $mainContent2 );
321 $updater2 = $this->getDerivedPageDataUpdater( $page, $rev2 );
322
323 $options = []; // TODO: test *all* the options...
324 $updater2->prepareUpdate( $rev2, $options );
325
326 $this->assertFalse( $updater2->isCreation() );
327 $this->assertTrue( $updater2->isChange() );
328
329 $canonicalOutput = $updater2->getCanonicalParserOutput();
330 $this->assertContains( 'second', $canonicalOutput->getText() );
331 }
332
333 /**
334 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::prepareUpdate()
335 */
336 public function testPrepareUpdateReusesParserOutput() {
337 $user = $this->getTestUser()->getUser();
338 $page = $this->getPage( __METHOD__ );
339
340 $mainContent1 = new WikitextContent( 'first [[main]] ~~~' );
341
342 $update = new RevisionSlotsUpdate();
343 $update->modifyContent( 'main', $mainContent1 );
344 $updater = $this->getDerivedPageDataUpdater( $page );
345 $updater->prepareContent( $user, $update, false );
346
347 $mainOutput = $updater->getSlotParserOutput( 'main' );
348 $canonicalOutput = $updater->getCanonicalParserOutput();
349
350 $rev = $this->createRevision( $page, 'first', $mainContent1 );
351
352 $options = []; // TODO: test *all* the options...
353 $updater->prepareUpdate( $rev, $options );
354
355 $this->assertTrue( $updater->isUpdatePrepared() );
356 $this->assertTrue( $updater->isContentPrepared() );
357
358 $this->assertSame( $mainOutput, $updater->getSlotParserOutput( 'main' ) );
359 $this->assertSame( $canonicalOutput, $updater->getCanonicalParserOutput() );
360 }
361
362 /**
363 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::prepareUpdate()
364 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::getSlotParserOutput()
365 */
366 public function testPrepareUpdateOutputReset() {
367 $user = $this->getTestUser()->getUser();
368 $page = $this->getPage( __METHOD__ );
369
370 $mainContent1 = new WikitextContent( 'first --{{REVISIONID}}--' );
371
372 $update = new RevisionSlotsUpdate();
373 $update->modifyContent( 'main', $mainContent1 );
374 $updater = $this->getDerivedPageDataUpdater( $page );
375 $updater->prepareContent( $user, $update, false );
376
377 $mainOutput = $updater->getSlotParserOutput( 'main' );
378 $canonicalOutput = $updater->getCanonicalParserOutput();
379
380 // prevent optimization on matching speculative ID
381 $mainOutput->setSpeculativeRevIdUsed( 0 );
382 $canonicalOutput->setSpeculativeRevIdUsed( 0 );
383
384 $rev = $this->createRevision( $page, 'first', $mainContent1 );
385
386 $options = []; // TODO: test *all* the options...
387 $updater->prepareUpdate( $rev, $options );
388
389 $this->assertTrue( $updater->isUpdatePrepared() );
390 $this->assertTrue( $updater->isContentPrepared() );
391
392 // ParserOutput objects should have been flushed.
393 $this->assertNotSame( $mainOutput, $updater->getSlotParserOutput( 'main' ) );
394 $this->assertNotSame( $canonicalOutput, $updater->getCanonicalParserOutput() );
395
396 $html = $updater->getCanonicalParserOutput()->getText();
397 $this->assertContains( '--' . $rev->getId() . '--', $html );
398
399 // TODO: MCR: ensure that when the main slot uses {{REVISIONID}} but another slot is
400 // updated, the main slot is still re-rendered!
401 }
402
403 // TODO: test failure of prepareUpdate() when called again with a different revision
404 // TODO: test failure of prepareUpdate() on inconsistency with prepareContent.
405
406 /**
407 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::getPreparedEdit()
408 */
409 public function testGetPreparedEditAfterPrepareContent() {
410 $user = $this->getTestUser()->getUser();
411
412 $mainContent = new WikitextContent( 'first [[main]] ~~~' );
413 $update = new RevisionSlotsUpdate();
414 $update->modifyContent( 'main', $mainContent );
415
416 $updater = $this->getDerivedPageDataUpdater( __METHOD__ );
417 $updater->prepareContent( $user, $update, false );
418
419 $canonicalOutput = $updater->getCanonicalParserOutput();
420
421 $preparedEdit = $updater->getPreparedEdit();
422 $this->assertSame( $canonicalOutput->getCacheTime(), $preparedEdit->timestamp );
423 $this->assertSame( $canonicalOutput, $preparedEdit->output );
424 $this->assertSame( $mainContent, $preparedEdit->newContent );
425 $this->assertSame( $updater->getRawContent( 'main' ), $preparedEdit->pstContent );
426 $this->assertSame( $updater->getCanonicalParserOptions(), $preparedEdit->popts );
427 $this->assertSame( null, $preparedEdit->revid );
428 }
429
430 /**
431 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::getPreparedEdit()
432 */
433 public function testGetPreparedEditAfterPrepareUpdate() {
434 $page = $this->getPage( __METHOD__ );
435
436 $mainContent = new WikitextContent( 'first [[main]] ~~~' );
437 $update = new MutableRevisionSlots();
438 $update->setContent( 'main', $mainContent );
439
440 $rev = $this->createRevision( $page, __METHOD__ );
441
442 $updater = $this->getDerivedPageDataUpdater( $page );
443 $updater->prepareUpdate( $rev );
444
445 $canonicalOutput = $updater->getCanonicalParserOutput();
446
447 $preparedEdit = $updater->getPreparedEdit();
448 $this->assertSame( $canonicalOutput->getCacheTime(), $preparedEdit->timestamp );
449 $this->assertSame( $canonicalOutput, $preparedEdit->output );
450 $this->assertSame( $updater->getRawContent( 'main' ), $preparedEdit->pstContent );
451 $this->assertSame( $updater->getCanonicalParserOptions(), $preparedEdit->popts );
452 $this->assertSame( $rev->getId(), $preparedEdit->revid );
453 }
454
455 public function testGetSecondaryDataUpdatesAfterPrepareContent() {
456 $user = $this->getTestUser()->getUser();
457 $page = $this->getPage( __METHOD__ );
458 $this->createRevision( $page, __METHOD__ );
459
460 $mainContent1 = new WikitextContent( 'first' );
461
462 $update = new RevisionSlotsUpdate();
463 $update->modifyContent( 'main', $mainContent1 );
464 $updater = $this->getDerivedPageDataUpdater( $page );
465 $updater->prepareContent( $user, $update, false );
466
467 $dataUpdates = $updater->getSecondaryDataUpdates();
468
469 // TODO: MCR: assert updates from all slots!
470 $this->assertNotEmpty( $dataUpdates );
471
472 $linksUpdates = array_filter( $dataUpdates, function ( $du ) {
473 return $du instanceof LinksUpdate;
474 } );
475 $this->assertCount( 1, $linksUpdates );
476 }
477
478 /**
479 * Creates a dummy revision object without touching the database.
480 *
481 * @param Title $title
482 * @param RevisionSlotsUpdate $update
483 * @param User $user
484 * @param string $comment
485 * @param int $id
486 * @param int $parentId
487 *
488 * @return MutableRevisionRecord
489 */
490 private function makeRevision(
491 Title $title,
492 RevisionSlotsUpdate $update,
493 User $user,
494 $comment,
495 $id,
496 $parentId = 0
497 ) {
498 $rev = new MutableRevisionRecord( $title );
499
500 $rev->applyUpdate( $update );
501 $rev->setUser( $user );
502 $rev->setComment( CommentStoreComment::newUnsavedComment( $comment ) );
503 $rev->setId( $id );
504 $rev->setPageId( $title->getArticleID() );
505 $rev->setParentId( $parentId );
506
507 return $rev;
508 }
509
510 public function provideIsReusableFor() {
511 $title = Title::makeTitleSafe( NS_MAIN, __METHOD__ );
512
513 $user1 = User::newFromName( 'Alice' );
514 $user2 = User::newFromName( 'Bob' );
515
516 $content1 = new WikitextContent( 'one' );
517 $content2 = new WikitextContent( 'two' );
518
519 $update1 = new RevisionSlotsUpdate();
520 $update1->modifyContent( 'main', $content1 );
521
522 $update1b = new RevisionSlotsUpdate();
523 $update1b->modifyContent( 'xyz', $content1 );
524
525 $update2 = new RevisionSlotsUpdate();
526 $update2->modifyContent( 'main', $content2 );
527
528 $rev1 = $this->makeRevision( $title, $update1, $user1, 'rev1', 11 );
529 $rev1b = $this->makeRevision( $title, $update1b, $user1, 'rev1', 11 );
530
531 $rev2 = $this->makeRevision( $title, $update2, $user1, 'rev2', 12 );
532 $rev2x = $this->makeRevision( $title, $update2, $user2, 'rev2', 12 );
533 $rev2y = $this->makeRevision( $title, $update2, $user1, 'rev2', 122 );
534
535 yield 'any' => [
536 '$prepUser' => null,
537 '$prepRevision' => null,
538 '$prepUpdate' => null,
539 '$forUser' => null,
540 '$forRevision' => null,
541 '$forUpdate' => null,
542 '$forParent' => null,
543 '$isReusable' => true,
544 ];
545 yield 'for any' => [
546 '$prepUser' => $user1,
547 '$prepRevision' => $rev1,
548 '$prepUpdate' => $update1,
549 '$forUser' => null,
550 '$forRevision' => null,
551 '$forUpdate' => null,
552 '$forParent' => null,
553 '$isReusable' => true,
554 ];
555 yield 'unprepared' => [
556 '$prepUser' => null,
557 '$prepRevision' => null,
558 '$prepUpdate' => null,
559 '$forUser' => $user1,
560 '$forRevision' => $rev1,
561 '$forUpdate' => $update1,
562 '$forParent' => 0,
563 '$isReusable' => true,
564 ];
565 yield 'match prepareContent' => [
566 '$prepUser' => $user1,
567 '$prepRevision' => null,
568 '$prepUpdate' => $update1,
569 '$forUser' => $user1,
570 '$forRevision' => null,
571 '$forUpdate' => $update1,
572 '$forParent' => 0,
573 '$isReusable' => true,
574 ];
575 yield 'match prepareUpdate' => [
576 '$prepUser' => null,
577 '$prepRevision' => $rev1,
578 '$prepUpdate' => null,
579 '$forUser' => $user1,
580 '$forRevision' => $rev1,
581 '$forUpdate' => null,
582 '$forParent' => 0,
583 '$isReusable' => true,
584 ];
585 yield 'match all' => [
586 '$prepUser' => $user1,
587 '$prepRevision' => $rev1,
588 '$prepUpdate' => $update1,
589 '$forUser' => $user1,
590 '$forRevision' => $rev1,
591 '$forUpdate' => $update1,
592 '$forParent' => 0,
593 '$isReusable' => true,
594 ];
595 yield 'mismatch prepareContent update' => [
596 '$prepUser' => $user1,
597 '$prepRevision' => null,
598 '$prepUpdate' => $update1,
599 '$forUser' => $user1,
600 '$forRevision' => null,
601 '$forUpdate' => $update1b,
602 '$forParent' => 0,
603 '$isReusable' => false,
604 ];
605 yield 'mismatch prepareContent user' => [
606 '$prepUser' => $user1,
607 '$prepRevision' => null,
608 '$prepUpdate' => $update1,
609 '$forUser' => $user2,
610 '$forRevision' => null,
611 '$forUpdate' => $update1,
612 '$forParent' => 0,
613 '$isReusable' => false,
614 ];
615 yield 'mismatch prepareContent parent' => [
616 '$prepUser' => $user1,
617 '$prepRevision' => null,
618 '$prepUpdate' => $update1,
619 '$forUser' => $user1,
620 '$forRevision' => null,
621 '$forUpdate' => $update1,
622 '$forParent' => 7,
623 '$isReusable' => false,
624 ];
625 yield 'mismatch prepareUpdate revision update' => [
626 '$prepUser' => null,
627 '$prepRevision' => $rev1,
628 '$prepUpdate' => null,
629 '$forUser' => null,
630 '$forRevision' => $rev1b,
631 '$forUpdate' => null,
632 '$forParent' => 0,
633 '$isReusable' => false,
634 ];
635 yield 'mismatch prepareUpdate revision user' => [
636 '$prepUser' => null,
637 '$prepRevision' => $rev2,
638 '$prepUpdate' => null,
639 '$forUser' => null,
640 '$forRevision' => $rev2x,
641 '$forUpdate' => null,
642 '$forParent' => 0,
643 '$isReusable' => false,
644 ];
645 yield 'mismatch prepareUpdate revision id' => [
646 '$prepUser' => null,
647 '$prepRevision' => $rev2,
648 '$prepUpdate' => null,
649 '$forUser' => null,
650 '$forRevision' => $rev2y,
651 '$forUpdate' => null,
652 '$forParent' => 0,
653 '$isReusable' => false,
654 ];
655 }
656
657 /**
658 * @dataProvider provideIsReusableFor
659 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::isReusableFor()
660 *
661 * @param User|null $prepUser
662 * @param RevisionRecord|null $prepRevision
663 * @param RevisionSlotsUpdate|null $prepUpdate
664 * @param User|null $forUser
665 * @param RevisionRecord|null $forRevision
666 * @param RevisionSlotsUpdate|null $forUpdate
667 * @param int|null $forParent
668 * @param bool $isReusable
669 */
670 public function testIsReusableFor(
671 User $prepUser = null,
672 RevisionRecord $prepRevision = null,
673 RevisionSlotsUpdate $prepUpdate = null,
674 User $forUser = null,
675 RevisionRecord $forRevision = null,
676 RevisionSlotsUpdate $forUpdate = null,
677 $forParent = null,
678 $isReusable = null
679 ) {
680 $updater = $this->getDerivedPageDataUpdater( __METHOD__ );
681
682 if ( $prepUpdate ) {
683 $updater->prepareContent( $prepUser, $prepUpdate, false );
684 }
685
686 if ( $prepRevision ) {
687 $updater->prepareUpdate( $prepRevision );
688 }
689
690 $this->assertSame(
691 $isReusable,
692 $updater->isReusableFor( $forUser, $forRevision, $forUpdate, $forParent )
693 );
694 }
695
696 /**
697 * @covers \MediaWiki\Storage\DerivedPageDataUpdater::doUpdates()
698 */
699 public function testDoUpdates() {
700 $page = $this->getPage( __METHOD__ );
701
702 $mainContent1 = new WikitextContent( 'first [[main]]' );
703 $rev = $this->createRevision( $page, 'first', $mainContent1 );
704 $pageId = $page->getId();
705 $oldStats = $this->db->selectRow( 'site_stats', '*', '1=1' );
706
707 $updater = $this->getDerivedPageDataUpdater( $page, $rev );
708 $updater->setArticleCountMethod( 'link' );
709
710 $options = []; // TODO: test *all* the options...
711 $updater->prepareUpdate( $rev, $options );
712
713 $updater->doUpdates();
714
715 // links table update
716 $linkCount = $this->db->selectRowCount( 'pagelinks', '*', [ 'pl_from' => $pageId ] );
717 $this->assertSame( 1, $linkCount );
718
719 $pageLinksRow = $this->db->selectRow( 'pagelinks', '*', [ 'pl_from' => $pageId ] );
720 $this->assertInternalType( 'object', $pageLinksRow );
721 $this->assertSame( 'Main', $pageLinksRow->pl_title );
722
723 // parser cache update
724 $pcache = MediaWikiServices::getInstance()->getParserCache();
725 $cached = $pcache->get( $page, $updater->getCanonicalParserOptions() );
726 $this->assertInternalType( 'object', $cached );
727 $this->assertSame( $updater->getCanonicalParserOutput(), $cached );
728
729 // site stats
730 $stats = $this->db->selectRow( 'site_stats', '*', '1=1' );
731 $this->assertSame( $oldStats->ss_total_pages + 1, (int)$stats->ss_total_pages );
732 $this->assertSame( $oldStats->ss_total_edits + 1, (int)$stats->ss_total_edits );
733 $this->assertSame( $oldStats->ss_good_articles + 1, (int)$stats->ss_good_articles );
734
735 // TODO: MCR: test data updates for additional slots!
736 // TODO: test update for edit without page creation
737 // TODO: test message cache purge
738 // TODO: test module cache purge
739 // TODO: test CDN purge
740 // TODO: test newtalk update
741 // TODO: test search update
742 // TODO: test site stats good_articles while turning the page into (or back from) a redir.
743 // TODO: test category membership update (with setRcWatchCategoryMembership())
744 }
745
746 }