Page Menu
Home
WickedGov Phorge
Search
Configure Global Search
Log In
Files
F4114333
RevertedTagUpdateTest.php
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
14 KB
Referenced Files
None
Subscribers
None
RevertedTagUpdateTest.php
View Options
<?php
namespace
MediaWiki\Tests\Storage
;
use
ChangeTags
;
use
MediaWiki\ChangeTags\ChangeTagsStore
;
use
MediaWiki\Config\ServiceOptions
;
use
MediaWiki\Json\FormatJson
;
use
MediaWiki\MainConfigNames
;
use
MediaWiki\Revision\MutableRevisionRecord
;
use
MediaWiki\Revision\RevisionRecord
;
use
MediaWiki\Revision\RevisionStore
;
use
MediaWiki\Storage\EditResult
;
use
MediaWiki\Storage\RevertedTagUpdate
;
use
MediaWikiUnitTestCase
;
use
MockTitleTrait
;
use
PHPUnit\Framework\MockObject\Stub\ReturnCallback
;
use
Psr\Log\LoggerInterface
;
use
Psr\Log\LogLevel
;
use
TestLogger
;
use
Wikimedia\Rdbms\IConnectionProvider
;
/**
* @covers \MediaWiki\Storage\RevertedTagUpdate
* @see RevertedTagUpdateIntegrationTest for an end-to-end test with the database
*/
class
RevertedTagUpdateTest
extends
MediaWikiUnitTestCase
{
use
MockTitleTrait
;
/**
* Convenience function for creating a RevertedTagUpdate object
*
* @param ChangeTagsStore $changeTagsStore
* @param RevisionStore $revisionStore
* @param LoggerInterface $logger
* @param string[] $softwareTags
* @param int $revertedTagMaxDepth
* @param int $revertId
* @param EditResult $editResult
*
* @return RevertedTagUpdate
*/
private
function
newRevertedTagUpdate
(
$changeTagsStore
,
RevisionStore
$revisionStore
,
LoggerInterface
$logger
,
array
$softwareTags
,
int
$revertedTagMaxDepth
,
int
$revertId
,
EditResult
$editResult
):
RevertedTagUpdate
{
$dbProvider
=
$this
->
createMock
(
IConnectionProvider
::
class
);
$changeTagsStore
->
method
(
'getSoftwareTags'
)
->
willReturn
(
$softwareTags
);
$serviceOptions
=
new
ServiceOptions
(
RevertedTagUpdate
::
CONSTRUCTOR_OPTIONS
,
[
MainConfigNames
::
RevertedTagMaxDepth
=>
$revertedTagMaxDepth
]
);
return
new
RevertedTagUpdate
(
$revisionStore
,
$logger
,
$changeTagsStore
,
$dbProvider
,
$serviceOptions
,
$revertId
,
$editResult
);
}
/**
* Returns a dummy RevisionRecord.
*
* @param int $pageId
* @param int $revisionId
* @param string $timestamp
* @param string|null $sha1 SHA1 of the revision. When null, will generate
* a distinct SHA1 for the revision. Does not have to be a proper SHA1.
*
* @return MutableRevisionRecord
*/
private
function
getDummyRevision
(
int
$pageId
=
100
,
int
$revisionId
=
123
,
string
$timestamp
=
'20100101202020'
,
?
string
$sha1
=
null
)
{
$revisionRecord
=
new
MutableRevisionRecord
(
$this
->
makeMockTitle
(
__METHOD__
,
[
'id'
=>
$pageId
]
)
);
$revisionRecord
->
setId
(
$revisionId
);
$revisionRecord
->
setTimestamp
(
$timestamp
);
$revisionRecord
->
setPageId
(
$pageId
);
// Not a valid SHA-1, but enough to make these revisions appear like they have
// different contents.
$revisionRecord
->
setSha1
(
$sha1
??
strval
(
$revisionId
)
);
return
$revisionRecord
;
}
/**
* Returns a closure that determines the return value of ChangeTagsStore::addTags()
*
* @param int $revertedRevisionId
* @param int $newRevisionId
* @param EditResult $editResult
* @return callable
*/
private
function
getChangeTagsReturnCallback
(
int
$revertedRevisionId
,
int
$newRevisionId
,
EditResult
$editResult
):
callable
{
return
function
(
$tags
,
$rc_id
,
$rev_id
,
$log_id
,
$params
)
use
(
$newRevisionId
,
$revertedRevisionId
,
$editResult
)
{
$this
->
assertSame
(
[
ChangeTags
::
TAG_REVERTED
],
$tags
,
'RevertedTagUpdate::markAsReverted() $revisionId'
);
$this
->
assertSame
(
$revertedRevisionId
,
$rev_id
,
'RevertedTagUpdate::markAsReverted() $revisionId'
);
$this
->
assertSame
(
FormatJson
::
encode
(
array_merge
(
[
'revertId'
=>
$newRevisionId
],
$editResult
->
jsonSerialize
()
)
),
$params
);
};
}
public
static
function
provideRevertedTagUpdateDisabled
()
{
yield
'mw-reverted tag is disabled'
=>
[
[],
15
];
yield
'$wgRevertedTagMaxDepth is 0'
=>
[
[
'mw-reverted'
],
0
];
}
/**
* Test cases where the update is disabled through configuration.
*
* @dataProvider provideRevertedTagUpdateDisabled
*/
public
function
testRevertedTagUpdateDisabled
(
array
$softwareChangeTags
,
int
$revertedTagMaxDepth
)
{
$update
=
$this
->
newRevertedTagUpdate
(
$this
->
createMock
(
ChangeTagsStore
::
class
),
$this
->
createNoOpMock
(
RevisionStore
::
class
),
new
TestLogger
(),
$softwareChangeTags
,
$revertedTagMaxDepth
,
123
,
$this
->
createNoOpMock
(
EditResult
::
class
)
);
$update
->
doUpdate
();
}
public
static
function
provideInvalidEditResults
()
{
yield
'edit is not a revert'
=>
[
new
EditResult
(
false
,
false
,
null
,
null
,
null
,
false
,
false
,
[]
)
];
yield
'newest reverted revision ID is null'
=>
[
new
EditResult
(
false
,
9
,
EditResult
::
REVERT_ROLLBACK
,
15
,
null
,
true
,
false
,
[
'mw-rollback'
]
)
];
}
/**
* @dataProvider provideInvalidEditResults
*/
public
function
testInvalidEditResult
(
EditResult
$editResult
)
{
$logger
=
new
TestLogger
(
true
);
$update
=
$this
->
newRevertedTagUpdate
(
$this
->
createMock
(
ChangeTagsStore
::
class
),
$this
->
createNoOpMock
(
RevisionStore
::
class
),
$logger
,
[
'mw-reverted'
],
15
,
123
,
$editResult
);
$update
->
doUpdate
();
$this
->
assertSame
(
[
[
LogLevel
::
ERROR
,
'Invalid EditResult specified.'
],
],
$logger
->
getBuffer
()
);
}
/**
* Test the case where the supposedly reverted revisions are missing from the DB.
*/
public
function
testMissingRevertedRevisions
()
{
$revisionStore
=
$this
->
createMock
(
RevisionStore
::
class
);
$revisionStore
->
expects
(
$this
->
once
()
)
->
method
(
'getRevisionById'
)
->
willReturn
(
null
);
$logger
=
new
TestLogger
(
true
);
$editResult
=
new
EditResult
(
false
,
122
,
EditResult
::
REVERT_ROLLBACK
,
123
,
125
,
true
,
false
,
[
'mw-rollback'
]
);
$update
=
$this
->
newRevertedTagUpdate
(
$this
->
createMock
(
ChangeTagsStore
::
class
),
$revisionStore
,
$logger
,
[
'mw-reverted'
],
15
,
300
,
$editResult
);
$update
->
doUpdate
();
$this
->
assertSame
(
[
[
LogLevel
::
ERROR
,
'Could not find the newest or oldest reverted revision in the database.'
],
],
$logger
->
getBuffer
()
);
}
/**
* Test the case where the actual revert revision is missing from the DB.
*/
public
function
testMissingRevertRevision
()
{
$dummyRevision
=
$this
->
createNoOpMock
(
RevisionRecord
::
class
);
$revisionStore
=
$this
->
createMock
(
RevisionStore
::
class
);
$revisionStore
->
method
(
'getRevisionById'
)
->
willReturnMap
(
[
[
123
,
0
,
null
,
$dummyRevision
],
// oldest reverted
[
125
,
0
,
null
,
$dummyRevision
],
// newest reverted
[
300
,
0
,
null
,
null
],
// the revert
]
);
$logger
=
new
TestLogger
(
true
);
$editResult
=
new
EditResult
(
false
,
122
,
EditResult
::
REVERT_ROLLBACK
,
123
,
125
,
true
,
false
,
[
'mw-rollback'
]
);
$update
=
$this
->
newRevertedTagUpdate
(
$this
->
createMock
(
ChangeTagsStore
::
class
),
$revisionStore
,
$logger
,
[
'mw-reverted'
],
15
,
300
,
$editResult
);
$update
->
doUpdate
();
$this
->
assertSame
(
[
[
LogLevel
::
ERROR
,
'Could not find the revert revision in the database.'
],
],
$logger
->
getBuffer
()
);
}
public
static
function
providePageIdMismatch
()
{
yield
'mismatch between reverted revisions'
=>
[
10
,
22
,
10
];
yield
'mismatch between reverted revisions and the revert'
=>
[
10
,
10
,
22
];
}
/**
* @dataProvider providePageIdMismatch
*/
public
function
testPageIdMismatch
(
int
$oldestRevertedPageId
,
int
$newestRevertedPageId
,
int
$revertPageId
)
{
$oldestReverted
=
$this
->
getDummyRevision
(
$oldestRevertedPageId
);
$newestReverted
=
$this
->
getDummyRevision
(
$newestRevertedPageId
);
$revert
=
$this
->
getDummyRevision
(
$revertPageId
);
$revisionStore
=
$this
->
createMock
(
RevisionStore
::
class
);
$revisionStore
->
method
(
'getRevisionById'
)
->
willReturnMap
(
[
[
123
,
0
,
null
,
$oldestReverted
],
[
125
,
0
,
null
,
$newestReverted
],
[
300
,
0
,
null
,
$revert
],
]
);
$logger
=
new
TestLogger
(
true
);
$editResult
=
new
EditResult
(
false
,
122
,
EditResult
::
REVERT_ROLLBACK
,
123
,
125
,
true
,
false
,
[
'mw-rollback'
]
);
$update
=
$this
->
newRevertedTagUpdate
(
$this
->
createMock
(
ChangeTagsStore
::
class
),
$revisionStore
,
$logger
,
[
'mw-reverted'
],
15
,
300
,
$editResult
);
$update
->
doUpdate
();
$this
->
assertSame
(
[
[
LogLevel
::
ERROR
,
'The revert and reverted revisions belong to different pages.'
],
],
$logger
->
getBuffer
()
);
}
/**
* Tests whether the update is skipped when the revert is marked as deleted.
*/
public
function
testSkipUpdateWhenRevertIsDeleted
()
{
$dummyRevision
=
$this
->
getDummyRevision
();
$dummyRevision
->
setVisibility
(
RevisionRecord
::
DELETED_TEXT
);
$revisionStore
=
$this
->
createMock
(
RevisionStore
::
class
);
$revisionStore
->
method
(
'getRevisionById'
)
->
willReturn
(
$dummyRevision
);
$logger
=
new
TestLogger
(
true
);
$editResult
=
new
EditResult
(
false
,
122
,
EditResult
::
REVERT_ROLLBACK
,
123
,
125
,
true
,
false
,
[
'mw-rollback'
]
);
$update
=
$this
->
newRevertedTagUpdate
(
$this
->
createMock
(
ChangeTagsStore
::
class
),
$revisionStore
,
$logger
,
[
'mw-reverted'
],
15
,
300
,
$editResult
);
$update
->
doUpdate
();
$this
->
assertSame
(
[
[
LogLevel
::
INFO
,
'The revert
\'
s text had been marked as deleted before the update was '
.
'executed. Skipping...'
,
],
],
$logger
->
getBuffer
()
);
}
/**
* Tests whether the update is skipped when the revert is marked as reverted.
*/
public
function
testSkipUpdateWhenRevertIsReverted
()
{
$dummyRevision
=
$this
->
getDummyRevision
();
$revisionStore
=
$this
->
createMock
(
RevisionStore
::
class
);
$revisionStore
->
method
(
'getRevisionById'
)
->
willReturn
(
$dummyRevision
);
$logger
=
new
TestLogger
(
true
);
$changeTagsStore
=
$this
->
createMock
(
ChangeTagsStore
::
class
);
$changeTagsStore
->
expects
(
$this
->
once
()
)
->
method
(
'getTags'
)
->
with
(
$this
->
anything
(),
null
,
300
)
->
willReturn
(
[
'mw-reverted'
]
);
$editResult
=
new
EditResult
(
false
,
122
,
EditResult
::
REVERT_ROLLBACK
,
123
,
125
,
true
,
false
,
[
'mw-rollback'
]
);
$update
=
$this
->
newRevertedTagUpdate
(
$changeTagsStore
,
$revisionStore
,
$logger
,
[
'mw-reverted'
],
15
,
300
,
$editResult
);
$update
->
doUpdate
();
$this
->
assertSame
(
[
[
LogLevel
::
INFO
,
'The revert had been reverted before the update was executed. Skipping...'
],
],
$logger
->
getBuffer
()
);
}
/**
* Test the case where only one revision was reverted and we can omit some DB code.
*/
public
function
testSingleRevertedRevision
()
{
$revisionStore
=
$this
->
createMock
(
RevisionStore
::
class
);
$revisionStore
->
method
(
'getRevisionById'
)
->
willReturn
(
$this
->
getDummyRevision
()
);
$editResult
=
new
EditResult
(
false
,
122
,
EditResult
::
REVERT_ROLLBACK
,
123
,
123
,
true
,
false
,
[
'mw-rollback'
]
);
$changeTagsStore
=
$this
->
createMock
(
ChangeTagsStore
::
class
);
$changeTagsStore
->
expects
(
$this
->
once
()
)
->
method
(
'getTags'
)
->
willReturn
(
[]
);
$changeTagsStore
->
expects
(
$this
->
once
()
)
->
method
(
'addTags'
)
->
willReturnCallback
(
$this
->
getChangeTagsReturnCallback
(
123
,
124
,
$editResult
)
);
$update
=
$this
->
newRevertedTagUpdate
(
$changeTagsStore
,
$revisionStore
,
new
TestLogger
(),
[
'mw-reverted'
],
15
,
124
,
$editResult
);
$update
->
doUpdate
();
}
/**
* Reverts deeper than $wgRevertedTagMaxDepth should not have the reverted tag applied.
*/
public
function
testRevertTooDeep
()
{
$revisionStore
=
$this
->
createMock
(
RevisionStore
::
class
);
$revisionStore
->
method
(
'getRevisionById'
)
->
willReturnCallback
(
function
(
int
$id
)
{
return
$this
->
getDummyRevision
(
100
,
$id
);
}
);
$revisionStore
->
expects
(
$this
->
once
()
)
->
method
(
'getRevisionIdsBetween'
)
->
willReturn
(
[
123
,
124
,
125
,
126
]
);
// 4 reverted edits
$editResult
=
new
EditResult
(
false
,
false
,
EditResult
::
REVERT_UNDO
,
123
,
126
,
false
,
false
,
[
'mw-undo'
]
);
$changeTagsStore
=
$this
->
createMock
(
ChangeTagsStore
::
class
,
[
'getTags'
]
);
$changeTagsStore
->
expects
(
$this
->
once
()
)
->
method
(
'getTags'
)
->
willReturn
(
[]
);
$logger
=
new
TestLogger
(
true
);
$update
=
$this
->
newRevertedTagUpdate
(
$changeTagsStore
,
$revisionStore
,
$logger
,
[
'mw-reverted'
],
3
,
130
,
$editResult
);
$update
->
doUpdate
();
$this
->
assertSame
(
[
[
LogLevel
::
INFO
,
'The revert is deeper than $wgRevertedTagMaxDepth. Skipping...'
],
],
$logger
->
getBuffer
()
);
}
/**
* Test marking multiple revisions as reverted.
*
* Also ensures that null revisions (e.g. move and protection entries) are not
* marked as 'reverted', see: T265312
*/
public
function
testMultipleRevertedRevisions
()
{
$revisionStore
=
$this
->
createMock
(
RevisionStore
::
class
);
$revisionStore
->
method
(
'getRevisionById'
)
->
willReturnCallback
(
function
(
int
$id
)
{
return
$this
->
getDummyRevision
(
100
,
$id
,
'20100101202020'
,
// Make it appear as though rev 125 has the same content as 124
$id
===
125
?
124
:
$id
);
}
);
$revisionStore
->
expects
(
$this
->
once
()
)
->
method
(
'getRevisionIdsBetween'
)
->
willReturn
(
[
123
,
124
,
125
,
126
]
);
$editResult
=
new
EditResult
(
false
,
false
,
EditResult
::
REVERT_UNDO
,
123
,
126
,
false
,
false
,
[
'mw-undo'
]
);
$changeTagsStore
=
$this
->
createMock
(
ChangeTagsStore
::
class
);
// Revision 125 has the same content as 124, so it should not be marked
// as reverted. See: T265312
$reallyRevertedRevs
=
[
123
,
124
,
126
];
$changeTagsStoreRetCallbacks
=
[];
for
(
$i
=
0
;
$i
<=
2
;
$i
++
)
{
$changeTagsStoreRetCallbacks
[]
=
new
ReturnCallback
(
$this
->
getChangeTagsReturnCallback
(
$reallyRevertedRevs
[
$i
],
130
,
$editResult
)
);
}
$changeTagsStore
->
method
(
'addTags'
)
->
willReturnOnConsecutiveCalls
(
...
$changeTagsStoreRetCallbacks
);
$changeTagsStore
->
expects
(
$this
->
exactly
(
3
)
)
->
method
(
'addTags'
);
$changeTagsStore
->
expects
(
$this
->
once
()
)
->
method
(
'getTags'
)
->
willReturn
(
[]
);
$update
=
$this
->
newRevertedTagUpdate
(
$changeTagsStore
,
$revisionStore
,
new
TestLogger
(),
[
'mw-reverted'
],
15
,
130
,
$editResult
);
$update
->
doUpdate
();
}
}
File Metadata
Details
Attached
Mime Type
text/x-php
Expires
Wed, Aug 19, 00:04 (2 d, 21 h ago)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
e3/b0/f57e885f7e0754375ab2ff07581b
Default Alt Text
RevertedTagUpdateTest.php (14 KB)
Attached To
Mode
rMWPROD MediaWiki Production
Attached
Detach File
Event Timeline
Log In to Comment