summaryrefslogtreecommitdiff
path: root/spec/models
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
parentc7eec01f1b68b2e047cdd709751cb695ab329933 (diff)
downloadgitlab-ce-2366f969a4b3a95e052e551cc7283a2db8d5562e.tar.gz
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec/models')
-rw-r--r--spec/models/ci/build_spec.rb5
-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
-rw-r--r--spec/models/merge_request_spec.rb44
-rw-r--r--spec/models/resource_milestone_event_spec.rb18
-rw-r--r--spec/models/resource_state_event_spec.rb17
-rw-r--r--spec/models/slack_integration_spec.rb147
-rw-r--r--spec/models/user_spec.rb3
9 files changed, 553 insertions, 44 deletions
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index f2c713c22a7..99326dfd02d 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -3743,7 +3743,8 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration, factory_def
[
{ key: 'APP_STORE_CONNECT_API_KEY_ISSUER_ID', value: apple_app_store_integration.app_store_issuer_id, masked: true, public: false },
{ key: 'APP_STORE_CONNECT_API_KEY_KEY', value: Base64.encode64(apple_app_store_integration.app_store_private_key), masked: true, public: false },
- { key: 'APP_STORE_CONNECT_API_KEY_KEY_ID', value: apple_app_store_integration.app_store_key_id, masked: true, public: false }
+ { key: 'APP_STORE_CONNECT_API_KEY_KEY_ID', value: apple_app_store_integration.app_store_key_id, masked: true, public: false },
+ { key: 'APP_STORE_CONNECT_API_KEY_IS_KEY_CONTENT_BASE64', value: "true", masked: false, public: false }
]
end
@@ -3769,6 +3770,7 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration, factory_def
expect(subject.find { |v| v[:key] == 'APP_STORE_CONNECT_API_KEY_ISSUER_ID' }).to be_nil
expect(subject.find { |v| v[:key] == 'APP_STORE_CONNECT_API_KEY_KEY' }).to be_nil
expect(subject.find { |v| v[:key] == 'APP_STORE_CONNECT_API_KEY_KEY_ID' }).to be_nil
+ expect(subject.find { |v| v[:key] == 'APP_STORE_CONNECT_API_KEY_IS_KEY_CONTENT_BASE64' }).to be_nil
end
end
end
@@ -3778,6 +3780,7 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration, factory_def
expect(subject.find { |v| v[:key] == 'APP_STORE_CONNECT_API_KEY_ISSUER_ID' }).to be_nil
expect(subject.find { |v| v[:key] == 'APP_STORE_CONNECT_API_KEY_KEY' }).to be_nil
expect(subject.find { |v| v[:key] == 'APP_STORE_CONNECT_API_KEY_KEY_ID' }).to be_nil
+ expect(subject.find { |v| v[:key] == 'APP_STORE_CONNECT_API_KEY_IS_KEY_CONTENT_BASE64' }).to be_nil
end
end
end
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
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index f3aa174a964..a66302da6f5 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -4082,12 +4082,18 @@ RSpec.describe MergeRequest, factory_default: :keep, feature_category: :code_rev
)
end
- context 'when service class is Ci::CompareCodequalityReportsService' do
- let(:service_class) { 'Ci::CompareCodequalityReportsService' }
+ context 'when service class uses merge base pipeline' do
+ where(:service_class) do
+ %w[
+ Ci::CompareMetricsReportsService
+ Ci::CompareCodequalityReportsService
+ Ci::CompareSecurityReportsService
+ ]
+ end
context 'when merge request has a merge request pipeline' do
let(:merge_request) do
- create(:merge_request, :with_merge_request_pipeline)
+ create(:merge_request, :with_merge_request_pipeline, source_project: project)
end
let(:merge_base_pipeline) do
@@ -4099,8 +4105,36 @@ RSpec.describe MergeRequest, factory_default: :keep, feature_category: :code_rev
merge_request.update_head_pipeline
end
- it 'returns the merge_base_pipeline' do
- expect(pipeline).to eq(merge_base_pipeline)
+ with_them do
+ it 'returns the merge_base_pipeline' do
+ expect(pipeline).to eq(merge_base_pipeline)
+ end
+ end
+ end
+
+ context 'when merge does not have a merge request pipeline' do
+ with_them do
+ it 'returns the base_pipeline' do
+ expect(pipeline).to eq(base_pipeline)
+ end
+ end
+ end
+ end
+
+ context 'when service class is Ci::CompareSecurityReportsService and feature flag is off' do
+ let(:service_class) { 'Ci::CompareSecurityReportsService' }
+
+ before do
+ stub_feature_flags(use_merge_base_for_security_widget: false)
+ end
+
+ context 'when merge request has a merge request pipeline' do
+ let(:merge_request) do
+ create(:merge_request, :with_merge_request_pipeline, source_project: project)
+ end
+
+ it 'returns the base pipeline' do
+ expect(pipeline).to eq(base_pipeline)
end
end
diff --git a/spec/models/resource_milestone_event_spec.rb b/spec/models/resource_milestone_event_spec.rb
index 80351862fc1..d237a16da8f 100644
--- a/spec/models/resource_milestone_event_spec.rb
+++ b/spec/models/resource_milestone_event_spec.rb
@@ -18,24 +18,6 @@ RSpec.describe ResourceMilestoneEvent, feature_category: :team_planning, type: :
it { is_expected.to belong_to(:milestone) }
end
- describe 'scopes' do
- describe '.aliased_for_timebox_report', :freeze_time do
- let!(:event) { create(:resource_milestone_event, milestone: milestone) }
-
- let(:milestone) { create(:milestone) }
- let(:scope) { described_class.aliased_for_timebox_report.first }
-
- it 'returns correct values with aliased names', :aggregate_failures do
- expect(scope.event_type).to eq('timebox')
- expect(scope.id).to eq(event.id)
- expect(scope.issue_id).to eq(event.issue_id)
- expect(scope.value).to eq(milestone.id)
- expect(scope.action).to eq(event.action)
- expect(scope.created_at).to eq(event.created_at)
- end
- end
- end
-
describe '#milestone_title' do
let(:milestone) { create(:milestone, title: 'v2.3') }
let(:event) { create(:resource_milestone_event, milestone: milestone) }
diff --git a/spec/models/resource_state_event_spec.rb b/spec/models/resource_state_event_spec.rb
index 699720b564a..a6d6b507b69 100644
--- a/spec/models/resource_state_event_spec.rb
+++ b/spec/models/resource_state_event_spec.rb
@@ -41,23 +41,6 @@ RSpec.describe ResourceStateEvent, feature_category: :team_planning, type: :mode
end
end
- describe 'scopes' do
- describe '.aliased_for_timebox_report', :freeze_time do
- let!(:event) { create(:resource_state_event, issue: issue) }
-
- let(:scope) { described_class.aliased_for_timebox_report.first }
-
- it 'returns correct values with aliased names', :aggregate_failures do
- expect(scope.event_type).to eq('state')
- expect(scope.id).to eq(event.id)
- expect(scope.issue_id).to eq(event.issue_id)
- expect(scope.value).to eq(issue.state_id)
- expect(scope.action).to eq(nil)
- expect(scope.created_at).to eq(event.created_at)
- end
- end
- end
-
context 'callbacks' do
describe '#issue_usage_metrics' do
describe 'when an issue is closed' do
diff --git a/spec/models/slack_integration_spec.rb b/spec/models/slack_integration_spec.rb
new file mode 100644
index 00000000000..41beeee598c
--- /dev/null
+++ b/spec/models/slack_integration_spec.rb
@@ -0,0 +1,147 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe SlackIntegration, feature_category: :integrations do
+ describe "Associations" do
+ it { is_expected.to belong_to(:integration) }
+ end
+
+ describe 'authorized_scope_names' do
+ subject(:slack_integration) { create(:slack_integration) }
+
+ it 'accepts assignment to nil' do
+ slack_integration.update!(authorized_scope_names: nil)
+
+ expect(slack_integration.authorized_scope_names).to be_empty
+ end
+
+ it 'accepts assignment to a string' do
+ slack_integration.update!(authorized_scope_names: 'foo')
+
+ expect(slack_integration.authorized_scope_names).to contain_exactly('foo')
+ end
+
+ it 'accepts assignment to an array of strings' do
+ slack_integration.update!(authorized_scope_names: %w[foo bar])
+
+ expect(slack_integration.authorized_scope_names).to contain_exactly('foo', 'bar')
+ end
+
+ it 'accepts assignment to a comma-separated string' do
+ slack_integration.update!(authorized_scope_names: 'foo,bar')
+
+ expect(slack_integration.authorized_scope_names).to contain_exactly('foo', 'bar')
+ end
+
+ it 'strips white-space' do
+ slack_integration.update!(authorized_scope_names: 'foo , bar,baz')
+
+ expect(slack_integration.authorized_scope_names).to contain_exactly('foo', 'bar', 'baz')
+ end
+ end
+
+ describe 'all_features_supported?/upgrade_needed?' do
+ subject(:slack_integration) { create(:slack_integration) }
+
+ context 'with enough scopes' do
+ before do
+ slack_integration.update!(authorized_scope_names: %w[chat:write.public chat:write commands])
+ end
+
+ it { is_expected.to be_all_features_supported }
+ it { is_expected.not_to be_upgrade_needed }
+ end
+
+ %w[chat:write.public chat:write].each do |scope_name|
+ context "without #{scope_name}" do
+ before do
+ scopes = %w[chat:write.public chat:write] - [scope_name]
+ slack_integration.update!(authorized_scope_names: scopes)
+ end
+
+ it { is_expected.not_to be_all_features_supported }
+ it { is_expected.to be_upgrade_needed }
+ end
+ end
+ end
+
+ describe 'feature_available?' do
+ subject(:slack_integration) { create(:slack_integration) }
+
+ context 'without any scopes' do
+ it 'is always true for :commands' do
+ expect(slack_integration).to be_feature_available(:commands)
+ end
+
+ it 'is always false for others' do
+ expect(slack_integration).not_to be_feature_available(:notifications)
+ expect(slack_integration).not_to be_feature_available(:foo)
+ end
+ end
+
+ context 'with enough scopes for notifications' do
+ before do
+ slack_integration.update!(authorized_scope_names: %w[chat:write.public chat:write foo])
+ end
+
+ it 'only has the correct features' do
+ expect(slack_integration).to be_feature_available(:commands)
+ expect(slack_integration).to be_feature_available(:notifications)
+ expect(slack_integration).not_to be_feature_available(:foo)
+ end
+ end
+
+ context 'with enough scopes for commands' do
+ before do
+ slack_integration.update!(authorized_scope_names: %w[commands foo])
+ end
+
+ it 'only has the correct features' do
+ expect(slack_integration).to be_feature_available(:commands)
+ expect(slack_integration).not_to be_feature_available(:notifications)
+ expect(slack_integration).not_to be_feature_available(:foo)
+ end
+ end
+
+ context 'with all scopes' do
+ before do
+ slack_integration.update!(authorized_scope_names: %w[commands chat:write chat:write.public])
+ end
+
+ it 'only has the correct features' do
+ expect(slack_integration).to be_feature_available(:commands)
+ expect(slack_integration).to be_feature_available(:notifications)
+ expect(slack_integration).not_to be_feature_available(:foo)
+ end
+ end
+ end
+
+ describe 'Scopes' do
+ let_it_be(:slack_integration) { create(:slack_integration) }
+ let_it_be(:legacy_slack_integration) { create(:slack_integration, :legacy) }
+
+ describe '#with_bot' do
+ it 'returns records with bot data' do
+ expect(described_class.with_bot).to contain_exactly(slack_integration)
+ end
+ end
+
+ describe '#by_team' do
+ it 'returns records with shared team_id' do
+ team_id = slack_integration.team_id
+ team_slack_integration = create(:slack_integration, team_id: team_id)
+
+ expect(described_class.by_team(team_id)).to contain_exactly(slack_integration, team_slack_integration)
+ end
+ end
+ end
+
+ describe 'Validations' do
+ it { is_expected.to validate_presence_of(:team_id) }
+ it { is_expected.to validate_presence_of(:team_name) }
+ it { is_expected.to validate_presence_of(:alias) }
+ it { is_expected.to validate_presence_of(:user_id) }
+ it { is_expected.to validate_presence_of(:integration) }
+ end
+end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index bc677aca0f4..b36599b1273 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -105,9 +105,6 @@ RSpec.describe User, feature_category: :user_profile do
it { is_expected.to delegate_method(:registration_objective).to(:user_detail).allow_nil }
it { is_expected.to delegate_method(:registration_objective=).to(:user_detail).with_arguments(:args).allow_nil }
- it { is_expected.to delegate_method(:requires_credit_card_verification).to(:user_detail).allow_nil }
- it { is_expected.to delegate_method(:requires_credit_card_verification=).to(:user_detail).with_arguments(:args).allow_nil }
-
it { is_expected.to delegate_method(:discord).to(:user_detail).allow_nil }
it { is_expected.to delegate_method(:discord=).to(:user_detail).with_arguments(:args).allow_nil }