diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-04-20 12:21:30 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-04-20 12:21:30 +0000 |
commit | 2366f969a4b3a95e052e551cc7283a2db8d5562e (patch) | |
tree | 33ea679dad4b92048697729f68f9c606f91b32e4 /spec/models/integrations | |
parent | c7eec01f1b68b2e047cdd709751cb695ab329933 (diff) | |
download | gitlab-ce-2366f969a4b3a95e052e551cc7283a2db8d5562e.tar.gz |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec/models/integrations')
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 |