]> BookStack Code Mirror - bookstack/blob - tests/User/UserMyAccountTest.php
Comments: Moved to tab UI, Converted tabs component to ts
[bookstack] / tests / User / UserMyAccountTest.php
1 <?php
2
3 namespace Tests\User;
4
5 use BookStack\Access\Mfa\MfaValue;
6 use BookStack\Activity\Tools\UserEntityWatchOptions;
7 use BookStack\Activity\WatchLevels;
8 use BookStack\Api\ApiToken;
9 use BookStack\Uploads\Image;
10 use Illuminate\Support\Facades\Hash;
11 use Illuminate\Support\Str;
12 use Tests\TestCase;
13
14 class UserMyAccountTest extends TestCase
15 {
16     public function test_index_view()
17     {
18         $resp = $this->asEditor()->get('/my-account');
19         $resp->assertRedirect('/my-account/profile');
20     }
21
22     public function test_views_not_accessible_to_guest_user()
23     {
24         $categories = ['profile', 'auth', 'shortcuts', 'notifications', ''];
25         $this->setSettings(['app-public' => 'true']);
26
27         $this->permissions->grantUserRolePermissions($this->users->guest(), ['receive-notifications']);
28
29         foreach ($categories as $category) {
30             $resp = $this->get('/my-account/' . $category);
31             $resp->assertRedirect('/');
32         }
33     }
34
35     public function test_profile_updating()
36     {
37         $editor = $this->users->editor();
38
39         $resp = $this->actingAs($editor)->get('/my-account/profile');
40         $resp->assertSee('Profile Details');
41
42         $html = $this->withHtml($resp);
43         $html->assertFieldHasValue('name', $editor->name);
44         $html->assertFieldHasValue('email', $editor->email);
45
46         $resp = $this->put('/my-account/profile', [
47             'name' => 'Barryius',
48             'email' => '[email protected]',
49             'language' => 'fr',
50         ]);
51
52         $resp->assertRedirect('/my-account/profile');
53         $this->assertDatabaseHas('users', [
54             'name' => 'Barryius',
55             'email' => $editor->email, // No email change due to not having permissions
56         ]);
57         $this->assertEquals(setting()->getUser($editor, 'language'), 'fr');
58     }
59
60     public function test_profile_user_avatar_update_and_reset()
61     {
62         $user = $this->users->viewer();
63         $avatarFile = $this->files->uploadedImage('avatar-icon.png');
64
65         $this->assertEquals(0, $user->image_id);
66
67         $upload = $this->actingAs($user)->call('PUT', "/my-account/profile", [
68             'name' => 'Barry Scott',
69         ], [], ['profile_image' => $avatarFile], []);
70         $upload->assertRedirect('/my-account/profile');
71
72
73         $user->refresh();
74         $this->assertNotEquals(0, $user->image_id);
75         /** @var Image $image */
76         $image = Image::query()->findOrFail($user->image_id);
77         $this->assertFileExists(public_path($image->path));
78
79         $reset = $this->put("/my-account/profile", [
80             'name' => 'Barry Scott',
81             'profile_image_reset' => 'true',
82         ]);
83         $upload->assertRedirect('/my-account/profile');
84
85         $user->refresh();
86         $this->assertFileDoesNotExist(public_path($image->path));
87         $this->assertEquals(0, $user->image_id);
88     }
89
90     public function test_profile_admin_options_link_shows_if_permissions_allow()
91     {
92         $editor = $this->users->editor();
93
94         $resp = $this->actingAs($editor)->get('/my-account/profile');
95         $resp->assertDontSee('Administrator Options');
96         $this->withHtml($resp)->assertLinkNotExists(url("/settings/users/{$editor->id}"));
97
98         $this->permissions->grantUserRolePermissions($editor, ['users-manage']);
99
100         $resp = $this->actingAs($editor)->get('/my-account/profile');
101         $resp->assertSee('Administrator Options');
102         $this->withHtml($resp)->assertLinkExists(url("/settings/users/{$editor->id}"));
103     }
104
105     public function test_profile_self_delete()
106     {
107         $editor = $this->users->editor();
108
109         $resp = $this->actingAs($editor)->get('/my-account/profile');
110         $this->withHtml($resp)->assertLinkExists(url('/my-account/delete'), 'Delete Account');
111
112         $resp = $this->get('/my-account/delete');
113         $resp->assertSee('Delete My Account');
114         $this->withHtml($resp)->assertElementContains('form[action$="/my-account"] button', 'Confirm');
115
116         $resp = $this->delete('/my-account');
117         $resp->assertRedirect('/');
118
119         $this->assertDatabaseMissing('users', ['id' => $editor->id]);
120     }
121
122     public function test_profile_self_delete_shows_ownership_migration_if_can_manage_users()
123     {
124         $editor = $this->users->editor();
125
126         $resp = $this->actingAs($editor)->get('/my-account/delete');
127         $resp->assertDontSee('Migrate Ownership');
128
129         $this->permissions->grantUserRolePermissions($editor, ['users-manage']);
130
131         $resp = $this->actingAs($editor)->get('/my-account/delete');
132         $resp->assertSee('Migrate Ownership');
133     }
134
135     public function test_auth_password_change()
136     {
137         $editor = $this->users->editor();
138
139         $resp = $this->actingAs($editor)->get('/my-account/auth');
140         $resp->assertSee('Change Password');
141         $this->withHtml($resp)->assertElementExists('form[action$="/my-account/auth/password"]');
142
143         $password = Str::random();
144         $resp = $this->put('/my-account/auth/password', [
145             'password' => $password,
146             'password-confirm' => $password,
147         ]);
148         $resp->assertRedirect('/my-account/auth');
149
150         $editor->refresh();
151         $this->assertTrue(Hash::check($password, $editor->password));
152     }
153
154     public function test_auth_password_change_hides_if_not_using_email_auth()
155     {
156         $editor = $this->users->editor();
157
158         $resp = $this->actingAs($editor)->get('/my-account/auth');
159         $resp->assertSee('Change Password');
160
161         config()->set('auth.method', 'oidc');
162
163         $resp = $this->actingAs($editor)->get('/my-account/auth');
164         $resp->assertDontSee('Change Password');
165     }
166
167     public function test_auth_page_has_mfa_links()
168     {
169         $editor = $this->users->editor();
170         $resp = $this->actingAs($editor)->get('/my-account/auth');
171         $resp->assertSee('0 methods configured');
172         $this->withHtml($resp)->assertLinkExists(url('/mfa/setup'));
173
174         MfaValue::upsertWithValue($editor, 'totp', 'testval');
175
176         $resp = $this->get('/my-account/auth');
177         $resp->assertSee('1 method configured');
178     }
179
180     public function test_auth_page_api_tokens()
181     {
182         $editor = $this->users->editor();
183         $resp = $this->actingAs($editor)->get('/my-account/auth');
184         $resp->assertSee('API Tokens');
185         $this->withHtml($resp)->assertLinkExists(url("/api-tokens/{$editor->id}/create?context=my-account"));
186
187         ApiToken::factory()->create(['user_id' => $editor->id, 'name' => 'My great token']);
188         $editor->unsetRelations();
189
190         $resp = $this->get('/my-account/auth');
191         $resp->assertSee('My great token');
192     }
193
194     public function test_interface_shortcuts_updating()
195     {
196         $this->asEditor();
197
198         // View preferences with defaults
199         $resp = $this->get('/my-account/shortcuts');
200         $resp->assertSee('UI Shortcut Preferences');
201
202         $html = $this->withHtml($resp);
203         $html->assertFieldHasValue('enabled', 'false');
204         $html->assertFieldHasValue('shortcut[home_view]', '1');
205
206         // Update preferences
207         $resp = $this->put('/my-account/shortcuts', [
208             'enabled' => 'true',
209             'shortcut' => ['home_view' => 'Ctrl + 1'],
210         ]);
211
212         $resp->assertRedirect('/my-account/shortcuts');
213         $resp->assertSessionHas('success', 'Shortcut preferences have been updated!');
214
215         // View updates to preferences page
216         $resp = $this->get('/my-account/shortcuts');
217         $html = $this->withHtml($resp);
218         $html->assertFieldHasValue('enabled', 'true');
219         $html->assertFieldHasValue('shortcut[home_view]', 'Ctrl + 1');
220     }
221
222     public function test_body_has_shortcuts_component_when_active()
223     {
224         $editor = $this->users->editor();
225         $this->actingAs($editor);
226
227         $this->withHtml($this->get('/'))->assertElementNotExists('body[component="shortcuts"]');
228
229         setting()->putUser($editor, 'ui-shortcuts-enabled', 'true');
230         $this->withHtml($this->get('/'))->assertElementExists('body[component="shortcuts"]');
231     }
232
233     public function test_notification_routes_requires_notification_permission()
234     {
235         $viewer = $this->users->viewer();
236         $resp = $this->actingAs($viewer)->get('/my-account/notifications');
237         $this->assertPermissionError($resp);
238
239         $resp = $this->actingAs($viewer)->get('/my-account/profile');
240         $resp->assertDontSeeText('Notification Preferences');
241
242         $resp = $this->put('/my-account/notifications');
243         $this->assertPermissionError($resp);
244
245         $this->permissions->grantUserRolePermissions($viewer, ['receive-notifications']);
246         $resp = $this->get('/my-account/notifications');
247         $resp->assertOk();
248         $resp->assertSee('Notification Preferences');
249     }
250
251     public function test_notification_preferences_updating()
252     {
253         $editor = $this->users->editor();
254
255         // View preferences with defaults
256         $resp = $this->actingAs($editor)->get('/my-account/notifications');
257         $resp->assertSee('Notification Preferences');
258
259         $html = $this->withHtml($resp);
260         $html->assertFieldHasValue('preferences[comment-replies]', 'false');
261
262         // Update preferences
263         $resp = $this->put('/my-account/notifications', [
264             'preferences' => ['comment-replies' => 'true'],
265         ]);
266
267         $resp->assertRedirect('/my-account/notifications');
268         $resp->assertSessionHas('success', 'Notification preferences have been updated!');
269
270         // View updates to preferences page
271         $resp = $this->get('/my-account/notifications');
272         $html = $this->withHtml($resp);
273         $html->assertFieldHasValue('preferences[comment-replies]', 'true');
274     }
275
276     public function test_notification_preferences_show_watches()
277     {
278         $editor = $this->users->editor();
279         $book = $this->entities->book();
280
281         $options = new UserEntityWatchOptions($editor, $book);
282         $options->updateLevelByValue(WatchLevels::COMMENTS);
283
284         $resp = $this->actingAs($editor)->get('/my-account/notifications');
285         $resp->assertSee($book->name);
286         $resp->assertSee('All Page Updates & Comments');
287
288         $options->updateLevelByValue(WatchLevels::DEFAULT);
289
290         $resp = $this->actingAs($editor)->get('/my-account/notifications');
291         $resp->assertDontSee($book->name);
292         $resp->assertDontSee('All Page Updates & Comments');
293     }
294
295     public function test_notification_preferences_dont_error_on_deleted_items()
296     {
297         $editor = $this->users->editor();
298         $book = $this->entities->book();
299
300         $options = new UserEntityWatchOptions($editor, $book);
301         $options->updateLevelByValue(WatchLevels::COMMENTS);
302
303         $this->actingAs($editor)->delete($book->getUrl());
304         $book->refresh();
305         $this->assertNotNull($book->deleted_at);
306
307         $resp = $this->actingAs($editor)->get('/my-account/notifications');
308         $resp->assertOk();
309         $resp->assertDontSee($book->name);
310     }
311
312     public function test_notification_preferences_not_accessible_to_guest()
313     {
314         $this->setSettings(['app-public' => 'true']);
315         $guest = $this->users->guest();
316         $this->permissions->grantUserRolePermissions($guest, ['receive-notifications']);
317
318         $resp = $this->get('/my-account/notifications');
319         $this->assertPermissionError($resp);
320
321         $resp = $this->put('/my-account/notifications', [
322             'preferences' => ['comment-replies' => 'true'],
323         ]);
324         $this->assertPermissionError($resp);
325     }
326
327     public function test_notification_comment_options_only_exist_if_comments_active()
328     {
329         $resp = $this->asEditor()->get('/my-account/notifications');
330         $resp->assertSee('Notify upon comments');
331         $resp->assertSee('Notify upon replies');
332
333         setting()->put('app-disable-comments', true);
334
335         $resp = $this->get('/my-account/notifications');
336         $resp->assertDontSee('Notify upon comments');
337         $resp->assertDontSee('Notify upon replies');
338     }
339 }