2018-01-27 01:48:19 +00:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
namespace MediaWiki\Tests\Storage;
|
|
|
|
|
|
|
|
|
|
use CommentStoreComment;
|
|
|
|
|
use Content;
|
2018-03-09 22:05:47 +00:00
|
|
|
use ContentHandler;
|
2018-01-27 01:48:19 +00:00
|
|
|
use LinksUpdate;
|
|
|
|
|
use MediaWiki\MediaWikiServices;
|
|
|
|
|
use MediaWiki\Storage\DerivedPageDataUpdater;
|
|
|
|
|
use MediaWiki\Storage\MutableRevisionRecord;
|
|
|
|
|
use MediaWiki\Storage\MutableRevisionSlots;
|
|
|
|
|
use MediaWiki\Storage\RevisionRecord;
|
|
|
|
|
use MediaWiki\Storage\RevisionSlotsUpdate;
|
|
|
|
|
use MediaWiki\Storage\SlotRecord;
|
|
|
|
|
use MediaWikiTestCase;
|
2018-03-09 22:05:47 +00:00
|
|
|
use MWCallableUpdate;
|
2018-10-04 10:32:06 +00:00
|
|
|
use MWTimestamp;
|
2018-03-09 22:05:47 +00:00
|
|
|
use PHPUnit\Framework\MockObject\MockObject;
|
|
|
|
|
use TextContent;
|
|
|
|
|
use TextContentHandler;
|
2018-01-27 01:48:19 +00:00
|
|
|
use Title;
|
|
|
|
|
use User;
|
|
|
|
|
use Wikimedia\TestingAccessWrapper;
|
|
|
|
|
use WikiPage;
|
|
|
|
|
use WikitextContent;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @group Database
|
|
|
|
|
*
|
2018-08-28 15:34:25 +00:00
|
|
|
* @covers \MediaWiki\Storage\DerivedPageDataUpdater
|
2018-01-27 01:48:19 +00:00
|
|
|
*/
|
|
|
|
|
class DerivedPageDataUpdaterTest extends MediaWikiTestCase {
|
|
|
|
|
|
2018-10-04 10:32:06 +00:00
|
|
|
public function tearDown() {
|
|
|
|
|
MWTimestamp::setFakeTime( false );
|
|
|
|
|
|
|
|
|
|
parent::tearDown();
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-27 01:48:19 +00:00
|
|
|
/**
|
|
|
|
|
* @param string $title
|
|
|
|
|
*
|
|
|
|
|
* @return Title
|
|
|
|
|
*/
|
|
|
|
|
private function getTitle( $title ) {
|
|
|
|
|
return Title::makeTitleSafe( $this->getDefaultWikitextNS(), $title );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param string|Title $title
|
|
|
|
|
*
|
|
|
|
|
* @return WikiPage
|
|
|
|
|
*/
|
|
|
|
|
private function getPage( $title ) {
|
|
|
|
|
$title = ( $title instanceof Title ) ? $title : $this->getTitle( $title );
|
|
|
|
|
|
|
|
|
|
return WikiPage::factory( $title );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param string|Title|WikiPage $page
|
|
|
|
|
*
|
|
|
|
|
* @return DerivedPageDataUpdater
|
|
|
|
|
*/
|
|
|
|
|
private function getDerivedPageDataUpdater( $page, RevisionRecord $rec = null ) {
|
|
|
|
|
if ( is_string( $page ) || $page instanceof Title ) {
|
|
|
|
|
$page = $this->getPage( $page );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$page = TestingAccessWrapper::newFromObject( $page );
|
|
|
|
|
return $page->getDerivedDataUpdater( null, $rec );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Creates a revision in the database.
|
|
|
|
|
*
|
|
|
|
|
* @param WikiPage $page
|
|
|
|
|
* @param $summary
|
|
|
|
|
* @param null|string|Content $content
|
|
|
|
|
*
|
|
|
|
|
* @return RevisionRecord|null
|
|
|
|
|
*/
|
|
|
|
|
private function createRevision( WikiPage $page, $summary, $content = null ) {
|
|
|
|
|
$user = $this->getTestUser()->getUser();
|
|
|
|
|
$comment = CommentStoreComment::newUnsavedComment( $summary );
|
|
|
|
|
|
2018-08-07 16:52:40 +00:00
|
|
|
if ( $content === null || is_string( $content ) ) {
|
2018-06-20 05:26:57 +00:00
|
|
|
$content = new WikitextContent( $content ?? $summary );
|
2018-01-27 01:48:19 +00:00
|
|
|
}
|
|
|
|
|
|
2018-08-07 16:52:40 +00:00
|
|
|
if ( !is_array( $content ) ) {
|
|
|
|
|
$content = [ 'main' => $content ];
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-27 01:48:19 +00:00
|
|
|
$this->getDerivedPageDataUpdater( $page ); // flush cached instance before.
|
|
|
|
|
|
|
|
|
|
$updater = $page->newPageUpdater( $user );
|
2018-08-07 16:52:40 +00:00
|
|
|
|
|
|
|
|
foreach ( $content as $role => $c ) {
|
|
|
|
|
$updater->setContent( $role, $c );
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-27 01:48:19 +00:00
|
|
|
$rev = $updater->saveRevision( $comment );
|
|
|
|
|
|
|
|
|
|
$this->getDerivedPageDataUpdater( $page ); // flush cached instance after.
|
|
|
|
|
return $rev;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO: test setArticleCountMethod() and isCountable();
|
|
|
|
|
// TODO: test isRedirect() and wasRedirect()
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @covers \MediaWiki\Storage\DerivedPageDataUpdater::getCanonicalParserOptions()
|
|
|
|
|
*/
|
|
|
|
|
public function testGetCanonicalParserOptions() {
|
|
|
|
|
$user = $this->getTestUser()->getUser();
|
|
|
|
|
$page = $this->getPage( __METHOD__ );
|
|
|
|
|
|
|
|
|
|
$parentRev = $this->createRevision( $page, 'first' );
|
|
|
|
|
|
|
|
|
|
$mainContent = new WikitextContent( 'Lorem ipsum' );
|
|
|
|
|
|
|
|
|
|
$update = new RevisionSlotsUpdate();
|
2018-09-24 21:10:08 +00:00
|
|
|
$update->modifyContent( SlotRecord::MAIN, $mainContent );
|
2018-01-27 01:48:19 +00:00
|
|
|
$updater = $this->getDerivedPageDataUpdater( $page );
|
|
|
|
|
$updater->prepareContent( $user, $update, false );
|
|
|
|
|
|
|
|
|
|
$options1 = $updater->getCanonicalParserOptions();
|
2018-07-29 12:24:54 +00:00
|
|
|
$this->assertSame( MediaWikiServices::getInstance()->getContentLanguage(),
|
|
|
|
|
$options1->getUserLangObj() );
|
2018-01-27 01:48:19 +00:00
|
|
|
|
2018-08-07 16:52:40 +00:00
|
|
|
$speculativeId = $options1->getSpeculativeRevId();
|
2018-01-27 01:48:19 +00:00
|
|
|
$this->assertSame( $parentRev->getId() + 1, $speculativeId );
|
|
|
|
|
|
|
|
|
|
$rev = $this->makeRevision(
|
|
|
|
|
$page->getTitle(),
|
|
|
|
|
$update,
|
|
|
|
|
$user,
|
|
|
|
|
$parentRev->getId() + 7,
|
|
|
|
|
$parentRev->getId()
|
|
|
|
|
);
|
|
|
|
|
$updater->prepareUpdate( $rev );
|
|
|
|
|
|
|
|
|
|
$options2 = $updater->getCanonicalParserOptions();
|
|
|
|
|
|
|
|
|
|
$currentRev = call_user_func( $options2->getCurrentRevisionCallback(), $page->getTitle() );
|
|
|
|
|
$this->assertSame( $rev->getId(), $currentRev->getId() );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @covers \MediaWiki\Storage\DerivedPageDataUpdater::grabCurrentRevision()
|
|
|
|
|
* @covers \MediaWiki\Storage\DerivedPageDataUpdater::pageExisted()
|
|
|
|
|
*/
|
|
|
|
|
public function testGrabCurrentRevision() {
|
|
|
|
|
$page = $this->getPage( __METHOD__ );
|
|
|
|
|
|
|
|
|
|
$updater0 = $this->getDerivedPageDataUpdater( $page );
|
|
|
|
|
$this->assertNull( $updater0->grabCurrentRevision() );
|
|
|
|
|
$this->assertFalse( $updater0->pageExisted() );
|
|
|
|
|
|
|
|
|
|
$rev1 = $this->createRevision( $page, 'first' );
|
|
|
|
|
$updater1 = $this->getDerivedPageDataUpdater( $page );
|
|
|
|
|
$this->assertSame( $rev1->getId(), $updater1->grabCurrentRevision()->getId() );
|
|
|
|
|
$this->assertFalse( $updater0->pageExisted() );
|
|
|
|
|
$this->assertTrue( $updater1->pageExisted() );
|
|
|
|
|
|
|
|
|
|
$rev2 = $this->createRevision( $page, 'second' );
|
|
|
|
|
$updater2 = $this->getDerivedPageDataUpdater( $page );
|
|
|
|
|
$this->assertSame( $rev1->getId(), $updater1->grabCurrentRevision()->getId() );
|
|
|
|
|
$this->assertSame( $rev2->getId(), $updater2->grabCurrentRevision()->getId() );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @covers \MediaWiki\Storage\DerivedPageDataUpdater::prepareContent()
|
|
|
|
|
* @covers \MediaWiki\Storage\DerivedPageDataUpdater::isContentPrepared()
|
|
|
|
|
* @covers \MediaWiki\Storage\DerivedPageDataUpdater::pageExisted()
|
|
|
|
|
* @covers \MediaWiki\Storage\DerivedPageDataUpdater::isCreation()
|
|
|
|
|
* @covers \MediaWiki\Storage\DerivedPageDataUpdater::isChange()
|
|
|
|
|
* @covers \MediaWiki\Storage\DerivedPageDataUpdater::getSlots()
|
|
|
|
|
* @covers \MediaWiki\Storage\DerivedPageDataUpdater::getRawSlot()
|
|
|
|
|
* @covers \MediaWiki\Storage\DerivedPageDataUpdater::getRawContent()
|
|
|
|
|
* @covers \MediaWiki\Storage\DerivedPageDataUpdater::getModifiedSlotRoles()
|
|
|
|
|
* @covers \MediaWiki\Storage\DerivedPageDataUpdater::getTouchedSlotRoles()
|
|
|
|
|
* @covers \MediaWiki\Storage\DerivedPageDataUpdater::getSlotParserOutput()
|
|
|
|
|
* @covers \MediaWiki\Storage\DerivedPageDataUpdater::getCanonicalParserOutput()
|
|
|
|
|
*/
|
|
|
|
|
public function testPrepareContent() {
|
2018-08-07 16:52:40 +00:00
|
|
|
$sysop = $this->getTestUser( [ 'sysop' ] )->getUser();
|
2018-01-27 01:48:19 +00:00
|
|
|
$updater = $this->getDerivedPageDataUpdater( __METHOD__ );
|
|
|
|
|
|
|
|
|
|
$this->assertFalse( $updater->isContentPrepared() );
|
|
|
|
|
|
|
|
|
|
// TODO: test stash
|
|
|
|
|
// TODO: MCR: Test multiple slots. Test slot removal.
|
|
|
|
|
$mainContent = new WikitextContent( 'first [[main]] ~~~' );
|
|
|
|
|
$auxContent = new WikitextContent( 'inherited ~~~ content' );
|
|
|
|
|
$auxSlot = SlotRecord::newSaved(
|
|
|
|
|
10, 7, 'tt:7',
|
|
|
|
|
SlotRecord::newUnsaved( 'aux', $auxContent )
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$update = new RevisionSlotsUpdate();
|
2018-09-24 21:10:08 +00:00
|
|
|
$update->modifyContent( SlotRecord::MAIN, $mainContent );
|
2018-01-27 01:48:19 +00:00
|
|
|
$update->modifySlot( SlotRecord::newInherited( $auxSlot ) );
|
|
|
|
|
// TODO: MCR: test removing slots!
|
|
|
|
|
|
2018-08-07 16:52:40 +00:00
|
|
|
$updater->prepareContent( $sysop, $update, false );
|
2018-01-27 01:48:19 +00:00
|
|
|
|
|
|
|
|
// second be ok to call again with the same params
|
2018-08-07 16:52:40 +00:00
|
|
|
$updater->prepareContent( $sysop, $update, false );
|
2018-01-27 01:48:19 +00:00
|
|
|
|
|
|
|
|
$this->assertNull( $updater->grabCurrentRevision() );
|
|
|
|
|
$this->assertTrue( $updater->isContentPrepared() );
|
|
|
|
|
$this->assertFalse( $updater->isUpdatePrepared() );
|
|
|
|
|
$this->assertFalse( $updater->pageExisted() );
|
|
|
|
|
$this->assertTrue( $updater->isCreation() );
|
|
|
|
|
$this->assertTrue( $updater->isChange() );
|
2018-08-07 16:52:40 +00:00
|
|
|
$this->assertFalse( $updater->isContentDeleted() );
|
|
|
|
|
|
|
|
|
|
$this->assertNotNull( $updater->getRevision() );
|
|
|
|
|
$this->assertNotNull( $updater->getRenderedRevision() );
|
2018-01-27 01:48:19 +00:00
|
|
|
|
|
|
|
|
$this->assertEquals( [ 'main', 'aux' ], $updater->getSlots()->getSlotRoles() );
|
|
|
|
|
$this->assertEquals( [ 'main' ], array_keys( $updater->getSlots()->getOriginalSlots() ) );
|
|
|
|
|
$this->assertEquals( [ 'aux' ], array_keys( $updater->getSlots()->getInheritedSlots() ) );
|
|
|
|
|
$this->assertEquals( [ 'main', 'aux' ], $updater->getModifiedSlotRoles() );
|
|
|
|
|
$this->assertEquals( [ 'main', 'aux' ], $updater->getTouchedSlotRoles() );
|
|
|
|
|
|
2018-09-24 21:10:08 +00:00
|
|
|
$mainSlot = $updater->getRawSlot( SlotRecord::MAIN );
|
2018-01-27 01:48:19 +00:00
|
|
|
$this->assertInstanceOf( SlotRecord::class, $mainSlot );
|
|
|
|
|
$this->assertNotContains( '~~~', $mainSlot->getContent()->serialize(), 'PST should apply.' );
|
2018-08-07 16:52:40 +00:00
|
|
|
$this->assertContains( $sysop->getName(), $mainSlot->getContent()->serialize() );
|
2018-01-27 01:48:19 +00:00
|
|
|
|
|
|
|
|
$auxSlot = $updater->getRawSlot( 'aux' );
|
|
|
|
|
$this->assertInstanceOf( SlotRecord::class, $auxSlot );
|
|
|
|
|
$this->assertContains( '~~~', $auxSlot->getContent()->serialize(), 'No PST should apply.' );
|
|
|
|
|
|
|
|
|
|
$mainOutput = $updater->getCanonicalParserOutput();
|
|
|
|
|
$this->assertContains( 'first', $mainOutput->getText() );
|
|
|
|
|
$this->assertContains( '<a ', $mainOutput->getText() );
|
|
|
|
|
$this->assertNotEmpty( $mainOutput->getLinks() );
|
|
|
|
|
|
|
|
|
|
$canonicalOutput = $updater->getCanonicalParserOutput();
|
|
|
|
|
$this->assertContains( 'first', $canonicalOutput->getText() );
|
|
|
|
|
$this->assertContains( '<a ', $canonicalOutput->getText() );
|
2018-08-07 16:52:40 +00:00
|
|
|
$this->assertContains( 'inherited ', $canonicalOutput->getText() );
|
2018-01-27 01:48:19 +00:00
|
|
|
$this->assertNotEmpty( $canonicalOutput->getLinks() );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @covers \MediaWiki\Storage\DerivedPageDataUpdater::prepareContent()
|
|
|
|
|
* @covers \MediaWiki\Storage\DerivedPageDataUpdater::pageExisted()
|
|
|
|
|
* @covers \MediaWiki\Storage\DerivedPageDataUpdater::isCreation()
|
|
|
|
|
* @covers \MediaWiki\Storage\DerivedPageDataUpdater::isChange()
|
|
|
|
|
*/
|
|
|
|
|
public function testPrepareContentInherit() {
|
2018-08-07 16:52:40 +00:00
|
|
|
$sysop = $this->getTestUser( [ 'sysop' ] )->getUser();
|
2018-01-27 01:48:19 +00:00
|
|
|
$page = $this->getPage( __METHOD__ );
|
|
|
|
|
|
2018-09-05 18:03:15 +00:00
|
|
|
$mainContent1 = new WikitextContent( 'first [[main]] ({{REVISIONUSER}}) #~~~#' );
|
|
|
|
|
$mainContent2 = new WikitextContent( 'second ({{subst:REVISIONUSER}}) #~~~#' );
|
2018-01-27 01:48:19 +00:00
|
|
|
|
2018-08-07 16:52:40 +00:00
|
|
|
$rev = $this->createRevision( $page, 'first', $mainContent1 );
|
2018-09-24 21:10:08 +00:00
|
|
|
$mainContent1 = $rev->getContent( SlotRecord::MAIN ); // get post-pst content
|
2018-09-05 18:03:15 +00:00
|
|
|
$userName = $rev->getUser()->getName();
|
|
|
|
|
$sysopName = $sysop->getName();
|
2018-01-27 01:48:19 +00:00
|
|
|
|
|
|
|
|
$update = new RevisionSlotsUpdate();
|
2018-09-24 21:10:08 +00:00
|
|
|
$update->modifyContent( SlotRecord::MAIN, $mainContent1 );
|
2018-01-27 01:48:19 +00:00
|
|
|
$updater1 = $this->getDerivedPageDataUpdater( $page );
|
2018-08-07 16:52:40 +00:00
|
|
|
$updater1->prepareContent( $sysop, $update, false );
|
2018-01-27 01:48:19 +00:00
|
|
|
|
|
|
|
|
$this->assertNotNull( $updater1->grabCurrentRevision() );
|
|
|
|
|
$this->assertTrue( $updater1->isContentPrepared() );
|
|
|
|
|
$this->assertTrue( $updater1->pageExisted() );
|
|
|
|
|
$this->assertFalse( $updater1->isCreation() );
|
|
|
|
|
$this->assertFalse( $updater1->isChange() );
|
|
|
|
|
|
2018-08-07 16:52:40 +00:00
|
|
|
$this->assertNotNull( $updater1->getRevision() );
|
|
|
|
|
$this->assertNotNull( $updater1->getRenderedRevision() );
|
|
|
|
|
|
|
|
|
|
// parser-output for null-edit uses the original author's name
|
|
|
|
|
$html = $updater1->getRenderedRevision()->getRevisionParserOutput()->getText();
|
2018-09-05 18:03:15 +00:00
|
|
|
$this->assertNotContains( $sysopName, $html, '{{REVISIONUSER}}' );
|
2018-08-07 16:52:40 +00:00
|
|
|
$this->assertNotContains( '{{REVISIONUSER}}', $html, '{{REVISIONUSER}}' );
|
2018-09-05 18:03:15 +00:00
|
|
|
$this->assertNotContains( '~~~', $html, 'signature ~~~' );
|
|
|
|
|
$this->assertContains( '(' . $userName . ')', $html, '{{REVISIONUSER}}' );
|
|
|
|
|
$this->assertContains( '>' . $userName . '<', $html, 'signature ~~~' );
|
2018-08-07 16:52:40 +00:00
|
|
|
|
2018-01-27 01:48:19 +00:00
|
|
|
// TODO: MCR: test inheritance from parent
|
|
|
|
|
$update = new RevisionSlotsUpdate();
|
2018-09-24 21:10:08 +00:00
|
|
|
$update->modifyContent( SlotRecord::MAIN, $mainContent2 );
|
2018-01-27 01:48:19 +00:00
|
|
|
$updater2 = $this->getDerivedPageDataUpdater( $page );
|
2018-08-07 16:52:40 +00:00
|
|
|
$updater2->prepareContent( $sysop, $update, false );
|
2018-01-27 01:48:19 +00:00
|
|
|
|
2018-09-05 18:03:15 +00:00
|
|
|
// non-null edit use the new user name in PST
|
2018-09-24 21:10:08 +00:00
|
|
|
$pstText = $updater2->getSlots()->getContent( SlotRecord::MAIN )->serialize();
|
2018-09-05 18:03:15 +00:00
|
|
|
$this->assertNotContains( '{{subst:REVISIONUSER}}', $pstText, '{{subst:REVISIONUSER}}' );
|
|
|
|
|
$this->assertNotContains( '~~~', $pstText, 'signature ~~~' );
|
|
|
|
|
$this->assertContains( '(' . $sysopName . ')', $pstText, '{{subst:REVISIONUSER}}' );
|
|
|
|
|
$this->assertContains( ':' . $sysopName . '|', $pstText, 'signature ~~~' );
|
|
|
|
|
|
2018-01-27 01:48:19 +00:00
|
|
|
$this->assertFalse( $updater2->isCreation() );
|
|
|
|
|
$this->assertTrue( $updater2->isChange() );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO: test failure of prepareContent() when called again...
|
|
|
|
|
// - with different user
|
|
|
|
|
// - with different update
|
|
|
|
|
// - after calling prepareUpdate()
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @covers \MediaWiki\Storage\DerivedPageDataUpdater::prepareUpdate()
|
|
|
|
|
* @covers \MediaWiki\Storage\DerivedPageDataUpdater::isUpdatePrepared()
|
|
|
|
|
* @covers \MediaWiki\Storage\DerivedPageDataUpdater::isCreation()
|
|
|
|
|
* @covers \MediaWiki\Storage\DerivedPageDataUpdater::getSlots()
|
|
|
|
|
* @covers \MediaWiki\Storage\DerivedPageDataUpdater::getRawSlot()
|
|
|
|
|
* @covers \MediaWiki\Storage\DerivedPageDataUpdater::getRawContent()
|
|
|
|
|
* @covers \MediaWiki\Storage\DerivedPageDataUpdater::getModifiedSlotRoles()
|
|
|
|
|
* @covers \MediaWiki\Storage\DerivedPageDataUpdater::getTouchedSlotRoles()
|
|
|
|
|
* @covers \MediaWiki\Storage\DerivedPageDataUpdater::getSlotParserOutput()
|
|
|
|
|
* @covers \MediaWiki\Storage\DerivedPageDataUpdater::getCanonicalParserOutput()
|
|
|
|
|
*/
|
|
|
|
|
public function testPrepareUpdate() {
|
|
|
|
|
$page = $this->getPage( __METHOD__ );
|
|
|
|
|
|
|
|
|
|
$mainContent1 = new WikitextContent( 'first [[main]] ~~~' );
|
|
|
|
|
$rev1 = $this->createRevision( $page, 'first', $mainContent1 );
|
|
|
|
|
$updater1 = $this->getDerivedPageDataUpdater( $page, $rev1 );
|
|
|
|
|
|
|
|
|
|
$options = []; // TODO: test *all* the options...
|
|
|
|
|
$updater1->prepareUpdate( $rev1, $options );
|
|
|
|
|
|
|
|
|
|
$this->assertTrue( $updater1->isUpdatePrepared() );
|
|
|
|
|
$this->assertTrue( $updater1->isContentPrepared() );
|
|
|
|
|
$this->assertTrue( $updater1->isCreation() );
|
|
|
|
|
$this->assertTrue( $updater1->isChange() );
|
2018-08-07 16:52:40 +00:00
|
|
|
$this->assertFalse( $updater1->isContentDeleted() );
|
|
|
|
|
|
|
|
|
|
$this->assertNotNull( $updater1->getRevision() );
|
|
|
|
|
$this->assertNotNull( $updater1->getRenderedRevision() );
|
2018-01-27 01:48:19 +00:00
|
|
|
|
|
|
|
|
$this->assertEquals( [ 'main' ], $updater1->getSlots()->getSlotRoles() );
|
|
|
|
|
$this->assertEquals( [ 'main' ], array_keys( $updater1->getSlots()->getOriginalSlots() ) );
|
|
|
|
|
$this->assertEquals( [], array_keys( $updater1->getSlots()->getInheritedSlots() ) );
|
|
|
|
|
$this->assertEquals( [ 'main' ], $updater1->getModifiedSlotRoles() );
|
|
|
|
|
$this->assertEquals( [ 'main' ], $updater1->getTouchedSlotRoles() );
|
|
|
|
|
|
|
|
|
|
// TODO: MCR: test multiple slots, test slot removal!
|
|
|
|
|
|
2018-09-24 21:10:08 +00:00
|
|
|
$this->assertInstanceOf( SlotRecord::class, $updater1->getRawSlot( SlotRecord::MAIN ) );
|
|
|
|
|
$this->assertNotContains( '~~~~', $updater1->getRawContent( SlotRecord::MAIN )->serialize() );
|
2018-01-27 01:48:19 +00:00
|
|
|
|
|
|
|
|
$mainOutput = $updater1->getCanonicalParserOutput();
|
|
|
|
|
$this->assertContains( 'first', $mainOutput->getText() );
|
|
|
|
|
$this->assertContains( '<a ', $mainOutput->getText() );
|
|
|
|
|
$this->assertNotEmpty( $mainOutput->getLinks() );
|
|
|
|
|
|
|
|
|
|
$canonicalOutput = $updater1->getCanonicalParserOutput();
|
|
|
|
|
$this->assertContains( 'first', $canonicalOutput->getText() );
|
|
|
|
|
$this->assertContains( '<a ', $canonicalOutput->getText() );
|
|
|
|
|
$this->assertNotEmpty( $canonicalOutput->getLinks() );
|
|
|
|
|
|
|
|
|
|
$mainContent2 = new WikitextContent( 'second' );
|
|
|
|
|
$rev2 = $this->createRevision( $page, 'second', $mainContent2 );
|
|
|
|
|
$updater2 = $this->getDerivedPageDataUpdater( $page, $rev2 );
|
|
|
|
|
|
|
|
|
|
$options = []; // TODO: test *all* the options...
|
|
|
|
|
$updater2->prepareUpdate( $rev2, $options );
|
|
|
|
|
|
|
|
|
|
$this->assertFalse( $updater2->isCreation() );
|
|
|
|
|
$this->assertTrue( $updater2->isChange() );
|
|
|
|
|
|
|
|
|
|
$canonicalOutput = $updater2->getCanonicalParserOutput();
|
|
|
|
|
$this->assertContains( 'second', $canonicalOutput->getText() );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @covers \MediaWiki\Storage\DerivedPageDataUpdater::prepareUpdate()
|
|
|
|
|
*/
|
|
|
|
|
public function testPrepareUpdateReusesParserOutput() {
|
|
|
|
|
$user = $this->getTestUser()->getUser();
|
|
|
|
|
$page = $this->getPage( __METHOD__ );
|
|
|
|
|
|
|
|
|
|
$mainContent1 = new WikitextContent( 'first [[main]] ~~~' );
|
|
|
|
|
|
|
|
|
|
$update = new RevisionSlotsUpdate();
|
2018-09-24 21:10:08 +00:00
|
|
|
$update->modifyContent( SlotRecord::MAIN, $mainContent1 );
|
2018-01-27 01:48:19 +00:00
|
|
|
$updater = $this->getDerivedPageDataUpdater( $page );
|
|
|
|
|
$updater->prepareContent( $user, $update, false );
|
|
|
|
|
|
2018-09-24 21:10:08 +00:00
|
|
|
$mainOutput = $updater->getSlotParserOutput( SlotRecord::MAIN );
|
2018-01-27 01:48:19 +00:00
|
|
|
$canonicalOutput = $updater->getCanonicalParserOutput();
|
|
|
|
|
|
|
|
|
|
$rev = $this->createRevision( $page, 'first', $mainContent1 );
|
|
|
|
|
|
|
|
|
|
$options = []; // TODO: test *all* the options...
|
|
|
|
|
$updater->prepareUpdate( $rev, $options );
|
|
|
|
|
|
|
|
|
|
$this->assertTrue( $updater->isUpdatePrepared() );
|
|
|
|
|
$this->assertTrue( $updater->isContentPrepared() );
|
|
|
|
|
|
2018-09-24 21:10:08 +00:00
|
|
|
$this->assertSame( $mainOutput, $updater->getSlotParserOutput( SlotRecord::MAIN ) );
|
2018-01-27 01:48:19 +00:00
|
|
|
$this->assertSame( $canonicalOutput, $updater->getCanonicalParserOutput() );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @covers \MediaWiki\Storage\DerivedPageDataUpdater::prepareUpdate()
|
|
|
|
|
* @covers \MediaWiki\Storage\DerivedPageDataUpdater::getSlotParserOutput()
|
|
|
|
|
*/
|
|
|
|
|
public function testPrepareUpdateOutputReset() {
|
|
|
|
|
$user = $this->getTestUser()->getUser();
|
|
|
|
|
$page = $this->getPage( __METHOD__ );
|
|
|
|
|
|
|
|
|
|
$mainContent1 = new WikitextContent( 'first --{{REVISIONID}}--' );
|
|
|
|
|
|
|
|
|
|
$update = new RevisionSlotsUpdate();
|
2018-09-24 21:10:08 +00:00
|
|
|
$update->modifyContent( SlotRecord::MAIN, $mainContent1 );
|
2018-01-27 01:48:19 +00:00
|
|
|
$updater = $this->getDerivedPageDataUpdater( $page );
|
|
|
|
|
$updater->prepareContent( $user, $update, false );
|
|
|
|
|
|
2018-09-24 21:10:08 +00:00
|
|
|
$mainOutput = $updater->getSlotParserOutput( SlotRecord::MAIN );
|
2018-01-27 01:48:19 +00:00
|
|
|
$canonicalOutput = $updater->getCanonicalParserOutput();
|
|
|
|
|
|
|
|
|
|
// prevent optimization on matching speculative ID
|
|
|
|
|
$mainOutput->setSpeculativeRevIdUsed( 0 );
|
|
|
|
|
$canonicalOutput->setSpeculativeRevIdUsed( 0 );
|
|
|
|
|
|
|
|
|
|
$rev = $this->createRevision( $page, 'first', $mainContent1 );
|
|
|
|
|
|
|
|
|
|
$options = []; // TODO: test *all* the options...
|
|
|
|
|
$updater->prepareUpdate( $rev, $options );
|
|
|
|
|
|
|
|
|
|
$this->assertTrue( $updater->isUpdatePrepared() );
|
|
|
|
|
$this->assertTrue( $updater->isContentPrepared() );
|
|
|
|
|
|
|
|
|
|
// ParserOutput objects should have been flushed.
|
2018-09-24 21:10:08 +00:00
|
|
|
$this->assertNotSame( $mainOutput, $updater->getSlotParserOutput( SlotRecord::MAIN ) );
|
2018-01-27 01:48:19 +00:00
|
|
|
$this->assertNotSame( $canonicalOutput, $updater->getCanonicalParserOutput() );
|
|
|
|
|
|
|
|
|
|
$html = $updater->getCanonicalParserOutput()->getText();
|
|
|
|
|
$this->assertContains( '--' . $rev->getId() . '--', $html );
|
|
|
|
|
|
|
|
|
|
// TODO: MCR: ensure that when the main slot uses {{REVISIONID}} but another slot is
|
|
|
|
|
// updated, the main slot is still re-rendered!
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO: test failure of prepareUpdate() when called again with a different revision
|
|
|
|
|
// TODO: test failure of prepareUpdate() on inconsistency with prepareContent.
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @covers \MediaWiki\Storage\DerivedPageDataUpdater::getPreparedEdit()
|
|
|
|
|
*/
|
|
|
|
|
public function testGetPreparedEditAfterPrepareContent() {
|
|
|
|
|
$user = $this->getTestUser()->getUser();
|
|
|
|
|
|
|
|
|
|
$mainContent = new WikitextContent( 'first [[main]] ~~~' );
|
|
|
|
|
$update = new RevisionSlotsUpdate();
|
2018-09-24 21:10:08 +00:00
|
|
|
$update->modifyContent( SlotRecord::MAIN, $mainContent );
|
2018-01-27 01:48:19 +00:00
|
|
|
|
|
|
|
|
$updater = $this->getDerivedPageDataUpdater( __METHOD__ );
|
|
|
|
|
$updater->prepareContent( $user, $update, false );
|
|
|
|
|
|
|
|
|
|
$canonicalOutput = $updater->getCanonicalParserOutput();
|
|
|
|
|
|
|
|
|
|
$preparedEdit = $updater->getPreparedEdit();
|
|
|
|
|
$this->assertSame( $canonicalOutput->getCacheTime(), $preparedEdit->timestamp );
|
|
|
|
|
$this->assertSame( $canonicalOutput, $preparedEdit->output );
|
|
|
|
|
$this->assertSame( $mainContent, $preparedEdit->newContent );
|
2018-09-24 21:10:08 +00:00
|
|
|
$this->assertSame( $updater->getRawContent( SlotRecord::MAIN ), $preparedEdit->pstContent );
|
2018-01-27 01:48:19 +00:00
|
|
|
$this->assertSame( $updater->getCanonicalParserOptions(), $preparedEdit->popts );
|
|
|
|
|
$this->assertSame( null, $preparedEdit->revid );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @covers \MediaWiki\Storage\DerivedPageDataUpdater::getPreparedEdit()
|
|
|
|
|
*/
|
|
|
|
|
public function testGetPreparedEditAfterPrepareUpdate() {
|
2018-10-04 10:32:06 +00:00
|
|
|
$clock = MWTimestamp::convert( TS_UNIX, '20100101000000' );
|
|
|
|
|
MWTimestamp::setFakeTime( function () use ( &$clock ) {
|
|
|
|
|
return $clock++;
|
|
|
|
|
} );
|
|
|
|
|
|
2018-01-27 01:48:19 +00:00
|
|
|
$page = $this->getPage( __METHOD__ );
|
|
|
|
|
|
|
|
|
|
$mainContent = new WikitextContent( 'first [[main]] ~~~' );
|
|
|
|
|
$update = new MutableRevisionSlots();
|
2018-09-24 21:10:08 +00:00
|
|
|
$update->setContent( SlotRecord::MAIN, $mainContent );
|
2018-01-27 01:48:19 +00:00
|
|
|
|
|
|
|
|
$rev = $this->createRevision( $page, __METHOD__ );
|
|
|
|
|
|
|
|
|
|
$updater = $this->getDerivedPageDataUpdater( $page );
|
|
|
|
|
$updater->prepareUpdate( $rev );
|
|
|
|
|
|
|
|
|
|
$canonicalOutput = $updater->getCanonicalParserOutput();
|
|
|
|
|
|
|
|
|
|
$preparedEdit = $updater->getPreparedEdit();
|
|
|
|
|
$this->assertSame( $canonicalOutput->getCacheTime(), $preparedEdit->timestamp );
|
|
|
|
|
$this->assertSame( $canonicalOutput, $preparedEdit->output );
|
2018-09-24 21:10:08 +00:00
|
|
|
$this->assertSame( $updater->getRawContent( SlotRecord::MAIN ), $preparedEdit->pstContent );
|
2018-01-27 01:48:19 +00:00
|
|
|
$this->assertSame( $updater->getCanonicalParserOptions(), $preparedEdit->popts );
|
|
|
|
|
$this->assertSame( $rev->getId(), $preparedEdit->revid );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testGetSecondaryDataUpdatesAfterPrepareContent() {
|
|
|
|
|
$user = $this->getTestUser()->getUser();
|
|
|
|
|
$page = $this->getPage( __METHOD__ );
|
|
|
|
|
$this->createRevision( $page, __METHOD__ );
|
|
|
|
|
|
|
|
|
|
$mainContent1 = new WikitextContent( 'first' );
|
|
|
|
|
|
|
|
|
|
$update = new RevisionSlotsUpdate();
|
2018-09-24 21:10:08 +00:00
|
|
|
$update->modifyContent( SlotRecord::MAIN, $mainContent1 );
|
2018-01-27 01:48:19 +00:00
|
|
|
$updater = $this->getDerivedPageDataUpdater( $page );
|
|
|
|
|
$updater->prepareContent( $user, $update, false );
|
|
|
|
|
|
|
|
|
|
$dataUpdates = $updater->getSecondaryDataUpdates();
|
|
|
|
|
|
|
|
|
|
$this->assertNotEmpty( $dataUpdates );
|
|
|
|
|
|
|
|
|
|
$linksUpdates = array_filter( $dataUpdates, function ( $du ) {
|
|
|
|
|
return $du instanceof LinksUpdate;
|
|
|
|
|
} );
|
|
|
|
|
$this->assertCount( 1, $linksUpdates );
|
|
|
|
|
}
|
|
|
|
|
|
2018-03-09 22:05:47 +00:00
|
|
|
/**
|
|
|
|
|
* @param string $name
|
|
|
|
|
*
|
|
|
|
|
* @return ContentHandler
|
|
|
|
|
*/
|
|
|
|
|
private function defineMockContentModelForUpdateTesting( $name ) {
|
|
|
|
|
/** @var ContentHandler|MockObject $handler */
|
|
|
|
|
$handler = $this->getMockBuilder( TextContentHandler::class )
|
|
|
|
|
->setConstructorArgs( [ $name ] )
|
|
|
|
|
->setMethods(
|
|
|
|
|
[ 'getSecondaryDataUpdates', 'getDeletionUpdates', 'unserializeContent' ]
|
|
|
|
|
)
|
|
|
|
|
->getMock();
|
|
|
|
|
|
|
|
|
|
$dataUpdate = new MWCallableUpdate( 'time' );
|
|
|
|
|
$dataUpdate->_name = "$name data update";
|
|
|
|
|
|
|
|
|
|
$deletionUpdate = new MWCallableUpdate( 'time' );
|
|
|
|
|
$deletionUpdate->_name = "$name deletion update";
|
|
|
|
|
|
|
|
|
|
$handler->method( 'getSecondaryDataUpdates' )->willReturn( [ $dataUpdate ] );
|
|
|
|
|
$handler->method( 'getDeletionUpdates' )->willReturn( [ $deletionUpdate ] );
|
|
|
|
|
$handler->method( 'unserializeContent' )->willReturnCallback(
|
|
|
|
|
function ( $text ) use ( $handler ) {
|
|
|
|
|
return $this->createMockContent( $handler, $text );
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$this->mergeMwGlobalArrayValue(
|
|
|
|
|
'wgContentHandlers', [
|
|
|
|
|
$name => function () use ( $handler ){
|
|
|
|
|
return $handler;
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return $handler;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param ContentHandler $handler
|
|
|
|
|
* @param string $text
|
|
|
|
|
*
|
|
|
|
|
* @return Content
|
|
|
|
|
*/
|
|
|
|
|
private function createMockContent( ContentHandler $handler, $text ) {
|
|
|
|
|
/** @var Content|MockObject $content */
|
|
|
|
|
$content = $this->getMockBuilder( TextContent::class )
|
|
|
|
|
->setConstructorArgs( [ $text ] )
|
|
|
|
|
->setMethods( [ 'getModel', 'getContentHandler' ] )
|
|
|
|
|
->getMock();
|
|
|
|
|
|
|
|
|
|
$content->method( 'getModel' )->willReturn( $handler->getModelID() );
|
|
|
|
|
$content->method( 'getContentHandler' )->willReturn( $handler );
|
|
|
|
|
|
|
|
|
|
return $content;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testGetSecondaryDataUpdatesWithSlotRemoval() {
|
|
|
|
|
global $wgMultiContentRevisionSchemaMigrationStage;
|
|
|
|
|
|
|
|
|
|
if ( ! ( $wgMultiContentRevisionSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) ) {
|
|
|
|
|
$this->markTestSkipped( 'Slot removal cannot happen with MCR being enabled' );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$m1 = $this->defineMockContentModelForUpdateTesting( 'M1' );
|
|
|
|
|
$a1 = $this->defineMockContentModelForUpdateTesting( 'A1' );
|
|
|
|
|
$m2 = $this->defineMockContentModelForUpdateTesting( 'M2' );
|
|
|
|
|
|
|
|
|
|
$mainContent1 = $this->createMockContent( $m1, 'main 1' );
|
|
|
|
|
$auxContent1 = $this->createMockContent( $a1, 'aux 1' );
|
|
|
|
|
$mainContent2 = $this->createMockContent( $m2, 'main 2' );
|
|
|
|
|
|
|
|
|
|
$user = $this->getTestUser()->getUser();
|
|
|
|
|
$page = $this->getPage( __METHOD__ );
|
|
|
|
|
$this->createRevision(
|
|
|
|
|
$page,
|
|
|
|
|
__METHOD__,
|
|
|
|
|
[ 'main' => $mainContent1, 'aux' => $auxContent1 ]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$update = new RevisionSlotsUpdate();
|
2018-09-24 21:10:08 +00:00
|
|
|
$update->modifyContent( SlotRecord::MAIN, $mainContent2 );
|
2018-03-09 22:05:47 +00:00
|
|
|
$update->removeSlot( 'aux' );
|
|
|
|
|
|
|
|
|
|
$page = $this->getPage( __METHOD__ );
|
|
|
|
|
$updater = $this->getDerivedPageDataUpdater( $page );
|
|
|
|
|
$updater->prepareContent( $user, $update, false );
|
|
|
|
|
|
|
|
|
|
$dataUpdates = $updater->getSecondaryDataUpdates();
|
|
|
|
|
|
|
|
|
|
$this->assertNotEmpty( $dataUpdates );
|
|
|
|
|
|
|
|
|
|
$updateNames = array_map( function ( $du ) {
|
|
|
|
|
return isset( $du->_name ) ? $du->_name : get_class( $du );
|
|
|
|
|
}, $dataUpdates );
|
|
|
|
|
|
|
|
|
|
$this->assertContains( LinksUpdate::class, $updateNames );
|
|
|
|
|
$this->assertContains( 'A1 deletion update', $updateNames );
|
|
|
|
|
$this->assertContains( 'M2 data update', $updateNames );
|
|
|
|
|
$this->assertNotContains( 'M1 data update', $updateNames );
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-27 01:48:19 +00:00
|
|
|
/**
|
|
|
|
|
* Creates a dummy revision object without touching the database.
|
|
|
|
|
*
|
|
|
|
|
* @param Title $title
|
|
|
|
|
* @param RevisionSlotsUpdate $update
|
|
|
|
|
* @param User $user
|
|
|
|
|
* @param string $comment
|
|
|
|
|
* @param int $id
|
|
|
|
|
* @param int $parentId
|
|
|
|
|
*
|
|
|
|
|
* @return MutableRevisionRecord
|
|
|
|
|
*/
|
|
|
|
|
private function makeRevision(
|
|
|
|
|
Title $title,
|
|
|
|
|
RevisionSlotsUpdate $update,
|
|
|
|
|
User $user,
|
|
|
|
|
$comment,
|
|
|
|
|
$id,
|
|
|
|
|
$parentId = 0
|
|
|
|
|
) {
|
|
|
|
|
$rev = new MutableRevisionRecord( $title );
|
|
|
|
|
|
|
|
|
|
$rev->applyUpdate( $update );
|
|
|
|
|
$rev->setUser( $user );
|
|
|
|
|
$rev->setComment( CommentStoreComment::newUnsavedComment( $comment ) );
|
|
|
|
|
$rev->setId( $id );
|
|
|
|
|
$rev->setPageId( $title->getArticleID() );
|
|
|
|
|
$rev->setParentId( $parentId );
|
|
|
|
|
|
|
|
|
|
return $rev;
|
|
|
|
|
}
|
|
|
|
|
|
2018-08-31 04:56:42 +00:00
|
|
|
/**
|
|
|
|
|
* @param int $id
|
|
|
|
|
* @return Title
|
|
|
|
|
*/
|
|
|
|
|
private function getMockTitle( $id = 23 ) {
|
|
|
|
|
$mock = $this->getMockBuilder( Title::class )
|
|
|
|
|
->disableOriginalConstructor()
|
|
|
|
|
->getMock();
|
|
|
|
|
$mock->expects( $this->any() )
|
|
|
|
|
->method( 'getDBkey' )
|
|
|
|
|
->will( $this->returnValue( __CLASS__ ) );
|
|
|
|
|
$mock->expects( $this->any() )
|
|
|
|
|
->method( 'getArticleID' )
|
|
|
|
|
->will( $this->returnValue( $id ) );
|
|
|
|
|
|
|
|
|
|
return $mock;
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-27 01:48:19 +00:00
|
|
|
public function provideIsReusableFor() {
|
2018-08-31 04:56:42 +00:00
|
|
|
$title = $this->getMockTitle();
|
2018-01-27 01:48:19 +00:00
|
|
|
|
|
|
|
|
$user1 = User::newFromName( 'Alice' );
|
|
|
|
|
$user2 = User::newFromName( 'Bob' );
|
|
|
|
|
|
|
|
|
|
$content1 = new WikitextContent( 'one' );
|
|
|
|
|
$content2 = new WikitextContent( 'two' );
|
|
|
|
|
|
|
|
|
|
$update1 = new RevisionSlotsUpdate();
|
2018-09-24 21:10:08 +00:00
|
|
|
$update1->modifyContent( SlotRecord::MAIN, $content1 );
|
2018-01-27 01:48:19 +00:00
|
|
|
|
|
|
|
|
$update1b = new RevisionSlotsUpdate();
|
|
|
|
|
$update1b->modifyContent( 'xyz', $content1 );
|
|
|
|
|
|
|
|
|
|
$update2 = new RevisionSlotsUpdate();
|
2018-09-24 21:10:08 +00:00
|
|
|
$update2->modifyContent( SlotRecord::MAIN, $content2 );
|
2018-01-27 01:48:19 +00:00
|
|
|
|
|
|
|
|
$rev1 = $this->makeRevision( $title, $update1, $user1, 'rev1', 11 );
|
|
|
|
|
$rev1b = $this->makeRevision( $title, $update1b, $user1, 'rev1', 11 );
|
|
|
|
|
|
|
|
|
|
$rev2 = $this->makeRevision( $title, $update2, $user1, 'rev2', 12 );
|
|
|
|
|
$rev2x = $this->makeRevision( $title, $update2, $user2, 'rev2', 12 );
|
|
|
|
|
$rev2y = $this->makeRevision( $title, $update2, $user1, 'rev2', 122 );
|
|
|
|
|
|
|
|
|
|
yield 'any' => [
|
|
|
|
|
'$prepUser' => null,
|
|
|
|
|
'$prepRevision' => null,
|
|
|
|
|
'$prepUpdate' => null,
|
|
|
|
|
'$forUser' => null,
|
|
|
|
|
'$forRevision' => null,
|
|
|
|
|
'$forUpdate' => null,
|
|
|
|
|
'$forParent' => null,
|
|
|
|
|
'$isReusable' => true,
|
|
|
|
|
];
|
|
|
|
|
yield 'for any' => [
|
|
|
|
|
'$prepUser' => $user1,
|
|
|
|
|
'$prepRevision' => $rev1,
|
|
|
|
|
'$prepUpdate' => $update1,
|
|
|
|
|
'$forUser' => null,
|
|
|
|
|
'$forRevision' => null,
|
|
|
|
|
'$forUpdate' => null,
|
|
|
|
|
'$forParent' => null,
|
|
|
|
|
'$isReusable' => true,
|
|
|
|
|
];
|
|
|
|
|
yield 'unprepared' => [
|
|
|
|
|
'$prepUser' => null,
|
|
|
|
|
'$prepRevision' => null,
|
|
|
|
|
'$prepUpdate' => null,
|
|
|
|
|
'$forUser' => $user1,
|
|
|
|
|
'$forRevision' => $rev1,
|
|
|
|
|
'$forUpdate' => $update1,
|
|
|
|
|
'$forParent' => 0,
|
|
|
|
|
'$isReusable' => true,
|
|
|
|
|
];
|
|
|
|
|
yield 'match prepareContent' => [
|
|
|
|
|
'$prepUser' => $user1,
|
|
|
|
|
'$prepRevision' => null,
|
|
|
|
|
'$prepUpdate' => $update1,
|
|
|
|
|
'$forUser' => $user1,
|
|
|
|
|
'$forRevision' => null,
|
|
|
|
|
'$forUpdate' => $update1,
|
|
|
|
|
'$forParent' => 0,
|
|
|
|
|
'$isReusable' => true,
|
|
|
|
|
];
|
|
|
|
|
yield 'match prepareUpdate' => [
|
|
|
|
|
'$prepUser' => null,
|
|
|
|
|
'$prepRevision' => $rev1,
|
|
|
|
|
'$prepUpdate' => null,
|
|
|
|
|
'$forUser' => $user1,
|
|
|
|
|
'$forRevision' => $rev1,
|
|
|
|
|
'$forUpdate' => null,
|
|
|
|
|
'$forParent' => 0,
|
|
|
|
|
'$isReusable' => true,
|
|
|
|
|
];
|
|
|
|
|
yield 'match all' => [
|
|
|
|
|
'$prepUser' => $user1,
|
|
|
|
|
'$prepRevision' => $rev1,
|
|
|
|
|
'$prepUpdate' => $update1,
|
|
|
|
|
'$forUser' => $user1,
|
|
|
|
|
'$forRevision' => $rev1,
|
|
|
|
|
'$forUpdate' => $update1,
|
|
|
|
|
'$forParent' => 0,
|
|
|
|
|
'$isReusable' => true,
|
|
|
|
|
];
|
|
|
|
|
yield 'mismatch prepareContent update' => [
|
|
|
|
|
'$prepUser' => $user1,
|
|
|
|
|
'$prepRevision' => null,
|
|
|
|
|
'$prepUpdate' => $update1,
|
|
|
|
|
'$forUser' => $user1,
|
|
|
|
|
'$forRevision' => null,
|
|
|
|
|
'$forUpdate' => $update1b,
|
|
|
|
|
'$forParent' => 0,
|
|
|
|
|
'$isReusable' => false,
|
|
|
|
|
];
|
|
|
|
|
yield 'mismatch prepareContent user' => [
|
|
|
|
|
'$prepUser' => $user1,
|
|
|
|
|
'$prepRevision' => null,
|
|
|
|
|
'$prepUpdate' => $update1,
|
|
|
|
|
'$forUser' => $user2,
|
|
|
|
|
'$forRevision' => null,
|
|
|
|
|
'$forUpdate' => $update1,
|
|
|
|
|
'$forParent' => 0,
|
|
|
|
|
'$isReusable' => false,
|
|
|
|
|
];
|
|
|
|
|
yield 'mismatch prepareContent parent' => [
|
|
|
|
|
'$prepUser' => $user1,
|
|
|
|
|
'$prepRevision' => null,
|
|
|
|
|
'$prepUpdate' => $update1,
|
|
|
|
|
'$forUser' => $user1,
|
|
|
|
|
'$forRevision' => null,
|
|
|
|
|
'$forUpdate' => $update1,
|
|
|
|
|
'$forParent' => 7,
|
|
|
|
|
'$isReusable' => false,
|
|
|
|
|
];
|
|
|
|
|
yield 'mismatch prepareUpdate revision update' => [
|
|
|
|
|
'$prepUser' => null,
|
|
|
|
|
'$prepRevision' => $rev1,
|
|
|
|
|
'$prepUpdate' => null,
|
|
|
|
|
'$forUser' => null,
|
|
|
|
|
'$forRevision' => $rev1b,
|
|
|
|
|
'$forUpdate' => null,
|
|
|
|
|
'$forParent' => 0,
|
|
|
|
|
'$isReusable' => false,
|
|
|
|
|
];
|
|
|
|
|
yield 'mismatch prepareUpdate revision user' => [
|
|
|
|
|
'$prepUser' => null,
|
|
|
|
|
'$prepRevision' => $rev2,
|
|
|
|
|
'$prepUpdate' => null,
|
|
|
|
|
'$forUser' => null,
|
|
|
|
|
'$forRevision' => $rev2x,
|
|
|
|
|
'$forUpdate' => null,
|
|
|
|
|
'$forParent' => 0,
|
|
|
|
|
'$isReusable' => false,
|
|
|
|
|
];
|
|
|
|
|
yield 'mismatch prepareUpdate revision id' => [
|
|
|
|
|
'$prepUser' => null,
|
|
|
|
|
'$prepRevision' => $rev2,
|
|
|
|
|
'$prepUpdate' => null,
|
|
|
|
|
'$forUser' => null,
|
|
|
|
|
'$forRevision' => $rev2y,
|
|
|
|
|
'$forUpdate' => null,
|
|
|
|
|
'$forParent' => 0,
|
|
|
|
|
'$isReusable' => false,
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @dataProvider provideIsReusableFor
|
|
|
|
|
* @covers \MediaWiki\Storage\DerivedPageDataUpdater::isReusableFor()
|
|
|
|
|
*
|
|
|
|
|
* @param User|null $prepUser
|
|
|
|
|
* @param RevisionRecord|null $prepRevision
|
|
|
|
|
* @param RevisionSlotsUpdate|null $prepUpdate
|
|
|
|
|
* @param User|null $forUser
|
|
|
|
|
* @param RevisionRecord|null $forRevision
|
|
|
|
|
* @param RevisionSlotsUpdate|null $forUpdate
|
|
|
|
|
* @param int|null $forParent
|
|
|
|
|
* @param bool $isReusable
|
|
|
|
|
*/
|
|
|
|
|
public function testIsReusableFor(
|
|
|
|
|
User $prepUser = null,
|
|
|
|
|
RevisionRecord $prepRevision = null,
|
|
|
|
|
RevisionSlotsUpdate $prepUpdate = null,
|
|
|
|
|
User $forUser = null,
|
|
|
|
|
RevisionRecord $forRevision = null,
|
|
|
|
|
RevisionSlotsUpdate $forUpdate = null,
|
|
|
|
|
$forParent = null,
|
|
|
|
|
$isReusable = null
|
|
|
|
|
) {
|
|
|
|
|
$updater = $this->getDerivedPageDataUpdater( __METHOD__ );
|
|
|
|
|
|
|
|
|
|
if ( $prepUpdate ) {
|
|
|
|
|
$updater->prepareContent( $prepUser, $prepUpdate, false );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( $prepRevision ) {
|
|
|
|
|
$updater->prepareUpdate( $prepRevision );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$this->assertSame(
|
|
|
|
|
$isReusable,
|
|
|
|
|
$updater->isReusableFor( $forUser, $forRevision, $forUpdate, $forParent )
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @covers \MediaWiki\Storage\DerivedPageDataUpdater::doUpdates()
|
2018-08-28 15:34:25 +00:00
|
|
|
* @covers \MediaWiki\Storage\DerivedPageDataUpdater::doSecondaryDataUpdates()
|
|
|
|
|
* @covers \MediaWiki\Storage\DerivedPageDataUpdater::doParserCacheUpdate()
|
2018-01-27 01:48:19 +00:00
|
|
|
*/
|
|
|
|
|
public function testDoUpdates() {
|
|
|
|
|
$page = $this->getPage( __METHOD__ );
|
|
|
|
|
|
2018-08-07 16:52:40 +00:00
|
|
|
$content = [ 'main' => new WikitextContent( 'first [[main]]' ) ];
|
|
|
|
|
|
|
|
|
|
if ( $this->hasMultiSlotSupport() ) {
|
|
|
|
|
$content['aux'] = new WikitextContent( 'Aux [[Nix]]' );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$rev = $this->createRevision( $page, 'first', $content );
|
2018-01-27 01:48:19 +00:00
|
|
|
$pageId = $page->getId();
|
2018-08-07 16:52:40 +00:00
|
|
|
|
2018-01-27 01:48:19 +00:00
|
|
|
$oldStats = $this->db->selectRow( 'site_stats', '*', '1=1' );
|
2018-08-07 16:52:40 +00:00
|
|
|
$this->db->delete( 'pagelinks', '*' );
|
|
|
|
|
|
|
|
|
|
$pcache = MediaWikiServices::getInstance()->getParserCache();
|
|
|
|
|
$pcache->deleteOptionsKey( $page );
|
2018-01-27 01:48:19 +00:00
|
|
|
|
|
|
|
|
$updater = $this->getDerivedPageDataUpdater( $page, $rev );
|
|
|
|
|
$updater->setArticleCountMethod( 'link' );
|
|
|
|
|
|
|
|
|
|
$options = []; // TODO: test *all* the options...
|
|
|
|
|
$updater->prepareUpdate( $rev, $options );
|
|
|
|
|
|
|
|
|
|
$updater->doUpdates();
|
|
|
|
|
|
|
|
|
|
// links table update
|
2018-08-07 16:52:40 +00:00
|
|
|
$pageLinks = $this->db->select(
|
|
|
|
|
'pagelinks',
|
|
|
|
|
'*',
|
|
|
|
|
[ 'pl_from' => $pageId ],
|
|
|
|
|
__METHOD__,
|
|
|
|
|
[ 'ORDER BY' => 'pl_namespace, pl_title' ]
|
|
|
|
|
);
|
2018-01-27 01:48:19 +00:00
|
|
|
|
2018-08-07 16:52:40 +00:00
|
|
|
$pageLinksRow = $pageLinks->fetchObject();
|
2018-01-27 01:48:19 +00:00
|
|
|
$this->assertInternalType( 'object', $pageLinksRow );
|
|
|
|
|
$this->assertSame( 'Main', $pageLinksRow->pl_title );
|
|
|
|
|
|
2018-08-07 16:52:40 +00:00
|
|
|
if ( $this->hasMultiSlotSupport() ) {
|
|
|
|
|
$pageLinksRow = $pageLinks->fetchObject();
|
|
|
|
|
$this->assertInternalType( 'object', $pageLinksRow );
|
|
|
|
|
$this->assertSame( 'Nix', $pageLinksRow->pl_title );
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-27 01:48:19 +00:00
|
|
|
// parser cache update
|
|
|
|
|
$cached = $pcache->get( $page, $updater->getCanonicalParserOptions() );
|
|
|
|
|
$this->assertInternalType( 'object', $cached );
|
|
|
|
|
$this->assertSame( $updater->getCanonicalParserOutput(), $cached );
|
|
|
|
|
|
|
|
|
|
// site stats
|
|
|
|
|
$stats = $this->db->selectRow( 'site_stats', '*', '1=1' );
|
|
|
|
|
$this->assertSame( $oldStats->ss_total_pages + 1, (int)$stats->ss_total_pages );
|
|
|
|
|
$this->assertSame( $oldStats->ss_total_edits + 1, (int)$stats->ss_total_edits );
|
|
|
|
|
$this->assertSame( $oldStats->ss_good_articles + 1, (int)$stats->ss_good_articles );
|
|
|
|
|
|
|
|
|
|
// TODO: MCR: test data updates for additional slots!
|
|
|
|
|
// TODO: test update for edit without page creation
|
|
|
|
|
// TODO: test message cache purge
|
|
|
|
|
// TODO: test module cache purge
|
|
|
|
|
// TODO: test CDN purge
|
|
|
|
|
// TODO: test newtalk update
|
|
|
|
|
// TODO: test search update
|
|
|
|
|
// TODO: test site stats good_articles while turning the page into (or back from) a redir.
|
|
|
|
|
// TODO: test category membership update (with setRcWatchCategoryMembership())
|
|
|
|
|
}
|
|
|
|
|
|
2018-08-07 16:52:40 +00:00
|
|
|
private function hasMultiSlotSupport() {
|
|
|
|
|
global $wgMultiContentRevisionSchemaMigrationStage;
|
|
|
|
|
|
|
|
|
|
return ( $wgMultiContentRevisionSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW )
|
|
|
|
|
&& ( $wgMultiContentRevisionSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW );
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-27 01:48:19 +00:00
|
|
|
}
|