summaryrefslogtreecommitdiff
path: root/tests/phpunit/includes/UserTest.php
diff options
context:
space:
mode:
Diffstat (limited to 'tests/phpunit/includes/UserTest.php')
-rw-r--r--tests/phpunit/includes/UserTest.php369
1 files changed, 369 insertions, 0 deletions
diff --git a/tests/phpunit/includes/UserTest.php b/tests/phpunit/includes/UserTest.php
new file mode 100644
index 00000000..af95a721
--- /dev/null
+++ b/tests/phpunit/includes/UserTest.php
@@ -0,0 +1,369 @@
+<?php
+
+define( 'NS_UNITTEST', 5600 );
+define( 'NS_UNITTEST_TALK', 5601 );
+
+/**
+ * @group Database
+ */
+class UserTest extends MediaWikiTestCase {
+ /**
+ * @var User
+ */
+ protected $user;
+
+ protected function setUp() {
+ parent::setUp();
+
+ $this->setMwGlobals( array(
+ 'wgGroupPermissions' => array(),
+ 'wgRevokePermissions' => array(),
+ ) );
+
+ $this->setUpPermissionGlobals();
+
+ $this->user = new User;
+ $this->user->addGroup( 'unittesters' );
+ }
+
+ private function setUpPermissionGlobals() {
+ global $wgGroupPermissions, $wgRevokePermissions;
+
+ # Data for regular $wgGroupPermissions test
+ $wgGroupPermissions['unittesters'] = array(
+ 'test' => true,
+ 'runtest' => true,
+ 'writetest' => false,
+ 'nukeworld' => false,
+ );
+ $wgGroupPermissions['testwriters'] = array(
+ 'test' => true,
+ 'writetest' => true,
+ 'modifytest' => true,
+ );
+
+ # Data for regular $wgRevokePermissions test
+ $wgRevokePermissions['formertesters'] = array(
+ 'runtest' => true,
+ );
+
+ # For the options test
+ $wgGroupPermissions['*'] = array(
+ 'editmyoptions' => true,
+ );
+ }
+
+ /**
+ * @covers User::getGroupPermissions
+ */
+ public function testGroupPermissions() {
+ $rights = User::getGroupPermissions( array( 'unittesters' ) );
+ $this->assertContains( 'runtest', $rights );
+ $this->assertNotContains( 'writetest', $rights );
+ $this->assertNotContains( 'modifytest', $rights );
+ $this->assertNotContains( 'nukeworld', $rights );
+
+ $rights = User::getGroupPermissions( array( 'unittesters', 'testwriters' ) );
+ $this->assertContains( 'runtest', $rights );
+ $this->assertContains( 'writetest', $rights );
+ $this->assertContains( 'modifytest', $rights );
+ $this->assertNotContains( 'nukeworld', $rights );
+ }
+
+ /**
+ * @covers User::getGroupPermissions
+ */
+ public function testRevokePermissions() {
+ $rights = User::getGroupPermissions( array( 'unittesters', 'formertesters' ) );
+ $this->assertNotContains( 'runtest', $rights );
+ $this->assertNotContains( 'writetest', $rights );
+ $this->assertNotContains( 'modifytest', $rights );
+ $this->assertNotContains( 'nukeworld', $rights );
+ }
+
+ /**
+ * @covers User::getRights
+ */
+ public function testUserPermissions() {
+ $rights = $this->user->getRights();
+ $this->assertContains( 'runtest', $rights );
+ $this->assertNotContains( 'writetest', $rights );
+ $this->assertNotContains( 'modifytest', $rights );
+ $this->assertNotContains( 'nukeworld', $rights );
+ }
+
+ /**
+ * @dataProvider provideGetGroupsWithPermission
+ * @covers User::getGroupsWithPermission
+ */
+ public function testGetGroupsWithPermission( $expected, $right ) {
+ $result = User::getGroupsWithPermission( $right );
+ sort( $result );
+ sort( $expected );
+
+ $this->assertEquals( $expected, $result, "Groups with permission $right" );
+ }
+
+ public static function provideGetGroupsWithPermission() {
+ return array(
+ array(
+ array( 'unittesters', 'testwriters' ),
+ 'test'
+ ),
+ array(
+ array( 'unittesters' ),
+ 'runtest'
+ ),
+ array(
+ array( 'testwriters' ),
+ 'writetest'
+ ),
+ array(
+ array( 'testwriters' ),
+ 'modifytest'
+ ),
+ );
+ }
+
+ /**
+ * @dataProvider provideIPs
+ * @covers User::isIP
+ */
+ public function testIsIP( $value, $result, $message ) {
+ $this->assertEquals( $this->user->isIP( $value ), $result, $message );
+ }
+
+ public static function provideIPs() {
+ return array(
+ array( '', false, 'Empty string' ),
+ array( ' ', false, 'Blank space' ),
+ array( '10.0.0.0', true, 'IPv4 private 10/8' ),
+ array( '10.255.255.255', true, 'IPv4 private 10/8' ),
+ array( '192.168.1.1', true, 'IPv4 private 192.168/16' ),
+ array( '203.0.113.0', true, 'IPv4 example' ),
+ array( '2002:ffff:ffff:ffff:ffff:ffff:ffff:ffff', true, 'IPv6 example' ),
+ // Not valid IPs but classified as such by MediaWiki for negated asserting
+ // of whether this might be the identifier of a logged-out user or whether
+ // to allow usernames like it.
+ array( '300.300.300.300', true, 'Looks too much like an IPv4 address' ),
+ array( '203.0.113.xxx', true, 'Assigned by UseMod to cloaked logged-out users' ),
+ );
+ }
+
+ /**
+ * @dataProvider provideUserNames
+ * @covers User::isValidUserName
+ */
+ public function testIsValidUserName( $username, $result, $message ) {
+ $this->assertEquals( $this->user->isValidUserName( $username ), $result, $message );
+ }
+
+ public static function provideUserNames() {
+ return array(
+ array( '', false, 'Empty string' ),
+ array( ' ', false, 'Blank space' ),
+ array( 'abcd', false, 'Starts with small letter' ),
+ array( 'Ab/cd', false, 'Contains slash' ),
+ array( 'Ab cd', true, 'Whitespace' ),
+ array( '192.168.1.1', false, 'IP' ),
+ array( 'User:Abcd', false, 'Reserved Namespace' ),
+ array( '12abcd232', true, 'Starts with Numbers' ),
+ array( '?abcd', true, 'Start with ? mark' ),
+ array( '#abcd', false, 'Start with #' ),
+ array( 'Abcdകഖഗഘ', true, ' Mixed scripts' ),
+ array( 'ജോസ്‌തോമസ്', false, 'ZWNJ- Format control character' ),
+ array( 'Ab cd', false, ' Ideographic space' ),
+ array( '300.300.300.300', false, 'Looks too much like an IPv4 address' ),
+ array( '302.113.311.900', false, 'Looks too much like an IPv4 address' ),
+ array( '203.0.113.xxx', false, 'Reserved for usage by UseMod for cloaked logged-out users' ),
+ );
+ }
+
+ /**
+ * Test, if for all rights a right- message exist,
+ * which is used on Special:ListGroupRights as help text
+ * Extensions and core
+ */
+ public function testAllRightsWithMessage() {
+ // Getting all user rights, for core: User::$mCoreRights, for extensions: $wgAvailableRights
+ $allRights = User::getAllRights();
+ $allMessageKeys = Language::getMessageKeysFor( 'en' );
+
+ $rightsWithMessage = array();
+ foreach ( $allMessageKeys as $message ) {
+ // === 0: must be at beginning of string (position 0)
+ if ( strpos( $message, 'right-' ) === 0 ) {
+ $rightsWithMessage[] = substr( $message, strlen( 'right-' ) );
+ }
+ }
+
+ sort( $allRights );
+ sort( $rightsWithMessage );
+
+ $this->assertEquals(
+ $allRights,
+ $rightsWithMessage,
+ 'Each user rights (core/extensions) has a corresponding right- message.'
+ );
+ }
+
+ /**
+ * Test User::editCount
+ * @group medium
+ * @covers User::getEditCount
+ */
+ public function testEditCount() {
+ $user = User::newFromName( 'UnitTestUser' );
+ $user->loadDefaults();
+ $user->addToDatabase();
+
+ // let the user have a few (3) edits
+ $page = WikiPage::factory( Title::newFromText( 'Help:UserTest_EditCount' ) );
+ for ( $i = 0; $i < 3; $i++ ) {
+ $page->doEdit( (string)$i, 'test', 0, false, $user );
+ }
+
+ $user->clearInstanceCache();
+ $this->assertEquals(
+ 3,
+ $user->getEditCount(),
+ 'After three edits, the user edit count should be 3'
+ );
+
+ // increase the edit count and clear the cache
+ $user->incEditCount();
+
+ $user->clearInstanceCache();
+ $this->assertEquals(
+ 4,
+ $user->getEditCount(),
+ 'After increasing the edit count manually, the user edit count should be 4'
+ );
+ }
+
+ /**
+ * Test changing user options.
+ * @covers User::setOption
+ * @covers User::getOption
+ */
+ public function testOptions() {
+ $user = User::newFromName( 'UnitTestUser' );
+ $user->addToDatabase();
+
+ $user->setOption( 'someoption', 'test' );
+ $user->setOption( 'cols', 200 );
+ $user->saveSettings();
+
+ $user = User::newFromName( 'UnitTestUser' );
+ $this->assertEquals( 'test', $user->getOption( 'someoption' ) );
+ $this->assertEquals( 200, $user->getOption( 'cols' ) );
+ }
+
+ /**
+ * Bug 37963
+ * Make sure defaults are loaded when setOption is called.
+ * @covers User::loadOptions
+ */
+ public function testAnonOptions() {
+ global $wgDefaultUserOptions;
+ $this->user->setOption( 'someoption', 'test' );
+ $this->assertEquals( $wgDefaultUserOptions['cols'], $this->user->getOption( 'cols' ) );
+ $this->assertEquals( 'test', $this->user->getOption( 'someoption' ) );
+ }
+
+ /**
+ * Test password expiration.
+ * @covers User::getPasswordExpired()
+ */
+ public function testPasswordExpire() {
+ global $wgPasswordExpireGrace;
+ $wgTemp = $wgPasswordExpireGrace;
+ $wgPasswordExpireGrace = 3600 * 24 * 7; // 7 days
+
+ $user = User::newFromName( 'UnitTestUser' );
+ $user->loadDefaults();
+ $this->assertEquals( false, $user->getPasswordExpired() );
+
+ $ts = time() - ( 3600 * 24 * 1 ); // 1 day ago
+ $user->expirePassword( $ts );
+ $this->assertEquals( 'soft', $user->getPasswordExpired() );
+
+ $ts = time() - ( 3600 * 24 * 10 ); // 10 days ago
+ $user->expirePassword( $ts );
+ $this->assertEquals( 'hard', $user->getPasswordExpired() );
+
+ $wgPasswordExpireGrace = $wgTemp;
+ }
+
+ /**
+ * Test password validity checks. There are 3 checks in core,
+ * - ensure the password meets the minimal length
+ * - ensure the password is not the same as the username
+ * - ensure the username/password combo isn't forbidden
+ * @covers User::checkPasswordValidity()
+ * @covers User::getPasswordValidity()
+ * @covers User::isValidPassword()
+ */
+ public function testCheckPasswordValidity() {
+ $this->setMwGlobals( array(
+ 'wgMinimalPasswordLength' => 6,
+ 'wgMaximalPasswordLength' => 30,
+ ) );
+ $user = User::newFromName( 'Useruser' );
+ // Sanity
+ $this->assertTrue( $user->isValidPassword( 'Password1234' ) );
+
+ // Minimum length
+ $this->assertFalse( $user->isValidPassword( 'a' ) );
+ $this->assertFalse( $user->checkPasswordValidity( 'a' )->isGood() );
+ $this->assertTrue( $user->checkPasswordValidity( 'a' )->isOK() );
+ $this->assertEquals( 'passwordtooshort', $user->getPasswordValidity( 'a' ) );
+
+ // Maximum length
+ $longPass = str_repeat( 'a', 31 );
+ $this->assertFalse( $user->isValidPassword( $longPass ) );
+ $this->assertFalse( $user->checkPasswordValidity( $longPass )->isGood() );
+ $this->assertFalse( $user->checkPasswordValidity( $longPass )->isOK() );
+ $this->assertEquals( 'passwordtoolong', $user->getPasswordValidity( $longPass ) );
+
+ // Matches username
+ $this->assertFalse( $user->checkPasswordValidity( 'Useruser' )->isGood() );
+ $this->assertTrue( $user->checkPasswordValidity( 'Useruser' )->isOK() );
+ $this->assertEquals( 'password-name-match', $user->getPasswordValidity( 'Useruser' ) );
+
+ // On the forbidden list
+ $this->assertFalse( $user->checkPasswordValidity( 'Passpass' )->isGood() );
+ $this->assertEquals( 'password-login-forbidden', $user->getPasswordValidity( 'Passpass' ) );
+ }
+
+ /**
+ * @covers User::getCanonicalName()
+ * @dataProvider provideGetCanonicalName
+ */
+ public function testGetCanonicalName( $name, $expectedArray, $msg ) {
+ foreach ( $expectedArray as $validate => $expected ) {
+ $this->assertEquals(
+ User::getCanonicalName( $name, $validate === 'false' ? false : $validate ),
+ $expected,
+ $msg . ' (' . $validate . ')'
+ );
+ }
+ }
+
+ public static function provideGetCanonicalName() {
+ return array(
+ array( ' trailing space ', array( 'creatable' => 'Trailing space' ), 'Trailing spaces' ),
+ // @todo FIXME: Maybe the createable name should be 'Talk:Username' or false to reject?
+ array( 'Talk:Username', array( 'creatable' => 'Username', 'usable' => 'Username',
+ 'valid' => 'Username', 'false' => 'Talk:Username' ), 'Namespace prefix' ),
+ array( ' name with # hash', array( 'creatable' => false, 'usable' => false ), 'With hash' ),
+ array( 'Multi spaces', array( 'creatable' => 'Multi spaces',
+ 'usable' => 'Multi spaces' ), 'Multi spaces' ),
+ array( 'lowercase', array( 'creatable' => 'Lowercase' ), 'Lowercase' ),
+ array( 'in[]valid', array( 'creatable' => false, 'usable' => false, 'valid' => false,
+ 'false' => 'In[]valid' ), 'Invalid' ),
+ array( 'with / slash', array( 'creatable' => false, 'usable' => false, 'valid' => false,
+ 'false' => 'With / slash' ), 'With slash' ),
+ );
+ }
+}