337 lines
11 KiB
Ruby
337 lines
11 KiB
Ruby
# 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
|