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\Controllers;
9
10use App\Mail\UserEmailUpdated;
11use App\Mail\UserPasswordUpdated;
12use App\Models\Country;
13use App\Models\User;
14use App\Models\UserProfileCustomization;
15use App\Models\WeakPassword;
16use Database\Factories\UserFactory;
17use Hash;
18use Mail;
19use Tests\TestCase;
20
21class AccountControllerTest extends TestCase
22{
23 private $user;
24
25 /**
26 * Checks whether an OK status is returned when the
27 * profile order update request is valid.
28 */
29 public function testValidProfileOrderChangeRequest()
30 {
31 $newOrder = UserProfileCustomization::SECTIONS;
32 seeded_shuffle($newOrder);
33
34 $this->actingAsVerified($this->user())
35 ->json('PUT', route('account.options'), [
36 'order' => $newOrder,
37 ])
38 ->assertJsonFragment(['profile_order' => $newOrder]);
39 }
40
41 public function testDuplicatesInProfileOrder()
42 {
43 $newOrder = UserProfileCustomization::SECTIONS;
44
45 $newOrderWithDuplicate = $newOrder;
46 $newOrderWithDuplicate[] = $newOrder[0];
47
48 $this->actingAsVerified($this->user())
49 ->json('PUT', route('account.options'), [
50 'order' => $newOrderWithDuplicate,
51 ])
52 ->assertJsonFragment(['profile_order' => $newOrder]);
53 }
54
55 public function testInvalidIdsInProfileOrder()
56 {
57 $newOrder = UserProfileCustomization::SECTIONS;
58
59 $newOrderWithInvalid = $newOrder;
60 $newOrderWithInvalid[] = 'test';
61
62 $this->actingAsVerified($this->user())
63 ->json('PUT', route('account.options'), [
64 'order' => $newOrderWithInvalid,
65 ])
66 ->assertJsonFragment(['profile_order' => $newOrder]);
67 }
68
69 /**
70 * @dataProvider dataProviderForUpdateCountry
71 * @group RequiresScoreIndexer
72 *
73 * More complete tests are done through CountryChange and CountryChangeTarget.
74 */
75 public function testUpdateCountry(?string $historyCountry, ?string $targetCountry, bool $success): void
76 {
77 $user = $this->user();
78 foreach (array_unique([$historyCountry, $targetCountry]) as $country) {
79 if ($country !== null) {
80 Country::factory()->create(['acronym' => $country]);
81 }
82 }
83 if ($historyCountry !== null) {
84 UserFactory::createRecentCountryHistory($user, $historyCountry, null);
85 }
86
87 $resultCountry = $success ? $targetCountry : $user->country_acronym;
88
89 $this->actingAsVerified($user)
90 ->json('PUT', route('account.country', ['country_acronym' => $targetCountry]))
91 ->assertStatus($success ? 200 : 403);
92
93 $this->assertSame($user->fresh()->country_acronym, $resultCountry);
94 }
95
96 public function testUpdateEmail()
97 {
98 $newEmail = 'new-'.$this->user->user_email;
99
100 Mail::fake();
101
102 $this->actingAsVerified($this->user())
103 ->json('PUT', route('account.email'), [
104 'user' => [
105 'current_password' => 'password',
106 'user_email' => $newEmail,
107 'user_email_confirmation' => $newEmail,
108 ],
109 ])
110 ->assertSuccessful();
111
112 $this->assertSame($newEmail, $this->user->fresh()->user_email);
113
114 Mail::assertQueued(UserEmailUpdated::class, 2);
115 }
116
117 public function testUpdateEmailLocked()
118 {
119 $newEmail = 'new-'.$this->user->user_email;
120 $this->user->update(['lock_email_changes' => true]);
121
122 $this->actingAsVerified($this->user())
123 ->json('PUT', route('account.email'), [
124 'user' => [
125 'current_password' => 'password',
126 'user_email' => $newEmail,
127 'user_email_confirmation' => $newEmail,
128 ],
129 ])
130 ->assertStatus(403);
131 }
132
133 public function testUpdateEmailInvalidPassword()
134 {
135 $newEmail = 'new-'.$this->user->user_email;
136
137 $this->actingAsVerified($this->user())
138 ->json('PUT', route('account.email'), [
139 'user' => [
140 'current_password' => 'password1',
141 'user_email' => $newEmail,
142 'user_email_confirmation' => $newEmail,
143 ],
144 ])
145 ->assertStatus(422);
146 }
147
148 public function testUpdatePassword()
149 {
150 $newPassword = 'newpassword';
151
152 Mail::fake();
153
154 $this->actingAsVerified($this->user())
155 ->json('PUT', route('account.password'), [
156 'user' => [
157 'current_password' => 'password',
158 'password' => $newPassword,
159 'password_confirmation' => $newPassword,
160 ],
161 ])
162 ->assertSuccessful();
163
164 $this->assertTrue(Hash::check($newPassword, $this->user->fresh()->user_password));
165
166 Mail::assertQueued(UserPasswordUpdated::class);
167 }
168
169 public function testUpdatePasswordInvalidCurrentPassword()
170 {
171 $this->actingAsVerified($this->user())
172 ->json('PUT', route('account.password'), [
173 'user' => [
174 'current_password' => 'notpassword',
175 'password' => 'newpassword',
176 'password_confirmation' => 'newpassword',
177 ],
178 ])
179 ->assertStatus(422);
180 }
181
182 public function testUpdatePasswordInvalidPasswordConfirmation()
183 {
184 $this->actingAsVerified($this->user())
185 ->json('PUT', route('account.password'), [
186 'user' => [
187 'current_password' => 'password',
188 'password' => 'newpassword',
189 'password_confirmation' => 'oldpassword',
190 ],
191 ])
192 ->assertStatus(422);
193 }
194
195 public function testUpdatePasswordUsernameAsPassword()
196 {
197 $this->actingAsVerified($this->user())
198 ->json('PUT', route('account.password'), [
199 'user' => [
200 'current_password' => 'password',
201 'password' => $this->user->username,
202 'password_confirmation' => $this->user->username,
203 ],
204 ])
205 ->assertStatus(422);
206 }
207
208 public function testUpdatePasswordShortPassword()
209 {
210 $this->actingAsVerified($this->user())
211 ->json('PUT', route('account.password'), [
212 'user' => [
213 'current_password' => 'password',
214 'password' => '1234567',
215 'password_confirmation' => '1234567',
216 ],
217 ])
218 ->assertStatus(422);
219 }
220
221 public function testUpdatePasswordWeakPassword()
222 {
223 $weakPassword = 'weakpassword';
224
225 WeakPassword::add($weakPassword);
226
227 $this->actingAsVerified($this->user())
228 ->json('PUT', route('account.password'), [
229 'user' => [
230 'current_password' => 'password',
231 'password' => $weakPassword,
232 'password_confirmation' => $weakPassword,
233 ],
234 ])
235 ->assertStatus(422);
236 }
237
238 public static function dataProviderForUpdateCountry(): array
239 {
240 return [
241 ['_A', '_A', true],
242 ['_B', '_A', false],
243 [null, null, false],
244 ];
245 }
246
247 protected function setUp(): void
248 {
249 parent::setUp();
250
251 $this->user = User::factory()->create();
252 }
253
254 private function user()
255 {
256 // To reset all the verify toggles.
257 return $this->user->fresh();
258 }
259}