engines.
* Skin::escapeSearchLink() is deprecated. Use Skin::getSearchLink() or the skin
template option 'searchaction' instead.
+* LoadBalancer::haveIndex() and LoadBalancer::isNonZeroLoad() have
+ been deprecated.
=== Other changes in 1.34 ===
* …
'ExtensionProcessor' => __DIR__ . '/includes/registration/ExtensionProcessor.php',
'ExtensionRegistry' => __DIR__ . '/includes/registration/ExtensionRegistry.php',
'ExternalStore' => __DIR__ . '/includes/externalstore/ExternalStore.php',
+ 'ExternalStoreAccess' => __DIR__ . '/includes/externalstore/ExternalStoreAccess.php',
'ExternalStoreDB' => __DIR__ . '/includes/externalstore/ExternalStoreDB.php',
+ 'ExternalStoreException' => __DIR__ . '/includes/externalstore/ExternalStoreException.php',
'ExternalStoreFactory' => __DIR__ . '/includes/externalstore/ExternalStoreFactory.php',
'ExternalStoreHttp' => __DIR__ . '/includes/externalstore/ExternalStoreHttp.php',
'ExternalStoreMedium' => __DIR__ . '/includes/externalstore/ExternalStoreMedium.php',
+ 'ExternalStoreMemory' => __DIR__ . '/includes/externalstore/ExternalStoreMemory.php',
'ExternalStoreMwstore' => __DIR__ . '/includes/externalstore/ExternalStoreMwstore.php',
'ExternalUserNames' => __DIR__ . '/includes/user/ExternalUserNames.php',
'FSFile' => __DIR__ . '/includes/libs/filebackend/fsfile/FSFile.php',
return $this->getService( 'EventRelayerGroup' );
}
+ /**
+ * @since 1.34
+ * @return \ExternalStoreAccess
+ */
+ public function getExternalStoreAccess() {
+ return $this->getService( 'ExternalStoreAccess' );
+ }
+
/**
* @since 1.31
* @return \ExternalStoreFactory
use Action;
use Exception;
-use FatalError;
use Hooks;
use MediaWiki\Linker\LinkTarget;
+use MediaWiki\Session\SessionManager;
use MediaWiki\Special\SpecialPageFactory;
+use MediaWiki\User\UserIdentity;
use MessageSpecifier;
-use MWException;
use NamespaceInfo;
use RequestContext;
use SpecialPage;
/** @var NamespaceInfo */
private $nsInfo;
+ /** @var string[][] Access rights for groups and users in these groups */
+ private $groupPermissions;
+
+ /** @var string[][] Permission keys revoked from users in each group */
+ private $revokePermissions;
+
+ /** @var string[] A list of available rights, in addition to the ones defined by the core */
+ private $availableRights;
+
+ /** @var string[] Cached results of getAllRights() */
+ private $allRights = false;
+
+ /** @var string[][] Cached user rights */
+ private $usersRights = null;
+
+ /** @var string[] Cached rights for isEveryoneAllowed */
+ private $cachedRights = [];
+
+ /**
+ * Array of Strings Core rights.
+ * Each of these should have a corresponding message of the form
+ * "right-$right".
+ * @showinitializer
+ */
+ private $coreRights = [
+ 'apihighlimits',
+ 'applychangetags',
+ 'autoconfirmed',
+ 'autocreateaccount',
+ 'autopatrol',
+ 'bigdelete',
+ 'block',
+ 'blockemail',
+ 'bot',
+ 'browsearchive',
+ 'changetags',
+ 'createaccount',
+ 'createpage',
+ 'createtalk',
+ 'delete',
+ 'deletechangetags',
+ 'deletedhistory',
+ 'deletedtext',
+ 'deletelogentry',
+ 'deleterevision',
+ 'edit',
+ 'editcontentmodel',
+ 'editinterface',
+ 'editprotected',
+ 'editmyoptions',
+ 'editmyprivateinfo',
+ 'editmyusercss',
+ 'editmyuserjson',
+ 'editmyuserjs',
+ 'editmywatchlist',
+ 'editsemiprotected',
+ 'editsitecss',
+ 'editsitejson',
+ 'editsitejs',
+ 'editusercss',
+ 'edituserjson',
+ 'edituserjs',
+ 'hideuser',
+ 'import',
+ 'importupload',
+ 'ipblock-exempt',
+ 'managechangetags',
+ 'markbotedits',
+ 'mergehistory',
+ 'minoredit',
+ 'move',
+ 'movefile',
+ 'move-categorypages',
+ 'move-rootuserpages',
+ 'move-subpages',
+ 'nominornewtalk',
+ 'noratelimit',
+ 'override-export-depth',
+ 'pagelang',
+ 'patrol',
+ 'patrolmarks',
+ 'protect',
+ 'purge',
+ 'read',
+ 'reupload',
+ 'reupload-own',
+ 'reupload-shared',
+ 'rollback',
+ 'sendemail',
+ 'siteadmin',
+ 'suppressionlog',
+ 'suppressredirect',
+ 'suppressrevision',
+ 'unblockself',
+ 'undelete',
+ 'unwatchedpages',
+ 'upload',
+ 'upload_by_url',
+ 'userrights',
+ 'userrights-interwiki',
+ 'viewmyprivateinfo',
+ 'viewmywatchlist',
+ 'viewsuppressed',
+ 'writeapi',
+ ];
+
/**
* @param SpecialPageFactory $specialPageFactory
* @param string[] $whitelistRead
* @param string[] $whitelistReadRegexp
* @param bool $emailConfirmToEdit
* @param bool $blockDisablesLogin
+ * @param string[][] $groupPermissions
+ * @param string[][] $revokePermissions
+ * @param string[] $availableRights
* @param NamespaceInfo $nsInfo
*/
public function __construct(
$whitelistReadRegexp,
$emailConfirmToEdit,
$blockDisablesLogin,
+ $groupPermissions,
+ $revokePermissions,
+ $availableRights,
NamespaceInfo $nsInfo
) {
$this->specialPageFactory = $specialPageFactory;
$this->whitelistReadRegexp = $whitelistReadRegexp;
$this->emailConfirmToEdit = $emailConfirmToEdit;
$this->blockDisablesLogin = $blockDisablesLogin;
+ $this->groupPermissions = $groupPermissions;
+ $this->revokePermissions = $revokePermissions;
+ $this->availableRights = $availableRights;
$this->nsInfo = $nsInfo;
}
* - RIGOR_SECURE : does cheap and expensive checks, using the master as needed
*
* @return bool
- * @throws Exception
*/
public function userCan( $action, User $user, LinkTarget $page, $rigor = self::RIGOR_SECURE ) {
return !count( $this->getPermissionErrorsInternal( $action, $user, $page, $rigor, true ) );
* whose corresponding errors may be ignored.
*
* @return array Array of arrays of the arguments to wfMessage to explain permissions problems.
- * @throws Exception
*/
public function getPermissionErrors(
$action,
* @param bool $fromReplica Whether to check the replica DB instead of the master
*
* @return bool
- * @throws FatalError
- * @throws MWException
*/
public function isBlockedFrom( User $user, LinkTarget $page, $fromReplica = false ) {
$blocked = $user->isHidden();
* @param LinkTarget $page
*
* @return array List of errors
- * @throws FatalError
- * @throws MWException
*/
private function checkPermissionHooks(
$action,
* @param LinkTarget $page
*
* @return array List of errors
- * @throws FatalError
- * @throws MWException
*/
private function checkReadPermissions(
$action,
* @param LinkTarget $page
*
* @return array List of errors
- * @throws MWException
*/
private function checkUserBlock(
$action,
* @param LinkTarget $page
*
* @return array List of errors
- * @throws FatalError
- * @throws MWException
*/
private function checkQuickPermissions(
$action,
}
if ( $right != '' && !$user->isAllowedAll( 'protect', $right ) ) {
$wikiPages = '';
+ /** @var Title $wikiPage */
foreach ( $cascadingSources as $wikiPage ) {
$wikiPages .= '* [[:' . $wikiPage->getPrefixedText() . "]]\n";
}
* @param LinkTarget $page
*
* @return array List of errors
- * @throws Exception
*/
private function checkActionPermissions(
$action,
return $errors;
}
+ /**
+ * Testing a permission
+ *
+ * @since 1.34
+ *
+ * @param UserIdentity $user
+ * @param string $action
+ *
+ * @return bool
+ */
+ public function userHasRight( UserIdentity $user, $action = '' ) {
+ if ( $action === '' ) {
+ return true; // In the spirit of DWIM
+ }
+ // Use strict parameter to avoid matching numeric 0 accidentally inserted
+ // by misconfiguration: 0 == 'foo'
+ return in_array( $action, $this->getUserPermissions( $user ), true );
+ }
+
+ /**
+ * Get the permissions this user has.
+ *
+ * @since 1.34
+ *
+ * @param UserIdentity $user
+ *
+ * @return string[] permission names
+ */
+ public function getUserPermissions( UserIdentity $user ) {
+ $user = User::newFromIdentity( $user );
+ if ( !isset( $this->usersRights[ $user->getId() ] ) ) {
+ $this->usersRights[ $user->getId() ] = $this->getGroupPermissions(
+ $user->getEffectiveGroups()
+ );
+ Hooks::run( 'UserGetRights', [ $user, &$this->usersRights[ $user->getId() ] ] );
+
+ // Deny any rights denied by the user's session, unless this
+ // endpoint has no sessions.
+ if ( !defined( 'MW_NO_SESSION' ) ) {
+ // FIXME: $user->getRequest().. need to be replaced with something else
+ $allowedRights = $user->getRequest()->getSession()->getAllowedUserRights();
+ if ( $allowedRights !== null ) {
+ $this->usersRights[ $user->getId() ] = array_intersect(
+ $this->usersRights[ $user->getId() ],
+ $allowedRights
+ );
+ }
+ }
+
+ Hooks::run( 'UserGetRightsRemove', [ $user, &$this->usersRights[ $user->getId() ] ] );
+ // Force reindexation of rights when a hook has unset one of them
+ $this->usersRights[ $user->getId() ] = array_values(
+ array_unique( $this->usersRights[ $user->getId() ] )
+ );
+
+ if (
+ $user->isLoggedIn() &&
+ $this->blockDisablesLogin &&
+ $user->getBlock()
+ ) {
+ $anon = new User;
+ $this->usersRights[ $user->getId() ] = array_intersect(
+ $this->usersRights[ $user->getId() ],
+ $this->getUserPermissions( $anon )
+ );
+ }
+ }
+ return $this->usersRights[ $user->getId() ];
+ }
+
+ /**
+ * Clears users permissions cache, if specific user is provided it tries to clear
+ * permissions cache only for provided user.
+ *
+ * @since 1.34
+ *
+ * @param User|null $user
+ */
+ public function invalidateUsersRightsCache( $user = null ) {
+ if ( $user !== null ) {
+ if ( isset( $this->usersRights[ $user->getId() ] ) ) {
+ unset( $this->usersRights[$user->getId()] );
+ }
+ } else {
+ $this->usersRights = null;
+ }
+ }
+
+ /**
+ * Check, if the given group has the given permission
+ *
+ * If you're wanting to check whether all users have a permission, use
+ * PermissionManager::isEveryoneAllowed() instead. That properly checks if it's revoked
+ * from anyone.
+ *
+ * @since 1.34
+ *
+ * @param string $group Group to check
+ * @param string $role Role to check
+ *
+ * @return bool
+ */
+ public function groupHasPermission( $group, $role ) {
+ return isset( $this->groupPermissions[$group][$role] ) &&
+ $this->groupPermissions[$group][$role] &&
+ !( isset( $this->revokePermissions[$group][$role] ) &&
+ $this->revokePermissions[$group][$role] );
+ }
+
+ /**
+ * Get the permissions associated with a given list of groups
+ *
+ * @since 1.34
+ *
+ * @param array $groups Array of Strings List of internal group names
+ * @return array Array of Strings List of permission key names for given groups combined
+ */
+ public function getGroupPermissions( $groups ) {
+ $rights = [];
+ // grant every granted permission first
+ foreach ( $groups as $group ) {
+ if ( isset( $this->groupPermissions[$group] ) ) {
+ $rights = array_merge( $rights,
+ // array_filter removes empty items
+ array_keys( array_filter( $this->groupPermissions[$group] ) ) );
+ }
+ }
+ // now revoke the revoked permissions
+ foreach ( $groups as $group ) {
+ if ( isset( $this->revokePermissions[$group] ) ) {
+ $rights = array_diff( $rights,
+ array_keys( array_filter( $this->revokePermissions[$group] ) ) );
+ }
+ }
+ return array_unique( $rights );
+ }
+
+ /**
+ * Get all the groups who have a given permission
+ *
+ * @since 1.34
+ *
+ * @param string $role Role to check
+ * @return array Array of Strings List of internal group names with the given permission
+ */
+ public function getGroupsWithPermission( $role ) {
+ $allowedGroups = [];
+ foreach ( array_keys( $this->groupPermissions ) as $group ) {
+ if ( $this->groupHasPermission( $group, $role ) ) {
+ $allowedGroups[] = $group;
+ }
+ }
+ return $allowedGroups;
+ }
+
+ /**
+ * Check if all users may be assumed to have the given permission
+ *
+ * We generally assume so if the right is granted to '*' and isn't revoked
+ * on any group. It doesn't attempt to take grants or other extension
+ * limitations on rights into account in the general case, though, as that
+ * would require it to always return false and defeat the purpose.
+ * Specifically, session-based rights restrictions (such as OAuth or bot
+ * passwords) are applied based on the current session.
+ *
+ * @param string $right Right to check
+ *
+ * @return bool
+ * @since 1.34
+ */
+ public function isEveryoneAllowed( $right ) {
+ // Use the cached results, except in unit tests which rely on
+ // being able change the permission mid-request
+ if ( isset( $this->cachedRights[$right] ) ) {
+ return $this->cachedRights[$right];
+ }
+
+ if ( !isset( $this->groupPermissions['*'][$right] )
+ || !$this->groupPermissions['*'][$right] ) {
+ $this->cachedRights[$right] = false;
+ return false;
+ }
+
+ // If it's revoked anywhere, then everyone doesn't have it
+ foreach ( $this->revokePermissions as $rights ) {
+ if ( isset( $rights[$right] ) && $rights[$right] ) {
+ $this->cachedRights[$right] = false;
+ return false;
+ }
+ }
+
+ // Remove any rights that aren't allowed to the global-session user,
+ // unless there are no sessions for this endpoint.
+ if ( !defined( 'MW_NO_SESSION' ) ) {
+
+ // XXX: think what could be done with the below
+ $allowedRights = SessionManager::getGlobalSession()->getAllowedUserRights();
+ if ( $allowedRights !== null && !in_array( $right, $allowedRights, true ) ) {
+ $this->cachedRights[$right] = false;
+ return false;
+ }
+ }
+
+ // Allow extensions to say false
+ if ( !Hooks::run( 'UserIsEveryoneAllowed', [ $right ] ) ) {
+ $this->cachedRights[$right] = false;
+ return false;
+ }
+
+ $this->cachedRights[$right] = true;
+ return true;
+ }
+
+ /**
+ * Get a list of all available permissions.
+ *
+ * @since 1.34
+ *
+ * @return string[] Array of permission names
+ */
+ public function getAllPermissions() {
+ if ( $this->allRights === false ) {
+ if ( count( $this->availableRights ) ) {
+ $this->allRights = array_unique( array_merge(
+ $this->coreRights,
+ $this->availableRights
+ ) );
+ } else {
+ $this->allRights = $this->coreRights;
+ }
+ Hooks::run( 'UserGetAllRights', [ &$this->allRights ] );
+ }
+ return $this->allRights;
+ }
+
+ /**
+ * Overrides user permissions cache
+ *
+ * @since 1.34
+ *
+ * @param User $user
+ * @param string[]|string $rights
+ *
+ * @throws Exception
+ */
+ public function overrideUserRightsForTesting( $user, $rights = [] ) {
+ if ( !defined( 'MW_PHPUNIT_TEST' ) ) {
+ throw new Exception( __METHOD__ . ' can not be called outside of tests' );
+ }
+ $this->usersRights[ $user->getId() ] = is_array( $rights ) ? $rights : [ $rights ];
+ }
+
}
* in RevisionStore instead.
*
* @param Title $title The title of the page this Revision is associated with.
- * @param bool|string $wikiId the wiki ID of the site this Revision belongs to,
- * or false for the local site.
+ * @param bool|string $dbDomain DB domain of the relevant wiki or false for the current one.
*
* @throws MWException
*/
- function __construct( Title $title, $wikiId = false ) {
+ function __construct( Title $title, $dbDomain = false ) {
$slots = new MutableRevisionSlots();
- parent::__construct( $title, $slots, $wikiId );
+ parent::__construct( $title, $slots, $dbDomain );
$this->mSlots = $slots; // redundant, but nice for static analysis
}
* @param object $row An archive table row. Use RevisionStore::getArchiveQueryInfo() to build
* a query that yields the required fields.
* @param RevisionSlots $slots The slots of this revision.
- * @param bool|string $wikiId the wiki ID of the site this Revision belongs to,
- * or false for the local site.
+ * @param bool|string $dbDomain DB domain of the relevant wiki or false for the current one.
*/
function __construct(
Title $title,
CommentStoreComment $comment,
$row,
RevisionSlots $slots,
- $wikiId = false
+ $dbDomain = false
) {
- parent::__construct( $title, $slots, $wikiId );
+ parent::__construct( $title, $slots, $dbDomain );
Assert::parameterType( 'object', $row, '$row' );
$timestamp = wfTimestamp( TS_MW, $row->ar_timestamp );
*
* @param Title $title The title of the page this Revision is associated with.
* @param RevisionSlots $slots The slots of this revision.
- * @param bool|string $wikiId the wiki ID of the site this Revision belongs to,
- * or false for the local site.
+ * @param bool|string $dbDomain DB domain of the relevant wiki or false for the current one.
*
* @throws MWException
*/
- function __construct( Title $title, RevisionSlots $slots, $wikiId = false ) {
- Assert::parameterType( 'string|boolean', $wikiId, '$wikiId' );
+ function __construct( Title $title, RevisionSlots $slots, $dbDomain = false ) {
+ Assert::parameterType( 'string|boolean', $dbDomain, '$dbDomain' );
$this->mTitle = $title;
$this->mSlots = $slots;
- $this->mWiki = $wikiId;
+ $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();
} else {
$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 ) {
wfDebug( "Checking for $permissionlist due to $field match on $bitfield\n" );
- return $user->isAllowedAny( ...$permissions );
+ foreach ( $permissions as $perm ) {
+ if ( $permissionManager->userHasRight( $user, $perm ) ) {
+ return true;
+ }
+ }
+ return false;
} else {
$text = $title->getPrefixedText();
wfDebug( "Checking for $permissionlist on $text due to $field match on $bitfield\n" );
private $roleRegistery;
/** @var string|bool */
- private $wikiId;
+ private $dbDomain;
/**
* @param ILoadBalancer $loadBalancer
* @param SlotRoleRegistry $roleRegistry
- * @param bool|string $wikiId
+ * @param bool|string $dbDomain DB domain of the relevant wiki or false for the current one
*/
public function __construct(
ILoadBalancer $loadBalancer,
SlotRoleRegistry $roleRegistry,
- $wikiId = false
+ $dbDomain = false
) {
$this->loadBalancer = $loadBalancer;
$this->roleRegistery = $roleRegistry;
- $this->wikiId = $wikiId;
-
+ $this->dbDomain = $dbDomain;
$this->saveParseLogger = new NullLogger();
}
User $forUser = null,
array $hints = []
) {
- if ( $rev->getWikiId() !== $this->wikiId ) {
+ if ( $rev->getWikiId() !== $this->dbDomain ) {
throw new InvalidArgumentException( 'Mismatching wiki ID ' . $rev->getWikiId() );
}
$flags = defined( 'MW_PHPUNIT_TEST' ) || $dbIndex === DB_REPLICA
? 0 : ILoadBalancer::CONN_TRX_AUTOCOMMIT;
- $db = $this->loadBalancer->getConnectionRef( $dbIndex, [], $this->wikiId, $flags );
+ $db = $this->loadBalancer->getConnectionRef( $dbIndex, [], $this->dbDomain, $flags );
return 1 + (int)$db->selectField(
'revision',
$slotOutput[$role] = $out;
// XXX: should the SlotRoleHandler be able to intervene here?
- $combinedOutput->mergeInternalMetaDataFrom( $out, $role );
+ $combinedOutput->mergeInternalMetaDataFrom( $out );
$combinedOutput->mergeTrackingMetaDataFrom( $out );
}
/**
* @var bool|string
*/
- private $wikiId;
+ private $dbDomain;
/**
* @var boolean
* @param ILoadBalancer $loadBalancer
* @param SqlBlobStore $blobStore
* @param WANObjectCache $cache A cache for caching revision rows. This can be the local
- * wiki's default instance even if $wikiId refers to a different wiki, since
+ * wiki's default instance even if $dbDomain refers to a different wiki, since
* makeGlobalKey() is used to constructed a key that allows cached revision rows from
* the same database to be re-used between wikis. For example, enwiki and frwiki will
* use the same cache keys for revision rows from the wikidatawiki database, regardless
* @param SlotRoleRegistry $slotRoleRegistry
* @param int $mcrMigrationStage An appropriate combination of SCHEMA_COMPAT_XXX flags
* @param ActorMigration $actorMigration
- * @param bool|string $wikiId
- *
+ * @param bool|string $dbDomain DB domain of the relevant wiki or false for the current one
*/
public function __construct(
ILoadBalancer $loadBalancer,
SlotRoleRegistry $slotRoleRegistry,
$mcrMigrationStage,
ActorMigration $actorMigration,
- $wikiId = false
+ $dbDomain = false
) {
- Assert::parameterType( 'string|boolean', $wikiId, '$wikiId' );
+ Assert::parameterType( 'string|boolean', $dbDomain, '$dbDomain' );
Assert::parameterType( 'integer', $mcrMigrationStage, '$mcrMigrationStage' );
Assert::parameter(
( $mcrMigrationStage & SCHEMA_COMPAT_READ_BOTH ) !== SCHEMA_COMPAT_READ_BOTH,
$this->slotRoleRegistry = $slotRoleRegistry;
$this->mcrMigrationStage = $mcrMigrationStage;
$this->actorMigration = $actorMigration;
- $this->wikiId = $wikiId;
+ $this->dbDomain = $dbDomain;
$this->logger = new NullLogger();
}
* @throws RevisionAccessException
*/
private function assertCrossWikiContentLoadingIsSafe() {
- if ( $this->wikiId !== false && $this->hasMcrSchemaFlags( SCHEMA_COMPAT_READ_OLD ) ) {
+ if ( $this->dbDomain !== false && $this->hasMcrSchemaFlags( SCHEMA_COMPAT_READ_OLD ) ) {
throw new RevisionAccessException(
"Cross-wiki content loading is not supported by the pre-MCR schema"
);
*/
private function getDBConnection( $mode, $groups = [] ) {
$lb = $this->getDBLoadBalancer();
- return $lb->getConnection( $mode, $groups, $this->wikiId );
+ return $lb->getConnection( $mode, $groups, $this->dbDomain );
}
/**
*/
private function getDBConnectionRef( $mode ) {
$lb = $this->getDBLoadBalancer();
- return $lb->getConnectionRef( $mode, [], $this->wikiId );
+ return $lb->getConnectionRef( $mode, [], $this->dbDomain );
}
/**
$queryFlags = self::READ_NORMAL;
}
- $canUseTitleNewFromId = ( $pageId !== null && $pageId > 0 && $this->wikiId === false );
+ $canUseTitleNewFromId = ( $pageId !== null && $pageId > 0 && $this->dbDomain === false );
list( $dbMode, $dbOptions ) = DBAccessObjectUtils::getDBOptions( $queryFlags );
$titleFlags = ( $dbMode == DB_MASTER ? Title::GAID_FOR_UPDATE : 0 );
$comment,
(object)$revisionRow,
new RevisionSlots( $newSlots ),
- $this->wikiId
+ $this->dbDomain
);
return $rev;
throw new MWException( 'Failed to get database lock for T202032' );
}
$fname = __METHOD__;
- $dbw->onTransactionResolution( function ( $trigger, $dbw ) use ( $fname ) {
- $dbw->unlock( 'fix-for-T202032', $fname );
- } );
+ $dbw->onTransactionResolution(
+ function ( $trigger, IDatabase $dbw ) use ( $fname ) {
+ $dbw->unlock( 'fix-for-T202032', $fname );
+ }
+ );
$dbw->delete( 'revision', [ 'rev_id' => $revisionRow['rev_id'] ], __METHOD__ );
$row->ar_user ?? null,
$row->ar_user_text ?? null,
$row->ar_actor ?? null,
- $this->wikiId
+ $this->dbDomain
);
} catch ( InvalidArgumentException $ex ) {
wfWarn( __METHOD__ . ': ' . $title->getPrefixedDBkey() . ': ' . $ex->getMessage() );
$slots = $this->newRevisionSlots( $row->ar_rev_id, $row, null, $queryFlags, $title );
- return new RevisionArchiveRecord( $title, $user, $comment, $row, $slots, $this->wikiId );
+ return new RevisionArchiveRecord( $title, $user, $comment, $row, $slots, $this->dbDomain );
}
/**
$row->rev_user ?? null,
$row->rev_user_text ?? null,
$row->rev_actor ?? null,
- $this->wikiId
+ $this->dbDomain
);
} catch ( InvalidArgumentException $ex ) {
wfWarn( __METHOD__ . ': ' . $title->getPrefixedDBkey() . ': ' . $ex->getMessage() );
[ 'rev_id' => intval( $revId ) ]
);
},
- $title, $user, $comment, $row, $slots, $this->wikiId
+ $title, $user, $comment, $row, $slots, $this->dbDomain
);
} else {
$rev = new RevisionStoreRecord(
- $title, $user, $comment, $row, $slots, $this->wikiId );
+ $title, $user, $comment, $row, $slots, $this->dbDomain );
}
return $rev;
}
}
}
- $revision = new MutableRevisionRecord( $title, $this->wikiId );
+ $revision = new MutableRevisionRecord( $title, $this->dbDomain );
$this->initializeMutableRevisionFromArray( $revision, $fields );
if ( isset( $fields['content'] ) && is_array( $fields['content'] ) ) {
// remote wiki with unsuppressed ids, due to issues described in T222212.
if ( isset( $fields['user'] ) &&
( $fields['user'] instanceof UserIdentity ) &&
- ( $this->wikiId === false ||
+ ( $this->dbDomain === false ||
( !$fields['user']->getId() && !$fields['user']->getActorId() ) )
) {
$user = $fields['user'];
$fields['user'] ?? null,
$fields['user_text'] ?? null,
$fields['actor'] ?? null,
- $this->wikiId
+ $this->dbDomain
);
} catch ( InvalidArgumentException $ex ) {
$user = null;
* @throws MWException
*/
private function checkDatabaseWikiId( IDatabase $db ) {
- $storeWiki = $this->wikiId;
+ $storeWiki = $this->dbDomain;
$dbWiki = $db->getDomainID();
if ( $dbWiki === $storeWiki ) {
* @param object $row A row from the revision table. Use RevisionStore::getQueryInfo() to build
* a query that yields the required fields.
* @param RevisionSlots $slots The slots of this revision.
- * @param bool|string $wikiId the wiki ID of the site this Revision belongs to,
- * or false for the local site.
+ * @param bool|string $dbDomain DB domain of the relevant wiki or false for the current one.
*/
function __construct(
$callback,
CommentStoreComment $comment,
$row,
RevisionSlots $slots,
- $wikiId = false
+ $dbDomain = false
) {
- parent::__construct( $title, $user, $comment, $row, $slots, $wikiId );
+ parent::__construct( $title, $user, $comment, $row, $slots, $dbDomain );
$this->mCallback = $callback;
}
/**
* @since 1.32
*
- * @param bool|string $wikiId false for the current domain / wikid
+ * @param bool|string $dbDomain DB domain of the relevant wiki or false for the current one
*
* @return RevisionStore for the given wikiId with all necessary services and a logger
*/
- public function getRevisionStore( $wikiId = false ) {
- Assert::parameterType( 'string|boolean', $wikiId, '$wikiId' );
+ public function getRevisionStore( $dbDomain = false ) {
+ Assert::parameterType( 'string|boolean', $dbDomain, '$dbDomain' );
$store = new RevisionStore(
- $this->dbLoadBalancerFactory->getMainLB( $wikiId ),
- $this->blobStoreFactory->newSqlBlobStore( $wikiId ),
+ $this->dbLoadBalancerFactory->getMainLB( $dbDomain ),
+ $this->blobStoreFactory->newSqlBlobStore( $dbDomain ),
$this->cache, // Pass local cache instance; Leave cache sharing to RevisionStore.
$this->commentStore,
- $this->nameTables->getContentModels( $wikiId ),
- $this->nameTables->getSlotRoles( $wikiId ),
+ $this->nameTables->getContentModels( $dbDomain ),
+ $this->nameTables->getSlotRoles( $dbDomain ),
$this->slotRoleRegistry,
$this->mcrMigrationStage,
$this->actorMigration,
- $wikiId
+ $dbDomain
);
$store->setLogger( $this->loggerProvider->getLogger( 'RevisionStore' ) );
* @param object $row A row from the revision table. Use RevisionStore::getQueryInfo() to build
* a query that yields the required fields.
* @param RevisionSlots $slots The slots of this revision.
- * @param bool|string $wikiId the wiki ID of the site this Revision belongs to,
- * or false for the local site.
+ * @param bool|string $dbDomain DB domain of the relevant wiki or false for the current one.
*/
function __construct(
Title $title,
CommentStoreComment $comment,
$row,
RevisionSlots $slots,
- $wikiId = false
+ $dbDomain = false
) {
- parent::__construct( $title, $slots, $wikiId );
+ parent::__construct( $title, $slots, $dbDomain );
Assert::parameterType( 'object', $row, '$row' );
$this->mId = intval( $row->rev_id );
'BlobStoreFactory' => function ( MediaWikiServices $services ) : BlobStoreFactory {
return new BlobStoreFactory(
$services->getDBLoadBalancerFactory(),
+ $services->getExternalStoreAccess(),
$services->getMainWANObjectCache(),
new ServiceOptions( BlobStoreFactory::$constructorOptions,
$services->getMainConfig() ),
return new EventRelayerGroup( $services->getMainConfig()->get( 'EventRelayerConfig' ) );
},
+ 'ExternalStoreAccess' => function ( MediaWikiServices $services ) : ExternalStoreAccess {
+ return new ExternalStoreAccess(
+ $services->getExternalStoreFactory(),
+ LoggerFactory::getInstance( 'ExternalStore' )
+ );
+ },
+
'ExternalStoreFactory' => function ( MediaWikiServices $services ) : ExternalStoreFactory {
$config = $services->getMainConfig();
+ $writeStores = $config->get( 'DefaultExternalStore' );
return new ExternalStoreFactory(
- $config->get( 'ExternalStores' )
+ $config->get( 'ExternalStores' ),
+ ( $writeStores !== false ) ? (array)$writeStores : [],
+ $services->getDBLoadBalancer()->getLocalDomainID(),
+ LoggerFactory::getInstance( 'ExternalStore' )
);
},
$config->get( 'WhitelistReadRegexp' ),
$config->get( 'EmailConfirmToEdit' ),
$config->get( 'BlockDisablesLogin' ),
+ $config->get( 'GroupPermissions' ),
+ $config->get( 'RevokePermissions' ),
+ $config->get( 'AvailableRights' ),
$services->getNamespaceInfo()
);
},
use MediaWiki\Config\ServiceOptions;
use WANObjectCache;
use Wikimedia\Rdbms\ILBFactory;
+use ExternalStoreAccess;
/**
* Service for instantiating BlobStores
*/
private $lbFactory;
+ /**
+ * @var ExternalStoreAccess
+ */
+ private $extStoreAccess;
+
/**
* @var WANObjectCache
*/
public function __construct(
ILBFactory $lbFactory,
+ ExternalStoreAccess $extStoreAccess,
WANObjectCache $cache,
ServiceOptions $options,
Language $contLang
$options->assertRequiredOptions( self::$constructorOptions );
$this->lbFactory = $lbFactory;
+ $this->extStoreAccess = $extStoreAccess;
$this->cache = $cache;
$this->options = $options;
$this->contLang = $contLang;
$lb = $this->lbFactory->getMainLB( $dbDomain );
$store = new SqlBlobStore(
$lb,
+ $this->extStoreAccess,
$this->cache,
$dbDomain
);
namespace MediaWiki\Storage;
use DBAccessObjectUtils;
-use ExternalStore;
use IDBAccessObject;
use IExpiringStore;
use InvalidArgumentException;
use Language;
use MWException;
use WANObjectCache;
+use ExternalStoreAccess;
use Wikimedia\Assert\Assert;
use Wikimedia\Rdbms\IDatabase;
use Wikimedia\Rdbms\ILoadBalancer;
*/
private $dbLoadBalancer;
+ /**
+ * @var ExternalStoreAccess
+ */
+ private $extStoreAccess;
+
/**
* @var WANObjectCache
*/
private $cache;
/**
- * @var bool|string Wiki ID
+ * @var string|bool DB domain ID of a wiki or false for the local one
*/
private $dbDomain;
/**
* @param ILoadBalancer $dbLoadBalancer A load balancer for acquiring database connections
+ * @param ExternalStoreAccess $extStoreAccess Access layer for external storage
* @param WANObjectCache $cache A cache manager for caching blobs. This can be the local
* wiki's default instance even if $dbDomain refers to a different wiki, since
* makeGlobalKey() is used to constructed a key that allows cached blobs from the
*/
public function __construct(
ILoadBalancer $dbLoadBalancer,
+ ExternalStoreAccess $extStoreAccess,
WANObjectCache $cache,
$dbDomain = false
) {
$this->dbLoadBalancer = $dbLoadBalancer;
+ $this->extStoreAccess = $extStoreAccess;
$this->cache = $cache;
$this->dbDomain = $dbDomain;
}
# Write to external storage if required
if ( $this->useExternalStore ) {
// Store and get the URL
- $data = ExternalStore::insertToDefault( $data, [ 'wiki' => $this->dbDomain ] );
+ $data = $this->extStoreAccess->insert( $data, [ 'domain' => $this->dbDomain ] );
+ if ( !$data ) {
+ throw new BlobAccessException( "Failed to store text to external storage" );
+ }
if ( $flags ) {
$flags .= ',';
}
$this->getCacheTTL(),
function () use ( $url, $flags ) {
// Ignore $setOpts; blobs are immutable and negatives are not cached
- $blob = ExternalStore::fetchFromURL( $url, [ 'wiki' => $this->dbDomain ] );
+ $blob = $this->extStoreAccess
+ ->fetchFromURL( $url, [ 'domain' => $this->dbDomain ] );
return $blob === false ? false : $this->decompressData( $blob, $flags );
},
[ 'pcGroup' => self::TEXT_CACHE_GROUP, 'pcTTL' => WANObjectCache::TTL_PROC_LONG ]
);
} else {
- $blob = ExternalStore::fetchFromURL( $url, [ 'wiki' => $this->dbDomain ] );
+ $blob = $this->extStoreAccess->fetchFromURL( $url, [ 'domain' => $this->dbDomain ] );
return $blob === false ? false : $this->decompressData( $blob, $flags );
}
} else {
}
public function isReadOnly() {
- if ( $this->useExternalStore && ExternalStore::defaultStoresAreReadOnly() ) {
+ if ( $this->useExternalStore && $this->extStoreAccess->isReadOnly() ) {
return true;
}
}
function execFlags() {
- return $this->trxLevel ? OCI_NO_AUTO_COMMIT : OCI_COMMIT_ON_SUCCESS;
+ return $this->trxLevel() ? OCI_NO_AUTO_COMMIT : OCI_COMMIT_ON_SUCCESS;
}
/**
}
}
- if ( !$this->trxLevel ) {
+ if ( !$this->trxLevel() ) {
oci_commit( $this->conn );
}
}
protected function doBegin( $fname = __METHOD__ ) {
- $this->trxLevel = 1;
- $this->doQuery( 'SET CONSTRAINTS ALL DEFERRED' );
+ $this->query( 'SET CONSTRAINTS ALL DEFERRED' );
}
protected function doCommit( $fname = __METHOD__ ) {
- if ( $this->trxLevel ) {
+ if ( $this->trxLevel() ) {
$ret = oci_commit( $this->conn );
if ( !$ret ) {
throw new DBUnexpectedError( $this, $this->lastError() );
}
- $this->trxLevel = 0;
- $this->doQuery( 'SET CONSTRAINTS ALL IMMEDIATE' );
+ $this->query( 'SET CONSTRAINTS ALL IMMEDIATE' );
}
}
protected function doRollback( $fname = __METHOD__ ) {
- if ( $this->trxLevel ) {
+ if ( $this->trxLevel() ) {
oci_rollback( $this->conn );
- $this->trxLevel = 0;
- $this->doQuery( 'SET CONSTRAINTS ALL IMMEDIATE' );
+ $ignoreErrors = true;
+ $this->query( 'SET CONSTRAINTS ALL IMMEDIATE', $fname, $ignoreErrors );
}
}
}
}
- if ( !$this->trxLevel ) {
+ if ( !$this->trxLevel() ) {
oci_commit( $this->conn );
}
* as the possibility to have any storage format (i.e. for archives).
*
* @ingroup ExternalStorage
+ * @deprecated 1.34 Use ExternalStoreFactory directly instead
*/
class ExternalStore {
/**
* @param string $proto Type of external storage, should be a value in $wgExternalStores
* @param array $params Associative array of ExternalStoreMedium parameters
* @return ExternalStoreMedium|bool The store class or false on error
+ * @deprecated 1.34
*/
public static function getStoreObject( $proto, array $params = [] ) {
- return MediaWikiServices::getInstance()
- ->getExternalStoreFactory()
- ->getStoreObject( $proto, $params );
+ try {
+ return MediaWikiServices::getInstance()
+ ->getExternalStoreFactory()
+ ->getStore( $proto, $params );
+ } catch ( ExternalStoreException $e ) {
+ return false;
+ }
}
/**
* @param array $params Associative array of ExternalStoreMedium parameters
* @return string|bool The text stored or false on error
* @throws MWException
+ * @deprecated 1.34
*/
public static function fetchFromURL( $url, array $params = [] ) {
- $parts = explode( '://', $url, 2 );
- if ( count( $parts ) != 2 ) {
- return false; // invalid URL
- }
-
- list( $proto, $path ) = $parts;
- if ( $path == '' ) { // bad URL
- return false;
- }
-
- $store = self::getStoreObject( $proto, $params );
- if ( $store === false ) {
+ try {
+ return MediaWikiServices::getInstance()
+ ->getExternalStoreAccess()
+ ->fetchFromURL( $url, $params );
+ } catch ( ExternalStoreException $e ) {
return false;
}
-
- return $store->fetchFromURL( $url );
- }
-
- /**
- * Fetch data from multiple URLs with a minimum of round trips
- *
- * @param array $urls The URLs of the text to get
- * @return array Map from url to its data. Data is either string when found
- * or false on failure.
- * @throws MWException
- */
- public static function batchFetchFromURLs( array $urls ) {
- $batches = [];
- foreach ( $urls as $url ) {
- $scheme = parse_url( $url, PHP_URL_SCHEME );
- if ( $scheme ) {
- $batches[$scheme][] = $url;
- }
- }
- $retval = [];
- foreach ( $batches as $proto => $batchedUrls ) {
- $store = self::getStoreObject( $proto );
- if ( $store === false ) {
- continue;
- }
- $retval += $store->batchFetchFromURLs( $batchedUrls );
- }
- // invalid, not found, db dead, etc.
- $missing = array_diff( $urls, array_keys( $retval ) );
- if ( $missing ) {
- foreach ( $missing as $url ) {
- $retval[$url] = false;
- }
- }
-
- return $retval;
}
/**
* @param array $params Associative array of ExternalStoreMedium parameters
* @return string|bool The URL of the stored data item, or false on error
* @throws MWException
+ * @deprecated 1.34
*/
public static function insert( $url, $data, array $params = [] ) {
- $parts = explode( '://', $url, 2 );
- if ( count( $parts ) != 2 ) {
- return false; // invalid URL
- }
+ try {
+ $esFactory = MediaWikiServices::getInstance()->getExternalStoreFactory();
+ $location = $esFactory->getStoreLocationFromUrl( $url );
- list( $proto, $path ) = $parts;
- if ( $path == '' ) { // bad URL
+ return $esFactory->getStoreForUrl( $url, $params )->store( $location, $data );
+ } catch ( ExternalStoreException $e ) {
return false;
}
+ }
- $store = self::getStoreObject( $proto, $params );
- if ( $store === false ) {
- return false;
- } else {
- return $store->store( $path, $data );
- }
+ /**
+ * Fetch data from multiple URLs with a minimum of round trips
+ *
+ * @param array $urls The URLs of the text to get
+ * @return array Map from url to its data. Data is either string when found
+ * or false on failure.
+ * @throws MWException
+ * @deprecated 1.34
+ */
+ public static function batchFetchFromURLs( array $urls ) {
+ return MediaWikiServices::getInstance()->getExternalStoreAccess()->fetchFromURLs( $urls );
}
/**
* @param array $params Map of ExternalStoreMedium::__construct context parameters
* @return string The URL of the stored data item
* @throws MWException
+ * @deprecated 1.34
*/
public static function insertToDefault( $data, array $params = [] ) {
- global $wgDefaultExternalStore;
-
- return self::insertWithFallback( (array)$wgDefaultExternalStore, $data, $params );
+ return MediaWikiServices::getInstance()->getExternalStoreAccess()->insert( $data, $params );
}
/**
* @param array $params Map of ExternalStoreMedium::__construct context parameters
* @return string The URL of the stored data item
* @throws MWException
+ * @deprecated 1.34
*/
public static function insertWithFallback( array $tryStores, $data, array $params = [] ) {
- $error = false;
- while ( count( $tryStores ) > 0 ) {
- $index = mt_rand( 0, count( $tryStores ) - 1 );
- $storeUrl = $tryStores[$index];
- wfDebug( __METHOD__ . ": trying $storeUrl\n" );
- list( $proto, $path ) = explode( '://', $storeUrl, 2 );
- $store = self::getStoreObject( $proto, $params );
- if ( $store === false ) {
- throw new MWException( "Invalid external storage protocol - $storeUrl" );
- }
-
- try {
- if ( $store->isReadOnly( $path ) ) {
- $msg = 'read only';
- } else {
- $url = $store->store( $path, $data );
- if ( $url !== false ) {
- return $url; // a store accepted the write; done!
- }
- $msg = 'operation failed';
- }
- } catch ( Exception $error ) {
- $msg = 'caught exception';
- }
-
- unset( $tryStores[$index] ); // Don't try this one again!
- $tryStores = array_values( $tryStores ); // Must have consecutive keys
- wfDebugLog( 'ExternalStorage',
- "Unable to store text to external storage $storeUrl ($msg)" );
- }
- // All stores failed
- if ( $error ) {
- throw $error; // rethrow the last error
- } else {
- throw new MWException( "Unable to store text to external storage" );
- }
- }
-
- /**
- * @return bool Whether all the default insertion stores are marked as read-only
- * @since 1.31
- */
- public static function defaultStoresAreReadOnly() {
- global $wgDefaultExternalStore;
-
- $tryStores = (array)$wgDefaultExternalStore;
- if ( !$tryStores ) {
- return false; // no stores exists which can be "read only"
- }
-
- foreach ( $tryStores as $storeUrl ) {
- list( $proto, $path ) = explode( '://', $storeUrl, 2 );
- $store = self::getStoreObject( $proto, [] );
- if ( !$store->isReadOnly( $path ) ) {
- return false; // at least one store is not read-only
- }
- }
-
- return true; // all stores are read-only
+ return MediaWikiServices::getInstance()
+ ->getExternalStoreAccess()
+ ->insert( $data, $params, $tryStores );
}
/**
* @param string $wiki
* @return string The URL of the stored data item
* @throws MWException
+ * @deprecated 1.34 Use insertToDefault() with 'wiki' set
*/
public static function insertToForeignDefault( $data, $wiki ) {
- return self::insertToDefault( $data, [ 'wiki' => $wiki ] );
+ return MediaWikiServices::getInstance()
+ ->getExternalStoreAccess()
+ ->insert( $data, [ 'domain' => $wiki ] );
}
}
--- /dev/null
+<?php
+/**
+ * @defgroup ExternalStorage ExternalStorage
+ */
+
+use \Psr\Log\LoggerAwareInterface;
+use \Psr\Log\LoggerInterface;
+use \Psr\Log\NullLogger;
+
+/**
+ * Key/value blob storage for a collection of storage medium types (e.g. RDBMs, files)
+ *
+ * Multiple medium types can be active and each one can have multiple "locations" available.
+ * Blobs are stored under URLs of the form "<protocol>://<location>/<path>". Each type of storage
+ * medium has an associated protocol. Insertions will randomly pick mediums and locations from
+ * the provided list of writable medium-qualified locations. Insertions will also fail-over to
+ * other writable locations or mediums if one or more are not available.
+ *
+ * @ingroup ExternalStorage
+ * @since 1.34
+ */
+class ExternalStoreAccess implements LoggerAwareInterface {
+ /** @var ExternalStoreFactory */
+ private $storeFactory;
+ /** @var LoggerInterface */
+ private $logger;
+
+ /**
+ * @param ExternalStoreFactory $factory
+ * @param LoggerInterface|null $logger
+ */
+ public function __construct( ExternalStoreFactory $factory, LoggerInterface $logger = null ) {
+ $this->storeFactory = $factory;
+ $this->logger = $logger ?: new NullLogger();
+ }
+
+ public function setLogger( LoggerInterface $logger ) {
+ $this->logger = $logger;
+ }
+
+ /**
+ * Fetch data from given URL
+ *
+ * @see ExternalStoreFactory::getStore()
+ *
+ * @param string $url The URL of the text to get
+ * @param array $params Map of context parameters; same as ExternalStoreFactory::getStore()
+ * @return string|bool The text stored or false on error
+ * @throws ExternalStoreException
+ */
+ public function fetchFromURL( $url, array $params = [] ) {
+ return $this->storeFactory->getStoreForUrl( $url, $params )->fetchFromURL( $url );
+ }
+
+ /**
+ * Fetch data from multiple URLs with a minimum of round trips
+ *
+ * @see ExternalStoreFactory::getStore()
+ *
+ * @param array $urls The URLs of the text to get
+ * @param array $params Map of context parameters; same as ExternalStoreFactory::getStore()
+ * @return array Map of (url => string or false if not found)
+ * @throws ExternalStoreException
+ */
+ public function fetchFromURLs( array $urls, array $params = [] ) {
+ $batches = $this->storeFactory->getUrlsByProtocol( $urls );
+ $retval = [];
+ foreach ( $batches as $proto => $batchedUrls ) {
+ $store = $this->storeFactory->getStore( $proto, $params );
+ $retval += $store->batchFetchFromURLs( $batchedUrls );
+ }
+ // invalid, not found, db dead, etc.
+ $missing = array_diff( $urls, array_keys( $retval ) );
+ foreach ( $missing as $url ) {
+ $retval[$url] = false;
+ }
+
+ return $retval;
+ }
+
+ /**
+ * Insert data into storage and return the assigned URL
+ *
+ * This will randomly pick one of the available write storage locations to put the data.
+ * It will keep failing-over to any untried storage locations whenever one location is
+ * not usable.
+ *
+ * @see ExternalStoreFactory::getStore()
+ *
+ * @param string $data
+ * @param array $params Map of context parameters; same as ExternalStoreFactory::getStore()
+ * @param string[]|null $tryStores Refer to $wgDefaultExternalStore
+ * @return string|bool The URL of the stored data item, or false on error
+ * @throws ExternalStoreException
+ */
+ public function insert( $data, array $params = [], array $tryStores = null ) {
+ $tryStores = $tryStores ?? $this->storeFactory->getWriteBaseUrls();
+ if ( !$tryStores ) {
+ throw new ExternalStoreException( "List of external stores provided is empty." );
+ }
+
+ $error = false;
+ while ( count( $tryStores ) > 0 ) {
+ $index = mt_rand( 0, count( $tryStores ) - 1 );
+ $storeUrl = $tryStores[$index];
+
+ $this->logger->debug( __METHOD__ . ": trying $storeUrl\n" );
+
+ $store = $this->storeFactory->getStoreForUrl( $storeUrl, $params );
+ if ( $store === false ) {
+ throw new ExternalStoreException( "Invalid external storage protocol - $storeUrl" );
+ }
+
+ $location = $this->storeFactory->getStoreLocationFromUrl( $storeUrl );
+ try {
+ if ( $store->isReadOnly( $location ) ) {
+ $msg = 'read only';
+ } else {
+ $url = $store->store( $location, $data );
+ if ( strlen( $url ) ) {
+ return $url; // a store accepted the write; done!
+ }
+ $msg = 'operation failed';
+ }
+ } catch ( Exception $error ) {
+ $msg = 'caught ' . get_class( $error ) . ' exception: ' . $error->getMessage();
+ }
+
+ unset( $tryStores[$index] ); // Don't try this one again!
+ $tryStores = array_values( $tryStores ); // Must have consecutive keys
+ $this->logger->error(
+ "Unable to store text to external storage {store_path} ({failure})",
+ [ 'store_path' => $storeUrl, 'failure' => $msg ]
+ );
+ }
+ // All stores failed
+ if ( $error ) {
+ throw $error; // rethrow the last error
+ } else {
+ throw new ExternalStoreException( "Unable to store text to external storage" );
+ }
+ }
+
+ /**
+ * @return bool Whether all the default insertion stores are marked as read-only
+ * @throws ExternalStoreException
+ */
+ public function isReadOnly() {
+ $writableStores = $this->storeFactory->getWriteBaseUrls();
+ if ( !$writableStores ) {
+ return false; // no stores exists which can be "read only"
+ }
+
+ foreach ( $writableStores as $storeUrl ) {
+ $store = $this->storeFactory->getStoreForUrl( $storeUrl );
+ $location = $this->storeFactory->getStoreLocationFromUrl( $storeUrl );
+ if ( $store !== false && !$store->isReadOnly( $location ) ) {
+ return false; // at least one store is not read-only
+ }
+ }
+
+ return true; // all stores are read-only
+ }
+}
* @file
*/
-use MediaWiki\MediaWikiServices;
+use Wikimedia\Rdbms\LBFactory;
use Wikimedia\Rdbms\ILoadBalancer;
use Wikimedia\Rdbms\IDatabase;
use Wikimedia\Rdbms\DBConnRef;
* @ingroup ExternalStorage
*/
class ExternalStoreDB extends ExternalStoreMedium {
+ /** @var LBFactory */
+ private $lbFactory;
+
+ /**
+ * @see ExternalStoreMedium::__construct()
+ * @param array $params Additional parameters include:
+ * - lbFactory: an LBFactory instance
+ */
+ public function __construct( array $params ) {
+ parent::__construct( $params );
+ if ( !isset( $params['lbFactory'] ) || !( $params['lbFactory'] instanceof LBFactory ) ) {
+ throw new InvalidArgumentException( "LBFactory required in 'lbFactory' field." );
+ }
+ $this->lbFactory = $params['lbFactory'];
+ }
+
/**
* The provided URL is in the form of DB://cluster/id
* or DB://cluster/id/itemid for concatened storage.
*/
public function store( $location, $data ) {
$dbw = $this->getMaster( $location );
- $dbw->insert( $this->getTable( $dbw ),
- [ 'blob_text' => $data ],
- __METHOD__ );
+ $dbw->insert( $this->getTable( $dbw ), [ 'blob_text' => $data ], __METHOD__ );
$id = $dbw->insertId();
if ( !$id ) {
throw new MWException( __METHOD__ . ': no insert ID' );
* @inheritDoc
*/
public function isReadOnly( $location ) {
+ if ( parent::isReadOnly( $location ) ) {
+ return true;
+ }
+
$lb = $this->getLoadBalancer( $location );
$domainId = $this->getDomainId( $lb->getServerInfo( $lb->getWriterIndex() ) );
+
return ( $lb->getReadOnlyReason( $domainId ) !== false );
}
* @return ILoadBalancer
*/
private function getLoadBalancer( $cluster ) {
- $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
- return $lbFactory->getExternalLB( $cluster );
+ return $this->lbFactory->getExternalLB( $cluster );
}
/**
* @return DBConnRef
*/
public function getSlave( $cluster ) {
- global $wgDefaultExternalStore;
-
$lb = $this->getLoadBalancer( $cluster );
$domainId = $this->getDomainId( $lb->getServerInfo( $lb->getWriterIndex() ) );
- if ( !in_array( "DB://" . $cluster, (array)$wgDefaultExternalStore ) ) {
- wfDebug( "read only external store\n" );
+ if ( !in_array( $cluster, $this->writableLocations, true ) ) {
+ $this->logger->debug( "read only external store\n" );
$lb->allowLagged( true );
} else {
- wfDebug( "writable external store\n" );
+ $this->logger->debug( "writable external store\n" );
}
$db = $lb->getConnectionRef( DB_REPLICA, [], $domainId );
* @return string|bool Database domain ID or false
*/
private function getDomainId( array $server ) {
- if ( isset( $this->params['wiki'] ) && $this->params['wiki'] !== false ) {
- return $this->params['wiki']; // explicit domain
+ if ( $this->isDbDomainExplicit ) {
+ return $this->dbDomain; // explicit foreign domain
}
if ( isset( $server['dbname'] ) ) {
static $externalBlobCache = [];
$cacheID = ( $itemID === false ) ? "$cluster/$id" : "$cluster/$id/";
-
- $wiki = $this->params['wiki'] ?? false;
- $cacheID = ( $wiki === false ) ? $cacheID : "$cacheID@$wiki";
+ $cacheID = "$cacheID@{$this->dbDomain}";
if ( isset( $externalBlobCache[$cacheID] ) ) {
- wfDebugLog( 'ExternalStoreDB-cache',
- "ExternalStoreDB::fetchBlob cache hit on $cacheID" );
+ $this->logger->debug( "ExternalStoreDB::fetchBlob cache hit on $cacheID" );
return $externalBlobCache[$cacheID];
}
- wfDebugLog( 'ExternalStoreDB-cache',
- "ExternalStoreDB::fetchBlob cache miss on $cacheID" );
+ $this->logger->debug( "ExternalStoreDB::fetchBlob cache miss on $cacheID" );
$dbr = $this->getSlave( $cluster );
$ret = $dbr->selectField( $this->getTable( $dbr ),
'blob_text', [ 'blob_id' => $id ], __METHOD__ );
if ( $ret === false ) {
- wfDebugLog( 'ExternalStoreDB',
- "ExternalStoreDB::fetchBlob master fallback on $cacheID" );
+ $this->logger->info( "ExternalStoreDB::fetchBlob master fallback on $cacheID" );
// Try the master
$dbw = $this->getMaster( $cluster );
$ret = $dbw->selectField( $this->getTable( $dbw ),
'blob_text', [ 'blob_id' => $id ], __METHOD__ );
if ( $ret === false ) {
- wfDebugLog( 'ExternalStoreDB',
- "ExternalStoreDB::fetchBlob master failed to find $cacheID" );
+ $this->logger->error( "ExternalStoreDB::fetchBlob master failed to find $cacheID" );
}
}
if ( $itemID !== false && $ret !== false ) {
*/
private function batchFetchBlobs( $cluster, array $ids ) {
$dbr = $this->getSlave( $cluster );
- $res = $dbr->select( $this->getTable( $dbr ),
- [ 'blob_id', 'blob_text' ], [ 'blob_id' => array_keys( $ids ) ], __METHOD__ );
+ $res = $dbr->select(
+ $this->getTable( $dbr ),
+ [ 'blob_id', 'blob_text' ],
+ [ 'blob_id' => array_keys( $ids ) ],
+ __METHOD__
+ );
+
$ret = [];
if ( $res !== false ) {
$this->mergeBatchResult( $ret, $ids, $res );
}
if ( $ids ) {
- wfDebugLog( __CLASS__, __METHOD__ .
- " master fallback on '$cluster' for: " .
- implode( ',', array_keys( $ids ) ) );
+ $this->logger->info(
+ __METHOD__ . ": master fallback on '$cluster' for: " .
+ implode( ',', array_keys( $ids ) )
+ );
// Try the master
$dbw = $this->getMaster( $cluster );
$res = $dbw->select( $this->getTable( $dbr ),
[ 'blob_id' => array_keys( $ids ) ],
__METHOD__ );
if ( $res === false ) {
- wfDebugLog( __CLASS__, __METHOD__ . " master failed on '$cluster'" );
+ $this->logger->error( __METHOD__ . ": master failed on '$cluster'" );
} else {
$this->mergeBatchResult( $ret, $ids, $res );
}
}
if ( $ids ) {
- wfDebugLog( __CLASS__, __METHOD__ .
- " master on '$cluster' failed locating items: " .
- implode( ',', array_keys( $ids ) ) );
+ $this->logger->error(
+ __METHOD__ . ": master on '$cluster' failed locating items: " .
+ implode( ',', array_keys( $ids ) )
+ );
}
return $ret;
--- /dev/null
+<?php
+
+class ExternalStoreException extends MWException {
+
+}
* @defgroup ExternalStorage ExternalStorage
*/
+use MediaWiki\MediaWikiServices;
+use Psr\Log\LoggerAwareInterface;
+use Psr\Log\LoggerInterface;
+use Psr\Log\NullLogger;
+use Wikimedia\Assert\Assert;
+
/**
* @ingroup ExternalStorage
*/
-class ExternalStoreFactory {
+class ExternalStoreFactory implements LoggerAwareInterface {
+ /** @var string[] List of storage access protocols */
+ private $protocols;
+ /** @var string[] List of base storage URLs that define locations for writes */
+ private $writeBaseUrls;
+ /** @var string Default database domain to store content under */
+ private $localDomainId;
+ /** @var LoggerInterface */
+ private $logger;
/**
- * @var array
+ * @param string[] $externalStores See $wgExternalStores
+ * @param string[] $defaultStores See $wgDefaultExternalStore
+ * @param string $localDomainId Local database/wiki ID
+ * @param LoggerInterface|null $logger
*/
- private $externalStores;
+ public function __construct(
+ array $externalStores,
+ array $defaultStores,
+ $localDomainId,
+ LoggerInterface $logger = null
+ ) {
+ Assert::parameterType( 'string', $localDomainId, '$localDomainId' );
+
+ $this->protocols = array_map( 'strtolower', $externalStores );
+ $this->writeBaseUrls = $defaultStores;
+ $this->localDomainId = $localDomainId;
+ $this->logger = $logger ?: new NullLogger();
+ }
+
+ public function setLogger( LoggerInterface $logger ) {
+ $this->logger = $logger;
+ }
/**
- * @param array $externalStores See $wgExternalStores
+ * @return string[] List of active store types/protocols (lowercased), e.g. [ "db" ]
+ * @since 1.34
*/
- public function __construct( array $externalStores ) {
- $this->externalStores = array_map( 'strtolower', $externalStores );
+ public function getProtocols() {
+ return $this->protocols;
+ }
+
+ /**
+ * @return string[] List of base URLs for writes, e.g. [ "DB://cluster1" ]
+ * @since 1.34
+ */
+ public function getWriteBaseUrls() {
+ return $this->writeBaseUrls;
}
/**
* Get an external store object of the given type, with the given parameters
*
+ * The 'domain' field in $params will be set to the local DB domain if it is unset
+ * or false. A special 'isDomainImplicit' flag is set when this happens, which should
+ * only be used to handle legacy DB domain configuration concerns (e.g. T200471).
+ *
* @param string $proto Type of external storage, should be a value in $wgExternalStores
- * @param array $params Associative array of ExternalStoreMedium parameters
- * @return ExternalStoreMedium|bool The store class or false on error
+ * @param array $params Map of ExternalStoreMedium::__construct context parameters.
+ * @return ExternalStoreMedium The store class or false on error
+ * @throws ExternalStoreException When $proto is not recognized
*/
- public function getStoreObject( $proto, array $params = [] ) {
- if ( !$this->externalStores || !in_array( strtolower( $proto ), $this->externalStores ) ) {
- // Protocol not enabled
- return false;
+ public function getStore( $proto, array $params = [] ) {
+ $protoLowercase = strtolower( $proto ); // normalize
+ if ( !$this->protocols || !in_array( $protoLowercase, $this->protocols ) ) {
+ throw new ExternalStoreException( "Protocol '$proto' is not enabled." );
}
$class = 'ExternalStore' . ucfirst( $proto );
+ if ( isset( $params['wiki'] ) ) {
+ $params += [ 'domain' => $params['wiki'] ]; // b/c
+ }
+ if ( !isset( $params['domain'] ) || $params['domain'] === false ) {
+ $params['domain'] = $this->localDomainId; // default
+ $params['isDomainImplicit'] = true; // b/c for ExternalStoreDB
+ }
+ $params['writableLocations'] = [];
+ // Determine the locations for this protocol/store still receiving writes
+ foreach ( $this->writeBaseUrls as $storeUrl ) {
+ list( $storeProto, $storePath ) = self::splitStorageUrl( $storeUrl );
+ if ( $protoLowercase === strtolower( $storeProto ) ) {
+ $params['writableLocations'][] = $storePath;
+ }
+ }
+ // @TODO: ideally, this class should not hardcode what classes need what backend factory
+ // objects. For now, inject the factory instances into __construct() for those that do.
+ if ( $protoLowercase === 'db' ) {
+ $params['lbFactory'] = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
+ } elseif ( $protoLowercase === 'mwstore' ) {
+ $params['fbGroup'] = FileBackendGroup::singleton();
+ }
+ $params['logger'] = $this->logger;
+
+ if ( !class_exists( $class ) ) {
+ throw new ExternalStoreException( "Class '$class' is not defined." );
+ }
// Any custom modules should be added to $wgAutoLoadClasses for on-demand loading
- return class_exists( $class ) ? new $class( $params ) : false;
+ return new $class( $params );
+ }
+
+ /**
+ * Get the ExternalStoreMedium for a given URL
+ *
+ * $url is either of the form:
+ * - a) "<proto>://<location>/<path>", for retrieval, or
+ * - b) "<proto>://<location>", for storage
+ *
+ * @param string $url
+ * @param array $params Map of ExternalStoreMedium::__construct context parameters
+ * @return ExternalStoreMedium
+ * @throws ExternalStoreException When the protocol is missing or not recognized
+ * @since 1.34
+ */
+ public function getStoreForUrl( $url, array $params = [] ) {
+ list( $proto, $path ) = self::splitStorageUrl( $url );
+ if ( $path == '' ) { // bad URL
+ throw new ExternalStoreException( "Invalid URL '$url'" );
+ }
+
+ return $this->getStore( $proto, $params );
}
+ /**
+ * Get the location within the appropriate store for a given a URL
+ *
+ * @param string $url
+ * @return string
+ * @throws ExternalStoreException
+ * @since 1.34
+ */
+ public function getStoreLocationFromUrl( $url ) {
+ list( , $location ) = self::splitStorageUrl( $url );
+ if ( $location == '' ) { // bad URL
+ throw new ExternalStoreException( "Invalid URL '$url'" );
+ }
+
+ return $location;
+ }
+
+ /**
+ * @param string[] $urls
+ * @return array[] Map of (protocol => list of URLs)
+ * @throws ExternalStoreException
+ * @since 1.34
+ */
+ public function getUrlsByProtocol( array $urls ) {
+ $urlsByProtocol = [];
+ foreach ( $urls as $url ) {
+ list( $proto, ) = self::splitStorageUrl( $url );
+ $urlsByProtocol[$proto][] = $url;
+ }
+
+ return $urlsByProtocol;
+ }
+
+ /**
+ * @param string $storeUrl
+ * @return string[] (protocol, store location or location-qualified path)
+ * @throws ExternalStoreException
+ */
+ private static function splitStorageUrl( $storeUrl ) {
+ $parts = explode( '://', $storeUrl );
+ if ( count( $parts ) != 2 || $parts[0] === '' || $parts[1] === '' ) {
+ throw new ExternalStoreException( "Invalid storage URL '$storeUrl'" );
+ }
+
+ return $parts;
+ }
}
* @ingroup ExternalStorage
*/
+use Psr\Log\LoggerAwareInterface;
+use Psr\Log\LoggerInterface;
+use Psr\Log\NullLogger;
+
/**
- * Accessable external objects in a particular storage medium
+ * Key/value blob storage for a particular storage medium type (e.g. RDBMs, files)
+ *
+ * There can be multiple "locations" for a storage medium type (e.g. DB clusters, filesystems).
+ * Blobs are stored under URLs of the form "<protocol>://<location>/<path>". Each type of storage
+ * medium has an associated protocol.
*
* @ingroup ExternalStorage
* @since 1.21
*/
-abstract class ExternalStoreMedium {
- /** @var array */
+abstract class ExternalStoreMedium implements LoggerAwareInterface {
+ /** @var array Usage context options for this instance */
protected $params = [];
+ /** @var string Default database domain to store content under */
+ protected $dbDomain;
+ /** @var bool Whether this was factoried with an explicit DB domain */
+ protected $isDbDomainExplicit;
+ /** @var string[] Writable locations */
+ protected $writableLocations = [];
+
+ /** @var LoggerInterface */
+ protected $logger;
/**
- * @param array $params Usage context options:
- * - wiki: the domain ID of the wiki this is being used for [optional]
+ * @param array $params Usage context options for this instance:
+ * - domain: the DB domain ID of the wiki the content is for [required]
+ * - writableLocations: locations that are writable [required]
+ * - logger: LoggerInterface instance [optional]
+ * - isDomainImplicit: whether this was factoried without an explicit DB domain [optional]
*/
- public function __construct( array $params = [] ) {
+ public function __construct( array $params ) {
$this->params = $params;
+ if ( isset( $params['domain'] ) ) {
+ $this->dbDomain = $params['domain'];
+ $this->isDbDomainExplicit = empty( $params['isDomainImplicit'] );
+ } else {
+ throw new InvalidArgumentException( 'Missing DB "domain" parameter.' );
+ }
+
+ $this->logger = $params['logger'] ?? new NullLogger();
+ $this->writableLocations = $params['writableLocations'] ?? [];
+ }
+
+ public function setLogger( LoggerInterface $logger ) {
+ $this->logger = $logger;
}
/**
* Fetch data from given external store URLs.
*
* @param array $urls A list of external store URLs
- * @return array Map from the url to the text stored. Unfound data is not represented
+ * @return string[] Map of (url => text) for the URLs where data was actually found
*/
public function batchFetchFromURLs( array $urls ) {
$retval = [];
foreach ( $urls as $url ) {
$data = $this->fetchFromURL( $url );
- // Dont return when false to allow for simpler implementations.
- // errored urls are handled in ExternalStore::batchFetchFromURLs
+ // Dont return when false to allow for simpler implementations
if ( $data !== false ) {
$retval[$url] = $data;
}
* @since 1.31
*/
public function isReadOnly( $location ) {
- return false;
+ return !in_array( $location, $this->writableLocations, true );
}
}
--- /dev/null
+<?php
+/**
+ * External storage in PHP process memory for testing.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+/**
+ * Process memory based external objects for testing.
+ *
+ * In this system, each store "location" is separate PHP array.
+ * URLs are of the form "memory://location/id". The id/value pairs
+ * at each location are segregated by DB domain ID.
+ *
+ * @ingroup ExternalStorage
+ * @since 1.33
+ */
+class ExternalStoreMemory extends ExternalStoreMedium {
+ /** @var array[] Map of (location => DB domain => id => value) */
+ private static $data = [];
+ /** @var int */
+ private static $nextId = 0;
+
+ public function __construct( array $params ) {
+ parent::__construct( $params );
+ }
+
+ public function fetchFromURL( $url ) {
+ list( $location, $id ) = self::getURLComponents( $url );
+ if ( $id === null ) {
+ throw new UnexpectedValueException( "Missing ID in URL component." );
+ }
+
+ return self::$data[$location][$this->dbDomain][$id] ?? false;
+ }
+
+ public function batchFetchFromURLs( array $urls ) {
+ $blobs = [];
+ foreach ( $urls as $url ) {
+ $blob = $this->fetchFromURL( $url );
+ if ( $blob !== false ) {
+ $blobs[$url] = $blob;
+ }
+ }
+
+ return $blobs;
+ }
+
+ public function store( $location, $data ) {
+ $index = ++self::$nextId;
+ self::$data[$location][$this->dbDomain][$index] = $data;
+
+ return "memory://$location/$index";
+ }
+
+ /**
+ * Remove all data from memory for this domain
+ */
+ public function clear() {
+ foreach ( self::$data as &$dataForLocation ) {
+ unset( $dataForLocation[$this->dbDomain] );
+ }
+ unset( $dataForLocation );
+ self::$data = array_filter( self::$data, 'count' );
+ self::$nextId = 0;
+ }
+
+ /**
+ * @param string $url
+ * @return array (location, ID or null)
+ */
+ private function getURLComponents( $url ) {
+ list( $proto, $path ) = explode( '://', $url, 2 ) + [ null, null ];
+ if ( $proto !== 'memory' ) {
+ throw new UnexpectedValueException( "Got URL of protocol '$proto', not 'memory'." );
+ } elseif ( $path === null ) {
+ throw new UnexpectedValueException( "URL is missing path component." );
+ }
+
+ $parts = explode( '/', $path );
+ if ( count( $parts ) > 2 ) {
+ throw new UnexpectedValueException( "Too components in URL '$path'." );
+ }
+
+ return [ $parts[0], $parts[1] ?? null ];
+ }
+}
* @since 1.21
*/
class ExternalStoreMwstore extends ExternalStoreMedium {
+ /** @var FileBackendGroup */
+ private $fbGroup;
+
+ /**
+ * @see ExternalStoreMedium::__construct()
+ * @param array $params Additional parameters include:
+ * - fbGroup: a FileBackendGroup instance
+ */
+ public function __construct( array $params ) {
+ parent::__construct( $params );
+ if ( !isset( $params['fbGroup'] ) || !( $params['fbGroup'] instanceof FileBackendGroup ) ) {
+ throw new InvalidArgumentException( "FileBackendGroup required in 'fbGroup' field." );
+ }
+ $this->fbGroup = $params['fbGroup'];
+ }
+
/**
* The URL returned is of the form of the form mwstore://backend/container/wiki/id
*
* @return bool
*/
public function fetchFromURL( $url ) {
- $be = FileBackendGroup::singleton()->backendFromPath( $url );
+ $be = $this->fbGroup->backendFromPath( $url );
if ( $be instanceof FileBackend ) {
// We don't need "latest" since objects are immutable and
// backends should at least have "read-after-create" consistency.
public function batchFetchFromURLs( array $urls ) {
$pathsByBackend = [];
foreach ( $urls as $url ) {
- $be = FileBackendGroup::singleton()->backendFromPath( $url );
+ $be = $this->fbGroup->backendFromPath( $url );
if ( $be instanceof FileBackend ) {
$pathsByBackend[$be->getName()][] = $url;
}
}
$blobs = [];
foreach ( $pathsByBackend as $backendName => $paths ) {
- $be = FileBackendGroup::singleton()->get( $backendName );
+ $be = $this->fbGroup->get( $backendName );
$blobs += $be->getFileContentsMulti( [ 'srcs' => $paths ] );
}
* @inheritDoc
*/
public function store( $backend, $data ) {
- $be = FileBackendGroup::singleton()->get( $backend );
+ $be = $this->fbGroup->get( $backend );
// Get three random base 36 characters to act as shard directories
$rand = Wikimedia\base_convert( mt_rand( 0, 46655 ), 10, 36, 3 );
// Make sure ID is roughly lexicographically increasing for performance
$id = str_pad( UIDGenerator::newTimestampedUID128( 32 ), 26, '0', STR_PAD_LEFT );
- // Segregate items by wiki ID for the sake of bookkeeping
- // @FIXME: this does not include the domain for b/c but it ideally should
- $wiki = $this->params['wiki'] ?? wfWikiID();
-
- $url = $be->getContainerStoragePath( 'data' ) . '/' . rawurlencode( $wiki );
+ // Segregate items by DB domain ID for the sake of bookkeeping
+ $domain = $this->isDbDomainExplicit
+ ? $this->dbDomain
+ // @FIXME: this does not include the schema for b/c but it ideally should
+ : WikiMap::getWikiIdFromDbDomain( $this->dbDomain );
+ $url = $be->getContainerStoragePath( 'data' ) . '/' . rawurlencode( $domain );
+ // Use directory/container sharding
$url .= ( $be instanceof FSFileBackend )
? "/{$rand[0]}/{$rand[1]}/{$rand[2]}/{$id}" // keep directories small
: "/{$rand[0]}/{$rand[1]}/{$id}"; // container sharding is only 2-levels
if ( $status->isOK() ) {
return $url;
- } else {
- throw new MWException( __METHOD__ . ": operation failed: $status" );
}
+
+ throw new MWException( __METHOD__ . ": operation failed: $status" );
}
public function isReadOnly( $backend ) {
- $be = FileBackendGroup::singleton()->get( $backend );
+ if ( parent::isReadOnly( $backend ) ) {
+ return true;
+ }
+
+ $be = $this->fbGroup->get( $backend );
return $be ? $be->isReadOnly() : false;
}
* @file
*/
+use MediaWiki\MediaWikiServices;
+
/**
* Pointer object for an item within a CGZ blob stored in the text table.
*/
if ( !isset( $parts[1] ) || $parts[1] == '' ) {
return false;
}
- $row->old_text = ExternalStore::fetchFromURL( $url );
-
+ $row->old_text = MediaWikiServices::getInstance()
+ ->getExternalStoreAccess()
+ ->fetchFromURL( $url );
}
if ( !in_array( 'object', $flags ) ) {
$method = __METHOD__;
$handler = function ( array $request, StatusValue $status ) use ( $method, $params ) {
list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $request['response'];
- if ( $rcode === 201 ) {
+ if ( $rcode === 201 || $rcode === 202 ) {
// good
} elseif ( $rcode === 412 ) {
$status->fatal( 'backend-fail-contenttype', $params['dst'] );
$method = __METHOD__;
$handler = function ( array $request, StatusValue $status ) use ( $method, $params ) {
list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $request['response'];
- if ( $rcode === 201 ) {
+ if ( $rcode === 201 || $rcode === 202 ) {
// good
} elseif ( $rcode === 412 ) {
$status->fatal( 'backend-fail-contenttype', $params['dst'] );
* @param int $i
* @param string[]|null $groups
*
- * @return Database
+ * @return IDatabase
*/
private function getConnection( $i, array $groups = null ) {
$groups = $groups === null ? $this->groups : $groups;
*
* @since 1.29
*
- * @return Database
+ * @return IDatabase
*/
public function getWriteConnection() {
return $this->getConnection( DB_MASTER );
*
* @param string[]|null $groups
*
- * @return Database
+ * @return IDatabase
*/
public function getReadConnection( array $groups = null ) {
$groups = $groups === null ? $this->groups : $groups;
*
* @param string[]|null $groups
*
- * @return Database
+ * @return IDatabase
*/
public function getReadConnection( array $groups = null ) {
if ( $this->forceWriteConnection ) {
/**
* @since 1.29
*
- * @return Database
+ * @return IDatabase
*/
public function getWriteConnection() {
$this->prepareForUpdates();
/** @var array Map of (table name => 1) for TEMPORARY tables */
protected $sessionTempTables = [];
- /** @var int Whether there is an active transaction (1 or 0) */
- protected $trxLevel = 0;
- /** @var string Hexidecimal string if a transaction is active or empty string otherwise */
+ /** @var string ID of the active transaction or the empty string otherwise */
protected $trxShortId = '';
/** @var int Transaction status */
protected $trxStatus = self::STATUS_TRX_NONE;
return $res;
}
- public function trxLevel() {
- return $this->trxLevel;
+ final public function trxLevel() {
+ return ( $this->trxShortId != '' ) ? 1 : 0;
}
public function trxTimestamp() {
- return $this->trxLevel ? $this->trxTimestamp : null;
+ return $this->trxLevel() ? $this->trxTimestamp : null;
}
/**
}
public function writesPending() {
- return $this->trxLevel && $this->trxDoneWrites;
+ return $this->trxLevel() && $this->trxDoneWrites;
}
public function writesOrCallbacksPending() {
- return $this->trxLevel && (
+ return $this->trxLevel() && (
$this->trxDoneWrites ||
$this->trxIdleCallbacks ||
$this->trxPreCommitCallbacks ||
}
public function preCommitCallbacksPending() {
- return $this->trxLevel && $this->trxPreCommitCallbacks;
+ return $this->trxLevel() && $this->trxPreCommitCallbacks;
}
/**
}
public function pendingWriteQueryDuration( $type = self::ESTIMATE_TOTAL ) {
- if ( !$this->trxLevel ) {
+ if ( !$this->trxLevel() ) {
return false;
} elseif ( !$this->trxDoneWrites ) {
return 0.0;
}
public function pendingWriteCallers() {
- return $this->trxLevel ? $this->trxWriteCallers : [];
+ return $this->trxLevel() ? $this->trxWriteCallers : [];
}
public function pendingWriteRowsAffected() {
// This should mostly do nothing if the connection is already closed
if ( $this->conn ) {
// Roll back any dangling transaction first
- if ( $this->trxLevel ) {
+ if ( $this->trxLevel() ) {
if ( $this->trxAtomicLevels ) {
// Cannot let incomplete atomic sections be committed
$levels = $this->flatAtomicSectionList();
final protected function executeQuery( $sql, $fname, $flags ) {
$this->assertHasConnectionHandle();
- $priorTransaction = $this->trxLevel;
+ $priorTransaction = $this->trxLevel();
if ( $this->isWriteQuery( $sql ) ) {
# In theory, non-persistent writes are allowed in read-only mode, but due to things
// Keep track of whether the transaction has write queries pending
if ( $isPermWrite ) {
$this->lastWriteTime = microtime( true );
- if ( $this->trxLevel && !$this->trxDoneWrites ) {
+ if ( $this->trxLevel() && !$this->trxDoneWrites ) {
$this->trxDoneWrites = true;
$this->trxProfiler->transactionWritingIn(
$this->server, $this->getDomainID(), $this->trxShortId );
if ( $ret !== false ) {
$this->lastPing = $startTime;
- if ( $isPermWrite && $this->trxLevel ) {
+ if ( $isPermWrite && $this->trxLevel() ) {
$this->updateTrxWriteQueryTime( $sql, $queryRuntime, $this->affectedRows() );
$this->trxWriteCallers[] = $fname;
}
*/
private function beginIfImplied( $sql, $fname ) {
if (
- !$this->trxLevel &&
+ !$this->trxLevel() &&
$this->getFlag( self::DBO_TRX ) &&
$this->isTransactableQuery( $sql )
) {
// https://www.postgresql.org/docs/9.4/static/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS
$this->sessionNamedLocks = [];
// Session loss implies transaction loss
- $this->trxLevel = 0;
+ $oldTrxShortId = $this->consumeTrxShortId();
$this->trxAtomicCounter = 0;
$this->trxIdleCallbacks = []; // T67263; transaction already lost
$this->trxPreCommitCallbacks = []; // T67263; transaction already lost
$this->trxProfiler->transactionWritingOut(
$this->server,
$this->getDomainID(),
- $this->trxShortId,
+ $oldTrxShortId,
$this->pendingWriteQueryDuration( self::ESTIMATE_TOTAL ),
$this->trxWriteAffectedRows
);
}
}
+ /**
+ * Reset the transaction ID and return the old one
+ *
+ * @return string The old transaction ID or the empty string if there wasn't one
+ */
+ private function consumeTrxShortId() {
+ $old = $this->trxShortId;
+ $this->trxShortId = '';
+
+ return $old;
+ }
+
/**
* Checks whether the cause of the error is detected to be a timeout.
*
public function lockForUpdate(
$table, $conds = '', $fname = __METHOD__, $options = [], $join_conds = []
) {
- if ( !$this->trxLevel && !$this->getFlag( self::DBO_TRX ) ) {
+ if ( !$this->trxLevel() && !$this->getFlag( self::DBO_TRX ) ) {
throw new DBUnexpectedError(
$this,
__METHOD__ . ': no transaction is active nor is DBO_TRX set'
}
final public function onTransactionResolution( callable $callback, $fname = __METHOD__ ) {
- if ( !$this->trxLevel ) {
+ if ( !$this->trxLevel() ) {
throw new DBUnexpectedError( $this, "No transaction is active." );
}
$this->trxEndCallbacks[] = [ $callback, $fname, $this->currentAtomicSectionId() ];
}
final public function onTransactionCommitOrIdle( callable $callback, $fname = __METHOD__ ) {
- if ( !$this->trxLevel && $this->getTransactionRoundId() ) {
+ if ( !$this->trxLevel() && $this->getTransactionRoundId() ) {
// Start an implicit transaction similar to how query() does
$this->begin( __METHOD__, self::TRANSACTION_INTERNAL );
$this->trxAutomatic = true;
}
$this->trxIdleCallbacks[] = [ $callback, $fname, $this->currentAtomicSectionId() ];
- if ( !$this->trxLevel ) {
+ if ( !$this->trxLevel() ) {
$this->runOnTransactionIdleCallbacks( self::TRIGGER_IDLE );
}
}
}
final public function onTransactionPreCommitOrIdle( callable $callback, $fname = __METHOD__ ) {
- if ( !$this->trxLevel && $this->getTransactionRoundId() ) {
+ if ( !$this->trxLevel() && $this->getTransactionRoundId() ) {
// Start an implicit transaction similar to how query() does
$this->begin( __METHOD__, self::TRANSACTION_INTERNAL );
$this->trxAutomatic = true;
}
- if ( $this->trxLevel ) {
+ if ( $this->trxLevel() ) {
$this->trxPreCommitCallbacks[] = [ $callback, $fname, $this->currentAtomicSectionId() ];
} else {
// No transaction is active nor will start implicitly, so make one for this callback
}
final public function onAtomicSectionCancel( callable $callback, $fname = __METHOD__ ) {
- if ( !$this->trxLevel || !$this->trxAtomicLevels ) {
+ if ( !$this->trxLevel() || !$this->trxAtomicLevels ) {
throw new DBUnexpectedError( $this, "No atomic section is open (got $fname)." );
}
$this->trxSectionCancelCallbacks[] = [ $callback, $fname, $this->currentAtomicSectionId() ];
* @return AtomicSectionIdentifier|null ID of the topmost atomic section level
*/
private function currentAtomicSectionId() {
- if ( $this->trxLevel && $this->trxAtomicLevels ) {
+ if ( $this->trxLevel() && $this->trxAtomicLevels ) {
$levelInfo = end( $this->trxAtomicLevels );
return $levelInfo[1];
* @throws Exception
*/
public function runOnTransactionIdleCallbacks( $trigger ) {
- if ( $this->trxLevel ) { // sanity
+ if ( $this->trxLevel() ) { // sanity
throw new DBUnexpectedError( $this, __METHOD__ . ': a transaction is still open.' );
}
) {
$savepointId = $cancelable === self::ATOMIC_CANCELABLE ? self::$NOT_APPLICABLE : null;
- if ( !$this->trxLevel ) {
+ if ( !$this->trxLevel() ) {
$this->begin( $fname, self::TRANSACTION_INTERNAL ); // sets trxAutomatic
// If DBO_TRX is set, a series of startAtomic/endAtomic pairs will result
// in all changes being in one transaction to keep requests transactional.
}
final public function endAtomic( $fname = __METHOD__ ) {
- if ( !$this->trxLevel || !$this->trxAtomicLevels ) {
+ if ( !$this->trxLevel() || !$this->trxAtomicLevels ) {
throw new DBUnexpectedError( $this, "No atomic section is open (got $fname)." );
}
final public function cancelAtomic(
$fname = __METHOD__, AtomicSectionIdentifier $sectionId = null
) {
- if ( !$this->trxLevel || !$this->trxAtomicLevels ) {
+ if ( !$this->trxLevel() || !$this->trxAtomicLevels ) {
throw new DBUnexpectedError( $this, "No atomic section is open (got $fname)." );
}
}
// Protect against mismatched atomic section, transaction nesting, and snapshot loss
- if ( $this->trxLevel ) {
+ if ( $this->trxLevel() ) {
if ( $this->trxAtomicLevels ) {
$levels = $this->flatAtomicSectionList();
$msg = "$fname: Got explicit BEGIN while atomic section(s) $levels are open.";
$this->assertHasConnectionHandle();
$this->doBegin( $fname );
+ $this->trxShortId = sprintf( '%06x', mt_rand( 0, 0xffffff ) );
$this->trxStatus = self::STATUS_TRX_OK;
$this->trxStatusIgnoredCause = null;
$this->trxAtomicCounter = 0;
$this->trxDoneWrites = false;
$this->trxAutomaticAtomic = false;
$this->trxAtomicLevels = [];
- $this->trxShortId = sprintf( '%06x', mt_rand( 0, 0xffffff ) );
$this->trxWriteDuration = 0.0;
$this->trxWriteQueryCount = 0;
$this->trxWriteAffectedRows = 0;
*
* @see Database::begin()
* @param string $fname
+ * @throws DBError
*/
protected function doBegin( $fname ) {
$this->query( 'BEGIN', $fname );
- $this->trxLevel = 1;
}
final public function commit( $fname = __METHOD__, $flush = self::FLUSHING_ONE ) {
throw new DBUnexpectedError( $this, "$fname: invalid flush parameter '$flush'." );
}
- if ( $this->trxLevel && $this->trxAtomicLevels ) {
+ if ( $this->trxLevel() && $this->trxAtomicLevels ) {
// There are still atomic sections open; this cannot be ignored
$levels = $this->flatAtomicSectionList();
throw new DBUnexpectedError(
}
if ( $flush === self::FLUSHING_INTERNAL || $flush === self::FLUSHING_ALL_PEERS ) {
- if ( !$this->trxLevel ) {
+ if ( !$this->trxLevel() ) {
return; // nothing to do
} elseif ( !$this->trxAutomatic ) {
throw new DBUnexpectedError(
"$fname: Flushing an explicit transaction, getting out of sync."
);
}
- } elseif ( !$this->trxLevel ) {
+ } elseif ( !$this->trxLevel() ) {
$this->queryLogger->error(
"$fname: No transaction to commit, something got out of sync." );
return; // nothing to do
$writeTime = $this->pendingWriteQueryDuration( self::ESTIMATE_DB_APPLY );
$this->doCommit( $fname );
+ $oldTrxShortId = $this->consumeTrxShortId();
$this->trxStatus = self::STATUS_TRX_NONE;
if ( $this->trxDoneWrites ) {
$this->trxProfiler->transactionWritingOut(
$this->server,
$this->getDomainID(),
- $this->trxShortId,
+ $oldTrxShortId,
$writeTime,
$this->trxWriteAffectedRows
);
*
* @see Database::commit()
* @param string $fname
+ * @throws DBError
*/
protected function doCommit( $fname ) {
- if ( $this->trxLevel ) {
+ if ( $this->trxLevel() ) {
$this->query( 'COMMIT', $fname );
- $this->trxLevel = 0;
}
}
final public function rollback( $fname = __METHOD__, $flush = self::FLUSHING_ONE ) {
- $trxActive = $this->trxLevel;
+ $trxActive = $this->trxLevel();
if ( $flush !== self::FLUSHING_INTERNAL
&& $flush !== self::FLUSHING_ALL_PEERS
$this->assertHasConnectionHandle();
$this->doRollback( $fname );
+ $oldTrxShortId = $this->consumeTrxShortId();
$this->trxStatus = self::STATUS_TRX_NONE;
$this->trxAtomicLevels = [];
// Estimate the RTT via a query now that trxStatus is OK
$this->trxProfiler->transactionWritingOut(
$this->server,
$this->getDomainID(),
- $this->trxShortId,
+ $oldTrxShortId,
$writeTime,
$this->trxWriteAffectedRows
);
*
* @see Database::rollback()
* @param string $fname
+ * @throws DBError
*/
protected function doRollback( $fname ) {
- if ( $this->trxLevel ) {
+ if ( $this->trxLevel() ) {
# Disconnects cause rollback anyway, so ignore those errors
$ignoreErrors = true;
$this->query( 'ROLLBACK', $fname, $ignoreErrors );
- $this->trxLevel = 0;
}
}
}
public function explicitTrxActive() {
- return $this->trxLevel && ( $this->trxAtomicLevels || !$this->trxAutomatic );
+ return $this->trxLevel() && ( $this->trxAtomicLevels || !$this->trxAutomatic );
}
public function duplicateTableStructure(
* @since 1.27
*/
final protected function getRecordedTransactionLagStatus() {
- return ( $this->trxLevel && $this->trxReplicaLag !== null )
+ return ( $this->trxLevel() && $this->trxReplicaLag !== null )
? [ 'lag' => $this->trxReplicaLag, 'since' => $this->trxTimestamp() ]
: null;
}
* Run a few simple sanity checks and close dangling connections
*/
public function __destruct() {
- if ( $this->trxLevel && $this->trxDoneWrites ) {
+ if ( $this->trxLevel() && $this->trxDoneWrites ) {
trigger_error( "Uncommitted DB writes (transaction from {$this->trxFname})." );
}
namespace Wikimedia\Rdbms;
use Exception;
+use RuntimeException;
use stdClass;
use Wikimedia\AtEase\AtEase;
return $statementOnly;
}
+ public function serverIsReadOnly() {
+ $encDatabase = $this->addQuotes( $this->getDBname() );
+ $res = $this->query(
+ "SELECT IS_READ_ONLY FROM SYS.DATABASES WHERE NAME = $encDatabase",
+ __METHOD__
+ );
+ $row = $this->fetchObject( $res );
+
+ return $row ? (bool)$row->IS_READ_ONLY : false;
+ }
+
/**
* @return int
*/
$this->query( 'ROLLBACK TRANSACTION ' . $this->addIdentifierQuotes( $identifier ), $fname );
}
- /**
- * Begin a transaction, committing any previously open transaction
- * @param string $fname
- */
protected function doBegin( $fname = __METHOD__ ) {
- sqlsrv_begin_transaction( $this->conn );
- $this->trxLevel = 1;
+ if ( !sqlsrv_begin_transaction( $this->conn ) ) {
+ $this->reportQueryError( $this->lastError(), $this->lastErrno(), 'BEGIN', $fname );
+ }
}
/**
* @param string $fname
*/
protected function doCommit( $fname = __METHOD__ ) {
- sqlsrv_commit( $this->conn );
- $this->trxLevel = 0;
+ if ( !sqlsrv_commit( $this->conn ) ) {
+ $this->reportQueryError( $this->lastError(), $this->lastErrno(), 'COMMIT', $fname );
+ }
}
/**
* @param string $fname
*/
protected function doRollback( $fname = __METHOD__ ) {
- sqlsrv_rollback( $this->conn );
- $this->trxLevel = 0;
+ if ( !sqlsrv_rollback( $this->conn ) ) {
+ $this->queryLogger->error(
+ "{fname}\t{db_server}\t{errno}\t{error}\t",
+ $this->getLogContext( [
+ 'errno' => $this->lastErrno(),
+ 'error' => $this->lastError(),
+ 'fname' => $fname,
+ 'trace' => ( new RuntimeException() )->getTraceAsString()
+ ] )
+ );
+ }
}
/**
* @param string $desiredSchema
*/
public function determineCoreSchema( $desiredSchema ) {
- if ( $this->trxLevel ) {
+ if ( $this->trxLevel() ) {
// We do not want the schema selection to change on ROLLBACK or INSERT SELECT.
// See https://www.postgresql.org/docs/8.3/sql-set.html
throw new DBUnexpectedError(
throw new DBExpectedError( $this, __CLASS__ . ": domain schemas are not supported." );
}
- $fileName = self::generateFileName( $this->dbDir, $dbName );
- if ( !is_readable( $fileName ) ) {
- $error = "SQLite database file not readable";
- $this->connLogger->error(
- "Error connecting to {db_server}: {error}",
- $this->getLogContext( [ 'method' => __METHOD__, 'error' => $error ] )
- );
- throw new DBConnectionError( $this, $error );
- }
-
// Only $dbName is used, the other parameters are irrelevant for SQLite databases
- $this->openFile( $fileName, $dbName, $tablePrefix );
+ $this->openFile( self::generateFileName( $this->dbDir, $dbName ), $dbName, $tablePrefix );
}
/**
* @throws DBConnectionError
*/
protected function openFile( $fileName, $dbName, $tablePrefix ) {
+ if ( !$this->hasMemoryPath() && !is_readable( $fileName ) ) {
+ $error = "SQLite database file not readable";
+ $this->connLogger->error(
+ "Error connecting to {db_server}: {error}",
+ $this->getLogContext( [ 'method' => __METHOD__, 'error' => $error ] )
+ );
+ throw new DBConnectionError( $this, $error );
+ }
+
$this->dbPath = $fileName;
try {
$this->conn = new PDO(
return false;
}
+ public function serverIsReadOnly() {
+ return ( !$this->hasMemoryPath() && !is_writable( $this->dbPath ) );
+ }
+
+ /**
+ * @return bool
+ */
+ private function hasMemoryPath() {
+ return ( strpos( $this->dbPath, ':memory:' ) === 0 );
+ }
+
/**
* @return string Wikitext of a link to the server software's web site
*/
} else {
$this->query( 'BEGIN', $fname );
}
- $this->trxLevel = 1;
}
/**
$this->next();
- return is_object( $row ) ? (array)$row : $row;
+ return is_object( $row ) ? get_object_vars( $row ) : $row;
}
function seek( $pos ) {
/**
* Get cached (tracked) load balancers for all main database clusters
*
- * @return LoadBalancer[] Map of (cluster name => LoadBalancer)
+ * @return ILoadBalancer[] Map of (cluster name => ILoadBalancer)
* @since 1.29
*/
public function getAllMainLBs();
/**
* Get cached (tracked) load balancers for all external database clusters
*
- * @return LoadBalancer[] Map of (cluster name => LoadBalancer)
+ * @return ILoadBalancer[] Map of (cluster name => ILoadBalancer)
* @since 1.29
*/
public function getAllExternalLBs();
class LBFactoryMulti extends LBFactory {
/** @var array A map of database names to section names */
private $sectionsByDB;
-
/**
* @var array A 2-d map. For each section, gives a map of server names to
* load ratios
*/
private $sectionLoads;
-
/**
* @var array[] Server info associative array
* @note The host, hostName and load entries will be overridden
*/
private $serverTemplate;
- // Optional settings
-
/** @var array A 3-d map giving server load ratios for each section and group */
private $groupLoadsBySection = [];
-
/** @var array A 3-d map giving server load ratios by DB name */
private $groupLoadsByDB = [];
-
/** @var array A map of hostname to IP address */
private $hostsByName = [];
-
/** @var array A map of external storage cluster name to server load map */
private $externalLoads = [];
-
/**
* @var array A set of server info keys overriding serverTemplate for
* external storage
*/
private $externalTemplateOverrides;
-
/**
* @var array A 2-d map overriding serverTemplate and
* externalTemplateOverrides on a server-by-server basis. Applies to both
* core and external storage
*/
private $templateOverridesByServer;
-
/** @var array A 2-d map overriding the server info by section */
private $templateOverridesBySection;
-
/** @var array A 2-d map overriding the server info by external storage cluster */
private $templateOverridesByCluster;
-
/** @var array An override array for all master servers */
private $masterTemplateOverrides;
-
/**
* @var array|bool A map of section name to read-only message. Missing or
* false for read/write
/** @var LoadBalancer[] */
private $mainLBs = [];
-
/** @var LoadBalancer[] */
private $extLBs = [];
-
/** @var string */
private $loadMonitorClass = 'LoadMonitor';
-
/** @var string */
private $lastDomain;
-
/** @var string */
private $lastSection;
if ( $this->lastDomain === $domain ) {
return $this->lastSection;
}
- list( $dbName, ) = $this->getDBNameAndPrefix( $domain );
- $section = $this->sectionsByDB[$dbName] ?? 'DEFAULT';
+
+ $database = $this->getDatabaseFromDomain( $domain );
+ $section = $this->sectionsByDB[$database] ?? 'DEFAULT';
$this->lastSection = $section;
$this->lastDomain = $domain;
return $section;
}
- /**
- * @param bool|string $domain
- * @return LoadBalancer
- */
public function newMainLB( $domain = false ) {
- list( $dbName, ) = $this->getDBNameAndPrefix( $domain );
+ $database = $this->getDatabaseFromDomain( $domain );
$section = $this->getSectionForDomain( $domain );
- $groupLoads = $this->groupLoadsByDB[$dbName] ?? [];
+ $groupLoads = $this->groupLoadsByDB[$database] ?? [];
if ( isset( $this->groupLoadsBySection[$section] ) ) {
$groupLoads = array_merge_recursive(
);
}
- /**
- * @param DatabaseDomain|string|bool $domain Domain ID, or false for the current domain
- * @return LoadBalancer
- */
public function getMainLB( $domain = false ) {
$section = $this->getSectionForDomain( $domain );
if ( !isset( $this->mainLBs[$section] ) ) {
/**
* @param DatabaseDomain|string|bool $domain Domain ID, or false for the current domain
- * @return array [database name, table prefix]
+ * @return string
*/
- private function getDBNameAndPrefix( $domain = false ) {
- $domain = ( $domain === false )
- ? $this->localDomain
- : DatabaseDomain::newFromId( $domain );
-
- return [ $domain->getDatabase(), $domain->getTablePrefix() ];
+ private function getDatabaseFromDomain( $domain = false ) {
+ return ( $domain === false )
+ ? $this->localDomain->getDatabase()
+ : DatabaseDomain::newFromId( $domain )->getDatabase();
}
- /**
- * Execute a function for each tracked load balancer
- * The callback is called with the load balancer as the first parameter,
- * and $params passed as the subsequent parameters.
- * @param callable $callback
- * @param array $params
- */
public function forEachLB( $callback, array $params = [] ) {
foreach ( $this->mainLBs as $lb ) {
$callback( $lb, ...$params );
$this->loadMonitorClass = $conf['loadMonitorClass'] ?? 'LoadMonitor';
}
- /**
- * @param bool|string $domain
- * @return LoadBalancer
- */
public function newMainLB( $domain = false ) {
return $this->newLoadBalancer( $this->servers );
}
- /**
- * @param bool|string $domain
- * @return LoadBalancer
- */
public function getMainLB( $domain = false ) {
- if ( !isset( $this->mainLB ) ) {
+ if ( !$this->mainLB ) {
$this->mainLB = $this->newMainLB( $domain );
}
return $lb;
}
- /**
- * Execute a function for each tracked load balancer
- * The callback is called with the load balancer as the first parameter,
- * and $params passed as the subsequent parameters.
- *
- * @param callable $callback
- * @param array $params
- */
public function forEachLB( $callback, array $params = [] ) {
if ( isset( $this->mainLB ) ) {
$callback( $this->mainLB, ...$params );
*/
public function getWriterIndex();
- /**
- * Returns true if the specified index is a valid server index
- *
- * @param int $i
- * @return bool
- */
- public function haveIndex( $i );
-
- /**
- * Returns true if the specified index is valid and has non-zero load
- *
- * @param int $i
- * @return bool
- */
- public function isNonZeroLoad( $i );
-
/**
* Get the number of servers defined in configuration
*
return 0;
}
+ /**
+ * Returns true if the specified index is a valid server index
+ *
+ * @param int $i
+ * @return bool
+ * @deprecated Since 1.34
+ */
public function haveIndex( $i ) {
return array_key_exists( $i, $this->servers );
}
+ /**
+ * Returns true if the specified index is valid and has non-zero load
+ *
+ * @param int $i
+ * @return bool
+ * @deprecated Since 1.34
+ */
public function isNonZeroLoad( $i ) {
return array_key_exists( $i, $this->servers ) && $this->genericLoads[$i] != 0;
}
[],
Xml::label(
$this->msg( 'namespace' )->text(),
- 'namespace',
- ''
+ 'namespace'
) . "\u{00A0}" .
Html::namespaceSelector(
[ 'selected' => $this->opts['namespace'], 'all' => '', 'in-user-lang' => true ],
];
/**
- * Array of Strings Core rights.
- * Each of these should have a corresponding message of the form
- * "right-$right".
- * @showinitializer
* @var string[]
- */
- protected static $mCoreRights = [
- 'apihighlimits',
- 'applychangetags',
- 'autoconfirmed',
- 'autocreateaccount',
- 'autopatrol',
- 'bigdelete',
- 'block',
- 'blockemail',
- 'bot',
- 'browsearchive',
- 'changetags',
- 'createaccount',
- 'createpage',
- 'createtalk',
- 'delete',
- 'deletechangetags',
- 'deletedhistory',
- 'deletedtext',
- 'deletelogentry',
- 'deleterevision',
- 'edit',
- 'editcontentmodel',
- 'editinterface',
- 'editprotected',
- 'editmyoptions',
- 'editmyprivateinfo',
- 'editmyusercss',
- 'editmyuserjson',
- 'editmyuserjs',
- 'editmywatchlist',
- 'editsemiprotected',
- 'editsitecss',
- 'editsitejson',
- 'editsitejs',
- 'editusercss',
- 'edituserjson',
- 'edituserjs',
- 'hideuser',
- 'import',
- 'importupload',
- 'ipblock-exempt',
- 'managechangetags',
- 'markbotedits',
- 'mergehistory',
- 'minoredit',
- 'move',
- 'movefile',
- 'move-categorypages',
- 'move-rootuserpages',
- 'move-subpages',
- 'nominornewtalk',
- 'noratelimit',
- 'override-export-depth',
- 'pagelang',
- 'patrol',
- 'patrolmarks',
- 'protect',
- 'purge',
- 'read',
- 'reupload',
- 'reupload-own',
- 'reupload-shared',
- 'rollback',
- 'sendemail',
- 'siteadmin',
- 'suppressionlog',
- 'suppressredirect',
- 'suppressrevision',
- 'unblockself',
- 'undelete',
- 'unwatchedpages',
- 'upload',
- 'upload_by_url',
- 'userrights',
- 'userrights-interwiki',
- 'viewmyprivateinfo',
- 'viewmywatchlist',
- 'viewsuppressed',
- 'writeapi',
- ];
-
- /**
* @var string[] Cached results of getAllRights()
*/
protected static $mAllRights = false;
public $mBlockedby;
/** @var string */
protected $mHash;
- /** @var array */
- public $mRights;
/** @var string */
protected $mBlockreason;
/** @var array */
return (string)$this->getName();
}
+ public function __get( $name ) {
+ // A shortcut for $mRights deprecation phase
+ if ( $name === 'mRights' ) {
+ return $this->getRights();
+ }
+ }
+
+ public function __set( $name, $value ) {
+ // A shortcut for $mRights deprecation phase, only known legitimate use was for
+ // testing purposes, other uses seem bad in principle
+ if ( $name === 'mRights' ) {
+ MediaWikiServices::getInstance()->getPermissionManager()->overrideUserRightsForTesting(
+ $this,
+ is_null( $value ) ? [] : $value
+ );
+ }
+ }
+
/**
* Test if it's safe to load this User object.
*
* given source. May be "name", "id", "actor", "defaults", "session", or false for no reload.
*/
public function clearInstanceCache( $reloadFrom = false ) {
+ global $wgFullyInitialised;
+
$this->mNewtalk = -1;
$this->mDatePreference = null;
$this->mBlockedby = -1; # Unset
$this->mHash = false;
- $this->mRights = null;
$this->mEffectiveGroups = null;
$this->mImplicitGroups = null;
$this->mGroupMemberships = null;
$this->mOptionsLoaded = false;
$this->mEditCount = null;
+ // Replacement of former `$this->mRights = null` line
+ if ( $wgFullyInitialised && $this->mFrom ) {
+ MediaWikiServices::getInstance()->getPermissionManager()->invalidateUsersRightsCache(
+ $this
+ );
+ }
+
if ( $reloadFrom ) {
$this->mLoadedItems = [];
$this->mFrom = $reloadFrom;
* @param Title $title Title to check
* @param bool $fromReplica Whether to check the replica DB instead of the master
* @return bool
- * @throws MWException
*
* @deprecated since 1.33,
* use MediaWikiServices::getInstance()->getPermissionManager()->isBlockedFrom(..)
/**
* Get the permissions this user has.
* @return string[] permission names
+ *
+ * @deprecated since 1.34, use MediaWikiServices::getInstance()->getPermissionManager()
+ * ->getUserPermissions(..) instead
+ *
*/
public function getRights() {
- if ( is_null( $this->mRights ) ) {
- $this->mRights = self::getGroupPermissions( $this->getEffectiveGroups() );
- Hooks::run( 'UserGetRights', [ $this, &$this->mRights ] );
-
- // Deny any rights denied by the user's session, unless this
- // endpoint has no sessions.
- if ( !defined( 'MW_NO_SESSION' ) ) {
- $allowedRights = $this->getRequest()->getSession()->getAllowedUserRights();
- if ( $allowedRights !== null ) {
- $this->mRights = array_intersect( $this->mRights, $allowedRights );
- }
- }
-
- Hooks::run( 'UserGetRightsRemove', [ $this, &$this->mRights ] );
- // Force reindexation of rights when a hook has unset one of them
- $this->mRights = array_values( array_unique( $this->mRights ) );
-
- // If block disables login, we should also remove any
- // extra rights blocked users might have, in case the
- // blocked user has a pre-existing session (T129738).
- // This is checked here for cases where people only call
- // $user->isAllowed(). It is also checked in Title::checkUserBlock()
- // to give a better error message in the common case.
- $config = RequestContext::getMain()->getConfig();
- // @TODO Partial blocks should not prevent the user from logging in.
- // see: https://phabricator.wikimedia.org/T208895
- if (
- $this->isLoggedIn() &&
- $config->get( 'BlockDisablesLogin' ) &&
- $this->getBlock()
- ) {
- $anon = new User;
- $this->mRights = array_intersect( $this->mRights, $anon->getRights() );
- }
- }
- return $this->mRights;
+ return MediaWikiServices::getInstance()->getPermissionManager()->getUserPermissions( $this );
}
/**
// Refresh the groups caches, and clear the rights cache so it will be
// refreshed on the next call to $this->getRights().
$this->getEffectiveGroups( true );
- $this->mRights = null;
-
+ MediaWikiServices::getInstance()->getPermissionManager()->invalidateUsersRightsCache( $this );
$this->invalidateCache();
return true;
// Refresh the groups caches, and clear the rights cache so it will be
// refreshed on the next call to $this->getRights().
$this->getEffectiveGroups( true );
- $this->mRights = null;
-
+ MediaWikiServices::getInstance()->getPermissionManager()->invalidateUsersRightsCache( $this );
$this->invalidateCache();
return true;
/**
* Internal mechanics of testing a permission
+ *
+ * @deprecated since 1.34, use MediaWikiServices::getInstance()
+ * ->getPermissionManager()->userHasRight(...) instead
+ *
* @param string $action
+ *
* @return bool
*/
public function isAllowed( $action = '' ) {
- if ( $action === '' ) {
- return true; // In the spirit of DWIM
- }
- // Use strict parameter to avoid matching numeric 0 accidentally inserted
- // by misconfiguration: 0 == 'foo'
- return in_array( $action, $this->getRights(), true );
+ return MediaWikiServices::getInstance()->getPermissionManager()
+ ->userHasRight( $this, $action );
}
/**
/**
* Get the permissions associated with a given list of groups
*
+ * @deprecated since 1.34, use MediaWikiServices::getInstance()->getPermissionManager()
+ * ->getGroupPermissions() instead
+ *
* @param array $groups Array of Strings List of internal group names
* @return array Array of Strings List of permission key names for given groups combined
*/
public static function getGroupPermissions( $groups ) {
- global $wgGroupPermissions, $wgRevokePermissions;
- $rights = [];
- // grant every granted permission first
- foreach ( $groups as $group ) {
- if ( isset( $wgGroupPermissions[$group] ) ) {
- $rights = array_merge( $rights,
- // array_filter removes empty items
- array_keys( array_filter( $wgGroupPermissions[$group] ) ) );
- }
- }
- // now revoke the revoked permissions
- foreach ( $groups as $group ) {
- if ( isset( $wgRevokePermissions[$group] ) ) {
- $rights = array_diff( $rights,
- array_keys( array_filter( $wgRevokePermissions[$group] ) ) );
- }
- }
- return array_unique( $rights );
+ return MediaWikiServices::getInstance()->getPermissionManager()->getGroupPermissions( $groups );
}
/**
* Get all the groups who have a given permission
*
+ * @deprecated since 1.34, use MediaWikiServices::getInstance()->getPermissionManager()
+ * ->getGroupsWithPermission() instead
+ *
* @param string $role Role to check
* @return array Array of Strings List of internal group names with the given permission
*/
public static function getGroupsWithPermission( $role ) {
- global $wgGroupPermissions;
- $allowedGroups = [];
- foreach ( array_keys( $wgGroupPermissions ) as $group ) {
- if ( self::groupHasPermission( $group, $role ) ) {
- $allowedGroups[] = $group;
- }
- }
- return $allowedGroups;
+ return MediaWikiServices::getInstance()->getPermissionManager()->getGroupsWithPermission( $role );
}
/**
* User::isEveryoneAllowed() instead. That properly checks if it's revoked
* from anyone.
*
+ * @deprecated since 1.34, use MediaWikiServices::getInstance()->getPermissionManager()
+ * ->groupHasPermission(..) instead
+ *
* @since 1.21
* @param string $group Group to check
* @param string $role Role to check
* @return bool
*/
public static function groupHasPermission( $group, $role ) {
- global $wgGroupPermissions, $wgRevokePermissions;
- return isset( $wgGroupPermissions[$group][$role] ) && $wgGroupPermissions[$group][$role]
- && !( isset( $wgRevokePermissions[$group][$role] ) && $wgRevokePermissions[$group][$role] );
+ return MediaWikiServices::getInstance()->getPermissionManager()
+ ->groupHasPermission( $group, $role );
}
/**
* Specifically, session-based rights restrictions (such as OAuth or bot
* passwords) are applied based on the current session.
*
- * @since 1.22
+ * @deprecated since 1.34, use MediaWikiServices::getInstance()->getPermissionManager()
+ * ->isEveryoneAllowed() instead
+ *
* @param string $right Right to check
+ *
* @return bool
+ * @since 1.22
*/
public static function isEveryoneAllowed( $right ) {
- global $wgGroupPermissions, $wgRevokePermissions;
- static $cache = [];
-
- // Use the cached results, except in unit tests which rely on
- // being able change the permission mid-request
- if ( isset( $cache[$right] ) && !defined( 'MW_PHPUNIT_TEST' ) ) {
- return $cache[$right];
- }
-
- if ( !isset( $wgGroupPermissions['*'][$right] ) || !$wgGroupPermissions['*'][$right] ) {
- $cache[$right] = false;
- return false;
- }
-
- // If it's revoked anywhere, then everyone doesn't have it
- foreach ( $wgRevokePermissions as $rights ) {
- if ( isset( $rights[$right] ) && $rights[$right] ) {
- $cache[$right] = false;
- return false;
- }
- }
-
- // Remove any rights that aren't allowed to the global-session user,
- // unless there are no sessions for this endpoint.
- if ( !defined( 'MW_NO_SESSION' ) ) {
- $allowedRights = SessionManager::getGlobalSession()->getAllowedUserRights();
- if ( $allowedRights !== null && !in_array( $right, $allowedRights, true ) ) {
- $cache[$right] = false;
- return false;
- }
- }
-
- // Allow extensions to say false
- if ( !Hooks::run( 'UserIsEveryoneAllowed', [ $right ] ) ) {
- $cache[$right] = false;
- return false;
- }
-
- $cache[$right] = true;
- return true;
+ return MediaWikiServices::getInstance()->getPermissionManager()->isEveryoneAllowed( $right );
}
/**
/**
* Get a list of all available permissions.
+ *
+ * @deprecated since 1.34, use MediaWikiServices::getInstance()->getPermissionManager()
+ * ->getAllPermissions() instead
+ *
* @return string[] Array of permission names
*/
public static function getAllRights() {
- if ( self::$mAllRights === false ) {
- global $wgAvailableRights;
- if ( count( $wgAvailableRights ) ) {
- self::$mAllRights = array_unique( array_merge( self::$mCoreRights, $wgAvailableRights ) );
- } else {
- self::$mAllRights = self::$mCoreRights;
- }
- Hooks::run( 'UserGetAllRights', [ &self::$mAllRights ] );
- }
- return self::$mAllRights;
+ return MediaWikiServices::getInstance()->getPermissionManager()->getAllPermissions();
}
/**
public function viewPrevNext( Title $title, $offset, $limit,
array $query = [], $atend = false
) {
+ wfDeprecated( __METHOD__, '1.34' );
// @todo FIXME: Why on earth this needs one message for the text and another one for tooltip?
# Make 'previous' link
class CheckStorage {
const CONCAT_HEADER = 'O:27:"concatenatedgziphistoryblob"';
public $oldIdMap, $errors;
+ /** @var ExternalStoreDB */
public $dbStore = null;
public $errorDescriptions = [
// Check external normal blobs for existence
if ( count( $externalNormalBlobs ) ) {
if ( is_null( $this->dbStore ) ) {
- $this->dbStore = new ExternalStoreDB;
+ $esFactory = MediaWikiServices::getInstance()->getExternalStoreFactory();
+ $this->dbStore = $esFactory->getStore( 'DB' );
}
foreach ( $externalConcatBlobs as $cluster => $xBlobIds ) {
$blobIds = array_keys( $xBlobIds );
}
if ( is_null( $this->dbStore ) ) {
- $this->dbStore = new ExternalStoreDB;
+ $esFactory = MediaWikiServices::getInstance()->getExternalStoreFactory();
+ $this->dbStore = $esFactory->getStore( 'DB' );
}
foreach ( $externalConcatBlobs as $cluster => $oldIds ) {
# Store in external storage if required
if ( $extdb !== '' ) {
- $storeObj = new ExternalStoreDB;
+ $esFactory = MediaWikiServices::getInstance()->getExternalStoreFactory();
+ /** @var ExternalStoreDB $storeObj */
+ $storeObj = $esFactory->getStore( 'DB' );
$compress = $storeObj->store( $extdb, $compress );
if ( $compress === false ) {
$this->error( "Unable to store object" );
# Set up external storage
if ( $extdb != '' ) {
- $storeObj = new ExternalStoreDB;
+ $esFactory = MediaWikiServices::getInstance()->getExternalStoreFactory();
+ /** @var ExternalStoreDB $storeObj */
+ $storeObj = $esFactory->getStore( 'DB' );
}
# Get all articles by page_id
* @ingroup Maintenance ExternalStorage
*/
+use MediaWiki\MediaWikiServices;
+
define( 'REPORTING_INTERVAL', 1 );
if ( !defined( 'MEDIAWIKI' ) ) {
$fname = 'moveToExternal';
- if ( !isset( $args[0] ) ) {
- print "Usage: php moveToExternal.php [-s <startid>] [-e <endid>] <cluster>\n";
+ if ( !isset( $args[1] ) ) {
+ print "Usage: php moveToExternal.php [-s <startid>] [-e <endid>] <type> <location>\n";
exit;
}
- $cluster = $args[0];
+ $type = $args[0]; // e.g. "DB" or "mwstore"
+ $location = $args[1]; // e.g. "cluster12" or "global-swift"
$dbw = wfGetDB( DB_MASTER );
$maxID = $options['e'] ?? $dbw->selectField( 'text', 'MAX(old_id)', '', $fname );
$minID = $options['s'] ?? 1;
- moveToExternal( $cluster, $maxID, $minID );
+ moveToExternal( $type, $location, $maxID, $minID );
}
-function moveToExternal( $cluster, $maxID, $minID = 1 ) {
+function moveToExternal( $type, $location, $maxID, $minID = 1 ) {
$fname = 'moveToExternal';
$dbw = wfGetDB( DB_MASTER );
$dbr = wfGetDB( DB_REPLICA );
$blockSize = 1000;
$numBlocks = ceil( $count / $blockSize );
print "Moving text rows from $minID to $maxID to external storage\n";
- $ext = new ExternalStoreDB;
+
+ $esFactory = MediaWikiServices::getInstance()->getExternalStoreFactory();
+ $extStore = $esFactory->getStore( $type );
$numMoved = 0;
for ( $block = 0; $block < $numBlocks; $block++ ) {
# print "Storing " . strlen( $text ) . " bytes to $url\n";
# print "old_id=$id\n";
- $url = $ext->store( $cluster, $text );
+ $url = $extStore->store( $location, $text );
if ( !$url ) {
print "Error writing to external storage\n";
exit;
public $replicaId = false;
public $noCount = false;
public $debugLog, $infoLog, $criticalLog;
+ /** @var ExternalStoreDB */
public $store;
private static $optionsWithArgs = [
foreach ( $options as $name => $value ) {
$this->$name = $value;
}
- $this->store = new ExternalStoreDB;
+ $esFactory = MediaWikiServices::getInstance()->getExternalStoreFactory();
+ $this->store = $esFactory->getStore( 'DB' );
if ( !$this->isChild ) {
$GLOBALS['wgDebugLogPrefix'] = "RCT M: ";
} elseif ( $this->replicaId !== false ) {
global $wgSessionProviders, $wgSessionPbkdf2Iterations;
global $wgJobTypeConf;
global $wgAuthManagerConfig;
+ global $wgShowExceptionDetails;
+
+ $wgShowExceptionDetails = true;
// wfWarn should cause tests to fail
$wgDevelopmentWarnings = true;
'HamcrestPHPUnitIntegration' => "$testDir/phpunit/HamcrestPHPUnitIntegration.php",
'LessFileCompilationTest' => "$testDir/phpunit/LessFileCompilationTest.php",
'MediaWikiCoversValidator' => "$testDir/phpunit/MediaWikiCoversValidator.php",
+ 'MediaWikiGroupValidator' => "$testDir/phpunit/MediaWikiGroupValidator.php",
'MediaWikiLangTestCase' => "$testDir/phpunit/MediaWikiLangTestCase.php",
'MediaWikiLoggerPHPUnitTestListener' => "$testDir/phpunit/MediaWikiLoggerPHPUnitTestListener.php",
'MediaWikiPHPUnitCommand' => "$testDir/phpunit/MediaWikiPHPUnitCommand.php",
--- /dev/null
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Testing
+ */
+
+/**
+ * Trait that provides methods to check if group annotations are valid.
+ */
+trait MediaWikiGroupValidator {
+
+ /**
+ * @return bool
+ * @throws ReflectionException
+ * @since 1.34
+ */
+ public function isTestInDatabaseGroup() {
+ // If the test class says it belongs to the Database group, it needs the database.
+ // NOTE: This ONLY checks for the group in the class level doc comment.
+ $rc = new ReflectionClass( $this );
+ return (bool)preg_match( '/@group +Database/im', $rc->getDocComment() );
+ }
+}
use MediaWikiCoversValidator;
use PHPUnit4And6Compat;
+ use MediaWikiGroupValidator;
/**
* The original service locator. This is overridden during setUp().
}
/**
+ * Overrides specific user permissions until services are reloaded
*
* @since 1.34
+ *
+ * @param User $user
+ * @param string[]|string $permissions
+ *
+ * @throws Exception
+ */
+ public function overrideUserPermissions( $user, $permissions = [] ) {
+ MediaWikiServices::getInstance()->getPermissionManager()->overrideUserRightsForTesting(
+ $user,
+ $permissions
+ );
+ }
+
+ /**
* Sets the logger for a specified channel, for the duration of the test.
* @since 1.27
* @param string $channel
return $this->tablesUsed || $this->isTestInDatabaseGroup();
}
- /**
- * @return bool
- * @since 1.32
- */
- protected function isTestInDatabaseGroup() {
- // If the test class says it belongs to the Database group, it needs the database.
- // NOTE: This ONLY checks for the group in the class level doc comment.
- $rc = new ReflectionClass( $this );
- return (bool)preg_match( '/@group +Database/im', $rc->getDocComment() );
- }
-
/**
* Insert a new page.
*
abstract class MediaWikiUnitTestCase extends TestCase {
use PHPUnit4And6Compat;
use MediaWikiCoversValidator;
+ use MediaWikiGroupValidator;
+
+ /**
+ * @throws ReflectionException
+ */
+ protected function setUp() {
+ parent::setUp();
+ if ( $this->isTestInDatabaseGroup() ) {
+ throw new \Exception( get_class( $this ) .
+ ' extends MediaWikiUnitTestCase, and may not have the @group Database annotation.' );
+ }
+ }
+
}
wfRequireOnceInGlobalScope( "$IP/includes/AutoLoader.php" );
wfRequireOnceInGlobalScope( "$IP/tests/common/TestsAutoLoader.php" );
+wfRequireOnceInGlobalScope( "$IP/includes/Defines.php" );
+++ /dev/null
-<?php
-/**
- * Copyright @ 2011 Alexandre Emsenhuber
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- */
-
-class FauxResponseTest extends MediaWikiTestCase {
- /** @var FauxResponse */
- protected $response;
-
- protected function setUp() {
- parent::setUp();
- $this->response = new FauxResponse;
- }
-
- /**
- * @covers FauxResponse::setCookie
- * @covers FauxResponse::getCookie
- * @covers FauxResponse::getCookieData
- * @covers FauxResponse::getCookies
- */
- public function testCookie() {
- $expire = time() + 100;
- $cookie = [
- 'value' => 'val',
- 'path' => '/path',
- 'domain' => 'domain',
- 'secure' => true,
- 'httpOnly' => false,
- 'raw' => false,
- 'expire' => $expire,
- ];
-
- $this->assertEquals( null, $this->response->getCookie( 'xkey' ), 'Non-existing cookie' );
- $this->response->setCookie( 'key', 'val', $expire, [
- 'prefix' => 'x',
- 'path' => '/path',
- 'domain' => 'domain',
- 'secure' => 1,
- 'httpOnly' => 0,
- ] );
- $this->assertEquals( 'val', $this->response->getCookie( 'xkey' ), 'Existing cookie' );
- $this->assertEquals( $cookie, $this->response->getCookieData( 'xkey' ),
- 'Existing cookie (data)' );
- $this->assertEquals( [ 'xkey' => $cookie ], $this->response->getCookies(),
- 'Existing cookies' );
- }
-
- /**
- * @covers FauxResponse::getheader
- * @covers FauxResponse::header
- */
- public function testHeader() {
- $this->assertEquals( null, $this->response->getHeader( 'Location' ), 'Non-existing header' );
-
- $this->response->header( 'Location: http://localhost/' );
- $this->assertEquals(
- 'http://localhost/',
- $this->response->getHeader( 'Location' ),
- 'Set header'
- );
-
- $this->response->header( 'Location: http://127.0.0.1/' );
- $this->assertEquals(
- 'http://127.0.0.1/',
- $this->response->getHeader( 'Location' ),
- 'Same header'
- );
-
- $this->response->header( 'Location: http://127.0.0.2/', false );
- $this->assertEquals(
- 'http://127.0.0.1/',
- $this->response->getHeader( 'Location' ),
- 'Same header with override disabled'
- );
-
- $this->response->header( 'Location: http://localhost/' );
- $this->assertEquals(
- 'http://localhost/',
- $this->response->getHeader( 'LOCATION' ),
- 'Get header case insensitive'
- );
- }
-
- /**
- * @covers FauxResponse::getStatusCode
- */
- public function testResponseCode() {
- $this->response->header( 'HTTP/1.1 200' );
- $this->assertEquals( 200, $this->response->getStatusCode(), 'Header with no message' );
-
- $this->response->header( 'HTTP/1.x 201' );
- $this->assertEquals(
- 201,
- $this->response->getStatusCode(),
- 'Header with no message and protocol 1.x'
- );
-
- $this->response->header( 'HTTP/1.1 202 OK' );
- $this->assertEquals( 202, $this->response->getStatusCode(), 'Normal header' );
-
- $this->response->header( 'HTTP/1.x 203 OK' );
- $this->assertEquals(
- 203,
- $this->response->getStatusCode(),
- 'Normal header with no message and protocol 1.x'
- );
-
- $this->response->header( 'HTTP/1.x 204 OK', false, 205 );
- $this->assertEquals(
- 205,
- $this->response->getStatusCode(),
- 'Third parameter overrides the HTTP/... header'
- );
-
- $this->response->statusHeader( 210 );
- $this->assertEquals(
- 210,
- $this->response->getStatusCode(),
- 'Handle statusHeader method'
- );
-
- $this->response->header( 'Location: http://localhost/', false, 206 );
- $this->assertEquals(
- 206,
- $this->response->getStatusCode(),
- 'Third parameter with another header'
- );
- }
-}
+++ /dev/null
-<?php
-
-use Wikimedia\TestingAccessWrapper;
-
-/**
- * Test class for FormOptions initialization
- * Ensure the FormOptions::add() does what we want it to do.
- *
- * Copyright © 2011, Antoine Musso
- *
- * @author Antoine Musso
- */
-class FormOptionsInitializationTest extends MediaWikiTestCase {
- /**
- * @var FormOptions
- */
- protected $object;
-
- /**
- * A new fresh and empty FormOptions object to test initialization
- * with.
- */
- protected function setUp() {
- parent::setUp();
- $this->object = TestingAccessWrapper::newFromObject( new FormOptions() );
- }
-
- /**
- * @covers FormOptions::add
- */
- public function testAddStringOption() {
- $this->object->add( 'foo', 'string value' );
- $this->assertEquals(
- [
- 'foo' => [
- 'default' => 'string value',
- 'consumed' => false,
- 'type' => FormOptions::STRING,
- 'value' => null,
- ]
- ],
- $this->object->options
- );
- }
-
- /**
- * @covers FormOptions::add
- */
- public function testAddIntegers() {
- $this->object->add( 'one', 1 );
- $this->object->add( 'negone', -1 );
- $this->assertEquals(
- [
- 'negone' => [
- 'default' => -1,
- 'value' => null,
- 'consumed' => false,
- 'type' => FormOptions::INT,
- ],
- 'one' => [
- 'default' => 1,
- 'value' => null,
- 'consumed' => false,
- 'type' => FormOptions::INT,
- ]
- ],
- $this->object->options
- );
- }
-}
+++ /dev/null
-<?php
-/**
- * This file host two test case classes for the MediaWiki FormOptions class:
- * - FormOptionsInitializationTest : tests initialization of the class.
- * - FormOptionsTest : tests methods an on instance
- *
- * The split let us take advantage of setting up a fixture for the methods
- * tests.
- */
-
-/**
- * Test class for FormOptions methods.
- *
- * Copyright © 2011, Antoine Musso
- *
- * @author Antoine Musso
- */
-class FormOptionsTest extends MediaWikiTestCase {
- /**
- * @var FormOptions
- */
- protected $object;
-
- /**
- * Instanciates a FormOptions object to play with.
- * FormOptions::add() is tested by the class FormOptionsInitializationTest
- * so we assume the function is well tested already an use it to create
- * the fixture.
- */
- protected function setUp() {
- parent::setUp();
- $this->object = new FormOptions;
- $this->object->add( 'string1', 'string one' );
- $this->object->add( 'string2', 'string two' );
- $this->object->add( 'integer', 0 );
- $this->object->add( 'float', 0.0 );
- $this->object->add( 'intnull', 0, FormOptions::INTNULL );
- }
-
- /** Helpers for testGuessType() */
- /* @{ */
- private function assertGuessBoolean( $data ) {
- $this->guess( FormOptions::BOOL, $data );
- }
-
- private function assertGuessInt( $data ) {
- $this->guess( FormOptions::INT, $data );
- }
-
- private function assertGuessFloat( $data ) {
- $this->guess( FormOptions::FLOAT, $data );
- }
-
- private function assertGuessString( $data ) {
- $this->guess( FormOptions::STRING, $data );
- }
-
- private function assertGuessArray( $data ) {
- $this->guess( FormOptions::ARR, $data );
- }
-
- /** Generic helper */
- private function guess( $expected, $data ) {
- $this->assertEquals(
- $expected,
- FormOptions::guessType( $data )
- );
- }
-
- /* @} */
-
- /**
- * Reuse helpers above assertGuessBoolean assertGuessInt assertGuessString
- * @covers FormOptions::guessType
- */
- public function testGuessTypeDetection() {
- $this->assertGuessBoolean( true );
- $this->assertGuessBoolean( false );
-
- $this->assertGuessInt( 0 );
- $this->assertGuessInt( -5 );
- $this->assertGuessInt( 5 );
- $this->assertGuessInt( 0x0F );
-
- $this->assertGuessFloat( 0.0 );
- $this->assertGuessFloat( 1.5 );
- $this->assertGuessFloat( 1e3 );
-
- $this->assertGuessString( 'true' );
- $this->assertGuessString( 'false' );
- $this->assertGuessString( '5' );
- $this->assertGuessString( '0' );
- $this->assertGuessString( '1.5' );
-
- $this->assertGuessArray( [ 'foo' ] );
- }
-
- /**
- * @expectedException MWException
- * @covers FormOptions::guessType
- */
- public function testGuessTypeOnNullThrowException() {
- $this->object->guessType( null );
- }
-}
+++ /dev/null
-<?php
-
-/**
- * @covers Licenses
- */
-class LicensesTest extends MediaWikiTestCase {
-
- public function testLicenses() {
- $str = "
-* Free licenses:
-** GFDL|Debian disagrees
-";
-
- $lc = new Licenses( [
- 'fieldname' => 'FooField',
- 'type' => 'select',
- 'section' => 'description',
- 'id' => 'wpLicense',
- 'label' => 'A label text', # Note can't test label-message because $wgOut is not defined
- 'name' => 'AnotherName',
- 'licenses' => $str,
- ] );
- $this->assertThat( $lc, $this->isInstanceOf( Licenses::class ) );
- }
-}
namespace MediaWiki\Tests\Permissions;
use Action;
+use FauxRequest;
+use MediaWiki\Session\SessionId;
+use MediaWiki\Session\TestUtils;
use MediaWikiLangTestCase;
use RequestContext;
+use stdClass;
use Title;
use User;
use MediaWiki\Block\DatabaseBlock;
use MediaWiki\Block\SystemBlock;
use MediaWiki\MediaWikiServices;
use MediaWiki\Permissions\PermissionManager;
+use Wikimedia\TestingAccessWrapper;
/**
* @group Database
'wgNamespaceProtection' => [
NS_MEDIAWIKI => 'editinterface',
],
+ 'wgRevokePermissions' => [
+ 'formertesters' => [
+ 'runtest' => true
+ ]
+ ],
+ 'wgAvailableRights' => [
+ 'test',
+ 'runtest',
+ 'writetest',
+ 'nukeworld',
+ 'modifytest',
+ 'editmyoptions'
+ ]
] );
+
+ $this->setGroupPermissions( 'unittesters', 'test', true );
+ $this->setGroupPermissions( 'unittesters', 'runtest', true );
+ $this->setGroupPermissions( 'unittesters', 'writetest', false );
+ $this->setGroupPermissions( 'unittesters', 'nukeworld', false );
+
+ $this->setGroupPermissions( 'testwriters', 'test', true );
+ $this->setGroupPermissions( 'testwriters', 'writetest', true );
+ $this->setGroupPermissions( 'testwriters', 'modifytest', true );
+
+ $this->setGroupPermissions( '*', 'editmyoptions', true );
+
// Without this testUserBlock will use a non-English context on non-English MediaWiki
// installations (because of how Title::checkUserBlock is implemented) and fail.
RequestContext::resetMain();
$this->user = $this->userUser;
}
- $this->permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
-
- $this->overrideMwServices();
+ $this->resetServices();
}
- protected function setUserPerm( $perm ) {
- // Setting member variables is evil!!!
-
- if ( is_array( $perm ) ) {
- $this->user->mRights = $perm;
- } else {
- $this->user->mRights = [ $perm ];
- }
+ public function tearDown() {
+ parent::tearDown();
+ $this->restoreMwServices();
}
protected function setTitle( $ns, $title = "Main_Page" ) {
} else {
$this->user = $this->altUser;
}
+ $this->resetServices();
}
/**
$this->setUser( 'anon' );
$this->setTitle( NS_TALK );
- $this->setUserPerm( "createtalk" );
- $res = $this->permissionManager
+ $this->overrideUserPermissions( $this->user, "createtalk" );
+ $res = MediaWikiServices::getInstance()->getPermissionManager()
->getPermissionErrors( 'create', $this->user, $this->title );
$this->assertEquals( [], $res );
$this->setTitle( NS_TALK );
- $this->setUserPerm( "createpage" );
- $res = $this->permissionManager
+ $this->overrideUserPermissions( $this->user, "createpage" );
+ $res = MediaWikiServices::getInstance()->getPermissionManager()
->getPermissionErrors( 'create', $this->user, $this->title );
$this->assertEquals( [ [ "nocreatetext" ] ], $res );
$this->setTitle( NS_TALK );
- $this->setUserPerm( "" );
- $res = $this->permissionManager
+ $this->overrideUserPermissions( $this->user, "" );
+ $res = MediaWikiServices::getInstance()->getPermissionManager()
->getPermissionErrors( 'create', $this->user, $this->title );
$this->assertEquals( [ [ 'nocreatetext' ] ], $res );
$this->setTitle( NS_MAIN );
- $this->setUserPerm( "createpage" );
- $res = $this->permissionManager
+ $this->overrideUserPermissions( $this->user, "createpage" );
+ $res = MediaWikiServices::getInstance()->getPermissionManager()
->getPermissionErrors( 'create', $this->user, $this->title );
$this->assertEquals( [], $res );
$this->setTitle( NS_MAIN );
- $this->setUserPerm( "createtalk" );
- $res = $this->permissionManager
+ $this->overrideUserPermissions( $this->user, "createtalk" );
+ $res = MediaWikiServices::getInstance()->getPermissionManager()
->getPermissionErrors( 'create', $this->user, $this->title );
$this->assertEquals( [ [ 'nocreatetext' ] ], $res );
$this->setUser( $this->userName );
$this->setTitle( NS_TALK );
- $this->setUserPerm( "createtalk" );
- $res = $this->permissionManager
+ $this->overrideUserPermissions( $this->user, "createtalk" );
+ $res = MediaWikiServices::getInstance()->getPermissionManager()
->getPermissionErrors( 'create', $this->user, $this->title );
$this->assertEquals( [], $res );
$this->setTitle( NS_TALK );
- $this->setUserPerm( "createpage" );
- $res = $this->permissionManager
+ $this->overrideUserPermissions( $this->user, "createpage" );
+ $res = MediaWikiServices::getInstance()->getPermissionManager()
->getPermissionErrors( 'create', $this->user, $this->title );
$this->assertEquals( [ [ 'nocreate-loggedin' ] ], $res );
$this->setTitle( NS_TALK );
- $this->setUserPerm( "" );
- $res = $this->permissionManager
+ $this->overrideUserPermissions( $this->user, "" );
+ $res = MediaWikiServices::getInstance()->getPermissionManager()
->getPermissionErrors( 'create', $this->user, $this->title );
$this->assertEquals( [ [ 'nocreate-loggedin' ] ], $res );
$this->setTitle( NS_MAIN );
- $this->setUserPerm( "createpage" );
- $res = $this->permissionManager
+ $this->overrideUserPermissions( $this->user, "createpage" );
+ $res = MediaWikiServices::getInstance()->getPermissionManager()
->getPermissionErrors( 'create', $this->user, $this->title );
$this->assertEquals( [], $res );
$this->setTitle( NS_MAIN );
- $this->setUserPerm( "createtalk" );
- $res = $this->permissionManager
+ $this->overrideUserPermissions( $this->user, "createtalk" );
+ $res = MediaWikiServices::getInstance()->getPermissionManager()
->getPermissionErrors( 'create', $this->user, $this->title );
$this->assertEquals( [ [ 'nocreate-loggedin' ] ], $res );
$this->setTitle( NS_MAIN );
- $this->setUserPerm( "" );
- $res = $this->permissionManager
+ $this->overrideUserPermissions( $this->user, "" );
+ $res = MediaWikiServices::getInstance()->getPermissionManager()
->getPermissionErrors( 'create', $this->user, $this->title );
$this->assertEquals( [ [ 'nocreate-loggedin' ] ], $res );
$this->setUser( 'anon' );
$this->setTitle( NS_USER, $this->userName . '' );
- $this->setUserPerm( "" );
- $res = $this->permissionManager
+ $this->overrideUserPermissions( $this->user, "" );
+ $res = MediaWikiServices::getInstance()->getPermissionManager()
->getPermissionErrors( 'move', $this->user, $this->title );
$this->assertEquals( [ [ 'cant-move-user-page' ], [ 'movenologintext' ] ], $res );
$this->setTitle( NS_USER, $this->userName . '/subpage' );
- $this->setUserPerm( "" );
- $res = $this->permissionManager
+ $this->overrideUserPermissions( $this->user, "" );
+ $res = MediaWikiServices::getInstance()->getPermissionManager()
->getPermissionErrors( 'move', $this->user, $this->title );
$this->assertEquals( [ [ 'movenologintext' ] ], $res );
$this->setTitle( NS_USER, $this->userName . '' );
- $this->setUserPerm( "move-rootuserpages" );
- $res = $this->permissionManager
+ $this->overrideUserPermissions( $this->user, "move-rootuserpages" );
+ $res = MediaWikiServices::getInstance()->getPermissionManager()
->getPermissionErrors( 'move', $this->user, $this->title );
$this->assertEquals( [ [ 'movenologintext' ] ], $res );
$this->setTitle( NS_USER, $this->userName . '/subpage' );
- $this->setUserPerm( "move-rootuserpages" );
- $res = $this->permissionManager
+ $this->overrideUserPermissions( $this->user, "move-rootuserpages" );
+ $res = MediaWikiServices::getInstance()->getPermissionManager()
->getPermissionErrors( 'move', $this->user, $this->title );
$this->assertEquals( [ [ 'movenologintext' ] ], $res );
$this->setTitle( NS_USER, $this->userName . '' );
- $this->setUserPerm( "" );
- $res = $this->permissionManager
+ $this->overrideUserPermissions( $this->user, "" );
+ $res = MediaWikiServices::getInstance()->getPermissionManager()
->getPermissionErrors( 'move', $this->user, $this->title );
$this->assertEquals( [ [ 'cant-move-user-page' ], [ 'movenologintext' ] ], $res );
$this->setTitle( NS_USER, $this->userName . '/subpage' );
- $this->setUserPerm( "" );
- $res = $this->permissionManager
+ $this->overrideUserPermissions( $this->user, "" );
+ $res = MediaWikiServices::getInstance()->getPermissionManager()
->getPermissionErrors( 'move', $this->user, $this->title );
$this->assertEquals( [ [ 'movenologintext' ] ], $res );
$this->setTitle( NS_USER, $this->userName . '' );
- $this->setUserPerm( "move-rootuserpages" );
- $res = $this->permissionManager
+ $this->overrideUserPermissions( $this->user, "move-rootuserpages" );
+ $res = MediaWikiServices::getInstance()->getPermissionManager()
->getPermissionErrors( 'move', $this->user, $this->title );
$this->assertEquals( [ [ 'movenologintext' ] ], $res );
$this->setTitle( NS_USER, $this->userName . '/subpage' );
- $this->setUserPerm( "move-rootuserpages" );
- $res = $this->permissionManager
+ $this->overrideUserPermissions( $this->user, "move-rootuserpages" );
+ $res = MediaWikiServices::getInstance()->getPermissionManager()
->getPermissionErrors( 'move', $this->user, $this->title );
$this->assertEquals( [ [ 'movenologintext' ] ], $res );
$this->setUser( $this->userName );
$this->setTitle( NS_FILE, "img.png" );
- $this->setUserPerm( "" );
- $res = $this->permissionManager
+ $this->overrideUserPermissions( $this->user, "" );
+ $res = MediaWikiServices::getInstance()->getPermissionManager()
->getPermissionErrors( 'move', $this->user, $this->title );
$this->assertEquals( [ [ 'movenotallowedfile' ], [ 'movenotallowed' ] ], $res );
$this->setTitle( NS_FILE, "img.png" );
- $this->setUserPerm( "movefile" );
- $res = $this->permissionManager
+ $this->overrideUserPermissions( $this->user, "movefile" );
+ $res = MediaWikiServices::getInstance()->getPermissionManager()
->getPermissionErrors( 'move', $this->user, $this->title );
$this->assertEquals( [ [ 'movenotallowed' ] ], $res );
$this->setUser( 'anon' );
$this->setTitle( NS_FILE, "img.png" );
- $this->setUserPerm( "" );
- $res = $this->permissionManager
+ $this->overrideUserPermissions( $this->user, "" );
+ $res = MediaWikiServices::getInstance()->getPermissionManager()
->getPermissionErrors( 'move', $this->user, $this->title );
$this->assertEquals( [ [ 'movenotallowedfile' ], [ 'movenologintext' ] ], $res );
$this->setTitle( NS_FILE, "img.png" );
- $this->setUserPerm( "movefile" );
- $res = $this->permissionManager
+ $this->overrideUserPermissions( $this->user, "movefile" );
+ $res = MediaWikiServices::getInstance()->getPermissionManager()
->getPermissionErrors( 'move', $this->user, $this->title );
$this->assertEquals( [ [ 'movenologintext' ] ], $res );
$this->setUser( $this->userName );
- $this->setUserPerm( "move" );
- $this->runGroupPermissions( 'move', [ [ 'movenotallowedfile' ] ] );
+ // $this->setUserPerm( "move" );
+ $this->runGroupPermissions( 'move', 'move', [ [ 'movenotallowedfile' ] ] );
- $this->setUserPerm( "" );
+ // $this->setUserPerm( "" );
$this->runGroupPermissions(
+ '',
'move',
[ [ 'movenotallowedfile' ], [ 'movenotallowed' ] ]
);
$this->setUser( 'anon' );
- $this->setUserPerm( "move" );
- $this->runGroupPermissions( 'move', [ [ 'movenotallowedfile' ] ] );
+ //$this->setUserPerm( "move" );
+ $this->runGroupPermissions( 'move', 'move', [ [ 'movenotallowedfile' ] ] );
- $this->setUserPerm( "" );
+ // $this->setUserPerm( "" );
$this->runGroupPermissions(
+ '',
'move',
[ [ 'movenotallowedfile' ], [ 'movenotallowed' ] ],
[ [ 'movenotallowedfile' ], [ 'movenologintext' ] ]
$this->setTitle( NS_MAIN );
$this->setUser( 'anon' );
- $this->setUserPerm( "move" );
- $this->runGroupPermissions( 'move', [] );
+ // $this->setUserPerm( "move" );
+ $this->runGroupPermissions( 'move', 'move', [] );
- $this->setUserPerm( "" );
- $this->runGroupPermissions( 'move', [ [ 'movenotallowed' ] ],
+ // $this->setUserPerm( "" );
+ $this->runGroupPermissions( '', 'move', [ [ 'movenotallowed' ] ],
[ [ 'movenologintext' ] ] );
$this->setUser( $this->userName );
- $this->setUserPerm( "" );
- $this->runGroupPermissions( 'move', [ [ 'movenotallowed' ] ] );
+ // $this->setUserPerm( "" );
+ $this->runGroupPermissions( '', 'move', [ [ 'movenotallowed' ] ] );
- $this->setUserPerm( "move" );
- $this->runGroupPermissions( 'move', [] );
+ //$this->setUserPerm( "move" );
+ $this->runGroupPermissions( 'move', 'move', [] );
$this->setUser( 'anon' );
- $this->setUserPerm( 'move' );
- $res = $this->permissionManager
+ $this->overrideUserPermissions( $this->user, 'move' );
+ $res = MediaWikiServices::getInstance()->getPermissionManager()
->getPermissionErrors( 'move-target', $this->user, $this->title );
$this->assertEquals( [], $res );
- $this->setUserPerm( '' );
- $res = $this->permissionManager
+ $this->overrideUserPermissions( $this->user, '' );
+ $res = MediaWikiServices::getInstance()->getPermissionManager()
->getPermissionErrors( 'move-target', $this->user, $this->title );
$this->assertEquals( [ [ 'movenotallowed' ] ], $res );
}
$this->setTitle( NS_USER );
$this->setUser( $this->userName );
- $this->setUserPerm( [ "move", "move-rootuserpages" ] );
- $res = $this->permissionManager
+ $this->overrideUserPermissions( $this->user, [ "move", "move-rootuserpages" ] );
+ $res = MediaWikiServices::getInstance()->getPermissionManager()
->getPermissionErrors( 'move-target', $this->user, $this->title );
$this->assertEquals( [], $res );
- $this->setUserPerm( "move" );
- $res = $this->permissionManager
+ $this->overrideUserPermissions( $this->user, "move" );
+ $res = MediaWikiServices::getInstance()->getPermissionManager()
->getPermissionErrors( 'move-target', $this->user, $this->title );
$this->assertEquals( [ [ 'cant-move-to-user-page' ] ], $res );
$this->setUser( 'anon' );
- $this->setUserPerm( [ "move", "move-rootuserpages" ] );
- $res = $this->permissionManager
+ $this->overrideUserPermissions( $this->user, [ "move", "move-rootuserpages" ] );
+ $res = MediaWikiServices::getInstance()->getPermissionManager()
->getPermissionErrors( 'move-target', $this->user, $this->title );
$this->assertEquals( [], $res );
$this->setTitle( NS_USER, "User/subpage" );
- $this->setUserPerm( [ "move", "move-rootuserpages" ] );
- $res = $this->permissionManager
+ $this->overrideUserPermissions( $this->user, [ "move", "move-rootuserpages" ] );
+ $res = MediaWikiServices::getInstance()->getPermissionManager()
->getPermissionErrors( 'move-target', $this->user, $this->title );
$this->assertEquals( [], $res );
- $this->setUserPerm( "move" );
- $res = $this->permissionManager
+ $this->overrideUserPermissions( $this->user, "move" );
+ $res = MediaWikiServices::getInstance()->getPermissionManager()
->getPermissionErrors( 'move-target', $this->user, $this->title );
$this->assertEquals( [], $res );
];
foreach ( [ "edit", "protect", "" ] as $action ) {
- $this->setUserPerm( null );
+ $this->overrideUserPermissions( $this->user );
$this->assertEquals( $check[$action][0],
- $this->permissionManager
+ MediaWikiServices::getInstance()->getPermissionManager()
->getPermissionErrors( $action, $this->user, $this->title, true ) );
$this->assertEquals( $check[$action][0],
- $this->permissionManager
+ MediaWikiServices::getInstance()->getPermissionManager()
->getPermissionErrors( $action, $this->user, $this->title, 'full' ) );
$this->assertEquals( $check[$action][0],
- $this->permissionManager
+ MediaWikiServices::getInstance()->getPermissionManager()
->getPermissionErrors( $action, $this->user, $this->title, 'secure' ) );
global $wgGroupPermissions;
$old = $wgGroupPermissions;
$wgGroupPermissions = [];
+ $this->resetServices();
$this->assertEquals( $check[$action][1],
- $this->permissionManager
+ MediaWikiServices::getInstance()->getPermissionManager()
->getPermissionErrors( $action, $this->user, $this->title, true ) );
$this->assertEquals( $check[$action][1],
- $this->permissionManager
+ MediaWikiServices::getInstance()->getPermissionManager()
->getPermissionErrors( $action, $this->user, $this->title, 'full' ) );
$this->assertEquals( $check[$action][1],
- $this->permissionManager
+ MediaWikiServices::getInstance()->getPermissionManager()
->getPermissionErrors( $action, $this->user, $this->title, 'secure' ) );
$wgGroupPermissions = $old;
+ $this->resetServices();
- $this->setUserPerm( $action );
+ $this->overrideUserPermissions( $this->user, $action );
$this->assertEquals( $check[$action][2],
- $this->permissionManager
+ MediaWikiServices::getInstance()->getPermissionManager()
->getPermissionErrors( $action, $this->user, $this->title, true ) );
$this->assertEquals( $check[$action][2],
- $this->permissionManager
+ MediaWikiServices::getInstance()->getPermissionManager()
->getPermissionErrors( $action, $this->user, $this->title, 'full' ) );
$this->assertEquals( $check[$action][2],
- $this->permissionManager
+ MediaWikiServices::getInstance()->getPermissionManager()
->getPermissionErrors( $action, $this->user, $this->title, 'secure' ) );
- $this->setUserPerm( $action );
+ $this->overrideUserPermissions( $this->user, $action );
$this->assertEquals( $check[$action][3],
- $this->permissionManager->userCan( $action, $this->user, $this->title, true ) );
+ MediaWikiServices::getInstance()->getPermissionManager()
+ ->userCan( $action, $this->user, $this->title, true ) );
$this->assertEquals( $check[$action][3],
- $this->permissionManager->userCan( $action, $this->user, $this->title,
+ MediaWikiServices::getInstance()->getPermissionManager()
+ ->userCan( $action, $this->user, $this->title,
PermissionManager::RIGOR_QUICK ) );
# count( User::getGroupsWithPermissions( $action ) ) < 1
}
}
- protected function runGroupPermissions( $action, $result, $result2 = null ) {
+ protected function runGroupPermissions( $perm, $action, $result, $result2 = null ) {
global $wgGroupPermissions;
if ( $result2 === null ) {
$wgGroupPermissions['autoconfirmed']['move'] = false;
$wgGroupPermissions['user']['move'] = false;
- $res = $this->permissionManager
+ $this->resetServices();
+ $this->overrideUserPermissions( $this->user, $perm );
+ $res = MediaWikiServices::getInstance()->getPermissionManager()
->getPermissionErrors( $action, $this->user, $this->title );
$this->assertEquals( $result, $res );
$wgGroupPermissions['autoconfirmed']['move'] = true;
$wgGroupPermissions['user']['move'] = false;
- $res = $this->permissionManager
+ $this->resetServices();
+ $this->overrideUserPermissions( $this->user, $perm );
+ $res = MediaWikiServices::getInstance()->getPermissionManager()
->getPermissionErrors( $action, $this->user, $this->title );
$this->assertEquals( $result2, $res );
$wgGroupPermissions['autoconfirmed']['move'] = true;
$wgGroupPermissions['user']['move'] = true;
- $res = $this->permissionManager
+ $this->resetServices();
+ $this->overrideUserPermissions( $this->user, $perm );
+ $res = MediaWikiServices::getInstance()->getPermissionManager()
->getPermissionErrors( $action, $this->user, $this->title );
$this->assertEquals( $result2, $res );
$wgGroupPermissions['autoconfirmed']['move'] = false;
$wgGroupPermissions['user']['move'] = true;
- $res = $this->permissionManager
+ $this->resetServices();
+ $this->overrideUserPermissions( $this->user, $perm );
+ $res = MediaWikiServices::getInstance()->getPermissionManager()
->getPermissionErrors( $action, $this->user, $this->title );
$this->assertEquals( $result2, $res );
}
$this->setTitle( NS_SPECIAL );
$this->assertEquals( [ [ 'badaccess-group0' ], [ 'ns-specialprotected' ] ],
- $this->permissionManager
+ MediaWikiServices::getInstance()->getPermissionManager()
->getPermissionErrors( 'bogus', $this->user, $this->title ) );
$this->setTitle( NS_MAIN );
- $this->setUserPerm( 'bogus' );
+ $this->overrideUserPermissions( $this->user, 'bogus' );
$this->assertEquals( [],
- $this->permissionManager
+ MediaWikiServices::getInstance()->getPermissionManager()
->getPermissionErrors( 'bogus', $this->user, $this->title ) );
$this->setTitle( NS_MAIN );
- $this->setUserPerm( '' );
+ $this->overrideUserPermissions( $this->user, '' );
$this->assertEquals( [ [ 'badaccess-group0' ] ],
- $this->permissionManager
+ MediaWikiServices::getInstance()->getPermissionManager()
->getPermissionErrors( 'bogus', $this->user, $this->title ) );
$wgNamespaceProtection[NS_USER] = [ 'bogus' ];
$this->setTitle( NS_USER );
- $this->setUserPerm( '' );
+ $this->overrideUserPermissions( $this->user, '' );
$this->assertEquals( [ [ 'badaccess-group0' ],
[ 'namespaceprotected', 'User', 'bogus' ] ],
- $this->permissionManager
+ MediaWikiServices::getInstance()->getPermissionManager()
->getPermissionErrors( 'bogus', $this->user, $this->title ) );
$this->setTitle( NS_MEDIAWIKI );
- $this->setUserPerm( 'bogus' );
+ $this->overrideUserPermissions( $this->user, 'bogus' );
$this->assertEquals( [ [ 'protectedinterface', 'bogus' ] ],
- $this->permissionManager
+ MediaWikiServices::getInstance()->getPermissionManager()
->getPermissionErrors( 'bogus', $this->user, $this->title ) );
$this->setTitle( NS_MEDIAWIKI );
- $this->setUserPerm( 'bogus' );
+ $this->overrideUserPermissions( $this->user, 'bogus' );
$this->assertEquals( [ [ 'protectedinterface', 'bogus' ] ],
- $this->permissionManager
+ MediaWikiServices::getInstance()->getPermissionManager()
->getPermissionErrors( 'bogus', $this->user, $this->title ) );
$wgNamespaceProtection = null;
- $this->setUserPerm( 'bogus' );
+ $this->overrideUserPermissions( $this->user, 'bogus' );
$this->assertEquals( [],
- $this->permissionManager
+ MediaWikiServices::getInstance()->getPermissionManager()
->getPermissionErrors( 'bogus', $this->user, $this->title ) );
$this->assertEquals( true,
- $this->permissionManager->userCan( 'bogus', $this->user, $this->title ) );
+ MediaWikiServices::getInstance()->getPermissionManager()
+ ->userCan( 'bogus', $this->user, $this->title ) );
- $this->setUserPerm( '' );
+ $this->overrideUserPermissions( $this->user, '' );
$this->assertEquals( [ [ 'badaccess-group0' ] ],
- $this->permissionManager
+ MediaWikiServices::getInstance()->getPermissionManager()
->getPermissionErrors( 'bogus', $this->user, $this->title ) );
$this->assertEquals( false,
- $this->permissionManager->userCan( 'bogus', $this->user, $this->title ) );
+ MediaWikiServices::getInstance()->getPermissionManager()
+ ->userCan( 'bogus', $this->user, $this->title ) );
}
/**
$resultUserJs,
$resultPatrol
) {
- $this->setUserPerm( '' );
- $result = $this->permissionManager
+ $this->overrideUserPermissions( $this->user );
+ $result = MediaWikiServices::getInstance()->getPermissionManager()
->getPermissionErrors( 'bogus', $this->user, $this->title );
$this->assertEquals( $resultNone, $result );
- $this->setUserPerm( 'editmyusercss' );
- $result = $this->permissionManager
+ $this->overrideUserPermissions( $this->user, 'editmyusercss' );
+ $result = MediaWikiServices::getInstance()->getPermissionManager()
->getPermissionErrors( 'bogus', $this->user, $this->title );
$this->assertEquals( $resultMyCss, $result );
- $this->setUserPerm( 'editmyuserjson' );
- $result = $this->permissionManager
+ $this->overrideUserPermissions( $this->user, 'editmyuserjson' );
+ $result = MediaWikiServices::getInstance()->getPermissionManager()
->getPermissionErrors( 'bogus', $this->user, $this->title );
$this->assertEquals( $resultMyJson, $result );
- $this->setUserPerm( 'editmyuserjs' );
- $result = $this->permissionManager
+ $this->overrideUserPermissions( $this->user, 'editmyuserjs' );
+ $result = MediaWikiServices::getInstance()->getPermissionManager()
->getPermissionErrors( 'bogus', $this->user, $this->title );
$this->assertEquals( $resultMyJs, $result );
- $this->setUserPerm( 'editusercss' );
- $result = $this->permissionManager
+ $this->overrideUserPermissions( $this->user, 'editusercss' );
+ $result = MediaWikiServices::getInstance()->getPermissionManager()
->getPermissionErrors( 'bogus', $this->user, $this->title );
$this->assertEquals( $resultUserCss, $result );
- $this->setUserPerm( 'edituserjson' );
- $result = $this->permissionManager
+ $this->overrideUserPermissions( $this->user, 'edituserjson' );
+ $result = MediaWikiServices::getInstance()->getPermissionManager()
->getPermissionErrors( 'bogus', $this->user, $this->title );
$this->assertEquals( $resultUserJson, $result );
- $this->setUserPerm( 'edituserjs' );
- $result = $this->permissionManager
+ $this->overrideUserPermissions( $this->user, 'edituserjs' );
+ $result = MediaWikiServices::getInstance()->getPermissionManager()
->getPermissionErrors( 'bogus', $this->user, $this->title );
$this->assertEquals( $resultUserJs, $result );
- $this->setUserPerm( '' );
- $result = $this->permissionManager
+ $this->overrideUserPermissions( $this->user );
+ $result = MediaWikiServices::getInstance()->getPermissionManager()
->getPermissionErrors( 'patrol', $this->user, $this->title );
$this->assertEquals( reset( $resultPatrol[0] ), reset( $result[0] ) );
- $this->setUserPerm( [ 'edituserjs', 'edituserjson', 'editusercss' ] );
- $result = $this->permissionManager
+ $this->overrideUserPermissions( $this->user, [ 'edituserjs', 'edituserjson', 'editusercss' ] );
+ $result = MediaWikiServices::getInstance()->getPermissionManager()
->getPermissionErrors( 'bogus', $this->user, $this->title );
$this->assertEquals( [ [ 'badaccess-group0' ] ], $result );
}
$this->setTitle( NS_MAIN );
$this->title->mRestrictionsLoaded = true;
- $this->setUserPerm( "edit" );
+ $this->overrideUserPermissions( $this->user, "edit" );
$this->title->mRestrictions = [ "bogus" => [ 'bogus', "sysop", "protect", "" ] ];
$this->assertEquals( [],
- $this->permissionManager->getPermissionErrors( 'edit',
- $this->user, $this->title ) );
+ MediaWikiServices::getInstance()->getPermissionManager()
+ ->getPermissionErrors( 'edit', $this->user, $this->title ) );
$this->assertEquals( true,
- $this->permissionManager->userCan( 'edit', $this->user, $this->title,
- PermissionManager::RIGOR_QUICK ) );
+ MediaWikiServices::getInstance()->getPermissionManager()
+ ->userCan( 'edit', $this->user, $this->title, PermissionManager::RIGOR_QUICK ) );
$this->title->mRestrictions = [ "edit" => [ 'bogus', "sysop", "protect", "" ],
"bogus" => [ 'bogus', "sysop", "protect", "" ] ];
[ 'protectedpagetext', 'bogus', 'bogus' ],
[ 'protectedpagetext', 'editprotected', 'bogus' ],
[ 'protectedpagetext', 'protect', 'bogus' ] ],
- $this->permissionManager->getPermissionErrors( 'bogus',
- $this->user, $this->title ) );
+ MediaWikiServices::getInstance()->getPermissionManager()->getPermissionErrors(
+ 'bogus', $this->user, $this->title ) );
$this->assertEquals( [ [ 'protectedpagetext', 'bogus', 'edit' ],
[ 'protectedpagetext', 'editprotected', 'edit' ],
[ 'protectedpagetext', 'protect', 'edit' ] ],
- $this->permissionManager->getPermissionErrors( 'edit',
- $this->user, $this->title ) );
- $this->setUserPerm( "" );
+ MediaWikiServices::getInstance()->getPermissionManager()->getPermissionErrors(
+ 'edit', $this->user, $this->title ) );
+ $this->overrideUserPermissions( $this->user );
$this->assertEquals( [ [ 'badaccess-group0' ],
[ 'protectedpagetext', 'bogus', 'bogus' ],
[ 'protectedpagetext', 'editprotected', 'bogus' ],
[ 'protectedpagetext', 'protect', 'bogus' ] ],
- $this->permissionManager->getPermissionErrors( 'bogus',
- $this->user, $this->title ) );
+ MediaWikiServices::getInstance()->getPermissionManager()->getPermissionErrors(
+ 'bogus', $this->user, $this->title ) );
$this->assertEquals( [ [ 'badaccess-groups', "*, [[$prefix:Users|Users]]", 2 ],
[ 'protectedpagetext', 'bogus', 'edit' ],
[ 'protectedpagetext', 'editprotected', 'edit' ],
[ 'protectedpagetext', 'protect', 'edit' ] ],
- $this->permissionManager->getPermissionErrors( 'edit',
- $this->user, $this->title ) );
- $this->setUserPerm( [ "edit", "editprotected" ] );
+ MediaWikiServices::getInstance()->getPermissionManager()->getPermissionErrors(
+ 'edit', $this->user, $this->title ) );
+ $this->overrideUserPermissions( $this->user, [ "edit", "editprotected" ] );
$this->assertEquals( [ [ 'badaccess-group0' ],
[ 'protectedpagetext', 'bogus', 'bogus' ],
[ 'protectedpagetext', 'protect', 'bogus' ] ],
- $this->permissionManager->getPermissionErrors( 'bogus',
- $this->user, $this->title ) );
+ MediaWikiServices::getInstance()->getPermissionManager()->getPermissionErrors(
+ 'bogus', $this->user, $this->title ) );
$this->assertEquals( [
[ 'protectedpagetext', 'bogus', 'edit' ],
[ 'protectedpagetext', 'protect', 'edit' ] ],
- $this->permissionManager->getPermissionErrors( 'edit',
- $this->user, $this->title ) );
+ MediaWikiServices::getInstance()->getPermissionManager()->getPermissionErrors(
+ 'edit', $this->user, $this->title ) );
$this->title->mCascadeRestriction = true;
- $this->setUserPerm( "edit" );
+ $this->overrideUserPermissions( $this->user, "edit" );
$this->assertEquals( false,
- $this->permissionManager->userCan( 'bogus', $this->user, $this->title,
- PermissionManager::RIGOR_QUICK ) );
+ MediaWikiServices::getInstance()->getPermissionManager()
+ ->userCan( 'bogus', $this->user, $this->title, PermissionManager::RIGOR_QUICK ) );
$this->assertEquals( false,
- $this->permissionManager->userCan( 'edit', $this->user, $this->title,
- PermissionManager::RIGOR_QUICK ) );
+ MediaWikiServices::getInstance()->getPermissionManager()->userCan(
+ 'edit', $this->user, $this->title, PermissionManager::RIGOR_QUICK ) );
$this->assertEquals( [ [ 'badaccess-group0' ],
[ 'protectedpagetext', 'bogus', 'bogus' ],
[ 'protectedpagetext', 'editprotected', 'bogus' ],
[ 'protectedpagetext', 'protect', 'bogus' ] ],
- $this->permissionManager->getPermissionErrors( 'bogus',
- $this->user, $this->title ) );
+ MediaWikiServices::getInstance()->getPermissionManager()->getPermissionErrors(
+ 'bogus', $this->user, $this->title ) );
$this->assertEquals( [ [ 'protectedpagetext', 'bogus', 'edit' ],
[ 'protectedpagetext', 'editprotected', 'edit' ],
[ 'protectedpagetext', 'protect', 'edit' ] ],
- $this->permissionManager->getPermissionErrors( 'edit',
- $this->user, $this->title ) );
+ MediaWikiServices::getInstance()->getPermissionManager()->getPermissionErrors(
+ 'edit', $this->user, $this->title ) );
- $this->setUserPerm( [ "edit", "editprotected" ] );
+ $this->overrideUserPermissions( $this->user, [ "edit", "editprotected" ] );
$this->assertEquals( false,
- $this->permissionManager->userCan( 'bogus', $this->user, $this->title,
- PermissionManager::RIGOR_QUICK ) );
+ MediaWikiServices::getInstance()->getPermissionManager()->userCan(
+ 'bogus', $this->user, $this->title, PermissionManager::RIGOR_QUICK ) );
$this->assertEquals( false,
- $this->permissionManager->userCan( 'edit', $this->user, $this->title,
- PermissionManager::RIGOR_QUICK ) );
+ MediaWikiServices::getInstance()->getPermissionManager()->userCan(
+ 'edit', $this->user, $this->title, PermissionManager::RIGOR_QUICK ) );
$this->assertEquals( [ [ 'badaccess-group0' ],
[ 'protectedpagetext', 'bogus', 'bogus' ],
[ 'protectedpagetext', 'protect', 'bogus' ],
[ 'protectedpagetext', 'protect', 'bogus' ] ],
- $this->permissionManager->getPermissionErrors( 'bogus',
- $this->user, $this->title ) );
+ MediaWikiServices::getInstance()->getPermissionManager()->getPermissionErrors(
+ 'bogus', $this->user, $this->title ) );
$this->assertEquals( [ [ 'protectedpagetext', 'bogus', 'edit' ],
[ 'protectedpagetext', 'protect', 'edit' ],
[ 'protectedpagetext', 'protect', 'edit' ] ],
- $this->permissionManager->getPermissionErrors( 'edit',
- $this->user, $this->title ) );
+ MediaWikiServices::getInstance()->getPermissionManager()->getPermissionErrors(
+ 'edit', $this->user, $this->title ) );
}
/**
*/
public function testCascadingSourcesRestrictions() {
$this->setTitle( NS_MAIN, "test page" );
- $this->setUserPerm( [ "edit", "bogus" ] );
+ $this->overrideUserPermissions( $this->user, [ "edit", "bogus" ] );
$this->title->mCascadeSources = [
Title::makeTitle( NS_MAIN, "Bogus" ),
];
$this->assertEquals( false,
- $this->permissionManager->userCan( 'bogus', $this->user, $this->title ) );
+ MediaWikiServices::getInstance()->getPermissionManager()->userCan(
+ 'bogus', $this->user, $this->title ) );
$this->assertEquals( [
[ "cascadeprotected", 2, "* [[:Bogus]]\n* [[:UnBogus]]\n", 'bogus' ],
[ "cascadeprotected", 2, "* [[:Bogus]]\n* [[:UnBogus]]\n", 'bogus' ],
[ "cascadeprotected", 2, "* [[:Bogus]]\n* [[:UnBogus]]\n", 'bogus' ] ],
- $this->permissionManager->getPermissionErrors( 'bogus', $this->user, $this->title ) );
+ MediaWikiServices::getInstance()->getPermissionManager()->getPermissionErrors(
+ 'bogus', $this->user, $this->title ) );
$this->assertEquals( true,
- $this->permissionManager->userCan( 'edit', $this->user, $this->title ) );
+ MediaWikiServices::getInstance()->getPermissionManager()->userCan(
+ 'edit', $this->user, $this->title ) );
$this->assertEquals( [],
- $this->permissionManager->getPermissionErrors( 'edit', $this->user, $this->title ) );
+ MediaWikiServices::getInstance()->getPermissionManager()->getPermissionErrors(
+ 'edit', $this->user, $this->title ) );
}
/**
* @covers \MediaWiki\Permissions\PermissionManager::checkActionPermissions
*/
public function testActionPermissions() {
- $this->setUserPerm( [ "createpage" ] );
+ $this->overrideUserPermissions( $this->user, [ "createpage" ] );
$this->setTitle( NS_MAIN, "test page" );
$this->title->mTitleProtection['permission'] = '';
$this->title->mTitleProtection['user'] = $this->user->getId();
$this->title->mCascadeRestriction = false;
$this->assertEquals( [ [ 'titleprotected', 'Useruser', 'test' ] ],
- $this->permissionManager
+ MediaWikiServices::getInstance()->getPermissionManager()
->getPermissionErrors( 'create', $this->user, $this->title ) );
$this->assertEquals( false,
- $this->permissionManager->userCan( 'create', $this->user, $this->title ) );
+ MediaWikiServices::getInstance()->getPermissionManager()->userCan(
+ 'create', $this->user, $this->title ) );
$this->title->mTitleProtection['permission'] = 'editprotected';
- $this->setUserPerm( [ 'createpage', 'protect' ] );
+ $this->overrideUserPermissions( $this->user, [ 'createpage', 'protect' ] );
$this->assertEquals( [ [ 'titleprotected', 'Useruser', 'test' ] ],
- $this->permissionManager
+ MediaWikiServices::getInstance()->getPermissionManager()
->getPermissionErrors( 'create', $this->user, $this->title ) );
$this->assertEquals( false,
- $this->permissionManager->userCan( 'create', $this->user, $this->title ) );
+ MediaWikiServices::getInstance()->getPermissionManager()->userCan(
+ 'create', $this->user, $this->title ) );
- $this->setUserPerm( [ 'createpage', 'editprotected' ] );
+ $this->overrideUserPermissions( $this->user, [ 'createpage', 'editprotected' ] );
$this->assertEquals( [],
- $this->permissionManager
+ MediaWikiServices::getInstance()->getPermissionManager()
->getPermissionErrors( 'create', $this->user, $this->title ) );
$this->assertEquals( true,
- $this->permissionManager->userCan( 'create', $this->user, $this->title ) );
+ MediaWikiServices::getInstance()->getPermissionManager()->userCan(
+ 'create', $this->user, $this->title ) );
- $this->setUserPerm( [ 'createpage' ] );
+ $this->overrideUserPermissions( $this->user, [ 'createpage' ] );
$this->assertEquals( [ [ 'titleprotected', 'Useruser', 'test' ] ],
- $this->permissionManager
+ MediaWikiServices::getInstance()->getPermissionManager()
->getPermissionErrors( 'create', $this->user, $this->title ) );
$this->assertEquals( false,
- $this->permissionManager->userCan( 'create', $this->user, $this->title ) );
+ MediaWikiServices::getInstance()->getPermissionManager()->userCan(
+ 'create', $this->user, $this->title ) );
$this->setTitle( NS_MEDIA, "test page" );
- $this->setUserPerm( [ "move" ] );
+ $this->overrideUserPermissions( $this->user, [ "move" ] );
$this->assertEquals( false,
- $this->permissionManager->userCan( 'move', $this->user, $this->title ) );
+ MediaWikiServices::getInstance()->getPermissionManager()->userCan(
+ 'move', $this->user, $this->title ) );
$this->assertEquals( [ [ 'immobile-source-namespace', 'Media' ] ],
- $this->permissionManager
+ MediaWikiServices::getInstance()->getPermissionManager()
->getPermissionErrors( 'move', $this->user, $this->title ) );
$this->setTitle( NS_HELP, "test page" );
$this->assertEquals( [],
- $this->permissionManager
+ MediaWikiServices::getInstance()->getPermissionManager()
->getPermissionErrors( 'move', $this->user, $this->title ) );
$this->assertEquals( true,
- $this->permissionManager->userCan( 'move', $this->user, $this->title ) );
+ MediaWikiServices::getInstance()->getPermissionManager()->userCan(
+ 'move', $this->user, $this->title ) );
$this->title->mInterwiki = "no";
$this->assertEquals( [ [ 'immobile-source-page' ] ],
- $this->permissionManager
+ MediaWikiServices::getInstance()->getPermissionManager()
->getPermissionErrors( 'move', $this->user, $this->title ) );
$this->assertEquals( false,
- $this->permissionManager->userCan( 'move', $this->user, $this->title ) );
+ MediaWikiServices::getInstance()->getPermissionManager()->userCan(
+ 'move', $this->user, $this->title ) );
$this->setTitle( NS_MEDIA, "test page" );
$this->assertEquals( false,
- $this->permissionManager->userCan( 'move-target', $this->user, $this->title ) );
+ MediaWikiServices::getInstance()->getPermissionManager()->userCan(
+ 'move-target', $this->user, $this->title ) );
$this->assertEquals( [ [ 'immobile-target-namespace', 'Media' ] ],
- $this->permissionManager
+ MediaWikiServices::getInstance()->getPermissionManager()
->getPermissionErrors( 'move-target', $this->user, $this->title ) );
$this->setTitle( NS_HELP, "test page" );
$this->assertEquals( [],
- $this->permissionManager
+ MediaWikiServices::getInstance()->getPermissionManager()
->getPermissionErrors( 'move-target', $this->user, $this->title ) );
$this->assertEquals( true,
- $this->permissionManager->userCan( 'move-target', $this->user, $this->title ) );
+ MediaWikiServices::getInstance()->getPermissionManager()->userCan(
+ 'move-target', $this->user, $this->title ) );
$this->title->mInterwiki = "no";
$this->assertEquals( [ [ 'immobile-target-page' ] ],
- $this->permissionManager
+ MediaWikiServices::getInstance()->getPermissionManager()
->getPermissionErrors( 'move-target', $this->user, $this->title ) );
$this->assertEquals( false,
- $this->permissionManager->userCan( 'move-target', $this->user, $this->title ) );
+ MediaWikiServices::getInstance()->getPermissionManager()->userCan(
+ 'move-target', $this->user, $this->title ) );
}
/**
'wgBlockDisablesLogin' => false,
] );
- $this->overrideMwServices();
- $this->permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
-
- $this->setUserPerm( [
+ $this->overrideUserPermissions( $this->user, [
'createpage',
'edit',
'move',
# $wgEmailConfirmToEdit only applies to 'edit' action
$this->assertEquals( [],
- $this->permissionManager->getPermissionErrors( 'move-target',
- $this->user, $this->title ) );
+ MediaWikiServices::getInstance()->getPermissionManager()->getPermissionErrors(
+ 'move-target', $this->user, $this->title ) );
$this->assertContains( [ 'confirmedittext' ],
- $this->permissionManager
+ MediaWikiServices::getInstance()->getPermissionManager()
->getPermissionErrors( 'edit', $this->user, $this->title ) );
$this->setMwGlobals( 'wgEmailConfirmToEdit', false );
- $this->overrideMwServices();
- $this->permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
+ $this->resetServices();
+ $this->overrideUserPermissions( $this->user, [
+ 'createpage',
+ 'edit',
+ 'move',
+ 'rollback',
+ 'patrol',
+ 'upload',
+ 'purge'
+ ] );
$this->assertNotContains( [ 'confirmedittext' ],
- $this->permissionManager
+ MediaWikiServices::getInstance()->getPermissionManager()
->getPermissionErrors( 'edit', $this->user, $this->title ) );
# $wgEmailConfirmToEdit && !$user->isEmailConfirmed() && $action != 'createaccount'
$this->assertEquals( [],
- $this->permissionManager->getPermissionErrors( 'move-target',
- $this->user, $this->title ) );
+ MediaWikiServices::getInstance()->getPermissionManager()->getPermissionErrors(
+ 'move-target', $this->user, $this->title ) );
global $wgLang;
$prev = time();
'[[User:Useruser|Useruser]]', 'no reason given', '127.0.0.1',
'Useruser', null, 'infinite', '127.0.8.1',
$wgLang->timeanddate( wfTimestamp( TS_MW, $prev ), true ) ] ],
- $this->permissionManager->getPermissionErrors( 'move-target',
- $this->user, $this->title ) );
+ MediaWikiServices::getInstance()->getPermissionManager()->getPermissionErrors(
+ 'move-target', $this->user, $this->title ) );
- $this->assertEquals( false, $this->permissionManager
+ $this->assertEquals( false, MediaWikiServices::getInstance()->getPermissionManager()
->userCan( 'move-target', $this->user, $this->title ) );
// quickUserCan should ignore user blocks
- $this->assertEquals( true, $this->permissionManager
+ $this->assertEquals( true, MediaWikiServices::getInstance()->getPermissionManager()
->userCan( 'move-target', $this->user, $this->title,
PermissionManager::RIGOR_QUICK ) );
'[[User:Useruser|Useruser]]', 'no reason given', '127.0.0.1',
'Useruser', null, '23:00, 31 December 1969', '127.0.8.1',
$wgLang->timeanddate( wfTimestamp( TS_MW, $now ), true ) ] ],
- $this->permissionManager
+ MediaWikiServices::getInstance()->getPermissionManager()
->getPermissionErrors( 'move-target', $this->user, $this->title ) );
# $action != 'read' && $action != 'createaccount' && $user->isBlockedFrom( $this )
# $user->blockedFor() == ''
$wgLang->timeanddate( wfTimestamp( TS_MW, $now ), true ) ] ];
$this->assertEquals( $errors,
- $this->permissionManager
+ MediaWikiServices::getInstance()->getPermissionManager()
->getPermissionErrors( 'edit', $this->user, $this->title ) );
$this->assertEquals( $errors,
- $this->permissionManager
+ MediaWikiServices::getInstance()->getPermissionManager()
->getPermissionErrors( 'move-target', $this->user, $this->title ) );
$this->assertEquals( $errors,
- $this->permissionManager
+ MediaWikiServices::getInstance()->getPermissionManager()
->getPermissionErrors( 'rollback', $this->user, $this->title ) );
$this->assertEquals( $errors,
- $this->permissionManager
+ MediaWikiServices::getInstance()->getPermissionManager()
->getPermissionErrors( 'patrol', $this->user, $this->title ) );
$this->assertEquals( $errors,
- $this->permissionManager
+ MediaWikiServices::getInstance()->getPermissionManager()
->getPermissionErrors( 'upload', $this->user, $this->title ) );
$this->assertEquals( [],
- $this->permissionManager
+ MediaWikiServices::getInstance()->getPermissionManager()
->getPermissionErrors( 'purge', $this->user, $this->title ) );
// partial block message test
] );
$this->assertEquals( [],
- $this->permissionManager
+ MediaWikiServices::getInstance()->getPermissionManager()
->getPermissionErrors( 'edit', $this->user, $this->title ) );
$this->assertEquals( [],
- $this->permissionManager
+ MediaWikiServices::getInstance()->getPermissionManager()
->getPermissionErrors( 'move-target', $this->user, $this->title ) );
$this->assertEquals( [],
- $this->permissionManager
+ MediaWikiServices::getInstance()->getPermissionManager()
->getPermissionErrors( 'rollback', $this->user, $this->title ) );
$this->assertEquals( [],
- $this->permissionManager
+ MediaWikiServices::getInstance()->getPermissionManager()
->getPermissionErrors( 'patrol', $this->user, $this->title ) );
$this->assertEquals( [],
- $this->permissionManager
+ MediaWikiServices::getInstance()->getPermissionManager()
->getPermissionErrors( 'upload', $this->user, $this->title ) );
$this->assertEquals( [],
- $this->permissionManager
+ MediaWikiServices::getInstance()->getPermissionManager()
->getPermissionErrors( 'purge', $this->user, $this->title ) );
$this->user->mBlock->setRestrictions( [
$wgLang->timeanddate( wfTimestamp( TS_MW, $now ), true ) ] ];
$this->assertEquals( $errors,
- $this->permissionManager
+ MediaWikiServices::getInstance()->getPermissionManager()
->getPermissionErrors( 'edit', $this->user, $this->title ) );
$this->assertEquals( $errors,
- $this->permissionManager
+ MediaWikiServices::getInstance()->getPermissionManager()
->getPermissionErrors( 'move-target', $this->user, $this->title ) );
$this->assertEquals( $errors,
- $this->permissionManager
+ MediaWikiServices::getInstance()->getPermissionManager()
->getPermissionErrors( 'rollback', $this->user, $this->title ) );
$this->assertEquals( $errors,
- $this->permissionManager
+ MediaWikiServices::getInstance()->getPermissionManager()
->getPermissionErrors( 'patrol', $this->user, $this->title ) );
$this->assertEquals( [],
- $this->permissionManager
+ MediaWikiServices::getInstance()->getPermissionManager()
->getPermissionErrors( 'upload', $this->user, $this->title ) );
$this->assertEquals( [],
- $this->permissionManager
+ MediaWikiServices::getInstance()->getPermissionManager()
->getPermissionErrors( 'purge', $this->user, $this->title ) );
// Test no block.
$this->user->mBlock = null;
$this->assertEquals( [],
- $this->permissionManager
+ MediaWikiServices::getInstance()->getPermissionManager()
->getPermissionErrors( 'edit', $this->user, $this->title ) );
}
$wgLang->timeanddate( wfTimestamp( TS_MW, $now ), true ) ] ];
$this->assertEquals( $errors,
- $this->permissionManager
+ MediaWikiServices::getInstance()->getPermissionManager()
->getPermissionErrors( 'tester', $this->user, $this->title ) );
}
//$this->assertSame( '', $user->blockedBy(), 'sanity check' );
//$this->assertSame( '', $user->blockedFor(), 'sanity check' );
//$this->assertFalse( (bool)$user->isHidden(), 'sanity check' );
- $this->assertFalse( $this->permissionManager
+ $this->assertFalse( MediaWikiServices::getInstance()->getPermissionManager()
->isBlockedFrom( $user, $ut ), 'sanity check' );
// Block the user
//$this->assertSame( $blocker->getName(), $user->blockedBy() );
//$this->assertSame( 'Because', $user->blockedFor() );
//$this->assertTrue( (bool)$user->isHidden() );
- $this->assertTrue( $this->permissionManager->isBlockedFrom( $user, $ut ) );
+ $this->assertTrue( MediaWikiServices::getInstance()->getPermissionManager()
+ ->isBlockedFrom( $user, $ut ) );
// Unblock
$block->delete();
//$this->assertSame( '', $user->blockedBy() );
//$this->assertSame( '', $user->blockedFor() );
//$this->assertFalse( (bool)$user->isHidden() );
- $this->assertFalse( $this->permissionManager->isBlockedFrom( $user, $ut ) );
+ $this->assertFalse( MediaWikiServices::getInstance()->getPermissionManager()
+ ->isBlockedFrom( $user, $ut ) );
}
/**
$block->insert();
try {
- $this->assertSame( $expect, $this->permissionManager->isBlockedFrom( $user, $title ) );
+ $this->assertSame( $expect, MediaWikiServices::getInstance()->getPermissionManager()
+ ->isBlockedFrom( $user, $title ) );
} finally {
$block->delete();
}
];
}
+ /**
+ * @covers \MediaWiki\Permissions\PermissionManager::getUserPermissions
+ */
+ public function testGetUserPermissions() {
+ $user = $this->getTestUser( [ 'unittesters' ] )->getUser();
+ $rights = MediaWikiServices::getInstance()->getPermissionManager()
+ ->getUserPermissions( $user );
+ $this->assertContains( 'runtest', $rights );
+ $this->assertNotContains( 'writetest', $rights );
+ $this->assertNotContains( 'modifytest', $rights );
+ $this->assertNotContains( 'nukeworld', $rights );
+ }
+
+ /**
+ * @covers \MediaWiki\Permissions\PermissionManager::getUserPermissions
+ */
+ public function testGetUserPermissionsHooks() {
+ $user = $this->getTestUser( [ 'unittesters', 'testwriters' ] )->getUser();
+ $userWrapper = TestingAccessWrapper::newFromObject( $user );
+
+ $rights = MediaWikiServices::getInstance()->getPermissionManager()
+ ->getUserPermissions( $user );
+ $this->assertContains( 'test', $rights, 'sanity check' );
+ $this->assertContains( 'runtest', $rights, 'sanity check' );
+ $this->assertContains( 'writetest', $rights, 'sanity check' );
+ $this->assertNotContains( 'nukeworld', $rights, 'sanity check' );
+
+ // Add a hook manipluating the rights
+ $this->mergeMwGlobalArrayValue( 'wgHooks', [ 'UserGetRights' => [ function ( $user, &$rights ) {
+ $rights[] = 'nukeworld';
+ $rights = array_diff( $rights, [ 'writetest' ] );
+ } ] ] );
+
+ $this->resetServices();
+ $rights = MediaWikiServices::getInstance()->getPermissionManager()
+ ->getUserPermissions( $user );
+ $this->assertContains( 'test', $rights );
+ $this->assertContains( 'runtest', $rights );
+ $this->assertNotContains( 'writetest', $rights );
+ $this->assertContains( 'nukeworld', $rights );
+
+ // Add a Session that limits rights
+ $mock = $this->getMockBuilder( stdClass::class )
+ ->setMethods( [ 'getAllowedUserRights', 'deregisterSession', 'getSessionId' ] )
+ ->getMock();
+ $mock->method( 'getAllowedUserRights' )->willReturn( [ 'test', 'writetest' ] );
+ $mock->method( 'getSessionId' )->willReturn(
+ new SessionId( str_repeat( 'X', 32 ) )
+ );
+ $session = TestUtils::getDummySession( $mock );
+ $mockRequest = $this->getMockBuilder( FauxRequest::class )
+ ->setMethods( [ 'getSession' ] )
+ ->getMock();
+ $mockRequest->method( 'getSession' )->willReturn( $session );
+ $userWrapper->mRequest = $mockRequest;
+
+ $this->resetServices();
+ $rights = MediaWikiServices::getInstance()->getPermissionManager()
+ ->getUserPermissions( $user );
+ $this->assertContains( 'test', $rights );
+ $this->assertNotContains( 'runtest', $rights );
+ $this->assertNotContains( 'writetest', $rights );
+ $this->assertNotContains( 'nukeworld', $rights );
+ }
+
+ /**
+ * @covers \MediaWiki\Permissions\PermissionManager::getGroupPermissions
+ */
+ public function testGroupPermissions() {
+ $rights = MediaWikiServices::getInstance()->getPermissionManager()
+ ->getGroupPermissions( [ 'unittesters' ] );
+ $this->assertContains( 'runtest', $rights );
+ $this->assertNotContains( 'writetest', $rights );
+ $this->assertNotContains( 'modifytest', $rights );
+ $this->assertNotContains( 'nukeworld', $rights );
+
+ $rights = MediaWikiServices::getInstance()->getPermissionManager()
+ ->getGroupPermissions( [ 'unittesters', 'testwriters' ] );
+ $this->assertContains( 'runtest', $rights );
+ $this->assertContains( 'writetest', $rights );
+ $this->assertContains( 'modifytest', $rights );
+ $this->assertNotContains( 'nukeworld', $rights );
+ }
+
+ /**
+ * @covers \MediaWiki\Permissions\PermissionManager::getGroupPermissions
+ */
+ public function testRevokePermissions() {
+ $rights = MediaWikiServices::getInstance()->getPermissionManager()
+ ->getGroupPermissions( [ 'unittesters', 'formertesters' ] );
+ $this->assertNotContains( 'runtest', $rights );
+ $this->assertNotContains( 'writetest', $rights );
+ $this->assertNotContains( 'modifytest', $rights );
+ $this->assertNotContains( 'nukeworld', $rights );
+ }
+
+ /**
+ * @dataProvider provideGetGroupsWithPermission
+ * @covers \MediaWiki\Permissions\PermissionManager::getGroupsWithPermission
+ */
+ public function testGetGroupsWithPermission( $expected, $right ) {
+ $result = MediaWikiServices::getInstance()->getPermissionManager()
+ ->getGroupsWithPermission( $right );
+ sort( $result );
+ sort( $expected );
+
+ $this->assertEquals( $expected, $result, "Groups with permission $right" );
+ }
+
+ public static function provideGetGroupsWithPermission() {
+ return [
+ [
+ [ 'unittesters', 'testwriters' ],
+ 'test'
+ ],
+ [
+ [ 'unittesters' ],
+ 'runtest'
+ ],
+ [
+ [ 'testwriters' ],
+ 'writetest'
+ ],
+ [
+ [ 'testwriters' ],
+ 'modifytest'
+ ],
+ ];
+ }
+
+ /**
+ * @covers \MediaWiki\Permissions\PermissionManager::userHasRight
+ */
+ public function testUserHasRight() {
+ $result = MediaWikiServices::getInstance()->getPermissionManager()->userHasRight(
+ $this->getTestUser( 'unittesters' )->getUser(),
+ 'test'
+ );
+ $this->assertTrue( $result );
+
+ $result = MediaWikiServices::getInstance()->getPermissionManager()->userHasRight(
+ $this->getTestUser( 'formertesters' )->getUser(),
+ 'runtest'
+ );
+ $this->assertFalse( $result );
+
+ $result = MediaWikiServices::getInstance()->getPermissionManager()->userHasRight(
+ $this->getTestUser( 'formertesters' )->getUser(),
+ ''
+ );
+ $this->assertTrue( $result );
+ }
+
+ /**
+ * @covers \MediaWiki\Permissions\PermissionManager::groupHasPermission
+ */
+ public function testGroupHasPermission() {
+ $result = MediaWikiServices::getInstance()->getPermissionManager()->groupHasPermission(
+ 'unittesters',
+ 'test'
+ );
+ $this->assertTrue( $result );
+
+ $result = MediaWikiServices::getInstance()->getPermissionManager()->groupHasPermission(
+ 'formertesters',
+ 'runtest'
+ );
+ $this->assertFalse( $result );
+ }
+
+ /**
+ * @covers \MediaWiki\Permissions\PermissionManager::isEveryoneAllowed
+ */
+ public function testIsEveryoneAllowed() {
+ $result = MediaWikiServices::getInstance()->getPermissionManager()
+ ->isEveryoneAllowed( 'editmyoptions' );
+ $this->assertTrue( $result );
+
+ $result = MediaWikiServices::getInstance()->getPermissionManager()
+ ->isEveryoneAllowed( 'test' );
+ $this->assertFalse( $result );
+ }
+
}
+++ /dev/null
-<?php
-
-namespace MediaWiki\Tests\Rest;
-
-use MediaWikiTestCase;
-use MediaWiki\Rest\HeaderContainer;
-
-/**
- * @covers \MediaWiki\Rest\HeaderContainer
- */
-class HeaderContainerTest extends MediaWikiTestCase {
- public static function provideSetHeader() {
- return [
- 'simple' => [
- [
- [ 'Test', 'foo' ]
- ],
- [ 'Test' => [ 'foo' ] ],
- [ 'Test' => 'foo' ]
- ],
- 'replace' => [
- [
- [ 'Test', 'foo' ],
- [ 'Test', 'bar' ],
- ],
- [ 'Test' => [ 'bar' ] ],
- [ 'Test' => 'bar' ],
- ],
- 'array value' => [
- [
- [ 'Test', [ '1', '2' ] ],
- [ 'Test', [ '3', '4' ] ],
- ],
- [ 'Test' => [ '3', '4' ] ],
- [ 'Test' => '3, 4' ]
- ],
- 'preserve most recent case' => [
- [
- [ 'test', 'foo' ],
- [ 'tesT', 'bar' ],
- ],
- [ 'tesT' => [ 'bar' ] ],
- [ 'tesT' => 'bar' ]
- ],
- 'empty' => [ [], [], [] ],
- ];
- }
-
- /** @dataProvider provideSetHeader */
- public function testSetHeader( $setOps, $headers, $lines ) {
- $hc = new HeaderContainer;
- foreach ( $setOps as list( $name, $value ) ) {
- $hc->setHeader( $name, $value );
- }
- $this->assertSame( $headers, $hc->getHeaders() );
- $this->assertSame( $lines, $hc->getHeaderLines() );
- }
-
- public static function provideAddHeader() {
- return [
- 'simple' => [
- [
- [ 'Test', 'foo' ]
- ],
- [ 'Test' => [ 'foo' ] ],
- [ 'Test' => 'foo' ]
- ],
- 'add' => [
- [
- [ 'Test', 'foo' ],
- [ 'Test', 'bar' ],
- ],
- [ 'Test' => [ 'foo', 'bar' ] ],
- [ 'Test' => 'foo, bar' ],
- ],
- 'array value' => [
- [
- [ 'Test', [ '1', '2' ] ],
- [ 'Test', [ '3', '4' ] ],
- ],
- [ 'Test' => [ '1', '2', '3', '4' ] ],
- [ 'Test' => '1, 2, 3, 4' ]
- ],
- 'preserve original case' => [
- [
- [ 'Test', 'foo' ],
- [ 'tesT', 'bar' ],
- ],
- [ 'Test' => [ 'foo', 'bar' ] ],
- [ 'Test' => 'foo, bar' ]
- ],
- ];
- }
-
- /** @dataProvider provideAddHeader */
- public function testAddHeader( $addOps, $headers, $lines ) {
- $hc = new HeaderContainer;
- foreach ( $addOps as list( $name, $value ) ) {
- $hc->addHeader( $name, $value );
- }
- $this->assertSame( $headers, $hc->getHeaders() );
- $this->assertSame( $lines, $hc->getHeaderLines() );
- }
-
- public static function provideRemoveHeader() {
- return [
- 'simple' => [
- [ [ 'Test', 'foo' ] ],
- [ 'Test' ],
- [],
- []
- ],
- 'case mismatch' => [
- [ [ 'Test', 'foo' ] ],
- [ 'tesT' ],
- [],
- []
- ],
- 'remove nonexistent' => [
- [ [ 'A', '1' ] ],
- [ 'B' ],
- [ 'A' => [ '1' ] ],
- [ 'A' => '1' ]
- ],
- ];
- }
-
- /** @dataProvider provideRemoveHeader */
- public function testRemoveHeader( $addOps, $removeOps, $headers, $lines ) {
- $hc = new HeaderContainer;
- foreach ( $addOps as list( $name, $value ) ) {
- $hc->addHeader( $name, $value );
- }
- foreach ( $removeOps as $name ) {
- $hc->removeHeader( $name );
- }
- $this->assertSame( $headers, $hc->getHeaders() );
- $this->assertSame( $lines, $hc->getHeaderLines() );
- }
-
- public function testHasHeader() {
- $hc = new HeaderContainer;
- $hc->addHeader( 'A', '1' );
- $hc->addHeader( 'B', '2' );
- $hc->addHeader( 'C', '3' );
- $hc->removeHeader( 'B' );
- $hc->removeHeader( 'c' );
- $this->assertTrue( $hc->hasHeader( 'A' ) );
- $this->assertTrue( $hc->hasHeader( 'a' ) );
- $this->assertFalse( $hc->hasHeader( 'B' ) );
- $this->assertFalse( $hc->hasHeader( 'c' ) );
- $this->assertFalse( $hc->hasHeader( 'C' ) );
- }
-
- public function testGetRawHeaderLines() {
- $hc = new HeaderContainer;
- $hc->addHeader( 'A', '1' );
- $hc->addHeader( 'a', '2' );
- $hc->addHeader( 'b', '3' );
- $hc->addHeader( 'Set-Cookie', 'x' );
- $hc->addHeader( 'SET-cookie', 'y' );
- $this->assertSame(
- [
- 'A: 1, 2',
- 'b: 3',
- 'Set-Cookie: x',
- 'Set-Cookie: y',
- ],
- $hc->getRawHeaderLines()
- );
- }
-}
+++ /dev/null
-<?php
-
-namespace MediaWiki\Tests\Rest\PathTemplateMatcher;
-
-use MediaWiki\Rest\PathTemplateMatcher\PathConflict;
-use MediaWiki\Rest\PathTemplateMatcher\PathMatcher;
-use MediaWikiTestCase;
-
-/**
- * @covers \MediaWiki\Rest\PathTemplateMatcher\PathMatcher
- * @covers \MediaWiki\Rest\PathTemplateMatcher\PathConflict
- */
-class PathMatcherTest extends MediaWikiTestCase {
- private static $normalRoutes = [
- '/a/b',
- '/b/{x}',
- '/c/{x}/d',
- '/c/{x}/e',
- '/c/{x}/{y}/d',
- ];
-
- public static function provideConflictingRoutes() {
- return [
- [ '/a/b', 0, '/a/b' ],
- [ '/a/{x}', 0, '/a/b' ],
- [ '/{x}/c', 1, '/b/{x}' ],
- [ '/b/a', 1, '/b/{x}' ],
- [ '/b/{x}', 1, '/b/{x}' ],
- [ '/{x}/{y}/d', 2, '/c/{x}/d' ],
- ];
- }
-
- public static function provideMatch() {
- return [
- [ '', false ],
- [ '/a/b', [ 'params' => [], 'userData' => 0 ] ],
- [ '/b', false ],
- [ '/b/1', [ 'params' => [ 'x' => '1' ], 'userData' => 1 ] ],
- [ '/c/1/d', [ 'params' => [ 'x' => '1' ], 'userData' => 2 ] ],
- [ '/c/1/e', [ 'params' => [ 'x' => '1' ], 'userData' => 3 ] ],
- [ '/c/000/e', [ 'params' => [ 'x' => '000' ], 'userData' => 3 ] ],
- [ '/c/1/f', false ],
- [ '/c//e', [ 'params' => [ 'x' => '' ], 'userData' => 3 ] ],
- [ '/c///e', false ],
- ];
- }
-
- public function createNormalRouter() {
- $pm = new PathMatcher;
- foreach ( self::$normalRoutes as $i => $route ) {
- $pm->add( $route, $i );
- }
- return $pm;
- }
-
- /** @dataProvider provideConflictingRoutes */
- public function testAddConflict( $attempt, $expectedUserData, $expectedTemplate ) {
- $pm = $this->createNormalRouter();
- $actualTemplate = null;
- $actualUserData = null;
- try {
- $pm->add( $attempt, 'conflict' );
- } catch ( PathConflict $pc ) {
- $actualTemplate = $pc->existingTemplate;
- $actualUserData = $pc->existingUserData;
- }
- $this->assertSame( $expectedUserData, $actualUserData );
- $this->assertSame( $expectedTemplate, $actualTemplate );
- }
-
- /** @dataProvider provideMatch */
- public function testMatch( $path, $expectedResult ) {
- $pm = $this->createNormalRouter();
- $result = $pm->match( $path );
- $this->assertSame( $expectedResult, $result );
- }
-}
+++ /dev/null
-<?php
-
-namespace MediaWiki\Tests\Rest;
-
-use MediaWiki\Rest\StringStream;
-use MediaWikiTestCase;
-
-/** @covers \MediaWiki\Rest\StringStream */
-class StringStreamTest extends MediaWikiTestCase {
- public static function provideSeekGetContents() {
- return [
- [ 'abcde', 0, SEEK_SET, 'abcde' ],
- [ 'abcde', 1, SEEK_SET, 'bcde' ],
- [ 'abcde', 5, SEEK_SET, '' ],
- [ 'abcde', 1, SEEK_CUR, 'cde' ],
- [ 'abcde', 0, SEEK_END, '' ],
- ];
- }
-
- /** @dataProvider provideSeekGetContents */
- public function testCopyToStream( $input, $offset, $whence, $expected ) {
- $ss = new StringStream;
- $ss->write( $input );
- $ss->seek( 1 );
- $ss->seek( $offset, $whence );
- $destStream = fopen( 'php://memory', 'w+' );
- $ss->copyToStream( $destStream );
- fseek( $destStream, 0 );
- $result = stream_get_contents( $destStream );
- $this->assertSame( $expected, $result );
- }
-
- public function testGetSize() {
- $ss = new StringStream;
- $this->assertSame( 0, $ss->getSize() );
- $ss->write( "hello" );
- $this->assertSame( 5, $ss->getSize() );
- $ss->rewind();
- $this->assertSame( 5, $ss->getSize() );
- }
-
- public function testTell() {
- $ss = new StringStream;
- $this->assertSame( $ss->tell(), 0 );
- $ss->write( "abc" );
- $this->assertSame( $ss->tell(), 3 );
- $ss->seek( 0 );
- $ss->read( 1 );
- $this->assertSame( $ss->tell(), 1 );
- }
-
- public function testEof() {
- $ss = new StringStream( 'abc' );
- $this->assertFalse( $ss->eof() );
- $ss->read( 1 );
- $this->assertFalse( $ss->eof() );
- $ss->read( 1 );
- $this->assertFalse( $ss->eof() );
- $ss->read( 1 );
- $this->assertTrue( $ss->eof() );
- $ss->rewind();
- $this->assertFalse( $ss->eof() );
- }
-
- public function testIsSeekable() {
- $ss = new StringStream;
- $this->assertTrue( $ss->isSeekable() );
- }
-
- public function testIsReadable() {
- $ss = new StringStream;
- $this->assertTrue( $ss->isReadable() );
- }
-
- public function testIsWritable() {
- $ss = new StringStream;
- $this->assertTrue( $ss->isWritable() );
- }
-
- public function testSeekWrite() {
- $ss = new StringStream;
- $this->assertSame( '', (string)$ss );
- $ss->write( 'a' );
- $this->assertSame( 'a', (string)$ss );
- $ss->write( 'b' );
- $this->assertSame( 'ab', (string)$ss );
- $ss->seek( 1 );
- $ss->write( 'c' );
- $this->assertSame( 'ac', (string)$ss );
- }
-
- /** @dataProvider provideSeekGetContents */
- public function testSeekGetContents( $input, $offset, $whence, $expected ) {
- $ss = new StringStream( $input );
- $ss->seek( 1 );
- $ss->seek( $offset, $whence );
- $this->assertSame( $expected, $ss->getContents() );
- }
-
- public static function provideSeekRead() {
- return [
- [ 'abcde', 0, SEEK_SET, 1, 'a' ],
- [ 'abcde', 0, SEEK_SET, 2, 'ab' ],
- [ 'abcde', 4, SEEK_SET, 2, 'e' ],
- [ 'abcde', 5, SEEK_SET, 1, '' ],
- [ 'abcde', 1, SEEK_CUR, 1, 'c' ],
- [ 'abcde', 0, SEEK_END, 1, '' ],
- [ 'abcde', -1, SEEK_END, 1, 'e' ],
- ];
- }
-
- /** @dataProvider provideSeekRead */
- public function testSeekRead( $input, $offset, $whence, $length, $expected ) {
- $ss = new StringStream( $input );
- $ss->seek( 1 );
- $ss->seek( $offset, $whence );
- $this->assertSame( $expected, $ss->read( $length ) );
- }
-
- /** @expectedException \InvalidArgumentException */
- public function testReadBeyondEnd() {
- $ss = new StringStream( 'abc' );
- $ss->seek( 1, SEEK_END );
- }
-
- /** @expectedException \InvalidArgumentException */
- public function testReadBeforeStart() {
- $ss = new StringStream( 'abc' );
- $ss->seek( -1 );
- }
-}
+++ /dev/null
-<?php
-
-namespace MediaWiki\Tests\Revision;
-
-use MediaWiki\Revision\FallbackSlotRoleHandler;
-use MediaWikiTestCase;
-use Title;
-
-/**
- * @covers \MediaWiki\Revision\FallbackSlotRoleHandler
- */
-class FallbackSlotRoleHandlerTest extends MediaWikiTestCase {
-
- /**
- * @return Title
- */
- private function makeBlankTitleObject() {
- return $this->createMock( Title::class );
- }
-
- /**
- * @covers \MediaWiki\Revision\FallbackSlotRoleHandler::__construct
- * @covers \MediaWiki\Revision\FallbackSlotRoleHandler::getRole()
- * @covers \MediaWiki\Revision\FallbackSlotRoleHandler::getNameMessageKey()
- * @covers \MediaWiki\Revision\FallbackSlotRoleHandler::getDefaultModel()
- * @covers \MediaWiki\Revision\FallbackSlotRoleHandler::getOutputLayoutHints()
- */
- public function testConstruction() {
- $handler = new FallbackSlotRoleHandler( 'foo' );
- $this->assertSame( 'foo', $handler->getRole() );
- $this->assertSame( 'slot-name-foo', $handler->getNameMessageKey() );
-
- $title = $this->makeBlankTitleObject();
- $this->assertSame( CONTENT_MODEL_TEXT, $handler->getDefaultModel( $title ) );
-
- $hints = $handler->getOutputLayoutHints();
- $this->assertArrayHasKey( 'display', $hints );
- $this->assertArrayHasKey( 'region', $hints );
- $this->assertArrayHasKey( 'placement', $hints );
- }
-
- /**
- * @covers \MediaWiki\Revision\FallbackSlotRoleHandler::isAllowedModel()
- */
- public function testIsAllowedModel() {
- $handler = new FallbackSlotRoleHandler( 'foo', 'FooModel' );
-
- // For the fallback handler, no models are allowed
- $title = $this->makeBlankTitleObject();
- $this->assertFalse( $handler->isAllowedModel( 'FooModel', $title ) );
- $this->assertFalse( $handler->isAllowedModel( 'QuaxModel', $title ) );
- }
-
- /**
- * @covers \MediaWiki\Revision\SlotRoleHandler::isAllowedModel()
- */
- public function testIsAllowedOn() {
- $handler = new FallbackSlotRoleHandler( 'foo', 'FooModel' );
-
- $title = $this->makeBlankTitleObject();
- $this->assertFalse( $handler->isAllowedOn( $title ) );
- }
-
- /**
- * @covers \MediaWiki\Revision\FallbackSlotRoleHandler::supportsArticleCount()
- */
- public function testSupportsArticleCount() {
- $handler = new FallbackSlotRoleHandler( 'foo', 'FooModel' );
-
- $this->assertFalse( $handler->supportsArticleCount() );
- }
-
-}
use Content;
use Language;
use LogicException;
-use MediaWiki\Permissions\PermissionManager;
use MediaWiki\Revision\MutableRevisionRecord;
use MediaWiki\Revision\MainSlotRoleHandler;
use MediaWiki\Revision\RevisionRecord;
use ParserOutput;
use PHPUnit\Framework\MockObject\MockObject;
use Title;
-use User;
use Wikimedia\Rdbms\IDatabase;
use Wikimedia\Rdbms\ILoadBalancer;
use WikitextContent;
*/
class RevisionRendererTest extends MediaWikiTestCase {
- /** @var PermissionManager|\PHPUnit_Framework_MockObject_MockObject $permissionManagerMock */
- private $permissionManagerMock;
-
- protected function setUp() {
- parent::setUp();
-
- $this->permissionManagerMock = $this->createMock( PermissionManager::class );
- $this->overrideMwServices( null, [
- 'PermissionManager' => function (): PermissionManager {
- return $this->permissionManagerMock;
- }
- ] );
- }
-
/**
* @param int $articleId
* @param int $revisionId
return $mock->getArticleID() === $other->getArticleID();
}
);
- $this->permissionManagerMock->expects( $this->any() )
- ->method( 'userCan' )
- ->willReturnCallback(
- function ( $perm, User $user ) {
- return $user->isAllowed( $perm );
- }
- );
+ $mock->expects( $this->any() )
+ ->method( 'getRestrictions' )
+ ->willReturn( [] );
return $mock;
}
$sysop = $this->getTestUser( [ 'sysop' ] )->getUser(); // privileged!
$rr = $renderer->getRenderedRevision( $rev, $options, $sysop );
+ $this->assertNotNull( $rr, 'getRenderedRevision' );
$this->assertTrue( $rr->isContentDeleted(), 'isContentDeleted' );
$this->assertSame( $rev, $rr->getRevision() );
*/
public function testNewMutableRevisionFromArray_legacyEncoding( array $array ) {
$cache = new WANObjectCache( [ 'cache' => new HashBagOStuff() ] );
- $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
- $blobStore = new SqlBlobStore( $lb, $cache );
+ $services = MediaWikiServices::getInstance();
+ $lb = $services->getDBLoadBalancer();
+ $access = $services->getExternalStoreAccess();
+ $blobStore = new SqlBlobStore( $lb, $access, $cache );
$blobStore->setLegacyEncoding( 'windows-1252', Language::factory( 'en' ) );
$factory = $this->getMockBuilder( BlobStoreFactory::class )
* @covers \MediaWiki\Revision\RevisionStoreFactory::getRevisionStore
*/
public function testGetRevisionStore(
- $wikiId,
+ $dbDomain,
$mcrMigrationStage = MIGRATION_OLD,
$contentHandlerUseDb = true
) {
$contentHandlerUseDb
);
- $store = $factory->getRevisionStore( $wikiId );
+ $store = $factory->getRevisionStore( $dbDomain );
$wrapper = TestingAccessWrapper::newFromObject( $store );
// ensure the correct object type is returned
$this->assertInstanceOf( RevisionStore::class, $store );
// ensure the RevisionStore is for the given wikiId
- $this->assertSame( $wikiId, $wrapper->wikiId );
+ $this->assertSame( $dbDomain, $wrapper->dbDomain );
// ensure all other required services are correctly set
$this->assertSame( $cache, $wrapper->cache );
}
$cache = new WANObjectCache( [ 'cache' => new HashBagOStuff() ] );
- $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
+ $services = MediaWikiServices::getInstance();
+ $lb = $services->getDBLoadBalancer();
+ $access = $services->getExternalStoreAccess();
+
+ $blobStore = new SqlBlobStore( $lb, $access, $cache );
- $blobStore = new SqlBlobStore( $lb, $cache );
$blobStore->setLegacyEncoding( $encoding, Language::factory( $locale ) );
$store = $this->getRevisionStore( $lb, $blobStore, $cache );
];
$cache = new WANObjectCache( [ 'cache' => new HashBagOStuff() ] );
- $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
+ $services = MediaWikiServices::getInstance();
+ $lb = $services->getDBLoadBalancer();
+ $access = $services->getExternalStoreAccess();
- $blobStore = new SqlBlobStore( $lb, $cache );
+ $blobStore = new SqlBlobStore( $lb, $access, $cache );
$blobStore->setLegacyEncoding( 'windows-1252', Language::factory( 'en' ) );
$store = $this->getRevisionStore( $lb, $blobStore, $cache );
+++ /dev/null
-<?php
-
-namespace MediaWiki\Tests\Revision;
-
-use MediaWiki\Revision\SlotRoleHandler;
-use MediaWikiTestCase;
-use Title;
-
-/**
- * @covers \MediaWiki\Revision\SlotRoleHandler
- */
-class SlotRoleHandlerTest extends MediaWikiTestCase {
-
- /**
- * @return Title
- */
- private function makeBlankTitleObject() {
- return $this->createMock( Title::class );
- }
-
- /**
- * @covers \MediaWiki\Revision\SlotRoleHandler::__construct
- * @covers \MediaWiki\Revision\SlotRoleHandler::getRole()
- * @covers \MediaWiki\Revision\SlotRoleHandler::getNameMessageKey()
- * @covers \MediaWiki\Revision\SlotRoleHandler::getDefaultModel()
- * @covers \MediaWiki\Revision\SlotRoleHandler::getOutputLayoutHints()
- */
- public function testConstruction() {
- $handler = new SlotRoleHandler( 'foo', 'FooModel', [ 'frob' => 'niz' ] );
- $this->assertSame( 'foo', $handler->getRole() );
- $this->assertSame( 'slot-name-foo', $handler->getNameMessageKey() );
-
- $title = $this->makeBlankTitleObject();
- $this->assertSame( 'FooModel', $handler->getDefaultModel( $title ) );
-
- $hints = $handler->getOutputLayoutHints();
- $this->assertArrayHasKey( 'frob', $hints );
- $this->assertSame( 'niz', $hints['frob'] );
-
- $this->assertArrayHasKey( 'display', $hints );
- $this->assertArrayHasKey( 'region', $hints );
- $this->assertArrayHasKey( 'placement', $hints );
- }
-
- /**
- * @covers \MediaWiki\Revision\SlotRoleHandler::isAllowedModel()
- */
- public function testIsAllowedModel() {
- $handler = new SlotRoleHandler( 'foo', 'FooModel' );
-
- $title = $this->makeBlankTitleObject();
- $this->assertTrue( $handler->isAllowedModel( 'FooModel', $title ) );
- $this->assertFalse( $handler->isAllowedModel( 'QuaxModel', $title ) );
- }
-
- /**
- * @covers \MediaWiki\Revision\SlotRoleHandler::supportsArticleCount()
- */
- public function testSupportsArticleCount() {
- $handler = new SlotRoleHandler( 'foo', 'FooModel' );
-
- $this->assertFalse( $handler->supportsArticleCount() );
- }
-
-}
public function testUserCanBitfield( $bitField, $field, $userGroups, $title, $expected ) {
$title = Title::newFromText( $title );
- $this->setMwGlobals(
- 'wgGroupPermissions',
+ $this->setGroupPermissions(
[
'sysop' => [
'deletedtext' => true,
* @covers Revision::userCan
*/
public function testUserCan( $bitField, $field, $userGroups, $expected ) {
- $this->setMwGlobals(
- 'wgGroupPermissions',
+ $this->setGroupPermissions(
[
'sysop' => [
'deletedtext' => true,
$lb = $this->getMockBuilder( LoadBalancer::class )
->disableOriginalConstructor()
->getMock();
-
+ $access = MediaWikiServices::getInstance()->getExternalStoreAccess();
$cache = $this->getWANObjectCache();
- $blobStore = new SqlBlobStore( $lb, $cache );
+ $blobStore = new SqlBlobStore( $lb, $access, $cache );
+
return $blobStore;
}
public function testGetRevisionText_external_noOldId() {
$this->setService(
'ExternalStoreFactory',
- new ExternalStoreFactory( [ 'ForTesting' ] )
+ new ExternalStoreFactory( [ 'ForTesting' ], [ 'ForTesting://cluster1' ], 'test-id' )
);
$this->assertSame(
'AAAABBAAA',
$this->setService(
'ExternalStoreFactory',
- new ExternalStoreFactory( [ 'ForTesting' ] )
+ new ExternalStoreFactory( [ 'ForTesting' ], [ 'ForTesting://cluster1' ], 'test-id' )
);
$lb = $this->getMockBuilder( LoadBalancer::class )
->disableOriginalConstructor()
->getMock();
+ $access = MediaWikiServices::getInstance()->getExternalStoreAccess();
- $blobStore = new SqlBlobStore( $lb, $cache );
+ $blobStore = new SqlBlobStore( $lb, $access, $cache );
$this->setService( 'BlobStoreFactory', $this->mockBlobStoreFactory( $blobStore ) );
$this->assertSame(
+++ /dev/null
-<?php
-
-/**
- * @coversNothing
- */
-class ServiceWiringTest extends MediaWikiTestCase {
- public function testServicesAreSorted() {
- global $IP;
- $services = array_keys( require "$IP/includes/ServiceWiring.php" );
- $sortedServices = $services;
- natcasesort( $sortedServices );
-
- $this->assertSame( $sortedServices, $services,
- 'Please keep services sorted alphabetically' );
- }
-}
+++ /dev/null
-<?php
-
-class SiteConfigurationTest extends MediaWikiTestCase {
-
- /**
- * @var SiteConfiguration
- */
- protected $mConf;
-
- protected function setUp() {
- parent::setUp();
-
- $this->mConf = new SiteConfiguration;
-
- $this->mConf->suffixes = [ 'wikipedia' => 'wiki' ];
- $this->mConf->wikis = [ 'enwiki', 'dewiki', 'frwiki' ];
- $this->mConf->settings = [
- 'SimpleKey' => [
- 'wiki' => 'wiki',
- 'tag' => 'tag',
- 'enwiki' => 'enwiki',
- 'dewiki' => 'dewiki',
- 'frwiki' => 'frwiki',
- ],
-
- 'Fallback' => [
- 'default' => 'default',
- 'wiki' => 'wiki',
- 'tag' => 'tag',
- 'frwiki' => 'frwiki',
- 'null_wiki' => null,
- ],
-
- 'WithParams' => [
- 'default' => '$lang $site $wiki',
- ],
-
- '+SomeGlobal' => [
- 'wiki' => [
- 'wiki' => 'wiki',
- ],
- 'tag' => [
- 'tag' => 'tag',
- ],
- 'enwiki' => [
- 'enwiki' => 'enwiki',
- ],
- 'dewiki' => [
- 'dewiki' => 'dewiki',
- ],
- 'frwiki' => [
- 'frwiki' => 'frwiki',
- ],
- ],
-
- 'MergeIt' => [
- '+wiki' => [
- 'wiki' => 'wiki',
- ],
- '+tag' => [
- 'tag' => 'tag',
- ],
- 'default' => [
- 'default' => 'default',
- ],
- '+enwiki' => [
- 'enwiki' => 'enwiki',
- ],
- '+dewiki' => [
- 'dewiki' => 'dewiki',
- ],
- '+frwiki' => [
- 'frwiki' => 'frwiki',
- ],
- ],
- ];
-
- $GLOBALS['SomeGlobal'] = [ 'SomeGlobal' => 'SomeGlobal' ];
- }
-
- /**
- * This function is used as a callback within the tests below
- */
- public static function getSiteParamsCallback( $conf, $wiki ) {
- $site = null;
- $lang = null;
- foreach ( $conf->suffixes as $suffix ) {
- if ( substr( $wiki, -strlen( $suffix ) ) == $suffix ) {
- $site = $suffix;
- $lang = substr( $wiki, 0, -strlen( $suffix ) );
- break;
- }
- }
-
- return [
- 'suffix' => $site,
- 'lang' => $lang,
- 'params' => [
- 'lang' => $lang,
- 'site' => $site,
- 'wiki' => $wiki,
- ],
- 'tags' => [ 'tag' ],
- ];
- }
-
- /**
- * @covers SiteConfiguration::siteFromDB
- */
- public function testSiteFromDb() {
- $this->assertEquals(
- [ 'wikipedia', 'en' ],
- $this->mConf->siteFromDB( 'enwiki' ),
- 'siteFromDB()'
- );
- $this->assertEquals(
- [ 'wikipedia', '' ],
- $this->mConf->siteFromDB( 'wiki' ),
- 'siteFromDB() on a suffix'
- );
- $this->assertEquals(
- [ null, null ],
- $this->mConf->siteFromDB( 'wikien' ),
- 'siteFromDB() on a non-existing wiki'
- );
-
- $this->mConf->suffixes = [ 'wiki', '' ];
- $this->assertEquals(
- [ '', 'wikien' ],
- $this->mConf->siteFromDB( 'wikien' ),
- 'siteFromDB() on a non-existing wiki (2)'
- );
- }
-
- /**
- * @covers SiteConfiguration::getLocalDatabases
- */
- public function testGetLocalDatabases() {
- $this->assertEquals(
- [ 'enwiki', 'dewiki', 'frwiki' ],
- $this->mConf->getLocalDatabases(),
- 'getLocalDatabases()'
- );
- }
-
- /**
- * @covers SiteConfiguration::get
- */
- public function testGetConfVariables() {
- // Simple
- $this->assertEquals(
- 'enwiki',
- $this->mConf->get( 'SimpleKey', 'enwiki', 'wiki' ),
- 'get(): simple setting on an existing wiki'
- );
- $this->assertEquals(
- 'dewiki',
- $this->mConf->get( 'SimpleKey', 'dewiki', 'wiki' ),
- 'get(): simple setting on an existing wiki (2)'
- );
- $this->assertEquals(
- 'frwiki',
- $this->mConf->get( 'SimpleKey', 'frwiki', 'wiki' ),
- 'get(): simple setting on an existing wiki (3)'
- );
- $this->assertEquals(
- 'wiki',
- $this->mConf->get( 'SimpleKey', 'wiki', 'wiki' ),
- 'get(): simple setting on an suffix'
- );
- $this->assertEquals(
- 'wiki',
- $this->mConf->get( 'SimpleKey', 'eswiki', 'wiki' ),
- 'get(): simple setting on an non-existing wiki'
- );
-
- // Fallback
- $this->assertEquals(
- 'wiki',
- $this->mConf->get( 'Fallback', 'enwiki', 'wiki' ),
- 'get(): fallback setting on an existing wiki'
- );
- $this->assertEquals(
- 'tag',
- $this->mConf->get( 'Fallback', 'dewiki', 'wiki', [], [ 'tag' ] ),
- 'get(): fallback setting on an existing wiki (with wiki tag)'
- );
- $this->assertEquals(
- 'frwiki',
- $this->mConf->get( 'Fallback', 'frwiki', 'wiki', [], [ 'tag' ] ),
- 'get(): no fallback if wiki has its own setting (matching tag)'
- );
- $this->assertSame(
- // Potential regression test for T192855
- null,
- $this->mConf->get( 'Fallback', 'null_wiki', 'wiki', [], [ 'tag' ] ),
- 'get(): no fallback if wiki has its own setting (matching tag and uses null)'
- );
- $this->assertEquals(
- 'wiki',
- $this->mConf->get( 'Fallback', 'wiki', 'wiki' ),
- 'get(): fallback setting on an suffix'
- );
- $this->assertEquals(
- 'wiki',
- $this->mConf->get( 'Fallback', 'wiki', 'wiki', [], [ 'tag' ] ),
- 'get(): fallback setting on an suffix (with wiki tag)'
- );
- $this->assertEquals(
- 'wiki',
- $this->mConf->get( 'Fallback', 'eswiki', 'wiki' ),
- 'get(): fallback setting on an non-existing wiki'
- );
- $this->assertEquals(
- 'tag',
- $this->mConf->get( 'Fallback', 'eswiki', 'wiki', [], [ 'tag' ] ),
- 'get(): fallback setting on an non-existing wiki (with wiki tag)'
- );
-
- // Merging
- $common = [ 'wiki' => 'wiki', 'default' => 'default' ];
- $commonTag = [ 'tag' => 'tag', 'wiki' => 'wiki', 'default' => 'default' ];
- $this->assertEquals(
- [ 'enwiki' => 'enwiki' ] + $common,
- $this->mConf->get( 'MergeIt', 'enwiki', 'wiki' ),
- 'get(): merging setting on an existing wiki'
- );
- $this->assertEquals(
- [ 'enwiki' => 'enwiki' ] + $commonTag,
- $this->mConf->get( 'MergeIt', 'enwiki', 'wiki', [], [ 'tag' ] ),
- 'get(): merging setting on an existing wiki (with tag)'
- );
- $this->assertEquals(
- [ 'dewiki' => 'dewiki' ] + $common,
- $this->mConf->get( 'MergeIt', 'dewiki', 'wiki' ),
- 'get(): merging setting on an existing wiki (2)'
- );
- $this->assertEquals(
- [ 'dewiki' => 'dewiki' ] + $commonTag,
- $this->mConf->get( 'MergeIt', 'dewiki', 'wiki', [], [ 'tag' ] ),
- 'get(): merging setting on an existing wiki (2) (with tag)'
- );
- $this->assertEquals(
- [ 'frwiki' => 'frwiki' ] + $common,
- $this->mConf->get( 'MergeIt', 'frwiki', 'wiki' ),
- 'get(): merging setting on an existing wiki (3)'
- );
- $this->assertEquals(
- [ 'frwiki' => 'frwiki' ] + $commonTag,
- $this->mConf->get( 'MergeIt', 'frwiki', 'wiki', [], [ 'tag' ] ),
- 'get(): merging setting on an existing wiki (3) (with tag)'
- );
- $this->assertEquals(
- [ 'wiki' => 'wiki' ] + $common,
- $this->mConf->get( 'MergeIt', 'wiki', 'wiki' ),
- 'get(): merging setting on an suffix'
- );
- $this->assertEquals(
- [ 'wiki' => 'wiki' ] + $commonTag,
- $this->mConf->get( 'MergeIt', 'wiki', 'wiki', [], [ 'tag' ] ),
- 'get(): merging setting on an suffix (with tag)'
- );
- $this->assertEquals(
- $common,
- $this->mConf->get( 'MergeIt', 'eswiki', 'wiki' ),
- 'get(): merging setting on an non-existing wiki'
- );
- $this->assertEquals(
- $commonTag,
- $this->mConf->get( 'MergeIt', 'eswiki', 'wiki', [], [ 'tag' ] ),
- 'get(): merging setting on an non-existing wiki (with tag)'
- );
- }
-
- /**
- * @covers SiteConfiguration::siteFromDB
- */
- public function testSiteFromDbWithCallback() {
- $this->mConf->siteParamsCallback = 'SiteConfigurationTest::getSiteParamsCallback';
-
- $this->assertEquals(
- [ 'wiki', 'en' ],
- $this->mConf->siteFromDB( 'enwiki' ),
- 'siteFromDB() with callback'
- );
- $this->assertEquals(
- [ 'wiki', '' ],
- $this->mConf->siteFromDB( 'wiki' ),
- 'siteFromDB() with callback on a suffix'
- );
- $this->assertEquals(
- [ null, null ],
- $this->mConf->siteFromDB( 'wikien' ),
- 'siteFromDB() with callback on a non-existing wiki'
- );
- }
-
- /**
- * @covers SiteConfiguration::get
- */
- public function testParameterReplacement() {
- $this->mConf->siteParamsCallback = 'SiteConfigurationTest::getSiteParamsCallback';
-
- $this->assertEquals(
- 'en wiki enwiki',
- $this->mConf->get( 'WithParams', 'enwiki', 'wiki' ),
- 'get(): parameter replacement on an existing wiki'
- );
- $this->assertEquals(
- 'de wiki dewiki',
- $this->mConf->get( 'WithParams', 'dewiki', 'wiki' ),
- 'get(): parameter replacement on an existing wiki (2)'
- );
- $this->assertEquals(
- 'fr wiki frwiki',
- $this->mConf->get( 'WithParams', 'frwiki', 'wiki' ),
- 'get(): parameter replacement on an existing wiki (3)'
- );
- $this->assertEquals(
- ' wiki wiki',
- $this->mConf->get( 'WithParams', 'wiki', 'wiki' ),
- 'get(): parameter replacement on an suffix'
- );
- $this->assertEquals(
- 'es wiki eswiki',
- $this->mConf->get( 'WithParams', 'eswiki', 'wiki' ),
- 'get(): parameter replacement on an non-existing wiki'
- );
- }
-
- /**
- * @covers SiteConfiguration::getAll
- */
- public function testGetAllGlobals() {
- $this->mConf->siteParamsCallback = 'SiteConfigurationTest::getSiteParamsCallback';
-
- $getall = [
- 'SimpleKey' => 'enwiki',
- 'Fallback' => 'tag',
- 'WithParams' => 'en wiki enwiki',
- 'SomeGlobal' => [ 'enwiki' => 'enwiki' ] + $GLOBALS['SomeGlobal'],
- 'MergeIt' => [
- 'enwiki' => 'enwiki',
- 'tag' => 'tag',
- 'wiki' => 'wiki',
- 'default' => 'default'
- ],
- ];
- $this->assertEquals( $getall, $this->mConf->getAll( 'enwiki' ), 'getAll()' );
-
- $this->mConf->extractAllGlobals( 'enwiki', 'wiki' );
-
- $this->assertEquals(
- $getall['SimpleKey'],
- $GLOBALS['SimpleKey'],
- 'extractAllGlobals(): simple setting'
- );
- $this->assertEquals(
- $getall['Fallback'],
- $GLOBALS['Fallback'],
- 'extractAllGlobals(): fallback setting'
- );
- $this->assertEquals(
- $getall['WithParams'],
- $GLOBALS['WithParams'],
- 'extractAllGlobals(): parameter replacement'
- );
- $this->assertEquals(
- $getall['SomeGlobal'],
- $GLOBALS['SomeGlobal'],
- 'extractAllGlobals(): merging with global'
- );
- $this->assertEquals(
- $getall['MergeIt'],
- $GLOBALS['MergeIt'],
- 'extractAllGlobals(): merging setting'
- );
- }
-}
+++ /dev/null
-<?php
-
-namespace MediaWiki\Edit;
-
-use ParserOutput;
-use MediaWikiTestCase;
-
-/**
- * @covers \MediaWiki\Edit\PreparedEdit
- */
-class PreparedEditTest extends MediaWikiTestCase {
- function testCallback() {
- $output = new ParserOutput();
- $edit = new PreparedEdit();
- $edit->parserOutputCallback = function () {
- return new ParserOutput();
- };
-
- $this->assertEquals( $output, $edit->getOutput() );
- $this->assertEquals( $output, $edit->output );
- }
-}
$store = new SqlBlobStore(
$services->getDBLoadBalancer(),
+ $services->getExternalStoreAccess(),
$services->getMainWANObjectCache()
);
*/
public function testTemplateCategories() {
$user = new User();
- $user->mRights = [ 'createpage', 'edit', 'purge', 'delete' ];
+ $this->overrideUserPermissions( $user, [ 'createpage', 'edit', 'purge', 'delete' ] );
$title = Title::newFromText( "Categorized from template" );
$page = WikiPage::factory( $title );
$this->user = $this->userUser;
}
- $this->overrideMwServices();
- }
-
- protected function setUserPerm( $perm ) {
- // Setting member variables is evil!!!
-
- if ( is_array( $perm ) ) {
- $this->user->mRights = $perm;
- } else {
- $this->user->mRights = [ $perm ];
- }
+ $this->resetServices();
}
protected function setTitle( $ns, $title = "Main_Page" ) {
$this->setUser( 'anon' );
$this->setTitle( NS_TALK );
- $this->setUserPerm( "createtalk" );
+ $this->overrideUserPermissions( $this->user, "createtalk" );
$res = $this->title->getUserPermissionsErrors( 'create', $this->user );
$this->assertEquals( [], $res );
$this->setTitle( NS_TALK );
- $this->setUserPerm( "createpage" );
+ $this->overrideUserPermissions( $this->user, "createpage" );
$res = $this->title->getUserPermissionsErrors( 'create', $this->user );
$this->assertEquals( [ [ "nocreatetext" ] ], $res );
$this->setTitle( NS_TALK );
- $this->setUserPerm( "" );
+ $this->overrideUserPermissions( $this->user, "" );
$res = $this->title->getUserPermissionsErrors( 'create', $this->user );
$this->assertEquals( [ [ 'nocreatetext' ] ], $res );
$this->setTitle( NS_MAIN );
- $this->setUserPerm( "createpage" );
+ $this->overrideUserPermissions( $this->user, "createpage" );
$res = $this->title->getUserPermissionsErrors( 'create', $this->user );
$this->assertEquals( [], $res );
$this->setTitle( NS_MAIN );
- $this->setUserPerm( "createtalk" );
+ $this->overrideUserPermissions( $this->user, "createtalk" );
$res = $this->title->getUserPermissionsErrors( 'create', $this->user );
$this->assertEquals( [ [ 'nocreatetext' ] ], $res );
$this->setUser( $this->userName );
$this->setTitle( NS_TALK );
- $this->setUserPerm( "createtalk" );
+ $this->overrideUserPermissions( $this->user, "createtalk" );
$res = $this->title->getUserPermissionsErrors( 'create', $this->user );
$this->assertEquals( [], $res );
$this->setTitle( NS_TALK );
- $this->setUserPerm( "createpage" );
+ $this->overrideUserPermissions( $this->user, "createpage" );
$res = $this->title->getUserPermissionsErrors( 'create', $this->user );
$this->assertEquals( [ [ 'nocreate-loggedin' ] ], $res );
$this->setTitle( NS_TALK );
- $this->setUserPerm( "" );
+ $this->overrideUserPermissions( $this->user );
$res = $this->title->getUserPermissionsErrors( 'create', $this->user );
$this->assertEquals( [ [ 'nocreate-loggedin' ] ], $res );
$this->setTitle( NS_MAIN );
- $this->setUserPerm( "createpage" );
+ $this->overrideUserPermissions( $this->user, "createpage" );
$res = $this->title->getUserPermissionsErrors( 'create', $this->user );
$this->assertEquals( [], $res );
$this->setTitle( NS_MAIN );
- $this->setUserPerm( "createtalk" );
+ $this->overrideUserPermissions( $this->user, "createtalk" );
$res = $this->title->getUserPermissionsErrors( 'create', $this->user );
$this->assertEquals( [ [ 'nocreate-loggedin' ] ], $res );
$this->setTitle( NS_MAIN );
- $this->setUserPerm( "" );
+ $this->overrideUserPermissions( $this->user );
$res = $this->title->getUserPermissionsErrors( 'create', $this->user );
$this->assertEquals( [ [ 'nocreate-loggedin' ] ], $res );
$this->setUser( 'anon' );
$this->setTitle( NS_USER, $this->userName . '' );
- $this->setUserPerm( "" );
+ $this->overrideUserPermissions( $this->user );
$res = $this->title->getUserPermissionsErrors( 'move', $this->user );
$this->assertEquals( [ [ 'cant-move-user-page' ], [ 'movenologintext' ] ], $res );
$this->setTitle( NS_USER, $this->userName . '/subpage' );
- $this->setUserPerm( "" );
+ $this->overrideUserPermissions( $this->user );
$res = $this->title->getUserPermissionsErrors( 'move', $this->user );
$this->assertEquals( [ [ 'movenologintext' ] ], $res );
$this->setTitle( NS_USER, $this->userName . '' );
- $this->setUserPerm( "move-rootuserpages" );
+ $this->overrideUserPermissions( $this->user, "move-rootuserpages" );
$res = $this->title->getUserPermissionsErrors( 'move', $this->user );
$this->assertEquals( [ [ 'movenologintext' ] ], $res );
$this->setTitle( NS_USER, $this->userName . '/subpage' );
- $this->setUserPerm( "move-rootuserpages" );
+ $this->overrideUserPermissions( $this->user, "move-rootuserpages" );
$res = $this->title->getUserPermissionsErrors( 'move', $this->user );
$this->assertEquals( [ [ 'movenologintext' ] ], $res );
$this->setTitle( NS_USER, $this->userName . '' );
- $this->setUserPerm( "" );
+ $this->overrideUserPermissions( $this->user, "" );
$res = $this->title->getUserPermissionsErrors( 'move', $this->user );
$this->assertEquals( [ [ 'cant-move-user-page' ], [ 'movenologintext' ] ], $res );
$this->setTitle( NS_USER, $this->userName . '/subpage' );
- $this->setUserPerm( "" );
+ $this->overrideUserPermissions( $this->user, "" );
$res = $this->title->getUserPermissionsErrors( 'move', $this->user );
$this->assertEquals( [ [ 'movenologintext' ] ], $res );
$this->setTitle( NS_USER, $this->userName . '' );
- $this->setUserPerm( "move-rootuserpages" );
+ $this->overrideUserPermissions( $this->user, "move-rootuserpages" );
$res = $this->title->getUserPermissionsErrors( 'move', $this->user );
$this->assertEquals( [ [ 'movenologintext' ] ], $res );
$this->setTitle( NS_USER, $this->userName . '/subpage' );
- $this->setUserPerm( "move-rootuserpages" );
+ $this->overrideUserPermissions( $this->user, "move-rootuserpages" );
$res = $this->title->getUserPermissionsErrors( 'move', $this->user );
$this->assertEquals( [ [ 'movenologintext' ] ], $res );
$this->setUser( $this->userName );
$this->setTitle( NS_FILE, "img.png" );
- $this->setUserPerm( "" );
+ $this->overrideUserPermissions( $this->user );
$res = $this->title->getUserPermissionsErrors( 'move', $this->user );
$this->assertEquals( [ [ 'movenotallowedfile' ], [ 'movenotallowed' ] ], $res );
$this->setTitle( NS_FILE, "img.png" );
- $this->setUserPerm( "movefile" );
+ $this->overrideUserPermissions( $this->user, "movefile" );
$res = $this->title->getUserPermissionsErrors( 'move', $this->user );
$this->assertEquals( [ [ 'movenotallowed' ] ], $res );
$this->setUser( 'anon' );
$this->setTitle( NS_FILE, "img.png" );
- $this->setUserPerm( "" );
+ $this->overrideUserPermissions( $this->user );
$res = $this->title->getUserPermissionsErrors( 'move', $this->user );
$this->assertEquals( [ [ 'movenotallowedfile' ], [ 'movenologintext' ] ], $res );
$this->setTitle( NS_FILE, "img.png" );
- $this->setUserPerm( "movefile" );
+ $this->overrideUserPermissions( $this->user, "movefile" );
$res = $this->title->getUserPermissionsErrors( 'move', $this->user );
$this->assertEquals( [ [ 'movenologintext' ] ], $res );
$this->setUser( $this->userName );
- $this->setUserPerm( "move" );
+ $this->overrideUserPermissions( $this->user, "move" );
$this->runGroupPermissions( 'move', [ [ 'movenotallowedfile' ] ] );
- $this->setUserPerm( "" );
+ $this->overrideUserPermissions( $this->user );
$this->runGroupPermissions(
'move',
[ [ 'movenotallowedfile' ], [ 'movenotallowed' ] ]
);
$this->setUser( 'anon' );
- $this->setUserPerm( "move" );
+ $this->overrideUserPermissions( $this->user, "move" );
$this->runGroupPermissions( 'move', [ [ 'movenotallowedfile' ] ] );
- $this->setUserPerm( "" );
+ $this->overrideUserPermissions( $this->user );
$this->runGroupPermissions(
'move',
[ [ 'movenotallowedfile' ], [ 'movenotallowed' ] ],
$this->setTitle( NS_MAIN );
$this->setUser( 'anon' );
- $this->setUserPerm( "move" );
+ $this->overrideUserPermissions( $this->user, "move" );
$this->runGroupPermissions( 'move', [] );
- $this->setUserPerm( "" );
+ $this->overrideUserPermissions( $this->user, "" );
$this->runGroupPermissions( 'move', [ [ 'movenotallowed' ] ],
[ [ 'movenologintext' ] ] );
$this->setUser( $this->userName );
- $this->setUserPerm( "" );
+ $this->overrideUserPermissions( $this->user, "" );
$this->runGroupPermissions( 'move', [ [ 'movenotallowed' ] ] );
- $this->setUserPerm( "move" );
+ $this->overrideUserPermissions( $this->user, "move" );
$this->runGroupPermissions( 'move', [] );
$this->setUser( 'anon' );
- $this->setUserPerm( 'move' );
+ $this->overrideUserPermissions( $this->user, 'move' );
$res = $this->title->getUserPermissionsErrors( 'move-target', $this->user );
$this->assertEquals( [], $res );
- $this->setUserPerm( '' );
+ $this->overrideUserPermissions( $this->user );
$res = $this->title->getUserPermissionsErrors( 'move-target', $this->user );
$this->assertEquals( [ [ 'movenotallowed' ] ], $res );
}
$this->setTitle( NS_USER );
$this->setUser( $this->userName );
- $this->setUserPerm( [ "move", "move-rootuserpages" ] );
+ $this->overrideUserPermissions( $this->user, [ "move", "move-rootuserpages" ] );
$res = $this->title->getUserPermissionsErrors( 'move-target', $this->user );
$this->assertEquals( [], $res );
- $this->setUserPerm( "move" );
+ $this->overrideUserPermissions( $this->user, "move" );
$res = $this->title->getUserPermissionsErrors( 'move-target', $this->user );
$this->assertEquals( [ [ 'cant-move-to-user-page' ] ], $res );
$this->setUser( 'anon' );
- $this->setUserPerm( [ "move", "move-rootuserpages" ] );
+ $this->overrideUserPermissions( $this->user, [ "move", "move-rootuserpages" ] );
$res = $this->title->getUserPermissionsErrors( 'move-target', $this->user );
$this->assertEquals( [], $res );
$this->setTitle( NS_USER, "User/subpage" );
- $this->setUserPerm( [ "move", "move-rootuserpages" ] );
+ $this->overrideUserPermissions( $this->user, [ "move", "move-rootuserpages" ] );
$res = $this->title->getUserPermissionsErrors( 'move-target', $this->user );
$this->assertEquals( [], $res );
- $this->setUserPerm( "move" );
+ $this->overrideUserPermissions( $this->user, "move" );
$res = $this->title->getUserPermissionsErrors( 'move-target', $this->user );
$this->assertEquals( [], $res );
];
foreach ( [ "edit", "protect", "" ] as $action ) {
- $this->setUserPerm( null );
+ $this->overrideUserPermissions( $this->user );
$this->assertEquals( $check[$action][0],
$this->title->getUserPermissionsErrors( $action, $this->user, true ) );
$this->assertEquals( $check[$action][0],
$old = $wgGroupPermissions;
$wgGroupPermissions = [];
+ $this->resetServices();
+
$this->assertEquals( $check[$action][1],
$this->title->getUserPermissionsErrors( $action, $this->user, true ) );
$this->assertEquals( $check[$action][1],
$this->title->getUserPermissionsErrors( $action, $this->user, 'full' ) );
$this->assertEquals( $check[$action][1],
$this->title->getUserPermissionsErrors( $action, $this->user, 'secure' ) );
+
$wgGroupPermissions = $old;
+ $this->resetServices();
- $this->setUserPerm( $action );
+ $this->overrideUserPermissions( $this->user, $action );
$this->assertEquals( $check[$action][2],
$this->title->getUserPermissionsErrors( $action, $this->user, true ) );
$this->assertEquals( $check[$action][2],
$this->assertEquals( $check[$action][2],
$this->title->getUserPermissionsErrors( $action, $this->user, 'secure' ) );
- $this->setUserPerm( $action );
+ $this->overrideUserPermissions( $this->user, $action );
$this->assertEquals( $check[$action][3],
$this->title->userCan( $action, $this->user, true ) );
$this->assertEquals( $check[$action][3],
$result2 = $result;
}
+ // XXX: there could be a better way to handle this, but since we need to
+ // override PermissionManager service each time globals are changed
+ // and in the same time we need to keep user permissions overrides from the outside
+ // the best we can do inside this method is to save & restore faked user perms
+
+ $userPermsOverrides = MediaWikiServices::getInstance()->getPermissionManager()
+ ->getUserPermissions( $this->user );
+
$wgGroupPermissions['autoconfirmed']['move'] = false;
$wgGroupPermissions['user']['move'] = false;
+ $this->resetServices();
+ $this->overrideUserPermissions( $this->user, $userPermsOverrides );
$res = $this->title->getUserPermissionsErrors( $action, $this->user );
$this->assertEquals( $result, $res );
$wgGroupPermissions['autoconfirmed']['move'] = true;
$wgGroupPermissions['user']['move'] = false;
+ $this->resetServices();
+ $this->overrideUserPermissions( $this->user, $userPermsOverrides );
$res = $this->title->getUserPermissionsErrors( $action, $this->user );
$this->assertEquals( $result2, $res );
$wgGroupPermissions['autoconfirmed']['move'] = true;
$wgGroupPermissions['user']['move'] = true;
+ $this->resetServices();
+ $this->overrideUserPermissions( $this->user, $userPermsOverrides );
$res = $this->title->getUserPermissionsErrors( $action, $this->user );
$this->assertEquals( $result2, $res );
$wgGroupPermissions['autoconfirmed']['move'] = false;
$wgGroupPermissions['user']['move'] = true;
+ $this->resetServices();
+ $this->overrideUserPermissions( $this->user, $userPermsOverrides );
$res = $this->title->getUserPermissionsErrors( $action, $this->user );
$this->assertEquals( $result2, $res );
}
$this->title->getUserPermissionsErrors( 'bogus', $this->user ) );
$this->setTitle( NS_MAIN );
- $this->setUserPerm( 'bogus' );
+ $this->overrideUserPermissions( $this->user, 'bogus' );
$this->assertEquals( [],
$this->title->getUserPermissionsErrors( 'bogus', $this->user ) );
$this->setTitle( NS_MAIN );
- $this->setUserPerm( '' );
+ $this->overrideUserPermissions( $this->user );
$this->assertEquals( [ [ 'badaccess-group0' ] ],
$this->title->getUserPermissionsErrors( 'bogus', $this->user ) );
$wgNamespaceProtection[NS_USER] = [ 'bogus' ];
$this->setTitle( NS_USER );
- $this->setUserPerm( '' );
+ $this->overrideUserPermissions( $this->user );
$this->assertEquals( [ [ 'badaccess-group0' ],
[ 'namespaceprotected', 'User', 'bogus' ] ],
$this->title->getUserPermissionsErrors( 'bogus', $this->user ) );
$this->setTitle( NS_MEDIAWIKI );
- $this->setUserPerm( 'bogus' );
+ $this->overrideUserPermissions( $this->user, 'bogus' );
$this->assertEquals( [ [ 'protectedinterface', 'bogus' ] ],
$this->title->getUserPermissionsErrors( 'bogus', $this->user ) );
$this->setTitle( NS_MEDIAWIKI );
- $this->setUserPerm( 'bogus' );
+ $this->overrideUserPermissions( $this->user, 'bogus' );
$this->assertEquals( [ [ 'protectedinterface', 'bogus' ] ],
$this->title->getUserPermissionsErrors( 'bogus', $this->user ) );
$wgNamespaceProtection = null;
- $this->setUserPerm( 'bogus' );
+ $this->overrideUserPermissions( $this->user, 'bogus' );
$this->assertEquals( [],
$this->title->getUserPermissionsErrors( 'bogus', $this->user ) );
$this->assertEquals( true,
$this->title->userCan( 'bogus', $this->user ) );
- $this->setUserPerm( '' );
+ $this->overrideUserPermissions( $this->user );
$this->assertEquals( [ [ 'badaccess-group0' ] ],
$this->title->getUserPermissionsErrors( 'bogus', $this->user ) );
$this->assertEquals( false,
$resultUserJs,
$resultPatrol
) {
- $this->setUserPerm( '' );
+ $this->overrideUserPermissions( $this->user );
$result = $this->title->getUserPermissionsErrors( 'bogus', $this->user );
$this->assertEquals( $resultNone, $result );
- $this->setUserPerm( 'editmyusercss' );
+ $this->overrideUserPermissions( $this->user, 'editmyusercss' );
$result = $this->title->getUserPermissionsErrors( 'bogus', $this->user );
$this->assertEquals( $resultMyCss, $result );
- $this->setUserPerm( 'editmyuserjson' );
+ $this->overrideUserPermissions( $this->user, 'editmyuserjson' );
$result = $this->title->getUserPermissionsErrors( 'bogus', $this->user );
$this->assertEquals( $resultMyJson, $result );
- $this->setUserPerm( 'editmyuserjs' );
+ $this->overrideUserPermissions( $this->user, 'editmyuserjs' );
$result = $this->title->getUserPermissionsErrors( 'bogus', $this->user );
$this->assertEquals( $resultMyJs, $result );
- $this->setUserPerm( 'editusercss' );
+ $this->overrideUserPermissions( $this->user, 'editusercss' );
$result = $this->title->getUserPermissionsErrors( 'bogus', $this->user );
$this->assertEquals( $resultUserCss, $result );
- $this->setUserPerm( 'edituserjson' );
+ $this->overrideUserPermissions( $this->user, 'edituserjson' );
$result = $this->title->getUserPermissionsErrors( 'bogus', $this->user );
$this->assertEquals( $resultUserJson, $result );
- $this->setUserPerm( 'edituserjs' );
+ $this->overrideUserPermissions( $this->user, 'edituserjs' );
$result = $this->title->getUserPermissionsErrors( 'bogus', $this->user );
$this->assertEquals( $resultUserJs, $result );
- $this->setUserPerm( '' );
+ $this->overrideUserPermissions( $this->user );
$result = $this->title->getUserPermissionsErrors( 'patrol', $this->user );
$this->assertEquals( reset( $resultPatrol[0] ), reset( $result[0] ) );
- $this->setUserPerm( [ 'edituserjs', 'edituserjson', 'editusercss' ] );
+ $this->overrideUserPermissions( $this->user, [ 'edituserjs', 'edituserjson', 'editusercss' ] );
$result = $this->title->getUserPermissionsErrors( 'bogus', $this->user );
$this->assertEquals( [ [ 'badaccess-group0' ] ], $result );
}
$this->setTitle( NS_MAIN );
$this->title->mRestrictionsLoaded = true;
- $this->setUserPerm( "edit" );
+ $this->overrideUserPermissions( $this->user, "edit" );
$this->title->mRestrictions = [ "bogus" => [ 'bogus', "sysop", "protect", "" ] ];
$this->assertEquals( [],
[ 'protectedpagetext', 'protect', 'edit' ] ],
$this->title->getUserPermissionsErrors( 'edit',
$this->user ) );
- $this->setUserPerm( "" );
+ $this->overrideUserPermissions( $this->user );
$this->assertEquals( [ [ 'badaccess-group0' ],
[ 'protectedpagetext', 'bogus', 'bogus' ],
[ 'protectedpagetext', 'editprotected', 'bogus' ],
[ 'protectedpagetext', 'protect', 'edit' ] ],
$this->title->getUserPermissionsErrors( 'edit',
$this->user ) );
- $this->setUserPerm( [ "edit", "editprotected" ] );
+ $this->overrideUserPermissions( $this->user, [ "edit", "editprotected" ] );
$this->assertEquals( [ [ 'badaccess-group0' ],
[ 'protectedpagetext', 'bogus', 'bogus' ],
[ 'protectedpagetext', 'protect', 'bogus' ] ],
$this->user ) );
$this->title->mCascadeRestriction = true;
- $this->setUserPerm( "edit" );
+ $this->overrideUserPermissions( $this->user, "edit" );
$this->assertEquals( false,
$this->title->quickUserCan( 'bogus', $this->user ) );
$this->assertEquals( false,
$this->title->getUserPermissionsErrors( 'edit',
$this->user ) );
- $this->setUserPerm( [ "edit", "editprotected" ] );
+ $this->overrideUserPermissions( $this->user, [ "edit", "editprotected" ] );
$this->assertEquals( false,
$this->title->quickUserCan( 'bogus', $this->user ) );
$this->assertEquals( false,
*/
public function testCascadingSourcesRestrictions() {
$this->setTitle( NS_MAIN, "test page" );
- $this->setUserPerm( [ "edit", "bogus" ] );
+ $this->overrideUserPermissions( $this->user, [ "edit", "bogus" ] );
$this->title->mCascadeSources = [
Title::makeTitle( NS_MAIN, "Bogus" ),
* @covers \MediaWiki\Permissions\PermissionManager::checkActionPermissions
*/
public function testActionPermissions() {
- $this->setUserPerm( [ "createpage" ] );
+ $this->overrideUserPermissions( $this->user, [ "createpage" ] );
$this->setTitle( NS_MAIN, "test page" );
$this->title->mTitleProtection['permission'] = '';
$this->title->mTitleProtection['user'] = $this->user->getId();
$this->title->userCan( 'create', $this->user ) );
$this->title->mTitleProtection['permission'] = 'editprotected';
- $this->setUserPerm( [ 'createpage', 'protect' ] );
+ $this->overrideUserPermissions( $this->user, [ 'createpage', 'protect' ] );
$this->assertEquals( [ [ 'titleprotected', 'Useruser', 'test' ] ],
$this->title->getUserPermissionsErrors( 'create', $this->user ) );
$this->assertEquals( false,
$this->title->userCan( 'create', $this->user ) );
- $this->setUserPerm( [ 'createpage', 'editprotected' ] );
+ $this->overrideUserPermissions( $this->user, [ 'createpage', 'editprotected' ] );
$this->assertEquals( [],
$this->title->getUserPermissionsErrors( 'create', $this->user ) );
$this->assertEquals( true,
$this->title->userCan( 'create', $this->user ) );
- $this->setUserPerm( [ 'createpage' ] );
+ $this->overrideUserPermissions( $this->user, [ 'createpage' ] );
$this->assertEquals( [ [ 'titleprotected', 'Useruser', 'test' ] ],
$this->title->getUserPermissionsErrors( 'create', $this->user ) );
$this->assertEquals( false,
$this->title->userCan( 'create', $this->user ) );
$this->setTitle( NS_MEDIA, "test page" );
- $this->setUserPerm( [ "move" ] );
+ $this->overrideUserPermissions( $this->user, [ "move" ] );
$this->assertEquals( false,
$this->title->userCan( 'move', $this->user ) );
$this->assertEquals( [ [ 'immobile-source-namespace', 'Media' ] ],
'wgEmailAuthentication' => true,
'wgBlockDisablesLogin' => false,
] );
- $this->overrideMwServices();
+ $this->resetServices();
- $this->setUserPerm( [ 'createpage', 'edit', 'move', 'rollback', 'patrol', 'upload', 'purge' ] );
+ $this->overrideUserPermissions(
+ $this->user,
+ [ 'createpage', 'edit', 'move', 'rollback', 'patrol', 'upload', 'purge' ]
+ );
$this->setTitle( NS_HELP, "test page" );
# $wgEmailConfirmToEdit only applies to 'edit' action
$this->title->getUserPermissionsErrors( 'edit', $this->user ) );
$this->setMwGlobals( 'wgEmailConfirmToEdit', false );
- $this->overrideMwServices();
+ $this->resetServices();
+ $this->overrideUserPermissions(
+ $this->user,
+ [ 'createpage', 'edit', 'move', 'rollback', 'patrol', 'upload', 'purge' ]
+ );
$this->assertNotContains( [ 'confirmedittext' ],
$this->title->getUserPermissionsErrors( 'edit', $this->user ) );
],
],
] );
+ $this->resetServices();
$now = time();
$this->user->mBlockedby = $this->user->getName();
// New anonymous user with no rights
$user = new User;
- $user->mRights = [];
+ $this->overrideUserPermissions( $user, [] );
$errors = $title->userCan( $action, $user );
if ( is_bool( $expected ) ) {
+++ /dev/null
-<?php
-
-/**
- * @group Xml
- */
-class XmlSelectTest extends MediaWikiTestCase {
-
- /**
- * @var XmlSelect
- */
- protected $select;
-
- protected function setUp() {
- parent::setUp();
- $this->select = new XmlSelect();
- }
-
- protected function tearDown() {
- parent::tearDown();
- $this->select = null;
- }
-
- /**
- * @covers XmlSelect::__construct
- */
- public function testConstructWithoutParameters() {
- $this->assertEquals( '<select></select>', $this->select->getHTML() );
- }
-
- /**
- * Parameters are $name (false), $id (false), $default (false)
- * @dataProvider provideConstructionParameters
- * @covers XmlSelect::__construct
- */
- public function testConstructParameters( $name, $id, $default, $expected ) {
- $this->select = new XmlSelect( $name, $id, $default );
- $this->assertEquals( $expected, $this->select->getHTML() );
- }
-
- /**
- * Provide parameters for testConstructParameters() which use three
- * parameters:
- * - $name (default: false)
- * - $id (default: false)
- * - $default (default: false)
- * Provides a fourth parameters representing the expected HTML output
- */
- public static function provideConstructionParameters() {
- return [
- /**
- * Values are set following a 3-bit Gray code where two successive
- * values differ by only one value.
- * See https://en.wikipedia.org/wiki/Gray_code
- */
- # $name $id $default
- [ false, false, false, '<select></select>' ],
- [ false, false, 'foo', '<select></select>' ],
- [ false, 'id', 'foo', '<select id="id"></select>' ],
- [ false, 'id', false, '<select id="id"></select>' ],
- [ 'name', 'id', false, '<select name="name" id="id"></select>' ],
- [ 'name', 'id', 'foo', '<select name="name" id="id"></select>' ],
- [ 'name', false, 'foo', '<select name="name"></select>' ],
- [ 'name', false, false, '<select name="name"></select>' ],
- ];
- }
-
- /**
- * @covers XmlSelect::addOption
- */
- public function testAddOption() {
- $this->select->addOption( 'foo' );
- $this->assertEquals(
- '<select><option value="foo">foo</option></select>',
- $this->select->getHTML()
- );
- }
-
- /**
- * @covers XmlSelect::addOption
- */
- public function testAddOptionWithDefault() {
- $this->select->addOption( 'foo', true );
- $this->assertEquals(
- '<select><option value="1">foo</option></select>',
- $this->select->getHTML()
- );
- }
-
- /**
- * @covers XmlSelect::addOption
- */
- public function testAddOptionWithFalse() {
- $this->select->addOption( 'foo', false );
- $this->assertEquals(
- '<select><option value="foo">foo</option></select>',
- $this->select->getHTML()
- );
- }
-
- /**
- * @covers XmlSelect::addOption
- */
- public function testAddOptionWithValueZero() {
- $this->select->addOption( 'foo', 0 );
- $this->assertEquals(
- '<select><option value="0">foo</option></select>',
- $this->select->getHTML()
- );
- }
-
- /**
- * @covers XmlSelect::setDefault
- */
- public function testSetDefault() {
- $this->select->setDefault( 'bar1' );
- $this->select->addOption( 'foo1' );
- $this->select->addOption( 'bar1' );
- $this->select->addOption( 'foo2' );
- $this->assertEquals(
- '<select><option value="foo1">foo1</option>' . "\n" .
- '<option value="bar1" selected="">bar1</option>' . "\n" .
- '<option value="foo2">foo2</option></select>', $this->select->getHTML() );
- }
-
- /**
- * Adding default later on should set the correct selection or
- * raise an exception.
- * To handle this, we need to render the options in getHtml()
- * @covers XmlSelect::setDefault
- */
- public function testSetDefaultAfterAddingOptions() {
- $this->select->addOption( 'foo1' );
- $this->select->addOption( 'bar1' );
- $this->select->addOption( 'foo2' );
- $this->select->setDefault( 'bar1' ); # setting default after adding options
- $this->assertEquals(
- '<select><option value="foo1">foo1</option>' . "\n" .
- '<option value="bar1" selected="">bar1</option>' . "\n" .
- '<option value="foo2">foo2</option></select>', $this->select->getHTML() );
- }
-
- /**
- * @covers XmlSelect::setAttribute
- * @covers XmlSelect::getAttribute
- */
- public function testGetAttributes() {
- # create some attributes
- $this->select->setAttribute( 'dummy', 0x777 );
- $this->select->setAttribute( 'string', 'euro €' );
- $this->select->setAttribute( 1911, 'razor' );
-
- # verify we can retrieve them
- $this->assertEquals(
- $this->select->getAttribute( 'dummy' ),
- 0x777
- );
- $this->assertEquals(
- $this->select->getAttribute( 'string' ),
- 'euro €'
- );
- $this->assertEquals(
- $this->select->getAttribute( 1911 ),
- 'razor'
- );
-
- # inexistent keys should give us 'null'
- $this->assertEquals(
- $this->select->getAttribute( 'I DO NOT EXIT' ),
- null
- );
-
- # verify string / integer
- $this->assertEquals(
- $this->select->getAttribute( '1911' ),
- 'razor'
- );
- $this->assertEquals(
- $this->select->getAttribute( 'dummy' ),
- 0x777
- );
- }
-}
public function testCanExecute() {
$user = $this->getTestUser()->getUser();
- $user->mRights = [ 'access' ];
+ $this->overrideUserPermissions( $user, 'access' );
$action = Action::factory( 'access', $this->getPage(), $this->getContext() );
$this->assertNull( $action->canExecute( $user ) );
}
public function testCanExecuteNoRight() {
$user = $this->getTestUser()->getUser();
- $user->mRights = [];
+ $this->overrideUserPermissions( $user, [] );
$action = Action::factory( 'access', $this->getPage(), $this->getContext() );
try {
public function testCanExecuteRequiresUnblock() {
$user = $this->getTestUser()->getUser();
- $user->mRights = [];
+ $this->overrideUserPermissions( $user, [] );
$page = $this->getExistingTestPage();
$action = Action::factory( 'unblock', $page, $this->getContext() );
$this->setMwGlobals( 'wgRevokePermissions',
[ 'user' => [ 'applychangetags' => true ] ] );
+ $this->resetServices();
+
$this->doBlock( [ 'tags' => 'custom tag' ] );
}
$this->mergeMwGlobalArrayValue( 'wgGroupPermissions',
[ 'sysop' => $newPermissions ] );
+ $this->resetServices();
$res = $this->doBlock( [ 'hidename' => '' ] );
$dbw = wfGetDB( DB_MASTER );
$this->setMwGlobals( 'wgRevokePermissions',
[ 'sysop' => [ 'blockemail' => true ] ] );
+ $this->resetServices();
+
$this->doBlock( [ 'noemail' => '' ] );
}
ChangeTags::defineTag( 'custom tag' );
$this->setMwGlobals( 'wgRevokePermissions',
[ 'user' => [ 'applychangetags' => true ] ] );
+ $this->resetServices();
$this->editPage( $name, 'Some text' );
$this->tablesUsed,
[ 'change_tag', 'change_tag_def', 'logging' ]
);
+ $this->resetServices();
}
public function testEdit() {
ChangeTags::defineTag( 'custom tag' );
$this->setMwGlobals( 'wgRevokePermissions',
[ 'user' => [ 'applychangetags' => true ] ] );
+ // Supply services with updated globals
+ $this->resetServices();
+
try {
$this->doApiRequestWithToken( [
'action' => 'edit',
$this->setMwGlobals( 'wgRevokePermissions',
[ 'user' => [ 'upload' => true ] ] );
+ // Supply services with updated globals
+ $this->resetServices();
$this->doApiRequestWithToken( [
'action' => 'edit',
'The content you supplied exceeds the article size limit of 1 kilobyte.' );
$this->setMwGlobals( 'wgMaxArticleSize', 1 );
+ // Supply services with updated globals
+ $this->resetServices();
$text = str_repeat( '!', 1025 );
'The action you have requested is limited to users in the group: ' );
$this->setMwGlobals( 'wgRevokePermissions', [ '*' => [ 'edit' => true ] ] );
+ // Supply services with updated globals
+ $this->resetServices();
$this->doApiRequestWithToken( [
'action' => 'edit',
$this->setMwGlobals( 'wgRevokePermissions',
[ 'user' => [ 'editcontentmodel' => true ] ] );
+ // Supply services with updated globals
+ $this->resetServices();
$this->doApiRequestWithToken( [
'action' => 'edit',
public function testSetCacheModeUnrecognized() {
$api = new ApiMain();
$api->setCacheMode( 'unrecognized' );
+ $this->resetServices();
$this->assertSame(
'private',
TestingAccessWrapper::newFromObject( $api )->mCacheMode,
public function testSetCacheModePrivateWiki() {
$this->setGroupPermissions( '*', 'read', false );
-
$wrappedApi = TestingAccessWrapper::newFromObject( new ApiMain() );
$wrappedApi->setCacheMode( 'public' );
$this->assertSame( 'private', $wrappedApi->mCacheMode );
} else {
$user = new User();
}
- $user->mRights = $rights;
+ $this->overrideUserPermissions( $user, $rights );
try {
$this->doApiRequest( [
'action' => 'query',
$name = ucfirst( __FUNCTION__ );
$this->mergeMwGlobalArrayValue( 'wgNamespacesWithSubpages', [ NS_MAIN => true ] );
+ $this->resetServices();
$pages = [ $name, "$name/1", "$name/2", "Talk:$name", "Talk:$name/1", "Talk:$name/3" ];
$ids = [];
$name = ucfirst( __FUNCTION__ );
$this->setGroupPermissions( 'sysop', 'suppressredirect', false );
-
$id = $this->createPage( $name );
$res = $this->doApiRequestWithToken( [
$this->setMwGlobals( 'wgExtraInterlanguageLinkPrefixes', [ 'madeuplanguage' ] );
$this->tablesUsed[] = 'interwiki';
- $this->overrideMwServices();
+ $this->resetServices();
}
/**
// Nor does the original one if they become a bot
$user->addGroup( 'bot' );
+ MediaWikiServices::getInstance()->getPermissionManager()->invalidateUsersRightsCache();
$this->assertFalse(
$this->doCheckCache( $user ),
"We assume bots don't have cache entries"
// But other groups are okay
$user->removeGroup( 'bot' );
$user->addGroup( 'sysop' );
+ MediaWikiServices::getInstance()->getPermissionManager()->invalidateUsersRightsCache();
$this->assertInstanceOf( stdClass::class, $this->doCheckCache( $user ) );
}
<?php
use MediaWiki\Block\DatabaseBlock;
+use MediaWiki\MediaWikiServices;
/**
* @group API
if ( $remove ) {
$this->mergeMwGlobalArrayValue( 'wgRemoveGroups', [ 'bureaucrat' => $remove ] );
}
+
+ $this->resetServices();
}
/**
$res = $this->doApiRequestWithToken( $params );
$user->clearInstanceCache();
+ MediaWikiServices::getInstance()->getPermissionManager()->invalidateUsersRightsCache();
$this->assertSame( $expectedGroups, $user->getGroups() );
$this->assertArrayNotHasKey( 'warnings', $res[0] );
ChangeTags::defineTag( 'custom tag' );
$this->setGroupPermissions( 'user', 'applychangetags', false );
+ $this->resetServices();
$this->doFailedRightsChange(
'You do not have permission to apply change tags along with your changes.',
];
$block = new DatabaseBlock( $blockOptions );
$block->insert();
+ $this->resetServices();
$status = $this->manager->checkAccountCreatePermissions( $user );
$this->assertFalse( $status->isOK() );
$this->assertTrue( $status->hasMessage( 'cantcreateaccount-text' ) );
],
'wgProxyWhitelist' => [],
] );
- $this->overrideMwServices();
+ $this->resetServices();
$status = $this->manager->checkAccountCreatePermissions( new \User );
$this->assertFalse( $status->isOK() );
$this->assertTrue( $status->hasMessage( 'sorbs_create_account_reason' ) );
$this->setMwGlobals( 'wgProxyWhitelist', [ '127.0.0.1' ] );
- $this->overrideMwServices();
+ $this->resetServices();
$status = $this->manager->checkAccountCreatePermissions( new \User );
$this->assertTrue( $status->isGood() );
}
$this->mergeMwGlobalArrayValue( 'wgObjectCaches',
[ __METHOD__ => [ 'class' => 'HashBagOStuff' ] ] );
$this->setMwGlobals( [ 'wgMainCacheType' => __METHOD__ ] );
+ // Supply services with updated globals
+ $this->resetServices();
// Set up lots of mocks...
$mocks = [];
+++ /dev/null
-<?php
-
-namespace MediaWiki\Auth;
-
-/**
- * @group AuthManager
- * @covers \MediaWiki\Auth\AuthenticationResponse
- */
-class AuthenticationResponseTest extends \MediaWikiTestCase {
- /**
- * @dataProvider provideConstructors
- * @param string $constructor
- * @param array $args
- * @param array|Exception $expect
- */
- public function testConstructors( $constructor, $args, $expect ) {
- if ( is_array( $expect ) ) {
- $res = new AuthenticationResponse();
- $res->messageType = 'warning';
- foreach ( $expect as $field => $value ) {
- $res->$field = $value;
- }
- $ret = call_user_func_array( "MediaWiki\\Auth\\AuthenticationResponse::$constructor", $args );
- $this->assertEquals( $res, $ret );
- } else {
- try {
- call_user_func_array( "MediaWiki\\Auth\\AuthenticationResponse::$constructor", $args );
- $this->fail( 'Expected exception not thrown' );
- } catch ( \Exception $ex ) {
- $this->assertEquals( $expect, $ex );
- }
- }
- }
-
- public function provideConstructors() {
- $req = $this->getMockForAbstractClass( AuthenticationRequest::class );
- $msg = new \Message( 'mainpage' );
-
- return [
- [ 'newPass', [], [
- 'status' => AuthenticationResponse::PASS,
- ] ],
- [ 'newPass', [ 'name' ], [
- 'status' => AuthenticationResponse::PASS,
- 'username' => 'name',
- ] ],
- [ 'newPass', [ 'name', null ], [
- 'status' => AuthenticationResponse::PASS,
- 'username' => 'name',
- ] ],
-
- [ 'newFail', [ $msg ], [
- 'status' => AuthenticationResponse::FAIL,
- 'message' => $msg,
- 'messageType' => 'error',
- ] ],
-
- [ 'newRestart', [ $msg ], [
- 'status' => AuthenticationResponse::RESTART,
- 'message' => $msg,
- ] ],
-
- [ 'newAbstain', [], [
- 'status' => AuthenticationResponse::ABSTAIN,
- ] ],
-
- [ 'newUI', [ [ $req ], $msg ], [
- 'status' => AuthenticationResponse::UI,
- 'neededRequests' => [ $req ],
- 'message' => $msg,
- 'messageType' => 'warning',
- ] ],
-
- [ 'newUI', [ [ $req ], $msg, 'warning' ], [
- 'status' => AuthenticationResponse::UI,
- 'neededRequests' => [ $req ],
- 'message' => $msg,
- 'messageType' => 'warning',
- ] ],
-
- [ 'newUI', [ [ $req ], $msg, 'error' ], [
- 'status' => AuthenticationResponse::UI,
- 'neededRequests' => [ $req ],
- 'message' => $msg,
- 'messageType' => 'error',
- ] ],
- [ 'newUI', [ [], $msg ],
- new \InvalidArgumentException( '$reqs may not be empty' )
- ],
-
- [ 'newRedirect', [ [ $req ], 'http://example.org/redir' ], [
- 'status' => AuthenticationResponse::REDIRECT,
- 'neededRequests' => [ $req ],
- 'redirectTarget' => 'http://example.org/redir',
- ] ],
- [
- 'newRedirect',
- [ [ $req ], 'http://example.org/redir', [ 'foo' => 'bar' ] ],
- [
- 'status' => AuthenticationResponse::REDIRECT,
- 'neededRequests' => [ $req ],
- 'redirectTarget' => 'http://example.org/redir',
- 'redirectApiData' => [ 'foo' => 'bar' ],
- ]
- ],
- [ 'newRedirect', [ [], 'http://example.org/redir' ],
- new \InvalidArgumentException( '$reqs may not be empty' )
- ],
- ];
- }
-
-}
+++ /dev/null
-<?php
-
-/**
- * @covers ChangesListFilterGroup
- */
-class ChangesListFilterGroupTest extends MediaWikiTestCase {
- /**
- * phpcs:disable Generic.Files.LineLength
- * @expectedException MWException
- * @expectedExceptionMessage Group names may not contain '_'. Use the naming convention: 'camelCase'
- * phpcs:enable
- */
- public function testReservedCharacter() {
- new MockChangesListFilterGroup(
- [
- 'type' => 'some_type',
- 'name' => 'group_name',
- 'priority' => 1,
- 'filters' => [],
- ]
- );
- }
-
- public function testAutoPriorities() {
- $group = new MockChangesListFilterGroup(
- [
- 'type' => 'some_type',
- 'name' => 'groupName',
- 'isFullCoverage' => true,
- 'priority' => 1,
- 'filters' => [
- [ 'name' => 'hidefoo' ],
- [ 'name' => 'hidebar' ],
- [ 'name' => 'hidebaz' ],
- ],
- ]
- );
-
- $filters = $group->getFilters();
- $this->assertEquals(
- [
- -2,
- -3,
- -4,
- ],
- array_map(
- function ( $f ) {
- return $f->getPriority();
- },
- array_values( $filters )
- )
- );
- }
-
- // Get without warnings
- public function testGetFilter() {
- $group = new MockChangesListFilterGroup(
- [
- 'type' => 'some_type',
- 'name' => 'groupName',
- 'isFullCoverage' => true,
- 'priority' => 1,
- 'filters' => [
- [ 'name' => 'foo' ],
- ],
- ]
- );
-
- $this->assertEquals(
- 'foo',
- $group->getFilter( 'foo' )->getName()
- );
-
- $this->assertEquals(
- null,
- $group->getFilter( 'bar' )
- );
- }
-}
+++ /dev/null
-<?php
-
-class HashConfigTest extends MediaWikiTestCase {
-
- /**
- * @covers HashConfig::newInstance
- */
- public function testNewInstance() {
- $conf = HashConfig::newInstance();
- $this->assertInstanceOf( HashConfig::class, $conf );
- }
-
- /**
- * @covers HashConfig::__construct
- */
- public function testConstructor() {
- $conf = new HashConfig();
- $this->assertInstanceOf( HashConfig::class, $conf );
-
- // Test passing arguments to the constructor
- $conf2 = new HashConfig( [
- 'one' => '1',
- ] );
- $this->assertEquals( '1', $conf2->get( 'one' ) );
- }
-
- /**
- * @covers HashConfig::get
- */
- public function testGet() {
- $conf = new HashConfig( [
- 'one' => '1',
- ] );
- $this->assertEquals( '1', $conf->get( 'one' ) );
- $this->setExpectedException( ConfigException::class, 'HashConfig::get: undefined option' );
- $conf->get( 'two' );
- }
-
- /**
- * @covers HashConfig::has
- */
- public function testHas() {
- $conf = new HashConfig( [
- 'one' => '1',
- ] );
- $this->assertTrue( $conf->has( 'one' ) );
- $this->assertFalse( $conf->has( 'two' ) );
- }
-
- /**
- * @covers HashConfig::set
- */
- public function testSet() {
- $conf = new HashConfig( [
- 'one' => '1',
- ] );
- $conf->set( 'two', '2' );
- $this->assertEquals( '2', $conf->get( 'two' ) );
- // Check that set overwrites
- $conf->set( 'one', '3' );
- $this->assertEquals( '3', $conf->get( 'one' ) );
- }
-}
+++ /dev/null
-<?php
-
-class MultiConfigTest extends MediaWikiTestCase {
-
- /**
- * Tests that settings are fetched in the right order
- *
- * @covers MultiConfig::__construct
- * @covers MultiConfig::get
- */
- public function testGet() {
- $multi = new MultiConfig( [
- new HashConfig( [ 'foo' => 'bar' ] ),
- new HashConfig( [ 'foo' => 'baz', 'bar' => 'foo' ] ),
- new HashConfig( [ 'bar' => 'baz' ] ),
- ] );
-
- $this->assertEquals( 'bar', $multi->get( 'foo' ) );
- $this->assertEquals( 'foo', $multi->get( 'bar' ) );
- $this->setExpectedException( ConfigException::class, 'MultiConfig::get: undefined option:' );
- $multi->get( 'notset' );
- }
-
- /**
- * @covers MultiConfig::has
- */
- public function testHas() {
- $conf = new MultiConfig( [
- new HashConfig( [ 'foo' => 'foo' ] ),
- new HashConfig( [ 'something' => 'bleh' ] ),
- new HashConfig( [ 'meh' => 'eh' ] ),
- ] );
-
- $this->assertTrue( $conf->has( 'foo' ) );
- $this->assertTrue( $conf->has( 'something' ) );
- $this->assertTrue( $conf->has( 'meh' ) );
- $this->assertFalse( $conf->has( 'what' ) );
- }
-}
+++ /dev/null
-<?php
-
-use MediaWiki\Config\ServiceOptions;
-
-/**
- * @coversDefaultClass \MediaWiki\Config\ServiceOptions
- */
-class ServiceOptionsTest extends MediaWikiTestCase {
- public static $testObj;
-
- public static function setUpBeforeClass() {
- parent::setUpBeforeClass();
-
- self::$testObj = new stdclass();
- }
-
- /**
- * @dataProvider provideConstructor
- * @covers ::__construct
- * @covers ::assertRequiredOptions
- * @covers ::get
- */
- public function testConstructor( $expected, $keys, ...$sources ) {
- $options = new ServiceOptions( $keys, ...$sources );
-
- foreach ( $expected as $key => $val ) {
- $this->assertSame( $val, $options->get( $key ) );
- }
-
- // This is lumped in the same test because there's no support for depending on a test that
- // has a data provider.
- $options->assertRequiredOptions( array_keys( $expected ) );
-
- // Suppress warning if no assertions were run. This is expected for empty arguments.
- $this->assertTrue( true );
- }
-
- public function provideConstructor() {
- return [
- 'No keys' => [ [], [], [ 'a' => 'aval' ] ],
- 'Simple array source' => [
- [ 'a' => 'aval', 'b' => 'bval' ],
- [ 'a', 'b' ],
- [ 'a' => 'aval', 'b' => 'bval', 'c' => 'cval' ],
- ],
- 'Simple HashConfig source' => [
- [ 'a' => 'aval', 'b' => 'bval' ],
- [ 'a', 'b' ],
- new HashConfig( [ 'a' => 'aval', 'b' => 'bval', 'c' => 'cval' ] ),
- ],
- 'Three different sources' => [
- [ 'a' => 'aval', 'b' => 'bval' ],
- [ 'a', 'b' ],
- [ 'z' => 'zval' ],
- new HashConfig( [ 'a' => 'aval', 'c' => 'cval' ] ),
- [ 'b' => 'bval', 'd' => 'dval' ],
- ],
- 'null key' => [
- [ 'a' => null ],
- [ 'a' ],
- [ 'a' => null ],
- ],
- 'Numeric option name' => [
- [ '0' => 'nothing' ],
- [ '0' ],
- [ '0' => 'nothing' ],
- ],
- 'Multiple sources for one key' => [
- [ 'a' => 'winner' ],
- [ 'a' ],
- [ 'a' => 'winner' ],
- [ 'a' => 'second place' ],
- ],
- 'Object value is passed by reference' => [
- [ 'a' => self::$testObj ],
- [ 'a' ],
- [ 'a' => self::$testObj ],
- ],
- ];
- }
-
- /**
- * @covers ::__construct
- */
- public function testKeyNotFound() {
- $this->setExpectedException( InvalidArgumentException::class,
- 'Key "a" not found in input sources' );
-
- new ServiceOptions( [ 'a' ], [ 'b' => 'bval' ], [ 'c' => 'cval' ] );
- }
-
- /**
- * @covers ::__construct
- * @covers ::assertRequiredOptions
- */
- public function testOutOfOrderAssertRequiredOptions() {
- $options = new ServiceOptions( [ 'a', 'b' ], [ 'a' => '', 'b' => '' ] );
- $options->assertRequiredOptions( [ 'b', 'a' ] );
- $this->assertTrue( true, 'No exception thrown' );
- }
-
- /**
- * @covers ::__construct
- * @covers ::get
- */
- public function testGetUnrecognized() {
- $this->setExpectedException( InvalidArgumentException::class,
- 'Unrecognized option "b"' );
-
- $options = new ServiceOptions( [ 'a' ], [ 'a' => '' ] );
- $options->get( 'b' );
- }
-
- /**
- * @covers ::__construct
- * @covers ::assertRequiredOptions
- */
- public function testExtraKeys() {
- $this->setExpectedException( Wikimedia\Assert\PreconditionException::class,
- 'Precondition failed: Unsupported options passed: b, c!' );
-
- $options = new ServiceOptions( [ 'a', 'b', 'c' ], [ 'a' => '', 'b' => '', 'c' => '' ] );
- $options->assertRequiredOptions( [ 'a' ] );
- }
-
- /**
- * @covers ::__construct
- * @covers ::assertRequiredOptions
- */
- public function testMissingKeys() {
- $this->setExpectedException( Wikimedia\Assert\PreconditionException::class,
- 'Precondition failed: Required options missing: a, b!' );
-
- $options = new ServiceOptions( [ 'c' ], [ 'c' => '' ] );
- $options->assertRequiredOptions( [ 'a', 'b', 'c' ] );
- }
-
- /**
- * @covers ::__construct
- * @covers ::assertRequiredOptions
- */
- public function testExtraAndMissingKeys() {
- $this->setExpectedException( Wikimedia\Assert\PreconditionException::class,
- 'Precondition failed: Unsupported options passed: b! Required options missing: c!' );
-
- $options = new ServiceOptions( [ 'a', 'b' ], [ 'a' => '', 'b' => '' ] );
- $options->assertRequiredOptions( [ 'a', 'c' ] );
- }
-}
+++ /dev/null
-<?php
-
-class JsonContentHandlerTest extends MediaWikiTestCase {
-
- /**
- * @covers JsonContentHandler::makeEmptyContent
- */
- public function testMakeEmptyContent() {
- $handler = new JsonContentHandler();
- $content = $handler->makeEmptyContent();
- $this->assertInstanceOf( JsonContent::class, $content );
- $this->assertTrue( $content->isValid() );
- }
-}
+++ /dev/null
-<?php
-/**
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- */
-
-namespace MediaWiki\Logger;
-
-use MediaWikiTestCase;
-use Wikimedia\TestingAccessWrapper;
-
-class MonologSpiTest extends MediaWikiTestCase {
-
- /**
- * @covers MediaWiki\Logger\MonologSpi::mergeConfig
- */
- public function testMergeConfig() {
- $base = [
- 'loggers' => [
- '@default' => [
- 'processors' => [ 'constructor' ],
- 'handlers' => [ 'constructor' ],
- ],
- ],
- 'processors' => [
- 'constructor' => [
- 'class' => 'constructor',
- ],
- ],
- 'handlers' => [
- 'constructor' => [
- 'class' => 'constructor',
- 'formatter' => 'constructor',
- ],
- ],
- 'formatters' => [
- 'constructor' => [
- 'class' => 'constructor',
- ],
- ],
- ];
-
- $fixture = new MonologSpi( $base );
- $this->assertSame(
- $base,
- TestingAccessWrapper::newFromObject( $fixture )->config
- );
-
- $fixture->mergeConfig( [
- 'loggers' => [
- 'merged' => [
- 'processors' => [ 'merged' ],
- 'handlers' => [ 'merged' ],
- ],
- ],
- 'processors' => [
- 'merged' => [
- 'class' => 'merged',
- ],
- ],
- 'magic' => [
- 'idkfa' => [ 'xyzzy' ],
- ],
- 'handlers' => [
- 'merged' => [
- 'class' => 'merged',
- 'formatter' => 'merged',
- ],
- ],
- 'formatters' => [
- 'merged' => [
- 'class' => 'merged',
- ],
- ],
- ] );
- $this->assertSame(
- [
- 'loggers' => [
- '@default' => [
- 'processors' => [ 'constructor' ],
- 'handlers' => [ 'constructor' ],
- ],
- 'merged' => [
- 'processors' => [ 'merged' ],
- 'handlers' => [ 'merged' ],
- ],
- ],
- 'processors' => [
- 'constructor' => [
- 'class' => 'constructor',
- ],
- 'merged' => [
- 'class' => 'merged',
- ],
- ],
- 'handlers' => [
- 'constructor' => [
- 'class' => 'constructor',
- 'formatter' => 'constructor',
- ],
- 'merged' => [
- 'class' => 'merged',
- 'formatter' => 'merged',
- ],
- ],
- 'formatters' => [
- 'constructor' => [
- 'class' => 'constructor',
- ],
- 'merged' => [
- 'class' => 'merged',
- ],
- ],
- 'magic' => [
- 'idkfa' => [ 'xyzzy' ],
- ],
- ],
- TestingAccessWrapper::newFromObject( $fixture )->config
- );
- }
-
-}
+++ /dev/null
-<?php
-/**
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- */
-
-namespace MediaWiki\Logger\Monolog;
-
-use MediaWikiTestCase;
-use PHPUnit_Framework_Error_Notice;
-
-/**
- * @covers \MediaWiki\Logger\Monolog\AvroFormatter
- */
-class AvroFormatterTest extends MediaWikiTestCase {
-
- protected function setUp() {
- if ( !class_exists( 'AvroStringIO' ) ) {
- $this->markTestSkipped( 'Avro is required for the AvroFormatterTest' );
- }
- parent::setUp();
- }
-
- public function testSchemaNotAvailable() {
- $formatter = new AvroFormatter( [] );
- $this->setExpectedException(
- 'PHPUnit_Framework_Error_Notice',
- "The schema for channel 'marty' is not available"
- );
- $formatter->format( [ 'channel' => 'marty' ] );
- }
-
- public function testSchemaNotAvailableReturnValue() {
- $formatter = new AvroFormatter( [] );
- $noticeEnabled = PHPUnit_Framework_Error_Notice::$enabled;
- // disable conversion of notices
- PHPUnit_Framework_Error_Notice::$enabled = false;
- // have to keep the user notice from being output
- \Wikimedia\suppressWarnings();
- $res = $formatter->format( [ 'channel' => 'marty' ] );
- \Wikimedia\restoreWarnings();
- PHPUnit_Framework_Error_Notice::$enabled = $noticeEnabled;
- $this->assertNull( $res );
- }
-
- public function testDoesSomethingWhenSchemaAvailable() {
- $formatter = new AvroFormatter( [
- 'string' => [
- 'schema' => [ 'type' => 'string' ],
- 'revision' => 1010101,
- ]
- ] );
- $res = $formatter->format( [
- 'channel' => 'string',
- 'context' => 'better to be',
- ] );
- $this->assertNotNull( $res );
- // basically just tell us if avro changes its string encoding, or if
- // we completely fail to generate a log message.
- $this->assertEquals( 'AAAAAAAAD2m1GGJldHRlciB0byBiZQ==', base64_encode( $res ) );
- }
-}
+++ /dev/null
-<?php
-/**
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- */
-
-namespace MediaWiki\Logger\Monolog;
-
-use MediaWikiTestCase;
-use Monolog\Logger;
-use Wikimedia\TestingAccessWrapper;
-
-/**
- * @covers \MediaWiki\Logger\Monolog\KafkaHandler
- */
-class KafkaHandlerTest extends MediaWikiTestCase {
-
- protected function setUp() {
- if ( !class_exists( 'Monolog\Handler\AbstractProcessingHandler' )
- || !class_exists( 'Kafka\Produce' )
- ) {
- $this->markTestSkipped( 'Monolog and Kafka are required for the KafkaHandlerTest' );
- }
-
- parent::setUp();
- }
-
- public function topicNamingProvider() {
- return [
- [ [], 'monolog_foo' ],
- [ [ 'alias' => [ 'foo' => 'bar' ] ], 'bar' ]
- ];
- }
-
- /**
- * @dataProvider topicNamingProvider
- */
- public function testTopicNaming( $options, $expect ) {
- $produce = $this->getMockBuilder( 'Kafka\Produce' )
- ->disableOriginalConstructor()
- ->getMock();
- $produce->expects( $this->any() )
- ->method( 'getAvailablePartitions' )
- ->will( $this->returnValue( [ 'A' ] ) );
- $produce->expects( $this->once() )
- ->method( 'setMessages' )
- ->with( $expect, $this->anything(), $this->anything() );
- $produce->expects( $this->any() )
- ->method( 'send' )
- ->will( $this->returnValue( true ) );
-
- $handler = new KafkaHandler( $produce, $options );
- $handler->handle( [
- 'channel' => 'foo',
- 'level' => Logger::EMERGENCY,
- 'extra' => [],
- 'context' => [],
- ] );
- }
-
- public function swallowsExceptionsWhenRequested() {
- return [
- // defaults to false
- [ [], true ],
- // also try false explicitly
- [ [ 'swallowExceptions' => false ], true ],
- // turn it on
- [ [ 'swallowExceptions' => true ], false ],
- ];
- }
-
- /**
- * @dataProvider swallowsExceptionsWhenRequested
- */
- public function testGetAvailablePartitionsException( $options, $expectException ) {
- $produce = $this->getMockBuilder( 'Kafka\Produce' )
- ->disableOriginalConstructor()
- ->getMock();
- $produce->expects( $this->any() )
- ->method( 'getAvailablePartitions' )
- ->will( $this->throwException( new \Kafka\Exception ) );
- $produce->expects( $this->any() )
- ->method( 'send' )
- ->will( $this->returnValue( true ) );
-
- if ( $expectException ) {
- $this->setExpectedException( 'Kafka\Exception' );
- }
-
- $handler = new KafkaHandler( $produce, $options );
- $handler->handle( [
- 'channel' => 'foo',
- 'level' => Logger::EMERGENCY,
- 'extra' => [],
- 'context' => [],
- ] );
-
- if ( !$expectException ) {
- $this->assertTrue( true, 'no exception was thrown' );
- }
- }
-
- /**
- * @dataProvider swallowsExceptionsWhenRequested
- */
- public function testSendException( $options, $expectException ) {
- $produce = $this->getMockBuilder( 'Kafka\Produce' )
- ->disableOriginalConstructor()
- ->getMock();
- $produce->expects( $this->any() )
- ->method( 'getAvailablePartitions' )
- ->will( $this->returnValue( [ 'A' ] ) );
- $produce->expects( $this->any() )
- ->method( 'send' )
- ->will( $this->throwException( new \Kafka\Exception ) );
-
- if ( $expectException ) {
- $this->setExpectedException( 'Kafka\Exception' );
- }
-
- $handler = new KafkaHandler( $produce, $options );
- $handler->handle( [
- 'channel' => 'foo',
- 'level' => Logger::EMERGENCY,
- 'extra' => [],
- 'context' => [],
- ] );
-
- if ( !$expectException ) {
- $this->assertTrue( true, 'no exception was thrown' );
- }
- }
-
- public function testHandlesNullFormatterResult() {
- $produce = $this->getMockBuilder( 'Kafka\Produce' )
- ->disableOriginalConstructor()
- ->getMock();
- $produce->expects( $this->any() )
- ->method( 'getAvailablePartitions' )
- ->will( $this->returnValue( [ 'A' ] ) );
- $mockMethod = $produce->expects( $this->exactly( 2 ) )
- ->method( 'setMessages' );
- $produce->expects( $this->any() )
- ->method( 'send' )
- ->will( $this->returnValue( true ) );
- // evil hax
- $matcher = TestingAccessWrapper::newFromObject( $mockMethod )->matcher;
- TestingAccessWrapper::newFromObject( $matcher )->parametersMatcher =
- new \PHPUnit_Framework_MockObject_Matcher_ConsecutiveParameters( [
- [ $this->anything(), $this->anything(), [ 'words' ] ],
- [ $this->anything(), $this->anything(), [ 'lines' ] ]
- ] );
-
- $formatter = $this->createMock( \Monolog\Formatter\FormatterInterface::class );
- $formatter->expects( $this->any() )
- ->method( 'format' )
- ->will( $this->onConsecutiveCalls( 'words', null, 'lines' ) );
-
- $handler = new KafkaHandler( $produce, [] );
- $handler->setFormatter( $formatter );
- for ( $i = 0; $i < 3; ++$i ) {
- $handler->handle( [
- 'channel' => 'foo',
- 'level' => Logger::EMERGENCY,
- 'extra' => [],
- 'context' => [],
- ] );
- }
- }
-
- public function testBatchHandlesNullFormatterResult() {
- $produce = $this->getMockBuilder( 'Kafka\Produce' )
- ->disableOriginalConstructor()
- ->getMock();
- $produce->expects( $this->any() )
- ->method( 'getAvailablePartitions' )
- ->will( $this->returnValue( [ 'A' ] ) );
- $produce->expects( $this->once() )
- ->method( 'setMessages' )
- ->with( $this->anything(), $this->anything(), [ 'words', 'lines' ] );
- $produce->expects( $this->any() )
- ->method( 'send' )
- ->will( $this->returnValue( true ) );
-
- $formatter = $this->createMock( \Monolog\Formatter\FormatterInterface::class );
- $formatter->expects( $this->any() )
- ->method( 'format' )
- ->will( $this->onConsecutiveCalls( 'words', null, 'lines' ) );
-
- $handler = new KafkaHandler( $produce, [] );
- $handler->setFormatter( $formatter );
- $handler->handleBatch( [
- [
- 'channel' => 'foo',
- 'level' => Logger::EMERGENCY,
- 'extra' => [],
- 'context' => [],
- ],
- [
- 'channel' => 'foo',
- 'level' => Logger::EMERGENCY,
- 'extra' => [],
- 'context' => [],
- ],
- [
- 'channel' => 'foo',
- 'level' => Logger::EMERGENCY,
- 'extra' => [],
- 'context' => [],
- ],
- ] );
- }
-}
+++ /dev/null
-<?php
-/**
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- */
-
-namespace MediaWiki\Logger\Monolog;
-
-use AssertionError;
-use InvalidArgumentException;
-use LengthException;
-use LogicException;
-use MediaWikiTestCase;
-use Wikimedia\TestingAccessWrapper;
-
-class LineFormatterTest extends MediaWikiTestCase {
-
- protected function setUp() {
- if ( !class_exists( 'Monolog\Formatter\LineFormatter' ) ) {
- $this->markTestSkipped( 'This test requires monolog to be installed' );
- }
- parent::setUp();
- }
-
- /**
- * @covers MediaWiki\Logger\Monolog\LineFormatter::normalizeException
- */
- public function testNormalizeExceptionNoTrace() {
- $fixture = new LineFormatter();
- $fixture->includeStacktraces( false );
- $fixture = TestingAccessWrapper::newFromObject( $fixture );
- $boom = new InvalidArgumentException( 'boom', 0,
- new LengthException( 'too long', 0,
- new LogicException( 'Spock wuz here' )
- )
- );
- $out = $fixture->normalizeException( $boom );
- $this->assertContains( "\n[Exception InvalidArgumentException]", $out );
- $this->assertContains( "\nCaused by: [Exception LengthException]", $out );
- $this->assertContains( "\nCaused by: [Exception LogicException]", $out );
- $this->assertNotContains( "\n #0", $out );
- }
-
- /**
- * @covers MediaWiki\Logger\Monolog\LineFormatter::normalizeException
- */
- public function testNormalizeExceptionTrace() {
- $fixture = new LineFormatter();
- $fixture->includeStacktraces( true );
- $fixture = TestingAccessWrapper::newFromObject( $fixture );
- $boom = new InvalidArgumentException( 'boom', 0,
- new LengthException( 'too long', 0,
- new LogicException( 'Spock wuz here' )
- )
- );
- $out = $fixture->normalizeException( $boom );
- $this->assertContains( "\n[Exception InvalidArgumentException]", $out );
- $this->assertContains( "\nCaused by: [Exception LengthException]", $out );
- $this->assertContains( "\nCaused by: [Exception LogicException]", $out );
- $this->assertContains( "\n #0", $out );
- }
-
- /**
- * @covers MediaWiki\Logger\Monolog\LineFormatter::normalizeException
- */
- public function testNormalizeExceptionErrorNoTrace() {
- if ( !class_exists( AssertionError::class ) ) {
- $this->markTestSkipped( 'AssertionError class does not exist' );
- }
-
- $fixture = new LineFormatter();
- $fixture->includeStacktraces( false );
- $fixture = TestingAccessWrapper::newFromObject( $fixture );
- $boom = new InvalidArgumentException( 'boom', 0,
- new LengthException( 'too long', 0,
- new AssertionError( 'Spock wuz here' )
- )
- );
- $out = $fixture->normalizeException( $boom );
- $this->assertContains( "\n[Exception InvalidArgumentException]", $out );
- $this->assertContains( "\nCaused by: [Exception LengthException]", $out );
- $this->assertContains( "\nCaused by: [Error AssertionError]", $out );
- $this->assertNotContains( "\n #0", $out );
- }
-
- /**
- * @covers MediaWiki\Logger\Monolog\LineFormatter::normalizeException
- */
- public function testNormalizeExceptionErrorTrace() {
- if ( !class_exists( AssertionError::class ) ) {
- $this->markTestSkipped( 'AssertionError class does not exist' );
- }
-
- $fixture = new LineFormatter();
- $fixture->includeStacktraces( true );
- $fixture = TestingAccessWrapper::newFromObject( $fixture );
- $boom = new InvalidArgumentException( 'boom', 0,
- new LengthException( 'too long', 0,
- new AssertionError( 'Spock wuz here' )
- )
- );
- $out = $fixture->normalizeException( $boom );
- $this->assertContains( "\n[Exception InvalidArgumentException]", $out );
- $this->assertContains( "\nCaused by: [Exception LengthException]", $out );
- $this->assertContains( "\nCaused by: [Error AssertionError]", $out );
- $this->assertContains( "\n #0", $out );
- }
-}
+++ /dev/null
-<?php
-
-/**
- * @author Addshore
- *
- * @group Diff
- */
-class ArrayDiffFormatterTest extends MediaWikiTestCase {
-
- /**
- * @param Diff $input
- * @param array $expectedOutput
- * @dataProvider provideTestFormat
- * @covers ArrayDiffFormatter::format
- */
- public function testFormat( $input, $expectedOutput ) {
- $instance = new ArrayDiffFormatter();
- $output = $instance->format( $input );
- $this->assertEquals( $expectedOutput, $output );
- }
-
- private function getMockDiff( $edits ) {
- $diff = $this->getMockBuilder( Diff::class )
- ->disableOriginalConstructor()
- ->getMock();
- $diff->expects( $this->any() )
- ->method( 'getEdits' )
- ->will( $this->returnValue( $edits ) );
- return $diff;
- }
-
- private function getMockDiffOp( $type = null, $orig = [], $closing = [] ) {
- $diffOp = $this->getMockBuilder( DiffOp::class )
- ->disableOriginalConstructor()
- ->getMock();
- $diffOp->expects( $this->any() )
- ->method( 'getType' )
- ->will( $this->returnValue( $type ) );
- $diffOp->expects( $this->any() )
- ->method( 'getOrig' )
- ->will( $this->returnValue( $orig ) );
- if ( $type === 'change' ) {
- $diffOp->expects( $this->any() )
- ->method( 'getClosing' )
- ->with( $this->isType( 'integer' ) )
- ->will( $this->returnCallback( function () {
- return 'mockLine';
- } ) );
- } else {
- $diffOp->expects( $this->any() )
- ->method( 'getClosing' )
- ->will( $this->returnValue( $closing ) );
- }
- return $diffOp;
- }
-
- public function provideTestFormat() {
- $emptyArrayTestCases = [
- $this->getMockDiff( [] ),
- $this->getMockDiff( [ $this->getMockDiffOp( 'add' ) ] ),
- $this->getMockDiff( [ $this->getMockDiffOp( 'delete' ) ] ),
- $this->getMockDiff( [ $this->getMockDiffOp( 'change' ) ] ),
- $this->getMockDiff( [ $this->getMockDiffOp( 'copy' ) ] ),
- $this->getMockDiff( [ $this->getMockDiffOp( 'FOOBARBAZ' ) ] ),
- $this->getMockDiff( [ $this->getMockDiffOp( 'add', 'line' ) ] ),
- $this->getMockDiff( [ $this->getMockDiffOp( 'delete', [], [ 'line' ] ) ] ),
- $this->getMockDiff( [ $this->getMockDiffOp( 'copy', [], [ 'line' ] ) ] ),
- ];
-
- $otherTestCases = [];
- $otherTestCases[] = [
- $this->getMockDiff( [ $this->getMockDiffOp( 'add', [], [ 'a1' ] ) ] ),
- [ [ 'action' => 'add', 'new' => 'a1', 'newline' => 1 ] ],
- ];
- $otherTestCases[] = [
- $this->getMockDiff( [ $this->getMockDiffOp( 'add', [], [ 'a1', 'a2' ] ) ] ),
- [
- [ 'action' => 'add', 'new' => 'a1', 'newline' => 1 ],
- [ 'action' => 'add', 'new' => 'a2', 'newline' => 2 ],
- ],
- ];
- $otherTestCases[] = [
- $this->getMockDiff( [ $this->getMockDiffOp( 'delete', [ 'd1' ] ) ] ),
- [ [ 'action' => 'delete', 'old' => 'd1', 'oldline' => 1 ] ],
- ];
- $otherTestCases[] = [
- $this->getMockDiff( [ $this->getMockDiffOp( 'delete', [ 'd1', 'd2' ] ) ] ),
- [
- [ 'action' => 'delete', 'old' => 'd1', 'oldline' => 1 ],
- [ 'action' => 'delete', 'old' => 'd2', 'oldline' => 2 ],
- ],
- ];
- $otherTestCases[] = [
- $this->getMockDiff( [ $this->getMockDiffOp( 'change', [ 'd1' ], [ 'a1' ] ) ] ),
- [ [
- 'action' => 'change',
- 'old' => 'd1',
- 'new' => 'mockLine',
- 'newline' => 1, 'oldline' => 1
- ] ],
- ];
- $otherTestCases[] = [
- $this->getMockDiff( [ $this->getMockDiffOp(
- 'change',
- [ 'd1', 'd2' ],
- [ 'a1', 'a2' ]
- ) ] ),
- [
- [
- 'action' => 'change',
- 'old' => 'd1',
- 'new' => 'mockLine',
- 'newline' => 1, 'oldline' => 1
- ],
- [
- 'action' => 'change',
- 'old' => 'd2',
- 'new' => 'mockLine',
- 'newline' => 2, 'oldline' => 2
- ],
- ],
- ];
-
- $testCases = [];
- foreach ( $emptyArrayTestCases as $testCase ) {
- $testCases[] = [ $testCase, [] ];
- }
- foreach ( $otherTestCases as $testCase ) {
- $testCases[] = [ $testCase[0], $testCase[1] ];
- }
- return $testCases;
- }
-
-}
+++ /dev/null
-<?php
-/**
- * @author Addshore
- *
- * @group Diff
- */
-class DiffOpTest extends MediaWikiTestCase {
-
- /**
- * @covers DiffOp::getType
- */
- public function testGetType() {
- $obj = new FakeDiffOp();
- $obj->type = 'foo';
- $this->assertEquals( 'foo', $obj->getType() );
- }
-
- /**
- * @covers DiffOp::getOrig
- */
- public function testGetOrig() {
- $obj = new FakeDiffOp();
- $obj->orig = [ 'foo' ];
- $this->assertEquals( [ 'foo' ], $obj->getOrig() );
- }
-
- /**
- * @covers DiffOp::getClosing
- */
- public function testGetClosing() {
- $obj = new FakeDiffOp();
- $obj->closing = [ 'foo' ];
- $this->assertEquals( [ 'foo' ], $obj->getClosing() );
- }
-
- /**
- * @covers DiffOp::getClosing
- */
- public function testGetClosingWithParameter() {
- $obj = new FakeDiffOp();
- $obj->closing = [ 'foo', 'bar', 'baz' ];
- $this->assertEquals( 'foo', $obj->getClosing( 0 ) );
- $this->assertEquals( 'bar', $obj->getClosing( 1 ) );
- $this->assertEquals( 'baz', $obj->getClosing( 2 ) );
- $this->assertEquals( null, $obj->getClosing( 3 ) );
- }
-
- /**
- * @covers DiffOp::norig
- */
- public function testNorig() {
- $obj = new FakeDiffOp();
- $this->assertEquals( 0, $obj->norig() );
- $obj->orig = [ 'foo' ];
- $this->assertEquals( 1, $obj->norig() );
- }
-
- /**
- * @covers DiffOp::nclosing
- */
- public function testNclosing() {
- $obj = new FakeDiffOp();
- $this->assertEquals( 0, $obj->nclosing() );
- $obj->closing = [ 'foo' ];
- $this->assertEquals( 1, $obj->nclosing() );
- }
-
-}
+++ /dev/null
-<?php
-
-/**
- * @author Addshore
- *
- * @group Diff
- */
-class DiffTest extends MediaWikiTestCase {
-
- /**
- * @covers Diff::getEdits
- */
- public function testGetEdits() {
- $obj = new Diff( [], [] );
- $obj->edits = 'FooBarBaz';
- $this->assertEquals( 'FooBarBaz', $obj->getEdits() );
- }
-
-}
+++ /dev/null
-<?php
-/**
- * @author Antoine Musso
- * @copyright Copyright © 2013, Antoine Musso
- * @copyright Copyright © 2013, Wikimedia Foundation Inc.
- * @file
- */
-
-class MWExceptionHandlerTest extends MediaWikiTestCase {
-
- /**
- * @covers MWExceptionHandler::getRedactedTrace
- */
- public function testGetRedactedTrace() {
- $refvar = 'value';
- try {
- $array = [ 'a', 'b' ];
- $object = new stdClass();
- self::helperThrowAnException( $array, $object, $refvar );
- } catch ( Exception $e ) {
- }
-
- # Make sure our stack trace contains an array and an object passed to
- # some function in the stacktrace. Else, we can not assert the trace
- # redaction achieved its job.
- $trace = $e->getTrace();
- $hasObject = false;
- $hasArray = false;
- foreach ( $trace as $frame ) {
- if ( !isset( $frame['args'] ) ) {
- continue;
- }
- foreach ( $frame['args'] as $arg ) {
- $hasObject = $hasObject || is_object( $arg );
- $hasArray = $hasArray || is_array( $arg );
- }
-
- if ( $hasObject && $hasArray ) {
- break;
- }
- }
- $this->assertTrue( $hasObject,
- "The stacktrace must have a function having an object has parameter" );
- $this->assertTrue( $hasArray,
- "The stacktrace must have a function having an array has parameter" );
-
- # Now we redact the trace.. and make sure no function arguments are
- # arrays or objects.
- $redacted = MWExceptionHandler::getRedactedTrace( $e );
-
- foreach ( $redacted as $frame ) {
- if ( !isset( $frame['args'] ) ) {
- continue;
- }
- foreach ( $frame['args'] as $arg ) {
- $this->assertNotInternalType( 'array', $arg );
- $this->assertNotInternalType( 'object', $arg );
- }
- }
-
- $this->assertEquals( 'value', $refvar, 'Ensuring reference variable wasn\'t changed' );
- }
-
- /**
- * Helper function for testExpandArgumentsInCall
- *
- * Pass it an object and an array, and something by reference :-)
- *
- * @throws Exception
- */
- protected static function helperThrowAnException( $a, $b, &$c ) {
- throw new Exception();
- }
-}
--- /dev/null
+<?php
+
+use Wikimedia\Rdbms\LBFactory;
+
+/**
+ * @covers ExternalStoreAccess
+ */
+class ExternalStoreAccessTest extends MediaWikiTestCase {
+
+ use MediaWikiCoversValidator;
+
+ /**
+ * @covers ExternalStoreAccess::isReadOnly
+ */
+ public function testBasic() {
+ $active = [ 'memory' ];
+ $defaults = [ 'memory://cluster1', 'memory://cluster2' ];
+ $esFactory = new ExternalStoreFactory( $active, $defaults, 'db-prefix' );
+ $access = new ExternalStoreAccess( $esFactory );
+
+ $this->assertEquals( false, $access->isReadOnly() );
+
+ /** @var ExternalStoreMemory $store */
+ $store = $esFactory->getStore( 'memory' );
+ $this->assertInstanceOf( ExternalStoreMemory::class, $store );
+
+ $lb = $this->getMockBuilder( LoadBalancer::class )
+ ->disableOriginalConstructor()->getMock();
+ $lb->expects( $this->any() )->method( 'getReadOnlyReason' )->willReturn( 'Locked' );
+ $lb->expects( $this->any() )->method( 'getServerInfo' )->willReturn( [] );
+
+ $lbFactory = $this->getMockBuilder( LBFactory::class )
+ ->disableOriginalConstructor()->getMock();
+ $lbFactory->expects( $this->any() )->method( 'getExternalLB' )->willReturn( $lb );
+
+ $this->setService( 'DBLoadBalancerFactory', $lbFactory );
+
+ $active = [ 'db', 'mwstore' ];
+ $defaults = [ 'DB://clusterX' ];
+ $esFactory = new ExternalStoreFactory( $active, $defaults, 'db-prefix' );
+ $access = new ExternalStoreAccess( $esFactory );
+ $this->assertEquals( true, $access->isReadOnly() );
+
+ $store->clear();
+ }
+
+ /**
+ * @covers ExternalStoreAccess::fetchFromURL
+ * @covers ExternalStoreAccess::fetchFromURLs
+ * @covers ExternalStoreAccess::insert
+ */
+ public function testReadWrite() {
+ $active = [ 'memory' ]; // active store types
+ $defaults = [ 'memory://cluster1', 'memory://cluster2' ];
+ $esFactory = new ExternalStoreFactory( $active, $defaults, 'db-prefix' );
+ $access = new ExternalStoreAccess( $esFactory );
+
+ /** @var ExternalStoreMemory $storeLocal */
+ $storeLocal = $esFactory->getStore( 'memory' );
+ /** @var ExternalStoreMemory $storeOther */
+ $storeOther = $esFactory->getStore( 'memory', [ 'domain' => 'other' ] );
+ $this->assertInstanceOf( ExternalStoreMemory::class, $storeLocal );
+ $this->assertInstanceOf( ExternalStoreMemory::class, $storeOther );
+
+ $v1 = wfRandomString();
+ $v2 = wfRandomString();
+ $v3 = wfRandomString();
+
+ $this->assertEquals( false, $storeLocal->fetchFromURL( 'memory://cluster1/1' ) );
+
+ $url1 = 'memory://cluster1/1';
+ $this->assertEquals(
+ $url1,
+ $esFactory->getStoreForUrl( 'memory://cluster1' )
+ ->store( $esFactory->getStoreLocationFromUrl( 'memory://cluster1' ), $v1 )
+ );
+ $this->assertEquals(
+ $v1,
+ $esFactory->getStoreForUrl( 'memory://cluster1/1' )
+ ->fetchFromURL( 'memory://cluster1/1' )
+ );
+ $this->assertEquals( $v1, $storeLocal->fetchFromURL( 'memory://cluster1/1' ) );
+
+ $url2 = $access->insert( $v2 );
+ $url3 = $access->insert( $v3, [ 'domain' => 'other' ] );
+ $this->assertNotFalse( $url2 );
+ $this->assertNotFalse( $url3 );
+ // There is only one active store type
+ $this->assertEquals( $v2, $storeLocal->fetchFromURL( $url2 ) );
+ $this->assertEquals( $v3, $storeOther->fetchFromURL( $url3 ) );
+ $this->assertEquals( false, $storeOther->fetchFromURL( $url2 ) );
+ $this->assertEquals( false, $storeLocal->fetchFromURL( $url3 ) );
+
+ $res = $access->fetchFromURLs( [ $url1, $url2, $url3 ] );
+ $this->assertEquals( [ $url1 => $v1, $url2 => $v2, $url3 => false ], $res, "Local-only" );
+
+ $storeLocal->clear();
+ $storeOther->clear();
+ }
+}
/**
* @covers ExternalStoreFactory
+ * @covers ExternalStoreAccess
*/
-class ExternalStoreFactoryTest extends PHPUnit\Framework\TestCase {
+class ExternalStoreFactoryTest extends MediaWikiTestCase {
use MediaWikiCoversValidator;
- public function testExternalStoreFactory_noStores() {
- $factory = new ExternalStoreFactory( [] );
- $this->assertFalse( $factory->getStoreObject( 'ForTesting' ) );
- $this->assertFalse( $factory->getStoreObject( 'foo' ) );
+ /**
+ * @expectedException ExternalStoreException
+ */
+ public function testExternalStoreFactory_noStores1() {
+ $factory = new ExternalStoreFactory( [], [], 'test-id' );
+ $factory->getStore( 'ForTesting' );
+ }
+
+ /**
+ * @expectedException ExternalStoreException
+ */
+ public function testExternalStoreFactory_noStores2() {
+ $factory = new ExternalStoreFactory( [], [], 'test-id' );
+ $factory->getStore( 'foo' );
}
public function provideStoreNames() {
* @dataProvider provideStoreNames
*/
public function testExternalStoreFactory_someStore_protoMatch( $proto ) {
- $factory = new ExternalStoreFactory( [ 'ForTesting' ] );
- $store = $factory->getStoreObject( $proto );
+ $factory = new ExternalStoreFactory( [ 'ForTesting' ], [], 'test-id' );
+ $store = $factory->getStore( $proto );
$this->assertInstanceOf( ExternalStoreForTesting::class, $store );
}
/**
* @dataProvider provideStoreNames
+ * @expectedException ExternalStoreException
*/
public function testExternalStoreFactory_someStore_noProtoMatch( $proto ) {
- $factory = new ExternalStoreFactory( [ 'SomeOtherClassName' ] );
- $store = $factory->getStoreObject( $proto );
- $this->assertFalse( $store );
+ $factory = new ExternalStoreFactory( [ 'SomeOtherClassName' ], [], 'test-id' );
+ $factory->getStore( $proto );
+ }
+
+ /**
+ * @covers ExternalStoreFactory::getProtocols
+ * @covers ExternalStoreFactory::getWriteBaseUrls
+ * @covers ExternalStoreFactory::getStore
+ */
+ public function testStoreFactoryBasic() {
+ $active = [ 'memory' ];
+ $defaults = [ 'memory://cluster1', 'memory://cluster2' ];
+ $esFactory = new ExternalStoreFactory( $active, $defaults, 'db-prefix' );
+
+ $this->assertEquals( $active, $esFactory->getProtocols() );
+ $this->assertEquals( $defaults, $esFactory->getWriteBaseUrls() );
+
+ /** @var ExternalStoreMemory $store */
+ $store = $esFactory->getStore( 'memory' );
+ $this->assertInstanceOf( ExternalStoreMemory::class, $store );
+ $this->assertEquals( false, $store->isReadOnly( 'cluster1' ) );
+ $this->assertEquals( false, $store->isReadOnly( 'cluster2' ) );
+ $this->assertEquals( true, $store->isReadOnly( 'clusterOld' ) );
+
+ $lb = $this->getMockBuilder( \Wikimedia\Rdbms\LoadBalancer::class )
+ ->disableOriginalConstructor()->getMock();
+ $lb->expects( $this->any() )->method( 'getReadOnlyReason' )->willReturn( 'Locked' );
+ $lbFactory = $this->getMockBuilder( \Wikimedia\Rdbms\LBFactory::class )
+ ->disableOriginalConstructor()->getMock();
+ $lbFactory->expects( $this->any() )->method( 'getExternalLB' )->willReturn( $lb );
+
+ $this->setService( 'DBLoadBalancerFactory', $lbFactory );
+
+ $active = [ 'db', 'mwstore' ];
+ $defaults = [ 'db://clusterX' ];
+ $esFactory = new ExternalStoreFactory( $active, $defaults, 'db-prefix' );
+ $this->assertEquals( $active, $esFactory->getProtocols() );
+ $this->assertEquals( $defaults, $esFactory->getWriteBaseUrls() );
+
+ $store->clear();
}
+ /**
+ * @covers ExternalStoreFactory::getStoreForUrl
+ * @covers ExternalStoreFactory::getStoreLocationFromUrl
+ */
+ public function testStoreFactoryReadWrite() {
+ $active = [ 'memory' ]; // active store types
+ $defaults = [ 'memory://cluster1', 'memory://cluster2' ];
+ $esFactory = new ExternalStoreFactory( $active, $defaults, 'db-prefix' );
+ $access = new ExternalStoreAccess( $esFactory );
+
+ /** @var ExternalStoreMemory $storeLocal */
+ $storeLocal = $esFactory->getStore( 'memory' );
+ /** @var ExternalStoreMemory $storeOther */
+ $storeOther = $esFactory->getStore( 'memory', [ 'domain' => 'other' ] );
+ $this->assertInstanceOf( ExternalStoreMemory::class, $storeLocal );
+ $this->assertInstanceOf( ExternalStoreMemory::class, $storeOther );
+
+ $v1 = wfRandomString();
+ $v2 = wfRandomString();
+ $v3 = wfRandomString();
+
+ $this->assertEquals( false, $storeLocal->fetchFromURL( 'memory://cluster1/1' ) );
+
+ $url1 = 'memory://cluster1/1';
+ $this->assertEquals(
+ $url1,
+ $esFactory->getStoreForUrl( 'memory://cluster1' )
+ ->store( $esFactory->getStoreLocationFromUrl( 'memory://cluster1' ), $v1 )
+ );
+ $this->assertEquals(
+ $v1,
+ $esFactory->getStoreForUrl( 'memory://cluster1/1' )
+ ->fetchFromURL( 'memory://cluster1/1' )
+ );
+ $this->assertEquals( $v1, $storeLocal->fetchFromURL( 'memory://cluster1/1' ) );
+
+ $url2 = $access->insert( $v2 );
+ $url3 = $access->insert( $v3, [ 'domain' => 'other' ] );
+ $this->assertNotFalse( $url2 );
+ $this->assertNotFalse( $url3 );
+ // There is only one active store type
+ $this->assertEquals( $v2, $storeLocal->fetchFromURL( $url2 ) );
+ $this->assertEquals( $v3, $storeOther->fetchFromURL( $url3 ) );
+ $this->assertEquals( false, $storeOther->fetchFromURL( $url2 ) );
+ $this->assertEquals( false, $storeLocal->fetchFromURL( $url3 ) );
+
+ $res = $access->fetchFromURLs( [ $url1, $url2, $url3 ] );
+ $this->assertEquals( [ $url1 => $v1, $url2 => $v2, $url3 => false ], $res, "Local-only" );
+
+ $storeLocal->clear();
+ $storeOther->clear();
+ }
}
public function testExternalFetchFromURL_noExternalStores() {
$this->setService(
'ExternalStoreFactory',
- new ExternalStoreFactory( [] )
+ new ExternalStoreFactory( [], [], 'test-id' )
);
$this->assertFalse(
public function testExternalFetchFromURL_someExternalStore() {
$this->setService(
'ExternalStoreFactory',
- new ExternalStoreFactory( [ 'ForTesting' ] )
+ new ExternalStoreFactory( [ 'ForTesting' ], [ 'ForTesting://cluster1' ], 'test-id' )
);
$this->assertEquals(
+++ /dev/null
-<?php
-
-class InstallDocFormatterTest extends MediaWikiTestCase {
- /**
- * @covers InstallDocFormatter
- * @dataProvider provideDocFormattingTests
- */
- public function testFormat( $expected, $unformattedText, $message = '' ) {
- $this->assertEquals(
- $expected,
- InstallDocFormatter::format( $unformattedText ),
- $message
- );
- }
-
- /**
- * Provider for testFormat()
- */
- public static function provideDocFormattingTests() {
- # Format: (expected string, unformattedText string, optional message)
- return [
- # Escape some wikitext
- [ 'Install <tag>', 'Install <tag>', 'Escaping <' ],
- [ 'Install {{template}}', 'Install {{template}}', 'Escaping [[' ],
- [ 'Install [[page]]', 'Install [[page]]', 'Escaping {{' ],
- [ 'Install __TOC__', 'Install __TOC__', 'Escaping __' ],
- [ 'Install ', "Install \r", 'Removing \r' ],
-
- # Transform \t{1,2} into :{1,2}
- [ ':One indentation', "\tOne indentation", 'Replacing a single \t' ],
- [ '::Two indentations', "\t\tTwo indentations", 'Replacing 2 x \t' ],
-
- # Transform 'T123' links
- [
- '<span class="config-plainlink">[https://phabricator.wikimedia.org/T123 T123]</span>',
- 'T123', 'Testing T123 links' ],
- [
- 'bug <span class="config-plainlink">[https://phabricator.wikimedia.org/T123 T123]</span>',
- 'bug T123', 'Testing bug T123 links' ],
- [
- '(<span class="config-plainlink">[https://phabricator.wikimedia.org/T987654 T987654]</span>)',
- '(T987654)', 'Testing (T987654) links' ],
-
- # "Tabc" shouldn't work
- [ 'Tfoobar', 'Tfoobar', "Don't match T followed by non-digits" ],
- [ 'T!!fakefake!!', 'T!!fakefake!!', "Don't match T followed by non-digits" ],
-
- # Transform 'bug 123' links
- [
- '<span class="config-plainlink">[https://bugzilla.wikimedia.org/123 bug 123]</span>',
- 'bug 123', 'Testing bug 123 links' ],
- [
- '(<span class="config-plainlink">[https://bugzilla.wikimedia.org/987654 bug 987654]</span>)',
- '(bug 987654)', 'Testing (bug 987654) links' ],
-
- # "bug abc" shouldn't work
- [ 'bug foobar', 'bug foobar', "Don't match bug followed by non-digits" ],
- [ 'bug !!fakefake!!', 'bug !!fakefake!!', "Don't match bug followed by non-digits" ],
-
- # Transform '$wgFooBar' links
- [
- '<span class="config-plainlink">'
- . '[https://www.mediawiki.org/wiki/Manual:$wgFooBar $wgFooBar]</span>',
- '$wgFooBar', 'Testing basic $wgFooBar' ],
- [
- '<span class="config-plainlink">'
- . '[https://www.mediawiki.org/wiki/Manual:$wgFooBar45 $wgFooBar45]</span>',
- '$wgFooBar45', 'Testing $wgFooBar45 (with numbers)' ],
- [
- '<span class="config-plainlink">'
- . '[https://www.mediawiki.org/wiki/Manual:$wgFoo_Bar $wgFoo_Bar]</span>',
- '$wgFoo_Bar', 'Testing $wgFoo_Bar (with underscore)' ],
-
- # Icky variables that shouldn't link
- [
- '$myAwesomeVariable',
- '$myAwesomeVariable',
- 'Testing $myAwesomeVariable (not starting with $wg)'
- ],
- [ '$()not!a&Var', '$()not!a&Var', 'Testing $()not!a&Var (obviously not a variable)' ],
- ];
- }
-}
+++ /dev/null
-<?php
-
-/**
- * @group Database
- * @group Installer
- */
-class OracleInstallerTest extends MediaWikiTestCase {
-
- /**
- * @dataProvider provideOracleConnectStrings
- * @covers OracleInstaller::checkConnectStringFormat
- */
- public function testCheckConnectStringFormat( $expected, $connectString, $msg = '' ) {
- $validity = $expected ? 'should be valid' : 'should NOT be valid';
- $msg = "'$connectString' ($msg) $validity.";
- $this->assertEquals( $expected,
- OracleInstaller::checkConnectStringFormat( $connectString ),
- $msg
- );
- }
-
- /**
- * Provider to test OracleInstaller::checkConnectStringFormat()
- */
- function provideOracleConnectStrings() {
- // expected result, connectString[, message]
- return [
- [ true, 'simple_01', 'Simple TNS name' ],
- [ true, 'simple_01.world', 'TNS name with domain' ],
- [ true, 'simple_01.domain.net', 'TNS name with domain' ],
- [ true, 'host123', 'Host only' ],
- [ true, 'host123.domain.net', 'FQDN only' ],
- [ true, '//host123.domain.net', 'FQDN URL only' ],
- [ true, '123.223.213.132', 'Host IP only' ],
- [ true, 'host:1521', 'Host and port' ],
- [ true, 'host:1521/service', 'Host, port and service' ],
- [ true, 'host:1521/service:shared', 'Host, port, service and shared server type' ],
- [ true, 'host:1521/service:dedicated', 'Host, port, service and dedicated server type' ],
- [ true, 'host:1521/service:pooled', 'Host, port, service and pooled server type' ],
- [
- true,
- 'host:1521/service:shared/instance1',
- 'Host, port, service, server type and instance'
- ],
- [ true, 'host:1521//instance1', 'Host, port and instance' ],
- ];
- }
-
-}
+++ /dev/null
-<?php
-
-use MediaWiki\Interwiki\InterwikiLookupAdapter;
-
-/**
- * @covers MediaWiki\Interwiki\InterwikiLookupAdapter
- *
- * @group MediaWiki
- * @group Interwiki
- */
-class InterwikiLookupAdapterTest extends MediaWikiTestCase {
-
- /**
- * @var InterwikiLookupAdapter
- */
- private $interwikiLookup;
-
- protected function setUp() {
- parent::setUp();
-
- $this->interwikiLookup = new InterwikiLookupAdapter(
- $this->getSiteLookup( $this->getSites() )
- );
- }
-
- public function testIsValidInterwiki() {
- $this->assertTrue(
- $this->interwikiLookup->isValidInterwiki( 'enwt' ),
- 'enwt known prefix is valid'
- );
- $this->assertTrue(
- $this->interwikiLookup->isValidInterwiki( 'foo' ),
- 'foo site known prefix is valid'
- );
- $this->assertFalse(
- $this->interwikiLookup->isValidInterwiki( 'xyz' ),
- 'unknown prefix is not valid'
- );
- }
-
- public function testFetch() {
- $interwiki = $this->interwikiLookup->fetch( '' );
- $this->assertNull( $interwiki );
-
- $interwiki = $this->interwikiLookup->fetch( 'xyz' );
- $this->assertFalse( $interwiki );
-
- $interwiki = $this->interwikiLookup->fetch( 'foo' );
- $this->assertInstanceOf( Interwiki::class, $interwiki );
- $this->assertSame( 'foobar', $interwiki->getWikiID() );
-
- $interwiki = $this->interwikiLookup->fetch( 'enwt' );
- $this->assertInstanceOf( Interwiki::class, $interwiki );
-
- $this->assertSame( 'https://en.wiktionary.org/wiki/$1', $interwiki->getURL(), 'getURL' );
- $this->assertSame( 'https://en.wiktionary.org/w/api.php', $interwiki->getAPI(), 'getAPI' );
- $this->assertSame( 'enwiktionary', $interwiki->getWikiID(), 'getWikiID' );
- $this->assertTrue( $interwiki->isLocal(), 'isLocal' );
- }
-
- public function testGetAllPrefixes() {
- $foo = [
- 'iw_prefix' => 'foo',
- 'iw_url' => '',
- 'iw_api' => '',
- 'iw_wikiid' => 'foobar',
- 'iw_local' => false,
- 'iw_trans' => false,
- ];
- $enwt = [
- 'iw_prefix' => 'enwt',
- 'iw_url' => 'https://en.wiktionary.org/wiki/$1',
- 'iw_api' => 'https://en.wiktionary.org/w/api.php',
- 'iw_wikiid' => 'enwiktionary',
- 'iw_local' => true,
- 'iw_trans' => false,
- ];
-
- $this->assertEquals(
- [ $foo, $enwt ],
- $this->interwikiLookup->getAllPrefixes(),
- 'getAllPrefixes()'
- );
-
- $this->assertEquals(
- [ $foo ],
- $this->interwikiLookup->getAllPrefixes( false ),
- 'get external prefixes'
- );
-
- $this->assertEquals(
- [ $enwt ],
- $this->interwikiLookup->getAllPrefixes( true ),
- 'get local prefixes'
- );
- }
-
- private function getSiteLookup( SiteList $sites ) {
- $siteLookup = $this->getMockBuilder( SiteLookup::class )
- ->disableOriginalConstructor()
- ->getMock();
-
- $siteLookup->expects( $this->any() )
- ->method( 'getSites' )
- ->will( $this->returnValue( $sites ) );
-
- return $siteLookup;
- }
-
- private function getSites() {
- $sites = [];
-
- $site = new Site();
- $site->setGlobalId( 'foobar' );
- $site->addInterwikiId( 'foo' );
- $site->setSource( 'external' );
- $sites[] = $site;
-
- $site = new MediaWikiSite();
- $site->setGlobalId( 'enwiktionary' );
- $site->setGroup( 'wiktionary' );
- $site->setLanguageCode( 'en' );
- $site->addNavigationId( 'enwiktionary' );
- $site->addInterwikiId( 'enwt' );
- $site->setSource( 'local' );
- $site->setPath( MediaWikiSite::PATH_PAGE, "https://en.wiktionary.org/wiki/$1" );
- $site->setPath( MediaWikiSite::PATH_FILE, "https://en.wiktionary.org/w/$1" );
- $sites[] = $site;
-
- return new SiteList( $sites );
- }
-
-}
+++ /dev/null
-<?php
-
-class ReplicatedBagOStuffTest extends MediaWikiTestCase {
- /** @var HashBagOStuff */
- private $writeCache;
- /** @var HashBagOStuff */
- private $readCache;
- /** @var ReplicatedBagOStuff */
- private $cache;
-
- protected function setUp() {
- parent::setUp();
-
- $this->writeCache = new HashBagOStuff();
- $this->readCache = new HashBagOStuff();
- $this->cache = new ReplicatedBagOStuff( [
- 'writeFactory' => $this->writeCache,
- 'readFactory' => $this->readCache,
- ] );
- }
-
- /**
- * @covers ReplicatedBagOStuff::set
- */
- public function testSet() {
- $key = 'a key';
- $value = 'a value';
- $this->cache->set( $key, $value );
-
- // Write to master.
- $this->assertEquals( $value, $this->writeCache->get( $key ) );
- // Don't write to replica. Replication is deferred to backend.
- $this->assertFalse( $this->readCache->get( $key ) );
- }
-
- /**
- * @covers ReplicatedBagOStuff::get
- */
- public function testGet() {
- $key = 'a key';
-
- $write = 'one value';
- $this->writeCache->set( $key, $write );
- $read = 'another value';
- $this->readCache->set( $key, $read );
-
- // Read from replica.
- $this->assertEquals( $read, $this->cache->get( $key ) );
- }
-
- /**
- * @covers ReplicatedBagOStuff::get
- */
- public function testGetAbsent() {
- $key = 'a key';
- $value = 'a value';
- $this->writeCache->set( $key, $value );
-
- // Don't read from master. No failover if value is absent.
- $this->assertFalse( $this->cache->get( $key ) );
- }
-}
+++ /dev/null
-<?php
-
-/**
- * @group Media
- */
-class IPTCTest extends MediaWikiTestCase {
-
- /**
- * @covers IPTC::getCharset
- */
- public function testRecognizeUtf8() {
- // utf-8 is the only one used in practise.
- $res = IPTC::getCharset( "\x1b%G" );
- $this->assertEquals( 'UTF-8', $res );
- }
-
- /**
- * @covers IPTC::parse
- */
- public function testIPTCParseNoCharset88591() {
- // basically IPTC for keyword with value of 0xBC which is 1/4 in iso-8859-1
- // This data doesn't specify a charset. We're supposed to guess
- // (which basically means utf-8 if valid, windows 1252 (iso 8859-1) if not)
- $iptcData = "Photoshop 3.0\08BIM\4\4\0\0\0\0\0\x06\x1c\x02\x19\x00\x01\xBC";
- $res = IPTC::parse( $iptcData );
- $this->assertEquals( [ '¼' ], $res['Keywords'] );
- }
-
- /**
- * @covers IPTC::parse
- */
- public function testIPTCParseNoCharset88591b() {
- /* This one contains a sequence that's valid iso 8859-1 but not valid utf8 */
- /* \xC3 = Ã, \xB8 = ¸ */
- $iptcData = "Photoshop 3.0\08BIM\4\4\0\0\0\0\0\x09\x1c\x02\x19\x00\x04\xC3\xC3\xC3\xB8";
- $res = IPTC::parse( $iptcData );
- $this->assertEquals( [ 'ÃÃø' ], $res['Keywords'] );
- }
-
- /**
- * Same as testIPTCParseNoCharset88591b, but forcing the charset to utf-8.
- * What should happen is the first "\xC3\xC3" should be dropped as invalid,
- * leaving \xC3\xB8, which is ø
- * @covers IPTC::parse
- */
- public function testIPTCParseForcedUTFButInvalid() {
- $iptcData = "Photoshop 3.0\08BIM\4\4\0\0\0\0\0\x11\x1c\x02\x19\x00\x04\xC3\xC3\xC3\xB8"
- . "\x1c\x01\x5A\x00\x03\x1B\x25\x47";
- $res = IPTC::parse( $iptcData );
- $this->assertEquals( [ 'ø' ], $res['Keywords'] );
- }
-
- /**
- * @covers IPTC::parse
- */
- public function testIPTCParseNoCharsetUTF8() {
- $iptcData = "Photoshop 3.0\08BIM\4\4\0\0\0\0\0\x07\x1c\x02\x19\x00\x02¼";
- $res = IPTC::parse( $iptcData );
- $this->assertEquals( [ '¼' ], $res['Keywords'] );
- }
-
- /**
- * Testing something that has 2 values for keyword
- * @covers IPTC::parse
- */
- public function testIPTCParseMulti() {
- $iptcData = /* identifier */ "Photoshop 3.0\08BIM\4\4"
- /* length */ . "\0\0\0\0\0\x0D"
- . "\x1c\x02\x19" . "\x00\x01" . "\xBC"
- . "\x1c\x02\x19" . "\x00\x02" . "\xBC\xBD";
- $res = IPTC::parse( $iptcData );
- $this->assertEquals( [ '¼', '¼½' ], $res['Keywords'] );
- }
-
- /**
- * @covers IPTC::parse
- */
- public function testIPTCParseUTF8() {
- // This has the magic "\x1c\x01\x5A\x00\x03\x1B\x25\x47" which marks content as UTF8.
- $iptcData =
- "Photoshop 3.0\08BIM\4\4\0\0\0\0\0\x0F\x1c\x02\x19\x00\x02¼\x1c\x01\x5A\x00\x03\x1B\x25\x47";
- $res = IPTC::parse( $iptcData );
- $this->assertEquals( [ '¼' ], $res['Keywords'] );
- }
-}
+++ /dev/null
-<?php
-
-/**
- * @group Media
- */
-class MediaHandlerTest extends MediaWikiTestCase {
-
- /**
- * @covers MediaHandler::fitBoxWidth
- *
- * @dataProvider provideTestFitBoxWidth
- */
- public function testFitBoxWidth( $width, $height, $max, $expected ) {
- $y = round( $expected * $height / $width );
- $result = MediaHandler::fitBoxWidth( $width, $height, $max );
- $y2 = round( $result * $height / $width );
- $this->assertEquals( $expected,
- $result,
- "($width, $height, $max) wanted: {$expected}x$y, got: {z$result}x$y2" );
- }
-
- public static function provideTestFitBoxWidth() {
- return array_merge(
- static::generateTestFitBoxWidthData( 50, 50, [
- 50 => 50,
- 17 => 17,
- 18 => 18 ]
- ),
- static::generateTestFitBoxWidthData( 366, 300, [
- 50 => 61,
- 17 => 21,
- 18 => 22 ]
- ),
- static::generateTestFitBoxWidthData( 300, 366, [
- 50 => 41,
- 17 => 14,
- 18 => 15 ]
- ),
- static::generateTestFitBoxWidthData( 100, 400, [
- 50 => 12,
- 17 => 4,
- 18 => 4 ]
- )
- );
- }
-
- /**
- * Generate single test cases by combining the dimensions and tests contents
- *
- * It creates:
- * [$width, $height, $max, $expected],
- * [$width, $height, $max2, $expected2], ...
- * out of parameters:
- * $width, $height, { $max => $expected, $max2 => $expected2, ... }
- *
- * @param int $width
- * @param int $height
- * @param array $tests associative array of $max => $expected values
- * @return array
- */
- private static function generateTestFitBoxWidthData( $width, $height, $tests ) {
- $result = [];
- foreach ( $tests as $max => $expected ) {
- $result[] = [ $width, $height, $max, $expected ];
- }
- return $result;
- }
-}
+++ /dev/null
-<?php
-/**
- * @group BagOStuff
- */
-class MemcachedBagOStuffTest extends MediaWikiTestCase {
- /** @var MemcachedBagOStuff */
- private $cache;
-
- protected function setUp() {
- parent::setUp();
- $this->cache = new MemcachedPhpBagOStuff( [ 'keyspace' => 'test', 'servers' => [] ] );
- }
-
- /**
- * @covers MemcachedBagOStuff::makeKey
- */
- public function testKeyNormalization() {
- $this->assertEquals(
- 'test:vanilla',
- $this->cache->makeKey( 'vanilla' )
- );
-
- $this->assertEquals(
- 'test:punctuation_marks_are_ok:!@$^&*()',
- $this->cache->makeKey( 'punctuation_marks_are_ok', '!@$^&*()' )
- );
-
- $this->assertEquals(
- 'test:but_spaces:hashes%23:and%0Anewlines:are_not',
- $this->cache->makeKey( 'but spaces', 'hashes#', "and\nnewlines", 'are_not' )
- );
-
- $this->assertEquals(
- 'test:this:key:contains:%F0%9D%95%9E%F0%9D%95%A6%F0%9D%95%9D%F0%9D%95%A5%F0%9' .
- 'D%95%9A%F0%9D%95%93%F0%9D%95%AA%F0%9D%95%A5%F0%9D%95%96:characters',
- $this->cache->makeKey( 'this', 'key', 'contains', '𝕞𝕦𝕝𝕥𝕚𝕓𝕪𝕥𝕖', 'characters' )
- );
-
- $this->assertEquals(
- 'test:this:key:contains:#c118f92685a635cb843039de50014c9c',
- $this->cache->makeKey( 'this', 'key', 'contains', '𝕥𝕠𝕠 𝕞𝕒𝕟𝕪 𝕞𝕦𝕝𝕥𝕚𝕓𝕪𝕥𝕖 𝕔𝕙𝕒𝕣𝕒𝕔𝕥𝕖𝕣𝕤' )
- );
-
- $this->assertEquals(
- 'test:BagOStuff-long-key:##dc89dcb43b28614da27660240af478b5',
- $this->cache->makeKey( '𝕖𝕧𝕖𝕟', '𝕚𝕗', '𝕨𝕖', '𝕄𝔻𝟝', '𝕖𝕒𝕔𝕙',
- '𝕒𝕣𝕘𝕦𝕞𝕖𝕟𝕥', '𝕥𝕙𝕚𝕤', '𝕜𝕖𝕪', '𝕨𝕠𝕦𝕝𝕕', '𝕤𝕥𝕚𝕝𝕝', '𝕓𝕖', '𝕥𝕠𝕠', '𝕝𝕠𝕟𝕘' )
- );
-
- $this->assertEquals(
- 'test:%23%235820ad1d105aa4dc698585c39df73e19',
- $this->cache->makeKey( '##5820ad1d105aa4dc698585c39df73e19' )
- );
-
- $this->assertEquals(
- 'test:percent_is_escaped:!@$%25^&*()',
- $this->cache->makeKey( 'percent_is_escaped', '!@$%^&*()' )
- );
-
- $this->assertEquals(
- 'test:colon_is_escaped:!@$%3A^&*()',
- $this->cache->makeKey( 'colon_is_escaped', '!@$:^&*()' )
- );
-
- $this->assertEquals(
- 'test:long_key_part_hashed:#0244f7b1811d982dd932dd7de01465ac',
- $this->cache->makeKey( 'long_key_part_hashed', str_repeat( 'y', 500 ) )
- );
- }
-
- /**
- * @dataProvider validKeyProvider
- * @covers MemcachedBagOStuff::validateKeyEncoding
- */
- public function testValidateKeyEncoding( $key ) {
- $this->assertSame( $key, $this->cache->validateKeyEncoding( $key ) );
- }
-
- public function validKeyProvider() {
- return [
- 'empty' => [ '' ],
- 'digits' => [ '09' ],
- 'letters' => [ 'AZaz' ],
- 'ASCII special characters' => [ '!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~' ],
- ];
- }
-
- /**
- * @dataProvider invalidKeyProvider
- * @covers MemcachedBagOStuff::validateKeyEncoding
- */
- public function testValidateKeyEncodingThrowsException( $key ) {
- $this->setExpectedException( Exception::class );
- $this->cache->validateKeyEncoding( $key );
- }
-
- public function invalidKeyProvider() {
- return [
- [ "\x00" ],
- [ ' ' ],
- [ "\x1F" ],
- [ "\x7F" ],
- [ "\x80" ],
- [ "\xFF" ],
- ];
- }
-}
+++ /dev/null
-<?php
-/**
- * @group BagOStuff
- *
- * @covers RESTBagOStuff
- */
-class RESTBagOStuffTest extends MediaWikiTestCase {
-
- /**
- * @var MultiHttpClient
- */
- private $client;
- /**
- * @var RESTBagOStuff
- */
- private $bag;
-
- public function setUp() {
- parent::setUp();
- $this->client =
- $this->getMockBuilder( MultiHttpClient::class )
- ->setConstructorArgs( [ [] ] )
- ->setMethods( [ 'run' ] )
- ->getMock();
- $this->bag = new RESTBagOStuff( [ 'client' => $this->client, 'url' => 'http://test/rest/' ] );
- }
-
- public function testGet() {
- $this->client->expects( $this->once() )->method( 'run' )->with( [
- 'method' => 'GET',
- 'url' => 'http://test/rest/42xyz42',
- 'headers' => []
- // list( $rcode, $rdesc, $rhdrs, $rbody, $rerr )
- ] )->willReturn( [ 200, 'OK', [], '"somedata"', 0 ] );
- $result = $this->bag->get( '42xyz42' );
- $this->assertEquals( 'somedata', $result );
- }
-
- public function testGetNotExist() {
- $this->client->expects( $this->once() )->method( 'run' )->with( [
- 'method' => 'GET',
- 'url' => 'http://test/rest/42xyz42',
- 'headers' => []
- // list( $rcode, $rdesc, $rhdrs, $rbody, $rerr )
- ] )->willReturn( [ 404, 'Not found', [], 'Nothing to see here', 0 ] );
- $result = $this->bag->get( '42xyz42' );
- $this->assertFalse( $result );
- }
-
- public function testGetBadClient() {
- $this->client->expects( $this->once() )->method( 'run' )->with( [
- 'method' => 'GET',
- 'url' => 'http://test/rest/42xyz42',
- 'headers' => []
- // list( $rcode, $rdesc, $rhdrs, $rbody, $rerr )
- ] )->willReturn( [ 0, '', [], '', 'cURL has failed you today' ] );
- $result = $this->bag->get( '42xyz42' );
- $this->assertFalse( $result );
- $this->assertEquals( BagOStuff::ERR_UNREACHABLE, $this->bag->getLastError() );
- }
-
- public function testGetBadServer() {
- $this->client->expects( $this->once() )->method( 'run' )->with( [
- 'method' => 'GET',
- 'url' => 'http://test/rest/42xyz42',
- 'headers' => []
- // list( $rcode, $rdesc, $rhdrs, $rbody, $rerr )
- ] )->willReturn( [ 500, 'Too busy', [], 'Server is too busy', '' ] );
- $result = $this->bag->get( '42xyz42' );
- $this->assertFalse( $result );
- $this->assertEquals( BagOStuff::ERR_UNEXPECTED, $this->bag->getLastError() );
- }
-
- public function testPut() {
- $this->client->expects( $this->once() )->method( 'run' )->with( [
- 'method' => 'PUT',
- 'url' => 'http://test/rest/42xyz42',
- 'body' => '"postdata"',
- 'headers' => []
- // list( $rcode, $rdesc, $rhdrs, $rbody, $rerr )
- ] )->willReturn( [ 200, 'OK', [], 'Done', 0 ] );
- $result = $this->bag->set( '42xyz42', 'postdata' );
- $this->assertTrue( $result );
- }
-
- public function testDelete() {
- $this->client->expects( $this->once() )->method( 'run' )->with( [
- 'method' => 'DELETE',
- 'url' => 'http://test/rest/42xyz42',
- 'headers' => []
- // list( $rcode, $rdesc, $rhdrs, $rbody, $rerr )
- ] )->willReturn( [ 200, 'OK', [], 'Done', 0 ] );
- $result = $this->bag->delete( '42xyz42' );
- $this->assertTrue( $result );
- }
-}
* @group Database
*/
class ArticleTablesTest extends MediaWikiLangTestCase {
+
/**
* Make sure that T16404 doesn't strike again. We don't want
* templatelinks based on the user language when {{int:}} is used, only the
$title = Title::newFromText( 'T16404' );
$page = WikiPage::factory( $title );
$user = new User();
- $user->mRights = [ 'createpage', 'edit', 'purge' ];
+ $this->overrideUserPermissions( $user, [ 'createpage', 'edit', 'purge' ] );
$this->setContentLang( 'es' );
$this->setUserLang( 'fr' );
// Now, try the rollback
$admin->addGroup( 'sysop' ); // Make the test user a sysop
+ MediaWikiServices::getInstance()->getPermissionManager()->invalidateUsersRightsCache();
$token = $admin->getEditToken( 'rollback' );
$errors = $page->doRollback(
$secondUser->getName(),
$bClocks = $b->mParseStartTime;
- $a->mergeInternalMetaDataFrom( $b->object, 'b' );
+ $a->mergeInternalMetaDataFrom( $b->object );
$mergedClocks = $a->mParseStartTime;
foreach ( $mergedClocks as $clock => $timestamp ) {
$a->resetParseStartTime();
$aClocks = $a->mParseStartTime;
- $a->mergeInternalMetaDataFrom( $b->object, 'b' );
+ $a->mergeInternalMetaDataFrom( $b->object );
$mergedClocks = $a->mParseStartTime;
foreach ( $mergedClocks as $clock => $timestamp ) {
$a = new ParserOutput();
$a = TestingAccessWrapper::newFromObject( $a );
- $a->mergeInternalMetaDataFrom( $b->object, 'b' );
+ $a->mergeInternalMetaDataFrom( $b->object );
$mergedClocks = $a->mParseStartTime;
foreach ( $mergedClocks as $clock => $timestamp ) {
+++ /dev/null
-<?php
-
-/**
- * @group Parser
- * @covers MWTidy
- */
-class TidyTest extends MediaWikiTestCase {
-
- protected function setUp() {
- parent::setUp();
- if ( !MWTidy::isEnabled() ) {
- $this->markTestSkipped( 'Tidy not found' );
- }
- }
-
- /**
- * @dataProvider provideTestWrapping
- */
- public function testTidyWrapping( $expected, $text, $msg = '' ) {
- $text = MWTidy::tidy( $text );
- // We don't care about where Tidy wants to stick is <p>s
- $text = trim( preg_replace( '#</?p>#', '', $text ) );
- // Windows, we love you!
- $text = str_replace( "\r", '', $text );
- $this->assertEquals( $expected, $text, $msg );
- }
-
- public static function provideTestWrapping() {
- $testMathML = <<<'MathML'
-<math xmlns="http://www.w3.org/1998/Math/MathML">
- <mrow>
- <mi>a</mi>
- <mo>⁢</mo>
- <msup>
- <mi>x</mi>
- <mn>2</mn>
- </msup>
- <mo>+</mo>
- <mi>b</mi>
- <mo>⁢ </mo>
- <mi>x</mi>
- <mo>+</mo>
- <mi>c</mi>
- </mrow>
- </math>
-MathML;
- return [
- [
- '<mw:editsection page="foo" section="bar">foo</mw:editsection>',
- '<mw:editsection page="foo" section="bar">foo</mw:editsection>',
- '<mw:editsection> should survive tidy'
- ],
- [
- '<editsection page="foo" section="bar">foo</editsection>',
- '<editsection page="foo" section="bar">foo</editsection>',
- '<editsection> should survive tidy'
- ],
- [ '<mw:toc>foo</mw:toc>', '<mw:toc>foo</mw:toc>', '<mw:toc> should survive tidy' ],
- [ "<link foo=\"bar\" />foo", '<link foo="bar"/>foo', '<link> should survive tidy' ],
- [ "<meta foo=\"bar\" />foo", '<meta foo="bar"/>foo', '<meta> should survive tidy' ],
- [ $testMathML, $testMathML, '<math> should survive tidy' ],
- ];
- }
-}
+++ /dev/null
-<?php
-/**
- * Testing framework for the Password infrastructure
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- */
-
-/**
- * @covers InvalidPassword
- */
-class PasswordTest extends MediaWikiTestCase {
- public function testInvalidPlaintext() {
- $passwordFactory = new PasswordFactory();
- $invalid = $passwordFactory->newFromPlaintext( null );
-
- $this->assertInstanceOf( InvalidPassword::class, $invalid );
- }
-}
+++ /dev/null
-<?php
-/**
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- */
-
-use MediaWiki\Preferences\IntvalFilter;
-use MediaWiki\Preferences\MultiUsernameFilter;
-use MediaWiki\Preferences\TimezoneFilter;
-
-/**
- * @group Preferences
- */
-class FiltersTest extends MediaWikiTestCase {
- /**
- * @covers MediaWiki\Preferences\IntvalFilter::filterFromForm()
- * @covers MediaWiki\Preferences\IntvalFilter::filterForForm()
- */
- public function testIntvalFilter() {
- $filter = new IntvalFilter();
- self::assertSame( 0, $filter->filterFromForm( '0' ) );
- self::assertSame( 3, $filter->filterFromForm( '3' ) );
- self::assertSame( '123', $filter->filterForForm( '123' ) );
- }
-
- /**
- * @covers MediaWiki\Preferences\TimezoneFilter::filterFromForm()
- * @dataProvider provideTimezoneFilter
- *
- * @param string $input
- * @param string $expected
- */
- public function testTimezoneFilter( $input, $expected ) {
- $filter = new TimezoneFilter();
- $result = $filter->filterFromForm( $input );
- self::assertEquals( $expected, $result );
- }
-
- public function provideTimezoneFilter() {
- return [
- [ 'ZoneInfo', 'Offset|0' ],
- [ 'ZoneInfo|bogus', 'Offset|0' ],
- [ 'System', 'System' ],
- [ '2:30', 'Offset|150' ],
- ];
- }
-
- /**
- * @covers MediaWiki\Preferences\MultiUsernameFilter::filterFromForm()
- * @dataProvider provideMultiUsernameFilterFrom
- *
- * @param string $input
- * @param string|null $expected
- */
- public function testMultiUsernameFilterFrom( $input, $expected ) {
- $filter = $this->makeMultiUsernameFilter();
- $result = $filter->filterFromForm( $input );
- self::assertSame( $expected, $result );
- }
-
- public function provideMultiUsernameFilterFrom() {
- return [
- [ '', null ],
- [ "\n\n\n", null ],
- [ 'Foo', '1' ],
- [ "\n\n\nFoo\nBar\n", "1\n2" ],
- [ "Baz\nInvalid\nFoo", "3\n1" ],
- [ "Invalid", null ],
- [ "Invalid\n\n\nInvalid\n", null ],
- ];
- }
-
- /**
- * @covers MediaWiki\Preferences\MultiUsernameFilter::filterForForm()
- * @dataProvider provideMultiUsernameFilterFor
- *
- * @param string $input
- * @param string $expected
- */
- public function testMultiUsernameFilterFor( $input, $expected ) {
- $filter = $this->makeMultiUsernameFilter();
- $result = $filter->filterForForm( $input );
- self::assertSame( $expected, $result );
- }
-
- public function provideMultiUsernameFilterFor() {
- return [
- [ '', '' ],
- [ "\n", '' ],
- [ '1', 'Foo' ],
- [ "\n1\n\n2\377\n", "Foo\nBar" ],
- [ "666\n667", '' ],
- ];
- }
-
- private function makeMultiUsernameFilter() {
- $userMapping = [
- 'Foo' => 1,
- 'Bar' => 2,
- 'Baz' => 3,
- ];
- $flipped = array_flip( $userMapping );
- $idLookup = self::getMockBuilder( CentralIdLookup::class )
- ->disableOriginalConstructor()
- ->setMethods( [ 'centralIdsFromNames', 'namesFromCentralIds' ] )
- ->getMockForAbstractClass();
-
- $idLookup->method( 'centralIdsFromNames' )
- ->will( self::returnCallback( function ( $names ) use ( $userMapping ) {
- $ids = [];
- foreach ( $names as $name ) {
- $ids[] = $userMapping[$name] ?? null;
- }
- return array_filter( $ids, 'is_numeric' );
- } ) );
- $idLookup->method( 'namesFromCentralIds' )
- ->will( self::returnCallback( function ( $ids ) use ( $flipped ) {
- $names = [];
- foreach ( $ids as $id ) {
- $names[] = $flipped[$id] ?? null;
- }
- return array_filter( $names, 'is_string' );
- } ) );
-
- return new MultiUsernameFilter( $idLookup );
- }
-}
+++ /dev/null
-<?php
-
-use Wikimedia\TestingAccessWrapper;
-
-/**
- * @covers ExtensionProcessor
- */
-class ExtensionProcessorTest extends MediaWikiTestCase {
-
- private $dir, $dirname;
-
- public function setUp() {
- parent::setUp();
- $this->dir = __DIR__ . '/FooBar/extension.json';
- $this->dirname = dirname( $this->dir );
- }
-
- /**
- * 'name' is absolutely required
- *
- * @var array
- */
- public static $default = [
- 'name' => 'FooBar',
- ];
-
- public function testExtractInfo() {
- // Test that attributes that begin with @ are ignored
- $processor = new ExtensionProcessor();
- $processor->extractInfo( $this->dir, self::$default + [
- '@metadata' => [ 'foobarbaz' ],
- 'AnAttribute' => [ 'omg' ],
- 'AutoloadClasses' => [ 'FooBar' => 'includes/FooBar.php' ],
- 'SpecialPages' => [ 'Foo' => 'SpecialFoo' ],
- 'callback' => 'FooBar::onRegistration',
- ], 1 );
-
- $extracted = $processor->getExtractedInfo();
- $attributes = $extracted['attributes'];
- $this->assertArrayHasKey( 'AnAttribute', $attributes );
- $this->assertArrayNotHasKey( '@metadata', $attributes );
- $this->assertArrayNotHasKey( 'AutoloadClasses', $attributes );
- $this->assertSame(
- [ 'FooBar' => 'FooBar::onRegistration' ],
- $extracted['callbacks']
- );
- $this->assertSame(
- [ 'Foo' => 'SpecialFoo' ],
- $extracted['globals']['wgSpecialPages']
- );
- }
-
- public function testExtractNamespaces() {
- // Test that namespace IDs can be overwritten
- if ( !defined( 'MW_EXTENSION_PROCESSOR_TEST_EXTRACT_INFO_X' ) ) {
- define( 'MW_EXTENSION_PROCESSOR_TEST_EXTRACT_INFO_X', 123456 );
- }
-
- $processor = new ExtensionProcessor();
- $processor->extractInfo( $this->dir, self::$default + [
- 'namespaces' => [
- [
- 'id' => 332200,
- 'constant' => 'MW_EXTENSION_PROCESSOR_TEST_EXTRACT_INFO_A',
- 'name' => 'Test_A',
- 'defaultcontentmodel' => 'TestModel',
- 'gender' => [
- 'male' => 'Male test',
- 'female' => 'Female test',
- ],
- 'subpages' => true,
- 'content' => true,
- 'protection' => 'userright',
- ],
- [ // Test_X will use ID 123456 not 334400
- 'id' => 334400,
- 'constant' => 'MW_EXTENSION_PROCESSOR_TEST_EXTRACT_INFO_X',
- 'name' => 'Test_X',
- 'defaultcontentmodel' => 'TestModel'
- ],
- ]
- ], 1 );
-
- $extracted = $processor->getExtractedInfo();
-
- $this->assertArrayHasKey(
- 'MW_EXTENSION_PROCESSOR_TEST_EXTRACT_INFO_A',
- $extracted['defines']
- );
- $this->assertArrayNotHasKey(
- 'MW_EXTENSION_PROCESSOR_TEST_EXTRACT_INFO_X',
- $extracted['defines']
- );
-
- $this->assertSame(
- $extracted['defines']['MW_EXTENSION_PROCESSOR_TEST_EXTRACT_INFO_A'],
- 332200
- );
-
- $this->assertArrayHasKey( 'ExtensionNamespaces', $extracted['attributes'] );
- $this->assertArrayHasKey( 123456, $extracted['attributes']['ExtensionNamespaces'] );
- $this->assertArrayHasKey( 332200, $extracted['attributes']['ExtensionNamespaces'] );
- $this->assertArrayNotHasKey( 334400, $extracted['attributes']['ExtensionNamespaces'] );
-
- $this->assertSame( 'Test_X', $extracted['attributes']['ExtensionNamespaces'][123456] );
- $this->assertSame( 'Test_A', $extracted['attributes']['ExtensionNamespaces'][332200] );
- $this->assertSame(
- [ 'male' => 'Male test', 'female' => 'Female test' ],
- $extracted['globals']['wgExtraGenderNamespaces'][332200]
- );
- // A has subpages, X does not
- $this->assertTrue( $extracted['globals']['wgNamespacesWithSubpages'][332200] );
- $this->assertArrayNotHasKey( 123456, $extracted['globals']['wgNamespacesWithSubpages'] );
- }
-
- public static function provideRegisterHooks() {
- $merge = [ ExtensionRegistry::MERGE_STRATEGY => 'array_merge_recursive' ];
- // Format:
- // Current $wgHooks
- // Content in extension.json
- // Expected value of $wgHooks
- return [
- // No hooks
- [
- [],
- self::$default,
- $merge,
- ],
- // No current hooks, adding one for "FooBaz" in string format
- [
- [],
- [ 'Hooks' => [ 'FooBaz' => 'FooBazCallback' ] ] + self::$default,
- [ 'FooBaz' => [ 'FooBazCallback' ] ] + $merge,
- ],
- // Hook for "FooBaz", adding another one
- [
- [ 'FooBaz' => [ 'PriorCallback' ] ],
- [ 'Hooks' => [ 'FooBaz' => 'FooBazCallback' ] ] + self::$default,
- [ 'FooBaz' => [ 'PriorCallback', 'FooBazCallback' ] ] + $merge,
- ],
- // No current hooks, adding one for "FooBaz" in verbose array format
- [
- [],
- [ 'Hooks' => [ 'FooBaz' => [ 'FooBazCallback' ] ] ] + self::$default,
- [ 'FooBaz' => [ 'FooBazCallback' ] ] + $merge,
- ],
- // Hook for "BarBaz", adding one for "FooBaz"
- [
- [ 'BarBaz' => [ 'BarBazCallback' ] ],
- [ 'Hooks' => [ 'FooBaz' => 'FooBazCallback' ] ] + self::$default,
- [
- 'BarBaz' => [ 'BarBazCallback' ],
- 'FooBaz' => [ 'FooBazCallback' ],
- ] + $merge,
- ],
- // Callbacks for FooBaz wrapped in an array
- [
- [],
- [ 'Hooks' => [ 'FooBaz' => [ 'Callback1' ] ] ] + self::$default,
- [
- 'FooBaz' => [ 'Callback1' ],
- ] + $merge,
- ],
- // Multiple callbacks for FooBaz hook
- [
- [],
- [ 'Hooks' => [ 'FooBaz' => [ 'Callback1', 'Callback2' ] ] ] + self::$default,
- [
- 'FooBaz' => [ 'Callback1', 'Callback2' ],
- ] + $merge,
- ],
- ];
- }
-
- /**
- * @dataProvider provideRegisterHooks
- */
- public function testRegisterHooks( $pre, $info, $expected ) {
- $processor = new MockExtensionProcessor( [ 'wgHooks' => $pre ] );
- $processor->extractInfo( $this->dir, $info, 1 );
- $extracted = $processor->getExtractedInfo();
- $this->assertEquals( $expected, $extracted['globals']['wgHooks'] );
- }
-
- public function testExtractConfig1() {
- $processor = new ExtensionProcessor;
- $info = [
- 'config' => [
- 'Bar' => 'somevalue',
- 'Foo' => 10,
- '@IGNORED' => 'yes',
- ],
- ] + self::$default;
- $info2 = [
- 'config' => [
- '_prefix' => 'eg',
- 'Bar' => 'somevalue'
- ],
- 'name' => 'FooBar2',
- ];
- $processor->extractInfo( $this->dir, $info, 1 );
- $processor->extractInfo( $this->dir, $info2, 1 );
- $extracted = $processor->getExtractedInfo();
- $this->assertEquals( 'somevalue', $extracted['globals']['wgBar'] );
- $this->assertEquals( 10, $extracted['globals']['wgFoo'] );
- $this->assertArrayNotHasKey( 'wg@IGNORED', $extracted['globals'] );
- // Custom prefix:
- $this->assertEquals( 'somevalue', $extracted['globals']['egBar'] );
- }
-
- public function testExtractConfig2() {
- $processor = new ExtensionProcessor;
- $info = [
- 'config' => [
- 'Bar' => [ 'value' => 'somevalue' ],
- 'Foo' => [ 'value' => 10 ],
- 'Path' => [ 'value' => 'foo.txt', 'path' => true ],
- 'Namespaces' => [
- 'value' => [
- '10' => true,
- '12' => false,
- ],
- 'merge_strategy' => 'array_plus',
- ],
- ],
- ] + self::$default;
- $info2 = [
- 'config' => [
- 'Bar' => [ 'value' => 'somevalue' ],
- ],
- 'config_prefix' => 'eg',
- 'name' => 'FooBar2',
- ];
- $processor->extractInfo( $this->dir, $info, 2 );
- $processor->extractInfo( $this->dir, $info2, 2 );
- $extracted = $processor->getExtractedInfo();
- $this->assertEquals( 'somevalue', $extracted['globals']['wgBar'] );
- $this->assertEquals( 10, $extracted['globals']['wgFoo'] );
- $this->assertEquals( "{$this->dirname}/foo.txt", $extracted['globals']['wgPath'] );
- // Custom prefix:
- $this->assertEquals( 'somevalue', $extracted['globals']['egBar'] );
- $this->assertSame(
- [ 10 => true, 12 => false, ExtensionRegistry::MERGE_STRATEGY => 'array_plus' ],
- $extracted['globals']['wgNamespaces']
- );
- }
-
- /**
- * @expectedException RuntimeException
- */
- public function testDuplicateConfigKey1() {
- $processor = new ExtensionProcessor;
- $info = [
- 'config' => [
- 'Bar' => '',
- ]
- ] + self::$default;
- $info2 = [
- 'config' => [
- 'Bar' => 'g',
- ],
- 'name' => 'FooBar2',
- ];
- $processor->extractInfo( $this->dir, $info, 1 );
- $processor->extractInfo( $this->dir, $info2, 1 );
- }
-
- /**
- * @expectedException RuntimeException
- */
- public function testDuplicateConfigKey2() {
- $processor = new ExtensionProcessor;
- $info = [
- 'config' => [
- 'Bar' => [ 'value' => 'somevalue' ],
- ]
- ] + self::$default;
- $info2 = [
- 'config' => [
- 'Bar' => [ 'value' => 'somevalue' ],
- ],
- 'name' => 'FooBar2',
- ];
- $processor->extractInfo( $this->dir, $info, 2 );
- $processor->extractInfo( $this->dir, $info2, 2 );
- }
-
- public static function provideExtractExtensionMessagesFiles() {
- $dir = __DIR__ . '/FooBar/';
- return [
- [
- [ 'ExtensionMessagesFiles' => [ 'FooBarAlias' => 'FooBar.alias.php' ] ],
- [ 'wgExtensionMessagesFiles' => [ 'FooBarAlias' => $dir . 'FooBar.alias.php' ] ]
- ],
- [
- [
- 'ExtensionMessagesFiles' => [
- 'FooBarAlias' => 'FooBar.alias.php',
- 'FooBarMagic' => 'FooBar.magic.i18n.php',
- ],
- ],
- [
- 'wgExtensionMessagesFiles' => [
- 'FooBarAlias' => $dir . 'FooBar.alias.php',
- 'FooBarMagic' => $dir . 'FooBar.magic.i18n.php',
- ],
- ],
- ],
- ];
- }
-
- /**
- * @dataProvider provideExtractExtensionMessagesFiles
- */
- public function testExtractExtensionMessagesFiles( $input, $expected ) {
- $processor = new ExtensionProcessor();
- $processor->extractInfo( $this->dir, $input + self::$default, 1 );
- $out = $processor->getExtractedInfo();
- foreach ( $expected as $key => $value ) {
- $this->assertEquals( $value, $out['globals'][$key] );
- }
- }
-
- public static function provideExtractMessagesDirs() {
- $dir = __DIR__ . '/FooBar/';
- return [
- [
- [ 'MessagesDirs' => [ 'VisualEditor' => 'i18n' ] ],
- [ 'wgMessagesDirs' => [ 'VisualEditor' => [ $dir . 'i18n' ] ] ]
- ],
- [
- [ 'MessagesDirs' => [ 'VisualEditor' => [ 'i18n', 'foobar' ] ] ],
- [ 'wgMessagesDirs' => [ 'VisualEditor' => [ $dir . 'i18n', $dir . 'foobar' ] ] ]
- ],
- ];
- }
-
- /**
- * @dataProvider provideExtractMessagesDirs
- */
- public function testExtractMessagesDirs( $input, $expected ) {
- $processor = new ExtensionProcessor();
- $processor->extractInfo( $this->dir, $input + self::$default, 1 );
- $out = $processor->getExtractedInfo();
- foreach ( $expected as $key => $value ) {
- $this->assertEquals( $value, $out['globals'][$key] );
- }
- }
-
- public function testExtractCredits() {
- $processor = new ExtensionProcessor();
- $processor->extractInfo( $this->dir, self::$default, 1 );
- $this->setExpectedException( Exception::class );
- $processor->extractInfo( $this->dir, self::$default, 1 );
- }
-
- /**
- * @dataProvider provideExtractResourceLoaderModules
- */
- public function testExtractResourceLoaderModules(
- $input,
- array $expectedGlobals,
- array $expectedAttribs = []
- ) {
- $processor = new ExtensionProcessor();
- $processor->extractInfo( $this->dir, $input + self::$default, 1 );
- $out = $processor->getExtractedInfo();
- foreach ( $expectedGlobals as $key => $value ) {
- $this->assertEquals( $value, $out['globals'][$key] );
- }
- foreach ( $expectedAttribs as $key => $value ) {
- $this->assertEquals( $value, $out['attributes'][$key] );
- }
- }
-
- public static function provideExtractResourceLoaderModules() {
- $dir = __DIR__ . '/FooBar';
- return [
- // Generic module with localBasePath/remoteExtPath specified
- [
- // Input
- [
- 'ResourceModules' => [
- 'test.foo' => [
- 'styles' => 'foobar.js',
- 'localBasePath' => '',
- 'remoteExtPath' => 'FooBar',
- ],
- ],
- ],
- // Expected
- [
- 'wgResourceModules' => [
- 'test.foo' => [
- 'styles' => 'foobar.js',
- 'localBasePath' => $dir,
- 'remoteExtPath' => 'FooBar',
- ],
- ],
- ],
- ],
- // ResourceFileModulePaths specified:
- [
- // Input
- [
- 'ResourceFileModulePaths' => [
- 'localBasePath' => 'modules',
- 'remoteExtPath' => 'FooBar/modules',
- ],
- 'ResourceModules' => [
- // No paths
- 'test.foo' => [
- 'styles' => 'foo.js',
- ],
- // Different paths set
- 'test.bar' => [
- 'styles' => 'bar.js',
- 'localBasePath' => 'subdir',
- 'remoteExtPath' => 'FooBar/subdir',
- ],
- // Custom class with no paths set
- 'test.class' => [
- 'class' => 'FooBarModule',
- 'extra' => 'argument',
- ],
- // Custom class with a localBasePath
- 'test.class.with.path' => [
- 'class' => 'FooBarPathModule',
- 'extra' => 'argument',
- 'localBasePath' => '',
- ]
- ],
- ],
- // Expected
- [
- 'wgResourceModules' => [
- 'test.foo' => [
- 'styles' => 'foo.js',
- 'localBasePath' => "$dir/modules",
- 'remoteExtPath' => 'FooBar/modules',
- ],
- 'test.bar' => [
- 'styles' => 'bar.js',
- 'localBasePath' => "$dir/subdir",
- 'remoteExtPath' => 'FooBar/subdir',
- ],
- 'test.class' => [
- 'class' => 'FooBarModule',
- 'extra' => 'argument',
- 'localBasePath' => "$dir/modules",
- 'remoteExtPath' => 'FooBar/modules',
- ],
- 'test.class.with.path' => [
- 'class' => 'FooBarPathModule',
- 'extra' => 'argument',
- 'localBasePath' => $dir,
- 'remoteExtPath' => 'FooBar/modules',
- ]
- ],
- ],
- ],
- // ResourceModuleSkinStyles with file module paths
- [
- // Input
- [
- 'ResourceFileModulePaths' => [
- 'localBasePath' => '',
- 'remoteSkinPath' => 'FooBar',
- ],
- 'ResourceModuleSkinStyles' => [
- 'foobar' => [
- 'test.foo' => 'foo.css',
- ]
- ],
- ],
- // Expected
- [
- 'wgResourceModuleSkinStyles' => [
- 'foobar' => [
- 'test.foo' => 'foo.css',
- 'localBasePath' => $dir,
- 'remoteSkinPath' => 'FooBar',
- ],
- ],
- ],
- ],
- // ResourceModuleSkinStyles with file module paths and an override
- [
- // Input
- [
- 'ResourceFileModulePaths' => [
- 'localBasePath' => '',
- 'remoteSkinPath' => 'FooBar',
- ],
- 'ResourceModuleSkinStyles' => [
- 'foobar' => [
- 'test.foo' => 'foo.css',
- 'remoteSkinPath' => 'BarFoo'
- ],
- ],
- ],
- // Expected
- [
- 'wgResourceModuleSkinStyles' => [
- 'foobar' => [
- 'test.foo' => 'foo.css',
- 'localBasePath' => $dir,
- 'remoteSkinPath' => 'BarFoo',
- ],
- ],
- ],
- ],
- 'QUnit test module' => [
- // Input
- [
- 'QUnitTestModule' => [
- 'localBasePath' => '',
- 'remoteExtPath' => 'Foo',
- 'scripts' => 'bar.js',
- ],
- ],
- // Expected
- [],
- [
- 'QUnitTestModules' => [
- 'test.FooBar' => [
- 'localBasePath' => $dir,
- 'remoteExtPath' => 'Foo',
- 'scripts' => 'bar.js',
- ],
- ],
- ],
- ],
- ];
- }
-
- public static function provideSetToGlobal() {
- return [
- [
- [ 'wgAPIModules', 'wgAvailableRights' ],
- [],
- [
- 'APIModules' => [ 'foobar' => 'ApiFooBar' ],
- 'AvailableRights' => [ 'foobar', 'unfoobar' ],
- ],
- [
- 'wgAPIModules' => [ 'foobar' => 'ApiFooBar' ],
- 'wgAvailableRights' => [ 'foobar', 'unfoobar' ],
- ],
- ],
- [
- [ 'wgAPIModules', 'wgAvailableRights' ],
- [
- 'wgAPIModules' => [ 'barbaz' => 'ApiBarBaz' ],
- 'wgAvailableRights' => [ 'barbaz' ]
- ],
- [
- 'APIModules' => [ 'foobar' => 'ApiFooBar' ],
- 'AvailableRights' => [ 'foobar', 'unfoobar' ],
- ],
- [
- 'wgAPIModules' => [ 'barbaz' => 'ApiBarBaz', 'foobar' => 'ApiFooBar' ],
- 'wgAvailableRights' => [ 'barbaz', 'foobar', 'unfoobar' ],
- ],
- ],
- [
- [ 'wgGroupPermissions' ],
- [
- 'wgGroupPermissions' => [
- 'sysop' => [ 'delete' ]
- ],
- ],
- [
- 'GroupPermissions' => [
- 'sysop' => [ 'undelete' ],
- 'user' => [ 'edit' ]
- ],
- ],
- [
- 'wgGroupPermissions' => [
- 'sysop' => [ 'delete', 'undelete' ],
- 'user' => [ 'edit' ]
- ],
- ]
- ]
- ];
- }
-
- /**
- * Attributes under manifest_version 2
- */
- public function testExtractAttributes() {
- $processor = new ExtensionProcessor();
- // Load FooBar extension
- $processor->extractInfo( $this->dir, [ 'name' => 'FooBar' ], 2 );
- $processor->extractInfo(
- $this->dir,
- [
- 'name' => 'Baz',
- 'attributes' => [
- // Loaded
- 'FooBar' => [
- 'Plugins' => [
- 'ext.baz.foobar',
- ],
- ],
- // Not loaded
- 'FizzBuzz' => [
- 'MorePlugins' => [
- 'ext.baz.fizzbuzz',
- ],
- ],
- ],
- ],
- 2
- );
-
- $info = $processor->getExtractedInfo();
- $this->assertArrayHasKey( 'FooBarPlugins', $info['attributes'] );
- $this->assertSame( [ 'ext.baz.foobar' ], $info['attributes']['FooBarPlugins'] );
- $this->assertArrayNotHasKey( 'FizzBuzzMorePlugins', $info['attributes'] );
- }
-
- /**
- * Attributes under manifest_version 1
- */
- public function testAttributes1() {
- $processor = new ExtensionProcessor();
- $processor->extractInfo(
- $this->dir,
- [
- 'name' => 'FooBar',
- 'FooBarPlugins' => [
- 'ext.baz.foobar',
- ],
- 'FizzBuzzMorePlugins' => [
- 'ext.baz.fizzbuzz',
- ],
- ],
- 1
- );
- $processor->extractInfo(
- $this->dir,
- [
- 'name' => 'FooBar2',
- 'FizzBuzzMorePlugins' => [
- 'ext.bar.fizzbuzz',
- ]
- ],
- 1
- );
-
- $info = $processor->getExtractedInfo();
- $this->assertArrayHasKey( 'FooBarPlugins', $info['attributes'] );
- $this->assertSame( [ 'ext.baz.foobar' ], $info['attributes']['FooBarPlugins'] );
- $this->assertArrayHasKey( 'FizzBuzzMorePlugins', $info['attributes'] );
- $this->assertSame(
- [ 'ext.baz.fizzbuzz', 'ext.bar.fizzbuzz' ],
- $info['attributes']['FizzBuzzMorePlugins']
- );
- }
-
- public function testAttributes1_notarray() {
- $processor = new ExtensionProcessor();
- $this->setExpectedException(
- InvalidArgumentException::class,
- "The value for 'FooBarPlugins' should be an array (from {$this->dir})"
- );
- $processor->extractInfo(
- $this->dir,
- [
- 'FooBarPlugins' => 'ext.baz.foobar',
- ] + self::$default,
- 1
- );
- }
-
- public function testExtractPathBasedGlobal() {
- $processor = new ExtensionProcessor();
- $processor->extractInfo(
- $this->dir,
- [
- 'ParserTestFiles' => [
- 'tests/parserTests.txt',
- 'tests/extraParserTests.txt',
- ],
- 'ServiceWiringFiles' => [
- 'includes/ServiceWiring.php'
- ],
- ] + self::$default,
- 1
- );
- $globals = $processor->getExtractedInfo()['globals'];
- $this->assertArrayHasKey( 'wgParserTestFiles', $globals );
- $this->assertSame( [
- "{$this->dirname}/tests/parserTests.txt",
- "{$this->dirname}/tests/extraParserTests.txt"
- ], $globals['wgParserTestFiles'] );
- $this->assertArrayHasKey( 'wgServiceWiringFiles', $globals );
- $this->assertSame( [
- "{$this->dirname}/includes/ServiceWiring.php"
- ], $globals['wgServiceWiringFiles'] );
- }
-
- public function testGetRequirements() {
- $info = self::$default + [
- 'requires' => [
- 'MediaWiki' => '>= 1.25.0',
- 'platform' => [
- 'php' => '>= 5.5.9'
- ],
- 'extensions' => [
- 'Bar' => '*'
- ]
- ]
- ];
- $processor = new ExtensionProcessor();
- $this->assertSame(
- $info['requires'],
- $processor->getRequirements( $info, false )
- );
- $this->assertSame(
- [],
- $processor->getRequirements( [], false )
- );
- }
-
- public function testGetDevRequirements() {
- $info = self::$default + [
- 'dev-requires' => [
- 'MediaWiki' => '>= 1.31.0',
- 'platform' => [
- 'ext-foo' => '*',
- ],
- 'skins' => [
- 'Baz' => '*',
- ],
- 'extensions' => [
- 'Biz' => '*',
- ],
- ],
- ];
- $processor = new ExtensionProcessor();
- $this->assertSame(
- $info['dev-requires'],
- $processor->getRequirements( $info, true )
- );
- // Set some standard requirements, so we can test merging
- $info['requires'] = [
- 'MediaWiki' => '>= 1.25.0',
- 'platform' => [
- 'php' => '>= 5.5.9'
- ],
- 'extensions' => [
- 'Bar' => '*'
- ]
- ];
- $this->assertSame(
- [
- 'MediaWiki' => '>= 1.25.0 >= 1.31.0',
- 'platform' => [
- 'php' => '>= 5.5.9',
- 'ext-foo' => '*',
- ],
- 'extensions' => [
- 'Bar' => '*',
- 'Biz' => '*',
- ],
- 'skins' => [
- 'Baz' => '*',
- ],
- ],
- $processor->getRequirements( $info, true )
- );
-
- // If there's no dev-requires, it just returns requires
- unset( $info['dev-requires'] );
- $this->assertSame(
- $info['requires'],
- $processor->getRequirements( $info, true )
- );
- }
-
- public function testGetExtraAutoloaderPaths() {
- $processor = new ExtensionProcessor();
- $this->assertSame(
- [ "{$this->dirname}/vendor/autoload.php" ],
- $processor->getExtraAutoloaderPaths( $this->dirname, [
- 'load_composer_autoloader' => true,
- ] )
- );
- }
-
- /**
- * Verify that extension.schema.json is in sync with ExtensionProcessor
- *
- * @coversNothing
- */
- public function testGlobalSettingsDocumentedInSchema() {
- global $IP;
- $globalSettings = TestingAccessWrapper::newFromClass(
- ExtensionProcessor::class )->globalSettings;
-
- $version = ExtensionRegistry::MANIFEST_VERSION;
- $schema = FormatJson::decode(
- file_get_contents( "$IP/docs/extension.schema.v$version.json" ),
- true
- );
- $missing = [];
- foreach ( $globalSettings as $global ) {
- if ( !isset( $schema['properties'][$global] ) ) {
- $missing[] = $global;
- }
- }
-
- $this->assertEquals( [], $missing,
- "The following global settings are not documented in docs/extension.schema.json" );
- }
-}
-
-/**
- * Allow overriding the default value of $this->globals
- * so we can test merging
- */
-class MockExtensionProcessor extends ExtensionProcessor {
- public function __construct( $globals = [] ) {
- $this->globals = $globals + $this->globals;
- }
-}
+++ /dev/null
-<?php
-
-/**
- * @group Search
- * @covers SearchIndexFieldDefinition
- */
-class SearchIndexFieldTest extends MediaWikiTestCase {
-
- public function getMergeCases() {
- return [
- [ 0, 'test', 0, 'test', true ],
- [ SearchIndexField::INDEX_TYPE_NESTED, 'test',
- SearchIndexField::INDEX_TYPE_NESTED, 'test', false ],
- [ 0, 'test', 0, 'test2', true ],
- [ 0, 'test', 1, 'test', false ],
- ];
- }
-
- /**
- * @dataProvider getMergeCases
- * @param int $t1
- * @param string $n1
- * @param int $t2
- * @param string $n2
- * @param bool $result
- */
- public function testMerge( $t1, $n1, $t2, $n2, $result ) {
- $field1 =
- $this->getMockBuilder( SearchIndexFieldDefinition::class )
- ->setMethods( [ 'getMapping' ] )
- ->setConstructorArgs( [ $n1, $t1 ] )
- ->getMock();
- $field2 =
- $this->getMockBuilder( SearchIndexFieldDefinition::class )
- ->setMethods( [ 'getMapping' ] )
- ->setConstructorArgs( [ $n2, $t2 ] )
- ->getMock();
-
- if ( $result ) {
- $this->assertNotFalse( $field1->merge( $field2 ) );
- } else {
- $this->assertFalse( $field1->merge( $field2 ) );
- }
-
- $field1->setFlag( 0xFF );
- $this->assertFalse( $field1->merge( $field2 ) );
-
- $field1->setMergeCallback(
- function ( $a, $b ) {
- return "test";
- }
- );
- $this->assertEquals( "test", $field1->merge( $field2 ) );
- }
-
-}
+++ /dev/null
-<?php
-
-namespace MediaWiki\Session;
-
-use MediaWikiTestCase;
-
-/**
- * @group Session
- * @covers MediaWiki\Session\MetadataMergeException
- */
-class MetadataMergeExceptionTest extends MediaWikiTestCase {
-
- public function testBasics() {
- $data = [ 'foo' => 'bar' ];
-
- $ex = new MetadataMergeException();
- $this->assertInstanceOf( \UnexpectedValueException::class, $ex );
- $this->assertSame( [], $ex->getContext() );
-
- $ex2 = new MetadataMergeException( 'Message', 42, $ex, $data );
- $this->assertSame( 'Message', $ex2->getMessage() );
- $this->assertSame( 42, $ex2->getCode() );
- $this->assertSame( $ex, $ex2->getPrevious() );
- $this->assertSame( $data, $ex2->getContext() );
-
- $ex->setContext( $data );
- $this->assertSame( $data, $ex->getContext() );
- }
-
-}
+++ /dev/null
-<?php
-
-namespace MediaWiki\Session;
-
-use MediaWikiTestCase;
-
-/**
- * @group Session
- * @covers MediaWiki\Session\SessionId
- */
-class SessionIdTest extends MediaWikiTestCase {
-
- public function testEverything() {
- $id = new SessionId( 'foo' );
- $this->assertSame( 'foo', $id->getId() );
- $this->assertSame( 'foo', (string)$id );
- $id->setId( 'bar' );
- $this->assertSame( 'bar', $id->getId() );
- $this->assertSame( 'bar', (string)$id );
- }
-
-}
+++ /dev/null
-<?php
-
-class SkinFactoryTest extends MediaWikiTestCase {
-
- /**
- * @covers SkinFactory::register
- */
- public function testRegister() {
- $factory = new SkinFactory();
- $factory->register( 'fallback', 'Fallback', function () {
- return new SkinFallback();
- } );
- $this->assertTrue( true ); // No exception thrown
- $this->setExpectedException( InvalidArgumentException::class );
- $factory->register( 'invalid', 'Invalid', 'Invalid callback' );
- }
-
- /**
- * @covers SkinFactory::makeSkin
- */
- public function testMakeSkinWithNoBuilders() {
- $factory = new SkinFactory();
- $this->setExpectedException( SkinException::class );
- $factory->makeSkin( 'nobuilderregistered' );
- }
-
- /**
- * @covers SkinFactory::makeSkin
- */
- public function testMakeSkinWithInvalidCallback() {
- $factory = new SkinFactory();
- $factory->register( 'unittest', 'Unittest', function () {
- return true; // Not a Skin object
- } );
- $this->setExpectedException( UnexpectedValueException::class );
- $factory->makeSkin( 'unittest' );
- }
-
- /**
- * @covers SkinFactory::makeSkin
- */
- public function testMakeSkinWithValidCallback() {
- $factory = new SkinFactory();
- $factory->register( 'testfallback', 'TestFallback', function () {
- return new SkinFallback();
- } );
-
- $skin = $factory->makeSkin( 'testfallback' );
- $this->assertInstanceOf( Skin::class, $skin );
- $this->assertInstanceOf( SkinFallback::class, $skin );
- $this->assertEquals( 'fallback', $skin->getSkinName() );
- }
-
- /**
- * @covers Skin::__construct
- * @covers Skin::getSkinName
- */
- public function testGetSkinName() {
- $skin = new SkinFallback();
- $this->assertEquals( 'fallback', $skin->getSkinName(), 'Default' );
- $skin = new SkinFallback( 'testname' );
- $this->assertEquals( 'testname', $skin->getSkinName(), 'Constructor argument' );
- }
-
- /**
- * @covers SkinFactory::getSkinNames
- */
- public function testGetSkinNames() {
- $factory = new SkinFactory();
- // A fake callback we can use that will never be called
- $callback = function () {
- // NOP
- };
- $factory->register( 'skin1', 'Skin1', $callback );
- $factory->register( 'skin2', 'Skin2', $callback );
- $names = $factory->getSkinNames();
- $this->assertArrayHasKey( 'skin1', $names );
- $this->assertArrayHasKey( 'skin2', $names );
- $this->assertEquals( 'Skin1', $names['skin1'] );
- $this->assertEquals( 'Skin2', $names['skin2'] );
- }
-}
+++ /dev/null
-<?php
-/**
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @author This, that and the other
- */
-
-/**
- * @covers ForeignTitle
- *
- * @group Title
- */
-class ForeignTitleTest extends MediaWikiTestCase {
-
- public function basicProvider() {
- return [
- [
- new ForeignTitle( 20, 'Contributor', 'JohnDoe' ),
- 20, 'Contributor', 'JohnDoe'
- ],
- [
- new ForeignTitle( '1', 'Discussion', 'Capital' ),
- 1, 'Discussion', 'Capital'
- ],
- [
- new ForeignTitle( 0, '', 'MainNamespace' ),
- 0, '', 'MainNamespace'
- ],
- [
- new ForeignTitle( 4, 'Some ns', 'Article title with spaces' ),
- 4, 'Some_ns', 'Article_title_with_spaces'
- ],
- ];
- }
-
- /**
- * @dataProvider basicProvider
- */
- public function testBasic( ForeignTitle $title, $expectedId, $expectedName,
- $expectedText
- ) {
- $this->assertEquals( true, $title->isNamespaceIdKnown() );
- $this->assertEquals( $expectedId, $title->getNamespaceId() );
- $this->assertEquals( $expectedName, $title->getNamespaceName() );
- $this->assertEquals( $expectedText, $title->getText() );
- }
-
- public function testUnknownNamespaceCheck() {
- $title = new ForeignTitle( null, 'this', 'that' );
-
- $this->assertEquals( false, $title->isNamespaceIdKnown() );
- $this->assertEquals( 'this', $title->getNamespaceName() );
- $this->assertEquals( 'that', $title->getText() );
- }
-
- public function testUnknownNamespaceError() {
- $this->setExpectedException( MWException::class );
- $title = new ForeignTitle( null, 'this', 'that' );
- $title->getNamespaceId();
- }
-
- public function fullTextProvider() {
- return [
- [
- new ForeignTitle( 20, 'Contributor', 'JohnDoe' ),
- 'Contributor:JohnDoe'
- ],
- [
- new ForeignTitle( '1', 'Discussion', 'Capital' ),
- 'Discussion:Capital'
- ],
- [
- new ForeignTitle( 0, '', 'MainNamespace' ),
- 'MainNamespace'
- ],
- [
- new ForeignTitle( 4, 'Some ns', 'Article title with spaces' ),
- 'Some_ns:Article_title_with_spaces'
- ],
- ];
- }
-
- /**
- * @dataProvider fullTextProvider
- */
- public function testFullText( ForeignTitle $title, $fullText ) {
- $this->assertEquals( $fullText, $title->getFullText() );
- }
-}
+++ /dev/null
-<?php
-/**
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @author This, that and the other
- */
-
-/**
- * @covers NamespaceAwareForeignTitleFactory
- *
- * @group Title
- */
-class NamespaceAwareForeignTitleFactoryTest extends MediaWikiTestCase {
-
- public function basicProvider() {
- return [
- [
- 'MainNamespaceArticle', 0,
- new ForeignTitle( 0, '', 'MainNamespaceArticle' ),
- ],
- [
- 'MainNamespaceArticle', null,
- new ForeignTitle( 0, '', 'MainNamespaceArticle' ),
- ],
- [
- 'Magic:_The_Gathering', 0,
- new ForeignTitle( 0, '', 'Magic:_The_Gathering' ),
- ],
- [
- 'Talk:Nice_talk', 1,
- new ForeignTitle( 1, 'Talk', 'Nice_talk' ),
- ],
- [
- 'Talk:Magic:_The_Gathering', 1,
- new ForeignTitle( 1, 'Talk', 'Magic:_The_Gathering' ),
- ],
- [
- 'Bogus:Nice_talk', 0,
- new ForeignTitle( 0, '', 'Bogus:Nice_talk' ),
- ],
- [
- 'Bogus:Nice_talk', null,
- new ForeignTitle( 9000, 'Bogus', 'Nice_talk' ),
- ],
- [
- 'Bogus:Nice_talk', 4,
- new ForeignTitle( 4, 'Bogus', 'Nice_talk' ),
- ],
- [
- 'Bogus:Nice_talk', 1,
- new ForeignTitle( 1, 'Talk', 'Nice_talk' ),
- ],
- // Misconfigured wiki with unregistered namespace (T114115)
- [
- 'Nice_talk', 1234,
- new ForeignTitle( 1234, 'Ns1234', 'Nice_talk' ),
- ],
- ];
- }
-
- /**
- * @dataProvider basicProvider
- */
- public function testBasic( $title, $ns, ForeignTitle $foreignTitle ) {
- $foreignNamespaces = [
- 0 => '', 1 => 'Talk', 100 => 'Portal', 9000 => 'Bogus'
- ];
-
- $factory = new NamespaceAwareForeignTitleFactory( $foreignNamespaces );
- $testTitle = $factory->createForeignTitle( $title, $ns );
-
- $this->assertEquals( $testTitle->isNamespaceIdKnown(),
- $foreignTitle->isNamespaceIdKnown() );
-
- if (
- $testTitle->isNamespaceIdKnown() &&
- $foreignTitle->isNamespaceIdKnown()
- ) {
- $this->assertEquals( $testTitle->getNamespaceId(),
- $foreignTitle->getNamespaceId() );
- }
-
- $this->assertEquals( $testTitle->getNamespaceName(),
- $foreignTitle->getNamespaceName() );
- $this->assertEquals( $testTitle->getText(), $foreignTitle->getText() );
- }
-}
+++ /dev/null
-<?php
-/**
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @author Daniel Kinzler
- */
-
-/**
- * @covers TitleValue
- *
- * @group Title
- */
-class TitleValueTest extends MediaWikiTestCase {
-
- public function goodConstructorProvider() {
- return [
- [ NS_MAIN, '', 'fragment', '', true, false ],
- [ NS_USER, 'TestThis', 'stuff', '', true, false ],
- [ NS_USER, 'TestThis', '', 'baz', false, true ],
- ];
- }
-
- /**
- * @dataProvider goodConstructorProvider
- */
- public function testConstruction( $ns, $text, $fragment, $interwiki, $hasFragment,
- $hasInterwiki
- ) {
- $title = new TitleValue( $ns, $text, $fragment, $interwiki );
-
- $this->assertEquals( $ns, $title->getNamespace() );
- $this->assertTrue( $title->inNamespace( $ns ) );
- $this->assertEquals( $text, $title->getText() );
- $this->assertEquals( $fragment, $title->getFragment() );
- $this->assertEquals( $hasFragment, $title->hasFragment() );
- $this->assertEquals( $interwiki, $title->getInterwiki() );
- $this->assertEquals( $hasInterwiki, $title->isExternal() );
- }
-
- public function badConstructorProvider() {
- return [
- [ 'foo', 'title', 'fragment', '' ],
- [ null, 'title', 'fragment', '' ],
- [ 2.3, 'title', 'fragment', '' ],
-
- [ NS_MAIN, 5, 'fragment', '' ],
- [ NS_MAIN, null, 'fragment', '' ],
- [ NS_USER, '', 'fragment', '' ],
- [ NS_MAIN, 'foo bar', '', '' ],
- [ NS_MAIN, 'bar_', '', '' ],
- [ NS_MAIN, '_foo', '', '' ],
- [ NS_MAIN, ' eek ', '', '' ],
-
- [ NS_MAIN, 'title', 5, '' ],
- [ NS_MAIN, 'title', null, '' ],
- [ NS_MAIN, 'title', [], '' ],
-
- [ NS_MAIN, 'title', '', 5 ],
- [ NS_MAIN, 'title', null, 5 ],
- [ NS_MAIN, 'title', [], 5 ],
- ];
- }
-
- /**
- * @dataProvider badConstructorProvider
- */
- public function testConstructionErrors( $ns, $text, $fragment, $interwiki ) {
- $this->setExpectedException( InvalidArgumentException::class );
- new TitleValue( $ns, $text, $fragment, $interwiki );
- }
-
- public function fragmentTitleProvider() {
- return [
- [ new TitleValue( NS_MAIN, 'Test' ), 'foo' ],
- [ new TitleValue( NS_TALK, 'Test', 'foo' ), '' ],
- [ new TitleValue( NS_CATEGORY, 'Test', 'foo' ), 'bar' ],
- ];
- }
-
- /**
- * @dataProvider fragmentTitleProvider
- */
- public function testCreateFragmentTitle( TitleValue $title, $fragment ) {
- $fragmentTitle = $title->createFragmentTarget( $fragment );
-
- $this->assertEquals( $title->getNamespace(), $fragmentTitle->getNamespace() );
- $this->assertEquals( $title->getText(), $fragmentTitle->getText() );
- $this->assertEquals( $fragment, $fragmentTitle->getFragment() );
- }
-
- public function getTextProvider() {
- return [
- [ 'Foo', 'Foo' ],
- [ 'Foo_Bar', 'Foo Bar' ],
- ];
- }
-
- /**
- * @dataProvider getTextProvider
- */
- public function testGetText( $dbkey, $text ) {
- $title = new TitleValue( NS_MAIN, $dbkey );
-
- $this->assertEquals( $text, $title->getText() );
- }
-
- public function provideTestToString() {
- yield [
- new TitleValue( 0, 'Foo' ),
- '0:Foo'
- ];
- yield [
- new TitleValue( 1, 'Bar_Baz' ),
- '1:Bar_Baz'
- ];
- yield [
- new TitleValue( 9, 'JoJo', 'Frag' ),
- '9:JoJo#Frag'
- ];
- yield [
- new TitleValue( 200, 'tea', 'Fragment', 'wikicode' ),
- 'wikicode:200:tea#Fragment'
- ];
- }
-
- /**
- * @dataProvider provideTestToString
- */
- public function testToString( TitleValue $value, $expected ) {
- $this->assertSame(
- $expected,
- $value->__toString()
- );
- }
-}
+++ /dev/null
-<?php
-
-/**
- * @author Addshore
- * @covers UserArrayFromResult
- */
-class UserArrayFromResultTest extends MediaWikiTestCase {
-
- private function getMockResultWrapper( $row = null, $numRows = 1 ) {
- $resultWrapper = $this->getMockBuilder( Wikimedia\Rdbms\ResultWrapper::class )
- ->disableOriginalConstructor();
-
- $resultWrapper = $resultWrapper->getMock();
- $resultWrapper->expects( $this->atLeastOnce() )
- ->method( 'current' )
- ->will( $this->returnValue( $row ) );
- $resultWrapper->expects( $this->any() )
- ->method( 'numRows' )
- ->will( $this->returnValue( $numRows ) );
-
- return $resultWrapper;
- }
-
- private function getRowWithUsername( $username = 'fooUser' ) {
- $row = new stdClass();
- $row->user_name = $username;
- return $row;
- }
-
- /**
- * @covers UserArrayFromResult::__construct
- */
- public function testConstructionWithFalseRow() {
- $row = false;
- $resultWrapper = $this->getMockResultWrapper( $row );
-
- $object = new UserArrayFromResult( $resultWrapper );
-
- $this->assertEquals( $resultWrapper, $object->res );
- $this->assertSame( 0, $object->key );
- $this->assertEquals( $row, $object->current );
- }
-
- /**
- * @covers UserArrayFromResult::__construct
- */
- public function testConstructionWithRow() {
- $username = 'addshore';
- $row = $this->getRowWithUsername( $username );
- $resultWrapper = $this->getMockResultWrapper( $row );
-
- $object = new UserArrayFromResult( $resultWrapper );
-
- $this->assertEquals( $resultWrapper, $object->res );
- $this->assertSame( 0, $object->key );
- $this->assertInstanceOf( User::class, $object->current );
- $this->assertEquals( $username, $object->current->mName );
- }
-
- public static function provideNumberOfRows() {
- return [
- [ 0 ],
- [ 1 ],
- [ 122 ],
- ];
- }
-
- /**
- * @dataProvider provideNumberOfRows
- * @covers UserArrayFromResult::count
- */
- public function testCountWithVaryingValues( $numRows ) {
- $object = new UserArrayFromResult( $this->getMockResultWrapper(
- $this->getRowWithUsername(),
- $numRows
- ) );
- $this->assertEquals( $numRows, $object->count() );
- }
-
- /**
- * @covers UserArrayFromResult::current
- */
- public function testCurrentAfterConstruction() {
- $username = 'addshore';
- $userRow = $this->getRowWithUsername( $username );
- $object = new UserArrayFromResult( $this->getMockResultWrapper( $userRow ) );
- $this->assertInstanceOf( User::class, $object->current() );
- $this->assertEquals( $username, $object->current()->mName );
- }
-
- public function provideTestValid() {
- return [
- [ $this->getRowWithUsername(), true ],
- [ false, false ],
- ];
- }
-
- /**
- * @dataProvider provideTestValid
- * @covers UserArrayFromResult::valid
- */
- public function testValid( $input, $expected ) {
- $object = new UserArrayFromResult( $this->getMockResultWrapper( $input ) );
- $this->assertEquals( $expected, $object->valid() );
- }
-
- // @todo unit test for key()
- // @todo unit test for next()
- // @todo unit test for rewind()
-}
$this->userTester->addGroup( 'unittesters' );
$this->expiryTime = wfTimestamp( TS_MW, time() + 100500 );
$this->userTester->addGroup( 'testwriters', $this->expiryTime );
+
+ $this->resetServices();
}
/**
$this->assertNotContains( 'nukeworld', $rights, 'sanity check' );
// Add a hook manipluating the rights
- $this->mergeMwGlobalArrayValue( 'wgHooks', [ 'UserGetRights' => [ function ( $user, &$rights ) {
+ $this->setTemporaryHook( 'UserGetRights', function ( $user, &$rights ) {
$rights[] = 'nukeworld';
$rights = array_diff( $rights, [ 'writetest' ] );
- } ] ] );
+ } );
- $userWrapper->mRights = null;
+ $this->resetServices();
$rights = $user->getRights();
$this->assertContains( 'test', $rights );
$this->assertContains( 'runtest', $rights );
$mockRequest->method( 'getSession' )->willReturn( $session );
$userWrapper->mRequest = $mockRequest;
- $userWrapper->mRights = null;
+ $this->resetServices();
$rights = $user->getRights();
$this->assertContains( 'test', $rights );
$this->assertNotContains( 'runtest', $rights );
$this->setMwGlobals( 'wgRateLimitsExcludedIPs', [] );
$noRateLimitUser = $this->getMockBuilder( User::class )->disableOriginalConstructor()
- ->setMethods( [ 'getIP', 'getRights' ] )->getMock();
+ ->setMethods( [ 'getIP', 'getId', 'getGroups' ] )->getMock();
$noRateLimitUser->expects( $this->any() )->method( 'getIP' )->willReturn( '1.2.3.4' );
- $noRateLimitUser->expects( $this->any() )->method( 'getRights' )->willReturn( [ 'noratelimit' ] );
+ $noRateLimitUser->expects( $this->any() )->method( 'getId' )->willReturn( 0 );
+ $noRateLimitUser->expects( $this->any() )->method( 'getGroups' )->willReturn( [] );
+ $this->overrideUserPermissions( $noRateLimitUser, 'noratelimit' );
$this->assertFalse( $noRateLimitUser->isPingLimitable() );
}
+++ /dev/null
-<?php
-
-use MediaWiki\User\UserIdentityValue;
-
-/**
- * @author Addshore
- *
- * @covers NoWriteWatchedItemStore
- */
-class NoWriteWatchedItemStoreUnitTest extends MediaWikiTestCase {
-
- public function testAddWatch() {
- /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */
- $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class );
- $innerService->expects( $this->never() )->method( 'addWatch' );
- $noWriteService = new NoWriteWatchedItemStore( $innerService );
-
- $this->setExpectedException( DBReadOnlyError::class );
- $noWriteService->addWatch(
- new UserIdentityValue( 1, 'MockUser', 0 ), new TitleValue( 0, 'Foo' ) );
- }
-
- public function testAddWatchBatchForUser() {
- /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */
- $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class );
- $innerService->expects( $this->never() )->method( 'addWatchBatchForUser' );
- $noWriteService = new NoWriteWatchedItemStore( $innerService );
-
- $this->setExpectedException( DBReadOnlyError::class );
- $noWriteService->addWatchBatchForUser( new UserIdentityValue( 1, 'MockUser', 0 ), [] );
- }
-
- public function testRemoveWatch() {
- /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */
- $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class );
- $innerService->expects( $this->never() )->method( 'removeWatch' );
- $noWriteService = new NoWriteWatchedItemStore( $innerService );
-
- $this->setExpectedException( DBReadOnlyError::class );
- $noWriteService->removeWatch(
- new UserIdentityValue( 1, 'MockUser', 0 ), new TitleValue( 0, 'Foo' ) );
- }
-
- public function testSetNotificationTimestampsForUser() {
- /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */
- $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class );
- $innerService->expects( $this->never() )->method( 'setNotificationTimestampsForUser' );
- $noWriteService = new NoWriteWatchedItemStore( $innerService );
-
- $this->setExpectedException( DBReadOnlyError::class );
- $noWriteService->setNotificationTimestampsForUser(
- new UserIdentityValue( 1, 'MockUser', 0 ),
- 'timestamp',
- []
- );
- }
-
- public function testUpdateNotificationTimestamp() {
- /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */
- $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class );
- $innerService->expects( $this->never() )->method( 'updateNotificationTimestamp' );
- $noWriteService = new NoWriteWatchedItemStore( $innerService );
-
- $this->setExpectedException( DBReadOnlyError::class );
- $noWriteService->updateNotificationTimestamp(
- new UserIdentityValue( 1, 'MockUser', 0 ),
- new TitleValue( 0, 'Foo' ),
- 'timestamp'
- );
- }
-
- public function testResetNotificationTimestamp() {
- /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */
- $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class );
- $innerService->expects( $this->never() )->method( 'resetNotificationTimestamp' );
- $noWriteService = new NoWriteWatchedItemStore( $innerService );
-
- $this->setExpectedException( DBReadOnlyError::class );
- $noWriteService->resetNotificationTimestamp(
- new UserIdentityValue( 1, 'MockUser', 0 ),
- new TitleValue( 0, 'Foo' )
- );
- }
-
- public function testCountWatchedItems() {
- /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */
- $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class );
- $innerService->expects( $this->once() )->method( 'countWatchedItems' )->willReturn( __METHOD__ );
- $noWriteService = new NoWriteWatchedItemStore( $innerService );
-
- $return = $noWriteService->countWatchedItems(
- new UserIdentityValue( 1, 'MockUser', 0 )
- );
- $this->assertEquals( __METHOD__, $return );
- }
-
- public function testCountWatchers() {
- /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */
- $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class );
- $innerService->expects( $this->once() )->method( 'countWatchers' )->willReturn( __METHOD__ );
- $noWriteService = new NoWriteWatchedItemStore( $innerService );
-
- $return = $noWriteService->countWatchers(
- new TitleValue( 0, 'Foo' )
- );
- $this->assertEquals( __METHOD__, $return );
- }
-
- public function testCountVisitingWatchers() {
- /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */
- $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class );
- $innerService->expects( $this->once() )
- ->method( 'countVisitingWatchers' )
- ->willReturn( __METHOD__ );
- $noWriteService = new NoWriteWatchedItemStore( $innerService );
-
- $return = $noWriteService->countVisitingWatchers(
- new TitleValue( 0, 'Foo' ),
- 9
- );
- $this->assertEquals( __METHOD__, $return );
- }
-
- public function testCountWatchersMultiple() {
- /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */
- $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class );
- $innerService->expects( $this->once() )
- ->method( 'countVisitingWatchersMultiple' )
- ->willReturn( __METHOD__ );
- $noWriteService = new NoWriteWatchedItemStore( $innerService );
-
- $return = $noWriteService->countWatchersMultiple(
- [ new TitleValue( 0, 'Foo' ) ],
- []
- );
- $this->assertEquals( __METHOD__, $return );
- }
-
- public function testCountVisitingWatchersMultiple() {
- /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */
- $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class );
- $innerService->expects( $this->once() )
- ->method( 'countVisitingWatchersMultiple' )
- ->willReturn( __METHOD__ );
- $noWriteService = new NoWriteWatchedItemStore( $innerService );
-
- $return = $noWriteService->countVisitingWatchersMultiple(
- [ [ new TitleValue( 0, 'Foo' ), 99 ] ],
- 11
- );
- $this->assertEquals( __METHOD__, $return );
- }
-
- public function testGetWatchedItem() {
- /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */
- $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class );
- $innerService->expects( $this->once() )->method( 'getWatchedItem' )->willReturn( __METHOD__ );
- $noWriteService = new NoWriteWatchedItemStore( $innerService );
-
- $return = $noWriteService->getWatchedItem(
- new UserIdentityValue( 1, 'MockUser', 0 ),
- new TitleValue( 0, 'Foo' )
- );
- $this->assertEquals( __METHOD__, $return );
- }
-
- public function testLoadWatchedItem() {
- /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */
- $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class );
- $innerService->expects( $this->once() )->method( 'loadWatchedItem' )->willReturn( __METHOD__ );
- $noWriteService = new NoWriteWatchedItemStore( $innerService );
-
- $return = $noWriteService->loadWatchedItem(
- new UserIdentityValue( 1, 'MockUser', 0 ),
- new TitleValue( 0, 'Foo' )
- );
- $this->assertEquals( __METHOD__, $return );
- }
-
- public function testGetWatchedItemsForUser() {
- /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */
- $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class );
- $innerService->expects( $this->once() )
- ->method( 'getWatchedItemsForUser' )
- ->willReturn( __METHOD__ );
- $noWriteService = new NoWriteWatchedItemStore( $innerService );
-
- $return = $noWriteService->getWatchedItemsForUser(
- new UserIdentityValue( 1, 'MockUser', 0 ),
- []
- );
- $this->assertEquals( __METHOD__, $return );
- }
-
- public function testIsWatched() {
- /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */
- $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class );
- $innerService->expects( $this->once() )->method( 'isWatched' )->willReturn( __METHOD__ );
- $noWriteService = new NoWriteWatchedItemStore( $innerService );
-
- $return = $noWriteService->isWatched(
- new UserIdentityValue( 1, 'MockUser', 0 ),
- new TitleValue( 0, 'Foo' )
- );
- $this->assertEquals( __METHOD__, $return );
- }
-
- public function testGetNotificationTimestampsBatch() {
- /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */
- $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class );
- $innerService->expects( $this->once() )
- ->method( 'getNotificationTimestampsBatch' )
- ->willReturn( __METHOD__ );
- $noWriteService = new NoWriteWatchedItemStore( $innerService );
-
- $return = $noWriteService->getNotificationTimestampsBatch(
- new UserIdentityValue( 1, 'MockUser', 0 ),
- [ new TitleValue( 0, 'Foo' ) ]
- );
- $this->assertEquals( __METHOD__, $return );
- }
-
- public function testCountUnreadNotifications() {
- /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */
- $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class );
- $innerService->expects( $this->once() )
- ->method( 'countUnreadNotifications' )
- ->willReturn( __METHOD__ );
- $noWriteService = new NoWriteWatchedItemStore( $innerService );
-
- $return = $noWriteService->countUnreadNotifications(
- new UserIdentityValue( 1, 'MockUser', 0 ),
- 88
- );
- $this->assertEquals( __METHOD__, $return );
- }
-
- public function testDuplicateAllAssociatedEntries() {
- /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */
- $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class );
- $noWriteService = new NoWriteWatchedItemStore( $innerService );
-
- $this->setExpectedException( DBReadOnlyError::class );
- $noWriteService->duplicateAllAssociatedEntries(
- new TitleValue( 0, 'Foo' ),
- new TitleValue( 0, 'Bar' )
- );
- }
-
-}
*
* @author Katie Filbert < aude.wiki@gmail.com >
*/
-class SpecialPageAliasTest extends MediaWikiTestCase {
+class SpecialPageAliasTest extends \MediaWikiUnitTestCase {
/**
* @coversNothing
--- /dev/null
+<?php
+/**
+ * Copyright @ 2011 Alexandre Emsenhuber
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+class FauxResponseTest extends \MediaWikiUnitTestCase {
+ /** @var FauxResponse */
+ protected $response;
+
+ protected function setUp() {
+ parent::setUp();
+ $this->response = new FauxResponse;
+ }
+
+ /**
+ * @covers FauxResponse::setCookie
+ * @covers FauxResponse::getCookie
+ * @covers FauxResponse::getCookieData
+ * @covers FauxResponse::getCookies
+ */
+ public function testCookie() {
+ $expire = time() + 100;
+ $cookie = [
+ 'value' => 'val',
+ 'path' => '/path',
+ 'domain' => 'domain',
+ 'secure' => true,
+ 'httpOnly' => false,
+ 'raw' => false,
+ 'expire' => $expire,
+ ];
+
+ $this->assertEquals( null, $this->response->getCookie( 'xkey' ), 'Non-existing cookie' );
+ $this->response->setCookie( 'key', 'val', $expire, [
+ 'prefix' => 'x',
+ 'path' => '/path',
+ 'domain' => 'domain',
+ 'secure' => 1,
+ 'httpOnly' => 0,
+ ] );
+ $this->assertEquals( 'val', $this->response->getCookie( 'xkey' ), 'Existing cookie' );
+ $this->assertEquals( $cookie, $this->response->getCookieData( 'xkey' ),
+ 'Existing cookie (data)' );
+ $this->assertEquals( [ 'xkey' => $cookie ], $this->response->getCookies(),
+ 'Existing cookies' );
+ }
+
+ /**
+ * @covers FauxResponse::getheader
+ * @covers FauxResponse::header
+ */
+ public function testHeader() {
+ $this->assertEquals( null, $this->response->getHeader( 'Location' ), 'Non-existing header' );
+
+ $this->response->header( 'Location: http://localhost/' );
+ $this->assertEquals(
+ 'http://localhost/',
+ $this->response->getHeader( 'Location' ),
+ 'Set header'
+ );
+
+ $this->response->header( 'Location: http://127.0.0.1/' );
+ $this->assertEquals(
+ 'http://127.0.0.1/',
+ $this->response->getHeader( 'Location' ),
+ 'Same header'
+ );
+
+ $this->response->header( 'Location: http://127.0.0.2/', false );
+ $this->assertEquals(
+ 'http://127.0.0.1/',
+ $this->response->getHeader( 'Location' ),
+ 'Same header with override disabled'
+ );
+
+ $this->response->header( 'Location: http://localhost/' );
+ $this->assertEquals(
+ 'http://localhost/',
+ $this->response->getHeader( 'LOCATION' ),
+ 'Get header case insensitive'
+ );
+ }
+
+ /**
+ * @covers FauxResponse::getStatusCode
+ */
+ public function testResponseCode() {
+ $this->response->header( 'HTTP/1.1 200' );
+ $this->assertEquals( 200, $this->response->getStatusCode(), 'Header with no message' );
+
+ $this->response->header( 'HTTP/1.x 201' );
+ $this->assertEquals(
+ 201,
+ $this->response->getStatusCode(),
+ 'Header with no message and protocol 1.x'
+ );
+
+ $this->response->header( 'HTTP/1.1 202 OK' );
+ $this->assertEquals( 202, $this->response->getStatusCode(), 'Normal header' );
+
+ $this->response->header( 'HTTP/1.x 203 OK' );
+ $this->assertEquals(
+ 203,
+ $this->response->getStatusCode(),
+ 'Normal header with no message and protocol 1.x'
+ );
+
+ $this->response->header( 'HTTP/1.x 204 OK', false, 205 );
+ $this->assertEquals(
+ 205,
+ $this->response->getStatusCode(),
+ 'Third parameter overrides the HTTP/... header'
+ );
+
+ $this->response->statusHeader( 210 );
+ $this->assertEquals(
+ 210,
+ $this->response->getStatusCode(),
+ 'Handle statusHeader method'
+ );
+
+ $this->response->header( 'Location: http://localhost/', false, 206 );
+ $this->assertEquals(
+ 206,
+ $this->response->getStatusCode(),
+ 'Third parameter with another header'
+ );
+ }
+}
--- /dev/null
+<?php
+
+use Wikimedia\TestingAccessWrapper;
+
+/**
+ * Test class for FormOptions initialization
+ * Ensure the FormOptions::add() does what we want it to do.
+ *
+ * Copyright © 2011, Antoine Musso
+ *
+ * @author Antoine Musso
+ */
+class FormOptionsInitializationTest extends \MediaWikiUnitTestCase {
+ /**
+ * @var FormOptions
+ */
+ protected $object;
+
+ /**
+ * A new fresh and empty FormOptions object to test initialization
+ * with.
+ */
+ protected function setUp() {
+ parent::setUp();
+ $this->object = TestingAccessWrapper::newFromObject( new FormOptions() );
+ }
+
+ /**
+ * @covers FormOptions::add
+ */
+ public function testAddStringOption() {
+ $this->object->add( 'foo', 'string value' );
+ $this->assertEquals(
+ [
+ 'foo' => [
+ 'default' => 'string value',
+ 'consumed' => false,
+ 'type' => FormOptions::STRING,
+ 'value' => null,
+ ]
+ ],
+ $this->object->options
+ );
+ }
+
+ /**
+ * @covers FormOptions::add
+ */
+ public function testAddIntegers() {
+ $this->object->add( 'one', 1 );
+ $this->object->add( 'negone', -1 );
+ $this->assertEquals(
+ [
+ 'negone' => [
+ 'default' => -1,
+ 'value' => null,
+ 'consumed' => false,
+ 'type' => FormOptions::INT,
+ ],
+ 'one' => [
+ 'default' => 1,
+ 'value' => null,
+ 'consumed' => false,
+ 'type' => FormOptions::INT,
+ ]
+ ],
+ $this->object->options
+ );
+ }
+}
--- /dev/null
+<?php
+/**
+ * This file host two test case classes for the MediaWiki FormOptions class:
+ * - FormOptionsInitializationTest : tests initialization of the class.
+ * - FormOptionsTest : tests methods an on instance
+ *
+ * The split let us take advantage of setting up a fixture for the methods
+ * tests.
+ */
+
+/**
+ * Test class for FormOptions methods.
+ *
+ * Copyright © 2011, Antoine Musso
+ *
+ * @author Antoine Musso
+ */
+class FormOptionsTest extends \MediaWikiUnitTestCase {
+ /**
+ * @var FormOptions
+ */
+ protected $object;
+
+ /**
+ * Instanciates a FormOptions object to play with.
+ * FormOptions::add() is tested by the class FormOptionsInitializationTest
+ * so we assume the function is well tested already an use it to create
+ * the fixture.
+ */
+ protected function setUp() {
+ parent::setUp();
+ $this->object = new FormOptions;
+ $this->object->add( 'string1', 'string one' );
+ $this->object->add( 'string2', 'string two' );
+ $this->object->add( 'integer', 0 );
+ $this->object->add( 'float', 0.0 );
+ $this->object->add( 'intnull', 0, FormOptions::INTNULL );
+ }
+
+ /** Helpers for testGuessType() */
+ /* @{ */
+ private function assertGuessBoolean( $data ) {
+ $this->guess( FormOptions::BOOL, $data );
+ }
+
+ private function assertGuessInt( $data ) {
+ $this->guess( FormOptions::INT, $data );
+ }
+
+ private function assertGuessFloat( $data ) {
+ $this->guess( FormOptions::FLOAT, $data );
+ }
+
+ private function assertGuessString( $data ) {
+ $this->guess( FormOptions::STRING, $data );
+ }
+
+ private function assertGuessArray( $data ) {
+ $this->guess( FormOptions::ARR, $data );
+ }
+
+ /** Generic helper */
+ private function guess( $expected, $data ) {
+ $this->assertEquals(
+ $expected,
+ FormOptions::guessType( $data )
+ );
+ }
+
+ /* @} */
+
+ /**
+ * Reuse helpers above assertGuessBoolean assertGuessInt assertGuessString
+ * @covers FormOptions::guessType
+ */
+ public function testGuessTypeDetection() {
+ $this->assertGuessBoolean( true );
+ $this->assertGuessBoolean( false );
+
+ $this->assertGuessInt( 0 );
+ $this->assertGuessInt( -5 );
+ $this->assertGuessInt( 5 );
+ $this->assertGuessInt( 0x0F );
+
+ $this->assertGuessFloat( 0.0 );
+ $this->assertGuessFloat( 1.5 );
+ $this->assertGuessFloat( 1e3 );
+
+ $this->assertGuessString( 'true' );
+ $this->assertGuessString( 'false' );
+ $this->assertGuessString( '5' );
+ $this->assertGuessString( '0' );
+ $this->assertGuessString( '1.5' );
+
+ $this->assertGuessArray( [ 'foo' ] );
+ }
+
+ /**
+ * @expectedException MWException
+ * @covers FormOptions::guessType
+ */
+ public function testGuessTypeOnNullThrowException() {
+ $this->object->guessType( null );
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * @covers Licenses
+ */
+class LicensesTest extends \MediaWikiUnitTestCase {
+
+ public function testLicenses() {
+ $str = "
+* Free licenses:
+** GFDL|Debian disagrees
+";
+
+ $lc = new Licenses( [
+ 'fieldname' => 'FooField',
+ 'type' => 'select',
+ 'section' => 'description',
+ 'id' => 'wpLicense',
+ 'label' => 'A label text', # Note can't test label-message because $wgOut is not defined
+ 'name' => 'AnotherName',
+ 'licenses' => $str,
+ ] );
+ $this->assertThat( $lc, $this->isInstanceOf( Licenses::class ) );
+ }
+}
--- /dev/null
+<?php
+
+namespace MediaWiki\Tests\Rest;
+
+use MediaWiki\Rest\HeaderContainer;
+
+/**
+ * @covers \MediaWiki\Rest\HeaderContainer
+ */
+class HeaderContainerTest extends \MediaWikiUnitTestCase {
+ public static function provideSetHeader() {
+ return [
+ 'simple' => [
+ [
+ [ 'Test', 'foo' ]
+ ],
+ [ 'Test' => [ 'foo' ] ],
+ [ 'Test' => 'foo' ]
+ ],
+ 'replace' => [
+ [
+ [ 'Test', 'foo' ],
+ [ 'Test', 'bar' ],
+ ],
+ [ 'Test' => [ 'bar' ] ],
+ [ 'Test' => 'bar' ],
+ ],
+ 'array value' => [
+ [
+ [ 'Test', [ '1', '2' ] ],
+ [ 'Test', [ '3', '4' ] ],
+ ],
+ [ 'Test' => [ '3', '4' ] ],
+ [ 'Test' => '3, 4' ]
+ ],
+ 'preserve most recent case' => [
+ [
+ [ 'test', 'foo' ],
+ [ 'tesT', 'bar' ],
+ ],
+ [ 'tesT' => [ 'bar' ] ],
+ [ 'tesT' => 'bar' ]
+ ],
+ 'empty' => [ [], [], [] ],
+ ];
+ }
+
+ /** @dataProvider provideSetHeader */
+ public function testSetHeader( $setOps, $headers, $lines ) {
+ $hc = new HeaderContainer;
+ foreach ( $setOps as list( $name, $value ) ) {
+ $hc->setHeader( $name, $value );
+ }
+ $this->assertSame( $headers, $hc->getHeaders() );
+ $this->assertSame( $lines, $hc->getHeaderLines() );
+ }
+
+ public static function provideAddHeader() {
+ return [
+ 'simple' => [
+ [
+ [ 'Test', 'foo' ]
+ ],
+ [ 'Test' => [ 'foo' ] ],
+ [ 'Test' => 'foo' ]
+ ],
+ 'add' => [
+ [
+ [ 'Test', 'foo' ],
+ [ 'Test', 'bar' ],
+ ],
+ [ 'Test' => [ 'foo', 'bar' ] ],
+ [ 'Test' => 'foo, bar' ],
+ ],
+ 'array value' => [
+ [
+ [ 'Test', [ '1', '2' ] ],
+ [ 'Test', [ '3', '4' ] ],
+ ],
+ [ 'Test' => [ '1', '2', '3', '4' ] ],
+ [ 'Test' => '1, 2, 3, 4' ]
+ ],
+ 'preserve original case' => [
+ [
+ [ 'Test', 'foo' ],
+ [ 'tesT', 'bar' ],
+ ],
+ [ 'Test' => [ 'foo', 'bar' ] ],
+ [ 'Test' => 'foo, bar' ]
+ ],
+ ];
+ }
+
+ /** @dataProvider provideAddHeader */
+ public function testAddHeader( $addOps, $headers, $lines ) {
+ $hc = new HeaderContainer;
+ foreach ( $addOps as list( $name, $value ) ) {
+ $hc->addHeader( $name, $value );
+ }
+ $this->assertSame( $headers, $hc->getHeaders() );
+ $this->assertSame( $lines, $hc->getHeaderLines() );
+ }
+
+ public static function provideRemoveHeader() {
+ return [
+ 'simple' => [
+ [ [ 'Test', 'foo' ] ],
+ [ 'Test' ],
+ [],
+ []
+ ],
+ 'case mismatch' => [
+ [ [ 'Test', 'foo' ] ],
+ [ 'tesT' ],
+ [],
+ []
+ ],
+ 'remove nonexistent' => [
+ [ [ 'A', '1' ] ],
+ [ 'B' ],
+ [ 'A' => [ '1' ] ],
+ [ 'A' => '1' ]
+ ],
+ ];
+ }
+
+ /** @dataProvider provideRemoveHeader */
+ public function testRemoveHeader( $addOps, $removeOps, $headers, $lines ) {
+ $hc = new HeaderContainer;
+ foreach ( $addOps as list( $name, $value ) ) {
+ $hc->addHeader( $name, $value );
+ }
+ foreach ( $removeOps as $name ) {
+ $hc->removeHeader( $name );
+ }
+ $this->assertSame( $headers, $hc->getHeaders() );
+ $this->assertSame( $lines, $hc->getHeaderLines() );
+ }
+
+ public function testHasHeader() {
+ $hc = new HeaderContainer;
+ $hc->addHeader( 'A', '1' );
+ $hc->addHeader( 'B', '2' );
+ $hc->addHeader( 'C', '3' );
+ $hc->removeHeader( 'B' );
+ $hc->removeHeader( 'c' );
+ $this->assertTrue( $hc->hasHeader( 'A' ) );
+ $this->assertTrue( $hc->hasHeader( 'a' ) );
+ $this->assertFalse( $hc->hasHeader( 'B' ) );
+ $this->assertFalse( $hc->hasHeader( 'c' ) );
+ $this->assertFalse( $hc->hasHeader( 'C' ) );
+ }
+
+ public function testGetRawHeaderLines() {
+ $hc = new HeaderContainer;
+ $hc->addHeader( 'A', '1' );
+ $hc->addHeader( 'a', '2' );
+ $hc->addHeader( 'b', '3' );
+ $hc->addHeader( 'Set-Cookie', 'x' );
+ $hc->addHeader( 'SET-cookie', 'y' );
+ $this->assertSame(
+ [
+ 'A: 1, 2',
+ 'b: 3',
+ 'Set-Cookie: x',
+ 'Set-Cookie: y',
+ ],
+ $hc->getRawHeaderLines()
+ );
+ }
+}
--- /dev/null
+<?php
+
+namespace MediaWiki\Tests\Rest\PathTemplateMatcher;
+
+use MediaWiki\Rest\PathTemplateMatcher\PathConflict;
+use MediaWiki\Rest\PathTemplateMatcher\PathMatcher;
+
+/**
+ * @covers \MediaWiki\Rest\PathTemplateMatcher\PathMatcher
+ * @covers \MediaWiki\Rest\PathTemplateMatcher\PathConflict
+ */
+class PathMatcherTest extends \MediaWikiUnitTestCase {
+ private static $normalRoutes = [
+ '/a/b',
+ '/b/{x}',
+ '/c/{x}/d',
+ '/c/{x}/e',
+ '/c/{x}/{y}/d',
+ ];
+
+ public static function provideConflictingRoutes() {
+ return [
+ [ '/a/b', 0, '/a/b' ],
+ [ '/a/{x}', 0, '/a/b' ],
+ [ '/{x}/c', 1, '/b/{x}' ],
+ [ '/b/a', 1, '/b/{x}' ],
+ [ '/b/{x}', 1, '/b/{x}' ],
+ [ '/{x}/{y}/d', 2, '/c/{x}/d' ],
+ ];
+ }
+
+ public static function provideMatch() {
+ return [
+ [ '', false ],
+ [ '/a/b', [ 'params' => [], 'userData' => 0 ] ],
+ [ '/b', false ],
+ [ '/b/1', [ 'params' => [ 'x' => '1' ], 'userData' => 1 ] ],
+ [ '/c/1/d', [ 'params' => [ 'x' => '1' ], 'userData' => 2 ] ],
+ [ '/c/1/e', [ 'params' => [ 'x' => '1' ], 'userData' => 3 ] ],
+ [ '/c/000/e', [ 'params' => [ 'x' => '000' ], 'userData' => 3 ] ],
+ [ '/c/1/f', false ],
+ [ '/c//e', [ 'params' => [ 'x' => '' ], 'userData' => 3 ] ],
+ [ '/c///e', false ],
+ ];
+ }
+
+ public function createNormalRouter() {
+ $pm = new PathMatcher;
+ foreach ( self::$normalRoutes as $i => $route ) {
+ $pm->add( $route, $i );
+ }
+ return $pm;
+ }
+
+ /** @dataProvider provideConflictingRoutes */
+ public function testAddConflict( $attempt, $expectedUserData, $expectedTemplate ) {
+ $pm = $this->createNormalRouter();
+ $actualTemplate = null;
+ $actualUserData = null;
+ try {
+ $pm->add( $attempt, 'conflict' );
+ } catch ( PathConflict $pc ) {
+ $actualTemplate = $pc->existingTemplate;
+ $actualUserData = $pc->existingUserData;
+ }
+ $this->assertSame( $expectedUserData, $actualUserData );
+ $this->assertSame( $expectedTemplate, $actualTemplate );
+ }
+
+ /** @dataProvider provideMatch */
+ public function testMatch( $path, $expectedResult ) {
+ $pm = $this->createNormalRouter();
+ $result = $pm->match( $path );
+ $this->assertSame( $expectedResult, $result );
+ }
+}
--- /dev/null
+<?php
+
+namespace MediaWiki\Tests\Rest;
+
+use MediaWiki\Rest\StringStream;
+
+/** @covers \MediaWiki\Rest\StringStream */
+class StringStreamTest extends \MediaWikiUnitTestCase {
+ public static function provideSeekGetContents() {
+ return [
+ [ 'abcde', 0, SEEK_SET, 'abcde' ],
+ [ 'abcde', 1, SEEK_SET, 'bcde' ],
+ [ 'abcde', 5, SEEK_SET, '' ],
+ [ 'abcde', 1, SEEK_CUR, 'cde' ],
+ [ 'abcde', 0, SEEK_END, '' ],
+ ];
+ }
+
+ /** @dataProvider provideSeekGetContents */
+ public function testCopyToStream( $input, $offset, $whence, $expected ) {
+ $ss = new StringStream;
+ $ss->write( $input );
+ $ss->seek( 1 );
+ $ss->seek( $offset, $whence );
+ $destStream = fopen( 'php://memory', 'w+' );
+ $ss->copyToStream( $destStream );
+ fseek( $destStream, 0 );
+ $result = stream_get_contents( $destStream );
+ $this->assertSame( $expected, $result );
+ }
+
+ public function testGetSize() {
+ $ss = new StringStream;
+ $this->assertSame( 0, $ss->getSize() );
+ $ss->write( "hello" );
+ $this->assertSame( 5, $ss->getSize() );
+ $ss->rewind();
+ $this->assertSame( 5, $ss->getSize() );
+ }
+
+ public function testTell() {
+ $ss = new StringStream;
+ $this->assertSame( $ss->tell(), 0 );
+ $ss->write( "abc" );
+ $this->assertSame( $ss->tell(), 3 );
+ $ss->seek( 0 );
+ $ss->read( 1 );
+ $this->assertSame( $ss->tell(), 1 );
+ }
+
+ public function testEof() {
+ $ss = new StringStream( 'abc' );
+ $this->assertFalse( $ss->eof() );
+ $ss->read( 1 );
+ $this->assertFalse( $ss->eof() );
+ $ss->read( 1 );
+ $this->assertFalse( $ss->eof() );
+ $ss->read( 1 );
+ $this->assertTrue( $ss->eof() );
+ $ss->rewind();
+ $this->assertFalse( $ss->eof() );
+ }
+
+ public function testIsSeekable() {
+ $ss = new StringStream;
+ $this->assertTrue( $ss->isSeekable() );
+ }
+
+ public function testIsReadable() {
+ $ss = new StringStream;
+ $this->assertTrue( $ss->isReadable() );
+ }
+
+ public function testIsWritable() {
+ $ss = new StringStream;
+ $this->assertTrue( $ss->isWritable() );
+ }
+
+ public function testSeekWrite() {
+ $ss = new StringStream;
+ $this->assertSame( '', (string)$ss );
+ $ss->write( 'a' );
+ $this->assertSame( 'a', (string)$ss );
+ $ss->write( 'b' );
+ $this->assertSame( 'ab', (string)$ss );
+ $ss->seek( 1 );
+ $ss->write( 'c' );
+ $this->assertSame( 'ac', (string)$ss );
+ }
+
+ /** @dataProvider provideSeekGetContents */
+ public function testSeekGetContents( $input, $offset, $whence, $expected ) {
+ $ss = new StringStream( $input );
+ $ss->seek( 1 );
+ $ss->seek( $offset, $whence );
+ $this->assertSame( $expected, $ss->getContents() );
+ }
+
+ public static function provideSeekRead() {
+ return [
+ [ 'abcde', 0, SEEK_SET, 1, 'a' ],
+ [ 'abcde', 0, SEEK_SET, 2, 'ab' ],
+ [ 'abcde', 4, SEEK_SET, 2, 'e' ],
+ [ 'abcde', 5, SEEK_SET, 1, '' ],
+ [ 'abcde', 1, SEEK_CUR, 1, 'c' ],
+ [ 'abcde', 0, SEEK_END, 1, '' ],
+ [ 'abcde', -1, SEEK_END, 1, 'e' ],
+ ];
+ }
+
+ /** @dataProvider provideSeekRead */
+ public function testSeekRead( $input, $offset, $whence, $length, $expected ) {
+ $ss = new StringStream( $input );
+ $ss->seek( 1 );
+ $ss->seek( $offset, $whence );
+ $this->assertSame( $expected, $ss->read( $length ) );
+ }
+
+ /** @expectedException \InvalidArgumentException */
+ public function testReadBeyondEnd() {
+ $ss = new StringStream( 'abc' );
+ $ss->seek( 1, SEEK_END );
+ }
+
+ /** @expectedException \InvalidArgumentException */
+ public function testReadBeforeStart() {
+ $ss = new StringStream( 'abc' );
+ $ss->seek( -1 );
+ }
+}
--- /dev/null
+<?php
+
+namespace MediaWiki\Tests\Revision;
+
+use MediaWiki\Revision\FallbackSlotRoleHandler;
+use Title;
+
+/**
+ * @covers \MediaWiki\Revision\FallbackSlotRoleHandler
+ */
+class FallbackSlotRoleHandlerTest extends \MediaWikiUnitTestCase {
+
+ /**
+ * @return Title
+ */
+ private function makeBlankTitleObject() {
+ return $this->createMock( Title::class );
+ }
+
+ /**
+ * @covers \MediaWiki\Revision\FallbackSlotRoleHandler::__construct
+ * @covers \MediaWiki\Revision\FallbackSlotRoleHandler::getRole()
+ * @covers \MediaWiki\Revision\FallbackSlotRoleHandler::getNameMessageKey()
+ * @covers \MediaWiki\Revision\FallbackSlotRoleHandler::getDefaultModel()
+ * @covers \MediaWiki\Revision\FallbackSlotRoleHandler::getOutputLayoutHints()
+ */
+ public function testConstruction() {
+ $handler = new FallbackSlotRoleHandler( 'foo' );
+ $this->assertSame( 'foo', $handler->getRole() );
+ $this->assertSame( 'slot-name-foo', $handler->getNameMessageKey() );
+
+ $title = $this->makeBlankTitleObject();
+ $this->assertSame( CONTENT_MODEL_TEXT, $handler->getDefaultModel( $title ) );
+
+ $hints = $handler->getOutputLayoutHints();
+ $this->assertArrayHasKey( 'display', $hints );
+ $this->assertArrayHasKey( 'region', $hints );
+ $this->assertArrayHasKey( 'placement', $hints );
+ }
+
+ /**
+ * @covers \MediaWiki\Revision\FallbackSlotRoleHandler::isAllowedModel()
+ */
+ public function testIsAllowedModel() {
+ $handler = new FallbackSlotRoleHandler( 'foo', 'FooModel' );
+
+ // For the fallback handler, no models are allowed
+ $title = $this->makeBlankTitleObject();
+ $this->assertFalse( $handler->isAllowedModel( 'FooModel', $title ) );
+ $this->assertFalse( $handler->isAllowedModel( 'QuaxModel', $title ) );
+ }
+
+ /**
+ * @covers \MediaWiki\Revision\SlotRoleHandler::isAllowedModel()
+ */
+ public function testIsAllowedOn() {
+ $handler = new FallbackSlotRoleHandler( 'foo', 'FooModel' );
+
+ $title = $this->makeBlankTitleObject();
+ $this->assertFalse( $handler->isAllowedOn( $title ) );
+ }
+
+ /**
+ * @covers \MediaWiki\Revision\FallbackSlotRoleHandler::supportsArticleCount()
+ */
+ public function testSupportsArticleCount() {
+ $handler = new FallbackSlotRoleHandler( 'foo', 'FooModel' );
+
+ $this->assertFalse( $handler->supportsArticleCount() );
+ }
+
+}
--- /dev/null
+<?php
+
+namespace MediaWiki\Tests\Revision;
+
+use MediaWiki\Revision\SlotRoleHandler;
+use Title;
+
+/**
+ * @covers \MediaWiki\Revision\SlotRoleHandler
+ */
+class SlotRoleHandlerTest extends \MediaWikiUnitTestCase {
+
+ /**
+ * @return Title
+ */
+ private function makeBlankTitleObject() {
+ return $this->createMock( Title::class );
+ }
+
+ /**
+ * @covers \MediaWiki\Revision\SlotRoleHandler::__construct
+ * @covers \MediaWiki\Revision\SlotRoleHandler::getRole()
+ * @covers \MediaWiki\Revision\SlotRoleHandler::getNameMessageKey()
+ * @covers \MediaWiki\Revision\SlotRoleHandler::getDefaultModel()
+ * @covers \MediaWiki\Revision\SlotRoleHandler::getOutputLayoutHints()
+ */
+ public function testConstruction() {
+ $handler = new SlotRoleHandler( 'foo', 'FooModel', [ 'frob' => 'niz' ] );
+ $this->assertSame( 'foo', $handler->getRole() );
+ $this->assertSame( 'slot-name-foo', $handler->getNameMessageKey() );
+
+ $title = $this->makeBlankTitleObject();
+ $this->assertSame( 'FooModel', $handler->getDefaultModel( $title ) );
+
+ $hints = $handler->getOutputLayoutHints();
+ $this->assertArrayHasKey( 'frob', $hints );
+ $this->assertSame( 'niz', $hints['frob'] );
+
+ $this->assertArrayHasKey( 'display', $hints );
+ $this->assertArrayHasKey( 'region', $hints );
+ $this->assertArrayHasKey( 'placement', $hints );
+ }
+
+ /**
+ * @covers \MediaWiki\Revision\SlotRoleHandler::isAllowedModel()
+ */
+ public function testIsAllowedModel() {
+ $handler = new SlotRoleHandler( 'foo', 'FooModel' );
+
+ $title = $this->makeBlankTitleObject();
+ $this->assertTrue( $handler->isAllowedModel( 'FooModel', $title ) );
+ $this->assertFalse( $handler->isAllowedModel( 'QuaxModel', $title ) );
+ }
+
+ /**
+ * @covers \MediaWiki\Revision\SlotRoleHandler::supportsArticleCount()
+ */
+ public function testSupportsArticleCount() {
+ $handler = new SlotRoleHandler( 'foo', 'FooModel' );
+
+ $this->assertFalse( $handler->supportsArticleCount() );
+ }
+
+}
--- /dev/null
+<?php
+
+/**
+ * @coversNothing
+ */
+class ServiceWiringTest extends \MediaWikiUnitTestCase {
+ public function testServicesAreSorted() {
+ global $IP;
+ $services = array_keys( require "$IP/includes/ServiceWiring.php" );
+ $sortedServices = $services;
+ natcasesort( $sortedServices );
+
+ $this->assertSame( $sortedServices, $services,
+ 'Please keep services sorted alphabetically' );
+ }
+}
--- /dev/null
+<?php
+
+class SiteConfigurationTest extends \MediaWikiUnitTestCase {
+
+ /**
+ * @var SiteConfiguration
+ */
+ protected $mConf;
+
+ protected function setUp() {
+ parent::setUp();
+
+ $this->mConf = new SiteConfiguration;
+
+ $this->mConf->suffixes = [ 'wikipedia' => 'wiki' ];
+ $this->mConf->wikis = [ 'enwiki', 'dewiki', 'frwiki' ];
+ $this->mConf->settings = [
+ 'SimpleKey' => [
+ 'wiki' => 'wiki',
+ 'tag' => 'tag',
+ 'enwiki' => 'enwiki',
+ 'dewiki' => 'dewiki',
+ 'frwiki' => 'frwiki',
+ ],
+
+ 'Fallback' => [
+ 'default' => 'default',
+ 'wiki' => 'wiki',
+ 'tag' => 'tag',
+ 'frwiki' => 'frwiki',
+ 'null_wiki' => null,
+ ],
+
+ 'WithParams' => [
+ 'default' => '$lang $site $wiki',
+ ],
+
+ '+SomeGlobal' => [
+ 'wiki' => [
+ 'wiki' => 'wiki',
+ ],
+ 'tag' => [
+ 'tag' => 'tag',
+ ],
+ 'enwiki' => [
+ 'enwiki' => 'enwiki',
+ ],
+ 'dewiki' => [
+ 'dewiki' => 'dewiki',
+ ],
+ 'frwiki' => [
+ 'frwiki' => 'frwiki',
+ ],
+ ],
+
+ 'MergeIt' => [
+ '+wiki' => [
+ 'wiki' => 'wiki',
+ ],
+ '+tag' => [
+ 'tag' => 'tag',
+ ],
+ 'default' => [
+ 'default' => 'default',
+ ],
+ '+enwiki' => [
+ 'enwiki' => 'enwiki',
+ ],
+ '+dewiki' => [
+ 'dewiki' => 'dewiki',
+ ],
+ '+frwiki' => [
+ 'frwiki' => 'frwiki',
+ ],
+ ],
+ ];
+
+ $GLOBALS['SomeGlobal'] = [ 'SomeGlobal' => 'SomeGlobal' ];
+ }
+
+ /**
+ * This function is used as a callback within the tests below
+ */
+ public static function getSiteParamsCallback( $conf, $wiki ) {
+ $site = null;
+ $lang = null;
+ foreach ( $conf->suffixes as $suffix ) {
+ if ( substr( $wiki, -strlen( $suffix ) ) == $suffix ) {
+ $site = $suffix;
+ $lang = substr( $wiki, 0, -strlen( $suffix ) );
+ break;
+ }
+ }
+
+ return [
+ 'suffix' => $site,
+ 'lang' => $lang,
+ 'params' => [
+ 'lang' => $lang,
+ 'site' => $site,
+ 'wiki' => $wiki,
+ ],
+ 'tags' => [ 'tag' ],
+ ];
+ }
+
+ /**
+ * @covers SiteConfiguration::siteFromDB
+ */
+ public function testSiteFromDb() {
+ $this->assertEquals(
+ [ 'wikipedia', 'en' ],
+ $this->mConf->siteFromDB( 'enwiki' ),
+ 'siteFromDB()'
+ );
+ $this->assertEquals(
+ [ 'wikipedia', '' ],
+ $this->mConf->siteFromDB( 'wiki' ),
+ 'siteFromDB() on a suffix'
+ );
+ $this->assertEquals(
+ [ null, null ],
+ $this->mConf->siteFromDB( 'wikien' ),
+ 'siteFromDB() on a non-existing wiki'
+ );
+
+ $this->mConf->suffixes = [ 'wiki', '' ];
+ $this->assertEquals(
+ [ '', 'wikien' ],
+ $this->mConf->siteFromDB( 'wikien' ),
+ 'siteFromDB() on a non-existing wiki (2)'
+ );
+ }
+
+ /**
+ * @covers SiteConfiguration::getLocalDatabases
+ */
+ public function testGetLocalDatabases() {
+ $this->assertEquals(
+ [ 'enwiki', 'dewiki', 'frwiki' ],
+ $this->mConf->getLocalDatabases(),
+ 'getLocalDatabases()'
+ );
+ }
+
+ /**
+ * @covers SiteConfiguration::get
+ */
+ public function testGetConfVariables() {
+ // Simple
+ $this->assertEquals(
+ 'enwiki',
+ $this->mConf->get( 'SimpleKey', 'enwiki', 'wiki' ),
+ 'get(): simple setting on an existing wiki'
+ );
+ $this->assertEquals(
+ 'dewiki',
+ $this->mConf->get( 'SimpleKey', 'dewiki', 'wiki' ),
+ 'get(): simple setting on an existing wiki (2)'
+ );
+ $this->assertEquals(
+ 'frwiki',
+ $this->mConf->get( 'SimpleKey', 'frwiki', 'wiki' ),
+ 'get(): simple setting on an existing wiki (3)'
+ );
+ $this->assertEquals(
+ 'wiki',
+ $this->mConf->get( 'SimpleKey', 'wiki', 'wiki' ),
+ 'get(): simple setting on an suffix'
+ );
+ $this->assertEquals(
+ 'wiki',
+ $this->mConf->get( 'SimpleKey', 'eswiki', 'wiki' ),
+ 'get(): simple setting on an non-existing wiki'
+ );
+
+ // Fallback
+ $this->assertEquals(
+ 'wiki',
+ $this->mConf->get( 'Fallback', 'enwiki', 'wiki' ),
+ 'get(): fallback setting on an existing wiki'
+ );
+ $this->assertEquals(
+ 'tag',
+ $this->mConf->get( 'Fallback', 'dewiki', 'wiki', [], [ 'tag' ] ),
+ 'get(): fallback setting on an existing wiki (with wiki tag)'
+ );
+ $this->assertEquals(
+ 'frwiki',
+ $this->mConf->get( 'Fallback', 'frwiki', 'wiki', [], [ 'tag' ] ),
+ 'get(): no fallback if wiki has its own setting (matching tag)'
+ );
+ $this->assertSame(
+ // Potential regression test for T192855
+ null,
+ $this->mConf->get( 'Fallback', 'null_wiki', 'wiki', [], [ 'tag' ] ),
+ 'get(): no fallback if wiki has its own setting (matching tag and uses null)'
+ );
+ $this->assertEquals(
+ 'wiki',
+ $this->mConf->get( 'Fallback', 'wiki', 'wiki' ),
+ 'get(): fallback setting on an suffix'
+ );
+ $this->assertEquals(
+ 'wiki',
+ $this->mConf->get( 'Fallback', 'wiki', 'wiki', [], [ 'tag' ] ),
+ 'get(): fallback setting on an suffix (with wiki tag)'
+ );
+ $this->assertEquals(
+ 'wiki',
+ $this->mConf->get( 'Fallback', 'eswiki', 'wiki' ),
+ 'get(): fallback setting on an non-existing wiki'
+ );
+ $this->assertEquals(
+ 'tag',
+ $this->mConf->get( 'Fallback', 'eswiki', 'wiki', [], [ 'tag' ] ),
+ 'get(): fallback setting on an non-existing wiki (with wiki tag)'
+ );
+
+ // Merging
+ $common = [ 'wiki' => 'wiki', 'default' => 'default' ];
+ $commonTag = [ 'tag' => 'tag', 'wiki' => 'wiki', 'default' => 'default' ];
+ $this->assertEquals(
+ [ 'enwiki' => 'enwiki' ] + $common,
+ $this->mConf->get( 'MergeIt', 'enwiki', 'wiki' ),
+ 'get(): merging setting on an existing wiki'
+ );
+ $this->assertEquals(
+ [ 'enwiki' => 'enwiki' ] + $commonTag,
+ $this->mConf->get( 'MergeIt', 'enwiki', 'wiki', [], [ 'tag' ] ),
+ 'get(): merging setting on an existing wiki (with tag)'
+ );
+ $this->assertEquals(
+ [ 'dewiki' => 'dewiki' ] + $common,
+ $this->mConf->get( 'MergeIt', 'dewiki', 'wiki' ),
+ 'get(): merging setting on an existing wiki (2)'
+ );
+ $this->assertEquals(
+ [ 'dewiki' => 'dewiki' ] + $commonTag,
+ $this->mConf->get( 'MergeIt', 'dewiki', 'wiki', [], [ 'tag' ] ),
+ 'get(): merging setting on an existing wiki (2) (with tag)'
+ );
+ $this->assertEquals(
+ [ 'frwiki' => 'frwiki' ] + $common,
+ $this->mConf->get( 'MergeIt', 'frwiki', 'wiki' ),
+ 'get(): merging setting on an existing wiki (3)'
+ );
+ $this->assertEquals(
+ [ 'frwiki' => 'frwiki' ] + $commonTag,
+ $this->mConf->get( 'MergeIt', 'frwiki', 'wiki', [], [ 'tag' ] ),
+ 'get(): merging setting on an existing wiki (3) (with tag)'
+ );
+ $this->assertEquals(
+ [ 'wiki' => 'wiki' ] + $common,
+ $this->mConf->get( 'MergeIt', 'wiki', 'wiki' ),
+ 'get(): merging setting on an suffix'
+ );
+ $this->assertEquals(
+ [ 'wiki' => 'wiki' ] + $commonTag,
+ $this->mConf->get( 'MergeIt', 'wiki', 'wiki', [], [ 'tag' ] ),
+ 'get(): merging setting on an suffix (with tag)'
+ );
+ $this->assertEquals(
+ $common,
+ $this->mConf->get( 'MergeIt', 'eswiki', 'wiki' ),
+ 'get(): merging setting on an non-existing wiki'
+ );
+ $this->assertEquals(
+ $commonTag,
+ $this->mConf->get( 'MergeIt', 'eswiki', 'wiki', [], [ 'tag' ] ),
+ 'get(): merging setting on an non-existing wiki (with tag)'
+ );
+ }
+
+ /**
+ * @covers SiteConfiguration::siteFromDB
+ */
+ public function testSiteFromDbWithCallback() {
+ $this->mConf->siteParamsCallback = 'SiteConfigurationTest::getSiteParamsCallback';
+
+ $this->assertEquals(
+ [ 'wiki', 'en' ],
+ $this->mConf->siteFromDB( 'enwiki' ),
+ 'siteFromDB() with callback'
+ );
+ $this->assertEquals(
+ [ 'wiki', '' ],
+ $this->mConf->siteFromDB( 'wiki' ),
+ 'siteFromDB() with callback on a suffix'
+ );
+ $this->assertEquals(
+ [ null, null ],
+ $this->mConf->siteFromDB( 'wikien' ),
+ 'siteFromDB() with callback on a non-existing wiki'
+ );
+ }
+
+ /**
+ * @covers SiteConfiguration::get
+ */
+ public function testParameterReplacement() {
+ $this->mConf->siteParamsCallback = 'SiteConfigurationTest::getSiteParamsCallback';
+
+ $this->assertEquals(
+ 'en wiki enwiki',
+ $this->mConf->get( 'WithParams', 'enwiki', 'wiki' ),
+ 'get(): parameter replacement on an existing wiki'
+ );
+ $this->assertEquals(
+ 'de wiki dewiki',
+ $this->mConf->get( 'WithParams', 'dewiki', 'wiki' ),
+ 'get(): parameter replacement on an existing wiki (2)'
+ );
+ $this->assertEquals(
+ 'fr wiki frwiki',
+ $this->mConf->get( 'WithParams', 'frwiki', 'wiki' ),
+ 'get(): parameter replacement on an existing wiki (3)'
+ );
+ $this->assertEquals(
+ ' wiki wiki',
+ $this->mConf->get( 'WithParams', 'wiki', 'wiki' ),
+ 'get(): parameter replacement on an suffix'
+ );
+ $this->assertEquals(
+ 'es wiki eswiki',
+ $this->mConf->get( 'WithParams', 'eswiki', 'wiki' ),
+ 'get(): parameter replacement on an non-existing wiki'
+ );
+ }
+
+ /**
+ * @covers SiteConfiguration::getAll
+ */
+ public function testGetAllGlobals() {
+ $this->mConf->siteParamsCallback = 'SiteConfigurationTest::getSiteParamsCallback';
+
+ $getall = [
+ 'SimpleKey' => 'enwiki',
+ 'Fallback' => 'tag',
+ 'WithParams' => 'en wiki enwiki',
+ 'SomeGlobal' => [ 'enwiki' => 'enwiki' ] + $GLOBALS['SomeGlobal'],
+ 'MergeIt' => [
+ 'enwiki' => 'enwiki',
+ 'tag' => 'tag',
+ 'wiki' => 'wiki',
+ 'default' => 'default'
+ ],
+ ];
+ $this->assertEquals( $getall, $this->mConf->getAll( 'enwiki' ), 'getAll()' );
+
+ $this->mConf->extractAllGlobals( 'enwiki', 'wiki' );
+
+ $this->assertEquals(
+ $getall['SimpleKey'],
+ $GLOBALS['SimpleKey'],
+ 'extractAllGlobals(): simple setting'
+ );
+ $this->assertEquals(
+ $getall['Fallback'],
+ $GLOBALS['Fallback'],
+ 'extractAllGlobals(): fallback setting'
+ );
+ $this->assertEquals(
+ $getall['WithParams'],
+ $GLOBALS['WithParams'],
+ 'extractAllGlobals(): parameter replacement'
+ );
+ $this->assertEquals(
+ $getall['SomeGlobal'],
+ $GLOBALS['SomeGlobal'],
+ 'extractAllGlobals(): merging with global'
+ );
+ $this->assertEquals(
+ $getall['MergeIt'],
+ $GLOBALS['MergeIt'],
+ 'extractAllGlobals(): merging setting'
+ );
+ }
+}
--- /dev/null
+<?php
+
+namespace MediaWiki\Edit;
+
+use ParserOutput;
+
+/**
+ * @covers \MediaWiki\Edit\PreparedEdit
+ */
+class PreparedEditTest extends \MediaWikiUnitTestCase {
+ function testCallback() {
+ $output = new ParserOutput();
+ $edit = new PreparedEdit();
+ $edit->parserOutputCallback = function () {
+ return new ParserOutput();
+ };
+
+ $this->assertEquals( $output, $edit->getOutput() );
+ $this->assertEquals( $output, $edit->output );
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * @group Xml
+ */
+class XmlSelectTest extends \MediaWikiUnitTestCase {
+
+ /**
+ * @var XmlSelect
+ */
+ protected $select;
+
+ protected function setUp() {
+ parent::setUp();
+ $this->select = new XmlSelect();
+ }
+
+ protected function tearDown() {
+ parent::tearDown();
+ $this->select = null;
+ }
+
+ /**
+ * @covers XmlSelect::__construct
+ */
+ public function testConstructWithoutParameters() {
+ $this->assertEquals( '<select></select>', $this->select->getHTML() );
+ }
+
+ /**
+ * Parameters are $name (false), $id (false), $default (false)
+ * @dataProvider provideConstructionParameters
+ * @covers XmlSelect::__construct
+ */
+ public function testConstructParameters( $name, $id, $default, $expected ) {
+ $this->select = new XmlSelect( $name, $id, $default );
+ $this->assertEquals( $expected, $this->select->getHTML() );
+ }
+
+ /**
+ * Provide parameters for testConstructParameters() which use three
+ * parameters:
+ * - $name (default: false)
+ * - $id (default: false)
+ * - $default (default: false)
+ * Provides a fourth parameters representing the expected HTML output
+ */
+ public static function provideConstructionParameters() {
+ return [
+ /**
+ * Values are set following a 3-bit Gray code where two successive
+ * values differ by only one value.
+ * See https://en.wikipedia.org/wiki/Gray_code
+ */
+ # $name $id $default
+ [ false, false, false, '<select></select>' ],
+ [ false, false, 'foo', '<select></select>' ],
+ [ false, 'id', 'foo', '<select id="id"></select>' ],
+ [ false, 'id', false, '<select id="id"></select>' ],
+ [ 'name', 'id', false, '<select name="name" id="id"></select>' ],
+ [ 'name', 'id', 'foo', '<select name="name" id="id"></select>' ],
+ [ 'name', false, 'foo', '<select name="name"></select>' ],
+ [ 'name', false, false, '<select name="name"></select>' ],
+ ];
+ }
+
+ /**
+ * @covers XmlSelect::addOption
+ */
+ public function testAddOption() {
+ $this->select->addOption( 'foo' );
+ $this->assertEquals(
+ '<select><option value="foo">foo</option></select>',
+ $this->select->getHTML()
+ );
+ }
+
+ /**
+ * @covers XmlSelect::addOption
+ */
+ public function testAddOptionWithDefault() {
+ $this->select->addOption( 'foo', true );
+ $this->assertEquals(
+ '<select><option value="1">foo</option></select>',
+ $this->select->getHTML()
+ );
+ }
+
+ /**
+ * @covers XmlSelect::addOption
+ */
+ public function testAddOptionWithFalse() {
+ $this->select->addOption( 'foo', false );
+ $this->assertEquals(
+ '<select><option value="foo">foo</option></select>',
+ $this->select->getHTML()
+ );
+ }
+
+ /**
+ * @covers XmlSelect::addOption
+ */
+ public function testAddOptionWithValueZero() {
+ $this->select->addOption( 'foo', 0 );
+ $this->assertEquals(
+ '<select><option value="0">foo</option></select>',
+ $this->select->getHTML()
+ );
+ }
+
+ /**
+ * @covers XmlSelect::setDefault
+ */
+ public function testSetDefault() {
+ $this->select->setDefault( 'bar1' );
+ $this->select->addOption( 'foo1' );
+ $this->select->addOption( 'bar1' );
+ $this->select->addOption( 'foo2' );
+ $this->assertEquals(
+ '<select><option value="foo1">foo1</option>' . "\n" .
+ '<option value="bar1" selected="">bar1</option>' . "\n" .
+ '<option value="foo2">foo2</option></select>', $this->select->getHTML() );
+ }
+
+ /**
+ * Adding default later on should set the correct selection or
+ * raise an exception.
+ * To handle this, we need to render the options in getHtml()
+ * @covers XmlSelect::setDefault
+ */
+ public function testSetDefaultAfterAddingOptions() {
+ $this->select->addOption( 'foo1' );
+ $this->select->addOption( 'bar1' );
+ $this->select->addOption( 'foo2' );
+ $this->select->setDefault( 'bar1' ); # setting default after adding options
+ $this->assertEquals(
+ '<select><option value="foo1">foo1</option>' . "\n" .
+ '<option value="bar1" selected="">bar1</option>' . "\n" .
+ '<option value="foo2">foo2</option></select>', $this->select->getHTML() );
+ }
+
+ /**
+ * @covers XmlSelect::setAttribute
+ * @covers XmlSelect::getAttribute
+ */
+ public function testGetAttributes() {
+ # create some attributes
+ $this->select->setAttribute( 'dummy', 0x777 );
+ $this->select->setAttribute( 'string', 'euro €' );
+ $this->select->setAttribute( 1911, 'razor' );
+
+ # verify we can retrieve them
+ $this->assertEquals(
+ $this->select->getAttribute( 'dummy' ),
+ 0x777
+ );
+ $this->assertEquals(
+ $this->select->getAttribute( 'string' ),
+ 'euro €'
+ );
+ $this->assertEquals(
+ $this->select->getAttribute( 1911 ),
+ 'razor'
+ );
+
+ # inexistent keys should give us 'null'
+ $this->assertEquals(
+ $this->select->getAttribute( 'I DO NOT EXIT' ),
+ null
+ );
+
+ # verify string / integer
+ $this->assertEquals(
+ $this->select->getAttribute( '1911' ),
+ 'razor'
+ );
+ $this->assertEquals(
+ $this->select->getAttribute( 'dummy' ),
+ 0x777
+ );
+ }
+}
--- /dev/null
+<?php
+
+namespace MediaWiki\Auth;
+
+/**
+ * @group AuthManager
+ * @covers \MediaWiki\Auth\AuthenticationResponse
+ */
+class AuthenticationResponseTest extends \MediaWikiUnitTestCase {
+ /**
+ * @dataProvider provideConstructors
+ * @param string $constructor
+ * @param array $args
+ * @param array|Exception $expect
+ */
+ public function testConstructors( $constructor, $args, $expect ) {
+ if ( is_array( $expect ) ) {
+ $res = new AuthenticationResponse();
+ $res->messageType = 'warning';
+ foreach ( $expect as $field => $value ) {
+ $res->$field = $value;
+ }
+ $ret = call_user_func_array( "MediaWiki\\Auth\\AuthenticationResponse::$constructor", $args );
+ $this->assertEquals( $res, $ret );
+ } else {
+ try {
+ call_user_func_array( "MediaWiki\\Auth\\AuthenticationResponse::$constructor", $args );
+ $this->fail( 'Expected exception not thrown' );
+ } catch ( \Exception $ex ) {
+ $this->assertEquals( $expect, $ex );
+ }
+ }
+ }
+
+ public function provideConstructors() {
+ $req = $this->getMockForAbstractClass( AuthenticationRequest::class );
+ $msg = new \Message( 'mainpage' );
+
+ return [
+ [ 'newPass', [], [
+ 'status' => AuthenticationResponse::PASS,
+ ] ],
+ [ 'newPass', [ 'name' ], [
+ 'status' => AuthenticationResponse::PASS,
+ 'username' => 'name',
+ ] ],
+ [ 'newPass', [ 'name', null ], [
+ 'status' => AuthenticationResponse::PASS,
+ 'username' => 'name',
+ ] ],
+
+ [ 'newFail', [ $msg ], [
+ 'status' => AuthenticationResponse::FAIL,
+ 'message' => $msg,
+ 'messageType' => 'error',
+ ] ],
+
+ [ 'newRestart', [ $msg ], [
+ 'status' => AuthenticationResponse::RESTART,
+ 'message' => $msg,
+ ] ],
+
+ [ 'newAbstain', [], [
+ 'status' => AuthenticationResponse::ABSTAIN,
+ ] ],
+
+ [ 'newUI', [ [ $req ], $msg ], [
+ 'status' => AuthenticationResponse::UI,
+ 'neededRequests' => [ $req ],
+ 'message' => $msg,
+ 'messageType' => 'warning',
+ ] ],
+
+ [ 'newUI', [ [ $req ], $msg, 'warning' ], [
+ 'status' => AuthenticationResponse::UI,
+ 'neededRequests' => [ $req ],
+ 'message' => $msg,
+ 'messageType' => 'warning',
+ ] ],
+
+ [ 'newUI', [ [ $req ], $msg, 'error' ], [
+ 'status' => AuthenticationResponse::UI,
+ 'neededRequests' => [ $req ],
+ 'message' => $msg,
+ 'messageType' => 'error',
+ ] ],
+ [ 'newUI', [ [], $msg ],
+ new \InvalidArgumentException( '$reqs may not be empty' )
+ ],
+
+ [ 'newRedirect', [ [ $req ], 'http://example.org/redir' ], [
+ 'status' => AuthenticationResponse::REDIRECT,
+ 'neededRequests' => [ $req ],
+ 'redirectTarget' => 'http://example.org/redir',
+ ] ],
+ [
+ 'newRedirect',
+ [ [ $req ], 'http://example.org/redir', [ 'foo' => 'bar' ] ],
+ [
+ 'status' => AuthenticationResponse::REDIRECT,
+ 'neededRequests' => [ $req ],
+ 'redirectTarget' => 'http://example.org/redir',
+ 'redirectApiData' => [ 'foo' => 'bar' ],
+ ]
+ ],
+ [ 'newRedirect', [ [], 'http://example.org/redir' ],
+ new \InvalidArgumentException( '$reqs may not be empty' )
+ ],
+ ];
+ }
+
+}
--- /dev/null
+<?php
+
+/**
+ * @covers ChangesListFilterGroup
+ */
+class ChangesListFilterGroupTest extends \MediaWikiUnitTestCase {
+ /**
+ * phpcs:disable Generic.Files.LineLength
+ * @expectedException MWException
+ * @expectedExceptionMessage Group names may not contain '_'. Use the naming convention: 'camelCase'
+ * phpcs:enable
+ */
+ public function testReservedCharacter() {
+ new MockChangesListFilterGroup(
+ [
+ 'type' => 'some_type',
+ 'name' => 'group_name',
+ 'priority' => 1,
+ 'filters' => [],
+ ]
+ );
+ }
+
+ public function testAutoPriorities() {
+ $group = new MockChangesListFilterGroup(
+ [
+ 'type' => 'some_type',
+ 'name' => 'groupName',
+ 'isFullCoverage' => true,
+ 'priority' => 1,
+ 'filters' => [
+ [ 'name' => 'hidefoo' ],
+ [ 'name' => 'hidebar' ],
+ [ 'name' => 'hidebaz' ],
+ ],
+ ]
+ );
+
+ $filters = $group->getFilters();
+ $this->assertEquals(
+ [
+ -2,
+ -3,
+ -4,
+ ],
+ array_map(
+ function ( $f ) {
+ return $f->getPriority();
+ },
+ array_values( $filters )
+ )
+ );
+ }
+
+ // Get without warnings
+ public function testGetFilter() {
+ $group = new MockChangesListFilterGroup(
+ [
+ 'type' => 'some_type',
+ 'name' => 'groupName',
+ 'isFullCoverage' => true,
+ 'priority' => 1,
+ 'filters' => [
+ [ 'name' => 'foo' ],
+ ],
+ ]
+ );
+
+ $this->assertEquals(
+ 'foo',
+ $group->getFilter( 'foo' )->getName()
+ );
+
+ $this->assertEquals(
+ null,
+ $group->getFilter( 'bar' )
+ );
+ }
+}
--- /dev/null
+<?php
+
+class HashConfigTest extends \MediaWikiUnitTestCase {
+
+ /**
+ * @covers HashConfig::newInstance
+ */
+ public function testNewInstance() {
+ $conf = HashConfig::newInstance();
+ $this->assertInstanceOf( HashConfig::class, $conf );
+ }
+
+ /**
+ * @covers HashConfig::__construct
+ */
+ public function testConstructor() {
+ $conf = new HashConfig();
+ $this->assertInstanceOf( HashConfig::class, $conf );
+
+ // Test passing arguments to the constructor
+ $conf2 = new HashConfig( [
+ 'one' => '1',
+ ] );
+ $this->assertEquals( '1', $conf2->get( 'one' ) );
+ }
+
+ /**
+ * @covers HashConfig::get
+ */
+ public function testGet() {
+ $conf = new HashConfig( [
+ 'one' => '1',
+ ] );
+ $this->assertEquals( '1', $conf->get( 'one' ) );
+ $this->setExpectedException( ConfigException::class, 'HashConfig::get: undefined option' );
+ $conf->get( 'two' );
+ }
+
+ /**
+ * @covers HashConfig::has
+ */
+ public function testHas() {
+ $conf = new HashConfig( [
+ 'one' => '1',
+ ] );
+ $this->assertTrue( $conf->has( 'one' ) );
+ $this->assertFalse( $conf->has( 'two' ) );
+ }
+
+ /**
+ * @covers HashConfig::set
+ */
+ public function testSet() {
+ $conf = new HashConfig( [
+ 'one' => '1',
+ ] );
+ $conf->set( 'two', '2' );
+ $this->assertEquals( '2', $conf->get( 'two' ) );
+ // Check that set overwrites
+ $conf->set( 'one', '3' );
+ $this->assertEquals( '3', $conf->get( 'one' ) );
+ }
+}
--- /dev/null
+<?php
+
+class MultiConfigTest extends \MediaWikiUnitTestCase {
+
+ /**
+ * Tests that settings are fetched in the right order
+ *
+ * @covers MultiConfig::__construct
+ * @covers MultiConfig::get
+ */
+ public function testGet() {
+ $multi = new MultiConfig( [
+ new HashConfig( [ 'foo' => 'bar' ] ),
+ new HashConfig( [ 'foo' => 'baz', 'bar' => 'foo' ] ),
+ new HashConfig( [ 'bar' => 'baz' ] ),
+ ] );
+
+ $this->assertEquals( 'bar', $multi->get( 'foo' ) );
+ $this->assertEquals( 'foo', $multi->get( 'bar' ) );
+ $this->setExpectedException( ConfigException::class, 'MultiConfig::get: undefined option:' );
+ $multi->get( 'notset' );
+ }
+
+ /**
+ * @covers MultiConfig::has
+ */
+ public function testHas() {
+ $conf = new MultiConfig( [
+ new HashConfig( [ 'foo' => 'foo' ] ),
+ new HashConfig( [ 'something' => 'bleh' ] ),
+ new HashConfig( [ 'meh' => 'eh' ] ),
+ ] );
+
+ $this->assertTrue( $conf->has( 'foo' ) );
+ $this->assertTrue( $conf->has( 'something' ) );
+ $this->assertTrue( $conf->has( 'meh' ) );
+ $this->assertFalse( $conf->has( 'what' ) );
+ }
+}
--- /dev/null
+<?php
+
+use MediaWiki\Config\ServiceOptions;
+
+/**
+ * @coversDefaultClass \MediaWiki\Config\ServiceOptions
+ */
+class ServiceOptionsTest extends \MediaWikiUnitTestCase {
+ public static $testObj;
+
+ public static function setUpBeforeClass() {
+ parent::setUpBeforeClass();
+
+ self::$testObj = new stdclass();
+ }
+
+ /**
+ * @dataProvider provideConstructor
+ * @covers ::__construct
+ * @covers ::assertRequiredOptions
+ * @covers ::get
+ */
+ public function testConstructor( $expected, $keys, ...$sources ) {
+ $options = new ServiceOptions( $keys, ...$sources );
+
+ foreach ( $expected as $key => $val ) {
+ $this->assertSame( $val, $options->get( $key ) );
+ }
+
+ // This is lumped in the same test because there's no support for depending on a test that
+ // has a data provider.
+ $options->assertRequiredOptions( array_keys( $expected ) );
+
+ // Suppress warning if no assertions were run. This is expected for empty arguments.
+ $this->assertTrue( true );
+ }
+
+ public function provideConstructor() {
+ return [
+ 'No keys' => [ [], [], [ 'a' => 'aval' ] ],
+ 'Simple array source' => [
+ [ 'a' => 'aval', 'b' => 'bval' ],
+ [ 'a', 'b' ],
+ [ 'a' => 'aval', 'b' => 'bval', 'c' => 'cval' ],
+ ],
+ 'Simple HashConfig source' => [
+ [ 'a' => 'aval', 'b' => 'bval' ],
+ [ 'a', 'b' ],
+ new HashConfig( [ 'a' => 'aval', 'b' => 'bval', 'c' => 'cval' ] ),
+ ],
+ 'Three different sources' => [
+ [ 'a' => 'aval', 'b' => 'bval' ],
+ [ 'a', 'b' ],
+ [ 'z' => 'zval' ],
+ new HashConfig( [ 'a' => 'aval', 'c' => 'cval' ] ),
+ [ 'b' => 'bval', 'd' => 'dval' ],
+ ],
+ 'null key' => [
+ [ 'a' => null ],
+ [ 'a' ],
+ [ 'a' => null ],
+ ],
+ 'Numeric option name' => [
+ [ '0' => 'nothing' ],
+ [ '0' ],
+ [ '0' => 'nothing' ],
+ ],
+ 'Multiple sources for one key' => [
+ [ 'a' => 'winner' ],
+ [ 'a' ],
+ [ 'a' => 'winner' ],
+ [ 'a' => 'second place' ],
+ ],
+ 'Object value is passed by reference' => [
+ [ 'a' => self::$testObj ],
+ [ 'a' ],
+ [ 'a' => self::$testObj ],
+ ],
+ ];
+ }
+
+ /**
+ * @covers ::__construct
+ */
+ public function testKeyNotFound() {
+ $this->setExpectedException( InvalidArgumentException::class,
+ 'Key "a" not found in input sources' );
+
+ new ServiceOptions( [ 'a' ], [ 'b' => 'bval' ], [ 'c' => 'cval' ] );
+ }
+
+ /**
+ * @covers ::__construct
+ * @covers ::assertRequiredOptions
+ */
+ public function testOutOfOrderAssertRequiredOptions() {
+ $options = new ServiceOptions( [ 'a', 'b' ], [ 'a' => '', 'b' => '' ] );
+ $options->assertRequiredOptions( [ 'b', 'a' ] );
+ $this->assertTrue( true, 'No exception thrown' );
+ }
+
+ /**
+ * @covers ::__construct
+ * @covers ::get
+ */
+ public function testGetUnrecognized() {
+ $this->setExpectedException( InvalidArgumentException::class,
+ 'Unrecognized option "b"' );
+
+ $options = new ServiceOptions( [ 'a' ], [ 'a' => '' ] );
+ $options->get( 'b' );
+ }
+
+ /**
+ * @covers ::__construct
+ * @covers ::assertRequiredOptions
+ */
+ public function testExtraKeys() {
+ $this->setExpectedException( Wikimedia\Assert\PreconditionException::class,
+ 'Precondition failed: Unsupported options passed: b, c!' );
+
+ $options = new ServiceOptions( [ 'a', 'b', 'c' ], [ 'a' => '', 'b' => '', 'c' => '' ] );
+ $options->assertRequiredOptions( [ 'a' ] );
+ }
+
+ /**
+ * @covers ::__construct
+ * @covers ::assertRequiredOptions
+ */
+ public function testMissingKeys() {
+ $this->setExpectedException( Wikimedia\Assert\PreconditionException::class,
+ 'Precondition failed: Required options missing: a, b!' );
+
+ $options = new ServiceOptions( [ 'c' ], [ 'c' => '' ] );
+ $options->assertRequiredOptions( [ 'a', 'b', 'c' ] );
+ }
+
+ /**
+ * @covers ::__construct
+ * @covers ::assertRequiredOptions
+ */
+ public function testExtraAndMissingKeys() {
+ $this->setExpectedException( Wikimedia\Assert\PreconditionException::class,
+ 'Precondition failed: Unsupported options passed: b! Required options missing: c!' );
+
+ $options = new ServiceOptions( [ 'a', 'b' ], [ 'a' => '', 'b' => '' ] );
+ $options->assertRequiredOptions( [ 'a', 'c' ] );
+ }
+}
--- /dev/null
+<?php
+
+class JsonContentHandlerTest extends \MediaWikiUnitTestCase {
+
+ /**
+ * @covers JsonContentHandler::makeEmptyContent
+ */
+ public function testMakeEmptyContent() {
+ $handler = new JsonContentHandler();
+ $content = $handler->makeEmptyContent();
+ $this->assertInstanceOf( JsonContent::class, $content );
+ $this->assertTrue( $content->isValid() );
+ }
+}
--- /dev/null
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+namespace MediaWiki\Logger;
+
+use Wikimedia\TestingAccessWrapper;
+
+class MonologSpiTest extends \MediaWikiUnitTestCase {
+
+ /**
+ * @covers MediaWiki\Logger\MonologSpi::mergeConfig
+ */
+ public function testMergeConfig() {
+ $base = [
+ 'loggers' => [
+ '@default' => [
+ 'processors' => [ 'constructor' ],
+ 'handlers' => [ 'constructor' ],
+ ],
+ ],
+ 'processors' => [
+ 'constructor' => [
+ 'class' => 'constructor',
+ ],
+ ],
+ 'handlers' => [
+ 'constructor' => [
+ 'class' => 'constructor',
+ 'formatter' => 'constructor',
+ ],
+ ],
+ 'formatters' => [
+ 'constructor' => [
+ 'class' => 'constructor',
+ ],
+ ],
+ ];
+
+ $fixture = new MonologSpi( $base );
+ $this->assertSame(
+ $base,
+ TestingAccessWrapper::newFromObject( $fixture )->config
+ );
+
+ $fixture->mergeConfig( [
+ 'loggers' => [
+ 'merged' => [
+ 'processors' => [ 'merged' ],
+ 'handlers' => [ 'merged' ],
+ ],
+ ],
+ 'processors' => [
+ 'merged' => [
+ 'class' => 'merged',
+ ],
+ ],
+ 'magic' => [
+ 'idkfa' => [ 'xyzzy' ],
+ ],
+ 'handlers' => [
+ 'merged' => [
+ 'class' => 'merged',
+ 'formatter' => 'merged',
+ ],
+ ],
+ 'formatters' => [
+ 'merged' => [
+ 'class' => 'merged',
+ ],
+ ],
+ ] );
+ $this->assertSame(
+ [
+ 'loggers' => [
+ '@default' => [
+ 'processors' => [ 'constructor' ],
+ 'handlers' => [ 'constructor' ],
+ ],
+ 'merged' => [
+ 'processors' => [ 'merged' ],
+ 'handlers' => [ 'merged' ],
+ ],
+ ],
+ 'processors' => [
+ 'constructor' => [
+ 'class' => 'constructor',
+ ],
+ 'merged' => [
+ 'class' => 'merged',
+ ],
+ ],
+ 'handlers' => [
+ 'constructor' => [
+ 'class' => 'constructor',
+ 'formatter' => 'constructor',
+ ],
+ 'merged' => [
+ 'class' => 'merged',
+ 'formatter' => 'merged',
+ ],
+ ],
+ 'formatters' => [
+ 'constructor' => [
+ 'class' => 'constructor',
+ ],
+ 'merged' => [
+ 'class' => 'merged',
+ ],
+ ],
+ 'magic' => [
+ 'idkfa' => [ 'xyzzy' ],
+ ],
+ ],
+ TestingAccessWrapper::newFromObject( $fixture )->config
+ );
+ }
+
+}
--- /dev/null
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+namespace MediaWiki\Logger\Monolog;
+
+use PHPUnit_Framework_Error_Notice;
+
+/**
+ * @covers \MediaWiki\Logger\Monolog\AvroFormatter
+ */
+class AvroFormatterTest extends \MediaWikiUnitTestCase {
+
+ protected function setUp() {
+ if ( !class_exists( 'AvroStringIO' ) ) {
+ $this->markTestSkipped( 'Avro is required for the AvroFormatterTest' );
+ }
+ parent::setUp();
+ }
+
+ public function testSchemaNotAvailable() {
+ $formatter = new AvroFormatter( [] );
+ $this->setExpectedException(
+ 'PHPUnit_Framework_Error_Notice',
+ "The schema for channel 'marty' is not available"
+ );
+ $formatter->format( [ 'channel' => 'marty' ] );
+ }
+
+ public function testSchemaNotAvailableReturnValue() {
+ $formatter = new AvroFormatter( [] );
+ $noticeEnabled = PHPUnit_Framework_Error_Notice::$enabled;
+ // disable conversion of notices
+ PHPUnit_Framework_Error_Notice::$enabled = false;
+ // have to keep the user notice from being output
+ \Wikimedia\suppressWarnings();
+ $res = $formatter->format( [ 'channel' => 'marty' ] );
+ \Wikimedia\restoreWarnings();
+ PHPUnit_Framework_Error_Notice::$enabled = $noticeEnabled;
+ $this->assertNull( $res );
+ }
+
+ public function testDoesSomethingWhenSchemaAvailable() {
+ $formatter = new AvroFormatter( [
+ 'string' => [
+ 'schema' => [ 'type' => 'string' ],
+ 'revision' => 1010101,
+ ]
+ ] );
+ $res = $formatter->format( [
+ 'channel' => 'string',
+ 'context' => 'better to be',
+ ] );
+ $this->assertNotNull( $res );
+ // basically just tell us if avro changes its string encoding, or if
+ // we completely fail to generate a log message.
+ $this->assertEquals( 'AAAAAAAAD2m1GGJldHRlciB0byBiZQ==', base64_encode( $res ) );
+ }
+}
--- /dev/null
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+namespace MediaWiki\Logger\Monolog;
+
+use Monolog\Logger;
+use Wikimedia\TestingAccessWrapper;
+
+/**
+ * @covers \MediaWiki\Logger\Monolog\KafkaHandler
+ */
+class KafkaHandlerTest extends \MediaWikiUnitTestCase {
+
+ protected function setUp() {
+ if ( !class_exists( 'Monolog\Handler\AbstractProcessingHandler' )
+ || !class_exists( 'Kafka\Produce' )
+ ) {
+ $this->markTestSkipped( 'Monolog and Kafka are required for the KafkaHandlerTest' );
+ }
+
+ parent::setUp();
+ }
+
+ public function topicNamingProvider() {
+ return [
+ [ [], 'monolog_foo' ],
+ [ [ 'alias' => [ 'foo' => 'bar' ] ], 'bar' ]
+ ];
+ }
+
+ /**
+ * @dataProvider topicNamingProvider
+ */
+ public function testTopicNaming( $options, $expect ) {
+ $produce = $this->getMockBuilder( 'Kafka\Produce' )
+ ->disableOriginalConstructor()
+ ->getMock();
+ $produce->expects( $this->any() )
+ ->method( 'getAvailablePartitions' )
+ ->will( $this->returnValue( [ 'A' ] ) );
+ $produce->expects( $this->once() )
+ ->method( 'setMessages' )
+ ->with( $expect, $this->anything(), $this->anything() );
+ $produce->expects( $this->any() )
+ ->method( 'send' )
+ ->will( $this->returnValue( true ) );
+
+ $handler = new KafkaHandler( $produce, $options );
+ $handler->handle( [
+ 'channel' => 'foo',
+ 'level' => Logger::EMERGENCY,
+ 'extra' => [],
+ 'context' => [],
+ ] );
+ }
+
+ public function swallowsExceptionsWhenRequested() {
+ return [
+ // defaults to false
+ [ [], true ],
+ // also try false explicitly
+ [ [ 'swallowExceptions' => false ], true ],
+ // turn it on
+ [ [ 'swallowExceptions' => true ], false ],
+ ];
+ }
+
+ /**
+ * @dataProvider swallowsExceptionsWhenRequested
+ */
+ public function testGetAvailablePartitionsException( $options, $expectException ) {
+ $produce = $this->getMockBuilder( 'Kafka\Produce' )
+ ->disableOriginalConstructor()
+ ->getMock();
+ $produce->expects( $this->any() )
+ ->method( 'getAvailablePartitions' )
+ ->will( $this->throwException( new \Kafka\Exception ) );
+ $produce->expects( $this->any() )
+ ->method( 'send' )
+ ->will( $this->returnValue( true ) );
+
+ if ( $expectException ) {
+ $this->setExpectedException( 'Kafka\Exception' );
+ }
+
+ $handler = new KafkaHandler( $produce, $options );
+ $handler->handle( [
+ 'channel' => 'foo',
+ 'level' => Logger::EMERGENCY,
+ 'extra' => [],
+ 'context' => [],
+ ] );
+
+ if ( !$expectException ) {
+ $this->assertTrue( true, 'no exception was thrown' );
+ }
+ }
+
+ /**
+ * @dataProvider swallowsExceptionsWhenRequested
+ */
+ public function testSendException( $options, $expectException ) {
+ $produce = $this->getMockBuilder( 'Kafka\Produce' )
+ ->disableOriginalConstructor()
+ ->getMock();
+ $produce->expects( $this->any() )
+ ->method( 'getAvailablePartitions' )
+ ->will( $this->returnValue( [ 'A' ] ) );
+ $produce->expects( $this->any() )
+ ->method( 'send' )
+ ->will( $this->throwException( new \Kafka\Exception ) );
+
+ if ( $expectException ) {
+ $this->setExpectedException( 'Kafka\Exception' );
+ }
+
+ $handler = new KafkaHandler( $produce, $options );
+ $handler->handle( [
+ 'channel' => 'foo',
+ 'level' => Logger::EMERGENCY,
+ 'extra' => [],
+ 'context' => [],
+ ] );
+
+ if ( !$expectException ) {
+ $this->assertTrue( true, 'no exception was thrown' );
+ }
+ }
+
+ public function testHandlesNullFormatterResult() {
+ $produce = $this->getMockBuilder( 'Kafka\Produce' )
+ ->disableOriginalConstructor()
+ ->getMock();
+ $produce->expects( $this->any() )
+ ->method( 'getAvailablePartitions' )
+ ->will( $this->returnValue( [ 'A' ] ) );
+ $mockMethod = $produce->expects( $this->exactly( 2 ) )
+ ->method( 'setMessages' );
+ $produce->expects( $this->any() )
+ ->method( 'send' )
+ ->will( $this->returnValue( true ) );
+ // evil hax
+ $matcher = TestingAccessWrapper::newFromObject( $mockMethod )->matcher;
+ TestingAccessWrapper::newFromObject( $matcher )->parametersMatcher =
+ new \PHPUnit_Framework_MockObject_Matcher_ConsecutiveParameters( [
+ [ $this->anything(), $this->anything(), [ 'words' ] ],
+ [ $this->anything(), $this->anything(), [ 'lines' ] ]
+ ] );
+
+ $formatter = $this->createMock( \Monolog\Formatter\FormatterInterface::class );
+ $formatter->expects( $this->any() )
+ ->method( 'format' )
+ ->will( $this->onConsecutiveCalls( 'words', null, 'lines' ) );
+
+ $handler = new KafkaHandler( $produce, [] );
+ $handler->setFormatter( $formatter );
+ for ( $i = 0; $i < 3; ++$i ) {
+ $handler->handle( [
+ 'channel' => 'foo',
+ 'level' => Logger::EMERGENCY,
+ 'extra' => [],
+ 'context' => [],
+ ] );
+ }
+ }
+
+ public function testBatchHandlesNullFormatterResult() {
+ $produce = $this->getMockBuilder( 'Kafka\Produce' )
+ ->disableOriginalConstructor()
+ ->getMock();
+ $produce->expects( $this->any() )
+ ->method( 'getAvailablePartitions' )
+ ->will( $this->returnValue( [ 'A' ] ) );
+ $produce->expects( $this->once() )
+ ->method( 'setMessages' )
+ ->with( $this->anything(), $this->anything(), [ 'words', 'lines' ] );
+ $produce->expects( $this->any() )
+ ->method( 'send' )
+ ->will( $this->returnValue( true ) );
+
+ $formatter = $this->createMock( \Monolog\Formatter\FormatterInterface::class );
+ $formatter->expects( $this->any() )
+ ->method( 'format' )
+ ->will( $this->onConsecutiveCalls( 'words', null, 'lines' ) );
+
+ $handler = new KafkaHandler( $produce, [] );
+ $handler->setFormatter( $formatter );
+ $handler->handleBatch( [
+ [
+ 'channel' => 'foo',
+ 'level' => Logger::EMERGENCY,
+ 'extra' => [],
+ 'context' => [],
+ ],
+ [
+ 'channel' => 'foo',
+ 'level' => Logger::EMERGENCY,
+ 'extra' => [],
+ 'context' => [],
+ ],
+ [
+ 'channel' => 'foo',
+ 'level' => Logger::EMERGENCY,
+ 'extra' => [],
+ 'context' => [],
+ ],
+ ] );
+ }
+}
--- /dev/null
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+namespace MediaWiki\Logger\Monolog;
+
+use AssertionError;
+use InvalidArgumentException;
+use LengthException;
+use LogicException;
+use Wikimedia\TestingAccessWrapper;
+
+class LineFormatterTest extends \MediaWikiUnitTestCase {
+
+ protected function setUp() {
+ if ( !class_exists( 'Monolog\Formatter\LineFormatter' ) ) {
+ $this->markTestSkipped( 'This test requires monolog to be installed' );
+ }
+ parent::setUp();
+ }
+
+ /**
+ * @covers MediaWiki\Logger\Monolog\LineFormatter::normalizeException
+ */
+ public function testNormalizeExceptionNoTrace() {
+ $fixture = new LineFormatter();
+ $fixture->includeStacktraces( false );
+ $fixture = TestingAccessWrapper::newFromObject( $fixture );
+ $boom = new InvalidArgumentException( 'boom', 0,
+ new LengthException( 'too long', 0,
+ new LogicException( 'Spock wuz here' )
+ )
+ );
+ $out = $fixture->normalizeException( $boom );
+ $this->assertContains( "\n[Exception InvalidArgumentException]", $out );
+ $this->assertContains( "\nCaused by: [Exception LengthException]", $out );
+ $this->assertContains( "\nCaused by: [Exception LogicException]", $out );
+ $this->assertNotContains( "\n #0", $out );
+ }
+
+ /**
+ * @covers MediaWiki\Logger\Monolog\LineFormatter::normalizeException
+ */
+ public function testNormalizeExceptionTrace() {
+ $fixture = new LineFormatter();
+ $fixture->includeStacktraces( true );
+ $fixture = TestingAccessWrapper::newFromObject( $fixture );
+ $boom = new InvalidArgumentException( 'boom', 0,
+ new LengthException( 'too long', 0,
+ new LogicException( 'Spock wuz here' )
+ )
+ );
+ $out = $fixture->normalizeException( $boom );
+ $this->assertContains( "\n[Exception InvalidArgumentException]", $out );
+ $this->assertContains( "\nCaused by: [Exception LengthException]", $out );
+ $this->assertContains( "\nCaused by: [Exception LogicException]", $out );
+ $this->assertContains( "\n #0", $out );
+ }
+
+ /**
+ * @covers MediaWiki\Logger\Monolog\LineFormatter::normalizeException
+ */
+ public function testNormalizeExceptionErrorNoTrace() {
+ if ( !class_exists( AssertionError::class ) ) {
+ $this->markTestSkipped( 'AssertionError class does not exist' );
+ }
+
+ $fixture = new LineFormatter();
+ $fixture->includeStacktraces( false );
+ $fixture = TestingAccessWrapper::newFromObject( $fixture );
+ $boom = new InvalidArgumentException( 'boom', 0,
+ new LengthException( 'too long', 0,
+ new AssertionError( 'Spock wuz here' )
+ )
+ );
+ $out = $fixture->normalizeException( $boom );
+ $this->assertContains( "\n[Exception InvalidArgumentException]", $out );
+ $this->assertContains( "\nCaused by: [Exception LengthException]", $out );
+ $this->assertContains( "\nCaused by: [Error AssertionError]", $out );
+ $this->assertNotContains( "\n #0", $out );
+ }
+
+ /**
+ * @covers MediaWiki\Logger\Monolog\LineFormatter::normalizeException
+ */
+ public function testNormalizeExceptionErrorTrace() {
+ if ( !class_exists( AssertionError::class ) ) {
+ $this->markTestSkipped( 'AssertionError class does not exist' );
+ }
+
+ $fixture = new LineFormatter();
+ $fixture->includeStacktraces( true );
+ $fixture = TestingAccessWrapper::newFromObject( $fixture );
+ $boom = new InvalidArgumentException( 'boom', 0,
+ new LengthException( 'too long', 0,
+ new AssertionError( 'Spock wuz here' )
+ )
+ );
+ $out = $fixture->normalizeException( $boom );
+ $this->assertContains( "\n[Exception InvalidArgumentException]", $out );
+ $this->assertContains( "\nCaused by: [Exception LengthException]", $out );
+ $this->assertContains( "\nCaused by: [Error AssertionError]", $out );
+ $this->assertContains( "\n #0", $out );
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * @author Addshore
+ *
+ * @group Diff
+ */
+class ArrayDiffFormatterTest extends \MediaWikiUnitTestCase {
+
+ /**
+ * @param Diff $input
+ * @param array $expectedOutput
+ * @dataProvider provideTestFormat
+ * @covers ArrayDiffFormatter::format
+ */
+ public function testFormat( $input, $expectedOutput ) {
+ $instance = new ArrayDiffFormatter();
+ $output = $instance->format( $input );
+ $this->assertEquals( $expectedOutput, $output );
+ }
+
+ private function getMockDiff( $edits ) {
+ $diff = $this->getMockBuilder( Diff::class )
+ ->disableOriginalConstructor()
+ ->getMock();
+ $diff->expects( $this->any() )
+ ->method( 'getEdits' )
+ ->will( $this->returnValue( $edits ) );
+ return $diff;
+ }
+
+ private function getMockDiffOp( $type = null, $orig = [], $closing = [] ) {
+ $diffOp = $this->getMockBuilder( DiffOp::class )
+ ->disableOriginalConstructor()
+ ->getMock();
+ $diffOp->expects( $this->any() )
+ ->method( 'getType' )
+ ->will( $this->returnValue( $type ) );
+ $diffOp->expects( $this->any() )
+ ->method( 'getOrig' )
+ ->will( $this->returnValue( $orig ) );
+ if ( $type === 'change' ) {
+ $diffOp->expects( $this->any() )
+ ->method( 'getClosing' )
+ ->with( $this->isType( 'integer' ) )
+ ->will( $this->returnCallback( function () {
+ return 'mockLine';
+ } ) );
+ } else {
+ $diffOp->expects( $this->any() )
+ ->method( 'getClosing' )
+ ->will( $this->returnValue( $closing ) );
+ }
+ return $diffOp;
+ }
+
+ public function provideTestFormat() {
+ $emptyArrayTestCases = [
+ $this->getMockDiff( [] ),
+ $this->getMockDiff( [ $this->getMockDiffOp( 'add' ) ] ),
+ $this->getMockDiff( [ $this->getMockDiffOp( 'delete' ) ] ),
+ $this->getMockDiff( [ $this->getMockDiffOp( 'change' ) ] ),
+ $this->getMockDiff( [ $this->getMockDiffOp( 'copy' ) ] ),
+ $this->getMockDiff( [ $this->getMockDiffOp( 'FOOBARBAZ' ) ] ),
+ $this->getMockDiff( [ $this->getMockDiffOp( 'add', 'line' ) ] ),
+ $this->getMockDiff( [ $this->getMockDiffOp( 'delete', [], [ 'line' ] ) ] ),
+ $this->getMockDiff( [ $this->getMockDiffOp( 'copy', [], [ 'line' ] ) ] ),
+ ];
+
+ $otherTestCases = [];
+ $otherTestCases[] = [
+ $this->getMockDiff( [ $this->getMockDiffOp( 'add', [], [ 'a1' ] ) ] ),
+ [ [ 'action' => 'add', 'new' => 'a1', 'newline' => 1 ] ],
+ ];
+ $otherTestCases[] = [
+ $this->getMockDiff( [ $this->getMockDiffOp( 'add', [], [ 'a1', 'a2' ] ) ] ),
+ [
+ [ 'action' => 'add', 'new' => 'a1', 'newline' => 1 ],
+ [ 'action' => 'add', 'new' => 'a2', 'newline' => 2 ],
+ ],
+ ];
+ $otherTestCases[] = [
+ $this->getMockDiff( [ $this->getMockDiffOp( 'delete', [ 'd1' ] ) ] ),
+ [ [ 'action' => 'delete', 'old' => 'd1', 'oldline' => 1 ] ],
+ ];
+ $otherTestCases[] = [
+ $this->getMockDiff( [ $this->getMockDiffOp( 'delete', [ 'd1', 'd2' ] ) ] ),
+ [
+ [ 'action' => 'delete', 'old' => 'd1', 'oldline' => 1 ],
+ [ 'action' => 'delete', 'old' => 'd2', 'oldline' => 2 ],
+ ],
+ ];
+ $otherTestCases[] = [
+ $this->getMockDiff( [ $this->getMockDiffOp( 'change', [ 'd1' ], [ 'a1' ] ) ] ),
+ [ [
+ 'action' => 'change',
+ 'old' => 'd1',
+ 'new' => 'mockLine',
+ 'newline' => 1, 'oldline' => 1
+ ] ],
+ ];
+ $otherTestCases[] = [
+ $this->getMockDiff( [ $this->getMockDiffOp(
+ 'change',
+ [ 'd1', 'd2' ],
+ [ 'a1', 'a2' ]
+ ) ] ),
+ [
+ [
+ 'action' => 'change',
+ 'old' => 'd1',
+ 'new' => 'mockLine',
+ 'newline' => 1, 'oldline' => 1
+ ],
+ [
+ 'action' => 'change',
+ 'old' => 'd2',
+ 'new' => 'mockLine',
+ 'newline' => 2, 'oldline' => 2
+ ],
+ ],
+ ];
+
+ $testCases = [];
+ foreach ( $emptyArrayTestCases as $testCase ) {
+ $testCases[] = [ $testCase, [] ];
+ }
+ foreach ( $otherTestCases as $testCase ) {
+ $testCases[] = [ $testCase[0], $testCase[1] ];
+ }
+ return $testCases;
+ }
+
+}
--- /dev/null
+<?php
+/**
+ * @author Addshore
+ *
+ * @group Diff
+ */
+class DiffOpTest extends \MediaWikiUnitTestCase {
+
+ /**
+ * @covers DiffOp::getType
+ */
+ public function testGetType() {
+ $obj = new FakeDiffOp();
+ $obj->type = 'foo';
+ $this->assertEquals( 'foo', $obj->getType() );
+ }
+
+ /**
+ * @covers DiffOp::getOrig
+ */
+ public function testGetOrig() {
+ $obj = new FakeDiffOp();
+ $obj->orig = [ 'foo' ];
+ $this->assertEquals( [ 'foo' ], $obj->getOrig() );
+ }
+
+ /**
+ * @covers DiffOp::getClosing
+ */
+ public function testGetClosing() {
+ $obj = new FakeDiffOp();
+ $obj->closing = [ 'foo' ];
+ $this->assertEquals( [ 'foo' ], $obj->getClosing() );
+ }
+
+ /**
+ * @covers DiffOp::getClosing
+ */
+ public function testGetClosingWithParameter() {
+ $obj = new FakeDiffOp();
+ $obj->closing = [ 'foo', 'bar', 'baz' ];
+ $this->assertEquals( 'foo', $obj->getClosing( 0 ) );
+ $this->assertEquals( 'bar', $obj->getClosing( 1 ) );
+ $this->assertEquals( 'baz', $obj->getClosing( 2 ) );
+ $this->assertEquals( null, $obj->getClosing( 3 ) );
+ }
+
+ /**
+ * @covers DiffOp::norig
+ */
+ public function testNorig() {
+ $obj = new FakeDiffOp();
+ $this->assertEquals( 0, $obj->norig() );
+ $obj->orig = [ 'foo' ];
+ $this->assertEquals( 1, $obj->norig() );
+ }
+
+ /**
+ * @covers DiffOp::nclosing
+ */
+ public function testNclosing() {
+ $obj = new FakeDiffOp();
+ $this->assertEquals( 0, $obj->nclosing() );
+ $obj->closing = [ 'foo' ];
+ $this->assertEquals( 1, $obj->nclosing() );
+ }
+
+}
--- /dev/null
+<?php
+
+/**
+ * @author Addshore
+ *
+ * @group Diff
+ */
+class DiffTest extends \MediaWikiUnitTestCase {
+
+ /**
+ * @covers Diff::getEdits
+ */
+ public function testGetEdits() {
+ $obj = new Diff( [], [] );
+ $obj->edits = 'FooBarBaz';
+ $this->assertEquals( 'FooBarBaz', $obj->getEdits() );
+ }
+
+}
--- /dev/null
+<?php
+/**
+ * @author Antoine Musso
+ * @copyright Copyright © 2013, Antoine Musso
+ * @copyright Copyright © 2013, Wikimedia Foundation Inc.
+ * @file
+ */
+
+class MWExceptionHandlerTest extends \MediaWikiUnitTestCase {
+
+ /**
+ * @covers MWExceptionHandler::getRedactedTrace
+ */
+ public function testGetRedactedTrace() {
+ $refvar = 'value';
+ try {
+ $array = [ 'a', 'b' ];
+ $object = new stdClass();
+ self::helperThrowAnException( $array, $object, $refvar );
+ } catch ( Exception $e ) {
+ }
+
+ # Make sure our stack trace contains an array and an object passed to
+ # some function in the stacktrace. Else, we can not assert the trace
+ # redaction achieved its job.
+ $trace = $e->getTrace();
+ $hasObject = false;
+ $hasArray = false;
+ foreach ( $trace as $frame ) {
+ if ( !isset( $frame['args'] ) ) {
+ continue;
+ }
+ foreach ( $frame['args'] as $arg ) {
+ $hasObject = $hasObject || is_object( $arg );
+ $hasArray = $hasArray || is_array( $arg );
+ }
+
+ if ( $hasObject && $hasArray ) {
+ break;
+ }
+ }
+ $this->assertTrue( $hasObject,
+ "The stacktrace must have a function having an object has parameter" );
+ $this->assertTrue( $hasArray,
+ "The stacktrace must have a function having an array has parameter" );
+
+ # Now we redact the trace.. and make sure no function arguments are
+ # arrays or objects.
+ $redacted = MWExceptionHandler::getRedactedTrace( $e );
+
+ foreach ( $redacted as $frame ) {
+ if ( !isset( $frame['args'] ) ) {
+ continue;
+ }
+ foreach ( $frame['args'] as $arg ) {
+ $this->assertNotInternalType( 'array', $arg );
+ $this->assertNotInternalType( 'object', $arg );
+ }
+ }
+
+ $this->assertEquals( 'value', $refvar, 'Ensuring reference variable wasn\'t changed' );
+ }
+
+ /**
+ * Helper function for testExpandArgumentsInCall
+ *
+ * Pass it an object and an array, and something by reference :-)
+ *
+ * @throws Exception
+ */
+ protected static function helperThrowAnException( $a, $b, &$c ) {
+ throw new Exception();
+ }
+}
--- /dev/null
+<?php
+
+class InstallDocFormatterTest extends \MediaWikiUnitTestCase {
+ /**
+ * @covers InstallDocFormatter
+ * @dataProvider provideDocFormattingTests
+ */
+ public function testFormat( $expected, $unformattedText, $message = '' ) {
+ $this->assertEquals(
+ $expected,
+ InstallDocFormatter::format( $unformattedText ),
+ $message
+ );
+ }
+
+ /**
+ * Provider for testFormat()
+ */
+ public static function provideDocFormattingTests() {
+ # Format: (expected string, unformattedText string, optional message)
+ return [
+ # Escape some wikitext
+ [ 'Install <tag>', 'Install <tag>', 'Escaping <' ],
+ [ 'Install {{template}}', 'Install {{template}}', 'Escaping [[' ],
+ [ 'Install [[page]]', 'Install [[page]]', 'Escaping {{' ],
+ [ 'Install __TOC__', 'Install __TOC__', 'Escaping __' ],
+ [ 'Install ', "Install \r", 'Removing \r' ],
+
+ # Transform \t{1,2} into :{1,2}
+ [ ':One indentation', "\tOne indentation", 'Replacing a single \t' ],
+ [ '::Two indentations', "\t\tTwo indentations", 'Replacing 2 x \t' ],
+
+ # Transform 'T123' links
+ [
+ '<span class="config-plainlink">[https://phabricator.wikimedia.org/T123 T123]</span>',
+ 'T123', 'Testing T123 links' ],
+ [
+ 'bug <span class="config-plainlink">[https://phabricator.wikimedia.org/T123 T123]</span>',
+ 'bug T123', 'Testing bug T123 links' ],
+ [
+ '(<span class="config-plainlink">[https://phabricator.wikimedia.org/T987654 T987654]</span>)',
+ '(T987654)', 'Testing (T987654) links' ],
+
+ # "Tabc" shouldn't work
+ [ 'Tfoobar', 'Tfoobar', "Don't match T followed by non-digits" ],
+ [ 'T!!fakefake!!', 'T!!fakefake!!', "Don't match T followed by non-digits" ],
+
+ # Transform 'bug 123' links
+ [
+ '<span class="config-plainlink">[https://bugzilla.wikimedia.org/123 bug 123]</span>',
+ 'bug 123', 'Testing bug 123 links' ],
+ [
+ '(<span class="config-plainlink">[https://bugzilla.wikimedia.org/987654 bug 987654]</span>)',
+ '(bug 987654)', 'Testing (bug 987654) links' ],
+
+ # "bug abc" shouldn't work
+ [ 'bug foobar', 'bug foobar', "Don't match bug followed by non-digits" ],
+ [ 'bug !!fakefake!!', 'bug !!fakefake!!', "Don't match bug followed by non-digits" ],
+
+ # Transform '$wgFooBar' links
+ [
+ '<span class="config-plainlink">'
+ . '[https://www.mediawiki.org/wiki/Manual:$wgFooBar $wgFooBar]</span>',
+ '$wgFooBar', 'Testing basic $wgFooBar' ],
+ [
+ '<span class="config-plainlink">'
+ . '[https://www.mediawiki.org/wiki/Manual:$wgFooBar45 $wgFooBar45]</span>',
+ '$wgFooBar45', 'Testing $wgFooBar45 (with numbers)' ],
+ [
+ '<span class="config-plainlink">'
+ . '[https://www.mediawiki.org/wiki/Manual:$wgFoo_Bar $wgFoo_Bar]</span>',
+ '$wgFoo_Bar', 'Testing $wgFoo_Bar (with underscore)' ],
+
+ # Icky variables that shouldn't link
+ [
+ '$myAwesomeVariable',
+ '$myAwesomeVariable',
+ 'Testing $myAwesomeVariable (not starting with $wg)'
+ ],
+ [ '$()not!a&Var', '$()not!a&Var', 'Testing $()not!a&Var (obviously not a variable)' ],
+ ];
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * @group Installer
+ */
+class OracleInstallerTest extends \MediaWikiUnitTestCase {
+
+ /**
+ * @dataProvider provideOracleConnectStrings
+ * @covers OracleInstaller::checkConnectStringFormat
+ */
+ public function testCheckConnectStringFormat( $expected, $connectString, $msg = '' ) {
+ $validity = $expected ? 'should be valid' : 'should NOT be valid';
+ $msg = "'$connectString' ($msg) $validity.";
+ $this->assertEquals( $expected,
+ OracleInstaller::checkConnectStringFormat( $connectString ),
+ $msg
+ );
+ }
+
+ /**
+ * Provider to test OracleInstaller::checkConnectStringFormat()
+ */
+ function provideOracleConnectStrings() {
+ // expected result, connectString[, message]
+ return [
+ [ true, 'simple_01', 'Simple TNS name' ],
+ [ true, 'simple_01.world', 'TNS name with domain' ],
+ [ true, 'simple_01.domain.net', 'TNS name with domain' ],
+ [ true, 'host123', 'Host only' ],
+ [ true, 'host123.domain.net', 'FQDN only' ],
+ [ true, '//host123.domain.net', 'FQDN URL only' ],
+ [ true, '123.223.213.132', 'Host IP only' ],
+ [ true, 'host:1521', 'Host and port' ],
+ [ true, 'host:1521/service', 'Host, port and service' ],
+ [ true, 'host:1521/service:shared', 'Host, port, service and shared server type' ],
+ [ true, 'host:1521/service:dedicated', 'Host, port, service and dedicated server type' ],
+ [ true, 'host:1521/service:pooled', 'Host, port, service and pooled server type' ],
+ [
+ true,
+ 'host:1521/service:shared/instance1',
+ 'Host, port, service, server type and instance'
+ ],
+ [ true, 'host:1521//instance1', 'Host, port and instance' ],
+ ];
+ }
+
+}
--- /dev/null
+<?php
+
+use MediaWiki\Interwiki\InterwikiLookupAdapter;
+
+/**
+ * @covers MediaWiki\Interwiki\InterwikiLookupAdapter
+ *
+ * @group MediaWiki
+ * @group Interwiki
+ */
+class InterwikiLookupAdapterTest extends \MediaWikiUnitTestCase {
+
+ /**
+ * @var InterwikiLookupAdapter
+ */
+ private $interwikiLookup;
+
+ protected function setUp() {
+ parent::setUp();
+
+ $this->interwikiLookup = new InterwikiLookupAdapter(
+ $this->getSiteLookup( $this->getSites() )
+ );
+ }
+
+ public function testIsValidInterwiki() {
+ $this->assertTrue(
+ $this->interwikiLookup->isValidInterwiki( 'enwt' ),
+ 'enwt known prefix is valid'
+ );
+ $this->assertTrue(
+ $this->interwikiLookup->isValidInterwiki( 'foo' ),
+ 'foo site known prefix is valid'
+ );
+ $this->assertFalse(
+ $this->interwikiLookup->isValidInterwiki( 'xyz' ),
+ 'unknown prefix is not valid'
+ );
+ }
+
+ public function testFetch() {
+ $interwiki = $this->interwikiLookup->fetch( '' );
+ $this->assertNull( $interwiki );
+
+ $interwiki = $this->interwikiLookup->fetch( 'xyz' );
+ $this->assertFalse( $interwiki );
+
+ $interwiki = $this->interwikiLookup->fetch( 'foo' );
+ $this->assertInstanceOf( Interwiki::class, $interwiki );
+ $this->assertSame( 'foobar', $interwiki->getWikiID() );
+
+ $interwiki = $this->interwikiLookup->fetch( 'enwt' );
+ $this->assertInstanceOf( Interwiki::class, $interwiki );
+
+ $this->assertSame( 'https://en.wiktionary.org/wiki/$1', $interwiki->getURL(), 'getURL' );
+ $this->assertSame( 'https://en.wiktionary.org/w/api.php', $interwiki->getAPI(), 'getAPI' );
+ $this->assertSame( 'enwiktionary', $interwiki->getWikiID(), 'getWikiID' );
+ $this->assertTrue( $interwiki->isLocal(), 'isLocal' );
+ }
+
+ public function testGetAllPrefixes() {
+ $foo = [
+ 'iw_prefix' => 'foo',
+ 'iw_url' => '',
+ 'iw_api' => '',
+ 'iw_wikiid' => 'foobar',
+ 'iw_local' => false,
+ 'iw_trans' => false,
+ ];
+ $enwt = [
+ 'iw_prefix' => 'enwt',
+ 'iw_url' => 'https://en.wiktionary.org/wiki/$1',
+ 'iw_api' => 'https://en.wiktionary.org/w/api.php',
+ 'iw_wikiid' => 'enwiktionary',
+ 'iw_local' => true,
+ 'iw_trans' => false,
+ ];
+
+ $this->assertEquals(
+ [ $foo, $enwt ],
+ $this->interwikiLookup->getAllPrefixes(),
+ 'getAllPrefixes()'
+ );
+
+ $this->assertEquals(
+ [ $foo ],
+ $this->interwikiLookup->getAllPrefixes( false ),
+ 'get external prefixes'
+ );
+
+ $this->assertEquals(
+ [ $enwt ],
+ $this->interwikiLookup->getAllPrefixes( true ),
+ 'get local prefixes'
+ );
+ }
+
+ private function getSiteLookup( SiteList $sites ) {
+ $siteLookup = $this->getMockBuilder( SiteLookup::class )
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $siteLookup->expects( $this->any() )
+ ->method( 'getSites' )
+ ->will( $this->returnValue( $sites ) );
+
+ return $siteLookup;
+ }
+
+ private function getSites() {
+ $sites = [];
+
+ $site = new Site();
+ $site->setGlobalId( 'foobar' );
+ $site->addInterwikiId( 'foo' );
+ $site->setSource( 'external' );
+ $sites[] = $site;
+
+ $site = new MediaWikiSite();
+ $site->setGlobalId( 'enwiktionary' );
+ $site->setGroup( 'wiktionary' );
+ $site->setLanguageCode( 'en' );
+ $site->addNavigationId( 'enwiktionary' );
+ $site->addInterwikiId( 'enwt' );
+ $site->setSource( 'local' );
+ $site->setPath( MediaWikiSite::PATH_PAGE, "https://en.wiktionary.org/wiki/$1" );
+ $site->setPath( MediaWikiSite::PATH_FILE, "https://en.wiktionary.org/w/$1" );
+ $sites[] = $site;
+
+ return new SiteList( $sites );
+ }
+
+}
--- /dev/null
+<?php
+
+class ReplicatedBagOStuffTest extends \MediaWikiUnitTestCase {
+ /** @var HashBagOStuff */
+ private $writeCache;
+ /** @var HashBagOStuff */
+ private $readCache;
+ /** @var ReplicatedBagOStuff */
+ private $cache;
+
+ protected function setUp() {
+ parent::setUp();
+
+ $this->writeCache = new HashBagOStuff();
+ $this->readCache = new HashBagOStuff();
+ $this->cache = new ReplicatedBagOStuff( [
+ 'writeFactory' => $this->writeCache,
+ 'readFactory' => $this->readCache,
+ ] );
+ }
+
+ /**
+ * @covers ReplicatedBagOStuff::set
+ */
+ public function testSet() {
+ $key = 'a key';
+ $value = 'a value';
+ $this->cache->set( $key, $value );
+
+ // Write to master.
+ $this->assertEquals( $value, $this->writeCache->get( $key ) );
+ // Don't write to replica. Replication is deferred to backend.
+ $this->assertFalse( $this->readCache->get( $key ) );
+ }
+
+ /**
+ * @covers ReplicatedBagOStuff::get
+ */
+ public function testGet() {
+ $key = 'a key';
+
+ $write = 'one value';
+ $this->writeCache->set( $key, $write );
+ $read = 'another value';
+ $this->readCache->set( $key, $read );
+
+ // Read from replica.
+ $this->assertEquals( $read, $this->cache->get( $key ) );
+ }
+
+ /**
+ * @covers ReplicatedBagOStuff::get
+ */
+ public function testGetAbsent() {
+ $key = 'a key';
+ $value = 'a value';
+ $this->writeCache->set( $key, $value );
+
+ // Don't read from master. No failover if value is absent.
+ $this->assertFalse( $this->cache->get( $key ) );
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * @group Media
+ */
+class IPTCTest extends \MediaWikiUnitTestCase {
+
+ /**
+ * @covers IPTC::getCharset
+ */
+ public function testRecognizeUtf8() {
+ // utf-8 is the only one used in practise.
+ $res = IPTC::getCharset( "\x1b%G" );
+ $this->assertEquals( 'UTF-8', $res );
+ }
+
+ /**
+ * @covers IPTC::parse
+ */
+ public function testIPTCParseNoCharset88591() {
+ // basically IPTC for keyword with value of 0xBC which is 1/4 in iso-8859-1
+ // This data doesn't specify a charset. We're supposed to guess
+ // (which basically means utf-8 if valid, windows 1252 (iso 8859-1) if not)
+ $iptcData = "Photoshop 3.0\08BIM\4\4\0\0\0\0\0\x06\x1c\x02\x19\x00\x01\xBC";
+ $res = IPTC::parse( $iptcData );
+ $this->assertEquals( [ '¼' ], $res['Keywords'] );
+ }
+
+ /**
+ * @covers IPTC::parse
+ */
+ public function testIPTCParseNoCharset88591b() {
+ /* This one contains a sequence that's valid iso 8859-1 but not valid utf8 */
+ /* \xC3 = Ã, \xB8 = ¸ */
+ $iptcData = "Photoshop 3.0\08BIM\4\4\0\0\0\0\0\x09\x1c\x02\x19\x00\x04\xC3\xC3\xC3\xB8";
+ $res = IPTC::parse( $iptcData );
+ $this->assertEquals( [ 'ÃÃø' ], $res['Keywords'] );
+ }
+
+ /**
+ * Same as testIPTCParseNoCharset88591b, but forcing the charset to utf-8.
+ * What should happen is the first "\xC3\xC3" should be dropped as invalid,
+ * leaving \xC3\xB8, which is ø
+ * @covers IPTC::parse
+ */
+ public function testIPTCParseForcedUTFButInvalid() {
+ $iptcData = "Photoshop 3.0\08BIM\4\4\0\0\0\0\0\x11\x1c\x02\x19\x00\x04\xC3\xC3\xC3\xB8"
+ . "\x1c\x01\x5A\x00\x03\x1B\x25\x47";
+ $res = IPTC::parse( $iptcData );
+ $this->assertEquals( [ 'ø' ], $res['Keywords'] );
+ }
+
+ /**
+ * @covers IPTC::parse
+ */
+ public function testIPTCParseNoCharsetUTF8() {
+ $iptcData = "Photoshop 3.0\08BIM\4\4\0\0\0\0\0\x07\x1c\x02\x19\x00\x02¼";
+ $res = IPTC::parse( $iptcData );
+ $this->assertEquals( [ '¼' ], $res['Keywords'] );
+ }
+
+ /**
+ * Testing something that has 2 values for keyword
+ * @covers IPTC::parse
+ */
+ public function testIPTCParseMulti() {
+ $iptcData = /* identifier */ "Photoshop 3.0\08BIM\4\4"
+ /* length */ . "\0\0\0\0\0\x0D"
+ . "\x1c\x02\x19" . "\x00\x01" . "\xBC"
+ . "\x1c\x02\x19" . "\x00\x02" . "\xBC\xBD";
+ $res = IPTC::parse( $iptcData );
+ $this->assertEquals( [ '¼', '¼½' ], $res['Keywords'] );
+ }
+
+ /**
+ * @covers IPTC::parse
+ */
+ public function testIPTCParseUTF8() {
+ // This has the magic "\x1c\x01\x5A\x00\x03\x1B\x25\x47" which marks content as UTF8.
+ $iptcData =
+ "Photoshop 3.0\08BIM\4\4\0\0\0\0\0\x0F\x1c\x02\x19\x00\x02¼\x1c\x01\x5A\x00\x03\x1B\x25\x47";
+ $res = IPTC::parse( $iptcData );
+ $this->assertEquals( [ '¼' ], $res['Keywords'] );
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * @group Media
+ */
+class MediaHandlerTest extends \MediaWikiUnitTestCase {
+
+ /**
+ * @covers MediaHandler::fitBoxWidth
+ *
+ * @dataProvider provideTestFitBoxWidth
+ */
+ public function testFitBoxWidth( $width, $height, $max, $expected ) {
+ $y = round( $expected * $height / $width );
+ $result = MediaHandler::fitBoxWidth( $width, $height, $max );
+ $y2 = round( $result * $height / $width );
+ $this->assertEquals( $expected,
+ $result,
+ "($width, $height, $max) wanted: {$expected}x$y, got: {z$result}x$y2" );
+ }
+
+ public static function provideTestFitBoxWidth() {
+ return array_merge(
+ static::generateTestFitBoxWidthData( 50, 50, [
+ 50 => 50,
+ 17 => 17,
+ 18 => 18 ]
+ ),
+ static::generateTestFitBoxWidthData( 366, 300, [
+ 50 => 61,
+ 17 => 21,
+ 18 => 22 ]
+ ),
+ static::generateTestFitBoxWidthData( 300, 366, [
+ 50 => 41,
+ 17 => 14,
+ 18 => 15 ]
+ ),
+ static::generateTestFitBoxWidthData( 100, 400, [
+ 50 => 12,
+ 17 => 4,
+ 18 => 4 ]
+ )
+ );
+ }
+
+ /**
+ * Generate single test cases by combining the dimensions and tests contents
+ *
+ * It creates:
+ * [$width, $height, $max, $expected],
+ * [$width, $height, $max2, $expected2], ...
+ * out of parameters:
+ * $width, $height, { $max => $expected, $max2 => $expected2, ... }
+ *
+ * @param int $width
+ * @param int $height
+ * @param array $tests associative array of $max => $expected values
+ * @return array
+ */
+ private static function generateTestFitBoxWidthData( $width, $height, $tests ) {
+ $result = [];
+ foreach ( $tests as $max => $expected ) {
+ $result[] = [ $width, $height, $max, $expected ];
+ }
+ return $result;
+ }
+}
--- /dev/null
+<?php
+/**
+ * @group BagOStuff
+ */
+class MemcachedBagOStuffTest extends \MediaWikiUnitTestCase {
+ /** @var MemcachedBagOStuff */
+ private $cache;
+
+ protected function setUp() {
+ parent::setUp();
+ $this->cache = new MemcachedPhpBagOStuff( [ 'keyspace' => 'test', 'servers' => [] ] );
+ }
+
+ /**
+ * @covers MemcachedBagOStuff::makeKey
+ */
+ public function testKeyNormalization() {
+ $this->assertEquals(
+ 'test:vanilla',
+ $this->cache->makeKey( 'vanilla' )
+ );
+
+ $this->assertEquals(
+ 'test:punctuation_marks_are_ok:!@$^&*()',
+ $this->cache->makeKey( 'punctuation_marks_are_ok', '!@$^&*()' )
+ );
+
+ $this->assertEquals(
+ 'test:but_spaces:hashes%23:and%0Anewlines:are_not',
+ $this->cache->makeKey( 'but spaces', 'hashes#', "and\nnewlines", 'are_not' )
+ );
+
+ $this->assertEquals(
+ 'test:this:key:contains:%F0%9D%95%9E%F0%9D%95%A6%F0%9D%95%9D%F0%9D%95%A5%F0%9' .
+ 'D%95%9A%F0%9D%95%93%F0%9D%95%AA%F0%9D%95%A5%F0%9D%95%96:characters',
+ $this->cache->makeKey( 'this', 'key', 'contains', '𝕞𝕦𝕝𝕥𝕚𝕓𝕪𝕥𝕖', 'characters' )
+ );
+
+ $this->assertEquals(
+ 'test:this:key:contains:#c118f92685a635cb843039de50014c9c',
+ $this->cache->makeKey( 'this', 'key', 'contains', '𝕥𝕠𝕠 𝕞𝕒𝕟𝕪 𝕞𝕦𝕝𝕥𝕚𝕓𝕪𝕥𝕖 𝕔𝕙𝕒𝕣𝕒𝕔𝕥𝕖𝕣𝕤' )
+ );
+
+ $this->assertEquals(
+ 'test:BagOStuff-long-key:##dc89dcb43b28614da27660240af478b5',
+ $this->cache->makeKey( '𝕖𝕧𝕖𝕟', '𝕚𝕗', '𝕨𝕖', '𝕄𝔻𝟝', '𝕖𝕒𝕔𝕙',
+ '𝕒𝕣𝕘𝕦𝕞𝕖𝕟𝕥', '𝕥𝕙𝕚𝕤', '𝕜𝕖𝕪', '𝕨𝕠𝕦𝕝𝕕', '𝕤𝕥𝕚𝕝𝕝', '𝕓𝕖', '𝕥𝕠𝕠', '𝕝𝕠𝕟𝕘' )
+ );
+
+ $this->assertEquals(
+ 'test:%23%235820ad1d105aa4dc698585c39df73e19',
+ $this->cache->makeKey( '##5820ad1d105aa4dc698585c39df73e19' )
+ );
+
+ $this->assertEquals(
+ 'test:percent_is_escaped:!@$%25^&*()',
+ $this->cache->makeKey( 'percent_is_escaped', '!@$%^&*()' )
+ );
+
+ $this->assertEquals(
+ 'test:colon_is_escaped:!@$%3A^&*()',
+ $this->cache->makeKey( 'colon_is_escaped', '!@$:^&*()' )
+ );
+
+ $this->assertEquals(
+ 'test:long_key_part_hashed:#0244f7b1811d982dd932dd7de01465ac',
+ $this->cache->makeKey( 'long_key_part_hashed', str_repeat( 'y', 500 ) )
+ );
+ }
+
+ /**
+ * @dataProvider validKeyProvider
+ * @covers MemcachedBagOStuff::validateKeyEncoding
+ */
+ public function testValidateKeyEncoding( $key ) {
+ $this->assertSame( $key, $this->cache->validateKeyEncoding( $key ) );
+ }
+
+ public function validKeyProvider() {
+ return [
+ 'empty' => [ '' ],
+ 'digits' => [ '09' ],
+ 'letters' => [ 'AZaz' ],
+ 'ASCII special characters' => [ '!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~' ],
+ ];
+ }
+
+ /**
+ * @dataProvider invalidKeyProvider
+ * @covers MemcachedBagOStuff::validateKeyEncoding
+ */
+ public function testValidateKeyEncodingThrowsException( $key ) {
+ $this->setExpectedException( Exception::class );
+ $this->cache->validateKeyEncoding( $key );
+ }
+
+ public function invalidKeyProvider() {
+ return [
+ [ "\x00" ],
+ [ ' ' ],
+ [ "\x1F" ],
+ [ "\x7F" ],
+ [ "\x80" ],
+ [ "\xFF" ],
+ ];
+ }
+}
--- /dev/null
+<?php
+/**
+ * @group BagOStuff
+ *
+ * @covers RESTBagOStuff
+ */
+class RESTBagOStuffTest extends \MediaWikiUnitTestCase {
+
+ /**
+ * @var MultiHttpClient
+ */
+ private $client;
+ /**
+ * @var RESTBagOStuff
+ */
+ private $bag;
+
+ public function setUp() {
+ parent::setUp();
+ $this->client =
+ $this->getMockBuilder( MultiHttpClient::class )
+ ->setConstructorArgs( [ [] ] )
+ ->setMethods( [ 'run' ] )
+ ->getMock();
+ $this->bag = new RESTBagOStuff( [ 'client' => $this->client, 'url' => 'http://test/rest/' ] );
+ }
+
+ public function testGet() {
+ $this->client->expects( $this->once() )->method( 'run' )->with( [
+ 'method' => 'GET',
+ 'url' => 'http://test/rest/42xyz42',
+ 'headers' => []
+ // list( $rcode, $rdesc, $rhdrs, $rbody, $rerr )
+ ] )->willReturn( [ 200, 'OK', [], '"somedata"', 0 ] );
+ $result = $this->bag->get( '42xyz42' );
+ $this->assertEquals( 'somedata', $result );
+ }
+
+ public function testGetNotExist() {
+ $this->client->expects( $this->once() )->method( 'run' )->with( [
+ 'method' => 'GET',
+ 'url' => 'http://test/rest/42xyz42',
+ 'headers' => []
+ // list( $rcode, $rdesc, $rhdrs, $rbody, $rerr )
+ ] )->willReturn( [ 404, 'Not found', [], 'Nothing to see here', 0 ] );
+ $result = $this->bag->get( '42xyz42' );
+ $this->assertFalse( $result );
+ }
+
+ public function testGetBadClient() {
+ $this->client->expects( $this->once() )->method( 'run' )->with( [
+ 'method' => 'GET',
+ 'url' => 'http://test/rest/42xyz42',
+ 'headers' => []
+ // list( $rcode, $rdesc, $rhdrs, $rbody, $rerr )
+ ] )->willReturn( [ 0, '', [], '', 'cURL has failed you today' ] );
+ $result = $this->bag->get( '42xyz42' );
+ $this->assertFalse( $result );
+ $this->assertEquals( BagOStuff::ERR_UNREACHABLE, $this->bag->getLastError() );
+ }
+
+ public function testGetBadServer() {
+ $this->client->expects( $this->once() )->method( 'run' )->with( [
+ 'method' => 'GET',
+ 'url' => 'http://test/rest/42xyz42',
+ 'headers' => []
+ // list( $rcode, $rdesc, $rhdrs, $rbody, $rerr )
+ ] )->willReturn( [ 500, 'Too busy', [], 'Server is too busy', '' ] );
+ $result = $this->bag->get( '42xyz42' );
+ $this->assertFalse( $result );
+ $this->assertEquals( BagOStuff::ERR_UNEXPECTED, $this->bag->getLastError() );
+ }
+
+ public function testPut() {
+ $this->client->expects( $this->once() )->method( 'run' )->with( [
+ 'method' => 'PUT',
+ 'url' => 'http://test/rest/42xyz42',
+ 'body' => '"postdata"',
+ 'headers' => []
+ // list( $rcode, $rdesc, $rhdrs, $rbody, $rerr )
+ ] )->willReturn( [ 200, 'OK', [], 'Done', 0 ] );
+ $result = $this->bag->set( '42xyz42', 'postdata' );
+ $this->assertTrue( $result );
+ }
+
+ public function testDelete() {
+ $this->client->expects( $this->once() )->method( 'run' )->with( [
+ 'method' => 'DELETE',
+ 'url' => 'http://test/rest/42xyz42',
+ 'headers' => []
+ // list( $rcode, $rdesc, $rhdrs, $rbody, $rerr )
+ ] )->willReturn( [ 200, 'OK', [], 'Done', 0 ] );
+ $result = $this->bag->delete( '42xyz42' );
+ $this->assertTrue( $result );
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * @group Parser
+ * @covers MWTidy
+ */
+class TidyTest extends \MediaWikiUnitTestCase {
+
+ protected function setUp() {
+ parent::setUp();
+ if ( !MWTidy::isEnabled() ) {
+ $this->markTestSkipped( 'Tidy not found' );
+ }
+ }
+
+ /**
+ * @dataProvider provideTestWrapping
+ */
+ public function testTidyWrapping( $expected, $text, $msg = '' ) {
+ $text = MWTidy::tidy( $text );
+ // We don't care about where Tidy wants to stick is <p>s
+ $text = trim( preg_replace( '#</?p>#', '', $text ) );
+ // Windows, we love you!
+ $text = str_replace( "\r", '', $text );
+ $this->assertEquals( $expected, $text, $msg );
+ }
+
+ public static function provideTestWrapping() {
+ $testMathML = <<<'MathML'
+<math xmlns="http://www.w3.org/1998/Math/MathML">
+ <mrow>
+ <mi>a</mi>
+ <mo>⁢</mo>
+ <msup>
+ <mi>x</mi>
+ <mn>2</mn>
+ </msup>
+ <mo>+</mo>
+ <mi>b</mi>
+ <mo>⁢ </mo>
+ <mi>x</mi>
+ <mo>+</mo>
+ <mi>c</mi>
+ </mrow>
+ </math>
+MathML;
+ return [
+ [
+ '<mw:editsection page="foo" section="bar">foo</mw:editsection>',
+ '<mw:editsection page="foo" section="bar">foo</mw:editsection>',
+ '<mw:editsection> should survive tidy'
+ ],
+ [
+ '<editsection page="foo" section="bar">foo</editsection>',
+ '<editsection page="foo" section="bar">foo</editsection>',
+ '<editsection> should survive tidy'
+ ],
+ [ '<mw:toc>foo</mw:toc>', '<mw:toc>foo</mw:toc>', '<mw:toc> should survive tidy' ],
+ [ "<link foo=\"bar\" />foo", '<link foo="bar"/>foo', '<link> should survive tidy' ],
+ [ "<meta foo=\"bar\" />foo", '<meta foo="bar"/>foo', '<meta> should survive tidy' ],
+ [ $testMathML, $testMathML, '<math> should survive tidy' ],
+ ];
+ }
+}
--- /dev/null
+<?php
+/**
+ * Testing framework for the Password infrastructure
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+/**
+ * @covers InvalidPassword
+ */
+class PasswordTest extends \MediaWikiUnitTestCase {
+ public function testInvalidPlaintext() {
+ $passwordFactory = new PasswordFactory();
+ $invalid = $passwordFactory->newFromPlaintext( null );
+
+ $this->assertInstanceOf( InvalidPassword::class, $invalid );
+ }
+}
--- /dev/null
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+use MediaWiki\Preferences\IntvalFilter;
+use MediaWiki\Preferences\MultiUsernameFilter;
+use MediaWiki\Preferences\TimezoneFilter;
+
+/**
+ * @group Preferences
+ */
+class FiltersTest extends \MediaWikiUnitTestCase {
+ /**
+ * @covers MediaWiki\Preferences\IntvalFilter::filterFromForm()
+ * @covers MediaWiki\Preferences\IntvalFilter::filterForForm()
+ */
+ public function testIntvalFilter() {
+ $filter = new IntvalFilter();
+ self::assertSame( 0, $filter->filterFromForm( '0' ) );
+ self::assertSame( 3, $filter->filterFromForm( '3' ) );
+ self::assertSame( '123', $filter->filterForForm( '123' ) );
+ }
+
+ /**
+ * @covers MediaWiki\Preferences\TimezoneFilter::filterFromForm()
+ * @dataProvider provideTimezoneFilter
+ *
+ * @param string $input
+ * @param string $expected
+ */
+ public function testTimezoneFilter( $input, $expected ) {
+ $filter = new TimezoneFilter();
+ $result = $filter->filterFromForm( $input );
+ self::assertEquals( $expected, $result );
+ }
+
+ public function provideTimezoneFilter() {
+ return [
+ [ 'ZoneInfo', 'Offset|0' ],
+ [ 'ZoneInfo|bogus', 'Offset|0' ],
+ [ 'System', 'System' ],
+ [ '2:30', 'Offset|150' ],
+ ];
+ }
+
+ /**
+ * @covers MediaWiki\Preferences\MultiUsernameFilter::filterFromForm()
+ * @dataProvider provideMultiUsernameFilterFrom
+ *
+ * @param string $input
+ * @param string|null $expected
+ */
+ public function testMultiUsernameFilterFrom( $input, $expected ) {
+ $filter = $this->makeMultiUsernameFilter();
+ $result = $filter->filterFromForm( $input );
+ self::assertSame( $expected, $result );
+ }
+
+ public function provideMultiUsernameFilterFrom() {
+ return [
+ [ '', null ],
+ [ "\n\n\n", null ],
+ [ 'Foo', '1' ],
+ [ "\n\n\nFoo\nBar\n", "1\n2" ],
+ [ "Baz\nInvalid\nFoo", "3\n1" ],
+ [ "Invalid", null ],
+ [ "Invalid\n\n\nInvalid\n", null ],
+ ];
+ }
+
+ /**
+ * @covers MediaWiki\Preferences\MultiUsernameFilter::filterForForm()
+ * @dataProvider provideMultiUsernameFilterFor
+ *
+ * @param string $input
+ * @param string $expected
+ */
+ public function testMultiUsernameFilterFor( $input, $expected ) {
+ $filter = $this->makeMultiUsernameFilter();
+ $result = $filter->filterForForm( $input );
+ self::assertSame( $expected, $result );
+ }
+
+ public function provideMultiUsernameFilterFor() {
+ return [
+ [ '', '' ],
+ [ "\n", '' ],
+ [ '1', 'Foo' ],
+ [ "\n1\n\n2\377\n", "Foo\nBar" ],
+ [ "666\n667", '' ],
+ ];
+ }
+
+ private function makeMultiUsernameFilter() {
+ $userMapping = [
+ 'Foo' => 1,
+ 'Bar' => 2,
+ 'Baz' => 3,
+ ];
+ $flipped = array_flip( $userMapping );
+ $idLookup = self::getMockBuilder( CentralIdLookup::class )
+ ->disableOriginalConstructor()
+ ->setMethods( [ 'centralIdsFromNames', 'namesFromCentralIds' ] )
+ ->getMockForAbstractClass();
+
+ $idLookup->method( 'centralIdsFromNames' )
+ ->will( self::returnCallback( function ( $names ) use ( $userMapping ) {
+ $ids = [];
+ foreach ( $names as $name ) {
+ $ids[] = $userMapping[$name] ?? null;
+ }
+ return array_filter( $ids, 'is_numeric' );
+ } ) );
+ $idLookup->method( 'namesFromCentralIds' )
+ ->will( self::returnCallback( function ( $ids ) use ( $flipped ) {
+ $names = [];
+ foreach ( $ids as $id ) {
+ $names[] = $flipped[$id] ?? null;
+ }
+ return array_filter( $names, 'is_string' );
+ } ) );
+
+ return new MultiUsernameFilter( $idLookup );
+ }
+}
--- /dev/null
+<?php
+
+use Wikimedia\TestingAccessWrapper;
+
+/**
+ * @covers ExtensionProcessor
+ */
+class ExtensionProcessorTest extends \MediaWikiUnitTestCase {
+
+ private $dir, $dirname;
+
+ public function setUp() {
+ parent::setUp();
+ $this->dir = __DIR__ . '/FooBar/extension.json';
+ $this->dirname = dirname( $this->dir );
+ }
+
+ /**
+ * 'name' is absolutely required
+ *
+ * @var array
+ */
+ public static $default = [
+ 'name' => 'FooBar',
+ ];
+
+ public function testExtractInfo() {
+ // Test that attributes that begin with @ are ignored
+ $processor = new ExtensionProcessor();
+ $processor->extractInfo( $this->dir, self::$default + [
+ '@metadata' => [ 'foobarbaz' ],
+ 'AnAttribute' => [ 'omg' ],
+ 'AutoloadClasses' => [ 'FooBar' => 'includes/FooBar.php' ],
+ 'SpecialPages' => [ 'Foo' => 'SpecialFoo' ],
+ 'callback' => 'FooBar::onRegistration',
+ ], 1 );
+
+ $extracted = $processor->getExtractedInfo();
+ $attributes = $extracted['attributes'];
+ $this->assertArrayHasKey( 'AnAttribute', $attributes );
+ $this->assertArrayNotHasKey( '@metadata', $attributes );
+ $this->assertArrayNotHasKey( 'AutoloadClasses', $attributes );
+ $this->assertSame(
+ [ 'FooBar' => 'FooBar::onRegistration' ],
+ $extracted['callbacks']
+ );
+ $this->assertSame(
+ [ 'Foo' => 'SpecialFoo' ],
+ $extracted['globals']['wgSpecialPages']
+ );
+ }
+
+ public function testExtractNamespaces() {
+ // Test that namespace IDs can be overwritten
+ if ( !defined( 'MW_EXTENSION_PROCESSOR_TEST_EXTRACT_INFO_X' ) ) {
+ define( 'MW_EXTENSION_PROCESSOR_TEST_EXTRACT_INFO_X', 123456 );
+ }
+
+ $processor = new ExtensionProcessor();
+ $processor->extractInfo( $this->dir, self::$default + [
+ 'namespaces' => [
+ [
+ 'id' => 332200,
+ 'constant' => 'MW_EXTENSION_PROCESSOR_TEST_EXTRACT_INFO_A',
+ 'name' => 'Test_A',
+ 'defaultcontentmodel' => 'TestModel',
+ 'gender' => [
+ 'male' => 'Male test',
+ 'female' => 'Female test',
+ ],
+ 'subpages' => true,
+ 'content' => true,
+ 'protection' => 'userright',
+ ],
+ [ // Test_X will use ID 123456 not 334400
+ 'id' => 334400,
+ 'constant' => 'MW_EXTENSION_PROCESSOR_TEST_EXTRACT_INFO_X',
+ 'name' => 'Test_X',
+ 'defaultcontentmodel' => 'TestModel'
+ ],
+ ]
+ ], 1 );
+
+ $extracted = $processor->getExtractedInfo();
+
+ $this->assertArrayHasKey(
+ 'MW_EXTENSION_PROCESSOR_TEST_EXTRACT_INFO_A',
+ $extracted['defines']
+ );
+ $this->assertArrayNotHasKey(
+ 'MW_EXTENSION_PROCESSOR_TEST_EXTRACT_INFO_X',
+ $extracted['defines']
+ );
+
+ $this->assertSame(
+ $extracted['defines']['MW_EXTENSION_PROCESSOR_TEST_EXTRACT_INFO_A'],
+ 332200
+ );
+
+ $this->assertArrayHasKey( 'ExtensionNamespaces', $extracted['attributes'] );
+ $this->assertArrayHasKey( 123456, $extracted['attributes']['ExtensionNamespaces'] );
+ $this->assertArrayHasKey( 332200, $extracted['attributes']['ExtensionNamespaces'] );
+ $this->assertArrayNotHasKey( 334400, $extracted['attributes']['ExtensionNamespaces'] );
+
+ $this->assertSame( 'Test_X', $extracted['attributes']['ExtensionNamespaces'][123456] );
+ $this->assertSame( 'Test_A', $extracted['attributes']['ExtensionNamespaces'][332200] );
+ $this->assertSame(
+ [ 'male' => 'Male test', 'female' => 'Female test' ],
+ $extracted['globals']['wgExtraGenderNamespaces'][332200]
+ );
+ // A has subpages, X does not
+ $this->assertTrue( $extracted['globals']['wgNamespacesWithSubpages'][332200] );
+ $this->assertArrayNotHasKey( 123456, $extracted['globals']['wgNamespacesWithSubpages'] );
+ }
+
+ public static function provideRegisterHooks() {
+ $merge = [ ExtensionRegistry::MERGE_STRATEGY => 'array_merge_recursive' ];
+ // Format:
+ // Current $wgHooks
+ // Content in extension.json
+ // Expected value of $wgHooks
+ return [
+ // No hooks
+ [
+ [],
+ self::$default,
+ $merge,
+ ],
+ // No current hooks, adding one for "FooBaz" in string format
+ [
+ [],
+ [ 'Hooks' => [ 'FooBaz' => 'FooBazCallback' ] ] + self::$default,
+ [ 'FooBaz' => [ 'FooBazCallback' ] ] + $merge,
+ ],
+ // Hook for "FooBaz", adding another one
+ [
+ [ 'FooBaz' => [ 'PriorCallback' ] ],
+ [ 'Hooks' => [ 'FooBaz' => 'FooBazCallback' ] ] + self::$default,
+ [ 'FooBaz' => [ 'PriorCallback', 'FooBazCallback' ] ] + $merge,
+ ],
+ // No current hooks, adding one for "FooBaz" in verbose array format
+ [
+ [],
+ [ 'Hooks' => [ 'FooBaz' => [ 'FooBazCallback' ] ] ] + self::$default,
+ [ 'FooBaz' => [ 'FooBazCallback' ] ] + $merge,
+ ],
+ // Hook for "BarBaz", adding one for "FooBaz"
+ [
+ [ 'BarBaz' => [ 'BarBazCallback' ] ],
+ [ 'Hooks' => [ 'FooBaz' => 'FooBazCallback' ] ] + self::$default,
+ [
+ 'BarBaz' => [ 'BarBazCallback' ],
+ 'FooBaz' => [ 'FooBazCallback' ],
+ ] + $merge,
+ ],
+ // Callbacks for FooBaz wrapped in an array
+ [
+ [],
+ [ 'Hooks' => [ 'FooBaz' => [ 'Callback1' ] ] ] + self::$default,
+ [
+ 'FooBaz' => [ 'Callback1' ],
+ ] + $merge,
+ ],
+ // Multiple callbacks for FooBaz hook
+ [
+ [],
+ [ 'Hooks' => [ 'FooBaz' => [ 'Callback1', 'Callback2' ] ] ] + self::$default,
+ [
+ 'FooBaz' => [ 'Callback1', 'Callback2' ],
+ ] + $merge,
+ ],
+ ];
+ }
+
+ /**
+ * @dataProvider provideRegisterHooks
+ */
+ public function testRegisterHooks( $pre, $info, $expected ) {
+ $processor = new MockExtensionProcessor( [ 'wgHooks' => $pre ] );
+ $processor->extractInfo( $this->dir, $info, 1 );
+ $extracted = $processor->getExtractedInfo();
+ $this->assertEquals( $expected, $extracted['globals']['wgHooks'] );
+ }
+
+ public function testExtractConfig1() {
+ $processor = new ExtensionProcessor;
+ $info = [
+ 'config' => [
+ 'Bar' => 'somevalue',
+ 'Foo' => 10,
+ '@IGNORED' => 'yes',
+ ],
+ ] + self::$default;
+ $info2 = [
+ 'config' => [
+ '_prefix' => 'eg',
+ 'Bar' => 'somevalue'
+ ],
+ 'name' => 'FooBar2',
+ ];
+ $processor->extractInfo( $this->dir, $info, 1 );
+ $processor->extractInfo( $this->dir, $info2, 1 );
+ $extracted = $processor->getExtractedInfo();
+ $this->assertEquals( 'somevalue', $extracted['globals']['wgBar'] );
+ $this->assertEquals( 10, $extracted['globals']['wgFoo'] );
+ $this->assertArrayNotHasKey( 'wg@IGNORED', $extracted['globals'] );
+ // Custom prefix:
+ $this->assertEquals( 'somevalue', $extracted['globals']['egBar'] );
+ }
+
+ public function testExtractConfig2() {
+ $processor = new ExtensionProcessor;
+ $info = [
+ 'config' => [
+ 'Bar' => [ 'value' => 'somevalue' ],
+ 'Foo' => [ 'value' => 10 ],
+ 'Path' => [ 'value' => 'foo.txt', 'path' => true ],
+ 'Namespaces' => [
+ 'value' => [
+ '10' => true,
+ '12' => false,
+ ],
+ 'merge_strategy' => 'array_plus',
+ ],
+ ],
+ ] + self::$default;
+ $info2 = [
+ 'config' => [
+ 'Bar' => [ 'value' => 'somevalue' ],
+ ],
+ 'config_prefix' => 'eg',
+ 'name' => 'FooBar2',
+ ];
+ $processor->extractInfo( $this->dir, $info, 2 );
+ $processor->extractInfo( $this->dir, $info2, 2 );
+ $extracted = $processor->getExtractedInfo();
+ $this->assertEquals( 'somevalue', $extracted['globals']['wgBar'] );
+ $this->assertEquals( 10, $extracted['globals']['wgFoo'] );
+ $this->assertEquals( "{$this->dirname}/foo.txt", $extracted['globals']['wgPath'] );
+ // Custom prefix:
+ $this->assertEquals( 'somevalue', $extracted['globals']['egBar'] );
+ $this->assertSame(
+ [ 10 => true, 12 => false, ExtensionRegistry::MERGE_STRATEGY => 'array_plus' ],
+ $extracted['globals']['wgNamespaces']
+ );
+ }
+
+ /**
+ * @expectedException RuntimeException
+ */
+ public function testDuplicateConfigKey1() {
+ $processor = new ExtensionProcessor;
+ $info = [
+ 'config' => [
+ 'Bar' => '',
+ ]
+ ] + self::$default;
+ $info2 = [
+ 'config' => [
+ 'Bar' => 'g',
+ ],
+ 'name' => 'FooBar2',
+ ];
+ $processor->extractInfo( $this->dir, $info, 1 );
+ $processor->extractInfo( $this->dir, $info2, 1 );
+ }
+
+ /**
+ * @expectedException RuntimeException
+ */
+ public function testDuplicateConfigKey2() {
+ $processor = new ExtensionProcessor;
+ $info = [
+ 'config' => [
+ 'Bar' => [ 'value' => 'somevalue' ],
+ ]
+ ] + self::$default;
+ $info2 = [
+ 'config' => [
+ 'Bar' => [ 'value' => 'somevalue' ],
+ ],
+ 'name' => 'FooBar2',
+ ];
+ $processor->extractInfo( $this->dir, $info, 2 );
+ $processor->extractInfo( $this->dir, $info2, 2 );
+ }
+
+ public static function provideExtractExtensionMessagesFiles() {
+ $dir = __DIR__ . '/FooBar/';
+ return [
+ [
+ [ 'ExtensionMessagesFiles' => [ 'FooBarAlias' => 'FooBar.alias.php' ] ],
+ [ 'wgExtensionMessagesFiles' => [ 'FooBarAlias' => $dir . 'FooBar.alias.php' ] ]
+ ],
+ [
+ [
+ 'ExtensionMessagesFiles' => [
+ 'FooBarAlias' => 'FooBar.alias.php',
+ 'FooBarMagic' => 'FooBar.magic.i18n.php',
+ ],
+ ],
+ [
+ 'wgExtensionMessagesFiles' => [
+ 'FooBarAlias' => $dir . 'FooBar.alias.php',
+ 'FooBarMagic' => $dir . 'FooBar.magic.i18n.php',
+ ],
+ ],
+ ],
+ ];
+ }
+
+ /**
+ * @dataProvider provideExtractExtensionMessagesFiles
+ */
+ public function testExtractExtensionMessagesFiles( $input, $expected ) {
+ $processor = new ExtensionProcessor();
+ $processor->extractInfo( $this->dir, $input + self::$default, 1 );
+ $out = $processor->getExtractedInfo();
+ foreach ( $expected as $key => $value ) {
+ $this->assertEquals( $value, $out['globals'][$key] );
+ }
+ }
+
+ public static function provideExtractMessagesDirs() {
+ $dir = __DIR__ . '/FooBar/';
+ return [
+ [
+ [ 'MessagesDirs' => [ 'VisualEditor' => 'i18n' ] ],
+ [ 'wgMessagesDirs' => [ 'VisualEditor' => [ $dir . 'i18n' ] ] ]
+ ],
+ [
+ [ 'MessagesDirs' => [ 'VisualEditor' => [ 'i18n', 'foobar' ] ] ],
+ [ 'wgMessagesDirs' => [ 'VisualEditor' => [ $dir . 'i18n', $dir . 'foobar' ] ] ]
+ ],
+ ];
+ }
+
+ /**
+ * @dataProvider provideExtractMessagesDirs
+ */
+ public function testExtractMessagesDirs( $input, $expected ) {
+ $processor = new ExtensionProcessor();
+ $processor->extractInfo( $this->dir, $input + self::$default, 1 );
+ $out = $processor->getExtractedInfo();
+ foreach ( $expected as $key => $value ) {
+ $this->assertEquals( $value, $out['globals'][$key] );
+ }
+ }
+
+ public function testExtractCredits() {
+ $processor = new ExtensionProcessor();
+ $processor->extractInfo( $this->dir, self::$default, 1 );
+ $this->setExpectedException( Exception::class );
+ $processor->extractInfo( $this->dir, self::$default, 1 );
+ }
+
+ /**
+ * @dataProvider provideExtractResourceLoaderModules
+ */
+ public function testExtractResourceLoaderModules(
+ $input,
+ array $expectedGlobals,
+ array $expectedAttribs = []
+ ) {
+ $processor = new ExtensionProcessor();
+ $processor->extractInfo( $this->dir, $input + self::$default, 1 );
+ $out = $processor->getExtractedInfo();
+ foreach ( $expectedGlobals as $key => $value ) {
+ $this->assertEquals( $value, $out['globals'][$key] );
+ }
+ foreach ( $expectedAttribs as $key => $value ) {
+ $this->assertEquals( $value, $out['attributes'][$key] );
+ }
+ }
+
+ public static function provideExtractResourceLoaderModules() {
+ $dir = __DIR__ . '/FooBar';
+ return [
+ // Generic module with localBasePath/remoteExtPath specified
+ [
+ // Input
+ [
+ 'ResourceModules' => [
+ 'test.foo' => [
+ 'styles' => 'foobar.js',
+ 'localBasePath' => '',
+ 'remoteExtPath' => 'FooBar',
+ ],
+ ],
+ ],
+ // Expected
+ [
+ 'wgResourceModules' => [
+ 'test.foo' => [
+ 'styles' => 'foobar.js',
+ 'localBasePath' => $dir,
+ 'remoteExtPath' => 'FooBar',
+ ],
+ ],
+ ],
+ ],
+ // ResourceFileModulePaths specified:
+ [
+ // Input
+ [
+ 'ResourceFileModulePaths' => [
+ 'localBasePath' => 'modules',
+ 'remoteExtPath' => 'FooBar/modules',
+ ],
+ 'ResourceModules' => [
+ // No paths
+ 'test.foo' => [
+ 'styles' => 'foo.js',
+ ],
+ // Different paths set
+ 'test.bar' => [
+ 'styles' => 'bar.js',
+ 'localBasePath' => 'subdir',
+ 'remoteExtPath' => 'FooBar/subdir',
+ ],
+ // Custom class with no paths set
+ 'test.class' => [
+ 'class' => 'FooBarModule',
+ 'extra' => 'argument',
+ ],
+ // Custom class with a localBasePath
+ 'test.class.with.path' => [
+ 'class' => 'FooBarPathModule',
+ 'extra' => 'argument',
+ 'localBasePath' => '',
+ ]
+ ],
+ ],
+ // Expected
+ [
+ 'wgResourceModules' => [
+ 'test.foo' => [
+ 'styles' => 'foo.js',
+ 'localBasePath' => "$dir/modules",
+ 'remoteExtPath' => 'FooBar/modules',
+ ],
+ 'test.bar' => [
+ 'styles' => 'bar.js',
+ 'localBasePath' => "$dir/subdir",
+ 'remoteExtPath' => 'FooBar/subdir',
+ ],
+ 'test.class' => [
+ 'class' => 'FooBarModule',
+ 'extra' => 'argument',
+ 'localBasePath' => "$dir/modules",
+ 'remoteExtPath' => 'FooBar/modules',
+ ],
+ 'test.class.with.path' => [
+ 'class' => 'FooBarPathModule',
+ 'extra' => 'argument',
+ 'localBasePath' => $dir,
+ 'remoteExtPath' => 'FooBar/modules',
+ ]
+ ],
+ ],
+ ],
+ // ResourceModuleSkinStyles with file module paths
+ [
+ // Input
+ [
+ 'ResourceFileModulePaths' => [
+ 'localBasePath' => '',
+ 'remoteSkinPath' => 'FooBar',
+ ],
+ 'ResourceModuleSkinStyles' => [
+ 'foobar' => [
+ 'test.foo' => 'foo.css',
+ ]
+ ],
+ ],
+ // Expected
+ [
+ 'wgResourceModuleSkinStyles' => [
+ 'foobar' => [
+ 'test.foo' => 'foo.css',
+ 'localBasePath' => $dir,
+ 'remoteSkinPath' => 'FooBar',
+ ],
+ ],
+ ],
+ ],
+ // ResourceModuleSkinStyles with file module paths and an override
+ [
+ // Input
+ [
+ 'ResourceFileModulePaths' => [
+ 'localBasePath' => '',
+ 'remoteSkinPath' => 'FooBar',
+ ],
+ 'ResourceModuleSkinStyles' => [
+ 'foobar' => [
+ 'test.foo' => 'foo.css',
+ 'remoteSkinPath' => 'BarFoo'
+ ],
+ ],
+ ],
+ // Expected
+ [
+ 'wgResourceModuleSkinStyles' => [
+ 'foobar' => [
+ 'test.foo' => 'foo.css',
+ 'localBasePath' => $dir,
+ 'remoteSkinPath' => 'BarFoo',
+ ],
+ ],
+ ],
+ ],
+ 'QUnit test module' => [
+ // Input
+ [
+ 'QUnitTestModule' => [
+ 'localBasePath' => '',
+ 'remoteExtPath' => 'Foo',
+ 'scripts' => 'bar.js',
+ ],
+ ],
+ // Expected
+ [],
+ [
+ 'QUnitTestModules' => [
+ 'test.FooBar' => [
+ 'localBasePath' => $dir,
+ 'remoteExtPath' => 'Foo',
+ 'scripts' => 'bar.js',
+ ],
+ ],
+ ],
+ ],
+ ];
+ }
+
+ public static function provideSetToGlobal() {
+ return [
+ [
+ [ 'wgAPIModules', 'wgAvailableRights' ],
+ [],
+ [
+ 'APIModules' => [ 'foobar' => 'ApiFooBar' ],
+ 'AvailableRights' => [ 'foobar', 'unfoobar' ],
+ ],
+ [
+ 'wgAPIModules' => [ 'foobar' => 'ApiFooBar' ],
+ 'wgAvailableRights' => [ 'foobar', 'unfoobar' ],
+ ],
+ ],
+ [
+ [ 'wgAPIModules', 'wgAvailableRights' ],
+ [
+ 'wgAPIModules' => [ 'barbaz' => 'ApiBarBaz' ],
+ 'wgAvailableRights' => [ 'barbaz' ]
+ ],
+ [
+ 'APIModules' => [ 'foobar' => 'ApiFooBar' ],
+ 'AvailableRights' => [ 'foobar', 'unfoobar' ],
+ ],
+ [
+ 'wgAPIModules' => [ 'barbaz' => 'ApiBarBaz', 'foobar' => 'ApiFooBar' ],
+ 'wgAvailableRights' => [ 'barbaz', 'foobar', 'unfoobar' ],
+ ],
+ ],
+ [
+ [ 'wgGroupPermissions' ],
+ [
+ 'wgGroupPermissions' => [
+ 'sysop' => [ 'delete' ]
+ ],
+ ],
+ [
+ 'GroupPermissions' => [
+ 'sysop' => [ 'undelete' ],
+ 'user' => [ 'edit' ]
+ ],
+ ],
+ [
+ 'wgGroupPermissions' => [
+ 'sysop' => [ 'delete', 'undelete' ],
+ 'user' => [ 'edit' ]
+ ],
+ ]
+ ]
+ ];
+ }
+
+ /**
+ * Attributes under manifest_version 2
+ */
+ public function testExtractAttributes() {
+ $processor = new ExtensionProcessor();
+ // Load FooBar extension
+ $processor->extractInfo( $this->dir, [ 'name' => 'FooBar' ], 2 );
+ $processor->extractInfo(
+ $this->dir,
+ [
+ 'name' => 'Baz',
+ 'attributes' => [
+ // Loaded
+ 'FooBar' => [
+ 'Plugins' => [
+ 'ext.baz.foobar',
+ ],
+ ],
+ // Not loaded
+ 'FizzBuzz' => [
+ 'MorePlugins' => [
+ 'ext.baz.fizzbuzz',
+ ],
+ ],
+ ],
+ ],
+ 2
+ );
+
+ $info = $processor->getExtractedInfo();
+ $this->assertArrayHasKey( 'FooBarPlugins', $info['attributes'] );
+ $this->assertSame( [ 'ext.baz.foobar' ], $info['attributes']['FooBarPlugins'] );
+ $this->assertArrayNotHasKey( 'FizzBuzzMorePlugins', $info['attributes'] );
+ }
+
+ /**
+ * Attributes under manifest_version 1
+ */
+ public function testAttributes1() {
+ $processor = new ExtensionProcessor();
+ $processor->extractInfo(
+ $this->dir,
+ [
+ 'name' => 'FooBar',
+ 'FooBarPlugins' => [
+ 'ext.baz.foobar',
+ ],
+ 'FizzBuzzMorePlugins' => [
+ 'ext.baz.fizzbuzz',
+ ],
+ ],
+ 1
+ );
+ $processor->extractInfo(
+ $this->dir,
+ [
+ 'name' => 'FooBar2',
+ 'FizzBuzzMorePlugins' => [
+ 'ext.bar.fizzbuzz',
+ ]
+ ],
+ 1
+ );
+
+ $info = $processor->getExtractedInfo();
+ $this->assertArrayHasKey( 'FooBarPlugins', $info['attributes'] );
+ $this->assertSame( [ 'ext.baz.foobar' ], $info['attributes']['FooBarPlugins'] );
+ $this->assertArrayHasKey( 'FizzBuzzMorePlugins', $info['attributes'] );
+ $this->assertSame(
+ [ 'ext.baz.fizzbuzz', 'ext.bar.fizzbuzz' ],
+ $info['attributes']['FizzBuzzMorePlugins']
+ );
+ }
+
+ public function testAttributes1_notarray() {
+ $processor = new ExtensionProcessor();
+ $this->setExpectedException(
+ InvalidArgumentException::class,
+ "The value for 'FooBarPlugins' should be an array (from {$this->dir})"
+ );
+ $processor->extractInfo(
+ $this->dir,
+ [
+ 'FooBarPlugins' => 'ext.baz.foobar',
+ ] + self::$default,
+ 1
+ );
+ }
+
+ public function testExtractPathBasedGlobal() {
+ $processor = new ExtensionProcessor();
+ $processor->extractInfo(
+ $this->dir,
+ [
+ 'ParserTestFiles' => [
+ 'tests/parserTests.txt',
+ 'tests/extraParserTests.txt',
+ ],
+ 'ServiceWiringFiles' => [
+ 'includes/ServiceWiring.php'
+ ],
+ ] + self::$default,
+ 1
+ );
+ $globals = $processor->getExtractedInfo()['globals'];
+ $this->assertArrayHasKey( 'wgParserTestFiles', $globals );
+ $this->assertSame( [
+ "{$this->dirname}/tests/parserTests.txt",
+ "{$this->dirname}/tests/extraParserTests.txt"
+ ], $globals['wgParserTestFiles'] );
+ $this->assertArrayHasKey( 'wgServiceWiringFiles', $globals );
+ $this->assertSame( [
+ "{$this->dirname}/includes/ServiceWiring.php"
+ ], $globals['wgServiceWiringFiles'] );
+ }
+
+ public function testGetRequirements() {
+ $info = self::$default + [
+ 'requires' => [
+ 'MediaWiki' => '>= 1.25.0',
+ 'platform' => [
+ 'php' => '>= 5.5.9'
+ ],
+ 'extensions' => [
+ 'Bar' => '*'
+ ]
+ ]
+ ];
+ $processor = new ExtensionProcessor();
+ $this->assertSame(
+ $info['requires'],
+ $processor->getRequirements( $info, false )
+ );
+ $this->assertSame(
+ [],
+ $processor->getRequirements( [], false )
+ );
+ }
+
+ public function testGetDevRequirements() {
+ $info = self::$default + [
+ 'dev-requires' => [
+ 'MediaWiki' => '>= 1.31.0',
+ 'platform' => [
+ 'ext-foo' => '*',
+ ],
+ 'skins' => [
+ 'Baz' => '*',
+ ],
+ 'extensions' => [
+ 'Biz' => '*',
+ ],
+ ],
+ ];
+ $processor = new ExtensionProcessor();
+ $this->assertSame(
+ $info['dev-requires'],
+ $processor->getRequirements( $info, true )
+ );
+ // Set some standard requirements, so we can test merging
+ $info['requires'] = [
+ 'MediaWiki' => '>= 1.25.0',
+ 'platform' => [
+ 'php' => '>= 5.5.9'
+ ],
+ 'extensions' => [
+ 'Bar' => '*'
+ ]
+ ];
+ $this->assertSame(
+ [
+ 'MediaWiki' => '>= 1.25.0 >= 1.31.0',
+ 'platform' => [
+ 'php' => '>= 5.5.9',
+ 'ext-foo' => '*',
+ ],
+ 'extensions' => [
+ 'Bar' => '*',
+ 'Biz' => '*',
+ ],
+ 'skins' => [
+ 'Baz' => '*',
+ ],
+ ],
+ $processor->getRequirements( $info, true )
+ );
+
+ // If there's no dev-requires, it just returns requires
+ unset( $info['dev-requires'] );
+ $this->assertSame(
+ $info['requires'],
+ $processor->getRequirements( $info, true )
+ );
+ }
+
+ public function testGetExtraAutoloaderPaths() {
+ $processor = new ExtensionProcessor();
+ $this->assertSame(
+ [ "{$this->dirname}/vendor/autoload.php" ],
+ $processor->getExtraAutoloaderPaths( $this->dirname, [
+ 'load_composer_autoloader' => true,
+ ] )
+ );
+ }
+
+ /**
+ * Verify that extension.schema.json is in sync with ExtensionProcessor
+ *
+ * @coversNothing
+ */
+ public function testGlobalSettingsDocumentedInSchema() {
+ global $IP;
+ $globalSettings = TestingAccessWrapper::newFromClass(
+ ExtensionProcessor::class )->globalSettings;
+
+ $version = ExtensionRegistry::MANIFEST_VERSION;
+ $schema = FormatJson::decode(
+ file_get_contents( "$IP/docs/extension.schema.v$version.json" ),
+ true
+ );
+ $missing = [];
+ foreach ( $globalSettings as $global ) {
+ if ( !isset( $schema['properties'][$global] ) ) {
+ $missing[] = $global;
+ }
+ }
+
+ $this->assertEquals( [], $missing,
+ "The following global settings are not documented in docs/extension.schema.json" );
+ }
+}
+
+/**
+ * Allow overriding the default value of $this->globals
+ * so we can test merging
+ */
+class MockExtensionProcessor extends ExtensionProcessor {
+ public function __construct( $globals = [] ) {
+ $this->globals = $globals + $this->globals;
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * @group Search
+ * @covers SearchIndexFieldDefinition
+ */
+class SearchIndexFieldTest extends \MediaWikiUnitTestCase {
+
+ public function getMergeCases() {
+ return [
+ [ 0, 'test', 0, 'test', true ],
+ [ SearchIndexField::INDEX_TYPE_NESTED, 'test',
+ SearchIndexField::INDEX_TYPE_NESTED, 'test', false ],
+ [ 0, 'test', 0, 'test2', true ],
+ [ 0, 'test', 1, 'test', false ],
+ ];
+ }
+
+ /**
+ * @dataProvider getMergeCases
+ * @param int $t1
+ * @param string $n1
+ * @param int $t2
+ * @param string $n2
+ * @param bool $result
+ */
+ public function testMerge( $t1, $n1, $t2, $n2, $result ) {
+ $field1 =
+ $this->getMockBuilder( SearchIndexFieldDefinition::class )
+ ->setMethods( [ 'getMapping' ] )
+ ->setConstructorArgs( [ $n1, $t1 ] )
+ ->getMock();
+ $field2 =
+ $this->getMockBuilder( SearchIndexFieldDefinition::class )
+ ->setMethods( [ 'getMapping' ] )
+ ->setConstructorArgs( [ $n2, $t2 ] )
+ ->getMock();
+
+ if ( $result ) {
+ $this->assertNotFalse( $field1->merge( $field2 ) );
+ } else {
+ $this->assertFalse( $field1->merge( $field2 ) );
+ }
+
+ $field1->setFlag( 0xFF );
+ $this->assertFalse( $field1->merge( $field2 ) );
+
+ $field1->setMergeCallback(
+ function ( $a, $b ) {
+ return "test";
+ }
+ );
+ $this->assertEquals( "test", $field1->merge( $field2 ) );
+ }
+
+}
--- /dev/null
+<?php
+
+namespace MediaWiki\Session;
+
+/**
+ * @group Session
+ * @covers MediaWiki\Session\MetadataMergeException
+ */
+class MetadataMergeExceptionTest extends \MediaWikiUnitTestCase {
+
+ public function testBasics() {
+ $data = [ 'foo' => 'bar' ];
+
+ $ex = new MetadataMergeException();
+ $this->assertInstanceOf( \UnexpectedValueException::class, $ex );
+ $this->assertSame( [], $ex->getContext() );
+
+ $ex2 = new MetadataMergeException( 'Message', 42, $ex, $data );
+ $this->assertSame( 'Message', $ex2->getMessage() );
+ $this->assertSame( 42, $ex2->getCode() );
+ $this->assertSame( $ex, $ex2->getPrevious() );
+ $this->assertSame( $data, $ex2->getContext() );
+
+ $ex->setContext( $data );
+ $this->assertSame( $data, $ex->getContext() );
+ }
+
+}
--- /dev/null
+<?php
+
+namespace MediaWiki\Session;
+
+/**
+ * @group Session
+ * @covers MediaWiki\Session\SessionId
+ */
+class SessionIdTest extends \MediaWikiUnitTestCase {
+
+ public function testEverything() {
+ $id = new SessionId( 'foo' );
+ $this->assertSame( 'foo', $id->getId() );
+ $this->assertSame( 'foo', (string)$id );
+ $id->setId( 'bar' );
+ $this->assertSame( 'bar', $id->getId() );
+ $this->assertSame( 'bar', (string)$id );
+ }
+
+}
--- /dev/null
+<?php
+
+class SkinFactoryTest extends \MediaWikiUnitTestCase {
+
+ /**
+ * @covers SkinFactory::register
+ */
+ public function testRegister() {
+ $factory = new SkinFactory();
+ $factory->register( 'fallback', 'Fallback', function () {
+ return new SkinFallback();
+ } );
+ $this->assertTrue( true ); // No exception thrown
+ $this->setExpectedException( InvalidArgumentException::class );
+ $factory->register( 'invalid', 'Invalid', 'Invalid callback' );
+ }
+
+ /**
+ * @covers SkinFactory::makeSkin
+ */
+ public function testMakeSkinWithNoBuilders() {
+ $factory = new SkinFactory();
+ $this->setExpectedException( SkinException::class );
+ $factory->makeSkin( 'nobuilderregistered' );
+ }
+
+ /**
+ * @covers SkinFactory::makeSkin
+ */
+ public function testMakeSkinWithInvalidCallback() {
+ $factory = new SkinFactory();
+ $factory->register( 'unittest', 'Unittest', function () {
+ return true; // Not a Skin object
+ } );
+ $this->setExpectedException( UnexpectedValueException::class );
+ $factory->makeSkin( 'unittest' );
+ }
+
+ /**
+ * @covers SkinFactory::makeSkin
+ */
+ public function testMakeSkinWithValidCallback() {
+ $factory = new SkinFactory();
+ $factory->register( 'testfallback', 'TestFallback', function () {
+ return new SkinFallback();
+ } );
+
+ $skin = $factory->makeSkin( 'testfallback' );
+ $this->assertInstanceOf( Skin::class, $skin );
+ $this->assertInstanceOf( SkinFallback::class, $skin );
+ $this->assertEquals( 'fallback', $skin->getSkinName() );
+ }
+
+ /**
+ * @covers Skin::__construct
+ * @covers Skin::getSkinName
+ */
+ public function testGetSkinName() {
+ $skin = new SkinFallback();
+ $this->assertEquals( 'fallback', $skin->getSkinName(), 'Default' );
+ $skin = new SkinFallback( 'testname' );
+ $this->assertEquals( 'testname', $skin->getSkinName(), 'Constructor argument' );
+ }
+
+ /**
+ * @covers SkinFactory::getSkinNames
+ */
+ public function testGetSkinNames() {
+ $factory = new SkinFactory();
+ // A fake callback we can use that will never be called
+ $callback = function () {
+ // NOP
+ };
+ $factory->register( 'skin1', 'Skin1', $callback );
+ $factory->register( 'skin2', 'Skin2', $callback );
+ $names = $factory->getSkinNames();
+ $this->assertArrayHasKey( 'skin1', $names );
+ $this->assertArrayHasKey( 'skin2', $names );
+ $this->assertEquals( 'Skin1', $names['skin1'] );
+ $this->assertEquals( 'Skin2', $names['skin2'] );
+ }
+}
--- /dev/null
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @author This, that and the other
+ */
+
+/**
+ * @covers ForeignTitle
+ *
+ * @group Title
+ */
+class ForeignTitleTest extends \MediaWikiUnitTestCase {
+
+ public function basicProvider() {
+ return [
+ [
+ new ForeignTitle( 20, 'Contributor', 'JohnDoe' ),
+ 20, 'Contributor', 'JohnDoe'
+ ],
+ [
+ new ForeignTitle( '1', 'Discussion', 'Capital' ),
+ 1, 'Discussion', 'Capital'
+ ],
+ [
+ new ForeignTitle( 0, '', 'MainNamespace' ),
+ 0, '', 'MainNamespace'
+ ],
+ [
+ new ForeignTitle( 4, 'Some ns', 'Article title with spaces' ),
+ 4, 'Some_ns', 'Article_title_with_spaces'
+ ],
+ ];
+ }
+
+ /**
+ * @dataProvider basicProvider
+ */
+ public function testBasic( ForeignTitle $title, $expectedId, $expectedName,
+ $expectedText
+ ) {
+ $this->assertEquals( true, $title->isNamespaceIdKnown() );
+ $this->assertEquals( $expectedId, $title->getNamespaceId() );
+ $this->assertEquals( $expectedName, $title->getNamespaceName() );
+ $this->assertEquals( $expectedText, $title->getText() );
+ }
+
+ public function testUnknownNamespaceCheck() {
+ $title = new ForeignTitle( null, 'this', 'that' );
+
+ $this->assertEquals( false, $title->isNamespaceIdKnown() );
+ $this->assertEquals( 'this', $title->getNamespaceName() );
+ $this->assertEquals( 'that', $title->getText() );
+ }
+
+ public function testUnknownNamespaceError() {
+ $this->setExpectedException( MWException::class );
+ $title = new ForeignTitle( null, 'this', 'that' );
+ $title->getNamespaceId();
+ }
+
+ public function fullTextProvider() {
+ return [
+ [
+ new ForeignTitle( 20, 'Contributor', 'JohnDoe' ),
+ 'Contributor:JohnDoe'
+ ],
+ [
+ new ForeignTitle( '1', 'Discussion', 'Capital' ),
+ 'Discussion:Capital'
+ ],
+ [
+ new ForeignTitle( 0, '', 'MainNamespace' ),
+ 'MainNamespace'
+ ],
+ [
+ new ForeignTitle( 4, 'Some ns', 'Article title with spaces' ),
+ 'Some_ns:Article_title_with_spaces'
+ ],
+ ];
+ }
+
+ /**
+ * @dataProvider fullTextProvider
+ */
+ public function testFullText( ForeignTitle $title, $fullText ) {
+ $this->assertEquals( $fullText, $title->getFullText() );
+ }
+}
--- /dev/null
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @author This, that and the other
+ */
+
+/**
+ * @covers NamespaceAwareForeignTitleFactory
+ *
+ * @group Title
+ */
+class NamespaceAwareForeignTitleFactoryTest extends \MediaWikiUnitTestCase {
+
+ public function basicProvider() {
+ return [
+ [
+ 'MainNamespaceArticle', 0,
+ new ForeignTitle( 0, '', 'MainNamespaceArticle' ),
+ ],
+ [
+ 'MainNamespaceArticle', null,
+ new ForeignTitle( 0, '', 'MainNamespaceArticle' ),
+ ],
+ [
+ 'Magic:_The_Gathering', 0,
+ new ForeignTitle( 0, '', 'Magic:_The_Gathering' ),
+ ],
+ [
+ 'Talk:Nice_talk', 1,
+ new ForeignTitle( 1, 'Talk', 'Nice_talk' ),
+ ],
+ [
+ 'Talk:Magic:_The_Gathering', 1,
+ new ForeignTitle( 1, 'Talk', 'Magic:_The_Gathering' ),
+ ],
+ [
+ 'Bogus:Nice_talk', 0,
+ new ForeignTitle( 0, '', 'Bogus:Nice_talk' ),
+ ],
+ [
+ 'Bogus:Nice_talk', null,
+ new ForeignTitle( 9000, 'Bogus', 'Nice_talk' ),
+ ],
+ [
+ 'Bogus:Nice_talk', 4,
+ new ForeignTitle( 4, 'Bogus', 'Nice_talk' ),
+ ],
+ [
+ 'Bogus:Nice_talk', 1,
+ new ForeignTitle( 1, 'Talk', 'Nice_talk' ),
+ ],
+ // Misconfigured wiki with unregistered namespace (T114115)
+ [
+ 'Nice_talk', 1234,
+ new ForeignTitle( 1234, 'Ns1234', 'Nice_talk' ),
+ ],
+ ];
+ }
+
+ /**
+ * @dataProvider basicProvider
+ */
+ public function testBasic( $title, $ns, ForeignTitle $foreignTitle ) {
+ $foreignNamespaces = [
+ 0 => '', 1 => 'Talk', 100 => 'Portal', 9000 => 'Bogus'
+ ];
+
+ $factory = new NamespaceAwareForeignTitleFactory( $foreignNamespaces );
+ $testTitle = $factory->createForeignTitle( $title, $ns );
+
+ $this->assertEquals( $testTitle->isNamespaceIdKnown(),
+ $foreignTitle->isNamespaceIdKnown() );
+
+ if (
+ $testTitle->isNamespaceIdKnown() &&
+ $foreignTitle->isNamespaceIdKnown()
+ ) {
+ $this->assertEquals( $testTitle->getNamespaceId(),
+ $foreignTitle->getNamespaceId() );
+ }
+
+ $this->assertEquals( $testTitle->getNamespaceName(),
+ $foreignTitle->getNamespaceName() );
+ $this->assertEquals( $testTitle->getText(), $foreignTitle->getText() );
+ }
+}
--- /dev/null
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @author Daniel Kinzler
+ */
+
+/**
+ * @covers TitleValue
+ *
+ * @group Title
+ */
+class TitleValueTest extends \MediaWikiUnitTestCase {
+
+ public function goodConstructorProvider() {
+ return [
+ [ NS_MAIN, '', 'fragment', '', true, false ],
+ [ NS_USER, 'TestThis', 'stuff', '', true, false ],
+ [ NS_USER, 'TestThis', '', 'baz', false, true ],
+ ];
+ }
+
+ /**
+ * @dataProvider goodConstructorProvider
+ */
+ public function testConstruction( $ns, $text, $fragment, $interwiki, $hasFragment,
+ $hasInterwiki
+ ) {
+ $title = new TitleValue( $ns, $text, $fragment, $interwiki );
+
+ $this->assertEquals( $ns, $title->getNamespace() );
+ $this->assertTrue( $title->inNamespace( $ns ) );
+ $this->assertEquals( $text, $title->getText() );
+ $this->assertEquals( $fragment, $title->getFragment() );
+ $this->assertEquals( $hasFragment, $title->hasFragment() );
+ $this->assertEquals( $interwiki, $title->getInterwiki() );
+ $this->assertEquals( $hasInterwiki, $title->isExternal() );
+ }
+
+ public function badConstructorProvider() {
+ return [
+ [ 'foo', 'title', 'fragment', '' ],
+ [ null, 'title', 'fragment', '' ],
+ [ 2.3, 'title', 'fragment', '' ],
+
+ [ NS_MAIN, 5, 'fragment', '' ],
+ [ NS_MAIN, null, 'fragment', '' ],
+ [ NS_USER, '', 'fragment', '' ],
+ [ NS_MAIN, 'foo bar', '', '' ],
+ [ NS_MAIN, 'bar_', '', '' ],
+ [ NS_MAIN, '_foo', '', '' ],
+ [ NS_MAIN, ' eek ', '', '' ],
+
+ [ NS_MAIN, 'title', 5, '' ],
+ [ NS_MAIN, 'title', null, '' ],
+ [ NS_MAIN, 'title', [], '' ],
+
+ [ NS_MAIN, 'title', '', 5 ],
+ [ NS_MAIN, 'title', null, 5 ],
+ [ NS_MAIN, 'title', [], 5 ],
+ ];
+ }
+
+ /**
+ * @dataProvider badConstructorProvider
+ */
+ public function testConstructionErrors( $ns, $text, $fragment, $interwiki ) {
+ $this->setExpectedException( InvalidArgumentException::class );
+ new TitleValue( $ns, $text, $fragment, $interwiki );
+ }
+
+ public function fragmentTitleProvider() {
+ return [
+ [ new TitleValue( NS_MAIN, 'Test' ), 'foo' ],
+ [ new TitleValue( NS_TALK, 'Test', 'foo' ), '' ],
+ [ new TitleValue( NS_CATEGORY, 'Test', 'foo' ), 'bar' ],
+ ];
+ }
+
+ /**
+ * @dataProvider fragmentTitleProvider
+ */
+ public function testCreateFragmentTitle( TitleValue $title, $fragment ) {
+ $fragmentTitle = $title->createFragmentTarget( $fragment );
+
+ $this->assertEquals( $title->getNamespace(), $fragmentTitle->getNamespace() );
+ $this->assertEquals( $title->getText(), $fragmentTitle->getText() );
+ $this->assertEquals( $fragment, $fragmentTitle->getFragment() );
+ }
+
+ public function getTextProvider() {
+ return [
+ [ 'Foo', 'Foo' ],
+ [ 'Foo_Bar', 'Foo Bar' ],
+ ];
+ }
+
+ /**
+ * @dataProvider getTextProvider
+ */
+ public function testGetText( $dbkey, $text ) {
+ $title = new TitleValue( NS_MAIN, $dbkey );
+
+ $this->assertEquals( $text, $title->getText() );
+ }
+
+ public function provideTestToString() {
+ yield [
+ new TitleValue( 0, 'Foo' ),
+ '0:Foo'
+ ];
+ yield [
+ new TitleValue( 1, 'Bar_Baz' ),
+ '1:Bar_Baz'
+ ];
+ yield [
+ new TitleValue( 9, 'JoJo', 'Frag' ),
+ '9:JoJo#Frag'
+ ];
+ yield [
+ new TitleValue( 200, 'tea', 'Fragment', 'wikicode' ),
+ 'wikicode:200:tea#Fragment'
+ ];
+ }
+
+ /**
+ * @dataProvider provideTestToString
+ */
+ public function testToString( TitleValue $value, $expected ) {
+ $this->assertSame(
+ $expected,
+ $value->__toString()
+ );
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * @author Addshore
+ * @covers UserArrayFromResult
+ */
+class UserArrayFromResultTest extends \MediaWikiUnitTestCase {
+
+ private function getMockResultWrapper( $row = null, $numRows = 1 ) {
+ $resultWrapper = $this->getMockBuilder( Wikimedia\Rdbms\ResultWrapper::class )
+ ->disableOriginalConstructor();
+
+ $resultWrapper = $resultWrapper->getMock();
+ $resultWrapper->expects( $this->atLeastOnce() )
+ ->method( 'current' )
+ ->will( $this->returnValue( $row ) );
+ $resultWrapper->expects( $this->any() )
+ ->method( 'numRows' )
+ ->will( $this->returnValue( $numRows ) );
+
+ return $resultWrapper;
+ }
+
+ private function getRowWithUsername( $username = 'fooUser' ) {
+ $row = new stdClass();
+ $row->user_name = $username;
+ return $row;
+ }
+
+ /**
+ * @covers UserArrayFromResult::__construct
+ */
+ public function testConstructionWithFalseRow() {
+ $row = false;
+ $resultWrapper = $this->getMockResultWrapper( $row );
+
+ $object = new UserArrayFromResult( $resultWrapper );
+
+ $this->assertEquals( $resultWrapper, $object->res );
+ $this->assertSame( 0, $object->key );
+ $this->assertEquals( $row, $object->current );
+ }
+
+ /**
+ * @covers UserArrayFromResult::__construct
+ */
+ public function testConstructionWithRow() {
+ $username = 'addshore';
+ $row = $this->getRowWithUsername( $username );
+ $resultWrapper = $this->getMockResultWrapper( $row );
+
+ $object = new UserArrayFromResult( $resultWrapper );
+
+ $this->assertEquals( $resultWrapper, $object->res );
+ $this->assertSame( 0, $object->key );
+ $this->assertInstanceOf( User::class, $object->current );
+ $this->assertEquals( $username, $object->current->mName );
+ }
+
+ public static function provideNumberOfRows() {
+ return [
+ [ 0 ],
+ [ 1 ],
+ [ 122 ],
+ ];
+ }
+
+ /**
+ * @dataProvider provideNumberOfRows
+ * @covers UserArrayFromResult::count
+ */
+ public function testCountWithVaryingValues( $numRows ) {
+ $object = new UserArrayFromResult( $this->getMockResultWrapper(
+ $this->getRowWithUsername(),
+ $numRows
+ ) );
+ $this->assertEquals( $numRows, $object->count() );
+ }
+
+ /**
+ * @covers UserArrayFromResult::current
+ */
+ public function testCurrentAfterConstruction() {
+ $username = 'addshore';
+ $userRow = $this->getRowWithUsername( $username );
+ $object = new UserArrayFromResult( $this->getMockResultWrapper( $userRow ) );
+ $this->assertInstanceOf( User::class, $object->current() );
+ $this->assertEquals( $username, $object->current()->mName );
+ }
+
+ public function provideTestValid() {
+ return [
+ [ $this->getRowWithUsername(), true ],
+ [ false, false ],
+ ];
+ }
+
+ /**
+ * @dataProvider provideTestValid
+ * @covers UserArrayFromResult::valid
+ */
+ public function testValid( $input, $expected ) {
+ $object = new UserArrayFromResult( $this->getMockResultWrapper( $input ) );
+ $this->assertEquals( $expected, $object->valid() );
+ }
+
+ // @todo unit test for key()
+ // @todo unit test for next()
+ // @todo unit test for rewind()
+}
--- /dev/null
+<?php
+
+use MediaWiki\User\UserIdentityValue;
+
+/**
+ * @author Addshore
+ *
+ * @covers NoWriteWatchedItemStore
+ */
+class NoWriteWatchedItemStoreUnitTest extends \MediaWikiUnitTestCase {
+
+ public function testAddWatch() {
+ /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */
+ $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class );
+ $innerService->expects( $this->never() )->method( 'addWatch' );
+ $noWriteService = new NoWriteWatchedItemStore( $innerService );
+
+ $this->setExpectedException( DBReadOnlyError::class );
+ $noWriteService->addWatch(
+ new UserIdentityValue( 1, 'MockUser', 0 ), new TitleValue( 0, 'Foo' ) );
+ }
+
+ public function testAddWatchBatchForUser() {
+ /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */
+ $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class );
+ $innerService->expects( $this->never() )->method( 'addWatchBatchForUser' );
+ $noWriteService = new NoWriteWatchedItemStore( $innerService );
+
+ $this->setExpectedException( DBReadOnlyError::class );
+ $noWriteService->addWatchBatchForUser( new UserIdentityValue( 1, 'MockUser', 0 ), [] );
+ }
+
+ public function testRemoveWatch() {
+ /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */
+ $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class );
+ $innerService->expects( $this->never() )->method( 'removeWatch' );
+ $noWriteService = new NoWriteWatchedItemStore( $innerService );
+
+ $this->setExpectedException( DBReadOnlyError::class );
+ $noWriteService->removeWatch(
+ new UserIdentityValue( 1, 'MockUser', 0 ), new TitleValue( 0, 'Foo' ) );
+ }
+
+ public function testSetNotificationTimestampsForUser() {
+ /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */
+ $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class );
+ $innerService->expects( $this->never() )->method( 'setNotificationTimestampsForUser' );
+ $noWriteService = new NoWriteWatchedItemStore( $innerService );
+
+ $this->setExpectedException( DBReadOnlyError::class );
+ $noWriteService->setNotificationTimestampsForUser(
+ new UserIdentityValue( 1, 'MockUser', 0 ),
+ 'timestamp',
+ []
+ );
+ }
+
+ public function testUpdateNotificationTimestamp() {
+ /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */
+ $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class );
+ $innerService->expects( $this->never() )->method( 'updateNotificationTimestamp' );
+ $noWriteService = new NoWriteWatchedItemStore( $innerService );
+
+ $this->setExpectedException( DBReadOnlyError::class );
+ $noWriteService->updateNotificationTimestamp(
+ new UserIdentityValue( 1, 'MockUser', 0 ),
+ new TitleValue( 0, 'Foo' ),
+ 'timestamp'
+ );
+ }
+
+ public function testResetNotificationTimestamp() {
+ /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */
+ $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class );
+ $innerService->expects( $this->never() )->method( 'resetNotificationTimestamp' );
+ $noWriteService = new NoWriteWatchedItemStore( $innerService );
+
+ $this->setExpectedException( DBReadOnlyError::class );
+ $noWriteService->resetNotificationTimestamp(
+ new UserIdentityValue( 1, 'MockUser', 0 ),
+ new TitleValue( 0, 'Foo' )
+ );
+ }
+
+ public function testCountWatchedItems() {
+ /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */
+ $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class );
+ $innerService->expects( $this->once() )->method( 'countWatchedItems' )->willReturn( __METHOD__ );
+ $noWriteService = new NoWriteWatchedItemStore( $innerService );
+
+ $return = $noWriteService->countWatchedItems(
+ new UserIdentityValue( 1, 'MockUser', 0 )
+ );
+ $this->assertEquals( __METHOD__, $return );
+ }
+
+ public function testCountWatchers() {
+ /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */
+ $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class );
+ $innerService->expects( $this->once() )->method( 'countWatchers' )->willReturn( __METHOD__ );
+ $noWriteService = new NoWriteWatchedItemStore( $innerService );
+
+ $return = $noWriteService->countWatchers(
+ new TitleValue( 0, 'Foo' )
+ );
+ $this->assertEquals( __METHOD__, $return );
+ }
+
+ public function testCountVisitingWatchers() {
+ /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */
+ $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class );
+ $innerService->expects( $this->once() )
+ ->method( 'countVisitingWatchers' )
+ ->willReturn( __METHOD__ );
+ $noWriteService = new NoWriteWatchedItemStore( $innerService );
+
+ $return = $noWriteService->countVisitingWatchers(
+ new TitleValue( 0, 'Foo' ),
+ 9
+ );
+ $this->assertEquals( __METHOD__, $return );
+ }
+
+ public function testCountWatchersMultiple() {
+ /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */
+ $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class );
+ $innerService->expects( $this->once() )
+ ->method( 'countVisitingWatchersMultiple' )
+ ->willReturn( __METHOD__ );
+ $noWriteService = new NoWriteWatchedItemStore( $innerService );
+
+ $return = $noWriteService->countWatchersMultiple(
+ [ new TitleValue( 0, 'Foo' ) ],
+ []
+ );
+ $this->assertEquals( __METHOD__, $return );
+ }
+
+ public function testCountVisitingWatchersMultiple() {
+ /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */
+ $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class );
+ $innerService->expects( $this->once() )
+ ->method( 'countVisitingWatchersMultiple' )
+ ->willReturn( __METHOD__ );
+ $noWriteService = new NoWriteWatchedItemStore( $innerService );
+
+ $return = $noWriteService->countVisitingWatchersMultiple(
+ [ [ new TitleValue( 0, 'Foo' ), 99 ] ],
+ 11
+ );
+ $this->assertEquals( __METHOD__, $return );
+ }
+
+ public function testGetWatchedItem() {
+ /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */
+ $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class );
+ $innerService->expects( $this->once() )->method( 'getWatchedItem' )->willReturn( __METHOD__ );
+ $noWriteService = new NoWriteWatchedItemStore( $innerService );
+
+ $return = $noWriteService->getWatchedItem(
+ new UserIdentityValue( 1, 'MockUser', 0 ),
+ new TitleValue( 0, 'Foo' )
+ );
+ $this->assertEquals( __METHOD__, $return );
+ }
+
+ public function testLoadWatchedItem() {
+ /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */
+ $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class );
+ $innerService->expects( $this->once() )->method( 'loadWatchedItem' )->willReturn( __METHOD__ );
+ $noWriteService = new NoWriteWatchedItemStore( $innerService );
+
+ $return = $noWriteService->loadWatchedItem(
+ new UserIdentityValue( 1, 'MockUser', 0 ),
+ new TitleValue( 0, 'Foo' )
+ );
+ $this->assertEquals( __METHOD__, $return );
+ }
+
+ public function testGetWatchedItemsForUser() {
+ /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */
+ $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class );
+ $innerService->expects( $this->once() )
+ ->method( 'getWatchedItemsForUser' )
+ ->willReturn( __METHOD__ );
+ $noWriteService = new NoWriteWatchedItemStore( $innerService );
+
+ $return = $noWriteService->getWatchedItemsForUser(
+ new UserIdentityValue( 1, 'MockUser', 0 ),
+ []
+ );
+ $this->assertEquals( __METHOD__, $return );
+ }
+
+ public function testIsWatched() {
+ /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */
+ $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class );
+ $innerService->expects( $this->once() )->method( 'isWatched' )->willReturn( __METHOD__ );
+ $noWriteService = new NoWriteWatchedItemStore( $innerService );
+
+ $return = $noWriteService->isWatched(
+ new UserIdentityValue( 1, 'MockUser', 0 ),
+ new TitleValue( 0, 'Foo' )
+ );
+ $this->assertEquals( __METHOD__, $return );
+ }
+
+ public function testGetNotificationTimestampsBatch() {
+ /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */
+ $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class );
+ $innerService->expects( $this->once() )
+ ->method( 'getNotificationTimestampsBatch' )
+ ->willReturn( __METHOD__ );
+ $noWriteService = new NoWriteWatchedItemStore( $innerService );
+
+ $return = $noWriteService->getNotificationTimestampsBatch(
+ new UserIdentityValue( 1, 'MockUser', 0 ),
+ [ new TitleValue( 0, 'Foo' ) ]
+ );
+ $this->assertEquals( __METHOD__, $return );
+ }
+
+ public function testCountUnreadNotifications() {
+ /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */
+ $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class );
+ $innerService->expects( $this->once() )
+ ->method( 'countUnreadNotifications' )
+ ->willReturn( __METHOD__ );
+ $noWriteService = new NoWriteWatchedItemStore( $innerService );
+
+ $return = $noWriteService->countUnreadNotifications(
+ new UserIdentityValue( 1, 'MockUser', 0 ),
+ 88
+ );
+ $this->assertEquals( __METHOD__, $return );
+ }
+
+ public function testDuplicateAllAssociatedEntries() {
+ /** @var WatchedItemStoreInterface|PHPUnit_Framework_MockObject_MockObject $innerService */
+ $innerService = $this->getMockForAbstractClass( WatchedItemStoreInterface::class );
+ $noWriteService = new NoWriteWatchedItemStore( $innerService );
+
+ $this->setExpectedException( DBReadOnlyError::class );
+ $noWriteService->duplicateAllAssociatedEntries(
+ new TitleValue( 0, 'Foo' ),
+ new TitleValue( 0, 'Bar' )
+ );
+ }
+
+}