diff options
Diffstat (limited to 'spec/support/shared_examples/models')
10 files changed, 529 insertions, 11 deletions
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 42f82987989..03f565e0aac 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 @@ -165,9 +165,9 @@ RSpec.shared_examples 'AtomicInternalId' do |validate_presence: true| 3.times { supply.next_value } end - current_value = described_class.public_send(method_name, scope_value, &:current_value) - - expect(current_value).to eq(iid + 3) + described_class.public_send(method_name, scope_value) do |supply| + expect(supply.next_value).to eq(iid + 4) + end end end diff --git a/spec/support/shared_examples/models/chat_integration_shared_examples.rb b/spec/support/shared_examples/models/chat_integration_shared_examples.rb index 9f3be3e2e06..72659dd5f3b 100644 --- a/spec/support/shared_examples/models/chat_integration_shared_examples.rb +++ b/spec/support/shared_examples/models/chat_integration_shared_examples.rb @@ -13,7 +13,7 @@ RSpec.shared_examples "chat integration" do |integration_name| end it { is_expected.to validate_presence_of(:webhook) } - it_behaves_like "issue tracker service URL attribute", :webhook + it_behaves_like "issue tracker integration URL attribute", :webhook end context "when integration is inactive" do @@ -163,7 +163,7 @@ RSpec.shared_examples "chat integration" do |integration_name| context "with issue events" do let(:opts) { { title: "Awesome issue", description: "please fix" } } let(:sample_data) do - service = Issues::CreateService.new(project: project, current_user: user, params: opts) + service = Issues::CreateService.new(project: project, current_user: user, params: opts, spam_params: nil) issue = service.execute service.hook_data(issue, "open") end diff --git a/spec/support/shared_examples/models/concerns/integrations/slack_mattermost_notifier_shared_examples.rb b/spec/support/shared_examples/models/concerns/integrations/slack_mattermost_notifier_shared_examples.rb index 66448aca2c5..2d4c0b60f2b 100644 --- a/spec/support/shared_examples/models/concerns/integrations/slack_mattermost_notifier_shared_examples.rb +++ b/spec/support/shared_examples/models/concerns/integrations/slack_mattermost_notifier_shared_examples.rb @@ -8,7 +8,7 @@ RSpec.shared_examples Integrations::SlackMattermostNotifier do |service_name| def execute_with_options(options) receive(:new).with(webhook_url, options.merge(http_client: Integrations::SlackMattermostNotifier::HTTPClient)) - .and_return(double(:slack_service).as_null_object) + .and_return(double(:slack_integration).as_null_object) end describe "Associations" do @@ -23,7 +23,7 @@ RSpec.shared_examples Integrations::SlackMattermostNotifier do |service_name| end it { is_expected.to validate_presence_of(:webhook) } - it_behaves_like 'issue tracker service URL attribute', :webhook + it_behaves_like 'issue tracker integration URL attribute', :webhook end context 'when service is inactive' do diff --git a/spec/support/shared_examples/models/cycle_analytics_stage_shared_examples.rb b/spec/support/shared_examples/models/cycle_analytics_stage_shared_examples.rb index d23f95b2e9e..cf38a583944 100644 --- a/spec/support/shared_examples/models/cycle_analytics_stage_shared_examples.rb +++ b/spec/support/shared_examples/models/cycle_analytics_stage_shared_examples.rb @@ -122,6 +122,22 @@ RSpec.shared_examples 'value stream analytics stage' do expect(stage.parent_id).to eq(parent.id) end end + + describe '#hash_code' do + it 'does not differ when the same object is built with the same params' do + stage_1 = build(factory) + stage_2 = build(factory) + + expect(stage_1.events_hash_code).to eq(stage_2.events_hash_code) + end + + it 'differs when the stage events are different' do + stage_1 = build(factory, start_event_identifier: :merge_request_created, end_event_identifier: :merge_request_merged) + stage_2 = build(factory, start_event_identifier: :issue_created, end_event_identifier: :issue_first_mentioned_in_commit) + + expect(stage_1.events_hash_code).not_to eq(stage_2.events_hash_code) + end + end end RSpec.shared_examples 'value stream analytics label based stage' do diff --git a/spec/support/shared_examples/models/integrations/base_slash_commands_shared_examples.rb b/spec/support/shared_examples/models/integrations/base_slash_commands_shared_examples.rb index 128999d02fa..e35ac9c0d0d 100644 --- a/spec/support/shared_examples/models/integrations/base_slash_commands_shared_examples.rb +++ b/spec/support/shared_examples/models/integrations/base_slash_commands_shared_examples.rb @@ -66,14 +66,14 @@ RSpec.shared_examples Integrations::BaseSlashCommands do } end - let(:service) do - project.create_mattermost_slash_commands_service( + let(:integration) do + project.create_mattermost_slash_commands_integration( properties: { token: 'token' } ) end it 'generates the url' do - response = service.trigger(params) + response = integration.trigger(params) expect(response[:text]).to start_with(':wave: Hi there!') end diff --git a/spec/support/shared_examples/models/integrations/has_web_hook_shared_examples.rb b/spec/support/shared_examples/models/integrations/has_web_hook_shared_examples.rb new file mode 100644 index 00000000000..1fa340a0cf4 --- /dev/null +++ b/spec/support/shared_examples/models/integrations/has_web_hook_shared_examples.rb @@ -0,0 +1,97 @@ +# frozen_string_literal: true + +RSpec.shared_examples Integrations::HasWebHook do + include AfterNextHelpers + + describe 'callbacks' do + it 'calls #update_web_hook! when enabled' do + expect(integration).to receive(:update_web_hook!) + + integration.active = true + integration.save! + end + + it 'does not call #update_web_hook! when disabled' do + expect(integration).not_to receive(:update_web_hook!) + + integration.active = false + integration.save! + end + + it 'does not call #update_web_hook! when validation fails' do + expect(integration).not_to receive(:update_web_hook!) + + integration.active = true + integration.project = nil + expect(integration.save).to be(false) + end + end + + describe '#hook_url' do + it 'returns a string' do + expect(integration.hook_url).to be_a(String) + end + end + + describe '#hook_ssl_verification' do + it 'returns a boolean' do + expect(integration.hook_ssl_verification).to be_in([true, false]) + end + end + + describe '#update_web_hook!' do + def call + integration.update_web_hook! + end + + it 'creates or updates a service hook' do + expect { call }.to change(ServiceHook, :count).by(1) + expect(integration.service_hook.url).to eq(hook_url) + + integration.service_hook.update!(url: 'http://other.com') + + expect { call }.to change { integration.service_hook.reload.url }.from('http://other.com').to(hook_url) + end + + it 'raises an error if the service hook could not be saved' do + call + integration.service_hook.integration = nil + + expect { call }.to raise_error(ActiveRecord::RecordInvalid) + end + + it 'does not attempt to save the service hook if there are no changes' do + call + + expect(integration.service_hook).not_to receive(:save!) + + call + end + end + + describe '#execute_web_hook!' do + let(:args) { ['foo', [1, 2, 3]] } + + def call + integration.execute_web_hook!(*args) + end + + it 'creates the webhook if necessary and executes it' do + expect_next(ServiceHook).to receive(:execute).with(*args) + expect { call }.to change(ServiceHook, :count).by(1) + + expect(integration.service_hook).to receive(:execute).with(*args) + expect { call }.not_to change(ServiceHook, :count) + end + + it 'raises an error if the service hook could not be saved' do + expect_next(ServiceHook).to receive(:execute).with(*args) + + call + integration.service_hook.integration = nil + + expect(integration.service_hook).not_to receive(:execute) + expect { call }.to raise_error(ActiveRecord::RecordInvalid) + end + end +end diff --git a/spec/support/shared_examples/models/issue_tracker_service_shared_examples.rb b/spec/support/shared_examples/models/issue_tracker_service_shared_examples.rb index b275d594792..6d519e561ee 100644 --- a/spec/support/shared_examples/models/issue_tracker_service_shared_examples.rb +++ b/spec/support/shared_examples/models/issue_tracker_service_shared_examples.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -RSpec.shared_examples 'issue tracker service URL attribute' do |url_attr| +RSpec.shared_examples 'issue tracker integration URL attribute' do |url_attr| it { is_expected.to allow_value('https://example.com').for(url_attr) } it { is_expected.not_to allow_value('example.com').for(url_attr) } diff --git a/spec/support/shared_examples/models/member_shared_examples.rb b/spec/support/shared_examples/models/member_shared_examples.rb index 7ede6f0d8d4..c111d250d34 100644 --- a/spec/support/shared_examples/models/member_shared_examples.rb +++ b/spec/support/shared_examples/models/member_shared_examples.rb @@ -75,3 +75,259 @@ RSpec.shared_examples '#valid_level_roles' do |entity_name| expect(presenter.valid_level_roles).to eq(expected_roles) end end + +RSpec.shared_examples_for "member creation" do + let_it_be(:user) { create(:user) } + let_it_be(:admin) { create(:admin) } + + describe '#execute' do + it 'returns a Member object', :aggregate_failures do + member = described_class.new(source, user, :maintainer).execute + + expect(member).to be_a member_type + expect(member).to be_persisted + end + + context 'when admin mode is enabled', :enable_admin_mode do + it 'sets members.created_by to the given admin current_user' do + member = described_class.new(source, user, :maintainer, current_user: admin).execute + + expect(member.created_by).to eq(admin) + end + end + + context 'when admin mode is disabled' do + it 'rejects setting members.created_by to the given admin current_user' do + member = described_class.new(source, user, :maintainer, current_user: admin).execute + + expect(member.created_by).to be_nil + end + end + + it 'sets members.expires_at to the given expires_at' do + member = described_class.new(source, user, :maintainer, expires_at: Date.new(2016, 9, 22)).execute + + expect(member.expires_at).to eq(Date.new(2016, 9, 22)) + end + + described_class.access_levels.each do |sym_key, int_access_level| + it "accepts the :#{sym_key} symbol as access level", :aggregate_failures do + expect(source.users).not_to include(user) + + member = described_class.new(source, user.id, sym_key).execute + + expect(member.access_level).to eq(int_access_level) + expect(source.users.reload).to include(user) + end + + it "accepts the #{int_access_level} integer as access level", :aggregate_failures do + expect(source.users).not_to include(user) + + member = described_class.new(source, user.id, int_access_level).execute + + expect(member.access_level).to eq(int_access_level) + expect(source.users.reload).to include(user) + end + end + + context 'with no current_user' do + context 'when called with a known user id' do + it 'adds the user as a member' do + expect(source.users).not_to include(user) + + described_class.new(source, user.id, :maintainer).execute + + expect(source.users.reload).to include(user) + end + end + + context 'when called with an unknown user id' do + it 'adds the user as a member' do + expect(source.users).not_to include(user) + + described_class.new(source, non_existing_record_id, :maintainer).execute + + expect(source.users.reload).not_to include(user) + end + end + + context 'when called with a user object' do + it 'adds the user as a member' do + expect(source.users).not_to include(user) + + described_class.new(source, user, :maintainer).execute + + expect(source.users.reload).to include(user) + end + end + + context 'when called with a requester user object' do + before do + source.request_access(user) + end + + it 'adds the requester as a member', :aggregate_failures do + expect(source.users).not_to include(user) + expect(source.requesters.exists?(user_id: user)).to be_truthy + + expect do + described_class.new(source, user, :maintainer).execute + end.to raise_error(Gitlab::Access::AccessDeniedError) + + expect(source.users.reload).not_to include(user) + expect(source.requesters.reload.exists?(user_id: user)).to be_truthy + end + end + + context 'when called with a known user email' do + it 'adds the user as a member' do + expect(source.users).not_to include(user) + + described_class.new(source, user.email, :maintainer).execute + + expect(source.users.reload).to include(user) + end + end + + context 'when called with an unknown user email' do + it 'creates an invited member' do + expect(source.users).not_to include(user) + + described_class.new(source, 'user@example.com', :maintainer).execute + + expect(source.members.invite.pluck(:invite_email)).to include('user@example.com') + end + end + + context 'when called with an unknown user email starting with a number' do + it 'creates an invited member', :aggregate_failures do + email_starting_with_number = "#{user.id}_email@example.com" + + described_class.new(source, email_starting_with_number, :maintainer).execute + + expect(source.members.invite.pluck(:invite_email)).to include(email_starting_with_number) + expect(source.users.reload).not_to include(user) + end + end + end + + context 'when current_user can update member', :enable_admin_mode do + it 'creates the member' do + expect(source.users).not_to include(user) + + described_class.new(source, user, :maintainer, current_user: admin).execute + + expect(source.users.reload).to include(user) + end + + context 'when called with a requester user object' do + before do + source.request_access(user) + end + + it 'adds the requester as a member', :aggregate_failures do + expect(source.users).not_to include(user) + expect(source.requesters.exists?(user_id: user)).to be_truthy + + described_class.new(source, user, :maintainer, current_user: admin).execute + + expect(source.users.reload).to include(user) + expect(source.requesters.reload.exists?(user_id: user)).to be_falsy + end + end + end + + context 'when current_user cannot update member' do + it 'does not create the member', :aggregate_failures do + expect(source.users).not_to include(user) + + member = described_class.new(source, user, :maintainer, current_user: user).execute + + expect(source.users.reload).not_to include(user) + expect(member).not_to be_persisted + end + + context 'when called with a requester user object' do + before do + source.request_access(user) + end + + it 'does not destroy the requester', :aggregate_failures do + expect(source.users).not_to include(user) + expect(source.requesters.exists?(user_id: user)).to be_truthy + + described_class.new(source, user, :maintainer, current_user: user).execute + + expect(source.users.reload).not_to include(user) + expect(source.requesters.exists?(user_id: user)).to be_truthy + end + end + end + + context 'when member already exists' do + before do + source.add_user(user, :developer) + end + + context 'with no current_user' do + it 'updates the member' do + expect(source.users).to include(user) + + described_class.new(source, user, :maintainer).execute + + expect(source.members.find_by(user_id: user).access_level).to eq(Gitlab::Access::MAINTAINER) + end + end + + context 'when current_user can update member', :enable_admin_mode do + it 'updates the member' do + expect(source.users).to include(user) + + described_class.new(source, user, :maintainer, current_user: admin).execute + + expect(source.members.find_by(user_id: user).access_level).to eq(Gitlab::Access::MAINTAINER) + end + end + + context 'when current_user cannot update member' do + it 'does not update the member' do + expect(source.users).to include(user) + + described_class.new(source, user, :maintainer, current_user: user).execute + + expect(source.members.find_by(user_id: user).access_level).to eq(Gitlab::Access::DEVELOPER) + end + end + end + end + + describe '.add_users' do + let_it_be(:user1) { create(:user) } + let_it_be(:user2) { create(:user) } + + it 'returns a Member objects' do + members = described_class.add_users(source, [user1, user2], :maintainer) + + expect(members).to be_a Array + expect(members.size).to eq(2) + expect(members.first).to be_a member_type + expect(members.first).to be_persisted + end + + it 'returns an empty array' do + members = described_class.add_users(source, [], :maintainer) + + expect(members).to be_a Array + expect(members).to be_empty + end + + it 'supports different formats' do + list = ['joe@local.test', admin, user1.id, user2.id.to_s] + + members = described_class.add_users(source, list, :maintainer) + + expect(members.size).to eq(4) + expect(members.first).to be_invite + end + end +end diff --git a/spec/support/shared_examples/models/project_ci_cd_settings_shared_examples.rb b/spec/support/shared_examples/models/project_ci_cd_settings_shared_examples.rb new file mode 100644 index 00000000000..c92e819db19 --- /dev/null +++ b/spec/support/shared_examples/models/project_ci_cd_settings_shared_examples.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'ci_cd_settings delegation' do + let(:exclude_attributes) { [] } + + context 'when ci_cd_settings is destroyed but project is not' do + it 'allows methods delegated to ci_cd_settings to be nil', :aggregate_failures do + project = create(:project) + attributes = project.ci_cd_settings.attributes.keys - %w(id project_id) - exclude_attributes + project.ci_cd_settings.destroy! + project.reload + attributes.each do |attr| + method = project.respond_to?("ci_#{attr}") ? "ci_#{attr}" : attr + expect(project.send(method)).to be_nil, "#{attr} was not nil" + end + end + end +end + +RSpec.shared_examples 'a ci_cd_settings predicate method' do |prefix: ''| + using RSpec::Parameterized::TableSyntax + + let_it_be(:project) { create(:project) } + + context 'when ci_cd_settings is nil' do + before do + allow(project).to receive(:ci_cd_settings).and_return(nil) + end + + it 'returns false' do + expect(project.send("#{prefix}#{delegated_method}")).to be(false) + end + end + + context 'when ci_cd_settings is not nil' do + where(:delegated_method_return, :subject_return) do + true | true + false | false + end + + with_them do + let(:ci_cd_settings_double) { double('ProjectCiCdSetting') } + + before do + allow(project).to receive(:ci_cd_settings).and_return(ci_cd_settings_double) + allow(ci_cd_settings_double).to receive(delegated_method).and_return(delegated_method_return) + end + + it 'returns the expected boolean value' do + expect(project.send("#{prefix}#{delegated_method}")).to be(subject_return) + end + end + end +end diff --git a/spec/support/shared_examples/models/wiki_shared_examples.rb b/spec/support/shared_examples/models/wiki_shared_examples.rb index 2498bf35a09..bc5956e3eec 100644 --- a/spec/support/shared_examples/models/wiki_shared_examples.rb +++ b/spec/support/shared_examples/models/wiki_shared_examples.rb @@ -2,6 +2,7 @@ RSpec.shared_examples 'wiki model' do let_it_be(:user) { create(:user, :commit_email) } + let(:wiki_container) { raise NotImplementedError } let(:wiki_container_without_repo) { raise NotImplementedError } let(:wiki_lfs_enabled) { false } @@ -536,4 +537,98 @@ RSpec.shared_examples 'wiki model' do expect(subject.hook_attrs.keys).to contain_exactly(:web_url, :git_ssh_url, :git_http_url, :path_with_namespace, :default_branch) end end + + describe '#default_branch' do + subject { wiki.default_branch } + + before do + allow(Gitlab::DefaultBranch).to receive(:value).and_return('main') + end + + context 'when repository is not created' do + let(:wiki_container) { wiki_container_without_repo } + + it 'returns the instance default branch' do + expect(subject).to eq 'main' + end + end + + context 'when repository is empty' do + let(:wiki_container) { wiki_container_without_repo } + + before do + wiki.repository.create_if_not_exists + end + + it 'returns the instance default branch' do + expect(subject).to eq 'main' + end + end + + context 'when repository is not empty' do + it 'returns the repository default branch' do + wiki.create_page('index', 'test content') + + expect(subject).to eq wiki.repository.root_ref + end + end + end + + describe '#create_wiki_repository' do + let(:head_path) { Rails.root.join(TestEnv.repos_path, "#{wiki.disk_path}.git", 'HEAD') } + let(:default_branch) { 'foo' } + + before do + allow(Gitlab::CurrentSettings).to receive(:default_branch_name).and_return(default_branch) + end + + subject { wiki.create_wiki_repository } + + context 'when repository is not created' do + let(:wiki_container) { wiki_container_without_repo } + + it 'changes the HEAD reference to the default branch' do + expect(wiki.empty?).to eq true + + subject + + expect(File.read(head_path).squish).to eq "ref: refs/heads/#{default_branch}" + end + end + + context 'when repository is empty' do + let(:wiki_container) { wiki_container_without_repo } + + it 'changes the HEAD reference to the default branch' do + wiki.repository.create_if_not_exists + wiki.repository.raw_repository.write_ref('HEAD', 'refs/heads/bar') + + subject + + expect(File.read(head_path).squish).to eq "ref: refs/heads/#{default_branch}" + end + end + + context 'when repository is not empty' do + before do + wiki.create_page('index', 'test content') + end + + it 'does nothing when HEAD points to the right branch' do + expect(wiki.repository.raw_repository).not_to receive(:write_ref) + + subject + end + + context 'when HEAD points to the wrong branch' do + it 'rewrites HEAD with the right branch' do + wiki.repository.raw_repository.write_ref('HEAD', 'refs/heads/bar') + + subject + + expect(File.read(head_path).squish).to eq "ref: refs/heads/#{default_branch}" + end + end + end + end end |