diff options
Diffstat (limited to 'spec/models/project_services')
35 files changed, 9 insertions, 4228 deletions
diff --git a/spec/models/project_services/bugzilla_service_spec.rb b/spec/models/project_services/bugzilla_service_spec.rb deleted file mode 100644 index 560c7c3ee83..00000000000 --- a/spec/models/project_services/bugzilla_service_spec.rb +++ /dev/null @@ -1,35 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe BugzillaService 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 - before do - subject.active = true - end - - 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 - end - - context 'when service is inactive' do - before do - subject.active = false - end - - it { is_expected.not_to validate_presence_of(:project_url) } - it { is_expected.not_to validate_presence_of(:issues_url) } - it { is_expected.not_to validate_presence_of(:new_issue_url) } - end - end -end diff --git a/spec/models/project_services/buildkite_service_spec.rb b/spec/models/project_services/buildkite_service_spec.rb deleted file mode 100644 index f6bf1551bf0..00000000000 --- a/spec/models/project_services/buildkite_service_spec.rb +++ /dev/null @@ -1,151 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe BuildkiteService, :use_clean_rails_memory_store_caching do - include ReactiveCachingHelpers - include StubRequests - - let(:project) { create(:project) } - - subject(:service) 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 } - end - - describe 'Validations' do - context 'when service 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 - end - - context 'when service is inactive' do - before do - subject.active = false - end - - it { is_expected.not_to validate_presence_of(:project_url) } - it { is_expected.not_to validate_presence_of(:token) } - end - end - - 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) - end - end - - describe 'commits methods' do - before do - allow(project).to receive(:default_branch).and_return('default-brancho') - end - - it 'always activates SSL verification after saved' do - service.create_service_hook(enable_ssl_verification: false) - - service.enable_ssl_verification = false - service.active = true - - expect { service.save! } - .to change { service.service_hook.enable_ssl_verification }.from(false).to(true) - end - - describe '#webhook_url' do - it 'returns the webhook url' do - expect(service.webhook_url).to eq( - 'https://webhook.buildkite.com/deliver/secret-sauce-webhook-token' - ) - end - end - - describe '#commit_status_path' do - it 'returns the correct status page' do - expect(service.commit_status_path('2ab7834c')).to eq( - 'https://gitlab.buildkite.com/status/secret-sauce-status-token.json?commit=2ab7834c' - ) - end - end - - describe '#build_page' do - it 'returns the correct build page' do - expect(service.build_page('2ab7834c', nil)).to eq( - 'https://buildkite.com/organization-name/example-pipeline/builds?commit=2ab7834c' - ) - end - end - - describe '#commit_status' do - it 'returns the contents of the reactive cache' do - stub_reactive_cache(service, { commit_status: 'foo' }, 'sha', 'ref') - - expect(service.commit_status('sha', 'ref')).to eq('foo') - end - end - - describe '#calculate_reactive_cache' do - describe '#commit_status' do - let(:buildkite_full_url) do - 'https://gitlab.buildkite.com/status/secret-sauce-status-token.json?commit=123' - end - - subject { service.calculate_reactive_cache('123', 'unused')[:commit_status] } - - it 'sets commit status to :error when status is 500' do - stub_request(status: 500) - - is_expected.to eq(:error) - end - - it 'sets commit status to :error when status is 404' do - stub_request(status: 404) - - is_expected.to eq(:error) - end - - it 'passes through build status untouched when status is 200' do - stub_request(body: %q({"status":"Great Success"})) - - is_expected.to eq('Great Success') - end - - Gitlab::HTTP::HTTP_ERRORS.each do |http_error| - it "sets commit status to :error with a #{http_error.name} error" do - WebMock.stub_request(:get, buildkite_full_url) - .to_raise(http_error) - - expect(Gitlab::ErrorTracking) - .to receive(:log_exception) - .with(instance_of(http_error), project_id: project.id) - - is_expected.to eq(:error) - end - end - end - end - end - - def stub_request(status: 200, body: nil) - body ||= %q({"status":"success"}) - - stub_full_request(buildkite_full_url) - .to_return(status: status, - headers: { 'Content-Type' => 'application/json' }, - body: body) - end -end diff --git a/spec/models/project_services/chat_notification_service_spec.rb b/spec/models/project_services/chat_notification_service_spec.rb deleted file mode 100644 index 62f97873a06..00000000000 --- a/spec/models/project_services/chat_notification_service_spec.rb +++ /dev/null @@ -1,282 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe ChatNotificationService do - describe 'Associations' do - before do - allow(subject).to receive(:activated?).and_return(true) - end - - it { is_expected.to validate_presence_of :webhook } - end - - describe 'validations' 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 } - - let_it_be(:project) { create(:project, :repository) } - - let(:user) { create(:user) } - let(:webhook_url) { 'https://example.gitlab.com/' } - let(:data) { Gitlab::DataBuilder::Push.build_sample(subject.project, user) } - - before do - allow(chat_service).to receive_messages( - project: project, - project_id: project.id, - service_hook: true, - webhook: webhook_url - ) - - WebMock.stub_request(:post, webhook_url) - - subject.active = true - end - - 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 - end - end - - context 'with an empty repository' 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 - end - end - - context 'with a project with name containing spaces' 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) - end - end - - context 'when the data object has a label' do - let_it_be(:label) { create(:label, name: 'Bug') } - let_it_be(:label_2) { create(:label, name: 'Community contribution') } - let_it_be(:label_3) { create(:label, name: 'Backend') } - let_it_be(:issue) { create(:labeled_issue, project: project, labels: [label, label_2, label_3]) } - let_it_be(:note) { create(:note, noteable: issue, project: project) } - - let(:data) { Gitlab::DataBuilder::Note.build(note, user) } - - it 'notifies the chat service' do - expect(chat_service).to receive(:notify).with(any_args) - - chat_service.execute(data) - end - - shared_examples 'notifies the chat service' do - specify do - expect(chat_service).to receive(:notify).with(any_args) - - chat_service.execute(data) - end - end - - shared_examples 'does not notify the chat service' do - specify do - expect(chat_service).not_to receive(:notify).with(any_args) - - chat_service.execute(data) - 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) } - - context 'no matching labels' do - let(:label_filter) { '~some random label' } - - it_behaves_like 'does not notify the chat service' - end - - context 'only one label matches' do - let(:label_filter) { '~some random label, ~Bug' } - - it_behaves_like 'notifies the chat service' - 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: '') } - - context 'no matching labels' do - let(:label_filter) { '~some random label' } - - it_behaves_like 'does not notify the chat service' - end - - context 'only one label matches' do - let(:label_filter) { '~some random label, ~Bug' } - - it_behaves_like 'notifies the chat service' - end - end - - context 'when labels_to_be_notified_behavior is match_any' do - subject(:chat_service) do - described_class.new( - labels_to_be_notified: label_filter, - labels_to_be_notified_behavior: 'match_any' - ) - end - - context 'no label filter' do - let(:label_filter) { nil } - - it_behaves_like 'notifies the chat service' - end - - context 'no matching labels' do - let(:label_filter) { '~some random label' } - - it_behaves_like 'does not notify the chat service' - end - - context 'only one label matches' do - let(:label_filter) { '~some random label, ~Bug' } - - it_behaves_like 'notifies the chat service' - end - end - - context 'when labels_to_be_notified_behavior is match_all' do - subject(:chat_service) do - described_class.new( - labels_to_be_notified: label_filter, - labels_to_be_notified_behavior: 'match_all' - ) - end - - context 'no label filter' do - let(:label_filter) { nil } - - it_behaves_like 'notifies the chat service' - end - - context 'no matching labels' do - let(:label_filter) { '~some random label' } - - it_behaves_like 'does not notify the chat service' - end - - context 'only one label matches' do - let(:label_filter) { '~some random label, ~Bug' } - - it_behaves_like 'does not notify the chat service' - end - - context 'labels matches exactly' do - let(:label_filter) { '~Bug, ~Backend, ~Community contribution' } - - it_behaves_like 'notifies the chat service' - end - - context 'labels matches but object has more' do - let(:label_filter) { '~Bug, ~Backend' } - - it_behaves_like 'notifies the chat service' - end - - context 'labels are distributed on multiple objects' do - let(:label_filter) { '~Bug, ~Backend' } - let(:data) do - Gitlab::DataBuilder::Note.build(note, user).merge({ - issue: { - labels: [ - { title: 'Bug' } - ] - }, - merge_request: { - labels: [ - { - title: 'Backend' - } - ] - } - }) - end - - it_behaves_like 'does not notify the chat service' - end - end - end - - context 'with "channel" property' do - before do - allow(chat_service).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) - end - end - - context 'empty spaces' 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) - end - end - end - - shared_examples 'with channel specified' do |channel, expected_channels| - before do - allow(chat_service).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) - end - end - - context 'with single channel specified' do - it_behaves_like 'with channel specified', 'slack-integration', ['slack-integration'] - end - - context 'with multiple channel names specified' do - it_behaves_like 'with channel specified', 'slack-integration,#slack-test', ['slack-integration', '#slack-test'] - end - - context 'with multiple channel names with spaces specified' do - it_behaves_like 'with channel specified', 'slack-integration, #slack-test, @UDLP91W0A', ['slack-integration', '#slack-test', '@UDLP91W0A'] - end - end -end diff --git a/spec/models/project_services/custom_issue_tracker_service_spec.rb b/spec/models/project_services/custom_issue_tracker_service_spec.rb deleted file mode 100644 index 881ae60a680..00000000000 --- a/spec/models/project_services/custom_issue_tracker_service_spec.rb +++ /dev/null @@ -1,35 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe CustomIssueTrackerService 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 - before do - subject.active = true - end - - 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 - end - - context 'when service is inactive' do - before do - subject.active = false - end - - it { is_expected.not_to validate_presence_of(:project_url) } - it { is_expected.not_to validate_presence_of(:issues_url) } - it { is_expected.not_to validate_presence_of(:new_issue_url) } - end - end -end diff --git a/spec/models/project_services/data_fields_spec.rb b/spec/models/project_services/data_fields_spec.rb deleted file mode 100644 index d3e6afe4978..00000000000 --- a/spec/models/project_services/data_fields_spec.rb +++ /dev/null @@ -1,156 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe DataFields do - let(:url) { 'http://url.com' } - let(:username) { 'username_one' } - let(:properties) do - { url: url, username: username } - end - - shared_examples 'data fields' do - describe '#arg' do - it 'returns an argument correctly' do - expect(service.url).to eq(url) - end - end - - describe '{arg}_changed?' do - it 'returns false when the property has not been assigned a new value' do - service.username = 'new_username' - service.validate - expect(service.url_changed?).to be_falsy - end - - it 'returns true when the property has been assigned a different value' do - service.url = "http://example.com" - service.validate - expect(service.url_changed?).to be_truthy - end - - it 'returns true when the property has been assigned a different value twice' do - service.url = "http://example.com" - service.url = "http://example.com" - service.validate - expect(service.url_changed?).to be_truthy - end - - it 'returns false when the property has been re-assigned the same value' do - service.url = 'http://url.com' - service.validate - expect(service.url_changed?).to be_falsy - end - end - - describe '{arg}_touched?' do - it 'returns false when the property has not been assigned a new value' do - service.username = 'new_username' - service.validate - expect(service.url_changed?).to be_falsy - end - - it 'returns true when the property has been assigned a different value' do - service.url = "http://example.com" - service.validate - expect(service.url_changed?).to be_truthy - end - - it 'returns true when the property has been assigned a different value twice' do - service.url = "http://example.com" - service.url = "http://example.com" - service.validate - expect(service.url_changed?).to be_truthy - end - - it 'returns true when the property has been re-assigned the same value' do - service.url = 'http://url.com' - expect(service.url_touched?).to be_truthy - end - - it 'returns false when the property has been re-assigned the same value' do - service.url = 'http://url.com' - service.validate - expect(service.url_changed?).to be_falsy - end - end - - describe 'data_fields_present?' do - it 'returns true from the issue tracker service' do - expect(service.data_fields_present?).to be true - end - end - end - - context 'when data are stored in data_fields' do - let(:service) do - create(:jira_service, url: url, username: username) - end - - it_behaves_like 'data fields' - - describe '{arg}_was?' do - it 'returns nil' do - service.url = 'http://example.com' - service.validate - expect(service.url_was).to be_nil - end - end - end - - context 'when service and data_fields are not persisted' do - let(:service) do - JiraService.new - end - - describe 'data_fields_present?' do - it 'returns true' do - expect(service.data_fields_present?).to be true - end - end - end - - context 'when data are stored in properties' do - let(:service) { create(:jira_service, :without_properties_callback, properties: properties) } - - it_behaves_like 'data fields' - - describe '{arg}_was?' do - it 'returns nil when the property has not been assigned a new value' do - service.username = 'new_username' - service.validate - expect(service.url_was).to be_nil - end - - it 'returns initial value when the property has been assigned a different value' do - service.url = 'http://example.com' - service.validate - expect(service.url_was).to eq('http://url.com') - end - - it 'returns initial value when the property has been re-assigned the same value' do - service.url = 'http://url.com' - service.validate - expect(service.url_was).to eq('http://url.com') - end - end - end - - context 'when data are stored in both properties and data_fields' do - let(:service) do - create(:jira_service, :without_properties_callback, active: false, properties: properties).tap do |integration| - create(:jira_tracker_data, properties.merge(integration: integration)) - end - end - - it_behaves_like 'data fields' - - describe '{arg}_was?' do - it 'returns nil' do - service.url = 'http://example.com' - service.validate - expect(service.url_was).to be_nil - end - end - end -end diff --git a/spec/models/project_services/discord_service_spec.rb b/spec/models/project_services/discord_service_spec.rb deleted file mode 100644 index ffe0a36dcdc..00000000000 --- a/spec/models/project_services/discord_service_spec.rb +++ /dev/null @@ -1,82 +0,0 @@ -# frozen_string_literal: true - -require "spec_helper" - -RSpec.describe DiscordService do - it_behaves_like "chat service", "Discord notifications" do - let(:client) { Discordrb::Webhooks::Client } - let(:client_arguments) { { url: webhook_url } } - let(:payload) do - { - embeds: [ - include( - author: include(name: be_present), - description: be_present - ) - ] - } - end - end - - describe '#execute' do - include StubRequests - - let(:user) { create(:user) } - let(:project) { create(:project, :repository) } - let(:webhook_url) { "https://example.gitlab.com/" } - - let(:sample_data) do - Gitlab::DataBuilder::Push.build_sample(project, user) - end - - before do - allow(subject).to receive_messages( - project: project, - project_id: project.id, - service_hook: true, - webhook: webhook_url - ) - - WebMock.stub_request(:post, webhook_url) - end - - it 'uses the right embed parameters' do - builder = Discordrb::Webhooks::Builder.new - - allow_next_instance_of(Discordrb::Webhooks::Client) do |client| - allow(client).to receive(:execute).and_yield(builder) - end - - 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 - ) - ) - end - - context 'DNS rebind to local address' do - before do - stub_dns(webhook_url, ip_address: '192.168.2.120') - end - - it 'does not allow DNS rebinding' do - expect { subject.execute(sample_data) }.to raise_error(ArgumentError, /is blocked/) - end - end - - context 'when the Discord request fails' do - before do - WebMock.stub_request(:post, webhook_url).to_return(status: 400) - end - - it 'logs an error and returns false' do - expect(subject).to receive(:log_error).with('400 Bad Request') - expect(subject.execute(sample_data)).to be(false) - end - end - end -end diff --git a/spec/models/project_services/drone_ci_service_spec.rb b/spec/models/project_services/drone_ci_service_spec.rb deleted file mode 100644 index 9aaf4f7a644..00000000000 --- a/spec/models/project_services/drone_ci_service_spec.rb +++ /dev/null @@ -1,148 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe DroneCiService, :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 - subject.active = true - end - - 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 - end - - context 'inactive' do - before do - subject.active = false - end - - it { is_expected.not_to validate_presence_of(:token) } - it { is_expected.not_to validate_presence_of(:drone_url) } - end - end - - shared_context :drone_ci_service do - let(:drone) { DroneCiService.new } - let(:project) { create(:project, :repository, name: 'project') } - let(:path) { project.full_path } - let(:drone_url) { 'http://drone.example.com' } - let(:sha) { '2ab7834c' } - let(:branch) { 'dev' } - let(:token) { 'secret' } - let(:iid) { rand(1..9999) } - - # URL's - 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"}) - - WebMock.stub_request(:get, commit_status_path).to_return( - status: status, - headers: { 'Content-Type' => 'application/json' }, - body: body - ) - end - end - - describe "service page/path methods" do - include_context :drone_ci_service - - it { expect(drone.build_page(sha, branch)).to eq(build_page) } - it { expect(drone.commit_status_path(sha, branch)).to eq(commit_status_path) } - end - - describe '#commit_status' do - include_context :drone_ci_service - - it 'returns the contents of the reactive cache' do - stub_reactive_cache(drone, { commit_status: 'foo' }, 'sha', 'ref') - - expect(drone.commit_status('sha', 'ref')).to eq('foo') - end - end - - describe '#calculate_reactive_cache' do - include_context :drone_ci_service - - describe '#commit_status' do - subject { drone.calculate_reactive_cache(sha, branch)[:commit_status] } - - it 'sets commit status to :error when status is 500' do - stub_request(status: 500) - - is_expected.to eq(:error) - end - - it 'sets commit status to :error when status is 404' do - stub_request(status: 404) - - is_expected.to eq(:error) - end - - Gitlab::HTTP::HTTP_ERRORS.each do |http_error| - it "sets commit status to :error with a #{http_error.name} error" do - WebMock.stub_request(:get, commit_status_path) - .to_raise(http_error) - - expect(Gitlab::ErrorTracking) - .to receive(:log_exception) - .with(instance_of(http_error), project_id: project.id) - - is_expected.to eq(:error) - end - end - - { - "killed" => :canceled, - "failure" => :failed, - "error" => :failed, - "success" => "success" - }.each do |drone_status, our_status| - it "sets commit status to #{our_status.inspect} when returned status is #{drone_status.inspect}" do - stub_request(body: %Q({"status":"#{drone_status}"})) - - is_expected.to eq(our_status) - end - end - end - end - - describe "execute" do - include_context :drone_ci_service - - let(:user) { create(:user, username: 'username') } - let(:push_sample_data) 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) - - drone.execute(push_sample_data) - end - end -end diff --git a/spec/models/project_services/ewm_service_spec.rb b/spec/models/project_services/ewm_service_spec.rb deleted file mode 100644 index 311c456569e..00000000000 --- a/spec/models/project_services/ewm_service_spec.rb +++ /dev/null @@ -1,61 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe EwmService 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 - before do - subject.active = true - end - - 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 - end - - context 'when service is inactive' do - before do - subject.active = false - end - - it { is_expected.not_to validate_presence_of(:project_url) } - it { is_expected.not_to validate_presence_of(:issues_url) } - it { is_expected.not_to validate_presence_of(:new_issue_url) } - end - end - - describe "ReferencePatternValidation" do - it "extracts bug" do - expect(described_class.reference_pattern.match("This is bug 123")[:issue]).to eq("bug 123") - end - - it "extracts task" do - expect(described_class.reference_pattern.match("This is task 123.")[:issue]).to eq("task 123") - end - - it "extracts work item" do - expect(described_class.reference_pattern.match("This is work item 123 now")[:issue]).to eq("work item 123") - end - - it "extracts workitem" do - expect(described_class.reference_pattern.match("workitem 123 at the beginning")[:issue]).to eq("workitem 123") - end - - it "extracts defect" do - expect(described_class.reference_pattern.match("This is defect 123 defect")[:issue]).to eq("defect 123") - end - - it "extracts rtcwi" do - expect(described_class.reference_pattern.match("This is rtcwi 123")[:issue]).to eq("rtcwi 123") - end - end -end diff --git a/spec/models/project_services/external_wiki_service_spec.rb b/spec/models/project_services/external_wiki_service_spec.rb deleted file mode 100644 index c6891401a0f..00000000000 --- a/spec/models/project_services/external_wiki_service_spec.rb +++ /dev/null @@ -1,59 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe ExternalWikiService 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 - 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 - end - - context 'when service is inactive' do - before do - subject.active = false - end - - it { is_expected.not_to validate_presence_of(:external_wiki_url) } - end - end - - describe 'test' do - before do - subject.properties['external_wiki_url'] = url - end - - let(:url) { 'http://foo' } - let(:data) { nil } - let(:result) { subject.test(data) } - - context 'the URL is not reachable' do - before do - WebMock.stub_request(:get, url).to_return(status: 404, body: 'not a page') - end - - it 'is not successful' do - expect(result[:success]).to be_falsey - end - end - - context 'the URL is reachable' do - before do - WebMock.stub_request(:get, url).to_return(status: 200, body: 'foo') - end - - it 'is successful' do - expect(result[:success]).to be_truthy - end - end - end -end diff --git a/spec/models/project_services/flowdock_service_spec.rb b/spec/models/project_services/flowdock_service_spec.rb deleted file mode 100644 index 94a49fb3080..00000000000 --- a/spec/models/project_services/flowdock_service_spec.rb +++ /dev/null @@ -1,58 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe FlowdockService 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 - before do - subject.active = true - end - - it { is_expected.to validate_presence_of(:token) } - end - - context 'when service is inactive' do - before do - subject.active = false - end - - it { is_expected.not_to validate_presence_of(:token) } - end - end - - describe "Execute" do - let(:user) { create(:user) } - let(:project) { create(:project, :repository) } - - before do - @flowdock_service = described_class.new - allow(@flowdock_service).to receive_messages( - project_id: project.id, - project: project, - service_hook: true, - token: 'verySecret' - ) - @sample_data = Gitlab::DataBuilder::Push.build_sample(project, user) - @api_url = 'https://api.flowdock.com/v1/messages' - WebMock.stub_request(:post, @api_url) - end - - it "calls FlowDock API" do - @flowdock_service.execute(@sample_data) - @sample_data[:commits].each do |commit| - # One request to Flowdock per new commit - next if commit[:id] == @sample_data[:before] - - expect(WebMock).to have_requested(:post, @api_url).with( - body: /#{commit[:id]}.*#{project.path}/ - ).once - end - end - end -end diff --git a/spec/models/project_services/hangouts_chat_service_spec.rb b/spec/models/project_services/hangouts_chat_service_spec.rb deleted file mode 100644 index 9d3bd457fc8..00000000000 --- a/spec/models/project_services/hangouts_chat_service_spec.rb +++ /dev/null @@ -1,15 +0,0 @@ -# frozen_string_literal: true - -require "spec_helper" - -RSpec.describe HangoutsChatService do - it_behaves_like "chat service", "Hangouts Chat" do - let(:client) { HangoutsChat::Sender } - let(:client_arguments) { webhook_url } - let(:payload) do - { - text: be_present - } - end - end -end diff --git a/spec/models/project_services/hipchat_service_spec.rb b/spec/models/project_services/hipchat_service_spec.rb deleted file mode 100644 index 42368c31ba0..00000000000 --- a/spec/models/project_services/hipchat_service_spec.rb +++ /dev/null @@ -1,36 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -# HipchatService is partially removed and it will be remove completely -# after the deletion of all the database records. -# https://gitlab.com/gitlab-org/gitlab/-/issues/27954 -RSpec.describe HipchatService do - let_it_be(:project) { create(:project) } - - subject(:service) { described_class.new(project: project) } - - it { is_expected.to be_valid } - - describe '#to_param' do - subject { service.to_param } - - it { is_expected.to eq('hipchat') } - end - - describe '#supported_events' do - subject { service.supported_events } - - it { is_expected.to be_empty } - end - - describe '#save' do - it 'prevents records from being created or updated' do - expect(service.save).to be_falsey - - expect(service.errors.full_messages).to include( - 'HipChat endpoint is deprecated and should not be created or modified.' - ) - end - end -end diff --git a/spec/models/project_services/irker_service_spec.rb b/spec/models/project_services/irker_service_spec.rb deleted file mode 100644 index 07963947de8..00000000000 --- a/spec/models/project_services/irker_service_spec.rb +++ /dev/null @@ -1,76 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' -require 'socket' -require 'json' - -RSpec.describe IrkerService 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 - before do - subject.active = true - end - - it { is_expected.to validate_presence_of(:recipients) } - end - - context 'when service is inactive' do - before do - subject.active = false - end - - it { is_expected.not_to validate_presence_of(:recipients) } - end - end - - describe 'Execute' do - let(:irker) { described_class.new } - let(:user) { create(:user) } - let(:project) { create(:project, :repository) } - let(:sample_data) do - Gitlab::DataBuilder::Push.build_sample(project, user) - end - - let(:recipients) { '#commits irc://test.net/#test ftp://bad' } - let(:colorize_messages) { '1' } - - before do - @irker_server = TCPServer.new 'localhost', 0 - - allow(irker).to receive_messages( - 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/', - recipients: recipients, - colorize_messages: colorize_messages) - - irker.valid? - end - - after do - @irker_server.close - end - - it 'sends valid JSON messages to an Irker listener', :sidekiq_might_not_need_inline do - irker.execute(sample_data) - - conn = @irker_server.accept - conn.each_line do |line| - msg = Gitlab::Json.parse(line.chomp("\n")) - expect(msg.keys).to match_array(%w(to privmsg)) - expect(msg['to']).to match_array(["irc://chat.freenode.net/#commits", - "irc://test.net/#test"]) - end - conn.close - end - end -end diff --git a/spec/models/project_services/issue_tracker_data_spec.rb b/spec/models/project_services/issue_tracker_data_spec.rb deleted file mode 100644 index a229285f09b..00000000000 --- a/spec/models/project_services/issue_tracker_data_spec.rb +++ /dev/null @@ -1,9 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe IssueTrackerData do - describe 'associations' do - it { is_expected.to belong_to :integration } - end -end diff --git a/spec/models/project_services/issue_tracker_service_spec.rb b/spec/models/project_services/issue_tracker_service_spec.rb deleted file mode 100644 index 5b12c7330b8..00000000000 --- a/spec/models/project_services/issue_tracker_service_spec.rb +++ /dev/null @@ -1,34 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe IssueTrackerService do - describe 'Validations' do - let(:project) { create :project } - - describe 'only one issue tracker per project' do - let(:service) { RedmineService.new(project: project, active: true, issue_tracker_data: build(:issue_tracker_data)) } - - before do - create(:custom_issue_tracker_service, project: project) - end - - context 'when service is changed manually by user' do - it 'executes the validation' do - valid = service.valid?(:manual_change) - - expect(valid).to be_falsey - expect(service.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 - it 'does not execute the validation' do - expect(service.valid?).to be_truthy - end - end - end - end -end diff --git a/spec/models/project_services/jenkins_service_spec.rb b/spec/models/project_services/jenkins_service_spec.rb deleted file mode 100644 index 4663e41736a..00000000000 --- a/spec/models/project_services/jenkins_service_spec.rb +++ /dev/null @@ -1,255 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe JenkinsService do - let(:project) { create(:project) } - let(:jenkins_url) { 'http://jenkins.example.com/' } - let(:jenkins_hook_url) { jenkins_url + 'project/my_project' } - let(:jenkins_username) { 'u$er name%2520' } - let(:jenkins_password) { 'pas$ word' } - - let(:jenkins_params) do - { - active: true, - project: project, - properties: { - password: jenkins_password, - username: jenkins_username, - jenkins_url: jenkins_url, - project_name: 'my_project' - } - } - end - - 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 } - end - - describe 'username validation' do - before do - @jenkins_service = described_class.create!( - active: active, - project: project, - properties: { - jenkins_url: 'http://jenkins.example.com/', - password: 'password', - username: 'username', - project_name: 'my_project' - } - ) - end - - subject { @jenkins_service } - - context 'when the service is active' do - let(:active) { true } - - context 'when password was not touched' do - before do - allow(subject).to receive(:password_touched?).and_return(false) - end - - it { is_expected.not_to validate_presence_of :username } - end - - context 'when password was touched' do - before do - allow(subject).to receive(:password_touched?).and_return(true) - end - - it { is_expected.to validate_presence_of :username } - end - - context 'when password is blank' do - it 'does not validate the username' do - expect(subject).not_to validate_presence_of :username - - subject.password = '' - subject.save! - end - end - end - - context 'when the service is inactive' do - let(:active) { false } - - it { is_expected.not_to validate_presence_of :username } - end - end - - describe '#hook_url' do - let(:username) { nil } - let(:password) { nil } - let(:jenkins_service) do - described_class.new( - project: project, - properties: { - jenkins_url: jenkins_url, - project_name: 'my_project', - username: username, - password: password - } - ) - end - - subject { jenkins_service.hook_url } - - context 'when the jenkins_url has no relative path' do - let(:jenkins_url) { 'http://jenkins.example.com/' } - - it { is_expected.to eq('http://jenkins.example.com/project/my_project') } - end - - context 'when the jenkins_url has relative path' do - let(:jenkins_url) { 'http://organization.example.com/jenkins' } - - it { is_expected.to eq('http://organization.example.com/jenkins/project/my_project') } - end - - context 'userinfo is missing and username and password are set' do - let(:jenkins_url) { 'http://organization.example.com/jenkins' } - let(:username) { 'u$ername' } - let(:password) { 'pas$ word' } - - it { is_expected.to eq('http://u%24ername:pas%24%20word@organization.example.com/jenkins/project/my_project') } - end - - context 'userinfo is provided and username and password are set' do - let(:jenkins_url) { 'http://u:p@organization.example.com/jenkins' } - let(:username) { 'username' } - let(:password) { 'password' } - - it { is_expected.to eq('http://username:password@organization.example.com/jenkins/project/my_project') } - end - - context 'userinfo is provided username and password are not set' do - let(:jenkins_url) { 'http://u:p@organization.example.com/jenkins' } - - it { is_expected.to eq('http://u:p@organization.example.com/jenkins/project/my_project') } - end - end - - describe '#test' do - it 'returns the right status' 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) - stub_request(:post, jenkins_hook_url).with(headers: { 'Authorization' => jenkins_authorization }) - - result = jenkins_service.test(push_sample_data) - - expect(result).to eq({ success: true, result: '' }) - end - end - - describe '#execute' do - let(:user) { create(:user, username: 'username') } - 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) } - - before do - stub_request(:post, jenkins_hook_url) - end - - it 'invokes the Jenkins API' do - jenkins_service.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) - - expect( - a_request(:post, jenkins_hook_url) - .with(headers: { 'X-Gitlab-Event' => 'Push Hook', 'Authorization' => jenkins_authorization }) - ).to have_been_made.once - end - - it 'request url contains properly serialized username and password' do - jenkins_service.execute(push_sample_data) - - expect( - a_request(:post, 'http://jenkins.example.com/project/my_project') - .with(headers: { 'Authorization' => jenkins_authorization }) - ).to have_been_made.once - end - end - - describe 'Stored password invalidation' do - let(:project) { create(:project) } - - context 'when a password was previously set' do - before do - @jenkins_service = described_class.create!( - project: project, - properties: { - jenkins_url: 'http://jenkins.example.com/', - username: 'jenkins', - password: 'password' - } - ) - 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 - end - - it 'resets password if username is blank' do - @jenkins_service.username = '' - @jenkins_service.save! - expect(@jenkins_service.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') - 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/') - 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 - end - end - - context 'when no password was previously set' do - before do - @jenkins_service = described_class.create!( - project: create(:project), - properties: { - jenkins_url: 'http://jenkins.example.com/', - username: 'jenkins' - } - ) - 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/') - end - end - end -end diff --git a/spec/models/project_services/jira_service_spec.rb b/spec/models/project_services/jira_service_spec.rb deleted file mode 100644 index 73e91bf9ea8..00000000000 --- a/spec/models/project_services/jira_service_spec.rb +++ /dev/null @@ -1,976 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe JiraService do - include AssetsHelpers - - let_it_be(:project) { create(:project, :repository) } - - let(:current_user) { build_stubbed(:user) } - let(:url) { 'http://jira.example.com' } - let(:api_url) { 'http://api-jira.example.com' } - let(:username) { 'jira-username' } - let(:password) { 'jira-password' } - let(:transition_id) { 'test27' } - let(:server_info_results) { { 'deploymentType' => 'Cloud' } } - let(:jira_service) do - described_class.new( - project: project, - url: url, - username: username, - password: password - ) - end - - before do - WebMock.stub_request(:get, /serverInfo/).to_return(body: server_info_results.to_json ) - end - - describe '#options' do - let(:options) do - { - project: project, - active: true, - username: 'username', - password: 'test', - jira_issue_transition_id: 24, - url: 'http://jira.test.com/path/' - } - end - - let(:service) { described_class.create!(options) } - - it 'sets the URL properly' do - # jira-ruby gem parses the URI and handles trailing slashes fine: - # https://github.com/sumoheavy/jira-ruby/blob/v1.7.0/lib/jira/http_client.rb#L62 - expect(service.options[:site]).to eq('http://jira.test.com/') - end - - it 'leaves out trailing slashes in context' do - expect(service.options[:context_path]).to eq('/path') - end - - context 'username with trailing whitespaces' do - before do - options.merge!(username: 'username ') - end - - it 'leaves out trailing whitespaces in username' do - expect(service.options[:username]).to eq('username') - end - end - - it 'provides additional cookies to allow basic auth with oracle webgate' do - expect(service.options[:use_cookies]).to eq(true) - expect(service.options[:additional_cookies]).to eq(['OBBasicAuth=fromDialog']) - end - - context 'using api URL' do - before do - options.merge!(api_url: 'http://jira.test.com/api_path/') - end - - it 'leaves out trailing slashes in context' do - expect(service.options[:context_path]).to eq('/api_path') - end - end - end - - describe '#fields' do - let(:service) { create(:jira_service) } - - subject(:fields) { service.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 - - where(:key, :result) do - '#123' | '' - '1#23#12' | '' - 'JIRA-1234A' | 'JIRA-1234' - 'JIRA-1234-some_tag' | 'JIRA-1234' - 'JIRA-1234_some_tag' | 'JIRA-1234' - 'EXT_EXT-1234' | 'EXT_EXT-1234' - 'EXT3_EXT-1234' | 'EXT3_EXT-1234' - '3EXT_EXT-1234' | '' - end - - with_them do - specify do - expect(described_class.reference_pattern.match(key).to_s).to eq(result) - end - end - end - - describe '#create' do - let(:params) do - { - project: project, - url: url, api_url: api_url, - username: username, password: password, - jira_issue_transition_id: transition_id - } - end - - subject { described_class.create!(params) } - - it 'does not store data into properties' do - expect(subject.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 - end - - context 'when loading serverInfo' do - let!(:jira_service) { subject } - - context 'Cloud instance' do - let(:server_info_results) { { 'deploymentType' => 'Cloud' } } - - it 'is detected' do - expect(jira_service.jira_tracker_data.deployment_cloud?).to be_truthy - end - end - - context 'Server instance' do - let(:server_info_results) { { 'deploymentType' => 'Server' } } - - it 'is detected' do - expect(jira_service.jira_tracker_data.deployment_server?).to be_truthy - end - end - - context 'Unknown instance' do - let(:server_info_results) { { 'deploymentType' => 'FutureCloud' } } - - it 'is detected' do - expect(jira_service.jira_tracker_data.deployment_unknown?).to be_truthy - end - end - end - end - - # we need to make sure we are able to read both from properties and jira_tracker_data table - # TODO: change this as part of https://gitlab.com/gitlab-org/gitlab/issues/29404 - context 'overriding properties' do - let(:access_params) do - { url: url, api_url: api_url, username: username, password: password, - jira_issue_transition_id: transition_id } - end - - let(:data_params) do - { - url: url, api_url: api_url, - username: username, password: password, - jira_issue_transition_id: transition_id - } - end - - shared_examples 'handles jira fields' do - let(:data_params) do - { - url: url, api_url: api_url, - username: username, password: password, - jira_issue_transition_id: transition_id - } - end - - 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) - end - end - - describe '#update' do - context 'basic update' do - let_it_be(:new_username) { 'new_username' } - 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 - end - - it 'stores updated data in jira_tracker_data table' do - data = service.jira_tracker_data.reload - - expect(data.url).to eq(new_url) - expect(data.api_url).to eq(api_url) - expect(data.username).to eq(new_username) - expect(data.password).to eq(password) - expect(data.jira_issue_transition_id).to eq(transition_id) - end - end - - context 'when updating the url, api_url, username, or password' do - it 'updates deployment type' do - service.update!(url: 'http://first.url') - service.jira_tracker_data.update!(deployment_type: 'server') - - expect(service.jira_tracker_data.deployment_server?).to be_truthy - - service.update!(api_url: 'http://another.url') - service.jira_tracker_data.reload - - expect(service.jira_tracker_data.deployment_cloud?).to be_truthy - expect(WebMock).to have_requested(:get, /serverInfo/).twice - end - - it 'calls serverInfo for url' do - service.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') - - expect(WebMock).to have_requested(:get, /serverInfo/) - end - - it 'calls serverInfo for username' do - service.update!(username: 'test-user') - - expect(WebMock).to have_requested(:get, /serverInfo/) - end - - it 'calls serverInfo for password' do - service.update!(password: 'test-password') - - expect(WebMock).to have_requested(:get, /serverInfo/) - end - end - - 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(WebMock).not_to have_requested(:get, /serverInfo/) - end - end - - 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) - - service.update!(url: 'http://first.url') - - expect(WebMock).not_to have_requested(:get, /serverInfo/) - end - end - - context 'stored password invalidation' do - context 'when a password was previously set' do - context 'when only web url present' do - let(:data_params) do - { - url: url, api_url: nil, - username: username, password: password, - jira_issue_transition_id: transition_id - } - end - - it 'resets password if url changed' do - service - service.url = 'http://jira_edited.example.com' - service.save! - - expect(service.reload.url).to eq('http://jira_edited.example.com') - expect(service.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! - - expect(service.reload.url).to eq('http://jira.example.com') - expect(service.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! - - expect(service.reload.api_url).to eq('http://jira_edited.example.com/rest/api/2') - expect(service.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! - - expect(service.password).to eq(password) - expect(service.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! - - expect(service.password).to be_nil - end - - it 'does not reset password if username changed' do - service.username = 'some_name' - service.save! - - expect(service.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! - - expect(service.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! - - expect(service.reload.password).to eq(password) - end - end - - context 'when both web and api url present' do - let(:data_params) do - { - url: url, api_url: 'http://jira.example.com/rest/api/2', - username: username, password: password, - jira_issue_transition_id: transition_id - } - end - - it 'resets password if api url changed' do - service.api_url = 'http://jira_edited.example.com/rest/api/2' - service.save! - - expect(service.password).to be_nil - end - - it 'does not reset password if url changed' do - service.url = 'http://jira_edited.example.com' - service.save! - - expect(service.password).to eq(password) - end - - it 'resets password if api url set to empty' do - service.update!(api_url: '') - - expect(service.reload.password).to be_nil - end - end - end - - context 'when no password was previously set' do - let(:data_params) do - { - url: url, username: username - } - 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') - end - end - end - end - end - - # 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')) - 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: {})) - end - - it_behaves_like 'handles jira fields' - end - - 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| - create(:jira_tracker_data, data_params.merge(integration: integration)) - end - end - - it_behaves_like 'handles jira fields' - end - end - - describe '#find_issue' do - let(:issue_key) { 'JIRA-123' } - let(:issue_url) { "#{url}/rest/api/2/issue/#{issue_key}" } - - before do - stub_request(:get, issue_url).with(basic_auth: [username, password]) - end - - it 'call the Jira API to get the issue' do - jira_service.find_issue(issue_key) - - expect(WebMock).to have_requested(:get, issue_url) - end - - context 'with options' 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) - - expect(WebMock).to have_requested(:get, issue_url) - end - end - end - - describe '#close_issue' do - let(:custom_base_url) { 'http://custom_url' } - - shared_examples 'close_issue' do - let(:issue_key) { 'JIRA-123' } - let(:issue_url) { "#{url}/rest/api/2/issue/#{issue_key}" } - let(:transitions_url) { "#{issue_url}/transitions" } - let(:comment_url) { "#{issue_url}/comment" } - let(:remote_link_url) { "#{issue_url}/remotelink" } - let(:transitions) { nil } - - let(:issue_fields) do - { - id: issue_key, - self: issue_url, - transitions: transitions - } - end - - subject(:close_issue) do - jira_service.close_issue(resource, ExternalIssue.new(issue_key, project)) - end - - before do - jira_service.jira_issue_transition_id = '999' - - # These stubs are needed to test JiraService#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) - closed_issue = open_issue.dup - allow(open_issue).to receive(:resolution).and_return(false) - allow(closed_issue).to receive(:resolution).and_return(true) - allow(JIRA::Resource::Issue).to receive(:find).and_return(open_issue, closed_issue) - - allow_any_instance_of(JIRA::Resource::Issue).to receive(:key).and_return(issue_key) - allow(JIRA::Resource::Remotelink).to receive(:all).and_return([]) - - WebMock.stub_request(:get, issue_url).with(basic_auth: %w(jira-username jira-password)) - WebMock.stub_request(:post, transitions_url).with(basic_auth: %w(jira-username jira-password)) - WebMock.stub_request(:post, comment_url).with(basic_auth: %w(jira-username jira-password)) - WebMock.stub_request(:post, remote_link_url).with(basic_auth: %w(jira-username jira-password)) - end - - let(:external_issue) { ExternalIssue.new('JIRA-123', project) } - - def close_issue - jira_service.close_issue(resource, external_issue, current_user) - end - - it 'calls Jira API' do - close_issue - - expect(WebMock).to have_requested(:post, comment_url).with( - body: /Issue solved with/ - ).once - end - - it 'tracks usage' do - expect(Gitlab::UsageDataCounters::HLLRedisCounter) - .to receive(:track_event) - .with('i_ecosystem_jira_service_close_issue', values: current_user.id) - - close_issue - end - - it 'does not fail if remote_link.all on issue returns nil' do - allow(JIRA::Resource::Remotelink).to receive(:all).and_return(nil) - - expect { close_issue }.not_to raise_error - end - - # Check https://developer.atlassian.com/jiradev/jira-platform/guides/other/guide-jira-remote-issue-links/fields-in-remote-issue-links - # for more information - it 'creates Remote Link reference in Jira for comment' do - close_issue - - favicon_path = "http://localhost/assets/#{find_asset('favicon.png').digest_path}" - - # Creates comment - expect(WebMock).to have_requested(:post, comment_url) - # Creates Remote Link in Jira issue fields - expect(WebMock).to have_requested(:post, remote_link_url).with( - body: hash_including( - GlobalID: 'GitLab', - relationship: 'mentioned on', - object: { - url: "#{Gitlab.config.gitlab.url}/#{project.full_path}/-/commit/#{commit_id}", - title: "Solved by commit #{commit_id}.", - icon: { title: 'GitLab', url16x16: favicon_path }, - status: { resolved: true } - } - ) - ).once - end - - 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) - close_issue - - expect(WebMock).not_to have_requested(:post, comment_url) - expect(WebMock).to have_requested(:post, remote_link_url) - end - end - - context 'when Remote Link already exists' do - let(:remote_link) do - double( - 'remote link', - object: { - url: "#{Gitlab.config.gitlab.url}/#{project.full_path}/-/commit/#{commit_id}" - }.with_indifferent_access - ) - end - - it 'does not create comment' do - allow(JIRA::Resource::Remotelink).to receive(:all).and_return([remote_link]) - - expect(remote_link).to receive(:save!) - - close_issue - - expect(WebMock).not_to have_requested(:post, comment_url) - end - end - - it 'does not send comment or remote links to issues already closed' do - allow_any_instance_of(JIRA::Resource::Issue).to receive(:resolution).and_return(true) - - close_issue - - expect(WebMock).not_to have_requested(:post, comment_url) - expect(WebMock).not_to have_requested(:post, remote_link_url) - end - - it 'does not send comment or remote links to issues with unknown resolution' do - allow_any_instance_of(JIRA::Resource::Issue).to receive(:respond_to?).with(:resolution).and_return(false) - - close_issue - - expect(WebMock).not_to have_requested(:post, comment_url) - expect(WebMock).not_to have_requested(:post, remote_link_url) - end - - it 'references the GitLab commit' do - stub_config_setting(base_url: custom_base_url) - - close_issue - - expect(WebMock).to have_requested(:post, comment_url).with( - body: %r{#{custom_base_url}/#{project.full_path}/-/commit/#{commit_id}} - ).once - end - - it 'references the GitLab commit' do - stub_config_setting(relative_url_root: '/gitlab') - stub_config_setting(url: Settings.send(:build_gitlab_url)) - - allow(described_class).to receive(:default_url_options) do - { script_name: '/gitlab' } - end - - close_issue - - expect(WebMock).to have_requested(:post, comment_url).with( - body: %r{#{Gitlab.config.gitlab.url}/#{project.full_path}/-/commit/#{commit_id}} - ).once - end - - it 'logs exception when transition id is not valid' do - allow(jira_service).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( - "Issue transition failed", - error: hash_including( - exception_class: 'StandardError', - exception_message: "Bad Request" - ), - client_url: "http://jira.example.com" - ) - end - - it 'calls the api with jira_issue_transition_id' do - close_issue - - expect(WebMock).to have_requested(:post, transitions_url).with( - body: /"id":"999"/ - ).once - end - - context 'when custom transition IDs are blank' do - before do - jira_service.jira_issue_transition_id = '' - end - - it 'does not transition the issue' do - close_issue - - expect(WebMock).not_to have_requested(:post, transitions_url) - end - end - - context 'when using automatic issue transitions' do - let(:transitions) do - [ - { id: '1' }, - { id: '2', to: { statusCategory: { key: 'new' } } }, - { id: '3', to: { statusCategory: { key: 'done' } } }, - { id: '4', to: { statusCategory: { key: 'done' } } } - ] - end - - before do - jira_service.jira_issue_transition_automatic = true - - close_issue - end - - it 'uses the next transition with a status category of done' do - expect(WebMock).to have_requested(:post, transitions_url).with( - body: /"id":"3"/ - ).once - end - - context 'when no done transition is available' do - let(:transitions) do - [ - { id: '1', to: { statusCategory: { key: 'new' } } } - ] - end - - it 'does not attempt to transition' do - expect(WebMock).not_to have_requested(:post, transitions_url) - end - end - - context 'when no valid transitions are returned' do - let(:transitions) { 'foo' } - - it 'does not attempt to transition' do - expect(WebMock).not_to have_requested(:post, transitions_url) - end - end - end - - context 'when using multiple transition ids' do - before do - allow(jira_service).to receive_messages(jira_issue_transition_id: '1,2,3') - end - - it 'calls the api with transition ids separated by comma' do - close_issue - - 1.upto(3) do |transition_id| - expect(WebMock).to have_requested(:post, transitions_url).with( - body: /"id":"#{transition_id}"/ - ).once - end - - expect(WebMock).to have_requested(:post, comment_url) - 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') - - close_issue - - 1.upto(3) do |transition_id| - expect(WebMock).to have_requested(:post, transitions_url).with( - body: /"id":"#{transition_id}"/ - ).once - end - - expect(WebMock).to have_requested(:post, comment_url) - end - - context 'when a transition fails' do - before do - WebMock.stub_request(:post, transitions_url).with(basic_auth: %w(jira-username jira-password)).to_return do |request| - { status: request.body.include?('"id":"2"') ? 500 : 200 } - end - end - - it 'stops the sequence' do - close_issue - - 1.upto(2) do |transition_id| - expect(WebMock).to have_requested(:post, transitions_url).with( - body: /"id":"#{transition_id}"/ - ) - end - - expect(WebMock).not_to have_requested(:post, transitions_url).with( - body: /"id":"3"/ - ) - - expect(WebMock).not_to have_requested(:post, comment_url) - end - end - end - end - - context 'when resource is a merge request' do - let(:resource) { create(:merge_request) } - let(:commit_id) { resource.diff_head_sha } - - it_behaves_like 'close_issue' - end - - context 'when resource is a commit' do - let(:resource) { project.commit('master') } - let(:commit_id) { resource.id } - - it_behaves_like 'close_issue' - end - end - - describe '#create_cross_reference_note' do - let_it_be(:user) { build_stubbed(:user) } - - let(:jira_issue) { ExternalIssue.new('JIRA-123', project) } - - subject { jira_service.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" } - let(:comment_url) { "#{issue_url}/comment" } - let(:remote_link_url) { "#{issue_url}/remotelink" } - - before do - allow(JIRA::Resource::Remotelink).to receive(:all).and_return([]) - stub_request(:get, issue_url).with(basic_auth: [username, password]) - stub_request(:post, comment_url).with(basic_auth: [username, password]) - stub_request(:post, remote_link_url).with(basic_auth: [username, password]) - end - - it 'creates a comment on Jira' do - subject - - expect(WebMock).to have_requested(:post, comment_url).with( - body: /mentioned this issue in/ - ).once - end - - it 'tracks usage' do - expect(Gitlab::UsageDataCounters::HLLRedisCounter) - .to receive(:track_event) - .with('i_ecosystem_jira_service_cross_reference', values: user.id) - - subject - end - end - - context 'when resource is a commit' do - let(:resource) { project.commit('master') } - - context 'when disabled' do - before do - allow_next_instance_of(JiraService) do |instance| - allow(instance).to receive(:commit_events) { false } - end - end - - it { is_expected.to eq('Events for commits are disabled.') } - end - - context 'when enabled' do - it_behaves_like 'creates a comment on Jira' - end - end - - context 'when resource is a merge request' do - let(:resource) { build_stubbed(:merge_request, source_project: project) } - - context 'when disabled' do - before do - allow_next_instance_of(JiraService) do |instance| - allow(instance).to receive(:merge_requests_events) { false } - end - end - - it { is_expected.to eq('Events for merge requests are disabled.') } - end - - context 'when enabled' do - it_behaves_like 'creates a comment on Jira' - end - end - end - - describe '#test' do - let(:server_info_results) { { 'url' => 'http://url', 'deploymentType' => 'Cloud' } } - - def server_info - jira_service.test(nil) - end - - context 'when the test succeeds' do - it 'gets Jira project with URL when API URL not set' do - expect(server_info).to eq(success: true, result: server_info_results) - expect(WebMock).to have_requested(:get, /jira.example.com/) - end - - it 'gets Jira project with API URL if set' do - jira_service.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/) - end - end - - context 'when the test fails' do - it 'returns result with the error' do - test_url = 'http://jira.example.com/rest/api/2/serverInfo' - error_message = 'Some specific failure.' - - 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( - 'Error sending message', - client_url: 'http://jira.example.com', - error: error_message - ) - - expect(jira_service.test(nil)).to eq(success: false, result: error_message) - end - end - end - - describe 'project and issue urls' do - context 'when gitlab.yml was initialized' do - it 'is prepopulated with the settings' do - settings = { - 'jira' => { - 'url' => 'http://jira.sample/projects/project_a', - 'api_url' => 'http://jira.sample/api' - } - } - allow(Gitlab.config).to receive(:issues_tracker).and_return(settings) - - service = project.create_jira_service(active: true) - - expect(service.url).to eq('http://jira.sample/projects/project_a') - expect(service.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/') - - expect(service.url).to eq('http://jira.test.com/path') - end - end - - describe 'favicon urls' do - it 'includes the standard favicon' do - props = described_class.new.send(:build_remote_link_props, url: 'http://example.com', title: 'title') - expect(props[:object][:icon][:url16x16]).to match %r{^http://localhost/assets/favicon(?:-\h+).png$} - end - - it 'includes returns the custom favicon' do - create :appearance, favicon: fixture_file_upload('spec/fixtures/dk.png') - - props = described_class.new.send(:build_remote_link_props, url: 'http://example.com', title: 'title') - expect(props[:object][:icon][:url16x16]).to match %r{^http://localhost/uploads/-/system/appearance/favicon/\d+/dk.png$} - end - end - - context 'generating external URLs' do - let(:service) { described_class.new(url: 'http://jira.test.com/path/') } - - describe '#issues_url' do - it 'handles trailing slashes' do - expect(service.issues_url).to eq('http://jira.test.com/path/browse/:id') - end - end - - describe '#new_issue_url' do - it 'handles trailing slashes' do - expect(service.new_issue_url).to eq('http://jira.test.com/path/secure/CreateIssue!default.jspa') - end - end - end - - describe '#issue_transition_enabled?' do - it 'returns true if automatic transitions are enabled' do - jira_service.jira_issue_transition_automatic = true - - expect(jira_service.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' - - expect(jira_service.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) - end - end -end diff --git a/spec/models/project_services/jira_tracker_data_spec.rb b/spec/models/project_services/jira_tracker_data_spec.rb deleted file mode 100644 index 72bdbe40a74..00000000000 --- a/spec/models/project_services/jira_tracker_data_spec.rb +++ /dev/null @@ -1,19 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe JiraTrackerData do - describe 'associations' do - it { is_expected.to belong_to(:integration) } - end - - describe 'deployment_type' do - it { is_expected.to define_enum_for(:deployment_type).with_values([:unknown, :server, :cloud]).with_prefix(:deployment) } - end - - describe 'encrypted attributes' do - subject { described_class.encrypted_attributes.keys } - - it { is_expected.to contain_exactly(:api_url, :password, :url, :username) } - end -end diff --git a/spec/models/project_services/mattermost_service_spec.rb b/spec/models/project_services/mattermost_service_spec.rb deleted file mode 100644 index af1944ea77d..00000000000 --- a/spec/models/project_services/mattermost_service_spec.rb +++ /dev/null @@ -1,7 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe MattermostService do - it_behaves_like "slack or mattermost notifications", "Mattermost" -end diff --git a/spec/models/project_services/mattermost_slash_commands_service_spec.rb b/spec/models/project_services/mattermost_slash_commands_service_spec.rb deleted file mode 100644 index 87befdd4303..00000000000 --- a/spec/models/project_services/mattermost_slash_commands_service_spec.rb +++ /dev/null @@ -1,132 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe MattermostSlashCommandsService do - it_behaves_like "chat slash commands service" - - context 'Mattermost API' do - let(:project) { create(:project) } - let(:service) { project.build_mattermost_slash_commands_service } - 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) - 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') - end - - context 'the requests succeeds' do - before do - stub_request(:post, 'http://mattermost.example.com/api/v4/commands') - .with(body: { - team_id: 'abc', - trigger: 'gitlab', - url: 'http://trigger.url', - icon_url: 'http://icon.url/icon.png', - auto_complete: true, - auto_complete_desc: "Perform common operations on: #{project.full_name}", - auto_complete_hint: '[help]', - description: "Perform common operations on: #{project.full_name}", - display_name: "GitLab / #{project.full_name}", - method: 'P', - username: 'GitLab' - }.to_json) - .to_return( - status: 200, - headers: { 'Content-Type' => 'application/json' }, - body: { token: 'token' }.to_json - ) - end - - it 'saves the service' do - expect { subject }.to change { project.integrations.count }.by(1) - end - - it 'saves the token' do - subject - - expect(service.reload.token).to eq('token') - end - end - - context 'an error is received' do - before do - stub_request(:post, 'http://mattermost.example.com/api/v4/commands') - .to_return( - status: 500, - headers: { 'Content-Type' => 'application/json' }, - body: { - id: 'api.command.duplicate_trigger.app_error', - message: 'This trigger word is already in use. Please choose another word.', - detailed_error: '', - request_id: 'obc374man7bx5r3dbc1q5qhf3r', - status_code: 500 - }.to_json - ) - end - - it 'shows error messages' do - succeeded, message = subject - - expect(succeeded).to be(false) - expect(message).to eq('This trigger word is already in use. Please choose another word.') - end - end - end - - describe '#list_teams' do - subject do - service.list_teams(user) - end - - context 'the requests succeeds' do - before do - stub_request(:get, 'http://mattermost.example.com/api/v4/users/me/teams') - .to_return( - status: 200, - headers: { 'Content-Type' => 'application/json' }, - body: [{ id: 'test_team_id' }].to_json - ) - end - - it 'returns a list of teams' do - expect(subject).not_to be_empty - end - end - - context 'an error is received' do - before do - stub_request(:get, 'http://mattermost.example.com/api/v4/users/me/teams') - .to_return( - status: 500, - headers: { 'Content-Type' => 'application/json' }, - body: { - message: 'Failed to get team list.' - }.to_json - ) - end - - it 'shows error messages' do - expect(subject).to eq([[], "Failed to get team list."]) - end - end - end - - describe '#chat_responder' do - it 'returns the responder to use for Mattermost' do - expect(described_class.new.chat_responder) - .to eq(Gitlab::Chat::Responder::Mattermost) - end - end - end -end diff --git a/spec/models/project_services/microsoft_teams_service_spec.rb b/spec/models/project_services/microsoft_teams_service_spec.rb deleted file mode 100644 index 5f3a94a5b99..00000000000 --- a/spec/models/project_services/microsoft_teams_service_spec.rb +++ /dev/null @@ -1,360 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe MicrosoftTeamsService do - let(:chat_service) { 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 - before do - subject.active = true - end - - it { is_expected.to validate_presence_of(:webhook) } - it_behaves_like 'issue tracker service URL attribute', :webhook - end - - context 'when service is inactive' do - before do - subject.active = false - end - - it { is_expected.not_to validate_presence_of(:webhook) } - end - end - - describe '.supported_events' do - it 'does not support deployment_events' do - expect(described_class.supported_events).not_to include('deployment') - end - end - - describe "#execute" do - let(:user) { create(:user) } - - let_it_be(:project) { create(:project, :repository, :wiki_repo) } - - before do - allow(chat_service).to receive_messages( - project: project, - project_id: project.id, - service_hook: true, - webhook: webhook_url - ) - - WebMock.stub_request(:post, webhook_url) - end - - context 'with push events' do - let(:push_sample_data) do - Gitlab::DataBuilder::Push.build_sample(project, user) - end - - it "calls Microsoft Teams API for push events" do - chat_service.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) - - chat_service.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) - issue = service.execute - service.hook_data(issue, 'open') - end - - it "calls Microsoft Teams API" do - chat_service.execute(issues_sample_data) - - expect(WebMock).to have_requested(:post, webhook_url).once - end - end - - context 'with merge events' do - let(:opts) do - { - title: 'Awesome merge_request', - description: 'please fix', - source_branch: 'feature', - target_branch: 'master' - } - end - - let(:merge_sample_data) do - service = MergeRequests::CreateService.new(project: project, current_user: user, params: opts) - merge_request = service.execute - service.hook_data(merge_request, 'open') - end - - before do - project.add_developer(user) - end - - it "calls Microsoft Teams API" do - chat_service.execute(merge_sample_data) - - expect(WebMock).to have_requested(:post, webhook_url).once - end - end - - context 'with wiki page events' do - let(:opts) do - { - title: "Awesome wiki_page", - content: "Some text describing some thing or another", - format: "md", - message: "user created page: Awesome wiki_page" - } - end - - let(:wiki_page) { create(:wiki_page, wiki: project.wiki, **opts) } - 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) - - expect(WebMock).to have_requested(:post, webhook_url).once - end - end - end - - describe "Note events" do - let(:user) { create(:user) } - let(:project) { create(:project, :repository, creator: user) } - - before do - allow(chat_service).to receive_messages( - project: project, - project_id: project.id, - service_hook: true, - webhook: webhook_url - ) - - WebMock.stub_request(:post, webhook_url) - end - - context 'when commit comment event executed' do - let(:commit_note) do - create(:note_on_commit, author: user, - project: project, - commit_id: project.repository.commit.id, - note: 'a comment on a commit') - end - - it "calls Microsoft Teams API for commit comment events" do - data = Gitlab::DataBuilder::Note.build(commit_note, user) - - chat_service.execute(data) - - expect(WebMock).to have_requested(:post, webhook_url).once - end - end - - context 'when merge request comment event executed' do - let(:merge_request_note) do - create(:note_on_merge_request, project: project, - note: "merge request note") - end - - it "calls Microsoft Teams API for merge request comment events" do - data = Gitlab::DataBuilder::Note.build(merge_request_note, user) - - chat_service.execute(data) - - expect(WebMock).to have_requested(:post, webhook_url).once - end - end - - context 'when issue comment event executed' do - let(:issue_note) do - create(:note_on_issue, project: project, note: "issue note") - end - - it "calls Microsoft Teams API for issue comment events" do - data = Gitlab::DataBuilder::Note.build(issue_note, user) - - chat_service.execute(data) - - expect(WebMock).to have_requested(:post, webhook_url).once - end - end - - context 'when snippet comment event executed' do - let(:snippet_note) do - create(:note_on_project_snippet, project: project, - note: "snippet note") - end - - it "calls Microsoft Teams API for snippet comment events" do - data = Gitlab::DataBuilder::Note.build(snippet_note, user) - - chat_service.execute(data) - - expect(WebMock).to have_requested(:post, webhook_url).once - end - end - end - - describe 'Pipeline events' do - let(:user) { create(:user) } - let(:project) { create(:project, :repository) } - - let(:pipeline) do - create(:ci_pipeline, - project: project, status: status, - sha: project.commit.sha, ref: project.default_branch) - end - - before do - allow(chat_service).to receive_messages( - project: project, - service_hook: true, - webhook: webhook_url - ) - end - - 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 - end - - it 'calls Microsoft Teams API for pipeline events' do - data = Gitlab::DataBuilder::Pipeline.build(pipeline) - data[:markdown] = true - - chat_service.execute(data) - - message = Integrations::ChatMessage::PipelineMessage.new(data) - - expect(WebMock).to have_requested(:post, webhook_url) - .with(body: hash_including({ summary: message.summary })) - .once - end - end - - 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 - end - it 'does not call Microsoft Teams API for pipeline events' do - data = Gitlab::DataBuilder::Pipeline.build(pipeline) - result = chat_service.execute(data) - - expect(result).to be_falsy - end - end - - context 'with failed pipeline' do - let(:status) { 'failed' } - - it_behaves_like 'call Microsoft Teams API' - end - - context 'with succeeded pipeline' do - let(:status) { 'success' } - - 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) - - expect(result).to be_falsy - end - end - - context 'with setting notify_only_broken_pipelines to false' do - before do - chat_service.notify_only_broken_pipelines = false - end - - it_behaves_like 'call Microsoft Teams API' - end - end - - context 'with default branch' do - let(:pipeline) do - create(:ci_pipeline, project: project, status: 'failed', sha: project.commit.sha, ref: project.default_branch) - end - - context 'only notify for the default branch' do - it_behaves_like 'call Microsoft Teams API', branches_to_be_notified: "default" - end - - context 'notify for only protected branches' do - it_behaves_like 'does not call Microsoft Teams API', branches_to_be_notified: "protected" - end - - context 'notify for only default and protected branches' do - it_behaves_like 'call Microsoft Teams API', branches_to_be_notified: "default_and_protected" - end - - context 'notify for all branches' do - it_behaves_like 'call Microsoft Teams API', branches_to_be_notified: "all" - end - end - - context 'with protected branch' do - before do - create(:protected_branch, project: project, name: 'a-protected-branch') - end - - let(:pipeline) do - create(:ci_pipeline, project: project, status: 'failed', sha: project.commit.sha, ref: 'a-protected-branch') - end - - context 'only notify for the default branch' do - it_behaves_like 'does not call Microsoft Teams API', branches_to_be_notified: "default" - end - - context 'notify for only protected branches' do - it_behaves_like 'call Microsoft Teams API', branches_to_be_notified: "protected" - end - - context 'notify for only default and protected branches' do - it_behaves_like 'call Microsoft Teams API', branches_to_be_notified: "default_and_protected" - end - - context 'notify for all branches' do - it_behaves_like 'call Microsoft Teams API', branches_to_be_notified: "all" - end - end - - context 'with neither protected nor default branch' do - let(:pipeline) do - create(:ci_pipeline, project: project, status: 'failed', sha: project.commit.sha, ref: 'a-random-branch') - end - - context 'only notify for the default branch' do - it_behaves_like 'does not call Microsoft Teams API', branches_to_be_notified: "default" - end - - context 'notify for only protected branches' do - it_behaves_like 'does not call Microsoft Teams API', branches_to_be_notified: "protected" - end - - context 'notify for only default and protected branches' do - it_behaves_like 'does not call Microsoft Teams API', branches_to_be_notified: "default_and_protected" - end - - context 'notify for all branches' do - it_behaves_like 'call Microsoft Teams API', branches_to_be_notified: "all" - end - end - end -end diff --git a/spec/models/project_services/open_project_service_spec.rb b/spec/models/project_services/open_project_service_spec.rb deleted file mode 100644 index 1abaab0ceff..00000000000 --- a/spec/models/project_services/open_project_service_spec.rb +++ /dev/null @@ -1,35 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe OpenProjectService do - describe 'Validations' do - context 'when service is active' do - before do - subject.active = true - end - - it { is_expected.to validate_presence_of(:url) } - 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 - end - - context 'when service is inactive' do - before do - subject.active = false - end - - it { is_expected.not_to validate_presence_of(:url) } - it { is_expected.not_to validate_presence_of(:token) } - 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/project_services/open_project_tracker_data_spec.rb b/spec/models/project_services/open_project_tracker_data_spec.rb deleted file mode 100644 index 1f7f01cfea4..00000000000 --- a/spec/models/project_services/open_project_tracker_data_spec.rb +++ /dev/null @@ -1,19 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe OpenProjectTrackerData do - describe 'associations' do - it { is_expected.to belong_to(:integration) } - end - - describe 'closed_status_id' do - it 'returns the set value' do - expect(build(:open_project_tracker_data).closed_status_id).to eq('15') - end - - it 'returns the default value if not set' do - expect(build(:open_project_tracker_data, closed_status_id: nil).closed_status_id).to eq('13') - end - end -end diff --git a/spec/models/project_services/packagist_service_spec.rb b/spec/models/project_services/packagist_service_spec.rb deleted file mode 100644 index 33b5c9809c7..00000000000 --- a/spec/models/project_services/packagist_service_spec.rb +++ /dev/null @@ -1,48 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe PackagistService do - let(:packagist_params) do - { - active: true, - project: project, - properties: { - username: packagist_username, - token: packagist_token, - server: packagist_server - } - } - end - - let(:packagist_hook_url) do - "#{packagist_server}/api/update-package?username=#{packagist_username}&apiToken=#{packagist_token}" - end - - let(:packagist_token) { 'verySecret' } - let(:packagist_username) { 'theUser' } - 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 } - 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) } - - before do - stub_request(:post, packagist_hook_url) - end - - it 'calls Packagist API' do - packagist_service.execute(push_sample_data) - - expect(a_request(:post, packagist_hook_url)).to have_been_made.once - end - end -end diff --git a/spec/models/project_services/pipelines_email_service_spec.rb b/spec/models/project_services/pipelines_email_service_spec.rb deleted file mode 100644 index 21cc5d44558..00000000000 --- a/spec/models/project_services/pipelines_email_service_spec.rb +++ /dev/null @@ -1,305 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe PipelinesEmailService, :mailer do - let(:pipeline) do - create(:ci_pipeline, :failed, - project: project, - sha: project.commit('master').sha, - ref: project.default_branch - ) - end - - let(:project) { create(:project, :repository) } - let(:recipients) { 'test@gitlab.com' } - let(:receivers) { [recipients] } - - let(:data) do - Gitlab::DataBuilder::Pipeline.build(pipeline) - end - - describe 'Validations' do - context 'when service is active' do - before do - subject.active = true - end - - it { is_expected.to validate_presence_of(:recipients) } - end - - context 'when service is inactive' do - before do - subject.active = false - end - - it { is_expected.not_to validate_presence_of(:recipients) } - end - end - - shared_examples 'sending email' do |branches_to_be_notified: nil| - before do - subject.recipients = recipients - subject.branches_to_be_notified = branches_to_be_notified if branches_to_be_notified - - perform_enqueued_jobs do - run - end - end - - it 'sends email' do - emails = receivers.map { |r| double(notification_email: r) } - - should_only_email(*emails, kind: :bcc) - end - end - - shared_examples 'not sending email' do |branches_to_be_notified: nil| - before do - subject.recipients = recipients - subject.branches_to_be_notified = branches_to_be_notified if branches_to_be_notified - - perform_enqueued_jobs do - run - end - end - - it 'does not send email' do - should_not_email_anyone - end - end - - describe '#test' do - def run - subject.test(data) - end - - context 'when pipeline is failed and on default branch' do - it_behaves_like 'sending email' - end - - context 'when pipeline is succeeded' do - before do - data[:object_attributes][:status] = 'success' - pipeline.update!(status: 'success') - end - - it_behaves_like 'sending email' - end - - context 'when the pipeline failed' do - context 'on default branch' do - before do - data[:object_attributes][:ref] = project.default_branch - pipeline.update!(ref: project.default_branch) - end - - context 'notifications are enabled only for default branch' do - it_behaves_like 'sending email', branches_to_be_notified: "default" - end - - context 'notifications are enabled only for protected branch' do - it_behaves_like 'sending email', branches_to_be_notified: "protected" - end - - context 'notifications are enabled only for default and protected branches ' do - it_behaves_like 'sending email', branches_to_be_notified: "default_and_protected" - end - - context 'notifications are enabled only for all branches' do - it_behaves_like 'sending email', branches_to_be_notified: "all" - end - end - - context 'on a protected branch' do - before do - create(:protected_branch, project: project, name: 'a-protected-branch') - data[:object_attributes][:ref] = 'a-protected-branch' - pipeline.update!(ref: 'a-protected-branch') - end - - context 'notifications are enabled only for default branch' do - it_behaves_like 'sending email', branches_to_be_notified: "default" - end - - context 'notifications are enabled only for protected branch' do - it_behaves_like 'sending email', branches_to_be_notified: "protected" - end - - context 'notifications are enabled only for default and protected branches ' do - it_behaves_like 'sending email', branches_to_be_notified: "default_and_protected" - end - - context 'notifications are enabled only for all branches' do - it_behaves_like 'sending email', branches_to_be_notified: "all" - end - end - - context 'on a neither protected nor default branch' do - before do - data[:object_attributes][:ref] = 'a-random-branch' - pipeline.update!(ref: 'a-random-branch') - end - - context 'notifications are enabled only for default branch' do - it_behaves_like 'sending email', branches_to_be_notified: "default" - end - - context 'notifications are enabled only for protected branch' do - it_behaves_like 'sending email', branches_to_be_notified: "protected" - end - - context 'notifications are enabled only for default and protected branches ' do - it_behaves_like 'sending email', branches_to_be_notified: "default_and_protected" - end - - context 'notifications are enabled only for all branches' do - it_behaves_like 'sending email', branches_to_be_notified: "all" - end - end - end - end - - describe '#execute' do - before do - subject.project = project - end - - def run - subject.execute(data) - end - - context 'with recipients' do - context 'with failed pipeline' do - it_behaves_like 'sending email' - end - - context 'with succeeded pipeline' do - before do - data[:object_attributes][:status] = 'success' - pipeline.update!(status: 'success') - end - - it_behaves_like 'not sending email' - end - - context 'with notify_only_broken_pipelines on' do - before do - subject.notify_only_broken_pipelines = true - end - - context 'with failed pipeline' do - it_behaves_like 'sending email' - end - - context 'with succeeded pipeline' do - before do - data[:object_attributes][:status] = 'success' - pipeline.update!(status: 'success') - end - - it_behaves_like 'not sending email' - end - end - - context 'when the pipeline failed' do - context 'on default branch' do - before do - data[:object_attributes][:ref] = project.default_branch - pipeline.update!(ref: project.default_branch) - end - - context 'notifications are enabled only for default branch' do - it_behaves_like 'sending email', branches_to_be_notified: "default" - end - - context 'notifications are enabled only for protected branch' do - it_behaves_like 'not sending email', branches_to_be_notified: "protected" - end - - context 'notifications are enabled only for default and protected branches ' do - it_behaves_like 'sending email', branches_to_be_notified: "default_and_protected" - end - - context 'notifications are enabled only for all branches' do - it_behaves_like 'sending email', branches_to_be_notified: "all" - end - end - - context 'on a protected branch' do - before do - create(:protected_branch, project: project, name: 'a-protected-branch') - data[:object_attributes][:ref] = 'a-protected-branch' - pipeline.update!(ref: 'a-protected-branch') - end - - context 'notifications are enabled only for default branch' do - it_behaves_like 'not sending email', branches_to_be_notified: "default" - end - - context 'notifications are enabled only for protected branch' do - it_behaves_like 'sending email', branches_to_be_notified: "protected" - end - - context 'notifications are enabled only for default and protected branches ' do - it_behaves_like 'sending email', branches_to_be_notified: "default_and_protected" - end - - context 'notifications are enabled only for all branches' do - it_behaves_like 'sending email', branches_to_be_notified: "all" - end - end - - context 'on a neither protected nor default branch' do - before do - data[:object_attributes][:ref] = 'a-random-branch' - pipeline.update!(ref: 'a-random-branch') - end - - context 'notifications are enabled only for default branch' do - it_behaves_like 'not sending email', branches_to_be_notified: "default" - end - - context 'notifications are enabled only for protected branch' do - it_behaves_like 'not sending email', branches_to_be_notified: "protected" - end - - context 'notifications are enabled only for default and protected branches ' do - it_behaves_like 'not sending email', branches_to_be_notified: "default_and_protected" - end - - context 'notifications are enabled only for all branches' do - it_behaves_like 'sending email', branches_to_be_notified: "all" - end - end - end - end - - context 'with empty recipients list' do - let(:recipients) { ' ,, ' } - - context 'with failed pipeline' do - before do - data[:object_attributes][:status] = 'failed' - pipeline.update!(status: 'failed') - end - - it_behaves_like 'not sending email' - end - end - - context 'with recipients list separating with newlines' do - let(:recipients) { "\ntest@gitlab.com, \r\nexample@gitlab.com\rother@gitlab.com" } - let(:receivers) { %w[test@gitlab.com example@gitlab.com other@gitlab.com] } - - context 'with failed pipeline' do - before do - data[:object_attributes][:status] = 'failed' - pipeline.update!(status: 'failed') - end - - it_behaves_like 'sending email' - end - end - end -end diff --git a/spec/models/project_services/pivotaltracker_service_spec.rb b/spec/models/project_services/pivotaltracker_service_spec.rb deleted file mode 100644 index 8de85cc7fa5..00000000000 --- a/spec/models/project_services/pivotaltracker_service_spec.rb +++ /dev/null @@ -1,101 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe PivotaltrackerService 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 - before do - subject.active = true - end - - it { is_expected.to validate_presence_of(:token) } - end - - context 'when service is inactive' do - before do - subject.active = false - end - - it { is_expected.not_to validate_presence_of(:token) } - end - end - - describe 'Execute' do - let(:service) do - described_class.new.tap do |service| - service.token = 'secret_api_token' - end - end - - let(:url) { PivotaltrackerService::API_ENDPOINT } - - def push_data(branch: 'master') - { - object_kind: 'push', - ref: "refs/heads/#{branch}", - commits: [ - { - id: '21c12ea', - author: { - name: 'Some User' - }, - url: 'https://example.com/commit', - message: 'commit message' - } - ] - } - end - - before do - stub_full_request(url, method: :post) - end - - it 'posts correct message' do - service.execute(push_data) - expect(WebMock).to have_requested(:post, stubbed_hostname(url)).with( - body: { - 'source_commit' => { - 'commit_id' => '21c12ea', - 'author' => 'Some User', - 'url' => 'https://example.com/commit', - 'message' => 'commit message' - } - }, - headers: { - 'Content-Type' => 'application/json', - 'X-TrackerToken' => 'secret_api_token' - } - ).once - end - - context 'when allowed branches is specified' do - let(:service) do - super().tap do |service| - service.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')) - - 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')) - - expect(WebMock).not_to have_requested(:post, stubbed_hostname(url)) - end - end - end -end diff --git a/spec/models/project_services/prometheus_service_spec.rb b/spec/models/project_services/prometheus_service_spec.rb index 37a6d49ff74..a2025388fab 100644 --- a/spec/models/project_services/prometheus_service_spec.rb +++ b/spec/models/project_services/prometheus_service_spec.rb @@ -323,9 +323,9 @@ RSpec.describe PrometheusService, :use_clean_rails_memory_store_caching, :snowpl end describe '#prometheus_available?' do - context 'clusters with installed prometheus' do + context 'clusters with enabled prometheus' do before do - create(:clusters_applications_prometheus, :installed, cluster: cluster) + create(:clusters_integrations_prometheus, cluster: cluster) end context 'cluster belongs to project' do @@ -340,7 +340,7 @@ RSpec.describe PrometheusService, :use_clean_rails_memory_store_caching, :snowpl let_it_be(:group) { create(:group) } let(:project) { create(:prometheus_project, group: group) } - let(:cluster) { create(:cluster_for_group, :with_installed_helm, groups: [group]) } + let(:cluster) { create(:cluster_for_group, groups: [group]) } it 'returns true' do expect(service.prometheus_available?).to be(true) @@ -349,8 +349,8 @@ RSpec.describe PrometheusService, :use_clean_rails_memory_store_caching, :snowpl it 'avoids N+1 queries' do service 5.times do |i| - other_cluster = create(:cluster_for_group, :with_installed_helm, groups: [group], environment_scope: i) - create(:clusters_applications_prometheus, :installing, cluster: other_cluster) + other_cluster = create(:cluster_for_group, groups: [group], environment_scope: i) + create(:clusters_integrations_prometheus, cluster: other_cluster) end expect { service.prometheus_available? }.not_to exceed_query_limit(1) end @@ -365,18 +365,9 @@ RSpec.describe PrometheusService, :use_clean_rails_memory_store_caching, :snowpl end end - context 'clusters with updated prometheus' do - let!(:cluster) { create(:cluster, projects: [project]) } - let!(:prometheus) { create(:clusters_applications_prometheus, :updated, cluster: cluster) } - - it 'returns true' do - expect(service.prometheus_available?).to be(true) - end - end - - context 'clusters without prometheus installed' do + context 'clusters with prometheus disabled' do let(:cluster) { create(:cluster, projects: [project]) } - let!(:prometheus) { create(:clusters_applications_prometheus, cluster: cluster) } + let!(:prometheus) { create(:clusters_integrations_prometheus, :disabled, cluster: cluster) } it 'returns false' do expect(service.prometheus_available?).to be(false) @@ -491,13 +482,13 @@ RSpec.describe PrometheusService, :use_clean_rails_memory_store_caching, :snowpl expect(service.editable?).to be(true) end - context 'when cluster exists with prometheus installed' do + context 'when cluster exists with prometheus enabled' do let(:cluster) { create(:cluster, projects: [project]) } before do service.update!(manual_configuration: false) - create(:clusters_applications_prometheus, :installed, cluster: cluster) + create(:clusters_integrations_prometheus, cluster: cluster) end it 'remains editable' do diff --git a/spec/models/project_services/pushover_service_spec.rb b/spec/models/project_services/pushover_service_spec.rb deleted file mode 100644 index b7d3b8987b8..00000000000 --- a/spec/models/project_services/pushover_service_spec.rb +++ /dev/null @@ -1,71 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe PushoverService 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 - before do - subject.active = true - end - - it { is_expected.to validate_presence_of(:api_key) } - it { is_expected.to validate_presence_of(:user_key) } - it { is_expected.to validate_presence_of(:priority) } - end - - context 'when service is inactive' do - before do - subject.active = false - end - - it { is_expected.not_to validate_presence_of(:api_key) } - it { is_expected.not_to validate_presence_of(:user_key) } - it { is_expected.not_to validate_presence_of(:priority) } - end - end - - describe 'Execute' do - let(:pushover) { described_class.new } - let(:user) { create(:user) } - let(:project) { create(:project, :repository) } - let(:sample_data) do - Gitlab::DataBuilder::Push.build_sample(project, user) - end - - let(:api_key) { 'verySecret' } - let(:user_key) { 'verySecret' } - let(:device) { 'myDevice' } - let(:priority) { 0 } - let(:sound) { 'bike' } - let(:api_url) { 'https://api.pushover.net/1/messages.json' } - - before 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, - priority: priority, - sound: sound - ) - - stub_full_request(api_url, method: :post, ip_address: '8.8.8.8') - end - - it 'calls Pushover API' do - pushover.execute(sample_data) - - expect(WebMock).to have_requested(:post, 'https://8.8.8.8/1/messages.json').once - end - end -end diff --git a/spec/models/project_services/redmine_service_spec.rb b/spec/models/project_services/redmine_service_spec.rb deleted file mode 100644 index b9be3940d34..00000000000 --- a/spec/models/project_services/redmine_service_spec.rb +++ /dev/null @@ -1,53 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe RedmineService 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 - before do - settings = { - 'redmine' => {} - } - allow(Gitlab.config).to receive(:issues_tracker).and_return(settings) - end - - context 'when service 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(: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 - end - - context 'when service is inactive' do - before do - subject.active = false - end - - it { is_expected.not_to validate_presence_of(:project_url) } - it { is_expected.not_to validate_presence_of(:issues_url) } - it { is_expected.not_to validate_presence_of(:new_issue_url) } - end - end - - describe '.reference_pattern' do - it_behaves_like 'allows project key on reference pattern' - - it 'does allow # on the reference' do - expect(described_class.reference_pattern.match('#123')[:issue]).to eq('123') - end - end -end diff --git a/spec/models/project_services/slack_service_spec.rb b/spec/models/project_services/slack_service_spec.rb deleted file mode 100644 index 2e2c1c666d9..00000000000 --- a/spec/models/project_services/slack_service_spec.rb +++ /dev/null @@ -1,133 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe SlackService do - it_behaves_like "slack or mattermost notifications", 'Slack' - - describe '#execute' do - before do - stub_request(:post, "https://slack.service.url/") - end - - let_it_be(:slack_service) { create(:slack_service, branches_to_be_notified: 'all') } - - it 'uses only known events', :aggregate_failures do - described_class::SUPPORTED_EVENTS_FOR_USAGE_LOG.each do |action| - expect(Gitlab::UsageDataCounters::HLLRedisCounter.known_event?("i_ecosystem_slack_service_#{action}_notification")).to be true - end - end - - context 'hook data includes a user object' do - let_it_be(:user) { create_default(:user) } - let_it_be(:project) { create_default(:project, :repository, :wiki_repo) } - - shared_examples 'increases the usage data counter' do |event_name| - 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) - end - end - - context 'event is not supported for usage log' do - let_it_be(:pipeline) { create(:ci_pipeline) } - - let(:data) { Gitlab::DataBuilder::Pipeline.build(pipeline) } - - 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) - end - end - - context 'issue notification' do - let_it_be(:issue) { create(:issue) } - - let(:data) { issue.to_hook_data(user) } - - it_behaves_like 'increases the usage data counter', 'i_ecosystem_slack_service_issue_notification' - end - - context 'push notification' do - let(:data) { Gitlab::DataBuilder::Push.build_sample(project, user) } - - it_behaves_like 'increases the usage data counter', 'i_ecosystem_slack_service_push_notification' - end - - context 'deployment notification' do - let_it_be(:deployment) { create(:deployment, user: user) } - - let(:data) { Gitlab::DataBuilder::Deployment.build(deployment, Time.current) } - - it_behaves_like 'increases the usage data counter', 'i_ecosystem_slack_service_deployment_notification' - end - - context 'wiki_page notification' do - let(:wiki_page) { create(:wiki_page, wiki: project.wiki, message: 'user created page: Awesome wiki_page') } - - let(:data) { Gitlab::DataBuilder::WikiPage.build(wiki_page, user, 'create') } - - before do - # Skip this method that is not relevant to this test to prevent having - # to update project which is frozen - allow(project.wiki).to receive(:after_wiki_activity) - end - - it_behaves_like 'increases the usage data counter', 'i_ecosystem_slack_service_wiki_page_notification' - end - - context 'merge_request notification' do - let_it_be(:merge_request) { create(:merge_request) } - - let(:data) { merge_request.to_hook_data(user) } - - it_behaves_like 'increases the usage data counter', 'i_ecosystem_slack_service_merge_request_notification' - end - - context 'note notification' do - let_it_be(:issue_note) { create(:note_on_issue, note: 'issue note') } - - let(:data) { Gitlab::DataBuilder::Note.build(issue_note, user) } - - it_behaves_like 'increases the usage data counter', 'i_ecosystem_slack_service_note_notification' - end - - context 'tag_push notification' do - let(:oldrev) { Gitlab::Git::BLANK_SHA } - let(:newrev) { '8a2a6eb295bb170b34c24c76c49ed0e9b2eaf34b' } # gitlab-test: git rev-parse refs/tags/v1.1.0 - let(:ref) { 'refs/tags/v1.1.0' } - let(:data) { Git::TagHooksService.new(project, user, change: { oldrev: oldrev, newrev: newrev, ref: ref }).send(:push_data) } - - it_behaves_like 'increases the usage data counter', 'i_ecosystem_slack_service_tag_push_notification' - end - - context 'confidential note notification' do - let_it_be(:confidential_issue_note) { create(:note_on_issue, note: 'issue note', confidential: true) } - - let(:data) { Gitlab::DataBuilder::Note.build(confidential_issue_note, user) } - - it_behaves_like 'increases the usage data counter', 'i_ecosystem_slack_service_confidential_note_notification' - end - - context 'confidential issue notification' do - let_it_be(:issue) { create(:issue, confidential: true) } - - let(:data) { issue.to_hook_data(user) } - - it_behaves_like 'increases the usage data counter', 'i_ecosystem_slack_service_confidential_issue_notification' - end - end - - context 'hook data does not include a user' do - let(:data) { Gitlab::DataBuilder::Pipeline.build(create(:ci_pipeline)) } - - it 'does not increase the usage data counter' do - expect(Gitlab::UsageDataCounters::HLLRedisCounter).not_to receive(:track_event) - - slack_service.execute(data) - end - end - end -end diff --git a/spec/models/project_services/slack_slash_commands_service_spec.rb b/spec/models/project_services/slack_slash_commands_service_spec.rb deleted file mode 100644 index 95c87ef01bc..00000000000 --- a/spec/models/project_services/slack_slash_commands_service_spec.rb +++ /dev/null @@ -1,50 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe SlackSlashCommandsService do - it_behaves_like "chat slash commands service" - - describe '#trigger' do - context 'when an auth url is generated' do - let(:project) { create(:project) } - let(:params) do - { - team_domain: 'http://domain.tld', - team_id: 'T3423423', - user_id: 'U234234', - user_name: 'mepmep', - token: 'token' - } - end - - let(:service) do - project.create_slack_slash_commands_service( - properties: { token: 'token' }, - active: true - ) - end - - let(:authorize_url) do - 'http://authorize.example.com/' - end - - before do - allow(service).to receive(:authorize_chat_name_url).and_return(authorize_url) - end - - it 'uses slack compatible links' do - response = service.trigger(params) - - expect(response[:text]).to include("<#{authorize_url}|connect your GitLab account>") - end - end - end - - describe '#chat_responder' do - it 'returns the responder to use for Slack' do - expect(described_class.new.chat_responder) - .to eq(Gitlab::Chat::Responder::Slack) - end - end -end diff --git a/spec/models/project_services/teamcity_service_spec.rb b/spec/models/project_services/teamcity_service_spec.rb deleted file mode 100644 index f71dad86a08..00000000000 --- a/spec/models/project_services/teamcity_service_spec.rb +++ /dev/null @@ -1,334 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe TeamcityService, :use_clean_rails_memory_store_caching do - include ReactiveCachingHelpers - include StubRequests - - let(:teamcity_url) { 'http://gitlab.com/teamcity' } - let(:teamcity_full_url) { 'http://gitlab.com/teamcity/httpAuth/app/rest/builds/branch:unspecified:any,revision:123' } - let(:project) { create(:project) } - - subject(:service) do - described_class.create!( - project: project, - properties: { - teamcity_url: teamcity_url, - username: 'mic', - password: 'password', - build_type: 'foo' - } - ) - 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 - 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 - - describe '#username' do - it 'does not validate the presence of username if password is nil' do - subject.password = nil - - expect(subject).not_to validate_presence_of(:username) - end - - it 'validates the presence of username if password is present' do - subject.password = 'secret' - - expect(subject).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 - - expect(subject).not_to validate_presence_of(:password) - end - - it 'validates the presence of password if username is present' do - subject.username = 'john' - - expect(subject).to validate_presence_of(:password) - end - end - end - - context 'when service is inactive' do - before do - subject.active = false - end - - it { is_expected.not_to validate_presence_of(:build_type) } - it { is_expected.not_to validate_presence_of(:teamcity_url) } - it { is_expected.not_to validate_presence_of(:username) } - it { is_expected.not_to validate_presence_of(:password) } - end - end - - describe 'Callbacks' do - 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_service.teamcity_url = 'http://gitlab1.com' - teamcity_service.save! - - expect(teamcity_service.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! - - expect(teamcity_service.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! - - expect(teamcity_service.password).to eq('password') - expect(teamcity_service.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_service.teamcity_url = 'http://gitlab_edited.com' - teamcity_service.password = 'password' - teamcity_service.save! - - expect(teamcity_service.password).to eq('password') - expect(teamcity_service.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') - - expect(service.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') - - expect(service.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] } - - it 'returns a specific URL when status is 500' do - stub_request(status: 500) - - is_expected.to eq('http://gitlab.com/teamcity/viewLog.html?buildTypeId=foo') - end - - it 'returns a build URL when teamcity_url has no trailing slash' do - stub_request(body: %q({"build":{"id":"666"}})) - - is_expected.to eq('http://gitlab.com/teamcity/viewLog.html?buildId=666&buildTypeId=foo') - end - - context 'teamcity_url has trailing slash' do - let(:teamcity_url) { 'http://gitlab.com/teamcity/' } - - it 'returns a build URL' do - stub_request(body: %q({"build":{"id":"666"}})) - - is_expected.to eq('http://gitlab.com/teamcity/viewLog.html?buildId=666&buildTypeId=foo') - end - end - - it 'returns the teamcity_url when teamcity is unreachable' do - stub_full_request(teamcity_full_url).to_raise(Errno::ECONNREFUSED) - - expect(Gitlab::ErrorTracking) - .to receive(:log_exception) - .with(instance_of(Errno::ECONNREFUSED), project_id: project.id) - - is_expected.to eq(teamcity_url) - end - end - - context 'commit_status' do - subject { service.calculate_reactive_cache('123', 'unused')[:commit_status] } - - it 'sets commit status to :error when status is 500' do - stub_request(status: 500) - - is_expected.to eq(:error) - end - - it 'sets commit status to "pending" when status is 404' do - stub_request(status: 404) - - is_expected.to eq('pending') - end - - it 'sets commit status to "success" when build status contains SUCCESS' do - stub_request(build_status: 'YAY SUCCESS!') - - is_expected.to eq('success') - end - - it 'sets commit status to "failed" when build status contains FAILURE' do - stub_request(build_status: 'NO FAILURE!') - - is_expected.to eq('failed') - end - - it 'sets commit status to "pending" when build status contains Pending' do - stub_request(build_status: 'NO Pending!') - - is_expected.to eq('pending') - end - - it 'sets commit status to :error when build status is unknown' do - stub_request(build_status: 'FOO BAR!') - - is_expected.to eq(:error) - end - - it 'sets commit status to :error when teamcity is unreachable' do - stub_full_request(teamcity_full_url).to_raise(Errno::ECONNREFUSED) - - expect(Gitlab::ErrorTracking) - .to receive(:log_exception) - .with(instance_of(Errno::ECONNREFUSED), project_id: project.id) - - is_expected.to eq(:error) - end - end - end - - describe '#execute' do - context 'when push' do - let(:data) do - { - object_kind: 'push', - ref: 'refs/heads/dev-123_branch', - after: '0220c11b9a3e6c69dc8fd35321254ca9a7b98f7e', - total_commits_count: 1 - } - end - - it 'handles push request correctly' do - stub_post_to_build_queue(branch: 'dev-123_branch') - - expect(service.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 - end - - it 'returns nil when there is no content' do - data[:total_commits_count] = 0 - - expect(service.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 - end - end - - context 'when merge_request' do - let(:data) do - { - object_kind: 'merge_request', - ref: 'refs/heads/dev-123_branch', - after: '0220c11b9a3e6c69dc8fd35321254ca9a7b98f7e', - total_commits_count: 1, - object_attributes: { - state: 'opened', - source_branch: 'dev-123_branch', - merge_status: 'unchecked' - } - } - end - - it 'handles merge request correctly' do - stub_post_to_build_queue(branch: 'dev-123_branch') - - expect(service.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 - 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 - end - end - - it 'returns nil when event is not supported' do - data = { object_kind: 'foo' } - - expect(service.execute(data)).to be_nil - end - end - - def stub_post_to_build_queue(branch:) - teamcity_full_url = 'http://gitlab.com/teamcity/httpAuth/app/rest/buildQueue' - body ||= %Q(<build branchName=\"#{branch}\"><buildType id=\"foo\"/></build>) - auth = %w(mic password) - - stub_full_request(teamcity_full_url, method: :post).with( - basic_auth: auth, - body: body, - headers: { - 'Content-Type' => 'application/xml' - } - ).to_return(status: 200, body: 'Ok', headers: {}) - end - - def stub_request(status: 200, body: nil, build_status: 'success') - auth = %w(mic password) - - body ||= %Q({"build":{"status":"#{build_status}","id":"666"}}) - - stub_full_request(teamcity_full_url).with(basic_auth: auth).to_return( - status: status, - headers: { 'Content-Type' => 'application/json' }, - body: body - ) - end -end diff --git a/spec/models/project_services/unify_circuit_service_spec.rb b/spec/models/project_services/unify_circuit_service_spec.rb deleted file mode 100644 index 0c749322e07..00000000000 --- a/spec/models/project_services/unify_circuit_service_spec.rb +++ /dev/null @@ -1,16 +0,0 @@ -# frozen_string_literal: true - -require "spec_helper" - -RSpec.describe UnifyCircuitService do - it_behaves_like "chat service", "Unify Circuit" do - let(:client_arguments) { webhook_url } - let(:payload) do - { - subject: project.full_name, - text: be_present, - markdown: true - } - end - end -end diff --git a/spec/models/project_services/webex_teams_service_spec.rb b/spec/models/project_services/webex_teams_service_spec.rb deleted file mode 100644 index ed63f5bc48c..00000000000 --- a/spec/models/project_services/webex_teams_service_spec.rb +++ /dev/null @@ -1,14 +0,0 @@ -# frozen_string_literal: true - -require "spec_helper" - -RSpec.describe WebexTeamsService do - it_behaves_like "chat service", "Webex Teams" do - let(:client_arguments) { webhook_url } - let(:payload) do - { - markdown: be_present - } - end - end -end diff --git a/spec/models/project_services/youtrack_service_spec.rb b/spec/models/project_services/youtrack_service_spec.rb deleted file mode 100644 index 4339b44e1de..00000000000 --- a/spec/models/project_services/youtrack_service_spec.rb +++ /dev/null @@ -1,45 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe YoutrackService 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 - before do - subject.active = true - end - - 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 - end - - context 'when service is inactive' do - before do - subject.active = false - end - - it { is_expected.not_to validate_presence_of(:project_url) } - it { is_expected.not_to validate_presence_of(:issues_url) } - end - end - - describe '.reference_pattern' do - it_behaves_like 'allows project key on reference pattern' - - it 'does allow project prefix on the reference' do - expect(described_class.reference_pattern.match('YT-123')[:issue]).to eq('YT-123') - end - - it 'allows lowercase project key on the reference' do - expect(described_class.reference_pattern.match('yt-123')[:issue]).to eq('yt-123') - end - end -end |