Skip to content

Commit

Permalink
Merge pull request #10457 from cl8n/username-validation-test
Browse files Browse the repository at this point in the history
Improve `UsernameValidation` test coverage
  • Loading branch information
nanaya authored Aug 14, 2023
2 parents 36d197d + bf216fc commit 32f332c
Show file tree
Hide file tree
Showing 2 changed files with 154 additions and 40 deletions.
2 changes: 1 addition & 1 deletion app/Libraries/UsernameValidation.php
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ public static function validateUsersOfUsername(string $username): ValidationErro
return $errors;
}

public static function usersOfUsername(string $username): Collection
private static function usersOfUsername(string $username): Collection
{
$userIds = UsernameChangeHistory::where('username_last', $username)->pluck('user_id');
$users = User::whereIn('user_id', $userIds)->get();
Expand Down
192 changes: 153 additions & 39 deletions tests/Libraries/UsernameValidationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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],
];
}
}

0 comments on commit 32f332c

Please sign in to comment.