diff options
Diffstat (limited to 'spec/lib/gitlab/usage/service_ping_report_spec.rb')
-rw-r--r-- | spec/lib/gitlab/usage/service_ping_report_spec.rb | 222 |
1 files changed, 186 insertions, 36 deletions
diff --git a/spec/lib/gitlab/usage/service_ping_report_spec.rb b/spec/lib/gitlab/usage/service_ping_report_spec.rb index 9b9b24ad128..1f62ddd0bbb 100644 --- a/spec/lib/gitlab/usage/service_ping_report_spec.rb +++ b/spec/lib/gitlab/usage/service_ping_report_spec.rb @@ -3,66 +3,216 @@ require 'spec_helper' RSpec.describe Gitlab::Usage::ServicePingReport, :use_clean_rails_memory_store_caching do - let(:usage_data) { { uuid: "1111" } } + include UsageDataHelpers - context 'for output: :all_metrics_values' do - it 'generates the service ping' do - expect(Gitlab::UsageData).to receive(:data) + let(:usage_data) { { uuid: "1111", counts: { issue: 0 } } } - described_class.for(output: :all_metrics_values) + context 'when feature merge_service_ping_instrumented_metrics enabled' do + before do + stub_feature_flags(merge_service_ping_instrumented_metrics: true) + + allow_next_instance_of(Gitlab::Usage::ServicePing::PayloadKeysProcessor) do |instance| + allow(instance).to receive(:missing_key_paths).and_return([]) + end + + allow_next_instance_of(Gitlab::Usage::ServicePing::InstrumentedPayload) do |instance| + allow(instance).to receive(:build).and_return({}) + end end - end - context 'for output: :metrics_queries' do - it 'generates the service ping' do - expect(Gitlab::UsageDataQueries).to receive(:data) + context 'all_metrics_values' do + it 'generates the service ping when there are no missing values' do + expect(Gitlab::UsageData).to receive(:data).and_return(usage_data) + expect(described_class.for(output: :all_metrics_values)).to eq({ uuid: "1111", counts: { issue: 0 } }) + end - described_class.for(output: :metrics_queries) + it 'generates the service ping with the missing values' do + expect_next_instance_of(Gitlab::Usage::ServicePing::PayloadKeysProcessor, usage_data) do |instance| + expect(instance).to receive(:missing_instrumented_metrics_key_paths).and_return(['counts.boards']) + end + + expect_next_instance_of(Gitlab::Usage::ServicePing::InstrumentedPayload, ['counts.boards'], :with_value) do |instance| + expect(instance).to receive(:build).and_return({ counts: { boards: 1 } }) + end + + expect(Gitlab::UsageData).to receive(:data).and_return(usage_data) + expect(described_class.for(output: :all_metrics_values)).to eq({ uuid: "1111", counts: { issue: 0, boards: 1 } }) + end end - end - context 'for output: :non_sql_metrics_values' do - it 'generates the service ping' do - expect(Gitlab::UsageDataNonSqlMetrics).to receive(:data) + context 'for output: :metrics_queries' do + it 'generates the service ping' do + expect(Gitlab::UsageData).to receive(:data).and_return(usage_data) + + described_class.for(output: :metrics_queries) + end + end + + context 'for output: :non_sql_metrics_values' do + it 'generates the service ping' do + expect(Gitlab::UsageData).to receive(:data).and_return(usage_data) - described_class.for(output: :non_sql_metrics_values) + described_class.for(output: :non_sql_metrics_values) + end + end + + context 'when using cached' do + context 'for cached: true' do + let(:new_usage_data) { { uuid: "1112" } } + + it 'caches the values' do + allow(Gitlab::UsageData).to receive(:data).and_return(usage_data, new_usage_data) + + expect(described_class.for(output: :all_metrics_values)).to eq(usage_data) + expect(described_class.for(output: :all_metrics_values, cached: true)).to eq(usage_data) + + expect(Rails.cache.fetch('usage_data')).to eq(usage_data) + end + + it 'writes to cache and returns fresh data' do + allow(Gitlab::UsageData).to receive(:data).and_return(usage_data, new_usage_data) + + expect(described_class.for(output: :all_metrics_values)).to eq(usage_data) + expect(described_class.for(output: :all_metrics_values)).to eq(new_usage_data) + expect(described_class.for(output: :all_metrics_values, cached: true)).to eq(new_usage_data) + + expect(Rails.cache.fetch('usage_data')).to eq(new_usage_data) + end + end + + context 'when no caching' do + let(:new_usage_data) { { uuid: "1112" } } + + it 'returns fresh data' do + allow(Gitlab::UsageData).to receive(:data).and_return(usage_data, new_usage_data) + + expect(described_class.for(output: :all_metrics_values)).to eq(usage_data) + expect(described_class.for(output: :all_metrics_values)).to eq(new_usage_data) + + expect(Rails.cache.fetch('usage_data')).to eq(new_usage_data) + end + end end end - context 'when using cached' do - context 'for cached: true' do - let(:new_usage_data) { { uuid: "1112" } } + context 'when feature merge_service_ping_instrumented_metrics disabled' do + before do + stub_feature_flags(merge_service_ping_instrumented_metrics: false) + end - it 'caches the values' do - allow(Gitlab::UsageData).to receive(:data).and_return(usage_data, new_usage_data) + context 'all_metrics_values' do + it 'generates the service ping when there are no missing values' do + expect(Gitlab::UsageData).to receive(:data).and_return(usage_data) + expect(described_class.for(output: :all_metrics_values)).to eq({ uuid: "1111", counts: { issue: 0 } }) + end + end - expect(described_class.for(output: :all_metrics_values)).to eq(usage_data) - expect(described_class.for(output: :all_metrics_values, cached: true)).to eq(usage_data) + context 'for output: :metrics_queries' do + it 'generates the service ping' do + expect(Gitlab::UsageData).to receive(:data).and_return(usage_data) - expect(Rails.cache.fetch('usage_data')).to eq(usage_data) + described_class.for(output: :metrics_queries) end + end - it 'writes to cache and returns fresh data' do - allow(Gitlab::UsageData).to receive(:data).and_return(usage_data, new_usage_data) + context 'for output: :non_sql_metrics_values' do + it 'generates the service ping' do + expect(Gitlab::UsageData).to receive(:data).and_return(usage_data) - expect(described_class.for(output: :all_metrics_values)).to eq(usage_data) - expect(described_class.for(output: :all_metrics_values)).to eq(new_usage_data) - expect(described_class.for(output: :all_metrics_values, cached: true)).to eq(new_usage_data) + described_class.for(output: :non_sql_metrics_values) + end + end + end + + context 'cross test values against queries' do + # TODO: fix failing metrics https://gitlab.com/gitlab-org/gitlab/-/issues/353559 + let(:failing_todo_metrics) do + ["counts.labels", + "counts.jira_imports_total_imported_issues_count", + "counts.in_product_marketing_email_create_0_sent", + "counts.in_product_marketing_email_create_0_cta_clicked", + "counts.in_product_marketing_email_create_1_sent", + "counts.in_product_marketing_email_create_1_cta_clicked", + "counts.in_product_marketing_email_create_2_sent", + "counts.in_product_marketing_email_create_2_cta_clicked", + "counts.in_product_marketing_email_verify_0_sent", + "counts.in_product_marketing_email_verify_0_cta_clicked", + "counts.in_product_marketing_email_verify_1_sent", + "counts.in_product_marketing_email_verify_1_cta_clicked", + "counts.in_product_marketing_email_verify_2_sent", + "counts.in_product_marketing_email_verify_2_cta_clicked", + "counts.in_product_marketing_email_trial_0_sent", + "counts.in_product_marketing_email_trial_0_cta_clicked", + "counts.in_product_marketing_email_trial_1_sent", + "counts.in_product_marketing_email_trial_1_cta_clicked", + "counts.in_product_marketing_email_trial_2_sent", + "counts.in_product_marketing_email_trial_2_cta_clicked", + "counts.in_product_marketing_email_team_0_sent", + "counts.in_product_marketing_email_team_0_cta_clicked", + "counts.in_product_marketing_email_team_1_sent", + "counts.in_product_marketing_email_team_1_cta_clicked", + "counts.in_product_marketing_email_team_2_sent", + "counts.in_product_marketing_email_team_2_cta_clicked", + "counts.in_product_marketing_email_experience_0_sent", + "counts.in_product_marketing_email_team_short_0_sent", + "counts.in_product_marketing_email_team_short_0_cta_clicked", + "counts.in_product_marketing_email_trial_short_0_sent", + "counts.in_product_marketing_email_trial_short_0_cta_clicked", + "counts.in_product_marketing_email_admin_verify_0_sent", + "counts.in_product_marketing_email_admin_verify_0_cta_clicked", + "counts.ldap_users", + "usage_activity_by_stage.create.projects_with_sectional_code_owner_rules", + "usage_activity_by_stage.monitor.clusters_integrations_prometheus", + "usage_activity_by_stage.monitor.projects_with_enabled_alert_integrations_histogram", + "usage_activity_by_stage_monthly.create.projects_with_sectional_code_owner_rules", + "usage_activity_by_stage_monthly.monitor.clusters_integrations_prometheus"] + end - expect(Rails.cache.fetch('usage_data')).to eq(new_usage_data) + def fetch_value_by_query(query) + # Because test cases are run inside a transaction, if any query raise and error all queries that follows + # it are automatically canceled by PostgreSQL, to avoid that problem, and to provide exhaustive information + # about every metric, queries are wrapped explicitly in sub transactions. + ApplicationRecord.transaction do + ApplicationRecord.connection.execute(query)&.first&.values&.first end + rescue ActiveRecord::StatementInvalid => e + e.message + end + + def build_payload_from_queries(payload, accumulator = [], key_path = []) + payload.each do |key, value| + if value.is_a?(Hash) + build_payload_from_queries(value, accumulator, key_path.dup << key) + elsif value.is_a?(String) && /SELECT .* FROM.*/ =~ value + accumulator << [key_path.dup << key, value, fetch_value_by_query(value)] + end + end + accumulator + end + + before do + stub_usage_data_connections + stub_object_store_settings + stub_prometheus_queries + memoized_constatns = Gitlab::UsageData::CE_MEMOIZED_VALUES + memoized_constatns += Gitlab::UsageData::EE_MEMOIZED_VALUES if defined? Gitlab::UsageData::EE_MEMOIZED_VALUES + memoized_constatns.each { |v| Gitlab::UsageData.clear_memoization(v) } + stub_database_flavor_check('Cloud SQL for PostgreSQL') end - context 'when no caching' do - let(:new_usage_data) { { uuid: "1112" } } + let(:service_ping_payload) { described_class.for(output: :all_metrics_values) } + let(:metrics_queries_with_values) { build_payload_from_queries(described_class.for(output: :metrics_queries)) } - it 'returns fresh data' do - allow(Gitlab::UsageData).to receive(:data).and_return(usage_data, new_usage_data) + it 'generates queries that match collected data', :aggregate_failures do + message = "Expected %{query} result to match %{value} for %{key_path} metric" - expect(described_class.for(output: :all_metrics_values)).to eq(usage_data) - expect(described_class.for(output: :all_metrics_values)).to eq(new_usage_data) + metrics_queries_with_values.each do |key_path, query, value| + next if failing_todo_metrics.include?(key_path.join('.')) - expect(Rails.cache.fetch('usage_data')).to eq(new_usage_data) + expect(value).to( + eq(service_ping_payload.dig(*key_path)), + message % { query: query, value: (value || 'NULL'), key_path: key_path.join('.') } + ) end end end |