summaryrefslogtreecommitdiff
path: root/spec/models/integrations
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-04-20 12:21:30 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2023-04-20 12:21:30 +0000
commit2366f969a4b3a95e052e551cc7283a2db8d5562e (patch)
tree33ea679dad4b92048697729f68f9c606f91b32e4 /spec/models/integrations
parentc7eec01f1b68b2e047cdd709751cb695ab329933 (diff)
downloadgitlab-ce-2366f969a4b3a95e052e551cc7283a2db8d5562e.tar.gz
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec/models/integrations')
-rw-r--r--spec/models/integrations/apple_app_store_spec.rb6
-rw-r--r--spec/models/integrations/gitlab_slack_application_spec.rb337
-rw-r--r--spec/models/integrations/slack_workspace/api_scope_spec.rb20
3 files changed, 363 insertions, 0 deletions
diff --git a/spec/models/integrations/apple_app_store_spec.rb b/spec/models/integrations/apple_app_store_spec.rb
index b2a52c8aaf0..7487793cf4f 100644
--- a/spec/models/integrations/apple_app_store_spec.rb
+++ b/spec/models/integrations/apple_app_store_spec.rb
@@ -81,6 +81,12 @@ RSpec.describe Integrations::AppleAppStore, feature_category: :mobile_devops do
value: apple_app_store_integration.app_store_key_id,
masked: true,
public: false
+ },
+ {
+ key: 'APP_STORE_CONNECT_API_KEY_IS_KEY_CONTENT_BASE64',
+ value: described_class::IS_KEY_CONTENT_BASE64,
+ masked: false,
+ public: false
}
]
diff --git a/spec/models/integrations/gitlab_slack_application_spec.rb b/spec/models/integrations/gitlab_slack_application_spec.rb
new file mode 100644
index 00000000000..68476dde2a3
--- /dev/null
+++ b/spec/models/integrations/gitlab_slack_application_spec.rb
@@ -0,0 +1,337 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Integrations::GitlabSlackApplication, feature_category: :integrations do
+ include AfterNextHelpers
+
+ it_behaves_like Integrations::BaseSlackNotification, factory: :gitlab_slack_application_integration do
+ before do
+ stub_request(:post, "#{::Slack::API::BASE_URL}/chat.postMessage").to_return(body: '{"ok":true}')
+ end
+ end
+
+ describe 'validations' do
+ it { is_expected.not_to validate_presence_of(:webhook) }
+ end
+
+ describe 'default values' do
+ it { expect(subject.category).to eq(:chat) }
+
+ it { is_expected.not_to be_alert_events }
+ it { is_expected.not_to be_commit_events }
+ it { is_expected.not_to be_confidential_issues_events }
+ it { is_expected.not_to be_confidential_note_events }
+ it { is_expected.not_to be_deployment_events }
+ it { is_expected.not_to be_issues_events }
+ it { is_expected.not_to be_job_events }
+ it { is_expected.not_to be_merge_requests_events }
+ it { is_expected.not_to be_note_events }
+ it { is_expected.not_to be_pipeline_events }
+ it { is_expected.not_to be_push_events }
+ it { is_expected.not_to be_tag_push_events }
+ it { is_expected.not_to be_vulnerability_events }
+ it { is_expected.not_to be_wiki_page_events }
+ end
+
+ describe '#execute' do
+ let_it_be(:user) { build_stubbed(:user) }
+
+ let(:slack_integration) { build(:slack_integration) }
+ let(:data) { Gitlab::DataBuilder::Push.build_sample(integration.project, user) }
+ let(:slack_api_method_uri) { "#{::Slack::API::BASE_URL}/chat.postMessage" }
+
+ let(:mock_message) do
+ instance_double(Integrations::ChatMessage::PushMessage, attachments: ['foo'], pretext: 'bar')
+ end
+
+ subject(:integration) { build(:gitlab_slack_application_integration, slack_integration: slack_integration) }
+
+ before do
+ allow(integration).to receive(:get_message).and_return(mock_message)
+ allow(integration).to receive(:log_usage)
+ end
+
+ def stub_slack_request(channel: '#push_channel', success: true)
+ post_body = {
+ body: {
+ attachments: mock_message.attachments,
+ text: mock_message.pretext,
+ unfurl_links: false,
+ unfurl_media: false,
+ channel: channel
+ }
+ }
+
+ response = { ok: success }.to_json
+
+ stub_request(:post, slack_api_method_uri).with(post_body)
+ .to_return(body: response, headers: { 'Content-Type' => 'application/json; charset=utf-8' })
+ end
+
+ it 'notifies Slack' do
+ stub_slack_request
+
+ expect(integration.execute(data)).to be true
+ end
+
+ context 'when the integration is not configured for event' do
+ before do
+ integration.push_channel = nil
+ end
+
+ it 'does not notify Slack' do
+ expect(integration.execute(data)).to be false
+ end
+ end
+
+ context 'when Slack API responds with an error' do
+ it 'logs the error and API response' do
+ stub_slack_request(success: false)
+
+ expect(Gitlab::IntegrationsLogger).to receive(:error).with(
+ {
+ integration_class: described_class.name,
+ integration_id: integration.id,
+ project_id: integration.project_id,
+ project_path: kind_of(String),
+ message: 'Slack API error when notifying',
+ api_response: { 'ok' => false }
+ }
+ )
+ expect(integration.execute(data)).to be false
+ end
+ end
+
+ context 'when there is an HTTP error' do
+ it 'logs the error' do
+ expect_next(Slack::API).to receive(:post).and_raise(Net::ReadTimeout)
+ expect(Gitlab::ErrorTracking).to receive(:track_and_raise_for_dev_exception).with(
+ kind_of(Net::ReadTimeout),
+ {
+ slack_integration_id: slack_integration.id,
+ integration_id: integration.id
+ }
+ )
+ expect(integration.execute(data)).to be false
+ end
+ end
+
+ context 'when configured to post to multiple Slack channels' do
+ before do
+ push_channels = '#first_channel, #second_channel'
+ integration.push_channel = push_channels
+ end
+
+ it 'posts to both Slack channels and returns true' do
+ stub_slack_request(channel: '#first_channel')
+ stub_slack_request(channel: '#second_channel')
+
+ expect(integration.execute(data)).to be true
+ end
+
+ context 'when one of the posts responds with an error' do
+ it 'posts to both channels and returns true' do
+ stub_slack_request(channel: '#first_channel', success: false)
+ stub_slack_request(channel: '#second_channel')
+
+ expect(Gitlab::IntegrationsLogger).to receive(:error).once
+ expect(integration.execute(data)).to be true
+ end
+ end
+
+ context 'when both of the posts respond with an error' do
+ it 'posts to both channels and returns false' do
+ stub_slack_request(channel: '#first_channel', success: false)
+ stub_slack_request(channel: '#second_channel', success: false)
+
+ expect(Gitlab::IntegrationsLogger).to receive(:error).twice
+ expect(integration.execute(data)).to be false
+ end
+ end
+
+ context 'when one of the posts raises an HTTP exception' do
+ it 'posts to one channel and returns true' do
+ stub_slack_request(channel: '#second_channel')
+
+ expect_next_instance_of(Slack::API) do |api_client|
+ expect(api_client).to receive(:post)
+ .with('chat.postMessage', hash_including(channel: '#first_channel')).and_raise(Net::ReadTimeout)
+ expect(api_client).to receive(:post)
+ .with('chat.postMessage', hash_including(channel: '#second_channel')).and_call_original
+ end
+ expect(Gitlab::ErrorTracking).to receive(:track_and_raise_for_dev_exception).once
+ expect(integration.execute(data)).to be true
+ end
+ end
+
+ context 'when both of the posts raise an HTTP exception' do
+ it 'posts to one channel and returns true' do
+ stub_slack_request(channel: '#second_channel')
+
+ expect_next(Slack::API).to receive(:post).twice.and_raise(Net::ReadTimeout)
+ expect(Gitlab::ErrorTracking).to receive(:track_and_raise_for_dev_exception).twice
+ expect(integration.execute(data)).to be false
+ end
+ end
+ end
+ end
+
+ describe '#test' do
+ let(:integration) { build(:gitlab_slack_application_integration) }
+
+ let(:slack_api_method_uri) { "#{::Slack::API::BASE_URL}/chat.postEphemeral" }
+ let(:response_failure) { { error: 'channel_not_found' } }
+ let(:response_success) { { error: 'user_not_in_channel' } }
+ let(:response_headers) { { 'Content-Type' => 'application/json; charset=utf-8' } }
+ let(:request_body) do
+ {
+ text: 'Test',
+ user: integration.bot_user_id
+ }
+ end
+
+ subject(:result) { integration.test({}) }
+
+ def stub_slack_request(channel:, success:)
+ response_body = success ? response_success : response_failure
+
+ stub_request(:post, slack_api_method_uri)
+ .with(body: request_body.merge(channel: channel))
+ .to_return(body: response_body.to_json, headers: response_headers)
+ end
+
+ context 'when all channels can be posted to' do
+ before do
+ stub_slack_request(channel: anything, success: true)
+ end
+
+ it 'is successful' do
+ is_expected.to eq({ success: true, result: nil })
+ end
+ end
+
+ context 'when the same channel is used for multiple events' do
+ let(:integration) do
+ build(:gitlab_slack_application_integration, all_channels: false, push_channel: '#foo', issue_channel: '#foo')
+ end
+
+ it 'only tests the channel once' do
+ stub_slack_request(channel: '#foo', success: true)
+
+ is_expected.to eq({ success: true, result: nil })
+ expect(WebMock).to have_requested(:post, slack_api_method_uri).once
+ end
+ end
+
+ context 'when there are channels that cannot be posted to' do
+ let(:unpostable_channels) { ['#push_channel', '#issue_channel'] }
+
+ before do
+ stub_slack_request(channel: anything, success: true)
+
+ unpostable_channels.each do |channel|
+ stub_slack_request(channel: channel, success: false)
+ end
+ end
+
+ it 'returns an error message informing which channels cannot be posted to' do
+ expected_message = "Unable to post to #{unpostable_channels.to_sentence}, " \
+ 'please add the GitLab Slack app to any private Slack channels'
+
+ is_expected.to eq({ success: false, result: expected_message })
+ end
+
+ context 'when integration is not configured for notifications' do
+ let_it_be(:integration) { build(:gitlab_slack_application_integration, all_channels: false) }
+
+ it 'is successful' do
+ is_expected.to eq({ success: true, result: nil })
+ end
+ end
+ end
+
+ context 'when integration is using legacy version of Slack app' do
+ before do
+ integration.slack_integration = build(:slack_integration, :legacy)
+ end
+
+ it 'returns an error to inform the user to update their integration' do
+ expected_message = 'GitLab for Slack app must be reinstalled to enable notifications'
+
+ is_expected.to eq({ success: false, result: expected_message })
+ end
+ end
+ end
+
+ context 'when the integration is active' do
+ before do
+ subject.active = true
+ end
+
+ it 'is editable, and presents editable fields' do
+ expect(subject).to be_editable
+ expect(subject.fields).not_to be_empty
+ expect(subject.configurable_events).not_to be_empty
+ end
+
+ it 'includes the expected sections' do
+ section_types = subject.sections.pluck(:type)
+
+ expect(section_types).to eq(
+ [
+ described_class::SECTION_TYPE_TRIGGER,
+ described_class::SECTION_TYPE_CONFIGURATION
+ ]
+ )
+ end
+ end
+
+ context 'when the integration is not active' do
+ before do
+ subject.active = false
+ end
+
+ it 'is not editable, and presents no editable fields' do
+ expect(subject).not_to be_editable
+ expect(subject.fields).to be_empty
+ expect(subject.configurable_events).to be_empty
+ end
+
+ it 'does not include sections' do
+ section_types = subject.sections.pluck(:type)
+
+ expect(section_types).to be_empty
+ end
+ end
+
+ describe '#description' do
+ specify { expect(subject.description).to be_present }
+ end
+
+ describe '#upgrade_needed?' do
+ context 'with all_features_supported' do
+ subject(:integration) { create(:gitlab_slack_application_integration, :all_features_supported) }
+
+ it 'is false' do
+ expect(integration).not_to be_upgrade_needed
+ end
+ end
+
+ context 'without all_features_supported' do
+ subject(:integration) { create(:gitlab_slack_application_integration) }
+
+ it 'is true' do
+ expect(integration).to be_upgrade_needed
+ end
+ end
+
+ context 'without slack_integration' do
+ subject(:integration) { create(:gitlab_slack_application_integration, slack_integration: nil) }
+
+ it 'is false' do
+ expect(integration).not_to be_upgrade_needed
+ end
+ end
+ end
+end
diff --git a/spec/models/integrations/slack_workspace/api_scope_spec.rb b/spec/models/integrations/slack_workspace/api_scope_spec.rb
new file mode 100644
index 00000000000..92052983242
--- /dev/null
+++ b/spec/models/integrations/slack_workspace/api_scope_spec.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Integrations::SlackWorkspace::ApiScope, feature_category: :integrations do
+ describe '.find_or_initialize_by_names' do
+ it 'acts as insert into a global set of scope names' do
+ expect { described_class.find_or_initialize_by_names(%w[foo bar baz]) }
+ .to change { described_class.count }.by(3)
+
+ expect { described_class.find_or_initialize_by_names(%w[bar baz foo buzz]) }
+ .to change { described_class.count }.by(1)
+
+ expect { described_class.find_or_initialize_by_names(%w[baz foo]) }
+ .to change { described_class.count }.by(0)
+
+ expect(described_class.pluck(:name)).to match_array(%w[foo bar baz buzz])
+ end
+ end
+end