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
6namespace Tests\Controllers;
7
8use App\Models\Country;
9use App\Models\User;
10use Tests\TestCase;
11
12class UsersControllerTest extends TestCase
13{
14 public function testIndexForApi()
15 {
16 $user = User::factory()->create();
17 $userB = User::factory()->create();
18
19 $this->actAsScopedUser($user, ['*']);
20
21 $this
22 ->get(route('api.users.index', ['ids' => [$user->getKey(), $userB->getKey()]]))
23 ->assertSuccessful()
24 ->assertJsonPath('users.0.id', $user->getKey())
25 ->assertJsonPath('users.1.id', $userB->getKey());
26 }
27
28 /**
29 * Checks whether an OK status is returned when the
30 * profile order update request is valid.
31 */
32 public function testStore()
33 {
34 $previousCount = User::count();
35
36 $locale = array_rand_val($GLOBALS['cfg']['app']['available_locales']);
37
38 $this->expectCountChange(fn () => User::count(), 1);
39 $this
40 ->json('POST', route('users.store'), [
41 'user' => [
42 'username' => 'user1',
43 'user_email' => 'user1@example.com',
44 'password' => 'hunter22',
45 ],
46 ], [
47 'accept-language' => $locale,
48 'user-agent' => $GLOBALS['cfg']['osu']['client']['user_agent'],
49 ])->assertJsonFragment([
50 'username' => 'user1',
51 'country_code' => Country::UNKNOWN,
52 ]);
53
54 $user = User::where('username', 'user1')->first();
55 $this->assertSame($locale, $user->user_lang);
56 }
57
58 public function testStoreRegModeWebOnly()
59 {
60 config_set('osu.user.registration_mode.client', false);
61 config_set('osu.user.registration_mode.web', true);
62 $this->expectCountChange(fn () => User::count(), 0);
63
64 $this
65 ->json('POST', route('users.store'), [
66 'user' => [
67 'username' => 'user1',
68 'user_email' => 'user1@example.com',
69 'password' => 'hunter22',
70 ],
71 ], [
72 'HTTP_USER_AGENT' => $GLOBALS['cfg']['osu']['client']['user_agent'],
73 ])->assertStatus(403)
74 ->assertJsonFragment([
75 'error' => osu_trans('users.store.from_web'),
76 ]);
77 }
78
79 /**
80 * Passing check=1 only validates user and not create.
81 */
82 public function testStoreDryRunValid()
83 {
84 $previousCount = User::count();
85
86 $this
87 ->json('POST', route('users.store'), [
88 'check' => '1',
89 'user' => [
90 'username' => 'user1',
91 'user_email' => 'user1@example.com',
92 'password' => 'hunter22',
93 ],
94 ], [
95 'HTTP_USER_AGENT' => $GLOBALS['cfg']['osu']['client']['user_agent'],
96 ])->assertSuccessful();
97
98 $this->assertSame($previousCount, User::count());
99 }
100
101 /**
102 * Invalid parameter returns 422.
103 */
104 public function testStoreInvalid()
105 {
106 $previousCount = User::count();
107
108 $this
109 ->json('POST', route('users.store'), [
110 'check' => '1',
111 'user' => [
112 'username' => '',
113 'user_email' => 'user1@example.com',
114 'password' => 'hunter22',
115 ],
116 ], [
117 'HTTP_USER_AGENT' => $GLOBALS['cfg']['osu']['client']['user_agent'],
118 ])->assertStatus(422)
119 ->assertJsonFragment([
120 'form_error' => ['user' => ['username' => ['Username is required.']]],
121 ]);
122
123 $this
124 ->json('POST', route('users.store'), [
125 'user' => [
126 'username' => '',
127 'user_email' => 'user1@example.com',
128 'password' => 'hunter22',
129 ],
130 ], [
131 'HTTP_USER_AGENT' => $GLOBALS['cfg']['osu']['client']['user_agent'],
132 ])->assertStatus(422)
133 ->assertJsonFragment([
134 'form_error' => ['user' => ['username' => ['Username is required.']]],
135 ]);
136
137 $this->assertSame($previousCount, User::count());
138 }
139
140 public function testStoreWebRegModeClientOnly()
141 {
142 config_set('osu.user.registration_mode.client', true);
143 config_set('osu.user.registration_mode.web', false);
144
145 $this->expectCountChange(fn () => User::count(), 0);
146
147 $this->post(route('users.store'), [
148 'user' => [
149 'username' => 'user1',
150 'user_email' => 'user1@example.com',
151 'password' => 'hunter22',
152 ],
153 ])->assertStatus(403)
154 ->assertJsonFragment([
155 'error' => osu_trans('users.store.from_client'),
156 ]);
157 }
158
159 public function testStoreWeb(): void
160 {
161 config_set('osu.user.registration_mode.web', true);
162 $this->expectCountChange(fn () => User::count(), 1);
163
164 $this->post(route('users.store-web'), [
165 'user' => [
166 'username' => 'user1',
167 'user_email' => 'user1@example.com',
168 'user_email_confirmation' => 'user1@example.com',
169 'password' => 'hunter22',
170 'password_confirmation' => 'hunter22',
171 ],
172 ])->assertRedirect(route('home'));
173 }
174
175 /**
176 * @dataProvider dataProviderForStoreWebInvalidParams
177 */
178 public function testStoreWebInvalidParams($username, $email, $emailConfirmation, $password, $passwordConfirmation): void
179 {
180 config_set('osu.user.registration_mode.web', true);
181 $this->expectCountChange(fn () => User::count(), 0);
182
183 $this->post(route('users.store-web'), [
184 'user' => [
185 'username' => $username,
186 'user_email' => $email,
187 'user_email_confirmation' => $emailConfirmation,
188 'password' => $password,
189 'password_confirmation' => $passwordConfirmation,
190 ],
191 ])->assertStatus(422);
192 }
193
194 public function testStoreWebLoggedIn(): void
195 {
196 config_set('osu.user.registration_mode.web', true);
197 $user = User::factory()->create();
198
199 $this->expectCountChange(fn () => User::count(), 0);
200
201 $this->actingAsVerified($user)->post(route('users.store-web'), [
202 'user' => [
203 'username' => 'user1',
204 'user_email' => 'user1@example.com',
205 'user_email_confirmation' => 'user1@example.com',
206 'password' => 'hunter22',
207 'password_confirmation' => 'hunter22',
208 ],
209 ])->assertRedirect('/');
210 }
211
212 public function testStoreWithCountry()
213 {
214 $country = Country::inRandomOrder()->first() ?? Country::factory()->create();
215
216 $previousCount = User::count();
217
218 $this
219 ->json('POST', route('users.store'), [
220 'user' => [
221 'username' => 'user1',
222 'user_email' => 'user1@example.com',
223 'password' => 'hunter22',
224 ],
225 ], [
226 'HTTP_USER_AGENT' => $GLOBALS['cfg']['osu']['client']['user_agent'],
227 'HTTP_CF_IPCOUNTRY' => $country->getKey(),
228 ])->assertJsonFragment([
229 'username' => 'user1',
230 'country' => [
231 'code' => $country->getKey(),
232 'name' => $country->name,
233 ],
234 ]);
235
236 $this->assertSame($previousCount + 1, User::count());
237 }
238
239 /**
240 * Disable registration for logged in user.
241 */
242 public function testStoreLoggedIn()
243 {
244 $user = User::factory()->create();
245
246 $previousCount = User::count();
247
248 $this
249 ->actingAsVerified($user)
250 ->json('POST', route('users.store'), [
251 'user' => [
252 'username' => 'user1',
253 'user_email' => 'user1@example.com',
254 'password' => 'hunter22',
255 ],
256 ], [
257 'HTTP_USER_AGENT' => $GLOBALS['cfg']['osu']['client']['user_agent'],
258 ])->assertStatus(302);
259
260 $this->assertSame($previousCount, User::count());
261 }
262
263 public function testPreviousUsernameShouldRedirect()
264 {
265 $oldUsername = 'potato';
266 $newUsername = 'carrot';
267
268 /** @var User $user */
269 $user = User::factory()->create([
270 'osu_subscriptionexpiry' => now()->addDays(),
271 'username' => $oldUsername,
272 'username_clean' => $oldUsername,
273 ]);
274 $user->changeUsername($newUsername, 'paid');
275
276 $this
277 ->get(route('users.show', ['user' => $oldUsername]))
278 ->assertRedirect(route('users.show', ['user' => $user->getKey(), 'mode' => null]));
279 }
280
281 public function testPreviousUsernameTakenShouldNotRedirect()
282 {
283 $oldUsername = 'potato';
284 $newUsername = 'carrot';
285
286 /** @var User $user1 */
287 $user1 = User::factory()->create([
288 'osu_subscriptionexpiry' => now()->addDays(),
289 'username' => $oldUsername,
290 'username_clean' => $oldUsername,
291 ]);
292 $user1->changeUsername($newUsername, 'paid');
293
294 $user2 = User::factory()->create([
295 'username' => $oldUsername,
296 'username_clean' => $oldUsername,
297 ]);
298
299 $this
300 ->get(route('users.show', ['user' => $oldUsername]))
301 ->assertRedirect(route('users.show', ['user' => $user2->getKey(), 'mode' => null]));
302 }
303
304 public function testUsernameRedirectToId()
305 {
306 $user = User::factory()->create();
307
308 $this
309 ->get(route('users.show', ['user' => $user->username]))
310 ->assertRedirect(route('users.show', ['user' => $user->getKey()]));
311 }
312
313 public function testUsernameAtPrefixedRedirectToId()
314 {
315 $user = User::factory()->create();
316
317 $this
318 ->get(route('users.show', ['user' => "@{$user->username}"]))
319 ->assertRedirect(route('users.show', ['user' => $user->getKey()]));
320 }
321
322 public function testUsernameRedirectToIdForApi()
323 {
324 $user = User::factory()->create();
325
326 $this->actAsScopedUser($user, ['public']);
327
328 $this
329 ->get(route('api.users.show', ['user' => $user->username]))
330 ->assertSuccessful();
331 }
332
333 public function testUsernameAtPrefixedRedirectToIdForApi()
334 {
335 $user = User::factory()->create();
336
337 $this->actAsScopedUser($user, ['public']);
338
339 $this
340 ->get(route('api.users.show', ['user' => "@{$user->username}"]))
341 ->assertSuccessful();
342 }
343
344 public static function dataProviderForStoreWebInvalidParams(): array
345 {
346 return [
347 ['user1', 'user@email.com', 'user@email.com', 'short', 'short'],
348 ['user1', 'user@email.com', 'user@email.com', '', ''],
349 ['user1', 'user@email.com', 'user@email.com', 'userpassword', 'userpassword1'],
350 ['user1', 'user@email.com', 'user@email.com', 'userpassword', null],
351 ['user1', 'user@email.com', 'user@email.com', null, null],
352
353 ['user1', 'notemail@.com', 'notemail@.com', 'userpassword', 'userpassword'],
354 ['user1', '', '', 'userpassword', 'userpassword'],
355 ['user1', 'user@email.com', 'user1@email.com', 'userpassword', 'userpassword'],
356 ['user1', 'user@email.com', null, 'userpassword', 'userpassword'],
357 ['user1', null, null, 'userpassword', 'userpassword'],
358
359 [null, 'user@email.com', 'user@email.com', 'userpassword', 'userpassword'],
360 ['', 'user@email.com', 'user@email.com', 'userpassword', 'userpassword'],
361 [null, 'user@email.com', 'user@email.com', null, null],
362 [null, null, null, 'userpassword', 'userpassword'],
363 ];
364 }
365
366 protected function setUp(): void
367 {
368 parent::setUp();
369
370 Country::factory()->fallback()->create();
371 }
372}