diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-06-16 18:25:58 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-06-16 18:25:58 +0000 |
commit | a5f4bba440d7f9ea47046a0a561d49adf0a1e6d4 (patch) | |
tree | fb69158581673816a8cd895f9d352dcb3c678b1e /spec/support/shared_examples | |
parent | d16b2e8639e99961de6ddc93909f3bb5c1445ba1 (diff) | |
download | gitlab-ce-a5f4bba440d7f9ea47046a0a561d49adf0a1e6d4.tar.gz |
Add latest changes from gitlab-org/gitlab@14-0-stable-eev14.0.0-rc42
Diffstat (limited to 'spec/support/shared_examples')
33 files changed, 1168 insertions, 280 deletions
diff --git a/spec/support/shared_examples/ci/badge_template_shared_examples.rb b/spec/support/shared_examples/ci/badge_template_shared_examples.rb new file mode 100644 index 00000000000..94aec33ecc2 --- /dev/null +++ b/spec/support/shared_examples/ci/badge_template_shared_examples.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'a badge template' do |badge_type| + describe '#key_text' do + it "says #{badge_type} by default" do + expect(template.key_text).to eq(badge_type) + end + + context 'when custom key_text is defined' do + before do + allow(badge).to receive(:customization).and_return({ key_text: "custom text" }) + end + + it 'returns custom value' do + expect(template.key_text).to eq("custom text") + end + + context 'when its size is larger than the max allowed value' do + before do + allow(badge).to receive(:customization).and_return({ key_text: 't' * (::Gitlab::Ci::Badge::Template::MAX_KEY_TEXT_SIZE + 1) } ) + end + + it 'returns default value' do + expect(template.key_text).to eq(badge_type) + end + end + end + end + + describe '#key_width' do + let_it_be(:default_key_width) { ::Gitlab::Ci::Badge::Template::DEFAULT_KEY_WIDTH } + + it 'is fixed by default' do + expect(template.key_width).to eq(default_key_width) + end + + context 'when custom key_width is defined' do + before do + allow(badge).to receive(:customization).and_return({ key_width: 101 }) + end + + it 'returns custom value' do + expect(template.key_width).to eq(101) + end + + context 'when it is larger than the max allowed value' do + before do + allow(badge).to receive(:customization).and_return({ key_width: ::Gitlab::Ci::Badge::Template::MAX_KEY_WIDTH + 1 }) + end + + it 'returns default value' do + expect(template.key_width).to eq(default_key_width) + end + end + end + end +end diff --git a/spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb b/spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb index cfee26a0d6a..9af35c189d0 100644 --- a/spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb +++ b/spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb @@ -298,7 +298,7 @@ RSpec.shared_examples 'wiki controller actions' do expect(response.headers['Content-Disposition']).to match(/^inline/) expect(response.headers[Gitlab::Workhorse::DETECT_HEADER]).to eq('true') expect(response.cache_control[:public]).to be(false) - expect(response.cache_control[:extras]).to include('no-store') + expect(response.headers['Cache-Control']).to eq('no-store') end end end @@ -486,7 +486,7 @@ RSpec.shared_examples 'wiki controller actions' do end.not_to change { wiki.list_pages.size } expect(response).to render_template('shared/wikis/edit') - expect(assigns(:error).message).to eq('Could not delete wiki page') + expect(assigns(:error)).to eq('Could not delete wiki page') end end end diff --git a/spec/support/shared_examples/features/discussion_comments_shared_example.rb b/spec/support/shared_examples/features/discussion_comments_shared_example.rb index 808e0be6be2..ff2878f77b4 100644 --- a/spec/support/shared_examples/features/discussion_comments_shared_example.rb +++ b/spec/support/shared_examples/features/discussion_comments_shared_example.rb @@ -11,6 +11,8 @@ RSpec.shared_examples 'thread comments for commit and snippet' do |resource_name let(:comment) { 'My comment' } it 'clicking "Comment" will post a comment' do + wait_for_all_requests + expect(page).to have_selector toggle_selector find("#{form_selector} .note-textarea").send_keys(comment) @@ -29,6 +31,8 @@ RSpec.shared_examples 'thread comments for commit and snippet' do |resource_name find("#{form_selector} .note-textarea").send_keys(comment) find(toggle_selector).click + + wait_for_all_requests end it 'has a "Comment" item (selected by default) and "Start thread" item' do diff --git a/spec/support/shared_examples/features/integrations/user_activates_mattermost_slash_command_integration_shared_examples.rb b/spec/support/shared_examples/features/integrations/user_activates_mattermost_slash_command_integration_shared_examples.rb new file mode 100644 index 00000000000..cfa043322db --- /dev/null +++ b/spec/support/shared_examples/features/integrations/user_activates_mattermost_slash_command_integration_shared_examples.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'user activates the Mattermost Slash Command integration' do + it 'shows a help message' do + expect(page).to have_content('Use this service to perform common') + end + + it 'shows a token placeholder' do + token_placeholder = find_field('service_token')['placeholder'] + + expect(token_placeholder).to eq('XXxxXXxxXXxxXXxxXXxxXXxx') + end + + it 'redirects to the integrations page after saving but not activating' do + token = ('a'..'z').to_a.join + + fill_in 'service_token', with: token + click_active_checkbox + click_save_integration + + expect(current_path).to eq(edit_path) + expect(page).to have_content('Mattermost slash commands settings saved, but not active.') + end + + it 'redirects to the integrations page after activating' do + token = ('a'..'z').to_a.join + + fill_in 'service_token', with: token + click_save_integration + + expect(current_path).to eq(edit_path) + expect(page).to have_content('Mattermost slash commands settings saved and active.') + end +end diff --git a/spec/support/shared_examples/features/issuable_invite_members_shared_examples.rb b/spec/support/shared_examples/features/issuable_invite_members_shared_examples.rb index 736c353c2aa..c0cfc27ceaf 100644 --- a/spec/support/shared_examples/features/issuable_invite_members_shared_examples.rb +++ b/spec/support/shared_examples/features/issuable_invite_members_shared_examples.rb @@ -1,9 +1,12 @@ # frozen_string_literal: true -RSpec.shared_examples 'issuable invite members experiments' do +RSpec.shared_examples 'issuable invite members' do context 'when a privileged user can invite' do - it 'shows a link for inviting members and launches invite modal' do + before do project.add_maintainer(user) + end + + it 'shows a link for inviting members and launches invite modal' do visit issuable_path find('.block.assignee .edit-link').click @@ -23,8 +26,11 @@ RSpec.shared_examples 'issuable invite members experiments' do end context 'when user cannot invite members in assignee dropdown' do - it 'shows author in assignee dropdown and no invite link' do + before do project.add_developer(user) + end + + it 'shows author in assignee dropdown and no invite link' do visit issuable_path find('.block.assignee .edit-link').click diff --git a/spec/support/shared_examples/features/variable_list_shared_examples.rb b/spec/support/shared_examples/features/variable_list_shared_examples.rb index 4b94411f009..997500415a9 100644 --- a/spec/support/shared_examples/features/variable_list_shared_examples.rb +++ b/spec/support/shared_examples/features/variable_list_shared_examples.rb @@ -283,6 +283,8 @@ RSpec.shared_examples 'variable list' do end def fill_variable(key, value, protected: false, masked: false) + wait_for_requests + page.within('#add-ci-variable') do find('[data-qa-selector="ci_variable_key_field"] input').set(key) find('[data-qa-selector="ci_variable_value_field"]').set(value) if value.present? diff --git a/spec/support/shared_examples/finders/assignees_filter_shared_examples.rb b/spec/support/shared_examples/finders/assignees_filter_shared_examples.rb index 96b05db4cd9..5cbbed1468f 100644 --- a/spec/support/shared_examples/finders/assignees_filter_shared_examples.rb +++ b/spec/support/shared_examples/finders/assignees_filter_shared_examples.rb @@ -24,6 +24,12 @@ RSpec.shared_examples 'assignee NOT username filter' do end end +RSpec.shared_examples 'assignee OR filter' do + it 'returns issuables assigned to the given users' do + expect(issuables).to contain_exactly(*expected_issuables) + end +end + RSpec.shared_examples 'no assignee filter' do let(:params) { { assignee_id: 'None' } } diff --git a/spec/support/shared_examples/graphql/mutations/can_mutate_spammable_examples.rb b/spec/support/shared_examples/graphql/mutations/can_mutate_spammable_examples.rb index fc795012ce7..5e15c91cd41 100644 --- a/spec/support/shared_examples/graphql/mutations/can_mutate_spammable_examples.rb +++ b/spec/support/shared_examples/graphql/mutations/can_mutate_spammable_examples.rb @@ -6,9 +6,9 @@ RSpec.shared_examples 'a mutation which can mutate a spammable' do describe "#additional_spam_params" do it 'passes additional spam params to the service' do args = [ - anything, - anything, - hash_including( + project: anything, + current_user: anything, + params: hash_including( api: true, request: instance_of(ActionDispatch::Request), captcha_response: captcha_response, diff --git a/spec/support/shared_examples/graphql/mutations/resolves_subscription_shared_examples.rb b/spec/support/shared_examples/graphql/mutations/resolves_subscription_shared_examples.rb index ebba312e895..678bb908343 100644 --- a/spec/support/shared_examples/graphql/mutations/resolves_subscription_shared_examples.rb +++ b/spec/support/shared_examples/graphql/mutations/resolves_subscription_shared_examples.rb @@ -2,44 +2,37 @@ require 'spec_helper' -RSpec.shared_examples 'a subscribeable graphql resource' do - let(:project) { resource.project } - let_it_be(:user) { create(:user) } - - subject(:mutation) { described_class.new(object: nil, context: { current_user: user }, field: nil) } +RSpec.shared_examples 'a subscribeable not accessible graphql resource' do + let(:mutation) { described_class.new(object: nil, context: { current_user: user }, field: nil) } - specify { expect(described_class).to require_graphql_authorizations(permission_name) } + subject { mutation.resolve(project_path: resource.project.full_path, iid: resource.iid, subscribed_state: true) } - describe '#resolve' do - let(:subscribe) { true } - let(:mutated_resource) { subject[resource.class.name.underscore.to_sym] } - - subject { mutation.resolve(project_path: resource.project.full_path, iid: resource.iid, subscribed_state: subscribe) } + it 'raises an error if the resource is not accessible to the user' do + expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) + end +end - it 'raises an error if the resource is not accessible to the user' do - expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) - end +RSpec.shared_examples 'a subscribeable graphql resource' do + let(:mutated_resource) { subject[resource.class.name.underscore.to_sym] } + let(:mutation) { described_class.new(object: nil, context: { current_user: user }, field: nil) } + let(:subscribe) { true } - context 'when the user can update the resource' do - before do - resource.project.add_developer(user) - end + subject { mutation.resolve(project_path: resource.project.full_path, iid: resource.iid, subscribed_state: subscribe) } - it 'subscribes to the resource' do - expect(mutated_resource).to eq(resource) - expect(mutated_resource.subscribed?(user, project)).to eq(true) - expect(subject[:errors]).to be_empty - end + it 'subscribes to the resource' do + expect(mutated_resource).to eq(resource) + expect(mutated_resource.subscribed?(user, project)).to eq(true) + expect(subject[:errors]).to be_empty + end - context 'when passing subscribe as false' do - let(:subscribe) { false } + context 'when passing subscribe as false' do + let(:subscribe) { false } - it 'unsubscribes from the discussion' do - resource.subscribe(user, project) + it 'unsubscribes from the discussion' do + resource.subscribe(user, project) - expect(mutated_resource.subscribed?(user, project)).to eq(false) - end - end + expect(mutated_resource.subscribed?(user, project)).to eq(false) + expect(subject[:errors]).to be_empty end end end diff --git a/spec/support/shared_examples/lib/gitlab/ci/ci_trace_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/ci/ci_trace_shared_examples.rb index 9c95d1ff9d9..3760325675a 100644 --- a/spec/support/shared_examples/lib/gitlab/ci/ci_trace_shared_examples.rb +++ b/spec/support/shared_examples/lib/gitlab/ci/ci_trace_shared_examples.rb @@ -29,6 +29,34 @@ RSpec.shared_examples 'common trace features' do end end + describe '#read' do + context 'gitlab_ci_archived_trace_consistent_reads feature flag enabled' do + before do + stub_feature_flags(gitlab_ci_archived_trace_consistent_reads: trace.job.project) + end + + it 'calls ::Gitlab::Database::LoadBalancing::Sticking.unstick_or_continue_sticking' do + expect(::Gitlab::Database::LoadBalancing::Sticking).to receive(:unstick_or_continue_sticking) + .with(described_class::LOAD_BALANCING_STICKING_NAMESPACE, trace.job.id) + .and_call_original + + trace.read { |stream| stream } + end + end + + context 'gitlab_ci_archived_trace_consistent_reads feature flag disabled' do + before do + stub_feature_flags(gitlab_ci_archived_trace_consistent_reads: false) + end + + it 'does not call ::Gitlab::Database::LoadBalancing::Sticking.unstick_or_continue_sticking' do + expect(::Gitlab::Database::LoadBalancing::Sticking).not_to receive(:unstick_or_continue_sticking) + + trace.read { |stream| stream } + end + end + end + describe '#extract_coverage' do let(:regex) { '\(\d+.\d+\%\) covered' } @@ -253,6 +281,52 @@ RSpec.shared_examples 'common trace features' do describe '#archive!' do subject { trace.archive! } + context 'when live trace chunks exists' do + before do + # Build a trace_chunk manually + # It is possible to do so with trace.set but only if ci_enable_live_trace FF is enabled + # + # We need the job to have a trace_chunk because we only use #stick in + # the case where trace_chunks exist. + stream = Gitlab::Ci::Trace::Stream.new do + Gitlab::Ci::Trace::ChunkedIO.new(trace.job) + end + + stream.set(+"12\n34") + end + + # We check the before setup actually sets up job trace_chunks + it 'has job trace_chunks' do + expect(trace.job.trace_chunks).to be_present + end + + context 'gitlab_ci_archived_trace_consistent_reads feature flag enabled' do + before do + stub_feature_flags(gitlab_ci_archived_trace_consistent_reads: trace.job.project) + end + + it 'calls ::Gitlab::Database::LoadBalancing::Sticking.stick' do + expect(::Gitlab::Database::LoadBalancing::Sticking).to receive(:stick) + .with(described_class::LOAD_BALANCING_STICKING_NAMESPACE, trace.job.id) + .and_call_original + + subject + end + end + + context 'gitlab_ci_archived_trace_consistent_reads feature flag disabled' do + before do + stub_feature_flags(gitlab_ci_archived_trace_consistent_reads: false) + end + + it 'does not call ::Gitlab::Database::LoadBalancing::Sticking.stick' do + expect(::Gitlab::Database::LoadBalancing::Sticking).not_to receive(:stick) + + subject + end + end + end + context 'when build status is success' do let!(:build) { create(:ci_build, :success, :trace_live) } diff --git a/spec/support/shared_examples/lib/gitlab/experimentation_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/experimentation_shared_examples.rb new file mode 100644 index 00000000000..5baa6478225 --- /dev/null +++ b/spec/support/shared_examples/lib/gitlab/experimentation_shared_examples.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'tracks assignment and records the subject' do |experiment, subject_type| + it 'tracks the assignment', :experiment do + expect(experiment(experiment)) + .to track(:assignment) + .with_context(subject_type => subject) + .on_next_instance + + action + end + + it 'records the subject' do + stub_experiments(experiment => :candidate) + + expect(Experiment).to receive(:add_subject).with(experiment.to_s, variant: :experimental, subject: subject) + + action + end +end diff --git a/spec/support/shared_examples/models/chat_service_shared_examples.rb b/spec/support/shared_examples/models/chat_integration_shared_examples.rb index 4a47aad0957..9f3be3e2e06 100644 --- a/spec/support/shared_examples/models/chat_service_shared_examples.rb +++ b/spec/support/shared_examples/models/chat_integration_shared_examples.rb @@ -1,13 +1,13 @@ # frozen_string_literal: true -RSpec.shared_examples "chat service" do |service_name| +RSpec.shared_examples "chat integration" do |integration_name| describe "Associations" do it { is_expected.to belong_to :project } it { is_expected.to have_one :service_hook } end describe "Validations" do - context "when service is active" do + context "when integration is active" do before do subject.active = true end @@ -16,7 +16,7 @@ RSpec.shared_examples "chat service" do |service_name| it_behaves_like "issue tracker service URL attribute", :webhook end - context "when service is inactive" do + context "when integration is inactive" do before do subject.active = false end @@ -47,12 +47,12 @@ RSpec.shared_examples "chat service" do |service_name| WebMock.stub_request(:post, webhook_url) end - shared_examples "triggered #{service_name} service" do |branches_to_be_notified: nil| + shared_examples "triggered #{integration_name} integration" do |branches_to_be_notified: nil| before do subject.branches_to_be_notified = branches_to_be_notified if branches_to_be_notified end - it "calls #{service_name} API" do + it "calls #{integration_name} API" do result = subject.execute(sample_data) expect(result).to be(true) @@ -63,12 +63,12 @@ RSpec.shared_examples "chat service" do |service_name| end end - shared_examples "untriggered #{service_name} service" do |branches_to_be_notified: nil| + shared_examples "untriggered #{integration_name} integration" do |branches_to_be_notified: nil| before do subject.branches_to_be_notified = branches_to_be_notified if branches_to_be_notified end - it "does not call #{service_name} API" do + it "does not call #{integration_name} API" do result = subject.execute(sample_data) expect(result).to be(false) @@ -81,7 +81,7 @@ RSpec.shared_examples "chat service" do |service_name| Gitlab::DataBuilder::Push.build_sample(project, user) end - it_behaves_like "triggered #{service_name} service" + it_behaves_like "triggered #{integration_name} integration" it "specifies the webhook when it is configured", if: defined?(client) do expect(client).to receive(:new).with(client_arguments).and_return(double(:chat_service).as_null_object) @@ -95,19 +95,19 @@ RSpec.shared_examples "chat service" do |service_name| end context "when only default branch are to be notified" do - it_behaves_like "triggered #{service_name} service", branches_to_be_notified: "default" + it_behaves_like "triggered #{integration_name} integration", branches_to_be_notified: "default" end context "when only protected branches are to be notified" do - it_behaves_like "untriggered #{service_name} service", branches_to_be_notified: "protected" + it_behaves_like "untriggered #{integration_name} integration", branches_to_be_notified: "protected" end context "when default and protected branches are to be notified" do - it_behaves_like "triggered #{service_name} service", branches_to_be_notified: "default_and_protected" + it_behaves_like "triggered #{integration_name} integration", branches_to_be_notified: "default_and_protected" end context "when all branches are to be notified" do - it_behaves_like "triggered #{service_name} service", branches_to_be_notified: "all" + it_behaves_like "triggered #{integration_name} integration", branches_to_be_notified: "all" end end @@ -121,19 +121,19 @@ RSpec.shared_examples "chat service" do |service_name| end context "when only default branch are to be notified" do - it_behaves_like "untriggered #{service_name} service", branches_to_be_notified: "default" + it_behaves_like "untriggered #{integration_name} integration", branches_to_be_notified: "default" end context "when only protected branches are to be notified" do - it_behaves_like "triggered #{service_name} service", branches_to_be_notified: "protected" + it_behaves_like "triggered #{integration_name} integration", branches_to_be_notified: "protected" end context "when default and protected branches are to be notified" do - it_behaves_like "triggered #{service_name} service", branches_to_be_notified: "default_and_protected" + it_behaves_like "triggered #{integration_name} integration", branches_to_be_notified: "default_and_protected" end context "when all branches are to be notified" do - it_behaves_like "triggered #{service_name} service", branches_to_be_notified: "all" + it_behaves_like "triggered #{integration_name} integration", branches_to_be_notified: "all" end end @@ -143,19 +143,19 @@ RSpec.shared_examples "chat service" do |service_name| end context "when only default branch are to be notified" do - it_behaves_like "untriggered #{service_name} service", branches_to_be_notified: "default" + it_behaves_like "untriggered #{integration_name} integration", branches_to_be_notified: "default" end context "when only protected branches are to be notified" do - it_behaves_like "untriggered #{service_name} service", branches_to_be_notified: "protected" + it_behaves_like "untriggered #{integration_name} integration", branches_to_be_notified: "protected" end context "when default and protected branches are to be notified" do - it_behaves_like "untriggered #{service_name} service", branches_to_be_notified: "default_and_protected" + it_behaves_like "untriggered #{integration_name} integration", branches_to_be_notified: "default_and_protected" end context "when all branches are to be notified" do - it_behaves_like "triggered #{service_name} service", branches_to_be_notified: "all" + it_behaves_like "triggered #{integration_name} integration", branches_to_be_notified: "all" end end end @@ -168,7 +168,7 @@ RSpec.shared_examples "chat service" do |service_name| service.hook_data(issue, "open") end - it_behaves_like "triggered #{service_name} service" + it_behaves_like "triggered #{integration_name} integration" end context "with merge events" do @@ -191,7 +191,7 @@ RSpec.shared_examples "chat service" do |service_name| project.add_developer(user) end - it_behaves_like "triggered #{service_name} service" + it_behaves_like "triggered #{integration_name} integration" end context "with wiki page events" do @@ -207,7 +207,7 @@ RSpec.shared_examples "chat service" do |service_name| let(:wiki_page) { create(:wiki_page, wiki: project.wiki, **opts) } let(:sample_data) { Gitlab::DataBuilder::WikiPage.build(wiki_page, user, "create") } - it_behaves_like "triggered #{service_name} service" + it_behaves_like "triggered #{integration_name} integration" end context "with note events" do @@ -222,7 +222,7 @@ RSpec.shared_examples "chat service" do |service_name| note: "a comment on a commit") end - it_behaves_like "triggered #{service_name} service" + it_behaves_like "triggered #{integration_name} integration" end context "with merge request comment" do @@ -230,7 +230,7 @@ RSpec.shared_examples "chat service" do |service_name| create(:note_on_merge_request, project: project, note: "merge request note") end - it_behaves_like "triggered #{service_name} service" + it_behaves_like "triggered #{integration_name} integration" end context "with issue comment" do @@ -238,7 +238,7 @@ RSpec.shared_examples "chat service" do |service_name| create(:note_on_issue, project: project, note: "issue note") end - it_behaves_like "triggered #{service_name} service" + it_behaves_like "triggered #{integration_name} integration" end context "with snippet comment" do @@ -246,7 +246,7 @@ RSpec.shared_examples "chat service" do |service_name| create(:note_on_project_snippet, project: project, note: "snippet note") end - it_behaves_like "triggered #{service_name} service" + it_behaves_like "triggered #{integration_name} integration" end end @@ -262,14 +262,14 @@ RSpec.shared_examples "chat service" do |service_name| context "with failed pipeline" do let(:status) { "failed" } - it_behaves_like "triggered #{service_name} service" + it_behaves_like "triggered #{integration_name} integration" end context "with succeeded pipeline" do let(:status) { "success" } context "with default notify_only_broken_pipelines" do - it "does not call #{service_name} API" do + it "does not call #{integration_name} API" do result = subject.execute(sample_data) expect(result).to be_falsy @@ -281,7 +281,7 @@ RSpec.shared_examples "chat service" do |service_name| subject.notify_only_broken_pipelines = false end - it_behaves_like "triggered #{service_name} service" + it_behaves_like "triggered #{integration_name} integration" end end @@ -291,19 +291,19 @@ RSpec.shared_examples "chat service" do |service_name| end context "when only default branch are to be notified" do - it_behaves_like "triggered #{service_name} service", branches_to_be_notified: "default" + it_behaves_like "triggered #{integration_name} integration", branches_to_be_notified: "default" end context "when only protected branches are to be notified" do - it_behaves_like "untriggered #{service_name} service", branches_to_be_notified: "protected" + it_behaves_like "untriggered #{integration_name} integration", branches_to_be_notified: "protected" end context "when default and protected branches are to be notified" do - it_behaves_like "triggered #{service_name} service", branches_to_be_notified: "default_and_protected" + it_behaves_like "triggered #{integration_name} integration", branches_to_be_notified: "default_and_protected" end context "when all branches are to be notified" do - it_behaves_like "triggered #{service_name} service", branches_to_be_notified: "all" + it_behaves_like "triggered #{integration_name} integration", branches_to_be_notified: "all" end end @@ -317,19 +317,19 @@ RSpec.shared_examples "chat service" do |service_name| end context "when only default branch are to be notified" do - it_behaves_like "untriggered #{service_name} service", branches_to_be_notified: "default" + it_behaves_like "untriggered #{integration_name} integration", branches_to_be_notified: "default" end context "when only protected branches are to be notified" do - it_behaves_like "triggered #{service_name} service", branches_to_be_notified: "protected" + it_behaves_like "triggered #{integration_name} integration", branches_to_be_notified: "protected" end context "when default and protected branches are to be notified" do - it_behaves_like "triggered #{service_name} service", branches_to_be_notified: "default_and_protected" + it_behaves_like "triggered #{integration_name} integration", branches_to_be_notified: "default_and_protected" end context "when all branches are to be notified" do - it_behaves_like "triggered #{service_name} service", branches_to_be_notified: "all" + it_behaves_like "triggered #{integration_name} integration", branches_to_be_notified: "all" end end @@ -339,19 +339,19 @@ RSpec.shared_examples "chat service" do |service_name| end context "when only default branch are to be notified" do - it_behaves_like "untriggered #{service_name} service", branches_to_be_notified: "default" + it_behaves_like "untriggered #{integration_name} integration", branches_to_be_notified: "default" end context "when only protected branches are to be notified" do - it_behaves_like "untriggered #{service_name} service", branches_to_be_notified: "protected" + it_behaves_like "untriggered #{integration_name} integration", branches_to_be_notified: "protected" end context "when default and protected branches are to be notified" do - it_behaves_like "untriggered #{service_name} service", branches_to_be_notified: "default_and_protected" + it_behaves_like "untriggered #{integration_name} integration", branches_to_be_notified: "default_and_protected" end context "when all branches are to be notified" do - it_behaves_like "triggered #{service_name} service", branches_to_be_notified: "all" + it_behaves_like "triggered #{integration_name} integration", branches_to_be_notified: "all" end end end diff --git a/spec/support/shared_examples/models/slack_mattermost_notifications_shared_examples.rb b/spec/support/shared_examples/models/concerns/integrations/slack_mattermost_notifier_shared_examples.rb index 09b7d1be704..66448aca2c5 100644 --- a/spec/support/shared_examples/models/slack_mattermost_notifications_shared_examples.rb +++ b/spec/support/shared_examples/models/concerns/integrations/slack_mattermost_notifier_shared_examples.rb @@ -1,13 +1,13 @@ # frozen_string_literal: true -RSpec.shared_examples 'slack or mattermost notifications' do |service_name| +RSpec.shared_examples Integrations::SlackMattermostNotifier do |service_name| include StubRequests let(:chat_service) { described_class.new } let(:webhook_url) { 'https://example.gitlab.com' } def execute_with_options(options) - receive(:new).with(webhook_url, options.merge(http_client: SlackMattermost::Notifier::HTTPClient)) + receive(:new).with(webhook_url, options.merge(http_client: Integrations::SlackMattermostNotifier::HTTPClient)) .and_return(double(:slack_service).as_null_object) end @@ -81,7 +81,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do |service_name| shared_examples 'calls the service API with the event message' do |event_message| specify do - expect_next_instance_of(Slack::Messenger) do |messenger| + expect_next_instance_of(::Slack::Messenger) do |messenger| expect(messenger).to receive(:ping).with(event_message, anything).and_call_original end @@ -95,7 +95,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do |service_name| let(:chat_service_params) { { username: 'slack_username' } } it 'uses the username as an option' do - expect(Slack::Messenger).to execute_with_options(username: 'slack_username') + expect(::Slack::Messenger).to execute_with_options(username: 'slack_username') execute_service end @@ -110,7 +110,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do |service_name| let(:chat_service_params) { { push_channel: 'random' } } it 'uses the right channel for push event' do - expect(Slack::Messenger).to execute_with_options(channel: ['random']) + expect(::Slack::Messenger).to execute_with_options(channel: ['random']) execute_service end @@ -128,6 +128,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do |service_name| context 'issue events' do let_it_be(:issue) { create(:issue) } + let(:data) { issue.to_hook_data(user) } it_behaves_like 'calls the service API with the event message', /Issue (.*?) opened by/ @@ -136,7 +137,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do |service_name| let(:chat_service_params) { { issue_channel: 'random' } } it 'uses the right channel for issue event' do - expect(Slack::Messenger).to execute_with_options(channel: ['random']) + expect(::Slack::Messenger).to execute_with_options(channel: ['random']) execute_service end @@ -147,7 +148,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do |service_name| end it 'falls back to issue channel' do - expect(Slack::Messenger).to execute_with_options(channel: ['random']) + expect(::Slack::Messenger).to execute_with_options(channel: ['random']) execute_service end @@ -156,7 +157,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do |service_name| let(:chat_service_params) { { issue_channel: 'random', confidential_issue_channel: 'confidential' } } it 'uses the confidential issue channel when it is defined' do - expect(Slack::Messenger).to execute_with_options(channel: ['confidential']) + expect(::Slack::Messenger).to execute_with_options(channel: ['confidential']) execute_service end @@ -167,6 +168,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do |service_name| context 'merge request events' do let_it_be(:merge_request) { create(:merge_request) } + let(:data) { merge_request.to_hook_data(user) } it_behaves_like 'calls the service API with the event message', /opened merge request/ @@ -175,7 +177,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do |service_name| let(:chat_service_params) { { merge_request_channel: 'random' } } it 'uses the right channel for merge request event' do - expect(Slack::Messenger).to execute_with_options(channel: ['random']) + expect(::Slack::Messenger).to execute_with_options(channel: ['random']) execute_service end @@ -184,15 +186,16 @@ RSpec.shared_examples 'slack or mattermost notifications' do |service_name| context 'wiki page events' do let_it_be(:wiki_page) { create(:wiki_page, wiki: project.wiki, message: 'user created page: Awesome wiki_page') } + let(:data) { Gitlab::DataBuilder::WikiPage.build(wiki_page, user, 'create') } - it_behaves_like 'calls the service API with the event message', / created (.*?)wikis\/(.*?)|wiki page> in/ + it_behaves_like 'calls the service API with the event message', %r{ created (.*?)wikis/(.*?)|wiki page> in} context 'with event channel' do let(:chat_service_params) { { wiki_page_channel: 'random' } } it 'uses the right channel for wiki event' do - expect(Slack::Messenger).to execute_with_options(channel: ['random']) + expect(::Slack::Messenger).to execute_with_options(channel: ['random']) execute_service end @@ -201,6 +204,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do |service_name| context 'deployment events' do let_it_be(:deployment) { create(:deployment) } + let(:data) { Gitlab::DataBuilder::Deployment.build(deployment, Time.current) } it_behaves_like 'calls the service API with the event message', /Deploy to (.*?) created/ @@ -208,6 +212,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do |service_name| context 'note event' do let_it_be(:issue_note) { create(:note_on_issue, project: project, note: "issue note") } + let(:data) { Gitlab::DataBuilder::Note.build(issue_note, user) } it_behaves_like 'calls the service API with the event message', /commented on issue/ @@ -216,7 +221,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do |service_name| let(:chat_service_params) { { note_channel: 'random' } } it 'uses the right channel' do - expect(Slack::Messenger).to execute_with_options(channel: ['random']) + expect(::Slack::Messenger).to execute_with_options(channel: ['random']) execute_service end @@ -227,7 +232,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do |service_name| end it 'falls back to note channel' do - expect(Slack::Messenger).to execute_with_options(channel: ['random']) + expect(::Slack::Messenger).to execute_with_options(channel: ['random']) execute_service end @@ -236,7 +241,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do |service_name| let(:chat_service_params) { { note_channel: 'random', confidential_note_channel: 'confidential' } } it 'uses confidential channel' do - expect(Slack::Messenger).to execute_with_options(channel: ['confidential']) + expect(::Slack::Messenger).to execute_with_options(channel: ['confidential']) execute_service end diff --git a/spec/support/shared_examples/models/concerns/timebox_shared_examples.rb b/spec/support/shared_examples/models/concerns/timebox_shared_examples.rb index 68142e667a4..39121b73bc5 100644 --- a/spec/support/shared_examples/models/concerns/timebox_shared_examples.rb +++ b/spec/support/shared_examples/models/concerns/timebox_shared_examples.rb @@ -86,45 +86,6 @@ RSpec.shared_examples 'a timebox' do |timebox_type| expect(timebox.errors[:project_id]).to include("#{timebox_type} should belong either to a project or a group.") end end - - describe "#uniqueness_of_title" do - context "per project" do - it "does not accept the same title in a project twice" do - new_timebox = timebox.dup - expect(new_timebox).not_to be_valid - end - - it "accepts the same title in another project" do - project = create(:project) - new_timebox = timebox.dup - new_timebox.project = project - - expect(new_timebox).to be_valid - end - end - - context "per group" do - let(:timebox) { create(timebox_type, *timebox_args, group: group) } - - before do - project.update!(group: group) - end - - it "does not accept the same title in a group twice" do - new_timebox = described_class.new(group: group, title: timebox.title) - - expect(new_timebox).not_to be_valid - end - - it "does not accept the same title of a child project timebox" do - create(timebox_type, *timebox_args, project: group.projects.first) - - new_timebox = described_class.new(group: group, title: timebox.title) - - expect(new_timebox).not_to be_valid - end - end - end end describe "Associations" do diff --git a/spec/support/shared_examples/models/chat_slash_commands_shared_examples.rb b/spec/support/shared_examples/models/integrations/base_slash_commands_shared_examples.rb index 49729afce61..128999d02fa 100644 --- a/spec/support/shared_examples/models/chat_slash_commands_shared_examples.rb +++ b/spec/support/shared_examples/models/integrations/base_slash_commands_shared_examples.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -RSpec.shared_examples 'chat slash commands service' do +RSpec.shared_examples Integrations::BaseSlashCommands do describe "Associations" do it { is_expected.to respond_to :token } it { is_expected.to have_many :chat_names } diff --git a/spec/support/shared_examples/models/mentionable_shared_examples.rb b/spec/support/shared_examples/models/mentionable_shared_examples.rb index 2392658e584..04630484964 100644 --- a/spec/support/shared_examples/models/mentionable_shared_examples.rb +++ b/spec/support/shared_examples/models/mentionable_shared_examples.rb @@ -66,7 +66,7 @@ RSpec.shared_examples 'a mentionable' do expect(subject.gfm_reference).to eq(backref_text) end - it "extracts references from its reference property" do + it "extracts references from its reference property", :clean_gitlab_redis_cache do # De-duplicate and omit itself refs = subject.referenced_mentionables expect(refs.size).to eq(6) @@ -98,7 +98,7 @@ RSpec.shared_examples 'a mentionable' do end end - it 'creates cross-reference notes' do + it 'creates cross-reference notes', :clean_gitlab_redis_cache do mentioned_objects = [mentioned_issue, mentioned_mr, mentioned_commit, ext_issue, ext_mr, ext_commit] diff --git a/spec/support/shared_examples/models/packages/debian/component_file_shared_example.rb b/spec/support/shared_examples/models/packages/debian/component_file_shared_example.rb index e6b16d5881d..f08ee820463 100644 --- a/spec/support/shared_examples/models/packages/debian/component_file_shared_example.rb +++ b/spec/support/shared_examples/models/packages/debian/component_file_shared_example.rb @@ -142,6 +142,14 @@ RSpec.shared_examples 'Debian Component File' do |container_type, can_freeze| end end + describe '.with_architecture' do + subject { described_class.with_architecture(architecture1_2) } + + it do + expect(subject.to_a).to contain_exactly(component_file_other_architecture) + end + end + describe '.with_architecture_name' do subject { described_class.with_architecture_name(architecture1_2.name) } @@ -166,12 +174,12 @@ RSpec.shared_examples 'Debian Component File' do |container_type, can_freeze| end end - describe '.created_before' do - let_it_be(:component_file1) { create("debian_#{container_type}_component_file", component: component1_1, architecture: architecture1_1, created_at: 4.hours.ago) } - let_it_be(:component_file2) { create("debian_#{container_type}_component_file", component: component1_1, architecture: architecture1_1, created_at: 3.hours.ago) } - let_it_be(:component_file3) { create("debian_#{container_type}_component_file", component: component1_1, architecture: architecture1_1, created_at: 1.hour.ago) } + describe '.updated_before' do + let_it_be(:component_file1) { create("debian_#{container_type}_component_file", component: component1_1, architecture: architecture1_1, updated_at: 4.hours.ago) } + let_it_be(:component_file2) { create("debian_#{container_type}_component_file", component: component1_1, architecture: architecture1_1, updated_at: 3.hours.ago) } + let_it_be(:component_file3) { create("debian_#{container_type}_component_file", component: component1_1, architecture: architecture1_1, updated_at: 1.hour.ago) } - subject { described_class.created_before(2.hours.ago) } + subject { described_class.updated_before(2.hours.ago) } it do expect(subject.to_a).to contain_exactly(component_file1, component_file2) diff --git a/spec/support/shared_examples/models/packages/debian/distribution_key_shared_examples.rb b/spec/support/shared_examples/models/packages/debian/distribution_key_shared_examples.rb new file mode 100644 index 00000000000..26794c83736 --- /dev/null +++ b/spec/support/shared_examples/models/packages/debian/distribution_key_shared_examples.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.shared_examples 'Debian Distribution Key' do |container| + let_it_be_with_refind(:distribution_key) { create("debian_#{container}_distribution_key") } # rubocop:disable Rails/SaveBang + + subject { distribution_key } + + describe 'relationships' do + it { is_expected.to belong_to(:distribution).class_name("Packages::Debian::#{container.capitalize}Distribution").inverse_of(:key) } + end + + describe 'validations' do + describe "#distribution" do + it { is_expected.to validate_presence_of(:distribution) } + end + + describe '#private_key' do + it { is_expected.to validate_presence_of(:private_key) } + + it { is_expected.to allow_value("-----BEGIN PGP PRIVATE KEY BLOCK-----\n...").for(:private_key) } + it { is_expected.not_to allow_value('A').for(:private_key).with_message('must be ASCII armored') } + end + + describe '#passphrase' do + it { is_expected.to validate_presence_of(:passphrase) } + + it { is_expected.to allow_value('P@$$w0rd').for(:passphrase) } + it { is_expected.to allow_value('A' * 255).for(:passphrase) } + it { is_expected.not_to allow_value('A' * 256).for(:passphrase) } + end + + describe '#public_key' do + it { is_expected.to validate_presence_of(:public_key) } + + it { is_expected.to allow_value("-----BEGIN PGP PUBLIC KEY BLOCK-----\n...").for(:public_key) } + it { is_expected.not_to allow_value('A').for(:public_key).with_message('must be ASCII armored') } + end + + describe '#fingerprint' do + it { is_expected.to validate_presence_of(:passphrase) } + + it { is_expected.to allow_value('abc').for(:passphrase) } + it { is_expected.to allow_value('A' * 255).for(:passphrase) } + it { is_expected.not_to allow_value('A' * 256).for(:passphrase) } + end + end +end diff --git a/spec/support/shared_examples/models/packages/debian/distribution_shared_examples.rb b/spec/support/shared_examples/models/packages/debian/distribution_shared_examples.rb index 8693d6868e9..5459d17b1df 100644 --- a/spec/support/shared_examples/models/packages/debian/distribution_shared_examples.rb +++ b/spec/support/shared_examples/models/packages/debian/distribution_shared_examples.rb @@ -17,6 +17,7 @@ RSpec.shared_examples 'Debian Distribution' do |factory, container, can_freeze| it { is_expected.to belong_to(container) } it { is_expected.to belong_to(:creator).class_name('User') } + it { is_expected.to have_one(:key).class_name("Packages::Debian::#{container.capitalize}DistributionKey").with_foreign_key(:distribution_id).inverse_of(:distribution) } it { is_expected.to have_many(:components).class_name("Packages::Debian::#{container.capitalize}Component").inverse_of(:distribution) } it { is_expected.to have_many(:architectures).class_name("Packages::Debian::#{container.capitalize}Architecture").inverse_of(:distribution) } end diff --git a/spec/support/shared_examples/namespaces/linear_traversal_examples.rb b/spec/support/shared_examples/namespaces/linear_traversal_examples.rb new file mode 100644 index 00000000000..2fd90c36953 --- /dev/null +++ b/spec/support/shared_examples/namespaces/linear_traversal_examples.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +# Traversal examples common to linear and recursive methods are in +# spec/support/shared_examples/namespaces/traversal_examples.rb + +RSpec.shared_examples 'linear namespace traversal' do + context 'when use_traversal_ids feature flag is enabled' do + before do + stub_feature_flags(use_traversal_ids: true) + end + + context 'scopes' do + describe '.as_ids' do + let_it_be(:namespace1) { create(:group) } + let_it_be(:namespace2) { create(:group) } + + subject { Namespace.where(id: [namespace1, namespace2]).as_ids.pluck(:id) } + + it { is_expected.to contain_exactly(namespace1.id, namespace2.id) } + end + end + end +end diff --git a/spec/support/shared_examples/namespaces/traversal_examples.rb b/spec/support/shared_examples/namespaces/traversal_examples.rb index 77a1705627e..ccc64c80fd4 100644 --- a/spec/support/shared_examples/namespaces/traversal_examples.rb +++ b/spec/support/shared_examples/namespaces/traversal_examples.rb @@ -17,6 +17,28 @@ RSpec.shared_examples 'namespace traversal' do end end + describe '#root_ancestor' do + let_it_be(:group) { create(:group) } + let_it_be(:nested_group) { create(:group, parent: group) } + let_it_be(:deep_nested_group) { create(:group, parent: nested_group) } + + it 'returns the correct root ancestor' do + expect(group.root_ancestor).to eq(group) + expect(nested_group.root_ancestor).to eq(group) + expect(deep_nested_group.root_ancestor).to eq(group) + end + + describe '#recursive_root_ancestor' do + let(:groups) { [group, nested_group, deep_nested_group] } + + it "is equivalent to #recursive_root_ancestor" do + groups.each do |group| + expect(group.root_ancestor).to eq(group.recursive_root_ancestor) + end + end + end + end + describe '#self_and_hierarchy' do let!(:group) { create(:group, path: 'git_lab') } let!(:nested_group) { create(:group, parent: group) } @@ -122,4 +144,20 @@ RSpec.shared_examples 'namespace traversal' do it_behaves_like 'recursive version', :self_and_descendants end end + + describe '#self_and_descendant_ids' do + let!(:group) { create(:group, path: 'git_lab') } + let!(:nested_group) { create(:group, parent: group) } + let!(:deep_nested_group) { create(:group, parent: nested_group) } + + subject { group.self_and_descendant_ids.pluck(:id) } + + it { is_expected.to contain_exactly(group.id, nested_group.id, deep_nested_group.id) } + + describe '#recursive_self_and_descendant_ids' do + let(:groups) { [group, nested_group, deep_nested_group] } + + it_behaves_like 'recursive version', :self_and_descendant_ids + end + end end diff --git a/spec/support/shared_examples/requests/api/conan_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/conan_packages_shared_examples.rb index c938c6432fe..20606ae942d 100644 --- a/spec/support/shared_examples/requests/api/conan_packages_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/conan_packages_shared_examples.rb @@ -294,16 +294,6 @@ RSpec.shared_examples 'rejects invalid upload_url params' do end end -RSpec.shared_examples 'successful response when using Unicorn' do - context 'on Unicorn', :unicorn do - it 'returns successfully' do - subject - - expect(response).to have_gitlab_http_status(:ok) - end - end -end - RSpec.shared_examples 'recipe snapshot endpoint' do subject { get api(url), headers: headers } @@ -372,7 +362,6 @@ RSpec.shared_examples 'recipe upload_urls endpoint' do it_behaves_like 'rejects invalid recipe' it_behaves_like 'rejects invalid upload_url params' - it_behaves_like 'successful response when using Unicorn' it 'returns a set of upload urls for the files requested' do subject @@ -434,7 +423,6 @@ RSpec.shared_examples 'package upload_urls endpoint' do it_behaves_like 'rejects invalid recipe' it_behaves_like 'rejects invalid upload_url params' - it_behaves_like 'successful response when using Unicorn' it 'returns a set of upload urls for the files requested' do expected_response = { diff --git a/spec/support/shared_examples/requests/api/debian_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/debian_packages_shared_examples.rb index dfd19167dcd..0530aa8c760 100644 --- a/spec/support/shared_examples/requests/api/debian_packages_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/debian_packages_shared_examples.rb @@ -12,7 +12,35 @@ RSpec.shared_context 'Debian repository shared context' do |container_type, can_ let_it_be(:user, freeze: true) { create(:user) } let_it_be(:personal_access_token, freeze: true) { create(:personal_access_token, user: user) } - let(:distribution) { 'bullseye' } + let_it_be(:private_distribution, freeze: true) { create("debian_#{container_type}_distribution", container: private_container, codename: 'existing-codename') } + let_it_be(:private_component, freeze: true) { create("debian_#{container_type}_component", distribution: private_distribution, name: 'existing-component') } + let_it_be(:private_architecture_all, freeze: true) { create("debian_#{container_type}_architecture", distribution: private_distribution, name: 'all') } + let_it_be(:private_architecture, freeze: true) { create("debian_#{container_type}_architecture", distribution: private_distribution, name: 'existing-arch') } + + let_it_be(:public_distribution, freeze: true) { create("debian_#{container_type}_distribution", container: public_container, codename: 'existing-codename') } + let_it_be(:public_component, freeze: true) { create("debian_#{container_type}_component", distribution: public_distribution, name: 'existing-component') } + let_it_be(:public_architecture_all, freeze: true) { create("debian_#{container_type}_architecture", distribution: public_distribution, name: 'all') } + let_it_be(:public_architecture, freeze: true) { create("debian_#{container_type}_architecture", distribution: public_distribution, name: 'existing-arch') } + + if container_type == :group + let_it_be(:private_project) { create(:project, :private, group: private_container) } + let_it_be(:public_project) { create(:project, :public, group: public_container) } + let_it_be(:private_project_distribution) { create(:debian_project_distribution, container: private_project, codename: 'existing-codename') } + let_it_be(:public_project_distribution) { create(:debian_project_distribution, container: public_project, codename: 'existing-codename') } + else + let_it_be(:private_project) { private_container } + let_it_be(:public_project) { public_container } + let_it_be(:private_project_distribution) { private_distribution } + let_it_be(:public_project_distribution) { public_distribution } + end + + let_it_be(:private_package) { create(:debian_package, project: private_project, published_in: private_project_distribution) } + let_it_be(:public_package) { create(:debian_package, project: public_project, published_in: public_project_distribution) } + + let(:visibility_level) { :public } + + let(:distribution) { { private: private_distribution, public: public_distribution }[visibility_level] } + let(:component) { 'main' } let(:architecture) { 'amd64' } let(:source_package) { 'sample' } @@ -97,7 +125,7 @@ RSpec.shared_examples 'Debian repository GET request' do |status, body = nil| expect(response).to have_gitlab_http_status(status) unless body.nil? - expect(response.body).to eq(body) + expect(response.body).to match(body) end end end @@ -107,16 +135,25 @@ RSpec.shared_examples 'Debian repository upload request' do |status, body = nil| if status == :created it 'creates package files', :aggregate_failures do - pending "Debian package creation not implemented" + expect(::Packages::Debian::FindOrCreateIncomingService).to receive(:new).with(container, user).and_call_original + expect(::Packages::Debian::CreatePackageFileService).to receive(:new).with(be_a(Packages::Package), be_an(Hash)).and_call_original + + if file_name.end_with? '.changes' + expect(::Packages::Debian::ProcessChangesWorker).to receive(:perform_async) + else + expect(::Packages::Debian::ProcessChangesWorker).not_to receive(:perform_async) + end expect { subject } .to change { container.packages.debian.count }.by(1) + .and change { container.packages.debian.where(name: 'incoming').count }.by(1) + .and change { container.package_files.count }.by(1) expect(response).to have_gitlab_http_status(status) expect(response.media_type).to eq('text/plain') unless body.nil? - expect(response.body).to eq(body) + expect(response.body).to match(body) end end it_behaves_like 'a package tracking event', described_class.name, 'push_package' @@ -127,7 +164,7 @@ RSpec.shared_examples 'Debian repository upload request' do |status, body = nil| expect(response).to have_gitlab_http_status(status) unless body.nil? - expect(response.body).to eq(body) + expect(response.body).to match(body) end end end @@ -173,18 +210,112 @@ RSpec.shared_examples 'Debian repository upload authorize request' do |status, b expect(response).to have_gitlab_http_status(status) unless body.nil? - expect(response.body).to eq(body) + expect(response.body).to match(body) + end + end + end +end + +RSpec.shared_examples 'Debian repository POST distribution request' do |status, body| + and_body = body.nil? ? '' : ' and expected body' + + if status == :created + it 'creates distribution', :aggregate_failures do + expect(::Packages::Debian::CreateDistributionService).to receive(:new).with(container, user, api_params).and_call_original + + expect { subject } + .to change { Packages::Debian::GroupDistribution.all.count + Packages::Debian::ProjectDistribution.all.count }.by(1) + .and change { Packages::Debian::GroupComponent.all.count + Packages::Debian::ProjectComponent.all.count }.by(1) + .and change { Packages::Debian::GroupArchitecture.all.count + Packages::Debian::ProjectArchitecture.all.count }.by(2) + + expect(response).to have_gitlab_http_status(status) + expect(response.media_type).to eq('application/json') + + unless body.nil? + expect(response.body).to match(body) + end + end + else + it "returns #{status}#{and_body}", :aggregate_failures do + subject + + expect(response).to have_gitlab_http_status(status) + + unless body.nil? + expect(response.body).to match(body) + end + end + end +end + +RSpec.shared_examples 'Debian repository PUT distribution request' do |status, body| + and_body = body.nil? ? '' : ' and expected body' + + if status == :success + it 'updates distribution', :aggregate_failures do + expect(::Packages::Debian::UpdateDistributionService).to receive(:new).with(distribution, api_params.except(:codename)).and_call_original + + expect { subject } + .to not_change { Packages::Debian::GroupDistribution.all.count + Packages::Debian::ProjectDistribution.all.count } + .and not_change { Packages::Debian::GroupComponent.all.count + Packages::Debian::ProjectComponent.all.count } + .and not_change { Packages::Debian::GroupArchitecture.all.count + Packages::Debian::ProjectArchitecture.all.count } + + expect(response).to have_gitlab_http_status(status) + expect(response.media_type).to eq('application/json') + + unless body.nil? + expect(response.body).to match(body) + end + end + else + it "returns #{status}#{and_body}", :aggregate_failures do + subject + + expect(response).to have_gitlab_http_status(status) + + unless body.nil? + expect(response.body).to match(body) end end end end -RSpec.shared_examples 'rejects Debian access with unknown container id' do +RSpec.shared_examples 'Debian repository DELETE distribution request' do |status, body| + and_body = body.nil? ? '' : ' and expected body' + + if status == :success + it 'updates distribution', :aggregate_failures do + expect { subject } + .to change { Packages::Debian::GroupDistribution.all.count + Packages::Debian::ProjectDistribution.all.count }.by(-1) + .and change { Packages::Debian::GroupComponent.all.count + Packages::Debian::ProjectComponent.all.count }.by(-1) + .and change { Packages::Debian::GroupArchitecture.all.count + Packages::Debian::ProjectArchitecture.all.count }.by(-2) + + expect(response).to have_gitlab_http_status(status) + expect(response.media_type).to eq('application/json') + + unless body.nil? + expect(response.body).to match(body) + end + end + else + it "returns #{status}#{and_body}", :aggregate_failures do + subject + + expect(response).to have_gitlab_http_status(status) + + unless body.nil? + expect(response.body).to match(body) + end + end + end +end + +RSpec.shared_examples 'rejects Debian access with unknown container id' do |hidden_status| context 'with an unknown container' do let(:container) { double(id: non_existing_record_id) } context 'as anonymous' do - it_behaves_like 'Debian repository GET request', :unauthorized, nil + it_behaves_like 'Debian repository GET request', hidden_status, nil end context 'as authenticated user' do @@ -195,19 +326,25 @@ RSpec.shared_examples 'rejects Debian access with unknown container id' do end end -RSpec.shared_examples 'Debian repository read endpoint' do |desired_behavior, success_status, success_body| +RSpec.shared_examples 'Debian repository read endpoint' do |desired_behavior, success_status, success_body, authenticate_non_public: true| + hidden_status = if authenticate_non_public + :unauthorized + else + :not_found + end + context 'with valid container' do using RSpec::Parameterized::TableSyntax where(:visibility_level, :user_role, :member, :user_token, :expected_status, :expected_body) do :public | :developer | true | true | success_status | success_body :public | :guest | true | true | success_status | success_body - :public | :developer | true | false | success_status | success_body - :public | :guest | true | false | success_status | success_body + :public | :developer | true | false | :unauthorized | nil + :public | :guest | true | false | :unauthorized | nil :public | :developer | false | true | success_status | success_body :public | :guest | false | true | success_status | success_body - :public | :developer | false | false | success_status | success_body - :public | :guest | false | false | success_status | success_body + :public | :developer | false | false | :unauthorized | nil + :public | :guest | false | false | :unauthorized | nil :public | :anonymous | false | true | success_status | success_body :private | :developer | true | true | success_status | success_body :private | :guest | true | true | :forbidden | nil @@ -217,7 +354,7 @@ RSpec.shared_examples 'Debian repository read endpoint' do |desired_behavior, su :private | :guest | false | true | :not_found | nil :private | :developer | false | false | :unauthorized | nil :private | :guest | false | false | :unauthorized | nil - :private | :anonymous | false | true | :unauthorized | nil + :private | :anonymous | false | true | hidden_status | nil end with_them do @@ -227,10 +364,16 @@ RSpec.shared_examples 'Debian repository read endpoint' do |desired_behavior, su end end - it_behaves_like 'rejects Debian access with unknown container id' + it_behaves_like 'rejects Debian access with unknown container id', hidden_status end -RSpec.shared_examples 'Debian repository write endpoint' do |desired_behavior, success_status, success_body| +RSpec.shared_examples 'Debian repository write endpoint' do |desired_behavior, success_status, success_body, authenticate_non_public: true| + hidden_status = if authenticate_non_public + :unauthorized + else + :not_found + end + context 'with valid container' do using RSpec::Parameterized::TableSyntax @@ -252,7 +395,50 @@ RSpec.shared_examples 'Debian repository write endpoint' do |desired_behavior, s :private | :guest | false | true | :not_found | nil :private | :developer | false | false | :unauthorized | nil :private | :guest | false | false | :unauthorized | nil - :private | :anonymous | false | true | :unauthorized | nil + :private | :anonymous | false | true | hidden_status | nil + end + + with_them do + include_context 'Debian repository access', params[:visibility_level], params[:user_role], params[:member], params[:user_token], :basic do + it_behaves_like "Debian repository #{desired_behavior}", params[:expected_status], params[:expected_body] + end + end + end + + it_behaves_like 'rejects Debian access with unknown container id', hidden_status +end + +RSpec.shared_examples 'Debian repository maintainer write endpoint' do |desired_behavior, success_status, success_body, authenticate_non_public: true| + hidden_status = if authenticate_non_public + :unauthorized + else + :not_found + end + + context 'with valid container' do + using RSpec::Parameterized::TableSyntax + + where(:visibility_level, :user_role, :member, :user_token, :expected_status, :expected_body) do + :public | :maintainer | true | true | success_status | success_body + :public | :developer | true | true | :forbidden | nil + :public | :guest | true | true | :forbidden | nil + :public | :maintainer | true | false | :unauthorized | nil + :public | :guest | true | false | :unauthorized | nil + :public | :maintainer | false | true | :forbidden | nil + :public | :guest | false | true | :forbidden | nil + :public | :maintainer | false | false | :unauthorized | nil + :public | :guest | false | false | :unauthorized | nil + :public | :anonymous | false | true | :unauthorized | nil + :private | :maintainer | true | true | success_status | success_body + :private | :developer | true | true | :forbidden | nil + :private | :guest | true | true | :forbidden | nil + :private | :maintainer | true | false | :unauthorized | nil + :private | :guest | true | false | :unauthorized | nil + :private | :maintainer | false | true | :not_found | nil + :private | :guest | false | true | :not_found | nil + :private | :maintainer | false | false | :unauthorized | nil + :private | :guest | false | false | :unauthorized | nil + :private | :anonymous | false | true | hidden_status | nil end with_them do @@ -262,5 +448,5 @@ RSpec.shared_examples 'Debian repository write endpoint' do |desired_behavior, s end end - it_behaves_like 'rejects Debian access with unknown container id' + it_behaves_like 'rejects Debian access with unknown container id', hidden_status end diff --git a/spec/support/shared_examples/requests/api/graphql/packages/package_details_shared_examples.rb b/spec/support/shared_examples/requests/api/graphql/packages/package_details_shared_examples.rb new file mode 100644 index 00000000000..41a61ba5fd7 --- /dev/null +++ b/spec/support/shared_examples/requests/api/graphql/packages/package_details_shared_examples.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'a package detail' do + it_behaves_like 'a working graphql query' do + it 'matches the JSON schema' do + expect(package_details).to match_schema('graphql/packages/package_details') + end + end +end + +RSpec.shared_examples 'a package with files' do + it 'has the right amount of files' do + expect(package_files_response.length).to be(package.package_files.length) + end + + it 'has the basic package files data' do + expect(first_file_response).to include( + 'id' => global_id_of(first_file), + 'fileName' => first_file.file_name, + 'size' => first_file.size.to_s, + 'downloadPath' => first_file.download_path, + 'fileSha1' => first_file.file_sha1, + 'fileMd5' => first_file.file_md5, + 'fileSha256' => first_file.file_sha256 + ) + end +end diff --git a/spec/support/shared_examples/requests/api/helm_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/helm_packages_shared_examples.rb new file mode 100644 index 00000000000..585c4fb8a4e --- /dev/null +++ b/spec/support/shared_examples/requests/api/helm_packages_shared_examples.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'rejects helm packages access' do |user_type, status, add_member = true| + context "for user type #{user_type}" do + before do + project.send("add_#{user_type}", user) if add_member && user_type != :anonymous + end + + it_behaves_like 'returning response status', status + + if status == :unauthorized + it 'has the correct response header' do + subject + + expect(response.headers['WWW-Authenticate']).to eq 'Basic realm="GitLab Packages Registry"' + end + end + end +end + +RSpec.shared_examples 'process helm download content request' do |user_type, status, add_member = true| + context "for user type #{user_type}" do + before do + project.send("add_#{user_type}", user) if add_member && user_type != :anonymous + end + + it_behaves_like 'returning response status', status + + it_behaves_like 'a package tracking event', 'API::HelmPackages', 'pull_package' + + it 'returns a valid package archive' do + subject + + expect(response.media_type).to eq('application/octet-stream') + end + end +end + +RSpec.shared_examples 'rejects helm access with unknown project id' do + context 'with an unknown project' do + let(:project) { OpenStruct.new(id: 1234567890) } + + context 'as anonymous' do + it_behaves_like 'rejects helm packages access', :anonymous, :unauthorized + end + + context 'as authenticated user' do + subject { get api(url), headers: basic_auth_header(user.username, personal_access_token.token) } + + it_behaves_like 'rejects helm packages access', :anonymous, :not_found + end + end +end diff --git a/spec/support/shared_examples/requests/api/packages_shared_examples.rb b/spec/support/shared_examples/requests/api/packages_shared_examples.rb index eb86b7c37d5..42c29084d7b 100644 --- a/spec/support/shared_examples/requests/api/packages_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/packages_shared_examples.rb @@ -146,6 +146,6 @@ RSpec.shared_examples 'a package tracking event' do |category, action| it "creates a gitlab tracking event #{action}", :snowplow do expect { subject }.to change { Packages::Event.count }.by(1) - expect_snowplow_event(category: category, action: action) + expect_snowplow_event(category: category, action: action, **snowplow_gitlab_standard_context) end end diff --git a/spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb index bbcf856350d..8a351226123 100644 --- a/spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb @@ -110,6 +110,7 @@ RSpec.shared_examples 'PyPI package versions' do |user_type, status, add_member context "for user type #{user_type}" do before do project.send("add_#{user_type}", user) if add_member && user_type != :anonymous + group.send("add_#{user_type}", user) if add_member && user_type != :anonymous end it 'returns the package listing' do @@ -127,6 +128,7 @@ RSpec.shared_examples 'PyPI package download' do |user_type, status, add_member context "for user type #{user_type}" do before do project.send("add_#{user_type}", user) if add_member && user_type != :anonymous + group.send("add_#{user_type}", user) if add_member && user_type != :anonymous end it 'returns the package listing' do @@ -144,24 +146,185 @@ RSpec.shared_examples 'process PyPI api request' do |user_type, status, add_memb context "for user type #{user_type}" do before do project.send("add_#{user_type}", user) if add_member && user_type != :anonymous + group.send("add_#{user_type}", user) if add_member && user_type != :anonymous end it_behaves_like 'returning response status', status end end +RSpec.shared_examples 'unknown PyPI scope id' do + context 'as anonymous' do + it_behaves_like 'process PyPI api request', :anonymous, :not_found + end + + context 'as authenticated user' do + subject { get api(url), headers: basic_auth_header(user.username, personal_access_token.token) } + + it_behaves_like 'process PyPI api request', :anonymous, :not_found + end +end + RSpec.shared_examples 'rejects PyPI access with unknown project id' do context 'with an unknown project' do let(:project) { OpenStruct.new(id: 1234567890) } - context 'as anonymous' do - it_behaves_like 'process PyPI api request', :anonymous, :not_found + it_behaves_like 'unknown PyPI scope id' + end +end + +RSpec.shared_examples 'rejects PyPI access with unknown group id' do + context 'with an unknown project' do + let(:group) { OpenStruct.new(id: 1234567890) } + + it_behaves_like 'unknown PyPI scope id' + end +end + +RSpec.shared_examples 'pypi simple API endpoint' do + using RSpec::Parameterized::TableSyntax + + context 'with valid project' do + where(:visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do + :public | :developer | true | true | 'PyPI package versions' | :success + :public | :guest | true | true | 'PyPI package versions' | :success + :public | :developer | true | false | 'PyPI package versions' | :success + :public | :guest | true | false | 'PyPI package versions' | :success + :public | :developer | false | true | 'PyPI package versions' | :success + :public | :guest | false | true | 'PyPI package versions' | :success + :public | :developer | false | false | 'PyPI package versions' | :success + :public | :guest | false | false | 'PyPI package versions' | :success + :public | :anonymous | false | true | 'PyPI package versions' | :success + :private | :developer | true | true | 'PyPI package versions' | :success + :private | :guest | true | true | 'process PyPI api request' | :forbidden + :private | :developer | true | false | 'process PyPI api request' | :unauthorized + :private | :guest | true | false | 'process PyPI api request' | :unauthorized + :private | :developer | false | true | 'process PyPI api request' | :not_found + :private | :guest | false | true | 'process PyPI api request' | :not_found + :private | :developer | false | false | 'process PyPI api request' | :unauthorized + :private | :guest | false | false | 'process PyPI api request' | :unauthorized + :private | :anonymous | false | true | 'process PyPI api request' | :unauthorized end - context 'as authenticated user' do - subject { get api(url), headers: basic_auth_header(user.username, personal_access_token.token) } + with_them do + let(:token) { user_token ? personal_access_token.token : 'wrong' } + let(:headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) } - it_behaves_like 'process PyPI api request', :anonymous, :not_found + before do + project.update_column(:visibility_level, Gitlab::VisibilityLevel.level_value(visibility_level.to_s)) + group.update_column(:visibility_level, Gitlab::VisibilityLevel.level_value(visibility_level.to_s)) + end + + it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member] + end + end + + context 'with a normalized package name' do + let_it_be(:package) { create(:pypi_package, project: project, name: 'my.package') } + + let(:url) { "/projects/#{project.id}/packages/pypi/simple/my-package" } + let(:headers) { basic_auth_header(user.username, personal_access_token.token) } + let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace } } + + it_behaves_like 'PyPI package versions', :developer, :success + end +end + +RSpec.shared_examples 'pypi file download endpoint' do + using RSpec::Parameterized::TableSyntax + + context 'with valid project' do + where(:visibility_level, :user_role, :member, :user_token) do + :public | :developer | true | true + :public | :guest | true | true + :public | :developer | true | false + :public | :guest | true | false + :public | :developer | false | true + :public | :guest | false | true + :public | :developer | false | false + :public | :guest | false | false + :public | :anonymous | false | true + :private | :developer | true | true + :private | :guest | true | true + :private | :developer | true | false + :private | :guest | true | false + :private | :developer | false | true + :private | :guest | false | true + :private | :developer | false | false + :private | :guest | false | false + :private | :anonymous | false | true end + + with_them do + let(:token) { user_token ? personal_access_token.token : 'wrong' } + let(:headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) } + + before do + project.update_column(:visibility_level, Gitlab::VisibilityLevel.level_value(visibility_level.to_s)) + group.update_column(:visibility_level, Gitlab::VisibilityLevel.level_value(visibility_level.to_s)) + end + + it_behaves_like 'PyPI package download', params[:user_role], :success, params[:member] + end + end + + context 'with deploy token headers' do + let(:headers) { basic_auth_header(deploy_token.username, deploy_token.token) } + + context 'valid token' do + it_behaves_like 'returning response status', :success + end + + context 'invalid token' do + let(:headers) { basic_auth_header('foo', 'bar') } + + it_behaves_like 'returning response status', :success + end + end + + context 'with job token headers' do + let(:headers) { basic_auth_header(::Gitlab::Auth::CI_JOB_USER, job.token) } + + context 'valid token' do + it_behaves_like 'returning response status', :success + end + + context 'invalid token' do + let(:headers) { basic_auth_header(::Gitlab::Auth::CI_JOB_USER, 'bar') } + + it_behaves_like 'returning response status', :unauthorized + end + + context 'invalid user' do + let(:headers) { basic_auth_header('foo', job.token) } + + it_behaves_like 'returning response status', :success + end + end +end + +RSpec.shared_examples 'a pypi user namespace endpoint' do + using RSpec::Parameterized::TableSyntax + + # only group namespaces are supported at this time + where(:visibility_level, :user_role, :expected_status) do + :public | :owner | :not_found + :private | :owner | :not_found + :public | :external | :not_found + :private | :external | :not_found + :public | :anonymous | :not_found + :private | :anonymous | :not_found + end + + with_them do + let_it_be_with_reload(:group) { create(:namespace) } + let(:headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, personal_access_token.token) } + + before do + group.update_column(:visibility_level, Gitlab::VisibilityLevel.level_value(visibility_level.to_s)) + group.update_column(:owner_id, user.id) if user_role == :owner + end + + it_behaves_like 'returning response status', params[:expected_status] end end diff --git a/spec/support/shared_examples/requests/api/resource_label_events_api_shared_examples.rb b/spec/support/shared_examples/requests/api/resource_label_events_api_shared_examples.rb index 675b6c5cef6..2ac78131e08 100644 --- a/spec/support/shared_examples/requests/api/resource_label_events_api_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/resource_label_events_api_shared_examples.rb @@ -48,7 +48,7 @@ RSpec.shared_examples 'resource_label_events API' do |parent_type, eventable_typ get api("/#{parent_type}/#{parent.id}/#{eventable_type}/#{eventable[id_name]}/resource_label_events", user) expect(json_response).to be_an Array - expect(json_response).to eq [] + expect(json_response).to be_empty end end end diff --git a/spec/support/shared_examples/requests/api/tracking_shared_examples.rb b/spec/support/shared_examples/requests/api/tracking_shared_examples.rb index 826139635ed..af13e3fc14d 100644 --- a/spec/support/shared_examples/requests/api/tracking_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/tracking_shared_examples.rb @@ -4,6 +4,6 @@ RSpec.shared_examples 'a gitlab tracking event' do |category, action| it "creates a gitlab tracking event #{action}", :snowplow do subject - expect_snowplow_event(category: category, action: action) + expect_snowplow_event(category: category, action: action, **snowplow_standard_context_params) end end diff --git a/spec/support/shared_examples/services/clusters/parse_cluster_applications_artifact_shared_examples.rb b/spec/support/shared_examples/services/clusters/parse_cluster_applications_artifact_shared_examples.rb deleted file mode 100644 index 466300017d9..00000000000 --- a/spec/support/shared_examples/services/clusters/parse_cluster_applications_artifact_shared_examples.rb +++ /dev/null @@ -1,89 +0,0 @@ -# frozen_string_literal: true - -RSpec.shared_examples 'parse cluster applications artifact' do |release_name| - let(:application_class) { Clusters::Cluster::APPLICATIONS[release_name] } - let(:cluster_application) { cluster.public_send("application_#{release_name}") } - let(:file) { fixture_file_upload(Rails.root.join(fixture)) } - let(:artifact) { create(:ci_job_artifact, :cluster_applications, job: job, file: file) } - - context 'release is missing' do - let(:fixture) { "spec/fixtures/helm/helm_list_v2_#{release_name}_missing.json.gz" } - - context 'application does not exist' do - it 'does not create or destroy an application' do - expect do - described_class.new(job, user).execute(artifact) - end.not_to change(application_class, :count) - end - end - - context 'application exists' do - before do - create("clusters_applications_#{release_name}".to_sym, :installed, cluster: cluster) - end - - it 'marks the application as uninstalled' do - described_class.new(job, user).execute(artifact) - - cluster_application.reload - expect(cluster_application).to be_uninstalled - end - end - end - - context 'release is deployed' do - let(:fixture) { "spec/fixtures/helm/helm_list_v2_#{release_name}_deployed.json.gz" } - - context 'application does not exist' do - it 'creates an application and marks it as installed' do - expect do - described_class.new(job, user).execute(artifact) - end.to change(application_class, :count) - - expect(cluster_application).to be_persisted - expect(cluster_application).to be_externally_installed - end - end - - context 'application exists' do - before do - create("clusters_applications_#{release_name}".to_sym, :errored, cluster: cluster) - end - - it 'marks the application as installed' do - described_class.new(job, user).execute(artifact) - - expect(cluster_application).to be_externally_installed - end - end - end - - context 'release is failed' do - let(:fixture) { "spec/fixtures/helm/helm_list_v2_#{release_name}_failed.json.gz" } - - context 'application does not exist' do - it 'creates an application and marks it as errored' do - expect do - described_class.new(job, user).execute(artifact) - end.to change(application_class, :count) - - expect(cluster_application).to be_persisted - expect(cluster_application).to be_errored - expect(cluster_application.status_reason).to eq('Helm release failed to install') - end - end - - context 'application exists' do - before do - create("clusters_applications_#{release_name}".to_sym, :installed, cluster: cluster) - end - - it 'marks the application as errored' do - described_class.new(job, user).execute(artifact) - - expect(cluster_application).to be_errored - expect(cluster_application.status_reason).to eq('Helm release failed to install') - end - end - end -end diff --git a/spec/support/shared_examples/services/packages/debian/generate_distribution_shared_examples.rb b/spec/support/shared_examples/services/packages/debian/generate_distribution_shared_examples.rb new file mode 100644 index 00000000000..9ffeba1b1d0 --- /dev/null +++ b/spec/support/shared_examples/services/packages/debian/generate_distribution_shared_examples.rb @@ -0,0 +1,166 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'Generate Debian Distribution and component files' do + let_it_be(:component_main) { create("debian_#{container_type}_component", distribution: distribution, name: 'main') } + let_it_be(:component_contrib) { create("debian_#{container_type}_component", distribution: distribution, name: 'contrib') } + + let_it_be(:architecture_all) { create("debian_#{container_type}_architecture", distribution: distribution, name: 'all') } + let_it_be(:architecture_amd64) { create("debian_#{container_type}_architecture", distribution: distribution, name: 'amd64') } + let_it_be(:architecture_arm64) { create("debian_#{container_type}_architecture", distribution: distribution, name: 'arm64') } + + let_it_be(:component_file1) { create("debian_#{container_type}_component_file", component: component_contrib, architecture: architecture_all, updated_at: '2020-01-24T08:00:00Z', file_sha256: 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', file_md5: 'd41d8cd98f00b204e9800998ecf8427e', file_fixture: nil, size: 0) } # updated + let_it_be(:component_file2) { create("debian_#{container_type}_component_file", component: component_main, architecture: architecture_all, updated_at: '2020-01-24T09:00:00Z', file_sha256: 'a') } # destroyed + let_it_be(:component_file3) { create("debian_#{container_type}_component_file", component: component_main, architecture: architecture_amd64, updated_at: '2020-01-24T10:54:59Z', file_sha256: 'b') } # destroyed, 1 second before last generation + let_it_be(:component_file4) { create("debian_#{container_type}_component_file", component: component_contrib, architecture: architecture_all, updated_at: '2020-01-24T10:55:00Z', file_sha256: 'c') } # kept, last generation + let_it_be(:component_file5) { create("debian_#{container_type}_component_file", component: component_contrib, architecture: architecture_all, updated_at: '2020-01-24T10:55:00Z', file_sha256: 'd') } # kept, last generation + let_it_be(:component_file6) { create("debian_#{container_type}_component_file", component: component_contrib, architecture: architecture_amd64, updated_at: '2020-01-25T15:17:18Z', file_sha256: 'e') } # kept, less than 1 hour ago + + def check_component_file(release_date, component_name, component_file_type, architecture_name, expected_content) + component_file = distribution + .component_files + .with_component_name(component_name) + .with_file_type(component_file_type) + .with_architecture_name(architecture_name) + .order_updated_asc + .last + + expect(component_file).not_to be_nil + expect(component_file.updated_at).to eq(release_date) + + unless expected_content.nil? + component_file.file.use_file do |file_path| + expect(File.read(file_path)).to eq(expected_content) + end + end + end + + it 'generates Debian distribution and component files', :aggregate_failures do + current_time = Time.utc(2020, 01, 25, 15, 17, 18, 123456) + + travel_to(current_time) do + expect(Gitlab::ErrorTracking).not_to receive(:log_exception) + + initial_count = 6 + destroyed_count = 2 + # updated_count = 1 + created_count = 5 + + expect { subject } + .to not_change { Packages::Package.count } + .and not_change { Packages::PackageFile.count } + .and change { distribution.reload.updated_at }.to(current_time.round) + .and change { distribution.component_files.reset.count }.from(initial_count).to(initial_count - destroyed_count + created_count) + .and change { component_file1.reload.updated_at }.to(current_time.round) + + debs = package.package_files.with_debian_file_type(:deb).preload_debian_file_metadata.to_a + pool_prefix = "pool/unstable/#{project.id}/p/#{package.name}" + expected_main_amd64_content = <<~EOF + Package: libsample0 + Source: #{package.name} + Version: #{package.version} + Installed-Size: 7 + Maintainer: #{debs[0].debian_fields['Maintainer']} + Architecture: amd64 + Description: Some mostly empty lib + Used in GitLab tests. + . + Testing another paragraph. + Multi-Arch: same + Homepage: #{debs[0].debian_fields['Homepage']} + Section: libs + Priority: optional + Filename: #{pool_prefix}/libsample0_1.2.3~alpha2_amd64.deb + Size: 409600 + MD5sum: #{debs[0].file_md5} + SHA256: #{debs[0].file_sha256} + + Package: sample-dev + Source: #{package.name} (#{package.version}) + Version: 1.2.3~binary + Installed-Size: 7 + Maintainer: #{debs[1].debian_fields['Maintainer']} + Architecture: amd64 + Depends: libsample0 (= 1.2.3~binary) + Description: Some mostly empty development files + Used in GitLab tests. + . + Testing another paragraph. + Multi-Arch: same + Homepage: #{debs[1].debian_fields['Homepage']} + Section: libdevel + Priority: optional + Filename: #{pool_prefix}/sample-dev_1.2.3~binary_amd64.deb + Size: 409600 + MD5sum: #{debs[1].file_md5} + SHA256: #{debs[1].file_sha256} + EOF + + check_component_file(current_time.round, 'main', :packages, 'all', nil) + check_component_file(current_time.round, 'main', :packages, 'amd64', expected_main_amd64_content) + check_component_file(current_time.round, 'main', :packages, 'arm64', nil) + + check_component_file(current_time.round, 'contrib', :packages, 'all', nil) + check_component_file(current_time.round, 'contrib', :packages, 'amd64', nil) + check_component_file(current_time.round, 'contrib', :packages, 'arm64', nil) + + main_amd64_size = expected_main_amd64_content.length + main_amd64_md5sum = Digest::MD5.hexdigest(expected_main_amd64_content) + main_amd64_sha256 = Digest::SHA256.hexdigest(expected_main_amd64_content) + + contrib_all_size = component_file1.size + contrib_all_md5sum = component_file1.file_md5 + contrib_all_sha256 = component_file1.file_sha256 + + expected_release_content = <<~EOF + Codename: unstable + Date: Sat, 25 Jan 2020 15:17:18 +0000 + Valid-Until: Mon, 27 Jan 2020 15:17:18 +0000 + Architectures: all amd64 arm64 + Components: contrib main + MD5Sum: + #{contrib_all_md5sum} #{contrib_all_size} contrib/binary-all/Packages + d41d8cd98f00b204e9800998ecf8427e 0 contrib/binary-amd64/Packages + d41d8cd98f00b204e9800998ecf8427e 0 contrib/binary-arm64/Packages + d41d8cd98f00b204e9800998ecf8427e 0 main/binary-all/Packages + #{main_amd64_md5sum} #{main_amd64_size} main/binary-amd64/Packages + d41d8cd98f00b204e9800998ecf8427e 0 main/binary-arm64/Packages + SHA256: + #{contrib_all_sha256} #{contrib_all_size} contrib/binary-all/Packages + e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/binary-amd64/Packages + e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/binary-arm64/Packages + e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/binary-all/Packages + #{main_amd64_sha256} #{main_amd64_size} main/binary-amd64/Packages + e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/binary-arm64/Packages + EOF + + distribution.file.use_file do |file_path| + expect(File.read(file_path)).to eq(expected_release_content) + end + end + end +end + +RSpec.shared_examples 'Generate minimal Debian Distribution' do + it 'generates minimal distribution', :aggregate_failures do + travel_to(Time.utc(2020, 01, 25, 15, 17, 18, 123456)) do + expect(Gitlab::ErrorTracking).not_to receive(:log_exception) + + expect { subject } + .to not_change { Packages::Package.count } + .and not_change { Packages::PackageFile.count } + .and not_change { distribution.component_files.reset.count } + + expected_release_content = <<~EOF + Codename: unstable + Date: Sat, 25 Jan 2020 15:17:18 +0000 + Valid-Until: Mon, 27 Jan 2020 15:17:18 +0000 + MD5Sum: + SHA256: + EOF + + distribution.file.use_file do |file_path| + expect(File.read(file_path)).to eq(expected_release_content) + end + end + end +end diff --git a/spec/support/shared_examples/services/users/build_service_shared_examples.rb b/spec/support/shared_examples/services/users/build_service_shared_examples.rb new file mode 100644 index 00000000000..6a8695e1786 --- /dev/null +++ b/spec/support/shared_examples/services/users/build_service_shared_examples.rb @@ -0,0 +1,125 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'common user build items' do + it { is_expected.to be_valid } + + it 'sets the created_by_id' do + expect(user.created_by_id).to eq(current_user&.id) + end + + it 'calls UpdateCanonicalEmailService' do + expect(Users::UpdateCanonicalEmailService).to receive(:new).and_call_original + + user + end + + context 'when user_type is provided' do + context 'when project_bot' do + before do + params.merge!({ user_type: :project_bot }) + end + + it { expect(user.project_bot?).to be true } + end + + context 'when not a project_bot' do + before do + params.merge!({ user_type: :alert_bot }) + end + + it { expect(user).to be_human } + end + end +end + +RSpec.shared_examples_for 'current user not admin build items' do + using RSpec::Parameterized::TableSyntax + + context 'with "user_default_external" application setting' do + where(:user_default_external, :external, :email, :user_default_internal_regex, :result) do + true | nil | 'fl@example.com' | nil | true + true | true | 'fl@example.com' | nil | true + true | false | 'fl@example.com' | nil | true # admin difference + + true | nil | 'fl@example.com' | '' | true + true | true | 'fl@example.com' | '' | true + true | false | 'fl@example.com' | '' | true # admin difference + + true | nil | 'fl@example.com' | '^(?:(?!\.ext@).)*$\r?' | false + true | true | 'fl@example.com' | '^(?:(?!\.ext@).)*$\r?' | false # admin difference + true | false | 'fl@example.com' | '^(?:(?!\.ext@).)*$\r?' | false + + true | nil | 'tester.ext@domain.com' | '^(?:(?!\.ext@).)*$\r?' | true + true | true | 'tester.ext@domain.com' | '^(?:(?!\.ext@).)*$\r?' | true + true | false | 'tester.ext@domain.com' | '^(?:(?!\.ext@).)*$\r?' | true # admin difference + + false | nil | 'fl@example.com' | nil | false + false | true | 'fl@example.com' | nil | false # admin difference + false | false | 'fl@example.com' | nil | false + + false | nil | 'fl@example.com' | '' | false + false | true | 'fl@example.com' | '' | false # admin difference + false | false | 'fl@example.com' | '' | false + + false | nil | 'fl@example.com' | '^(?:(?!\.ext@).)*$\r?' | false + false | true | 'fl@example.com' | '^(?:(?!\.ext@).)*$\r?' | false # admin difference + false | false | 'fl@example.com' | '^(?:(?!\.ext@).)*$\r?' | false + + false | nil | 'tester.ext@domain.com' | '^(?:(?!\.ext@).)*$\r?' | false + false | true | 'tester.ext@domain.com' | '^(?:(?!\.ext@).)*$\r?' | false # admin difference + false | false | 'tester.ext@domain.com' | '^(?:(?!\.ext@).)*$\r?' | false + end + + with_them do + before do + stub_application_setting(user_default_external: user_default_external) + stub_application_setting(user_default_internal_regex: user_default_internal_regex) + + params.merge!({ external: external, email: email }.compact) + end + + it 'sets the value of Gitlab::CurrentSettings.user_default_external' do + expect(user.external).to eq(result) + end + end + end + + context 'when "send_user_confirmation_email" application setting is true' do + before do + stub_application_setting(send_user_confirmation_email: true, signup_enabled?: true) + end + + it 'does not confirm the user' do + expect(user).not_to be_confirmed + end + end + + context 'when "send_user_confirmation_email" application setting is false' do + before do + stub_application_setting(send_user_confirmation_email: false, signup_enabled?: true) + end + + it 'confirms the user' do + expect(user).to be_confirmed + end + end + + context 'with allowed params' do + let(:params) do + { + email: 1, + name: 1, + password: 1, + password_automatically_set: 1, + username: 1, + user_type: 'project_bot' + } + end + + it 'sets all allowed attributes' do + expect(User).to receive(:new).with(hash_including(params)).and_call_original + + user + end + end +end diff --git a/spec/support/shared_examples/uncached_response_shared_examples.rb b/spec/support/shared_examples/uncached_response_shared_examples.rb deleted file mode 100644 index 3997017ff35..00000000000 --- a/spec/support/shared_examples/uncached_response_shared_examples.rb +++ /dev/null @@ -1,12 +0,0 @@ -# frozen_string_literal: true -# -# Pairs with lib/gitlab/no_cache_headers.rb -# - -RSpec.shared_examples 'uncached response' do - it 'defines an uncached header response' do - expect(response.headers["Cache-Control"]).to include("no-store", "no-cache") - expect(response.headers["Pragma"]).to eq("no-cache") - expect(response.headers["Expires"]).to eq("Fri, 01 Jan 1990 00:00:00 GMT") - end -end |