-
Notifications
You must be signed in to change notification settings - Fork 382
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #10457 from cl8n/username-validation-test
Improve `UsernameValidation` test coverage
- Loading branch information
Showing
2 changed files
with
154 additions
and
40 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,73 +3,187 @@ | |
// Copyright (c) ppy Pty Ltd <[email protected]>. Licensed under the GNU Affero General Public License v3.0. | ||
// See the LICENCE file in the repository root for full licence text. | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Tests\Libraries; | ||
|
||
use App\Libraries\UsernameValidation; | ||
use App\Models\Beatmapset; | ||
use App\Models\RankHighest; | ||
use App\Models\User; | ||
use Carbon\Carbon; | ||
use Tests\TestCase; | ||
|
||
// FIXME: need more tests | ||
class UsernameValidationTest extends TestCase | ||
{ | ||
public function testusersOfUsernameIncludesCurrentUsernameOwner() | ||
public function testValidateAvailabilityWhenNotInUse(): void | ||
{ | ||
$existing = User::factory()->create([ | ||
'username' => 'user1', | ||
'username_clean' => 'user1', | ||
'user_lastvisit' => Carbon::now()->subYears(), | ||
]); | ||
$this->assertTrue(UsernameValidation::validateAvailability('Free username')->isEmpty()); | ||
} | ||
|
||
public function testValidateAvailabilityWithActiveUser(): void | ||
{ | ||
$user = User::factory()->create(['user_lastvisit' => Carbon::now()]); | ||
|
||
$users = UsernameValidation::usersOfUsername('user1'); | ||
$this->assertCount(1, $users); | ||
$this->assertTrue($existing->is($users->first())); | ||
$this->assertFalse(UsernameValidation::validateAvailability($user->username)->isEmpty()); | ||
} | ||
|
||
public function testValidateUsersOfUsernameInactive() | ||
public function testValidateAvailabilityWithInactiveUser(): void | ||
{ | ||
$existing = User::factory()->create([ | ||
'username' => 'user1', | ||
'username_clean' => 'user1', | ||
'user_lastvisit' => Carbon::now()->subYears(20), | ||
]); | ||
$user = User::factory()->create(['user_lastvisit' => Carbon::now()->subDecade()]); | ||
|
||
$this->assertTrue(UsernameValidation::validateAvailability($user->username)->isEmpty()); | ||
} | ||
|
||
public function testValidateAvailabilityWithRecentlyUsedUsername(): void | ||
{ | ||
User | ||
::factory() | ||
->create([ | ||
'user_lastvisit' => Carbon::now(), | ||
'username' => 'New username', | ||
'username_clean' => 'new username', | ||
]) | ||
->usernameChangeHistory() | ||
->create([ | ||
'timestamp' => Carbon::now(), | ||
'username' => 'New username', | ||
'username_last' => 'Old username', | ||
]); | ||
|
||
$this->assertFalse(UsernameValidation::validateUsersOfUsername('user1')->isAny()); | ||
$this->assertFalse(UsernameValidation::validateAvailability('Old username')->isEmpty()); | ||
} | ||
|
||
public function testValidateUsersOfUsernameInactiveFormerTopRank() | ||
/** | ||
* @dataProvider usernameValidationDataProvider | ||
*/ | ||
public function testValidateUsername(string $username, bool $expectValid): void | ||
{ | ||
$existing = User::factory()->create([ | ||
'username' => 'user1', | ||
'username_clean' => 'user1', | ||
'user_lastvisit' => Carbon::now()->subYears(20), | ||
$this->assertSame( | ||
$expectValid, | ||
UsernameValidation::validateUsername($username)->isEmpty(), | ||
); | ||
} | ||
|
||
/** | ||
* @dataProvider usersOfUsernameLookupDataProvider | ||
*/ | ||
public function testValidateUsersOfUsername( | ||
bool $throughUsernameHistory, | ||
bool $underscoresReplaced, | ||
bool $expectLookupSuccess, | ||
): void { | ||
$username = 'username_1'; | ||
$user = User::factory()->create([ | ||
'username' => $username, | ||
'username_clean' => $username, | ||
]); | ||
|
||
if ($throughUsernameHistory) { | ||
$username = "Old_{$username}"; | ||
$user->usernameChangeHistory()->create([ | ||
'username' => $user->username, | ||
'username_last' => $username, | ||
]); | ||
} | ||
|
||
if ($underscoresReplaced) { | ||
$username = str_replace('_', ' ', $username); | ||
} | ||
|
||
// Make the user fail at least one of the checks | ||
RankHighest::factory()->create([ | ||
'user_id' => $existing, | ||
'rank' => 100, | ||
'user_id' => $user, | ||
]); | ||
|
||
$this->assertTrue(UsernameValidation::validateUsersOfUsername('user1')->isAny()); | ||
// The validation should succeed only if the lookup does not | ||
$this->assertNotSame( | ||
$expectLookupSuccess, | ||
UsernameValidation::validateUsersOfUsername($username)->isEmpty(), | ||
); | ||
} | ||
|
||
public function testValidateUsersOfUsernameRenamedTopRank() | ||
public function testValidateUsersOfUsernameFormerlyAlmostTopRanked(): void | ||
{ | ||
$existing = User::factory()->create([ | ||
'username' => 'user2', | ||
'username_clean' => 'user2', | ||
'user_lastvisit' => Carbon::now(), | ||
]); | ||
$existing->usernameChangeHistory()->make([ | ||
'timestamp' => Carbon::now()->subYears(20), | ||
'username' => 'user2', | ||
'username_last' => 'user1', | ||
])->saveOrExplode(); | ||
RankHighest::factory()->create([ | ||
'user_id' => $existing, | ||
'rank' => 100, | ||
$user = User | ||
::factory() | ||
->has(RankHighest::factory()->state(['rank' => 101])) | ||
->create(); | ||
|
||
$this->assertTrue(UsernameValidation::validateUsersOfUsername($user->username)->isEmpty()); | ||
} | ||
|
||
public function testValidateUsersOfUsernameFormerlyTopRanked(): void | ||
{ | ||
$user = User | ||
::factory() | ||
->has(RankHighest::factory()->state(['rank' => 100])) | ||
->create(); | ||
|
||
$this->assertFalse(UsernameValidation::validateUsersOfUsername($user->username)->isEmpty()); | ||
} | ||
|
||
public function testValidateUsersOfUsernameHasBadges(): void | ||
{ | ||
$user = User::factory()->create(); | ||
|
||
$user->badges()->create([ | ||
'description' => '', | ||
'image' => '', | ||
]); | ||
|
||
$this->assertTrue(UsernameValidation::validateUsersOfUsername('user1')->isAny()); | ||
$this->assertFalse(UsernameValidation::validateUsersOfUsername($user->username)->isEmpty()); | ||
} | ||
|
||
public function testValidateUsersOfUsernameHasRankedBeatmapsets(): void | ||
{ | ||
$user = User | ||
::factory() | ||
->has(Beatmapset::factory()->state(['approved' => Beatmapset::STATES['ranked']])) | ||
->create(); | ||
|
||
$this->assertFalse(UsernameValidation::validateUsersOfUsername($user->username)->isEmpty()); | ||
} | ||
|
||
/** | ||
* Data in order: | ||
* - Username | ||
* - Whether the username should be valid | ||
*/ | ||
public function usernameValidationDataProvider(): array | ||
{ | ||
return [ | ||
'alphabetic' => ['Username', true], | ||
'alphanumeric' => ['Username1000', true], | ||
'numeric' => ['1000', true], | ||
'space at beginning' => [' Username', false], | ||
'space at end' => ['Username ', false], | ||
'space in middle' => ['Username 1000', true], | ||
'too short' => ['aa', false], | ||
'shortest' => ['aaa', true], | ||
'too long' => ['aaaaaaaaaaaaaaaa', false], | ||
'longest' => ['aaaaaaaaaaaaaaa', true], | ||
'two spaces in middle' => ['Username 1000', false], | ||
'invalid special characters' => ['Usern@me', false], | ||
'all valid special characters' => ['-[]_', true], | ||
'mixed space and underscore' => ['Username_1 2', false], | ||
]; | ||
} | ||
|
||
/** | ||
* Data in order: | ||
* - Whether the user lookup should be done through username change history | ||
* - Whether the user lookup should have its underscores replaced with spaces | ||
* - Whether the user lookup should return the user | ||
*/ | ||
public function usersOfUsernameLookupDataProvider(): array | ||
{ | ||
return [ | ||
[true, true, false], | ||
[true, false, true], | ||
[false, true, true], | ||
[false, false, true], | ||
]; | ||
} | ||
} |