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

service_ping_report_spec.rb « usage « gitlab « lib « spec - gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: e709698803567312702e1ceec40a791b2bc640ac (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Gitlab::Usage::ServicePingReport, :use_clean_rails_memory_store_caching do
  include UsageDataHelpers

  let(:usage_data) { { uuid: "1111", counts: { issue: 0 } } }

  before do
    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

  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

    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

  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)
    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

  context 'cross test values against queries' do
    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

    def type_cast_to_defined_type(value, metric_definition)
      case metric_definition&.attributes&.fetch(:value_type)
      when "string"
        value.to_s
      when "number"
        value.to_i
      when "object"
        case metric_definition&.json_schema&.fetch("type")
        when "array"
          value.to_a
        else
          value.to_h
        end
      else
        value
      end
    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

    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)) }
    let(:metric_definitions) { ::Gitlab::Usage::MetricDefinition.definitions }

    it 'generates queries that match collected data', :aggregate_failures do
      message = "Expected %{query} result to match %{value} for %{key_path} metric"

      metrics_queries_with_values.each do |key_path, query, value|
        value = type_cast_to_defined_type(value, metric_definitions[key_path.join('.')])

        expect(value).to(
          eq(service_ping_payload.dig(*key_path)),
          message % { query: query, value: (value || 'NULL'), key_path: key_path.join('.') }
        )
      end
    end
  end
end