From f64a639bcfa1fc2bc89ca7db268f594306edfd7c Mon Sep 17 00:00:00 2001
From: GitLab Bot
Date: Tue, 16 Mar 2021 18:18:33 +0000
Subject: Add latest changes from gitlab-org/gitlab@13-10-stable-ee
---
.../alert_notification_service_shared_examples.rb | 15 +
.../banzai/filters/emoji_shared_examples.rb | 46 ++
.../multiple_issue_boards_shared_examples.rb | 2 +
.../create_notes_rate_limit_shared_examples.rb | 44 ++
.../controllers/snippet_blob_shared_examples.rb | 32 ++
.../comment_and_close_button_shared_examples.rb | 29 -
.../features/discussion_comments_shared_example.rb | 216 ++++++--
.../issuable_invite_members_shared_examples.rb | 10 +-
.../project_upload_files_shared_examples.rb | 34 +-
.../features/variable_list_shared_examples.rb | 26 +-
.../graphql/mutation_shared_examples.rb | 2 +-
.../mutations/boards_create_shared_examples.rb | 6 +-
.../mutations/can_mutate_spammable_examples.rb | 8 +-
.../graphql/notes_on_noteables_shared_examples.rb | 2 +-
.../gitlab_style_deprecations_shared_examples.rb | 2 +-
...suable_description_templates_shared_examples.rb | 4 +-
.../cycle_analytics/event_shared_examples.rb | 1 +
.../issuable_activity_shared_examples.rb | 37 ++
.../issue_activity_shared_examples.rb | 27 -
.../lib/sentry/client_shared_examples.rb | 4 +-
.../active_record_subscriber_shared_examples.rb | 137 +++++
.../models/application_setting_shared_examples.rb | 15 +-
.../boards/user_preferences_shared_examples.rb | 68 +++
.../models/chat_service_shared_examples.rb | 11 +-
.../models/concerns/timebox_shared_examples.rb | 4 +-
.../models/email_format_shared_examples.rb | 4 +-
...ack_mattermost_notifications_shared_examples.rb | 241 ++++-----
.../shared_examples/models/wiki_shared_examples.rb | 46 +-
.../time_tracking_quick_action_shared_examples.rb | 2 +-
.../requests/api/conan_packages_shared_examples.rb | 106 ++--
...oup_and_project_boards_query_shared_examples.rb | 2 +-
...up_and_project_packages_list_shared_examples.rb | 51 ++
.../logging_application_context_shared_examples.rb | 16 +
.../requests/api/npm_packages_shared_examples.rb | 593 ++++++++++++++-------
.../api/rubygems_packages_shared_examples.rb | 194 +++++++
.../service_desk_issue_templates_examples.rb | 12 +
.../boards/update_boards_shared_examples.rb | 83 +++
.../packages/maven/metadata_shared_examples.rb | 57 ++
38 files changed, 1640 insertions(+), 549 deletions(-)
create mode 100644 spec/support/shared_examples/banzai/filters/emoji_shared_examples.rb
create mode 100644 spec/support/shared_examples/controllers/create_notes_rate_limit_shared_examples.rb
delete mode 100644 spec/support/shared_examples/features/comment_and_close_button_shared_examples.rb
create mode 100644 spec/support/shared_examples/lib/gitlab/usage_data_counters/issuable_activity_shared_examples.rb
delete mode 100644 spec/support/shared_examples/lib/gitlab/usage_data_counters/issue_activity_shared_examples.rb
create mode 100644 spec/support/shared_examples/metrics/active_record_subscriber_shared_examples.rb
create mode 100644 spec/support/shared_examples/models/boards/user_preferences_shared_examples.rb
create mode 100644 spec/support/shared_examples/requests/api/graphql/packages/group_and_project_packages_list_shared_examples.rb
create mode 100644 spec/support/shared_examples/requests/api/rubygems_packages_shared_examples.rb
create mode 100644 spec/support/shared_examples/service_desk_issue_templates_examples.rb
create mode 100644 spec/support/shared_examples/services/boards/update_boards_shared_examples.rb
create mode 100644 spec/support/shared_examples/services/packages/maven/metadata_shared_examples.rb
(limited to 'spec/support/shared_examples')
diff --git a/spec/support/shared_examples/alert_notification_service_shared_examples.rb b/spec/support/shared_examples/alert_notification_service_shared_examples.rb
index 7bd6df8c608..fc935effe0e 100644
--- a/spec/support/shared_examples/alert_notification_service_shared_examples.rb
+++ b/spec/support/shared_examples/alert_notification_service_shared_examples.rb
@@ -27,3 +27,18 @@ RSpec.shared_examples 'Alert Notification Service sends no notifications' do |ht
end
end
end
+
+RSpec.shared_examples 'creates status-change system note for an auto-resolved alert' do
+ it 'has 2 new system notes' do
+ expect { subject }.to change(Note, :count).by(2)
+ expect(Note.last.note).to include('Resolved')
+ end
+end
+
+# Requires `source` to be defined
+RSpec.shared_examples 'creates single system note based on the source of the alert' do
+ it 'has one new system note' do
+ expect { subject }.to change(Note, :count).by(1)
+ expect(Note.last.note).to include(source)
+ end
+end
diff --git a/spec/support/shared_examples/banzai/filters/emoji_shared_examples.rb b/spec/support/shared_examples/banzai/filters/emoji_shared_examples.rb
new file mode 100644
index 00000000000..da305f5ccaa
--- /dev/null
+++ b/spec/support/shared_examples/banzai/filters/emoji_shared_examples.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'emoji filter' do
+ it 'keeps whitespace intact' do
+ doc = filter("This deserves a #{emoji_name}, big time.")
+
+ expect(doc.to_html).to match(/^This deserves a , big time\.\z/)
+ end
+
+ it 'does not match emoji in a string' do
+ doc = filter("'2a00:a4c0#{emoji_name}:1'")
+
+ expect(doc.css('gl-emoji')).to be_empty
+ end
+
+ it 'ignores non existent/unsupported emoji' do
+ exp = ':foo:
'
+ doc = filter(exp)
+
+ expect(doc.to_html).to eq(exp)
+ end
+
+ it 'matches with adjacent text' do
+ doc = filter("#{emoji_name.delete(':')} (#{emoji_name})")
+
+ expect(doc.css('gl-emoji').size).to eq 1
+ end
+
+ it 'does not match emoji in a pre tag' do
+ doc = filter("
#{emoji_name}
")
+
+ expect(doc.css('img')).to be_empty
+ end
+
+ it 'does not match emoji in code tag' do
+ doc = filter("#{emoji_name} wow
")
+
+ expect(doc.css('img')).to be_empty
+ end
+
+ it 'does not match emoji in tt tag' do
+ doc = filter("#{emoji_name} yes!
")
+
+ expect(doc.css('img')).to be_empty
+ end
+end
diff --git a/spec/support/shared_examples/boards/multiple_issue_boards_shared_examples.rb b/spec/support/shared_examples/boards/multiple_issue_boards_shared_examples.rb
index 7f49d20c83e..9c8006ce4f1 100644
--- a/spec/support/shared_examples/boards/multiple_issue_boards_shared_examples.rb
+++ b/spec/support/shared_examples/boards/multiple_issue_boards_shared_examples.rb
@@ -9,6 +9,8 @@ RSpec.shared_examples 'multiple issue boards' do
login_as(user)
+ stub_feature_flags(board_new_list: false)
+
visit boards_path
wait_for_requests
end
diff --git a/spec/support/shared_examples/controllers/create_notes_rate_limit_shared_examples.rb b/spec/support/shared_examples/controllers/create_notes_rate_limit_shared_examples.rb
new file mode 100644
index 00000000000..74a98c20383
--- /dev/null
+++ b/spec/support/shared_examples/controllers/create_notes_rate_limit_shared_examples.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+#
+# Requires a context containing:
+# - user
+# - params
+# - request_full_path
+
+RSpec.shared_examples 'request exceeding rate limit' do
+ before do
+ stub_application_setting(notes_create_limit: 2)
+ 2.times { post :create, params: params }
+ end
+
+ it 'prevents from creating more notes', :request_store do
+ expect { post :create, params: params }
+ .to change { Note.count }.by(0)
+
+ expect(response).to have_gitlab_http_status(:too_many_requests)
+ expect(response.body).to eq(_('This endpoint has been requested too many times. Try again later.'))
+ end
+
+ it 'logs the event in auth.log' do
+ attributes = {
+ message: 'Application_Rate_Limiter_Request',
+ env: :notes_create_request_limit,
+ remote_ip: '0.0.0.0',
+ request_method: 'POST',
+ path: request_full_path,
+ user_id: user.id,
+ username: user.username
+ }
+
+ expect(Gitlab::AuthLogger).to receive(:error).with(attributes).once
+ post :create, params: params
+ end
+
+ it 'allows user in allow-list to create notes, even if the case is different' do
+ user.update_attribute(:username, user.username.titleize)
+ stub_application_setting(notes_create_limit_allowlist: ["#{user.username.downcase}"])
+
+ post :create, params: params
+ expect(response).to have_gitlab_http_status(:found)
+ end
+end
diff --git a/spec/support/shared_examples/controllers/snippet_blob_shared_examples.rb b/spec/support/shared_examples/controllers/snippet_blob_shared_examples.rb
index c3e8f807afb..62aaec85162 100644
--- a/spec/support/shared_examples/controllers/snippet_blob_shared_examples.rb
+++ b/spec/support/shared_examples/controllers/snippet_blob_shared_examples.rb
@@ -17,6 +17,38 @@ RSpec.shared_examples 'raw snippet blob' do
end
end
+ context 'Content Disposition' do
+ context 'when the disposition is inline' do
+ let(:inline) { true }
+
+ it 'returns inline in the content disposition header' do
+ subject
+
+ expect(response.header['Content-Disposition']).to eq('inline')
+ end
+ end
+
+ context 'when the disposition is attachment' do
+ let(:inline) { false }
+
+ it 'returns attachment plus the filename in the content disposition header' do
+ subject
+
+ expect(response.header['Content-Disposition']).to match "attachment; filename=\"#{filepath}\""
+ end
+
+ context 'when the feature flag attachment_with_filename is disabled' do
+ it 'returns just attachment in the disposition header' do
+ stub_feature_flags(attachment_with_filename: false)
+
+ subject
+
+ expect(response.header['Content-Disposition']).to eq 'attachment'
+ end
+ end
+ end
+ end
+
context 'with invalid file path' do
let(:filepath) { 'doesnotexist' }
diff --git a/spec/support/shared_examples/features/comment_and_close_button_shared_examples.rb b/spec/support/shared_examples/features/comment_and_close_button_shared_examples.rb
deleted file mode 100644
index 4ee2840ed9f..00000000000
--- a/spec/support/shared_examples/features/comment_and_close_button_shared_examples.rb
+++ /dev/null
@@ -1,29 +0,0 @@
-# frozen_string_literal: true
-
-RSpec.shared_examples 'page with comment and close button' do |button_text|
- context 'when remove_comment_close_reopen feature flag is enabled' do
- before do
- stub_feature_flags(remove_comment_close_reopen: true)
- setup
- end
-
- it "does not show #{button_text} button" do
- within '.note-form-actions' do
- expect(page).not_to have_button(button_text)
- end
- end
- end
-
- context 'when remove_comment_close_reopen feature flag is disabled' do
- before do
- stub_feature_flags(remove_comment_close_reopen: false)
- setup
- end
-
- it "shows #{button_text} button" do
- within '.note-form-actions' do
- expect(page).to have_button(button_text)
- end
- end
- end
-end
diff --git a/spec/support/shared_examples/features/discussion_comments_shared_example.rb b/spec/support/shared_examples/features/discussion_comments_shared_example.rb
index 6bebd59ed70..86ba2821c78 100644
--- a/spec/support/shared_examples/features/discussion_comments_shared_example.rb
+++ b/spec/support/shared_examples/features/discussion_comments_shared_example.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-RSpec.shared_examples 'thread comments' do |resource_name|
+RSpec.shared_examples 'thread comments for commit and snippet' do |resource_name|
let(:form_selector) { '.js-main-target-form' }
let(:dropdown_selector) { "#{form_selector} .comment-type-dropdown" }
let(:toggle_selector) { "#{dropdown_selector} .dropdown-toggle" }
@@ -24,23 +24,6 @@ RSpec.shared_examples 'thread comments' do |resource_name|
expect(new_comment).not_to have_selector '.discussion'
end
- if resource_name == 'issue'
- it "clicking 'Comment & close #{resource_name}' will post a comment and close the #{resource_name}" do
- find("#{form_selector} .note-textarea").send_keys(comment)
-
- click_button 'Comment & close issue'
-
- wait_for_all_requests
-
- expect(page).to have_content(comment)
- expect(page).to have_content "@#{user.username} closed"
-
- new_comment = all(comments_selector).last
-
- expect(new_comment).not_to have_selector '.discussion'
- end
- end
-
describe 'when the toggle is clicked' do
before do
find("#{form_selector} .note-textarea").send_keys(comment)
@@ -110,33 +93,172 @@ RSpec.shared_examples 'thread comments' do |resource_name|
end
it 'updates the submit button text and closes the dropdown' do
- button = find(submit_selector)
+ expect(find(submit_selector).value).to eq 'Start thread'
- # on issues page, the submit input is a , on other pages it is
- if button.tag_name == 'button'
- expect(find(submit_selector)).to have_content 'Start thread'
- else
- expect(find(submit_selector).value).to eq 'Start thread'
+ expect(page).not_to have_selector menu_selector
+ end
+
+ describe 'creating a thread' do
+ before do
+ find(submit_selector).click
+ wait_for_requests
+
+ find(comments_selector, match: :first)
end
- expect(page).not_to have_selector menu_selector
+ def submit_reply(text)
+ find("#{comments_selector} .js-vue-discussion-reply").click
+ find("#{comments_selector} .note-textarea").send_keys(text)
+
+ find("#{comments_selector} .js-comment-button").click
+ wait_for_requests
+ end
+
+ it 'clicking "Start thread" will post a thread' do
+ expect(page).to have_content(comment)
+
+ new_comment = all(comments_selector).last
+
+ expect(new_comment).to have_selector('.discussion')
+ end
end
- if resource_name =~ /(issue|merge request)/
- it 'updates the close button text' do
- expect(find(close_selector)).to have_content "Start thread & close #{resource_name}"
+ describe 'when opening the menu' do
+ before do
+ find(toggle_selector).click
+ end
+
+ it 'has "Start thread" selected' do
+ find("#{menu_selector} li", match: :first)
+ items = all("#{menu_selector} li")
+
+ expect(items.first).to have_content 'Comment'
+ expect(items.first).not_to have_selector '[data-testid="check-icon"]'
+ expect(items.first['class']).not_to match 'droplab-item-selected'
+
+ expect(items.last).to have_content 'Start thread'
+ expect(items.last).to have_selector '[data-testid="check-icon"]'
+ expect(items.last['class']).to match 'droplab-item-selected'
end
- it 'typing does not change the close button text' do
- find("#{form_selector} .note-textarea").send_keys('b')
+ describe 'when selecting "Comment"' do
+ before do
+ find("#{menu_selector} li", match: :first).click
+ end
+
+ it 'updates the submit button text and closes the dropdown' do
+ button = find(submit_selector)
+
+ expect(button.value).to eq 'Comment'
+
+ expect(page).not_to have_selector menu_selector
+ end
- expect(find(close_selector)).to have_content "Start thread & close #{resource_name}"
+ it 'has "Comment" selected when opening the menu', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/196825' do
+ find(toggle_selector).click
+
+ find("#{menu_selector} li", match: :first)
+ items = all("#{menu_selector} li")
+
+ aggregate_failures do
+ expect(items.first).to have_content 'Comment'
+ expect(items.first).to have_selector '[data-testid="check-icon"]'
+ expect(items.first['class']).to match 'droplab-item-selected'
+
+ expect(items.last).to have_content 'Start thread'
+ expect(items.last).not_to have_selector '[data-testid="check-icon"]'
+ expect(items.last['class']).not_to match 'droplab-item-selected'
+ end
+ end
end
end
+ end
+ end
+end
+
+RSpec.shared_examples 'thread comments for issue, epic and merge request' do |resource_name|
+ let(:form_selector) { '.js-main-target-form' }
+ let(:dropdown_selector) { "#{form_selector} [data-testid='comment-button']" }
+ let(:submit_button_selector) { "#{dropdown_selector} .split-content-button" }
+ let(:toggle_selector) { "#{dropdown_selector} .dropdown-toggle-split" }
+ let(:menu_selector) { "#{dropdown_selector} .dropdown-menu" }
+ let(:close_selector) { "#{form_selector} .btn-comment-and-close" }
+ let(:comments_selector) { '.timeline > .note.timeline-entry' }
+ let(:comment) { 'My comment' }
+
+ it 'clicking "Comment" will post a comment' do
+ expect(page).to have_selector toggle_selector
+
+ find("#{form_selector} .note-textarea").send_keys(comment)
+
+ find(submit_button_selector).click
+
+ expect(page).to have_content(comment)
+
+ new_comment = all(comments_selector).last
+
+ expect(new_comment).not_to have_selector '.discussion'
+ end
+
+ if resource_name == 'issue'
+ it "clicking 'Comment & close #{resource_name}' will post a comment and close the #{resource_name}" do
+ find("#{form_selector} .note-textarea").send_keys(comment)
+
+ click_button 'Comment & close issue'
+
+ wait_for_all_requests
+
+ expect(page).to have_content(comment)
+ expect(page).to have_content "@#{user.username} closed"
+
+ new_comment = all(comments_selector).last
+
+ expect(new_comment).not_to have_selector '.discussion'
+ end
+ end
+
+ describe 'when the toggle is clicked' do
+ before do
+ find("#{form_selector} .note-textarea").send_keys(comment)
+
+ find(toggle_selector).click
+ end
+
+ it 'has a "Comment" item (selected by default) and "Start thread" item' do
+ expect(page).to have_selector menu_selector
+
+ find("#{menu_selector} li", match: :first)
+ items = all("#{menu_selector} li")
+
+ expect(page).to have_selector("#{dropdown_selector}[data-track-label='comment_button']")
+
+ expect(items.first).to have_content 'Comment'
+ expect(items.first).to have_content "Add a general comment to this #{resource_name}."
+
+ expect(items.last).to have_content 'Start thread'
+ expect(items.last).to have_content "Discuss a specific suggestion or question#{' that needs to be resolved' if resource_name == 'merge request'}."
+ end
+
+ it 'closes the menu when clicking the toggle or body' do
+ find(toggle_selector).click
+
+ expect(page).not_to have_selector menu_selector
+
+ find(toggle_selector).click
+ find("#{form_selector} .note-textarea").click
+
+ expect(page).not_to have_selector menu_selector
+ end
+
+ describe 'when selecting "Start thread"' do
+ before do
+ find("#{menu_selector} li", match: :first)
+ all("#{menu_selector} li").last.click
+ end
describe 'creating a thread' do
before do
- find(submit_selector).click
+ find(submit_button_selector).click
wait_for_requests
find(comments_selector, match: :first)
@@ -146,6 +268,7 @@ RSpec.shared_examples 'thread comments' do |resource_name|
find("#{comments_selector} .js-vue-discussion-reply").click
find("#{comments_selector} .note-textarea").send_keys(text)
+ # .js-comment-button here refers to the reply button in note_form.vue
find("#{comments_selector} .js-comment-button").click
wait_for_requests
end
@@ -228,13 +351,11 @@ RSpec.shared_examples 'thread comments' do |resource_name|
find("#{menu_selector} li", match: :first)
items = all("#{menu_selector} li")
+ expect(page).to have_selector("#{dropdown_selector}[data-track-label='start_thread_button']")
+
expect(items.first).to have_content 'Comment'
- expect(items.first).not_to have_selector '[data-testid="check-icon"]'
- expect(items.first['class']).not_to match 'droplab-item-selected'
expect(items.last).to have_content 'Start thread'
- expect(items.last).to have_selector '[data-testid="check-icon"]'
- expect(items.last['class']).to match 'droplab-item-selected'
end
describe 'when selecting "Comment"' do
@@ -243,14 +364,9 @@ RSpec.shared_examples 'thread comments' do |resource_name|
end
it 'updates the submit button text and closes the dropdown' do
- button = find(submit_selector)
+ button = find(submit_button_selector)
- # on issues page, the submit input is a , on other pages it is
- if button.tag_name == 'button'
- expect(button).to have_content 'Comment'
- else
- expect(button.value).to eq 'Comment'
- end
+ expect(button).to have_content 'Comment'
expect(page).not_to have_selector menu_selector
end
@@ -267,21 +383,17 @@ RSpec.shared_examples 'thread comments' do |resource_name|
end
end
- it 'has "Comment" selected when opening the menu', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/196825' do
+ it 'has "Comment" selected when opening the menu' do
find(toggle_selector).click
find("#{menu_selector} li", match: :first)
items = all("#{menu_selector} li")
- aggregate_failures do
- expect(items.first).to have_content 'Comment'
- expect(items.first).to have_selector '[data-testid="check-icon"]'
- expect(items.first['class']).to match 'droplab-item-selected'
+ expect(page).to have_selector("#{dropdown_selector}[data-track-label='comment_button']")
- expect(items.last).to have_content 'Start thread'
- expect(items.last).not_to have_selector '[data-testid="check-icon"]'
- expect(items.last['class']).not_to match 'droplab-item-selected'
- end
+ expect(items.first).to have_content 'Comment'
+
+ expect(items.last).to have_content 'Start thread'
end
end
end
diff --git a/spec/support/shared_examples/features/issuable_invite_members_shared_examples.rb b/spec/support/shared_examples/features/issuable_invite_members_shared_examples.rb
index 3fec1a56c0c..7a32f61d4fa 100644
--- a/spec/support/shared_examples/features/issuable_invite_members_shared_examples.rb
+++ b/spec/support/shared_examples/features/issuable_invite_members_shared_examples.rb
@@ -1,11 +1,7 @@
# frozen_string_literal: true
RSpec.shared_examples 'issuable invite members experiments' do
- context 'when invite_members_version_a experiment is enabled' do
- before do
- stub_experiment_for_subject(invite_members_version_a: true)
- end
-
+ context 'when a privileged user can invite' do
it 'shows a link for inviting members and follows through to the members page' do
project.add_maintainer(user)
visit issuable_path
@@ -51,9 +47,9 @@ RSpec.shared_examples 'issuable invite members experiments' do
end
end
- context 'when no invite members experiments are enabled' do
+ context 'when invite_members_version_b experiment is disabled' do
it 'shows author in assignee dropdown and no invite link' do
- project.add_maintainer(user)
+ project.add_developer(user)
visit issuable_path
find('.block.assignee .edit-link').click
diff --git a/spec/support/shared_examples/features/project_upload_files_shared_examples.rb b/spec/support/shared_examples/features/project_upload_files_shared_examples.rb
index 25203fa3182..00d3bd08218 100644
--- a/spec/support/shared_examples/features/project_upload_files_shared_examples.rb
+++ b/spec/support/shared_examples/features/project_upload_files_shared_examples.rb
@@ -3,7 +3,13 @@
RSpec.shared_examples 'it uploads and commit a new text file' do
it 'uploads and commit a new text file', :js do
find('.add-to-tree').click
- click_link('Upload file')
+
+ page.within('.dropdown-menu') do
+ click_link('Upload file')
+
+ wait_for_requests
+ end
+
drop_in_dropzone(File.join(Rails.root, 'spec', 'fixtures', 'doc_sample.txt'))
page.within('#modal-upload-blob') do
@@ -29,7 +35,13 @@ end
RSpec.shared_examples 'it uploads and commit a new image file' do
it 'uploads and commit a new image file', :js do
find('.add-to-tree').click
- click_link('Upload file')
+
+ page.within('.dropdown-menu') do
+ click_link('Upload file')
+
+ wait_for_requests
+ end
+
drop_in_dropzone(File.join(Rails.root, 'spec', 'fixtures', 'logo_sample.svg'))
page.within('#modal-upload-blob') do
@@ -82,3 +94,21 @@ RSpec.shared_examples 'it uploads and commit a new file to a forked project' do
expect(page).to have_content('Sed ut perspiciatis unde omnis')
end
end
+
+RSpec.shared_examples 'uploads and commits a new text file via "upload file" button' do
+ it 'uploads and commits a new text file via "upload file" button', :js do
+ find('[data-testid="upload-file-button"]').click
+
+ attach_file('upload_file', File.join(Rails.root, 'spec', 'fixtures', 'doc_sample.txt'), make_visible: true)
+
+ page.within('#details-modal-upload-blob') do
+ fill_in(:commit_message, with: 'New commit message')
+ end
+
+ click_button('Upload file')
+
+ expect(page).to have_content('New commit message')
+ expect(page).to have_content('Lorem ipsum dolor sit amet')
+ expect(page).to have_content('Sed ut perspiciatis unde omnis')
+ end
+end
diff --git a/spec/support/shared_examples/features/variable_list_shared_examples.rb b/spec/support/shared_examples/features/variable_list_shared_examples.rb
index e0d169c6868..2fd88b610e9 100644
--- a/spec/support/shared_examples/features/variable_list_shared_examples.rb
+++ b/spec/support/shared_examples/features/variable_list_shared_examples.rb
@@ -2,7 +2,7 @@
RSpec.shared_examples 'variable list' do
it 'shows a list of variables' do
- page.within('.ci-variable-table') do
+ page.within('[data-testid="ci-variable-table"]') do
expect(find('.js-ci-variable-row:nth-child(1) td[data-label="Key"]').text).to eq(variable.key)
end
end
@@ -16,7 +16,7 @@ RSpec.shared_examples 'variable list' do
wait_for_requests
- page.within('.ci-variable-table') do
+ page.within('[data-testid="ci-variable-table"]') do
expect(find('.js-ci-variable-row:nth-child(1) td[data-label="Key"]').text).to eq('key')
end
end
@@ -30,7 +30,7 @@ RSpec.shared_examples 'variable list' do
wait_for_requests
- page.within('.ci-variable-table') do
+ page.within('[data-testid="ci-variable-table"]') do
expect(find('.js-ci-variable-row:nth-child(1) td[data-label="Key"]').text).to eq('key')
expect(find('.js-ci-variable-row:nth-child(1) td[data-label="Protected"] svg[data-testid="mobile-issue-close-icon"]')).to be_present
end
@@ -45,14 +45,14 @@ RSpec.shared_examples 'variable list' do
wait_for_requests
- page.within('.ci-variable-table') do
+ page.within('[data-testid="ci-variable-table"]') do
expect(find('.js-ci-variable-row:nth-child(1) td[data-label="Key"]').text).to eq('key')
expect(find('.js-ci-variable-row:nth-child(1) td[data-label="Masked"] svg[data-testid="close-icon"]')).to be_present
end
end
it 'reveals and hides variables' do
- page.within('.ci-variable-table') do
+ page.within('[data-testid="ci-variable-table"]') do
expect(first('.js-ci-variable-row td[data-label="Key"]').text).to eq(variable.key)
expect(page).to have_content('*' * 17)
@@ -72,7 +72,7 @@ RSpec.shared_examples 'variable list' do
it 'deletes a variable' do
expect(page).to have_selector('.js-ci-variable-row', count: 1)
- page.within('.ci-variable-table') do
+ page.within('[data-testid="ci-variable-table"]') do
click_button('Edit')
end
@@ -86,7 +86,7 @@ RSpec.shared_examples 'variable list' do
end
it 'edits a variable' do
- page.within('.ci-variable-table') do
+ page.within('[data-testid="ci-variable-table"]') do
click_button('Edit')
end
@@ -102,7 +102,7 @@ RSpec.shared_examples 'variable list' do
end
it 'edits a variable to be unmasked' do
- page.within('.ci-variable-table') do
+ page.within('[data-testid="ci-variable-table"]') do
click_button('Edit')
end
@@ -115,13 +115,13 @@ RSpec.shared_examples 'variable list' do
wait_for_requests
- page.within('.ci-variable-table') do
+ page.within('[data-testid="ci-variable-table"]') do
expect(find('.js-ci-variable-row:nth-child(1) td[data-label="Masked"] svg[data-testid="close-icon"]')).to be_present
end
end
it 'edits a variable to be masked' do
- page.within('.ci-variable-table') do
+ page.within('[data-testid="ci-variable-table"]') do
click_button('Edit')
end
@@ -133,7 +133,7 @@ RSpec.shared_examples 'variable list' do
wait_for_requests
- page.within('.ci-variable-table') do
+ page.within('[data-testid="ci-variable-table"]') do
click_button('Edit')
end
@@ -143,7 +143,7 @@ RSpec.shared_examples 'variable list' do
click_button('Update variable')
end
- page.within('.ci-variable-table') do
+ page.within('[data-testid="ci-variable-table"]') do
expect(find('.js-ci-variable-row:nth-child(1) td[data-label="Masked"] svg[data-testid="mobile-issue-close-icon"]')).to be_present
end
end
@@ -211,7 +211,7 @@ RSpec.shared_examples 'variable list' do
expect(page).to have_selector('.js-ci-variable-row', count: 3)
# Remove the `akey` variable
- page.within('.ci-variable-table') do
+ page.within('[data-testid="ci-variable-table"]') do
page.within('.js-ci-variable-row:first-child') do
click_button('Edit')
end
diff --git a/spec/support/shared_examples/graphql/mutation_shared_examples.rb b/spec/support/shared_examples/graphql/mutation_shared_examples.rb
index 84ebd4852b9..51d52cbb901 100644
--- a/spec/support/shared_examples/graphql/mutation_shared_examples.rb
+++ b/spec/support/shared_examples/graphql/mutation_shared_examples.rb
@@ -48,6 +48,6 @@ RSpec.shared_examples 'a mutation that returns errors in the response' do |error
it do
post_graphql_mutation(mutation, current_user: current_user)
- expect(mutation_response['errors']).to eq(errors)
+ expect(mutation_response['errors']).to match_array(errors)
end
end
diff --git a/spec/support/shared_examples/graphql/mutations/boards_create_shared_examples.rb b/spec/support/shared_examples/graphql/mutations/boards_create_shared_examples.rb
index 2b93d174653..2e3a3ce6b41 100644
--- a/spec/support/shared_examples/graphql/mutations/boards_create_shared_examples.rb
+++ b/spec/support/shared_examples/graphql/mutations/boards_create_shared_examples.rb
@@ -66,9 +66,7 @@ RSpec.shared_examples 'boards create mutation' do
context 'when the Boards::CreateService returns an error response' do
before do
- allow_next_instance_of(Boards::CreateService) do |service|
- allow(service).to receive(:execute).and_return(ServiceResponse.error(message: 'There was an error.'))
- end
+ params[:name] = ''
end
it 'does not create a board' do
@@ -80,7 +78,7 @@ RSpec.shared_examples 'boards create mutation' do
expect(mutation_response).to have_key('board')
expect(mutation_response['board']).to be_nil
- expect(mutation_response['errors'].first).to eq('There was an error.')
+ expect(mutation_response['errors'].first).to eq('There was an error when creating a board.')
end
end
end
diff --git a/spec/support/shared_examples/graphql/mutations/can_mutate_spammable_examples.rb b/spec/support/shared_examples/graphql/mutations/can_mutate_spammable_examples.rb
index d294f034d2e..bb4270d7db6 100644
--- a/spec/support/shared_examples/graphql/mutations/can_mutate_spammable_examples.rb
+++ b/spec/support/shared_examples/graphql/mutations/can_mutate_spammable_examples.rb
@@ -21,14 +21,14 @@ RSpec.shared_examples 'a mutation which can mutate a spammable' do
end
end
- describe "#with_spam_action_fields" do
+ describe "#with_spam_action_response_fields" do
it 'resolves with spam action fields' do
subject
# NOTE: We do not need to assert on the specific values of spam action fields here, we only need
- # to verify that #with_spam_action_fields was invoked and that the fields are present in the
- # response. The specific behavior of #with_spam_action_fields is covered in the
- # CanMutateSpammable unit tests.
+ # to verify that #with_spam_action_response_fields was invoked and that the fields are present in the
+ # response. The specific behavior of #with_spam_action_response_fields is covered in the
+ # HasSpamActionResponseFields unit tests.
expect(mutation_response.keys)
.to include('spam', 'spamLogId', 'needsCaptchaResponse', 'captchaSiteKey')
end
diff --git a/spec/support/shared_examples/graphql/notes_on_noteables_shared_examples.rb b/spec/support/shared_examples/graphql/notes_on_noteables_shared_examples.rb
index 41b7da07d2d..0d2e9f6ec8c 100644
--- a/spec/support/shared_examples/graphql/notes_on_noteables_shared_examples.rb
+++ b/spec/support/shared_examples/graphql/notes_on_noteables_shared_examples.rb
@@ -17,7 +17,7 @@ RSpec.shared_context 'exposing regular notes on a noteable in GraphQL' do
notes {
edges {
node {
- #{all_graphql_fields_for('Note')}
+ #{all_graphql_fields_for('Note', max_depth: 1)}
}
}
}
diff --git a/spec/support/shared_examples/graphql/types/gitlab_style_deprecations_shared_examples.rb b/spec/support/shared_examples/graphql/types/gitlab_style_deprecations_shared_examples.rb
index 269e9170906..bc091a678e2 100644
--- a/spec/support/shared_examples/graphql/types/gitlab_style_deprecations_shared_examples.rb
+++ b/spec/support/shared_examples/graphql/types/gitlab_style_deprecations_shared_examples.rb
@@ -6,7 +6,7 @@ RSpec.shared_examples 'Gitlab-style deprecations' do
expect { subject(deprecation_reason: 'foo') }.to raise_error(
ArgumentError,
'Use `deprecated` property instead of `deprecation_reason`. ' \
- 'See https://docs.gitlab.com/ee/development/api_graphql_styleguide.html#deprecating-fields-and-enum-values'
+ 'See https://docs.gitlab.com/ee/development/api_graphql_styleguide.html#deprecating-fields-arguments-and-enum-values'
)
end
diff --git a/spec/support/shared_examples/helpers/issuable_description_templates_shared_examples.rb b/spec/support/shared_examples/helpers/issuable_description_templates_shared_examples.rb
index 9e8c96d576a..47e34b21036 100644
--- a/spec/support/shared_examples/helpers/issuable_description_templates_shared_examples.rb
+++ b/spec/support/shared_examples/helpers/issuable_description_templates_shared_examples.rb
@@ -23,11 +23,11 @@ RSpec.shared_examples 'project issuable templates' do
end
it 'returns only md files as issue templates' do
- expect(helper.issuable_templates(project, 'issue')).to eq(templates('issue', project))
+ expect(helper.issuable_templates(project, 'issue')).to eq(expected_templates('issue'))
end
it 'returns only md files as merge_request templates' do
- expect(helper.issuable_templates(project, 'merge_request')).to eq(templates('merge_request', project))
+ expect(helper.issuable_templates(project, 'merge_request')).to eq(expected_templates('merge_request'))
end
end
diff --git a/spec/support/shared_examples/lib/gitlab/cycle_analytics/event_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/cycle_analytics/event_shared_examples.rb
index 145a7290ac8..7d341d79bae 100644
--- a/spec/support/shared_examples/lib/gitlab/cycle_analytics/event_shared_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/cycle_analytics/event_shared_examples.rb
@@ -8,6 +8,7 @@ RSpec.shared_examples_for 'value stream analytics event' do
it { expect(described_class.identifier).to be_a_kind_of(Symbol) }
it { expect(instance.object_type.ancestors).to include(ApplicationRecord) }
it { expect(instance).to respond_to(:timestamp_projection) }
+ it { expect(instance).to respond_to(:markdown_description) }
it { expect(instance.column_list).to be_a_kind_of(Array) }
describe '#apply_query_customization' do
diff --git a/spec/support/shared_examples/lib/gitlab/usage_data_counters/issuable_activity_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/usage_data_counters/issuable_activity_shared_examples.rb
new file mode 100644
index 00000000000..aa6e64a3820
--- /dev/null
+++ b/spec/support/shared_examples/lib/gitlab/usage_data_counters/issuable_activity_shared_examples.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'a daily tracked issuable event' do
+ before do
+ stub_application_setting(usage_ping_enabled: true)
+ end
+
+ def count_unique(date_from:, date_to:)
+ Gitlab::UsageDataCounters::HLLRedisCounter.unique_events(event_names: action, start_date: date_from, end_date: date_to)
+ end
+
+ specify do
+ aggregate_failures do
+ expect(track_action(author: user1)).to be_truthy
+ expect(track_action(author: user1)).to be_truthy
+ expect(track_action(author: user2)).to be_truthy
+ expect(track_action(author: user3, time: time - 3.days)).to be_truthy
+
+ expect(count_unique(date_from: time, date_to: time)).to eq(2)
+ expect(count_unique(date_from: time - 5.days, date_to: 1.day.since(time))).to eq(3)
+ end
+ end
+
+ it 'does not track edit actions if author is not present' do
+ expect(track_action(author: nil)).to be_nil
+ end
+end
+
+RSpec.shared_examples 'does not track when feature flag is disabled' do |feature_flag|
+ context "when feature flag #{feature_flag} is disabled" do
+ it 'does not track action' do
+ stub_feature_flags(feature_flag => false)
+
+ expect(track_action(author: user1)).to be_nil
+ end
+ end
+end
diff --git a/spec/support/shared_examples/lib/gitlab/usage_data_counters/issue_activity_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/usage_data_counters/issue_activity_shared_examples.rb
deleted file mode 100644
index edd9b6cdf37..00000000000
--- a/spec/support/shared_examples/lib/gitlab/usage_data_counters/issue_activity_shared_examples.rb
+++ /dev/null
@@ -1,27 +0,0 @@
-# frozen_string_literal: true
-
-RSpec.shared_examples 'a tracked issue edit event' do |event|
- before do
- stub_application_setting(usage_ping_enabled: true)
- end
-
- def count_unique(date_from:, date_to:)
- Gitlab::UsageDataCounters::HLLRedisCounter.unique_events(event_names: action, start_date: date_from, end_date: date_to)
- end
-
- specify do
- aggregate_failures do
- expect(track_action(author: user1)).to be_truthy
- expect(track_action(author: user1)).to be_truthy
- expect(track_action(author: user2)).to be_truthy
- expect(track_action(author: user3, time: time - 3.days)).to be_truthy
-
- expect(count_unique(date_from: time, date_to: time)).to eq(2)
- expect(count_unique(date_from: time - 5.days, date_to: 1.day.since(time))).to eq(3)
- end
- end
-
- it 'does not track edit actions if author is not present' do
- expect(track_action(author: nil)).to be_nil
- end
-end
diff --git a/spec/support/shared_examples/lib/sentry/client_shared_examples.rb b/spec/support/shared_examples/lib/sentry/client_shared_examples.rb
index 4221708b55c..d73c7b6848d 100644
--- a/spec/support/shared_examples/lib/sentry/client_shared_examples.rb
+++ b/spec/support/shared_examples/lib/sentry/client_shared_examples.rb
@@ -26,7 +26,7 @@ RSpec.shared_examples 'no Sentry redirects' do |http_method|
end
it 'does not follow redirects' do
- expect { subject }.to raise_exception(Sentry::Client::Error, 'Sentry response status code: 302')
+ expect { subject }.to raise_exception(ErrorTracking::SentryClient::Error, 'Sentry response status code: 302')
expect(redirect_req_stub).to have_been_requested
expect(redirected_req_stub).not_to have_been_requested
end
@@ -53,7 +53,7 @@ RSpec.shared_examples 'maps Sentry exceptions' do |http_method|
it do
expect { subject }
- .to raise_exception(Sentry::Client::Error, message)
+ .to raise_exception(ErrorTracking::SentryClient::Error, message)
end
end
end
diff --git a/spec/support/shared_examples/metrics/active_record_subscriber_shared_examples.rb b/spec/support/shared_examples/metrics/active_record_subscriber_shared_examples.rb
new file mode 100644
index 00000000000..7bf2456c548
--- /dev/null
+++ b/spec/support/shared_examples/metrics/active_record_subscriber_shared_examples.rb
@@ -0,0 +1,137 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'store ActiveRecord info in RequestStore' do |db_role|
+ it 'prevents db counters from leaking to the next transaction' do
+ 2.times do
+ Gitlab::WithRequestStore.with_request_store do
+ subscriber.sql(event)
+
+ if db_role == :primary
+ expect(described_class.db_counter_payload).to eq(
+ db_count: record_query ? 1 : 0,
+ db_write_count: record_write_query ? 1 : 0,
+ db_cached_count: record_cached_query ? 1 : 0,
+ db_primary_cached_count: record_cached_query ? 1 : 0,
+ db_primary_count: record_query ? 1 : 0,
+ db_primary_duration_s: record_query ? 0.002 : 0,
+ db_replica_cached_count: 0,
+ db_replica_count: 0,
+ db_replica_duration_s: 0.0
+ )
+ elsif db_role == :replica
+ expect(described_class.db_counter_payload).to eq(
+ db_count: record_query ? 1 : 0,
+ db_write_count: record_write_query ? 1 : 0,
+ db_cached_count: record_cached_query ? 1 : 0,
+ db_primary_cached_count: 0,
+ db_primary_count: 0,
+ db_primary_duration_s: 0.0,
+ db_replica_cached_count: record_cached_query ? 1 : 0,
+ db_replica_count: record_query ? 1 : 0,
+ db_replica_duration_s: record_query ? 0.002 : 0
+ )
+ else
+ expect(described_class.db_counter_payload).to eq(
+ db_count: record_query ? 1 : 0,
+ db_write_count: record_write_query ? 1 : 0,
+ db_cached_count: record_cached_query ? 1 : 0
+ )
+ end
+ end
+ end
+ end
+end
+
+RSpec.shared_examples 'record ActiveRecord metrics in a metrics transaction' do |db_role|
+ it 'increments only db counters' do
+ if record_query
+ expect(transaction).to receive(:increment).with(:gitlab_transaction_db_count_total, 1)
+ expect(transaction).to receive(:increment).with("gitlab_transaction_db_#{db_role}_count_total".to_sym, 1) if db_role
+ else
+ expect(transaction).not_to receive(:increment).with(:gitlab_transaction_db_count_total, 1)
+ expect(transaction).not_to receive(:increment).with("gitlab_transaction_db_#{db_role}_count_total".to_sym, 1) if db_role
+ end
+
+ if record_write_query
+ expect(transaction).to receive(:increment).with(:gitlab_transaction_db_write_count_total, 1)
+ else
+ expect(transaction).not_to receive(:increment).with(:gitlab_transaction_db_write_count_total, 1)
+ end
+
+ if record_cached_query
+ expect(transaction).to receive(:increment).with(:gitlab_transaction_db_cached_count_total, 1)
+ expect(transaction).to receive(:increment).with("gitlab_transaction_db_#{db_role}_cached_count_total".to_sym, 1) if db_role
+ else
+ expect(transaction).not_to receive(:increment).with(:gitlab_transaction_db_cached_count_total, 1)
+ expect(transaction).not_to receive(:increment).with("gitlab_transaction_db_#{db_role}_cached_count_total".to_sym, 1) if db_role
+ end
+
+ subscriber.sql(event)
+ end
+
+ it 'observes sql_duration metric' do
+ if record_query
+ expect(transaction).to receive(:observe).with(:gitlab_sql_duration_seconds, 0.002)
+ expect(transaction).to receive(:observe).with("gitlab_sql_#{db_role}_duration_seconds".to_sym, 0.002) if db_role
+ else
+ expect(transaction).not_to receive(:observe)
+ end
+
+ subscriber.sql(event)
+ end
+end
+
+RSpec.shared_examples 'record ActiveRecord metrics' do |db_role|
+ context 'when both web and background transaction are available' do
+ let(:transaction) { double('Gitlab::Metrics::WebTransaction') }
+ let(:background_transaction) { double('Gitlab::Metrics::WebTransaction') }
+
+ before do
+ allow(::Gitlab::Metrics::WebTransaction).to receive(:current)
+ .and_return(transaction)
+ allow(::Gitlab::Metrics::BackgroundTransaction).to receive(:current)
+ .and_return(background_transaction)
+ allow(transaction).to receive(:increment)
+ allow(transaction).to receive(:observe)
+ end
+
+ it_behaves_like 'record ActiveRecord metrics in a metrics transaction', db_role
+
+ it 'captures the metrics for web only' do
+ expect(background_transaction).not_to receive(:observe)
+ expect(background_transaction).not_to receive(:increment)
+
+ subscriber.sql(event)
+ end
+ end
+
+ context 'when web transaction is available' do
+ let(:transaction) { double('Gitlab::Metrics::WebTransaction') }
+
+ before do
+ allow(::Gitlab::Metrics::WebTransaction).to receive(:current)
+ .and_return(transaction)
+ allow(::Gitlab::Metrics::BackgroundTransaction).to receive(:current)
+ .and_return(nil)
+ allow(transaction).to receive(:increment)
+ allow(transaction).to receive(:observe)
+ end
+
+ it_behaves_like 'record ActiveRecord metrics in a metrics transaction', db_role
+ end
+
+ context 'when background transaction is available' do
+ let(:transaction) { double('Gitlab::Metrics::BackgroundTransaction') }
+
+ before do
+ allow(::Gitlab::Metrics::WebTransaction).to receive(:current)
+ .and_return(nil)
+ allow(::Gitlab::Metrics::BackgroundTransaction).to receive(:current)
+ .and_return(transaction)
+ allow(transaction).to receive(:increment)
+ allow(transaction).to receive(:observe)
+ end
+
+ it_behaves_like 'record ActiveRecord metrics in a metrics transaction', db_role
+ end
+end
diff --git a/spec/support/shared_examples/models/application_setting_shared_examples.rb b/spec/support/shared_examples/models/application_setting_shared_examples.rb
index 92fd4363134..60a02d85a1e 100644
--- a/spec/support/shared_examples/models/application_setting_shared_examples.rb
+++ b/spec/support/shared_examples/models/application_setting_shared_examples.rb
@@ -289,6 +289,7 @@ RSpec.shared_examples 'application settings examples' do
describe '#pick_repository_storage' do
before do
+ allow(Gitlab.config.repositories.storages).to receive(:keys).and_return(%w(default backup))
allow(setting).to receive(:repository_storages_weighted).and_return({ 'default' => 20, 'backup' => 80 })
end
@@ -304,15 +305,19 @@ RSpec.shared_examples 'application settings examples' do
describe '#normalized_repository_storage_weights' do
using RSpec::Parameterized::TableSyntax
- where(:storages, :normalized) do
- { 'default' => 0, 'backup' => 100 } | { 'default' => 0.0, 'backup' => 1.0 }
- { 'default' => 100, 'backup' => 100 } | { 'default' => 0.5, 'backup' => 0.5 }
- { 'default' => 20, 'backup' => 80 } | { 'default' => 0.2, 'backup' => 0.8 }
- { 'default' => 0, 'backup' => 0 } | { 'default' => 0.0, 'backup' => 0.0 }
+ where(:config_storages, :storages, :normalized) do
+ %w(default backup) | { 'default' => 0, 'backup' => 100 } | { 'default' => 0.0, 'backup' => 1.0 }
+ %w(default backup) | { 'default' => 100, 'backup' => 100 } | { 'default' => 0.5, 'backup' => 0.5 }
+ %w(default backup) | { 'default' => 20, 'backup' => 80 } | { 'default' => 0.2, 'backup' => 0.8 }
+ %w(default backup) | { 'default' => 0, 'backup' => 0 } | { 'default' => 0.0, 'backup' => 0.0 }
+ %w(default) | { 'default' => 0, 'backup' => 100 } | { 'default' => 0.0 }
+ %w(default) | { 'default' => 100, 'backup' => 100 } | { 'default' => 1.0 }
+ %w(default) | { 'default' => 20, 'backup' => 80 } | { 'default' => 1.0 }
end
with_them do
before do
+ allow(Gitlab.config.repositories.storages).to receive(:keys).and_return(config_storages)
allow(setting).to receive(:repository_storages_weighted).and_return(storages)
end
diff --git a/spec/support/shared_examples/models/boards/user_preferences_shared_examples.rb b/spec/support/shared_examples/models/boards/user_preferences_shared_examples.rb
new file mode 100644
index 00000000000..766aeac9476
--- /dev/null
+++ b/spec/support/shared_examples/models/boards/user_preferences_shared_examples.rb
@@ -0,0 +1,68 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'list_preferences_for user' do |list_factory, list_id_attribute|
+ subject { create(list_factory) } # rubocop:disable Rails/SaveBang
+
+ let_it_be(:user) { create(:user) }
+
+ describe '#preferences_for' do
+ context 'when user is nil' do
+ it 'returns not persisted preferences' do
+ preferences = subject.preferences_for(nil)
+
+ expect(preferences).not_to be_persisted
+ expect(preferences[list_id_attribute]).to eq(subject.id)
+ expect(preferences.user_id).to be_nil
+ end
+ end
+
+ context 'when a user preference already exists' do
+ before do
+ subject.update_preferences_for(user, collapsed: true)
+ end
+
+ it 'loads preference for user' do
+ preferences = subject.preferences_for(user)
+
+ expect(preferences).to be_persisted
+ expect(preferences.collapsed).to eq(true)
+ end
+ end
+
+ context 'when preferences for user does not exist' do
+ it 'returns not persisted preferences' do
+ preferences = subject.preferences_for(user)
+
+ expect(preferences).not_to be_persisted
+ expect(preferences.user_id).to eq(user.id)
+ expect(preferences.public_send(list_id_attribute)).to eq(subject.id)
+ end
+ end
+ end
+
+ describe '#update_preferences_for' do
+ context 'when user is present' do
+ context 'when there are no preferences for user' do
+ it 'creates new user preferences' do
+ expect { subject.update_preferences_for(user, collapsed: true) }.to change { subject.preferences.count }.by(1)
+ expect(subject.preferences_for(user).collapsed).to eq(true)
+ end
+ end
+
+ context 'when there are preferences for user' do
+ it 'updates user preferences' do
+ subject.update_preferences_for(user, collapsed: false)
+
+ expect { subject.update_preferences_for(user, collapsed: true) }.not_to change { subject.preferences.count }
+ expect(subject.preferences_for(user).collapsed).to eq(true)
+ end
+ end
+
+ context 'when user is nil' do
+ it 'does not create user preferences' do
+ expect { subject.update_preferences_for(nil, collapsed: true) }.not_to change { subject.preferences.count }
+ end
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/models/chat_service_shared_examples.rb b/spec/support/shared_examples/models/chat_service_shared_examples.rb
index ad237ad9f49..59e249bb865 100644
--- a/spec/support/shared_examples/models/chat_service_shared_examples.rb
+++ b/spec/support/shared_examples/models/chat_service_shared_examples.rb
@@ -53,9 +53,13 @@ RSpec.shared_examples "chat service" do |service_name|
end
it "calls #{service_name} API" do
- subject.execute(sample_data)
+ result = subject.execute(sample_data)
- expect(WebMock).to have_requested(:post, webhook_url).with { |req| req.body =~ /\A{"#{content_key}":.+}\Z/ }.once
+ expect(result).to be(true)
+ expect(WebMock).to have_requested(:post, webhook_url).once.with { |req|
+ json_body = Gitlab::Json.parse(req.body).with_indifferent_access
+ expect(json_body).to include(payload)
+ }
end
end
@@ -67,7 +71,8 @@ RSpec.shared_examples "chat service" do |service_name|
it "does not call #{service_name} API" do
result = subject.execute(sample_data)
- expect(result).to be_falsy
+ expect(result).to be(false)
+ expect(WebMock).not_to have_requested(:post, webhook_url)
end
end
diff --git a/spec/support/shared_examples/models/concerns/timebox_shared_examples.rb b/spec/support/shared_examples/models/concerns/timebox_shared_examples.rb
index f91e4bd8cf7..68142e667a4 100644
--- a/spec/support/shared_examples/models/concerns/timebox_shared_examples.rb
+++ b/spec/support/shared_examples/models/concerns/timebox_shared_examples.rb
@@ -18,7 +18,7 @@ RSpec.shared_examples 'a timebox' do |timebox_type|
context 'with a project' do
it_behaves_like 'AtomicInternalId' do
let(:internal_id_attribute) { :iid }
- let(:instance) { build(timebox_type, *timebox_args, project: build(:project), group: nil) }
+ let(:instance) { build(timebox_type, *timebox_args, project: create(:project), group: nil) }
let(:scope) { :project }
let(:scope_attrs) { { project: instance.project } }
let(:usage) { timebox_table_name }
@@ -28,7 +28,7 @@ RSpec.shared_examples 'a timebox' do |timebox_type|
context 'with a group' do
it_behaves_like 'AtomicInternalId' do
let(:internal_id_attribute) { :iid }
- let(:instance) { build(timebox_type, *timebox_args, project: nil, group: build(:group)) }
+ let(:instance) { build(timebox_type, *timebox_args, project: nil, group: create(:group)) }
let(:scope) { :group }
let(:scope_attrs) { { namespace: instance.group } }
let(:usage) { timebox_table_name }
diff --git a/spec/support/shared_examples/models/email_format_shared_examples.rb b/spec/support/shared_examples/models/email_format_shared_examples.rb
index a8115e440a4..77ded168637 100644
--- a/spec/support/shared_examples/models/email_format_shared_examples.rb
+++ b/spec/support/shared_examples/models/email_format_shared_examples.rb
@@ -6,7 +6,7 @@
# Note: You have access to `email_value` which is the email address value
# being currently tested).
-RSpec.shared_examples 'an object with email-formated attributes' do |*attributes|
+RSpec.shared_examples 'an object with email-formatted attributes' do |*attributes|
attributes.each do |attribute|
describe "specifically its :#{attribute} attribute" do
%w[
@@ -45,7 +45,7 @@ RSpec.shared_examples 'an object with email-formated attributes' do |*attributes
end
end
-RSpec.shared_examples 'an object with RFC3696 compliant email-formated attributes' do |*attributes|
+RSpec.shared_examples 'an object with RFC3696 compliant email-formatted attributes' do |*attributes|
attributes.each do |attribute|
describe "specifically its :#{attribute} attribute" do
%w[
diff --git a/spec/support/shared_examples/models/slack_mattermost_notifications_shared_examples.rb b/spec/support/shared_examples/models/slack_mattermost_notifications_shared_examples.rb
index a1867e1ce39..71a76121d38 100644
--- a/spec/support/shared_examples/models/slack_mattermost_notifications_shared_examples.rb
+++ b/spec/support/shared_examples/models/slack_mattermost_notifications_shared_examples.rb
@@ -7,7 +7,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do |service_name|
let(:webhook_url) { 'https://example.gitlab.com' }
def execute_with_options(options)
- receive(:new).with(webhook_url, options.merge(http_client: SlackService::Notifier::HTTPClient))
+ receive(:new).with(webhook_url, options.merge(http_client: SlackMattermost::Notifier::HTTPClient))
.and_return(double(:slack_service).as_null_object)
end
@@ -66,193 +66,180 @@ RSpec.shared_examples 'slack or mattermost notifications' do |service_name|
end
describe "#execute" do
- let(:user) { create(:user) }
- let(:project) { create(:project, :repository, :wiki_repo) }
- let(:username) { 'slack_username' }
- let(:channel) { 'slack_channel' }
- let(:issue_service_options) { { title: 'Awesome issue', description: 'please fix' } }
+ let_it_be(:project) { create(:project, :repository, :wiki_repo) }
+ let_it_be(:user) { create(:user) }
- let(:data) do
- Gitlab::DataBuilder::Push.build_sample(project, user)
- end
+ let(:chat_service) { described_class.new( { project: project, webhook: webhook_url, branches_to_be_notified: 'all' }.merge(chat_service_params)) }
+ let(:chat_service_params) { {} }
+ let(:data) { Gitlab::DataBuilder::Push.build_sample(project, user) }
let!(:stubbed_resolved_hostname) do
stub_full_request(webhook_url, method: :post).request_pattern.uri_pattern.to_s
end
- before do
- allow(chat_service).to receive_messages(
- project: project,
- project_id: project.id,
- service_hook: true,
- webhook: webhook_url
- )
+ subject(:execute_service) { chat_service.execute(data) }
- issue_service = Issues::CreateService.new(project, user, issue_service_options)
- @issue = issue_service.execute
- @issues_sample_data = issue_service.hook_data(@issue, 'open')
-
- project.add_developer(user)
- opts = {
- title: 'Awesome merge_request',
- description: 'please fix',
- source_branch: 'feature',
- target_branch: 'master'
- }
- merge_service = MergeRequests::CreateService.new(project,
- user, opts)
- @merge_request = merge_service.execute
- @merge_sample_data = merge_service.hook_data(@merge_request,
- 'open')
-
- opts = {
- title: "Awesome wiki_page",
- content: "Some text describing some thing or another",
- format: "md",
- message: "user created page: Awesome wiki_page"
- }
-
- @wiki_page = create(:wiki_page, wiki: project.wiki, **opts)
- @wiki_page_sample_data = Gitlab::DataBuilder::WikiPage.build(@wiki_page, user, 'create')
- end
-
- it "calls #{service_name} API for push events" do
- chat_service.execute(data)
-
- expect(WebMock).to have_requested(:post, stubbed_resolved_hostname).once
- end
+ shared_examples 'calls the service API with the event message' do |event_message|
+ specify do
+ expect_next_instance_of(Slack::Messenger) do |messenger|
+ expect(messenger).to receive(:ping).with(event_message, anything).and_call_original
+ end
- it "calls #{service_name} API for issue events" do
- chat_service.execute(@issues_sample_data)
+ execute_service
- expect(WebMock).to have_requested(:post, stubbed_resolved_hostname).once
+ expect(WebMock).to have_requested(:post, stubbed_resolved_hostname).once
+ end
end
- it "calls #{service_name} API for merge requests events" do
- chat_service.execute(@merge_sample_data)
+ context 'with username for slack configured' do
+ let(:chat_service_params) { { username: 'slack_username' } }
+
+ it 'uses the username as an option' do
+ expect(Slack::Messenger).to execute_with_options(username: 'slack_username')
- expect(WebMock).to have_requested(:post, stubbed_resolved_hostname).once
+ execute_service
+ end
end
- it "calls #{service_name} API for wiki page events" do
- chat_service.execute(@wiki_page_sample_data)
+ context 'push events' do
+ let(:data) { Gitlab::DataBuilder::Push.build_sample(project, user) }
- expect(WebMock).to have_requested(:post, stubbed_resolved_hostname).once
- end
+ it_behaves_like 'calls the service API with the event message', /pushed to branch/
- it "calls #{service_name} API for deployment events" do
- deployment_event_data = { object_kind: 'deployment' }
+ context 'with event channel' do
+ let(:chat_service_params) { { push_channel: 'random' } }
- chat_service.execute(deployment_event_data)
+ it 'uses the right channel for push event' do
+ expect(Slack::Messenger).to execute_with_options(channel: ['random'])
- expect(WebMock).to have_requested(:post, stubbed_resolved_hostname).once
+ execute_service
+ end
+ end
end
- it 'uses the username as an option for slack when configured' do
- allow(chat_service).to receive(:username).and_return(username)
-
- expect(Slack::Messenger).to execute_with_options(username: username)
+ context 'tag_push events' do
+ let(:oldrev) { Gitlab::Git::BLANK_SHA }
+ let(:newrev) { '8a2a6eb295bb170b34c24c76c49ed0e9b2eaf34b' } # gitlab-test: git rev-parse refs/tags/v1.1.0
+ let(:ref) { 'refs/tags/v1.1.0' }
+ let(:data) { Git::TagHooksService.new(project, user, change: { oldrev: oldrev, newrev: newrev, ref: ref }).send(:push_data) }
- chat_service.execute(data)
+ it_behaves_like 'calls the service API with the event message', /pushed new tag/
end
- it 'uses the channel as an option when it is configured' do
- allow(chat_service).to receive(:channel).and_return(channel)
- expect(Slack::Messenger).to execute_with_options(channel: [channel])
- chat_service.execute(data)
- end
+ context 'issue events' do
+ let_it_be(:issue) { create(:issue) }
+ let(:data) { issue.to_hook_data(user) }
- context "event channels" do
- it "uses the right channel for push event" do
- chat_service.update!(push_channel: "random")
+ it_behaves_like 'calls the service API with the event message', /Issue (.*?) opened by/
- expect(Slack::Messenger).to execute_with_options(channel: ['random'])
+ context 'whith event channel' do
+ let(:chat_service_params) { { issue_channel: 'random' } }
- chat_service.execute(data)
- end
+ it 'uses the right channel for issue event' do
+ expect(Slack::Messenger).to execute_with_options(channel: ['random'])
- it "uses the right channel for merge request event" do
- chat_service.update!(merge_request_channel: "random")
+ execute_service
+ end
- expect(Slack::Messenger).to execute_with_options(channel: ['random'])
+ context 'for confidential issues' do
+ before_all do
+ issue.update!(confidential: true)
+ end
- chat_service.execute(@merge_sample_data)
- end
+ it 'falls back to issue channel' do
+ expect(Slack::Messenger).to execute_with_options(channel: ['random'])
+
+ execute_service
+ end
- it "uses the right channel for issue event" do
- chat_service.update!(issue_channel: "random")
+ context 'and confidential_issue_channel is defined' do
+ let(:chat_service_params) { { issue_channel: 'random', confidential_issue_channel: 'confidential' } }
- expect(Slack::Messenger).to execute_with_options(channel: ['random'])
+ it 'uses the confidential issue channel when it is defined' do
+ expect(Slack::Messenger).to execute_with_options(channel: ['confidential'])
- chat_service.execute(@issues_sample_data)
+ execute_service
+ end
+ end
+ end
end
+ end
+
+ context 'merge request events' do
+ let_it_be(:merge_request) { create(:merge_request) }
+ let(:data) { merge_request.to_hook_data(user) }
- context 'for confidential issues' do
- let(:issue_service_options) { { title: 'Secret', confidential: true } }
+ it_behaves_like 'calls the service API with the event message', /opened merge request/
- it "uses confidential issue channel" do
- chat_service.update!(confidential_issue_channel: 'confidential')
+ context 'with event channel' do
+ let(:chat_service_params) { { merge_request_channel: 'random' } }
- expect(Slack::Messenger).to execute_with_options(channel: ['confidential'])
+ it 'uses the right channel for merge request event' do
+ expect(Slack::Messenger).to execute_with_options(channel: ['random'])
- chat_service.execute(@issues_sample_data)
+ execute_service
end
+ end
+ end
+
+ context 'wiki page events' do
+ let_it_be(:wiki_page) { create(:wiki_page, wiki: project.wiki, message: 'user created page: Awesome wiki_page') }
+ let(:data) { Gitlab::DataBuilder::WikiPage.build(wiki_page, user, 'create') }
- it 'falls back to issue channel' do
- chat_service.update!(issue_channel: 'fallback_channel')
+ it_behaves_like 'calls the service API with the event message', / created (.*?)wikis\/(.*?)|wiki page> in/
- expect(Slack::Messenger).to execute_with_options(channel: ['fallback_channel'])
+ context 'with event channel' do
+ let(:chat_service_params) { { wiki_page_channel: 'random' } }
- chat_service.execute(@issues_sample_data)
+ it 'uses the right channel for wiki event' do
+ expect(Slack::Messenger).to execute_with_options(channel: ['random'])
+
+ execute_service
end
end
+ end
- it "uses the right channel for wiki event" do
- chat_service.update!(wiki_page_channel: "random")
-
- expect(Slack::Messenger).to execute_with_options(channel: ['random'])
+ context 'deployment events' do
+ let_it_be(:deployment) { create(:deployment) }
+ let(:data) { Gitlab::DataBuilder::Deployment.build(deployment) }
- chat_service.execute(@wiki_page_sample_data)
- end
+ it_behaves_like 'calls the service API with the event message', /Deploy to (.*?) created/
+ end
- context "note event" do
- let(:issue_note) do
- create(:note_on_issue, project: project, note: "issue note")
- end
+ context 'note event' do
+ let_it_be(:issue_note) { create(:note_on_issue, project: project, note: "issue note") }
+ let(:data) { Gitlab::DataBuilder::Note.build(issue_note, user) }
- it "uses the right channel" do
- chat_service.update!(note_channel: "random")
+ it_behaves_like 'calls the service API with the event message', /commented on issue/
- note_data = Gitlab::DataBuilder::Note.build(issue_note, user)
+ context 'with event channel' do
+ let(:chat_service_params) { { note_channel: 'random' } }
+ it 'uses the right channel' do
expect(Slack::Messenger).to execute_with_options(channel: ['random'])
- chat_service.execute(note_data)
+ execute_service
end
context 'for confidential notes' do
- before do
- issue_note.noteable.update!(confidential: true)
+ before_all do
+ issue_note.update!(confidential: true)
end
- it "uses confidential channel" do
- chat_service.update!(confidential_note_channel: "confidential")
-
- note_data = Gitlab::DataBuilder::Note.build(issue_note, user)
-
- expect(Slack::Messenger).to execute_with_options(channel: ['confidential'])
+ it 'falls back to note channel' do
+ expect(Slack::Messenger).to execute_with_options(channel: ['random'])
- chat_service.execute(note_data)
+ execute_service
end
- it 'falls back to note channel' do
- chat_service.update!(note_channel: "fallback_channel")
-
- note_data = Gitlab::DataBuilder::Note.build(issue_note, user)
+ context 'and confidential_note_channel is defined' do
+ let(:chat_service_params) { { note_channel: 'random', confidential_note_channel: 'confidential' } }
- expect(Slack::Messenger).to execute_with_options(channel: ['fallback_channel'])
+ it 'uses confidential channel' do
+ expect(Slack::Messenger).to execute_with_options(channel: ['confidential'])
- chat_service.execute(note_data)
+ execute_service
+ end
end
end
end
diff --git a/spec/support/shared_examples/models/wiki_shared_examples.rb b/spec/support/shared_examples/models/wiki_shared_examples.rb
index 89d30688b5c..abc6e3ecce8 100644
--- a/spec/support/shared_examples/models/wiki_shared_examples.rb
+++ b/spec/support/shared_examples/models/wiki_shared_examples.rb
@@ -354,27 +354,47 @@ RSpec.shared_examples 'wiki model' do
subject.repository.create_file(user, 'image.png', image, branch_name: subject.default_branch, message: 'add image')
end
- it 'returns the latest version of the file if it exists' do
- file = subject.find_file('image.png')
+ shared_examples 'find_file results' do
+ it 'returns the latest version of the file if it exists' do
+ file = subject.find_file('image.png')
- expect(file.mime_type).to eq('image/png')
- end
+ expect(file.mime_type).to eq('image/png')
+ end
+
+ it 'returns nil if the page does not exist' do
+ expect(subject.find_file('non-existent')).to eq(nil)
+ end
+
+ it 'returns a Gitlab::Git::WikiFile instance' do
+ file = subject.find_file('image.png')
+
+ expect(file).to be_a Gitlab::Git::WikiFile
+ end
- it 'returns nil if the page does not exist' do
- expect(subject.find_file('non-existent')).to eq(nil)
+ it 'returns the whole file' do
+ file = subject.find_file('image.png')
+ image.rewind
+
+ expect(file.raw_data.b).to eq(image.read.b)
+ end
end
- it 'returns a Gitlab::Git::WikiFile instance' do
- file = subject.find_file('image.png')
+ it_behaves_like 'find_file results'
+
+ context 'when load_content is disabled' do
+ it 'includes the file data in the Gitlab::Git::WikiFile' do
+ file = subject.find_file('image.png', load_content: false)
- expect(file).to be_a Gitlab::Git::WikiFile
+ expect(file.raw_data).to be_empty
+ end
end
- it 'returns the whole file' do
- file = subject.find_file('image.png')
- image.rewind
+ context 'when feature flag :gitaly_find_file is disabled' do
+ before do
+ stub_feature_flags(gitaly_find_file: false)
+ end
- expect(file.raw_data.b).to eq(image.read.b)
+ it_behaves_like 'find_file results'
end
end
diff --git a/spec/support/shared_examples/quick_actions/issuable/time_tracking_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/time_tracking_quick_action_shared_examples.rb
index 17fd2b836d3..92849ddf1cb 100644
--- a/spec/support/shared_examples/quick_actions/issuable/time_tracking_quick_action_shared_examples.rb
+++ b/spec/support/shared_examples/quick_actions/issuable/time_tracking_quick_action_shared_examples.rb
@@ -93,6 +93,6 @@ end
def submit_time(quick_action)
fill_in 'note[note]', with: quick_action
- find('.js-comment-submit-button').click
+ find('[data-testid="comment-button"]').click
wait_for_requests
end
diff --git a/spec/support/shared_examples/requests/api/conan_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/conan_packages_shared_examples.rb
index 49b6fc13900..54ea876bed2 100644
--- a/spec/support/shared_examples/requests/api/conan_packages_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/conan_packages_shared_examples.rb
@@ -1,63 +1,13 @@
# frozen_string_literal: true
RSpec.shared_examples 'conan ping endpoint' do
- it 'responds with 401 Unauthorized when no token provided' do
+ it 'responds with 200 OK when no token provided' do
get api(url)
- expect(response).to have_gitlab_http_status(:unauthorized)
- end
-
- it 'responds with 200 OK when valid token is provided' do
- jwt = build_jwt(personal_access_token)
- get api(url), headers: build_token_auth_header(jwt.encoded)
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(response.headers['X-Conan-Server-Capabilities']).to eq("")
- end
-
- it 'responds with 200 OK when valid job token is provided' do
- jwt = build_jwt_from_job(job)
- get api(url), headers: build_token_auth_header(jwt.encoded)
-
expect(response).to have_gitlab_http_status(:ok)
expect(response.headers['X-Conan-Server-Capabilities']).to eq("")
end
- it 'responds with 200 OK when valid deploy token is provided' do
- jwt = build_jwt_from_deploy_token(deploy_token)
- get api(url), headers: build_token_auth_header(jwt.encoded)
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(response.headers['X-Conan-Server-Capabilities']).to eq("")
- end
-
- it 'responds with 401 Unauthorized when invalid access token ID is provided' do
- jwt = build_jwt(double(id: 12345), user_id: personal_access_token.user_id)
- get api(url), headers: build_token_auth_header(jwt.encoded)
-
- expect(response).to have_gitlab_http_status(:unauthorized)
- end
-
- it 'responds with 401 Unauthorized when invalid user is provided' do
- jwt = build_jwt(personal_access_token, user_id: 12345)
- get api(url), headers: build_token_auth_header(jwt.encoded)
-
- expect(response).to have_gitlab_http_status(:unauthorized)
- end
-
- it 'responds with 401 Unauthorized when the provided JWT is signed with different secret' do
- jwt = build_jwt(personal_access_token, secret: SecureRandom.base64(32))
- get api(url), headers: build_token_auth_header(jwt.encoded)
-
- expect(response).to have_gitlab_http_status(:unauthorized)
- end
-
- it 'responds with 401 Unauthorized when invalid JWT is provided' do
- get api(url), headers: build_token_auth_header('invalid-jwt')
-
- expect(response).to have_gitlab_http_status(:unauthorized)
- end
-
context 'packages feature disabled' do
it 'responds with 404 Not Found' do
stub_packages_setting(enabled: false)
@@ -72,7 +22,10 @@ RSpec.shared_examples 'conan search endpoint' do
before do
project.update_column(:visibility_level, Gitlab::VisibilityLevel::PUBLIC)
- get api(url), headers: headers, params: params
+ # Do not pass the HTTP_AUTHORIZATION header,
+ # in order to test that this public project's packages
+ # are visible to anonymous search.
+ get api(url), params: params
end
subject { json_response['results'] }
@@ -109,6 +62,33 @@ RSpec.shared_examples 'conan authenticate endpoint' do
end
end
+ it 'responds with 401 Unauthorized when an invalid access token ID is provided' do
+ jwt = build_jwt(double(id: 12345), user_id: personal_access_token.user_id)
+ get api(url), headers: build_token_auth_header(jwt.encoded)
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+
+ it 'responds with 401 Unauthorized when invalid user is provided' do
+ jwt = build_jwt(personal_access_token, user_id: 12345)
+ get api(url), headers: build_token_auth_header(jwt.encoded)
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+
+ it 'responds with 401 Unauthorized when the provided JWT is signed with different secret' do
+ jwt = build_jwt(personal_access_token, secret: SecureRandom.base64(32))
+ get api(url), headers: build_token_auth_header(jwt.encoded)
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+
+ it 'responds with 401 UnauthorizedOK when invalid JWT is provided' do
+ get api(url), headers: build_token_auth_header('invalid-jwt')
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+
context 'when valid JWT access token is provided' do
it 'responds with 200' do
subject
@@ -507,19 +487,37 @@ RSpec.shared_examples 'delete package endpoint' do
end
end
+RSpec.shared_examples 'allows download with no token' do
+ context 'with no private token' do
+ let(:headers) { {} }
+
+ it 'returns 200' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+end
+
RSpec.shared_examples 'denies download with no token' do
context 'with no private token' do
let(:headers) { {} }
- it 'returns 400' do
+ it 'returns 404' do
subject
- expect(response).to have_gitlab_http_status(:unauthorized)
+ expect(response).to have_gitlab_http_status(:not_found)
end
end
end
RSpec.shared_examples 'a public project with packages' do
+ before do
+ project.update_column(:visibility_level, Gitlab::VisibilityLevel::PUBLIC)
+ end
+
+ it_behaves_like 'allows download with no token'
+
it 'returns the file' do
subject
diff --git a/spec/support/shared_examples/requests/api/graphql/group_and_project_boards_query_shared_examples.rb b/spec/support/shared_examples/requests/api/graphql/group_and_project_boards_query_shared_examples.rb
index 54f4ba7ff73..274516cd87b 100644
--- a/spec/support/shared_examples/requests/api/graphql/group_and_project_boards_query_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/graphql/group_and_project_boards_query_shared_examples.rb
@@ -25,7 +25,7 @@ RSpec.shared_examples 'group and project boards query' do
board = create(:board, resource_parent: board_parent, name: 'A')
allow(Ability).to receive(:allowed?).and_call_original
- allow(Ability).to receive(:allowed?).with(user, :read_board, board).and_return(false)
+ allow(Ability).to receive(:allowed?).with(user, :read_issue_board, board).and_return(false)
post_graphql(query, current_user: current_user)
diff --git a/spec/support/shared_examples/requests/api/graphql/packages/group_and_project_packages_list_shared_examples.rb b/spec/support/shared_examples/requests/api/graphql/packages/group_and_project_packages_list_shared_examples.rb
new file mode 100644
index 00000000000..66fbfa798b0
--- /dev/null
+++ b/spec/support/shared_examples/requests/api/graphql/packages/group_and_project_packages_list_shared_examples.rb
@@ -0,0 +1,51 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'group and project packages query' do
+ include GraphqlHelpers
+
+ context 'when user has access to the resource' do
+ before do
+ resource.add_reporter(current_user)
+ post_graphql(query, current_user: current_user)
+ end
+
+ it_behaves_like 'a working graphql query'
+
+ it 'returns packages successfully' do
+ expect(package_names).to contain_exactly(
+ package.name,
+ maven_package.name,
+ debian_package.name,
+ composer_package.name
+ )
+ end
+
+ it 'deals with metadata' do
+ expect(target_shas).to contain_exactly(composer_metadatum.target_sha)
+ end
+ end
+
+ context 'when the user does not have access to the resource' do
+ before do
+ post_graphql(query, current_user: current_user)
+ end
+
+ it_behaves_like 'a working graphql query'
+
+ it 'returns nil' do
+ expect(packages).to be_nil
+ end
+ end
+
+ context 'when the user is not authenticated' do
+ before do
+ post_graphql(query)
+ end
+
+ it_behaves_like 'a working graphql query'
+
+ it 'returns nil' do
+ expect(packages).to be_nil
+ end
+ end
+end
diff --git a/spec/support/shared_examples/requests/api/logging_application_context_shared_examples.rb b/spec/support/shared_examples/requests/api/logging_application_context_shared_examples.rb
index 038ede884c8..4a71b696d57 100644
--- a/spec/support/shared_examples/requests/api/logging_application_context_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/logging_application_context_shared_examples.rb
@@ -22,3 +22,19 @@ RSpec.shared_examples 'storing arguments in the application context' do
hash.transform_keys! { |key| "meta.#{key}" }
end
end
+
+RSpec.shared_examples 'not executing any extra queries for the application context' do |expected_extra_queries = 0|
+ it 'does not execute more queries than without adding anything to the application context' do
+ # Call the subject once to memoize all factories being used for the spec, so they won't
+ # add any queries to the expectation.
+ subject_proc.call
+
+ expect do
+ allow(Gitlab::ApplicationContext).to receive(:push).and_call_original
+ subject_proc.call
+ end.to issue_same_number_of_queries_as {
+ allow(Gitlab::ApplicationContext).to receive(:push)
+ subject_proc.call
+ }.with_threshold(expected_extra_queries).ignoring_cached_queries
+ end
+end
diff --git a/spec/support/shared_examples/requests/api/npm_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/npm_packages_shared_examples.rb
index be051dcbb7b..c15c59e1a1d 100644
--- a/spec/support/shared_examples/requests/api/npm_packages_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/npm_packages_shared_examples.rb
@@ -45,136 +45,234 @@ RSpec.shared_examples 'handling get metadata requests' do |scope: :project|
end
end
- where(:auth, :package_name_type, :request_forward, :visibility, :user_role, :expected_result, :expected_status) do
- nil | :scoped_naming_convention | true | 'PUBLIC' | nil | :accept | :ok
- nil | :scoped_naming_convention | false | 'PUBLIC' | nil | :accept | :ok
- nil | :non_existing | true | 'PUBLIC' | nil | :redirect | :redirected
- nil | :non_existing | false | 'PUBLIC' | nil | :reject | :not_found
- nil | :scoped_naming_convention | true | 'PRIVATE' | nil | :reject | :not_found
- nil | :scoped_naming_convention | false | 'PRIVATE' | nil | :reject | :not_found
- nil | :non_existing | true | 'PRIVATE' | nil | :redirect | :redirected
- nil | :non_existing | false | 'PRIVATE' | nil | :reject | :not_found
- nil | :scoped_naming_convention | true | 'INTERNAL' | nil | :reject | :not_found
- nil | :scoped_naming_convention | false | 'INTERNAL' | nil | :reject | :not_found
- nil | :non_existing | true | 'INTERNAL' | nil | :redirect | :redirected
- nil | :non_existing | false | 'INTERNAL' | nil | :reject | :not_found
-
- :oauth | :scoped_naming_convention | true | 'PUBLIC' | :guest | :accept | :ok
- :oauth | :scoped_naming_convention | true | 'PUBLIC' | :reporter | :accept | :ok
- :oauth | :scoped_naming_convention | false | 'PUBLIC' | :guest | :accept | :ok
- :oauth | :scoped_naming_convention | false | 'PUBLIC' | :reporter | :accept | :ok
- :oauth | :non_existing | true | 'PUBLIC' | :guest | :redirect | :redirected
- :oauth | :non_existing | true | 'PUBLIC' | :reporter | :redirect | :redirected
- :oauth | :non_existing | false | 'PUBLIC' | :guest | :reject | :not_found
- :oauth | :non_existing | false | 'PUBLIC' | :reporter | :reject | :not_found
- :oauth | :scoped_naming_convention | true | 'PRIVATE' | :guest | :reject | :forbidden
- :oauth | :scoped_naming_convention | true | 'PRIVATE' | :reporter | :accept | :ok
- :oauth | :scoped_naming_convention | false | 'PRIVATE' | :guest | :reject | :forbidden
- :oauth | :scoped_naming_convention | false | 'PRIVATE' | :reporter | :accept | :ok
- :oauth | :non_existing | true | 'PRIVATE' | :guest | :redirect | :redirected
- :oauth | :non_existing | true | 'PRIVATE' | :reporter | :redirect | :redirected
- :oauth | :non_existing | false | 'PRIVATE' | :guest | :reject | :forbidden
- :oauth | :non_existing | false | 'PRIVATE' | :reporter | :reject | :not_found
- :oauth | :scoped_naming_convention | true | 'INTERNAL' | :guest | :accept | :ok
- :oauth | :scoped_naming_convention | true | 'INTERNAL' | :reporter | :accept | :ok
- :oauth | :scoped_naming_convention | false | 'INTERNAL' | :guest | :accept | :ok
- :oauth | :scoped_naming_convention | false | 'INTERNAL' | :reporter | :accept | :ok
- :oauth | :non_existing | true | 'INTERNAL' | :guest | :redirect | :redirected
- :oauth | :non_existing | true | 'INTERNAL' | :reporter | :redirect | :redirected
- :oauth | :non_existing | false | 'INTERNAL' | :guest | :reject | :not_found
- :oauth | :non_existing | false | 'INTERNAL' | :reporter | :reject | :not_found
-
- :personal_access_token | :scoped_naming_convention | true | 'PUBLIC' | :guest | :accept | :ok
- :personal_access_token | :scoped_naming_convention | true | 'PUBLIC' | :reporter | :accept | :ok
- :personal_access_token | :scoped_naming_convention | false | 'PUBLIC' | :guest | :accept | :ok
- :personal_access_token | :scoped_naming_convention | false | 'PUBLIC' | :reporter | :accept | :ok
- :personal_access_token | :non_existing | true | 'PUBLIC' | :guest | :redirect | :redirected
- :personal_access_token | :non_existing | true | 'PUBLIC' | :reporter | :redirect | :redirected
- :personal_access_token | :non_existing | false | 'PUBLIC' | :guest | :reject | :not_found
- :personal_access_token | :non_existing | false | 'PUBLIC' | :reporter | :reject | :not_found
- :personal_access_token | :scoped_naming_convention | true | 'PRIVATE' | :guest | :reject | :forbidden
- :personal_access_token | :scoped_naming_convention | true | 'PRIVATE' | :reporter | :accept | :ok
- :personal_access_token | :scoped_naming_convention | false | 'PRIVATE' | :guest | :reject | :forbidden
- :personal_access_token | :scoped_naming_convention | false | 'PRIVATE' | :reporter | :accept | :ok
- :personal_access_token | :non_existing | true | 'PRIVATE' | :guest | :redirect | :redirected
- :personal_access_token | :non_existing | true | 'PRIVATE' | :reporter | :redirect | :redirected
- :personal_access_token | :non_existing | false | 'PRIVATE' | :guest | :reject | :forbidden
- :personal_access_token | :non_existing | false | 'PRIVATE' | :reporter | :reject | :not_found
- :personal_access_token | :scoped_naming_convention | true | 'INTERNAL' | :guest | :accept | :ok
- :personal_access_token | :scoped_naming_convention | true | 'INTERNAL' | :reporter | :accept | :ok
- :personal_access_token | :scoped_naming_convention | false | 'INTERNAL' | :guest | :accept | :ok
- :personal_access_token | :scoped_naming_convention | false | 'INTERNAL' | :reporter | :accept | :ok
- :personal_access_token | :non_existing | true | 'INTERNAL' | :guest | :redirect | :redirected
- :personal_access_token | :non_existing | true | 'INTERNAL' | :reporter | :redirect | :redirected
- :personal_access_token | :non_existing | false | 'INTERNAL' | :guest | :reject | :not_found
- :personal_access_token | :non_existing | false | 'INTERNAL' | :reporter | :reject | :not_found
-
- :job_token | :scoped_naming_convention | true | 'PUBLIC' | :developer | :accept | :ok
- :job_token | :scoped_naming_convention | false | 'PUBLIC' | :developer | :accept | :ok
- :job_token | :non_existing | true | 'PUBLIC' | :developer | :redirect | :redirected
- :job_token | :non_existing | false | 'PUBLIC' | :developer | :reject | :not_found
- :job_token | :scoped_naming_convention | true | 'PRIVATE' | :developer | :accept | :ok
- :job_token | :scoped_naming_convention | false | 'PRIVATE' | :developer | :accept | :ok
- :job_token | :non_existing | true | 'PRIVATE' | :developer | :redirect | :redirected
- :job_token | :non_existing | false | 'PRIVATE' | :developer | :reject | :not_found
- :job_token | :scoped_naming_convention | true | 'INTERNAL' | :developer | :accept | :ok
- :job_token | :scoped_naming_convention | false | 'INTERNAL' | :developer | :accept | :ok
- :job_token | :non_existing | true | 'INTERNAL' | :developer | :redirect | :redirected
- :job_token | :non_existing | false | 'INTERNAL' | :developer | :reject | :not_found
-
- :deploy_token | :scoped_naming_convention | true | 'PUBLIC' | nil | :accept | :ok
- :deploy_token | :scoped_naming_convention | false | 'PUBLIC' | nil | :accept | :ok
- :deploy_token | :non_existing | true | 'PUBLIC' | nil | :redirect | :redirected
- :deploy_token | :non_existing | false | 'PUBLIC' | nil | :reject | :not_found
- :deploy_token | :scoped_naming_convention | true | 'PRIVATE' | nil | :accept | :ok
- :deploy_token | :scoped_naming_convention | false | 'PRIVATE' | nil | :accept | :ok
- :deploy_token | :non_existing | true | 'PRIVATE' | nil | :redirect | :redirected
- :deploy_token | :non_existing | false | 'PRIVATE' | nil | :reject | :not_found
- :deploy_token | :scoped_naming_convention | true | 'INTERNAL' | nil | :accept | :ok
- :deploy_token | :scoped_naming_convention | false | 'INTERNAL' | nil | :accept | :ok
- :deploy_token | :non_existing | true | 'INTERNAL' | nil | :redirect | :redirected
- :deploy_token | :non_existing | false | 'INTERNAL' | nil | :reject | :not_found
- end
+ shared_examples 'handling all conditions' do
+ where(:auth, :package_name_type, :request_forward, :visibility, :user_role, :expected_result, :expected_status) do
+ nil | :scoped_naming_convention | true | :public | nil | :accept | :ok
+ nil | :scoped_naming_convention | false | :public | nil | :accept | :ok
+ nil | :scoped_no_naming_convention | true | :public | nil | :accept | :ok
+ nil | :scoped_no_naming_convention | false | :public | nil | :accept | :ok
+ nil | :unscoped | true | :public | nil | :accept | :ok
+ nil | :unscoped | false | :public | nil | :accept | :ok
+ nil | :non_existing | true | :public | nil | :redirect | :redirected
+ nil | :non_existing | false | :public | nil | :reject | :not_found
+ nil | :scoped_naming_convention | true | :private | nil | :reject | :not_found
+ nil | :scoped_naming_convention | false | :private | nil | :reject | :not_found
+ nil | :scoped_no_naming_convention | true | :private | nil | :reject | :not_found
+ nil | :scoped_no_naming_convention | false | :private | nil | :reject | :not_found
+ nil | :unscoped | true | :private | nil | :reject | :not_found
+ nil | :unscoped | false | :private | nil | :reject | :not_found
+ nil | :non_existing | true | :private | nil | :redirect | :redirected
+ nil | :non_existing | false | :private | nil | :reject | :not_found
+ nil | :scoped_naming_convention | true | :internal | nil | :reject | :not_found
+ nil | :scoped_naming_convention | false | :internal | nil | :reject | :not_found
+ nil | :scoped_no_naming_convention | true | :internal | nil | :reject | :not_found
+ nil | :scoped_no_naming_convention | false | :internal | nil | :reject | :not_found
+ nil | :unscoped | true | :internal | nil | :reject | :not_found
+ nil | :unscoped | false | :internal | nil | :reject | :not_found
+ nil | :non_existing | true | :internal | nil | :redirect | :redirected
+ nil | :non_existing | false | :internal | nil | :reject | :not_found
+
+ :oauth | :scoped_naming_convention | true | :public | :guest | :accept | :ok
+ :oauth | :scoped_naming_convention | true | :public | :reporter | :accept | :ok
+ :oauth | :scoped_naming_convention | false | :public | :guest | :accept | :ok
+ :oauth | :scoped_naming_convention | false | :public | :reporter | :accept | :ok
+ :oauth | :scoped_no_naming_convention | true | :public | :guest | :accept | :ok
+ :oauth | :scoped_no_naming_convention | true | :public | :reporter | :accept | :ok
+ :oauth | :scoped_no_naming_convention | false | :public | :guest | :accept | :ok
+ :oauth | :scoped_no_naming_convention | false | :public | :reporter | :accept | :ok
+ :oauth | :unscoped | true | :public | :guest | :accept | :ok
+ :oauth | :unscoped | true | :public | :reporter | :accept | :ok
+ :oauth | :unscoped | false | :public | :guest | :accept | :ok
+ :oauth | :unscoped | false | :public | :reporter | :accept | :ok
+ :oauth | :non_existing | true | :public | :guest | :redirect | :redirected
+ :oauth | :non_existing | true | :public | :reporter | :redirect | :redirected
+ :oauth | :non_existing | false | :public | :guest | :reject | :not_found
+ :oauth | :non_existing | false | :public | :reporter | :reject | :not_found
+ :oauth | :scoped_naming_convention | true | :private | :guest | :reject | :forbidden
+ :oauth | :scoped_naming_convention | true | :private | :reporter | :accept | :ok
+ :oauth | :scoped_naming_convention | false | :private | :guest | :reject | :forbidden
+ :oauth | :scoped_naming_convention | false | :private | :reporter | :accept | :ok
+ :oauth | :scoped_no_naming_convention | true | :private | :guest | :reject | :forbidden
+ :oauth | :scoped_no_naming_convention | true | :private | :reporter | :accept | :ok
+ :oauth | :scoped_no_naming_convention | false | :private | :guest | :reject | :forbidden
+ :oauth | :scoped_no_naming_convention | false | :private | :reporter | :accept | :ok
+ :oauth | :unscoped | true | :private | :guest | :reject | :forbidden
+ :oauth | :unscoped | true | :private | :reporter | :accept | :ok
+ :oauth | :unscoped | false | :private | :guest | :reject | :forbidden
+ :oauth | :unscoped | false | :private | :reporter | :accept | :ok
+ :oauth | :non_existing | true | :private | :guest | :redirect | :redirected
+ :oauth | :non_existing | true | :private | :reporter | :redirect | :redirected
+ :oauth | :non_existing | false | :private | :guest | :reject | :forbidden
+ :oauth | :non_existing | false | :private | :reporter | :reject | :not_found
+ :oauth | :scoped_naming_convention | true | :internal | :guest | :accept | :ok
+ :oauth | :scoped_naming_convention | true | :internal | :reporter | :accept | :ok
+ :oauth | :scoped_naming_convention | false | :internal | :guest | :accept | :ok
+ :oauth | :scoped_naming_convention | false | :internal | :reporter | :accept | :ok
+ :oauth | :scoped_no_naming_convention | true | :internal | :guest | :accept | :ok
+ :oauth | :scoped_no_naming_convention | true | :internal | :reporter | :accept | :ok
+ :oauth | :scoped_no_naming_convention | false | :internal | :guest | :accept | :ok
+ :oauth | :scoped_no_naming_convention | false | :internal | :reporter | :accept | :ok
+ :oauth | :unscoped | true | :internal | :guest | :accept | :ok
+ :oauth | :unscoped | true | :internal | :reporter | :accept | :ok
+ :oauth | :unscoped | false | :internal | :guest | :accept | :ok
+ :oauth | :unscoped | false | :internal | :reporter | :accept | :ok
+ :oauth | :non_existing | true | :internal | :guest | :redirect | :redirected
+ :oauth | :non_existing | true | :internal | :reporter | :redirect | :redirected
+ :oauth | :non_existing | false | :internal | :guest | :reject | :not_found
+ :oauth | :non_existing | false | :internal | :reporter | :reject | :not_found
+
+ :personal_access_token | :scoped_naming_convention | true | :public | :guest | :accept | :ok
+ :personal_access_token | :scoped_naming_convention | true | :public | :reporter | :accept | :ok
+ :personal_access_token | :scoped_naming_convention | false | :public | :guest | :accept | :ok
+ :personal_access_token | :scoped_naming_convention | false | :public | :reporter | :accept | :ok
+ :personal_access_token | :scoped_no_naming_convention | true | :public | :guest | :accept | :ok
+ :personal_access_token | :scoped_no_naming_convention | true | :public | :reporter | :accept | :ok
+ :personal_access_token | :scoped_no_naming_convention | false | :public | :guest | :accept | :ok
+ :personal_access_token | :scoped_no_naming_convention | false | :public | :reporter | :accept | :ok
+ :personal_access_token | :unscoped | true | :public | :guest | :accept | :ok
+ :personal_access_token | :unscoped | true | :public | :reporter | :accept | :ok
+ :personal_access_token | :unscoped | false | :public | :guest | :accept | :ok
+ :personal_access_token | :unscoped | false | :public | :reporter | :accept | :ok
+ :personal_access_token | :non_existing | true | :public | :guest | :redirect | :redirected
+ :personal_access_token | :non_existing | true | :public | :reporter | :redirect | :redirected
+ :personal_access_token | :non_existing | false | :public | :guest | :reject | :not_found
+ :personal_access_token | :non_existing | false | :public | :reporter | :reject | :not_found
+ :personal_access_token | :scoped_naming_convention | true | :private | :guest | :reject | :forbidden
+ :personal_access_token | :scoped_naming_convention | true | :private | :reporter | :accept | :ok
+ :personal_access_token | :scoped_naming_convention | false | :private | :guest | :reject | :forbidden
+ :personal_access_token | :scoped_naming_convention | false | :private | :reporter | :accept | :ok
+ :personal_access_token | :scoped_no_naming_convention | true | :private | :guest | :reject | :forbidden
+ :personal_access_token | :scoped_no_naming_convention | true | :private | :reporter | :accept | :ok
+ :personal_access_token | :scoped_no_naming_convention | false | :private | :guest | :reject | :forbidden
+ :personal_access_token | :scoped_no_naming_convention | false | :private | :reporter | :accept | :ok
+ :personal_access_token | :unscoped | true | :private | :guest | :reject | :forbidden
+ :personal_access_token | :unscoped | true | :private | :reporter | :accept | :ok
+ :personal_access_token | :unscoped | false | :private | :guest | :reject | :forbidden
+ :personal_access_token | :unscoped | false | :private | :reporter | :accept | :ok
+ :personal_access_token | :non_existing | true | :private | :guest | :redirect | :redirected
+ :personal_access_token | :non_existing | true | :private | :reporter | :redirect | :redirected
+ :personal_access_token | :non_existing | false | :private | :guest | :reject | :forbidden
+ :personal_access_token | :non_existing | false | :private | :reporter | :reject | :not_found
+ :personal_access_token | :scoped_naming_convention | true | :internal | :guest | :accept | :ok
+ :personal_access_token | :scoped_naming_convention | true | :internal | :reporter | :accept | :ok
+ :personal_access_token | :scoped_naming_convention | false | :internal | :guest | :accept | :ok
+ :personal_access_token | :scoped_naming_convention | false | :internal | :reporter | :accept | :ok
+ :personal_access_token | :scoped_no_naming_convention | true | :internal | :guest | :accept | :ok
+ :personal_access_token | :scoped_no_naming_convention | true | :internal | :reporter | :accept | :ok
+ :personal_access_token | :scoped_no_naming_convention | false | :internal | :guest | :accept | :ok
+ :personal_access_token | :scoped_no_naming_convention | false | :internal | :reporter | :accept | :ok
+ :personal_access_token | :unscoped | true | :internal | :guest | :accept | :ok
+ :personal_access_token | :unscoped | true | :internal | :reporter | :accept | :ok
+ :personal_access_token | :unscoped | false | :internal | :guest | :accept | :ok
+ :personal_access_token | :unscoped | false | :internal | :reporter | :accept | :ok
+ :personal_access_token | :non_existing | true | :internal | :guest | :redirect | :redirected
+ :personal_access_token | :non_existing | true | :internal | :reporter | :redirect | :redirected
+ :personal_access_token | :non_existing | false | :internal | :guest | :reject | :not_found
+ :personal_access_token | :non_existing | false | :internal | :reporter | :reject | :not_found
+
+ :job_token | :scoped_naming_convention | true | :public | :developer | :accept | :ok
+ :job_token | :scoped_naming_convention | false | :public | :developer | :accept | :ok
+ :job_token | :scoped_no_naming_convention | true | :public | :developer | :accept | :ok
+ :job_token | :scoped_no_naming_convention | false | :public | :developer | :accept | :ok
+ :job_token | :unscoped | true | :public | :developer | :accept | :ok
+ :job_token | :unscoped | false | :public | :developer | :accept | :ok
+ :job_token | :non_existing | true | :public | :developer | :redirect | :redirected
+ :job_token | :non_existing | false | :public | :developer | :reject | :not_found
+ :job_token | :scoped_naming_convention | true | :private | :developer | :accept | :ok
+ :job_token | :scoped_naming_convention | false | :private | :developer | :accept | :ok
+ :job_token | :scoped_no_naming_convention | true | :private | :developer | :accept | :ok
+ :job_token | :scoped_no_naming_convention | false | :private | :developer | :accept | :ok
+ :job_token | :unscoped | true | :private | :developer | :accept | :ok
+ :job_token | :unscoped | false | :private | :developer | :accept | :ok
+ :job_token | :non_existing | true | :private | :developer | :redirect | :redirected
+ :job_token | :non_existing | false | :private | :developer | :reject | :not_found
+ :job_token | :scoped_naming_convention | true | :internal | :developer | :accept | :ok
+ :job_token | :scoped_naming_convention | false | :internal | :developer | :accept | :ok
+ :job_token | :scoped_no_naming_convention | true | :internal | :developer | :accept | :ok
+ :job_token | :scoped_no_naming_convention | false | :internal | :developer | :accept | :ok
+ :job_token | :unscoped | true | :internal | :developer | :accept | :ok
+ :job_token | :unscoped | false | :internal | :developer | :accept | :ok
+ :job_token | :non_existing | true | :internal | :developer | :redirect | :redirected
+ :job_token | :non_existing | false | :internal | :developer | :reject | :not_found
+
+ :deploy_token | :scoped_naming_convention | true | :public | nil | :accept | :ok
+ :deploy_token | :scoped_naming_convention | false | :public | nil | :accept | :ok
+ :deploy_token | :scoped_no_naming_convention | true | :public | nil | :accept | :ok
+ :deploy_token | :scoped_no_naming_convention | false | :public | nil | :accept | :ok
+ :deploy_token | :unscoped | true | :public | nil | :accept | :ok
+ :deploy_token | :unscoped | false | :public | nil | :accept | :ok
+ :deploy_token | :non_existing | true | :public | nil | :redirect | :redirected
+ :deploy_token | :non_existing | false | :public | nil | :reject | :not_found
+ :deploy_token | :scoped_naming_convention | true | :private | nil | :accept | :ok
+ :deploy_token | :scoped_naming_convention | false | :private | nil | :accept | :ok
+ :deploy_token | :scoped_no_naming_convention | true | :private | nil | :accept | :ok
+ :deploy_token | :scoped_no_naming_convention | false | :private | nil | :accept | :ok
+ :deploy_token | :unscoped | true | :private | nil | :accept | :ok
+ :deploy_token | :unscoped | false | :private | nil | :accept | :ok
+ :deploy_token | :non_existing | true | :private | nil | :redirect | :redirected
+ :deploy_token | :non_existing | false | :private | nil | :reject | :not_found
+ :deploy_token | :scoped_naming_convention | true | :internal | nil | :accept | :ok
+ :deploy_token | :scoped_naming_convention | false | :internal | nil | :accept | :ok
+ :deploy_token | :scoped_no_naming_convention | true | :internal | nil | :accept | :ok
+ :deploy_token | :scoped_no_naming_convention | false | :internal | nil | :accept | :ok
+ :deploy_token | :unscoped | true | :internal | nil | :accept | :ok
+ :deploy_token | :unscoped | false | :internal | nil | :accept | :ok
+ :deploy_token | :non_existing | true | :internal | nil | :redirect | :redirected
+ :deploy_token | :non_existing | false | :internal | nil | :reject | :not_found
+ end
- with_them do
- include_context 'set package name from package name type'
-
- let(:headers) do
- case auth
- when :oauth
- build_token_auth_header(token.token)
- when :personal_access_token
- build_token_auth_header(personal_access_token.token)
- when :job_token
- build_token_auth_header(job.token)
- when :deploy_token
- build_token_auth_header(deploy_token.token)
- else
- {}
+ with_them do
+ include_context 'set package name from package name type'
+
+ let(:headers) do
+ case auth
+ when :oauth
+ build_token_auth_header(token.token)
+ when :personal_access_token
+ build_token_auth_header(personal_access_token.token)
+ when :job_token
+ build_token_auth_header(job.token)
+ when :deploy_token
+ build_token_auth_header(deploy_token.token)
+ else
+ {}
+ end
end
- end
- before do
- project.send("add_#{user_role}", user) if user_role
- project.update!(visibility: Gitlab::VisibilityLevel.const_get(visibility, false))
- package.update!(name: package_name) unless package_name == 'non-existing-package'
- stub_application_setting(npm_package_requests_forwarding: request_forward)
- end
+ before do
+ project.send("add_#{user_role}", user) if user_role
+ project.update!(visibility: visibility.to_s)
+ package.update!(name: package_name) unless package_name == 'non-existing-package'
+ stub_application_setting(npm_package_requests_forwarding: request_forward)
+ end
- example_name = "#{params[:expected_result]} metadata request"
- status = params[:expected_status]
+ example_name = "#{params[:expected_result]} metadata request"
+ status = params[:expected_status]
- if scope == :instance && params[:package_name_type] != :scoped_naming_convention
- if params[:request_forward]
- example_name = 'redirect metadata request'
- status = :redirected
- else
- example_name = 'reject metadata request'
- status = :not_found
+ if scope == :instance && params[:package_name_type] != :scoped_naming_convention
+ if params[:request_forward]
+ example_name = 'redirect metadata request'
+ status = :redirected
+ else
+ example_name = 'reject metadata request'
+ status = :not_found
+ end
end
+
+ it_behaves_like example_name, status: status
end
+ end
- it_behaves_like example_name, status: status
+ context 'with a group namespace' do
+ it_behaves_like 'handling all conditions'
+ end
+
+ if scope != :project
+ context 'with a user namespace' do
+ let_it_be(:namespace) { user.namespace }
+
+ it_behaves_like 'handling all conditions'
+ end
end
context 'with a developer' do
@@ -225,26 +323,44 @@ RSpec.shared_examples 'handling get dist tags requests' do |scope: :project|
shared_examples 'handling different package names, visibilities and user roles' do
where(:package_name_type, :visibility, :user_role, :expected_result, :expected_status) do
- :scoped_naming_convention | 'PUBLIC' | :anonymous | :accept | :ok
- :scoped_naming_convention | 'PUBLIC' | :guest | :accept | :ok
- :scoped_naming_convention | 'PUBLIC' | :reporter | :accept | :ok
- :non_existing | 'PUBLIC' | :anonymous | :reject | :not_found
- :non_existing | 'PUBLIC' | :guest | :reject | :not_found
- :non_existing | 'PUBLIC' | :reporter | :reject | :not_found
-
- :scoped_naming_convention | 'PRIVATE' | :anonymous | :reject | :not_found
- :scoped_naming_convention | 'PRIVATE' | :guest | :reject | :forbidden
- :scoped_naming_convention | 'PRIVATE' | :reporter | :accept | :ok
- :non_existing | 'PRIVATE' | :anonymous | :reject | :not_found
- :non_existing | 'PRIVATE' | :guest | :reject | :forbidden
- :non_existing | 'PRIVATE' | :reporter | :reject | :not_found
-
- :scoped_naming_convention | 'INTERNAL' | :anonymous | :reject | :not_found
- :scoped_naming_convention | 'INTERNAL' | :guest | :accept | :ok
- :scoped_naming_convention | 'INTERNAL' | :reporter | :accept | :ok
- :non_existing | 'INTERNAL' | :anonymous | :reject | :not_found
- :non_existing | 'INTERNAL' | :guest | :reject | :not_found
- :non_existing | 'INTERNAL' | :reporter | :reject | :not_found
+ :scoped_naming_convention | :public | :anonymous | :accept | :ok
+ :scoped_naming_convention | :public | :guest | :accept | :ok
+ :scoped_naming_convention | :public | :reporter | :accept | :ok
+ :scoped_no_naming_convention | :public | :anonymous | :accept | :ok
+ :scoped_no_naming_convention | :public | :guest | :accept | :ok
+ :scoped_no_naming_convention | :public | :reporter | :accept | :ok
+ :unscoped | :public | :anonymous | :accept | :ok
+ :unscoped | :public | :guest | :accept | :ok
+ :unscoped | :public | :reporter | :accept | :ok
+ :non_existing | :public | :anonymous | :reject | :not_found
+ :non_existing | :public | :guest | :reject | :not_found
+ :non_existing | :public | :reporter | :reject | :not_found
+
+ :scoped_naming_convention | :private | :anonymous | :reject | :not_found
+ :scoped_naming_convention | :private | :guest | :reject | :forbidden
+ :scoped_naming_convention | :private | :reporter | :accept | :ok
+ :scoped_no_naming_convention | :private | :anonymous | :reject | :not_found
+ :scoped_no_naming_convention | :private | :guest | :reject | :forbidden
+ :scoped_no_naming_convention | :private | :reporter | :accept | :ok
+ :unscoped | :private | :anonymous | :reject | :not_found
+ :unscoped | :private | :guest | :reject | :forbidden
+ :unscoped | :private | :reporter | :accept | :ok
+ :non_existing | :private | :anonymous | :reject | :not_found
+ :non_existing | :private | :guest | :reject | :forbidden
+ :non_existing | :private | :reporter | :reject | :not_found
+
+ :scoped_naming_convention | :internal | :anonymous | :reject | :not_found
+ :scoped_naming_convention | :internal | :guest | :accept | :ok
+ :scoped_naming_convention | :internal | :reporter | :accept | :ok
+ :scoped_no_naming_convention | :internal | :anonymous | :reject | :not_found
+ :scoped_no_naming_convention | :internal | :guest | :accept | :ok
+ :scoped_no_naming_convention | :internal | :reporter | :accept | :ok
+ :unscoped | :internal | :anonymous | :reject | :not_found
+ :unscoped | :internal | :guest | :accept | :ok
+ :unscoped | :internal | :reporter | :accept | :ok
+ :non_existing | :internal | :anonymous | :reject | :not_found
+ :non_existing | :internal | :guest | :reject | :not_found
+ :non_existing | :internal | :reporter | :reject | :not_found
end
with_them do
@@ -254,7 +370,7 @@ RSpec.shared_examples 'handling get dist tags requests' do |scope: :project|
before do
project.send("add_#{user_role}", user) unless anonymous
- project.update!(visibility: Gitlab::VisibilityLevel.const_get(visibility, false))
+ project.update!(visibility: visibility.to_s)
end
example_name = "#{params[:expected_result]} package tags request"
@@ -269,16 +385,30 @@ RSpec.shared_examples 'handling get dist tags requests' do |scope: :project|
end
end
- context 'with oauth token' do
- let(:headers) { build_token_auth_header(token.token) }
+ shared_examples 'handling all conditions' do
+ context 'with oauth token' do
+ let(:headers) { build_token_auth_header(token.token) }
+
+ it_behaves_like 'handling different package names, visibilities and user roles'
+ end
+
+ context 'with personal access token' do
+ let(:headers) { build_token_auth_header(personal_access_token.token) }
- it_behaves_like 'handling different package names, visibilities and user roles'
+ it_behaves_like 'handling different package names, visibilities and user roles'
+ end
end
- context 'with personal access token' do
- let(:headers) { build_token_auth_header(personal_access_token.token) }
+ context 'with a group namespace' do
+ it_behaves_like 'handling all conditions'
+ end
- it_behaves_like 'handling different package names, visibilities and user roles'
+ if scope != :project
+ context 'with a user namespace' do
+ let_it_be(:namespace) { user.namespace }
+
+ it_behaves_like 'handling all conditions'
+ end
end
end
@@ -303,26 +433,44 @@ RSpec.shared_examples 'handling create dist tag requests' do |scope: :project|
shared_examples 'handling different package names, visibilities and user roles' do
where(:package_name_type, :visibility, :user_role, :expected_result, :expected_status) do
- :scoped_naming_convention | 'PUBLIC' | :anonymous | :reject | :forbidden
- :scoped_naming_convention | 'PUBLIC' | :guest | :reject | :forbidden
- :scoped_naming_convention | 'PUBLIC' | :developer | :accept | :ok
- :non_existing | 'PUBLIC' | :anonymous | :reject | :forbidden
- :non_existing | 'PUBLIC' | :guest | :reject | :forbidden
- :non_existing | 'PUBLIC' | :developer | :reject | :not_found
-
- :scoped_naming_convention | 'PRIVATE' | :anonymous | :reject | :not_found
- :scoped_naming_convention | 'PRIVATE' | :guest | :reject | :forbidden
- :scoped_naming_convention | 'PRIVATE' | :developer | :accept | :ok
- :non_existing | 'PRIVATE' | :anonymous | :reject | :not_found
- :non_existing | 'PRIVATE' | :guest | :reject | :forbidden
- :non_existing | 'PRIVATE' | :developer | :reject | :not_found
-
- :scoped_naming_convention | 'INTERNAL' | :anonymous | :reject | :forbidden
- :scoped_naming_convention | 'INTERNAL' | :guest | :reject | :forbidden
- :scoped_naming_convention | 'INTERNAL' | :developer | :accept | :ok
- :non_existing | 'INTERNAL' | :anonymous | :reject | :forbidden
- :non_existing | 'INTERNAL' | :guest | :reject | :forbidden
- :non_existing | 'INTERNAL' | :developer | :reject | :not_found
+ :scoped_naming_convention | :public | :anonymous | :reject | :forbidden
+ :scoped_naming_convention | :public | :guest | :reject | :forbidden
+ :scoped_naming_convention | :public | :developer | :accept | :ok
+ :scoped_no_naming_convention | :public | :anonymous | :reject | :forbidden
+ :scoped_no_naming_convention | :public | :guest | :reject | :forbidden
+ :scoped_no_naming_convention | :public | :developer | :accept | :ok
+ :unscoped | :public | :anonymous | :reject | :forbidden
+ :unscoped | :public | :guest | :reject | :forbidden
+ :unscoped | :public | :developer | :accept | :ok
+ :non_existing | :public | :anonymous | :reject | :forbidden
+ :non_existing | :public | :guest | :reject | :forbidden
+ :non_existing | :public | :developer | :reject | :not_found
+
+ :scoped_naming_convention | :private | :anonymous | :reject | :not_found
+ :scoped_naming_convention | :private | :guest | :reject | :forbidden
+ :scoped_naming_convention | :private | :developer | :accept | :ok
+ :scoped_no_naming_convention | :private | :anonymous | :reject | :not_found
+ :scoped_no_naming_convention | :private | :guest | :reject | :forbidden
+ :scoped_no_naming_convention | :private | :developer | :accept | :ok
+ :unscoped | :private | :anonymous | :reject | :not_found
+ :unscoped | :private | :guest | :reject | :forbidden
+ :unscoped | :private | :developer | :accept | :ok
+ :non_existing | :private | :anonymous | :reject | :not_found
+ :non_existing | :private | :guest | :reject | :forbidden
+ :non_existing | :private | :developer | :reject | :not_found
+
+ :scoped_naming_convention | :internal | :anonymous | :reject | :forbidden
+ :scoped_naming_convention | :internal | :guest | :reject | :forbidden
+ :scoped_naming_convention | :internal | :developer | :accept | :ok
+ :scoped_no_naming_convention | :internal | :anonymous | :reject | :forbidden
+ :scoped_no_naming_convention | :internal | :guest | :reject | :forbidden
+ :scoped_no_naming_convention | :internal | :developer | :accept | :ok
+ :unscoped | :internal | :anonymous | :reject | :forbidden
+ :unscoped | :internal | :guest | :reject | :forbidden
+ :unscoped | :internal | :developer | :accept | :ok
+ :non_existing | :internal | :anonymous | :reject | :forbidden
+ :non_existing | :internal | :guest | :reject | :forbidden
+ :non_existing | :internal | :developer | :reject | :not_found
end
with_them do
@@ -332,7 +480,7 @@ RSpec.shared_examples 'handling create dist tag requests' do |scope: :project|
before do
project.send("add_#{user_role}", user) unless anonymous
- project.update!(visibility: Gitlab::VisibilityLevel.const_get(visibility, false))
+ project.update!(visibility: visibility.to_s)
end
example_name = "#{params[:expected_result]} create package tag request"
@@ -347,16 +495,30 @@ RSpec.shared_examples 'handling create dist tag requests' do |scope: :project|
end
end
- context 'with oauth token' do
- let(:headers) { build_token_auth_header(token.token) }
+ shared_examples 'handling all conditions' do
+ context 'with oauth token' do
+ let(:headers) { build_token_auth_header(token.token) }
- it_behaves_like 'handling different package names, visibilities and user roles'
+ it_behaves_like 'handling different package names, visibilities and user roles'
+ end
+
+ context 'with personal access token' do
+ let(:headers) { build_token_auth_header(personal_access_token.token) }
+
+ it_behaves_like 'handling different package names, visibilities and user roles'
+ end
end
- context 'with personal access token' do
- let(:headers) { build_token_auth_header(personal_access_token.token) }
+ context 'with a group namespace' do
+ it_behaves_like 'handling all conditions'
+ end
- it_behaves_like 'handling different package names, visibilities and user roles'
+ if scope != :project
+ context 'with a user namespace' do
+ let_it_be(:namespace) { user.namespace }
+
+ it_behaves_like 'handling all conditions'
+ end
end
end
@@ -379,19 +541,44 @@ RSpec.shared_examples 'handling delete dist tag requests' do |scope: :project|
shared_examples 'handling different package names, visibilities and user roles' do
where(:package_name_type, :visibility, :user_role, :expected_result, :expected_status) do
- :scoped_naming_convention | 'PUBLIC' | :anonymous | :reject | :forbidden
- :scoped_naming_convention | 'PUBLIC' | :guest | :reject | :forbidden
- :scoped_naming_convention | 'PUBLIC' | :maintainer | :accept | :ok
- :non_existing | 'PUBLIC' | :anonymous | :reject | :forbidden
- :non_existing | 'PUBLIC' | :guest | :reject | :forbidden
- :non_existing | 'PUBLIC' | :maintainer | :reject | :not_found
-
- :scoped_naming_convention | 'PRIVATE' | :anonymous | :reject | :not_found
- :scoped_naming_convention | 'PRIVATE' | :guest | :reject | :forbidden
- :scoped_naming_convention | 'PRIVATE' | :maintainer | :accept | :ok
- :non_existing | 'INTERNAL' | :anonymous | :reject | :forbidden
- :non_existing | 'INTERNAL' | :guest | :reject | :forbidden
- :non_existing | 'INTERNAL' | :maintainer | :reject | :not_found
+ :scoped_naming_convention | :public | :anonymous | :reject | :forbidden
+ :scoped_naming_convention | :public | :guest | :reject | :forbidden
+ :scoped_naming_convention | :public | :maintainer | :accept | :ok
+ :scoped_no_naming_convention | :public | :anonymous | :reject | :forbidden
+ :scoped_no_naming_convention | :public | :guest | :reject | :forbidden
+ :scoped_no_naming_convention | :public | :maintainer | :accept | :ok
+ :unscoped | :public | :anonymous | :reject | :forbidden
+ :unscoped | :public | :guest | :reject | :forbidden
+ :unscoped | :public | :maintainer | :accept | :ok
+ :non_existing | :public | :anonymous | :reject | :forbidden
+ :non_existing | :public | :guest | :reject | :forbidden
+ :non_existing | :public | :maintainer | :reject | :not_found
+
+ :scoped_naming_convention | :private | :anonymous | :reject | :not_found
+ :scoped_naming_convention | :private | :guest | :reject | :forbidden
+ :scoped_naming_convention | :private | :maintainer | :accept | :ok
+ :scoped_no_naming_convention | :private | :anonymous | :reject | :not_found
+ :scoped_no_naming_convention | :private | :guest | :reject | :forbidden
+ :scoped_no_naming_convention | :private | :maintainer | :accept | :ok
+ :unscoped | :private | :anonymous | :reject | :not_found
+ :unscoped | :private | :guest | :reject | :forbidden
+ :unscoped | :private | :maintainer | :accept | :ok
+ :non_existing | :private | :anonymous | :reject | :not_found
+ :non_existing | :private | :guest | :reject | :forbidden
+ :non_existing | :private | :maintainer | :reject | :not_found
+
+ :scoped_naming_convention | :internal | :anonymous | :reject | :forbidden
+ :scoped_naming_convention | :internal | :guest | :reject | :forbidden
+ :scoped_naming_convention | :internal | :maintainer | :accept | :ok
+ :scoped_no_naming_convention | :internal | :anonymous | :reject | :forbidden
+ :scoped_no_naming_convention | :internal | :guest | :reject | :forbidden
+ :scoped_no_naming_convention | :internal | :maintainer | :accept | :ok
+ :unscoped | :internal | :anonymous | :reject | :forbidden
+ :unscoped | :internal | :guest | :reject | :forbidden
+ :unscoped | :internal | :maintainer | :accept | :ok
+ :non_existing | :internal | :anonymous | :reject | :forbidden
+ :non_existing | :internal | :guest | :reject | :forbidden
+ :non_existing | :internal | :maintainer | :reject | :not_found
end
with_them do
@@ -401,7 +588,7 @@ RSpec.shared_examples 'handling delete dist tag requests' do |scope: :project|
before do
project.send("add_#{user_role}", user) unless anonymous
- project.update!(visibility: Gitlab::VisibilityLevel.const_get(visibility, false))
+ project.update!(visibility: visibility.to_s)
end
example_name = "#{params[:expected_result]} delete package tag request"
@@ -416,15 +603,29 @@ RSpec.shared_examples 'handling delete dist tag requests' do |scope: :project|
end
end
- context 'with oauth token' do
- let(:headers) { build_token_auth_header(token.token) }
+ shared_examples 'handling all conditions' do
+ context 'with oauth token' do
+ let(:headers) { build_token_auth_header(token.token) }
+
+ it_behaves_like 'handling different package names, visibilities and user roles'
+ end
+
+ context 'with personal access token' do
+ let(:headers) { build_token_auth_header(personal_access_token.token) }
- it_behaves_like 'handling different package names, visibilities and user roles'
+ it_behaves_like 'handling different package names, visibilities and user roles'
+ end
end
- context 'with personal access token' do
- let(:headers) { build_token_auth_header(personal_access_token.token) }
+ context 'with a group namespace' do
+ it_behaves_like 'handling all conditions'
+ end
- it_behaves_like 'handling different package names, visibilities and user roles'
+ if scope != :project
+ context 'with a user namespace' do
+ let_it_be(:namespace) { user.namespace }
+
+ it_behaves_like 'handling all conditions'
+ end
end
end
diff --git a/spec/support/shared_examples/requests/api/rubygems_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/rubygems_packages_shared_examples.rb
new file mode 100644
index 00000000000..15fb6611b90
--- /dev/null
+++ b/spec/support/shared_examples/requests/api/rubygems_packages_shared_examples.rb
@@ -0,0 +1,194 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'rejects rubygems packages access' do |user_type, status, add_member = true|
+ context "for user type #{user_type}" do
+ before do
+ project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ end
+
+ it_behaves_like 'returning response status', status
+ end
+end
+
+RSpec.shared_examples 'process rubygems workhorse authorization' do |user_type, status, add_member = true|
+ context "for user type #{user_type}" do
+ before do
+ project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ end
+
+ it_behaves_like 'returning response status', status
+
+ it 'has the proper content type' do
+ subject
+
+ expect(response.media_type).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
+ end
+
+ context 'with a request that bypassed gitlab-workhorse' do
+ let(:headers) do
+ { 'HTTP_AUTHORIZATION' => personal_access_token.token }
+ .merge(workhorse_headers)
+ .tap { |h| h.delete(Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER) }
+ end
+
+ before do
+ project.add_maintainer(user)
+ end
+
+ it_behaves_like 'returning response status', :forbidden
+ end
+ end
+end
+
+RSpec.shared_examples 'process rubygems upload' do |user_type, status, add_member = true|
+ RSpec.shared_examples 'creates rubygems package files' do
+ it 'creates package files', :aggregate_failures do
+ expect { subject }
+ .to change { project.packages.count }.by(1)
+ .and change { Packages::PackageFile.count }.by(1)
+ expect(response).to have_gitlab_http_status(status)
+
+ package_file = project.packages.last.package_files.reload.last
+ expect(package_file.file_name).to eq('package.gem')
+ end
+ end
+
+ context "for user type #{user_type}" do
+ before do
+ project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ end
+
+ context 'with object storage disabled' do
+ before do
+ stub_package_file_object_storage(enabled: false)
+ end
+
+ context 'without a file from workhorse' do
+ let(:send_rewritten_field) { false }
+
+ it_behaves_like 'returning response status', :bad_request
+ end
+
+ context 'with correct params' do
+ it_behaves_like 'package workhorse uploads'
+ it_behaves_like 'creates rubygems package files'
+ it_behaves_like 'a package tracking event', 'API::RubygemPackages', 'push_package'
+ end
+ end
+
+ context 'with object storage enabled' do
+ let(:tmp_object) do
+ fog_connection.directories.new(key: 'packages').files.create( # rubocop:disable Rails/SaveBang
+ key: "tmp/uploads/#{file_name}",
+ body: 'content'
+ )
+ end
+
+ let(:fog_file) { fog_to_uploaded_file(tmp_object) }
+ let(:params) { { file: fog_file, 'file.remote_id' => file_name } }
+
+ context 'and direct upload enabled' do
+ let(:fog_connection) do
+ stub_package_file_object_storage(direct_upload: true)
+ end
+
+ it_behaves_like 'creates rubygems package files'
+
+ ['123123', '../../123123'].each do |remote_id|
+ context "with invalid remote_id: #{remote_id}" do
+ let(:params) do
+ {
+ file: fog_file,
+ 'file.remote_id' => remote_id
+ }
+ end
+
+ it_behaves_like 'returning response status', :forbidden
+ end
+ end
+ end
+
+ context 'and direct upload disabled' do
+ context 'and background upload disabled' do
+ let(:fog_connection) do
+ stub_package_file_object_storage(direct_upload: false, background_upload: false)
+ end
+
+ it_behaves_like 'creates rubygems package files'
+ end
+
+ context 'and background upload enabled' do
+ let(:fog_connection) do
+ stub_package_file_object_storage(direct_upload: false, background_upload: true)
+ end
+
+ it_behaves_like 'creates rubygems package files'
+ end
+ end
+ end
+ end
+end
+
+RSpec.shared_examples 'dependency endpoint success' do |user_type, status, add_member = true|
+ context "for user type #{user_type}" do
+ before do
+ project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ end
+
+ raise 'Status is not :success' if status != :success
+
+ context 'with no params', :aggregate_failures do
+ it 'returns empty' do
+ subject
+
+ expect(response.body).to eq('200')
+ expect(response).to have_gitlab_http_status(status)
+ end
+ end
+
+ context 'with gems params' do
+ let(:params) { { gems: 'foo,bar' } }
+ let(:expected_response) { Marshal.dump(%w(result result)) }
+
+ it 'returns successfully', :aggregate_failures do
+ service_result = double('DependencyResolverService', execute: ServiceResponse.success(payload: 'result'))
+
+ expect(Packages::Rubygems::DependencyResolverService).to receive(:new).with(project, anything, gem_name: 'foo').and_return(service_result)
+ expect(Packages::Rubygems::DependencyResolverService).to receive(:new).with(project, anything, gem_name: 'bar').and_return(service_result)
+
+ subject
+
+ expect(response.body).to eq(expected_response) # rubocop:disable Security/MarshalLoad
+ expect(response).to have_gitlab_http_status(status)
+ end
+
+ it 'rejects if the service fails', :aggregate_failures do
+ service_result = double('DependencyResolverService', execute: ServiceResponse.error(message: 'rejected', http_status: :bad_request))
+
+ expect(Packages::Rubygems::DependencyResolverService).to receive(:new).with(project, anything, gem_name: 'foo').and_return(service_result)
+
+ subject
+
+ expect(response.body).to match(/rejected/)
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+ end
+ end
+end
+
+RSpec.shared_examples 'Rubygems gem download' do |user_type, status, add_member = true|
+ context "for user type #{user_type}" do
+ before do
+ project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
+ end
+
+ it 'returns the gem', :aggregate_failures do
+ subject
+
+ expect(response.media_type).to eq('application/octet-stream')
+ expect(response).to have_gitlab_http_status(status)
+ end
+
+ it_behaves_like 'a package tracking event', described_class.name, 'pull_package'
+ end
+end
diff --git a/spec/support/shared_examples/service_desk_issue_templates_examples.rb b/spec/support/shared_examples/service_desk_issue_templates_examples.rb
new file mode 100644
index 00000000000..fd9645df7a3
--- /dev/null
+++ b/spec/support/shared_examples/service_desk_issue_templates_examples.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'issue description templates from current project only' do
+ it 'loads issue description templates from the project only' do
+ within('#service-desk-template-select') do
+ expect(page).to have_content('project-issue-bar')
+ expect(page).to have_content('project-issue-foo')
+ expect(page).not_to have_content('group-issue-bar')
+ expect(page).not_to have_content('group-issue-foo')
+ end
+ end
+end
diff --git a/spec/support/shared_examples/services/boards/update_boards_shared_examples.rb b/spec/support/shared_examples/services/boards/update_boards_shared_examples.rb
new file mode 100644
index 00000000000..cd773a2a04a
--- /dev/null
+++ b/spec/support/shared_examples/services/boards/update_boards_shared_examples.rb
@@ -0,0 +1,83 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'board update service' do
+ subject(:service) { described_class.new(board.resource_parent, user, all_params) }
+
+ it 'updates the board with valid params' do
+ result = described_class.new(group, user, name: 'Engineering').execute(board)
+
+ expect(result).to eq(true)
+ expect(board.reload.name).to eq('Engineering')
+ end
+
+ it 'does not update the board with invalid params' do
+ orig_name = board.name
+
+ result = described_class.new(group, user, name: nil).execute(board)
+
+ expect(result).to eq(false)
+ expect(board.reload.name).to eq(orig_name)
+ end
+
+ context 'with scoped_issue_board available' do
+ before do
+ stub_licensed_features(scoped_issue_board: true)
+ end
+
+ context 'user is member of the board parent' do
+ before do
+ board.resource_parent.add_reporter(user)
+ end
+
+ it 'updates the configuration params when scoped issue board is enabled' do
+ service.execute(board)
+
+ labels = updated_scoped_params.delete(:labels)
+ expect(board.reload).to have_attributes(updated_scoped_params)
+ expect(board.labels).to match_array(labels)
+ end
+ end
+
+ context 'when labels param is used' do
+ let(:params) { { labels: [label.name, parent_label.name, 'new label'].join(',') } }
+
+ subject(:service) { described_class.new(board.resource_parent, user, params) }
+
+ context 'when user can create new labels' do
+ before do
+ board.resource_parent.add_reporter(user)
+ end
+
+ it 'adds labels to the board' do
+ service.execute(board)
+
+ expect(board.reload.labels.map(&:name)).to match_array([label.name, parent_label.name, 'new label'])
+ end
+ end
+
+ context 'when user can not create new labels' do
+ before do
+ board.resource_parent.add_guest(user)
+ end
+
+ it 'adds only existing labels to the board' do
+ service.execute(board)
+
+ expect(board.reload.labels.map(&:name)).to match_array([label.name, parent_label.name])
+ end
+ end
+ end
+ end
+
+ context 'without scoped_issue_board available' do
+ before do
+ stub_licensed_features(scoped_issue_board: false)
+ end
+
+ it 'filters unpermitted params when scoped issue board is not enabled' do
+ service.execute(board)
+
+ expect(board.reload).to have_attributes(updated_without_scoped_params)
+ end
+ end
+end
diff --git a/spec/support/shared_examples/services/packages/maven/metadata_shared_examples.rb b/spec/support/shared_examples/services/packages/maven/metadata_shared_examples.rb
new file mode 100644
index 00000000000..4de672bb732
--- /dev/null
+++ b/spec/support/shared_examples/services/packages/maven/metadata_shared_examples.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'handling metadata content pointing to a file for the create xml service' do
+ context 'with metadata content pointing to a file' do
+ let(:service) { described_class.new(metadata_content: file, package: package) }
+ let(:file) do
+ Tempfile.new('metadata').tap do |file|
+ if file_contents
+ file.write(file_contents)
+ file.flush
+ file.rewind
+ end
+ end
+ end
+
+ after do
+ file.close
+ file.unlink
+ end
+
+ context 'with valid content' do
+ let(:file_contents) { metadata_xml }
+
+ it 'returns no changes' do
+ expect(subject).to be_success
+ expect(subject.payload).to eq(changes_exist: false, empty_versions: false)
+ end
+ end
+
+ context 'with invalid content' do
+ let(:file_contents) { ' ' }
+
+ it_behaves_like 'returning an error service response', message: 'metadata_content is invalid'
+ end
+
+ context 'with no content' do
+ let(:file_contents) { nil }
+
+ it_behaves_like 'returning an error service response', message: 'metadata_content is invalid'
+ end
+ end
+end
+
+RSpec.shared_examples 'handling invalid parameters for create xml service' do
+ context 'with no package' do
+ let(:metadata_xml) { '' }
+ let(:package) { nil }
+
+ it_behaves_like 'returning an error service response', message: 'package not set'
+ end
+
+ context 'with no metadata content' do
+ let(:metadata_xml) { nil }
+
+ it_behaves_like 'returning an error service response', message: 'metadata_content not set'
+ end
+end
--
cgit v1.2.3