From 4df6bb0a52cdfc0c924bd289e0ce3e8d77dd9ed7 Mon Sep 17 00:00:00 2001 From: Chris Huynh <82407232+ChrisHuynh333@users.noreply.github.com> Date: Tue, 31 Dec 2024 09:53:08 -0600 Subject: [PATCH] Data Table Bot Accounts: Remove PAT panel on destroy (#860) * some cleanup, fix some tests * fix translations * refactor PAT tables into component * finish refactoring translations * rework logic to handle closing PAT panel upon destroy bot or revoke PAT * cleanup, add tests * cleanup translations * remove whitespace for some styling classes * cleanup tests * further cleanup tests * fix selector * attempt to fix flakes * run normalize * remove commented out code * cleanup tests, add unauthorized test cases * change new_destroy and new_revoke to destroy_confirmation and revoke_confirmation * run normalize --- app/components/bots/table_component.html.erb | 4 +- app/components/bots/table_component.rb | 4 +- .../table_component.html.erb | 5 +- .../personal_access_tokens/table_component.rb | 15 +- app/controllers/concerns/bot_actions.rb | 15 +- .../bot_personal_access_token_actions.rb | 13 +- .../controllers/token_controller.js | 9 +- .../bots/_destroy_confirmation_modal.html.erb | 31 +++ app/views/groups/bots/index.html.erb | 1 + .../_revoke_confirmation_modal.html.erb | 38 ++++ .../bots/_destroy_confirmation_modal.html.erb | 31 +++ app/views/projects/bots/index.html.erb | 1 + .../_revoke_confirmation_modal.html.erb | 38 ++++ config/locales/en.yml | 9 +- config/locales/fr.yml | 9 +- config/routes/group.rb | 2 + config/routes/project.rb | 2 + .../concerns/bot_actions_concern_test.rb | 112 +++++------ ...sonal_access_token_actions_concern_test.rb | 50 +++++ test/system/groups/bots_test.rb | 169 +++++++++++++---- test/system/projects/bots_test.rb | 179 ++++++++++++++---- 21 files changed, 580 insertions(+), 157 deletions(-) create mode 100644 app/views/groups/bots/_destroy_confirmation_modal.html.erb create mode 100644 app/views/groups/bots/personal_access_tokens/_revoke_confirmation_modal.html.erb create mode 100644 app/views/projects/bots/_destroy_confirmation_modal.html.erb create mode 100644 app/views/projects/bots/personal_access_tokens/_revoke_confirmation_modal.html.erb diff --git a/app/components/bots/table_component.html.erb b/app/components/bots/table_component.html.erb index ab63e6c814..0edc6697c6 100644 --- a/app/components/bots/table_component.html.erb +++ b/app/components/bots/table_component.html.erb @@ -52,9 +52,7 @@ t("bots.index.table.actions.destroy"), destroy_path(row), data: { - turbo_method: :delete, - turbo_confirm: t("bots.index.table.actions.destroy_confirmation"), - turbo_frame: "_top", + "turbo-stream": true, }, aria: { label: t("bots.index.table.actions.destroy_aria_label"), diff --git a/app/components/bots/table_component.rb b/app/components/bots/table_component.rb index b477c2764c..cd970ea75a 100644 --- a/app/components/bots/table_component.rb +++ b/app/components/bots/table_component.rb @@ -45,9 +45,9 @@ def new_token_path(bot) def destroy_path(bot) if @namespace.is_a?(Group) - group_bot_path(id: bot.id) + group_bot_destroy_confirmation_path(bot_id: bot.id) elsif @namespace.is_a?(Namespaces::ProjectNamespace) - namespace_project_bot_path(id: bot.id) + namespace_project_bot_destroy_confirmation_path(bot_id: bot.id) end end end diff --git a/app/components/personal_access_tokens/table_component.html.erb b/app/components/personal_access_tokens/table_component.html.erb index c7dc96af69..a556ca4ec1 100644 --- a/app/components/personal_access_tokens/table_component.html.erb +++ b/app/components/personal_access_tokens/table_component.html.erb @@ -29,10 +29,7 @@ <%= link_to( t("personal_access_tokens.table.revoke"), revoke_path(row), - data: { - turbo_method: :delete, - turbo_confirm: t("personal_access_tokens.table.revoke_confirmation"), - }, + data: revoke_data_attributes, class: "font-medium text-blue-600 underline dark:text-blue-500 hover:no-underline cursor-pointer", ) %> diff --git a/app/components/personal_access_tokens/table_component.rb b/app/components/personal_access_tokens/table_component.rb index 0812d72389..39675d9362 100644 --- a/app/components/personal_access_tokens/table_component.rb +++ b/app/components/personal_access_tokens/table_component.rb @@ -25,12 +25,12 @@ def initialize( def revoke_path(token) if @namespace.is_a?(Group) - revoke_group_bot_personal_access_token_path( + revoke_confirmation_group_bot_personal_access_token_path( bot_id: @bot_account.id, id: token.id ) elsif @namespace.is_a?(Namespaces::ProjectNamespace) - revoke_namespace_project_bot_personal_access_token_path( + revoke_confirmation_namespace_project_bot_personal_access_token_path( bot_id: @bot_account.id, id: token.id ) @@ -38,5 +38,16 @@ def revoke_path(token) revoke_profile_personal_access_token_path(id: token.id) end end + + def revoke_data_attributes + if @namespace + { 'turbo-stream': true } + else + { + turbo_method: :delete, + turbo_confirm: t('personal_access_tokens.table.revoke_confirmation') + } + end + end end end diff --git a/app/controllers/concerns/bot_actions.rb b/app/controllers/concerns/bot_actions.rb index 192a6c97e4..fced0d5f77 100644 --- a/app/controllers/concerns/bot_actions.rb +++ b/app/controllers/concerns/bot_actions.rb @@ -7,7 +7,7 @@ module BotActions included do before_action proc { namespace } before_action proc { access_levels } - before_action proc { bot_account }, only: %i[destroy] + before_action proc { bot_account }, only: %i[destroy destroy_confirmation] before_action proc { bot_type }, only: %i[create] before_action proc { bot_accounts } end @@ -53,6 +53,16 @@ def create # rubocop:disable Metrics/MethodLength end end + def destroy_confirmation + authorize! @namespace, to: :destroy_bot_accounts? + render turbo_stream: turbo_stream.update('bot_modal', + partial: 'destroy_confirmation_modal', + locals: { + open: true, + bot_account: @bot_account + }), status: :ok + end + def destroy Bots::DestroyService.new(@bot_account, current_user).execute respond_to do |format| @@ -74,7 +84,8 @@ def destroy private def bot_account - @bot_account = @namespace.namespace_bots.find_by(id: params[:id]) || not_found + id = params[:bot_id] || params[:id] + @bot_account = @namespace.namespace_bots.find_by(id:) || not_found end def access_levels diff --git a/app/controllers/concerns/bot_personal_access_token_actions.rb b/app/controllers/concerns/bot_personal_access_token_actions.rb index f0a0267141..1104ca5806 100644 --- a/app/controllers/concerns/bot_personal_access_token_actions.rb +++ b/app/controllers/concerns/bot_personal_access_token_actions.rb @@ -8,7 +8,7 @@ module BotPersonalAccessTokenActions before_action proc { namespace } before_action proc { bot_account } before_action proc { personal_access_tokens }, only: %i[index revoke] - before_action proc { personal_access_token }, only: %i[revoke] + before_action proc { personal_access_token }, only: %i[revoke revoke_confirmation] before_action proc { bot_accounts } end @@ -54,9 +54,18 @@ def create # rubocop:disable Metrics/MethodLength end end - def revoke + def revoke_confirmation authorize! @namespace, to: :revoke_bot_personal_access_token? + render turbo_stream: turbo_stream.update('token_dialog', + partial: 'revoke_confirmation_modal', + locals: { + open: true, + personal_access_token: @personal_access_token, + bot_account: @bot_account + }), status: :ok + end + def revoke respond_to do |format| format.turbo_stream do if @personal_access_token.revoke! diff --git a/app/javascript/controllers/token_controller.js b/app/javascript/controllers/token_controller.js index 847cc103cd..9db6b63ba7 100644 --- a/app/javascript/controllers/token_controller.js +++ b/app/javascript/controllers/token_controller.js @@ -26,7 +26,7 @@ export default class extends Controller { this.viewTarget.classList.add("hidden"); this.inputTarget.value = Array.prototype.join.call( { length: this.itemValue.length }, - "*" + "*", ); } else { this.hideTarget.classList.add("hidden"); @@ -35,4 +35,11 @@ export default class extends Controller { } this.visible = !this.visible; } + + removeTokenPanel() { + let panel = document.getElementById("access-token-section"); + if (panel) { + panel.remove(); + } + } } diff --git a/app/views/groups/bots/_destroy_confirmation_modal.html.erb b/app/views/groups/bots/_destroy_confirmation_modal.html.erb new file mode 100644 index 0000000000..eb7c5a3f00 --- /dev/null +++ b/app/views/groups/bots/_destroy_confirmation_modal.html.erb @@ -0,0 +1,31 @@ +<%= viral_dialog(open: open) do |dialog| %> + <%= dialog.with_header(title: t("bots.destroy_confirmation.title")) %> + <%= dialog.with_section do %> + + <%= turbo_frame_tag("deletion-alert") %> + +
+

+ <%= t("bots.destroy_confirmation.description", bot_name: bot_account.user.email) %> +

+ <%= form_for(:deletion, url: group_bot_path(id: bot_account.id), method: :delete, + data: { + turbo_frame: "_top", + controller: "token", + action:"turbo:submit-end->viral--dialog#close" + } + ) do |form| %> + <%= form.submit t("bots.destroy_confirmation.submit_button"), + class: + "button text-sm px-5 py-2.5 text-white bg-red-700 border-red-800 focus:outline-none hover:bg-red-800 focus:ring-red-300 dark:focus:ring-red-700 dark:bg-red-600 dark:text-white dark:border-red-600 dark:hover:bg-red-700", + data: { + action: "click->token#removeTokenPanel", + } %> + <% end %> +
+ <% end %> +<% end %> diff --git a/app/views/groups/bots/index.html.erb b/app/views/groups/bots/index.html.erb index c7675bf8b9..e340bd0077 100644 --- a/app/views/groups/bots/index.html.erb +++ b/app/views/groups/bots/index.html.erb @@ -1,4 +1,5 @@ <%= turbo_refreshes_with method: :morph, scroll: :preserve %> +<%= turbo_frame_tag "token_dialog" %> <%= render Viral::PageHeaderComponent.new(title: t('.title'), subtitle: t('.subtitle')) do |component| %> <%= component.with_icon(name: "users", classes: "h-14 w-14 text-primary-700") %> <%= component.with_buttons do %> diff --git a/app/views/groups/bots/personal_access_tokens/_revoke_confirmation_modal.html.erb b/app/views/groups/bots/personal_access_tokens/_revoke_confirmation_modal.html.erb new file mode 100644 index 0000000000..3ecffe37f7 --- /dev/null +++ b/app/views/groups/bots/personal_access_tokens/_revoke_confirmation_modal.html.erb @@ -0,0 +1,38 @@ +<%= viral_dialog(open: open) do |dialog| %> + <%= dialog.with_header(title: t("personal_access_tokens.revoke_confirmation.title")) %> + <%= dialog.with_section do %> + + <%= turbo_frame_tag("deletion-alert") %> + +
+

+ <%= t( + "personal_access_tokens.revoke_confirmation.description", + token_name: personal_access_token.name, + bot_name: bot_account.user.email, + ) %> +

+ <%= form_for(:deletion, url: revoke_group_bot_personal_access_token_path( + bot_id: @bot_account.id, + id: personal_access_token.id + ), method: :delete, + data: { + turbo_frame: "_top", + controller: "token", + action:"turbo:submit-end->viral--dialog#close" + } + ) do |form| %> + <%= form.submit t("personal_access_tokens.revoke_confirmation.submit_button"), + class: + "button text-sm px-5 py-2.5 text-white bg-red-700 border-red-800 focus:outline-none hover:bg-red-800 focus:ring-red-300 dark:focus:ring-red-700 dark:bg-red-600 dark:text-white dark:border-red-600 dark:hover:bg-red-700", + data: { + action: "click->token#removeTokenPanel", + } %> + <% end %> +
+ <% end %> +<% end %> diff --git a/app/views/projects/bots/_destroy_confirmation_modal.html.erb b/app/views/projects/bots/_destroy_confirmation_modal.html.erb new file mode 100644 index 0000000000..e56b3e5c92 --- /dev/null +++ b/app/views/projects/bots/_destroy_confirmation_modal.html.erb @@ -0,0 +1,31 @@ +<%= viral_dialog(open: open) do |dialog| %> + <%= dialog.with_header(title: t("bots.destroy_confirmation.title")) %> + <%= dialog.with_section do %> + + <%= turbo_frame_tag("deletion-alert") %> + +
+

+ <%= t("bots.destroy_confirmation.description", bot_name: bot_account.user.email) %> +

+ <%= form_for(:deletion, url: namespace_project_bot_path(id: bot_account.id), method: :delete, + data: { + turbo_frame: "_top", + controller: "token", + action:"turbo:submit-end->viral--dialog#close" + } + ) do |form| %> + <%= form.submit t("bots.destroy_confirmation.submit_button"), + class: + "button text-sm px-5 py-2.5 text-white bg-red-700 border-red-800 focus:outline-none hover:bg-red-800 focus:ring-red-300 dark:focus:ring-red-700 dark:bg-red-600 dark:text-white dark:border-red-600 dark:hover:bg-red-700", + data: { + action: "click->token#removeTokenPanel", + } %> + <% end %> +
+ <% end %> +<% end %> diff --git a/app/views/projects/bots/index.html.erb b/app/views/projects/bots/index.html.erb index 612d162dda..77273159cd 100644 --- a/app/views/projects/bots/index.html.erb +++ b/app/views/projects/bots/index.html.erb @@ -1,4 +1,5 @@ <%= turbo_refreshes_with method: :morph, scroll: :preserve %> +<%= turbo_frame_tag "token_dialog" %> <%= render Viral::PageHeaderComponent.new(title: t('.title'), subtitle: t('.subtitle')) do |component| %> <%= component.with_icon(name: "users", classes: "h-14 w-14 text-primary-700") %> <% if allowed_to?(:create_bot_accounts?, @namespace) %> diff --git a/app/views/projects/bots/personal_access_tokens/_revoke_confirmation_modal.html.erb b/app/views/projects/bots/personal_access_tokens/_revoke_confirmation_modal.html.erb new file mode 100644 index 0000000000..46c39b5fbe --- /dev/null +++ b/app/views/projects/bots/personal_access_tokens/_revoke_confirmation_modal.html.erb @@ -0,0 +1,38 @@ +<%= viral_dialog(open: open) do |dialog| %> + <%= dialog.with_header(title: t("personal_access_tokens.revoke_confirmation.title")) %> + <%= dialog.with_section do %> + + <%= turbo_frame_tag("deletion-alert") %> + +
+

+ <%= t( + "personal_access_tokens.revoke_confirmation.description", + token_name: personal_access_token.name, + bot_name: bot_account.user.email, + ) %> +

+ <%= form_for(:deletion, url: revoke_namespace_project_bot_personal_access_token_path( + bot_id: bot_account.id, + id: personal_access_token.id + ), method: :delete, + data: { + turbo_frame: "_top", + controller: "token", + action:"turbo:submit-end->viral--dialog#close" + } + ) do |form| %> + <%= form.submit t("personal_access_tokens.revoke_confirmation.submit_button"), + class: + "button text-sm px-5 py-2.5 text-white bg-red-700 border-red-800 focus:outline-none hover:bg-red-800 focus:ring-red-300 dark:focus:ring-red-700 dark:bg-red-600 dark:text-white dark:border-red-600 dark:hover:bg-red-700", + data: { + action: "click->token#removeTokenPanel", + } %> + <% end %> +
+ <% end %> +<% end %> diff --git a/config/locales/en.yml b/config/locales/en.yml index 9ec52159e4..edad40fad6 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -312,6 +312,10 @@ en: label: Scopes read_api: Grants read access to the API. bots: + destroy_confirmation: + description: This action will permanently delete the bot account and will remove any existing memberships for the bot account. Are you sure you want to permanently delete bot account %{bot_name}? + submit_button: Confirm + title: Delete Bot index: pagy_item: Bot Accounts table: @@ -325,7 +329,6 @@ en: actions: destroy: Delete destroy_aria_label: Delete bot account - destroy_confirmation: This action will permanently delete the bot account and will remove any existing memberships for the bot account. Are you sure you want to permanently delete this bot account? generate_new_token: Generate new token generate_new_token_aria_label: Generate a new personal access token for bot account empty_state: @@ -944,6 +947,10 @@ en: helper: A custom name will make it easier to search for this in the future. label: Name (Optional) personal_access_tokens: + revoke_confirmation: + description: Are you sure you'd like to remove access token '%{token_name}' from bot %{bot_name}? + submit_button: Confirm + title: Revoke personal access token table: header: action: Action diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 9c9cda4601..7ac0599d10 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -312,6 +312,10 @@ fr: label: Scopes read_api: Grants read access to the API. bots: + destroy_confirmation: + description: This action will permanently delete the bot account and will remove any existing memberships for the bot account. Are you sure you want to permanently delete bot account %{bot_name}? + submit_button: Confirm + title: Delete Bot index: pagy_item: Bot Accounts table: @@ -325,7 +329,6 @@ fr: actions: destroy: Delete destroy_aria_label: Delete bot account - destroy_confirmation: This action will permanently delete the bot account and will remove any existing memberships for the bot account. Are you sure you want to permanently delete this bot account? generate_new_token: Generate new token generate_new_token_aria_label: Generate a new personal access token for bot account empty_state: @@ -944,6 +947,10 @@ fr: helper: A custom name will make it easier to search for this in the future. label: Name (Optional) personal_access_tokens: + revoke_confirmation: + description: Are you sure you'd like to remove access token '%{token_name}' from bot %{bot_name}? + submit_button: Confirm + title: Revoke personal access token table: header: action: Action diff --git a/config/routes/group.rb b/config/routes/group.rb index dfa60aab2b..63a9f6bd1a 100644 --- a/config/routes/group.rb +++ b/config/routes/group.rb @@ -18,8 +18,10 @@ resources :members, only: %i[create destroy index new update] resources :bots, only: %i[create destroy index new] do + get :destroy_confirmation resources :personal_access_tokens, module: :bots, only: %i[index new create] do member do + get :revoke_confirmation delete :revoke end end diff --git a/config/routes/project.rb b/config/routes/project.rb index d329342ed2..f44387020d 100644 --- a/config/routes/project.rb +++ b/config/routes/project.rb @@ -23,9 +23,11 @@ end resources :bots, only: %i[create destroy index new] do + get :destroy_confirmation resources :personal_access_tokens, module: :bots, only: %i[index new create] do member do delete :revoke + get :revoke_confirmation end end end diff --git a/test/controllers/concerns/bot_actions_concern_test.rb b/test/controllers/concerns/bot_actions_concern_test.rb index 1711d54741..e5aa9774fe 100644 --- a/test/controllers/concerns/bot_actions_concern_test.rb +++ b/test/controllers/concerns/bot_actions_concern_test.rb @@ -5,35 +5,28 @@ class BotActionsConcernTest < ActionDispatch::IntegrationTest include Devise::Test::IntegrationHelpers - test 'project bot accounts index' do + setup do sign_in users(:john_doe) + @namespace = groups(:group_one) + @project = projects(:project1) + @project_bot = namespace_bots(:project1_bot0) + @group_bot = namespace_bots(:group1_bot0) + end - namespace = groups(:group_one) - project = projects(:project1) - - get namespace_project_bots_path(namespace, project) + test 'project bot accounts index' do + get namespace_project_bots_path(@namespace, @project) assert_response :success end test 'new project bot account' do - sign_in users(:john_doe) - - namespace = groups(:group_one) - project = projects(:project1) - - get new_namespace_project_bot_path(namespace, project, format: :turbo_stream) + get new_namespace_project_bot_path(@namespace, @project, format: :turbo_stream) assert_response :success end test 'project bot account create' do - sign_in users(:john_doe) - - namespace = groups(:group_one) - project = projects(:project1) - - post namespace_project_bots_path(namespace, project, format: :turbo_stream), + post namespace_project_bots_path(@namespace, @project, format: :turbo_stream), params: { bot: { token_name: 'newtesttoken', access_level: Member::AccessLevel::UPLOADER, @@ -44,12 +37,7 @@ class BotActionsConcernTest < ActionDispatch::IntegrationTest end test 'project bot account create error' do - sign_in users(:john_doe) - - namespace = groups(:group_one) - project = projects(:project1) - - post namespace_project_bots_path(namespace, project, format: :turbo_stream), + post namespace_project_bots_path(@namespace, @project, format: :turbo_stream), params: { bot: { access_level: Member::AccessLevel::UPLOADER, scopes: ['read_api'] @@ -59,55 +47,33 @@ class BotActionsConcernTest < ActionDispatch::IntegrationTest end test 'project bot account destroy' do - sign_in users(:john_doe) - - namespace_bot = namespace_bots(:project1_bot0) - - namespace = groups(:group_one) - project = projects(:project1) - - delete namespace_project_bot_path(namespace, project, id: namespace_bot.id, format: :turbo_stream) + delete namespace_project_bot_path(@namespace, @project, id: @project_bot.id, format: :turbo_stream) assert_response :redirect end test 'project bot account destroy error' do - sign_in users(:john_doe) - - namespace = groups(:group_one) - project = projects(:project2) + project2 = projects(:project2) - delete namespace_project_bot_path(namespace, project, id: 0, format: :turbo_stream) + delete namespace_project_bot_path(@namespace, project2, id: 0, format: :turbo_stream) assert_response :not_found end test 'group bot accounts index' do - sign_in users(:john_doe) - - namespace = groups(:group_one) - - get group_bots_path(namespace) + get group_bots_path(@namespace) assert_response :success end test 'new group bot account' do - sign_in users(:john_doe) - - namespace = groups(:group_one) - - get new_group_bot_path(namespace, format: :turbo_stream) + get new_group_bot_path(@namespace, format: :turbo_stream) assert_response :success end test 'group bot account create' do - sign_in users(:john_doe) - - namespace = groups(:group_one) - - post group_bots_path(namespace, format: :turbo_stream), + post group_bots_path(@namespace, format: :turbo_stream), params: { bot: { token_name: 'newtesttoken', access_level: Member::AccessLevel::UPLOADER, @@ -118,11 +84,7 @@ class BotActionsConcernTest < ActionDispatch::IntegrationTest end test 'group bot account create error' do - sign_in users(:john_doe) - - namespace = groups(:group_one) - - post group_bots_path(namespace, format: :turbo_stream), + post group_bots_path(@namespace, format: :turbo_stream), params: { bot: { access_level: Member::AccessLevel::UPLOADER, scopes: ['read_api'] @@ -132,24 +94,42 @@ class BotActionsConcernTest < ActionDispatch::IntegrationTest end test 'group bot account destroy' do - sign_in users(:john_doe) + delete group_bot_path(@namespace, id: @group_bot.id, format: :turbo_stream) - namespace_bot = namespace_bots(:group1_bot0) + assert_response :redirect + end - namespace = groups(:group_one) + test 'group bot account destroy error' do + delete group_bot_path(@namespace, id: 0, format: :turbo_stream) - delete group_bot_path(namespace, id: namespace_bot.id, format: :turbo_stream) + assert_response :not_found + end - assert_response :redirect + test 'destroy_confirmation in group' do + get group_bot_destroy_confirmation_path(@namespace, bot_id: @group_bot.id) + + assert_response :success end - test 'group bot account destroy error' do - sign_in users(:john_doe) + test 'unauthorized destroy_confirmation in group' do + sign_in users(:ryan_doe) - namespace = groups(:group_one) + get group_bot_destroy_confirmation_path(@namespace, bot_id: @group_bot.id) - delete group_bot_path(namespace, id: 0, format: :turbo_stream) + assert_response :unauthorized + end - assert_response :not_found + test 'destroy_confirmation in project' do + get namespace_project_bot_destroy_confirmation_path(@namespace, @project, bot_id: @project_bot.id) + + assert_response :success + end + + test 'unauthorized destroy_confirmation in project' do + sign_in users(:ryan_doe) + + get namespace_project_bot_destroy_confirmation_path(@namespace, @project, bot_id: @project_bot.id) + + assert_response :unauthorized end end diff --git a/test/controllers/concerns/bot_personal_access_token_actions_concern_test.rb b/test/controllers/concerns/bot_personal_access_token_actions_concern_test.rb index 8f5cd21420..8b41650daf 100644 --- a/test/controllers/concerns/bot_personal_access_token_actions_concern_test.rb +++ b/test/controllers/concerns/bot_personal_access_token_actions_concern_test.rb @@ -244,4 +244,54 @@ class BotPersonalAcessTokenActionsConcernTest < ActionDispatch::IntegrationTest assert_response :unprocessable_entity end + + test 'can open new revoke in group' do + sign_in users(:john_doe) + + namespace = groups(:group_one) + + namespace_bot = namespace_bots(:group1_bot0) + token = personal_access_tokens(:user_group_bot_account0_valid_pat) + get revoke_confirmation_group_bot_personal_access_token_path( + namespace, + bot_id: namespace_bot.id, + id: token.id + ) + + assert_response :success + end + + test 'cannot open new revoke in group due to permissions' do + sign_in users(:micha_doe) + + namespace = groups(:group_one) + namespace_bot = namespace_bots(:group1_bot0) + token = personal_access_tokens(:user_group_bot_account0_valid_pat) + + get revoke_confirmation_group_bot_personal_access_token_path( + namespace, + bot_id: namespace_bot.id, + id: token.id + ) + + assert_response :unauthorized + end + + test 'can open new revoke in project' do + sign_in users(:john_doe) + + namespace = groups(:group_one) + project = projects(:project1) + + namespace_bot = namespace_bots(:project1_bot0) + token = personal_access_tokens(:user_bot_account0_valid_pat) + + get revoke_confirmation_namespace_project_bot_personal_access_token_path( + namespace, + project, + bot_id: namespace_bot.id, + id: token.id + ) + assert_response :success + end end diff --git a/test/system/groups/bots_test.rb b/test/system/groups/bots_test.rb index 8e707fb905..279f24b09d 100644 --- a/test/system/groups/bots_test.rb +++ b/test/system/groups/bots_test.rb @@ -7,9 +7,10 @@ class BotsTest < ApplicationSystemTestCase header_row_count = 1 def setup - @user = users(:john_doe) - login_as @user + login_as users(:john_doe) @namespace = groups(:group_one) + @group_bot = namespace_bots(:group1_bot0) + @group_bot_active_tokens = @group_bot.user.personal_access_tokens.active end test 'can see a table listing of group bot accounts' do @@ -72,7 +73,7 @@ def setup assert_selector 'h1', text: I18n.t(:'groups.bots.index.bot_listing.new_bot_modal.title') assert_selector 'p', text: I18n.t(:'groups.bots.index.bot_listing.new_bot_modal.description') - fill_in 'Token Name', with: 'Uploader' + fill_in I18n.t('groups.bots.index.bot_listing.new_bot_modal.token_name'), with: 'Uploader' find('#bot_access_level').find('option', text: I18n.t('activerecord.models.member.access_level.analyst')).select_option @@ -115,7 +116,7 @@ def setup assert_selector 'h1', text: I18n.t(:'groups.bots.index.bot_listing.new_bot_modal.title') assert_selector 'p', text: I18n.t(:'groups.bots.index.bot_listing.new_bot_modal.description') - fill_in 'Token Name', with: 'Uploader' + fill_in I18n.t('groups.bots.index.bot_listing.new_bot_modal.token_name'), with: 'Uploader' find('#bot_access_level').find('option', text: I18n.t('activerecord.models.member.access_level.analyst')).select_option @@ -140,8 +141,8 @@ def setup end end - within('#turbo-confirm[open]') do - click_button 'Confirm' + within('dialog') do + click_button I18n.t('bots.destroy_confirmation.submit_button') end assert_text I18n.t(:'concerns.bot_actions.destroy.success') @@ -151,15 +152,12 @@ def setup end test 'can view personal access tokens for bot account' do - namespace_bot = namespace_bots(:group1_bot0) - active_personal_tokens = namespace_bot.user.personal_access_tokens.active - visit group_bots_path(@namespace) assert_selector 'h1', text: I18n.t(:'groups.bots.index.title') assert_selector 'p', text: I18n.t(:'groups.bots.index.subtitle') - within "tr[id='#{namespace_bot.id}']" do - click_link active_personal_tokens.count.to_s + within "tr[id='#{@group_bot.id}']" do + click_link @group_bot_active_tokens.count.to_s end within('dialog') do @@ -167,12 +165,12 @@ def setup assert_selector 'p', text: I18n.t( 'groups.bots.index.personal_access_tokens_listing_modal.description', - bot_account: namespace_bot.user.email + bot_account: @group_bot.user.email ) within('table') do assert_selector 'tr', count: 2 - token = active_personal_tokens.first + token = @group_bot_active_tokens.first within "tr[id='#{token.id}']" do assert_equal 'Valid PAT0', token.name @@ -195,13 +193,11 @@ def setup end test 'can generate a new personal access token for bot account' do - namespace_bot = namespace_bots(:group1_bot0) - visit group_bots_path(@namespace) assert_selector 'h1', text: I18n.t(:'groups.bots.index.title') assert_selector 'p', text: I18n.t(:'groups.bots.index.subtitle') - within "tr[id='#{namespace_bot.id}']" do + within "tr[id='#{@group_bot.id}']" do click_link 'Generate new token' end @@ -211,9 +207,9 @@ def setup ) assert_text I18n.t('groups.bots.index.bot_listing.generate_personal_access_token_modal.description', - bot_account: namespace_bot.user.email) + bot_account: @group_bot.user.email) - fill_in 'Token Name', with: 'Newest token' + fill_in I18n.t('groups.bots.index.bot_listing.new_bot_modal.token_name'), with: 'Newest token' all('input[type=checkbox]').each(&:click) @@ -221,24 +217,21 @@ def setup end within('#access-token-section') do - bot_account_name = namespace_bot.user.email - assert_selector 'h2', text: I18n.t('groups.bots.index.access_token_section.label', bot_name: bot_account_name) + assert_selector 'h2', text: I18n.t('groups.bots.index.access_token_section.label', + bot_name: @group_bot.user.email) assert_selector 'p', text: I18n.t('groups.bots.index.access_token_section.description') assert_selector 'button', text: I18n.t('components.token.copy') end end test 'can revoke a personal access token' do - namespace_bot = namespace_bots(:group1_bot0) - active_personal_tokens = namespace_bot.user.personal_access_tokens.active - token = nil - + token = @group_bot_active_tokens.first visit group_bots_path(@namespace) assert_selector 'h1', text: I18n.t(:'groups.bots.index.title') assert_selector 'p', text: I18n.t(:'groups.bots.index.subtitle') - within "tr[id='#{namespace_bot.id}']" do - click_link active_personal_tokens.count.to_s + within "tr[id='#{@group_bot.id}']" do + click_link @group_bot_active_tokens.count.to_s end within('dialog') do @@ -246,28 +239,138 @@ def setup assert_selector 'p', text: I18n.t( 'groups.bots.index.personal_access_tokens_listing_modal.description', - bot_account: namespace_bot.user.email + bot_account: @group_bot.user.email ) within('table') do assert_selector 'tr', count: 2 - token = active_personal_tokens.first within "tr[id='#{token.id}']" do - click_link 'Revoke' + click_link I18n.t('personal_access_tokens.table.revoke') end end end - within('#turbo-confirm[open]') do - click_button 'Confirm' - end - within('dialog') do + click_button I18n.t('personal_access_tokens.revoke_confirmation.submit_button') within('#personal-access-token-alert') do assert_text I18n.t('concerns.bot_personal_access_token_actions.revoke.success', pat_name: token.name) end end end + + test 'PAT panel removed after personal access token revoke' do + ### SETUP START ### + + visit group_bots_path(@namespace) + # PAT panel is not present + assert_no_selector '#access-token-section div' + # create new PAT to render PAT panel + within "tr[id='#{@group_bot.id}']" do + click_link I18n.t('bots.index.table.actions.generate_new_token') + end + + within('#dialog') do + assert_text I18n.t( + 'groups.bots.index.bot_listing.generate_personal_access_token_modal.title' + ) + + assert_text I18n.t('groups.bots.index.bot_listing.generate_personal_access_token_modal.description', + bot_account: @group_bot.user.email) + + fill_in I18n.t('groups.bots.index.bot_listing.new_bot_modal.token_name'), with: 'Newest token' + + all('input[type=checkbox]').each(&:click) + + click_button I18n.t('groups.bots.index.bot_listing.generate_personal_access_token_modal.submit') + end + + # PAT panel now present + assert_selector '#access-token-section div' + # additional asserts to prevent flakes + within('#access-token-section') do + assert_text I18n.t('groups.bots.index.access_token_section.label', bot_name: @group_bot.user.email) + assert_text I18n.t('groups.bots.index.access_token_section.description') + end + ### SETUP END ### + + ### ACTIONS START ### + # additional asserts to prevent flakes + assert_selector '#bots-table' + assert_selector '#bots-table table tbody tr', count: 20 + within "tr[id='#{@group_bot.id}']" do + # click active tokens number + click_link @group_bot_active_tokens.count.to_s + end + + # bot's current PATs dialog + within('#dialog') do + # revoke a PAT + within("table tbody tr[id='#{@group_bot_active_tokens.first.id}']") do + click_link I18n.t('personal_access_tokens.table.revoke') + end + + click_button I18n.t('personal_access_tokens.revoke_confirmation.submit_button') + end + ### ACTIONS END ### + + ### VERIFY START ### + # PAT panel no longer present + assert_no_selector '#access-token-section div' + ### VERIFY END ### + end + + test 'PAT panel removed after bot destroy' do + ### SETUP START ### + visit group_bots_path(@namespace) + # PAT panel is not present + assert_no_selector '#access-token-section div' + # create new PAT to render PAT panel + within "tr[id='#{@group_bot.id}']" do + click_link I18n.t('bots.index.table.actions.generate_new_token') + end + + within('#dialog') do + assert_text I18n.t( + 'groups.bots.index.bot_listing.generate_personal_access_token_modal.title' + ) + + assert_text I18n.t('groups.bots.index.bot_listing.generate_personal_access_token_modal.description', + bot_account: @group_bot.user.email) + + fill_in I18n.t('groups.bots.index.bot_listing.new_bot_modal.token_name'), with: 'Newest token' + + all('input[type=checkbox]').each(&:click) + + click_button I18n.t('groups.bots.index.bot_listing.generate_personal_access_token_modal.submit') + end + + # PAT panel now present + assert_selector '#access-token-section div' + # additional asserts to prevent flakes + within('#access-token-section') do + assert_text I18n.t('groups.bots.index.access_token_section.label', bot_name: @group_bot.user.email) + assert_text I18n.t('groups.bots.index.access_token_section.description') + end + ### SETUP END ### + + ### ACTIONS START ### + # additional asserts to prevent flakes + assert_selector '#bots-table' + assert_selector '#bots-table table tbody tr', count: 20 + within('#bots-table table tbody tr:first-child td:last-child') do + # destroy bot + click_link I18n.t(:'bots.index.table.actions.destroy') + end + + # confirm destroy bot + click_button I18n.t('bots.destroy_confirmation.submit_button') + ### ACTIONS END ### + + ### VERIFY START ### + # PAT panel no longer present + assert_no_selector '#access-token-section div' + ### VERIFY END ### + end end end diff --git a/test/system/projects/bots_test.rb b/test/system/projects/bots_test.rb index 0a9c5d26b3..d5063a32c1 100644 --- a/test/system/projects/bots_test.rb +++ b/test/system/projects/bots_test.rb @@ -7,10 +7,12 @@ class BotsTest < ApplicationSystemTestCase header_row_count = 1 def setup - @user = users(:john_doe) - login_as @user + login_as users(:john_doe) @namespace = groups(:group_one) @project = projects(:project1) + @project2 = projects(:project2) + @project_bot = namespace_bots(:project1_bot0) + @project_bot_active_tokens = @project_bot.user.personal_access_tokens.active end test 'can see a table listing of project bot accounts' do @@ -35,8 +37,7 @@ def setup end test 'can see an empty state for table listing of project bot accounts' do - project = projects(:project2) - visit namespace_project_bots_path(@namespace, project) + visit namespace_project_bots_path(@namespace, @project2) assert_selector 'h1', text: I18n.t(:'projects.bots.index.title') assert_selector 'p', text: I18n.t(:'projects.bots.index.subtitle') @@ -53,8 +54,7 @@ def setup end test 'can create a new project bot account' do - project = projects(:project2) - visit namespace_project_bots_path(@namespace, project) + visit namespace_project_bots_path(@namespace, @project2) assert_selector 'h1', text: I18n.t(:'projects.bots.index.title') assert_selector 'p', text: I18n.t(:'projects.bots.index.subtitle') @@ -74,7 +74,7 @@ def setup assert_selector 'h1', text: I18n.t(:'projects.bots.index.bot_listing.new_bot_modal.title') assert_selector 'p', text: I18n.t(:'projects.bots.index.bot_listing.new_bot_modal.description') - fill_in 'Token Name', with: 'Uploader' + fill_in I18n.t('projects.bots.index.bot_listing.new_bot_modal.token_name'), with: 'Uploader' find('#bot_access_level').find('option', text: I18n.t('activerecord.models.member.access_level.analyst')).select_option @@ -86,7 +86,7 @@ def setup assert_no_selector 'dialog[open]' within('#access-token-section') do - bot_account_name = project.namespace.bots.last.email + bot_account_name = @project2.namespace.bots.last.email assert_selector 'h2', text: I18n.t('projects.bots.index.access_token_section.label', bot_name: bot_account_name) assert_selector 'p', text: I18n.t('projects.bots.index.access_token_section.description') assert_selector 'button', text: I18n.t('components.token.copy') @@ -96,8 +96,7 @@ def setup end test 'can\'t create a new project bot account without selecting scopes' do - project = projects(:project2) - visit namespace_project_bots_path(@namespace, project) + visit namespace_project_bots_path(@namespace, @project2) assert_selector 'h1', text: I18n.t(:'projects.bots.index.title') assert_selector 'p', text: I18n.t(:'projects.bots.index.subtitle') @@ -117,7 +116,7 @@ def setup assert_selector 'h1', text: I18n.t(:'projects.bots.index.bot_listing.new_bot_modal.title') assert_selector 'p', text: I18n.t(:'projects.bots.index.bot_listing.new_bot_modal.description') - fill_in 'Token Name', with: 'Uploader' + fill_in I18n.t('projects.bots.index.bot_listing.new_bot_modal.token_name'), with: 'Uploader' find('#bot_access_level').find('option', text: I18n.t('activerecord.models.member.access_level.analyst')).select_option @@ -142,8 +141,8 @@ def setup end end - within('#turbo-confirm[open]') do - click_button 'Confirm' + within('dialog') do + click_button I18n.t('bots.destroy_confirmation.submit_button') end assert_text I18n.t(:'concerns.bot_actions.destroy.success') @@ -153,15 +152,14 @@ def setup end test 'can view personal access tokens for bot account' do - namespace_bot = namespace_bots(:project1_bot0) - active_personal_tokens = namespace_bot.user.personal_access_tokens.active - + token = @project_bot_active_tokens.first visit namespace_project_bots_path(@namespace, @project) + assert_selector 'h1', text: I18n.t(:'projects.bots.index.title') assert_selector 'p', text: I18n.t(:'projects.bots.index.subtitle') - within "tr[id='#{namespace_bot.id}']" do - click_link active_personal_tokens.count.to_s + within "tr[id='#{@project_bot.id}']" do + click_link @project_bot_active_tokens.count.to_s end within('dialog') do @@ -169,12 +167,11 @@ def setup assert_selector 'p', text: I18n.t( 'projects.bots.index.personal_access_tokens_listing_modal.description', - bot_account: namespace_bot.user.email + bot_account: @project_bot.user.email ) within('table') do assert_selector 'tr', count: 2 - token = active_personal_tokens.first within "tr[id='#{token.id}']" do assert_equal 'Valid PAT0', token.name @@ -197,13 +194,11 @@ def setup end test 'can generate a new personal access token for bot account' do - namespace_bot = namespace_bots(:project1_bot0) - visit namespace_project_bots_path(@namespace, @project) assert_selector 'h1', text: I18n.t(:'projects.bots.index.title') assert_selector 'p', text: I18n.t(:'projects.bots.index.subtitle') - within "tr[id='#{namespace_bot.id}']" do + within "tr[id='#{@project_bot.id}']" do click_link 'Generate new token' end @@ -213,9 +208,9 @@ def setup ) assert_text I18n.t('projects.bots.index.bot_listing.generate_personal_access_token_modal.description', - bot_account: namespace_bot.user.email) + bot_account: @project_bot.user.email) - fill_in 'Token Name', with: 'Newest token' + fill_in I18n.t('projects.bots.index.bot_listing.new_bot_modal.token_name'), with: 'Newest token' all('input[type=checkbox]').each(&:click) @@ -223,7 +218,7 @@ def setup end within('#access-token-section') do - bot_account_name = namespace_bot.user.email + bot_account_name = @project_bot.user.email assert_selector 'h2', text: I18n.t('projects.bots.index.access_token_section.label', bot_name: bot_account_name) assert_selector 'p', text: I18n.t('projects.bots.index.access_token_section.description') assert_selector 'button', text: I18n.t('components.token.copy') @@ -231,16 +226,14 @@ def setup end test 'can revoke a personal access token' do - namespace_bot = namespace_bots(:project1_bot0) - active_personal_tokens = namespace_bot.user.personal_access_tokens.active - token = nil - + token = @project_bot_active_tokens.first visit namespace_project_bots_path(@namespace, @project) + assert_selector 'h1', text: I18n.t(:'projects.bots.index.title') assert_selector 'p', text: I18n.t(:'projects.bots.index.subtitle') - within "tr[id='#{namespace_bot.id}']" do - click_link active_personal_tokens.count.to_s + within "tr[id='#{@project_bot.id}']" do + click_link @project_bot_active_tokens.count.to_s end within('dialog') do @@ -248,28 +241,134 @@ def setup assert_selector 'p', text: I18n.t( 'projects.bots.index.personal_access_tokens_listing_modal.description', - bot_account: namespace_bot.user.email + bot_account: @project_bot.user.email ) within('table') do assert_selector 'tr', count: 2 - token = active_personal_tokens.first - within "tr[id='#{token.id}']" do click_link 'Revoke' end end end - within('#turbo-confirm[open]') do - click_button 'Confirm' + click_button I18n.t('personal_access_tokens.revoke_confirmation.submit_button') + within('#personal-access-token-alert') do + assert_text I18n.t('concerns.bot_personal_access_token_actions.revoke.success', pat_name: token.name) end + end - within('dialog') do - within('#personal-access-token-alert') do - assert_text I18n.t('concerns.bot_personal_access_token_actions.revoke.success', pat_name: token.name) + test 'PAT panel removed after personal access token revoke' do + ### SETUP START ### + visit namespace_project_bots_path(@namespace, @project) + # PAT panel is not present + assert_no_selector '#access-token-section div' + # create new PAT to render PAT panel + within "tr[id='#{@project_bot.id}']" do + click_link I18n.t('bots.index.table.actions.generate_new_token') + end + + within('#dialog') do + assert_text I18n.t( + 'projects.bots.index.bot_listing.generate_personal_access_token_modal.title' + ) + + assert_text I18n.t('projects.bots.index.bot_listing.generate_personal_access_token_modal.description', + bot_account: @project_bot.user.email) + + fill_in I18n.t('projects.bots.index.bot_listing.new_bot_modal.token_name'), with: 'Newest token' + + all('input[type=checkbox]').each(&:click) + + click_button I18n.t('projects.bots.index.bot_listing.generate_personal_access_token_modal.submit') + end + + # PAT panel now present + # additional asserts to prevent flakes + assert_selector '#access-token-section div' + within('#access-token-section') do + assert_text I18n.t('projects.bots.index.access_token_section.label', bot_name: @project_bot.user.email) + assert_text I18n.t('projects.bots.index.access_token_section.description') + end + ### SETUP END ### + + ### ACTIONS START ### + # additional asserts to prevent flakes + assert_selector '#bots-table' + assert_selector '#bots-table table tbody tr', count: 20 + within "tr[id='#{@project_bot.id}']" do + # click active tokens number + click_link @project_bot_active_tokens.count.to_s + end + + # bot's current PATs dialog + within('#dialog') do + # revoke a PAT + within("table tbody tr[id='#{@project_bot_active_tokens.first.id}']") do + click_link I18n.t('personal_access_tokens.table.revoke') end + + click_button I18n.t('personal_access_tokens.revoke_confirmation.submit_button') + end + ### ACTIONS END ### + + ### VERIFY START ### + # PAT panel no longer present + assert_no_selector '#access-token-section div' + ### VERIFY END ### + end + + test 'PAT panel removed after bot destroy' do + ### SETUP START ### + visit namespace_project_bots_path(@namespace, @project) + # PAT panel is not present + assert_no_selector '#access-token-section div' + # create new PAT to render PAT panel + within "tr[id='#{@project_bot.id}']" do + click_link I18n.t('bots.index.table.actions.generate_new_token') + end + + within('#dialog') do + assert_text I18n.t( + 'projects.bots.index.bot_listing.generate_personal_access_token_modal.title' + ) + + assert_text I18n.t('projects.bots.index.bot_listing.generate_personal_access_token_modal.description', + bot_account: @project_bot.user.email) + + fill_in I18n.t('projects.bots.index.bot_listing.new_bot_modal.token_name'), with: 'Newest token' + + all('input[type=checkbox]').each(&:click) + + click_button I18n.t('projects.bots.index.bot_listing.generate_personal_access_token_modal.submit') end + + # PAT panel now present + assert_selector '#access-token-section div' + # additional asserts to prevent flakes + within('#access-token-section') do + assert_text I18n.t('projects.bots.index.access_token_section.label', bot_name: @project_bot.user.email) + assert_text I18n.t('projects.bots.index.access_token_section.description') + end + ### SETUP END ### + + ### ACTIONS START ### + # additional asserts to prevent flakes + assert_selector '#bots-table' + assert_selector '#bots-table table tbody tr', count: 20 + within('#bots-table table tbody tr:first-child td:last-child') do + # destroy bot + click_link I18n.t(:'bots.index.table.actions.destroy') + end + + # confirm destroy bot + click_button I18n.t('bots.destroy_confirmation.submit_button') + ### ACTIONS END ### + + ### VERIFY START ### + # PAT panel no longer present + assert_no_selector '#access-token-section div' + ### VERIFY END ### end end end