Page Menu
Home
WickedGov Phorge
Search
Configure Global Search
Log In
Files
F4107816
DerivedPageDataUpdaterTest.php
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
62 KB
Referenced Files
None
Subscribers
None
DerivedPageDataUpdaterTest.php
View Options
<?php
namespace
MediaWiki\Tests\Storage
;
use
ArrayUtils
;
use
DummyContentHandlerForTesting
;
use
MediaWiki\CommentStore\CommentStoreComment
;
use
MediaWiki\Config\ServiceOptions
;
use
MediaWiki\Content\Content
;
use
MediaWiki\Content\ContentHandler
;
use
MediaWiki\Content\JavaScriptContent
;
use
MediaWiki\Content\TextContent
;
use
MediaWiki\Content\TextContentHandler
;
use
MediaWiki\Content\WikitextContent
;
use
MediaWiki\Content\WikitextContentHandler
;
use
MediaWiki\Deferred\DeferredUpdates
;
use
MediaWiki\Deferred\LinksUpdate\LinksUpdate
;
use
MediaWiki\Deferred\MWCallableUpdate
;
use
MediaWiki\Edit\ParsoidRenderID
;
use
MediaWiki\Logging\LogPage
;
use
MediaWiki\Logging\ManualLogEntry
;
use
MediaWiki\MainConfigNames
;
use
MediaWiki\Message\Message
;
use
MediaWiki\Page\Event\PageRevisionUpdatedEvent
;
use
MediaWiki\Page\PageIdentity
;
use
MediaWiki\Page\PageIdentityValue
;
use
MediaWiki\Page\ParserOutputAccess
;
use
MediaWiki\Page\WikiPage
;
use
MediaWiki\Parser\ParserCacheFactory
;
use
MediaWiki\Parser\ParserOptions
;
use
MediaWiki\RecentChanges\RecentChange
;
use
MediaWiki\Revision\MutableRevisionRecord
;
use
MediaWiki\Revision\MutableRevisionSlots
;
use
MediaWiki\Revision\RevisionRecord
;
use
MediaWiki\Revision\SlotRecord
;
use
MediaWiki\Storage\DerivedPageDataUpdater
;
use
MediaWiki\Storage\EditResult
;
use
MediaWiki\Storage\EditResultCache
;
use
MediaWiki\Storage\RevisionSlotsUpdate
;
use
MediaWiki\Tests\ExpectCallbackTrait
;
use
MediaWiki\Title\Title
;
use
MediaWiki\User\User
;
use
MediaWiki\User\UserIdentity
;
use
MediaWiki\User\UserIdentityValue
;
use
MediaWiki\Utils\MWTimestamp
;
use
MediaWikiIntegrationTestCase
;
use
MockTitleTrait
;
use
PHPUnit\Framework\Assert
;
use
PHPUnit\Framework\MockObject\MockObject
;
use
Wikimedia\ObjectCache\BagOStuff
;
use
Wikimedia\Rdbms\Platform\ISQLPlatform
;
use
Wikimedia\TestingAccessWrapper
;
use
Wikimedia\Timestamp\ConvertibleTimestamp
;
/**
* @group Database
*
* @covers \MediaWiki\Storage\DerivedPageDataUpdater
*/
class
DerivedPageDataUpdaterTest
extends
MediaWikiIntegrationTestCase
{
use
MockTitleTrait
;
use
ExpectCallbackTrait
;
/**
* @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
$this
->
getServiceContainer
()->
getWikiPageFactory
()->
newFromTitle
(
$title
);
}
/**
* @param string|Title|WikiPage $page
* @param RevisionRecord|null $rec
* @param User|null $user
*
* @return DerivedPageDataUpdater
*/
private
function
getDerivedPageDataUpdater
(
$page
,
?
RevisionRecord
$rec
=
null
,
?
User
$user
=
null
)
{
if
(
is_string
(
$page
)
||
$page
instanceof
Title
)
{
$page
=
$this
->
getPage
(
$page
);
}
$page
=
TestingAccessWrapper
::
newFromObject
(
$page
);
return
$page
->
getDerivedDataUpdater
(
$user
,
$rec
);
}
/**
* Creates a revision in the database.
*
* @param WikiPage $page
* @param string|Message|CommentStoreComment $summary
* @param null|string|Content|Content[] $content
* @param User|null $user
*
* @return RevisionRecord|null
*/
private
function
createRevision
(
WikiPage
$page
,
$summary
,
$content
=
null
,
$user
=
null
)
{
$user
??=
$this
->
getTestUser
()->
getUser
();
$comment
=
CommentStoreComment
::
newUnsavedComment
(
$summary
);
if
(
$content
===
null
||
is_string
(
$content
)
)
{
$content
=
new
WikitextContent
(
$content
??
$summary
);
}
if
(
!
is_array
(
$content
)
)
{
$content
=
[
SlotRecord
::
MAIN
=>
$content
];
}
$this
->
getDerivedPageDataUpdater
(
$page
);
// flush cached instance before.
$updater
=
$page
->
newPageUpdater
(
$user
);
foreach
(
$content
as
$role
=>
$c
)
{
$updater
->
setContent
(
$role
,
$c
);
}
$rev
=
$updater
->
saveRevision
(
$comment
);
if
(
!
$updater
->
wasSuccessful
()
)
{
$this
->
fail
(
$updater
->
getStatus
()->
getWikiText
()
);
}
$this
->
getDerivedPageDataUpdater
(
$page
);
// flush cached instance after.
$this
->
runJobs
(
[
'minJobs'
=>
0
]
);
// flush pending updates
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
();
$update
->
modifyContent
(
SlotRecord
::
MAIN
,
$mainContent
);
$updater
=
$this
->
getDerivedPageDataUpdater
(
$page
);
$updater
->
prepareContent
(
$user
,
$update
,
false
);
$options1
=
$updater
->
getCanonicalParserOptions
();
$this
->
assertSame
(
$this
->
getServiceContainer
()->
getContentLanguage
(),
$options1
->
getUserLangObj
()
);
$speculativeId
=
$options1
->
getSpeculativeRevId
();
$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
=
$options2
->
getCurrentRevisionRecordCallback
()(
$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
()
{
$slotRoleRegistry
=
$this
->
getServiceContainer
()->
getSlotRoleRegistry
();
if
(
!
$slotRoleRegistry
->
isDefinedRole
(
'aux'
)
)
{
$slotRoleRegistry
->
defineRoleWithModel
(
'aux'
,
CONTENT_MODEL_WIKITEXT
);
}
$sysop
=
$this
->
getTestUser
(
[
'sysop'
]
)->
getUser
();
$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
();
$update
->
modifyContent
(
SlotRecord
::
MAIN
,
$mainContent
);
$update
->
modifySlot
(
SlotRecord
::
newInherited
(
$auxSlot
)
);
// TODO: MCR: test removing slots!
$updater
->
prepareContent
(
$sysop
,
$update
,
false
);
// second be ok to call again with the same params
$updater
->
prepareContent
(
$sysop
,
$update
,
false
);
$this
->
assertNull
(
$updater
->
grabCurrentRevision
()
);
$this
->
assertTrue
(
$updater
->
isContentPrepared
()
);
$this
->
assertFalse
(
$updater
->
isUpdatePrepared
()
);
$this
->
assertFalse
(
$updater
->
pageExisted
()
);
$this
->
assertTrue
(
$updater
->
isCreation
()
);
$this
->
assertTrue
(
$updater
->
isChange
()
);
$this
->
assertFalse
(
$updater
->
isContentDeleted
()
);
$this
->
assertNotNull
(
$updater
->
getRevision
()
);
$this
->
assertNotNull
(
$updater
->
getRenderedRevision
()
);
$this
->
assertEquals
(
[
SlotRecord
::
MAIN
,
'aux'
],
$updater
->
getSlots
()->
getSlotRoles
()
);
$this
->
assertEquals
(
[
SlotRecord
::
MAIN
],
array_keys
(
$updater
->
getSlots
()->
getOriginalSlots
()
)
);
$this
->
assertEquals
(
[
'aux'
],
array_keys
(
$updater
->
getSlots
()->
getInheritedSlots
()
)
);
$this
->
assertEquals
(
[
SlotRecord
::
MAIN
,
'aux'
],
$updater
->
getModifiedSlotRoles
()
);
$this
->
assertEquals
(
[
SlotRecord
::
MAIN
,
'aux'
],
$updater
->
getTouchedSlotRoles
()
);
$mainSlot
=
$updater
->
getRawSlot
(
SlotRecord
::
MAIN
);
$this
->
assertInstanceOf
(
SlotRecord
::
class
,
$mainSlot
);
$this
->
assertStringNotContainsString
(
'~~~'
,
$mainSlot
->
getContent
()->
serialize
(),
'PST should apply.'
);
$this
->
assertStringContainsString
(
$sysop
->
getName
(),
$mainSlot
->
getContent
()->
serialize
()
);
$auxSlot
=
$updater
->
getRawSlot
(
'aux'
);
$this
->
assertInstanceOf
(
SlotRecord
::
class
,
$auxSlot
);
$this
->
assertStringContainsString
(
'~~~'
,
$auxSlot
->
getContent
()->
serialize
(),
'No PST should apply.'
);
$mainOutput
=
$updater
->
getSlotParserOutput
(
SlotRecord
::
MAIN
);
$text
=
$mainOutput
->
getRawText
();
$this
->
assertStringContainsString
(
'first'
,
$text
);
$this
->
assertStringContainsString
(
'<a '
,
$text
);
$this
->
assertNotEmpty
(
$mainOutput
->
getLinks
()
);
$canonicalOutput
=
$updater
->
getCanonicalParserOutput
();
$text
=
$canonicalOutput
->
getRawText
();
$this
->
assertStringContainsString
(
'first'
,
$text
);
$this
->
assertStringContainsString
(
'<a '
,
$text
);
$this
->
assertStringContainsString
(
'inherited '
,
$text
);
$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
()
{
$sysop
=
$this
->
getTestUser
(
[
'sysop'
]
)->
getUser
();
$page
=
$this
->
getPage
(
__METHOD__
);
$mainContent1
=
new
WikitextContent
(
'first [[main]] ({{REVISIONUSER}}) #~~~#'
);
$mainContent2
=
new
WikitextContent
(
'second ({{subst:REVISIONUSER}}) #~~~#'
);
$rev
=
$this
->
createRevision
(
$page
,
'first'
,
$mainContent1
);
$mainContent1
=
$rev
->
getContent
(
SlotRecord
::
MAIN
);
// get post-pst content
$userName
=
$rev
->
getUser
()->
getName
();
$sysopName
=
$sysop
->
getName
();
$update
=
new
RevisionSlotsUpdate
();
$update
->
modifyContent
(
SlotRecord
::
MAIN
,
$mainContent1
);
$updater1
=
$this
->
getDerivedPageDataUpdater
(
$page
);
$updater1
->
prepareContent
(
$sysop
,
$update
,
false
);
$this
->
assertNotNull
(
$updater1
->
grabCurrentRevision
()
);
$this
->
assertTrue
(
$updater1
->
isContentPrepared
()
);
$this
->
assertTrue
(
$updater1
->
pageExisted
()
);
$this
->
assertFalse
(
$updater1
->
isCreation
()
);
$this
->
assertFalse
(
$updater1
->
isChange
()
);
$this
->
assertNotNull
(
$updater1
->
getRevision
()
);
$this
->
assertNotNull
(
$updater1
->
getRenderedRevision
()
);
// parser-output for null-edit uses the original author's name
$html
=
$updater1
->
getRenderedRevision
()->
getRevisionParserOutput
()->
getRawText
();
$this
->
assertStringNotContainsString
(
$sysopName
,
$html
,
'{{REVISIONUSER}}'
);
$this
->
assertStringNotContainsString
(
'{{REVISIONUSER}}'
,
$html
,
'{{REVISIONUSER}}'
);
$this
->
assertStringNotContainsString
(
'~~~'
,
$html
,
'signature ~~~'
);
$this
->
assertStringContainsString
(
'('
.
$userName
.
')'
,
$html
,
'{{REVISIONUSER}}'
);
$this
->
assertStringContainsString
(
'>'
.
$userName
.
'<'
,
$html
,
'signature ~~~'
);
// prepare forced dummy revision
$emptyUpdate
=
new
RevisionSlotsUpdate
();
$updater1
=
$this
->
getDerivedPageDataUpdater
(
$page
);
$updater1
->
setForceEmptyRevision
(
true
);
$updater1
->
prepareContent
(
$sysop
,
$emptyUpdate
,
false
);
// dummy revision inherits slots, but not revision ID
$mainSlot0
=
$rev
->
getSlot
(
SlotRecord
::
MAIN
);
$dummyRev
=
$updater1
->
getRevision
();
$dummySlot
=
$dummyRev
->
getSlot
(
SlotRecord
::
MAIN
);
$this
->
assertSame
(
$mainSlot0
->
getAddress
(),
$dummySlot
->
getAddress
()
);
$this
->
assertNull
(
$dummyRev
->
getId
()
);
$this
->
assertSame
(
$rev
->
getId
(),
$dummyRev
->
getParentId
()
);
// prepare non-null
$update
=
new
RevisionSlotsUpdate
();
$update
->
modifyContent
(
SlotRecord
::
MAIN
,
$mainContent2
);
$updater2
=
$this
->
getDerivedPageDataUpdater
(
$page
);
$updater2
->
prepareContent
(
$sysop
,
$update
,
false
);
// non-null edit use the new user name in PST
$pstText
=
$updater2
->
getSlots
()->
getContent
(
SlotRecord
::
MAIN
)->
serialize
();
$this
->
assertStringNotContainsString
(
'{{subst:REVISIONUSER}}'
,
$pstText
,
'{{subst:REVISIONUSER}}'
);
$this
->
assertStringNotContainsString
(
'~~~'
,
$pstText
,
'signature ~~~'
);
$this
->
assertStringContainsString
(
'('
.
$sysopName
.
')'
,
$pstText
,
'{{subst:REVISIONUSER}}'
);
$this
->
assertStringContainsString
(
':'
.
$sysopName
.
'|'
,
$pstText
,
'signature ~~~'
);
$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
()
);
$this
->
assertFalse
(
$updater1
->
isContentDeleted
()
);
$this
->
assertNotNull
(
$updater1
->
getRevision
()
);
$this
->
assertNotNull
(
$updater1
->
getRenderedRevision
()
);
$this
->
assertEquals
(
[
SlotRecord
::
MAIN
],
$updater1
->
getSlots
()->
getSlotRoles
()
);
$this
->
assertEquals
(
[
SlotRecord
::
MAIN
],
array_keys
(
$updater1
->
getSlots
()->
getOriginalSlots
()
)
);
$this
->
assertEquals
(
[],
array_keys
(
$updater1
->
getSlots
()->
getInheritedSlots
()
)
);
$this
->
assertEquals
(
[
SlotRecord
::
MAIN
],
$updater1
->
getModifiedSlotRoles
()
);
$this
->
assertEquals
(
[
SlotRecord
::
MAIN
],
$updater1
->
getTouchedSlotRoles
()
);
// TODO: MCR: test multiple slots, test slot removal!
$this
->
assertInstanceOf
(
SlotRecord
::
class
,
$updater1
->
getRawSlot
(
SlotRecord
::
MAIN
)
);
$this
->
assertStringNotContainsString
(
'~~~~'
,
$updater1
->
getRawContent
(
SlotRecord
::
MAIN
)->
serialize
()
);
$mainOutput
=
$updater1
->
getSlotParserOutput
(
SlotRecord
::
MAIN
);
$mainText
=
$mainOutput
->
getRawText
();
$this
->
assertStringContainsString
(
'first'
,
$mainText
);
$this
->
assertStringContainsString
(
'<a '
,
$mainText
);
$this
->
assertNotEmpty
(
$mainOutput
->
getLinks
()
);
$canonicalOutput
=
$updater1
->
getCanonicalParserOutput
();
$canonicalText
=
$canonicalOutput
->
getRawText
();
$this
->
assertStringContainsString
(
'first'
,
$canonicalText
);
$this
->
assertStringContainsString
(
'<a '
,
$canonicalText
);
$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
->
assertStringContainsString
(
'second'
,
$canonicalOutput
->
getRawText
()
);
}
/**
* @covers \MediaWiki\Storage\DerivedPageDataUpdater::prepareUpdate()
*/
public
function
testPrepareUpdate_null
()
{
$page
=
$this
->
getExistingTestPage
(
__METHOD__
);
$rev1
=
$page
->
getRevisionRecord
();
$page
=
$this
->
getPage
(
$page
->
getTitle
()
);
$updater1
=
$this
->
getDerivedPageDataUpdater
(
$page
,
$rev1
);
$editResult
=
new
EditResult
(
false
,
$rev1
->
getId
(),
null
,
null
,
null
,
false
,
true
,
[]
);
$options
=
[
'editResult'
=>
$editResult
,
'changed'
=>
false
];
$updater1
->
grabCurrentRevision
();
$updater1
->
prepareContent
(
$rev1
->
getUser
(),
RevisionSlotsUpdate
::
newFromContent
(
[
SlotRecord
::
MAIN
=>
$rev1
->
getMainContentRaw
()
]
)
);
$updater1
->
prepareUpdate
(
$rev1
,
$options
);
$this
->
assertTrue
(
$updater1
->
isUpdatePrepared
()
);
$this
->
assertTrue
(
$updater1
->
isContentPrepared
()
);
$this
->
assertFalse
(
$updater1
->
isCreation
()
);
$this
->
assertFalse
(
$updater1
->
isChange
()
);
$this
->
expectDomainEvent
(
PageRevisionUpdatedEvent
::
TYPE
,
1
,
static
function
(
PageRevisionUpdatedEvent
$event
)
use
(
$rev1
,
$editResult
)
{
Assert
::
assertSame
(
$rev1
,
$event
->
getLatestRevisionAfter
()
);
Assert
::
assertSame
(
$rev1
->
getId
(),
$event
->
getLatestRevisionBefore
()->
getId
()
);
Assert
::
assertSame
(
$editResult
,
$event
->
getEditResult
()
);
Assert
::
assertFalse
(
$event
->
isCreation
()
);
Assert
::
assertFalse
(
$event
->
changedLatestRevisionId
()
);
Assert
::
assertTrue
(
$event
->
isReconciliationRequest
()
);
}
);
$updater1
->
doUpdates
();
}
/**
* @covers \MediaWiki\Storage\DerivedPageDataUpdater::prepareUpdate()
*/
public
function
testPrepareUpdateReusesParserOutput
()
{
$user
=
$this
->
getTestUser
()->
getUser
();
$page
=
$this
->
getPage
(
__METHOD__
);
$mainContent1
=
new
WikitextContent
(
'first [[main]] ~~~'
);
$update
=
new
RevisionSlotsUpdate
();
$update
->
modifyContent
(
SlotRecord
::
MAIN
,
$mainContent1
);
$updater
=
$this
->
getDerivedPageDataUpdater
(
$page
);
$updater
->
prepareContent
(
$user
,
$update
,
false
);
$mainOutput
=
$updater
->
getSlotParserOutput
(
SlotRecord
::
MAIN
);
$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
()
);
$this
->
assertSame
(
$mainOutput
,
$updater
->
getSlotParserOutput
(
SlotRecord
::
MAIN
)
);
$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
();
$update
->
modifyContent
(
SlotRecord
::
MAIN
,
$mainContent1
);
$updater
=
$this
->
getDerivedPageDataUpdater
(
$page
);
$updater
->
prepareContent
(
$user
,
$update
,
false
);
$mainOutput
=
$updater
->
getSlotParserOutput
(
SlotRecord
::
MAIN
);
$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.
$this
->
assertNotSame
(
$mainOutput
,
$updater
->
getSlotParserOutput
(
SlotRecord
::
MAIN
)
);
$this
->
assertNotSame
(
$canonicalOutput
,
$updater
->
getCanonicalParserOutput
()
);
$html
=
$updater
->
getCanonicalParserOutput
()->
getRawText
();
$this
->
assertStringContainsString
(
'--'
.
$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
();
$update
->
modifyContent
(
SlotRecord
::
MAIN
,
$mainContent
);
$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
);
$this
->
assertSame
(
$updater
->
getRawContent
(
SlotRecord
::
MAIN
),
$preparedEdit
->
pstContent
);
$this
->
assertSame
(
$updater
->
getCanonicalParserOptions
(),
$preparedEdit
->
popts
);
$this
->
assertSame
(
null
,
$preparedEdit
->
revid
);
}
/**
* @covers \MediaWiki\Storage\DerivedPageDataUpdater::getPreparedEdit()
*/
public
function
testGetPreparedEditAfterPrepareUpdate
()
{
$clock
=
MWTimestamp
::
convert
(
TS_UNIX
,
'20100101000000'
);
MWTimestamp
::
setFakeTime
(
static
function
()
use
(
&
$clock
)
{
return
$clock
++;
}
);
$page
=
$this
->
getPage
(
__METHOD__
);
$mainContent
=
new
WikitextContent
(
'first [[main]] ~~~'
);
$update
=
new
MutableRevisionSlots
();
$update
->
setContent
(
SlotRecord
::
MAIN
,
$mainContent
);
$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
);
$this
->
assertSame
(
$updater
->
getRawContent
(
SlotRecord
::
MAIN
),
$preparedEdit
->
pstContent
);
$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
();
$update
->
modifyContent
(
SlotRecord
::
MAIN
,
$mainContent1
);
$updater
=
$this
->
getDerivedPageDataUpdater
(
$page
);
$updater
->
prepareContent
(
$user
,
$update
,
false
);
$dataUpdates
=
$updater
->
getSecondaryDataUpdates
();
$this
->
assertNotEmpty
(
$dataUpdates
);
$linksUpdates
=
array_filter
(
$dataUpdates
,
static
function
(
$du
)
{
return
$du
instanceof
LinksUpdate
;
}
);
$this
->
assertCount
(
1
,
$linksUpdates
);
}
public
function
testAvoidSecondaryDataUpdatesOnNonHTMLContentHandlers
()
{
$this
->
overrideConfigValue
(
MainConfigNames
::
ContentHandlers
,
[
CONTENT_MODEL_WIKITEXT
=>
[
'class'
=>
WikitextContentHandler
::
class
,
'services'
=>
[
'TitleFactory'
,
'ParserFactory'
,
'GlobalIdGenerator'
,
'LanguageNameUtils'
,
'LinkRenderer'
,
'MagicWordFactory'
,
'ParsoidParserFactory'
,
],
],
'testing'
=>
DummyContentHandlerForTesting
::
class
,
]
);
$user
=
$this
->
getTestUser
()->
getUser
();
$page
=
$this
->
getPage
(
__METHOD__
);
$this
->
createRevision
(
$page
,
__METHOD__
);
$contentHandler
=
new
DummyContentHandlerForTesting
(
'testing'
);
$mainContent1
=
$contentHandler
->
unserializeContent
(
serialize
(
'first'
)
);
$update
=
new
RevisionSlotsUpdate
();
$pcache
=
$this
->
getServiceContainer
()->
getParserCache
();
$pcache
->
deleteOptionsKey
(
$page
);
$rev
=
$this
->
createRevision
(
$page
,
'first'
,
$mainContent1
);
// Run updates
$update
->
modifyContent
(
SlotRecord
::
MAIN
,
$mainContent1
);
$updater
=
$this
->
getDerivedPageDataUpdater
(
$page
);
$updater
->
prepareContent
(
$user
,
$update
,
false
);
$dataUpdates
=
$updater
->
getSecondaryDataUpdates
();
$updater
->
prepareUpdate
(
$rev
);
$updater
->
doUpdates
();
// Links updates should be triggered
$this
->
assertNotEmpty
(
$dataUpdates
);
$linksUpdates
=
array_filter
(
$dataUpdates
,
static
function
(
$du
)
{
return
$du
instanceof
LinksUpdate
;
}
);
$this
->
assertCount
(
1
,
$linksUpdates
);
// Parser cache should not be populated.
$cached
=
$pcache
->
get
(
$page
,
$updater
->
getCanonicalParserOptions
()
);
$this
->
assertFalse
(
$cached
);
}
public
function
testGetSecondaryDataUpdatesDeleted
()
{
$user
=
$this
->
getTestUser
()->
getUser
();
$page
=
$this
->
getPage
(
__METHOD__
);
$this
->
createRevision
(
$page
,
__METHOD__
);
$mainContent1
=
new
WikitextContent
(
'first'
);
$update
=
new
RevisionSlotsUpdate
();
$update
->
modifyContent
(
SlotRecord
::
MAIN
,
$mainContent1
);
$updater
=
$this
->
getDerivedPageDataUpdater
(
$page
);
$updater
->
prepareContent
(
$user
,
$update
,
false
);
// Test that nothing happens if the page was deleted in the meantime
// This can happen when started by the job queue
$this
->
deletePage
(
$page
);
$dataUpdates
=
$updater
->
getSecondaryDataUpdates
();
$this
->
assertSame
(
[],
$dataUpdates
);
}
/**
* @param string $name
*
* @return ContentHandler
*/
private
function
defineMockContentModelForUpdateTesting
(
$name
)
{
/** @var ContentHandler|MockObject $handler */
$handler
=
$this
->
getMockBuilder
(
TextContentHandler
::
class
)
->
setConstructorArgs
(
[
$name
]
)
->
onlyMethods
(
[
'getSecondaryDataUpdates'
,
'getDeletionUpdates'
,
'unserializeContent'
]
)
->
getMock
();
$dataUpdate
=
new
MWCallableUpdate
(
'time'
,
"$name data update"
);
$deletionUpdate
=
new
MWCallableUpdate
(
'time'
,
"$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
=>
static
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
]
)
->
onlyMethods
(
[
'getModel'
,
'getContentHandler'
]
)
->
getMock
();
$content
->
method
(
'getModel'
)->
willReturn
(
$handler
->
getModelID
()
);
$content
->
method
(
'getContentHandler'
)->
willReturn
(
$handler
);
return
$content
;
}
public
function
testGetSecondaryDataUpdatesWithSlotRemoval
()
{
$m1
=
$this
->
defineMockContentModelForUpdateTesting
(
'M1'
);
$a1
=
$this
->
defineMockContentModelForUpdateTesting
(
'A1'
);
$m2
=
$this
->
defineMockContentModelForUpdateTesting
(
'M2'
);
$role
=
'dpdu-test-a1'
;
$slotRoleRegistry
=
$this
->
getServiceContainer
()->
getSlotRoleRegistry
();
$slotRoleRegistry
->
defineRoleWithModel
(
$role
,
$a1
->
getModelID
()
);
// pin the service instance for this test
$this
->
setService
(
'SlotRoleRegistry'
,
$slotRoleRegistry
);
$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__
,
[
SlotRecord
::
MAIN
=>
$mainContent1
,
$role
=>
$auxContent1
]
);
$update
=
new
RevisionSlotsUpdate
();
$update
->
modifyContent
(
SlotRecord
::
MAIN
,
$mainContent2
);
$update
->
removeSlot
(
$role
);
$page
=
$this
->
getPage
(
__METHOD__
);
$updater
=
$this
->
getDerivedPageDataUpdater
(
$page
);
$updater
->
prepareContent
(
$user
,
$update
,
false
);
$dataUpdates
=
$updater
->
getSecondaryDataUpdates
();
$this
->
assertNotEmpty
(
$dataUpdates
);
$updateNames
=
array_map
(
static
function
(
$du
)
{
return
$du
instanceof
MWCallableUpdate
?
$du
->
getOrigin
()
:
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
);
}
/**
* Creates a dummy MutableRevisionRecord without touching the database.
*
* @param PageIdentity $title
* @param string|Content|Content[]|RevisionSlotsUpdate $update
* @param UserIdentity|null $user
* @param string $comment
* @param int $id
* @param int $parentId
*
* @return MutableRevisionRecord
*/
private
function
makeRevision
(
PageIdentity
$title
,
$update
,
?
UserIdentity
$user
=
null
,
$comment
=
"testing"
,
$id
=
0
,
$parentId
=
0
)
{
$rev
=
new
MutableRevisionRecord
(
$title
);
if
(
$update
instanceof
RevisionSlotsUpdate
)
{
$rev
->
applyUpdate
(
$update
);
}
else
{
if
(
is_string
(
$update
)
)
{
$update
=
new
WikitextContent
(
$update
);
}
if
(
!
is_array
(
$update
)
)
{
$update
=
[
SlotRecord
::
MAIN
=>
$update
];
}
foreach
(
$update
as
$role
=>
$content
)
{
$rev
->
setContent
(
$role
,
$content
);
}
}
if
(
!
$user
)
{
$user
=
$this
->
getTestUser
()->
getUser
();
}
$rev
->
setUser
(
$user
);
$rev
->
setComment
(
CommentStoreComment
::
newUnsavedComment
(
$comment
)
);
$rev
->
setPageId
(
$title
->
getId
()
);
$rev
->
setParentId
(
$parentId
);
if
(
$id
)
{
$rev
->
setId
(
$id
);
}
return
$rev
;
}
public
static
function
provideIsReusableFor
()
{
$title
=
PageIdentityValue
::
localIdentity
(
1234
,
NS_MAIN
,
__CLASS__
);
$user1
=
new
UserIdentityValue
(
111
,
'Alice'
);
$user2
=
new
UserIdentityValue
(
222
,
'Bob'
);
$content1
=
new
WikitextContent
(
'one'
);
$content2
=
new
WikitextContent
(
'two'
);
$update1
=
new
RevisionSlotsUpdate
();
$update1
->
modifyContent
(
SlotRecord
::
MAIN
,
$content1
);
$update1b
=
new
RevisionSlotsUpdate
();
$update1b
->
modifyContent
(
'xyz'
,
$content1
);
$update2
=
new
RevisionSlotsUpdate
();
$update2
->
modifyContent
(
SlotRecord
::
MAIN
,
$content2
);
$rev1
=
[
$title
,
$update1
,
$user1
,
'rev1'
,
11
];
$rev1b
=
[
$title
,
$update1b
,
$user1
,
'rev1'
,
11
];
$rev2
=
[
$title
,
$update2
,
$user1
,
'rev2'
,
12
];
$rev2x
=
[
$title
,
$update2
,
$user2
,
'rev2'
,
12
];
$rev2y
=
[
$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 id'
=>
[
'$prepUser'
=>
null
,
'$prepRevision'
=>
$rev2
,
'$prepUpdate'
=>
null
,
'$forUser'
=>
null
,
'$forRevision'
=>
$rev2y
,
'$forUpdate'
=>
null
,
'$forParent'
=>
0
,
'$isReusable'
=>
false
,
];
}
/**
* @dataProvider provideIsReusableFor
* @covers \MediaWiki\Storage\DerivedPageDataUpdater::isReusableFor()
*/
public
function
testIsReusableFor
(
?
UserIdentity
$prepUser
,
?
array
$prepRevision
,
?
RevisionSlotsUpdate
$prepUpdate
,
?
UserIdentity
$forUser
,
?
array
$forRevision
,
?
RevisionSlotsUpdate
$forUpdate
,
$forParent
,
$isReusable
)
{
$updater
=
$this
->
getDerivedPageDataUpdater
(
__METHOD__
);
if
(
$prepUpdate
)
{
$updater
->
prepareContent
(
$prepUser
,
$prepUpdate
,
false
);
}
if
(
$prepRevision
)
{
$updater
->
prepareUpdate
(
$this
->
makeRevision
(
...
$prepRevision
)
);
}
if
(
$forRevision
)
{
$forRevision
=
$this
->
makeRevision
(
...
$forRevision
);
}
$this
->
assertSame
(
$isReusable
,
$updater
->
isReusableFor
(
$forUser
,
$forRevision
,
$forUpdate
,
$forParent
)
);
}
/**
* * @covers \MediaWiki\Storage\DerivedPageDataUpdater::isCountable
*/
public
function
testIsCountableNotContentPage
()
{
$updater
=
$this
->
getDerivedPageDataUpdater
(
Title
::
makeTitle
(
NS_TALK
,
'Main_Page'
)
);
self
::
assertFalse
(
$updater
->
isCountable
()
);
}
public
static
function
provideIsCountable
()
{
yield
'deleted revision'
=>
[
'$articleCountMethod'
=>
'any'
,
'$wikitextContent'
=>
'Test'
,
'$revisionVisibility'
=>
RevisionRecord
::
SUPPRESSED_ALL
,
'$isCountable'
=>
false
];
yield
'redirect'
=>
[
'$articleCountMethod'
=>
'any'
,
'$wikitextContent'
=>
'#REDIRECT [[Main_Page]]'
,
'$revisionVisibility'
=>
0
,
'$isCountable'
=>
false
];
yield
'no links count method any'
=>
[
'$articleCountMethod'
=>
'any'
,
'$wikitextContent'
=>
'Test'
,
'$revisionVisibility'
=>
0
,
'$isCountable'
=>
true
];
yield
'no links count method link'
=>
[
'$articleCountMethod'
=>
'link'
,
'$wikitextContent'
=>
'Test'
,
'$revisionVisibility'
=>
0
,
'$isCountable'
=>
false
];
yield
'with links count method link'
=>
[
'$articleCountMethod'
=>
'link'
,
'$wikitextContent'
=>
'[[Test]]'
,
'$revisionVisibility'
=>
0
,
'$isCountable'
=>
true
];
}
/**
* @dataProvider provideIsCountable
*
* @covers \MediaWiki\Storage\DerivedPageDataUpdater::isCountable
*/
public
function
testIsCountable
(
$articleCountMethod
,
$wikitextContent
,
$revisionVisibility
,
$isCountable
)
{
$this
->
overrideConfigValue
(
MainConfigNames
::
ArticleCountMethod
,
$articleCountMethod
);
$title
=
$this
->
getTitle
(
'Main_Page'
);
$content
=
new
WikitextContent
(
$wikitextContent
);
$update
=
new
RevisionSlotsUpdate
();
$update
->
modifyContent
(
SlotRecord
::
MAIN
,
$content
);
$revision
=
$this
->
makeRevision
(
$title
,
$update
,
User
::
newFromName
(
'Alice'
),
'rev1'
,
13
);
$revision
->
setVisibility
(
$revisionVisibility
);
$updater
=
$this
->
getDerivedPageDataUpdater
(
$title
);
$updater
->
prepareUpdate
(
$revision
);
self
::
assertSame
(
$isCountable
,
$updater
->
isCountable
()
);
}
/**
* @covers \MediaWiki\Storage\DerivedPageDataUpdater::isCountable
*/
public
function
testIsCountableNoModifiedSlots
()
{
$page
=
$this
->
getPage
(
__METHOD__
);
$content
=
[
SlotRecord
::
MAIN
=>
new
WikitextContent
(
'[[Test]]'
)
];
$rev
=
$this
->
createRevision
(
$page
,
'first'
,
$content
);
$nullRevision
=
MutableRevisionRecord
::
newFromParentRevision
(
$rev
);
$nullRevision
->
setId
(
14
);
$updater
=
$this
->
getDerivedPageDataUpdater
(
$page
,
$nullRevision
);
$updater
->
prepareUpdate
(
$nullRevision
);
$this
->
assertTrue
(
$updater
->
isCountable
()
);
}
/**
* @dataProvider provideDoUpdatesParams
*
* @covers \MediaWiki\Storage\DerivedPageDataUpdater::doUpdates()
* @covers \MediaWiki\Storage\DerivedPageDataUpdater::doSecondaryDataUpdates()
* @covers \MediaWiki\Storage\DerivedPageDataUpdater::doParserCacheUpdate()
*/
public
function
testDoUpdates
(
bool
$simulateNullEdit
,
bool
$simulatePageCreation
)
{
$page
=
$this
->
getPage
(
__METHOD__
);
$content
=
[
SlotRecord
::
MAIN
=>
new
WikitextContent
(
'current [[main]]'
)
];
$content
[
'aux'
]
=
new
WikitextContent
(
'Aux [[Nix]]'
);
$slotRoleRegistry
=
$this
->
getServiceContainer
()->
getSlotRoleRegistry
();
if
(
!
$slotRoleRegistry
->
isDefinedRole
(
'aux'
)
)
{
$slotRoleRegistry
->
defineRoleWithModel
(
'aux'
,
CONTENT_MODEL_WIKITEXT
);
}
// If simulating a null edit, set up a previous revision with the same content as our change.
// Otherwise, initialize the previous revision with different content unless simulating page creation,
// in which case no previous revision should be created at all.
if
(
!
$simulatePageCreation
)
{
$oldContent
=
$simulateNullEdit
?
$content
:
[
SlotRecord
::
MAIN
=>
new
WikitextContent
(
'first [[main]]'
)
];
$firstRev
=
$this
->
createRevision
(
$page
,
'first'
,
$oldContent
);
}
else
{
$firstRev
=
null
;
}
$slotsUpdate
=
RevisionSlotsUpdate
::
newFromContent
(
$content
);
$updater
=
$this
->
getServiceContainer
()
->
getPageUpdaterFactory
()
->
newDerivedPageDataUpdater
(
$page
);
$updater
->
prepareContent
(
$this
->
getTestUser
()->
getUserIdentity
(),
$slotsUpdate
);
// Don't create a new revision if simulating a null edit.
if
(
$simulateNullEdit
)
{
$rev
=
$firstRev
;
}
else
{
$rev
=
$this
->
createRevision
(
$page
,
'current'
,
$content
);
}
$pageId
=
$page
->
getId
();
$listenerCalled
=
0
;
$this
->
getServiceContainer
()->
getDomainEventSource
()->
registerListener
(
PageRevisionUpdatedEvent
::
TYPE
,
static
function
(
PageRevisionUpdatedEvent
$event
)
use
(
&
$listenerCalled
,
$page
)
{
$listenerCalled
++;
Assert
::
assertTrue
(
$page
->
isSamePageAs
(
$event
->
getPage
()
)
);
}
);
$oldStats
=
$this
->
getDb
()->
newSelectQueryBuilder
()
->
select
(
'*'
)
->
from
(
'site_stats'
)
->
where
(
'1=1'
)
->
fetchRow
();
$this
->
getDb
()->
newDeleteQueryBuilder
()
->
deleteFrom
(
'pagelinks'
)
->
where
(
ISQLPlatform
::
ALL_ROWS
)
->
caller
(
__METHOD__
)
->
execute
();
$pcache
=
$this
->
getServiceContainer
()->
getParserCache
();
$pcache
->
deleteOptionsKey
(
$page
);
$updater
->
setArticleCountMethod
(
'link'
);
$options
=
[];
// TODO: test *all* the options...
$updater
->
prepareUpdate
(
$rev
,
$options
);
$updater
->
doUpdates
();
// links table update
$pageLinks
=
$this
->
getDb
()->
newSelectQueryBuilder
()
->
select
(
'lt_title'
)
->
from
(
'pagelinks'
)
->
join
(
'linktarget'
,
null
,
'pl_target_id=lt_id'
)
->
where
(
[
'pl_from'
=>
$pageId
]
)
->
orderBy
(
[
'lt_namespace'
,
'lt_title'
]
)
->
caller
(
__METHOD__
)->
fetchResultSet
();
$pageLinksRow
=
$pageLinks
->
fetchObject
();
$this
->
assertIsObject
(
$pageLinksRow
);
$this
->
assertSame
(
'Main'
,
$pageLinksRow
->
lt_title
);
$pageLinksRow
=
$pageLinks
->
fetchObject
();
$this
->
assertIsObject
(
$pageLinksRow
);
$this
->
assertSame
(
'Nix'
,
$pageLinksRow
->
lt_title
);
// parser cache update
$cached
=
$pcache
->
get
(
$page
,
$updater
->
getCanonicalParserOptions
()
);
$this
->
assertIsObject
(
$cached
);
$expected
=
$updater
->
getCanonicalParserOutput
();
$expected
->
clearParseStartTime
();
$this
->
assertEquals
(
$expected
,
$cached
);
// site stats
$stats
=
$this
->
getDb
()->
newSelectQueryBuilder
()
->
select
(
'*'
)
->
from
(
'site_stats'
)
->
where
(
'1=1'
)
->
fetchRow
();
if
(
$simulatePageCreation
)
{
$this
->
assertSame
(
$oldStats
->
ss_total_pages
+
1
,
(
int
)
$stats
->
ss_total_pages
);
$this
->
assertSame
(
$oldStats
->
ss_good_articles
+
1
,
(
int
)
$stats
->
ss_good_articles
);
}
else
{
$this
->
assertSame
(
$oldStats
->
ss_total_pages
,
$stats
->
ss_total_pages
);
$this
->
assertSame
(
$oldStats
->
ss_good_articles
,
$stats
->
ss_good_articles
);
}
if
(
!
$simulateNullEdit
)
{
$this
->
assertSame
(
$oldStats
->
ss_total_edits
+
1
,
(
int
)
$stats
->
ss_total_edits
);
}
else
{
$this
->
assertSame
(
$oldStats
->
ss_total_edits
,
$stats
->
ss_total_edits
);
}
$this
->
runDeferredUpdates
();
$this
->
assertSame
(
1
,
$listenerCalled
,
'PageRevisionUpdatedEvent listener'
);
// 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.
}
public
static
function
provideDoUpdatesParams
():
iterable
{
$testCases
=
ArrayUtils
::
cartesianProduct
(
// null or non-null edit
[
true
,
false
],
// page creation
[
true
,
false
]
);
foreach
(
$testCases
as
$params
)
{
[
$simulateNullEdit
,
$simulatePageCreation
]
=
$params
;
if
(
$simulateNullEdit
&&
$simulatePageCreation
)
{
// Page creations cannot be null edits, so don't simulate an impossible scenario
continue
;
}
$description
=
sprintf
(
'%s edit%s'
,
$simulateNullEdit
?
'null'
:
'non-null'
,
$simulatePageCreation
?
', page creation, '
:
''
);
yield
$description
=>
$params
;
}
}
/**
* @covers \MediaWiki\Storage\DerivedPageDataUpdater::emitEvents()
*/
public
function
testDispatchPageRevisionUpdatedEvent
()
{
$page
=
$this
->
getPage
(
__METHOD__
);
$content
=
[
SlotRecord
::
MAIN
=>
new
WikitextContent
(
'first [[main]]'
)
];
$rev
=
$this
->
createRevision
(
$page
,
'first'
,
$content
);
$listenerCalled
=
0
;
$this
->
getServiceContainer
()->
getDomainEventSource
()->
registerListener
(
PageRevisionUpdatedEvent
::
TYPE
,
static
function
(
PageRevisionUpdatedEvent
$event
)
use
(
&
$listenerCalled
,
$page
)
{
$listenerCalled
++;
Assert
::
assertTrue
(
$page
->
isSamePageAs
(
$event
->
getPage
()
)
);
}
);
$updater
=
$this
->
getDerivedPageDataUpdater
(
$page
,
$rev
);
$updater
->
prepareUpdate
(
$rev
);
// Dispatch PageRevisionUpdatedEvent explicitly, then assert that doUpdates()
// doesn't dispatch it again.
$updater
->
emitEvents
();
$updater
->
doUpdates
();
$this
->
runDeferredUpdates
();
$this
->
assertSame
(
1
,
$listenerCalled
,
'PageRevisionUpdatedEvent listener'
);
}
/**
* @covers \MediaWiki\Storage\DerivedPageDataUpdater::doUpdates()
* @covers \MediaWiki\Storage\DerivedPageDataUpdater::doSecondaryDataUpdates()
* @covers \MediaWiki\Storage\DerivedPageDataUpdater::doParserCacheUpdate()
*/
public
function
testDoUpdatesCacheSaveDeferral_canonical
()
{
$page
=
$this
->
getPage
(
__METHOD__
);
// Case where user has canonical parser options
$content
=
[
SlotRecord
::
MAIN
=>
new
WikitextContent
(
'rev ID ver #1: {{REVISIONID}}'
)
];
$rev
=
$this
->
createRevision
(
$page
,
'first'
,
$content
);
$pcache
=
$this
->
getServiceContainer
()->
getParserCache
();
$pcache
->
deleteOptionsKey
(
$page
);
$this
->
getDb
()->
startAtomic
(
__METHOD__
);
// let deferred updates queue up
$updater
=
$this
->
getDerivedPageDataUpdater
(
$page
,
$rev
);
$updater
->
prepareUpdate
(
$rev
,
[]
);
$updater
->
doUpdates
();
$this
->
assertGreaterThan
(
0
,
DeferredUpdates
::
pendingUpdatesCount
(),
'Pending updates'
);
$this
->
assertNotFalse
(
$pcache
->
get
(
$page
,
$updater
->
getCanonicalParserOptions
()
)
);
$this
->
getDb
()->
endAtomic
(
__METHOD__
);
// run deferred updates
$this
->
runDeferredUpdates
();
$this
->
assertSame
(
0
,
DeferredUpdates
::
pendingUpdatesCount
(),
'No pending updates'
);
}
/**
* @covers \MediaWiki\Storage\DerivedPageDataUpdater::doUpdates()
* @covers \MediaWiki\Storage\DerivedPageDataUpdater::doSecondaryDataUpdates()
* @covers \MediaWiki\Storage\DerivedPageDataUpdater::doParserCacheUpdate()
*/
public
function
testDoUpdatesCacheSaveDeferral_noncanonical
()
{
$page
=
$this
->
getPage
(
__METHOD__
);
// Case where user does not have canonical parser options
$user
=
$this
->
getMutableTestUser
()->
getUser
();
$services
=
$this
->
getServiceContainer
();
$userOptionsManager
=
$services
->
getUserOptionsManager
();
$userOptionsManager
->
setOption
(
$user
,
'thumbsize'
,
$userOptionsManager
->
getOption
(
$user
,
'thumbsize'
)
+
1
);
$content
=
[
SlotRecord
::
MAIN
=>
new
WikitextContent
(
'rev ID ver #2: {{REVISIONID}}'
)
];
$rev
=
$this
->
createRevision
(
$page
,
'first'
,
$content
,
$user
);
$pcache
=
$services
->
getParserCache
();
$pcache
->
deleteOptionsKey
(
$page
);
$this
->
getDb
()->
startAtomic
(
__METHOD__
);
// let deferred updates queue up
$updater
=
$this
->
getDerivedPageDataUpdater
(
$page
,
$rev
,
$user
);
$updater
->
prepareUpdate
(
$rev
,
[]
);
$updater
->
doUpdates
();
$this
->
assertGreaterThan
(
1
,
DeferredUpdates
::
pendingUpdatesCount
(),
'Pending updates'
);
$this
->
assertFalse
(
$pcache
->
get
(
$page
,
$updater
->
getCanonicalParserOptions
()
)
);
$this
->
getDb
()->
endAtomic
(
__METHOD__
);
// run deferred updates
$this
->
runDeferredUpdates
();
$this
->
assertSame
(
0
,
DeferredUpdates
::
pendingUpdatesCount
(),
'No pending updates'
);
$this
->
assertNotFalse
(
$pcache
->
get
(
$page
,
$updater
->
getCanonicalParserOptions
()
)
);
}
/**
* @covers \MediaWiki\Storage\DerivedPageDataUpdater::doUpdates()
* @covers \MediaWiki\Storage\DerivedPageDataUpdater::maybeAddRecreateChangeTag
*/
public
function
testDoUpdatesTagsEditAsRecreatedWhenDeletionLogEntry
()
{
$page
=
$this
->
getPage
(
__METHOD__
);
$title
=
$this
->
getTitle
(
__METHOD__
);
$content
=
[
SlotRecord
::
MAIN
=>
new
WikitextContent
(
'rev ID ver #1: {{REVISIONID}}'
)
];
// create a deletion log entry
$deleteLogEntry
=
new
ManualLogEntry
(
'delete'
,
'delete'
);
$deleteLogEntry
->
setPerformer
(
$this
->
getTestUser
()->
getUser
()
);
$deleteLogEntry
->
setTarget
(
$title
);
$logId
=
$deleteLogEntry
->
insert
(
$this
->
getDb
()
);
$deleteLogEntry
->
publish
(
$logId
);
$rev
=
$this
->
createRevision
(
$page
,
'first'
,
$content
);
$this
->
assertSame
(
[
'mw-recreated'
],
$this
->
getServiceContainer
()->
getChangeTagsStore
()->
getTags
(
$this
->
getDb
(),
null
,
$rev
->
getId
()
)
);
}
/**
* @covers \MediaWiki\Storage\DerivedPageDataUpdater::doUpdates()
* @covers \MediaWiki\Storage\DerivedPageDataUpdater::maybeAddRecreateChangeTag
*/
public
function
testDoUpdatesDoesNotTagEditAsRecreatedWhenNotNewPageCreation
()
{
$page
=
$this
->
getPage
(
__METHOD__
);
$title
=
$this
->
getTitle
(
__METHOD__
);
$content
=
[
SlotRecord
::
MAIN
=>
new
WikitextContent
(
'rev ID ver #1: {{REVISIONID}}'
)
];
$content2
=
[
SlotRecord
::
MAIN
=>
new
WikitextContent
(
'rev ID ver #2: {{REVISIONID}}'
)
];
$this
->
createRevision
(
$page
,
'first'
,
$content
);
// create a deletion log entry
$deleteLogEntry
=
new
ManualLogEntry
(
'delete'
,
'delete'
);
$deleteLogEntry
->
setPerformer
(
$this
->
getTestUser
()->
getUser
()
);
$deleteLogEntry
->
setTarget
(
$title
);
$logId
=
$deleteLogEntry
->
insert
(
$this
->
getDb
()
);
$deleteLogEntry
->
publish
(
$logId
);
$rev
=
$this
->
createRevision
(
$page
,
'second'
,
$content2
);
$this
->
assertSame
(
[],
$this
->
getServiceContainer
()->
getChangeTagsStore
()->
getTags
(
$this
->
getDb
(),
null
,
$rev
->
getId
()
)
);
}
/**
* @covers \MediaWiki\Storage\DerivedPageDataUpdater::doUpdates()
* @covers \MediaWiki\Storage\DerivedPageDataUpdater::maybeAddRecreateChangeTag
*/
public
function
testDoUpdatesDoesNotTagEditAsRecreatedWhenDeletionLogEntryAndUndelete
()
{
$page
=
$this
->
getPage
(
__METHOD__
);
$title
=
$this
->
getTitle
(
__METHOD__
);
$user
=
$this
->
getMutableTestUser
()->
getUser
();
$mediaWikiServices
=
$this
->
getServiceContainer
();
$changeTagsStore
=
$mediaWikiServices
->
getChangeTagsStore
();
$content
=
[
SlotRecord
::
MAIN
=>
new
WikitextContent
(
'rev ID ver #1: {{REVISIONID}}'
)
];
// create a revision on the page
$this
->
createRevision
(
$page
,
'first'
,
$content
);
// make sure no tags on initial revision
$this
->
assertSame
(
[],
$changeTagsStore
->
getTags
(
$this
->
getDb
(),
null
,
$page
->
getRevisionRecord
()->
getId
()
)
);
// create a deletion log entry
$deleteLogEntry
=
new
ManualLogEntry
(
'delete'
,
'delete'
);
$deleteLogEntry
->
setPerformer
(
$this
->
getTestUser
()->
getUser
()
);
$deleteLogEntry
->
setTarget
(
$title
);
$logId
=
$deleteLogEntry
->
insert
(
$this
->
getDb
()
);
$deleteLogEntry
->
publish
(
$logId
);
// undelete the page
$mediaWikiServices
->
getUndeletePageFactory
()
->
newUndeletePage
(
$page
,
$user
);
// ensure revision after undelete is not tagged with recreate
$this
->
assertSame
(
[],
$changeTagsStore
->
getTags
(
$this
->
getDb
(),
null
,
$page
->
getRevisionRecord
()->
getId
()
)
);
}
/**
* @covers \MediaWiki\Storage\DerivedPageDataUpdater::doUpdates()
* @covers \MediaWiki\Storage\DerivedPageDataUpdater::maybeAddRecreateChangeTag
*/
public
function
testDoUpdatesDoesNotTagEditAsRecreatedWhenNoDeletionLogEntry
()
{
$page
=
$this
->
getPage
(
__METHOD__
);
$content
=
[
SlotRecord
::
MAIN
=>
new
WikitextContent
(
'rev ID ver #1: {{REVISIONID}}'
)
];
$rev
=
$this
->
createRevision
(
$page
,
'first'
,
$content
);
$this
->
assertSame
(
[],
$this
->
getServiceContainer
()->
getChangeTagsStore
()->
getTags
(
$this
->
getDb
(),
null
,
$rev
->
getId
()
)
);
}
/**
* See T385792
*
* @covers \MediaWiki\Storage\DerivedPageDataUpdater::doUpdates()
* @covers \MediaWiki\Storage\DerivedPageDataUpdater::maybeAddRecreateChangeTag
*/
public
function
testDoUpdatesDoesNotTagEditAsRecreatedWhenDeletionLogEntryActionHidden
()
{
$page
=
$this
->
getPage
(
__METHOD__
);
$title
=
$this
->
getTitle
(
__METHOD__
);
$content
=
[
SlotRecord
::
MAIN
=>
new
WikitextContent
(
'rev ID ver #1: {{REVISIONID}}'
)
];
// create a deletion log entry
$deleteLogEntry
=
new
ManualLogEntry
(
'delete'
,
'delete'
);
$deleteLogEntry
->
setPerformer
(
$this
->
getTestUser
()->
getUser
()
);
$deleteLogEntry
->
setTarget
(
$title
);
// hide the target of the deletion log entry
$deleteLogEntry
->
setDeleted
(
LogPage
::
DELETED_ACTION
);
$logId
=
$deleteLogEntry
->
insert
(
$this
->
getDb
()
);
$deleteLogEntry
->
publish
(
$logId
);
$rev
=
$this
->
createRevision
(
$page
,
'first'
,
$content
);
$this
->
assertSame
(
[],
$this
->
getServiceContainer
()->
getChangeTagsStore
()->
getTags
(
$this
->
getDb
(),
null
,
$rev
->
getId
()
)
);
}
public
static
function
provideEnqueueRevertedTagUpdateJob
()
{
return
[
'not patrolled'
=>
[
true
,
0
,
0
],
'patrolled'
=>
[
true
,
RecentChange
::
PRC_AUTOPATROLLED
,
1
],
'autopatrolled'
=>
[
true
,
RecentChange
::
PRC_AUTOPATROLLED
,
1
],
'patrolling disabled'
=>
[
false
,
0
,
1
]
];
}
/**
* @covers \MediaWiki\Storage\DerivedPageDataUpdater::doUpdates
* @covers \MediaWiki\RecentChanges\ChangeTrackingEventIngress::updateRevertTagAfterPageUpdated
* @dataProvider provideEnqueueRevertedTagUpdateJob
*/
public
function
testEnqueueRevertedTagUpdateJob
(
bool
$useRcPatrol
,
int
$rcPatrolStatus
,
int
$expectQueueSize
)
{
$this
->
overrideConfigValue
(
MainConfigNames
::
UseRCPatrol
,
$useRcPatrol
);
$page
=
$this
->
getPage
(
__METHOD__
);
$content
=
[
SlotRecord
::
MAIN
=>
new
WikitextContent
(
'1'
)
];
$rev
=
$this
->
createRevision
(
$page
,
''
,
$content
);
$editResult
=
new
EditResult
(
false
,
10
,
EditResult
::
REVERT_ROLLBACK
,
11
,
12
,
true
,
false
,
[
'mw-rollback'
]
);
$updater
=
$this
->
getDerivedPageDataUpdater
(
$page
,
$rev
);
$updater
->
prepareUpdate
(
$rev
,
[
'editResult'
=>
$editResult
,
'rcPatrolStatus'
=>
$rcPatrolStatus
,
]
);
$updater
->
doUpdates
();
$services
=
$this
->
getServiceContainer
();
$editResultCache
=
new
EditResultCache
(
$services
->
getMainObjectStash
(),
$services
->
getConnectionProvider
(),
new
ServiceOptions
(
EditResultCache
::
CONSTRUCTOR_OPTIONS
,
[
MainConfigNames
::
RCMaxAge
=>
BagOStuff
::
TTL_MONTH
]
)
);
if
(
$useRcPatrol
)
{
$this
->
assertEquals
(
$editResult
,
$editResultCache
->
get
(
$rev
->
getId
()
),
'EditResult should be cached if patrolling is enabled'
);
}
else
{
$this
->
assertNull
(
$editResultCache
->
get
(
$rev
->
getId
()
),
'EditResult should not be cached if patrolling is disabled'
);
}
$jobQueueGroup
=
$this
->
getServiceContainer
()->
getJobQueueGroup
();
$jobQueue
=
$jobQueueGroup
->
get
(
'revertedTagUpdate'
);
$this
->
assertSame
(
$expectQueueSize
,
$jobQueue
->
getSize
()
);
}
/**
* @covers \MediaWiki\Storage\DerivedPageDataUpdater::doParserCacheUpdate()
* @covers \MediaWiki\JobQueue\Jobs\ParsoidCachePrewarmJob::doParsoidCacheUpdate()
*/
public
function
testDoParserCacheUpdate
()
{
$this
->
overrideConfigValue
(
MainConfigNames
::
ParsoidCacheConfig
,
[
'CacheThresholdTime'
=>
0.0
,
'WarmParsoidParserCache'
=>
true
,
// enable caching
]
);
$slotRoleRegistry
=
$this
->
getServiceContainer
()->
getSlotRoleRegistry
();
if
(
!
$slotRoleRegistry
->
isDefinedRole
(
'aux'
)
)
{
$slotRoleRegistry
->
defineRoleWithModel
(
'aux'
,
CONTENT_MODEL_WIKITEXT
);
}
// Create page
$page
=
$this
->
getPage
(
__METHOD__
);
ConvertibleTimestamp
::
setFakeTime
(
'2022-01-01T00:01:00Z'
);
$this
->
createRevision
(
$page
,
'Dummy'
);
// Assert cache update after edit ----------
$parserCacheFactory
=
$this
->
getServiceContainer
()->
getParserCacheFactory
();
$parserCache
=
$parserCacheFactory
->
getParserCache
(
ParserCacheFactory
::
DEFAULT_NAME
);
$parsoidCache
=
$parserCacheFactory
->
getParserCache
(
"parsoid-"
.
ParserCacheFactory
::
DEFAULT_NAME
);
$parserCache
->
deleteOptionsKey
(
$page
);
$parsoidCache
->
deleteOptionsKey
(
$page
);
$user
=
$this
->
getTestUser
()->
getUser
();
ConvertibleTimestamp
::
setFakeTime
(
'2022-01-01T00:02:00Z'
);
$updater
=
$page
->
newPageUpdater
(
$user
);
$updater
->
setContent
(
SlotRecord
::
MAIN
,
new
WikitextContent
(
'first [[Main]]'
)
);
$updater
->
setContent
(
'aux'
,
new
WikitextContent
(
'Aux [[Nix]]'
)
);
$rev
=
$updater
->
saveRevision
(
CommentStoreComment
::
newUnsavedComment
(
'testing'
)
);
// run all the jobs
ConvertibleTimestamp
::
setFakeTime
(
'2022-01-01T00:03:00Z'
);
$this
->
runJobs
();
// Parsoid cache should have an entry
$parserOptions
=
ParserOptions
::
newFromAnon
();
$parserOptions
->
setUseParsoid
();
$parsoidCached
=
$parsoidCache
->
get
(
$page
,
$parserOptions
,
true
);
$this
->
assertIsObject
(
$parsoidCached
);
$this
->
assertStringContainsString
(
'first'
,
$parsoidCached
->
getRawText
()
);
// The parsoid parser output is generated during runJobs(), after the last call to setFakeTime().
$this
->
assertGreaterThan
(
$rev
->
getTimestamp
(),
$parsoidCached
->
getCacheTime
()
);
$this
->
assertSame
(
$rev
->
getId
(),
$parsoidCached
->
getCacheRevisionId
()
);
// Check that ParsoidRenderID::newFromParserOutput() doesn't throw,
// so we know that $parsoidCached is valid.
ParsoidRenderID
::
newFromParserOutput
(
$parsoidCached
);
// The cached ParserOutput should not use the revision timestamp
// Create nwe ParserOptions object since we setUseParsoid() above
$parserOptions
=
ParserOptions
::
newFromAnon
();
$cached
=
$parserCache
->
get
(
$page
,
$parserOptions
,
true
);
$this
->
assertIsObject
(
$cached
);
$this
->
assertNotSame
(
$parsoidCached
,
$cached
);
$this
->
assertStringContainsString
(
'first'
,
$cached
->
getRawText
()
);
// The regular parser output is generated immediately during saveRevision(),
// so it uses the same timestamp as the revision.
$this
->
assertSame
(
$rev
->
getTimestamp
(),
$cached
->
getCacheTime
()
);
$this
->
assertSame
(
$rev
->
getId
(),
$cached
->
getCacheRevisionId
()
);
// Emulate forced update of an old revision ----------
ConvertibleTimestamp
::
setFakeTime
(
'2022-01-01T00:04:00Z'
);
$parserCache
->
deleteOptionsKey
(
$page
);
$updater
=
$this
->
getDerivedPageDataUpdater
(
$page
);
$updater
->
prepareUpdate
(
$rev
);
// Force the page timestamp, so we notice whether ParserOutput::getTimestamp
// or ParserOutput::getCacheTime are used.
$page
->
setTimestamp
(
$rev
->
getTimestamp
()
);
$updater
->
doParserCacheUpdate
();
// The cached ParserOutput should not use the revision timestamp
$cached
=
$parserCache
->
get
(
$page
,
$updater
->
getCanonicalParserOptions
(),
true
);
$this
->
assertIsObject
(
$cached
);
$expected
=
$updater
->
getCanonicalParserOutput
();
$expected
->
clearParseStartTime
();
$this
->
assertEquals
(
$expected
,
$cached
);
$this
->
assertGreaterThan
(
$rev
->
getTimestamp
(),
$cached
->
getCacheTime
()
);
$this
->
assertSame
(
$rev
->
getId
(),
$cached
->
getCacheRevisionId
()
);
}
/**
* @covers \MediaWiki\Storage\DerivedPageDataUpdater::doParserCacheUpdate()
* @covers \MediaWiki\JobQueue\Jobs\ParsoidCachePrewarmJob::doParsoidCacheUpdate()
*/
public
function
testDoParserCacheUpdateForJavaScriptContent
()
{
$this
->
overrideConfigValue
(
MainConfigNames
::
ParsoidCacheConfig
,
[
'CacheThresholdTime'
=>
0.0
,
'WarmParsoidParserCache'
=>
true
,
// enable caching
]
);
$page
=
$this
->
getPage
(
__METHOD__
);
$this
->
createRevision
(
$page
,
'Dummy'
);
$user
=
$this
->
getTestUser
()->
getUser
();
$update
=
new
RevisionSlotsUpdate
();
$update
->
modifyContent
(
SlotRecord
::
MAIN
,
new
JavaScriptContent
(
'{ first: "main"; }'
)
);
// Emulate update after edit ----------
$parserCacheFactory
=
$this
->
getServiceContainer
()->
getParserCacheFactory
();
$parserCache
=
$parserCacheFactory
->
getParserCache
(
ParserCacheFactory
::
DEFAULT_NAME
);
$parsoidCache
=
$parserCacheFactory
->
getParserCache
(
ParserOutputAccess
::
PARSOID_PCACHE_NAME
);
$parserCache
->
deleteOptionsKey
(
$page
);
$parsoidCache
->
deleteOptionsKey
(
$page
);
$rev
=
$this
->
makeRevision
(
$page
->
getTitle
(),
$update
,
$user
,
'rev'
,
null
);
$rev
->
setTimestamp
(
'20100101000000'
);
$rev
->
setParentId
(
$page
->
getLatest
()
);
$updater
=
$this
->
getDerivedPageDataUpdater
(
$page
);
$updater
->
prepareContent
(
$user
,
$update
,
false
);
$rev
->
setId
(
1107
);
$updater
->
prepareUpdate
(
$rev
);
// Force the page timestamp, so we notice whether ParserOutput::getTimestamp
// or ParserOutput::getCacheTime are used.
// Also ensure $page->getLatest() returns the correct revision ID, so the parser
// cache doesn't get confused.
TestingAccessWrapper
::
newFromObject
(
$page
)->
setLastEdit
(
$rev
);
$updater
->
doParserCacheUpdate
();
// The cached ParserOutput should not use the revision timestamp
$cached
=
$parserCache
->
get
(
$page
,
$updater
->
getCanonicalParserOptions
(),
true
);
$this
->
assertIsObject
(
$cached
);
}
/**
* Helper for testTemplateUpdate
*
* @param WikiPage $page
* @param string $content
*/
private
function
editAndUpdate
(
$page
,
$content
)
{
$this
->
createRevision
(
$page
,
$content
);
$this
->
getServiceContainer
()->
resetServiceForTesting
(
'BacklinkCacheFactory'
);
DeferredUpdates
::
doUpdates
();
$this
->
runJobs
(
[
'minJobs'
=>
0
]
);
}
/**
* Regression test for T368006
*/
public
function
testTemplateUpdate
()
{
$clock
=
MWTimestamp
::
convert
(
TS_UNIX
,
'20100101000000'
);
MWTimestamp
::
setFakeTime
(
static
function
()
use
(
&
$clock
)
{
return
$clock
++;
}
);
$template
=
$this
->
getPage
(
'Template:TestTemplateUpdate'
);
$page
=
$this
->
getPage
(
'TestTemplateUpdate'
);
$this
->
editAndUpdate
(
$template
,
'1'
);
$this
->
editAndUpdate
(
$page
,
'{{TestTemplateUpdate}}'
);
$oldTouched
=
$page
->
getTouched
();
$page
->
clear
();
$this
->
editAndUpdate
(
$template
,
'2'
);
$newTouched
=
$page
->
getTouched
();
$this
->
assertGreaterThan
(
$oldTouched
,
$newTouched
);
}
public
static
function
provideNewTalk
()
{
yield
'Talk page edit'
=>
[
'NewTalk TestAuthor'
,
'User_talk:NewTalk_TestUser'
,
'NewTalk TestUser'
,
true
,
];
yield
'Own talk page'
=>
[
'NewTalk TestUser'
,
'User_talk:NewTalk_TestUser'
,
'NewTalk TestUser'
,
false
,
];
yield
'IP user page'
=>
[
'NewTalk TestAuthor'
,
'User_talk:192.168.0.1'
,
'192.168.0.1'
,
true
,
];
yield
'User talk subpage'
=>
[
'NewTalk TestAuthor'
,
'User_talk:NewTalk_TestUser/sandbox'
,
'NewTalk TestUser'
,
false
,
];
yield
'Not talk page'
=>
[
'NewTalk TestAuthor'
,
'User:NewTalk_TestUser'
,
'NewTalk TestUser'
,
false
,
];
}
private
function
createUser
(
string
$name
)
{
$userFactory
=
$this
->
getServiceContainer
()->
getUserFactory
();
$user
=
$userFactory
->
newFromName
(
$name
);
if
(
!
$user
)
{
$user
=
$userFactory
->
newAnonymous
(
$name
);
}
elseif
(
!
$user
->
getId
()
)
{
$user
->
addToDatabase
();
}
return
$user
;
}
/**
* @dataProvider provideNewTalk
*/
public
function
testNewTalk
(
string
$authorName
,
string
$pageName
,
string
$recipientName
,
bool
$expected
)
{
$author
=
$this
->
createUser
(
$authorName
);
$recipient
=
$this
->
createUser
(
$recipientName
);
$notificationManager
=
$this
->
getServiceContainer
()->
getTalkPageNotificationManager
();
$notificationManager
->
clearForPageView
(
$recipient
);
$page
=
$this
->
getPage
(
$pageName
);
$content
=
new
WikitextContent
(
'Hi there!'
);
$this
->
createRevision
(
$page
,
'Testing'
,
$content
,
$author
);
DeferredUpdates
::
doUpdates
();
$hasNewMessage
=
$notificationManager
->
userHasNewMessages
(
$recipient
);
$this
->
assertSame
(
$expected
,
$hasNewMessage
);
}
}
File Metadata
Details
Attached
Mime Type
text/x-php
Expires
Tue, Aug 18, 20:13 (2 w, 3 d ago)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
0d/42/da5fea62b7683eeb2f3fbef4702e
Default Alt Text
DerivedPageDataUpdaterTest.php (62 KB)
Attached To
Mode
rMWPROD MediaWiki Production
Attached
Detach File
Event Timeline
Log In to Comment