diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-07-20 12:55:51 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-07-20 12:55:51 +0300 |
commit | e8d2c2579383897a1dd7f9debd359abe8ae8373d (patch) | |
tree | c42be41678c2586d49a75cabce89322082698334 /spec/models/integrations | |
parent | fc845b37ec3a90aaa719975f607740c22ba6a113 (diff) |
Add latest changes from gitlab-org/gitlab@14-1-stable-eev14.1.0-rc42
Diffstat (limited to 'spec/models/integrations')
33 files changed, 1081 insertions, 613 deletions
diff --git a/spec/models/integrations/asana_spec.rb b/spec/models/integrations/asana_spec.rb index 4473478910a..f7e7eb1b0ae 100644 --- a/spec/models/integrations/asana_spec.rb +++ b/spec/models/integrations/asana_spec.rb @@ -3,11 +3,6 @@ require 'spec_helper' RSpec.describe Integrations::Asana do - describe 'Associations' do - it { is_expected.to belong_to :project } - it { is_expected.to have_one :service_hook } - end - describe 'Validations' do context 'active' do before do @@ -42,13 +37,12 @@ RSpec.describe Integrations::Asana do allow(@asana).to receive_messages( project: project, project_id: project.id, - service_hook: true, api_key: 'verySecret', restrict_to_branch: 'master' ) end - it 'calls Asana service to create a story' do + it 'calls Asana integration to create a story' do data = create_data_for_commits("Message from commit. related to ##{gid}") expected_message = "#{data[:user_name]} pushed to branch #{data[:ref]} of #{project.full_name} ( #{data[:commits][0][:url]} ): #{data[:commits][0][:message]}" @@ -59,7 +53,7 @@ RSpec.describe Integrations::Asana do @asana.execute(data) end - it 'calls Asana service to create a story and close a task' do + it 'calls Asana integration to create a story and close a task' do data = create_data_for_commits('fix #456789') d1 = double('Asana::Resources::Task') expect(d1).to receive(:add_comment) diff --git a/spec/models/integrations/assembla_spec.rb b/spec/models/integrations/assembla_spec.rb index e5972bce95d..960dfea3dc4 100644 --- a/spec/models/integrations/assembla_spec.rb +++ b/spec/models/integrations/assembla_spec.rb @@ -5,11 +5,6 @@ require 'spec_helper' RSpec.describe Integrations::Assembla do include StubRequests - describe "Associations" do - it { is_expected.to belong_to :project } - it { is_expected.to have_one :service_hook } - end - describe "Execute" do let(:user) { create(:user) } let(:project) { create(:project, :repository) } @@ -19,7 +14,6 @@ RSpec.describe Integrations::Assembla do allow(@assembla_integration).to receive_messages( project_id: project.id, project: project, - service_hook: true, token: 'verySecret', subdomain: 'project_name' ) diff --git a/spec/models/integrations/bamboo_spec.rb b/spec/models/integrations/bamboo_spec.rb index 39966f7978d..73ebf404828 100644 --- a/spec/models/integrations/bamboo_spec.rb +++ b/spec/models/integrations/bamboo_spec.rb @@ -10,7 +10,7 @@ RSpec.describe Integrations::Bamboo, :use_clean_rails_memory_store_caching do let_it_be(:project) { create(:project) } - subject(:service) do + subject(:integration) do described_class.create!( project: project, properties: { @@ -22,53 +22,48 @@ RSpec.describe Integrations::Bamboo, :use_clean_rails_memory_store_caching do ) end - 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 active' do before do - subject.active = true + integration.active = true end it { is_expected.to validate_presence_of(:build_key) } it { is_expected.to validate_presence_of(:bamboo_url) } - it_behaves_like 'issue tracker service URL attribute', :bamboo_url + it_behaves_like 'issue tracker integration URL attribute', :bamboo_url describe '#username' do it 'does not validate the presence of username if password is nil' do - subject.password = nil + integration.password = nil - expect(subject).not_to validate_presence_of(:username) + expect(integration).not_to validate_presence_of(:username) end it 'validates the presence of username if password is present' do - subject.password = 'secret' + integration.password = 'secret' - expect(subject).to validate_presence_of(:username) + expect(integration).to validate_presence_of(:username) end end describe '#password' do it 'does not validate the presence of password if username is nil' do - subject.username = nil + integration.username = nil - expect(subject).not_to validate_presence_of(:password) + expect(integration).not_to validate_presence_of(:password) end it 'validates the presence of password if username is present' do - subject.username = 'john' + integration.username = 'john' - expect(subject).to validate_presence_of(:password) + expect(integration).to validate_presence_of(:password) end end end - context 'when service is inactive' do + context 'when inactive' do before do - subject.active = false + integration.active = false end it { is_expected.not_to validate_presence_of(:build_key) } @@ -82,45 +77,38 @@ RSpec.describe Integrations::Bamboo, :use_clean_rails_memory_store_caching do describe 'before_update :reset_password' do context 'when a password was previously set' do it 'resets password if url changed' do - bamboo_integration = service - - bamboo_integration.bamboo_url = 'http://gitlab1.com' - bamboo_integration.save! + integration.bamboo_url = 'http://gitlab1.com' + integration.save! - expect(bamboo_integration.password).to be_nil + expect(integration.password).to be_nil end it 'does not reset password if username changed' do - bamboo_integration = service + integration.username = 'some_name' + integration.save! - bamboo_integration.username = 'some_name' - bamboo_integration.save! - - expect(bamboo_integration.password).to eq('password') + expect(integration.password).to eq('password') end it "does not reset password if new url is set together with password, even if it's the same password" do - bamboo_integration = service - - bamboo_integration.bamboo_url = 'http://gitlab_edited.com' - bamboo_integration.password = 'password' - bamboo_integration.save! + integration.bamboo_url = 'http://gitlab_edited.com' + integration.password = 'password' + integration.save! - expect(bamboo_integration.password).to eq('password') - expect(bamboo_integration.bamboo_url).to eq('http://gitlab_edited.com') + expect(integration.password).to eq('password') + expect(integration.bamboo_url).to eq('http://gitlab_edited.com') end end it 'saves password if new url is set together with password when no password was previously set' do - bamboo_integration = service - bamboo_integration.password = nil + integration.password = nil - bamboo_integration.bamboo_url = 'http://gitlab_edited.com' - bamboo_integration.password = 'password' - bamboo_integration.save! + integration.bamboo_url = 'http://gitlab_edited.com' + integration.password = 'password' + integration.save! - expect(bamboo_integration.password).to eq('password') - expect(bamboo_integration.bamboo_url).to eq('http://gitlab_edited.com') + expect(integration.password).to eq('password') + expect(integration.bamboo_url).to eq('http://gitlab_edited.com') end end end @@ -129,29 +117,29 @@ RSpec.describe Integrations::Bamboo, :use_clean_rails_memory_store_caching do it 'runs update and build action' do stub_update_and_build_request - subject.execute(Gitlab::DataBuilder::Push::SAMPLE_DATA) + integration.execute(Gitlab::DataBuilder::Push::SAMPLE_DATA) end end describe '#build_page' do it 'returns the contents of the reactive cache' do - stub_reactive_cache(service, { build_page: 'foo' }, 'sha', 'ref') + stub_reactive_cache(integration, { build_page: 'foo' }, 'sha', 'ref') - expect(service.build_page('sha', 'ref')).to eq('foo') + expect(integration.build_page('sha', 'ref')).to eq('foo') end end describe '#commit_status' do it 'returns the contents of the reactive cache' do - stub_reactive_cache(service, { commit_status: 'foo' }, 'sha', 'ref') + stub_reactive_cache(integration, { commit_status: 'foo' }, 'sha', 'ref') - expect(service.commit_status('sha', 'ref')).to eq('foo') + expect(integration.commit_status('sha', 'ref')).to eq('foo') end end shared_examples 'reactive cache calculation' do describe '#build_page' do - subject { service.calculate_reactive_cache('123', 'unused')[:build_page] } + subject { integration.calculate_reactive_cache('123', 'unused')[:build_page] } it 'returns a specific URL when status is 500' do stub_request(status: 500) @@ -183,7 +171,7 @@ RSpec.describe Integrations::Bamboo, :use_clean_rails_memory_store_caching do end describe '#commit_status' do - subject { service.calculate_reactive_cache('123', 'unused')[:commit_status] } + subject { integration.calculate_reactive_cache('123', 'unused')[:commit_status] } it 'sets commit status to :error when status is 500' do stub_request(status: 500) diff --git a/spec/models/integrations/base_chat_notification_spec.rb b/spec/models/integrations/base_chat_notification_spec.rb index 656eaa3bbdd..ac4031a9b7d 100644 --- a/spec/models/integrations/base_chat_notification_spec.rb +++ b/spec/models/integrations/base_chat_notification_spec.rb @@ -15,26 +15,8 @@ RSpec.describe Integrations::BaseChatNotification do it { is_expected.to validate_inclusion_of(:labels_to_be_notified_behavior).in_array(%w[match_any match_all]).allow_blank } end - describe '#can_test?' do - context 'with empty repository' do - it 'returns true' do - subject.project = create(:project, :empty_repo) - - expect(subject.can_test?).to be true - end - end - - context 'with repository' do - it 'returns true' do - subject.project = create(:project, :repository) - - expect(subject.can_test?).to be true - end - end - end - describe '#execute' do - subject(:chat_service) { described_class.new } + subject(:chat_integration) { described_class.new } let_it_be(:project) { create(:project, :repository) } @@ -43,10 +25,9 @@ RSpec.describe Integrations::BaseChatNotification do let(:data) { Gitlab::DataBuilder::Push.build_sample(subject.project, user) } before do - allow(chat_service).to receive_messages( + allow(chat_integration).to receive_messages( project: project, project_id: project.id, - service_hook: true, webhook: webhook_url ) @@ -57,8 +38,8 @@ RSpec.describe Integrations::BaseChatNotification do context 'with a repository' do it 'returns true' do - expect(chat_service).to receive(:notify).and_return(true) - expect(chat_service.execute(data)).to be true + expect(chat_integration).to receive(:notify).and_return(true) + expect(chat_integration.execute(data)).to be true end end @@ -66,8 +47,8 @@ RSpec.describe Integrations::BaseChatNotification do it 'returns true' do subject.project = create(:project, :empty_repo) - expect(chat_service).to receive(:notify).and_return(true) - expect(chat_service.execute(data)).to be true + expect(chat_integration).to receive(:notify).and_return(true) + expect(chat_integration.execute(data)).to be true end end @@ -75,8 +56,8 @@ RSpec.describe Integrations::BaseChatNotification do it 'does not remove spaces' do allow(project).to receive(:full_name).and_return('Project Name') - expect(chat_service).to receive(:get_message).with(any_args, hash_including(project_name: 'Project Name')) - chat_service.execute(data) + expect(chat_integration).to receive(:get_message).with(any_args, hash_including(project_name: 'Project Name')) + chat_integration.execute(data) end end @@ -89,76 +70,76 @@ RSpec.describe Integrations::BaseChatNotification do let(:data) { Gitlab::DataBuilder::Note.build(note, user) } - shared_examples 'notifies the chat service' do + shared_examples 'notifies the chat integration' do specify do - expect(chat_service).to receive(:notify).with(any_args) + expect(chat_integration).to receive(:notify).with(any_args) - chat_service.execute(data) + chat_integration.execute(data) end end - shared_examples 'does not notify the chat service' do + shared_examples 'does not notify the chat integration' do specify do - expect(chat_service).not_to receive(:notify).with(any_args) + expect(chat_integration).not_to receive(:notify).with(any_args) - chat_service.execute(data) + chat_integration.execute(data) end end - it_behaves_like 'notifies the chat service' + it_behaves_like 'notifies the chat integration' context 'with label filter' do - subject(:chat_service) { described_class.new(labels_to_be_notified: '~Bug') } + subject(:chat_integration) { described_class.new(labels_to_be_notified: '~Bug') } - it_behaves_like 'notifies the chat service' + it_behaves_like 'notifies the chat integration' context 'MergeRequest events' do let(:data) { create(:merge_request, labels: [label]).to_hook_data(user) } - it_behaves_like 'notifies the chat service' + it_behaves_like 'notifies the chat integration' end context 'Issue events' do let(:data) { issue.to_hook_data(user) } - it_behaves_like 'notifies the chat service' + it_behaves_like 'notifies the chat integration' end end context 'when labels_to_be_notified_behavior is not defined' do - subject(:chat_service) { described_class.new(labels_to_be_notified: label_filter) } + subject(:chat_integration) { described_class.new(labels_to_be_notified: label_filter) } context 'no matching labels' do let(:label_filter) { '~some random label' } - it_behaves_like 'does not notify the chat service' + it_behaves_like 'does not notify the chat integration' end context 'only one label matches' do let(:label_filter) { '~some random label, ~Bug' } - it_behaves_like 'notifies the chat service' + it_behaves_like 'notifies the chat integration' end end context 'when labels_to_be_notified_behavior is blank' do - subject(:chat_service) { described_class.new(labels_to_be_notified: label_filter, labels_to_be_notified_behavior: '') } + subject(:chat_integration) { described_class.new(labels_to_be_notified: label_filter, labels_to_be_notified_behavior: '') } context 'no matching labels' do let(:label_filter) { '~some random label' } - it_behaves_like 'does not notify the chat service' + it_behaves_like 'does not notify the chat integration' end context 'only one label matches' do let(:label_filter) { '~some random label, ~Bug' } - it_behaves_like 'notifies the chat service' + it_behaves_like 'notifies the chat integration' end end context 'when labels_to_be_notified_behavior is match_any' do - subject(:chat_service) do + subject(:chat_integration) do described_class.new( labels_to_be_notified: label_filter, labels_to_be_notified_behavior: 'match_any' @@ -168,24 +149,24 @@ RSpec.describe Integrations::BaseChatNotification do context 'no label filter' do let(:label_filter) { nil } - it_behaves_like 'notifies the chat service' + it_behaves_like 'notifies the chat integration' end context 'no matching labels' do let(:label_filter) { '~some random label' } - it_behaves_like 'does not notify the chat service' + it_behaves_like 'does not notify the chat integration' end context 'only one label matches' do let(:label_filter) { '~some random label, ~Bug' } - it_behaves_like 'notifies the chat service' + it_behaves_like 'notifies the chat integration' end end context 'when labels_to_be_notified_behavior is match_all' do - subject(:chat_service) do + subject(:chat_integration) do described_class.new( labels_to_be_notified: label_filter, labels_to_be_notified_behavior: 'match_all' @@ -195,31 +176,31 @@ RSpec.describe Integrations::BaseChatNotification do context 'no label filter' do let(:label_filter) { nil } - it_behaves_like 'notifies the chat service' + it_behaves_like 'notifies the chat integration' end context 'no matching labels' do let(:label_filter) { '~some random label' } - it_behaves_like 'does not notify the chat service' + it_behaves_like 'does not notify the chat integration' end context 'only one label matches' do let(:label_filter) { '~some random label, ~Bug' } - it_behaves_like 'does not notify the chat service' + it_behaves_like 'does not notify the chat integration' end context 'labels matches exactly' do let(:label_filter) { '~Bug, ~Backend, ~Community contribution' } - it_behaves_like 'notifies the chat service' + it_behaves_like 'notifies the chat integration' end context 'labels matches but object has more' do let(:label_filter) { '~Bug, ~Backend' } - it_behaves_like 'notifies the chat service' + it_behaves_like 'notifies the chat integration' end context 'labels are distributed on multiple objects' do @@ -241,22 +222,22 @@ RSpec.describe Integrations::BaseChatNotification do }) end - it_behaves_like 'does not notify the chat service' + it_behaves_like 'does not notify the chat integration' end end end context 'with "channel" property' do before do - allow(chat_service).to receive(:channel).and_return(channel) + allow(chat_integration).to receive(:channel).and_return(channel) end context 'empty string' do let(:channel) { '' } it 'does not include the channel' do - expect(chat_service).to receive(:notify).with(any_args, hash_excluding(:channel)).and_return(true) - expect(chat_service.execute(data)).to be(true) + expect(chat_integration).to receive(:notify).with(any_args, hash_excluding(:channel)).and_return(true) + expect(chat_integration.execute(data)).to be(true) end end @@ -264,20 +245,20 @@ RSpec.describe Integrations::BaseChatNotification do let(:channel) { ' ' } it 'does not include the channel' do - expect(chat_service).to receive(:notify).with(any_args, hash_excluding(:channel)).and_return(true) - expect(chat_service.execute(data)).to be(true) + expect(chat_integration).to receive(:notify).with(any_args, hash_excluding(:channel)).and_return(true) + expect(chat_integration.execute(data)).to be(true) end end end shared_examples 'with channel specified' do |channel, expected_channels| before do - allow(chat_service).to receive(:push_channel).and_return(channel) + allow(chat_integration).to receive(:push_channel).and_return(channel) end it 'notifies all channels' do - expect(chat_service).to receive(:notify).with(any_args, hash_including(channel: expected_channels)).and_return(true) - expect(chat_service.execute(data)).to be(true) + expect(chat_integration).to receive(:notify).with(any_args, hash_including(channel: expected_channels)).and_return(true) + expect(chat_integration.execute(data)).to be(true) end end diff --git a/spec/models/integrations/base_issue_tracker_spec.rb b/spec/models/integrations/base_issue_tracker_spec.rb index 0f1bc39929a..25e27e96a84 100644 --- a/spec/models/integrations/base_issue_tracker_spec.rb +++ b/spec/models/integrations/base_issue_tracker_spec.rb @@ -7,26 +7,26 @@ RSpec.describe Integrations::BaseIssueTracker do let(:project) { create :project } describe 'only one issue tracker per project' do - let(:service) { Integrations::Redmine.new(project: project, active: true, issue_tracker_data: build(:issue_tracker_data)) } + let(:integration) { Integrations::Redmine.new(project: project, active: true, issue_tracker_data: build(:issue_tracker_data)) } before do create(:custom_issue_tracker_integration, project: project) end - context 'when service is changed manually by user' do + context 'when integration is changed manually by user' do it 'executes the validation' do - valid = service.valid?(:manual_change) + valid = integration.valid?(:manual_change) expect(valid).to be_falsey - expect(service.errors[:base]).to include( + expect(integration.errors[:base]).to include( 'Another issue tracker is already in use. Only one issue tracker service can be active at a time' ) end end - context 'when service is changed internally' do + context 'when integration is changed internally' do it 'does not execute the validation' do - expect(service.valid?).to be_truthy + expect(integration.valid?).to be_truthy end end end diff --git a/spec/models/integrations/bugzilla_spec.rb b/spec/models/integrations/bugzilla_spec.rb index e75fa8dd4d4..432306c8fa8 100644 --- a/spec/models/integrations/bugzilla_spec.rb +++ b/spec/models/integrations/bugzilla_spec.rb @@ -3,13 +3,8 @@ require 'spec_helper' RSpec.describe Integrations::Bugzilla do - 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 @@ -17,12 +12,12 @@ RSpec.describe Integrations::Bugzilla do it { is_expected.to validate_presence_of(:project_url) } it { is_expected.to validate_presence_of(:issues_url) } it { is_expected.to validate_presence_of(:new_issue_url) } - it_behaves_like 'issue tracker service URL attribute', :project_url - it_behaves_like 'issue tracker service URL attribute', :issues_url - it_behaves_like 'issue tracker service URL attribute', :new_issue_url + it_behaves_like 'issue tracker integration URL attribute', :project_url + it_behaves_like 'issue tracker integration URL attribute', :issues_url + it_behaves_like 'issue tracker integration URL attribute', :new_issue_url end - context 'when service is inactive' do + context 'when integration is inactive' do before do subject.active = false end diff --git a/spec/models/integrations/buildkite_spec.rb b/spec/models/integrations/buildkite_spec.rb index 7dc81da7003..4207ae0d555 100644 --- a/spec/models/integrations/buildkite_spec.rb +++ b/spec/models/integrations/buildkite_spec.rb @@ -8,34 +8,32 @@ RSpec.describe Integrations::Buildkite, :use_clean_rails_memory_store_caching do let(:project) { create(:project) } - subject(:service) do + subject(:integration) do described_class.create!( project: project, properties: { - service_hook: true, project_url: 'https://buildkite.com/organization-name/example-pipeline', token: 'secret-sauce-webhook-token:secret-sauce-status-token' } ) end - describe 'Associations' do - it { is_expected.to belong_to :project } - it { is_expected.to have_one :service_hook } + it_behaves_like Integrations::HasWebHook do + let(:hook_url) { 'https://webhook.buildkite.com/deliver/secret-sauce-webhook-token' } end describe 'Validations' do - context 'when service is active' do + context 'when integration is active' do before do subject.active = true end it { is_expected.to validate_presence_of(:project_url) } it { is_expected.to validate_presence_of(:token) } - it_behaves_like 'issue tracker service URL attribute', :project_url + it_behaves_like 'issue tracker integration URL attribute', :project_url end - context 'when service is inactive' do + context 'when integration is inactive' do before do subject.active = false end @@ -47,7 +45,7 @@ RSpec.describe Integrations::Buildkite, :use_clean_rails_memory_store_caching do describe '.supported_events' do it 'supports push, merge_request, and tag_push events' do - expect(service.supported_events).to eq %w(push merge_request tag_push) + expect(integration.supported_events).to eq %w(push merge_request tag_push) end end @@ -57,18 +55,18 @@ RSpec.describe Integrations::Buildkite, :use_clean_rails_memory_store_caching do end it 'always activates SSL verification after saved' do - service.create_service_hook(enable_ssl_verification: false) + integration.create_service_hook(enable_ssl_verification: false) - service.enable_ssl_verification = false - service.active = true + integration.enable_ssl_verification = false + integration.active = true - expect { service.save! } - .to change { service.service_hook.enable_ssl_verification }.from(false).to(true) + expect { integration.save! } + .to change { integration.service_hook.enable_ssl_verification }.from(false).to(true) end - describe '#webhook_url' do + describe '#hook_url' do it 'returns the webhook url' do - expect(service.webhook_url).to eq( + expect(integration.hook_url).to eq( 'https://webhook.buildkite.com/deliver/secret-sauce-webhook-token' ) end @@ -76,7 +74,7 @@ RSpec.describe Integrations::Buildkite, :use_clean_rails_memory_store_caching do describe '#commit_status_path' do it 'returns the correct status page' do - expect(service.commit_status_path('2ab7834c')).to eq( + expect(integration.commit_status_path('2ab7834c')).to eq( 'https://gitlab.buildkite.com/status/secret-sauce-status-token.json?commit=2ab7834c' ) end @@ -84,7 +82,7 @@ RSpec.describe Integrations::Buildkite, :use_clean_rails_memory_store_caching do describe '#build_page' do it 'returns the correct build page' do - expect(service.build_page('2ab7834c', nil)).to eq( + expect(integration.build_page('2ab7834c', nil)).to eq( 'https://buildkite.com/organization-name/example-pipeline/builds?commit=2ab7834c' ) end @@ -92,9 +90,9 @@ RSpec.describe Integrations::Buildkite, :use_clean_rails_memory_store_caching do describe '#commit_status' do it 'returns the contents of the reactive cache' do - stub_reactive_cache(service, { commit_status: 'foo' }, 'sha', 'ref') + stub_reactive_cache(integration, { commit_status: 'foo' }, 'sha', 'ref') - expect(service.commit_status('sha', 'ref')).to eq('foo') + expect(integration.commit_status('sha', 'ref')).to eq('foo') end end @@ -104,7 +102,7 @@ RSpec.describe Integrations::Buildkite, :use_clean_rails_memory_store_caching do 'https://gitlab.buildkite.com/status/secret-sauce-status-token.json?commit=123' end - subject { service.calculate_reactive_cache('123', 'unused')[:commit_status] } + subject { integration.calculate_reactive_cache('123', 'unused')[:commit_status] } it 'sets commit status to :error when status is 500' do stub_request(status: 500) diff --git a/spec/models/integrations/campfire_spec.rb b/spec/models/integrations/campfire_spec.rb index d68f8e0bd4e..0044e6fae21 100644 --- a/spec/models/integrations/campfire_spec.rb +++ b/spec/models/integrations/campfire_spec.rb @@ -5,13 +5,8 @@ require 'spec_helper' RSpec.describe Integrations::Campfire do include StubRequests - 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 @@ -19,7 +14,7 @@ RSpec.describe Integrations::Campfire do it { is_expected.to validate_presence_of(:token) } end - context 'when service is inactive' do + context 'when integration is inactive' do before do subject.active = false end @@ -37,7 +32,6 @@ RSpec.describe Integrations::Campfire do allow(@campfire_integration).to receive_messages( project_id: project.id, project: project, - service_hook: true, token: 'verySecret', subdomain: 'project-name', room: 'test-room' diff --git a/spec/models/integrations/confluence_spec.rb b/spec/models/integrations/confluence_spec.rb index 08e18c99376..e2f9316bc95 100644 --- a/spec/models/integrations/confluence_spec.rb +++ b/spec/models/integrations/confluence_spec.rb @@ -3,17 +3,12 @@ require 'spec_helper' RSpec.describe Integrations::Confluence do - describe 'Associations' do - it { is_expected.to belong_to :project } - it { is_expected.to have_one :service_hook } - end - describe 'Validations' do before do subject.active = active end - context 'when service is active' do + context 'when integration is active' do let(:active) { true } it { is_expected.not_to allow_value('https://example.com').for(:confluence_url) } @@ -35,7 +30,7 @@ RSpec.describe Integrations::Confluence do it { is_expected.to validate_presence_of(:confluence_url) } end - context 'when service is inactive' do + context 'when integration is inactive' do let(:active) { false } it { is_expected.not_to validate_presence_of(:confluence_url) } @@ -71,13 +66,13 @@ RSpec.describe Integrations::Confluence do subject { project.project_setting.has_confluence? } - it 'sets the property to true when service is active' do + it 'sets the property to true when integration is active' do create(:confluence_integration, project: project, active: true) is_expected.to be(true) end - it 'sets the property to false when service is not active' do + it 'sets the property to false when integration is not active' do create(:confluence_integration, project: project, active: false) is_expected.to be(false) diff --git a/spec/models/integrations/custom_issue_tracker_spec.rb b/spec/models/integrations/custom_issue_tracker_spec.rb index 25f2648e738..e1ffe7a74f0 100644 --- a/spec/models/integrations/custom_issue_tracker_spec.rb +++ b/spec/models/integrations/custom_issue_tracker_spec.rb @@ -3,13 +3,8 @@ require 'spec_helper' RSpec.describe Integrations::CustomIssueTracker do - 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 @@ -17,12 +12,12 @@ RSpec.describe Integrations::CustomIssueTracker do it { is_expected.to validate_presence_of(:project_url) } it { is_expected.to validate_presence_of(:issues_url) } it { is_expected.to validate_presence_of(:new_issue_url) } - it_behaves_like 'issue tracker service URL attribute', :project_url - it_behaves_like 'issue tracker service URL attribute', :issues_url - it_behaves_like 'issue tracker service URL attribute', :new_issue_url + it_behaves_like 'issue tracker integration URL attribute', :project_url + it_behaves_like 'issue tracker integration URL attribute', :issues_url + it_behaves_like 'issue tracker integration URL attribute', :new_issue_url end - context 'when service is inactive' do + context 'when integration is inactive' do before do subject.active = false end diff --git a/spec/models/integrations/datadog_spec.rb b/spec/models/integrations/datadog_spec.rb index 165b21840e0..e2749ab1bc1 100644 --- a/spec/models/integrations/datadog_spec.rb +++ b/spec/models/integrations/datadog_spec.rb @@ -10,13 +10,13 @@ RSpec.describe Integrations::Datadog do let(:active) { true } let(:dd_site) { 'datadoghq.com' } - let(:default_url) { 'https://webhooks-http-intake.logs.datadoghq.com/v1/input/' } + let(:default_url) { 'https://webhooks-http-intake.logs.datadoghq.com/api/v2/webhook' } let(:api_url) { '' } let(:api_key) { SecureRandom.hex(32) } let(:dd_env) { 'ci' } let(:dd_service) { 'awesome-gitlab' } - let(:expected_hook_url) { default_url + api_key + "?env=#{dd_env}&service=#{dd_service}" } + let(:expected_hook_url) { default_url + "?dd-api-key=#{api_key}&env=#{dd_env}&service=#{dd_service}" } let(:instance) do described_class.new( @@ -38,9 +38,9 @@ RSpec.describe Integrations::Datadog do let(:pipeline_data) { Gitlab::DataBuilder::Pipeline.build(pipeline) } let(:build_data) { Gitlab::DataBuilder::Build.build(build) } - describe 'associations' do - it { is_expected.to belong_to(:project) } - it { is_expected.to have_one(:service_hook) } + it_behaves_like Integrations::HasWebHook do + let(:integration) { instance } + let(:hook_url) { "#{described_class::URL_TEMPLATE % { datadog_domain: dd_site }}?dd-api-key=#{api_key}&env=#{dd_env}&service=#{dd_service}" } end describe 'validations' do @@ -65,7 +65,7 @@ RSpec.describe Integrations::Datadog do context 'with custom api_url' do let(:dd_site) { '' } - let(:api_url) { 'https://webhooks-http-intake.logs.datad0g.com/v1/input/' } + let(:api_url) { 'https://webhooks-http-intake.logs.datad0g.com/api/v2/webhook' } it { is_expected.not_to validate_presence_of(:datadog_site) } it { is_expected.to validate_presence_of(:api_url) } @@ -91,7 +91,7 @@ RSpec.describe Integrations::Datadog do end end - context 'when service is not active' do + context 'when integration is not active' do let(:active) { false } it { is_expected.to be_valid } @@ -107,9 +107,9 @@ RSpec.describe Integrations::Datadog do end context 'with custom URL' do - let(:api_url) { 'https://webhooks-http-intake.logs.datad0g.com/v1/input/' } + let(:api_url) { 'https://webhooks-http-intake.logs.datad0g.com/api/v2/webhook' } - it { is_expected.to eq(api_url + api_key + "?env=#{dd_env}&service=#{dd_service}") } + it { is_expected.to eq(api_url + "?dd-api-key=#{api_key}&env=#{dd_env}&service=#{dd_service}") } context 'blank' do let(:api_url) { '' } @@ -122,7 +122,7 @@ RSpec.describe Integrations::Datadog do let(:dd_service) { '' } let(:dd_env) { '' } - it { is_expected.to eq(default_url + api_key) } + it { is_expected.to eq(default_url + "?dd-api-key=#{api_key}") } end end diff --git a/spec/models/integrations/discord_spec.rb b/spec/models/integrations/discord_spec.rb index bff6a8ee5b2..b85620782c1 100644 --- a/spec/models/integrations/discord_spec.rb +++ b/spec/models/integrations/discord_spec.rb @@ -11,7 +11,9 @@ RSpec.describe Integrations::Discord do embeds: [ include( author: include(name: be_present), - description: be_present + description: be_present, + color: be_present, + timestamp: be_present ) ] } @@ -33,7 +35,6 @@ RSpec.describe Integrations::Discord do allow(subject).to receive_messages( project: project, project_id: project.id, - service_hook: true, webhook: webhook_url ) @@ -47,15 +48,19 @@ RSpec.describe Integrations::Discord do allow(client).to receive(:execute).and_yield(builder) end - subject.execute(sample_data) + freeze_time do + subject.execute(sample_data) - expect(builder.to_json_hash[:embeds].first).to include( - description: start_with("#{user.name} pushed to branch [master](http://localhost/#{project.namespace.path}/#{project.path}/commits/master) of"), - author: hash_including( - icon_url: start_with('https://www.gravatar.com/avatar/'), - name: user.name + expect(builder.to_json_hash[:embeds].first).to include( + description: start_with("#{user.name} pushed to branch [master](http://localhost/#{project.namespace.path}/#{project.path}/commits/master) of"), + author: hash_including( + icon_url: start_with('https://www.gravatar.com/avatar/'), + name: user.name + ), + color: 16543014, + timestamp: Time.now.utc.iso8601 ) - ) + end end context 'DNS rebind to local address' do diff --git a/spec/models/integrations/drone_ci_spec.rb b/spec/models/integrations/drone_ci_spec.rb index 137f078edca..062e23d628e 100644 --- a/spec/models/integrations/drone_ci_spec.rb +++ b/spec/models/integrations/drone_ci_spec.rb @@ -5,11 +5,6 @@ require 'spec_helper' RSpec.describe Integrations::DroneCi, :use_clean_rails_memory_store_caching do include ReactiveCachingHelpers - describe 'associations' do - it { is_expected.to belong_to(:project) } - it { is_expected.to have_one(:service_hook) } - end - describe 'validations' do context 'active' do before do @@ -18,7 +13,7 @@ RSpec.describe Integrations::DroneCi, :use_clean_rails_memory_store_caching do it { is_expected.to validate_presence_of(:token) } it { is_expected.to validate_presence_of(:drone_url) } - it_behaves_like 'issue tracker service URL attribute', :drone_url + it_behaves_like 'issue tracker integration URL attribute', :drone_url end context 'inactive' do @@ -32,7 +27,15 @@ RSpec.describe Integrations::DroneCi, :use_clean_rails_memory_store_caching do end shared_context :drone_ci_integration do - let(:drone) { described_class.new } + subject(:drone) do + described_class.new( + project: project, + active: true, + drone_url: drone_url, + token: token + ) + end + let(:project) { create(:project, :repository, name: 'project') } let(:path) { project.full_path } let(:drone_url) { 'http://drone.example.com' } @@ -45,16 +48,6 @@ RSpec.describe Integrations::DroneCi, :use_clean_rails_memory_store_caching do let(:build_page) { "#{drone_url}/gitlab/#{path}/redirect/commits/#{sha}?branch=#{branch}" } let(:commit_status_path) { "#{drone_url}/gitlab/#{path}/commits/#{sha}?branch=#{branch}&access_token=#{token}" } - before do - allow(drone).to receive_messages( - project_id: project.id, - project: project, - active: true, - drone_url: drone_url, - token: token - ) - end - def stub_request(status: 200, body: nil) body ||= %q({"status":"success"}) @@ -66,7 +59,21 @@ RSpec.describe Integrations::DroneCi, :use_clean_rails_memory_store_caching do end end - describe "service page/path methods" do + it_behaves_like Integrations::HasWebHook do + include_context :drone_ci_integration + + let(:integration) { drone } + let(:hook_url) { "#{drone_url}/hook?owner=#{project.namespace.full_path}&name=#{project.path}&access_token=#{token}" } + + it 'does not create a hook if project is not present' do + integration.project = nil + integration.instance = true + + expect { integration.save! }.not_to change(ServiceHook, :count) + end + end + + describe "integration page/path methods" do include_context :drone_ci_integration it { expect(drone.build_page(sha, branch)).to eq(build_page) } @@ -137,10 +144,17 @@ RSpec.describe Integrations::DroneCi, :use_clean_rails_memory_store_caching do Gitlab::DataBuilder::Push.build_sample(project, user) end - it do - service_hook = double - expect(service_hook).to receive(:execute) - expect(drone).to receive(:service_hook).and_return(service_hook) + it 'executes the webhook' do + expect(drone).to receive(:execute_web_hook!).with(push_sample_data) + + drone.execute(push_sample_data) + end + + it 'does not try to execute the webhook if the integration is not in a project' do + drone.project = nil + drone.instance = true + + expect(drone).not_to receive(:execute_web_hook!) drone.execute(push_sample_data) end diff --git a/spec/models/integrations/emails_on_push_spec.rb b/spec/models/integrations/emails_on_push_spec.rb index c82d4bdff9b..bdca267f6cb 100644 --- a/spec/models/integrations/emails_on_push_spec.rb +++ b/spec/models/integrations/emails_on_push_spec.rb @@ -6,7 +6,7 @@ RSpec.describe Integrations::EmailsOnPush do let_it_be(:project) { create_default(:project).freeze } describe 'Validations' do - context 'when service is active' do + context 'when integration is active' do before do subject.active = true end @@ -14,7 +14,7 @@ RSpec.describe Integrations::EmailsOnPush do it { is_expected.to validate_presence_of(:recipients) } end - context 'when service is inactive' do + context 'when integration is inactive' do before do subject.active = false end @@ -27,7 +27,7 @@ RSpec.describe Integrations::EmailsOnPush do stub_const("#{described_class}::RECIPIENTS_LIMIT", 2) end - subject(:service) { described_class.new(project: project, recipients: recipients, active: true) } + subject(:integration) { described_class.new(project: project, recipients: recipients, active: true) } context 'valid number of recipients' do let(:recipients) { 'foo@bar.com duplicate@example.com Duplicate@example.com invalid-email' } @@ -43,14 +43,14 @@ RSpec.describe Integrations::EmailsOnPush do it { is_expected.not_to be_valid } it 'adds an error message' do - service.valid? + integration.valid? - expect(service.errors).to contain_exactly('Recipients can\'t exceed 2') + expect(integration.errors).to contain_exactly('Recipients can\'t exceed 2') end - context 'when service is not active' do + context 'when integration is not active' do before do - service.active = false + integration.active = false end it { is_expected.to be_valid } diff --git a/spec/models/integrations/ewm_spec.rb b/spec/models/integrations/ewm_spec.rb index 38897adb447..49681fefe55 100644 --- a/spec/models/integrations/ewm_spec.rb +++ b/spec/models/integrations/ewm_spec.rb @@ -3,13 +3,8 @@ require 'spec_helper' RSpec.describe Integrations::Ewm do - 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 @@ -17,12 +12,12 @@ RSpec.describe Integrations::Ewm do it { is_expected.to validate_presence_of(:project_url) } it { is_expected.to validate_presence_of(:issues_url) } it { is_expected.to validate_presence_of(:new_issue_url) } - it_behaves_like 'issue tracker service URL attribute', :project_url - it_behaves_like 'issue tracker service URL attribute', :issues_url - it_behaves_like 'issue tracker service URL attribute', :new_issue_url + it_behaves_like 'issue tracker integration URL attribute', :project_url + it_behaves_like 'issue tracker integration URL attribute', :issues_url + it_behaves_like 'issue tracker integration URL attribute', :new_issue_url end - context 'when service is inactive' do + context 'when integration is inactive' do before do subject.active = false end diff --git a/spec/models/integrations/external_wiki_spec.rb b/spec/models/integrations/external_wiki_spec.rb index 8c20b810301..e4d6a1c7c84 100644 --- a/spec/models/integrations/external_wiki_spec.rb +++ b/spec/models/integrations/external_wiki_spec.rb @@ -3,22 +3,17 @@ require 'spec_helper' RSpec.describe Integrations::ExternalWiki do - 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 it { is_expected.to validate_presence_of(:external_wiki_url) } - it_behaves_like 'issue tracker service URL attribute', :external_wiki_url + it_behaves_like 'issue tracker integration URL attribute', :external_wiki_url end - context 'when service is inactive' do + context 'when integration is inactive' do before do subject.active = false end diff --git a/spec/models/integrations/flowdock_spec.rb b/spec/models/integrations/flowdock_spec.rb index 189831fa32d..daafb1b3958 100644 --- a/spec/models/integrations/flowdock_spec.rb +++ b/spec/models/integrations/flowdock_spec.rb @@ -3,13 +3,8 @@ require 'spec_helper' RSpec.describe Integrations::Flowdock do - 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 @@ -17,7 +12,7 @@ RSpec.describe Integrations::Flowdock do it { is_expected.to validate_presence_of(:token) } end - context 'when service is inactive' do + context 'when integration is inactive' do before do subject.active = false end @@ -38,7 +33,6 @@ RSpec.describe Integrations::Flowdock do allow(flowdock_integration).to receive_messages( project_id: project.id, project: project, - service_hook: true, token: 'verySecret' ) WebMock.stub_request(:post, api_url) diff --git a/spec/models/integrations/irker_spec.rb b/spec/models/integrations/irker_spec.rb index a69be1292e0..8b207e8b43e 100644 --- a/spec/models/integrations/irker_spec.rb +++ b/spec/models/integrations/irker_spec.rb @@ -5,13 +5,8 @@ require 'socket' require 'json' RSpec.describe Integrations::Irker do - 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 @@ -19,7 +14,7 @@ RSpec.describe Integrations::Irker do it { is_expected.to validate_presence_of(:recipients) } end - context 'when service is inactive' do + context 'when integration is inactive' do before do subject.active = false end @@ -46,7 +41,6 @@ RSpec.describe Integrations::Irker do active: true, project: project, project_id: project.id, - service_hook: true, server_host: @irker_server.addr[2], server_port: @irker_server.addr[1], default_irc_uri: 'irc://chat.freenode.net/', diff --git a/spec/models/integrations/jenkins_spec.rb b/spec/models/integrations/jenkins_spec.rb index 2374dfe4480..9eb2a7fc098 100644 --- a/spec/models/integrations/jenkins_spec.rb +++ b/spec/models/integrations/jenkins_spec.rb @@ -24,14 +24,14 @@ RSpec.describe Integrations::Jenkins do let(:jenkins_authorization) { "Basic " + ::Base64.strict_encode64(jenkins_username + ':' + jenkins_password) } - describe 'Associations' do - it { is_expected.to belong_to :project } - it { is_expected.to have_one :service_hook } + it_behaves_like Integrations::HasWebHook do + let(:integration) { described_class.new(jenkins_params) } + let(:hook_url) { "http://#{ERB::Util.url_encode jenkins_username}:#{ERB::Util.url_encode jenkins_password}@jenkins.example.com/project/my_project" } end describe 'username validation' do - before do - @jenkins_service = described_class.create!( + let(:jenkins_integration) do + described_class.create!( active: active, project: project, properties: { @@ -43,9 +43,9 @@ RSpec.describe Integrations::Jenkins do ) end - subject { @jenkins_service } + subject { jenkins_integration } - context 'when the service is active' do + context 'when the integration is active' do let(:active) { true } context 'when password was not touched' do @@ -74,7 +74,7 @@ RSpec.describe Integrations::Jenkins do end end - context 'when the service is inactive' do + context 'when the integration is inactive' do let(:active) { false } it { is_expected.not_to validate_presence_of :username } @@ -84,7 +84,7 @@ RSpec.describe Integrations::Jenkins do describe '#hook_url' do let(:username) { nil } let(:password) { nil } - let(:jenkins_service) do + let(:jenkins_integration) do described_class.new( project: project, properties: { @@ -96,7 +96,7 @@ RSpec.describe Integrations::Jenkins do ) end - subject { jenkins_service.hook_url } + subject { jenkins_integration.hook_url } context 'when the jenkins_url has no relative path' do let(:jenkins_url) { 'http://jenkins.example.com/' } @@ -138,10 +138,10 @@ RSpec.describe Integrations::Jenkins do user = create(:user, username: 'username') project = create(:project, name: 'project') push_sample_data = Gitlab::DataBuilder::Push.build_sample(project, user) - jenkins_service = described_class.create!(jenkins_params) + jenkins_integration = described_class.create!(jenkins_params) stub_request(:post, jenkins_hook_url).with(headers: { 'Authorization' => jenkins_authorization }) - result = jenkins_service.test(push_sample_data) + result = jenkins_integration.test(push_sample_data) expect(result).to eq({ success: true, result: '' }) end @@ -152,20 +152,20 @@ RSpec.describe Integrations::Jenkins do let(:namespace) { create(:group, :private) } let(:project) { create(:project, :private, name: 'project', namespace: namespace) } let(:push_sample_data) { Gitlab::DataBuilder::Push.build_sample(project, user) } - let(:jenkins_service) { described_class.create!(jenkins_params) } + let(:jenkins_integration) { described_class.create!(jenkins_params) } before do stub_request(:post, jenkins_hook_url) end it 'invokes the Jenkins API' do - jenkins_service.execute(push_sample_data) + jenkins_integration.execute(push_sample_data) expect(a_request(:post, jenkins_hook_url)).to have_been_made.once end it 'adds default web hook headers to the request' do - jenkins_service.execute(push_sample_data) + jenkins_integration.execute(push_sample_data) expect( a_request(:post, jenkins_hook_url) @@ -174,7 +174,7 @@ RSpec.describe Integrations::Jenkins do end it 'request url contains properly serialized username and password' do - jenkins_service.execute(push_sample_data) + jenkins_integration.execute(push_sample_data) expect( a_request(:post, 'http://jenkins.example.com/project/my_project') @@ -187,8 +187,8 @@ RSpec.describe Integrations::Jenkins do let(:project) { create(:project) } context 'when a password was previously set' do - before do - @jenkins_service = described_class.create!( + let(:jenkins_integration) do + described_class.create!( project: project, properties: { jenkins_url: 'http://jenkins.example.com/', @@ -199,42 +199,47 @@ RSpec.describe Integrations::Jenkins do end it 'resets password if url changed' do - @jenkins_service.jenkins_url = 'http://jenkins-edited.example.com/' - @jenkins_service.save! - expect(@jenkins_service.password).to be_nil + jenkins_integration.jenkins_url = 'http://jenkins-edited.example.com/' + jenkins_integration.save! + + expect(jenkins_integration.password).to be_nil end it 'resets password if username is blank' do - @jenkins_service.username = '' - @jenkins_service.save! - expect(@jenkins_service.password).to be_nil + jenkins_integration.username = '' + jenkins_integration.save! + + expect(jenkins_integration.password).to be_nil end it 'does not reset password if username changed' do - @jenkins_service.username = 'some_name' - @jenkins_service.save! - expect(@jenkins_service.password).to eq('password') + jenkins_integration.username = 'some_name' + jenkins_integration.save! + + expect(jenkins_integration.password).to eq('password') end it 'does not reset password if new url is set together with password, even if it\'s the same password' do - @jenkins_service.jenkins_url = 'http://jenkins_edited.example.com/' - @jenkins_service.password = 'password' - @jenkins_service.save! - expect(@jenkins_service.password).to eq('password') - expect(@jenkins_service.jenkins_url).to eq('http://jenkins_edited.example.com/') + jenkins_integration.jenkins_url = 'http://jenkins_edited.example.com/' + jenkins_integration.password = 'password' + jenkins_integration.save! + + expect(jenkins_integration.password).to eq('password') + expect(jenkins_integration.jenkins_url).to eq('http://jenkins_edited.example.com/') end it 'resets password if url changed, even if setter called multiple times' do - @jenkins_service.jenkins_url = 'http://jenkins1.example.com/' - @jenkins_service.jenkins_url = 'http://jenkins1.example.com/' - @jenkins_service.save! - expect(@jenkins_service.password).to be_nil + jenkins_integration.jenkins_url = 'http://jenkins1.example.com/' + jenkins_integration.jenkins_url = 'http://jenkins1.example.com/' + jenkins_integration.save! + + expect(jenkins_integration.password).to be_nil end end context 'when no password was previously set' do - before do - @jenkins_service = described_class.create!( + let(:jenkins_integration) do + described_class.create!( project: create(:project), properties: { jenkins_url: 'http://jenkins.example.com/', @@ -244,11 +249,12 @@ RSpec.describe Integrations::Jenkins do end it 'saves password if new url is set together with password' do - @jenkins_service.jenkins_url = 'http://jenkins_edited.example.com/' - @jenkins_service.password = 'password' - @jenkins_service.save! - expect(@jenkins_service.password).to eq('password') - expect(@jenkins_service.jenkins_url).to eq('http://jenkins_edited.example.com/') + jenkins_integration.jenkins_url = 'http://jenkins_edited.example.com/' + jenkins_integration.password = 'password' + jenkins_integration.save! + + expect(jenkins_integration.password).to eq('password') + expect(jenkins_integration.jenkins_url).to eq('http://jenkins_edited.example.com/') end end end diff --git a/spec/models/integrations/jira_spec.rb b/spec/models/integrations/jira_spec.rb index 23ade570383..6ca72d68bbb 100644 --- a/spec/models/integrations/jira_spec.rb +++ b/spec/models/integrations/jira_spec.rb @@ -14,7 +14,7 @@ RSpec.describe Integrations::Jira do let(:password) { 'jira-password' } let(:transition_id) { 'test27' } let(:server_info_results) { { 'deploymentType' => 'Cloud' } } - let(:jira_service) do + let(:jira_integration) do described_class.new( project: project, url: url, @@ -100,20 +100,15 @@ RSpec.describe Integrations::Jira do end describe '#fields' do - let(:service) { create(:jira_service) } + let(:integration) { create(:jira_integration) } - subject(:fields) { service.fields } + subject(:fields) { integration.fields } it 'returns custom fields' do expect(fields.pluck(:name)).to eq(%w[url api_url username password]) end end - describe 'Associations' do - it { is_expected.to belong_to :project } - it { is_expected.to have_one :service_hook } - end - describe '.reference_pattern' do using RSpec::Parameterized::TableSyntax @@ -146,39 +141,35 @@ RSpec.describe Integrations::Jira do } end - subject { described_class.create!(params) } + subject(:integration) { described_class.create!(params) } it 'does not store data into properties' do - expect(subject.properties).to be_nil + expect(integration.properties).to be_nil end it 'stores data in data_fields correctly' do - service = subject - - expect(service.jira_tracker_data.url).to eq(url) - expect(service.jira_tracker_data.api_url).to eq(api_url) - expect(service.jira_tracker_data.username).to eq(username) - expect(service.jira_tracker_data.password).to eq(password) - expect(service.jira_tracker_data.jira_issue_transition_id).to eq(transition_id) - expect(service.jira_tracker_data.deployment_cloud?).to be_truthy + expect(integration.jira_tracker_data.url).to eq(url) + expect(integration.jira_tracker_data.api_url).to eq(api_url) + expect(integration.jira_tracker_data.username).to eq(username) + expect(integration.jira_tracker_data.password).to eq(password) + expect(integration.jira_tracker_data.jira_issue_transition_id).to eq(transition_id) + expect(integration.jira_tracker_data.deployment_cloud?).to be_truthy end context 'when loading serverInfo' do - let(:jira_service) { subject } - - context 'from a Cloud instance' do + context 'with a Cloud instance' do let(:server_info_results) { { 'deploymentType' => 'Cloud' } } it 'is detected' do - expect(jira_service.jira_tracker_data.deployment_cloud?).to be_truthy + expect(integration.jira_tracker_data).to be_deployment_cloud end end - context 'from a Server instance' do + context 'with a Server instance' do let(:server_info_results) { { 'deploymentType' => 'Server' } } it 'is detected' do - expect(jira_service.jira_tracker_data.deployment_server?).to be_truthy + expect(integration.jira_tracker_data).to be_deployment_server end end @@ -189,7 +180,7 @@ RSpec.describe Integrations::Jira do let(:api_url) { 'http://example-api.atlassian.net' } it 'deployment_type is set to cloud' do - expect(jira_service.jira_tracker_data.deployment_cloud?).to be_truthy + expect(integration.jira_tracker_data).to be_deployment_cloud end end @@ -197,7 +188,7 @@ RSpec.describe Integrations::Jira do let(:api_url) { 'http://my-jira-api.someserver.com' } it 'deployment_type is set to server' do - expect(jira_service.jira_tracker_data.deployment_server?).to be_truthy + expect(integration.jira_tracker_data).to be_deployment_server end end end @@ -210,7 +201,7 @@ RSpec.describe Integrations::Jira do it 'deployment_type is set to cloud' do expect(Gitlab::AppLogger).to receive(:warn).with(message: "Jira API returned no ServerInfo, setting deployment_type from URL", server_info: server_info_results, url: api_url) - expect(jira_service.jira_tracker_data.deployment_cloud?).to be_truthy + expect(integration.jira_tracker_data).to be_deployment_cloud end end @@ -219,7 +210,7 @@ RSpec.describe Integrations::Jira do it 'deployment_type is set to server' do expect(Gitlab::AppLogger).to receive(:warn).with(message: "Jira API returned no ServerInfo, setting deployment_type from URL", server_info: server_info_results, url: api_url) - expect(jira_service.jira_tracker_data.deployment_server?).to be_truthy + expect(integration.jira_tracker_data).to be_deployment_server end end end @@ -253,11 +244,11 @@ RSpec.describe Integrations::Jira do context 'reading data' do it 'reads data correctly' do - expect(service.url).to eq(url) - expect(service.api_url).to eq(api_url) - expect(service.username).to eq(username) - expect(service.password).to eq(password) - expect(service.jira_issue_transition_id).to eq(transition_id) + expect(integration.url).to eq(url) + expect(integration.api_url).to eq(api_url) + expect(integration.username).to eq(username) + expect(integration.password).to eq(password) + expect(integration.jira_issue_transition_id).to eq(transition_id) end end @@ -267,15 +258,11 @@ RSpec.describe Integrations::Jira do let_it_be(:new_url) { 'http://jira-new.example.com' } before do - service.update!(username: new_username, url: new_url) - end - - it 'leaves properties field emtpy' do - # expect(service.reload.properties).to be_empty + integration.update!(username: new_username, url: new_url) end it 'stores updated data in jira_tracker_data table' do - data = service.jira_tracker_data.reload + data = integration.jira_tracker_data.reload expect(data.url).to eq(new_url) expect(data.api_url).to eq(api_url) @@ -288,15 +275,15 @@ RSpec.describe Integrations::Jira do context 'when updating the url, api_url, username, or password' do context 'when updating the integration' do it 'updates deployment type' do - service.update!(url: 'http://first.url') - service.jira_tracker_data.update!(deployment_type: 'server') + integration.update!(url: 'http://first.url') + integration.jira_tracker_data.update!(deployment_type: 'server') - expect(service.jira_tracker_data.deployment_server?).to be_truthy + expect(integration.jira_tracker_data.deployment_server?).to be_truthy - service.update!(api_url: 'http://another.url') - service.jira_tracker_data.reload + integration.update!(api_url: 'http://another.url') + integration.jira_tracker_data.reload - expect(service.jira_tracker_data.deployment_cloud?).to be_truthy + expect(integration.jira_tracker_data.deployment_cloud?).to be_truthy expect(WebMock).to have_requested(:get, /serverInfo/).twice end end @@ -305,34 +292,34 @@ RSpec.describe Integrations::Jira do let(:server_info_results) { {} } it 'updates deployment type' do - service.update!(url: nil, api_url: nil, active: false) + integration.update!(url: nil, api_url: nil, active: false) - service.jira_tracker_data.reload + integration.jira_tracker_data.reload - expect(service.jira_tracker_data.deployment_unknown?).to be_truthy + expect(integration.jira_tracker_data.deployment_unknown?).to be_truthy end end it 'calls serverInfo for url' do - service.update!(url: 'http://first.url') + integration.update!(url: 'http://first.url') expect(WebMock).to have_requested(:get, /serverInfo/) end it 'calls serverInfo for api_url' do - service.update!(api_url: 'http://another.url') + integration.update!(api_url: 'http://another.url') expect(WebMock).to have_requested(:get, /serverInfo/) end it 'calls serverInfo for username' do - service.update!(username: 'test-user') + integration.update!(username: 'test-user') expect(WebMock).to have_requested(:get, /serverInfo/) end it 'calls serverInfo for password' do - service.update!(password: 'test-password') + integration.update!(password: 'test-password') expect(WebMock).to have_requested(:get, /serverInfo/) end @@ -340,7 +327,8 @@ RSpec.describe Integrations::Jira do context 'when not updating the url, api_url, username, or password' do it 'does not update deployment type' do - expect {service.update!(jira_issue_transition_id: 'jira_issue_transition_id')}.to raise_error(ActiveRecord::RecordInvalid) + expect { integration.update!(jira_issue_transition_id: 'jira_issue_transition_id') } + .to raise_error(ActiveRecord::RecordInvalid) expect(WebMock).not_to have_requested(:get, /serverInfo/) end @@ -348,9 +336,9 @@ RSpec.describe Integrations::Jira do context 'when not allowed to test an instance or group' do it 'does not update deployment type' do - allow(service).to receive(:can_test?).and_return(false) + allow(integration).to receive(:testable?).and_return(false) - service.update!(url: 'http://first.url') + integration.update!(url: 'http://first.url') expect(WebMock).not_to have_requested(:get, /serverInfo/) end @@ -368,68 +356,68 @@ RSpec.describe Integrations::Jira do end it 'resets password if url changed' do - service - service.url = 'http://jira_edited.example.com' - service.save! + integration + integration.url = 'http://jira_edited.example.com' + integration.save! - expect(service.reload.url).to eq('http://jira_edited.example.com') - expect(service.password).to be_nil + expect(integration.reload.url).to eq('http://jira_edited.example.com') + expect(integration.password).to be_nil end it 'does not reset password if url "changed" to the same url as before' do - service.url = 'http://jira.example.com' - service.save! + integration.url = 'http://jira.example.com' + integration.save! - expect(service.reload.url).to eq('http://jira.example.com') - expect(service.password).not_to be_nil + expect(integration.reload.url).to eq('http://jira.example.com') + expect(integration.password).not_to be_nil end it 'resets password if url not changed but api url added' do - service.api_url = 'http://jira_edited.example.com/rest/api/2' - service.save! + integration.api_url = 'http://jira_edited.example.com/rest/api/2' + integration.save! - expect(service.reload.api_url).to eq('http://jira_edited.example.com/rest/api/2') - expect(service.password).to be_nil + expect(integration.reload.api_url).to eq('http://jira_edited.example.com/rest/api/2') + expect(integration.password).to be_nil end it 'does not reset password if new url is set together with password, even if it\'s the same password' do - service.url = 'http://jira_edited.example.com' - service.password = password - service.save! + integration.url = 'http://jira_edited.example.com' + integration.password = password + integration.save! - expect(service.password).to eq(password) - expect(service.url).to eq('http://jira_edited.example.com') + expect(integration.password).to eq(password) + expect(integration.url).to eq('http://jira_edited.example.com') end it 'resets password if url changed, even if setter called multiple times' do - service.url = 'http://jira1.example.com/rest/api/2' - service.url = 'http://jira1.example.com/rest/api/2' - service.save! + integration.url = 'http://jira1.example.com/rest/api/2' + integration.url = 'http://jira1.example.com/rest/api/2' + integration.save! - expect(service.password).to be_nil + expect(integration.password).to be_nil end it 'does not reset password if username changed' do - service.username = 'some_name' - service.save! + integration.username = 'some_name' + integration.save! - expect(service.reload.password).to eq(password) + expect(integration.reload.password).to eq(password) end it 'does not reset password if password changed' do - service.url = 'http://jira_edited.example.com' - service.password = 'new_password' - service.save! + integration.url = 'http://jira_edited.example.com' + integration.password = 'new_password' + integration.save! - expect(service.reload.password).to eq('new_password') + expect(integration.reload.password).to eq('new_password') end it 'does not reset password if the password is touched and same as before' do - service.url = 'http://jira_edited.example.com' - service.password = password - service.save! + integration.url = 'http://jira_edited.example.com' + integration.password = password + integration.save! - expect(service.reload.password).to eq(password) + expect(integration.reload.password).to eq(password) end end @@ -443,23 +431,23 @@ RSpec.describe Integrations::Jira do end it 'resets password if api url changed' do - service.api_url = 'http://jira_edited.example.com/rest/api/2' - service.save! + integration.api_url = 'http://jira_edited.example.com/rest/api/2' + integration.save! - expect(service.password).to be_nil + expect(integration.password).to be_nil end it 'does not reset password if url changed' do - service.url = 'http://jira_edited.example.com' - service.save! + integration.url = 'http://jira_edited.example.com' + integration.save! - expect(service.password).to eq(password) + expect(integration.password).to eq(password) end it 'resets password if api url set to empty' do - service.update!(api_url: '') + integration.update!(api_url: '') - expect(service.reload.password).to be_nil + expect(integration.reload.password).to be_nil end end end @@ -472,11 +460,11 @@ RSpec.describe Integrations::Jira do end it 'saves password if new url is set together with password' do - service.url = 'http://jira_edited.example.com/rest/api/2' - service.password = 'password' - service.save! - expect(service.reload.password).to eq('password') - expect(service.reload.url).to eq('http://jira_edited.example.com/rest/api/2') + integration.url = 'http://jira_edited.example.com/rest/api/2' + integration.password = 'password' + integration.save! + expect(integration.reload.password).to eq('password') + expect(integration.reload.url).to eq('http://jira_edited.example.com/rest/api/2') end end end @@ -486,16 +474,16 @@ RSpec.describe Integrations::Jira do # this will be removed as part of https://gitlab.com/gitlab-org/gitlab/issues/29404 context 'when data are stored in properties' do let(:properties) { data_params } - let!(:service) do - create(:jira_service, :without_properties_callback, properties: properties.merge(additional: 'something')) + let!(:integration) do + create(:jira_integration, :without_properties_callback, properties: properties.merge(additional: 'something')) end it_behaves_like 'handles jira fields' end context 'when data are stored in separated fields' do - let(:service) do - create(:jira_service, data_params.merge(properties: {})) + let(:integration) do + create(:jira_integration, data_params.merge(properties: {})) end it_behaves_like 'handles jira fields' @@ -503,8 +491,8 @@ RSpec.describe Integrations::Jira do context 'when data are stored in both properties and separated fields' do let(:properties) { data_params } - let(:service) do - create(:jira_service, :without_properties_callback, active: false, properties: properties).tap do |integration| + let(:integration) do + create(:jira_integration, :without_properties_callback, active: false, properties: properties).tap do |integration| create(:jira_tracker_data, data_params.merge(integration: integration)) end end @@ -522,7 +510,7 @@ RSpec.describe Integrations::Jira do end it 'call the Jira API to get the issue' do - jira_service.find_issue(issue_key) + jira_integration.find_issue(issue_key) expect(WebMock).to have_requested(:get, issue_url) end @@ -531,7 +519,7 @@ RSpec.describe Integrations::Jira do let(:issue_url) { "#{url}/rest/api/2/issue/#{issue_key}?expand=renderedFields,transitions" } it 'calls the Jira API with the options to get the issue' do - jira_service.find_issue(issue_key, rendered_fields: true, transitions: true) + jira_integration.find_issue(issue_key, rendered_fields: true, transitions: true) expect(WebMock).to have_requested(:get, issue_url) end @@ -558,16 +546,16 @@ RSpec.describe Integrations::Jira do end subject(:close_issue) do - jira_service.close_issue(resource, ExternalIssue.new(issue_key, project)) + jira_integration.close_issue(resource, ExternalIssue.new(issue_key, project)) end before do - jira_service.jira_issue_transition_id = '999' + jira_integration.jira_issue_transition_id = '999' # These stubs are needed to test Integrations::Jira#close_issue. # We close the issue then do another request to API to check if it got closed. # Here is stubbed the API return with a closed and an opened issues. - open_issue = JIRA::Resource::Issue.new(jira_service.client, attrs: issue_fields.deep_stringify_keys) + open_issue = JIRA::Resource::Issue.new(jira_integration.client, attrs: issue_fields.deep_stringify_keys) closed_issue = open_issue.dup allow(open_issue).to receive(:resolution).and_return(false) allow(closed_issue).to receive(:resolution).and_return(true) @@ -585,7 +573,7 @@ RSpec.describe Integrations::Jira do let(:external_issue) { ExternalIssue.new('JIRA-123', project) } def close_issue - jira_service.close_issue(resource, external_issue, current_user) + jira_integration.close_issue(resource, external_issue, current_user) end it 'calls Jira API' do @@ -636,7 +624,7 @@ RSpec.describe Integrations::Jira do context 'when "comment_on_event_enabled" is set to false' do it 'creates Remote Link reference but does not create comment' do - allow(jira_service).to receive_messages(comment_on_event_enabled: false) + allow(jira_integration).to receive_messages(comment_on_event_enabled: false) close_issue expect(WebMock).not_to have_requested(:post, comment_url) @@ -709,12 +697,12 @@ RSpec.describe Integrations::Jira do end it 'logs exception when transition id is not valid' do - allow(jira_service).to receive(:log_error) + allow(jira_integration).to receive(:log_error) WebMock.stub_request(:post, transitions_url).with(basic_auth: %w(jira-username jira-password)).and_raise("Bad Request") close_issue - expect(jira_service).to have_received(:log_error).with( + expect(jira_integration).to have_received(:log_error).with( "Issue transition failed", error: hash_including( exception_class: 'StandardError', @@ -734,7 +722,7 @@ RSpec.describe Integrations::Jira do context 'when custom transition IDs are blank' do before do - jira_service.jira_issue_transition_id = '' + jira_integration.jira_issue_transition_id = '' end it 'does not transition the issue' do @@ -755,7 +743,7 @@ RSpec.describe Integrations::Jira do end before do - jira_service.jira_issue_transition_automatic = true + jira_integration.jira_issue_transition_automatic = true close_issue end @@ -789,7 +777,7 @@ RSpec.describe Integrations::Jira do context 'when using multiple transition ids' do before do - allow(jira_service).to receive_messages(jira_issue_transition_id: '1,2,3') + allow(jira_integration).to receive_messages(jira_issue_transition_id: '1,2,3') end it 'calls the api with transition ids separated by comma' do @@ -805,7 +793,7 @@ RSpec.describe Integrations::Jira do end it 'calls the api with transition ids separated by semicolon' do - allow(jira_service).to receive_messages(jira_issue_transition_id: '1;2;3') + allow(jira_integration).to receive_messages(jira_issue_transition_id: '1;2;3') close_issue @@ -864,7 +852,7 @@ RSpec.describe Integrations::Jira do let(:jira_issue) { ExternalIssue.new('JIRA-123', project) } - subject { jira_service.create_cross_reference_note(jira_issue, resource, user) } + subject { jira_integration.create_cross_reference_note(jira_issue, resource, user) } shared_examples 'creates a comment on Jira' do let(:issue_url) { "#{url}/rest/api/2/issue/JIRA-123" } @@ -936,7 +924,7 @@ RSpec.describe Integrations::Jira do let(:server_info_results) { { 'url' => 'http://url', 'deploymentType' => 'Cloud' } } def server_info - jira_service.test(nil) + jira_integration.test(nil) end context 'when the test succeeds' do @@ -946,7 +934,7 @@ RSpec.describe Integrations::Jira do end it 'gets Jira project with API URL if set' do - jira_service.update!(api_url: 'http://jira.api.com') + jira_integration.update!(api_url: 'http://jira.api.com') expect(server_info).to eq(success: true, result: server_info_results) expect(WebMock).to have_requested(:get, /jira.api.com/) @@ -961,13 +949,13 @@ RSpec.describe Integrations::Jira do WebMock.stub_request(:get, test_url).with(basic_auth: [username, password]) .to_raise(JIRA::HTTPError.new(double(message: error_message))) - expect(jira_service).to receive(:log_error).with( + expect(jira_integration).to receive(:log_error).with( 'Error sending message', client_url: 'http://jira.example.com', error: error_message ) - expect(jira_service.test(nil)).to eq(success: false, result: error_message) + expect(jira_integration.test(nil)).to eq(success: false, result: error_message) end end end @@ -983,17 +971,17 @@ RSpec.describe Integrations::Jira do } allow(Gitlab.config).to receive(:issues_tracker).and_return(settings) - service = project.create_jira_service(active: true) + integration = project.create_jira_integration(active: true) - expect(service.url).to eq('http://jira.sample/projects/project_a') - expect(service.api_url).to eq('http://jira.sample/api') + expect(integration.url).to eq('http://jira.sample/projects/project_a') + expect(integration.api_url).to eq('http://jira.sample/api') end end it 'removes trailing slashes from url' do - service = described_class.new(url: 'http://jira.test.com/path/') + integration = described_class.new(url: 'http://jira.test.com/path/') - expect(service.url).to eq('http://jira.test.com/path') + expect(integration.url).to eq('http://jira.test.com/path') end end @@ -1093,19 +1081,65 @@ RSpec.describe Integrations::Jira do describe '#issue_transition_enabled?' do it 'returns true if automatic transitions are enabled' do - jira_service.jira_issue_transition_automatic = true + jira_integration.jira_issue_transition_automatic = true - expect(jira_service.issue_transition_enabled?).to be(true) + expect(jira_integration.issue_transition_enabled?).to be(true) end it 'returns true if custom transitions are set' do - jira_service.jira_issue_transition_id = '1, 2, 3' + jira_integration.jira_issue_transition_id = '1, 2, 3' - expect(jira_service.issue_transition_enabled?).to be(true) + expect(jira_integration.issue_transition_enabled?).to be(true) end it 'returns false if automatic and custom transitions are disabled' do - expect(jira_service.issue_transition_enabled?).to be(false) + expect(jira_integration.issue_transition_enabled?).to be(false) + end + end + + describe 'valid_connection? and configured?' do + before do + allow(jira_integration).to receive(:test).with(nil).and_return(test_result) + end + + context 'when the test fails' do + let(:test_result) { { success: false } } + + it 'is falsey' do + expect(jira_integration).not_to be_valid_connection + end + + it 'implies that configured? is also falsey' do + expect(jira_integration).not_to be_configured + end + end + + context 'when the test succeeds' do + let(:test_result) { { success: true } } + + it 'is truthy' do + expect(jira_integration).to be_valid_connection + end + + context 'when the integration is active' do + before do + jira_integration.active = true + end + + it 'implies that configured? is also truthy' do + expect(jira_integration).to be_configured + end + end + + context 'when the integration is inactive' do + before do + jira_integration.active = false + end + + it 'implies that configured? is falsey' do + expect(jira_integration).not_to be_configured + end + end end end end diff --git a/spec/models/integrations/mattermost_slash_commands_spec.rb b/spec/models/integrations/mattermost_slash_commands_spec.rb index c8a6584591c..b6abe00469b 100644 --- a/spec/models/integrations/mattermost_slash_commands_spec.rb +++ b/spec/models/integrations/mattermost_slash_commands_spec.rb @@ -5,27 +5,29 @@ require 'spec_helper' RSpec.describe Integrations::MattermostSlashCommands do it_behaves_like Integrations::BaseSlashCommands - context 'Mattermost API' do + describe 'Mattermost API' do let(:project) { create(:project) } - let(:service) { project.build_mattermost_slash_commands_service } + let(:integration) { project.build_mattermost_slash_commands_integration } let(:user) { create(:user) } before do session = ::Mattermost::Session.new(nil) session.base_uri = 'http://mattermost.example.com' - allow_any_instance_of(::Mattermost::Client).to receive(:with_session) - .and_yield(session) + allow(session).to receive(:with_session).and_yield(session) + allow(::Mattermost::Session).to receive(:new).and_return(session) end describe '#configure' do subject do - service.configure(user, team_id: 'abc', - trigger: 'gitlab', url: 'http://trigger.url', - icon_url: 'http://icon.url/icon.png') + integration.configure(user, + team_id: 'abc', + trigger: 'gitlab', + url: 'http://trigger.url', + icon_url: 'http://icon.url/icon.png') end - context 'the requests succeeds' do + context 'when the request succeeds' do before do stub_request(:post, 'http://mattermost.example.com/api/v4/commands') .with(body: { @@ -48,18 +50,18 @@ RSpec.describe Integrations::MattermostSlashCommands do ) end - it 'saves the service' do + it 'saves the integration' do expect { subject }.to change { project.integrations.count }.by(1) end it 'saves the token' do subject - expect(service.reload.token).to eq('token') + expect(integration.reload.token).to eq('token') end end - context 'an error is received' do + context 'when an error is received' do before do stub_request(:post, 'http://mattermost.example.com/api/v4/commands') .to_return( @@ -86,10 +88,10 @@ RSpec.describe Integrations::MattermostSlashCommands do describe '#list_teams' do subject do - service.list_teams(user) + integration.list_teams(user) end - context 'the requests succeeds' do + context 'when the request succeeds' do before do stub_request(:get, 'http://mattermost.example.com/api/v4/users/me/teams') .to_return( @@ -104,7 +106,7 @@ RSpec.describe Integrations::MattermostSlashCommands do end end - context 'an error is received' do + context 'when an error is received' do before do stub_request(:get, 'http://mattermost.example.com/api/v4/users/me/teams') .to_return( diff --git a/spec/models/integrations/microsoft_teams_spec.rb b/spec/models/integrations/microsoft_teams_spec.rb index 2f1be233eb2..21b9a005746 100644 --- a/spec/models/integrations/microsoft_teams_spec.rb +++ b/spec/models/integrations/microsoft_teams_spec.rb @@ -3,25 +3,20 @@ require 'spec_helper' RSpec.describe Integrations::MicrosoftTeams do - let(:chat_service) { described_class.new } + let(:chat_integration) { described_class.new } let(:webhook_url) { 'https://example.gitlab.com/' } - 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 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 + context 'when integration is inactive' do before do subject.active = false end @@ -42,10 +37,9 @@ RSpec.describe Integrations::MicrosoftTeams do let_it_be(:project) { create(:project, :repository, :wiki_repo) } before do - allow(chat_service).to receive_messages( + allow(chat_integration).to receive_messages( project: project, project_id: project.id, - service_hook: true, webhook: webhook_url ) @@ -58,28 +52,29 @@ RSpec.describe Integrations::MicrosoftTeams do end it "calls Microsoft Teams API for push events" do - chat_service.execute(push_sample_data) + chat_integration.execute(push_sample_data) expect(WebMock).to have_requested(:post, webhook_url).once end it 'specifies the webhook when it is configured' do - expect(::MicrosoftTeams::Notifier).to receive(:new).with(webhook_url).and_return(double(:microsoft_teams_service).as_null_object) + integration = double(:microsoft_teams_integration).as_null_object + expect(::MicrosoftTeams::Notifier).to receive(:new).with(webhook_url).and_return(integration) - chat_service.execute(push_sample_data) + chat_integration.execute(push_sample_data) end end context 'with issue events' do let(:opts) { { title: 'Awesome issue', description: 'please fix' } } let(:issues_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 it "calls Microsoft Teams API" do - chat_service.execute(issues_sample_data) + chat_integration.execute(issues_sample_data) expect(WebMock).to have_requested(:post, webhook_url).once end @@ -106,7 +101,7 @@ RSpec.describe Integrations::MicrosoftTeams do end it "calls Microsoft Teams API" do - chat_service.execute(merge_sample_data) + chat_integration.execute(merge_sample_data) expect(WebMock).to have_requested(:post, webhook_url).once end @@ -126,7 +121,7 @@ RSpec.describe Integrations::MicrosoftTeams do let(:wiki_page_sample_data) { Gitlab::DataBuilder::WikiPage.build(wiki_page, user, 'create') } it "calls Microsoft Teams API" do - chat_service.execute(wiki_page_sample_data) + chat_integration.execute(wiki_page_sample_data) expect(WebMock).to have_requested(:post, webhook_url).once end @@ -138,10 +133,9 @@ RSpec.describe Integrations::MicrosoftTeams do let(:project) { create(:project, :repository, creator: user) } before do - allow(chat_service).to receive_messages( + allow(chat_integration).to receive_messages( project: project, project_id: project.id, - service_hook: true, webhook: webhook_url ) @@ -159,7 +153,7 @@ RSpec.describe Integrations::MicrosoftTeams do it "calls Microsoft Teams API for commit comment events" do data = Gitlab::DataBuilder::Note.build(commit_note, user) - chat_service.execute(data) + chat_integration.execute(data) expect(WebMock).to have_requested(:post, webhook_url).once end @@ -174,7 +168,7 @@ RSpec.describe Integrations::MicrosoftTeams do it "calls Microsoft Teams API for merge request comment events" do data = Gitlab::DataBuilder::Note.build(merge_request_note, user) - chat_service.execute(data) + chat_integration.execute(data) expect(WebMock).to have_requested(:post, webhook_url).once end @@ -188,7 +182,7 @@ RSpec.describe Integrations::MicrosoftTeams do it "calls Microsoft Teams API for issue comment events" do data = Gitlab::DataBuilder::Note.build(issue_note, user) - chat_service.execute(data) + chat_integration.execute(data) expect(WebMock).to have_requested(:post, webhook_url).once end @@ -203,7 +197,7 @@ RSpec.describe Integrations::MicrosoftTeams do it "calls Microsoft Teams API for snippet comment events" do data = Gitlab::DataBuilder::Note.build(snippet_note, user) - chat_service.execute(data) + chat_integration.execute(data) expect(WebMock).to have_requested(:post, webhook_url).once end @@ -221,9 +215,8 @@ RSpec.describe Integrations::MicrosoftTeams do end before do - allow(chat_service).to receive_messages( + allow(chat_integration).to receive_messages( project: project, - service_hook: true, webhook: webhook_url ) end @@ -231,14 +224,14 @@ RSpec.describe Integrations::MicrosoftTeams do shared_examples 'call Microsoft Teams API' do |branches_to_be_notified: nil| before do WebMock.stub_request(:post, webhook_url) - chat_service.branches_to_be_notified = branches_to_be_notified if branches_to_be_notified + chat_integration.branches_to_be_notified = branches_to_be_notified if branches_to_be_notified end it 'calls Microsoft Teams API for pipeline events' do data = Gitlab::DataBuilder::Pipeline.build(pipeline) data[:markdown] = true - chat_service.execute(data) + chat_integration.execute(data) message = Integrations::ChatMessage::PipelineMessage.new(data) @@ -250,11 +243,11 @@ RSpec.describe Integrations::MicrosoftTeams do shared_examples 'does not call Microsoft Teams API' do |branches_to_be_notified: nil| before do - chat_service.branches_to_be_notified = branches_to_be_notified if branches_to_be_notified + chat_integration.branches_to_be_notified = branches_to_be_notified if branches_to_be_notified end it 'does not call Microsoft Teams API for pipeline events' do data = Gitlab::DataBuilder::Pipeline.build(pipeline) - result = chat_service.execute(data) + result = chat_integration.execute(data) expect(result).to be_falsy end @@ -272,7 +265,7 @@ RSpec.describe Integrations::MicrosoftTeams do context 'with default to notify_only_broken_pipelines' do it 'does not call Microsoft Teams API for pipeline events' do data = Gitlab::DataBuilder::Pipeline.build(pipeline) - result = chat_service.execute(data) + result = chat_integration.execute(data) expect(result).to be_falsy end @@ -280,7 +273,7 @@ RSpec.describe Integrations::MicrosoftTeams do context 'with setting notify_only_broken_pipelines to false' do before do - chat_service.notify_only_broken_pipelines = false + chat_integration.notify_only_broken_pipelines = false end it_behaves_like 'call Microsoft Teams API' diff --git a/spec/models/integrations/open_project_spec.rb b/spec/models/integrations/open_project_spec.rb index e5b976dc91d..789911acae8 100644 --- a/spec/models/integrations/open_project_spec.rb +++ b/spec/models/integrations/open_project_spec.rb @@ -4,7 +4,7 @@ require 'spec_helper' RSpec.describe Integrations::OpenProject do describe 'Validations' do - context 'when service is active' do + context 'when integration is active' do before do subject.active = true end @@ -13,11 +13,11 @@ RSpec.describe Integrations::OpenProject do it { is_expected.to validate_presence_of(:token) } it { is_expected.to validate_presence_of(:project_identifier_code) } - it_behaves_like 'issue tracker service URL attribute', :url - it_behaves_like 'issue tracker service URL attribute', :api_url + it_behaves_like 'issue tracker integration URL attribute', :url + it_behaves_like 'issue tracker integration URL attribute', :api_url end - context 'when service is inactive' do + context 'when integration is inactive' do before do subject.active = false end @@ -27,9 +27,4 @@ RSpec.describe Integrations::OpenProject do it { is_expected.not_to validate_presence_of(:project_identifier_code) } end end - - describe 'Associations' do - it { is_expected.to belong_to :project } - it { is_expected.to have_one :service_hook } - end end diff --git a/spec/models/integrations/packagist_spec.rb b/spec/models/integrations/packagist_spec.rb index 48f7e81adca..dce96890522 100644 --- a/spec/models/integrations/packagist_spec.rb +++ b/spec/models/integrations/packagist_spec.rb @@ -24,23 +24,23 @@ RSpec.describe Integrations::Packagist do let(:packagist_server) { 'https://packagist.example.com' } let(:project) { create(:project) } - describe "Associations" do - it { is_expected.to belong_to :project } - it { is_expected.to have_one :service_hook } + it_behaves_like Integrations::HasWebHook do + let(:integration) { described_class.new(packagist_params) } + let(:hook_url) { "#{packagist_server}/api/update-package?username=#{packagist_username}&apiToken=#{packagist_token}" } end describe '#execute' do let(:user) { create(:user) } let(:project) { create(:project, :repository) } let(:push_sample_data) { Gitlab::DataBuilder::Push.build_sample(project, user) } - let(:packagist_service) { described_class.create!(packagist_params) } + let(:packagist_integration) { described_class.create!(packagist_params) } before do stub_request(:post, packagist_hook_url) end it 'calls Packagist API' do - packagist_service.execute(push_sample_data) + packagist_integration.execute(push_sample_data) expect(a_request(:post, packagist_hook_url)).to have_been_made.once end diff --git a/spec/models/integrations/pipelines_email_spec.rb b/spec/models/integrations/pipelines_email_spec.rb index 90055b04bb8..761049f25fe 100644 --- a/spec/models/integrations/pipelines_email_spec.rb +++ b/spec/models/integrations/pipelines_email_spec.rb @@ -20,7 +20,7 @@ RSpec.describe Integrations::PipelinesEmail, :mailer do end describe 'Validations' do - context 'when service is active' do + context 'when integration is active' do before do subject.active = true end @@ -28,7 +28,7 @@ RSpec.describe Integrations::PipelinesEmail, :mailer do it { is_expected.to validate_presence_of(:recipients) } end - context 'when service is inactive' do + context 'when integration is inactive' do before do subject.active = false end diff --git a/spec/models/integrations/pivotaltracker_spec.rb b/spec/models/integrations/pivotaltracker_spec.rb index 2ce90b6f739..bf8458a376c 100644 --- a/spec/models/integrations/pivotaltracker_spec.rb +++ b/spec/models/integrations/pivotaltracker_spec.rb @@ -5,13 +5,8 @@ require 'spec_helper' RSpec.describe Integrations::Pivotaltracker do include StubRequests - 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 @@ -19,7 +14,7 @@ RSpec.describe Integrations::Pivotaltracker do it { is_expected.to validate_presence_of(:token) } end - context 'when service is inactive' do + context 'when integration is inactive' do before do subject.active = false end @@ -29,9 +24,9 @@ RSpec.describe Integrations::Pivotaltracker do end describe 'Execute' do - let(:service) do - described_class.new.tap do |service| - service.token = 'secret_api_token' + let(:integration) do + described_class.new.tap do |integration| + integration.token = 'secret_api_token' end end @@ -59,7 +54,7 @@ RSpec.describe Integrations::Pivotaltracker do end it 'posts correct message' do - service.execute(push_data) + integration.execute(push_data) expect(WebMock).to have_requested(:post, stubbed_hostname(url)).with( body: { 'source_commit' => { @@ -77,22 +72,22 @@ RSpec.describe Integrations::Pivotaltracker do end context 'when allowed branches is specified' do - let(:service) do - super().tap do |service| - service.restrict_to_branch = 'master,v10' + let(:integration) do + super().tap do |integration| + integration.restrict_to_branch = 'master,v10' end end it 'posts message if branch is in the list' do - service.execute(push_data(branch: 'master')) - service.execute(push_data(branch: 'v10')) + integration.execute(push_data(branch: 'master')) + integration.execute(push_data(branch: 'v10')) expect(WebMock).to have_requested(:post, stubbed_hostname(url)).twice end it 'does not post message if branch is not in the list' do - service.execute(push_data(branch: 'mas')) - service.execute(push_data(branch: 'v11')) + integration.execute(push_data(branch: 'mas')) + integration.execute(push_data(branch: 'v11')) expect(WebMock).not_to have_requested(:post, stubbed_hostname(url)) end diff --git a/spec/models/integrations/prometheus_spec.rb b/spec/models/integrations/prometheus_spec.rb new file mode 100644 index 00000000000..f6f242bf58e --- /dev/null +++ b/spec/models/integrations/prometheus_spec.rb @@ -0,0 +1,538 @@ +# frozen_string_literal: true + +require 'spec_helper' + +require 'googleauth' + +RSpec.describe Integrations::Prometheus, :use_clean_rails_memory_store_caching, :snowplow do + include PrometheusHelpers + include ReactiveCachingHelpers + + let_it_be_with_reload(:project) { create(:prometheus_project) } + + let(:integration) { project.prometheus_integration } + + context 'redirects' do + it 'does not follow redirects' do + redirect_to = 'https://redirected.example.com' + redirect_req_stub = stub_prometheus_request(prometheus_query_url('1'), status: 302, headers: { location: redirect_to }) + redirected_req_stub = stub_prometheus_request(redirect_to, body: { 'status': 'success' }) + + result = integration.test + + # result = { success: false, result: error } + expect(result[:success]).to be_falsy + expect(result[:result]).to be_instance_of(Gitlab::PrometheusClient::UnexpectedResponseError) + + expect(redirect_req_stub).to have_been_requested + expect(redirected_req_stub).not_to have_been_requested + end + end + + describe 'Validations' do + context 'when manual_configuration is enabled' do + before do + integration.manual_configuration = true + end + + it 'validates presence of api_url' do + expect(integration).to validate_presence_of(:api_url) + end + end + + context 'when manual configuration is disabled' do + before do + integration.manual_configuration = false + end + + it 'does not validate presence of api_url' do + expect(integration).not_to validate_presence_of(:api_url) + expect(integration.valid?).to eq(true) + end + + context 'local connections allowed' do + before do + stub_application_setting(allow_local_requests_from_web_hooks_and_services: true) + end + + it 'does not validate presence of api_url' do + expect(integration).not_to validate_presence_of(:api_url) + expect(integration.valid?).to eq(true) + end + end + end + + context 'when the api_url domain points to localhost or local network' do + let(:domain) { Addressable::URI.parse(integration.api_url).hostname } + + it 'cannot query' do + expect(integration.can_query?).to be true + + aggregate_failures do + ['127.0.0.1', '192.168.2.3'].each do |url| + allow(Addrinfo).to receive(:getaddrinfo).with(domain, any_args).and_return([Addrinfo.tcp(url, 80)]) + + expect(integration.can_query?).to be false + end + end + end + + it 'can query when local requests are allowed' do + stub_application_setting(allow_local_requests_from_web_hooks_and_services: true) + + aggregate_failures do + ['127.0.0.1', '192.168.2.3'].each do |url| + allow(Addrinfo).to receive(:getaddrinfo).with(domain, any_args).and_return([Addrinfo.tcp(url, 80)]) + + expect(integration.can_query?).to be true + end + end + end + + context 'with self-monitoring project and internal Prometheus' do + before do + integration.api_url = 'http://localhost:9090' + + stub_application_setting(self_monitoring_project_id: project.id) + stub_config(prometheus: { enable: true, server_address: 'localhost:9090' }) + end + + it 'allows self-monitoring project to connect to internal Prometheus' do + aggregate_failures do + ['127.0.0.1', '192.168.2.3'].each do |url| + allow(Addrinfo).to receive(:getaddrinfo).with(domain, any_args).and_return([Addrinfo.tcp(url, 80)]) + + expect(integration.can_query?).to be true + end + end + end + + it 'does not allow self-monitoring project to connect to other local URLs' do + integration.api_url = 'http://localhost:8000' + + aggregate_failures do + ['127.0.0.1', '192.168.2.3'].each do |url| + allow(Addrinfo).to receive(:getaddrinfo).with(domain, any_args).and_return([Addrinfo.tcp(url, 80)]) + + expect(integration.can_query?).to be false + end + end + end + end + end + end + + describe 'callbacks' do + context 'after_create' do + let(:project) { create(:project) } + let(:integration) { build(:prometheus_integration, project: project) } + + subject(:create_integration) { integration.save! } + + it 'creates default alerts' do + expect(Prometheus::CreateDefaultAlertsWorker) + .to receive(:perform_async) + .with(project.id) + + create_integration + end + + context 'no project exists' do + let(:integration) { build(:prometheus_integration, :instance) } + + it 'does not create default alerts' do + expect(Prometheus::CreateDefaultAlertsWorker) + .not_to receive(:perform_async) + + create_integration + end + end + end + end + + describe '#test' do + before do + integration.manual_configuration = true + end + + let!(:req_stub) { stub_prometheus_request(prometheus_query_url('1'), body: prometheus_value_body('vector')) } + + context 'success' do + it 'reads the discovery endpoint' do + expect(integration.test[:result]).to eq('Checked API endpoint') + expect(integration.test[:success]).to be_truthy + expect(req_stub).to have_been_requested.twice + end + end + + context 'failure' do + let!(:req_stub) { stub_prometheus_request(prometheus_query_url('1'), status: 404) } + + it 'fails to read the discovery endpoint' do + expect(integration.test[:success]).to be_falsy + expect(req_stub).to have_been_requested + end + end + end + + describe '#prometheus_client' do + let(:api_url) { 'http://some_url' } + + before do + integration.active = true + integration.api_url = api_url + integration.manual_configuration = manual_configuration + end + + context 'manual configuration is enabled' do + let(:manual_configuration) { true } + + it 'calls valid?' do + allow(integration).to receive(:valid?).and_call_original + + expect(integration.prometheus_client).not_to be_nil + + expect(integration).to have_received(:valid?) + end + end + + context 'manual configuration is disabled' do + let(:manual_configuration) { false } + + it 'no client provided' do + expect(integration.prometheus_client).to be_nil + end + end + + context 'when local requests are allowed' do + let(:manual_configuration) { true } + let(:api_url) { 'http://192.168.1.1:9090' } + + before do + stub_application_setting(allow_local_requests_from_web_hooks_and_services: true) + + stub_prometheus_request("#{api_url}/api/v1/query?query=1") + end + + it 'allows local requests' do + expect(integration.prometheus_client).not_to be_nil + expect { integration.prometheus_client.ping }.not_to raise_error + end + end + + context 'when local requests are blocked' do + let(:manual_configuration) { true } + let(:api_url) { 'http://192.168.1.1:9090' } + + before do + stub_application_setting(allow_local_requests_from_web_hooks_and_services: false) + + stub_prometheus_request("#{api_url}/api/v1/query?query=1") + end + + it 'blocks local requests' do + expect(integration.prometheus_client).to be_nil + end + + context 'with self monitoring project and internal Prometheus URL' do + before do + stub_application_setting(allow_local_requests_from_web_hooks_and_services: false) + stub_application_setting(self_monitoring_project_id: project.id) + + stub_config(prometheus: { + enable: true, + server_address: api_url + }) + end + + it 'allows local requests' do + expect(integration.prometheus_client).not_to be_nil + expect { integration.prometheus_client.ping }.not_to raise_error + end + end + end + + context 'behind IAP' do + let(:manual_configuration) { true } + + let(:google_iap_service_account) do + { + type: "service_account", + # dummy private key generated only for this test to pass openssl validation + private_key: <<~KEY + -----BEGIN RSA PRIVATE KEY----- + MIIBOAIBAAJAU85LgUY5o6j6j/07GMLCNUcWJOBA1buZnNgKELayA6mSsHrIv31J + Y8kS+9WzGPQninea7DcM4hHA7smMgQD1BwIDAQABAkAqKxMy6PL3tn7dFL43p0ex + JyOtSmlVIiAZG1t1LXhE/uoLpYi5DnbYqGgu0oih+7nzLY/dXpNpXUmiRMOUEKmB + AiEAoTi2rBXbrLSi2C+H7M/nTOjMQQDuZ8Wr4uWpKcjYJTMCIQCFEskL565oFl/7 + RRQVH+cARrAsAAoJSbrOBAvYZ0PI3QIgIEFwis10vgEF86rOzxppdIG/G+JL0IdD + 9IluZuXAGPECIGUo7qSaLr75o2VEEgwtAFH5aptIPFjrL5LFCKwtdB4RAiAYZgFV + HCMmaooAw/eELuMoMWNYmujZ7VaAnOewGDW0uw== + -----END RSA PRIVATE KEY----- + KEY + } + end + + def stub_iap_request + integration.google_iap_service_account_json = Gitlab::Json.generate(google_iap_service_account) + integration.google_iap_audience_client_id = 'IAP_CLIENT_ID.apps.googleusercontent.com' + + stub_request(:post, 'https://oauth2.googleapis.com/token') + .to_return( + status: 200, + body: '{"id_token": "FOO"}', + headers: { 'Content-Type': 'application/json; charset=UTF-8' } + ) + end + + it 'includes the authorization header' do + stub_iap_request + + expect(integration.prometheus_client).not_to be_nil + expect(integration.prometheus_client.send(:options)).to have_key(:headers) + expect(integration.prometheus_client.send(:options)[:headers]).to eq(authorization: "Bearer FOO") + end + + context 'when passed with token_credential_uri', issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/284819' do + let(:malicious_host) { 'http://example.com' } + + where(:param_name) do + [ + :token_credential_uri, + :tokencredentialuri, + :Token_credential_uri, + :tokenCredentialUri + ] + end + + with_them do + it 'does not make any unexpected HTTP requests' do + google_iap_service_account[param_name] = malicious_host + stub_iap_request + stub_request(:any, malicious_host).to_raise('Making additional HTTP requests is forbidden!') + + expect(integration.prometheus_client).not_to be_nil + end + end + end + end + end + + describe '#prometheus_available?' do + context 'clusters with enabled prometheus' do + before do + create(:clusters_integrations_prometheus, cluster: cluster) + end + + context 'cluster belongs to project' do + let(:cluster) { create(:cluster, projects: [project]) } + + it 'returns true' do + expect(integration.prometheus_available?).to be(true) + end + end + + context 'cluster belongs to projects group' do + let_it_be(:group) { create(:group) } + + let(:project) { create(:prometheus_project, group: group) } + let(:cluster) { create(:cluster_for_group, groups: [group]) } + + it 'returns true' do + expect(integration.prometheus_available?).to be(true) + end + + it 'avoids N+1 queries' do + integration + 5.times do |i| + other_cluster = create(:cluster_for_group, groups: [group], environment_scope: i) + create(:clusters_integrations_prometheus, cluster: other_cluster) + end + expect { integration.prometheus_available? }.not_to exceed_query_limit(1) + end + end + + context 'cluster belongs to gitlab instance' do + let(:cluster) { create(:cluster, :instance) } + + it 'returns true' do + expect(integration.prometheus_available?).to be(true) + end + end + end + + context 'clusters with prometheus disabled' do + let(:cluster) { create(:cluster, projects: [project]) } + let!(:prometheus) { create(:clusters_integrations_prometheus, :disabled, cluster: cluster) } + + it 'returns false' do + expect(integration.prometheus_available?).to be(false) + end + end + + context 'clusters without prometheus' do + let(:cluster) { create(:cluster, projects: [project]) } + + it 'returns false' do + expect(integration.prometheus_available?).to be(false) + end + end + + context 'no clusters' do + it 'returns false' do + expect(integration.prometheus_available?).to be(false) + end + end + end + + describe '#synchronize_service_state before_save callback' do + context 'no clusters with prometheus are installed' do + context 'when integration is inactive' do + before do + integration.active = false + end + + it 'activates integration when manual_configuration is enabled' do + expect { integration.update!(manual_configuration: true) }.to change { integration.active }.from(false).to(true) + end + + it 'keeps integration inactive when manual_configuration is disabled' do + expect { integration.update!(manual_configuration: false) }.not_to change { integration.active }.from(false) + end + end + + context 'when integration is active' do + before do + integration.active = true + end + + it 'keeps the integration active when manual_configuration is enabled' do + expect { integration.update!(manual_configuration: true) }.not_to change { integration.active }.from(true) + end + + it 'inactivates the integration when manual_configuration is disabled' do + expect { integration.update!(manual_configuration: false) }.to change { integration.active }.from(true).to(false) + end + end + end + + context 'with prometheus installed in the cluster' do + before do + allow(integration).to receive(:prometheus_available?).and_return(true) + end + + context 'when integration is inactive' do + before do + integration.active = false + end + + it 'activates integration when manual_configuration is enabled' do + expect { integration.update!(manual_configuration: true) }.to change { integration.active }.from(false).to(true) + end + + it 'activates integration when manual_configuration is disabled' do + expect { integration.update!(manual_configuration: false) }.to change { integration.active }.from(false).to(true) + end + end + + context 'when integration is active' do + before do + integration.active = true + end + + it 'keeps integration active when manual_configuration is enabled' do + expect { integration.update!(manual_configuration: true) }.not_to change { integration.active }.from(true) + end + + it 'keeps integration active when manual_configuration is disabled' do + expect { integration.update!(manual_configuration: false) }.not_to change { integration.active }.from(true) + end + end + end + end + + describe '#track_events after_commit callback' do + before do + allow(integration).to receive(:prometheus_available?).and_return(true) + end + + context "enabling manual_configuration" do + it "tracks enable event" do + integration.update!(manual_configuration: false) + integration.update!(manual_configuration: true) + + expect_snowplow_event(category: 'cluster:services:prometheus', action: 'enabled_manual_prometheus') + end + + it "tracks disable event" do + integration.update!(manual_configuration: true) + integration.update!(manual_configuration: false) + + expect_snowplow_event(category: 'cluster:services:prometheus', action: 'disabled_manual_prometheus') + end + end + end + + describe '#editable?' do + it 'is editable' do + expect(integration.editable?).to be(true) + end + + context 'when cluster exists with prometheus enabled' do + let(:cluster) { create(:cluster, projects: [project]) } + + before do + integration.update!(manual_configuration: false) + + create(:clusters_integrations_prometheus, cluster: cluster) + end + + it 'remains editable' do + expect(integration.editable?).to be(true) + end + end + end + + describe '#fields' do + let(:expected_fields) do + [ + { + type: 'checkbox', + name: 'manual_configuration', + title: s_('PrometheusService|Active'), + help: s_('PrometheusService|Select this checkbox to override the auto configuration settings with your own settings.'), + required: true + }, + { + type: 'text', + name: 'api_url', + title: 'API URL', + placeholder: s_('PrometheusService|https://prometheus.example.com/'), + help: s_('PrometheusService|The Prometheus API base URL.'), + required: true + }, + { + type: 'text', + name: 'google_iap_audience_client_id', + title: 'Google IAP Audience Client ID', + placeholder: s_('PrometheusService|IAP_CLIENT_ID.apps.googleusercontent.com'), + help: s_('PrometheusService|PrometheusService|The ID of the IAP-secured resource.'), + autocomplete: 'off', + required: false + }, + { + type: 'textarea', + name: 'google_iap_service_account_json', + title: 'Google IAP Service Account JSON', + placeholder: s_('PrometheusService|{ "type": "service_account", "project_id": ... }'), + help: s_('PrometheusService|The contents of the credentials.json file of your service account.'), + required: false + } + ] + end + + it 'returns fields' do + expect(integration.fields).to eq(expected_fields) + end + end +end diff --git a/spec/models/integrations/pushover_spec.rb b/spec/models/integrations/pushover_spec.rb index be8dc5634a0..716a00c5bcf 100644 --- a/spec/models/integrations/pushover_spec.rb +++ b/spec/models/integrations/pushover_spec.rb @@ -5,13 +5,8 @@ require 'spec_helper' RSpec.describe Integrations::Pushover do include StubRequests - 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 @@ -21,7 +16,7 @@ RSpec.describe Integrations::Pushover do it { is_expected.to validate_presence_of(:priority) } end - context 'when service is inactive' do + context 'when integration is inactive' do before do subject.active = false end @@ -51,7 +46,6 @@ RSpec.describe Integrations::Pushover do allow(pushover).to receive_messages( project: project, project_id: project.id, - service_hook: true, api_key: api_key, user_key: user_key, device: device, diff --git a/spec/models/integrations/redmine_spec.rb b/spec/models/integrations/redmine_spec.rb index 083585d4fed..59997d2b6f6 100644 --- a/spec/models/integrations/redmine_spec.rb +++ b/spec/models/integrations/redmine_spec.rb @@ -3,11 +3,6 @@ require 'spec_helper' RSpec.describe Integrations::Redmine do - describe 'Associations' do - it { is_expected.to belong_to :project } - it { is_expected.to have_one :service_hook } - end - describe 'Validations' do # if redmine is set in setting the urls are set to defaults # therefore the validation passes as the values are not nil @@ -18,7 +13,7 @@ RSpec.describe Integrations::Redmine do allow(Gitlab.config).to receive(:issues_tracker).and_return(settings) end - context 'when service is active' do + context 'when integration is active' do before do subject.active = true end @@ -27,12 +22,12 @@ RSpec.describe Integrations::Redmine do it { is_expected.to validate_presence_of(:issues_url) } it { is_expected.to validate_presence_of(:new_issue_url) } - it_behaves_like 'issue tracker service URL attribute', :project_url - it_behaves_like 'issue tracker service URL attribute', :issues_url - it_behaves_like 'issue tracker service URL attribute', :new_issue_url + it_behaves_like 'issue tracker integration URL attribute', :project_url + it_behaves_like 'issue tracker integration URL attribute', :issues_url + it_behaves_like 'issue tracker integration URL attribute', :new_issue_url end - context 'when service is inactive' do + context 'when integration is inactive' do before do subject.active = false end diff --git a/spec/models/integrations/slack_slash_commands_spec.rb b/spec/models/integrations/slack_slash_commands_spec.rb index a9d3c820a3c..ff89d2c6a40 100644 --- a/spec/models/integrations/slack_slash_commands_spec.rb +++ b/spec/models/integrations/slack_slash_commands_spec.rb @@ -18,8 +18,8 @@ RSpec.describe Integrations::SlackSlashCommands do } end - let(:service) do - project.create_slack_slash_commands_service( + let(:integration) do + project.create_slack_slash_commands_integration( properties: { token: 'token' }, active: true ) @@ -30,11 +30,11 @@ RSpec.describe Integrations::SlackSlashCommands do end before do - allow(service).to receive(:authorize_chat_name_url).and_return(authorize_url) + allow(integration).to receive(:authorize_chat_name_url).and_return(authorize_url) end it 'uses slack compatible links' do - response = service.trigger(params) + response = integration.trigger(params) expect(response[:text]).to include("<#{authorize_url}|connect your GitLab account>") end diff --git a/spec/models/integrations/slack_spec.rb b/spec/models/integrations/slack_spec.rb index e598c528967..4661d9c8291 100644 --- a/spec/models/integrations/slack_spec.rb +++ b/spec/models/integrations/slack_spec.rb @@ -10,7 +10,7 @@ RSpec.describe Integrations::Slack do stub_request(:post, "https://slack.service.url/") end - let_it_be(:slack_service) { create(:slack_service, branches_to_be_notified: 'all') } + let_it_be(:slack_integration) { create(:integrations_slack, branches_to_be_notified: 'all') } it 'uses only known events', :aggregate_failures do described_class::SUPPORTED_EVENTS_FOR_USAGE_LOG.each do |action| @@ -26,7 +26,7 @@ RSpec.describe Integrations::Slack do it 'increases the usage data counter' do expect(Gitlab::UsageDataCounters::HLLRedisCounter).to receive(:track_event).with(event_name, values: user.id).and_call_original - slack_service.execute(data) + slack_integration.execute(data) end end @@ -38,7 +38,7 @@ RSpec.describe Integrations::Slack do it 'does not increase the usage data counter' do expect(Gitlab::UsageDataCounters::HLLRedisCounter).not_to receive(:track_event).with('i_ecosystem_slack_service_pipeline_notification', values: user.id) - slack_service.execute(data) + slack_integration.execute(data) end end @@ -126,7 +126,7 @@ RSpec.describe Integrations::Slack do it 'does not increase the usage data counter' do expect(Gitlab::UsageDataCounters::HLLRedisCounter).not_to receive(:track_event) - slack_service.execute(data) + slack_integration.execute(data) end end end diff --git a/spec/models/integrations/teamcity_spec.rb b/spec/models/integrations/teamcity_spec.rb index b88a4722ad4..d425357aef0 100644 --- a/spec/models/integrations/teamcity_spec.rb +++ b/spec/models/integrations/teamcity_spec.rb @@ -10,7 +10,7 @@ RSpec.describe Integrations::Teamcity, :use_clean_rails_memory_store_caching do let(:teamcity_full_url) { 'http://gitlab.com/teamcity/httpAuth/app/rest/builds/branch:unspecified:any,revision:123' } let(:project) { create(:project) } - subject(:service) do + subject(:integration) do described_class.create!( project: project, properties: { @@ -22,20 +22,15 @@ RSpec.describe Integrations::Teamcity, :use_clean_rails_memory_store_caching do ) end - 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 it { is_expected.to validate_presence_of(:build_type) } it { is_expected.to validate_presence_of(:teamcity_url) } - it_behaves_like 'issue tracker service URL attribute', :teamcity_url + it_behaves_like 'issue tracker integration URL attribute', :teamcity_url describe '#username' do it 'does not validate the presence of username if password is nil' do @@ -66,7 +61,7 @@ RSpec.describe Integrations::Teamcity, :use_clean_rails_memory_store_caching do end end - context 'when service is inactive' do + context 'when integration is inactive' do before do subject.active = false end @@ -79,71 +74,66 @@ RSpec.describe Integrations::Teamcity, :use_clean_rails_memory_store_caching do end describe 'Callbacks' do + let(:teamcity_integration) { integration } + describe 'before_update :reset_password' do context 'when a password was previously set' do it 'resets password if url changed' do - teamcity_service = service + teamcity_integration.teamcity_url = 'http://gitlab1.com' + teamcity_integration.save! - teamcity_service.teamcity_url = 'http://gitlab1.com' - teamcity_service.save! - - expect(teamcity_service.password).to be_nil + expect(teamcity_integration.password).to be_nil end it 'does not reset password if username changed' do - teamcity_service = service - - teamcity_service.username = 'some_name' - teamcity_service.save! + teamcity_integration.username = 'some_name' + teamcity_integration.save! - expect(teamcity_service.password).to eq('password') + expect(teamcity_integration.password).to eq('password') end it "does not reset password if new url is set together with password, even if it's the same password" do - teamcity_service = service - - teamcity_service.teamcity_url = 'http://gitlab_edited.com' - teamcity_service.password = 'password' - teamcity_service.save! + teamcity_integration.teamcity_url = 'http://gitlab_edited.com' + teamcity_integration.password = 'password' + teamcity_integration.save! - expect(teamcity_service.password).to eq('password') - expect(teamcity_service.teamcity_url).to eq('http://gitlab_edited.com') + expect(teamcity_integration.password).to eq('password') + expect(teamcity_integration.teamcity_url).to eq('http://gitlab_edited.com') end end it 'saves password if new url is set together with password when no password was previously set' do - teamcity_service = service - teamcity_service.password = nil + teamcity_integration.password = nil - teamcity_service.teamcity_url = 'http://gitlab_edited.com' - teamcity_service.password = 'password' - teamcity_service.save! + teamcity_integration.teamcity_url = 'http://gitlab_edited.com' + teamcity_integration.password = 'password' + teamcity_integration.save! - expect(teamcity_service.password).to eq('password') - expect(teamcity_service.teamcity_url).to eq('http://gitlab_edited.com') + expect(teamcity_integration.password).to eq('password') + expect(teamcity_integration.teamcity_url).to eq('http://gitlab_edited.com') end end end describe '#build_page' do it 'returns the contents of the reactive cache' do - stub_reactive_cache(service, { build_page: 'foo' }, 'sha', 'ref') + stub_reactive_cache(integration, { build_page: 'foo' }, 'sha', 'ref') - expect(service.build_page('sha', 'ref')).to eq('foo') + expect(integration.build_page('sha', 'ref')).to eq('foo') end end describe '#commit_status' do it 'returns the contents of the reactive cache' do - stub_reactive_cache(service, { commit_status: 'foo' }, 'sha', 'ref') + stub_reactive_cache(integration, { commit_status: 'foo' }, 'sha', 'ref') - expect(service.commit_status('sha', 'ref')).to eq('foo') + expect(integration.commit_status('sha', 'ref')).to eq('foo') end end describe '#calculate_reactive_cache' do context 'build_page' do - subject { service.calculate_reactive_cache('123', 'unused')[:build_page] } + subject { integration.calculate_reactive_cache('123', 'unused')[:build_page] } it 'returns a specific URL when status is 500' do stub_request(status: 500) @@ -179,7 +169,7 @@ RSpec.describe Integrations::Teamcity, :use_clean_rails_memory_store_caching do end context 'commit_status' do - subject { service.calculate_reactive_cache('123', 'unused')[:commit_status] } + subject { integration.calculate_reactive_cache('123', 'unused')[:commit_status] } it 'sets commit status to :error when status is 500' do stub_request(status: 500) @@ -243,25 +233,25 @@ RSpec.describe Integrations::Teamcity, :use_clean_rails_memory_store_caching do it 'handles push request correctly' do stub_post_to_build_queue(branch: 'dev-123_branch') - expect(service.execute(data)).to include('Ok') + expect(integration.execute(data)).to include('Ok') end it 'returns nil when ref is blank' do data[:after] = Gitlab::Git::BLANK_SHA - expect(service.execute(data)).to be_nil + expect(integration.execute(data)).to be_nil end it 'returns nil when there is no content' do data[:total_commits_count] = 0 - expect(service.execute(data)).to be_nil + expect(integration.execute(data)).to be_nil end it 'returns nil when a merge request is opened for the same ref' do create(:merge_request, source_project: project, source_branch: 'dev-123_branch') - expect(service.execute(data)).to be_nil + expect(integration.execute(data)).to be_nil end end @@ -283,26 +273,26 @@ RSpec.describe Integrations::Teamcity, :use_clean_rails_memory_store_caching do it 'handles merge request correctly' do stub_post_to_build_queue(branch: 'dev-123_branch') - expect(service.execute(data)).to include('Ok') + expect(integration.execute(data)).to include('Ok') end it 'returns nil when merge request is not opened' do data[:object_attributes][:state] = 'closed' - expect(service.execute(data)).to be_nil + expect(integration.execute(data)).to be_nil end it 'returns nil unless merge request is marked as unchecked' do data[:object_attributes][:merge_status] = 'can_be_merged' - expect(service.execute(data)).to be_nil + expect(integration.execute(data)).to be_nil end end it 'returns nil when event is not supported' do data = { object_kind: 'foo' } - expect(service.execute(data)).to be_nil + expect(integration.execute(data)).to be_nil end end diff --git a/spec/models/integrations/youtrack_spec.rb b/spec/models/integrations/youtrack_spec.rb index 314204f6fb4..f6a9dd8ef37 100644 --- a/spec/models/integrations/youtrack_spec.rb +++ b/spec/models/integrations/youtrack_spec.rb @@ -3,13 +3,8 @@ require 'spec_helper' RSpec.describe Integrations::Youtrack do - 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 @@ -17,11 +12,11 @@ RSpec.describe Integrations::Youtrack do it { is_expected.to validate_presence_of(:project_url) } it { is_expected.to validate_presence_of(:issues_url) } - it_behaves_like 'issue tracker service URL attribute', :project_url - it_behaves_like 'issue tracker service URL attribute', :issues_url + it_behaves_like 'issue tracker integration URL attribute', :project_url + it_behaves_like 'issue tracker integration URL attribute', :issues_url end - context 'when service is inactive' do + context 'when integration is inactive' do before do subject.active = false end |