]> BookStack Code Mirror - bookstack/commitdiff
My Account: Updated and started adding to tests
authorDan Brown <redacted>
Thu, 19 Oct 2023 13:18:42 +0000 (14:18 +0100)
committerDan Brown <redacted>
Thu, 19 Oct 2023 13:18:42 +0000 (14:18 +0100)
- Updated existing tests now affected by my-account changes.
- Updated some existing tests to more accuractly check the scenario.
- Updated some code styling in SocialController.
- Fixed redirects for social account flows to fit my-account.
- Added test for social account attaching.
- Added test for api token redirect handling.

14 files changed:
app/Access/Controllers/SocialController.php
app/Access/SocialAuthService.php
app/Api/UserApiTokenController.php
lang/en/settings.php
resources/views/users/account/layout.blade.php
resources/views/users/edit.blade.php
routes/web.php
tests/Actions/WebhookCallTest.php
tests/Auth/SocialAuthTest.php
tests/Permissions/RolePermissionsTest.php
tests/Uploads/ImageTest.php
tests/User/UserApiTokenTest.php
tests/User/UserMyAccountTest.php [new file with mode: 0644]
tests/User/UserPreferencesTest.php

index 3df895dd8ffec995c4e0d7f8c3fb4066ae3002cb..ff6d5c2ddd2d5ef014edfbf2eeaf82ecafbab4dd 100644 (file)
@@ -16,22 +16,12 @@ use Laravel\Socialite\Contracts\User as SocialUser;
 
 class SocialController extends Controller
 {
-    protected SocialAuthService $socialAuthService;
-    protected RegistrationService $registrationService;
-    protected LoginService $loginService;
-
-    /**
-     * SocialController constructor.
-     */
     public function __construct(
-        SocialAuthService $socialAuthService,
-        RegistrationService $registrationService,
-        LoginService $loginService
+        protected SocialAuthService $socialAuthService,
+        protected RegistrationService $registrationService,
+        protected LoginService $loginService,
     ) {
         $this->middleware('guest')->only(['register']);
-        $this->socialAuthService = $socialAuthService;
-        $this->registrationService = $registrationService;
-        $this->loginService = $loginService;
     }
 
     /**
@@ -112,7 +102,7 @@ class SocialController extends Controller
         $this->socialAuthService->detachSocialAccount($socialDriver);
         session()->flash('success', trans('settings.users_social_disconnected', ['socialAccount' => Str::title($socialDriver)]));
 
-        return redirect(user()->getEditUrl());
+        return redirect('/my-account/auth#social-accounts');
     }
 
     /**
index fe919543094f1d636a4b934f5fd2c6674531fe33..f0e0413f092dbff1ff34ce0131f72214f739dfff 100644 (file)
@@ -154,21 +154,21 @@ class SocialAuthService
             $currentUser->socialAccounts()->save($account);
             session()->flash('success', trans('settings.users_social_connected', ['socialAccount' => $titleCaseDriver]));
 
-            return redirect($currentUser->getEditUrl());
+            return redirect('/my-account/auth#social_accounts');
         }
 
         // When a user is logged in and the social account exists and is already linked to the current user.
         if ($isLoggedIn && $socialAccount !== null && $socialAccount->user->id === $currentUser->id) {
             session()->flash('error', trans('errors.social_account_existing', ['socialAccount' => $titleCaseDriver]));
 
-            return redirect($currentUser->getEditUrl());
+            return redirect('/my-account/auth#social_accounts');
         }
 
         // When a user is logged in, A social account exists but the users do not match.
         if ($isLoggedIn && $socialAccount !== null && $socialAccount->user->id != $currentUser->id) {
             session()->flash('error', trans('errors.social_account_already_used_existing', ['socialAccount' => $titleCaseDriver]));
 
-            return redirect($currentUser->getEditUrl());
+            return redirect('/my-account/auth#social_accounts');
         }
 
         // Otherwise let the user know this social account is not used by anyone.
index 7455be4ff6948c07e7722e5eaa7d2cbbe7dad4d8..b77e390896626087270c4eba37f3251fe3567667 100644 (file)
@@ -166,7 +166,7 @@ class UserApiTokenController extends Controller
     protected function getRedirectPath(User $relatedUser): string
     {
         $context = session()->get('api-token-context');
-        if ($context === 'settings') {
+        if ($context === 'settings' || user()->id !== $relatedUser->id) {
             return $relatedUser->getEditUrl('#api_tokens');
         }
 
index dfd0f7841e3b0315a45b34e3ae33af954f94c12f..9e49c7ca74c3cfb679ff783c818541f03626443d 100644 (file)
@@ -214,6 +214,8 @@ return [
     'users_social_accounts_info' => 'Here you can connect your other accounts for quicker and easier login. Disconnecting an account here does not revoke previously authorized access. Revoke access from your profile settings on the connected social account.',
     'users_social_connect' => 'Connect Account',
     'users_social_disconnect' => 'Disconnect Account',
+    'users_social_status_connected' => 'Connected',
+    'users_social_status_disconnected' => 'Disconnected',
     'users_social_connected' => ':socialAccount account was successfully attached to your profile.',
     'users_social_disconnected' => ':socialAccount account was successfully disconnected from your profile.',
     'users_api_tokens' => 'API Tokens',
index ff5ad36223a54ffae6e469ce0a4138694d6ca1a8..f54a51c5ac8d3c9cbd8774efb5cc5e8f216e8cee 100644 (file)
@@ -12,7 +12,9 @@
                         <a href="{{ url('/my-account/profile') }}" class="{{ $category === 'profile' ? 'active' : '' }}">@icon('user') {{ trans('preferences.profile') }}</a>
                         <a href="{{ url('/my-account/auth') }}" class="{{ $category === 'auth' ? 'active' : '' }}">@icon('security') {{ trans('preferences.auth') }}</a>
                         <a href="{{ url('/my-account/shortcuts') }}" class="{{ $category === 'shortcuts' ? 'active' : '' }}">@icon('shortcuts') {{ trans('preferences.shortcuts_interface') }}</a>
-                        <a href="{{ url('/my-account/notifications') }}" class="{{ $category === 'notifications' ? 'active' : '' }}">@icon('notifications') {{ trans('preferences.notifications') }}</a>
+                        @if(userCan('receive-notifications'))
+                            <a href="{{ url('/my-account/notifications') }}" class="{{ $category === 'notifications' ? 'active' : '' }}">@icon('notifications') {{ trans('preferences.notifications') }}</a>
+                        @endif
                     </nav>
                 </div>
             </div>
index 076b28c746ba41c67b0f7e69cfd3d6aa5c451d0f..2b736d81edeebad3b99ed0d59b391a98d0618b8b 100644 (file)
@@ -89,9 +89,9 @@
                                 <div role="presentation">@icon('auth/'. $driver, ['style' => 'width: 56px;height: 56px;'])</div>
                                 <p class="my-none bold">{{ $driverName }}</p>
                                 @if($user->hasSocialAccount($driver))
-                                    <p class="text-pos bold text-small my-none">Connected</p>
+                                    <p class="text-pos bold text-small my-none">{{ trans('settings.users_social_status_connected') }}</p>
                                 @else
-                                    <p class="text-neg bold text-small my-none">Disconnected</p>
+                                    <p class="text-neg bold text-small my-none">{{ trans('settings.users_social_status_disconnected') }}</p>
                                 @endif
                             </div>
                         @endforeach
index c2f4891b834268b8fbfaf2a21a15dd872bfe6750..c86509c68bf78c559bae298652970547b75c9a77 100644 (file)
@@ -244,6 +244,8 @@ Route::middleware('auth')->group(function () {
     Route::put('/my-account/auth/password', [UserControllers\UserAccountController::class, 'updatePassword']);
     Route::get('/my-account/delete', [UserControllers\UserAccountController::class, 'delete']);
     Route::delete('/my-account', [UserControllers\UserAccountController::class, 'destroy']);
+
+    // User Preference Endpoints
     Route::patch('/preferences/change-view/{type}', [UserControllers\UserPreferencesController::class, 'changeView']);
     Route::patch('/preferences/change-sort/{type}', [UserControllers\UserPreferencesController::class, 'changeSort']);
     Route::patch('/preferences/change-expansion/{type}', [UserControllers\UserPreferencesController::class, 'changeExpansion']);
index 81bd7e7e8ce303df28e86f5fc07177c4a5762096..16986ba2e97ac11610ce4b8266767fe2d85b8533 100644 (file)
@@ -51,7 +51,7 @@ class WebhookCallTest extends TestCase
     {
         // This test must not fake the queue/bus since this covers an issue
         // around handling and serialization of items now deleted from the database.
-        $this->newWebhook(['active' => true, 'endpoint' => 'https://wh.example.com'], ['all']);
+        $webhook = $this->newWebhook(['active' => true, 'endpoint' => 'https://wh.example.com'], ['all']);
         $this->mockHttpClient([new Response(500)]);
 
         $user = $this->users->newUser();
@@ -61,8 +61,10 @@ class WebhookCallTest extends TestCase
         /** @var ApiToken $apiToken */
         $editor = $this->users->editor();
         $apiToken = ApiToken::factory()->create(['user_id' => $editor]);
-        $resp = $this->delete($editor->getEditUrl('/api-tokens/' . $apiToken->id));
-        $resp->assertRedirect($editor->getEditUrl('#api_tokens'));
+        $this->delete($apiToken->getUrl())->assertRedirect();
+
+        $webhook->refresh();
+        $this->assertEquals('Response status from endpoint was 500', $webhook->last_error);
     }
 
     public function test_failed_webhook_call_logs_error()
