diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-06-10 00:09:51 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-06-10 00:09:51 +0300 |
commit | e374f6b2297582fde956350e92c19d1ae93ddfc8 (patch) | |
tree | 30be153d2883438955cfbf0d576052adc5875f67 /spec | |
parent | 1268cfeaf73a73c5593f13639530513716826e2b (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
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 |