Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-06-10 00:09:51 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-06-10 00:09:51 +0300
commite374f6b2297582fde956350e92c19d1ae93ddfc8 (patch)
tree30be153d2883438955cfbf0d576052adc5875f67 /spec
parent1268cfeaf73a73c5593f13639530513716826e2b (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
-rw-r--r--spec/config/metrics/every_metric_definition_spec.rb38
-rw-r--r--spec/controllers/graphql_controller_spec.rb20
-rw-r--r--spec/lib/gitlab/avatar_cache_spec.rb60
-rw-r--r--spec/lib/gitlab/discussions_diff/highlight_cache_spec.rb88
-rw-r--r--spec/lib/gitlab/reactive_cache_set_cache_spec.rb34
-rw-r--r--spec/lib/gitlab/repository_set_cache_spec.rb74
-rw-r--r--spec/lib/gitlab/usage_data_counters/jetbrains_bundled_plugin_activity_unique_counter_spec.rb19
-rw-r--r--spec/services/notification_service_spec.rb182
-rw-r--r--spec/support/shared_examples/config/metrics/every_metric_definition_shared_examples.rb161
9 files changed, 464 insertions, 212 deletions
diff --git a/spec/config/metrics/every_metric_definition_spec.rb b/spec/config/metrics/every_metric_definition_spec.rb
new file mode 100644
index 00000000000..7492d6cdae5
--- /dev/null
+++ b/spec/config/metrics/every_metric_definition_spec.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Every metric definition', feature_category: :service_ping, unless: Gitlab.ee? do
+ include_examples "every metric definition" do
+ let(:ce_key_paths_mistakenly_defined_in_ee) do
+ %w[
+ counts.assignee_lists
+ counts.milestone_lists
+ counts.projects_with_repositories_enabled
+ counts.protected_branches
+ ].freeze
+ end
+
+ let(:ee_key_paths_mistakenly_defined_in_ce) do
+ %w[
+ counts.operations_dashboard_default_dashboard
+ counts.operations_dashboard_users_with_projects_added
+ usage_activity_by_stage.create.projects_imported_from_github
+ usage_activity_by_stage.monitor.operations_dashboard_users_with_projects_added
+ usage_activity_by_stage.plan.epics
+ usage_activity_by_stage.plan.label_lists
+ usage_activity_by_stage_monthly.create.projects_imported_from_github
+ usage_activity_by_stage_monthly.create.protected_branches
+ usage_activity_by_stage_monthly.monitor.operations_dashboard_users_with_projects_added
+ usage_activity_by_stage_monthly.plan.epics
+ usage_activity_by_stage_monthly.plan.label_lists
+ usage_activity_by_stage_monthly.secure.sast_pipeline
+ usage_activity_by_stage_monthly.secure.secret_detection_pipeline
+ ].freeze
+ end
+
+ let(:expected_metric_files_key_paths) do
+ metric_files_key_paths - ee_key_paths_mistakenly_defined_in_ce + ce_key_paths_mistakenly_defined_in_ee
+ end
+ end
+end
diff --git a/spec/controllers/graphql_controller_spec.rb b/spec/controllers/graphql_controller_spec.rb
index 8281cd595f8..b1399835b45 100644
--- a/spec/controllers/graphql_controller_spec.rb
+++ b/spec/controllers/graphql_controller_spec.rb
@@ -224,6 +224,16 @@ RSpec.describe GraphqlController, feature_category: :integrations do
post :execute
end
+ it 'calls the track jetbrains bundled third party api when trackable method' do
+ agent = 'IntelliJ-GitLab-Plugin PhpStorm/PS-232.6734.11 (JRE 17.0.7+7-b966.2; Linux 6.2.0-20-generic; amd64)'
+ request.env['HTTP_USER_AGENT'] = agent
+
+ expect(Gitlab::UsageDataCounters::JetBrainsBundledPluginActivityUniqueCounter)
+ .to receive(:track_api_request_when_trackable).with(user_agent: agent, user: user)
+
+ post :execute
+ end
+
context 'if using the GitLab CLI' do
it 'call trackable for the old UserAgent' do
agent = 'GLab - GitLab CLI'
@@ -359,6 +369,16 @@ RSpec.describe GraphqlController, feature_category: :integrations do
subject
end
+ it 'calls the track jetbrains bundled third party api when trackable method' do
+ agent = 'IntelliJ-GitLab-Plugin PhpStorm/PS-232.6734.11 (JRE 17.0.7+7-b966.2; Linux 6.2.0-20-generic; amd64)'
+ request.env['HTTP_USER_AGENT'] = agent
+
+ expect(Gitlab::UsageDataCounters::JetBrainsBundledPluginActivityUniqueCounter)
+ .to receive(:track_api_request_when_trackable).with(user_agent: agent, user: user)
+
+ subject
+ end
+
it 'calls the track gitlab cli when trackable method' do
agent = 'GLab - GitLab CLI'
request.env['HTTP_USER_AGENT'] = agent
diff --git a/spec/lib/gitlab/avatar_cache_spec.rb b/spec/lib/gitlab/avatar_cache_spec.rb
index 35ba767a5c5..c959c5d80b2 100644
--- a/spec/lib/gitlab/avatar_cache_spec.rb
+++ b/spec/lib/gitlab/avatar_cache_spec.rb
@@ -62,62 +62,54 @@ RSpec.describe Gitlab::AvatarCache, :clean_gitlab_redis_cache do
end
describe "#delete_by_email" do
- shared_examples 'delete emails' do
- subject { described_class.delete_by_email(*emails) }
+ subject { described_class.delete_by_email(*emails) }
- before do
- perform_fetch
- end
+ before do
+ perform_fetch
+ end
- context "no emails, somehow" do
- let(:emails) { [] }
+ context "no emails, somehow" do
+ let(:emails) { [] }
- it { is_expected.to eq(0) }
- end
+ it { is_expected.to eq(0) }
+ end
- context "single email" do
- let(:emails) { "foo@bar.com" }
+ context "single email" do
+ let(:emails) { "foo@bar.com" }
- it "removes the email" do
- expect(read(key, "20:2:true")).to eq(avatar_path)
+ it "removes the email" do
+ expect(read(key, "20:2:true")).to eq(avatar_path)
- expect(subject).to eq(1)
+ expect(subject).to eq(1)
- expect(read(key, "20:2:true")).to eq(nil)
- end
+ expect(read(key, "20:2:true")).to eq(nil)
end
+ end
- context "multiple emails" do
- let(:emails) { ["foo@bar.com", "missing@baz.com"] }
+ context "multiple emails" do
+ let(:emails) { ["foo@bar.com", "missing@baz.com"] }
- it "removes the emails it finds" do
- expect(read(key, "20:2:true")).to eq(avatar_path)
+ it "removes the emails it finds" do
+ expect(read(key, "20:2:true")).to eq(avatar_path)
- expect(subject).to eq(1)
+ expect(subject).to eq(1)
- expect(read(key, "20:2:true")).to eq(nil)
- end
+ expect(read(key, "20:2:true")).to eq(nil)
end
end
context 'when deleting over 1000 emails' do
it 'deletes in batches of 1000' do
Gitlab::Redis::Cache.with do |redis|
- expect(redis).to receive(:pipelined).at_least(2).and_call_original
+ if Gitlab::Redis::ClusterUtil.cluster?(redis)
+ expect(redis).to receive(:pipelined).at_least(2).and_call_original
+ else
+ expect(redis).to receive(:unlink).and_call_original
+ end
end
described_class.delete_by_email(*(Array.new(1001) { |i| i }))
end
end
-
- context 'when feature flag disabled' do
- before do
- stub_feature_flags(use_pipeline_over_multikey: false)
- end
-
- it_behaves_like 'delete emails'
- end
-
- it_behaves_like 'delete emails'
end
end
diff --git a/spec/lib/gitlab/discussions_diff/highlight_cache_spec.rb b/spec/lib/gitlab/discussions_diff/highlight_cache_spec.rb
index 0dc0f50b104..30981e4bd7d 100644
--- a/spec/lib/gitlab/discussions_diff/highlight_cache_spec.rb
+++ b/spec/lib/gitlab/discussions_diff/highlight_cache_spec.rb
@@ -41,81 +41,57 @@ RSpec.describe Gitlab::DiscussionsDiff::HighlightCache, :clean_gitlab_redis_cach
end
describe '#read_multiple' do
- shared_examples 'read multiple keys' do
- it 'reads multiple keys and serializes content into Gitlab::Diff::Line objects' do
- described_class.write_multiple(mapping)
-
- found = described_class.read_multiple(mapping.keys)
-
- expect(found.size).to eq(2)
- expect(found.first.size).to eq(2)
- expect(found.first).to all(be_a(Gitlab::Diff::Line))
- end
-
- it 'returns nil when cached key is not found' do
- described_class.write_multiple(mapping)
+ it 'reads multiple keys and serializes content into Gitlab::Diff::Line objects' do
+ described_class.write_multiple(mapping)
- found = described_class.read_multiple([2, 3])
+ found = described_class.read_multiple(mapping.keys)
- expect(found.size).to eq(2)
+ expect(found.size).to eq(2)
+ expect(found.first.size).to eq(2)
+ expect(found.first).to all(be_a(Gitlab::Diff::Line))
+ end
- expect(found.first).to eq(nil)
- expect(found.second.size).to eq(2)
- expect(found.second).to all(be_a(Gitlab::Diff::Line))
- end
+ it 'returns nil when cached key is not found' do
+ described_class.write_multiple(mapping)
- it 'returns lines which rich_text are HTML-safe' do
- described_class.write_multiple(mapping)
+ found = described_class.read_multiple([2, 3])
- found = described_class.read_multiple(mapping.keys)
- rich_texts = found.flatten.map(&:rich_text)
+ expect(found.size).to eq(2)
- expect(rich_texts).to all(be_html_safe)
- end
+ expect(found.first).to eq(nil)
+ expect(found.second.size).to eq(2)
+ expect(found.second).to all(be_a(Gitlab::Diff::Line))
end
- context 'when feature flag is disabled' do
- before do
- stub_feature_flags(use_pipeline_over_multikey: false)
- end
+ it 'returns lines which rich_text are HTML-safe' do
+ described_class.write_multiple(mapping)
- it_behaves_like 'read multiple keys'
- end
+ found = described_class.read_multiple(mapping.keys)
+ rich_texts = found.flatten.map(&:rich_text)
- it_behaves_like 'read multiple keys'
+ expect(rich_texts).to all(be_html_safe)
+ end
end
describe '#clear_multiple' do
- shared_examples 'delete multiple keys' do
- it 'removes all named keys' do
- described_class.write_multiple(mapping)
-
- described_class.clear_multiple(mapping.keys)
-
- expect(described_class.read_multiple(mapping.keys)).to all(be_nil)
- end
+ it 'removes all named keys' do
+ described_class.write_multiple(mapping)
- it 'only removed named keys' do
- to_clear, to_leave = mapping.keys
+ described_class.clear_multiple(mapping.keys)
- described_class.write_multiple(mapping)
- described_class.clear_multiple([to_clear])
+ expect(described_class.read_multiple(mapping.keys)).to all(be_nil)
+ end
- cleared, left = described_class.read_multiple([to_clear, to_leave])
+ it 'only removed named keys' do
+ to_clear, to_leave = mapping.keys
- expect(cleared).to be_nil
- expect(left).to all(be_a(Gitlab::Diff::Line))
- end
- end
+ described_class.write_multiple(mapping)
+ described_class.clear_multiple([to_clear])
- context 'when feature flag is disabled' do
- before do
- stub_feature_flags(use_pipeline_over_multikey: false)
- end
+ cleared, left = described_class.read_multiple([to_clear, to_leave])
- it_behaves_like 'delete multiple keys'
+ expect(cleared).to be_nil
+ expect(left).to all(be_a(Gitlab::Diff::Line))
end
-
- it_behaves_like 'delete multiple keys'
end
end
diff --git a/spec/lib/gitlab/reactive_cache_set_cache_spec.rb b/spec/lib/gitlab/reactive_cache_set_cache_spec.rb
index 9956078999f..44bbe888c64 100644
--- a/spec/lib/gitlab/reactive_cache_set_cache_spec.rb
+++ b/spec/lib/gitlab/reactive_cache_set_cache_spec.rb
@@ -46,26 +46,16 @@ RSpec.describe Gitlab::ReactiveCacheSetCache, :clean_gitlab_redis_cache do
end
describe '#clear_cache!', :use_clean_rails_redis_caching do
- shared_examples 'clears cache' do
- it 'deletes the cached items' do
- # Cached key and value
- Rails.cache.write('test_item', 'test_value')
- # Add key to set
- cache.write(cache_prefix, 'test_item')
-
- expect(cache.read(cache_prefix)).to contain_exactly('test_item')
- cache.clear_cache!(cache_prefix)
-
- expect(cache.read(cache_prefix)).to be_empty
- end
- end
+ it 'deletes the cached items' do
+ # Cached key and value
+ Rails.cache.write('test_item', 'test_value')
+ # Add key to set
+ cache.write(cache_prefix, 'test_item')
- context 'when featuer flag disabled' do
- before do
- stub_feature_flags(use_pipeline_over_multikey: false)
- end
+ expect(cache.read(cache_prefix)).to contain_exactly('test_item')
+ cache.clear_cache!(cache_prefix)
- it_behaves_like 'clears cache'
+ expect(cache.read(cache_prefix)).to be_empty
end
context 'when key size is large' do
@@ -75,14 +65,16 @@ RSpec.describe Gitlab::ReactiveCacheSetCache, :clean_gitlab_redis_cache do
it 'sends multiple pipelines of 1000 unlinks' do
Gitlab::Redis::Cache.with do |redis|
- expect(redis).to receive(:pipelined).at_least(2).and_call_original
+ if Gitlab::Redis::ClusterUtil.cluster?(redis)
+ expect(redis).to receive(:pipelined).at_least(2).and_call_original
+ else
+ expect(redis).to receive(:pipelined).once.and_call_original
+ end
end
cache.clear_cache!(cache_prefix)
end
end
-
- it_behaves_like 'clears cache'
end
describe '#include?' do
diff --git a/spec/lib/gitlab/repository_set_cache_spec.rb b/spec/lib/gitlab/repository_set_cache_spec.rb
index dc2081fe3a9..23b2a2b9493 100644
--- a/spec/lib/gitlab/repository_set_cache_spec.rb
+++ b/spec/lib/gitlab/repository_set_cache_spec.rb
@@ -77,70 +77,64 @@ RSpec.describe Gitlab::RepositorySetCache, :clean_gitlab_redis_repository_cache,
end
describe '#expire' do
- shared_examples 'expires varying amount of keys' do
- subject { cache.expire(*keys) }
+ subject { cache.expire(*keys) }
- before do
- cache.write(:foo, ['value'])
- cache.write(:bar, ['value2'])
- end
+ before do
+ cache.write(:foo, ['value'])
+ cache.write(:bar, ['value2'])
+ end
- it 'actually wrote the values' do
- expect(cache.read(:foo)).to contain_exactly('value')
- expect(cache.read(:bar)).to contain_exactly('value2')
- end
+ it 'actually wrote the values' do
+ expect(cache.read(:foo)).to contain_exactly('value')
+ expect(cache.read(:bar)).to contain_exactly('value2')
+ end
- context 'single key' do
- let(:keys) { %w(foo) }
+ context 'single key' do
+ let(:keys) { %w(foo) }
- it { is_expected.to eq(1) }
+ it { is_expected.to eq(1) }
- it 'deletes the given key from the cache' do
- subject
+ it 'deletes the given key from the cache' do
+ subject
- expect(cache.read(:foo)).to be_empty
- end
+ expect(cache.read(:foo)).to be_empty
end
+ end
- context 'multiple keys' do
- let(:keys) { %w(foo bar) }
+ context 'multiple keys' do
+ let(:keys) { %w(foo bar) }
- it { is_expected.to eq(2) }
+ it { is_expected.to eq(2) }
- it 'deletes the given keys from the cache' do
- subject
+ it 'deletes the given keys from the cache' do
+ subject
- expect(cache.read(:foo)).to be_empty
- expect(cache.read(:bar)).to be_empty
- end
+ expect(cache.read(:foo)).to be_empty
+ expect(cache.read(:bar)).to be_empty
end
+ end
- context 'no keys' do
- let(:keys) { [] }
+ context 'no keys' do
+ let(:keys) { [] }
- it { is_expected.to eq(0) }
- end
+ it { is_expected.to eq(0) }
end
context 'when deleting over 1000 keys' do
it 'deletes in batches of 1000' do
Gitlab::Redis::RepositoryCache.with do |redis|
- expect(redis).to receive(:pipelined).at_least(2).and_call_original
+ # In a Redis Cluster, we do not want a pipeline to have too many keys
+ # but in a standalone Redis, multi-key commands can be used.
+ if ::Gitlab::Redis::ClusterUtil.cluster?(redis)
+ expect(redis).to receive(:pipelined).at_least(2).and_call_original
+ else
+ expect(redis).to receive(:unlink).and_call_original
+ end
end
cache.expire(*(Array.new(1001) { |i| i }))
end
end
-
- context 'when feature flag is disabled' do
- before do
- stub_feature_flags(use_pipeline_over_multikey: false)
- end
-
- it_behaves_like 'expires varying amount of keys'
- end
-
- it_behaves_like 'expires varying amount of keys'
end
describe '#exist?' do
diff --git a/spec/lib/gitlab/usage_data_counters/jetbrains_bundled_plugin_activity_unique_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/jetbrains_bundled_plugin_activity_unique_counter_spec.rb
new file mode 100644
index 00000000000..e034f04ff92
--- /dev/null
+++ b/spec/lib/gitlab/usage_data_counters/jetbrains_bundled_plugin_activity_unique_counter_spec.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::UsageDataCounters::JetBrainsBundledPluginActivityUniqueCounter, :clean_gitlab_redis_shared_state, feature_category: :editor_extensions do # rubocop:disable RSpec/FilePath
+ let(:user1) { build(:user, id: 1) }
+ let(:user2) { build(:user, id: 2) }
+ let(:time) { Time.current }
+ let(:action) { described_class::JETBRAINS_BUNDLED_API_REQUEST_ACTION }
+ let(:user_agent_string) do
+ 'IntelliJ-GitLab-Plugin PhpStorm/PS-232.6734.11 (JRE 17.0.7+7-b966.2; Linux 6.2.0-20-generic; amd64)'
+ end
+
+ let(:user_agent) { { user_agent: user_agent_string } }
+
+ context 'when tracking a jetbrains bundled api request' do
+ it_behaves_like 'a request from an extension'
+ end
+end
diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb
index f63f982708d..0e1afaa8378 100644
--- a/spec/services/notification_service_spec.rb
+++ b/spec/services/notification_service_spec.rb
@@ -800,7 +800,21 @@ RSpec.describe NotificationService, :mailer, feature_category: :team_planning do
let_it_be(:mentioned_issue) { create(:issue, assignees: issue.assignees) }
let_it_be(:author) { create(:user) }
- let(:note) { create(:note_on_issue, author: author, noteable: issue, project_id: issue.project_id, note: '@all mentioned') }
+ let(:user_mentions) do
+ other_members = [
+ @unsubscribed_mentioned,
+ @u_guest_watcher,
+ @pg_watcher,
+ @u_mentioned,
+ @u_not_mentioned,
+ @u_disabled,
+ @pg_disabled
+ ]
+
+ (issue.project.team.members + other_members).map(&:to_reference).join(' ')
+ end
+
+ let(:note) { create(:note_on_issue, author: author, noteable: issue, project_id: issue.project_id, note: note_content) }
before_all do
build_team(project)
@@ -815,73 +829,103 @@ RSpec.describe NotificationService, :mailer, feature_category: :team_planning do
end
describe '#new_note' do
- it 'notifies the team members' do
+ it 'notifies parent group members with mention level' do
+ note = create(:note_on_issue, noteable: issue, project_id: issue.project_id, note: "@#{@pg_mention.username}")
+
notification.new_note(note)
- # Make sure @unsubscribed_mentioned is part of the team
- expect(note.project.team.members).to include(@unsubscribed_mentioned)
+ should_email_nested_group_user(@pg_mention)
+ end
+
+ shared_examples 'correct team members are notified' do
+ it 'notifies the team members' do
+ notification.new_note(note)
- # Notify all team members
- note.project.team.members.each do |member|
- # User with disabled notification should not be notified
- next if member.id == @u_disabled.id
- # Author should not be notified
- next if member.id == note.author.id
+ # Make sure @unsubscribed_mentioned is part of the team
+ expect(note.project.team.members).to include(@unsubscribed_mentioned)
- should_email(member)
+ # Notify all team members
+ note.project.team.members.each do |member|
+ # User with disabled notification should not be notified
+ next if member.id == @u_disabled.id
+ # Author should not be notified
+ next if member.id == note.author.id
+
+ should_email(member)
+ end
+
+ should_email(@u_guest_watcher)
+ should_email(note.noteable.author)
+ should_email(note.noteable.assignees.first)
+ should_email_nested_group_user(@pg_watcher)
+ should_email(@u_mentioned)
+ should_email(@u_not_mentioned)
+ should_not_email(note.author)
+ should_not_email(@u_disabled)
+ should_not_email_nested_group_user(@pg_disabled)
end
- should_email(@u_guest_watcher)
- should_email(note.noteable.author)
- should_email(note.noteable.assignees.first)
- should_email_nested_group_user(@pg_watcher)
- should_email(@u_mentioned)
- should_email(@u_not_mentioned)
- should_not_email(note.author)
- should_not_email(@u_disabled)
- should_not_email_nested_group_user(@pg_disabled)
- end
+ it 'filters out "mentioned in" notes' do
+ mentioned_note = SystemNoteService.cross_reference(mentioned_issue, issue, issue.author)
- it 'notifies parent group members with mention level' do
- note = create(:note_on_issue, noteable: issue, project_id: issue.project_id, note: "@#{@pg_mention.username}")
+ expect(Notify).not_to receive(:note_issue_email)
+ notification.new_note(mentioned_note)
+ end
- notification.new_note(note)
+ it_behaves_like 'project emails are disabled' do
+ let(:notification_target) { note }
+ let(:notification_trigger) { notification.new_note(note) }
+ end
- should_email_nested_group_user(@pg_mention)
- end
+ context 'when note is confidential' do
+ let(:note) { create(:note_on_issue, author: author, noteable: issue, project_id: issue.project_id, note: note_content, confidential: true) }
+ let(:guest) { create(:user) }
- it 'filters out "mentioned in" notes' do
- mentioned_note = SystemNoteService.cross_reference(mentioned_issue, issue, issue.author)
+ it 'does not notify users that cannot read note' do
+ project.add_guest(guest)
+ reset_delivered_emails!
- expect(Notify).not_to receive(:note_issue_email)
- notification.new_note(mentioned_note)
- end
+ notification.new_note(note)
- it_behaves_like 'project emails are disabled' do
- let(:notification_target) { note }
- let(:notification_trigger) { notification.new_note(note) }
+ should_not_email(guest)
+ end
+ end
end
- context 'when note is confidential' do
- let(:note) { create(:note_on_issue, author: author, noteable: issue, project_id: issue.project_id, note: '@all mentioned', confidential: true) }
- let(:guest) { create(:user) }
+ context 'when `@all` mention is used' do
+ let(:note_content) { "@all mentioned" }
- it 'does not notify users that cannot read note' do
- project.add_guest(guest)
- reset_delivered_emails!
+ it_behaves_like 'correct team members are notified'
+ end
- notification.new_note(note)
+ context 'when users are individually mentioned' do
+ # `user_mentions` is concatenanting individual user mentions
+ # so that the end result is the same as `@all`.
+ let(:note_content) { "#{user_mentions} mentioned" }
- should_not_email(guest)
- end
+ it_behaves_like 'correct team members are notified'
end
end
end
context 'project snippet note', :deliver_mails_inline do
+ let(:user_mentions) do
+ other_members = [
+ @u_custom_global,
+ @u_guest_watcher,
+ snippet.author, # snippet = note.noteable's author
+ author, # note's author
+ @u_disabled,
+ @u_mentioned,
+ @u_not_mentioned
+ ]
+
+ (snippet.project.team.members + other_members).map(&:to_reference).join(' ')
+ end
+
let(:snippet) { create(:project_snippet, project: project, author: create(:user)) }
let(:author) { create(:user) }
- let(:note) { create(:note_on_project_snippet, author: author, noteable: snippet, project_id: project.id, note: '@all mentioned') }
+ let(:note) { create(:note_on_project_snippet, author: author, noteable: snippet, project_id: project.id, note: note_content) }
before do
build_team(project)
@@ -896,27 +940,43 @@ RSpec.describe NotificationService, :mailer, feature_category: :team_planning do
end
describe '#new_note' do
- it 'notifies the team members' do
- notification.new_note(note)
- # Notify all team members
- note.project.team.members.each do |member|
- # User with disabled notification should not be notified
- next if member.id == @u_disabled.id
- # Author should not be notified
- next if member.id == note.author.id
+ shared_examples 'correct team members are notified' do
+ it 'notifies the team members' do
+ notification.new_note(note)
+ # Notify all team members
+ note.project.team.members.each do |member|
+ # User with disabled notification should not be notified
+ next if member.id == @u_disabled.id
+ # Author should not be notified
+ next if member.id == note.author.id
+
+ should_email(member)
+ end
- should_email(member)
+ # it emails custom global users on mention
+ should_email(@u_custom_global)
+
+ should_email(@u_guest_watcher)
+ should_email(note.noteable.author)
+ should_not_email(note.author)
+ should_email(@u_mentioned)
+ should_not_email(@u_disabled)
+ should_email(@u_not_mentioned)
end
+ end
- # it emails custom global users on mention
- should_email(@u_custom_global)
+ context 'when `@all` mention is used' do
+ let(:note_content) { "@all mentioned" }
- should_email(@u_guest_watcher)
- should_email(note.noteable.author)
- should_not_email(note.author)
- should_email(@u_mentioned)
- should_not_email(@u_disabled)
- should_email(@u_not_mentioned)
+ it_behaves_like 'correct team members are notified'
+ end
+
+ context 'when users are individually mentioned' do
+ # `user_mentions` is concatenanting individual user mentions
+ # so that the end result is the same as `@all`.
+ let(:note_content) { "#{user_mentions} mentioned" }
+
+ it_behaves_like 'correct team members are notified'
end
end
end
diff --git a/spec/support/shared_examples/config/metrics/every_metric_definition_shared_examples.rb b/spec/support/shared_examples/config/metrics/every_metric_definition_shared_examples.rb
new file mode 100644
index 00000000000..c8eaef764af
--- /dev/null
+++ b/spec/support/shared_examples/config/metrics/every_metric_definition_shared_examples.rb
@@ -0,0 +1,161 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'every metric definition' do
+ include UsageDataHelpers
+
+ let(:usage_ping) { Gitlab::Usage::ServicePingReport.for(output: :all_metrics_values, cached: false) }
+ let(:ignored_usage_ping_key_patterns) do
+ %w[
+ testing_total_unique_counts
+ user_auth_by_provider
+ ].freeze
+ end
+
+ let(:usage_ping_key_paths) do
+ parse_usage_ping_keys(usage_ping)
+ .flatten
+ .grep_v(Regexp.union(ignored_usage_ping_key_patterns))
+ .sort
+ end
+
+ let(:ignored_metric_files_key_patterns) do
+ %w[
+ ci_runners_online
+ mock_ci
+ mock_monitoring
+ user_auth_by_provider
+ p_ci_templates_5_min_production_app
+ p_ci_templates_aws_cf_deploy_ec2
+ p_ci_templates_auto_devops_build
+ p_ci_templates_auto_devops_deploy
+ p_ci_templates_auto_devops_deploy_latest
+ p_ci_templates_implicit_auto_devops_build
+ p_ci_templates_implicit_auto_devops_deploy_latest
+ p_ci_templates_implicit_auto_devops_deploy
+ ].freeze
+ end
+
+ let(:metric_files_key_paths) do
+ Gitlab::Usage::MetricDefinition
+ .definitions
+ .reject { |_, v| v.status == 'removed' || v.key_path =~ Regexp.union(ignored_metric_files_key_patterns) }
+ .keys
+ .sort
+ end
+
+ let(:metric_files_with_schema) do
+ Gitlab::Usage::MetricDefinition
+ .definitions
+ .select { |_, v| v.respond_to?(:value_json_schema) }
+ end
+
+ let(:expected_metric_files_key_paths) { metric_files_key_paths }
+
+ # Recursively traverse nested Hash of a generated Usage Ping to return an Array of key paths
+ # in the dotted format used in metric definition YAML files, e.g.: 'count.category.metric_name'
+ def parse_usage_ping_keys(object, key_path = [])
+ if object.is_a?(Hash) && !object_with_schema?(key_path.join('.'))
+ object.each_with_object([]) do |(key, value), result|
+ result.append parse_usage_ping_keys(value, key_path + [key])
+ end
+ else
+ key_path.join('.')
+ end
+ end
+
+ def object_with_schema?(key_path)
+ metric_files_with_schema.key?(key_path)
+ end
+
+ before do
+ allow(Gitlab::UsageData).to receive_messages(count: -1, distinct_count: -1, estimate_batch_distinct_count: -1,
+ sum: -1)
+ allow(Gitlab::UsageData).to receive(:alt_usage_data).and_wrap_original do |_m, *_args, **kwargs|
+ kwargs[:fallback] || Gitlab::Utils::UsageData::FALLBACK
+ end
+ stub_licensed_features(requirements: true)
+ stub_prometheus_queries
+ stub_usage_data_connections
+ end
+
+ it 'is included in the Usage Ping hash structure' do
+ msg = "see https://docs.gitlab.com/ee/development/service_ping/metrics_dictionary.html#metrics-added-dynamic-to-service-ping-payload"
+ expect(expected_metric_files_key_paths).to match_array(usage_ping_key_paths), msg
+ end
+
+ it 'only uses .yml and .json formats from metric related files in (ee/)config/metrics directory' do
+ metric_definition_format = '.yml'
+ object_schema_format = '.json'
+ allowed_formats = [metric_definition_format, object_schema_format]
+ glob_paths = Gitlab::Usage::MetricDefinition.paths.map do |glob_path|
+ File.join(File.dirname(glob_path), '*.*')
+ end
+
+ files_with_wrong_extensions = glob_paths.each_with_object([]) do |glob_path, array|
+ Dir.glob(glob_path).each do |path|
+ array << path unless allowed_formats.include? File.extname(path)
+ end
+ end
+
+ msg = <<~MSG
+ The only supported file extensions are: #{allowed_formats.join(', ')}.
+ The following files has the wrong extension: #{files_with_wrong_extensions}"
+ MSG
+
+ expect(files_with_wrong_extensions).to be_empty, msg
+ end
+
+ describe 'metrics classes' do
+ let(:parent_metric_classes) do
+ [
+ Gitlab::Usage::Metrics::Instrumentations::BaseMetric,
+ Gitlab::Usage::Metrics::Instrumentations::GenericMetric,
+ Gitlab::Usage::Metrics::Instrumentations::DatabaseMetric,
+ Gitlab::Usage::Metrics::Instrumentations::RedisMetric,
+ Gitlab::Usage::Metrics::Instrumentations::RedisHLLMetric,
+ Gitlab::Usage::Metrics::Instrumentations::NumbersMetric
+ ]
+ end
+
+ let(:ignored_classes) do
+ [
+ Gitlab::Usage::Metrics::Instrumentations::IssuesWithAlertManagementAlertsMetric,
+ Gitlab::Usage::Metrics::Instrumentations::IssuesWithPrometheusAlertEvents,
+ Gitlab::Usage::Metrics::Instrumentations::IssuesWithSelfManagedPrometheusAlertEvents
+ ].freeze
+ end
+
+ def assert_uses_all_nested_classes(parent_module)
+ parent_module.constants(false).each do |const_name|
+ constant = parent_module.const_get(const_name, false)
+ next if parent_metric_classes.include?(constant) || ignored_classes.include?(constant)
+
+ case constant
+ when Class
+ metric_class_instance = instance_double(constant)
+ expect(constant).to receive(:new).at_least(:once).and_return(metric_class_instance)
+ allow(metric_class_instance).to receive(:available?).and_return(true)
+ allow(metric_class_instance).to receive(:value).and_return(-1)
+ expect(metric_class_instance).to receive(:value).at_least(:once)
+ when Module
+ assert_uses_all_nested_classes(constant)
+ end
+ end
+ end
+
+ it 'uses all metrics classes' do
+ assert_uses_all_nested_classes(Gitlab::Usage::Metrics::Instrumentations)
+ usage_ping
+ end
+ end
+
+ context 'with value json schema' do
+ it 'has a valid structure', :aggregate_failures do
+ metric_files_with_schema.each do |key_path, metric|
+ structure = usage_ping.dig(*key_path.split('.').map(&:to_sym))
+
+ expect(structure).to match_metric_definition_schema(metric.value_json_schema)
+ end
+ end
+ end
+end