the browser-facing portion of osu!
at master 7.7 kB view raw
1<?php 2 3// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the GNU Affero General Public License v3.0. 4// See the LICENCE file in the repository root for full licence text. 5 6declare(strict_types=1); 7 8namespace Tests\Libraries; 9 10use App\Libraries\UsernameValidation; 11use App\Models\Beatmap; 12use App\Models\Beatmapset; 13use App\Models\RankHighest; 14use App\Models\User; 15use Carbon\Carbon; 16use Tests\TestCase; 17 18class UsernameValidationTest extends TestCase 19{ 20 public function testValidateAvailabilityWhenNotInUse(): void 21 { 22 $this->assertTrue(UsernameValidation::validateAvailability('Free username')->isEmpty()); 23 } 24 25 public function testValidateAvailabilityWithActiveUser(): void 26 { 27 $user = User::factory()->create(['user_lastvisit' => Carbon::now()]); 28 29 $this->assertFalse(UsernameValidation::validateAvailability($user->username)->isEmpty()); 30 } 31 32 public function testValidateAvailabilityWithInactiveUser(): void 33 { 34 $user = User::factory()->create(['user_lastvisit' => Carbon::now()->subDecade()]); 35 36 $this->assertTrue(UsernameValidation::validateAvailability($user->username)->isEmpty()); 37 } 38 39 public function testValidateAvailabilityWithRecentlyUsedUsername(): void 40 { 41 User 42 ::factory() 43 ->create([ 44 'user_lastvisit' => Carbon::now(), 45 'username' => 'New username', 46 'username_clean' => 'new username', 47 ]) 48 ->usernameChangeHistory() 49 ->create([ 50 'timestamp' => Carbon::now(), 51 'username' => 'New username', 52 'username_last' => 'Old username', 53 ]); 54 55 $this->assertFalse(UsernameValidation::validateAvailability('Old username')->isEmpty()); 56 } 57 58 /** 59 * @dataProvider usernameValidationDataProvider 60 */ 61 public function testValidateUsername(string $username, bool $expectValid): void 62 { 63 $this->assertSame( 64 $expectValid, 65 UsernameValidation::validateUsername($username)->isEmpty(), 66 ); 67 } 68 69 /** 70 * @dataProvider usersOfUsernameLookupDataProvider 71 */ 72 public function testValidateUsersOfUsername( 73 bool $throughUsernameHistory, 74 bool $underscoresReplaced, 75 bool $expectLookupSuccess, 76 ): void { 77 $username = 'username_1'; 78 $user = User::factory()->create([ 79 'username' => $username, 80 'username_clean' => $username, 81 ]); 82 83 if ($throughUsernameHistory) { 84 $username = "Old_{$username}"; 85 $user->usernameChangeHistory()->create([ 86 'username' => $user->username, 87 'username_last' => $username, 88 ]); 89 } 90 91 if ($underscoresReplaced) { 92 $username = str_replace('_', ' ', $username); 93 } 94 95 // Make the user fail at least one of the checks 96 RankHighest::factory()->create([ 97 'rank' => 100, 98 'user_id' => $user, 99 ]); 100 101 // The validation should succeed only if the lookup does not 102 $this->assertNotSame( 103 $expectLookupSuccess, 104 UsernameValidation::validateUsersOfUsername($username)->isEmpty(), 105 ); 106 } 107 108 public function testValidateUsersOfUsernameFormerlyAlmostTopRanked(): void 109 { 110 $user = User 111 ::factory() 112 ->has(RankHighest::factory()->state(['rank' => 101])) 113 ->create(); 114 115 $this->assertTrue(UsernameValidation::validateUsersOfUsername($user->username)->isEmpty()); 116 } 117 118 public function testValidateUsersOfUsernameFormerlyTopRanked(): void 119 { 120 $user = User 121 ::factory() 122 ->has(RankHighest::factory()->state(['rank' => 100])) 123 ->create(); 124 125 $this->assertFalse(UsernameValidation::validateUsersOfUsername($user->username)->isEmpty()); 126 } 127 128 public function testValidateUsersOfUsernameHasBadges(): void 129 { 130 $user = User::factory()->create(); 131 132 $user->badges()->create([ 133 'description' => '', 134 'image' => '', 135 ]); 136 137 $this->assertFalse(UsernameValidation::validateUsersOfUsername($user->username)->isEmpty()); 138 } 139 140 /** 141 * @dataProvider usernameAvailabilityWithBeatmapStateDataProvider 142 */ 143 public function testValidateUsersOfUsernameHasBeatmapsets(string $state, bool $expectValid): void 144 { 145 $user = User 146 ::factory() 147 ->has(Beatmapset::factory()->state(['approved' => Beatmapset::STATES[$state]])) 148 ->create(); 149 150 $this->assertSame( 151 $expectValid, 152 UsernameValidation::validateUsersOfUsername($user->username)->isEmpty(), 153 ); 154 } 155 156 /** 157 * @dataProvider usernameAvailabilityWithBeatmapStateDataProvider 158 */ 159 public function testValidateUsersOfUsernameHasGuestBeatmaps(string $state, bool $expectValid): void 160 { 161 $user = User::factory()->create(); 162 163 Beatmapset 164 ::factory() 165 ->has(Beatmap::factory()->state([ 166 'approved' => Beatmapset::STATES[$state], 167 'user_id' => $user, 168 ])) 169 ->create(['approved' => Beatmapset::STATES['ranked']]); 170 171 $this->assertSame( 172 $expectValid, 173 UsernameValidation::validateUsersOfUsername($user->username)->isEmpty(), 174 ); 175 } 176 177 /** 178 * Data in order: 179 * - Beatmap or beatmapset state 180 * - Whether the username should be available 181 */ 182 public static function usernameAvailabilityWithBeatmapStateDataProvider(): array 183 { 184 return [ 185 ['graveyard', true], 186 ['wip', true], 187 ['pending', true], 188 ['ranked', false], 189 ['approved', false], 190 ['qualified', false], 191 ['loved', false], 192 ]; 193 } 194 195 /** 196 * Data in order: 197 * - Username 198 * - Whether the username should be valid 199 */ 200 public static function usernameValidationDataProvider(): array 201 { 202 return [ 203 'alphabetic' => ['Username', true], 204 'alphanumeric' => ['Username1000', true], 205 'numeric' => ['1000', true], 206 'space at beginning' => [' Username', false], 207 'space at end' => ['Username ', false], 208 'space in middle' => ['Username 1000', true], 209 'too short' => ['aa', false], 210 'shortest' => ['aaa', true], 211 'too long' => ['aaaaaaaaaaaaaaaa', false], 212 'longest' => ['aaaaaaaaaaaaaaa', true], 213 'two spaces in middle' => ['Username 1000', false], 214 'invalid special characters' => ['Usern@me', false], 215 'all valid special characters' => ['-[]_', true], 216 'mixed space and underscore' => ['Username_1 2', false], 217 ]; 218 } 219 220 /** 221 * Data in order: 222 * - Whether the user lookup should be done through username change history 223 * - Whether the user lookup should have its underscores replaced with spaces 224 * - Whether the user lookup should return the user 225 */ 226 public static function usersOfUsernameLookupDataProvider(): array 227 { 228 return [ 229 [true, true, false], 230 [true, false, true], 231 [false, true, true], 232 [false, false, true], 233 ]; 234 } 235}