index 5b7071a0779e2c60dd4d99d839e0c00c45d21da8..89b8fd167dcdedf4d7dfea043e43b58a2245f241 100644 (file)
@@ -18,7 +18,7 @@ class SocialAuthTest extends TestCase
         $user = User::factory()->make();
 
         $this->setSettings(['registration-enabled' => 'true']);
-        config(['GOOGLE_APP_ID' => 'abc123', 'GOOGLE_APP_SECRET' => '123abc', 'APP_URL' => 'http://localhost']);
+        config(['GOOGLE_APP_ID' => 'abc123', 'GOOGLE_APP_SECRET' => '123abc']);
 
         $mockSocialite = $this->mock(Factory::class);
         $mockSocialDriver = Mockery::mock(Provider::class);
@@ -45,7 +45,6 @@ class SocialAuthTest extends TestCase
         config([
             'GOOGLE_APP_ID' => 'abc123', 'GOOGLE_APP_SECRET' => '123abc',
             'GITHUB_APP_ID' => 'abc123', 'GITHUB_APP_SECRET' => '123abc',
-            'APP_URL'       => 'http://localhost',
         ]);
 
         $mockSocialite = $this->mock(Factory::class);
@@ -86,12 +85,41 @@ class SocialAuthTest extends TestCase
         $this->assertActivityExists(ActivityType::AUTH_LOGIN, null, 'github; (' . $this->users->admin()->id . ') ' . $this->users->admin()->name);
     }
 
