summaryrefslogtreecommitdiff
path: root/spec/lib/gitlab/usage
diff options
context:
space:
mode:
Diffstat (limited to 'spec/lib/gitlab/usage')
-rw-r--r--spec/lib/gitlab/usage/docs/renderer_spec.rb22
-rw-r--r--spec/lib/gitlab/usage/docs/value_formatter_spec.rb26
-rw-r--r--spec/lib/gitlab/usage/metric_definition_spec.rb29
-rw-r--r--spec/lib/gitlab/usage/metric_spec.rb8
-rw-r--r--spec/lib/gitlab/usage/metrics/aggregates/aggregate_spec.rb281
-rw-r--r--spec/lib/gitlab/usage/metrics/aggregates/sources/postgres_hll_spec.rb146
-rw-r--r--spec/lib/gitlab/usage/metrics/aggregates/sources/redis_hll_spec.rb29
7 files changed, 527 insertions, 14 deletions
diff --git a/spec/lib/gitlab/usage/docs/renderer_spec.rb b/spec/lib/gitlab/usage/docs/renderer_spec.rb
new file mode 100644
index 00000000000..0677aa2d9d7
--- /dev/null
+++ b/spec/lib/gitlab/usage/docs/renderer_spec.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Usage::Docs::Renderer do
+ describe 'contents' do
+ let(:dictionary_path) { Gitlab::Usage::Docs::Renderer::DICTIONARY_PATH }
+ let(:items) { Gitlab::Usage::MetricDefinition.definitions }
+
+ it 'generates dictionary for given items' do
+ generated_dictionary = described_class.new(items).contents
+ generated_dictionary_keys = RDoc::Markdown
+ .parse(generated_dictionary)
+ .table_of_contents
+ .select { |metric_doc| metric_doc.level == 2 && !metric_doc.text.start_with?('info:') }
+ .map(&:text)
+ .map { |text| text.sub('<code>', '').sub('</code>', '') }
+
+ expect(generated_dictionary_keys).to match_array(items.keys)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/usage/docs/value_formatter_spec.rb b/spec/lib/gitlab/usage/docs/value_formatter_spec.rb
new file mode 100644
index 00000000000..7002c76a7cf
--- /dev/null
+++ b/spec/lib/gitlab/usage/docs/value_formatter_spec.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Usage::Docs::ValueFormatter do
+ describe '.format' do
+ using RSpec::Parameterized::TableSyntax
+ where(:key, :value, :expected_value) do
+ :product_group | 'growth::product intelligence' | '`growth::product intelligence`'
+ :data_source | 'redis' | 'Redis'
+ :data_source | 'ruby' | 'Ruby'
+ :introduced_by_url | 'http://test.com' | '[Introduced by](http://test.com)'
+ :tier | %w(gold premium) | 'gold, premium'
+ :distribution | %w(ce ee) | 'ce, ee'
+ :key_path | 'key.path' | '**`key.path`**'
+ :milestone | '13.4' | '13.4'
+ :status | 'data_available' | 'data_available'
+ end
+
+ with_them do
+ subject { described_class.format(key, value) }
+
+ it { is_expected.to eq(expected_value) }
+ end
+ end
+end
diff --git a/spec/lib/gitlab/usage/metric_definition_spec.rb b/spec/lib/gitlab/usage/metric_definition_spec.rb
index e101f837324..8b592838f5d 100644
--- a/spec/lib/gitlab/usage/metric_definition_spec.rb
+++ b/spec/lib/gitlab/usage/metric_definition_spec.rb
@@ -5,18 +5,14 @@ require 'spec_helper'
RSpec.describe Gitlab::Usage::MetricDefinition do
let(:attributes) do
{
- name: 'uuid',
description: 'GitLab instance unique identifier',
value_type: 'string',
product_category: 'collection',
- stage: 'growth',
+ product_stage: 'growth',
status: 'data_available',
default_generation: 'generation_1',
- full_path: {
- generation_1: 'uuid',
- generation_2: 'license.uuid'
- },
- group: 'group::product analytics',
+ key_path: 'uuid',
+ product_group: 'group::product analytics',
time_frame: 'none',
data_source: 'database',
distribution: %w(ee ce),
@@ -44,13 +40,12 @@ RSpec.describe Gitlab::Usage::MetricDefinition do
using RSpec::Parameterized::TableSyntax
where(:attribute, :value) do
- :name | nil
:description | nil
:value_type | nil
:value_type | 'test'
:status | nil
- :default_generation | nil
- :group | nil
+ :key_path | nil
+ :product_group | nil
:time_frame | nil
:time_frame | '29d'
:data_source | 'other'
@@ -70,6 +65,20 @@ RSpec.describe Gitlab::Usage::MetricDefinition do
described_class.new(path, attributes).validate!
end
+
+ context 'with skip_validation' do
+ it 'raise exception if skip_validation: false' do
+ expect(Gitlab::ErrorTracking).to receive(:track_and_raise_for_dev_exception).at_least(:once).with(instance_of(Gitlab::Usage::Metric::InvalidMetricError))
+
+ described_class.new(path, attributes.merge( { skip_validation: false } )).validate!
+ end
+
+ it 'does not raise exception if has skip_validation: true' do
+ expect(Gitlab::ErrorTracking).not_to receive(:track_and_raise_for_dev_exception)
+
+ described_class.new(path, attributes.merge( { skip_validation: true } )).validate!
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/usage/metric_spec.rb b/spec/lib/gitlab/usage/metric_spec.rb
index 40671d980d6..d4a789419a4 100644
--- a/spec/lib/gitlab/usage/metric_spec.rb
+++ b/spec/lib/gitlab/usage/metric_spec.rb
@@ -4,15 +4,15 @@ require 'spec_helper'
RSpec.describe Gitlab::Usage::Metric do
describe '#definition' do
- it 'returns generation_1 metric definiton' do
- expect(described_class.new(default_generation_path: 'uuid').definition).to be_an(Gitlab::Usage::MetricDefinition)
+ it 'returns key_path metric definiton' do
+ expect(described_class.new(key_path: 'uuid').definition).to be_an(Gitlab::Usage::MetricDefinition)
end
end
describe '#unflatten_default_path' do
using RSpec::Parameterized::TableSyntax
- where(:default_generation_path, :value, :expected_hash) do
+ where(:key_path, :value, :expected_hash) do
'uuid' | nil | { uuid: nil }
'uuid' | '1111' | { uuid: '1111' }
'counts.issues' | nil | { counts: { issues: nil } }
@@ -21,7 +21,7 @@ RSpec.describe Gitlab::Usage::Metric do
end
with_them do
- subject { described_class.new(default_generation_path: default_generation_path, value: value).unflatten_default_path }
+ subject { described_class.new(key_path: key_path, value: value).unflatten_key_path }
it { is_expected.to eq(expected_hash) }
end
diff --git a/spec/lib/gitlab/usage/metrics/aggregates/aggregate_spec.rb b/spec/lib/gitlab/usage/metrics/aggregates/aggregate_spec.rb
new file mode 100644
index 00000000000..5469ded18f9
--- /dev/null
+++ b/spec/lib/gitlab/usage/metrics/aggregates/aggregate_spec.rb
@@ -0,0 +1,281 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Usage::Metrics::Aggregates::Aggregate, :clean_gitlab_redis_shared_state do
+ let(:entity1) { 'dfb9d2d2-f56c-4c77-8aeb-6cddc4a1f857' }
+ let(:entity2) { '1dd9afb2-a3ee-4de1-8ae3-a405579c8584' }
+ let(:entity3) { '34rfjuuy-ce56-sa35-ds34-dfer567dfrf2' }
+ let(:entity4) { '8b9a2671-2abf-4bec-a682-22f6a8f7bf31' }
+ let(:end_date) { Date.current }
+ let(:sources) { Gitlab::Usage::Metrics::Aggregates::Sources }
+
+ let_it_be(:recorded_at) { Time.current.to_i }
+
+ context 'aggregated_metrics_data' do
+ shared_examples 'aggregated_metrics_data' do
+ context 'no aggregated metric is defined' do
+ it 'returns empty hash' do
+ allow_next_instance_of(described_class) do |instance|
+ allow(instance).to receive(:aggregated_metrics).and_return([])
+ end
+
+ expect(aggregated_metrics_data).to eq({})
+ end
+ end
+
+ context 'there are aggregated metrics defined' do
+ before do
+ allow_next_instance_of(described_class) do |instance|
+ allow(instance).to receive(:aggregated_metrics).and_return(aggregated_metrics)
+ end
+ end
+
+ context 'with disabled database_sourced_aggregated_metrics feature flag' do
+ before do
+ stub_feature_flags(database_sourced_aggregated_metrics: false)
+ end
+
+ let(:aggregated_metrics) do
+ [
+ { name: 'gmau_1', source: 'redis', events: %w[event3 event5], operator: "OR" },
+ { name: 'gmau_2', source: 'database', events: %w[event1 event2 event3], operator: "OR" }
+ ].map(&:with_indifferent_access)
+ end
+
+ it 'skips database sourced metrics', :aggregate_failures do
+ results = {
+ 'gmau_1' => 5
+ }
+
+ params = { start_date: start_date, end_date: end_date, recorded_at: recorded_at }
+
+ expect(sources::RedisHll).to receive(:calculate_metrics_union).with(params.merge(metric_names: %w[event3 event5])).and_return(5)
+ expect(sources::PostgresHll).not_to receive(:calculate_metrics_union).with(params.merge(metric_names: %w[event1 event2 event3]))
+ expect(aggregated_metrics_data).to eq(results)
+ end
+ end
+
+ context 'with AND operator' do
+ let(:aggregated_metrics) do
+ [
+ { name: 'gmau_1', source: 'redis', events: %w[event3 event5], operator: "AND" },
+ { name: 'gmau_2', source: 'database', events: %w[event1 event2 event3], operator: "AND" }
+ ].map(&:with_indifferent_access)
+ end
+
+ it 'returns the number of unique events recorded for every metric in aggregate', :aggregate_failures do
+ results = {
+ 'gmau_1' => 2,
+ 'gmau_2' => 1
+ }
+ params = { start_date: start_date, end_date: end_date, recorded_at: recorded_at }
+
+ # gmau_1 data is as follow
+ # |A| => 4
+ expect(sources::RedisHll).to receive(:calculate_metrics_union).with(params.merge(metric_names: 'event3')).and_return(4)
+ # |B| => 6
+ expect(sources::RedisHll).to receive(:calculate_metrics_union).with(params.merge(metric_names: 'event5')).and_return(6)
+ # |A + B| => 8
+ expect(sources::RedisHll).to receive(:calculate_metrics_union).with(params.merge(metric_names: %w[event3 event5])).and_return(8)
+ # Exclusion inclusion principle formula to calculate intersection of 2 sets
+ # |A & B| = (|A| + |B|) - |A + B| => (4 + 6) - 8 => 2
+
+ # gmau_2 data is as follow:
+ # |A| => 2
+ expect(sources::PostgresHll).to receive(:calculate_metrics_union).with(params.merge(metric_names: 'event1')).and_return(2)
+ # |B| => 3
+ expect(sources::PostgresHll).to receive(:calculate_metrics_union).with(params.merge(metric_names: 'event2')).and_return(3)
+ # |C| => 5
+ expect(sources::PostgresHll).to receive(:calculate_metrics_union).with(params.merge(metric_names: 'event3')).and_return(5)
+
+ # |A + B| => 4 therefore |A & B| = (|A| + |B|) - |A + B| => 2 + 3 - 4 => 1
+ expect(sources::PostgresHll).to receive(:calculate_metrics_union).with(params.merge(metric_names: %w[event1 event2])).and_return(4)
+ # |A + C| => 6 therefore |A & C| = (|A| + |C|) - |A + C| => 2 + 5 - 6 => 1
+ expect(sources::PostgresHll).to receive(:calculate_metrics_union).with(params.merge(metric_names: %w[event1 event3])).and_return(6)
+ # |B + C| => 7 therefore |B & C| = (|B| + |C|) - |B + C| => 3 + 5 - 7 => 1
+ expect(sources::PostgresHll).to receive(:calculate_metrics_union).with(params.merge(metric_names: %w[event2 event3])).and_return(7)
+ # |A + B + C| => 8
+ expect(sources::PostgresHll).to receive(:calculate_metrics_union).with(params.merge(metric_names: %w[event1 event2 event3])).and_return(8)
+ # Exclusion inclusion principle formula to calculate intersection of 3 sets
+ # |A & B & C| = (|A & B| + |A & C| + |B & C|) - (|A| + |B| + |C|) + |A + B + C|
+ # (1 + 1 + 1) - (2 + 3 + 5) + 8 => 1
+
+ expect(aggregated_metrics_data).to eq(results)
+ end
+ end
+
+ context 'with OR operator' do
+ let(:aggregated_metrics) do
+ [
+ { name: 'gmau_1', source: 'redis', events: %w[event3 event5], operator: "OR" },
+ { name: 'gmau_2', source: 'database', events: %w[event1 event2 event3], operator: "OR" }
+ ].map(&:with_indifferent_access)
+ end
+
+ it 'returns the number of unique events occurred for any metric in aggregate', :aggregate_failures do
+ results = {
+ 'gmau_1' => 5,
+ 'gmau_2' => 3
+ }
+ params = { start_date: start_date, end_date: end_date, recorded_at: recorded_at }
+
+ expect(sources::RedisHll).to receive(:calculate_metrics_union).with(params.merge(metric_names: %w[event3 event5])).and_return(5)
+ expect(sources::PostgresHll).to receive(:calculate_metrics_union).with(params.merge(metric_names: %w[event1 event2 event3])).and_return(3)
+ expect(aggregated_metrics_data).to eq(results)
+ end
+ end
+
+ context 'hidden behind feature flag' do
+ let(:enabled_feature_flag) { 'test_ff_enabled' }
+ let(:disabled_feature_flag) { 'test_ff_disabled' }
+ let(:aggregated_metrics) do
+ [
+ # represents stable aggregated metrics that has been fully released
+ { name: 'gmau_without_ff', source: 'redis', events: %w[event3_slot event5_slot], operator: "OR" },
+ # represents new aggregated metric that is under performance testing on gitlab.com
+ { name: 'gmau_enabled', source: 'redis', events: %w[event4], operator: "OR", feature_flag: enabled_feature_flag },
+ # represents aggregated metric that is under development and shouldn't be yet collected even on gitlab.com
+ { name: 'gmau_disabled', source: 'redis', events: %w[event4], operator: "OR", feature_flag: disabled_feature_flag }
+ ].map(&:with_indifferent_access)
+ end
+
+ it 'does not calculate data for aggregates with ff turned off' do
+ skip_feature_flags_yaml_validation
+ skip_default_enabled_yaml_check
+ stub_feature_flags(enabled_feature_flag => true, disabled_feature_flag => false)
+ allow(sources::RedisHll).to receive(:calculate_metrics_union).and_return(6)
+
+ expect(aggregated_metrics_data).to eq('gmau_without_ff' => 6, 'gmau_enabled' => 6)
+ end
+ end
+ end
+
+ context 'error handling' do
+ context 'development and test environment' do
+ it 'raises error when unknown aggregation operator is used' do
+ allow_next_instance_of(described_class) do |instance|
+ allow(instance).to receive(:aggregated_metrics)
+ .and_return([{ name: 'gmau_1', source: 'redis', events: %w[event1_slot], operator: "SUM" }])
+ end
+
+ expect { aggregated_metrics_data }.to raise_error Gitlab::Usage::Metrics::Aggregates::UnknownAggregationOperator
+ end
+
+ it 'raises error when unknown aggregation source is used' do
+ allow_next_instance_of(described_class) do |instance|
+ allow(instance).to receive(:aggregated_metrics)
+ .and_return([{ name: 'gmau_1', source: 'whoami', events: %w[event1_slot], operator: "AND" }])
+ end
+
+ expect { aggregated_metrics_data }.to raise_error Gitlab::Usage::Metrics::Aggregates::UnknownAggregationSource
+ end
+
+ it 're raises Gitlab::UsageDataCounters::HLLRedisCounter::EventError' do
+ error = Gitlab::UsageDataCounters::HLLRedisCounter::EventError
+ allow(Gitlab::UsageDataCounters::HLLRedisCounter).to receive(:calculate_events_union).and_raise(error)
+
+ allow_next_instance_of(described_class) do |instance|
+ allow(instance).to receive(:aggregated_metrics)
+ .and_return([{ name: 'gmau_1', source: 'redis', events: %w[event1_slot], operator: "OR" }])
+ end
+
+ expect { aggregated_metrics_data }.to raise_error error
+ end
+ end
+
+ context 'production' do
+ before do
+ stub_rails_env('production')
+ end
+
+ it 'rescues unknown aggregation operator error' do
+ allow_next_instance_of(described_class) do |instance|
+ allow(instance).to receive(:aggregated_metrics)
+ .and_return([{ name: 'gmau_1', source: 'redis', events: %w[event1_slot], operator: "SUM" }])
+ end
+
+ expect(aggregated_metrics_data).to eq('gmau_1' => -1)
+ end
+
+ it 'rescues unknown aggregation source error' do
+ allow_next_instance_of(described_class) do |instance|
+ allow(instance).to receive(:aggregated_metrics)
+ .and_return([{ name: 'gmau_1', source: 'whoami', events: %w[event1_slot], operator: "AND" }])
+ end
+
+ expect(aggregated_metrics_data).to eq('gmau_1' => -1)
+ end
+
+ it 'rescues Gitlab::UsageDataCounters::HLLRedisCounter::EventError' do
+ error = Gitlab::UsageDataCounters::HLLRedisCounter::EventError
+ allow(Gitlab::UsageDataCounters::HLLRedisCounter).to receive(:calculate_events_union).and_raise(error)
+
+ allow_next_instance_of(described_class) do |instance|
+ allow(instance).to receive(:aggregated_metrics)
+ .and_return([{ name: 'gmau_1', source: 'redis', events: %w[event1_slot], operator: "OR" }])
+ end
+
+ expect(aggregated_metrics_data).to eq('gmau_1' => -1)
+ end
+ end
+ end
+ end
+
+ it 'allows for YAML aliases in aggregated metrics configs' do
+ expect(YAML).to receive(:safe_load).with(kind_of(String), aliases: true)
+
+ described_class.new(recorded_at)
+ end
+
+ describe '.aggregated_metrics_weekly_data' do
+ subject(:aggregated_metrics_data) { described_class.new(recorded_at).weekly_data }
+
+ let(:start_date) { 7.days.ago.to_date }
+
+ it_behaves_like 'aggregated_metrics_data'
+ end
+
+ describe '.aggregated_metrics_monthly_data' do
+ subject(:aggregated_metrics_data) { described_class.new(recorded_at).monthly_data }
+
+ let(:start_date) { 4.weeks.ago.to_date }
+
+ it_behaves_like 'aggregated_metrics_data'
+
+ context 'metrics union calls' do
+ let(:aggregated_metrics) do
+ [
+ { name: 'gmau_3', source: 'redis', events: %w[event1_slot event2_slot event3_slot event5_slot], operator: "AND" }
+ ].map(&:with_indifferent_access)
+ end
+
+ it 'caches intermediate operations', :aggregate_failures do
+ allow_next_instance_of(described_class) do |instance|
+ allow(instance).to receive(:aggregated_metrics).and_return(aggregated_metrics)
+ end
+
+ params = { start_date: start_date, end_date: end_date, recorded_at: recorded_at }
+
+ aggregated_metrics[0][:events].each do |event|
+ expect(sources::RedisHll).to receive(:calculate_metrics_union)
+ .with(params.merge(metric_names: event))
+ .once
+ .and_return(0)
+ end
+
+ 2.upto(4) do |subset_size|
+ aggregated_metrics[0][:events].combination(subset_size).each do |events|
+ expect(sources::RedisHll).to receive(:calculate_metrics_union)
+ .with(params.merge(metric_names: events))
+ .once
+ .and_return(0)
+ end
+ end
+
+ aggregated_metrics_data
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/usage/metrics/aggregates/sources/postgres_hll_spec.rb b/spec/lib/gitlab/usage/metrics/aggregates/sources/postgres_hll_spec.rb
new file mode 100644
index 00000000000..7b8be8e8bc6
--- /dev/null
+++ b/spec/lib/gitlab/usage/metrics/aggregates/sources/postgres_hll_spec.rb
@@ -0,0 +1,146 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Usage::Metrics::Aggregates::Sources::PostgresHll, :clean_gitlab_redis_shared_state do
+ let_it_be(:start_date) { 7.days.ago }
+ let_it_be(:end_date) { Date.current }
+ let_it_be(:recorded_at) { Time.current }
+ let_it_be(:time_period) { { created_at: (start_date..end_date) } }
+ let(:metric_1) { 'metric_1' }
+ let(:metric_2) { 'metric_2' }
+ let(:metric_names) { [metric_1, metric_2] }
+
+ describe '.calculate_events_union' do
+ subject(:calculate_metrics_union) do
+ described_class.calculate_metrics_union(metric_names: metric_names, start_date: start_date, end_date: end_date, recorded_at: recorded_at)
+ end
+
+ before do
+ [
+ {
+ metric_name: metric_1,
+ time_period: time_period,
+ recorded_at_timestamp: recorded_at,
+ data: ::Gitlab::Database::PostgresHll::Buckets.new(141 => 1, 56 => 1)
+ },
+ {
+ metric_name: metric_2,
+ time_period: time_period,
+ recorded_at_timestamp: recorded_at,
+ data: ::Gitlab::Database::PostgresHll::Buckets.new(10 => 1, 56 => 1)
+ }
+ ].each do |params|
+ described_class.save_aggregated_metrics(**params)
+ end
+ end
+
+ it 'returns the number of unique events in the union of all metrics' do
+ expect(calculate_metrics_union.round(2)).to eq(3.12)
+ end
+
+ context 'when there is no aggregated data saved' do
+ let(:metric_names) { [metric_1, 'i do not have any records'] }
+
+ it 'raises error when union data is missing' do
+ expect { calculate_metrics_union }.to raise_error Gitlab::Usage::Metrics::Aggregates::Sources::UnionNotAvailable
+ end
+ end
+
+ context 'when there is only one metric defined as aggregated' do
+ let(:metric_names) { [metric_1] }
+
+ it 'returns the number of unique events for that metric' do
+ expect(calculate_metrics_union.round(2)).to eq(2.08)
+ end
+ end
+ end
+
+ describe '.save_aggregated_metrics' do
+ subject(:save_aggregated_metrics) do
+ described_class.save_aggregated_metrics(metric_name: metric_1,
+ time_period: time_period,
+ recorded_at_timestamp: recorded_at,
+ data: data)
+ end
+
+ context 'with compatible data argument' do
+ let(:data) { ::Gitlab::Database::PostgresHll::Buckets.new(141 => 1, 56 => 1) }
+
+ it 'persists serialized data in Redis' do
+ Gitlab::Redis::SharedState.with do |redis|
+ expect(redis).to receive(:set).with("#{metric_1}_weekly-#{recorded_at.to_i}", '{"141":1,"56":1}', ex: 120.hours)
+ end
+
+ save_aggregated_metrics
+ end
+
+ context 'with monthly key' do
+ let_it_be(:start_date) { 4.weeks.ago }
+ let_it_be(:time_period) { { created_at: (start_date..end_date) } }
+
+ it 'persists serialized data in Redis' do
+ Gitlab::Redis::SharedState.with do |redis|
+ expect(redis).to receive(:set).with("#{metric_1}_monthly-#{recorded_at.to_i}", '{"141":1,"56":1}', ex: 120.hours)
+ end
+
+ save_aggregated_metrics
+ end
+ end
+
+ context 'with all_time key' do
+ let_it_be(:time_period) { nil }
+
+ it 'persists serialized data in Redis' do
+ Gitlab::Redis::SharedState.with do |redis|
+ expect(redis).to receive(:set).with("#{metric_1}_all_time-#{recorded_at.to_i}", '{"141":1,"56":1}', ex: 120.hours)
+ end
+
+ save_aggregated_metrics
+ end
+ end
+
+ context 'error handling' do
+ before do
+ allow(Gitlab::Redis::SharedState).to receive(:with).and_raise(::Redis::CommandError)
+ end
+
+ it 'rescues and reraise ::Redis::CommandError for development and test environments' do
+ expect { save_aggregated_metrics }.to raise_error ::Redis::CommandError
+ end
+
+ context 'for environment different than development' do
+ before do
+ stub_rails_env('production')
+ end
+
+ it 'rescues ::Redis::CommandError' do
+ expect { save_aggregated_metrics }.not_to raise_error
+ end
+ end
+ end
+ end
+
+ context 'with incompatible data argument' do
+ let(:data) { 1 }
+
+ context 'for environment different than development' do
+ before do
+ stub_rails_env('production')
+ end
+
+ it 'does not persist data in Redis' do
+ Gitlab::Redis::SharedState.with do |redis|
+ expect(redis).not_to receive(:set)
+ end
+
+ save_aggregated_metrics
+ end
+ end
+
+ it 'raises error for development environment' do
+ expect { save_aggregated_metrics }.to raise_error /Unsupported data type/
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/usage/metrics/aggregates/sources/redis_hll_spec.rb b/spec/lib/gitlab/usage/metrics/aggregates/sources/redis_hll_spec.rb
new file mode 100644
index 00000000000..af2de5ea343
--- /dev/null
+++ b/spec/lib/gitlab/usage/metrics/aggregates/sources/redis_hll_spec.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Usage::Metrics::Aggregates::Sources::RedisHll do
+ describe '.calculate_events_union' do
+ let(:event_names) { %w[event_a event_b] }
+ let(:start_date) { 7.days.ago }
+ let(:end_date) { Date.current }
+
+ subject(:calculate_metrics_union) do
+ described_class.calculate_metrics_union(metric_names: event_names, start_date: start_date, end_date: end_date, recorded_at: nil)
+ end
+
+ it 'calls Gitlab::UsageDataCounters::HLLRedisCounter.calculate_events_union' do
+ expect(Gitlab::UsageDataCounters::HLLRedisCounter).to receive(:calculate_events_union)
+ .with(event_names: event_names, start_date: start_date, end_date: end_date)
+ .and_return(5)
+
+ calculate_metrics_union
+ end
+
+ it 'prevents from using fallback value as valid union result' do
+ allow(Gitlab::UsageDataCounters::HLLRedisCounter).to receive(:calculate_events_union).and_return(-1)
+
+ expect { calculate_metrics_union }.to raise_error Gitlab::Usage::Metrics::Aggregates::Sources::UnionNotAvailable
+ end
+ end
+end