diff options
Diffstat (limited to 'spec/support/shared_examples')
99 files changed, 1785 insertions, 154 deletions
diff --git a/spec/support/shared_examples/application_setting_examples.rb b/spec/support/shared_examples/application_setting_examples.rb index e7ec24c5b7e..a43d2a75082 100644 --- a/spec/support/shared_examples/application_setting_examples.rb +++ b/spec/support/shared_examples/application_setting_examples.rb @@ -1,62 +1,150 @@ # frozen_string_literal: true +RSpec.shared_examples 'string of domains' do |attribute| + it 'sets single domain' do + setting.method("#{attribute}_raw=").call('example.com') + expect(setting.method(attribute).call).to eq(['example.com']) + end + + it 'sets multiple domains with spaces' do + setting.method("#{attribute}_raw=").call('example.com *.example.com') + expect(setting.method(attribute).call).to eq(['example.com', '*.example.com']) + end + + it 'sets multiple domains with newlines and a space' do + setting.method("#{attribute}_raw=").call("example.com\n *.example.com") + expect(setting.method(attribute).call).to eq(['example.com', '*.example.com']) + end + + it 'sets multiple domains with commas' do + setting.method("#{attribute}_raw=").call("example.com, *.example.com") + expect(setting.method(attribute).call).to eq(['example.com', '*.example.com']) + end + + it 'sets multiple domains with semicolon' do + setting.method("#{attribute}_raw=").call("example.com; *.example.com") + expect(setting.method(attribute).call).to contain_exactly('example.com', '*.example.com') + end + + it 'sets multiple domains with mixture of everything' do + setting.method("#{attribute}_raw=").call("example.com; *.example.com\n test.com\sblock.com yes.com") + expect(setting.method(attribute).call).to contain_exactly('example.com', '*.example.com', 'test.com', 'block.com', 'yes.com') + end + + it 'removes duplicates' do + setting.method("#{attribute}_raw=").call("example.com; example.com; 127.0.0.1; 127.0.0.1") + expect(setting.method(attribute).call).to contain_exactly('example.com', '127.0.0.1') + end + + it 'does not fail with garbage values' do + setting.method("#{attribute}_raw=").call("example;34543:garbage:fdh5654;") + expect(setting.method(attribute).call).to contain_exactly('example', '34543:garbage:fdh5654') + end + + it 'does not raise error with nil' do + setting.method("#{attribute}_raw=").call(nil) + expect(setting.method(attribute).call).to eq([]) + end +end + RSpec.shared_examples 'application settings examples' do context 'restricted signup domains' do - it 'sets single domain' do - setting.domain_whitelist_raw = 'example.com' - expect(setting.domain_whitelist).to eq(['example.com']) - end + it_behaves_like 'string of domains', :domain_whitelist + end - it 'sets multiple domains with spaces' do - setting.domain_whitelist_raw = 'example.com *.example.com' - expect(setting.domain_whitelist).to eq(['example.com', '*.example.com']) - end + context 'blacklisted signup domains' do + it_behaves_like 'string of domains', :domain_blacklist - it 'sets multiple domains with newlines and a space' do - setting.domain_whitelist_raw = "example.com\n *.example.com" - expect(setting.domain_whitelist).to eq(['example.com', '*.example.com']) + it 'sets multiple domain with file' do + setting.domain_blacklist_file = File.open(Rails.root.join('spec/fixtures/', 'domain_blacklist.txt')) + expect(setting.domain_blacklist).to contain_exactly('example.com', 'test.com', 'foo.bar') end + end + + context 'outbound_local_requests_whitelist' do + it_behaves_like 'string of domains', :outbound_local_requests_whitelist - it 'sets multiple domains with commas' do - setting.domain_whitelist_raw = "example.com, *.example.com" - expect(setting.domain_whitelist).to eq(['example.com', '*.example.com']) + it 'clears outbound_local_requests_whitelist_arrays memoization' do + setting.outbound_local_requests_whitelist_raw = 'example.com' + + expect(setting.outbound_local_requests_whitelist_arrays).to contain_exactly( + [], ['example.com'] + ) + + setting.outbound_local_requests_whitelist_raw = 'gitlab.com' + expect(setting.outbound_local_requests_whitelist_arrays).to contain_exactly( + [], ['gitlab.com'] + ) end end - context 'blacklisted signup domains' do - it 'sets single domain' do - setting.domain_blacklist_raw = 'example.com' - expect(setting.domain_blacklist).to contain_exactly('example.com') + context 'outbound_local_requests_whitelist_arrays' do + it 'separates the IPs and domains' do + setting.outbound_local_requests_whitelist = [ + '192.168.1.1', '127.0.0.0/28', 'www.example.com', 'example.com', + '::ffff:a00:2', '1:0:0:0:0:0:0:0/124', 'subdomain.example.com' + ] + + ip_whitelist = [ + IPAddr.new('192.168.1.1'), IPAddr.new('127.0.0.0/8'), + IPAddr.new('::ffff:a00:2'), IPAddr.new('1:0:0:0:0:0:0:0/124') + ] + domain_whitelist = ['www.example.com', 'example.com', 'subdomain.example.com'] + + expect(setting.outbound_local_requests_whitelist_arrays).to contain_exactly( + ip_whitelist, domain_whitelist + ) end + end - it 'sets multiple domains with spaces' do - setting.domain_blacklist_raw = 'example.com *.example.com' - expect(setting.domain_blacklist).to contain_exactly('example.com', '*.example.com') - end + context 'add_to_outbound_local_requests_whitelist' do + it 'adds entry to outbound_local_requests_whitelist' do + setting.outbound_local_requests_whitelist = ['example.com'] - it 'sets multiple domains with newlines and a space' do - setting.domain_blacklist_raw = "example.com\n *.example.com" - expect(setting.domain_blacklist).to contain_exactly('example.com', '*.example.com') - end + setting.add_to_outbound_local_requests_whitelist( + ['example.com', '127.0.0.1', 'gitlab.com'] + ) - it 'sets multiple domains with commas' do - setting.domain_blacklist_raw = "example.com, *.example.com" - expect(setting.domain_blacklist).to contain_exactly('example.com', '*.example.com') + expect(setting.outbound_local_requests_whitelist).to contain_exactly( + 'example.com', + '127.0.0.1', + 'gitlab.com' + ) end - it 'sets multiple domains with semicolon' do - setting.domain_blacklist_raw = "example.com; *.example.com" - expect(setting.domain_blacklist).to contain_exactly('example.com', '*.example.com') + it 'clears outbound_local_requests_whitelist_arrays memoization' do + setting.outbound_local_requests_whitelist = ['example.com'] + + expect(setting.outbound_local_requests_whitelist_arrays).to contain_exactly( + [], + ['example.com'] + ) + + setting.add_to_outbound_local_requests_whitelist( + ['example.com', 'gitlab.com'] + ) + + expect(setting.outbound_local_requests_whitelist_arrays).to contain_exactly( + [], + ['example.com', 'gitlab.com'] + ) end - it 'sets multiple domains with mixture of everything' do - setting.domain_blacklist_raw = "example.com; *.example.com\n test.com\sblock.com yes.com" - expect(setting.domain_blacklist).to contain_exactly('example.com', '*.example.com', 'test.com', 'block.com', 'yes.com') + it 'does not raise error with nil' do + setting.outbound_local_requests_whitelist = nil + + setting.add_to_outbound_local_requests_whitelist(['gitlab.com']) + + expect(setting.outbound_local_requests_whitelist).to contain_exactly('gitlab.com') + expect(setting.outbound_local_requests_whitelist_arrays).to contain_exactly( + [], ['gitlab.com'] + ) end - it 'sets multiple domain with file' do - setting.domain_blacklist_file = File.open(Rails.root.join('spec/fixtures/', 'domain_blacklist.txt')) - expect(setting.domain_blacklist).to contain_exactly('example.com', 'test.com', 'foo.bar') + it 'does not raise error with nil' do + setting.outbound_local_requests_whitelist = nil + + expect(setting.outbound_local_requests_whitelist_arrays).to contain_exactly([], []) end end diff --git a/spec/support/shared_examples/award_emoji_todo_shared_examples.rb b/spec/support/shared_examples/award_emoji_todo_shared_examples.rb new file mode 100644 index 00000000000..88ad37d232f --- /dev/null +++ b/spec/support/shared_examples/award_emoji_todo_shared_examples.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +# Shared examples to that test code that creates AwardEmoji also mark Todos +# as done. +# +# The examples expect these to be defined in the calling spec: +# - `subject` the callable code that executes the creation of an AwardEmoji +# - `user` +# - `project` +RSpec.shared_examples 'creating award emojis marks Todos as done' do + using RSpec::Parameterized::TableSyntax + + before do + project.add_developer(user) + end + + where(:type, :expectation) do + :issue | true + :merge_request | true + :project_snippet | false + end + + with_them do + let(:project) { awardable.project } + let(:awardable) { create(type) } + let!(:todo) { create(:todo, target: awardable, project: project, user: user) } + + it do + subject + + expect(todo.reload.done?).to eq(expectation) + end + end + + # Notes have more complicated rules than other Todoables + describe 'for notes' do + let!(:todo) { create(:todo, target: awardable.noteable, project: project, user: user) } + + context 'regular Notes' do + let(:awardable) { create(:note, project: project) } + + it 'marks the Todo as done' do + subject + + expect(todo.reload.done?).to eq(true) + end + end + + context 'PersonalSnippet Notes' do + let(:awardable) { create(:note, noteable: create(:personal_snippet, author: user)) } + + it 'does not mark the Todo as done' do + subject + + expect(todo.reload.done?).to eq(false) + end + end + 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 new file mode 100644 index 00000000000..76d82649c5f --- /dev/null +++ b/spec/support/shared_examples/boards/multiple_issue_boards_shared_examples.rb @@ -0,0 +1,144 @@ +# frozen_string_literal: true + +shared_examples_for 'multiple issue boards' do + dropdown_selector = '.js-boards-selector .dropdown-menu' + + context 'authorized user' do + before do + parent.add_maintainer(user) + + login_as(user) + + visit boards_path + wait_for_requests + end + + it 'shows current board name' do + page.within('.boards-switcher') do + expect(page).to have_content(board.name) + end + end + + it 'shows a list of boards' do + click_button board.name + + page.within(dropdown_selector) do + expect(page).to have_content(board.name) + expect(page).to have_content(board2.name) + end + end + + it 'switches current board' do + click_button board.name + + page.within(dropdown_selector) do + click_link board2.name + end + + wait_for_requests + + page.within('.boards-switcher') do + expect(page).to have_content(board2.name) + end + end + + it 'creates new board without detailed configuration' do + click_button board.name + + page.within(dropdown_selector) do + click_button 'Create new board' + end + + fill_in 'board-new-name', with: 'This is a new board' + click_button 'Create board' + wait_for_requests + + expect(page).to have_button('This is a new board') + end + + it 'deletes board' do + click_button board.name + + wait_for_requests + + page.within(dropdown_selector) do + click_button 'Delete board' + end + + expect(page).to have_content('Are you sure you want to delete this board?') + click_button 'Delete' + + click_button board2.name + page.within(dropdown_selector) do + expect(page).not_to have_content(board.name) + expect(page).to have_content(board2.name) + end + end + + it 'adds a list to the none default board' do + click_button board.name + + page.within(dropdown_selector) do + click_link board2.name + end + + wait_for_requests + + page.within('.boards-switcher') do + expect(page).to have_content(board2.name) + end + + click_button 'Add list' + + wait_for_requests + + page.within '.dropdown-menu-issues-board-new' do + click_link planning.title + end + + wait_for_requests + + expect(page).to have_selector('.board', count: 3) + + click_button board2.name + + page.within(dropdown_selector) do + click_link board.name + end + + wait_for_requests + + expect(page).to have_selector('.board', count: 2) + end + + it 'maintains sidebar state over board switch' do + assert_boards_nav_active + + find('.boards-switcher').click + wait_for_requests + click_link board2.name + + assert_boards_nav_active + end + end + + context 'unauthorized user' do + before do + visit boards_path + wait_for_requests + end + + it 'does not show action links' do + click_button board.name + + page.within(dropdown_selector) do + expect(page).not_to have_content('Create new board') + expect(page).not_to have_content('Delete board') + end + end + end + + def assert_boards_nav_active + expect(find('.nav-sidebar .active .active')).to have_selector('a', text: 'Boards') + end +end diff --git a/spec/support/shared_examples/chat_slash_commands_shared_examples.rb b/spec/support/shared_examples/chat_slash_commands_shared_examples.rb index dc97a39f051..dcc92dda950 100644 --- a/spec/support/shared_examples/chat_slash_commands_shared_examples.rb +++ b/spec/support/shared_examples/chat_slash_commands_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + RSpec.shared_examples 'chat slash commands service' do describe "Associations" do it { is_expected.to respond_to :token } @@ -91,6 +93,19 @@ RSpec.shared_examples 'chat slash commands service' do subject.trigger(params) end + + context 'when user is blocked' do + before do + chat_name.user.block + end + + it 'blocks command execution' do + expect_any_instance_of(Gitlab::SlashCommands::Command).not_to receive(:execute) + + result = subject.trigger(params) + expect(result).to include(text: /^Whoops! This action is not allowed/) + end + end end end end diff --git a/spec/support/shared_examples/ci_trace_shared_examples.rb b/spec/support/shared_examples/ci_trace_shared_examples.rb index ab0550e2613..e2b4b50d41d 100644 --- a/spec/support/shared_examples/ci_trace_shared_examples.rb +++ b/spec/support/shared_examples/ci_trace_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples_for 'common trace features' do describe '#html' do before do @@ -5,11 +7,11 @@ shared_examples_for 'common trace features' do end it "returns formatted html" do - expect(trace.html).to eq("<span class=\"\">12<br/><span class=\"\">34</span></span>") + expect(trace.html).to eq("<span>12<br/>34</span>") end it "returns last line of formatted html" do - expect(trace.html(last_lines: 1)).to eq("<span class=\"\">34</span>") + expect(trace.html(last_lines: 1)).to eq("<span>34</span>") end end @@ -720,6 +722,58 @@ shared_examples_for 'trace with enabled live trace feature' do end end + describe '#archived_trace_exist?' do + subject { trace.archived_trace_exist? } + + context 'when trace does not exist' do + it { is_expected.to be_falsy } + end + + context 'when archived trace exists' do + before do + create(:ci_job_artifact, :trace, job: build) + end + + it { is_expected.to be_truthy } + end + + context 'when live trace exists' do + before do + Gitlab::Ci::Trace::ChunkedIO.new(build) do |stream| + stream.write('abc') + end + end + + it { is_expected.to be_falsy } + end + end + + describe '#live_trace_exist?' do + subject { trace.live_trace_exist? } + + context 'when trace does not exist' do + it { is_expected.to be_falsy } + end + + context 'when archived trace exists' do + before do + create(:ci_job_artifact, :trace, job: build) + end + + it { is_expected.to be_falsy } + end + + context 'when live trace exists' do + before do + Gitlab::Ci::Trace::ChunkedIO.new(build) do |stream| + stream.write('abc') + end + end + + it { is_expected.to be_truthy } + end + end + describe '#archive!' do subject { trace.archive! } diff --git a/spec/support/shared_examples/common_system_notes_examples.rb b/spec/support/shared_examples/common_system_notes_examples.rb index da5a4f3e319..75f93a32d78 100644 --- a/spec/support/shared_examples/common_system_notes_examples.rb +++ b/spec/support/shared_examples/common_system_notes_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples 'system note creation' do |update_params, note_text| subject { described_class.new(project, user).execute(issuable, old_labels: []) } diff --git a/spec/support/shared_examples/container_repositories_shared_examples.rb b/spec/support/shared_examples/container_repositories_shared_examples.rb new file mode 100644 index 00000000000..946b130fca2 --- /dev/null +++ b/spec/support/shared_examples/container_repositories_shared_examples.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +shared_examples 'rejected container repository access' do |user_type, status| + context "for #{user_type}" do + let(:api_user) { users[user_type] } + + it "returns #{status}" do + subject + + expect(response).to have_gitlab_http_status(status) + end + end +end + +shared_examples 'returns repositories for allowed users' do |user_type, scope| + context "for #{user_type}" do + it 'returns a list of repositories' do + subject + + expect(json_response.length).to eq(2) + expect(json_response.map { |repository| repository['id'] }).to contain_exactly( + root_repository.id, test_repository.id) + expect(response.body).not_to include('tags') + end + + it 'returns a matching schema' do + subject + + expect(response).to have_gitlab_http_status(:ok) + expect(response).to match_response_schema('registry/repositories') + end + + context 'with tags param' do + let(:url) { "/#{scope}s/#{object.id}/registry/repositories?tags=true" } + + before do + stub_container_registry_tags(repository: root_repository.path, tags: %w(rootA latest), with_manifest: true) + stub_container_registry_tags(repository: test_repository.path, tags: %w(rootA latest), with_manifest: true) + end + + it 'returns a list of repositories and their tags' do + subject + + expect(json_response.length).to eq(2) + expect(json_response.map { |repository| repository['id'] }).to contain_exactly( + root_repository.id, test_repository.id) + expect(response.body).to include('tags') + end + + it 'returns a matching schema' do + subject + + expect(response).to have_gitlab_http_status(:ok) + expect(response).to match_response_schema('registry/repositories') + end + end + end +end diff --git a/spec/support/shared_examples/controllers/external_authorization_service_shared_examples.rb b/spec/support/shared_examples/controllers/external_authorization_service_shared_examples.rb index 8dd78fd0a25..d8a1ae83f61 100644 --- a/spec/support/shared_examples/controllers/external_authorization_service_shared_examples.rb +++ b/spec/support/shared_examples/controllers/external_authorization_service_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' shared_examples 'disabled when using an external authorization service' do @@ -6,7 +8,7 @@ shared_examples 'disabled when using an external authorization service' do it 'works when the feature is not enabled' do subject - expect(response).to be_success + expect(response).to be_successful end it 'renders a 404 with a message when the feature is enabled' do diff --git a/spec/support/shared_examples/controllers/issuable_notes_filter_shared_examples.rb b/spec/support/shared_examples/controllers/issuable_notes_filter_shared_examples.rb index 0acc9e2a836..26ed86bfe26 100644 --- a/spec/support/shared_examples/controllers/issuable_notes_filter_shared_examples.rb +++ b/spec/support/shared_examples/controllers/issuable_notes_filter_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples 'issuable notes filter' do let(:params) do if issuable_parent.is_a?(Project) @@ -39,14 +41,22 @@ shared_examples 'issuable notes filter' do get :discussions, params: params.merge(notes_filter: notes_filter) - expect(user.reload.notes_filter_for(issuable)).to eq(0) + expect(user.reload.notes_filter_for(issuable)).to eq(UserPreference::NOTES_FILTERS[:all_notes]) + end + + it 'does not set notes filter when persist_filter param is false' do + notes_filter = UserPreference::NOTES_FILTERS[:only_comments] + + get :discussions, params: params.merge(notes_filter: notes_filter, persist_filter: false) + + expect(user.reload.notes_filter_for(issuable)).to eq(UserPreference::NOTES_FILTERS[:all_notes]) end it 'returns only user comments' do user.set_notes_filter(UserPreference::NOTES_FILTERS[:only_comments], issuable) get :discussions, params: params - discussions = JSON.parse(response.body) + discussions = json_response expect(discussions.count).to eq(1) expect(discussions.first["notes"].first["system"]).to be(false) @@ -56,7 +66,7 @@ shared_examples 'issuable notes filter' do user.set_notes_filter(UserPreference::NOTES_FILTERS[:only_activity], issuable) get :discussions, params: params - discussions = JSON.parse(response.body) + discussions = json_response expect(discussions.count).to eq(1) expect(discussions.first["notes"].first["system"]).to be(true) diff --git a/spec/support/shared_examples/controllers/set_sort_order_from_user_preference_shared_examples.rb b/spec/support/shared_examples/controllers/set_sort_order_from_user_preference_shared_examples.rb index eb051166a69..d89eded6e69 100644 --- a/spec/support/shared_examples/controllers/set_sort_order_from_user_preference_shared_examples.rb +++ b/spec/support/shared_examples/controllers/set_sort_order_from_user_preference_shared_examples.rb @@ -1,13 +1,15 @@ +# frozen_string_literal: true + shared_examples 'set sort order from user preference' do describe '#set_sort_order_from_user_preference' do - # There is no issuable_sorting_field defined in any CE controllers yet, + # There is no sorting_field defined in any CE controllers yet, # however any other field present in user_preferences table can be used for testing. context 'when database is in read-only mode' do it 'does not update user preference' do allow(Gitlab::Database).to receive(:read_only?).and_return(true) - expect_any_instance_of(UserPreference).not_to receive(:update).with({ controller.send(:issuable_sorting_field) => sorting_param }) + expect_any_instance_of(UserPreference).not_to receive(:update).with({ controller.send(:sorting_field) => sorting_param }) get :index, params: { namespace_id: project.namespace, project_id: project, sort: sorting_param } end @@ -17,7 +19,7 @@ shared_examples 'set sort order from user preference' do it 'updates user preference' do allow(Gitlab::Database).to receive(:read_only?).and_return(false) - expect_any_instance_of(UserPreference).to receive(:update).with({ controller.send(:issuable_sorting_field) => sorting_param }) + expect_any_instance_of(UserPreference).to receive(:update).with({ controller.send(:sorting_field) => sorting_param }) get :index, params: { namespace_id: project.namespace, project_id: project, sort: sorting_param } end diff --git a/spec/support/shared_examples/controllers/todos_shared_examples.rb b/spec/support/shared_examples/controllers/todos_shared_examples.rb index bafd9bac8d0..f3f9abb7da2 100644 --- a/spec/support/shared_examples/controllers/todos_shared_examples.rb +++ b/spec/support/shared_examples/controllers/todos_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples 'todos actions' do context 'when authorized' do before do diff --git a/spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb b/spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb index 59708173716..39d13cccb13 100644 --- a/spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb +++ b/spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples 'handle uploads' do let(:user) { create(:user) } let(:jpg) { fixture_file_upload('spec/fixtures/rails_sample.jpg', 'image/jpg') } @@ -74,6 +76,16 @@ shared_examples 'handle uploads' do UploadService.new(model, jpg, uploader_class).execute end + context 'when accessing a specific upload via different model' do + it 'responds with status 404' do + params.merge!(other_params) + + show_upload + + expect(response).to have_gitlab_http_status(404) + end + end + context "when the model is public" do before do model.update_attribute(:visibility_level, Gitlab::VisibilityLevel::PUBLIC) diff --git a/spec/support/shared_examples/controllers/variables_shared_examples.rb b/spec/support/shared_examples/controllers/variables_shared_examples.rb index e80722857ec..78666e677ef 100644 --- a/spec/support/shared_examples/controllers/variables_shared_examples.rb +++ b/spec/support/shared_examples/controllers/variables_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples 'GET #show lists all variables' do it 'renders the variables as json' do subject diff --git a/spec/support/shared_examples/cycle_analytics_stage_examples.rb b/spec/support/shared_examples/cycle_analytics_stage_examples.rb new file mode 100644 index 00000000000..151f5325e84 --- /dev/null +++ b/spec/support/shared_examples/cycle_analytics_stage_examples.rb @@ -0,0 +1,74 @@ +# frozen_string_literal: true + +shared_examples_for 'cycle analytics stage' do + let(:valid_params) do + { + name: 'My Stage', + parent: parent, + start_event_identifier: :merge_request_created, + end_event_identifier: :merge_request_merged + } + end + + describe 'validation' do + it 'is valid' do + expect(described_class.new(valid_params)).to be_valid + end + + it 'validates presence of parent' do + stage = described_class.new(valid_params.except(:parent)) + + expect(stage).not_to be_valid + expect(stage.errors.details[parent_name]).to eq([{ error: :blank }]) + end + + it 'validates presence of start_event_identifier' do + stage = described_class.new(valid_params.except(:start_event_identifier)) + + expect(stage).not_to be_valid + expect(stage.errors.details[:start_event_identifier]).to eq([{ error: :blank }]) + end + + it 'validates presence of end_event_identifier' do + stage = described_class.new(valid_params.except(:end_event_identifier)) + + expect(stage).not_to be_valid + expect(stage.errors.details[:end_event_identifier]).to eq([{ error: :blank }]) + end + + it 'is invalid when end_event is not allowed for the given start_event' do + invalid_params = valid_params.merge( + start_event_identifier: :merge_request_merged, + end_event_identifier: :merge_request_created + ) + stage = described_class.new(invalid_params) + + expect(stage).not_to be_valid + expect(stage.errors.details[:end_event]).to eq([{ error: :not_allowed_for_the_given_start_event }]) + end + end + + describe '#subject_model' do + it 'infers the model from the start event' do + stage = described_class.new(valid_params) + + expect(stage.subject_model).to eq(MergeRequest) + end + end + + describe '#start_event' do + it 'builds start_event object based on start_event_identifier' do + stage = described_class.new(start_event_identifier: 'merge_request_created') + + expect(stage.start_event).to be_a_kind_of(Gitlab::Analytics::CycleAnalytics::StageEvents::MergeRequestCreated) + end + end + + describe '#end_event' do + it 'builds end_event object based on end_event_identifier' do + stage = described_class.new(end_event_identifier: 'merge_request_merged') + + expect(stage.end_event).to be_a_kind_of(Gitlab::Analytics::CycleAnalytics::StageEvents::MergeRequestMerged) + end + end +end diff --git a/spec/support/shared_examples/dirty_submit_form_shared_examples.rb b/spec/support/shared_examples/dirty_submit_form_shared_examples.rb index 4e45e2921e7..60c8899d349 100644 --- a/spec/support/shared_examples/dirty_submit_form_shared_examples.rb +++ b/spec/support/shared_examples/dirty_submit_form_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples 'dirty submit form' do |selector_args| selectors = selector_args.is_a?(Array) ? selector_args : [selector_args] diff --git a/spec/support/shared_examples/discussions_provider_shared_examples.rb b/spec/support/shared_examples/discussions_provider_shared_examples.rb new file mode 100644 index 00000000000..77cf1ac3f51 --- /dev/null +++ b/spec/support/shared_examples/discussions_provider_shared_examples.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +require 'spec_helper' + +shared_examples 'discussions provider' do + it 'returns the expected discussions' do + get :discussions, params: { namespace_id: project.namespace, project_id: project, id: requested_iid } + + expect(response).to have_gitlab_http_status(200) + expect(response).to match_response_schema('entities/discussions') + + expect(json_response.size).to eq(expected_discussion_count) + expect(json_response.pluck('id')).to eq(expected_discussion_ids) + end +end diff --git a/spec/support/shared_examples/email_format_shared_examples.rb b/spec/support/shared_examples/email_format_shared_examples.rb index b924a208e71..22d6c2b38e3 100644 --- a/spec/support/shared_examples/email_format_shared_examples.rb +++ b/spec/support/shared_examples/email_format_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Specifications for behavior common to all objects with an email attribute. # Takes a list of email-format attributes and requires: # - subject { "the object with a attribute= setter" } diff --git a/spec/support/shared_examples/fast_destroy_all.rb b/spec/support/shared_examples/fast_destroy_all.rb index a8079b6d864..a64259c03f2 100644 --- a/spec/support/shared_examples/fast_destroy_all.rb +++ b/spec/support/shared_examples/fast_destroy_all.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples_for 'fast destroyable' do describe 'Forbid #destroy and #destroy_all' do it 'does not delete database rows and associted external data' do diff --git a/spec/support/shared_examples/features/comments_on_merge_request_files_shared_examples.rb b/spec/support/shared_examples/features/comments_on_merge_request_files_shared_examples.rb index 2b36955a3c4..f24e47f4638 100644 --- a/spec/support/shared_examples/features/comments_on_merge_request_files_shared_examples.rb +++ b/spec/support/shared_examples/features/comments_on_merge_request_files_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples 'comment on merge request file' do it 'adds a comment' do click_diff_line(find("[id='#{sample_commit.line_code}']")) diff --git a/spec/support/shared_examples/features/creatable_merge_request_shared_examples.rb b/spec/support/shared_examples/features/creatable_merge_request_shared_examples.rb index ec1b1754cf0..c0db4cdde72 100644 --- a/spec/support/shared_examples/features/creatable_merge_request_shared_examples.rb +++ b/spec/support/shared_examples/features/creatable_merge_request_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + RSpec.shared_examples 'a creatable merge request' do include WaitForRequests diff --git a/spec/support/shared_examples/features/editable_merge_request_shared_examples.rb b/spec/support/shared_examples/features/editable_merge_request_shared_examples.rb index a6121fcc50a..964c80007b0 100644 --- a/spec/support/shared_examples/features/editable_merge_request_shared_examples.rb +++ b/spec/support/shared_examples/features/editable_merge_request_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + RSpec.shared_examples 'an editable merge request' do it 'updates merge request', :js do find('.js-assignee-search').click diff --git a/spec/support/shared_examples/features/issuable_sidebar_shared_examples.rb b/spec/support/shared_examples/features/issuable_sidebar_shared_examples.rb index 96c821b26f7..09a48533ee3 100644 --- a/spec/support/shared_examples/features/issuable_sidebar_shared_examples.rb +++ b/spec/support/shared_examples/features/issuable_sidebar_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples 'issue sidebar stays collapsed on mobile' do before do resize_screen_xs diff --git a/spec/support/shared_examples/features/issuables_user_dropdown_behaviors_shared_examples.rb b/spec/support/shared_examples/features/issuables_user_dropdown_behaviors_shared_examples.rb index c92c7f603d6..63ed37cde03 100644 --- a/spec/support/shared_examples/features/issuables_user_dropdown_behaviors_shared_examples.rb +++ b/spec/support/shared_examples/features/issuables_user_dropdown_behaviors_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples 'issuable user dropdown behaviors' do include FilteredSearchHelpers diff --git a/spec/support/shared_examples/features/master_manages_access_requests_shared_example.rb b/spec/support/shared_examples/features/master_manages_access_requests_shared_example.rb index d87e5fcaa88..8e1d24c4be2 100644 --- a/spec/support/shared_examples/features/master_manages_access_requests_shared_example.rb +++ b/spec/support/shared_examples/features/master_manages_access_requests_shared_example.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + RSpec.shared_examples 'Maintainer manages access requests' do let(:user) { create(:user) } let(:maintainer) { create(:user) } diff --git a/spec/support/shared_examples/features/project_features_apply_to_issuables_shared_examples.rb b/spec/support/shared_examples/features/project_features_apply_to_issuables_shared_examples.rb index 64c3b80136d..51559c0b110 100644 --- a/spec/support/shared_examples/features/project_features_apply_to_issuables_shared_examples.rb +++ b/spec/support/shared_examples/features/project_features_apply_to_issuables_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples 'project features apply to issuables' do |klass| let(:described_class) { klass } diff --git a/spec/support/shared_examples/features/protected_branches_access_control_ce.rb b/spec/support/shared_examples/features/protected_branches_access_control_ce.rb index a8f2c2e7a5a..db83d6f0793 100644 --- a/spec/support/shared_examples/features/protected_branches_access_control_ce.rb +++ b/spec/support/shared_examples/features/protected_branches_access_control_ce.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples "protected branches > access control > CE" do ProtectedRefAccess::HUMAN_ACCESS_LEVELS.each do |(access_type_id, access_type_name)| it "allows creating protected branches that #{access_type_name} can push to" do @@ -6,7 +8,7 @@ shared_examples "protected branches > access control > CE" do set_protected_branch_name('master') find(".js-allowed-to-merge").click - within('.qa-allowed-to-merge-dropdown') do + within('.rspec-allowed-to-merge-dropdown') do expect(first("li")).to have_content("Roles") find(:link, 'No one').click end @@ -32,13 +34,13 @@ shared_examples "protected branches > access control > CE" do set_protected_branch_name('master') find(".js-allowed-to-merge").click - within('.qa-allowed-to-merge-dropdown') do + within('.rspec-allowed-to-merge-dropdown') do expect(first("li")).to have_content("Roles") find(:link, 'No one').click end find(".js-allowed-to-push").click - within('.qa-allowed-to-push-dropdown') do + within('.rspec-allowed-to-push-dropdown') do expect(first("li")).to have_content("Roles") find(:link, 'No one').click end @@ -78,7 +80,7 @@ shared_examples "protected branches > access control > CE" do end find(".js-allowed-to-push").click - within('.qa-allowed-to-push-dropdown') do + within('.rspec-allowed-to-push-dropdown') do expect(first("li")).to have_content("Roles") find(:link, 'No one').click end @@ -95,13 +97,13 @@ shared_examples "protected branches > access control > CE" do set_protected_branch_name('master') find(".js-allowed-to-merge").click - within('.qa-allowed-to-merge-dropdown') do + within('.rspec-allowed-to-merge-dropdown') do expect(first("li")).to have_content("Roles") find(:link, 'No one').click end find(".js-allowed-to-push").click - within('.qa-allowed-to-push-dropdown') do + within('.rspec-allowed-to-push-dropdown') do expect(first("li")).to have_content("Roles") find(:link, 'No one').click end diff --git a/spec/support/shared_examples/features/search_shared_examples.rb b/spec/support/shared_examples/features/search_shared_examples.rb index 25ebbf011d5..e27d6700cbf 100644 --- a/spec/support/shared_examples/features/search_shared_examples.rb +++ b/spec/support/shared_examples/features/search_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples 'top right search form' do it 'does not show top right search form' do expect(page).not_to have_selector('.search') diff --git a/spec/support/shared_examples/file_finder.rb b/spec/support/shared_examples/file_finder.rb index 0dc351b5149..984a06ccd1a 100644 --- a/spec/support/shared_examples/file_finder.rb +++ b/spec/support/shared_examples/file_finder.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples 'file finder' do let(:query) { 'files' } let(:search_results) { subject.find(query) } diff --git a/spec/support/shared_examples/finders/finder_with_external_authorization_enabled.rb b/spec/support/shared_examples/finders/finder_with_external_authorization_enabled.rb index d7e17cc0b70..b8b0079e36d 100644 --- a/spec/support/shared_examples/finders/finder_with_external_authorization_enabled.rb +++ b/spec/support/shared_examples/finders/finder_with_external_authorization_enabled.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' shared_examples 'a finder with external authorization service' do diff --git a/spec/support/shared_examples/gitlab_verify.rb b/spec/support/shared_examples/gitlab_verify.rb index 560913ca92f..721ea3b4c88 100644 --- a/spec/support/shared_examples/gitlab_verify.rb +++ b/spec/support/shared_examples/gitlab_verify.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + RSpec.shared_examples 'Gitlab::Verify::BatchVerifier subclass' do describe 'batching' do let(:first_batch) { objects[0].id..objects[0].id } diff --git a/spec/support/shared_examples/graphql/issuable_state_shared_examples.rb b/spec/support/shared_examples/graphql/issuable_state_shared_examples.rb index 713f0a879c1..145c476c7f7 100644 --- a/spec/support/shared_examples/graphql/issuable_state_shared_examples.rb +++ b/spec/support/shared_examples/graphql/issuable_state_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + RSpec.shared_examples 'issuable state' do it 'exposes all the existing issuable states' do expect(described_class.values.keys).to include(*%w[opened closed locked]) diff --git a/spec/support/shared_examples/group_members_shared_example.rb b/spec/support/shared_examples/group_members_shared_example.rb index 547c83c7955..4f7d496741d 100644 --- a/spec/support/shared_examples/group_members_shared_example.rb +++ b/spec/support/shared_examples/group_members_shared_example.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + RSpec.shared_examples 'members and requesters associations' do describe '#members_and_requesters' do it 'includes members and requesters' do diff --git a/spec/support/shared_examples/helm_generated_script.rb b/spec/support/shared_examples/helm_generated_script.rb index 01bee603274..17f495ebe46 100644 --- a/spec/support/shared_examples/helm_generated_script.rb +++ b/spec/support/shared_examples/helm_generated_script.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples 'helm commands' do describe '#generate_script' do let(:helm_setup) do diff --git a/spec/support/shared_examples/issuable_shared_examples.rb b/spec/support/shared_examples/issuable_shared_examples.rb index d97b21f71cd..3460a8ba297 100644 --- a/spec/support/shared_examples/issuable_shared_examples.rb +++ b/spec/support/shared_examples/issuable_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples 'cache counters invalidator' do it 'invalidates counter cache for assignees' do expect_any_instance_of(User).to receive(:invalidate_merge_request_cache_counts) diff --git a/spec/support/shared_examples/issuables_list_metadata_shared_examples.rb b/spec/support/shared_examples/issuables_list_metadata_shared_examples.rb index 244f4766a84..52d90b5f183 100644 --- a/spec/support/shared_examples/issuables_list_metadata_shared_examples.rb +++ b/spec/support/shared_examples/issuables_list_metadata_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples 'issuables list meta-data' do |issuable_type, action = nil| include ProjectForksHelper diff --git a/spec/support/shared_examples/issue_tracker_service_shared_example.rb b/spec/support/shared_examples/issue_tracker_service_shared_example.rb index a6ab03cb808..0a483fd30ba 100644 --- a/spec/support/shared_examples/issue_tracker_service_shared_example.rb +++ b/spec/support/shared_examples/issue_tracker_service_shared_example.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + RSpec.shared_examples 'issue tracker service URL attribute' do |url_attr| it { is_expected.to allow_value('https://example.com').for(url_attr) } diff --git a/spec/support/shared_examples/ldap_shared_examples.rb b/spec/support/shared_examples/ldap_shared_examples.rb index 52c34e78965..0a70ce7ea0c 100644 --- a/spec/support/shared_examples/ldap_shared_examples.rb +++ b/spec/support/shared_examples/ldap_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples_for 'normalizes a DN' do using RSpec::Parameterized::TableSyntax diff --git a/spec/support/shared_examples/legacy_path_redirect_shared_examples.rb b/spec/support/shared_examples/legacy_path_redirect_shared_examples.rb index f326e502092..22e5698825d 100644 --- a/spec/support/shared_examples/legacy_path_redirect_shared_examples.rb +++ b/spec/support/shared_examples/legacy_path_redirect_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples 'redirecting a legacy path' do |source, target| include RSpec::Rails::RequestExampleGroup diff --git a/spec/support/shared_examples/lib/gitlab/background_migration/backfill_project_repositories_examples.rb b/spec/support/shared_examples/lib/gitlab/background_migration/backfill_project_repositories_examples.rb index dcf7c1a90c2..2cbc0c2bdf2 100644 --- a/spec/support/shared_examples/lib/gitlab/background_migration/backfill_project_repositories_examples.rb +++ b/spec/support/shared_examples/lib/gitlab/background_migration/backfill_project_repositories_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples 'backfill migration for project repositories' do |storage| describe '#perform' do let(:storage_versions) { storage == :legacy ? [nil, 0] : [1, 2] } diff --git a/spec/support/shared_examples/lib/gitlab/usage_data_counters/a_redis_counter.rb b/spec/support/shared_examples/lib/gitlab/usage_data_counters/a_redis_counter.rb new file mode 100644 index 00000000000..91bf804978d --- /dev/null +++ b/spec/support/shared_examples/lib/gitlab/usage_data_counters/a_redis_counter.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +shared_examples 'a redis usage counter' do |thing, event| + describe ".count(#{event})", :clean_gitlab_redis_shared_state do + it "increments the #{thing} #{event} counter by 1" do + expect do + described_class.count(event) + end.to change { described_class.read(event) }.by 1 + end + end + + describe ".read(#{event})", :clean_gitlab_redis_shared_state do + event_count = 5 + + it "returns the total number of #{event} events" do + event_count.times do + described_class.count(event) + end + + expect(described_class.read(event)).to eq(event_count) + end + end +end + +shared_examples 'a redis usage counter with totals' do |prefix, events| + describe 'totals', :clean_gitlab_redis_shared_state do + before do + events.each do |k, n| + n.times do + described_class.count(k) + end + end + end + + let(:expected_totals) do + events.transform_keys { |k| "#{prefix}_#{k}".to_sym } + end + + it 'can report all totals' do + expect(described_class.totals).to include(expected_totals) + end + end + + # Override these let-bindings to adjust the unknown events tests + let(:unknown_event) { described_class::UnknownEvent } + let(:bad_event) { :wibble } + + describe 'unknown events' do + it 'cannot increment' do + expect { described_class.count(bad_event) }.to raise_error unknown_event + end + + it 'cannot read' do + expect { described_class.read(bad_event) }.to raise_error unknown_event + end + end +end diff --git a/spec/support/shared_examples/malicious_regexp_shared_examples.rb b/spec/support/shared_examples/malicious_regexp_shared_examples.rb index a86050e2cf2..96c02260d53 100644 --- a/spec/support/shared_examples/malicious_regexp_shared_examples.rb +++ b/spec/support/shared_examples/malicious_regexp_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'timeout' shared_examples 'malicious regexp' do diff --git a/spec/support/shared_examples/mentionable_shared_examples.rb b/spec/support/shared_examples/mentionable_shared_examples.rb index 1226841f24c..93a8c4709a6 100644 --- a/spec/support/shared_examples/mentionable_shared_examples.rb +++ b/spec/support/shared_examples/mentionable_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Specifications for behavior common to all Mentionable implementations. # Requires a shared context containing: # - subject { "the mentionable implementation" } @@ -76,6 +78,30 @@ shared_examples 'a mentionable' do expect(refs).to include(ext_commit) end + context 'when there are cached markdown fields' do + before do + if subject.is_a?(CacheMarkdownField) + subject.refresh_markdown_cache + end + end + + it 'sends in cached markdown fields when appropriate' do + if subject.is_a?(CacheMarkdownField) + expect_next_instance_of(Gitlab::ReferenceExtractor) do |ext| + attrs = subject.class.mentionable_attrs.collect(&:first) & subject.cached_markdown_fields.markdown_fields + attrs.each do |field| + expect(ext).to receive(:analyze).with(subject.send(field), hash_including(rendered: anything)) + end + end + + expect(subject).not_to receive(:refresh_markdown_cache) + expect(subject).to receive(:cached_markdown_fields).at_least(:once).and_call_original + + subject.all_references(author) + end + end + end + it 'creates cross-reference notes' do mentioned_objects = [mentioned_issue, mentioned_mr, mentioned_commit, ext_issue, ext_mr, ext_commit] @@ -98,6 +124,33 @@ shared_examples 'an editable mentionable' do [create(:issue, project: project), create(:issue, project: ext_proj)] end + context 'when there are cached markdown fields' do + before do + if subject.is_a?(CacheMarkdownField) + subject.refresh_markdown_cache + end + end + + it 'refreshes markdown cache if necessary' do + subject.save! + + set_mentionable_text.call('This is a text') + + if subject.is_a?(CacheMarkdownField) + expect_next_instance_of(Gitlab::ReferenceExtractor) do |ext| + subject.cached_markdown_fields.markdown_fields.each do |field| + expect(ext).to receive(:analyze).with(subject.send(field), hash_including(rendered: anything)) + end + end + + expect(subject).to receive(:refresh_markdown_cache) + expect(subject).to receive(:cached_markdown_fields).at_least(:once).and_call_original + + subject.all_references(author) + end + end + end + it 'creates new cross-reference notes when the mentionable text is edited' do subject.save subject.create_cross_references! diff --git a/spec/support/shared_examples/milestone_tabs_examples.rb b/spec/support/shared_examples/milestone_tabs_examples.rb index 8b757586941..bda4b978737 100644 --- a/spec/support/shared_examples/milestone_tabs_examples.rb +++ b/spec/support/shared_examples/milestone_tabs_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples 'milestone tabs' do def go(path, extra_params = {}) params = diff --git a/spec/support/shared_examples/models/atomic_internal_id_shared_examples.rb b/spec/support/shared_examples/models/atomic_internal_id_shared_examples.rb index a248f60d23e..b837ca87256 100644 --- a/spec/support/shared_examples/models/atomic_internal_id_shared_examples.rb +++ b/spec/support/shared_examples/models/atomic_internal_id_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' shared_examples_for 'AtomicInternalId' do |validate_presence: true| 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 0a302e7d030..b6a3d50d14a 100644 --- a/spec/support/shared_examples/models/chat_service_shared_examples.rb +++ b/spec/support/shared_examples/models/chat_service_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "spec_helper" shared_examples_for "chat service" do |service_name| @@ -220,7 +222,8 @@ shared_examples_for "chat service" do |service_name| context "with not default branch" do let(:pipeline) do - create(:ci_pipeline, project: project, status: "failed", ref: "not-the-default-branch") + create(:ci_pipeline, :failed, project: project, + sha: project.commit.sha, ref: "not-the-default-branch") end context "when notify_only_default_branch enabled" do diff --git a/spec/support/shared_examples/models/cluster_application_core_shared_examples.rb b/spec/support/shared_examples/models/cluster_application_core_shared_examples.rb index d6490a808ce..8e58cc7ba22 100644 --- a/spec/support/shared_examples/models/cluster_application_core_shared_examples.rb +++ b/spec/support/shared_examples/models/cluster_application_core_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples 'cluster application core specs' do |application_name| it { is_expected.to belong_to(:cluster) } it { is_expected.to validate_presence_of(:cluster) } diff --git a/spec/support/shared_examples/models/cluster_application_helm_cert_examples.rb b/spec/support/shared_examples/models/cluster_application_helm_cert_examples.rb index bd3661471f8..7ddb3b11c85 100644 --- a/spec/support/shared_examples/models/cluster_application_helm_cert_examples.rb +++ b/spec/support/shared_examples/models/cluster_application_helm_cert_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples 'cluster application helm specs' do |application_name| let(:application) { create(application_name) } diff --git a/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb b/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb index 4525c03837f..5341aacb445 100644 --- a/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb +++ b/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples 'cluster application status specs' do |application_name| describe '#status' do let(:cluster) { create(:cluster, :provided_by_gcp) } diff --git a/spec/support/shared_examples/models/diff_positionable_note_shared_examples.rb b/spec/support/shared_examples/models/diff_positionable_note_shared_examples.rb new file mode 100644 index 00000000000..8b298c5c974 --- /dev/null +++ b/spec/support/shared_examples/models/diff_positionable_note_shared_examples.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +shared_examples_for 'a valid diff positionable note' do |factory_on_commit| + context 'for commit' do + let(:project) { create(:project, :repository) } + let(:commit) { project.commit(sample_commit.id) } + let(:commit_id) { commit.id } + let(:diff_refs) { commit.diff_refs } + + let(:position) do + Gitlab::Diff::Position.new( + old_path: "files/ruby/popen.rb", + new_path: "files/ruby/popen.rb", + old_line: nil, + new_line: 14, + diff_refs: diff_refs + ) + end + + subject { build(factory_on_commit, commit_id: commit_id, position: position) } + + context 'position diff refs matches commit diff refs' do + it 'is valid' do + expect(subject).to be_valid + expect(subject.errors).not_to have_key(:commit_id) + end + end + + context 'position diff refs does not match commit diff refs' do + let(:diff_refs) do + Gitlab::Diff::DiffRefs.new( + base_sha: "not_existing_sha", + head_sha: "existing_sha" + ) + end + + it 'is invalid' do + expect(subject).to be_invalid + expect(subject.errors).to have_key(:commit_id) + end + end + + context 'commit does not exist' do + let(:commit_id) { 'non-existing' } + + it 'is invalid' do + expect(subject).to be_invalid + expect(subject.errors).to have_key(:commit_id) + end + end + end +end diff --git a/spec/support/shared_examples/models/issuable_hook_data_shared_examples.rb b/spec/support/shared_examples/models/issuable_hook_data_shared_examples.rb index a4762b68858..7ea2bb265cc 100644 --- a/spec/support/shared_examples/models/issuable_hook_data_shared_examples.rb +++ b/spec/support/shared_examples/models/issuable_hook_data_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # This shared example requires a `builder` and `user` variable shared_examples 'issuable hook data' do |kind| let(:data) { builder.build(user: user) } diff --git a/spec/support/shared_examples/models/members_notifications_shared_example.rb b/spec/support/shared_examples/models/members_notifications_shared_example.rb index ef5cea3f2a5..050d710f1de 100644 --- a/spec/support/shared_examples/models/members_notifications_shared_example.rb +++ b/spec/support/shared_examples/models/members_notifications_shared_example.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + RSpec.shared_examples 'members notifications' do |entity_type| let(:notification_service) { double('NotificationService').as_null_object } diff --git a/spec/support/shared_examples/models/project_hook_data_shared_examples.rb b/spec/support/shared_examples/models/project_hook_data_shared_examples.rb index f0264878811..03d10c10e3c 100644 --- a/spec/support/shared_examples/models/project_hook_data_shared_examples.rb +++ b/spec/support/shared_examples/models/project_hook_data_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples 'project hook data with deprecateds' do |project_key: :project| it 'contains project data' do expect(data[project_key][:name]).to eq(project.name) diff --git a/spec/support/shared_examples/models/update_project_statistics_shared_examples.rb b/spec/support/shared_examples/models/update_project_statistics_shared_examples.rb index aad63982e7a..e03435cafe8 100644 --- a/spec/support/shared_examples/models/update_project_statistics_shared_examples.rb +++ b/spec/support/shared_examples/models/update_project_statistics_shared_examples.rb @@ -32,19 +32,6 @@ shared_examples_for 'UpdateProjectStatistics' do subject.save! end - - context 'when feature flag is disabled for the namespace' do - it 'does not schedules a namespace statistics worker' do - namespace = subject.project.root_ancestor - - stub_feature_flags(update_statistics_namespace: false, namespace: namespace) - - expect(Namespaces::ScheduleAggregationWorker) - .not_to receive(:perform_async) - - subject.save! - end - end end context 'when updating' do @@ -87,20 +74,6 @@ shared_examples_for 'UpdateProjectStatistics' do subject.save! end.not_to exceed_query_limit(control_count) end - - context 'when the feature flag is disabled for the namespace' do - it 'does not schedule a namespace statistics worker' do - namespace = subject.project.root_ancestor - - stub_feature_flags(update_statistics_namespace: false, namespace: namespace) - - expect(Namespaces::ScheduleAggregationWorker) - .not_to receive(:perform_async) - - subject.write_attribute(statistic_attribute, read_attribute + delta) - subject.save! - end - end end context 'when destroying' do @@ -144,18 +117,5 @@ shared_examples_for 'UpdateProjectStatistics' do project.destroy! end end - - context 'when feature flag is disabled for the namespace' do - it 'does not schedule a namespace statistics worker' do - namespace = subject.project.root_ancestor - - stub_feature_flags(update_statistics_namespace: false, namespace: namespace) - - expect(Namespaces::ScheduleAggregationWorker) - .not_to receive(:perform_async) - - subject.destroy! - end - end end end diff --git a/spec/support/shared_examples/models/with_uploads_shared_examples.rb b/spec/support/shared_examples/models/with_uploads_shared_examples.rb index 43033a2d256..eb1ade03017 100644 --- a/spec/support/shared_examples/models/with_uploads_shared_examples.rb +++ b/spec/support/shared_examples/models/with_uploads_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' shared_examples_for 'model with uploads' do |supports_fileuploads| diff --git a/spec/support/shared_examples/notify_shared_examples.rb b/spec/support/shared_examples/notify_shared_examples.rb index e64c7e37a0c..ca031df000e 100644 --- a/spec/support/shared_examples/notify_shared_examples.rb +++ b/spec/support/shared_examples/notify_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_context 'gitlab email notification' do set(:group) { create(:group) } set(:subgroup) { create(:group, parent: group) } @@ -42,42 +44,17 @@ shared_examples 'an email sent from GitLab' do end shared_examples 'an email sent to a user' do - let(:group_notification_email) { 'user+group@example.com' } - it 'is sent to user\'s global notification email address' do expect(subject).to deliver_to(recipient.notification_email) end - context 'that is part of a project\'s group' do - it 'is sent to user\'s group notification email address when set' do - create(:notification_setting, user: recipient, source: project.group, notification_email: group_notification_email) - expect(subject).to deliver_to(group_notification_email) - end - - it 'is sent to user\'s global notification email address when no group email set' do - create(:notification_setting, user: recipient, source: project.group, notification_email: '') - expect(subject).to deliver_to(recipient.notification_email) - end - end + context 'with group notification email' do + it 'is sent to user\'s group notification email' do + group_notification_email = 'user+group@example.com' - context 'when project is in a sub-group', :nested_groups do - before do - project.update!(group: subgroup) - end - - it 'is sent to user\'s subgroup notification email address when set' do - # Set top-level group notification email address to make sure it doesn't get selected create(:notification_setting, user: recipient, source: group, notification_email: group_notification_email) - subgroup_notification_email = 'user+subgroup@example.com' - create(:notification_setting, user: recipient, source: subgroup, notification_email: subgroup_notification_email) - - expect(subject).to deliver_to(subgroup_notification_email) - end - - it 'is sent to user\'s group notification email address when set and subgroup email address not set' do - create(:notification_setting, user: recipient, source: subgroup, notification_email: '') - expect(subject).to deliver_to(recipient.notification_email) + expect(subject).to deliver_to(group_notification_email) end end end diff --git a/spec/support/shared_examples/policies/clusterable_shared_examples.rb b/spec/support/shared_examples/policies/clusterable_shared_examples.rb index d99f94c76c3..0b427c23256 100644 --- a/spec/support/shared_examples/policies/clusterable_shared_examples.rb +++ b/spec/support/shared_examples/policies/clusterable_shared_examples.rb @@ -13,7 +13,11 @@ shared_examples 'clusterable policies' do clusterable.add_developer(current_user) end + it { expect_disallowed(:read_cluster) } it { expect_disallowed(:add_cluster) } + it { expect_disallowed(:create_cluster) } + it { expect_disallowed(:update_cluster) } + it { expect_disallowed(:admin_cluster) } end context 'with a maintainer' do @@ -22,15 +26,11 @@ shared_examples 'clusterable policies' do end context 'with no clusters' do + it { expect_allowed(:read_cluster) } it { expect_allowed(:add_cluster) } - end - - context 'with an existing cluster' do - before do - cluster - end - - it { expect_disallowed(:add_cluster) } + it { expect_allowed(:create_cluster) } + it { expect_allowed(:update_cluster) } + it { expect_allowed(:admin_cluster) } end end end diff --git a/spec/support/shared_examples/position_formatters.rb b/spec/support/shared_examples/position_formatters.rb index ffc9456dbc7..30b6b8d24f0 100644 --- a/spec/support/shared_examples/position_formatters.rb +++ b/spec/support/shared_examples/position_formatters.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples_for "position formatter" do let(:formatter) { described_class.new(attrs) } diff --git a/spec/support/shared_examples/project_latest_successful_build_for_examples.rb b/spec/support/shared_examples/project_latest_successful_build_for_examples.rb new file mode 100644 index 00000000000..a9bd23e9fc9 --- /dev/null +++ b/spec/support/shared_examples/project_latest_successful_build_for_examples.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +shared_examples 'latest successful build for sha or ref' do + context 'with many builds' do + let(:other_pipeline) { create_pipeline(project) } + let(:other_build) { create_build(other_pipeline, 'test') } + let(:build_name) { other_build.name } + + before do + pipeline1 = create_pipeline(project) + pipeline2 = create_pipeline(project) + create_build(pipeline1, 'test') + create_build(pipeline1, 'test2') + create_build(pipeline2, 'test2') + end + + it 'gives the latest builds from latest pipeline' do + expect(subject).to eq(other_build) + end + end + + context 'with succeeded pipeline' do + let!(:build) { create_build } + let(:build_name) { build.name } + + context 'standalone pipeline' do + it 'returns builds for ref for default_branch' do + expect(subject).to eq(build) + end + + context 'with nonexistent build' do + let(:build_name) { 'TAIL' } + + it 'returns empty relation if the build cannot be found' do + expect(subject).to be_nil + end + end + end + + context 'with some pending pipeline' do + before do + create_build(create_pipeline(project, 'pending')) + end + + it 'gives the latest build from latest pipeline' do + expect(subject).to eq(build) + end + end + end + + context 'with pending pipeline' do + let!(:pending_build) { create_build(pipeline) } + let(:build_name) { pending_build.name } + + before do + pipeline.update(status: 'pending') + end + + it 'returns empty relation' do + expect(subject).to be_nil + end + end +end diff --git a/spec/support/shared_examples/quick_actions/commit/tag_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/commit/tag_quick_action_shared_examples.rb index b337a1c18d8..f5a86e4dc2c 100644 --- a/spec/support/shared_examples/quick_actions/commit/tag_quick_action_shared_examples.rb +++ b/spec/support/shared_examples/quick_actions/commit/tag_quick_action_shared_examples.rb @@ -5,7 +5,7 @@ shared_examples 'tag quick action' do it 'tags this commit' do add_note("/tag #{tag_name} #{tag_message}") - expect(page).to have_content 'Commands applied' + expect(page).to have_content %{Tagged this commit to #{tag_name} with "#{tag_message}".} expect(page).to have_content "tagged commit #{truncated_commit_sha}" expect(page).to have_content tag_name diff --git a/spec/support/shared_examples/quick_actions/issuable/close_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/close_quick_action_shared_examples.rb index a79a61bc708..6e7eb78261a 100644 --- a/spec/support/shared_examples/quick_actions/issuable/close_quick_action_shared_examples.rb +++ b/spec/support/shared_examples/quick_actions/issuable/close_quick_action_shared_examples.rb @@ -68,7 +68,7 @@ shared_examples 'close quick action' do |issuable_type| it "does not close the #{issuable_type}" do add_note('/close') - expect(page).not_to have_content 'Commands applied' + expect(page).not_to have_content "Closed this #{issuable.to_ability_name.humanize(capitalize: false)}." expect(issuable).to be_open end end diff --git a/spec/support/shared_examples/quick_actions/issue/create_merge_request_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issue/create_merge_request_quick_action_shared_examples.rb index 34dba5dbc31..3e9ee9a633f 100644 --- a/spec/support/shared_examples/quick_actions/issue/create_merge_request_quick_action_shared_examples.rb +++ b/spec/support/shared_examples/quick_actions/issue/create_merge_request_quick_action_shared_examples.rb @@ -2,8 +2,14 @@ shared_examples 'create_merge_request quick action' do context 'create a merge request starting from an issue' do - def expect_mr_quickaction(success) - expect(page).to have_content 'Commands applied' + def expect_mr_quickaction(success, branch_name = nil) + command_message = if branch_name + "Created branch '#{branch_name}' and a merge request to resolve this issue" + else + "Created a branch and a merge request to resolve this issue" + end + + expect(page).to have_content command_message if success expect(page).to have_content 'created merge request' @@ -13,19 +19,21 @@ shared_examples 'create_merge_request quick action' do end it "doesn't create a merge request when the branch name is invalid" do - add_note("/create_merge_request invalid branch name") + branch_name = 'invalid branch name' + add_note("/create_merge_request #{branch_name}") wait_for_requests - expect_mr_quickaction(false) + expect_mr_quickaction(false, branch_name) end it "doesn't create a merge request when a branch with that name already exists" do - add_note("/create_merge_request feature") + branch_name = 'feature' + add_note("/create_merge_request #{branch_name}") wait_for_requests - expect_mr_quickaction(false) + expect_mr_quickaction(false, branch_name) end it 'creates a new merge request using issue iid and title as branch name when the branch name is empty' do @@ -46,7 +54,7 @@ shared_examples 'create_merge_request quick action' do branch_name = '1-feature' add_note("/create_merge_request #{branch_name}") - expect_mr_quickaction(true) + expect_mr_quickaction(true, branch_name) created_mr = project.merge_requests.last expect(created_mr.source_branch).to eq(branch_name) diff --git a/spec/support/shared_examples/quick_actions/issue/duplicate_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issue/duplicate_quick_action_shared_examples.rb index 633c7135fbc..3834b8b2b87 100644 --- a/spec/support/shared_examples/quick_actions/issue/duplicate_quick_action_shared_examples.rb +++ b/spec/support/shared_examples/quick_actions/issue/duplicate_quick_action_shared_examples.rb @@ -9,7 +9,6 @@ shared_examples 'duplicate quick action' do add_note("/duplicate ##{original_issue.to_reference}") expect(page).not_to have_content "/duplicate #{original_issue.to_reference}" - expect(page).to have_content 'Commands applied' expect(page).to have_content "marked this issue as a duplicate of #{original_issue.to_reference}" expect(issue.reload).to be_closed @@ -28,7 +27,6 @@ shared_examples 'duplicate quick action' do it 'does not create a note, and does not mark the issue as a duplicate' do add_note("/duplicate ##{original_issue.to_reference}") - expect(page).not_to have_content 'Commands applied' expect(page).not_to have_content "marked this issue as a duplicate of #{original_issue.to_reference}" expect(issue.reload).to be_open diff --git a/spec/support/shared_examples/quick_actions/issue/move_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issue/move_quick_action_shared_examples.rb index a0b0d888769..bebc8509d53 100644 --- a/spec/support/shared_examples/quick_actions/issue/move_quick_action_shared_examples.rb +++ b/spec/support/shared_examples/quick_actions/issue/move_quick_action_shared_examples.rb @@ -12,7 +12,7 @@ shared_examples 'move quick action' do it 'moves the issue' do add_note("/move #{target_project.full_path}") - expect(page).to have_content 'Commands applied' + expect(page).to have_content "Moved this issue to #{target_project.full_path}." expect(issue.reload).to be_closed visit project_issue_path(target_project, issue) @@ -29,7 +29,7 @@ shared_examples 'move quick action' do wait_for_requests - expect(page).to have_content 'Commands applied' + expect(page).to have_content "Moved this issue to #{project_unauthorized.full_path}." expect(issue.reload).to be_open end end @@ -40,7 +40,7 @@ shared_examples 'move quick action' do wait_for_requests - expect(page).to have_content 'Commands applied' + expect(page).to have_content "Failed to move this issue because target project doesn't exist." expect(issue.reload).to be_open end end @@ -56,7 +56,7 @@ shared_examples 'move quick action' do shared_examples 'applies the commands to issues in both projects, target and source' do it "applies quick actions" do - expect(page).to have_content 'Commands applied' + expect(page).to have_content "Moved this issue to #{target_project.full_path}." expect(issue.reload).to be_closed visit project_issue_path(target_project, issue) @@ -89,5 +89,54 @@ shared_examples 'move quick action' do it_behaves_like 'applies the commands to issues in both projects, target and source' end end + + context 'when editing comments' do + let(:target_project) { create(:project, :public) } + + before do + target_project.add_maintainer(user) + + sign_in(user) + visit project_issue_path(project, issue) + wait_for_all_requests + end + + it 'moves the issue after quickcommand note was updated' do + # misspelled quick action + add_note("test note.\n/mvoe #{target_project.full_path}") + + expect(issue.reload).not_to be_closed + + edit_note("/mvoe #{target_project.full_path}", "test note.\n/move #{target_project.full_path}") + wait_for_all_requests + + expect(page).to have_content 'test note.' + expect(issue.reload).to be_closed + + visit project_issue_path(target_project, issue) + wait_for_all_requests + + expect(page).to have_content 'Issues 1' + end + + it 'deletes the note if it was updated to just contain a command' do + # missspelled quick action + add_note("test note.\n/mvoe #{target_project.full_path}") + + expect(page).not_to have_content 'Commands applied' + expect(issue.reload).not_to be_closed + + edit_note("/mvoe #{target_project.full_path}", "/move #{target_project.full_path}") + wait_for_all_requests + + expect(page).not_to have_content "/move #{target_project.full_path}" + expect(issue.reload).to be_closed + + visit project_issue_path(target_project, issue) + wait_for_all_requests + + expect(page).to have_content 'Issues 1' + end + end end end diff --git a/spec/support/shared_examples/quick_actions/merge_request/merge_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/merge_request/merge_quick_action_shared_examples.rb index c454ddc4bba..ac7c17915de 100644 --- a/spec/support/shared_examples/quick_actions/merge_request/merge_quick_action_shared_examples.rb +++ b/spec/support/shared_examples/quick_actions/merge_request/merge_quick_action_shared_examples.rb @@ -10,7 +10,7 @@ shared_examples 'merge quick action' do it 'merges the MR' do add_note("/merge") - expect(page).to have_content 'Commands applied' + expect(page).to have_content 'Scheduled to merge this merge request when the pipeline succeeds.' expect(merge_request.reload).to be_merged end diff --git a/spec/support/shared_examples/reference_parser_shared_examples.rb b/spec/support/shared_examples/reference_parser_shared_examples.rb index baf8bcc04b8..d903c0f10e0 100644 --- a/spec/support/shared_examples/reference_parser_shared_examples.rb +++ b/spec/support/shared_examples/reference_parser_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + RSpec.shared_examples "referenced feature visibility" do |*related_features| let(:feature_fields) do related_features.map { |feature| (feature + "_access_level").to_sym } diff --git a/spec/support/shared_examples/relative_positioning_shared_examples.rb b/spec/support/shared_examples/relative_positioning_shared_examples.rb new file mode 100644 index 00000000000..b7382cea93c --- /dev/null +++ b/spec/support/shared_examples/relative_positioning_shared_examples.rb @@ -0,0 +1,283 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'a class that supports relative positioning' do + let(:item1) { create(factory, default_params) } + let(:item2) { create(factory, default_params) } + let(:new_item) { create(factory, default_params) } + + def create_item(params) + create(factory, params.merge(default_params)) + end + + def create_items_with_positions(positions) + positions.map do |position| + create_item(relative_position: position) + end + end + + describe '.move_nulls_to_end' do + it 'moves items with null relative_position to the end' do + item1.update!(relative_position: nil) + item2.update!(relative_position: nil) + + described_class.move_nulls_to_end([item1, item2]) + + expect(item2.prev_relative_position).to eq item1.relative_position + expect(item1.prev_relative_position).to eq nil + expect(item2.next_relative_position).to eq nil + end + + it 'moves the item near the start position when there are no existing positions' do + item1.update!(relative_position: nil) + + described_class.move_nulls_to_end([item1]) + + expect(item1.relative_position).to eq(described_class::START_POSITION + described_class::IDEAL_DISTANCE) + end + + it 'does not perform any moves if all items have their relative_position set' do + item1.update!(relative_position: 1) + + expect(item1).not_to receive(:save) + + described_class.move_nulls_to_end([item1]) + end + end + + describe '#max_relative_position' do + it 'returns maximum position' do + expect(item1.max_relative_position).to eq item2.relative_position + end + end + + describe '#prev_relative_position' do + it 'returns previous position if there is an item above' do + item1.update(relative_position: 5) + item2.update(relative_position: 15) + + expect(item2.prev_relative_position).to eq item1.relative_position + end + + it 'returns nil if there is no item above' do + expect(item1.prev_relative_position).to eq nil + end + end + + describe '#next_relative_position' do + it 'returns next position if there is an item below' do + item1.update(relative_position: 5) + item2.update(relative_position: 15) + + expect(item1.next_relative_position).to eq item2.relative_position + end + + it 'returns nil if there is no item below' do + expect(item2.next_relative_position).to eq nil + end + end + + describe '#move_before' do + it 'moves item before' do + [item2, item1].each(&:move_to_end) + + item1.move_before(item2) + + expect(item1.relative_position).to be < item2.relative_position + end + end + + describe '#move_after' do + it 'moves item after' do + [item1, item2].each(&:move_to_end) + + item1.move_after(item2) + + expect(item1.relative_position).to be > item2.relative_position + end + end + + describe '#move_to_end' do + before do + [item1, item2].each do |item1| + item1.move_to_end && item1.save + end + end + + it 'moves item to the end' do + new_item.move_to_end + + expect(new_item.relative_position).to be > item2.relative_position + end + end + + describe '#move_between' do + before do + [item1, item2].each do |item1| + item1.move_to_end && item1.save + end + end + + it 'positions item between two other' do + new_item.move_between(item1, item2) + + expect(new_item.relative_position).to be > item1.relative_position + expect(new_item.relative_position).to be < item2.relative_position + end + + it 'positions item between on top' do + new_item.move_between(nil, item1) + + expect(new_item.relative_position).to be < item1.relative_position + end + + it 'positions item between to end' do + new_item.move_between(item2, nil) + + expect(new_item.relative_position).to be > item2.relative_position + end + + it 'positions items even when after and before positions are the same' do + item2.update relative_position: item1.relative_position + + new_item.move_between(item1, item2) + + expect(new_item.relative_position).to be > item1.relative_position + expect(item1.relative_position).to be < item2.relative_position + end + + it 'positions items between other two if distance is 1' do + item2.update relative_position: item1.relative_position + 1 + + new_item.move_between(item1, item2) + + expect(new_item.relative_position).to be > item1.relative_position + expect(item1.relative_position).to be < item2.relative_position + end + + it 'positions item in the middle of other two if distance is big enough' do + item1.update relative_position: 6000 + item2.update relative_position: 10000 + + new_item.move_between(item1, item2) + + expect(new_item.relative_position).to eq(8000) + end + + it 'positions item closer to the middle if we are at the very top' do + item2.update relative_position: 6000 + + new_item.move_between(nil, item2) + + expect(new_item.relative_position).to eq(6000 - RelativePositioning::IDEAL_DISTANCE) + end + + it 'positions item closer to the middle if we are at the very bottom' do + new_item.update relative_position: 1 + item1.update relative_position: 6000 + item2.destroy + + new_item.move_between(item1, nil) + + expect(new_item.relative_position).to eq(6000 + RelativePositioning::IDEAL_DISTANCE) + end + + it 'positions item in the middle of other two if distance is not big enough' do + item1.update relative_position: 100 + item2.update relative_position: 400 + + new_item.move_between(item1, item2) + + expect(new_item.relative_position).to eq(250) + end + + it 'positions item in the middle of other two is there is no place' do + item1.update relative_position: 100 + item2.update relative_position: 101 + + new_item.move_between(item1, item2) + + expect(new_item.relative_position).to be_between(item1.relative_position, item2.relative_position) + end + + it 'uses rebalancing if there is no place' do + item1.update relative_position: 100 + item2.update relative_position: 101 + item3 = create_item(relative_position: 102) + new_item.update relative_position: 103 + + new_item.move_between(item2, item3) + new_item.save! + + expect(new_item.relative_position).to be_between(item2.relative_position, item3.relative_position) + expect(item1.reload.relative_position).not_to eq(100) + end + + it 'positions item right if we pass none-sequential parameters' do + item1.update relative_position: 99 + item2.update relative_position: 101 + item3 = create_item(relative_position: 102) + new_item.update relative_position: 103 + + new_item.move_between(item1, item3) + new_item.save! + + expect(new_item.relative_position).to be(100) + end + + it 'avoids N+1 queries when rebalancing other items' do + items = create_items_with_positions([100, 101, 102]) + + count = ActiveRecord::QueryRecorder.new do + new_item.move_between(items[-2], items[-1]) + end + + items = create_items_with_positions([150, 151, 152, 153, 154]) + + expect { new_item.move_between(items[-2], items[-1]) }.not_to exceed_query_limit(count) + end + end + + describe '#move_sequence_before' do + it 'moves the whole sequence of items to the middle of the nearest gap' do + items = create_items_with_positions([90, 100, 101, 102]) + + items.last.move_sequence_before + items.last.save! + + positions = items.map { |item| item.reload.relative_position } + expect(positions).to eq([90, 95, 96, 102]) + end + + it 'finds a gap if there are unused positions' do + items = create_items_with_positions([100, 101, 102]) + + items.last.move_sequence_before + items.last.save! + + positions = items.map { |item| item.reload.relative_position } + expect(positions).to eq([50, 51, 102]) + end + end + + describe '#move_sequence_after' do + it 'moves the whole sequence of items to the middle of the nearest gap' do + items = create_items_with_positions([100, 101, 102, 110]) + + items.first.move_sequence_after + items.first.save! + + positions = items.map { |item| item.reload.relative_position } + expect(positions).to eq([100, 105, 106, 110]) + end + + it 'finds a gap if there are unused positions' do + items = create_items_with_positions([100, 101, 102]) + + items.first.move_sequence_after + items.first.save! + + positions = items.map { |item| item.reload.relative_position } + expect(positions).to eq([100, 601, 602]) + end + end +end diff --git a/spec/support/shared_examples/requests/api/custom_attributes_shared_examples.rb b/spec/support/shared_examples/requests/api/custom_attributes_shared_examples.rb index 8a7fcf856a1..776a0bdd29e 100644 --- a/spec/support/shared_examples/requests/api/custom_attributes_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/custom_attributes_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples 'custom attributes endpoints' do |attributable_name| let!(:custom_attribute1) { attributable.custom_attributes.create key: 'foo', value: 'foo' } let!(:custom_attribute2) { attributable.custom_attributes.create key: 'bar', value: 'bar' } diff --git a/spec/support/shared_examples/requests/api/diff_discussions.rb b/spec/support/shared_examples/requests/api/diff_discussions.rb index 366c2955359..76c6c93964a 100644 --- a/spec/support/shared_examples/requests/api/diff_discussions.rb +++ b/spec/support/shared_examples/requests/api/diff_discussions.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples 'diff discussions API' do |parent_type, noteable_type, id_name| describe "GET /#{parent_type}/:id/#{noteable_type}/:noteable_id/discussions" do it "includes diff discussions" do diff --git a/spec/support/shared_examples/requests/api/discussions.rb b/spec/support/shared_examples/requests/api/discussions.rb index c3132c41f5b..a36bc2dc9b5 100644 --- a/spec/support/shared_examples/requests/api/discussions.rb +++ b/spec/support/shared_examples/requests/api/discussions.rb @@ -1,3 +1,59 @@ +# frozen_string_literal: true + +shared_examples 'with cross-reference system notes' do + let(:merge_request) { create(:merge_request) } + let(:project) { merge_request.project } + let(:new_merge_request) { create(:merge_request) } + let(:commit) { new_merge_request.project.commit } + let!(:note) { create(:system_note, noteable: merge_request, project: project, note: cross_reference) } + let!(:note_metadata) { create(:system_note_metadata, note: note, action: 'cross_reference') } + let(:cross_reference) { "test commit #{commit.to_reference(project)}" } + let(:pat) { create(:personal_access_token, user: user) } + + before do + project.add_developer(user) + new_merge_request.project.add_developer(user) + + hidden_merge_request = create(:merge_request) + new_cross_reference = "test commit #{hidden_merge_request.project.commit}" + new_note = create(:system_note, noteable: merge_request, project: project, note: new_cross_reference) + create(:system_note_metadata, note: new_note, action: 'cross_reference') + end + + it 'returns only the note that the user should see' do + get api(url, user, personal_access_token: pat) + + expect(response).to have_gitlab_http_status(200) + expect(json_response.count).to eq(1) + expect(notes_in_response.count).to eq(1) + + parsed_note = notes_in_response.first + expect(parsed_note['id']).to eq(note.id) + expect(parsed_note['body']).to eq(cross_reference) + expect(parsed_note['system']).to be true + end + + it 'avoids Git calls and N+1 SQL queries', :request_store do + expect_any_instance_of(Repository).not_to receive(:find_commit).with(commit.id) + + control = ActiveRecord::QueryRecorder.new do + get api(url, user, personal_access_token: pat) + end + + expect(response).to have_gitlab_http_status(200) + + RequestStore.clear! + + new_note = create(:system_note, noteable: merge_request, project: project, note: cross_reference) + create(:system_note_metadata, note: new_note, action: 'cross_reference') + + RequestStore.clear! + + expect { get api(url, user, personal_access_token: pat) }.not_to exceed_query_limit(control) + expect(response).to have_gitlab_http_status(200) + end +end + shared_examples 'discussions API' do |parent_type, noteable_type, id_name, can_reply_to_individual_notes: false| describe "GET /#{parent_type}/:id/#{noteable_type}/:noteable_id/discussions" do it "returns an array of discussions" do diff --git a/spec/support/shared_examples/requests/api/issuable_participants_examples.rb b/spec/support/shared_examples/requests/api/issuable_participants_examples.rb index 96d59e0c472..9fe6288d53f 100644 --- a/spec/support/shared_examples/requests/api/issuable_participants_examples.rb +++ b/spec/support/shared_examples/requests/api/issuable_participants_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples 'issuable participants endpoint' do let(:area) { entity.class.name.underscore.pluralize } diff --git a/spec/support/shared_examples/requests/api/issues/merge_requests_count_shared_examples.rb b/spec/support/shared_examples/requests/api/issues/merge_requests_count_shared_examples.rb index 5f4e178f2e5..90c1ed8d09b 100644 --- a/spec/support/shared_examples/requests/api/issues/merge_requests_count_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/issues/merge_requests_count_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + def get_issue json_response.is_a?(Array) ? json_response.detect {|issue| issue['id'] == target_issue.id} : json_response end diff --git a/spec/support/shared_examples/requests/api/notes.rb b/spec/support/shared_examples/requests/api/notes.rb index 57eefd5ef01..354ae7288b1 100644 --- a/spec/support/shared_examples/requests/api/notes.rb +++ b/spec/support/shared_examples/requests/api/notes.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples 'noteable API' do |parent_type, noteable_type, id_name| describe "GET /#{parent_type}/:id/#{noteable_type}/:noteable_id/notes" do context 'sorting' do diff --git a/spec/support/shared_examples/requests/api/pipelines/visibility_table_examples.rb b/spec/support/shared_examples/requests/api/pipelines/visibility_table_examples.rb new file mode 100644 index 00000000000..dfd07176b1c --- /dev/null +++ b/spec/support/shared_examples/requests/api/pipelines/visibility_table_examples.rb @@ -0,0 +1,235 @@ +# frozen_string_literal: true + +shared_examples 'pipelines visibility table' do + using RSpec::Parameterized::TableSyntax + + let(:ci_user) { create(:user) } + let(:api_user) { user_role && ci_user } + + let(:pipelines_api_path) do + "/projects/#{project.id}/pipelines" + end + + let(:response_200) do + a_collection_containing_exactly( + a_hash_including('sha', 'ref', 'status', 'web_url', 'id' => pipeline.id) + ) + end + + let(:response_40x) do + a_hash_including('message') + end + + let(:expected_response) do + if response_status == 200 + response_200 + else + response_40x + end + end + + let(:api_response) { json_response } + + let(:visibility_levels) do + { + private: Gitlab::VisibilityLevel::PRIVATE, + internal: Gitlab::VisibilityLevel::INTERNAL, + public: Gitlab::VisibilityLevel::PUBLIC + } + end + + let(:builds_access_levels) do + { + enabled: ProjectFeature::ENABLED, + private: ProjectFeature::PRIVATE + } + end + + let(:project_attributes) do + { + visibility_level: visibility_levels[visibility_level], + public_builds: public_builds + } + end + + let(:project_feature_attributes) do + { + builds_access_level: builds_access_levels[builds_access_level] + } + end + + where(:visibility_level, :builds_access_level, :public_builds, :is_admin, :user_role, :response_status) do + :private | :enabled | true | true | :non_member | 200 + :private | :enabled | true | true | :guest | 200 + :private | :enabled | true | true | :reporter | 200 + :private | :enabled | true | true | :developer | 200 + :private | :enabled | true | true | :maintainer | 200 + + :private | :enabled | true | false | nil | 404 + :private | :enabled | true | false | :non_member | 404 + :private | :enabled | true | false | :guest | 200 + :private | :enabled | true | false | :reporter | 200 + :private | :enabled | true | false | :developer | 200 + :private | :enabled | true | false | :maintainer | 200 + + :private | :enabled | false | true | :non_member | 200 + :private | :enabled | false | true | :guest | 200 + :private | :enabled | false | true | :reporter | 200 + :private | :enabled | false | true | :developer | 200 + :private | :enabled | false | true | :maintainer | 200 + + :private | :enabled | false | false | nil | 404 + :private | :enabled | false | false | :non_member | 404 + :private | :enabled | false | false | :guest | 403 + :private | :enabled | false | false | :reporter | 200 + :private | :enabled | false | false | :developer | 200 + :private | :enabled | false | false | :maintainer | 200 + + :private | :private | true | true | :non_member | 200 + :private | :private | true | true | :guest | 200 + :private | :private | true | true | :reporter | 200 + :private | :private | true | true | :developer | 200 + :private | :private | true | true | :maintainer | 200 + + :private | :private | true | false | nil | 404 + :private | :private | true | false | :non_member | 404 + :private | :private | true | false | :guest | 200 + :private | :private | true | false | :reporter | 200 + :private | :private | true | false | :developer | 200 + :private | :private | true | false | :maintainer | 200 + + :private | :private | false | true | :non_member | 200 + :private | :private | false | true | :guest | 200 + :private | :private | false | true | :reporter | 200 + :private | :private | false | true | :developer | 200 + :private | :private | false | true | :maintainer | 200 + + :private | :private | false | false | nil | 404 + :private | :private | false | false | :non_member | 404 + :private | :private | false | false | :guest | 403 + :private | :private | false | false | :reporter | 200 + :private | :private | false | false | :developer | 200 + :private | :private | false | false | :maintainer | 200 + + :internal | :enabled | true | true | :non_member | 200 + :internal | :enabled | true | true | :guest | 200 + :internal | :enabled | true | true | :reporter | 200 + :internal | :enabled | true | true | :developer | 200 + :internal | :enabled | true | true | :maintainer | 200 + + :internal | :enabled | true | false | nil | 404 + :internal | :enabled | true | false | :non_member | 200 + :internal | :enabled | true | false | :guest | 200 + :internal | :enabled | true | false | :reporter | 200 + :internal | :enabled | true | false | :developer | 200 + :internal | :enabled | true | false | :maintainer | 200 + + :internal | :enabled | false | true | :non_member | 200 + :internal | :enabled | false | true | :guest | 200 + :internal | :enabled | false | true | :reporter | 200 + :internal | :enabled | false | true | :developer | 200 + :internal | :enabled | false | true | :maintainer | 200 + + :internal | :enabled | false | false | nil | 404 + :internal | :enabled | false | false | :non_member | 403 + :internal | :enabled | false | false | :guest | 403 + :internal | :enabled | false | false | :reporter | 200 + :internal | :enabled | false | false | :developer | 200 + :internal | :enabled | false | false | :maintainer | 200 + + :internal | :private | true | true | :non_member | 200 + :internal | :private | true | true | :guest | 200 + :internal | :private | true | true | :reporter | 200 + :internal | :private | true | true | :developer | 200 + :internal | :private | true | true | :maintainer | 200 + + :internal | :private | true | false | nil | 404 + :internal | :private | true | false | :non_member | 403 + :internal | :private | true | false | :guest | 200 + :internal | :private | true | false | :reporter | 200 + :internal | :private | true | false | :developer | 200 + :internal | :private | true | false | :maintainer | 200 + + :internal | :private | false | true | :non_member | 200 + :internal | :private | false | true | :guest | 200 + :internal | :private | false | true | :reporter | 200 + :internal | :private | false | true | :developer | 200 + :internal | :private | false | true | :maintainer | 200 + + :internal | :private | false | false | nil | 404 + :internal | :private | false | false | :non_member | 403 + :internal | :private | false | false | :guest | 403 + :internal | :private | false | false | :reporter | 200 + :internal | :private | false | false | :developer | 200 + :internal | :private | false | false | :maintainer | 200 + + :public | :enabled | true | true | :non_member | 200 + :public | :enabled | true | true | :guest | 200 + :public | :enabled | true | true | :reporter | 200 + :public | :enabled | true | true | :developer | 200 + :public | :enabled | true | true | :maintainer | 200 + + :public | :enabled | true | false | nil | 200 + :public | :enabled | true | false | :non_member | 200 + :public | :enabled | true | false | :guest | 200 + :public | :enabled | true | false | :reporter | 200 + :public | :enabled | true | false | :developer | 200 + :public | :enabled | true | false | :maintainer | 200 + + :public | :enabled | false | true | :non_member | 200 + :public | :enabled | false | true | :guest | 200 + :public | :enabled | false | true | :reporter | 200 + :public | :enabled | false | true | :developer | 200 + :public | :enabled | false | true | :maintainer | 200 + + :public | :enabled | false | false | nil | 403 + :public | :enabled | false | false | :non_member | 403 + :public | :enabled | false | false | :guest | 403 + :public | :enabled | false | false | :reporter | 200 + :public | :enabled | false | false | :developer | 200 + :public | :enabled | false | false | :maintainer | 200 + + :public | :private | true | true | :non_member | 200 + :public | :private | true | true | :guest | 200 + :public | :private | true | true | :reporter | 200 + :public | :private | true | true | :developer | 200 + :public | :private | true | true | :maintainer | 200 + + :public | :private | true | false | nil | 403 + :public | :private | true | false | :non_member | 403 + :public | :private | true | false | :guest | 200 + :public | :private | true | false | :reporter | 200 + :public | :private | true | false | :developer | 200 + :public | :private | true | false | :maintainer | 200 + + :public | :private | false | true | :non_member | 200 + :public | :private | false | true | :guest | 200 + :public | :private | false | true | :reporter | 200 + :public | :private | false | true | :developer | 200 + :public | :private | false | true | :maintainer | 200 + + :public | :private | false | false | nil | 403 + :public | :private | false | false | :non_member | 403 + :public | :private | false | false | :guest | 403 + :public | :private | false | false | :reporter | 200 + :public | :private | false | false | :developer | 200 + :public | :private | false | false | :maintainer | 200 + end + + with_them do + before do + ci_user.update!(admin: is_admin) if user_role + + project.update!(project_attributes) + project.project_feature.update!(project_feature_attributes) + project.add_role(ci_user, user_role) if user_role && user_role != :non_member + + get api(pipelines_api_path, api_user) + end + + it do + expect(response).to have_gitlab_http_status(response_status) + expect(api_response).to match(expected_response) + end + end +end diff --git a/spec/support/shared_examples/requests/api/resolvable_discussions.rb b/spec/support/shared_examples/requests/api/resolvable_discussions.rb index 7e2416b23f3..42054a273f3 100644 --- a/spec/support/shared_examples/requests/api/resolvable_discussions.rb +++ b/spec/support/shared_examples/requests/api/resolvable_discussions.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples 'resolvable discussions API' do |parent_type, noteable_type, id_name| describe "PUT /#{parent_type}/:id/#{noteable_type}/:noteable_id/discussions/:discussion_id" do it "resolves discussion if resolved is true" do diff --git a/spec/support/shared_examples/requests/api/status_shared_examples.rb b/spec/support/shared_examples/requests/api/status_shared_examples.rb index ebfc5fed3bb..eebed7e42c1 100644 --- a/spec/support/shared_examples/requests/api/status_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/status_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Specs for status checking. # # Requires an API request: diff --git a/spec/support/shared_examples/requests/graphql_shared_examples.rb b/spec/support/shared_examples/requests/graphql_shared_examples.rb index 04140cad3f0..2a38d56141a 100644 --- a/spec/support/shared_examples/requests/graphql_shared_examples.rb +++ b/spec/support/shared_examples/requests/graphql_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' shared_examples 'a working graphql query' do diff --git a/spec/support/shared_examples/resource_label_events_api.rb b/spec/support/shared_examples/resource_label_events_api.rb new file mode 100644 index 00000000000..945cb8d9f2c --- /dev/null +++ b/spec/support/shared_examples/resource_label_events_api.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +shared_examples 'resource_label_events API' do |parent_type, eventable_type, id_name| + describe "GET /#{parent_type}/:id/#{eventable_type}/:noteable_id/resource_label_events" do + it "returns an array of resource label events" do + get api("/#{parent_type}/#{parent.id}/#{eventable_type}/#{eventable[id_name]}/resource_label_events", user) + + expect(response).to have_gitlab_http_status(200) + expect(response).to include_pagination_headers + expect(json_response).to be_an Array + expect(json_response.first['id']).to eq(event.id) + end + + it "returns a 404 error when eventable id not found" do + get api("/#{parent_type}/#{parent.id}/#{eventable_type}/12345/resource_label_events", user) + + expect(response).to have_gitlab_http_status(404) + end + + it "returns 404 when not authorized" do + parent.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE) + private_user = create(:user) + + get api("/#{parent_type}/#{parent.id}/#{eventable_type}/#{eventable[id_name]}/resource_label_events", private_user) + + expect(response).to have_gitlab_http_status(404) + end + end + + describe "GET /#{parent_type}/:id/#{eventable_type}/:noteable_id/resource_label_events/:event_id" do + it "returns a resource label event by id" do + get api("/#{parent_type}/#{parent.id}/#{eventable_type}/#{eventable[id_name]}/resource_label_events/#{event.id}", user) + + expect(response).to have_gitlab_http_status(200) + expect(json_response['id']).to eq(event.id) + end + + it "returns a 404 error if resource label event not found" do + get api("/#{parent_type}/#{parent.id}/#{eventable_type}/#{eventable[id_name]}/resource_label_events/12345", user) + + expect(response).to have_gitlab_http_status(404) + end + end +end diff --git a/spec/support/shared_examples/serializers/note_entity_examples.rb b/spec/support/shared_examples/serializers/note_entity_examples.rb index ec208aba2a9..bfcaa2f1bd5 100644 --- a/spec/support/shared_examples/serializers/note_entity_examples.rb +++ b/spec/support/shared_examples/serializers/note_entity_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples 'note entity' do subject { entity.as_json } diff --git a/spec/support/shared_examples/services/boards/boards_create_service.rb b/spec/support/shared_examples/services/boards/boards_create_service.rb index 5bdc04f660f..19818a6091b 100644 --- a/spec/support/shared_examples/services/boards/boards_create_service.rb +++ b/spec/support/shared_examples/services/boards/boards_create_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples 'boards create service' do context 'when parent does not have a board' do it 'creates a new board' do diff --git a/spec/support/shared_examples/services/boards/boards_list_service.rb b/spec/support/shared_examples/services/boards/boards_list_service.rb index e0d5a7c61f2..566e5050f8e 100644 --- a/spec/support/shared_examples/services/boards/boards_list_service.rb +++ b/spec/support/shared_examples/services/boards/boards_list_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples 'boards list service' do context 'when parent does not have a board' do it 'creates a new parent board' do diff --git a/spec/support/shared_examples/services/boards/issues_list_service.rb b/spec/support/shared_examples/services/boards/issues_list_service.rb index 8b879cef084..75733c774ef 100644 --- a/spec/support/shared_examples/services/boards/issues_list_service.rb +++ b/spec/support/shared_examples/services/boards/issues_list_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples 'issues list service' do it 'delegates search to IssuesFinder' do params = { board_id: board.id, id: list1.id } diff --git a/spec/support/shared_examples/services/boards/issues_move_service.rb b/spec/support/shared_examples/services/boards/issues_move_service.rb index 5359831f8f8..d3fa8084185 100644 --- a/spec/support/shared_examples/services/boards/issues_move_service.rb +++ b/spec/support/shared_examples/services/boards/issues_move_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples 'issues move service' do |group| shared_examples 'updating timestamps' do it 'updates updated_at' do diff --git a/spec/support/shared_examples/services/boards/lists_destroy_service.rb b/spec/support/shared_examples/services/boards/lists_destroy_service.rb index 62b6ffe1836..95725078f9d 100644 --- a/spec/support/shared_examples/services/boards/lists_destroy_service.rb +++ b/spec/support/shared_examples/services/boards/lists_destroy_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples 'lists destroy service' do context 'when list type is label' do it 'removes list from board' do diff --git a/spec/support/shared_examples/services/boards/lists_list_service.rb b/spec/support/shared_examples/services/boards/lists_list_service.rb index 0a8220111ab..29784f6da08 100644 --- a/spec/support/shared_examples/services/boards/lists_list_service.rb +++ b/spec/support/shared_examples/services/boards/lists_list_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples 'lists list service' do context 'when the board has a backlog list' do let!(:backlog_list) { create(:backlog_list, board: board) } diff --git a/spec/support/shared_examples/services/boards/lists_move_service.rb b/spec/support/shared_examples/services/boards/lists_move_service.rb index 2cdb968a45d..0b3bfd8e2a8 100644 --- a/spec/support/shared_examples/services/boards/lists_move_service.rb +++ b/spec/support/shared_examples/services/boards/lists_move_service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples 'lists move service' do let!(:planning) { create(:list, board: board, position: 0) } let!(:development) { create(:list, board: board, position: 1) } diff --git a/spec/support/shared_examples/services/check_ingress_ip_address_service_shared_examples.rb b/spec/support/shared_examples/services/check_ingress_ip_address_service_shared_examples.rb index 02de47a96dd..1e0ac8b7615 100644 --- a/spec/support/shared_examples/services/check_ingress_ip_address_service_shared_examples.rb +++ b/spec/support/shared_examples/services/check_ingress_ip_address_service_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples 'check ingress ip executions' do |app_name| describe '#execute' do let(:application) { create(app_name, :installed) } diff --git a/spec/support/shared_examples/services/gitlab_projects_import_service_shared_examples.rb b/spec/support/shared_examples/services/gitlab_projects_import_service_shared_examples.rb index b8db35a6ef9..1c3fa5644d3 100644 --- a/spec/support/shared_examples/services/gitlab_projects_import_service_shared_examples.rb +++ b/spec/support/shared_examples/services/gitlab_projects_import_service_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples 'gitlab projects import validations' do context 'with an invalid path' do let(:path) { '/invalid-path/' } diff --git a/spec/support/shared_examples/services/notification_service_shared_examples.rb b/spec/support/shared_examples/services/notification_service_shared_examples.rb new file mode 100644 index 00000000000..dd338ea47c7 --- /dev/null +++ b/spec/support/shared_examples/services/notification_service_shared_examples.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +# Note that we actually update the attribute on the target_project/group, rather than +# using `allow`. This is because there are some specs where, based on how the notification +# is done, using an `allow` doesn't change the correct object. +shared_examples 'project emails are disabled' do + let(:target_project) { notification_target.is_a?(Project) ? notification_target : notification_target.project } + + before do + reset_delivered_emails! + target_project.clear_memoization(:emails_disabled) + end + + it 'sends no emails with project emails disabled' do + target_project.update_attribute(:emails_disabled, true) + + notification_trigger + + should_not_email_anyone + end + + it 'sends emails to someone' do + target_project.update_attribute(:emails_disabled, false) + + notification_trigger + + should_email_anyone + end +end + +shared_examples 'group emails are disabled' do + let(:target_group) { notification_target.is_a?(Group) ? notification_target : notification_target.project.group } + + before do + reset_delivered_emails! + target_group.clear_memoization(:emails_disabled) + end + + it 'sends no emails with group emails disabled' do + target_group.update_attribute(:emails_disabled, true) + + notification_trigger + + should_not_email_anyone + end + + it 'sends emails to someone' do + target_group.update_attribute(:emails_disabled, false) + + notification_trigger + + should_email_anyone + end +end diff --git a/spec/support/shared_examples/slack_mattermost_notifications_shared_examples.rb b/spec/support/shared_examples/slack_mattermost_notifications_shared_examples.rb index 36c486dbdd6..8ce94064dc3 100644 --- a/spec/support/shared_examples/slack_mattermost_notifications_shared_examples.rb +++ b/spec/support/shared_examples/slack_mattermost_notifications_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + Dir[Rails.root.join("app/models/project_services/chat_message/*.rb")].each { |f| require f } RSpec.shared_examples 'slack or mattermost notifications' do @@ -452,7 +454,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do context 'only notify for the default branch' do context 'when enabled' do let(:pipeline) do - create(:ci_pipeline, :failed, project: project, ref: 'not-the-default-branch') + create(:ci_pipeline, :failed, project: project, sha: project.commit.sha, ref: 'not-the-default-branch') end before do @@ -470,7 +472,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do context 'when disabled' do let(:pipeline) do - create(:ci_pipeline, :failed, project: project, ref: 'not-the-default-branch') + create(:ci_pipeline, :failed, project: project, sha: project.commit.sha, ref: 'not-the-default-branch') end before do diff --git a/spec/support/shared_examples/snippet_visibility_shared_examples.rb b/spec/support/shared_examples/snippet_visibility_shared_examples.rb index 833c31a57cb..b5321c6db34 100644 --- a/spec/support/shared_examples/snippet_visibility_shared_examples.rb +++ b/spec/support/shared_examples/snippet_visibility_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + RSpec.shared_examples 'snippet visibility' do using RSpec::Parameterized::TableSyntax diff --git a/spec/support/shared_examples/snippets_shared_examples.rb b/spec/support/shared_examples/snippets_shared_examples.rb index 85f0facd5c3..5c35617bd36 100644 --- a/spec/support/shared_examples/snippets_shared_examples.rb +++ b/spec/support/shared_examples/snippets_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # These shared examples expect a `snippets` array of snippets RSpec.shared_examples 'paginated snippets' do |remote: false| it "is limited to #{Snippet.default_per_page} items per page" do diff --git a/spec/support/shared_examples/taskable_shared_examples.rb b/spec/support/shared_examples/taskable_shared_examples.rb index 4056ff06b84..f04f509f3d2 100644 --- a/spec/support/shared_examples/taskable_shared_examples.rb +++ b/spec/support/shared_examples/taskable_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Specs for task state functionality for issues and merge requests. # # Requires a context containing: @@ -105,4 +107,25 @@ shared_examples 'a Taskable' do expect(subject.task_status_short).to match('1 task') end end + + describe 'with tasks in blockquotes' do + before do + subject.description = <<-EOT.strip_heredoc + > - [ ] Task a + > > - [x] Task a.1 + + >>> + 1. [ ] Task 1 + 1. [x] Task 2 + >>> + EOT + end + + it 'returns the correct task status' do + expect(subject.task_status).to match('2 of') + expect(subject.task_status).to match('4 tasks completed') + expect(subject.task_status_short).to match('2/') + expect(subject.task_status_short).to match('4 tasks') + end + end end diff --git a/spec/support/shared_examples/throttled_touch.rb b/spec/support/shared_examples/throttled_touch.rb index eba990d4037..aaaa590862d 100644 --- a/spec/support/shared_examples/throttled_touch.rb +++ b/spec/support/shared_examples/throttled_touch.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples_for 'throttled touch' do describe '#touch' do it 'updates the updated_at timestamp' do diff --git a/spec/support/shared_examples/unique_ip_check_shared_examples.rb b/spec/support/shared_examples/unique_ip_check_shared_examples.rb index e5c8ac6a004..65d86ddee9e 100644 --- a/spec/support/shared_examples/unique_ip_check_shared_examples.rb +++ b/spec/support/shared_examples/unique_ip_check_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_context 'unique ips sign in limit' do include StubENV before do diff --git a/spec/support/shared_examples/update_invalid_issuable.rb b/spec/support/shared_examples/update_invalid_issuable.rb index 64568de424e..b7ac08372f9 100644 --- a/spec/support/shared_examples/update_invalid_issuable.rb +++ b/spec/support/shared_examples/update_invalid_issuable.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples 'update invalid issuable' do |klass| let(:params) do { @@ -38,7 +40,7 @@ shared_examples 'update invalid issuable' do |klass| put :update, params: params expect(response.status).to eq(409) - expect(JSON.parse(response.body)).to have_key('errors') + expect(json_response).to have_key('errors') end end diff --git a/spec/support/shared_examples/updating_mentions_shared_examples.rb b/spec/support/shared_examples/updating_mentions_shared_examples.rb index 5e3f19ba19e..ef385f94cc2 100644 --- a/spec/support/shared_examples/updating_mentions_shared_examples.rb +++ b/spec/support/shared_examples/updating_mentions_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + RSpec.shared_examples 'updating mentions' do |service_class| let(:mentioned_user) { create(:user) } let(:service_class) { service_class } diff --git a/spec/support/shared_examples/uploaders/gitlab_uploader_shared_examples.rb b/spec/support/shared_examples/uploaders/gitlab_uploader_shared_examples.rb index 1190863d88e..9263aaff89a 100644 --- a/spec/support/shared_examples/uploaders/gitlab_uploader_shared_examples.rb +++ b/spec/support/shared_examples/uploaders/gitlab_uploader_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples "matches the method pattern" do |method| let(:target) { subject } let(:args) { nil } diff --git a/spec/support/shared_examples/uploaders/object_storage_shared_examples.rb b/spec/support/shared_examples/uploaders/object_storage_shared_examples.rb index 1bd176280c5..5d605dd811b 100644 --- a/spec/support/shared_examples/uploaders/object_storage_shared_examples.rb +++ b/spec/support/shared_examples/uploaders/object_storage_shared_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_context 'with storage' do |store, **stub_params| before do subject.object_store = store diff --git a/spec/support/shared_examples/url_validator_examples.rb b/spec/support/shared_examples/url_validator_examples.rb index 25277ccd9aa..c5a775fefb6 100644 --- a/spec/support/shared_examples/url_validator_examples.rb +++ b/spec/support/shared_examples/url_validator_examples.rb @@ -1,10 +1,12 @@ +# frozen_string_literal: true + RSpec.shared_examples 'url validator examples' do |schemes| - let(:validator) { described_class.new(attributes: [:link_url], **options) } - let!(:badge) { build(:badge, link_url: 'http://www.example.com') } + describe '#validate' do + let(:validator) { described_class.new(attributes: [:link_url], **options) } + let(:badge) { build(:badge, link_url: 'http://www.example.com') } - subject { validator.validate(badge) } + subject { validator.validate(badge) } - describe '#validate' do context 'with no options' do let(:options) { {} } @@ -40,3 +42,52 @@ RSpec.shared_examples 'url validator examples' do |schemes| end end end + +RSpec.shared_examples 'public url validator examples' do |setting| + let(:validator) { described_class.new(attributes: [:link_url]) } + let(:badge) { build(:badge, link_url: 'http://www.example.com') } + + subject { validator.validate(badge) } + + context 'by default' do + it 'blocks urls pointing to localhost' do + badge.link_url = 'https://127.0.0.1' + + subject + + expect(badge.errors).to be_present + end + + it 'blocks urls pointing to the local network' do + badge.link_url = 'https://192.168.1.1' + + subject + + expect(badge.errors).to be_present + end + end + + context 'when local requests are allowed' do + let!(:settings) { create(:application_setting) } + + before do + stub_application_setting(setting) + end + + it 'does not block urls pointing to localhost' do + badge.link_url = 'https://127.0.0.1' + + subject + + expect(badge.errors).not_to be_present + end + + it 'does not block urls pointing to the local network' do + badge.link_url = 'https://192.168.1.1' + + subject + + expect(badge.errors).not_to be_present + end + end +end |