+    public function test_social_account_attach()
+    {
+        config([
+            'GOOGLE_APP_ID' => 'abc123', 'GOOGLE_APP_SECRET' => '123abc',
+        ]);
+        $editor = $this->users->editor();
+
+        $mockSocialite = $this->mock(Factory::class);
+        $mockSocialDriver = Mockery::mock(Provider::class);
+        $mockSocialUser = Mockery::mock(\Laravel\Socialite\Contracts\User::class);
+
+        $mockSocialUser->shouldReceive('getId')->twice()->andReturn('logintest123');
+        $mockSocialUser->shouldReceive('getAvatar')->andReturn(null);
+
+        $mockSocialite->shouldReceive('driver')->twice()->with('google')->andReturn($mockSocialDriver);
+        $mockSocialDriver->shouldReceive('redirect')->once()->andReturn(redirect('/login/service/google/callback'));
+        $mockSocialDriver->shouldReceive('user')->once()->andReturn($mockSocialUser);
+
+        // Test login routes
+        $resp = $this->actingAs($editor)->followingRedirects()->get('/login/service/google');
+        $resp->assertSee('Access & Security');
+
+        // Test social callback with matching social account
+        $this->assertDatabaseHas('social_accounts', [
+            'user_id'   => $editor->id,
+            'driver'    => 'google',
+            'driver_id' => 'logintest123',
+        ]);
+    }
+
     public function test_social_account_detach()
     {
         $editor = $this->users->editor();
         config([
             'GITHUB_APP_ID' => 'abc123', 'GITHUB_APP_SECRET' => '123abc',
-            'APP_URL'       => 'http://localhost',
         ]);
 
         $socialAccount = SocialAccount::query()->forceCreate([
@@ -100,11 +128,11 @@ class SocialAuthTest extends TestCase
             'driver_id' => 'logintest123',
         ]);
 
-        $resp = $this->actingAs($editor)->get($editor->getEditUrl());
+        $resp = $this->actingAs($editor)->get('/my-account/auth');
         $this->withHtml($resp)->assertElementContains('form[action$="/login/service/github/detach"]', 'Disconnect Account');
 
         $resp = $this->post('/login/service/github/detach');
-        $resp->assertRedirect($editor->getEditUrl());
+        $resp->assertRedirect('/my-account/auth#social-accounts');
         $resp = $this->followRedirects($resp);
         $resp->assertSee('Github account was successfully disconnected from your profile.');
 
@@ -115,7 +143,6 @@ class SocialAuthTest extends TestCase
     {
         config([
             'services.google.client_id' => 'abc123', 'services.google.client_secret' => '123abc',
-            'APP_URL'                   => 'http://localhost',
         ]);
 
         $user = User::factory()->make();
@@ -153,7 +180,7 @@ class SocialAuthTest extends TestCase
     {
         config([
             'services.google.client_id' => 'abc123', 'services.google.client_secret' => '123abc',
-            'APP_URL'                   => 'http://localhost', 'services.google.auto_register' => true, 'services.google.auto_confirm' => true,
+            'services.google.auto_register' => true, 'services.google.auto_confirm' => true,
         ]);
 
         $user = User::factory()->make();
@@ -191,7 +218,7 @@ class SocialAuthTest extends TestCase
         $user = User::factory()->make(['email' => 'nonameuser@example.com']);
 
         $this->setSettings(['registration-enabled' => 'true']);
-        config(['GITHUB_APP_ID' => 'abc123', 'GITHUB_APP_SECRET' => '123abc', 'APP_URL' => 'http://localhost']);
+        config(['GITHUB_APP_ID' => 'abc123', 'GITHUB_APP_SECRET' => '123abc']);
 
         $mockSocialite = $this->mock(Factory::class);
         $mockSocialDriver = Mockery::mock(Provider::class);
index 0b2e1668655b211f954eb801a190da18488cb097..d15c1617c67b885276a9ac67b08bf8494439f23e 100644 (file)
@@ -44,14 +44,12 @@ class RolePermissionsTest extends TestCase
 
     public function test_user_cannot_change_email_unless_they_have_manage_users_permission()
     {
-        $userProfileUrl = '/settings/users/' . $this->user->id;
         $originalEmail = $this->user->email;
         $this->actingAs($this->user);
 
-        $resp = $this->get($userProfileUrl)
-            ->assertOk();
+        $resp = $this->get('/my-account/profile')->assertOk();
         $this->withHtml($resp)->assertElementExists('input[name=email][disabled]');
-        $this->put($userProfileUrl, [
+        $this->put('/my-account/profile', [
             'name'  => 'my_new_name',
             'email' => 'new_email@example.com',
         ]);
@@ -63,11 +61,12 @@ class RolePermissionsTest extends TestCase
 
         $this->permissions->grantUserRolePermissions($this->user, ['users-manage']);
 
-        $resp = $this->get($userProfileUrl)
-            ->assertOk();
-        $this->withHtml($resp)->assertElementNotExists('input[name=email][disabled]')
+        $resp = $this->get('/my-account/profile')->assertOk();
+        $this->withHtml($resp)
+            ->assertElementNotExists('input[name=email][disabled]')
             ->assertElementExists('input[name=email]');
-        $this->put($userProfileUrl, [
+
+        $this->put('/my-account/profile', [
             'name'  => 'my_new_name_2',
             'email' => 'new_email@example.com',
         ]);
index 4da964d4804db70e0cd75c877844ccfe46bd121b..af249951f3038d836ed5a27958801fe7dbc93cf9 100644 (file)
@@ -607,7 +607,7 @@ class ImageTest extends TestCase
         $this->actingAs($editor);
 
         $file = $this->getTestProfileImage();
-        $this->call('PUT', '/settings/users/' . $editor->id, [], [], ['profile_image' => $file], []);
+        $this->call('PUT', '/my-account/profile', [], [], ['profile_image' => $file], []);
 
         $profileImages = Image::where('type', '=', 'user')->where('created_by', '=', $editor->id)->get();
         $this->assertTrue($profileImages->count() === 1, 'Found profile images does not match upload count');
@@ -615,7 +615,7 @@ class ImageTest extends TestCase
         $imagePath = public_path($profileImages->first()->path);
         $this->assertTrue(file_exists($imagePath));
 
-        $userDelete = $this->asAdmin()->delete("/settings/users/{$editor->id}");
+        $userDelete = $this->asAdmin()->delete($editor->getEditUrl());
         $userDelete->assertStatus(302);
 
         $this->assertDatabaseMissing('images', [
index 75de49aed9a25342bacb956b1f851cb4070a614b..d94e97659bbfb024898fbeefe6859b32bb999a10 100644 (file)
@@ -5,25 +5,26 @@ namespace Tests\User;
 use BookStack\Activity\ActivityType;
 use BookStack\Api\ApiToken;
 use Carbon\Carbon;
+use Illuminate\Support\Facades\Hash;
 use Tests\TestCase;
 
 class UserApiTokenTest extends TestCase
 {
-    protected $testTokenData = [
+    protected array $testTokenData = [
         'name'       => 'My test API token',
         'expires_at' => '2050-04-01',
     ];
 
-    public function test_tokens_section_not_visible_without_access_api_permission()
+    public function test_tokens_section_not_visible_in_my_account_without_access_api_permission()
     {
         $user = $this->users->viewer();
 
-        $resp = $this->actingAs($user)->get($user->getEditUrl());
+        $resp = $this->actingAs($user)->get('/my-account/auth');
         $resp->assertDontSeeText('API Tokens');
 
         $this->permissions->grantUserRolePermissions($user, ['access-api']);
 
-        $resp = $this->actingAs($user)->get($user->getEditUrl());
+        $resp = $this->actingAs($user)->get('/my-account/auth');
         $resp->assertSeeText('API Tokens');
         $resp->assertSeeText('Create Token');
     }
@@ -43,14 +44,14 @@ class UserApiTokenTest extends TestCase
     {
         $editor = $this->users->editor();
 
-        $resp = $this->asAdmin()->get($editor->getEditUrl('/create-api-token'));
+        $resp = $this->asAdmin()->get("/api-tokens/{$editor->id}/create");
         $resp->assertStatus(200);
         $resp->assertSee('Create API Token');
         $resp->assertSee('Token Secret');
 
-        $resp = $this->post($editor->getEditUrl('/create-api-token'), $this->testTokenData);
+        $resp = $this->post("/api-tokens/{$editor->id}/create", $this->testTokenData);
         $token = ApiToken::query()->latest()->first();
-        $resp->assertRedirect($editor->getEditUrl('/api-tokens/' . $token->id));
+        $resp->assertRedirect("/api-tokens/{$editor->id}/{$token->id}");
         $this->assertDatabaseHas('api_tokens', [
             'user_id'    => $editor->id,
             'name'       => $this->testTokenData['name'],
@@ -63,7 +64,7 @@ class UserApiTokenTest extends TestCase
         $this->assertDatabaseMissing('api_tokens', [
             'secret' => $secret,
         ]);
-        $this->assertTrue(\Hash::check($secret, $token->secret));
+        $this->assertTrue(Hash::check($secret, $token->secret));
 
         $this->assertTrue(strlen($token->token_id) === 32);
         $this->assertTrue(strlen($secret) === 32);
@@ -75,7 +76,10 @@ class UserApiTokenTest extends TestCase
     public function test_create_with_no_expiry_sets_expiry_hundred_years_away()
     {
         $editor = $this->users->editor();
-        $this->asAdmin()->post($editor->getEditUrl('/create-api-token'), ['name' => 'No expiry token', 'expires_at' => '']);
+
+        $resp = $this->asAdmin()->post("/api-tokens/{$editor->id}/create", ['name' => 'No expiry token', 'expires_at' => '']);
+        $resp->assertRedirect();
+
         $token = ApiToken::query()->latest()->first();
 
         $over = Carbon::now()->addYears(101);
@@ -89,7 +93,9 @@ class UserApiTokenTest extends TestCase
     public function test_created_token_displays_on_profile_page()
     {
         $editor = $this->users->editor();
-        $this->asAdmin()->post($editor->getEditUrl('/create-api-token'), $this->testTokenData);
+        $resp = $this->asAdmin()->post("/api-tokens/{$editor->id}/create", $this->testTokenData);
+        $resp->assertRedirect();
+
         $token = ApiToken::query()->latest()->first();
 
         $resp = $this->get($editor->getEditUrl());
@@ -102,28 +108,29 @@ class UserApiTokenTest extends TestCase
     public function test_secret_shown_once_after_creation()
     {
         $editor = $this->users->editor();
-        $resp = $this->asAdmin()->followingRedirects()->post($editor->getEditUrl('/create-api-token'), $this->testTokenData);
+        $resp = $this->asAdmin()->followingRedirects()->post("/api-tokens/{$editor->id}/create", $this->testTokenData);
         $resp->assertSeeText('Token Secret');
 
         $token = ApiToken::query()->latest()->first();
         $this->assertNull(session('api-token-secret:' . $token->id));
 
-        $resp = $this->get($editor->getEditUrl('/api-tokens/' . $token->id));
+        $resp = $this->get("/api-tokens/{$editor->id}/{$token->id}");
+        $resp->assertOk();
         $resp->assertDontSeeText('Client Secret');
     }
 
     public function test_token_update()
     {
         $editor = $this->users->editor();
-        $this->asAdmin()->post($editor->getEditUrl('/create-api-token'), $this->testTokenData);
+        $this->asAdmin()->post("/api-tokens/{$editor->id}/create", $this->testTokenData);
         $token = ApiToken::query()->latest()->first();
         $updateData = [
             'name'       => 'My updated token',
             'expires_at' => '2011-01-01',
         ];
 
-        $resp = $this->put($editor->getEditUrl('/api-tokens/' . $token->id), $updateData);
-        $resp->assertRedirect($editor->getEditUrl('/api-tokens/' . $token->id));
+        $resp = $this->put("/api-tokens/{$editor->id}/{$token->id}", $updateData);
+        $resp->assertRedirect("/api-tokens/{$editor->id}/{$token->id}");
 
         $this->assertDatabaseHas('api_tokens', array_merge($updateData, ['id' => $token->id]));
         $this->assertSessionHas('success');
@@ -133,13 +140,13 @@ class UserApiTokenTest extends TestCase
     public function test_token_update_with_blank_expiry_sets_to_hundred_years_away()
     {
         $editor = $this->users->editor();
-        $this->asAdmin()->post($editor->getEditUrl('/create-api-token'), $this->testTokenData);
+        $this->asAdmin()->post("/api-tokens/{$editor->id}/create", $this->testTokenData);
         $token = ApiToken::query()->latest()->first();
 
-        $resp = $this->put($editor->getEditUrl('/api-tokens/' . $token->id), [
+        $this->put("/api-tokens/{$editor->id}/{$token->id}", [
             'name'       => 'My updated token',
             'expires_at' => '',
-        ]);
+        ])->assertRedirect();
         $token->refresh();
 
         $over = Carbon::now()->addYears(101);
@@ -153,15 +160,15 @@ class UserApiTokenTest extends TestCase
     public function test_token_delete()
     {
         $editor = $this->users->editor();
-        $this->asAdmin()->post($editor->getEditUrl('/create-api-token'), $this->testTokenData);
+        $this->asAdmin()->post("/api-tokens/{$editor->id}/create", $this->testTokenData);
         $token = ApiToken::query()->latest()->first();
 
-        $tokenUrl = $editor->getEditUrl('/api-tokens/' . $token->id);
+        $tokenUrl = "/api-tokens/{$editor->id}/{$token->id}";
 
         $resp = $this->get($tokenUrl . '/delete');
         $resp->assertSeeText('Delete Token');
         $resp->assertSeeText($token->name);
-        $this->withHtml($resp)->assertElementExists('form[action="' . $tokenUrl . '"]');
+        $this->withHtml($resp)->assertElementExists('form[action$="' . $tokenUrl . '"]');
 
         $resp = $this->delete($tokenUrl);
         $resp->assertRedirect($editor->getEditUrl('#api_tokens'));
@@ -175,15 +182,46 @@ class UserApiTokenTest extends TestCase
         $editor = $this->users->editor();
         $this->permissions->grantUserRolePermissions($editor, ['users-manage']);
 
-        $this->asAdmin()->post($viewer->getEditUrl('/create-api-token'), $this->testTokenData);
+        $this->asAdmin()->post("/api-tokens/{$viewer->id}/create", $this->testTokenData);
         $token = ApiToken::query()->latest()->first();
 
-        $resp = $this->actingAs($editor)->get($viewer->getEditUrl('/api-tokens/' . $token->id));
+        $resp = $this->actingAs($editor)->get("/api-tokens/{$viewer->id}/{$token->id}");
         $resp->assertStatus(200);
         $resp->assertSeeText('Delete Token');
 
-        $resp = $this->actingAs($editor)->delete($viewer->getEditUrl('/api-tokens/' . $token->id));
+        $resp = $this->actingAs($editor)->delete("/api-tokens/{$viewer->id}/{$token->id}");
         $resp->assertRedirect($viewer->getEditUrl('#api_tokens'));
         $this->assertDatabaseMissing('api_tokens', ['id' => $token->id]);
     }
+
+    public function test_return_routes_change_depending_on_entry_context()
+    {
+        $user = $this->users->admin();
+        $returnByContext = [
+            'settings' => url("/settings/users/{$user->id}/#api_tokens"),
+            'my-account' => url('/my-account/auth#api_tokens'),
+        ];
+
+        foreach ($returnByContext as $context => $returnUrl) {
+            $resp = $this->actingAs($user)->get("/api-tokens/{$user->id}/create?context={$context}");
+            $this->withHtml($resp)->assertLinkExists($returnUrl, 'Cancel');
+
+            $this->post("/api-tokens/{$user->id}/create", $this->testTokenData);
+            $token = $user->apiTokens()->latest()->first();
+
+            $resp = $this->get($token->getUrl());
+            $this->withHtml($resp)->assertLinkExists($returnUrl, 'Back');
+
+            $resp = $this->delete($token->getUrl());
+            $resp->assertRedirect($returnUrl);
+        }
+    }
+
+    public function test_context_assumed_for_editing_tokens_of_another_user()
+    {
+        $user = $this->users->viewer();
+
+        $resp = $this->asAdmin()->get("/api-tokens/{$user->id}/create?context=my-account");
+        $this->withHtml($resp)->assertLinkExists($user->getEditUrl('#api_tokens'), 'Cancel');
+    }
 }
diff --git a/tests/User/UserMyAccountTest.php b/tests/User/UserMyAccountTest.php
new file mode 100644 (file)
index 0000000..63c54da
--- /dev/null
@@ -0,0 +1,174 @@
+<?php
+
+namespace Tests\User;
+
+use BookStack\Activity\Tools\UserEntityWatchOptions;
+use BookStack\Activity\WatchLevels;
+use Tests\TestCase;
+
+class UserMyAccountTest extends TestCase
+{
+    public function test_index_view()
+    {
+        $resp = $this->asEditor()->get('/my-account');
+        $resp->assertRedirect('/my-account/profile');
+    }
+
+    public function test_views_not_accessible_to_guest_user()
+    {
+        $categories = ['profile', 'auth', 'shortcuts', 'notifications', ''];
+        $this->setSettings(['app-public' => 'true']);
+
+        $this->permissions->grantUserRolePermissions($this->users->guest(), ['receive-notifications']);
+
+        foreach ($categories as $category) {
+            $resp = $this->get('/my-account/' . $category);
+            $resp->assertRedirect('/');
+        }
+    }
+    public function test_interface_shortcuts_updating()
+    {
+        $this->asEditor();
+
+        // View preferences with defaults
+        $resp = $this->get('/my-account/shortcuts');
+        $resp->assertSee('UI Shortcut Preferences');
+
+        $html = $this->withHtml($resp);
+        $html->assertFieldHasValue('enabled', 'false');
+        $html->assertFieldHasValue('shortcut[home_view]', '1');
+
+        // Update preferences
+        $resp = $this->put('/my-account/shortcuts', [
+            'enabled' => 'true',
+            'shortcut' => ['home_view' => 'Ctrl + 1'],
+        ]);
+
+        $resp->assertRedirect('/my-account/shortcuts');
+        $resp->assertSessionHas('success', 'Shortcut preferences have been updated!');
+
+        // View updates to preferences page
+        $resp = $this->get('/my-account/shortcuts');
+        $html = $this->withHtml($resp);
+        $html->assertFieldHasValue('enabled', 'true');
+        $html->assertFieldHasValue('shortcut[home_view]', 'Ctrl + 1');
+    }
+
+    public function test_body_has_shortcuts_component_when_active()
+    {
+        $editor = $this->users->editor();
+        $this->actingAs($editor);
+
+        $this->withHtml($this->get('/'))->assertElementNotExists('body[component="shortcuts"]');
+
+        setting()->putUser($editor, 'ui-shortcuts-enabled', 'true');
+        $this->withHtml($this->get('/'))->assertElementExists('body[component="shortcuts"]');
+    }
+
+    public function test_notification_routes_requires_notification_permission()
+    {
+        $viewer = $this->users->viewer();
+        $resp = $this->actingAs($viewer)->get('/my-account/notifications');
+        $this->assertPermissionError($resp);
+
+        $resp = $this->actingAs($viewer)->get('/my-account/profile');
+        $resp->assertDontSeeText('Notification Preferences');
+
+        $resp = $this->put('/my-account/notifications');
+        $this->assertPermissionError($resp);
+
+        $this->permissions->grantUserRolePermissions($viewer, ['receive-notifications']);
+        $resp = $this->get('/my-account/notifications');
+        $resp->assertOk();
+        $resp->assertSee('Notification Preferences');
+    }
+
+    public function test_notification_preferences_updating()
+    {
+        $editor = $this->users->editor();
+
+        // View preferences with defaults
+        $resp = $this->actingAs($editor)->get('/my-account/notifications');
+        $resp->assertSee('Notification Preferences');
+
+        $html = $this->withHtml($resp);
+        $html->assertFieldHasValue('preferences[comment-replies]', 'false');
+
+        // Update preferences
+        $resp = $this->put('/my-account/notifications', [
+            'preferences' => ['comment-replies' => 'true'],
+        ]);
+
+        $resp->assertRedirect('/my-account/notifications');
+        $resp->assertSessionHas('success', 'Notification preferences have been updated!');
+
+        // View updates to preferences page
+        $resp = $this->get('/my-account/notifications');
+        $html = $this->withHtml($resp);
+        $html->assertFieldHasValue('preferences[comment-replies]', 'true');
+    }
+
+    public function test_notification_preferences_show_watches()
+    {
+        $editor = $this->users->editor();
+        $book = $this->entities->book();
+
+        $options = new UserEntityWatchOptions($editor, $book);
+        $options->updateLevelByValue(WatchLevels::COMMENTS);
+
+        $resp = $this->actingAs($editor)->get('/my-account/notifications');
+        $resp->assertSee($book->name);
+        $resp->assertSee('All Page Updates & Comments');
+
+        $options->updateLevelByValue(WatchLevels::DEFAULT);
+
+        $resp = $this->actingAs($editor)->get('/my-account/notifications');
+        $resp->assertDontSee($book->name);
+        $resp->assertDontSee('All Page Updates & Comments');
+    }
+
+    public function test_notification_preferences_dont_error_on_deleted_items()
+    {
+        $editor = $this->users->editor();
+        $book = $this->entities->book();
+
+        $options = new UserEntityWatchOptions($editor, $book);
+        $options->updateLevelByValue(WatchLevels::COMMENTS);
+
+        $this->actingAs($editor)->delete($book->getUrl());
+        $book->refresh();
+        $this->assertNotNull($book->deleted_at);
+
+        $resp = $this->actingAs($editor)->get('/my-account/notifications');
+        $resp->assertOk();
+        $resp->assertDontSee($book->name);
+    }
+
+    public function test_notification_preferences_not_accessible_to_guest()
+    {
+        $this->setSettings(['app-public' => 'true']);
+        $guest = $this->users->guest();
+        $this->permissions->grantUserRolePermissions($guest, ['receive-notifications']);
+
+        $resp = $this->get('/my-account/notifications');
+        $this->assertPermissionError($resp);
+
+        $resp = $this->put('/my-account/notifications', [
+            'preferences' => ['comment-replies' => 'true'],
+        ]);
+        $this->assertPermissionError($resp);
+    }
+
+    public function test_notification_comment_options_only_exist_if_comments_active()
+    {
+        $resp = $this->asEditor()->get('/my-account/notifications');
+        $resp->assertSee('Notify upon comments');
+        $resp->assertSee('Notify upon replies');
+
+        setting()->put('app-disable-comments', true);
+
+        $resp = $this->get('/my-account/notifications');
+        $resp->assertDontSee('Notify upon comments');
+        $resp->assertDontSee('Notify upon replies');
+    }
+}
index 4a6cba7b32c59ceee59a2f5e060ca7b9d3729a0b..d78ac2ea7df2ec28876406323f88b245e32c65c8 100644 (file)
@@ -8,167 +8,6 @@ use Tests\TestCase;
 
 class UserPreferencesTest extends TestCase
 {
-    public function test_index_view()
-    {
-        $resp = $this->asEditor()->get('/preferences');
-        $resp->assertOk();
-        $resp->assertSee('Interface Keyboard Shortcuts');
-        $resp->assertSee('Edit Profile');
-    }
-
-    public function test_index_view_accessible_but_without_profile_and_notifications_for_guest_user()
-    {
-        $this->setSettings(['app-public' => 'true']);
-        $this->permissions->grantUserRolePermissions($this->users->guest(), ['receive-notifications']);
-        $resp = $this->get('/preferences');
-        $resp->assertOk();
-        $resp->assertSee('Interface Keyboard Shortcuts');
-        $resp->assertDontSee('Edit Profile');
-        $resp->assertDontSee('Notification');
-    }
-    public function test_interface_shortcuts_updating()
-    {
-        $this->asEditor();
-
-        // View preferences with defaults
-        $resp = $this->get('/preferences/shortcuts');
-        $resp->assertSee('Interface Keyboard Shortcuts');
-
-        $html = $this->withHtml($resp);
-        $html->assertFieldHasValue('enabled', 'false');
-        $html->assertFieldHasValue('shortcut[home_view]', '1');
-
-        // Update preferences
-        $resp = $this->put('/preferences/shortcuts', [
-            'enabled' => 'true',
-            'shortcut' => ['home_view' => 'Ctrl + 1'],
-        ]);
-
-        $resp->assertRedirect('/preferences/shortcuts');
-        $resp->assertSessionHas('success', 'Shortcut preferences have been updated!');
-
-        // View updates to preferences page
-        $resp = $this->get('/preferences/shortcuts');
-        $html = $this->withHtml($resp);
-        $html->assertFieldHasValue('enabled', 'true');
-        $html->assertFieldHasValue('shortcut[home_view]', 'Ctrl + 1');
-    }
-
-    public function test_body_has_shortcuts_component_when_active()
-    {
-        $editor = $this->users->editor();
-        $this->actingAs($editor);
-
-        $this->withHtml($this->get('/'))->assertElementNotExists('body[component="shortcuts"]');
-
-        setting()->putUser($editor, 'ui-shortcuts-enabled', 'true');
-        $this->withHtml($this->get('/'))->assertElementExists('body[component="shortcuts"]');
-    }
-
-    public function test_notification_routes_requires_notification_permission()
-    {
-        $viewer = $this->users->viewer();
-        $resp = $this->actingAs($viewer)->get('/preferences/notifications');
-        $this->assertPermissionError($resp);
-
-        $resp = $this->put('/preferences/notifications');
-        $this->assertPermissionError($resp);
-
-        $this->permissions->grantUserRolePermissions($viewer, ['receive-notifications']);
-        $resp = $this->get('/preferences/notifications');
-        $resp->assertOk();
-        $resp->assertSee('Notification Preferences');
-    }
-
-    public function test_notification_preferences_updating()
-    {
-        $editor = $this->users->editor();
-
-        // View preferences with defaults
-        $resp = $this->actingAs($editor)->get('/preferences/notifications');
-        $resp->assertSee('Notification Preferences');
-
-        $html = $this->withHtml($resp);
-        $html->assertFieldHasValue('preferences[comment-replies]', 'false');
-
-        // Update preferences
-        $resp = $this->put('/preferences/notifications', [
-            'preferences' => ['comment-replies' => 'true'],
-        ]);
-
-        $resp->assertRedirect('/preferences/notifications');
-        $resp->assertSessionHas('success', 'Notification preferences have been updated!');
-
-        // View updates to preferences page
-        $resp = $this->get('/preferences/notifications');
-        $html = $this->withHtml($resp);
-        $html->assertFieldHasValue('preferences[comment-replies]', 'true');
-    }
-
-    public function test_notification_preferences_show_watches()
-    {
-        $editor = $this->users->editor();
-        $book = $this->entities->book();
-
-        $options = new UserEntityWatchOptions($editor, $book);
-        $options->updateLevelByValue(WatchLevels::COMMENTS);
-
-        $resp = $this->actingAs($editor)->get('/preferences/notifications');
-        $resp->assertSee($book->name);
-        $resp->assertSee('All Page Updates & Comments');
-
-        $options->updateLevelByValue(WatchLevels::DEFAULT);
-
-        $resp = $this->actingAs($editor)->get('/preferences/notifications');
-        $resp->assertDontSee($book->name);
-        $resp->assertDontSee('All Page Updates & Comments');
-    }
-
-    public function test_notification_preferences_dont_error_on_deleted_items()
-    {
-        $editor = $this->users->editor();
-        $book = $this->entities->book();
-
-        $options = new UserEntityWatchOptions($editor, $book);
-        $options->updateLevelByValue(WatchLevels::COMMENTS);
-
-        $this->actingAs($editor)->delete($book->getUrl());
-        $book->refresh();
-        $this->assertNotNull($book->deleted_at);
-
-        $resp = $this->actingAs($editor)->get('/preferences/notifications');
-        $resp->assertOk();
-        $resp->assertDontSee($book->name);
-    }
-
-    public function test_notification_preferences_not_accessible_to_guest()
-    {
-        $this->setSettings(['app-public' => 'true']);
-        $guest = $this->users->guest();
-        $this->permissions->grantUserRolePermissions($guest, ['receive-notifications']);
-
-        $resp = $this->get('/preferences/notifications');
-        $this->assertPermissionError($resp);
-
-        $resp = $this->put('/preferences/notifications', [
-            'preferences' => ['comment-replies' => 'true'],
-        ]);
-        $this->assertPermissionError($resp);
-    }
-
-    public function test_notification_comment_options_only_exist_if_comments_active()
-    {
-        $resp = $this->asEditor()->get('/preferences/notifications');
-        $resp->assertSee('Notify upon comments');
-        $resp->assertSee('Notify upon replies');
-
-        setting()->put('app-disable-comments', true);
-
-        $resp = $this->get('/preferences/notifications');
-        $resp->assertDontSee('Notify upon comments');
-        $resp->assertDontSee('Notify upon replies');
-    }
-
     public function test_update_sort_preference()
     {
         $editor = $this->users->editor();
Morty Proxy This is a proxified and sanitized view of the page, visit original site.