Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-05-04 15:17:18 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-05-04 15:17:18 +0300
commitfb5d3cceb8d43f8c2dc22a5d8c74327e9397f2e8 (patch)
treedbd3a17217fa46cf279ed692b605e03222fca360 /spec
parent6cd4578a23ffe0fb94632f83a07a25d01f8d6821 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
-rw-r--r--spec/controllers/application_controller_spec.rb24
-rw-r--r--spec/controllers/graphql_controller_spec.rb16
-rw-r--r--spec/features/abuse_report_spec.rb1
-rw-r--r--spec/features/ide/user_opens_merge_request_spec.rb4
-rw-r--r--spec/features/incidents/incident_details_spec.rb2
-rw-r--r--spec/features/issues/discussion_lock_spec.rb1
-rw-r--r--spec/features/issues/gfm_autocomplete_spec.rb3
-rw-r--r--spec/features/issues/issue_detail_spec.rb5
-rw-r--r--spec/features/issues/issue_sidebar_spec.rb4
-rw-r--r--spec/features/issues/user_edits_issue_spec.rb3
-rw-r--r--spec/features/issues/user_toggles_subscription_spec.rb4
-rw-r--r--spec/features/merge_request/user_manages_subscription_spec.rb4
-rw-r--r--spec/features/merge_request/user_opens_checkout_branch_modal_spec.rb2
-rw-r--r--spec/features/merge_request/user_sees_check_out_branch_modal_spec.rb3
-rw-r--r--spec/features/merge_request/user_selects_branches_for_new_mr_spec.rb2
-rw-r--r--spec/features/profiles/user_uses_comment_template_spec.rb2
-rw-r--r--spec/features/projects/issuable_templates_spec.rb2
-rw-r--r--spec/features/reportable_note/issue_spec.rb4
-rw-r--r--spec/finders/packages/conan/package_finder_spec.rb25
-rw-r--r--spec/frontend/group_settings/components/shared_runners_form_spec.js66
-rw-r--r--spec/frontend/issues/show/components/header_actions_spec.js317
-rw-r--r--spec/frontend/issues/show/components/new_header_actions_popover_spec.js77
-rw-r--r--spec/frontend/lib/utils/confirm_via_gl_modal/confirm_action_spec.js2
-rw-r--r--spec/frontend/lib/utils/confirm_via_gl_modal/confirm_modal_spec.js14
-rw-r--r--spec/frontend/sidebar/components/lock/issuable_lock_form_spec.js21
-rw-r--r--spec/frontend/vue_shared/components/markdown/comment_templates_dropdown_spec.js4
-rw-r--r--spec/helpers/ci/runners_helper_spec.rb18
-rw-r--r--spec/helpers/issues_helper_spec.rb10
-rw-r--r--spec/helpers/sessions_helper_spec.rb28
-rw-r--r--spec/lib/gitlab/avatar_cache_spec.rb54
-rw-r--r--spec/lib/gitlab/database/with_lock_retries_outside_transaction_spec.rb4
-rw-r--r--spec/lib/gitlab/database/with_lock_retries_spec.rb4
-rw-r--r--spec/lib/gitlab/discussions_diff/highlight_cache_spec.rb88
-rw-r--r--spec/lib/gitlab/git/repository_spec.rb8
-rw-r--r--spec/lib/gitlab/git/wraps_gitaly_errors_spec.rb81
-rw-r--r--spec/lib/gitlab/reactive_cache_set_cache_spec.rb28
-rw-r--r--spec/lib/gitlab/repository_set_cache_spec.rb64
-rw-r--r--spec/lib/gitlab/utils/email_spec.rb32
-rw-r--r--spec/models/packages/package_spec.rb8
-rw-r--r--spec/requests/api/api_spec.rb22
-rw-r--r--spec/requests/api/conan_project_packages_spec.rb23
-rw-r--r--spec/services/packages/conan/search_service_spec.rb28
-rw-r--r--spec/services/packages/conan/single_package_search_service_spec.rb45
43 files changed, 932 insertions, 225 deletions
diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb
index cdd088c2d5e..ce76be9f509 100644
--- a/spec/controllers/application_controller_spec.rb
+++ b/spec/controllers/application_controller_spec.rb
@@ -1117,4 +1117,28 @@ RSpec.describe ApplicationController, feature_category: :shared do
end
end
end
+
+ context 'when Gitlab::Git::ResourceExhaustedError exception is raised' do
+ before do
+ sign_in user
+ end
+
+ controller(described_class) do
+ def index
+ raise Gitlab::Git::ResourceExhaustedError.new(
+ "Upstream Gitaly has been exhausted: maximum time in concurrency queue reached. Try again later", 50
+ )
+ end
+ end
+
+ it 'returns a plaintext error response with 429 status' do
+ get :index
+
+ expect(response).to have_gitlab_http_status(:too_many_requests)
+ expect(response.body).to include(
+ "Upstream Gitaly has been exhausted: maximum time in concurrency queue reached. Try again later"
+ )
+ expect(response.headers['Retry-After']).to eq(50)
+ end
+ end
end
diff --git a/spec/controllers/graphql_controller_spec.rb b/spec/controllers/graphql_controller_spec.rb
index cd72dbe394a..4bc7e11ec6b 100644
--- a/spec/controllers/graphql_controller_spec.rb
+++ b/spec/controllers/graphql_controller_spec.rb
@@ -65,6 +65,22 @@ RSpec.describe GraphqlController, feature_category: :integrations do
)
expect(response).to have_gitlab_http_status(:forbidden)
end
+
+ it 'handles Gitlab::Git::ResourceExhaustedError', :aggregate_failures do
+ allow(controller).to receive(:execute) do
+ raise Gitlab::Git::ResourceExhaustedError.new("Upstream Gitaly has been exhausted. Try again later", 50)
+ end
+
+ post :execute
+
+ expect(json_response).to include(
+ 'errors' => include(
+ a_hash_including('message' => 'Upstream Gitaly has been exhausted. Try again later')
+ )
+ )
+ expect(response).to have_gitlab_http_status(:too_many_requests)
+ expect(response.headers['Retry-After']).to be(50)
+ end
end
describe 'POST #execute' do
diff --git a/spec/features/abuse_report_spec.rb b/spec/features/abuse_report_spec.rb
index 272656fb4ca..98c1f9baf12 100644
--- a/spec/features/abuse_report_spec.rb
+++ b/spec/features/abuse_report_spec.rb
@@ -12,6 +12,7 @@ RSpec.describe 'Abuse reports', :js, feature_category: :insider_threat do
before do
sign_in(reporter1)
+ stub_feature_flags(moved_mr_sidebar: false)
end
describe 'report abuse to administrator' do
diff --git a/spec/features/ide/user_opens_merge_request_spec.rb b/spec/features/ide/user_opens_merge_request_spec.rb
index 0074b4b1eb0..dc280133a20 100644
--- a/spec/features/ide/user_opens_merge_request_spec.rb
+++ b/spec/features/ide/user_opens_merge_request_spec.rb
@@ -3,6 +3,8 @@
require 'spec_helper'
RSpec.describe 'IDE merge request', :js, feature_category: :web_ide do
+ include CookieHelper
+
let(:merge_request) { create(:merge_request, :simple, source_project: project) }
let(:project) { create(:project, :public, :repository) }
let(:user) { project.first_owner }
@@ -12,6 +14,8 @@ RSpec.describe 'IDE merge request', :js, feature_category: :web_ide do
sign_in(user)
+ set_cookie('new-actions-popover-viewed', 'true')
+
visit(merge_request_path(merge_request))
end
diff --git a/spec/features/incidents/incident_details_spec.rb b/spec/features/incidents/incident_details_spec.rb
index 709919d0196..a166ff46177 100644
--- a/spec/features/incidents/incident_details_spec.rb
+++ b/spec/features/incidents/incident_details_spec.rb
@@ -94,6 +94,7 @@ RSpec.describe 'Incident details', :js, feature_category: :incident_management d
end
it 'routes the user to the incident details page when the `issue_type` is set to incident' do
+ set_cookie('new-actions-popover-viewed', 'true')
visit project_issue_path(project, issue)
wait_for_requests
@@ -113,6 +114,7 @@ RSpec.describe 'Incident details', :js, feature_category: :incident_management d
end
it 'routes the user to the issue details page when the `issue_type` is set to issue' do
+ set_cookie('new-actions-popover-viewed', 'true')
visit incident_project_issues_path(project, incident)
wait_for_requests
diff --git a/spec/features/issues/discussion_lock_spec.rb b/spec/features/issues/discussion_lock_spec.rb
index 47865d2b6ba..fb9addff1a2 100644
--- a/spec/features/issues/discussion_lock_spec.rb
+++ b/spec/features/issues/discussion_lock_spec.rb
@@ -9,6 +9,7 @@ RSpec.describe 'Discussion Lock', :js, feature_category: :team_planning do
before do
sign_in(user)
+ stub_feature_flags(moved_mr_sidebar: false)
end
context 'when a user is a team member' do
diff --git a/spec/features/issues/gfm_autocomplete_spec.rb b/spec/features/issues/gfm_autocomplete_spec.rb
index 2bd5373b715..665c7307231 100644
--- a/spec/features/issues/gfm_autocomplete_spec.rb
+++ b/spec/features/issues/gfm_autocomplete_spec.rb
@@ -3,6 +3,8 @@
require 'spec_helper'
RSpec.describe 'GFM autocomplete', :js, feature_category: :team_planning do
+ include CookieHelper
+
let_it_be(:user) { create(:user, name: '💃speciąl someone💃', username: 'someone.special') }
let_it_be(:user2) { create(:user, name: 'Marge Simpson', username: 'msimpson') }
@@ -45,6 +47,7 @@ RSpec.describe 'GFM autocomplete', :js, feature_category: :team_planning do
before do
sign_in(user)
+ set_cookie('new-actions-popover-viewed', 'true')
visit project_issue_path(project, issue_to_edit)
wait_for_requests
diff --git a/spec/features/issues/issue_detail_spec.rb b/spec/features/issues/issue_detail_spec.rb
index d5f90bb9260..29a61d584ee 100644
--- a/spec/features/issues/issue_detail_spec.rb
+++ b/spec/features/issues/issue_detail_spec.rb
@@ -98,6 +98,7 @@ RSpec.describe 'Issue Detail', :js, feature_category: :team_planning do
project.add_developer(user_to_be_deleted)
sign_in(user_to_be_deleted)
+ stub_feature_flags(moved_mr_sidebar: false)
visit project_issue_path(project, issue)
wait_for_requests
@@ -129,7 +130,7 @@ RSpec.describe 'Issue Detail', :js, feature_category: :team_planning do
describe 'when an issue `issue_type` is edited' do
before do
sign_in(user)
-
+ set_cookie('new-actions-popover-viewed', 'true')
visit project_issue_path(project, issue)
wait_for_requests
end
@@ -163,7 +164,7 @@ RSpec.describe 'Issue Detail', :js, feature_category: :team_planning do
describe 'when an incident `issue_type` is edited' do
before do
sign_in(user)
-
+ set_cookie('new-actions-popover-viewed', 'true')
visit project_issue_path(project, incident)
wait_for_requests
end
diff --git a/spec/features/issues/issue_sidebar_spec.rb b/spec/features/issues/issue_sidebar_spec.rb
index 2ae347d4f9e..ee71181fba2 100644
--- a/spec/features/issues/issue_sidebar_spec.rb
+++ b/spec/features/issues/issue_sidebar_spec.rb
@@ -5,6 +5,7 @@ require 'spec_helper'
RSpec.describe 'Issue Sidebar', feature_category: :team_planning do
include MobileHelpers
include Features::InviteMembersModalHelpers
+ include CookieHelper
let_it_be(:group) { create(:group, :nested) }
let_it_be(:project) { create(:project, :public, namespace: group) }
@@ -20,6 +21,7 @@ RSpec.describe 'Issue Sidebar', feature_category: :team_planning do
context 'when signed in' do
before do
sign_in(user)
+ set_cookie('new-actions-popover-viewed', 'true')
end
context 'when concerning the assignee', :js do
@@ -205,6 +207,7 @@ RSpec.describe 'Issue Sidebar', feature_category: :team_planning do
context 'as an allowed user' do
before do
+ stub_feature_flags(moved_mr_sidebar: false)
project.add_developer(user)
visit_issue(project, issue)
end
@@ -293,6 +296,7 @@ RSpec.describe 'Issue Sidebar', feature_category: :team_planning do
context 'as a guest' do
before do
+ stub_feature_flags(moved_mr_sidebar: false)
project.add_guest(user)
visit_issue(project, issue)
end
diff --git a/spec/features/issues/user_edits_issue_spec.rb b/spec/features/issues/user_edits_issue_spec.rb
index c6cedbc83cd..4ef58918a2b 100644
--- a/spec/features/issues/user_edits_issue_spec.rb
+++ b/spec/features/issues/user_edits_issue_spec.rb
@@ -3,6 +3,8 @@
require "spec_helper"
RSpec.describe "Issues > User edits issue", :js, feature_category: :team_planning do
+ include CookieHelper
+
let_it_be(:project) { create(:project_empty_repo, :public) }
let_it_be(:project_with_milestones) { create(:project_empty_repo, :public) }
let_it_be(:user) { create(:user) }
@@ -18,6 +20,7 @@ RSpec.describe "Issues > User edits issue", :js, feature_category: :team_plannin
project.add_developer(user)
project_with_milestones.add_developer(user)
sign_in(user)
+ set_cookie('new-actions-popover-viewed', 'true')
end
context "from edit page" do
diff --git a/spec/features/issues/user_toggles_subscription_spec.rb b/spec/features/issues/user_toggles_subscription_spec.rb
index 904fafdf56a..00b04c10d33 100644
--- a/spec/features/issues/user_toggles_subscription_spec.rb
+++ b/spec/features/issues/user_toggles_subscription_spec.rb
@@ -10,6 +10,7 @@ RSpec.describe "User toggles subscription", :js, feature_category: :team_plannin
context 'user is not logged in' do
before do
+ stub_feature_flags(moved_mr_sidebar: false)
visit(project_issue_path(project, issue))
end
@@ -20,9 +21,9 @@ RSpec.describe "User toggles subscription", :js, feature_category: :team_plannin
context 'user is logged in' do
before do
+ stub_feature_flags(moved_mr_sidebar: false)
project.add_developer(user)
sign_in(user)
-
visit(project_issue_path(project, issue))
end
@@ -52,6 +53,7 @@ RSpec.describe "User toggles subscription", :js, feature_category: :team_plannin
context 'user is logged in without edit permission' do
before do
+ stub_feature_flags(moved_mr_sidebar: false)
sign_in(user2)
visit(project_issue_path(project, issue))
diff --git a/spec/features/merge_request/user_manages_subscription_spec.rb b/spec/features/merge_request/user_manages_subscription_spec.rb
index d4ccc4a93b5..3bcc8255ab7 100644
--- a/spec/features/merge_request/user_manages_subscription_spec.rb
+++ b/spec/features/merge_request/user_manages_subscription_spec.rb
@@ -3,6 +3,8 @@
require 'spec_helper'
RSpec.describe 'User manages subscription', :js, feature_category: :code_review_workflow do
+ include CookieHelper
+
let(:project) { create(:project, :public, :repository) }
let(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
let(:user) { create(:user) }
@@ -10,7 +12,7 @@ RSpec.describe 'User manages subscription', :js, feature_category: :code_review_
before do
stub_feature_flags(moved_mr_sidebar: moved_mr_sidebar_enabled)
-
+ set_cookie('new-actions-popover-viewed', 'true')
project.add_maintainer(user)
sign_in(user)
diff --git a/spec/features/merge_request/user_opens_checkout_branch_modal_spec.rb b/spec/features/merge_request/user_opens_checkout_branch_modal_spec.rb
index 7cb1c95f6dc..601310cbacf 100644
--- a/spec/features/merge_request/user_opens_checkout_branch_modal_spec.rb
+++ b/spec/features/merge_request/user_opens_checkout_branch_modal_spec.rb
@@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe 'Merge request > User opens checkout branch modal', :js, feature_category: :code_review_workflow do
include ProjectForksHelper
+ include CookieHelper
let(:project) { create(:project, :public, :repository) }
let(:user) { project.creator }
@@ -11,6 +12,7 @@ RSpec.describe 'Merge request > User opens checkout branch modal', :js, feature_
before do
project.add_maintainer(user)
sign_in(user)
+ set_cookie('new-actions-popover-viewed', 'true')
end
describe 'for fork' do
diff --git a/spec/features/merge_request/user_sees_check_out_branch_modal_spec.rb b/spec/features/merge_request/user_sees_check_out_branch_modal_spec.rb
index ad2ceeb23e2..21c62b0d0d8 100644
--- a/spec/features/merge_request/user_sees_check_out_branch_modal_spec.rb
+++ b/spec/features/merge_request/user_sees_check_out_branch_modal_spec.rb
@@ -3,6 +3,8 @@
require 'spec_helper'
RSpec.describe 'Merge request > User sees check out branch modal', :js, feature_category: :code_review_workflow do
+ include CookieHelper
+
let(:project) { create(:project, :public, :repository) }
let(:user) { project.creator }
let(:merge_request) { create(:merge_request, source_project: project) }
@@ -10,6 +12,7 @@ RSpec.describe 'Merge request > User sees check out branch modal', :js, feature_
before do
sign_in(user)
+ set_cookie('new-actions-popover-viewed', 'true')
visit project_merge_request_path(project, merge_request)
wait_for_requests
diff --git a/spec/features/merge_request/user_selects_branches_for_new_mr_spec.rb b/spec/features/merge_request/user_selects_branches_for_new_mr_spec.rb
index 0de59ea21c5..dae28cbb05c 100644
--- a/spec/features/merge_request/user_selects_branches_for_new_mr_spec.rb
+++ b/spec/features/merge_request/user_selects_branches_for_new_mr_spec.rb
@@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe 'Merge request > User selects branches for new MR', :js, feature_category: :code_review_workflow do
include ListboxHelpers
+ include CookieHelper
let(:project) { create(:project, :public, :repository) }
let(:user) { project.creator }
@@ -17,6 +18,7 @@ RSpec.describe 'Merge request > User selects branches for new MR', :js, feature_
before do
project.add_maintainer(user)
sign_in(user)
+ set_cookie('new-actions-popover-viewed', 'true')
end
it 'selects the source branch sha when a tag with the same name exists' do
diff --git a/spec/features/profiles/user_uses_comment_template_spec.rb b/spec/features/profiles/user_uses_comment_template_spec.rb
index b426e3fb433..704d02e94f4 100644
--- a/spec/features/profiles/user_uses_comment_template_spec.rb
+++ b/spec/features/profiles/user_uses_comment_template_spec.rb
@@ -18,7 +18,7 @@ RSpec.describe 'User uses comment template', :js,
it 'applies comment template' do
visit project_merge_request_path(merge_request.project, merge_request)
- find('[data-testid="comment-template-dropdown-toggle"]').click
+ find('.js-comment-template-toggle').click
wait_for_requests
diff --git a/spec/features/projects/issuable_templates_spec.rb b/spec/features/projects/issuable_templates_spec.rb
index adf410ce6e8..77f88994bfb 100644
--- a/spec/features/projects/issuable_templates_spec.rb
+++ b/spec/features/projects/issuable_templates_spec.rb
@@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe 'issuable templates', :js, feature_category: :projects do
include ProjectForksHelper
+ include CookieHelper
let(:user) { create(:user) }
let(:project) { create(:project, :public, :repository) }
@@ -12,6 +13,7 @@ RSpec.describe 'issuable templates', :js, feature_category: :projects do
before do
project.add_maintainer(user)
sign_in user
+ set_cookie('new-actions-popover-viewed', 'true')
end
context 'user creates an issue using templates' do
diff --git a/spec/features/reportable_note/issue_spec.rb b/spec/features/reportable_note/issue_spec.rb
index 55e7f5897bc..a18cdf27294 100644
--- a/spec/features/reportable_note/issue_spec.rb
+++ b/spec/features/reportable_note/issue_spec.rb
@@ -3,6 +3,8 @@
require 'spec_helper'
RSpec.describe 'Reportable note on issue', :js, feature_category: :team_planning do
+ include CookieHelper
+
let(:user) { create(:user) }
let(:project) { create(:project) }
let(:issue) { create(:issue, project: project) }
@@ -11,7 +13,7 @@ RSpec.describe 'Reportable note on issue', :js, feature_category: :team_planning
before do
project.add_maintainer(user)
sign_in(user)
-
+ set_cookie('new-actions-popover-viewed', 'true')
visit project_issue_path(project, issue)
end
diff --git a/spec/finders/packages/conan/package_finder_spec.rb b/spec/finders/packages/conan/package_finder_spec.rb
index f25a62225a8..787cb256486 100644
--- a/spec/finders/packages/conan/package_finder_spec.rb
+++ b/spec/finders/packages/conan/package_finder_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-RSpec.describe ::Packages::Conan::PackageFinder do
+RSpec.describe ::Packages::Conan::PackageFinder, feature_category: :package_registry do
using RSpec::Parameterized::TableSyntax
let_it_be_with_reload(:project) { create(:project) }
@@ -15,7 +15,8 @@ RSpec.describe ::Packages::Conan::PackageFinder do
describe '#execute' do
let(:query) { "#{conan_package.name.split('/').first[0, 3]}%" }
- let(:finder) { described_class.new(user, query: query) }
+ let(:finder) { described_class.new(user, params) }
+ let(:params) { { query: query } }
subject { finder.execute }
@@ -40,7 +41,7 @@ RSpec.describe ::Packages::Conan::PackageFinder do
end
with_them do
- let(:expected_packages) { packages_visible ? [conan_package, conan_package2] : [] }
+ let(:expected_packages) { packages_visible ? [conan_package2, conan_package] : [] }
let(:user) { role == :anonymous ? nil : super() }
before do
@@ -50,5 +51,23 @@ RSpec.describe ::Packages::Conan::PackageFinder do
it { is_expected.to eq(expected_packages) }
end
+
+ context 'with project' do
+ subject { described_class.new(user, params, project: project).execute }
+
+ it { is_expected.to match_array([conan_package2, conan_package]) }
+
+ it 'respects the limit' do
+ stub_const("#{described_class}::MAX_PACKAGES_COUNT", 1)
+
+ expect(subject).to match_array([conan_package2])
+ end
+
+ context 'with a different project' do
+ let_it_be(:project) { private_project }
+
+ it { is_expected.to match_array([private_package]) }
+ end
+ end
end
end
diff --git a/spec/frontend/group_settings/components/shared_runners_form_spec.js b/spec/frontend/group_settings/components/shared_runners_form_spec.js
index e92493315f7..5daa21fd618 100644
--- a/spec/frontend/group_settings/components/shared_runners_form_spec.js
+++ b/spec/frontend/group_settings/components/shared_runners_form_spec.js
@@ -2,12 +2,17 @@ import { GlAlert } from '@gitlab/ui';
import { nextTick } from 'vue';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
-import SharedRunnersForm from '~/group_settings/components/shared_runners_form.vue';
+import { confirmAction } from '~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal';
import { updateGroup } from '~/api/groups_api';
+import SharedRunnersForm from '~/group_settings/components/shared_runners_form.vue';
+import { I18N_CONFIRM_MESSAGE } from '~/group_settings/constants';
+
jest.mock('~/api/groups_api');
+jest.mock('~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal');
const GROUP_ID = '99';
+const GROUP_NAME = 'My group';
const RUNNER_ENABLED_VALUE = 'enabled';
const RUNNER_DISABLED_VALUE = 'disabled_and_unoverridable';
const RUNNER_ALLOW_OVERRIDE_VALUE = 'disabled_and_overridable';
@@ -19,6 +24,8 @@ describe('group_settings/components/shared_runners_form', () => {
wrapper = shallowMountExtended(SharedRunnersForm, {
provide: {
groupId: GROUP_ID,
+ groupName: GROUP_NAME,
+ groupIsEmpty: false,
sharedRunnersSetting: RUNNER_ENABLED_VALUE,
parentSharedRunnersSetting: null,
runnerEnabledValue: RUNNER_ENABLED_VALUE,
@@ -41,10 +48,12 @@ describe('group_settings/components/shared_runners_form', () => {
};
beforeEach(() => {
+ confirmAction.mockResolvedValue(true);
updateGroup.mockResolvedValue({});
});
afterEach(() => {
+ confirmAction.mockReset();
updateGroup.mockReset();
});
@@ -110,8 +119,9 @@ describe('group_settings/components/shared_runners_form', () => {
it('does not update settings while loading', async () => {
findSharedRunnersToggle().vm.$emit('change', true);
+ await nextTick();
findSharedRunnersToggle().vm.$emit('change', false);
- await waitForPromises();
+ await nextTick();
expect(updateGroup).toHaveBeenCalledTimes(1);
});
@@ -134,6 +144,8 @@ describe('group_settings/components/shared_runners_form', () => {
findSharedRunnersToggle().vm.$emit('change', true);
await waitForPromises();
+ expect(confirmAction).not.toHaveBeenCalled();
+
expect(getSharedRunnersSetting()).toEqual(RUNNER_ENABLED_VALUE);
expect(findOverrideToggle().props('disabled')).toBe(true);
});
@@ -142,17 +154,59 @@ describe('group_settings/components/shared_runners_form', () => {
findSharedRunnersToggle().vm.$emit('change', false);
await waitForPromises();
+ expect(confirmAction).toHaveBeenCalledTimes(1);
+ expect(confirmAction).toHaveBeenCalledWith(
+ I18N_CONFIRM_MESSAGE,
+ expect.objectContaining({
+ title: expect.stringContaining(GROUP_NAME),
+ }),
+ );
+
expect(getSharedRunnersSetting()).toEqual(RUNNER_DISABLED_VALUE);
expect(findOverrideToggle().props('disabled')).toBe(false);
});
+
+ describe('when user cancels operation', () => {
+ beforeEach(() => {
+ confirmAction.mockResolvedValue(false);
+ });
+
+ it('sends no payload when turned off', async () => {
+ findSharedRunnersToggle().vm.$emit('change', false);
+ await waitForPromises();
+
+ expect(confirmAction).toHaveBeenCalledTimes(1);
+ expect(confirmAction).toHaveBeenCalledWith(
+ I18N_CONFIRM_MESSAGE,
+ expect.objectContaining({
+ title: expect.stringContaining(GROUP_NAME),
+ }),
+ );
+
+ expect(updateGroup).not.toHaveBeenCalled();
+ expect(findOverrideToggle().props('disabled')).toBe(true);
+ });
+ });
+
+ describe('when group is empty', () => {
+ beforeEach(() => {
+ createComponent({ groupIsEmpty: true });
+ });
+
+ it('confirmation is not shown when turned off', async () => {
+ findSharedRunnersToggle().vm.$emit('change', false);
+ await waitForPromises();
+
+ expect(confirmAction).not.toHaveBeenCalled();
+ expect(getSharedRunnersSetting()).toEqual(RUNNER_DISABLED_VALUE);
+ });
+ });
});
describe('"Override the group setting" toggle', () => {
- beforeEach(() => {
+ it('enabling the override toggle sends correct payload', async () => {
createComponent({ sharedRunnersSetting: RUNNER_ALLOW_OVERRIDE_VALUE });
- });
- it('enabling the override toggle sends correct payload', async () => {
findOverrideToggle().vm.$emit('change', true);
await waitForPromises();
@@ -160,6 +214,8 @@ describe('group_settings/components/shared_runners_form', () => {
});
it('disabling the override toggle sends correct payload', async () => {
+ createComponent({ sharedRunnersSetting: RUNNER_ALLOW_OVERRIDE_VALUE });
+
findOverrideToggle().vm.$emit('change', false);
await waitForPromises();
diff --git a/spec/frontend/issues/show/components/header_actions_spec.js b/spec/frontend/issues/show/components/header_actions_spec.js
index db3435855f6..a5ba512434c 100644
--- a/spec/frontend/issues/show/components/header_actions_spec.js
+++ b/spec/frontend/issues/show/components/header_actions_spec.js
@@ -2,6 +2,8 @@ import Vue, { nextTick } from 'vue';
import { GlDropdownItem, GlLink, GlModal, GlButton } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import Vuex from 'vuex';
+import VueApollo from 'vue-apollo';
+import waitForPromises from 'helpers/wait_for_promises';
import { mockTracking } from 'helpers/tracking_helper';
import { createAlert, VARIANT_SUCCESS } from '~/alert';
import { STATUS_CLOSED, STATUS_OPEN, TYPE_INCIDENT, TYPE_ISSUE } from '~/issues/constants';
@@ -14,17 +16,22 @@ import promoteToEpicMutation from '~/issues/show/queries/promote_to_epic.mutatio
import * as urlUtility from '~/lib/utils/url_utility';
import eventHub from '~/notes/event_hub';
import createStore from '~/notes/stores';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import issueReferenceQuery from '~/sidebar/queries/issue_reference.query.graphql';
+import updateIssueMutation from '~/issues/show/queries/update_issue.mutation.graphql';
+import toast from '~/vue_shared/plugins/global_toast';
jest.mock('~/alert');
jest.mock('~/issues/show/event_hub', () => ({ $emit: jest.fn() }));
+jest.mock('~/vue_shared/plugins/global_toast');
describe('HeaderActions component', () => {
let dispatchEventSpy;
- let mutateMock;
let wrapper;
let visitUrlSpy;
Vue.use(Vuex);
+ Vue.use(VueApollo);
const store = createStore();
@@ -45,15 +52,28 @@ describe('HeaderActions component', () => {
reportedUserId: 1,
reportedFromUrl: 'http://localhost:/gitlab-org/-/issues/32',
submitAsSpamPath: 'gitlab-org/gitlab-test/-/issues/32/submit_as_spam',
+ issuableEmailAddress: null,
+ fullPath: 'full-path',
};
- const updateIssueMutationResponse = { data: { updateIssue: { errors: [] } } };
+ const updateIssueMutationResponse = {
+ data: {
+ updateIssue: {
+ errors: [],
+ issuable: {
+ id: 'gid://gitlab/Issue/511',
+ state: STATUS_OPEN,
+ },
+ },
+ },
+ };
const promoteToEpicMutationResponse = {
data: {
promoteToEpic: {
errors: [],
epic: {
+ id: 'gid://gitlab/Epic/1',
webPath: '/groups/gitlab-org/-/epics/1',
},
},
@@ -69,6 +89,20 @@ describe('HeaderActions component', () => {
},
};
+ const mockIssueReferenceData = {
+ data: {
+ workspace: {
+ id: 'gid://gitlab/Project/7',
+ issuable: {
+ id: 'gid://gitlab/Issue/511',
+ reference: 'flightjs/Flight#33',
+ __typename: 'Issue',
+ },
+ __typename: 'Project',
+ },
+ },
+ };
+
const findToggleIssueStateButton = () => wrapper.find(`[data-testid="toggle-button"]`);
const findEditButton = () => wrapper.find(`[data-testid="edit-button"]`);
@@ -77,33 +111,54 @@ describe('HeaderActions component', () => {
const findDesktopDropdown = () => findDropdownBy('desktop-dropdown');
const findMobileDropdownItems = () => findMobileDropdown().findAllComponents(GlDropdownItem);
const findDesktopDropdownItems = () => findDesktopDropdown().findAllComponents(GlDropdownItem);
+ const findAbuseCategorySelector = () => wrapper.findComponent(AbuseCategorySelector);
+ const findReportAbuseSelectorItem = () => wrapper.find(`[data-testid="report-abuse-item"]`);
+ const findNotificationWidget = () => wrapper.find(`[data-testid="notification-toggle"]`);
+ const findLockIssueWidget = () => wrapper.find(`[data-testid="lock-issue-toggle"]`);
+ const findCopyRefenceDropdownItem = () => wrapper.find(`[data-testid="copy-reference"]`);
+ const findCopyEmailItem = () => wrapper.find(`[data-testid="copy-email"]`);
const findModal = () => wrapper.findComponent(GlModal);
const findModalLinkAt = (index) => findModal().findAllComponents(GlLink).at(index);
+ const issueReferenceSuccessHandler = jest.fn().mockResolvedValue(mockIssueReferenceData);
+ const updateIssueMutationResponseHandler = jest
+ .fn()
+ .mockResolvedValue(updateIssueMutationResponse);
+ const promoteToEpicMutationSuccessResponseHandler = jest
+ .fn()
+ .mockResolvedValue(promoteToEpicMutationResponse);
+ const promoteToEpicMutationErrorHandler = jest
+ .fn()
+ .mockResolvedValue(promoteToEpicMutationErrorResponse);
+
const mountComponent = ({
props = {},
issueState = STATUS_OPEN,
blockedByIssues = [],
- mutateResponse = {},
+ movedMrSidebarEnabled = false,
+ promoteToEpicHandler = promoteToEpicMutationSuccessResponseHandler,
} = {}) => {
- mutateMock = jest.fn().mockResolvedValue(mutateResponse);
-
store.dispatch('setNoteableData', {
blocked_by_issues: blockedByIssues,
state: issueState,
});
+ const handlers = [
+ [issueReferenceQuery, issueReferenceSuccessHandler],
+ [updateIssueMutation, updateIssueMutationResponseHandler],
+ [promoteToEpicMutation, promoteToEpicHandler],
+ ];
+
return shallowMount(HeaderActions, {
+ apolloProvider: createMockApollo(handlers),
store,
provide: {
...defaultProps,
...props,
- },
- mocks: {
- $apollo: {
- mutate: mutateMock,
+ glFeatures: {
+ movedMrSidebar: movedMrSidebarEnabled,
},
},
stubs: {
@@ -138,7 +193,6 @@ describe('HeaderActions component', () => {
wrapper = mountComponent({
props: { issueType },
issueState,
- mutateResponse: updateIssueMutationResponse,
});
});
@@ -149,23 +203,19 @@ describe('HeaderActions component', () => {
it('calls apollo mutation', () => {
findToggleIssueStateButton().vm.$emit('click');
- expect(mutateMock).toHaveBeenCalledWith(
- expect.objectContaining({
- variables: {
- input: {
- iid: defaultProps.iid,
- projectPath: defaultProps.projectPath,
- stateEvent: newIssueState,
- },
- },
- }),
- );
+ expect(updateIssueMutationResponseHandler).toHaveBeenCalledWith({
+ input: {
+ iid: defaultProps.iid,
+ projectPath: defaultProps.projectPath,
+ stateEvent: newIssueState,
+ },
+ });
});
it('dispatches a custom event to update the issue page', async () => {
findToggleIssueStateButton().vm.$emit('click');
- await nextTick();
+ await waitForPromises();
expect(dispatchEventSpy).toHaveBeenCalledTimes(1);
});
@@ -290,28 +340,25 @@ describe('HeaderActions component', () => {
describe('when "Promote to epic" button is clicked', () => {
describe('when response is successful', () => {
- beforeEach(() => {
+ beforeEach(async () => {
visitUrlSpy = jest.spyOn(urlUtility, 'visitUrl').mockReturnValue({});
wrapper = mountComponent({
- mutateResponse: promoteToEpicMutationResponse,
+ promoteToEpicHandler: promoteToEpicMutationSuccessResponseHandler,
});
wrapper.find('[data-testid="promote-button"]').vm.$emit('click');
+
+ await waitForPromises();
});
it('invokes GraphQL mutation when clicked', () => {
- expect(mutateMock).toHaveBeenCalledWith(
- expect.objectContaining({
- mutation: promoteToEpicMutation,
- variables: {
- input: {
- iid: defaultProps.iid,
- projectPath: defaultProps.projectPath,
- },
- },
- }),
- );
+ expect(promoteToEpicMutationSuccessResponseHandler).toHaveBeenCalledWith({
+ input: {
+ iid: defaultProps.iid,
+ projectPath: defaultProps.projectPath,
+ },
+ });
});
it('shows a success message and tells the user they are being redirected', () => {
@@ -329,14 +376,16 @@ describe('HeaderActions component', () => {
});
describe('when response contains errors', () => {
- beforeEach(() => {
+ beforeEach(async () => {
visitUrlSpy = jest.spyOn(urlUtility, 'visitUrl').mockReturnValue({});
wrapper = mountComponent({
- mutateResponse: promoteToEpicMutationErrorResponse,
+ promoteToEpicHandler: promoteToEpicMutationErrorHandler,
});
wrapper.find('[data-testid="promote-button"]').vm.$emit('click');
+
+ await waitForPromises();
});
it('shows an error message', () => {
@@ -349,21 +398,17 @@ describe('HeaderActions component', () => {
describe('when `toggle.issuable.state` event is emitted', () => {
it('invokes a method to toggle the issue state', () => {
- wrapper = mountComponent({ mutateResponse: updateIssueMutationResponse });
+ wrapper = mountComponent();
eventHub.$emit('toggle.issuable.state');
- expect(mutateMock).toHaveBeenCalledWith(
- expect.objectContaining({
- variables: {
- input: {
- iid: defaultProps.iid,
- projectPath: defaultProps.projectPath,
- stateEvent: ISSUE_STATE_EVENT_CLOSE,
- },
- },
- }),
- );
+ expect(updateIssueMutationResponseHandler).toHaveBeenCalledWith({
+ input: {
+ iid: defaultProps.iid,
+ projectPath: defaultProps.projectPath,
+ stateEvent: ISSUE_STATE_EVENT_CLOSE,
+ },
+ });
});
});
@@ -392,17 +437,13 @@ describe('HeaderActions component', () => {
it('calls apollo mutation when primary button is clicked', () => {
findModal().vm.$emit('primary');
- expect(mutateMock).toHaveBeenCalledWith(
- expect.objectContaining({
- variables: {
- input: {
- iid: defaultProps.iid.toString(),
- projectPath: defaultProps.projectPath,
- stateEvent: ISSUE_STATE_EVENT_CLOSE,
- },
- },
- }),
- );
+ expect(updateIssueMutationResponseHandler).toHaveBeenCalledWith({
+ input: {
+ iid: defaultProps.iid.toString(),
+ projectPath: defaultProps.projectPath,
+ stateEvent: ISSUE_STATE_EVENT_CLOSE,
+ },
+ });
});
describe.each`
@@ -434,8 +475,6 @@ describe('HeaderActions component', () => {
});
describe('abuse category selector', () => {
- const findAbuseCategorySelector = () => wrapper.findComponent(AbuseCategorySelector);
-
beforeEach(() => {
wrapper = mountComponent({ props: { isIssueAuthor: false } });
});
@@ -445,7 +484,7 @@ describe('HeaderActions component', () => {
});
it('opens the drawer', async () => {
- findDesktopDropdownItems().at(2).vm.$emit('click');
+ findReportAbuseSelectorItem().vm.$emit('click');
await nextTick();
@@ -453,10 +492,160 @@ describe('HeaderActions component', () => {
});
it('closes the drawer', async () => {
- await findDesktopDropdownItems().at(2).vm.$emit('click');
+ await findReportAbuseSelectorItem().vm.$emit('click');
await findAbuseCategorySelector().vm.$emit('close-drawer');
expect(findAbuseCategorySelector().exists()).toEqual(false);
});
});
+
+ describe('notification toggle', () => {
+ describe('visibility', () => {
+ describe.each`
+ movedMrSidebarEnabled | issueType | visible
+ ${true} | ${TYPE_ISSUE} | ${true}
+ ${true} | ${TYPE_INCIDENT} | ${true}
+ ${false} | ${TYPE_ISSUE} | ${false}
+ ${false} | ${TYPE_INCIDENT} | ${false}
+ `(
+ `when movedMrSidebarEnabled flag is "$movedMrSidebarEnabled" with issue type "$issueType"`,
+ ({ movedMrSidebarEnabled, issueType, visible }) => {
+ beforeEach(() => {
+ wrapper = mountComponent({
+ props: {
+ issueType,
+ },
+ movedMrSidebarEnabled,
+ });
+ });
+
+ it(`${visible ? 'shows' : 'hides'} Notification toggle`, () => {
+ expect(findNotificationWidget().exists()).toBe(visible);
+ });
+ },
+ );
+ });
+ });
+
+ describe('lock issue option', () => {
+ describe('visibility', () => {
+ describe.each`
+ movedMrSidebarEnabled | issueType | visible
+ ${true} | ${TYPE_ISSUE} | ${true}
+ ${true} | ${TYPE_INCIDENT} | ${false}
+ ${false} | ${TYPE_ISSUE} | ${false}
+ ${false} | ${TYPE_INCIDENT} | ${false}
+ `(
+ `when movedMrSidebarEnabled flag is "$movedMrSidebarEnabled" with issue type "$issueType"`,
+ ({ movedMrSidebarEnabled, issueType, visible }) => {
+ beforeEach(() => {
+ wrapper = mountComponent({
+ props: {
+ issueType,
+ },
+ movedMrSidebarEnabled,
+ });
+ });
+
+ it(`${visible ? 'shows' : 'hides'} Lock issue option`, () => {
+ expect(findLockIssueWidget().exists()).toBe(visible);
+ });
+ },
+ );
+ });
+ });
+
+ describe('copy reference option', () => {
+ describe('visibility', () => {
+ describe.each`
+ movedMrSidebarEnabled | issueType | visible
+ ${true} | ${TYPE_ISSUE} | ${true}
+ ${true} | ${TYPE_INCIDENT} | ${true}
+ ${false} | ${TYPE_ISSUE} | ${false}
+ ${false} | ${TYPE_INCIDENT} | ${false}
+ `(
+ 'when movedMrSidebarFlagEnabled is "$movedMrSidebarEnabled" with issue type "$issueType"',
+ ({ movedMrSidebarEnabled, issueType, visible }) => {
+ beforeEach(() => {
+ wrapper = mountComponent({
+ props: {
+ issueType,
+ },
+ movedMrSidebarEnabled,
+ });
+ });
+
+ it(`${visible ? 'shows' : 'hides'} Copy reference option`, () => {
+ expect(findCopyRefenceDropdownItem().exists()).toBe(visible);
+ });
+ },
+ );
+ });
+
+ describe('clicking when visible', () => {
+ beforeEach(() => {
+ wrapper = mountComponent({
+ props: {
+ issueType: TYPE_ISSUE,
+ },
+ movedMrSidebarEnabled: true,
+ });
+ });
+
+ it('shows toast message', () => {
+ findCopyRefenceDropdownItem().vm.$emit('click');
+
+ expect(toast).toHaveBeenCalledWith('Reference copied');
+ });
+ });
+ });
+
+ describe('copy email option', () => {
+ describe('visibility', () => {
+ describe.each`
+ movedMrSidebarEnabled | issueType | issuableEmailAddress | visible
+ ${true} | ${TYPE_ISSUE} | ${'mock-email-address'} | ${true}
+ ${true} | ${TYPE_ISSUE} | ${''} | ${false}
+ ${true} | ${TYPE_INCIDENT} | ${'mock-email-address'} | ${true}
+ ${true} | ${TYPE_INCIDENT} | ${''} | ${false}
+ ${false} | ${TYPE_ISSUE} | ${'mock-email-address'} | ${false}
+ ${false} | ${TYPE_INCIDENT} | ${'mock-email-address'} | ${false}
+ `(
+ 'when movedMrSidebarEnabled flag is "$movedMrSidebarEnabled" issue type is "$issueType" and issuableEmailAddress="$issuableEmailAddress"',
+ ({ movedMrSidebarEnabled, issueType, issuableEmailAddress, visible }) => {
+ beforeEach(() => {
+ wrapper = mountComponent({
+ props: {
+ issueType,
+ issuableEmailAddress,
+ },
+ movedMrSidebarEnabled,
+ });
+ });
+
+ it(`${visible ? 'shows' : 'hides'} Copy email option`, () => {
+ expect(findCopyEmailItem().exists()).toBe(visible);
+ });
+ },
+ );
+ });
+
+ describe('clicking when visible', () => {
+ beforeEach(() => {
+ wrapper = mountComponent({
+ props: {
+ issueType: TYPE_ISSUE,
+ issuableEmailAddress: 'mock-email-address',
+ },
+ movedMrSidebarEnabled: true,
+ });
+ });
+
+ it('shows toast message', () => {
+ findCopyEmailItem().vm.$emit('click');
+
+ expect(toast).toHaveBeenCalledWith('Email address copied');
+ });
+ });
+ });
});
diff --git a/spec/frontend/issues/show/components/new_header_actions_popover_spec.js b/spec/frontend/issues/show/components/new_header_actions_popover_spec.js
new file mode 100644
index 00000000000..bf3e81c7d3a
--- /dev/null
+++ b/spec/frontend/issues/show/components/new_header_actions_popover_spec.js
@@ -0,0 +1,77 @@
+import { GlPopover } from '@gitlab/ui';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import NewHeaderActionsPopover from '~/issues/show/components/new_header_actions_popover.vue';
+import { NEW_ACTIONS_POPOVER_KEY } from '~/issues/show/constants';
+import { TYPE_ISSUE } from '~/issues/constants';
+import * as utils from '~/lib/utils/common_utils';
+
+describe('NewHeaderActionsPopover', () => {
+ let wrapper;
+
+ const createComponent = ({ issueType = TYPE_ISSUE, movedMrSidebarEnabled = true }) => {
+ wrapper = shallowMountExtended(NewHeaderActionsPopover, {
+ propsData: {
+ issueType,
+ },
+ stubs: {
+ GlPopover,
+ },
+ provide: {
+ glFeatures: {
+ movedMrSidebar: movedMrSidebarEnabled,
+ },
+ },
+ });
+ };
+
+ const findPopover = () => wrapper.findComponent(GlPopover);
+ const findConfirmButton = () => wrapper.findByTestId('confirm-button');
+
+ it('should not be visible when the feature flag :moved_mr_sidebar is disabled', () => {
+ createComponent({ movedMrSidebarEnabled: false });
+ expect(findPopover().exists()).toBe(false);
+ });
+
+ describe('without the popover cookie', () => {
+ beforeEach(() => {
+ utils.setCookie = jest.fn();
+
+ createComponent({});
+ });
+
+ it('renders the popover with correct text', () => {
+ expect(findPopover().exists()).toBe(true);
+ expect(findPopover().text()).toContain('issue actions');
+ });
+
+ it('does not call setCookie', () => {
+ expect(utils.setCookie).not.toHaveBeenCalled();
+ });
+
+ describe('when the confirm button is clicked', () => {
+ beforeEach(() => {
+ findConfirmButton().vm.$emit('click');
+ });
+
+ it('sets the popover cookie', () => {
+ expect(utils.setCookie).toHaveBeenCalledWith(NEW_ACTIONS_POPOVER_KEY, true);
+ });
+
+ it('hides the popover', () => {
+ expect(findPopover().exists()).toBe(false);
+ });
+ });
+ });
+
+ describe('with the popover cookie', () => {
+ beforeEach(() => {
+ jest.spyOn(utils, 'getCookie').mockReturnValue('true');
+
+ createComponent({});
+ });
+
+ it('does not render the popover', () => {
+ expect(findPopover().exists()).toBe(false);
+ });
+ });
+});
diff --git a/spec/frontend/lib/utils/confirm_via_gl_modal/confirm_action_spec.js b/spec/frontend/lib/utils/confirm_via_gl_modal/confirm_action_spec.js
index 9b790e739fb..fab5a7b8844 100644
--- a/spec/frontend/lib/utils/confirm_via_gl_modal/confirm_action_spec.js
+++ b/spec/frontend/lib/utils/confirm_via_gl_modal/confirm_action_spec.js
@@ -66,6 +66,7 @@ describe('confirmAction', () => {
modalHtmlMessage: '<strong>Hello</strong>',
title: 'title',
hideCancel: true,
+ size: 'md',
};
await renderRootComponent('', options);
expect(modal.props()).toEqual(
@@ -79,6 +80,7 @@ describe('confirmAction', () => {
modalHtmlMessage: options.modalHtmlMessage,
title: options.title,
hideCancel: options.hideCancel,
+ size: 'md',
}),
);
});
diff --git a/spec/frontend/lib/utils/confirm_via_gl_modal/confirm_modal_spec.js b/spec/frontend/lib/utils/confirm_via_gl_modal/confirm_modal_spec.js
index c135180c9df..9dcb850076c 100644
--- a/spec/frontend/lib/utils/confirm_via_gl_modal/confirm_modal_spec.js
+++ b/spec/frontend/lib/utils/confirm_via_gl_modal/confirm_modal_spec.js
@@ -14,6 +14,7 @@ describe('Confirm Modal', () => {
secondaryText,
secondaryVariant,
title,
+ size,
hideCancel = false,
} = {}) => {
wrapper = mount(ConfirmModal, {
@@ -24,6 +25,7 @@ describe('Confirm Modal', () => {
secondaryVariant,
hideCancel,
title,
+ size,
},
});
};
@@ -91,5 +93,17 @@ describe('Confirm Modal', () => {
expect(findGlModal().props().title).toBe(title);
});
+
+ it('should set modal size to `sm` by default', () => {
+ createComponent();
+
+ expect(findGlModal().props('size')).toBe('sm');
+ });
+
+ it('should set modal size when `size` prop is set', () => {
+ createComponent({ size: 'md' });
+
+ expect(findGlModal().props('size')).toBe('md');
+ });
});
});
diff --git a/spec/frontend/sidebar/components/lock/issuable_lock_form_spec.js b/spec/frontend/sidebar/components/lock/issuable_lock_form_spec.js
index d26ef7298ce..5e766e9a41c 100644
--- a/spec/frontend/sidebar/components/lock/issuable_lock_form_spec.js
+++ b/spec/frontend/sidebar/components/lock/issuable_lock_form_spec.js
@@ -29,6 +29,7 @@ describe('IssuableLockForm', () => {
const findEditForm = () => wrapper.findComponent(EditForm);
const findSidebarLockStatusTooltip = () =>
getBinding(findSidebarCollapseIcon().element, 'gl-tooltip');
+ const findIssuableLockClickable = () => wrapper.find('[data-testid="issuable-lock"]');
const initStore = (isLocked) => {
if (issuableType === ISSUABLE_TYPE_ISSUE) {
@@ -48,7 +49,7 @@ describe('IssuableLockForm', () => {
store.getters.getNoteableData.discussion_locked = isLocked;
};
- const createComponent = ({ props = {} }, movedMrSidebar = false) => {
+ const createComponent = ({ props = {}, movedMrSidebar = false }) => {
wrapper = shallowMount(IssuableLockForm, {
store,
provide: {
@@ -169,11 +170,27 @@ describe('IssuableLockForm', () => {
`('displays $message when merge request is $locked', async ({ locked, message }) => {
initStore(locked);
- createComponent({}, true);
+ createComponent({ movedMrSidebar: true });
await wrapper.find('.dropdown-item').trigger('click');
expect(toast).toHaveBeenCalledWith(message);
});
});
+
+ describe('moved_mr_sidebar flag', () => {
+ describe('when the flag is off', () => {
+ it('does not show the non editable lock status', () => {
+ createComponent({ movedMrSidebar: false });
+ expect(findIssuableLockClickable().exists()).toBe(false);
+ });
+ });
+
+ describe('when the flag is on', () => {
+ it('does not show the non editable lock status', () => {
+ createComponent({ movedMrSidebar: true });
+ expect(findIssuableLockClickable().exists()).toBe(true);
+ });
+ });
+ });
});
diff --git a/spec/frontend/vue_shared/components/markdown/comment_templates_dropdown_spec.js b/spec/frontend/vue_shared/components/markdown/comment_templates_dropdown_spec.js
index bfa9b7dd706..aea25abb324 100644
--- a/spec/frontend/vue_shared/components/markdown/comment_templates_dropdown_spec.js
+++ b/spec/frontend/vue_shared/components/markdown/comment_templates_dropdown_spec.js
@@ -49,7 +49,7 @@ describe('Comment templates dropdown', () => {
const mockApollo = createMockApolloProvider(savedRepliesResponse);
wrapper = createComponent({ mockApollo });
- wrapper.findByTestId('comment-template-dropdown-toggle').trigger('click');
+ wrapper.find('.js-comment-template-toggle').trigger('click');
await waitForPromises();
@@ -60,7 +60,7 @@ describe('Comment templates dropdown', () => {
const mockApollo = createMockApolloProvider(savedRepliesResponse);
wrapper = createComponent({ mockApollo });
- wrapper.findByTestId('comment-template-dropdown-toggle').trigger('click');
+ wrapper.find('.js-comment-template-toggle').trigger('click');
await waitForPromises();
diff --git a/spec/helpers/ci/runners_helper_spec.rb b/spec/helpers/ci/runners_helper_spec.rb
index a01002a2a1e..ab3f0b193ac 100644
--- a/spec/helpers/ci/runners_helper_spec.rb
+++ b/spec/helpers/ci/runners_helper_spec.rb
@@ -106,6 +106,8 @@ RSpec.describe Ci::RunnersHelper, feature_category: :runner_fleet do
describe '#group_shared_runners_settings_data' do
let_it_be(:parent) { create(:group) }
let_it_be(:group) { create(:group, parent: parent, shared_runners_enabled: false) }
+ let_it_be(:group_with_project) { create(:group, parent: parent) }
+ let_it_be(:project) { create(:project, group: group_with_project) }
let(:runner_constants) do
{
@@ -118,6 +120,8 @@ RSpec.describe Ci::RunnersHelper, feature_category: :runner_fleet do
it 'returns group data for top level group' do
result = {
group_id: parent.id,
+ group_name: parent.name,
+ group_is_empty: 'false',
shared_runners_setting: Namespace::SR_ENABLED,
parent_shared_runners_setting: nil
}.merge(runner_constants)
@@ -128,12 +132,26 @@ RSpec.describe Ci::RunnersHelper, feature_category: :runner_fleet do
it 'returns group data for child group' do
result = {
group_id: group.id,
+ group_name: group.name,
+ group_is_empty: 'true',
shared_runners_setting: Namespace::SR_DISABLED_AND_UNOVERRIDABLE,
parent_shared_runners_setting: Namespace::SR_ENABLED
}.merge(runner_constants)
expect(helper.group_shared_runners_settings_data(group)).to eq result
end
+
+ it 'returns group data for child group with project' do
+ result = {
+ group_id: group_with_project.id,
+ group_name: group_with_project.name,
+ group_is_empty: 'false',
+ shared_runners_setting: Namespace::SR_ENABLED,
+ parent_shared_runners_setting: Namespace::SR_ENABLED
+ }.merge(runner_constants)
+
+ expect(helper.group_shared_runners_settings_data(group_with_project)).to eq result
+ end
end
describe '#group_runners_data_attributes' do
diff --git a/spec/helpers/issues_helper_spec.rb b/spec/helpers/issues_helper_spec.rb
index d940c696fb3..38cbb5a1d66 100644
--- a/spec/helpers/issues_helper_spec.rb
+++ b/spec/helpers/issues_helper_spec.rb
@@ -3,6 +3,8 @@
require 'spec_helper'
RSpec.describe IssuesHelper do
+ include Features::MergeRequestHelpers
+
let_it_be(:project) { create(:project) }
let_it_be_with_reload(:issue) { create(:issue, project: project) }
@@ -235,10 +237,13 @@ RSpec.describe IssuesHelper do
describe '#issue_header_actions_data' do
let(:current_user) { create(:user) }
+ let(:merge_request) { create(:merge_request, :opened, source_project: project, author: current_user) }
+ let(:issuable_sidebar_issue) { serialize_issuable_sidebar(current_user, project, merge_request) }
before do
allow(helper).to receive(:current_user).and_return(current_user)
allow(helper).to receive(:can?).and_return(true)
+ allow(helper).to receive(:issuable_sidebar).and_return(issuable_sidebar_issue)
end
it 'returns expected result' do
@@ -257,10 +262,11 @@ RSpec.describe IssuesHelper do
report_abuse_path: add_category_abuse_reports_path,
reported_user_id: issue.author.id,
reported_from_url: issue_url(issue),
- submit_as_spam_path: mark_as_spam_project_issue_path(project, issue)
+ submit_as_spam_path: mark_as_spam_project_issue_path(project, issue),
+ issuable_email_address: issuable_sidebar_issue[:create_note_email]
}
- expect(helper.issue_header_actions_data(project, issue, current_user)).to include(expected)
+ expect(helper.issue_header_actions_data(project, issue, current_user, issuable_sidebar_issue)).to include(expected)
end
end
diff --git a/spec/helpers/sessions_helper_spec.rb b/spec/helpers/sessions_helper_spec.rb
index c7b8225b866..b8290fa2337 100644
--- a/spec/helpers/sessions_helper_spec.rb
+++ b/spec/helpers/sessions_helper_spec.rb
@@ -52,7 +52,7 @@ RSpec.describe SessionsHelper do
end
describe '#send_rate_limited?' do
- let_it_be(:user) { build(:user) }
+ let(:user) { build_stubbed(:user) }
subject { helper.send_rate_limited?(user) }
@@ -77,30 +77,14 @@ RSpec.describe SessionsHelper do
end
describe '#obfuscated_email' do
- subject { helper.obfuscated_email(email) }
-
- context 'when an email address is normal length' do
- let(:email) { 'alex@gitlab.com' }
-
- it { is_expected.to eq('al**@g*****.com') }
- end
-
- context 'when an email address contains multiple top level domains' do
- let(:email) { 'alex@gl.co.uk' }
+ let(:email) { 'mail@example.com' }
- it { is_expected.to eq('al**@g****.uk') }
- end
-
- context 'when an email address is very short' do
- let(:email) { 'a@b.c' }
-
- it { is_expected.to eq('a@b.c') }
- end
+ subject { helper.obfuscated_email(email) }
- context 'when an email address is even shorter' do
- let(:email) { 'a@b' }
+ it 'delegates to Gitlab::Utils::Email.obfuscated_email' do
+ expect(Gitlab::Utils::Email).to receive(:obfuscated_email).with(email).and_call_original
- it { is_expected.to eq('a@b') }
+ expect(subject).to eq('ma**@e******.com')
end
end
end
diff --git a/spec/lib/gitlab/avatar_cache_spec.rb b/spec/lib/gitlab/avatar_cache_spec.rb
index ffe6f81b6e7..a57d811edaf 100644
--- a/spec/lib/gitlab/avatar_cache_spec.rb
+++ b/spec/lib/gitlab/avatar_cache_spec.rb
@@ -62,40 +62,52 @@ RSpec.describe Gitlab::AvatarCache, :clean_gitlab_redis_cache do
end
describe "#delete_by_email" do
- subject { described_class.delete_by_email(*emails) }
+ shared_examples 'delete emails' do
+ subject { described_class.delete_by_email(*emails) }
- before do
- perform_fetch
- end
+ before do
+ perform_fetch
+ end
- context "no emails, somehow" do
- let(:emails) { [] }
+ context "no emails, somehow" do
+ let(:emails) { [] }
- it { is_expected.to eq(0) }
- end
+ it { is_expected.to eq(0) }
+ end
- context "single email" do
- let(:emails) { "foo@bar.com" }
+ context "single email" do
+ let(:emails) { "foo@bar.com" }
- it "removes the email" do
- expect(read(key, "20:2:true")).to eq(avatar_path)
+ it "removes the email" do
+ expect(read(key, "20:2:true")).to eq(avatar_path)
- expect(subject).to eq(1)
+ expect(subject).to eq(1)
- expect(read(key, "20:2:true")).to eq(nil)
+ expect(read(key, "20:2:true")).to eq(nil)
+ end
end
- end
- context "multiple emails" do
- let(:emails) { ["foo@bar.com", "missing@baz.com"] }
+ context "multiple emails" do
+ let(:emails) { ["foo@bar.com", "missing@baz.com"] }
- it "removes the emails it finds" do
- expect(read(key, "20:2:true")).to eq(avatar_path)
+ it "removes the emails it finds" do
+ expect(read(key, "20:2:true")).to eq(avatar_path)
- expect(subject).to eq(1)
+ expect(subject).to eq(1)
- expect(read(key, "20:2:true")).to eq(nil)
+ expect(read(key, "20:2:true")).to eq(nil)
+ end
+ end
+ end
+
+ context 'when feature flag disabled' do
+ before do
+ stub_feature_flags(use_pipeline_over_multikey: false)
end
+
+ it_behaves_like 'delete emails'
end
+
+ it_behaves_like 'delete emails'
end
end
diff --git a/spec/lib/gitlab/database/with_lock_retries_outside_transaction_spec.rb b/spec/lib/gitlab/database/with_lock_retries_outside_transaction_spec.rb
index 9ccae754a92..82bba31193b 100644
--- a/spec/lib/gitlab/database/with_lock_retries_outside_transaction_spec.rb
+++ b/spec/lib/gitlab/database/with_lock_retries_outside_transaction_spec.rb
@@ -61,12 +61,12 @@ RSpec.describe Gitlab::Database::WithLockRetriesOutsideTransaction, feature_cate
context 'lock_fiber' do
it 'acquires lock successfully' do
- check_exclusive_lock_query = """
+ check_exclusive_lock_query = <<~QUERY
SELECT 1
FROM pg_locks l
JOIN pg_class t ON l.relation = t.oid
WHERE t.relkind = 'r' AND l.mode = 'ExclusiveLock' AND t.relname = '#{Project.table_name}'
- """
+ QUERY
expect(connection.execute(check_exclusive_lock_query).to_a).to be_present
end
diff --git a/spec/lib/gitlab/database/with_lock_retries_spec.rb b/spec/lib/gitlab/database/with_lock_retries_spec.rb
index 7fe6362634b..7e0435c815b 100644
--- a/spec/lib/gitlab/database/with_lock_retries_spec.rb
+++ b/spec/lib/gitlab/database/with_lock_retries_spec.rb
@@ -61,12 +61,12 @@ RSpec.describe Gitlab::Database::WithLockRetries, feature_category: :database do
context 'lock_fiber' do
it 'acquires lock successfully' do
- check_exclusive_lock_query = """
+ check_exclusive_lock_query = <<~QUERY
SELECT 1
FROM pg_locks l
JOIN pg_class t ON l.relation = t.oid
WHERE t.relkind = 'r' AND l.mode = 'ExclusiveLock' AND t.relname = '#{Project.table_name}'
- """
+ QUERY
expect(connection.execute(check_exclusive_lock_query).to_a).to be_present
end
diff --git a/spec/lib/gitlab/discussions_diff/highlight_cache_spec.rb b/spec/lib/gitlab/discussions_diff/highlight_cache_spec.rb
index 30981e4bd7d..0dc0f50b104 100644
--- a/spec/lib/gitlab/discussions_diff/highlight_cache_spec.rb
+++ b/spec/lib/gitlab/discussions_diff/highlight_cache_spec.rb
@@ -41,57 +41,81 @@ RSpec.describe Gitlab::DiscussionsDiff::HighlightCache, :clean_gitlab_redis_cach
end
describe '#read_multiple' do
- it 'reads multiple keys and serializes content into Gitlab::Diff::Line objects' do
- described_class.write_multiple(mapping)
+ shared_examples 'read multiple keys' do
+ it 'reads multiple keys and serializes content into Gitlab::Diff::Line objects' do
+ described_class.write_multiple(mapping)
- found = described_class.read_multiple(mapping.keys)
+ found = described_class.read_multiple(mapping.keys)
- expect(found.size).to eq(2)
- expect(found.first.size).to eq(2)
- expect(found.first).to all(be_a(Gitlab::Diff::Line))
- end
+ expect(found.size).to eq(2)
+ expect(found.first.size).to eq(2)
+ expect(found.first).to all(be_a(Gitlab::Diff::Line))
+ end
- it 'returns nil when cached key is not found' do
- described_class.write_multiple(mapping)
+ it 'returns nil when cached key is not found' do
+ described_class.write_multiple(mapping)
- found = described_class.read_multiple([2, 3])
+ found = described_class.read_multiple([2, 3])
- expect(found.size).to eq(2)
+ expect(found.size).to eq(2)
- expect(found.first).to eq(nil)
- expect(found.second.size).to eq(2)
- expect(found.second).to all(be_a(Gitlab::Diff::Line))
- end
+ expect(found.first).to eq(nil)
+ expect(found.second.size).to eq(2)
+ expect(found.second).to all(be_a(Gitlab::Diff::Line))
+ end
- it 'returns lines which rich_text are HTML-safe' do
- described_class.write_multiple(mapping)
+ it 'returns lines which rich_text are HTML-safe' do
+ described_class.write_multiple(mapping)
+
+ found = described_class.read_multiple(mapping.keys)
+ rich_texts = found.flatten.map(&:rich_text)
+
+ expect(rich_texts).to all(be_html_safe)
+ end
+ end
- found = described_class.read_multiple(mapping.keys)
- rich_texts = found.flatten.map(&:rich_text)
+ context 'when feature flag is disabled' do
+ before do
+ stub_feature_flags(use_pipeline_over_multikey: false)
+ end
- expect(rich_texts).to all(be_html_safe)
+ it_behaves_like 'read multiple keys'
end
+
+ it_behaves_like 'read multiple keys'
end
describe '#clear_multiple' do
- it 'removes all named keys' do
- described_class.write_multiple(mapping)
+ shared_examples 'delete multiple keys' do
+ it 'removes all named keys' do
+ described_class.write_multiple(mapping)
- described_class.clear_multiple(mapping.keys)
+ described_class.clear_multiple(mapping.keys)
- expect(described_class.read_multiple(mapping.keys)).to all(be_nil)
- end
+ expect(described_class.read_multiple(mapping.keys)).to all(be_nil)
+ end
- it 'only removed named keys' do
- to_clear, to_leave = mapping.keys
+ it 'only removed named keys' do
+ to_clear, to_leave = mapping.keys
- described_class.write_multiple(mapping)
- described_class.clear_multiple([to_clear])
+ described_class.write_multiple(mapping)
+ described_class.clear_multiple([to_clear])
- cleared, left = described_class.read_multiple([to_clear, to_leave])
+ cleared, left = described_class.read_multiple([to_clear, to_leave])
- expect(cleared).to be_nil
- expect(left).to all(be_a(Gitlab::Diff::Line))
+ expect(cleared).to be_nil
+ expect(left).to all(be_a(Gitlab::Diff::Line))
+ end
end
+
+ context 'when feature flag is disabled' do
+ before do
+ stub_feature_flags(use_pipeline_over_multikey: false)
+ end
+
+ it_behaves_like 'delete multiple keys'
+ end
+
+ it_behaves_like 'delete multiple keys'
end
end
diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb
index e78e01ae129..06904849ef5 100644
--- a/spec/lib/gitlab/git/repository_spec.rb
+++ b/spec/lib/gitlab/git/repository_spec.rb
@@ -1858,8 +1858,8 @@ RSpec.describe Gitlab::Git::Repository, feature_category: :source_code_managemen
context 'when Gitaly returns Internal error' do
before do
- expect(repository.gitaly_ref_client)
- .to receive(:find_tag)
+ expect(Gitlab::GitalyClient)
+ .to receive(:call)
.and_raise(GRPC::Internal, "tag not found")
end
@@ -1868,8 +1868,8 @@ RSpec.describe Gitlab::Git::Repository, feature_category: :source_code_managemen
context 'when Gitaly returns tag_not_found error' do
before do
- expect(repository.gitaly_ref_client)
- .to receive(:find_tag)
+ expect(Gitlab::GitalyClient)
+ .to receive(:call)
.and_raise(new_detailed_error(GRPC::Core::StatusCodes::NOT_FOUND,
"tag was not found",
Gitaly::FindTagError.new(tag_not_found: Gitaly::ReferenceNotFoundError.new)))
diff --git a/spec/lib/gitlab/git/wraps_gitaly_errors_spec.rb b/spec/lib/gitlab/git/wraps_gitaly_errors_spec.rb
index e551dfaa1c5..c321d4bbdb9 100644
--- a/spec/lib/gitlab/git/wraps_gitaly_errors_spec.rb
+++ b/spec/lib/gitlab/git/wraps_gitaly_errors_spec.rb
@@ -2,24 +2,81 @@
require 'spec_helper'
-RSpec.describe Gitlab::Git::WrapsGitalyErrors do
+RSpec.describe Gitlab::Git::WrapsGitalyErrors, feature_category: :gitaly do
subject(:wrapper) do
klazz = Class.new { include Gitlab::Git::WrapsGitalyErrors }
klazz.new
end
describe "#wrapped_gitaly_errors" do
- mapping = {
- GRPC::NotFound => Gitlab::Git::Repository::NoRepository,
- GRPC::InvalidArgument => ArgumentError,
- GRPC::DeadlineExceeded => Gitlab::Git::CommandTimedOut,
- GRPC::BadStatus => Gitlab::Git::CommandError
- }
-
- mapping.each do |grpc_error, error|
- it "wraps #{grpc_error} in a #{error}" do
- expect { wrapper.wrapped_gitaly_errors { raise grpc_error, 'wrapped' } }
- .to raise_error(error)
+ where(:original_error, :wrapped_error) do
+ [
+ [GRPC::NotFound, Gitlab::Git::Repository::NoRepository],
+ [GRPC::InvalidArgument, ArgumentError],
+ [GRPC::DeadlineExceeded, Gitlab::Git::CommandTimedOut],
+ [GRPC::BadStatus, Gitlab::Git::CommandError]
+ ]
+ end
+
+ with_them do
+ it "wraps #{params[:original_error]} in a #{params[:wrapped_error]}" do
+ expect { wrapper.wrapped_gitaly_errors { raise original_error, 'wrapped' } }
+ .to raise_error(wrapped_error)
+ end
+ end
+
+ context 'when wrap GRPC::ResourceExhausted' do
+ context 'with Gitaly::LimitError detail' do
+ let(:original_error) do
+ new_detailed_error(
+ GRPC::Core::StatusCodes::RESOURCE_EXHAUSTED,
+ 'resource exhausted',
+ Gitaly::LimitError.new(
+ error_message: "maximum time in concurrency queue reached",
+ retry_after: Google::Protobuf::Duration.new(seconds: 5, nanos: 1500)
+ )
+ )
+ end
+
+ it "wraps in a Gitlab::Git::ResourceExhaustedError with error message" do
+ expect { wrapper.wrapped_gitaly_errors { raise original_error } }.to raise_error do |wrapped_error|
+ expect(wrapped_error).to be_a(Gitlab::Git::ResourceExhaustedError)
+ expect(wrapped_error.message).to eql(
+ "Upstream Gitaly has been exhausted: maximum time in concurrency queue reached. Try again later"
+ )
+ expect(wrapped_error.headers).to eql({ 'Retry-After' => 5 })
+ end
+ end
+ end
+
+ context 'with Gitaly::LimitError detail without retry after' do
+ let(:original_error) do
+ new_detailed_error(
+ GRPC::Core::StatusCodes::RESOURCE_EXHAUSTED,
+ 'resource exhausted',
+ Gitaly::LimitError.new(error_message: "maximum time in concurrency queue reached")
+ )
+ end
+
+ it "wraps in a Gitlab::Git::ResourceExhaustedError with error message" do
+ expect { wrapper.wrapped_gitaly_errors { raise original_error } }.to raise_error do |wrapped_error|
+ expect(wrapped_error).to be_a(Gitlab::Git::ResourceExhaustedError)
+ expect(wrapped_error.message).to eql(
+ "Upstream Gitaly has been exhausted: maximum time in concurrency queue reached. Try again later"
+ )
+ expect(wrapped_error.headers).to eql({})
+ end
+ end
+ end
+
+ context 'without Gitaly::LimitError detail' do
+ it("wraps in a Gitlab::Git::ResourceExhaustedError with default message") {
+ expect { wrapper.wrapped_gitaly_errors { raise GRPC::ResourceExhausted } }.to raise_error do |wrapped_error|
+ expect(wrapped_error).to be_a(Gitlab::Git::ResourceExhaustedError)
+ expect(wrapped_error.message).to eql("Upstream Gitaly has been exhausted. Try again later")
+ expect(wrapped_error.headers).to eql({})
+ end
+ }
end
end
diff --git a/spec/lib/gitlab/reactive_cache_set_cache_spec.rb b/spec/lib/gitlab/reactive_cache_set_cache_spec.rb
index 207ac1c0eaa..a78d15134fa 100644
--- a/spec/lib/gitlab/reactive_cache_set_cache_spec.rb
+++ b/spec/lib/gitlab/reactive_cache_set_cache_spec.rb
@@ -46,17 +46,29 @@ RSpec.describe Gitlab::ReactiveCacheSetCache, :clean_gitlab_redis_cache do
end
describe '#clear_cache!', :use_clean_rails_redis_caching do
- it 'deletes the cached items' do
- # Cached key and value
- Rails.cache.write('test_item', 'test_value')
- # Add key to set
- cache.write(cache_prefix, 'test_item')
+ shared_examples 'clears cache' do
+ it 'deletes the cached items' do
+ # Cached key and value
+ Rails.cache.write('test_item', 'test_value')
+ # Add key to set
+ cache.write(cache_prefix, 'test_item')
- expect(cache.read(cache_prefix)).to contain_exactly('test_item')
- cache.clear_cache!(cache_prefix)
+ expect(cache.read(cache_prefix)).to contain_exactly('test_item')
+ cache.clear_cache!(cache_prefix)
+
+ expect(cache.read(cache_prefix)).to be_empty
+ end
+ end
- expect(cache.read(cache_prefix)).to be_empty
+ context 'when featuer flag disabled' do
+ before do
+ stub_feature_flags(use_pipeline_over_multikey: false)
+ end
+
+ it_behaves_like 'clears cache'
end
+
+ it_behaves_like 'clears cache'
end
describe '#include?' do
diff --git a/spec/lib/gitlab/repository_set_cache_spec.rb b/spec/lib/gitlab/repository_set_cache_spec.rb
index c93fd884347..65a50b68c44 100644
--- a/spec/lib/gitlab/repository_set_cache_spec.rb
+++ b/spec/lib/gitlab/repository_set_cache_spec.rb
@@ -72,48 +72,60 @@ RSpec.describe Gitlab::RepositorySetCache, :clean_gitlab_redis_cache do
end
describe '#expire' do
- subject { cache.expire(*keys) }
+ shared_examples 'expires varying amount of keys' do
+ subject { cache.expire(*keys) }
- before do
- cache.write(:foo, ['value'])
- cache.write(:bar, ['value2'])
- end
+ before do
+ cache.write(:foo, ['value'])
+ cache.write(:bar, ['value2'])
+ end
- it 'actually wrote the values' do
- expect(cache.read(:foo)).to contain_exactly('value')
- expect(cache.read(:bar)).to contain_exactly('value2')
- end
+ it 'actually wrote the values' do
+ expect(cache.read(:foo)).to contain_exactly('value')
+ expect(cache.read(:bar)).to contain_exactly('value2')
+ end
- context 'single key' do
- let(:keys) { %w(foo) }
+ context 'single key' do
+ let(:keys) { %w(foo) }
- it { is_expected.to eq(1) }
+ it { is_expected.to eq(1) }
- it 'deletes the given key from the cache' do
- subject
+ it 'deletes the given key from the cache' do
+ subject
- expect(cache.read(:foo)).to be_empty
+ expect(cache.read(:foo)).to be_empty
+ end
end
- end
- context 'multiple keys' do
- let(:keys) { %w(foo bar) }
+ context 'multiple keys' do
+ let(:keys) { %w(foo bar) }
- it { is_expected.to eq(2) }
+ it { is_expected.to eq(2) }
- it 'deletes the given keys from the cache' do
- subject
+ it 'deletes the given keys from the cache' do
+ subject
- expect(cache.read(:foo)).to be_empty
- expect(cache.read(:bar)).to be_empty
+ expect(cache.read(:foo)).to be_empty
+ expect(cache.read(:bar)).to be_empty
+ end
+ end
+
+ context 'no keys' do
+ let(:keys) { [] }
+
+ it { is_expected.to eq(0) }
end
end
- context 'no keys' do
- let(:keys) { [] }
+ context 'when feature flag is disabled' do
+ before do
+ stub_feature_flags(use_pipeline_over_multikey: false)
+ end
- it { is_expected.to eq(0) }
+ it_behaves_like 'expires varying amount of keys'
end
+
+ it_behaves_like 'expires varying amount of keys'
end
describe '#exist?' do
diff --git a/spec/lib/gitlab/utils/email_spec.rb b/spec/lib/gitlab/utils/email_spec.rb
index d7a881d8655..c81c2558f70 100644
--- a/spec/lib/gitlab/utils/email_spec.rb
+++ b/spec/lib/gitlab/utils/email_spec.rb
@@ -8,13 +8,20 @@ RSpec.describe Gitlab::Utils::Email, feature_category: :service_desk do
describe '.obfuscated_email' do
where(:input, :output) do
- 'alex@gitlab.com' | 'al**@g*****.com'
- 'alex@gl.co.uk' | 'al**@g****.uk'
- 'a@b.c' | 'a@b.c'
- 'q@example.com' | 'q@e******.com'
- 'q@w.' | 'q@w.'
- 'a@b' | 'a@b'
- 'no mail' | 'no mail'
+ 'alex@gitlab.com' | 'al**@g*****.com'
+ 'alex@gl.co.uk' | 'al**@g****.uk'
+ 'a@b.c' | 'aa@b.c'
+ 'qqwweerrttyy@example.com' | 'qq**********@e******.com'
+ 'getsuperfancysupport@paywhatyouwant.accounting' | 'ge******************@p*************.accounting'
+ 'q@example.com' | 'qq@e******.com'
+ 'q@w.' | 'qq@w.'
+ 'a@b' | 'aa@b'
+ 'trun"@"e@example.com' | 'tr******@e******.com'
+ '@' | '@'
+ 'n' | 'n'
+ 'no mail' | 'n******'
+ 'truncated@exa' | 'tr*******@exa'
+ '' | ''
end
with_them do
@@ -29,9 +36,14 @@ RSpec.describe Gitlab::Utils::Email, feature_category: :service_desk do
'qqwweerrttyy@example.com' | 'qq*****@e*****.c**'
'getsuperfancysupport@paywhatyouwant.accounting' | 'ge*****@p*****.a**'
'q@example.com' | 'qq*****@e*****.c**'
- 'q@w.' | 'q@w.'
- 'a@b' | 'a@b'
- 'no mail' | 'no mail'
+ 'q@w.' | 'qq*****@w*****.'
+ 'a@b' | 'aa*****@b**'
+ 'trun"@"e@example.com' | 'tr*****@e*****.c**'
+ '@' | '@'
+ 'no mail' | 'n**'
+ 'n' | 'n**'
+ 'truncated@exa' | 'tr*****@e**'
+ '' | ''
end
with_them do
diff --git a/spec/models/packages/package_spec.rb b/spec/models/packages/package_spec.rb
index fcf60f0559a..3291eba48d3 100644
--- a/spec/models/packages/package_spec.rb
+++ b/spec/models/packages/package_spec.rb
@@ -847,6 +847,14 @@ RSpec.describe Packages::Package, type: :model, feature_category: :package_regis
is_expected.to match_array([package])
end
end
+
+ describe '.preload_conan_metadatum' do
+ subject { described_class.preload_conan_metadatum }
+
+ it 'loads conan metadatum' do
+ expect(subject.first.association(:conan_metadatum)).to be_loaded
+ end
+ end
end
describe '.without_nuget_temporary_name' do
diff --git a/spec/requests/api/api_spec.rb b/spec/requests/api/api_spec.rb
index d5ad0779bd9..219c7dbdbc5 100644
--- a/spec/requests/api/api_spec.rb
+++ b/spec/requests/api/api_spec.rb
@@ -359,4 +359,26 @@ RSpec.describe API::API, feature_category: :system_access do
end
end
end
+
+ describe 'Handle Gitlab::Git::ResourceExhaustedError exception' do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project) { create(:project, :repository, creator: user) }
+
+ before do
+ project.add_maintainer(user)
+ allow(Gitlab::GitalyClient).to receive(:call).with(any_args).and_raise(
+ Gitlab::Git::ResourceExhaustedError.new("Upstream Gitaly has been exhausted. Try again later", 50)
+ )
+ end
+
+ it 'returns 429 status with exhausted' do
+ get api("/projects/#{project.id}/repository/commits", user)
+
+ expect(response).to have_gitlab_http_status(:too_many_requests)
+ expect(response.headers['Retry-After']).to be(50)
+ expect(json_response).to eql(
+ 'message' => 'Upstream Gitaly has been exhausted. Try again later'
+ )
+ end
+ end
end
diff --git a/spec/requests/api/conan_project_packages_spec.rb b/spec/requests/api/conan_project_packages_spec.rb
index 814745f9e29..06f175233db 100644
--- a/spec/requests/api/conan_project_packages_spec.rb
+++ b/spec/requests/api/conan_project_packages_spec.rb
@@ -33,6 +33,29 @@ RSpec.describe API::ConanProjectPackages, feature_category: :package_registry do
subject { get api(url), params: params }
end
+
+ context 'with access to package registry for everyone' do
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
+ project.project_feature.update!(package_registry_access_level: ProjectFeature::PUBLIC)
+
+ get api(url), params: params
+ end
+
+ subject { json_response['results'] }
+
+ context 'with a matching name' do
+ let(:params) { { q: package.conan_recipe } }
+
+ it { is_expected.to contain_exactly(package.conan_recipe) }
+ end
+
+ context 'with a * wildcard' do
+ let(:params) { { q: "#{package.name[0, 3]}*" } }
+
+ it { is_expected.to contain_exactly(package.conan_recipe) }
+ end
+ end
end
describe 'GET /api/v4/projects/:id/packages/conan/v1/users/authenticate' do
diff --git a/spec/services/packages/conan/search_service_spec.rb b/spec/services/packages/conan/search_service_spec.rb
index 9e8be164d8c..83ece404d5f 100644
--- a/spec/services/packages/conan/search_service_spec.rb
+++ b/spec/services/packages/conan/search_service_spec.rb
@@ -9,7 +9,7 @@ RSpec.describe Packages::Conan::SearchService, feature_category: :package_regist
let!(:conan_package) { create(:conan_package, project: project) }
let!(:conan_package2) { create(:conan_package, project: project) }
- subject { described_class.new(user, query: query) }
+ subject { described_class.new(project, user, query: query) }
before do
project.add_developer(user)
@@ -24,7 +24,7 @@ RSpec.describe Packages::Conan::SearchService, feature_category: :package_regist
result = subject.execute
expect(result.status).to eq :success
- expect(result.payload).to eq(results: [conan_package.conan_recipe, conan_package2.conan_recipe])
+ expect(result.payload).to eq(results: [conan_package2.conan_recipe, conan_package.conan_recipe])
end
end
@@ -71,5 +71,29 @@ RSpec.describe Packages::Conan::SearchService, feature_category: :package_regist
expect(result.payload).to eq(results: [])
end
end
+
+ context 'for project' do
+ let_it_be(:project2) { create(:project, :public) }
+ let(:query) { conan_package.name }
+ let!(:conan_package3) { create(:conan_package, name: conan_package.name, project: project2) }
+
+ context 'when passing a project' do
+ it 'returns only packages of the given project' do
+ result = subject.execute
+
+ expect(result.status).to eq :success
+ expect(result[:results]).to match_array([conan_package.conan_recipe])
+ end
+ end
+
+ context 'when passing a project with nil' do
+ it 'returns all packages' do
+ result = described_class.new(nil, user, query: query).execute
+
+ expect(result.status).to eq :success
+ expect(result[:results]).to eq([conan_package3.conan_recipe, conan_package.conan_recipe])
+ end
+ end
+ end
end
end
diff --git a/spec/services/packages/conan/single_package_search_service_spec.rb b/spec/services/packages/conan/single_package_search_service_spec.rb
new file mode 100644
index 00000000000..1d95d1d4f64
--- /dev/null
+++ b/spec/services/packages/conan/single_package_search_service_spec.rb
@@ -0,0 +1,45 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Packages::Conan::SinglePackageSearchService, feature_category: :package_registry do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project) { create(:project, :public) }
+
+ let!(:conan_package) { create(:conan_package, project: project) }
+ let!(:conan_package2) { create(:conan_package, project: project) }
+
+ describe '#execute' do
+ context 'with a valid query and user with permissions' do
+ before do
+ allow_next_instance_of(described_class) do |service|
+ allow(service).to receive(:can_access_project_package?).and_return(true)
+ end
+ end
+
+ it 'returns the correct package' do
+ [conan_package, conan_package2].each do |package|
+ result = described_class.new(package.conan_recipe, user).execute
+
+ expect(result.status).to eq :success
+ expect(result[:results]).to match_array([package.conan_recipe])
+ end
+ end
+ end
+
+ context 'with a user without permissions' do
+ before do
+ allow_next_instance_of(described_class) do |service|
+ allow(service).to receive(:can_access_project_package?).and_return(false)
+ end
+ end
+
+ it 'returns an empty array' do
+ result = described_class.new(conan_package.conan_recipe, user).execute
+
+ expect(result.status).to eq :success
+ expect(result[:results]).to match_array([])
+ end
+ end
+ end
+end