From 1f5ca81aa6e674089c9652484e5f3bb89f86703c Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Mon, 24 May 2021 12:10:31 +0000 Subject: Add latest changes from gitlab-org/gitlab@master --- .rubocop_manual_todo.yml | 6 - app/models/integrations/external_wiki.rb | 52 ++++ app/models/integrations/flowdock.rb | 52 ++++ app/models/integrations/irker.rb | 123 +++++++++ app/models/integrations/packagist.rb | 67 +++++ app/models/integrations/pipelines_email.rb | 105 +++++++ app/models/integrations/pivotaltracker.rb | 78 ++++++ app/models/project.rb | 12 +- .../project_services/external_wiki_service.rb | 50 ---- app/models/project_services/flowdock_service.rb | 50 ---- app/models/project_services/irker_service.rb | 121 -------- app/models/project_services/jira_tracker_data.rb | 12 - app/models/project_services/packagist_service.rb | 65 ----- .../project_services/pipelines_email_service.rb | 103 ------- .../project_services/pivotaltracker_service.rb | 76 ----- .../development/ci_needs_optional.yml | 8 - ...e_data_unique_users_committing_ciconfigfile.yml | 8 - .../20210216175026_service_desk_issues.yml | 1 - doc/ci/yaml/README.md | 28 +- doc/development/snowplow/index.md | 14 + lib/api/helpers/services_helpers.rb | 12 +- lib/flowdock/git.rb | 2 +- lib/gitlab/ci/config/entry/need.rb | 22 +- lib/gitlab/ci/pipeline/seed/build.rb | 2 +- lib/gitlab/integrations/sti_type.rb | 3 +- .../usage_data_counters/known_events/common.yml | 1 - spec/factories/integrations.rb | 6 +- .../integrations/user_activates_flowdock_spec.rb | 22 ++ .../user_activates_pivotaltracker_spec.rb | 20 ++ .../services/user_activates_flowdock_spec.rb | 22 -- .../services/user_activates_pivotaltracker_spec.rb | 20 -- spec/lib/gitlab/ci/config/entry/need_spec.rb | 20 -- spec/lib/gitlab/ci/pipeline/seed/build_spec.rb | 11 - spec/models/integration_spec.rb | 2 +- spec/models/integrations/external_wiki_spec.rb | 59 ++++ spec/models/integrations/flowdock_spec.rb | 58 ++++ spec/models/integrations/irker_spec.rb | 76 +++++ spec/models/integrations/packagist_spec.rb | 48 ++++ spec/models/integrations/pipelines_email_spec.rb | 305 +++++++++++++++++++++ spec/models/integrations/pivotaltracker_spec.rb | 101 +++++++ .../project_services/external_wiki_service_spec.rb | 59 ---- .../project_services/flowdock_service_spec.rb | 58 ---- spec/models/project_services/irker_service_spec.rb | 76 ----- .../project_services/packagist_service_spec.rb | 48 ---- .../pipelines_email_service_spec.rb | 305 --------------------- .../pivotaltracker_service_spec.rb | 101 ------- spec/models/project_spec.rb | 2 +- 47 files changed, 1208 insertions(+), 1284 deletions(-) create mode 100644 app/models/integrations/external_wiki.rb create mode 100644 app/models/integrations/flowdock.rb create mode 100644 app/models/integrations/irker.rb create mode 100644 app/models/integrations/packagist.rb create mode 100644 app/models/integrations/pipelines_email.rb create mode 100644 app/models/integrations/pivotaltracker.rb delete mode 100644 app/models/project_services/external_wiki_service.rb delete mode 100644 app/models/project_services/flowdock_service.rb delete mode 100644 app/models/project_services/irker_service.rb delete mode 100644 app/models/project_services/packagist_service.rb delete mode 100644 app/models/project_services/pipelines_email_service.rb delete mode 100644 app/models/project_services/pivotaltracker_service.rb delete mode 100644 config/feature_flags/development/ci_needs_optional.yml delete mode 100644 config/feature_flags/development/usage_data_unique_users_committing_ciconfigfile.yml create mode 100644 spec/features/projects/integrations/user_activates_flowdock_spec.rb create mode 100644 spec/features/projects/integrations/user_activates_pivotaltracker_spec.rb delete mode 100644 spec/features/projects/services/user_activates_flowdock_spec.rb delete mode 100644 spec/features/projects/services/user_activates_pivotaltracker_spec.rb create mode 100644 spec/models/integrations/external_wiki_spec.rb create mode 100644 spec/models/integrations/flowdock_spec.rb create mode 100644 spec/models/integrations/irker_spec.rb create mode 100644 spec/models/integrations/packagist_spec.rb create mode 100644 spec/models/integrations/pipelines_email_spec.rb create mode 100644 spec/models/integrations/pivotaltracker_spec.rb delete mode 100644 spec/models/project_services/external_wiki_service_spec.rb delete mode 100644 spec/models/project_services/flowdock_service_spec.rb delete mode 100644 spec/models/project_services/irker_service_spec.rb delete mode 100644 spec/models/project_services/packagist_service_spec.rb delete mode 100644 spec/models/project_services/pipelines_email_service_spec.rb delete mode 100644 spec/models/project_services/pivotaltracker_service_spec.rb diff --git a/.rubocop_manual_todo.yml b/.rubocop_manual_todo.yml index 9bdf1aa0082..9a6e8451c97 100644 --- a/.rubocop_manual_todo.yml +++ b/.rubocop_manual_todo.yml @@ -1654,11 +1654,8 @@ Gitlab/NamespacedClass: - 'app/models/project_services/ci_service.rb' - 'app/models/project_services/discord_service.rb' - 'app/models/project_services/drone_ci_service.rb' - - 'app/models/project_services/external_wiki_service.rb' - - 'app/models/project_services/flowdock_service.rb' - 'app/models/project_services/hangouts_chat_service.rb' - 'app/models/project_services/hipchat_service.rb' - - 'app/models/project_services/irker_service.rb' - 'app/models/project_services/issue_tracker_data.rb' - 'app/models/project_services/jenkins_service.rb' - 'app/models/project_services/jira_tracker_data.rb' @@ -1669,9 +1666,6 @@ Gitlab/NamespacedClass: - 'app/models/project_services/mock_monitoring_service.rb' - 'app/models/project_services/monitoring_service.rb' - 'app/models/project_services/open_project_tracker_data.rb' - - 'app/models/project_services/packagist_service.rb' - - 'app/models/project_services/pipelines_email_service.rb' - - 'app/models/project_services/pivotaltracker_service.rb' - 'app/models/project_services/prometheus_service.rb' - 'app/models/project_services/pushover_service.rb' - 'app/models/project_services/slack_service.rb' diff --git a/app/models/integrations/external_wiki.rb b/app/models/integrations/external_wiki.rb new file mode 100644 index 00000000000..fec435443fa --- /dev/null +++ b/app/models/integrations/external_wiki.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +module Integrations + class ExternalWiki < Integration + include ActionView::Helpers::UrlHelper + + prop_accessor :external_wiki_url + validates :external_wiki_url, presence: true, public_url: true, if: :activated? + + def title + s_('ExternalWikiService|External wiki') + end + + def description + s_('ExternalWikiService|Link to an external wiki from the sidebar.') + end + + def self.to_param + 'external_wiki' + end + + def fields + [ + { + type: 'text', + name: 'external_wiki_url', + title: s_('ExternalWikiService|External wiki URL'), + placeholder: s_('ExternalWikiService|https://example.com/xxx/wiki/...'), + help: 'Enter the URL to the external wiki.', + required: true + } + ] + end + + def help + docs_link = link_to _('Learn more.'), Rails.application.routes.url_helpers.help_page_url('user/project/wiki/index', anchor: 'link-an-external-wiki'), target: '_blank', rel: 'noopener noreferrer' + + s_('Link an external wiki from the project\'s sidebar. %{docs_link}').html_safe % { docs_link: docs_link.html_safe } + end + + def execute(_data) + response = Gitlab::HTTP.get(properties['external_wiki_url'], verify: true) + response.body if response.code == 200 + rescue StandardError + nil + end + + def self.supported_events + %w() + end + end +end diff --git a/app/models/integrations/flowdock.rb b/app/models/integrations/flowdock.rb new file mode 100644 index 00000000000..443f61e65dd --- /dev/null +++ b/app/models/integrations/flowdock.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +module Integrations + class Flowdock < Integration + include ActionView::Helpers::UrlHelper + + prop_accessor :token + validates :token, presence: true, if: :activated? + + def title + 'Flowdock' + end + + def description + s_('FlowdockService|Send event notifications from GitLab to Flowdock flows.') + end + + def help + docs_link = link_to _('Learn more.'), Rails.application.routes.url_helpers.help_page_url('api/services', anchor: 'flowdock'), target: '_blank', rel: 'noopener noreferrer' + s_('FlowdockService|Send event notifications from GitLab to Flowdock flows. %{docs_link}').html_safe % { docs_link: docs_link.html_safe } + end + + def self.to_param + 'flowdock' + end + + def fields + [ + { type: 'text', name: 'token', placeholder: s_('FlowdockService|1b609b52537...'), required: true, help: 'Enter your Flowdock token.' } + ] + end + + def self.supported_events + %w(push) + end + + def execute(data) + return unless supported_events.include?(data[:object_kind]) + + ::Flowdock::Git.post( + data[:ref], + data[:before], + data[:after], + token: token, + repo: project.repository, + repo_url: "#{Gitlab.config.gitlab.url}/#{project.full_path}", + commit_url: "#{Gitlab.config.gitlab.url}/#{project.full_path}/-/commit/%s", + diff_url: "#{Gitlab.config.gitlab.url}/#{project.full_path}/compare/%s...%s" + ) + end + end +end diff --git a/app/models/integrations/irker.rb b/app/models/integrations/irker.rb new file mode 100644 index 00000000000..7048dd641ea --- /dev/null +++ b/app/models/integrations/irker.rb @@ -0,0 +1,123 @@ +# frozen_string_literal: true + +require 'uri' + +module Integrations + class Irker < Integration + prop_accessor :server_host, :server_port, :default_irc_uri + prop_accessor :recipients, :channels + boolean_accessor :colorize_messages + validates :recipients, presence: true, if: :validate_recipients? + + before_validation :get_channels + + def title + 'Irker (IRC gateway)' + end + + def description + 'Send IRC messages.' + end + + def self.to_param + 'irker' + end + + def self.supported_events + %w(push) + end + + def execute(data) + return unless supported_events.include?(data[:object_kind]) + + IrkerWorker.perform_async(project_id, channels, + colorize_messages, data, settings) + end + + def settings + { + server_host: server_host.presence || 'localhost', + server_port: server_port.presence || 6659 + } + end + + def fields + [ + { type: 'text', name: 'server_host', placeholder: 'localhost', + help: 'Irker daemon hostname (defaults to localhost)' }, + { type: 'text', name: 'server_port', placeholder: 6659, + help: 'Irker daemon port (defaults to 6659)' }, + { type: 'text', name: 'default_irc_uri', title: 'Default IRC URI', + help: 'A default IRC URI to prepend before each recipient (optional)', + placeholder: 'irc://irc.network.net:6697/' }, + { type: 'textarea', name: 'recipients', + placeholder: 'Recipients/channels separated by whitespaces', required: true, + help: 'Recipients have to be specified with a full URI: '\ + 'irc[s]://irc.network.net[:port]/#channel. Special cases: if '\ + 'you want the channel to be a nickname instead, append ",isnick" to ' \ + 'the channel name; if the channel is protected by a secret password, ' \ + ' append "?key=secretpassword" to the URI (Note that due to a bug, if you ' \ + ' want to use a password, you have to omit the "#" on the channel). If you ' \ + ' specify a default IRC URI to prepend before each recipient, you can just ' \ + ' give a channel name.' }, + { type: 'checkbox', name: 'colorize_messages' } + ] + end + + def help + ' NOTE: Irker does NOT have built-in authentication, which makes it' \ + ' vulnerable to spamming IRC channels if it is hosted outside of a ' \ + ' firewall. Please make sure you run the daemon within a secured network ' \ + ' to prevent abuse. For more details, read: http://www.catb.org/~esr/irker/security.html.' + end + + private + + def get_channels + return true unless activated? + return true if recipients.nil? || recipients.empty? + + map_recipients + + errors.add(:recipients, 'are all invalid') if channels.empty? + true + end + + def map_recipients + self.channels = recipients.split(/\s+/).map do |recipient| + format_channel(recipient) + end + channels.reject!(&:nil?) + end + + def format_channel(recipient) + uri = nil + + # Try to parse the chan as a full URI + begin + uri = consider_uri(URI.parse(recipient)) + rescue URI::InvalidURIError + end + + unless uri.present? && default_irc_uri.nil? + begin + new_recipient = URI.join(default_irc_uri, '/', recipient).to_s + uri = consider_uri(URI.parse(new_recipient)) + rescue StandardError + log_error("Unable to create a valid URL", default_irc_uri: default_irc_uri, recipient: recipient) + end + end + + uri + end + + def consider_uri(uri) + return if uri.scheme.nil? + + # Authorize both irc://domain.com/#chan and irc://domain.com/chan + if uri.is_a?(URI) && uri.scheme[/^ircs?\z/] && !uri.path.nil? + uri.to_s + end + end + end +end diff --git a/app/models/integrations/packagist.rb b/app/models/integrations/packagist.rb new file mode 100644 index 00000000000..b597bd11175 --- /dev/null +++ b/app/models/integrations/packagist.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +module Integrations + class Packagist < Integration + prop_accessor :username, :token, :server + + validates :username, presence: true, if: :activated? + validates :token, presence: true, if: :activated? + + default_value_for :push_events, true + default_value_for :tag_push_events, true + + after_save :compose_service_hook, if: :activated? + + def title + 'Packagist' + end + + def description + s_('Integrations|Update your Packagist projects.') + end + + def self.to_param + 'packagist' + end + + def fields + [ + { type: 'text', name: 'username', placeholder: '', required: true }, + { type: 'text', name: 'token', placeholder: '', required: true }, + { type: 'text', name: 'server', placeholder: 'https://packagist.org', required: false } + ] + end + + def self.supported_events + %w(push merge_request tag_push) + end + + def execute(data) + return unless supported_events.include?(data[:object_kind]) + + service_hook.execute(data) + end + + def test(data) + begin + result = execute(data) + return { success: false, result: result[:message] } if result[:http_status] != 202 + rescue StandardError => error + return { success: false, result: error } + end + + { success: true, result: result[:message] } + end + + def compose_service_hook + hook = service_hook || build_service_hook + hook.url = hook_url + hook.save + end + + def hook_url + base_url = server.presence || 'https://packagist.org' + "#{base_url}/api/update-package?username=#{username}&apiToken=#{token}" + end + end +end diff --git a/app/models/integrations/pipelines_email.rb b/app/models/integrations/pipelines_email.rb new file mode 100644 index 00000000000..585bc14242a --- /dev/null +++ b/app/models/integrations/pipelines_email.rb @@ -0,0 +1,105 @@ +# frozen_string_literal: true + +module Integrations + class PipelinesEmail < Integration + include NotificationBranchSelection + + prop_accessor :recipients, :branches_to_be_notified + boolean_accessor :notify_only_broken_pipelines, :notify_only_default_branch + validates :recipients, presence: true, if: :validate_recipients? + + def initialize_properties + if properties.nil? + self.properties = {} + self.notify_only_broken_pipelines = true + self.branches_to_be_notified = "default" + elsif !self.notify_only_default_branch.nil? + # In older versions, there was only a boolean property named + # `notify_only_default_branch`. Now we have a string property named + # `branches_to_be_notified`. Instead of doing a background migration, we + # opted to set a value for the new property based on the old one, if + # users hasn't specified one already. When users edit the service and + # selects a value for this new property, it will override everything. + + self.branches_to_be_notified ||= notify_only_default_branch? ? "default" : "all" + end + end + + def title + _('Pipeline status emails') + end + + def description + _('Email the pipeline status to a list of recipients.') + end + + def self.to_param + 'pipelines_email' + end + + def self.supported_events + %w[pipeline] + end + + def self.default_test_event + 'pipeline' + end + + def execute(data, force: false) + return unless supported_events.include?(data[:object_kind]) + return unless force || should_pipeline_be_notified?(data) + + all_recipients = retrieve_recipients(data) + + return unless all_recipients.any? + + pipeline_id = data[:object_attributes][:id] + PipelineNotificationWorker.new.perform(pipeline_id, recipients: all_recipients) + end + + def can_test? + project&.ci_pipelines&.any? + end + + def fields + [ + { type: 'textarea', + name: 'recipients', + help: _('Comma-separated list of email addresses.'), + required: true }, + { type: 'checkbox', + name: 'notify_only_broken_pipelines' }, + { type: 'select', + name: 'branches_to_be_notified', + choices: branch_choices } + ] + end + + def test(data) + result = execute(data, force: true) + + { success: true, result: result } + rescue StandardError => error + { success: false, result: error } + end + + def should_pipeline_be_notified?(data) + notify_for_branch?(data) && notify_for_pipeline?(data) + end + + def notify_for_pipeline?(data) + case data[:object_attributes][:status] + when 'success' + !notify_only_broken_pipelines? + when 'failed' + true + else + false + end + end + + def retrieve_recipients(data) + recipients.to_s.split(/[,\r\n ]+/).reject(&:empty?) + end + end +end diff --git a/app/models/integrations/pivotaltracker.rb b/app/models/integrations/pivotaltracker.rb new file mode 100644 index 00000000000..46f97cc3c6b --- /dev/null +++ b/app/models/integrations/pivotaltracker.rb @@ -0,0 +1,78 @@ +# frozen_string_literal: true + +module Integrations + class Pivotaltracker < Integration + API_ENDPOINT = 'https://www.pivotaltracker.com/services/v5/source_commits' + + prop_accessor :token, :restrict_to_branch + validates :token, presence: true, if: :activated? + + def title + 'PivotalTracker' + end + + def description + s_('PivotalTrackerService|Add commit messages as comments to PivotalTracker stories.') + end + + def self.to_param + 'pivotaltracker' + end + + def fields + [ + { + type: 'text', + name: 'token', + placeholder: s_('PivotalTrackerService|Pivotal Tracker API token.'), + required: true + }, + { + type: 'text', + name: 'restrict_to_branch', + placeholder: s_('PivotalTrackerService|Comma-separated list of branches which will be ' \ + 'automatically inspected. Leave blank to include all branches.') + } + ] + end + + def self.supported_events + %w(push) + end + + def execute(data) + return unless supported_events.include?(data[:object_kind]) + return unless allowed_branch?(data[:ref]) + + data[:commits].each do |commit| + message = { + 'source_commit' => { + 'commit_id' => commit[:id], + 'author' => commit[:author][:name], + 'url' => commit[:url], + 'message' => commit[:message] + } + } + Gitlab::HTTP.post( + API_ENDPOINT, + body: message.to_json, + headers: { + 'Content-Type' => 'application/json', + 'X-TrackerToken' => token + } + ) + end + end + + private + + def allowed_branch?(ref) + return true unless ref.present? && restrict_to_branch.present? + + branch = Gitlab::Git.ref_name(ref) + allowed_branches = restrict_to_branch.split(',').map(&:strip) + + branch.present? && allowed_branches.include?(branch) + end + end +end diff --git a/app/models/project.rb b/app/models/project.rb index 26205244efe..31b527f405d 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -193,15 +193,17 @@ class Project < ApplicationRecord has_one :datadog_service, class_name: 'Integrations::Datadog' has_one :emails_on_push_service, class_name: 'Integrations::EmailsOnPush' has_one :ewm_service, class_name: 'Integrations::Ewm' + has_one :external_wiki_service, class_name: 'Integrations::ExternalWiki' + has_one :flowdock_service, class_name: 'Integrations::Flowdock' + has_one :irker_service, class_name: 'Integrations::Irker' has_one :jira_service, class_name: 'Integrations::Jira' + has_one :packagist_service, class_name: 'Integrations::Packagist' + has_one :pipelines_email_service, class_name: 'Integrations::PipelinesEmail' + has_one :pivotaltracker_service, class_name: 'Integrations::Pivotaltracker' has_one :redmine_service, class_name: 'Integrations::Redmine' has_one :youtrack_service, class_name: 'Integrations::Youtrack' has_one :discord_service has_one :drone_ci_service - has_one :pipelines_email_service - has_one :irker_service - has_one :pivotaltracker_service - has_one :flowdock_service has_one :mattermost_slash_commands_service has_one :mattermost_service has_one :slack_slash_commands_service @@ -210,12 +212,10 @@ class Project < ApplicationRecord has_one :teamcity_service has_one :pushover_service has_one :jenkins_service - has_one :external_wiki_service has_one :prometheus_service, inverse_of: :project has_one :mock_ci_service has_one :mock_monitoring_service has_one :microsoft_teams_service - has_one :packagist_service has_one :hangouts_chat_service has_one :unify_circuit_service has_one :webex_teams_service diff --git a/app/models/project_services/external_wiki_service.rb b/app/models/project_services/external_wiki_service.rb deleted file mode 100644 index f49b008533d..00000000000 --- a/app/models/project_services/external_wiki_service.rb +++ /dev/null @@ -1,50 +0,0 @@ -# frozen_string_literal: true - -class ExternalWikiService < Integration - include ActionView::Helpers::UrlHelper - - prop_accessor :external_wiki_url - validates :external_wiki_url, presence: true, public_url: true, if: :activated? - - def title - s_('ExternalWikiService|External wiki') - end - - def description - s_('ExternalWikiService|Link to an external wiki from the sidebar.') - end - - def self.to_param - 'external_wiki' - end - - def fields - [ - { - type: 'text', - name: 'external_wiki_url', - title: s_('ExternalWikiService|External wiki URL'), - placeholder: s_('ExternalWikiService|https://example.com/xxx/wiki/...'), - help: 'Enter the URL to the external wiki.', - required: true - } - ] - end - - def help - docs_link = link_to _('Learn more.'), Rails.application.routes.url_helpers.help_page_url('user/project/wiki/index', anchor: 'link-an-external-wiki'), target: '_blank', rel: 'noopener noreferrer' - - s_('Link an external wiki from the project\'s sidebar. %{docs_link}').html_safe % { docs_link: docs_link.html_safe } - end - - def execute(_data) - response = Gitlab::HTTP.get(properties['external_wiki_url'], verify: true) - response.body if response.code == 200 - rescue StandardError - nil - end - - def self.supported_events - %w() - end -end diff --git a/app/models/project_services/flowdock_service.rb b/app/models/project_services/flowdock_service.rb deleted file mode 100644 index 7aae5af7454..00000000000 --- a/app/models/project_services/flowdock_service.rb +++ /dev/null @@ -1,50 +0,0 @@ -# frozen_string_literal: true - -class FlowdockService < Integration - include ActionView::Helpers::UrlHelper - - prop_accessor :token - validates :token, presence: true, if: :activated? - - def title - 'Flowdock' - end - - def description - s_('FlowdockService|Send event notifications from GitLab to Flowdock flows.') - end - - def help - docs_link = link_to _('Learn more.'), Rails.application.routes.url_helpers.help_page_url('api/services', anchor: 'flowdock'), target: '_blank', rel: 'noopener noreferrer' - s_('FlowdockService|Send event notifications from GitLab to Flowdock flows. %{docs_link}').html_safe % { docs_link: docs_link.html_safe } - end - - def self.to_param - 'flowdock' - end - - def fields - [ - { type: 'text', name: 'token', placeholder: s_('FlowdockService|1b609b52537...'), required: true, help: 'Enter your Flowdock token.' } - ] - end - - def self.supported_events - %w(push) - end - - def execute(data) - return unless supported_events.include?(data[:object_kind]) - - Flowdock::Git.post( - data[:ref], - data[:before], - data[:after], - token: token, - repo: project.repository, - repo_url: "#{Gitlab.config.gitlab.url}/#{project.full_path}", - commit_url: "#{Gitlab.config.gitlab.url}/#{project.full_path}/-/commit/%s", - diff_url: "#{Gitlab.config.gitlab.url}/#{project.full_path}/compare/%s...%s" - ) - end -end diff --git a/app/models/project_services/irker_service.rb b/app/models/project_services/irker_service.rb deleted file mode 100644 index 5cca620c659..00000000000 --- a/app/models/project_services/irker_service.rb +++ /dev/null @@ -1,121 +0,0 @@ -# frozen_string_literal: true - -require 'uri' - -class IrkerService < Integration - prop_accessor :server_host, :server_port, :default_irc_uri - prop_accessor :recipients, :channels - boolean_accessor :colorize_messages - validates :recipients, presence: true, if: :validate_recipients? - - before_validation :get_channels - - def title - 'Irker (IRC gateway)' - end - - def description - 'Send IRC messages.' - end - - def self.to_param - 'irker' - end - - def self.supported_events - %w(push) - end - - def execute(data) - return unless supported_events.include?(data[:object_kind]) - - IrkerWorker.perform_async(project_id, channels, - colorize_messages, data, settings) - end - - def settings - { - server_host: server_host.presence || 'localhost', - server_port: server_port.presence || 6659 - } - end - - def fields - [ - { type: 'text', name: 'server_host', placeholder: 'localhost', - help: 'Irker daemon hostname (defaults to localhost)' }, - { type: 'text', name: 'server_port', placeholder: 6659, - help: 'Irker daemon port (defaults to 6659)' }, - { type: 'text', name: 'default_irc_uri', title: 'Default IRC URI', - help: 'A default IRC URI to prepend before each recipient (optional)', - placeholder: 'irc://irc.network.net:6697/' }, - { type: 'textarea', name: 'recipients', - placeholder: 'Recipients/channels separated by whitespaces', required: true, - help: 'Recipients have to be specified with a full URI: '\ - 'irc[s]://irc.network.net[:port]/#channel. Special cases: if '\ - 'you want the channel to be a nickname instead, append ",isnick" to ' \ - 'the channel name; if the channel is protected by a secret password, ' \ - ' append "?key=secretpassword" to the URI (Note that due to a bug, if you ' \ - ' want to use a password, you have to omit the "#" on the channel). If you ' \ - ' specify a default IRC URI to prepend before each recipient, you can just ' \ - ' give a channel name.' }, - { type: 'checkbox', name: 'colorize_messages' } - ] - end - - def help - ' NOTE: Irker does NOT have built-in authentication, which makes it' \ - ' vulnerable to spamming IRC channels if it is hosted outside of a ' \ - ' firewall. Please make sure you run the daemon within a secured network ' \ - ' to prevent abuse. For more details, read: http://www.catb.org/~esr/irker/security.html.' - end - - private - - def get_channels - return true unless activated? - return true if recipients.nil? || recipients.empty? - - map_recipients - - errors.add(:recipients, 'are all invalid') if channels.empty? - true - end - - def map_recipients - self.channels = recipients.split(/\s+/).map do |recipient| - format_channel(recipient) - end - channels.reject!(&:nil?) - end - - def format_channel(recipient) - uri = nil - - # Try to parse the chan as a full URI - begin - uri = consider_uri(URI.parse(recipient)) - rescue URI::InvalidURIError - end - - unless uri.present? && default_irc_uri.nil? - begin - new_recipient = URI.join(default_irc_uri, '/', recipient).to_s - uri = consider_uri(URI.parse(new_recipient)) - rescue StandardError - log_error("Unable to create a valid URL", default_irc_uri: default_irc_uri, recipient: recipient) - end - end - - uri - end - - def consider_uri(uri) - return if uri.scheme.nil? - - # Authorize both irc://domain.com/#chan and irc://domain.com/chan - if uri.is_a?(URI) && uri.scheme[/^ircs?\z/] && !uri.path.nil? - uri.to_s - end - end -end diff --git a/app/models/project_services/jira_tracker_data.rb b/app/models/project_services/jira_tracker_data.rb index 2c145abf5c9..00b6ab6a70f 100644 --- a/app/models/project_services/jira_tracker_data.rb +++ b/app/models/project_services/jira_tracker_data.rb @@ -2,18 +2,6 @@ class JiraTrackerData < ApplicationRecord include Services::DataFields - include IgnorableColumns - - ignore_columns %i[ - encrypted_proxy_address - encrypted_proxy_address_iv - encrypted_proxy_port - encrypted_proxy_port_iv - encrypted_proxy_username - encrypted_proxy_username_iv - encrypted_proxy_password - encrypted_proxy_password_iv - ], remove_with: '14.0', remove_after: '2021-05-22' attr_encrypted :url, encryption_options attr_encrypted :api_url, encryption_options diff --git a/app/models/project_services/packagist_service.rb b/app/models/project_services/packagist_service.rb deleted file mode 100644 index f3ea8c64302..00000000000 --- a/app/models/project_services/packagist_service.rb +++ /dev/null @@ -1,65 +0,0 @@ -# frozen_string_literal: true - -class PackagistService < Integration - prop_accessor :username, :token, :server - - validates :username, presence: true, if: :activated? - validates :token, presence: true, if: :activated? - - default_value_for :push_events, true - default_value_for :tag_push_events, true - - after_save :compose_service_hook, if: :activated? - - def title - 'Packagist' - end - - def description - s_('Integrations|Update your Packagist projects.') - end - - def self.to_param - 'packagist' - end - - def fields - [ - { type: 'text', name: 'username', placeholder: '', required: true }, - { type: 'text', name: 'token', placeholder: '', required: true }, - { type: 'text', name: 'server', placeholder: 'https://packagist.org', required: false } - ] - end - - def self.supported_events - %w(push merge_request tag_push) - end - - def execute(data) - return unless supported_events.include?(data[:object_kind]) - - service_hook.execute(data) - end - - def test(data) - begin - result = execute(data) - return { success: false, result: result[:message] } if result[:http_status] != 202 - rescue StandardError => error - return { success: false, result: error } - end - - { success: true, result: result[:message] } - end - - def compose_service_hook - hook = service_hook || build_service_hook - hook.url = hook_url - hook.save - end - - def hook_url - base_url = server.presence || 'https://packagist.org' - "#{base_url}/api/update-package?username=#{username}&apiToken=#{token}" - end -end diff --git a/app/models/project_services/pipelines_email_service.rb b/app/models/project_services/pipelines_email_service.rb deleted file mode 100644 index 4603193ac8e..00000000000 --- a/app/models/project_services/pipelines_email_service.rb +++ /dev/null @@ -1,103 +0,0 @@ -# frozen_string_literal: true - -class PipelinesEmailService < Integration - include NotificationBranchSelection - - prop_accessor :recipients, :branches_to_be_notified - boolean_accessor :notify_only_broken_pipelines, :notify_only_default_branch - validates :recipients, presence: true, if: :validate_recipients? - - def initialize_properties - if properties.nil? - self.properties = {} - self.notify_only_broken_pipelines = true - self.branches_to_be_notified = "default" - elsif !self.notify_only_default_branch.nil? - # In older versions, there was only a boolean property named - # `notify_only_default_branch`. Now we have a string property named - # `branches_to_be_notified`. Instead of doing a background migration, we - # opted to set a value for the new property based on the old one, if - # users hasn't specified one already. When users edit the service and - # selects a value for this new property, it will override everything. - - self.branches_to_be_notified ||= notify_only_default_branch? ? "default" : "all" - end - end - - def title - _('Pipeline status emails') - end - - def description - _('Email the pipeline status to a list of recipients.') - end - - def self.to_param - 'pipelines_email' - end - - def self.supported_events - %w[pipeline] - end - - def self.default_test_event - 'pipeline' - end - - def execute(data, force: false) - return unless supported_events.include?(data[:object_kind]) - return unless force || should_pipeline_be_notified?(data) - - all_recipients = retrieve_recipients(data) - - return unless all_recipients.any? - - pipeline_id = data[:object_attributes][:id] - PipelineNotificationWorker.new.perform(pipeline_id, recipients: all_recipients) - end - - def can_test? - project&.ci_pipelines&.any? - end - - def fields - [ - { type: 'textarea', - name: 'recipients', - help: _('Comma-separated list of email addresses.'), - required: true }, - { type: 'checkbox', - name: 'notify_only_broken_pipelines' }, - { type: 'select', - name: 'branches_to_be_notified', - choices: branch_choices } - ] - end - - def test(data) - result = execute(data, force: true) - - { success: true, result: result } - rescue StandardError => error - { success: false, result: error } - end - - def should_pipeline_be_notified?(data) - notify_for_branch?(data) && notify_for_pipeline?(data) - end - - def notify_for_pipeline?(data) - case data[:object_attributes][:status] - when 'success' - !notify_only_broken_pipelines? - when 'failed' - true - else - false - end - end - - def retrieve_recipients(data) - recipients.to_s.split(/[,\r\n ]+/).reject(&:empty?) - end -end diff --git a/app/models/project_services/pivotaltracker_service.rb b/app/models/project_services/pivotaltracker_service.rb deleted file mode 100644 index 6e67984591d..00000000000 --- a/app/models/project_services/pivotaltracker_service.rb +++ /dev/null @@ -1,76 +0,0 @@ -# frozen_string_literal: true - -class PivotaltrackerService < Integration - API_ENDPOINT = 'https://www.pivotaltracker.com/services/v5/source_commits' - - prop_accessor :token, :restrict_to_branch - validates :token, presence: true, if: :activated? - - def title - 'PivotalTracker' - end - - def description - s_('PivotalTrackerService|Add commit messages as comments to PivotalTracker stories.') - end - - def self.to_param - 'pivotaltracker' - end - - def fields - [ - { - type: 'text', - name: 'token', - placeholder: s_('PivotalTrackerService|Pivotal Tracker API token.'), - required: true - }, - { - type: 'text', - name: 'restrict_to_branch', - placeholder: s_('PivotalTrackerService|Comma-separated list of branches which will be ' \ - 'automatically inspected. Leave blank to include all branches.') - } - ] - end - - def self.supported_events - %w(push) - end - - def execute(data) - return unless supported_events.include?(data[:object_kind]) - return unless allowed_branch?(data[:ref]) - - data[:commits].each do |commit| - message = { - 'source_commit' => { - 'commit_id' => commit[:id], - 'author' => commit[:author][:name], - 'url' => commit[:url], - 'message' => commit[:message] - } - } - Gitlab::HTTP.post( - API_ENDPOINT, - body: message.to_json, - headers: { - 'Content-Type' => 'application/json', - 'X-TrackerToken' => token - } - ) - end - end - - private - - def allowed_branch?(ref) - return true unless ref.present? && restrict_to_branch.present? - - branch = Gitlab::Git.ref_name(ref) - allowed_branches = restrict_to_branch.split(',').map(&:strip) - - branch.present? && allowed_branches.include?(branch) - end -end diff --git a/config/feature_flags/development/ci_needs_optional.yml b/config/feature_flags/development/ci_needs_optional.yml deleted file mode 100644 index eacb0ab6d51..00000000000 --- a/config/feature_flags/development/ci_needs_optional.yml +++ /dev/null @@ -1,8 +0,0 @@ ---- -name: ci_needs_optional -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/55468 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/323891 -milestone: '13.10' -type: development -group: group::pipeline authoring -default_enabled: true diff --git a/config/feature_flags/development/usage_data_unique_users_committing_ciconfigfile.yml b/config/feature_flags/development/usage_data_unique_users_committing_ciconfigfile.yml deleted file mode 100644 index 1d3092ed615..00000000000 --- a/config/feature_flags/development/usage_data_unique_users_committing_ciconfigfile.yml +++ /dev/null @@ -1,8 +0,0 @@ ---- -name: usage_data_unique_users_committing_ciconfigfile -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/52172 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/299403 -milestone: '13.9' -type: development -group: group::pipeline authoring -default_enabled: true diff --git a/config/metrics/counts_all/20210216175026_service_desk_issues.yml b/config/metrics/counts_all/20210216175026_service_desk_issues.yml index b852ae8b62f..299fbfa3b7f 100644 --- a/config/metrics/counts_all/20210216175026_service_desk_issues.yml +++ b/config/metrics/counts_all/20210216175026_service_desk_issues.yml @@ -14,4 +14,3 @@ distribution: - ee tier: - free -skip_validation: true diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index e9b3a2213c8..1d166ed0ca2 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -2069,14 +2069,7 @@ To download artifacts from a job in the current pipeline, use the basic form of #### Optional `needs` > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/30680) in GitLab 13.10. -> - [Deployed behind a feature flag](../../user/feature_flags.md), disabled by default. -> - [Enabled by default](https://gitlab.com/gitlab-org/gitlab/-/issues/323891) in GitLab 13.11. -> - Enabled on GitLab.com. -> - Recommended for production use. -> - For GitLab self-managed instances, GitLab administrators can opt to [disable it](#enable-or-disable-optional-needs). **(FREE SELF)** - -WARNING: -This feature might not be available to you. Check the **version history** note above for details. +> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/323891) in GitLab 14.0. To need a job that sometimes does not exist in the pipeline, add `optional: true` to the `needs` configuration. If not defined, `optional: false` is the default. @@ -2110,25 +2103,6 @@ rspec: optional: true ``` -#### Enable or disable optional needs **(FREE SELF)** - -Optional needs is under development but ready for production use. -It is deployed behind a feature flag that is **enabled by default**. -[GitLab administrators with access to the GitLab Rails console](../../administration/feature_flags.md) -can opt to disable it. - -To enable it: - -```ruby -Feature.enable(:ci_needs_optional) -``` - -To disable it: - -```ruby -Feature.disable(:ci_needs_optional) -``` - ### `tags` Use `tags` to select a specific runner from the list of all runners that are diff --git a/doc/development/snowplow/index.md b/doc/development/snowplow/index.md index 6e2bf00f191..c4f5125ac0d 100644 --- a/doc/development/snowplow/index.md +++ b/doc/development/snowplow/index.md @@ -573,6 +573,20 @@ Snowplow Mini can be used for testing frontend and backend events on a productio For GitLab.com, we're setting up a [QA and Testing environment](https://gitlab.com/gitlab-org/telemetry/-/issues/266) using Snowplow Mini. +### Troubleshooting + +To control content security policy warnings when using an external host, you can allow or disallow them by modifying `config/gitlab.yml`. To allow them, add the relevant host for `connect_src`. For example, for `https://snowplow.trx.gitlab.net`: + +```yaml +development: + <<: *base + gitlab: + content_security_policy: + enabled: true + directives: + connect_src: "'self' http://localhost:* http://127.0.0.1:* ws://localhost:* wss://localhost:* ws://127.0.0.1:* https://snowplow.trx.gitlab.net/" +``` + ## Snowplow Schemas ### `gitlab_standard` diff --git a/lib/api/helpers/services_helpers.rb b/lib/api/helpers/services_helpers.rb index f9abc069e4b..c796b50f5f3 100644 --- a/lib/api/helpers/services_helpers.rb +++ b/lib/api/helpers/services_helpers.rb @@ -784,22 +784,22 @@ module API ::Integrations::Datadog, ::Integrations::EmailsOnPush, ::Integrations::Ewm, + ::Integrations::ExternalWiki, + ::Integrations::Flowdock, + ::Integrations::Irker, ::Integrations::Jira, + ::Integrations::Packagist, + ::Integrations::PipelinesEmail, + ::Integrations::Pivotaltracker, ::Integrations::Redmine, ::Integrations::Youtrack, ::BuildkiteService, ::DiscordService, ::DroneCiService, - ::ExternalWikiService, - ::FlowdockService, ::HangoutsChatService, - ::IrkerService, ::JenkinsService, ::MattermostSlashCommandsService, ::SlackSlashCommandsService, - ::PackagistService, - ::PipelinesEmailService, - ::PivotaltrackerService, ::PrometheusService, ::PushoverService, ::SlackService, diff --git a/lib/flowdock/git.rb b/lib/flowdock/git.rb index 539fd66a510..897ee647d87 100644 --- a/lib/flowdock/git.rb +++ b/lib/flowdock/git.rb @@ -34,7 +34,7 @@ module Flowdock # Send git push notification to Flowdock def post messages.each do |message| - Flowdock::Client.new(flow_token: @token).post_to_thread(message) + ::Flowdock::Client.new(flow_token: @token).post_to_thread(message) end end diff --git a/lib/gitlab/ci/config/entry/need.rb b/lib/gitlab/ci/config/entry/need.rb index 29dc48c7b42..f1b67635c08 100644 --- a/lib/gitlab/ci/config/entry/need.rb +++ b/lib/gitlab/ci/config/entry/need.rb @@ -35,14 +35,9 @@ module Gitlab end def value - if ::Feature.enabled?(:ci_needs_optional, default_enabled: :yaml) - { name: @config, - artifacts: true, - optional: false } - else - { name: @config, - artifacts: true } - end + { name: @config, + artifacts: true, + optional: false } end end @@ -66,14 +61,9 @@ module Gitlab end def value - if ::Feature.enabled?(:ci_needs_optional, default_enabled: :yaml) - { name: job, - artifacts: artifacts || artifacts.nil?, - optional: !!optional } - else - { name: job, - artifacts: artifacts || artifacts.nil? } - end + { name: job, + artifacts: artifacts || artifacts.nil?, + optional: !!optional } end end diff --git a/lib/gitlab/ci/pipeline/seed/build.rb b/lib/gitlab/ci/pipeline/seed/build.rb index 39dee7750d6..299b27a5f13 100644 --- a/lib/gitlab/ci/pipeline/seed/build.rb +++ b/lib/gitlab/ci/pipeline/seed/build.rb @@ -146,7 +146,7 @@ module Gitlab end @needs_attributes.flat_map do |need| - next if ::Feature.enabled?(:ci_needs_optional, default_enabled: :yaml) && need[:optional] + next if need[:optional] result = @previous_stages.any? do |stage| stage.seeds_names.include?(need[:name]) diff --git a/lib/gitlab/integrations/sti_type.rb b/lib/gitlab/integrations/sti_type.rb index ccb8551fb3f..5a38c382b81 100644 --- a/lib/gitlab/integrations/sti_type.rb +++ b/lib/gitlab/integrations/sti_type.rb @@ -5,7 +5,8 @@ module Gitlab class StiType < ActiveRecord::Type::String NAMESPACED_INTEGRATIONS = Set.new(%w( Asana Assembla Bamboo Bugzilla Campfire Confluence CustomIssueTracker Datadog - EmailsOnPush Ewm IssueTracker Jira Redmine Youtrack + EmailsOnPush Ewm ExternalWiki Flowdock IssueTracker Irker Jira Packagist PipelinesEmail + Pivotaltracker Redmine Youtrack )).freeze def cast(value) diff --git a/lib/gitlab/usage_data_counters/known_events/common.yml b/lib/gitlab/usage_data_counters/known_events/common.yml index f2504396cc4..95b7b30b236 100644 --- a/lib/gitlab/usage_data_counters/known_events/common.yml +++ b/lib/gitlab/usage_data_counters/known_events/common.yml @@ -351,7 +351,6 @@ category: pipeline_authoring redis_slot: pipeline_authoring aggregation: weekly - feature_flag: usage_data_unique_users_committing_ciconfigfile - name: o_pipeline_authoring_unique_users_pushing_mr_ciconfigfile category: pipeline_authoring redis_slot: pipeline_authoring diff --git a/spec/factories/integrations.rb b/spec/factories/integrations.rb index 12de759b10c..8b8a950d081 100644 --- a/spec/factories/integrations.rb +++ b/spec/factories/integrations.rb @@ -127,9 +127,9 @@ FactoryBot.define do end end - factory :external_wiki_service do + factory :external_wiki_service, class: 'Integrations::ExternalWiki' do project - type { ExternalWikiService } + type { 'ExternalWikiService' } active { true } external_wiki_url { 'http://external-wiki-url.com' } end @@ -167,7 +167,7 @@ FactoryBot.define do type { 'SlackService' } end - factory :pipelines_email_service do + factory :pipelines_email_service, class: 'Integrations::PipelinesEmail' do project active { true } type { 'PipelinesEmailService' } diff --git a/spec/features/projects/integrations/user_activates_flowdock_spec.rb b/spec/features/projects/integrations/user_activates_flowdock_spec.rb new file mode 100644 index 00000000000..4a4d7bbecfd --- /dev/null +++ b/spec/features/projects/integrations/user_activates_flowdock_spec.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'User activates Flowdock' do + include_context 'project service activation' do + let(:project) { create(:project, :repository) } + end + + before do + stub_request(:post, /.*api.flowdock.com.*/) + end + + it 'activates service', :js do + visit_project_integration('Flowdock') + fill_in('Token', with: 'verySecret') + + click_test_then_save_integration(expect_test_to_fail: false) + + expect(page).to have_content('Flowdock settings saved and active.') + end +end diff --git a/spec/features/projects/integrations/user_activates_pivotaltracker_spec.rb b/spec/features/projects/integrations/user_activates_pivotaltracker_spec.rb new file mode 100644 index 00000000000..83f66d4fa7b --- /dev/null +++ b/spec/features/projects/integrations/user_activates_pivotaltracker_spec.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'User activates PivotalTracker' do + include_context 'project service activation' + + before do + stub_request(:post, /.*www.pivotaltracker.com.*/) + end + + it 'activates service', :js do + visit_project_integration('PivotalTracker') + fill_in('Token', with: 'verySecret') + + click_test_then_save_integration(expect_test_to_fail: false) + + expect(page).to have_content('PivotalTracker settings saved and active.') + end +end diff --git a/spec/features/projects/services/user_activates_flowdock_spec.rb b/spec/features/projects/services/user_activates_flowdock_spec.rb deleted file mode 100644 index 4a4d7bbecfd..00000000000 --- a/spec/features/projects/services/user_activates_flowdock_spec.rb +++ /dev/null @@ -1,22 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe 'User activates Flowdock' do - include_context 'project service activation' do - let(:project) { create(:project, :repository) } - end - - before do - stub_request(:post, /.*api.flowdock.com.*/) - end - - it 'activates service', :js do - visit_project_integration('Flowdock') - fill_in('Token', with: 'verySecret') - - click_test_then_save_integration(expect_test_to_fail: false) - - expect(page).to have_content('Flowdock settings saved and active.') - end -end diff --git a/spec/features/projects/services/user_activates_pivotaltracker_spec.rb b/spec/features/projects/services/user_activates_pivotaltracker_spec.rb deleted file mode 100644 index 83f66d4fa7b..00000000000 --- a/spec/features/projects/services/user_activates_pivotaltracker_spec.rb +++ /dev/null @@ -1,20 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe 'User activates PivotalTracker' do - include_context 'project service activation' - - before do - stub_request(:post, /.*www.pivotaltracker.com.*/) - end - - it 'activates service', :js do - visit_project_integration('PivotalTracker') - fill_in('Token', with: 'verySecret') - - click_test_then_save_integration(expect_test_to_fail: false) - - expect(page).to have_content('PivotalTracker settings saved and active.') - end -end diff --git a/spec/lib/gitlab/ci/config/entry/need_spec.rb b/spec/lib/gitlab/ci/config/entry/need_spec.rb index a0a5dd52ad4..ab2e8d4db78 100644 --- a/spec/lib/gitlab/ci/config/entry/need_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/need_spec.rb @@ -25,16 +25,6 @@ RSpec.describe ::Gitlab::Ci::Config::Entry::Need do it 'returns job needs configuration' do expect(need.value).to eq(name: 'job_name', artifacts: true, optional: false) end - - context 'when the FF ci_needs_optional is disabled' do - before do - stub_feature_flags(ci_needs_optional: false) - end - - it 'returns job needs configuration without `optional`' do - expect(need.value).to eq(name: 'job_name', artifacts: true) - end - end end it_behaves_like 'job type' @@ -134,16 +124,6 @@ RSpec.describe ::Gitlab::Ci::Config::Entry::Need do it 'returns job needs configuration' do expect(need.value).to eq(name: 'job_name', artifacts: true, optional: true) end - - context 'when the FF ci_needs_optional is disabled' do - before do - stub_feature_flags(ci_needs_optional: false) - end - - it 'returns job needs configuration without `optional`' do - expect(need.value).to eq(name: 'job_name', artifacts: true) - end - end end end diff --git a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb index 058fb25807d..020f957cf70 100644 --- a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb @@ -1101,17 +1101,6 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do it "does not return an error" do expect(subject.errors).to be_empty end - - context 'when the FF ci_needs_optional is disabled' do - before do - stub_feature_flags(ci_needs_optional: false) - end - - it "returns an error" do - expect(subject.errors).to contain_exactly( - "'rspec' job needs 'build' job, but it was not added to the pipeline") - end - end end end diff --git a/spec/models/integration_spec.rb b/spec/models/integration_spec.rb index cb3fb7a011c..976d5750a12 100644 --- a/spec/models/integration_spec.rb +++ b/spec/models/integration_spec.rb @@ -905,7 +905,7 @@ RSpec.describe Integration do with_them do it 'returns the right result' do - expect(build(:service, type: type, active: active).external_wiki?).to eq(result) + expect(create(:service, type: type, active: active).external_wiki?).to eq(result) end end end diff --git a/spec/models/integrations/external_wiki_spec.rb b/spec/models/integrations/external_wiki_spec.rb new file mode 100644 index 00000000000..8c20b810301 --- /dev/null +++ b/spec/models/integrations/external_wiki_spec.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Integrations::ExternalWiki do + describe "Associations" do + it { is_expected.to belong_to :project } + it { is_expected.to have_one :service_hook } + end + + describe 'Validations' do + context 'when service is active' do + 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/integrations/flowdock_spec.rb b/spec/models/integrations/flowdock_spec.rb new file mode 100644 index 00000000000..2de6f7dd2f1 --- /dev/null +++ b/spec/models/integrations/flowdock_spec.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Integrations::Flowdock do + describe "Associations" do + it { is_expected.to belong_to :project } + it { is_expected.to have_one :service_hook } + end + + describe 'Validations' do + context 'when service is active' do + 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/integrations/irker_spec.rb b/spec/models/integrations/irker_spec.rb new file mode 100644 index 00000000000..a69be1292e0 --- /dev/null +++ b/spec/models/integrations/irker_spec.rb @@ -0,0 +1,76 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'socket' +require 'json' + +RSpec.describe Integrations::Irker do + describe 'Associations' do + it { is_expected.to belong_to :project } + it { is_expected.to have_one :service_hook } + end + + describe 'Validations' do + context 'when service is active' do + 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/integrations/packagist_spec.rb b/spec/models/integrations/packagist_spec.rb new file mode 100644 index 00000000000..48f7e81adca --- /dev/null +++ b/spec/models/integrations/packagist_spec.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Integrations::Packagist 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/integrations/pipelines_email_spec.rb b/spec/models/integrations/pipelines_email_spec.rb new file mode 100644 index 00000000000..90055b04bb8 --- /dev/null +++ b/spec/models/integrations/pipelines_email_spec.rb @@ -0,0 +1,305 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Integrations::PipelinesEmail, :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/integrations/pivotaltracker_spec.rb b/spec/models/integrations/pivotaltracker_spec.rb new file mode 100644 index 00000000000..2ce90b6f739 --- /dev/null +++ b/spec/models/integrations/pivotaltracker_spec.rb @@ -0,0 +1,101 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Integrations::Pivotaltracker do + include StubRequests + + describe 'Associations' do + it { is_expected.to belong_to :project } + it { is_expected.to have_one :service_hook } + end + + describe 'Validations' do + context 'when service is active' do + 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) { described_class::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/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/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/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_spec.rb b/spec/models/project_spec.rb index e98821a04b6..8e606a00144 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -1158,7 +1158,7 @@ RSpec.describe Project, factory_default: :keep do it 'returns an active external wiki' do create(:service, project: project, type: 'ExternalWikiService', active: true) - is_expected.to be_kind_of(ExternalWikiService) + is_expected.to be_kind_of(Integrations::ExternalWiki) end it 'does not return an inactive external wiki' do -- cgit v1.2.1