the browser-facing portion of osu!
at master 8.1 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\Models; 9 10use App\Libraries\Session\Store; 11use App\Models\OAuth\Token; 12use App\Models\User; 13use App\Models\UsernameChangeHistory; 14use Carbon\CarbonImmutable; 15use Database\Factories\OAuth\RefreshTokenFactory; 16use Tests\TestCase; 17 18class UserTest extends TestCase 19{ 20 public static function dataProviderForAttributeTwitter(): array 21 { 22 return [ 23 ['@hello', 'hello'], 24 ['hello', 'hello'], 25 ['@', null], 26 ['', null], 27 [null, null], 28 ]; 29 } 30 31 public static function dataProviderForUsernameChangeCost() 32 { 33 return [ 34 [0, 0], 35 [1, 8], 36 [2, 16], 37 [3, 32], 38 [4, 64], 39 [5, 100], 40 [6, 100], 41 [10, 100], 42 ]; 43 } 44 45 public static function dataProviderForUsernameChangeCostType() 46 { 47 return [ 48 ['admin', 0], 49 ['inactive', 0], 50 ['paid', 8], 51 ['revert', 0], 52 ['support', 8], 53 ]; 54 } 55 56 public static function dataProviderForUsernameChangeCostWindow() 57 { 58 // years with no name changes, cost 59 // test is setup with name change every 6 months. 60 return [ 61 [0, 100], 62 [1, 32], 63 [2, 8], 64 [3, 8], 65 [4, 8], 66 ]; 67 } 68 69 public static function dataProviderValidDiscordUsername(): array 70 { 71 return [ 72 ['username', true], 73 ['user_name', true], 74 ['user.name', true], 75 ['user2name', true], 76 ['u_sernam.e1337', true], 77 ['username#', false], 78 ['u', false], 79 ['morethan32characterinthisusername', false], // 33 characters 80 81 // old format 82 ['username#1337', true], 83 ['ユーザー名#1337', true], 84 ['username#1', false], 85 ['username#13bb', false], 86 ['username#abcd', false], 87 ['user@name#1337', false], 88 ['user#name#1337', false], 89 ['user:name#1337', false], 90 ]; 91 } 92 93 /** 94 * @dataProvider dataProviderForAttributeTwitter 95 */ 96 public function testAttributeTwitter($setValue, $getValue) 97 { 98 $user = new User(['user_twitter' => $setValue]); 99 100 $this->assertSame($getValue, $user->user_twitter); 101 } 102 103 public function testEmailLoginDisabled() 104 { 105 config_set('osu.user.allow_email_login', false); 106 User::factory()->create([ 107 'username' => 'test', 108 'user_email' => 'test@example.org', 109 ]); 110 111 $this->assertNull(User::findForLogin('test@example.org')); 112 } 113 114 public function testEmailLoginEnabled() 115 { 116 config_set('osu.user.allow_email_login', true); 117 $user = User::factory()->create([ 118 'username' => 'test', 119 'user_email' => 'test@example.org', 120 ]); 121 122 $this->assertTrue($user->is(User::findForLogin('test@example.org'))); 123 } 124 125 public function testResetSessions(): void 126 { 127 $user = User::factory()->create(); 128 129 // create session 130 $this->post(route('login'), ['username' => $user->username, 'password' => User::factory()::DEFAULT_PASSWORD]); 131 // sanity check 132 $this->assertNotEmpty(Store::ids($user->getKey())); 133 134 // create token 135 $token = Token::factory()->create(['user_id' => $user, 'revoked' => false]); 136 $refreshToken = (new RefreshTokenFactory())->create(['access_token_id' => $token, 'revoked' => false]); 137 138 $user->resetSessions(); 139 140 $this->assertEmpty(Store::ids($user->getKey())); 141 $this->assertTrue($token->fresh()->revoked); 142 $this->assertTrue($refreshToken->fresh()->revoked); 143 } 144 145 public function testUsernameAvailableAtForDefaultGroup() 146 { 147 config_set('osu.user.allowed_rename_groups', ['default']); 148 $allowedAtUpTo = now()->addYears(5); 149 $user = User::factory()->withGroup('default')->create(); 150 151 $this->assertLessThanOrEqual($allowedAtUpTo, $user->getUsernameAvailableAt()); 152 } 153 154 public function testUsernameAvailableAtForNonDefaultGroup() 155 { 156 config_set('osu.user.allowed_rename_groups', ['default']); 157 $allowedAt = now()->addYears(10); 158 $user = User::factory()->withGroup('gmt')->create(['group_id' => app('groups')->byIdentifier('default')]); 159 160 $this->assertGreaterThanOrEqual($allowedAt, $user->getUsernameAvailableAt()); 161 } 162 163 /** 164 * @dataProvider dataProviderForUsernameChangeCost 165 */ 166 public function testUsernameChangeCost(int $changes, int $cost) 167 { 168 $user = User::factory() 169 ->has(UsernameChangeHistory::factory()->count($changes)) 170 ->create(); 171 172 $this->assertSame($cost, $user->usernameChangeCost()); 173 } 174 175 public function testUsernameChangeCostMultiple() 176 { 177 $user = User::factory()->create(); 178 179 $this->assertSame(0, $user->usernameChangeCost()); 180 181 $user->usernameChangeHistory()->create([ 182 'timestamp' => CarbonImmutable::now(), 183 'type' => 'paid', 184 'username' => 'marty', 185 ]); 186 187 // 1 change in last 3 years 188 $this->travelTo(CarbonImmutable::now()->addYears(3)); 189 $this->assertSame(8, $user->usernameChangeCost()); 190 191 // 0 changes in last 3 years 192 $this->travelTo(CarbonImmutable::now()->addYears(1)); 193 $this->assertSame(8, $user->usernameChangeCost()); 194 195 $user->usernameChangeHistory()->create([ 196 'timestamp' => CarbonImmutable::now(), 197 'type' => 'paid', 198 'username' => 'mcfly', 199 ]); 200 201 // 1 change in last 3 years 202 $this->assertSame(8, $user->usernameChangeCost()); 203 204 $user->usernameChangeHistory()->create([ 205 'timestamp' => CarbonImmutable::now(), 206 'type' => 'paid', 207 'username' => 'futuremarty', 208 ]); 209 210 // 2 changes in last 3 years 211 $this->assertSame(16, $user->usernameChangeCost()); 212 213 // 1 changes in last 3 years 214 $this->travelTo(CarbonImmutable::now()->addYears(3)); 215 $this->assertSame(8, $user->usernameChangeCost()); 216 // 0 changes in last 3 years 217 $this->travelTo(CarbonImmutable::now()->addYears(1)); 218 $this->assertSame(8, $user->usernameChangeCost()); 219 } 220 221 /** 222 * @dataProvider dataProviderForUsernameChangeCostType 223 */ 224 public function testUsernameChangeCostType(string $type, int $cost) 225 { 226 $user = User::factory() 227 ->has(UsernameChangeHistory::factory()->state(['type' => $type])) 228 ->create(); 229 230 $this->assertSame($cost, $user->usernameChangeCost()); 231 } 232 233 /** 234 * @dataProvider dataProviderForUsernameChangeCostWindow 235 */ 236 public function testUsernameChangeCostWindow(int $years, int $cost) 237 { 238 $now = CarbonImmutable::now(); 239 $this->travelTo(CarbonImmutable::now()->subYears(3)); 240 241 $user = User::factory()->create(); 242 while (CarbonImmutable::now()->isBefore($now)) { 243 $user->usernameChangeHistory()->create([ 244 'timestamp' => CarbonImmutable::now(), 245 'type' => 'paid', 246 'username' => 'marty', 247 ]); 248 249 $this->travelTo(CarbonImmutable::now()->addMonths(6)); 250 } 251 252 $this->travelTo($now->addYears($years)); 253 $this->assertSame($cost, $user->usernameChangeCost()); 254 } 255 256 /** 257 * @dataProvider dataProviderValidDiscordUsername 258 */ 259 public function testValidDiscordUsername(string $username, bool $valid) 260 { 261 $user = User::factory()->make(); 262 $user->user_discord = $username; 263 264 $this->assertSame($valid, $user->isValid()); 265 266 if (!$valid) { 267 $this->assertArrayHasKey('user_discord', $user->validationErrors()->all()); 268 } 269 } 270}