Convert RevisionRecord to Authority and PageIdentity

As we convert the RevisionRecord to using Authority,
we no longer need Title instances, so we can convert
that to PageIdentity.

Ideally, we'd part away from using Title at all, but:
1. For foreign wikis PageIdentity has stronger validation,
so calling PageIdentity getId() on Title will break things.
There's still a lot of code depending on lax Title guarantees,
so we keep it.
2. A lot of code still depends on Title, so we try to pass it
through even if we don't nesessarily need to, to save cost
on recreating it later on.

Bug: T271458
Depends-On: I287400b967b467ea18bebbb579e881a785a19158
Change-Id: I63d9807264d7e2295afef51fc9d982447f92fcbd
This commit is contained in:
Petr Pchelko 2021-01-11 16:36:33 -06:00
parent a5823e607c
commit 816e02ae51
23 changed files with 379 additions and 300 deletions

View file

@ -404,6 +404,7 @@ class Revision implements IDBAccessObject {
* @param int $queryFlags
* @param Title|null $title
*
* @throws RevisionAccessException if title can not be found
* @return Title $title if not null, or a Title constructed from information in $row.
*/
private function ensureTitle( $row, $queryFlags, $title = null ) {
@ -427,18 +428,7 @@ class Revision implements IDBAccessObject {
$revId = $row->rev_id ?? 0;
}
try {
$title = self::getRevisionStore()->getTitle( $pageId, $revId, $queryFlags );
} catch ( RevisionAccessException $ex ) {
// construct a dummy title!
wfLogWarning( __METHOD__ . ': ' . $ex->getMessage() );
// NOTE: this Title will only be used inside RevisionRecord
$title = Title::makeTitleSafe( NS_SPECIAL, "Badtitle/ID=$pageId" );
$title->resetArticleID( $pageId );
}
return $title;
return self::getRevisionStore()->getTitle( $pageId, $revId, $queryFlags );
}
/**
@ -595,8 +585,7 @@ class Revision implements IDBAccessObject {
*/
public function getTitle() {
wfDeprecated( __METHOD__, '1.31' );
$linkTarget = $this->mRecord->getPageAsLinkTarget();
return Title::newFromLinkTarget( $linkTarget );
return Title::castFromPageIdentity( $this->mRecord->getPage() );
}
/**

View file

@ -25,11 +25,11 @@ namespace MediaWiki\Revision;
use CommentStoreComment;
use Content;
use InvalidArgumentException;
use MediaWiki\Page\PageIdentity;
use MediaWiki\Storage\RevisionSlotsUpdate;
use MediaWiki\User\UserIdentity;
use MWException;
use MWTimestamp;
use Title;
use Wikimedia\Assert\Assert;
/**
@ -54,9 +54,7 @@ class MutableRevisionRecord extends RevisionRecord {
* @return MutableRevisionRecord
*/
public static function newFromParentRevision( RevisionRecord $parent ) {
// TODO: ideally, we wouldn't need a Title here
$title = Title::newFromLinkTarget( $parent->getPageAsLinkTarget() );
$rev = new MutableRevisionRecord( $title, $parent->getWikiId() );
$rev = new MutableRevisionRecord( $parent->getPage(), $parent->getWikiId() );
foreach ( $parent->getSlotRoles() as $role ) {
$slot = $parent->getSlot( $role, self::RAW );
@ -75,17 +73,17 @@ class MutableRevisionRecord extends RevisionRecord {
*
* @stable to call.
*
* @param Title $title The title of the page this Revision is associated with.
* @param PageIdentity $page The page this Revision is associated with.
* @param bool|string $dbDomain DB domain of the relevant wiki or false for the current one.
*
* @throws MWException
*/
public function __construct( Title $title, $dbDomain = false ) {
public function __construct( PageIdentity $page, $dbDomain = false ) {
$slots = new MutableRevisionSlots( [], function () {
$this->resetAggregateValues();
} );
parent::__construct( $title, $slots, $dbDomain );
parent::__construct( $page, $slots, $dbDomain );
}
/**
@ -319,9 +317,10 @@ class MutableRevisionRecord extends RevisionRecord {
public function setPageId( $pageId ) {
Assert::parameterType( 'integer', $pageId, '$pageId' );
if ( $this->mTitle->exists() && $pageId !== $this->mTitle->getArticleID() ) {
$pageIdBasedOnPage = $this->getArticleId( $this->mPage );
if ( $pageIdBasedOnPage && $pageIdBasedOnPage !== $this->getArticleId( $this->mPage ) ) {
throw new InvalidArgumentException(
'The given Title does not belong to page ID ' . $this->mPageId
'The given page does not belong to page ID ' . $this->mPageId
);
}

View file

@ -23,10 +23,10 @@
namespace MediaWiki\Revision;
use CommentStoreComment;
use MediaWiki\Page\PageIdentity;
use MediaWiki\Permissions\Authority;
use MediaWiki\User\UserIdentity;
use MWTimestamp;
use Title;
use User;
use Wikimedia\Assert\Assert;
/**
@ -49,7 +49,7 @@ class RevisionArchiveRecord extends RevisionRecord {
* @note Avoid calling this constructor directly. Use the appropriate methods
* in RevisionStore instead.
*
* @param Title $title The title of the page this Revision is associated with.
* @param PageIdentity $page The page this Revision is associated with.
* @param UserIdentity $user
* @param CommentStoreComment $comment
* @param \stdClass $row An archive table row. Use RevisionStore::getArchiveQueryInfo() to build
@ -58,14 +58,14 @@ class RevisionArchiveRecord extends RevisionRecord {
* @param bool|string $dbDomain DB domain of the relevant wiki or false for the current one.
*/
public function __construct(
Title $title,
PageIdentity $page,
UserIdentity $user,
CommentStoreComment $comment,
$row,
RevisionSlots $slots,
$dbDomain = false
) {
parent::__construct( $title, $slots, $dbDomain );
parent::__construct( $page, $slots, $dbDomain );
Assert::parameterType( \stdClass::class, $row, '$row' );
$timestamp = MWTimestamp::convert( TS_MW, $row->ar_timestamp );
@ -73,10 +73,10 @@ class RevisionArchiveRecord extends RevisionRecord {
$this->mArchiveId = intval( $row->ar_id );
// NOTE: ar_page_id may be different from $this->mTitle->getArticleID() in some cases,
// NOTE: ar_page_id may be different from $this->mPage->getId() in some cases,
// notably when a partially restored page has been moved, and a new page has been created
// with the same title. Archive rows for that title will then have the wrong page id.
$this->mPageId = isset( $row->ar_page_id ) ? intval( $row->ar_page_id ) : $title->getArticleID();
$this->mPageId = isset( $row->ar_page_id ) ? intval( $row->ar_page_id ) : $this->getArticleId( $this->mPage );
// NOTE: ar_parent_id = 0 indicates that there is no parent revision, while null
// indicates that the parent revision is unknown. As per MW 1.31, the database schema
@ -140,24 +140,24 @@ class RevisionArchiveRecord extends RevisionRecord {
/**
* @param int $audience
* @param User|null $user
* @param Authority|null $performer
*
* @return UserIdentity The identity of the revision author, null if access is forbidden.
*/
public function getUser( $audience = self::FOR_PUBLIC, User $user = null ) {
public function getUser( $audience = self::FOR_PUBLIC, Authority $performer = null ) {
// overwritten just to add a guarantee to the contract
return parent::getUser( $audience, $user );
return parent::getUser( $audience, $performer );
}
/**
* @param int $audience
* @param User|null $user
* @param Authority|null $performer
*
* @return CommentStoreComment The revision comment, null if access is forbidden.
*/
public function getComment( $audience = self::FOR_PUBLIC, User $user = null ) {
public function getComment( $audience = self::FOR_PUBLIC, Authority $performer = null ) {
// overwritten just to add a guarantee to the contract
return parent::getComment( $audience, $user );
return parent::getComment( $audience, $performer );
}
/**

View file

@ -26,12 +26,10 @@ use CommentStoreComment;
use Content;
use InvalidArgumentException;
use MediaWiki\Linker\LinkTarget;
use MediaWiki\MediaWikiServices;
use MediaWiki\Page\PageIdentity;
use MediaWiki\Permissions\Authority;
use MediaWiki\User\UserIdentity;
use MWException;
use Title;
use User;
use Wikimedia\Assert\Assert;
use Wikimedia\NonSerializable\NonSerializableTrait;
@ -84,8 +82,8 @@ abstract class RevisionRecord {
/** @var CommentStoreComment|null */
protected $mComment;
/** @var Title */
protected $mTitle; // TODO: we only need the title for permission checks!
/** @var PageIdentity */
protected $mPage;
/** @var RevisionSlots */
protected $mSlots;
@ -94,21 +92,17 @@ abstract class RevisionRecord {
* @note Avoid calling this constructor directly. Use the appropriate methods
* in RevisionStore instead.
*
* @param Title $title The title of the page this Revision is associated with.
* @param PageIdentity $page The page this Revision is associated with.
* @param RevisionSlots $slots The slots of this revision.
* @param bool|string $dbDomain DB domain of the relevant wiki or false for the current one.
*
* @throws MWException
*/
public function __construct( Title $title, RevisionSlots $slots, $dbDomain = false ) {
public function __construct( PageIdentity $page, RevisionSlots $slots, $dbDomain = false ) {
Assert::parameterType( 'string|boolean', $dbDomain, '$dbDomain' );
$this->mTitle = $title;
$this->mPage = $page;
$this->mSlots = $slots;
$this->mWiki = $dbDomain;
// XXX: this is a sensible default, but we may not have a Title object here in the future.
$this->mPageId = $title->getArticleID();
$this->mPageId = $this->getArticleId( $page );
}
/**
@ -151,21 +145,19 @@ abstract class RevisionRecord {
*
* @param string $role The role name of the desired slot
* @param int $audience
* @param User|null $user
* @param Authority|null $performer user on who's behalf to check
*
* @throws RevisionAccessException if the slot does not exist or slot data
* could not be lazy-loaded.
* @return Content|null The content of the given slot, or null if access is forbidden.
*/
public function getContent( $role, $audience = self::FOR_PUBLIC, User $user = null ) {
public function getContent( $role, $audience = self::FOR_PUBLIC, Authority $performer = null ) {
// XXX: throwing an exception would be nicer, but would a further
// departure from the signature of Revision::getContent(), and thus
// more complex and error prone refactoring.
if ( !$this->audienceCan( self::DELETED_TEXT, $audience, $user ) ) {
if ( !$this->audienceCan( self::DELETED_TEXT, $audience, $performer ) ) {
return null;
}
$content = $this->getSlot( $role, $audience, $user )->getContent();
$content = $this->getSlot( $role, $audience, $performer )->getContent();
return $content->copy();
}
@ -174,17 +166,17 @@ abstract class RevisionRecord {
*
* @param string $role The role name of the desired slot
* @param int $audience
* @param User|null $user
* @param Authority|null $performer user on who's behalf to check
*
* @throws RevisionAccessException if the slot does not exist or slot data
* could not be lazy-loaded.
* @return SlotRecord The slot meta-data. If access to the slot's content is forbidden,
* calling getContent() on the SlotRecord will throw an exception.
*/
public function getSlot( $role, $audience = self::FOR_PUBLIC, User $user = null ) {
public function getSlot( $role, $audience = self::FOR_PUBLIC, Authority $performer = null ) {
$slot = $this->mSlots->getSlot( $role );
if ( !$this->audienceCan( self::DELETED_TEXT, $audience, $user ) ) {
if ( !$this->audienceCan( self::DELETED_TEXT, $audience, $performer ) ) {
return SlotRecord::newWithSuppressedContent( $slot );
}
@ -218,8 +210,8 @@ abstract class RevisionRecord {
* @note This provides access to slot content with no audience checks applied.
* Calling getContent() on the RevisionSlots object returned here, or on any
* SlotRecord it returns from getSlot(), will not fail due to access restrictions.
* If audience checks are desired, use getSlot( $role, $audience, $user )
* or getContent( $role, $audience, $user ) instead.
* If audience checks are desired, use getSlot( $role, $audience, $performer )
* or getContent( $role, $audience, $performer ) instead.
*
* @return RevisionSlots
*/
@ -337,23 +329,26 @@ abstract class RevisionRecord {
/**
* Returns the title of the page this revision is associated with as a LinkTarget object.
*
* MCR migration note: this replaces Revision::getTitle
*
* @throws InvalidArgumentException if this revision does not belong to a local wiki
* @return LinkTarget
*/
public function getPageAsLinkTarget() {
return $this->mTitle;
// TODO: Should be TitleValue::newFromPage( $this->mPage ),
// but Title is used too much still, so let's keep propagating it
return Title::castFromPageIdentity( $this->mPage );
}
/**
* Returns the page this revision belongs to.
*
* MCR migration note: this replaces Revision::getTitle
*
* @since 1.36
*
* @return PageIdentity
*/
public function getPage() {
return $this->mTitle;
return $this->mPage;
}
/**
@ -368,12 +363,11 @@ abstract class RevisionRecord {
* RevisionRecord::FOR_PUBLIC to be displayed to all users
* RevisionRecord::FOR_THIS_USER to be displayed to the given user
* RevisionRecord::RAW get the ID regardless of permissions
* @param User|null $user User object to check for, only if FOR_THIS_USER is passed
* to the $audience parameter
* @param Authority|null $performer user on who's behalf to check
* @return UserIdentity|null
*/
public function getUser( $audience = self::FOR_PUBLIC, User $user = null ) {
if ( !$this->audienceCan( self::DELETED_USER, $audience, $user ) ) {
public function getUser( $audience = self::FOR_PUBLIC, Authority $performer = null ) {
if ( !$this->audienceCan( self::DELETED_USER, $audience, $performer ) ) {
return null;
} else {
return $this->mUser;
@ -392,13 +386,12 @@ abstract class RevisionRecord {
* RevisionRecord::FOR_PUBLIC to be displayed to all users
* RevisionRecord::FOR_THIS_USER to be displayed to the given user
* RevisionRecord::RAW get the text regardless of permissions
* @param User|null $user User object to check for, only if FOR_THIS_USER is passed
* to the $audience parameter
* @param Authority|null $performer user on who's behalf to check
*
* @return CommentStoreComment|null
*/
public function getComment( $audience = self::FOR_PUBLIC, User $user = null ) {
if ( !$this->audienceCan( self::DELETED_COMMENT, $audience, $user ) ) {
public function getComment( $audience = self::FOR_PUBLIC, Authority $performer = null ) {
if ( !$this->audienceCan( self::DELETED_COMMENT, $audience, $performer ) ) {
return null;
} else {
return $this->mComment;
@ -459,22 +452,21 @@ abstract class RevisionRecord {
* RevisionRecord::FOR_PUBLIC to be displayed to all users
* RevisionRecord::FOR_THIS_USER to be displayed to the given user
* RevisionRecord::RAW get the text regardless of permissions
* @param User|null $user User object to check. Required if $audience is FOR_THIS_USER,
* ignored otherwise.
* @param Authority|null $performer user on who's behalf to check
*
* @return bool
*/
public function audienceCan( $field, $audience, User $user = null ) {
public function audienceCan( $field, $audience, Authority $performer = null ) {
if ( $audience == self::FOR_PUBLIC && $this->isDeleted( $field ) ) {
return false;
} elseif ( $audience == self::FOR_THIS_USER ) {
if ( !$user ) {
if ( !$performer ) {
throw new InvalidArgumentException(
'A User object must be given when checking FOR_THIS_USER audience.'
'An Authority object must be given when checking FOR_THIS_USER audience.'
);
}
if ( !$this->userCan( $field, $user ) ) {
if ( !$this->userCan( $field, $performer ) ) {
return false;
}
}
@ -483,7 +475,7 @@ abstract class RevisionRecord {
}
/**
* Determine if the current user is allowed to view a particular
* Determine if the give authority is allowed to view a particular
* field of this revision, if it's marked as deleted.
*
* MCR migration note: this corresponds to Revision::userCan
@ -491,12 +483,11 @@ abstract class RevisionRecord {
* @param int $field One of self::DELETED_TEXT,
* self::DELETED_COMMENT,
* self::DELETED_USER
* @param User $user User object to check
* @param Authority $performer user on who's behalf to check
* @return bool
*/
protected function userCan( $field, User $user ) {
// TODO: use callback for permission checks, so we don't need to know a Title object!
return self::userCanBitfield( $this->getVisibility(), $field, $user, $this->mTitle );
public function userCan( $field, Authority $performer ) {
return self::userCanBitfield( $this->getVisibility(), $field, $performer, $this->mPage );
}
/**
@ -510,12 +501,12 @@ abstract class RevisionRecord {
* @param int $field One of self::DELETED_TEXT = File::DELETED_FILE,
* self::DELETED_COMMENT = File::DELETED_COMMENT,
* self::DELETED_USER = File::DELETED_USER
* @param User $user User object to check
* @param Title|null $title A Title object to check for per-page restrictions on,
* instead of just plain userrights
* @param Authority $performer user on who's behalf to check
* @param PageIdentity|null $page A PageIdentity object to check for per-page restrictions on,
* instead of just plain user rights
* @return bool
*/
public static function userCanBitfield( $bitfield, $field, User $user, Title $title = null ) {
public static function userCanBitfield( $bitfield, $field, Authority $performer, PageIdentity $page = null ) {
if ( $bitfield & $field ) { // aspect is deleted
if ( $bitfield & self::DELETED_RESTRICTED ) {
$permissions = [ 'suppressrevision', 'viewsuppressed' ];
@ -525,24 +516,14 @@ abstract class RevisionRecord {
$permissions = [ 'deletedhistory' ];
}
// XXX: How can we avoid global scope here?
// Perhaps the audience check should be done in a callback.
$permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
$permissionlist = implode( ', ', $permissions );
if ( $title === null ) {
if ( $page === null ) {
wfDebug( "Checking for $permissionlist due to $field match on $bitfield" );
foreach ( $permissions as $perm ) {
if ( $permissionManager->userHasRight( $user, $perm ) ) {
return true;
}
}
return false;
return $performer->isAllowedAny( ...$permissions );
} else {
$text = $title->getPrefixedText();
wfDebug( "Checking for $permissionlist on $text due to $field match on $bitfield" );
wfDebug( "Checking for $permissionlist on $page due to $field match on $bitfield" );
foreach ( $permissions as $perm ) {
if ( $permissionManager->userCan( $perm, $user, $title ) ) {
if ( $performer->authorizeRead( $perm, $page ) ) {
return true;
}
}
@ -587,6 +568,26 @@ abstract class RevisionRecord {
return false;
}
/**
* Before transition to PageIdentity, Title could exist for foreign wikis.
* It was very brittle, but it worked. Until Title is deprecated in RevisionStore
* and in the codebase more general, most of the PageIdentity instances passed
* in here are Titles. So for cross-wiki access, stricter domain validation
* of PageIdentity::getId will break wikis. This method supposed to exist
* only for the transition period and will be removed after.
*
* Additionally, loose checks on Title regarding whether the page can exist or not
* have been depended upon in a number of places in the codebase.
*
* @param PageIdentity $title
* @return int
*/
protected function getArticleId( PageIdentity $title ): int {
if ( $title instanceof Title ) {
return $title->getArticleID();
}
return $title->getId( $this->getWikiId() );
}
}
/**

View file

@ -24,9 +24,10 @@ namespace MediaWiki\Revision;
use CommentStoreComment;
use InvalidArgumentException;
use MediaWiki\Page\PageIdentity;
use MediaWiki\Permissions\Authority;
use MediaWiki\User\UserIdentity;
use MediaWiki\User\UserIdentityValue;
use Title;
use User;
/**
@ -47,7 +48,7 @@ class RevisionStoreCacheRecord extends RevisionStoreRecord {
* in RevisionStore instead.
*
* @param callable $callback Callback for loading data. Signature: function ( $id ): \stdClass
* @param Title $title The title of the page this Revision is associated with.
* @param PageIdentity $page The page this Revision is associated with.
* @param UserIdentity $user
* @param CommentStoreComment $comment
* @param \stdClass $row A row from the revision table. Use RevisionStore::getQueryInfo() to build
@ -57,14 +58,14 @@ class RevisionStoreCacheRecord extends RevisionStoreRecord {
*/
public function __construct(
$callback,
Title $title,
PageIdentity $page,
UserIdentity $user,
CommentStoreComment $comment,
$row,
RevisionSlots $slots,
$dbDomain = false
) {
parent::__construct( $title, $user, $comment, $row, $slots, $dbDomain );
parent::__construct( $page, $user, $comment, $row, $slots, $dbDomain );
$this->mCallback = $callback;
}
@ -84,15 +85,15 @@ class RevisionStoreCacheRecord extends RevisionStoreRecord {
* Overridden to ensure that we return a fresh value and not a cached one.
*
* @param int $audience
* @param User|null $user
* @param Authority|null $performer
*
* @return UserIdentity The identity of the revision author, null if access is forbidden.
*/
public function getUser( $audience = self::FOR_PUBLIC, User $user = null ) {
public function getUser( $audience = self::FOR_PUBLIC, Authority $performer = null ) {
if ( $this->mCallback ) {
$this->loadFreshRow();
}
return parent::getUser( $audience, $user );
return parent::getUser( $audience, $performer );
}
/**
@ -120,7 +121,7 @@ class RevisionStoreCacheRecord extends RevisionStoreRecord {
wfWarn(
__METHOD__
. ': '
. $this->mTitle->getPrefixedDBkey()
. $this->mPage
. ': '
. $ex->getMessage()
);

View file

@ -24,10 +24,10 @@ namespace MediaWiki\Revision;
use CommentStoreComment;
use InvalidArgumentException;
use MediaWiki\Page\PageIdentity;
use MediaWiki\Permissions\Authority;
use MediaWiki\User\UserIdentity;
use MWTimestamp;
use Title;
use User;
use Wikimedia\Assert\Assert;
/**
@ -46,7 +46,7 @@ class RevisionStoreRecord extends RevisionRecord {
* @note Avoid calling this constructor directly. Use the appropriate methods
* in RevisionStore instead.
*
* @param Title $title The title of the page this Revision is associated with.
* @param PageIdentity $page The page this Revision is associated with.
* @param UserIdentity $user
* @param CommentStoreComment $comment
* @param \stdClass $row A row from the revision table. Use RevisionStore::getQueryInfo() to build
@ -55,16 +55,15 @@ class RevisionStoreRecord extends RevisionRecord {
* @param bool|string $dbDomain DB domain of the relevant wiki or false for the current one.
*/
public function __construct(
Title $title,
PageIdentity $page,
UserIdentity $user,
CommentStoreComment $comment,
$row,
RevisionSlots $slots,
$dbDomain = false
) {
parent::__construct( $title, $slots, $dbDomain );
parent::__construct( $page, $slots, $dbDomain );
Assert::parameterType( \stdClass::class, $row, '$row' );
$this->mId = intval( $row->rev_id );
$this->mPageId = intval( $row->rev_page );
$this->mComment = $comment;
@ -97,14 +96,12 @@ class RevisionStoreRecord extends RevisionRecord {
}
// sanity check
if (
$this->mPageId && $this->mTitle->exists()
&& $this->mPageId !== $this->mTitle->getArticleID()
) {
$pageIdBasedOnPage = $this->getArticleId( $this->mPage );
if ( $this->mPageId && $pageIdBasedOnPage && $this->mPageId !== $pageIdBasedOnPage ) {
throw new InvalidArgumentException(
'The given Title (' . $this->mTitle->getPrefixedText() . ')' .
'The given page (' . $this->mPage . ')' .
' does not belong to page ID ' . $this->mPageId .
' but actually belongs to ' . $this->mTitle->getArticleID()
' but actually belongs to ' . $this->getArticleId( $this->mPage )
);
}
}
@ -134,7 +131,7 @@ class RevisionStoreRecord extends RevisionRecord {
return parent::isDeleted( $field );
}
protected function userCan( $field, User $user ) {
public function userCan( $field, Authority $performer ) {
if ( $this->isCurrent() && $field === self::DELETED_TEXT ) {
// Current revisions of pages cannot have the content hidden. Skipping this
// check is very useful for Parser as it fetches templates using newKnownCurrent().
@ -142,7 +139,7 @@ class RevisionStoreRecord extends RevisionRecord {
return true; // no need to check
}
return parent::userCan( $field, $user );
return parent::userCan( $field, $performer );
}
/**
@ -183,24 +180,24 @@ class RevisionStoreRecord extends RevisionRecord {
/**
* @param int $audience
* @param User|null $user
* @param Authority|null $performer
*
* @return UserIdentity The identity of the revision author, null if access is forbidden.
*/
public function getUser( $audience = self::FOR_PUBLIC, User $user = null ) {
public function getUser( $audience = self::FOR_PUBLIC, Authority $performer = null ) {
// overwritten just to add a guarantee to the contract
return parent::getUser( $audience, $user );
return parent::getUser( $audience, $performer );
}
/**
* @param int $audience
* @param User|null $user
* @param Authority|null $performer
*
* @return CommentStoreComment The revision comment, null if access is forbidden.
*/
public function getComment( $audience = self::FOR_PUBLIC, User $user = null ) {
public function getComment( $audience = self::FOR_PUBLIC, Authority $performer = null ) {
// overwritten just to add a guarantee to the contract
return parent::getComment( $audience, $user );
return parent::getComment( $audience, $performer );
}
/**

View file

@ -340,9 +340,9 @@ class ContributionsLookupTest extends MediaWikiIntegrationTestCase {
$actual = $revisions[$idx];
$this->assertSame( $expected->getId(), $actual->getId(), 'rev_id' );
$this->assertSame( $expected->getPageId(), $actual->getPageId(), 'rev_page_id' );
$this->assertSame(
$expected->getPageAsLinkTarget()->getPrefixedDBkey(),
$actual->getPageAsLinkTarget()->getPrefixedDBkey()
$this->assertTrue(
$expected->getPageAsLinkTarget()->isSameLinkAs( $actual->getPageAsLinkTarget() ),
'getPageAsLinkTarget'
);
$expectedUser = $expected->getUser( RevisionRecord::RAW )->getName();

View file

@ -4,6 +4,8 @@ namespace MediaWiki\Tests\Revision;
use CommentStoreComment;
use InvalidArgumentException;
use MediaWiki\Page\PageIdentity;
use MediaWiki\Page\PageIdentityValue;
use MediaWiki\Revision\MutableRevisionRecord;
use MediaWiki\Revision\MutableRevisionSlots;
use MediaWiki\Revision\RevisionAccessException;
@ -12,9 +14,12 @@ use MediaWiki\Revision\SlotRecord;
use MediaWiki\Storage\RevisionSlotsUpdate;
use MediaWiki\User\UserIdentityValue;
use MediaWikiIntegrationTestCase;
use MockTitleTrait;
use TextContent;
use Title;
use TitleValue;
use User;
use Wikimedia\Assert\PreconditionException;
use WikitextContent;
/**
@ -22,7 +27,7 @@ use WikitextContent;
* @covers \MediaWiki\Revision\RevisionRecord
*/
class MutableRevisionRecordTest extends MediaWikiIntegrationTestCase {
use MockTitleTrait;
use RevisionRecordTests;
protected function setUp() : void {
@ -67,28 +72,46 @@ class MutableRevisionRecordTest extends MediaWikiIntegrationTestCase {
public function provideConstructor() {
$title = Title::newFromText( 'Dummy' );
$title->resetArticleID( 17 );
yield [
$title,
'acmewiki'
yield 'local wiki, with title' => [ $title, PageIdentity::LOCAL ];
yield 'local wiki' => [
new PageIdentityValue( 17, NS_MAIN, 'Dummy', PageIdentity::LOCAL ),
PageIdentity::LOCAL,
];
yield 'foreign wiki' => [
new PageIdentityValue( 17, NS_MAIN, 'Dummy', 'acmewiki' ),
'acmewiki',
PreconditionException::class
];
// This case exists for b/c and should eventually be deprecated.
yield 'foreign wiki, with Title' => [ $title, 'acmewiki' ];
}
/**
* @dataProvider provideConstructor
*
* @param Title $title
* @param PageIdentity $page
* @param bool $wikiId
* @param string|null $expectedException
*/
public function testConstructorAndGetters(
Title $title,
$wikiId = false
PageIdentity $page,
$wikiId = PageIdentity::LOCAL,
string $expectedException = null
) {
$rec = new MutableRevisionRecord( $title, $wikiId );
$rec = new MutableRevisionRecord( $page, $wikiId );
$this->assertSame( $title, $rec->getPageAsLinkTarget(), 'getPageAsLinkTarget' );
$this->assertSame( $title, $rec->getPage(), 'getPage' );
$this->assertTrue( $page->isSamePageAs( $rec->getPage() ), 'getPage' );
$this->assertSame( $wikiId, $rec->getWikiId(), 'getWikiId' );
if ( $expectedException ) {
$this->expectException( $expectedException );
$rec->getPageAsLinkTarget();
} else {
$this->assertTrue(
TitleValue::newFromPage( $page )->isSameLinkAs( $rec->getPageAsLinkTarget() ),
'getPageAsLinkTarget'
);
}
}
public function provideConstructorFailure() {
@ -337,8 +360,7 @@ class MutableRevisionRecordTest extends MediaWikiIntegrationTestCase {
}
public function provideNotReadyForInsertion() {
/** @var Title $title */
$title = $this->createMock( Title::class );
$title = $this->makeMockTitle( 'Dummy' );
/** @var User $user */
$user = $this->createMock( User::class );
@ -414,7 +436,7 @@ class MutableRevisionRecordTest extends MediaWikiIntegrationTestCase {
$rev = new MutableRevisionRecord( Title::newFromText( 'Foo' ) );
$this->expectException( InvalidArgumentException::class );
$this->expectExceptionMessage(
'A User object must be given when checking FOR_THIS_USER audience.'
'An Authority object must be given when checking FOR_THIS_USER audience.'
);
$rev->audienceCan( RevisionRecord::DELETED_TEXT, RevisionRecord::FOR_THIS_USER );
}

View file

@ -4,6 +4,8 @@ namespace MediaWiki\Tests\Revision;
use CommentStoreComment;
use InvalidArgumentException;
use MediaWiki\Page\PageIdentity;
use MediaWiki\Page\PageIdentityValue;
use MediaWiki\Revision\RevisionArchiveRecord;
use MediaWiki\Revision\RevisionRecord;
use MediaWiki\Revision\RevisionSlots;
@ -11,8 +13,11 @@ use MediaWiki\Revision\SlotRecord;
use MediaWiki\User\UserIdentity;
use MediaWiki\User\UserIdentityValue;
use MediaWikiIntegrationTestCase;
use stdClass;
use TextContent;
use Title;
use TitleValue;
use Wikimedia\Assert\PreconditionException;
/**
* @covers \MediaWiki\Revision\RevisionArchiveRecord
@ -59,9 +64,6 @@ class RevisionArchiveRecordTest extends MediaWikiIntegrationTestCase {
}
public function provideConstructor() {
$title = Title::newFromText( 'Dummy' );
$title->resetArticleID( 17 );
$user = new UserIdentityValue( 11, 'Tester', 0 );
$comment = CommentStoreComment::newUnsavedComment( 'Hello World' );
@ -72,7 +74,7 @@ class RevisionArchiveRecordTest extends MediaWikiIntegrationTestCase {
$protoRow = [
'ar_id' => '5',
'ar_rev_id' => '7',
'ar_page_id' => strval( $title->getArticleID() ),
'ar_page_id' => '17',
'ar_timestamp' => '20200101000000',
'ar_deleted' => 0,
'ar_minor_edit' => 0,
@ -83,12 +85,42 @@ class RevisionArchiveRecordTest extends MediaWikiIntegrationTestCase {
$row = $protoRow;
yield 'all info' => [
new PageIdentityValue( 17, NS_MAIN, 'Dummy', 'acmewiki' ),
$user,
$comment,
(object)$row,
$slots,
'acmewiki',
PreconditionException::class
];
yield 'all info, local' => [
new PageIdentityValue( 17, NS_MAIN, 'Dummy', PageIdentity::LOCAL ),
$user,
$comment,
(object)$row,
$slots,
];
$title = Title::newFromText( 'Dummy' );
$title->resetArticleID( 17 );
yield 'all info, local, with Title' => [
$title,
$user,
$comment,
(object)$row,
$slots,
'acmewiki'
];
// this case exists for b/c and should be deprecated
yield 'all info, foreign, with Title' => [
$title,
$user,
$comment,
(object)$row,
$slots,
'acmewiki',
];
$row = $protoRow;
@ -139,25 +171,26 @@ class RevisionArchiveRecordTest extends MediaWikiIntegrationTestCase {
/**
* @dataProvider provideConstructor
*
* @param Title $title
* @param Title $page
* @param UserIdentity $user
* @param CommentStoreComment $comment
* @param stdClass $row
* @param RevisionSlots $slots
* @param bool $wikiId
* @param string|null $expectedException
*/
public function testConstructorAndGetters(
Title $title,
PageIdentity $page,
UserIdentity $user,
CommentStoreComment $comment,
$row,
RevisionSlots $slots,
$wikiId = false
$wikiId = PageIdentity::LOCAL,
string $expectedException = null
) {
$rec = new RevisionArchiveRecord( $title, $user, $comment, $row, $slots, $wikiId );
$rec = new RevisionArchiveRecord( $page, $user, $comment, $row, $slots, $wikiId );
$this->assertSame( $title, $rec->getPageAsLinkTarget(), 'getPageAsLinkTarget' );
$this->assertSame( $title, $rec->getPage(), 'getPage' );
$this->assertTrue( $page->isSamePageAs( $rec->getPage() ), 'getPage' );
$this->assertSame( $user, $rec->getUser( RevisionRecord::RAW ), 'getUser' );
$this->assertSame( $comment, $rec->getComment(), 'getComment' );
@ -188,6 +221,16 @@ class RevisionArchiveRecordTest extends MediaWikiIntegrationTestCase {
} else {
$this->assertSame( $slots->computeSha1(), $rec->getSha1(), 'getSha1' );
}
if ( $expectedException ) {
$this->expectException( $expectedException );
$rec->getPageAsLinkTarget();
} else {
$this->assertTrue(
TitleValue::newFromPage( $page )->isSameLinkAs( $rec->getPageAsLinkTarget() ),
'getPageAsLinkTarget'
);
}
}
public function provideConstructorFailure() {
@ -214,8 +257,28 @@ class RevisionArchiveRecordTest extends MediaWikiIntegrationTestCase {
'ar_sha1' => $slots->computeSha1(),
];
yield 'mismatching wiki ID' => [
new PageIdentityValue(
$title->getArticleID(),
$title->getNamespace(),
$title->getDBkey(),
PageIdentity::LOCAL
),
$user,
$comment,
'not a row',
$slots,
'acmewiki',
PreconditionException::class
];
yield 'not a row' => [
$title,
new PageIdentityValue(
$title->getArticleID(),
$title->getNamespace(),
$title->getDBkey(),
'acmewiki'
),
$user,
$comment,
'not a row',
@ -251,23 +314,25 @@ class RevisionArchiveRecordTest extends MediaWikiIntegrationTestCase {
/**
* @dataProvider provideConstructorFailure
*
* @param Title $title
* @param PageIdentity $page
* @param UserIdentity $user
* @param CommentStoreComment $comment
* @param stdClass $row
* @param RevisionSlots $slots
* @param bool $wikiId
* @param bool $wikiId,
* @param string|null $expectedException
*/
public function testConstructorFailure(
Title $title,
PageIdentity $page,
UserIdentity $user,
CommentStoreComment $comment,
$row,
RevisionSlots $slots,
$wikiId = false
$wikiId = false,
string $expectedException = InvalidArgumentException::class
) {
$this->expectException( InvalidArgumentException::class );
new RevisionArchiveRecord( $title, $user, $comment, $row, $slots, $wikiId );
$this->expectException( $expectedException );
new RevisionArchiveRecord( $page, $user, $comment, $row, $slots, $wikiId );
}
/**

View file

@ -838,18 +838,20 @@ abstract class RevisionStoreDbTestBase extends MediaWikiIntegrationTestCase {
$localLoadBalancerMock->expects( $this->never() )
->method( $this->anything() );
$this->setService( 'DBLoadBalancer', $localLoadBalancerMock );
try {
$this->setService( 'DBLoadBalancer', $localLoadBalancerMock );
$storeRecord = $store->getRevisionByTitle(
new TitleValue( $page->getTitle()->getNamespace(), $page->getTitle()->getDBkey() )
);
$storeRecord = $store->getRevisionByTitle(
new TitleValue( $page->getTitle()->getNamespace(), $page->getTitle()->getDBkey() )
);
$this->assertSame( $revRecord->getId(), $storeRecord->getId() );
$this->assertTrue( $storeRecord->getSlot( SlotRecord::MAIN )->getContent()->equals( $content ) );
$this->assertSame( __METHOD__, $storeRecord->getComment()->text );
// Restore the original load balancer to make test teardown work
$this->setService( 'DBLoadBalancer', $dbLoadBalancer );
$this->assertSame( $revRecord->getId(), $storeRecord->getId() );
$this->assertTrue( $storeRecord->getSlot( SlotRecord::MAIN )->getContent()->equals( $content ) );
$this->assertSame( __METHOD__, $storeRecord->getComment()->text );
} finally {
// Restore the original load balancer to make test teardown work
$this->setService( 'DBLoadBalancer', $dbLoadBalancer );
}
}
/**

View file

@ -4,6 +4,8 @@ namespace MediaWiki\Tests\Revision;
use CommentStoreComment;
use InvalidArgumentException;
use MediaWiki\Page\PageIdentity;
use MediaWiki\Page\PageIdentityValue;
use MediaWiki\Revision\RevisionRecord;
use MediaWiki\Revision\RevisionSlots;
use MediaWiki\Revision\RevisionStoreRecord;
@ -11,8 +13,11 @@ use MediaWiki\Revision\SlotRecord;
use MediaWiki\User\UserIdentity;
use MediaWiki\User\UserIdentityValue;
use MediaWikiIntegrationTestCase;
use stdClass;
use TextContent;
use Title;
use TitleValue;
use Wikimedia\Assert\PreconditionException;
/**
* @covers \MediaWiki\Revision\RevisionStoreRecord
@ -56,9 +61,6 @@ class RevisionStoreRecordTest extends MediaWikiIntegrationTestCase {
}
public function provideConstructor() {
$title = Title::newFromText( 'Dummy' );
$title->resetArticleID( 17 );
$user = new UserIdentityValue( 11, 'Tester', 0 );
$comment = CommentStoreComment::newUnsavedComment( 'Hello World' );
@ -68,7 +70,7 @@ class RevisionStoreRecordTest extends MediaWikiIntegrationTestCase {
$protoRow = [
'rev_id' => '7',
'rev_page' => strval( $title->getArticleID() ),
'rev_page' => '17',
'rev_timestamp' => '20200101000000',
'rev_deleted' => 0,
'rev_minor_edit' => 0,
@ -80,6 +82,28 @@ class RevisionStoreRecordTest extends MediaWikiIntegrationTestCase {
$row = $protoRow;
yield 'all info' => [
new PageIdentityValue( 17, NS_MAIN, 'Dummy', 'acmewiki' ),
$user,
$comment,
(object)$row,
$slots,
'acmewiki',
PreconditionException::class
];
yield 'all info, local' => [
new PageIdentityValue( 17, NS_MAIN, 'Dummy', PageIdentity::LOCAL ),
$user,
$comment,
(object)$row,
$slots,
];
$title = Title::newFromText( 'Dummy' );
$title->resetArticleID( 17 );
// This case exists for b/c and should be deprecated.
yield 'all info, foreign with Title' => [
$title,
$user,
$comment,
@ -88,6 +112,14 @@ class RevisionStoreRecordTest extends MediaWikiIntegrationTestCase {
'acmewiki'
];
yield 'all info, local with Title' => [
$title,
$user,
$comment,
(object)$row,
$slots,
];
$row = $protoRow;
$row['rev_minor_edit'] = '1';
$row['rev_deleted'] = strval( RevisionRecord::DELETED_USER );
@ -147,25 +179,26 @@ class RevisionStoreRecordTest extends MediaWikiIntegrationTestCase {
/**
* @dataProvider provideConstructor
*
* @param Title $title
* @param PageIdentity $page
* @param UserIdentity $user
* @param CommentStoreComment $comment
* @param stdClass $row
* @param RevisionSlots $slots
* @param bool $wikiId
* @param string|null $expectedException
*/
public function testConstructorAndGetters(
Title $title,
PageIdentity $page,
UserIdentity $user,
CommentStoreComment $comment,
$row,
RevisionSlots $slots,
$wikiId = false
$wikiId = PageIdentity::LOCAL,
string $expectedException = null
) {
$rec = new RevisionStoreRecord( $title, $user, $comment, $row, $slots, $wikiId );
$rec = new RevisionStoreRecord( $page, $user, $comment, $row, $slots, $wikiId );
$this->assertSame( $title, $rec->getPageAsLinkTarget(), 'getPageAsLinkTarget' );
$this->assertSame( $title, $rec->getPage(), 'getPage' );
$this->assertTrue( $page->isSamePageAs( $rec->getPage() ), 'getPage' );
$this->assertSame( $user, $rec->getUser( RevisionRecord::RAW ), 'getUser' );
$this->assertSame( $comment, $rec->getComment(), 'getComment' );
@ -211,6 +244,16 @@ class RevisionStoreRecordTest extends MediaWikiIntegrationTestCase {
'isCurrent'
);
}
if ( $expectedException ) {
$this->expectException( $expectedException );
$rec->getPageAsLinkTarget();
} else {
$this->assertTrue(
TitleValue::newFromPage( $page )->isSameLinkAs( $rec->getPageAsLinkTarget() ),
'getPageAsLinkTarget'
);
}
}
public function provideConstructorFailure() {
@ -238,6 +281,15 @@ class RevisionStoreRecordTest extends MediaWikiIntegrationTestCase {
];
yield 'not a row' => [
new PageIdentityValue( 17, NS_MAIN, 'Dummy', 'acmewiki' ),
$user,
$comment,
'not a row',
$slots,
'acmewiki'
];
yield 'wiki mismatch' => [
$title,
$user,
$comment,
@ -283,7 +335,7 @@ class RevisionStoreRecordTest extends MediaWikiIntegrationTestCase {
/**
* @dataProvider provideConstructorFailure
*
* @param Title $title
* @param PageIdentity $page
* @param UserIdentity $user
* @param CommentStoreComment $comment
* @param stdClass $row
@ -291,7 +343,7 @@ class RevisionStoreRecordTest extends MediaWikiIntegrationTestCase {
* @param bool $wikiId
*/
public function testConstructorFailure(
Title $title,
PageIdentity $page,
UserIdentity $user,
CommentStoreComment $comment,
$row,
@ -299,7 +351,7 @@ class RevisionStoreRecordTest extends MediaWikiIntegrationTestCase {
$wikiId = false
) {
$this->expectException( InvalidArgumentException::class );
new RevisionStoreRecord( $title, $user, $comment, $row, $slots, $wikiId );
new RevisionStoreRecord( $page, $user, $comment, $row, $slots, $wikiId );
}
public function provideIsCurrent() {

View file

@ -16,6 +16,7 @@ use Wikimedia\Rdbms\LoadBalancer;
* For test cases that need Database interaction see RevisionDbTestBase.
*/
class RevisionTest extends MediaWikiIntegrationTestCase {
use MockTitleTrait;
protected function setUp() : void {
parent::setUp();
@ -43,33 +44,6 @@ class RevisionTest extends MediaWikiIntegrationTestCase {
// We can't prepare that here though, since we don't yet have a dummy DB
}
/**
* @param string $model
* @return Title
*/
public function getMockTitle( $model = CONTENT_MODEL_WIKITEXT ) {
$mock = $this->getMockBuilder( Title::class )
->disableOriginalConstructor()
->getMock();
$mock->expects( $this->any() )
->method( 'getNamespace' )
->will( $this->returnValue( $this->getDefaultWikitextNS() ) );
$mock->expects( $this->any() )
->method( 'getPrefixedText' )
->will( $this->returnValue( 'RevisionTest' ) );
$mock->expects( $this->any() )
->method( 'getDBkey' )
->will( $this->returnValue( 'RevisionTest' ) );
$mock->expects( $this->any() )
->method( 'getArticleID' )
->will( $this->returnValue( 23 ) );
$mock->expects( $this->any() )
->method( 'getContentModel' )
->will( $this->returnValue( $model ) );
return $mock;
}
/**
* @dataProvider provideConstructFromArray
* @covers Revision::__construct
@ -80,7 +54,7 @@ class RevisionTest extends MediaWikiIntegrationTestCase {
$this->hideDeprecated( 'Revision::getContent' );
$this->hideDeprecated( 'Revision::__construct' );
$rev = new Revision( $rowArray, 0, $this->getMockTitle() );
$rev = new Revision( $rowArray, 0, $this->makeMockTitle( __METHOD__, [ 'id' => 23 ] ) );
$this->assertNotNull( $rev->getContent(), 'no content object available' );
$this->assertEquals( CONTENT_MODEL_JAVASCRIPT, $rev->getContent()->getModel() );
$this->assertEquals( CONTENT_MODEL_JAVASCRIPT, $rev->getContentModel() );
@ -94,7 +68,7 @@ class RevisionTest extends MediaWikiIntegrationTestCase {
$this->hideDeprecated( 'Revision::getContent' );
$this->hideDeprecated( 'Revision::__construct' );
$rev = new Revision( [], 0, $this->getMockTitle() );
$rev = new Revision( [], 0, $this->makeMockTitle( __METHOD__, [ 'id' => 23 ] ) );
$this->assertNull( $rev->getContent(), 'no content object should be available' );
}
@ -103,10 +77,9 @@ class RevisionTest extends MediaWikiIntegrationTestCase {
* @covers \MediaWiki\Revision\RevisionStore::newMutableRevisionFromArray
*/
public function testConstructFromArrayWithBadPageId() {
Wikimedia\suppressWarnings();
$rev = new Revision( [ 'page' => 77777777 ] );
$this->assertSame( 77777777, $rev->getPage() );
Wikimedia\restoreWarnings();
$this->hideDeprecated( 'Revision::__construct' );
$this->expectException( RevisionAccessException::class );
new Revision( [ 'page' => 77777777 ] );
}
public function provideConstructFromArray_userSetAsExpected() {
@ -163,7 +136,7 @@ class RevisionTest extends MediaWikiIntegrationTestCase {
$expectedUserName = $testUser->getName();
}
$rev = new Revision( $rowArray, 0, $this->getMockTitle() );
$rev = new Revision( $rowArray, 0, $this->makeMockTitle( __METHOD__, [ 'id' => 23 ] ) );
$this->assertEquals( $expectedUserId, $rev->getUser() );
$this->assertEquals( $expectedUserName, $rev->getUserText() );
}
@ -204,7 +177,7 @@ class RevisionTest extends MediaWikiIntegrationTestCase {
$this->expectException( get_class( $expectedException ) );
$this->expectExceptionMessage( $expectedException->getMessage() );
$this->expectExceptionCode( $expectedException->getCode() );
new Revision( $rowArray, 0, $this->getMockTitle() );
new Revision( $rowArray, 0, $this->makeMockTitle( __METHOD__, [ 'id' => 23 ] ) );
}
/**
@ -303,7 +276,7 @@ class RevisionTest extends MediaWikiIntegrationTestCase {
$this->hideDeprecated( 'Revision::__construct' );
$row = (object)$arrayData;
$rev = new Revision( $row, 0, $this->getMockTitle() );
$rev = new Revision( $row, 0, $this->makeMockTitle( __METHOD__, [ 'id' => 23 ] ) );
$assertions( $this, $rev );
}
@ -312,14 +285,13 @@ class RevisionTest extends MediaWikiIntegrationTestCase {
* @covers \MediaWiki\Revision\RevisionStore::newMutableRevisionFromArray
*/
public function testConstructFromRowWithBadPageId() {
Wikimedia\suppressWarnings();
$rev = new Revision( (object)[
$this->hideDeprecated( 'Revision::__construct' );
$this->expectException( RevisionAccessException::class );
new Revision( (object)[
'rev_page' => 77777777,
'rev_comment_text' => '',
'rev_comment_data' => null,
] );
$this->assertSame( 77777777, $rev->getPage() );
Wikimedia\restoreWarnings();
}
public function provideGetId() {
@ -340,7 +312,7 @@ class RevisionTest extends MediaWikiIntegrationTestCase {
public function testGetId( $rowArray, $expectedId ) {
$this->hideDeprecated( 'Revision::__construct' );
$rev = new Revision( $rowArray, 0, $this->getMockTitle() );
$rev = new Revision( $rowArray, 0, $this->makeMockTitle( __METHOD__, [ 'id' => 23 ] ) );
$this->assertEquals( $expectedId, $rev->getId() );
}
@ -357,7 +329,7 @@ class RevisionTest extends MediaWikiIntegrationTestCase {
$this->hideDeprecated( 'Revision::setId' );
$this->hideDeprecated( 'Revision::__construct' );
$rev = new Revision( [], 0, $this->getMockTitle() );
$rev = new Revision( [], 0, $this->makeMockTitle( __METHOD__, [ 'id' => 23 ] ) );
$rev->setId( $input );
$this->assertSame( $expected, $rev->getId() );
}
@ -376,7 +348,7 @@ class RevisionTest extends MediaWikiIntegrationTestCase {
$this->hideDeprecated( 'Revision::getUserText' );
$this->hideDeprecated( 'Revision::__construct' );
$rev = new Revision( [], 0, $this->getMockTitle() );
$rev = new Revision( [], 0, $this->makeMockTitle( __METHOD__, [ 'id' => 23 ] ) );
$rev->setUserIdAndName( $inputId, $name );
$this->assertSame( $expectedId, $rev->getUser( Revision::RAW ) );
$this->assertEquals( $name, $rev->getUserText( Revision::RAW ) );
@ -396,7 +368,7 @@ class RevisionTest extends MediaWikiIntegrationTestCase {
$this->hideDeprecated( 'Revision::getParentId' );
$this->hideDeprecated( 'Revision::__construct' );
$rev = new Revision( $rowArray, 0, $this->getMockTitle() );
$rev = new Revision( $rowArray, 0, $this->makeMockTitle( __METHOD__, [ 'id' => 23 ] ) );
$this->assertSame( $expected, $rev->getParentId() );
}
@ -496,7 +468,7 @@ class RevisionTest extends MediaWikiIntegrationTestCase {
$this->hideDeprecated( 'Revision::__construct' );
$this->hideDeprecated( RevisionStore::class . '::loadRevisionFromTitle' );
$title = $this->getMockTitle();
$title = $this->makeMockTitle( __METHOD__, [ 'id' => 23 ] );
$conditions = [
'rev_id=page_latest',
@ -642,7 +614,7 @@ class RevisionTest extends MediaWikiIntegrationTestCase {
$this->hideDeprecated( 'Revision::getSize' );
$this->hideDeprecated( 'Revision::__construct' );
$title = $this->getMockTitle();
$title = $this->makeMockTitle( __METHOD__, [ 'id' => 23 ] );
$rec = new MutableRevisionRecord( $title );
$rev = new Revision( $rec, 0, $title );
@ -660,7 +632,7 @@ class RevisionTest extends MediaWikiIntegrationTestCase {
$this->hideDeprecated( 'Revision::getSize' );
$this->hideDeprecated( 'Revision::__construct' );
$title = $this->getMockTitle();
$title = $this->makeMockTitle( __METHOD__, [ 'id' => 23 ] );
$rec = $this->getMockBuilder( RevisionRecord::class )
->disableOriginalConstructor()
@ -680,7 +652,7 @@ class RevisionTest extends MediaWikiIntegrationTestCase {
$this->hideDeprecated( 'Revision::getSha1' );
$this->hideDeprecated( 'Revision::__construct' );
$title = $this->getMockTitle();
$title = $this->makeMockTitle( __METHOD__, [ 'id' => 23 ] );
$rec = new MutableRevisionRecord( $title );
$rev = new Revision( $rec, 0, $title );
@ -699,7 +671,7 @@ class RevisionTest extends MediaWikiIntegrationTestCase {
$this->hideDeprecated( 'Revision::getSha1' );
$this->hideDeprecated( 'Revision::__construct' );
$title = $this->getMockTitle();
$title = $this->makeMockTitle( __METHOD__, [ 'id' => 23 ] );
$rec = $this->getMockBuilder( RevisionRecord::class )
->disableOriginalConstructor()
@ -719,7 +691,7 @@ class RevisionTest extends MediaWikiIntegrationTestCase {
$this->hideDeprecated( 'Revision::getContent' );
$this->hideDeprecated( 'Revision::__construct' );
$title = $this->getMockTitle();
$title = $this->makeMockTitle( __METHOD__, [ 'id' => 23 ] );
$rec = new MutableRevisionRecord( $title );
$rev = new Revision( $rec, 0, $title );
@ -738,7 +710,7 @@ class RevisionTest extends MediaWikiIntegrationTestCase {
$this->hideDeprecated( 'Revision::getContent' );
$this->hideDeprecated( 'Revision::__construct' );
$title = $this->getMockTitle();
$title = $this->makeMockTitle( __METHOD__, [ 'id' => 23 ] );
$rec = $this->getMockBuilder( RevisionRecord::class )
->disableOriginalConstructor()

View file

@ -420,8 +420,12 @@ class PageUpdaterTest extends MediaWikiIntegrationTestCase {
$this->assertSame( $expected['summary'], $summary );
$this->assertSame( EDIT_NEW, $flags );
$title = $renderedRevision->getRevision()->getPageAsLinkTarget();
$this->assertSame( $expected['title']->getFullText(), $title->getFullText() );
$this->assertTrue(
$expected['title']->isSamePageAs( $renderedRevision->getRevision()->getPage() )
);
$this->assertTrue(
$expected['title']->isSameLinkAs( $renderedRevision->getRevision()->getPageAsLinkTarget() )
);
$slots = $renderedRevision->getRevision()->getSlots();
foreach ( $expected['slots'] as $slot => $content ) {

View file

@ -379,7 +379,7 @@ class TextContentTest extends MediaWikiLangTestCase {
* @covers TextContent::getDeletionUpdates
*/
public function testDeletionUpdates( $model, $text, $expectedStuff ) {
$page = $this->getNonexistingTestPage( get_class( $this ) . '-' . $this->getName() );
$page = $this->getNonexistingTestPage( __METHOD__ );
$title = $page->getTitle();
$content = ContentHandler::makeContent( $text, $title, $model );

View file

@ -12,6 +12,7 @@ use MediaWiki\User\UserIdentityValue;
* @covers BlockLevelPass
*/
class ParserMethodsTest extends MediaWikiLangTestCase {
use MockTitleTrait;
public static function providePreSaveTransform() {
return [
@ -211,25 +212,10 @@ class ParserMethodsTest extends MediaWikiLangTestCase {
$this->assertStringContainsString( 'class="mw-parser-output"', $text );
}
/**
* @param string $name
* @return Title
*/
private function getMockTitle( $name ) {
$title = $this->createMock( Title::class );
$title->method( 'getPrefixedDBkey' )->willReturn( $name );
$title->method( 'getPrefixedText' )->willReturn( $name );
$title->method( 'getDBkey' )->willReturn( $name );
$title->method( 'getText' )->willReturn( $name );
$title->method( 'getNamespace' )->willReturn( 0 );
$title->method( 'getPageLanguage' )->willReturn(
MediaWikiServices::getInstance()->getLanguageFactory()->getLanguage( 'en' ) );
return $title;
}
public function provideRevisionAccess() {
$title = $this->getMockTitle( 'ParserRevisionAccessTest' );
$title = $this->makeMockTitle( 'ParserRevisionAccessTest', [
'language' => MediaWikiServices::getInstance()->getLanguageFactory()->getLanguage( 'en' )
] );
$frank = $this->getMockBuilder( User::class )
->disableOriginalConstructor()
@ -337,7 +323,9 @@ class ParserMethodsTest extends MediaWikiLangTestCase {
$expectedInHtml,
$expectedInPst = null
) {
$title = $this->getMockTitle( 'ParserRevisionAccessTest' );
$title = $this->makeMockTitle( 'ParserRevisionAccessTest', [
'language' => MediaWikiServices::getInstance()->getLanguageFactory()->getLanguage( 'en' )
] );
$po->enableLimitReport( false );

View file

@ -140,8 +140,6 @@ class RevisionSourceHandlerTest extends MediaWikiIntegrationTestCase {
* @param array $data
*/
private function assertResponseData( RevisionRecord $rev, array $data ): void {
$title = $rev->getPageAsLinkTarget();
$this->assertSame( $rev->getId(), $data['id'] );
$this->assertSame( $rev->getSize(), $data['size'] );
$this->assertSame( $rev->isMinor(), $data['minor'] );
@ -149,9 +147,12 @@ class RevisionSourceHandlerTest extends MediaWikiIntegrationTestCase {
wfTimestampOrNull( TS_ISO_8601, $rev->getTimestamp() ),
$data['timestamp']
);
$this->assertSame( $title->getArticleID(), $data['page']['id'] );
$this->assertSame( $title->getDBkey(), $data['page']['key'] ); // assume main namespace
$this->assertSame( $title->getText(), $data['page']['title'] ); // assume main namespace
$this->assertSame( $rev->getPage()->getId(), $data['page']['id'] );
$this->assertSame( $rev->getPage()->getDBkey(), $data['page']['key'] ); // assume main namespace
$this->assertSame(
$rev->getPageAsLinkTarget()->getText(),
$data['page']['title']
); // assume main namespace
$this->assertSame( CONTENT_MODEL_WIKITEXT, $data['content_model'] );
$this->assertSame( 'https://example.com/rights', $data['license']['url'] );
$this->assertSame( 'some rights', $data['license']['title'] );

View file

@ -14,7 +14,7 @@ use MediaWiki\Revision\SlotRecord;
use MediaWiki\Storage\EditResult;
use MediaWiki\Storage\EditResultBuilder;
use MediaWikiIntegrationTestCase;
use Title;
use MockTitleTrait;
use WikiPage;
use WikitextContent;
@ -24,6 +24,7 @@ use WikitextContent;
* @see EditResultBuilderTest for non-DB tests
*/
class EditResultBuilderDbTest extends MediaWikiIntegrationTestCase {
use MockTitleTrait;
private const PAGE_NAME = 'ManualRevertTestPage';
private const CONTENT_A = 'Aaa.';
@ -151,9 +152,7 @@ class EditResultBuilderDbTest extends MediaWikiIntegrationTestCase {
) : MutableRevisionRecord {
$parentRevision = $this->getLatestTestRevision();
$revision = new MutableRevisionRecord(
$this->createMock( Title::class )
);
$revision = new MutableRevisionRecord( $this->wikiPage->getTitle() );
$revision->setParentId( $parentRevision->getId() );
$revision->setPageId( $this->wikiPage->getId() );
$revision->setContent(

View file

@ -13,6 +13,7 @@ trait MockTitleTrait {
* @param array $props Additional properties to set. Supported keys:
* - id: int
* - namespace: int
* - language: Language
*
* @return Title|MockObject
*/
@ -39,9 +40,11 @@ trait MockTitleTrait {
$title->method( 'getPrefixedDBkey' )->willReturn( str_replace( ' ', '_', $preText ) );
$title->method( 'getArticleID' )->willReturn( $id );
$title->method( 'getId' )->willReturn( $id );
$title->method( 'getNamespace' )->willReturn( $props['namespace'] ?? 0 );
$title->method( 'exists' )->willReturn( $id > 0 );
$title->method( 'getTouched' )->willReturn( $id ? '20200101223344' : false );
$title->method( 'getPageLanguage' )->willReturn( $props['language'] ?? 'qqx' );
return $title;
}

View file

@ -4,9 +4,10 @@ namespace MediaWiki\Tests\Revision;
use MediaWiki\Revision\ContributionsSegment;
use MediaWiki\Storage\MutableRevisionRecord;
use Title;
use MockTitleTrait;
class ContributionsSegmentTest extends \MediaWikiUnitTestCase {
use MockTitleTrait;
public function provideFlags() {
yield [
@ -72,8 +73,7 @@ class ContributionsSegmentTest extends \MediaWikiUnitTestCase {
* @covers \MediaWiki\Revision\ContributionsSegment
*/
public function testConstruction() {
$mockTitle = $this->createNoOpMock( Title::class, [ 'getArticleID' ] );
$mockTitle->method( 'getArticleID' )->willReturn( 1 );
$mockTitle = $this->makeMockTitle( 'Foo', [ 'id' => 1 ] );
$revisionRecords = [ new MutableRevisionRecord( $mockTitle ), new MutableRevisionRecord( $mockTitle ) ];
$before = 'before';
$after = 'after';

View file

@ -10,7 +10,7 @@ use MediaWiki\Storage\EditResult;
use MediaWiki\Storage\EditResultBuilder;
use MediaWiki\Storage\PageUpdateException;
use MediaWikiUnitTestCase;
use Title;
use MockTitleTrait;
use Wikimedia\Rdbms\ILoadBalancer;
/**
@ -19,6 +19,7 @@ use Wikimedia\Rdbms\ILoadBalancer;
* @see EditResultBuilderDbTest for integration tests with the database
*/
class EditResultBuilderTest extends MediaWikiUnitTestCase {
use MockTitleTrait;
/**
* @covers \MediaWiki\Storage\EditResultBuilder::buildEditResult
@ -325,7 +326,7 @@ class EditResultBuilderTest extends MediaWikiUnitTestCase {
*/
private function getDummyRevision() : MutableRevisionRecord {
return new MutableRevisionRecord(
$this->createMock( Title::class )
$this->makeMockTitle( 'Dummy' )
);
}

View file

@ -9,9 +9,9 @@ use MediaWiki\Storage\EditResult;
use MediaWiki\Storage\RevertedTagUpdate;
use MediaWiki\Storage\RevisionRecord;
use MediaWikiUnitTestCase;
use MockTitleTrait;
use PHPUnit\Framework\MockObject\Builder\InvocationMocker;
use Psr\Log\LoggerInterface;
use Title;
use Wikimedia\Rdbms\ILoadBalancer;
/**
@ -19,6 +19,7 @@ use Wikimedia\Rdbms\ILoadBalancer;
* @see RevertedTagUpdateIntegrationTest for an end-to-end test with the database
*/
class RevertedTagUpdateTest extends MediaWikiUnitTestCase {
use MockTitleTrait;
/**
* Convenience function for creating a RevertedTagUpdate that does not use
@ -121,7 +122,7 @@ class RevertedTagUpdateTest extends MediaWikiUnitTestCase {
?string $sha1 = null
) {
$revisionRecord = new MutableRevisionRecord(
$this->createMock( Title::class )
$this->makeMockTitle( __METHOD__, [ 'id' => $pageId ] )
);
$revisionRecord->setId( $revisionId );
$revisionRecord->setTimestamp( $timestamp );

View file

@ -90,7 +90,7 @@ class PageIdentityValueTest extends MediaWikiUnitTestCase {
'#0 [1:Bar_Baz]'
];
yield [
new PageIdentityValue( 7, 200, 'tea', 'codewiki', PageIdentity::LOCAL ),
new PageIdentityValue( 7, 200, 'tea', 'codewiki' ),
'#7@codewiki [200:tea]'
];
}

View file

@ -7,52 +7,34 @@ use MediaWiki\Revision\MutableRevisionRecord;
* @covers FauxSearchResultSet
*/
class FauxSearchResultTest extends MediaWikiUnitTestCase {
use MockTitleTrait;
public function testConstruct() {
$title = $this->getTitleMock( 'Foo' );
$title = $this->makeMockTitle( 'Foo', [ 'id' => 0 ] );
$r = new FauxSearchResult( $title );
$this->assertSame( $title, $r->getTitle() );
$this->assertTrue( $r->isMissingRevision() );
$this->assertNull( $r->getFile() );
$this->assertSame( 0, $r->getWordCount() );
$title = $this->getTitleMock( 'Foo', 1 );
$title = $this->makeMockTitle( 'Foo', [ 'id' => 1 ] );
$rev = new MutableRevisionRecord( $title );
$rev->setTimestamp( '20000101000000' );
$r = new FauxSearchResult( $title, $rev );
$this->assertFalse( $r->isMissingRevision() );
$this->assertSame( '20000101000000', $r->getTimestamp() );
$title = $this->getTitleMock( 'Foo', 1 );
$title = $this->makeMockTitle( 'Foo', [ 'id' => 1 ] );
$rev = new MutableRevisionRecord( $title );
$r = new FauxSearchResult( $title, $rev, null, '123' );
$this->assertSame( 3, $r->getByteSize() );
$title = $this->getTitleMock( 'Foo' );
$title = $this->makeMockTitle( 'Foo', [ 'id' => 0 ] );
$file = $this->getFileMock( 'Foo.png' );
$r = new FauxSearchResult( $title, null, $file );
$this->assertSame( $file, $r->getFile() );
}
/**
* @param string $titleText
* @param int|null $articleId
* @return Title
*/
private function getTitleMock( $titleText, $articleId = null ) {
$title = $this->getMockBuilder( Title::class )
->disableOriginalConstructor()
->setMethods( [ 'getPrefixedText', 'getArticleID' ] )
->getMock();
$title->method( 'getPrefixedText' )->willReturn( $titleText );
if ( $articleId ) {
$title->method( 'getArticleID' )->willReturn( $articleId );
} else {
$title->expects( $this->never() )->method( 'getArticleID' );
}
return $title;
}
/**
* @param string $filename
* @return File