diff options
Diffstat (limited to 'spec/lib/gitlab')
4 files changed, 224 insertions, 46 deletions
diff --git a/spec/lib/gitlab/database/partitioning/replace_table_spec.rb b/spec/lib/gitlab/database/partitioning/replace_table_spec.rb new file mode 100644 index 00000000000..8800ff845ae --- /dev/null +++ b/spec/lib/gitlab/database/partitioning/replace_table_spec.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Database::Partitioning::ReplaceTable, '#perform' do + include TableSchemaHelpers + + subject(:replace_table) { described_class.new(original_table, replacement_table, archived_table, 'id').perform } + + let(:original_table) { '_test_original_table' } + let(:replacement_table) { '_test_replacement_table' } + let(:archived_table) { '_test_archived_table' } + + let(:original_sequence) { "#{original_table}_id_seq" } + + let(:original_primary_key) { "#{original_table}_pkey" } + let(:replacement_primary_key) { "#{replacement_table}_pkey" } + let(:archived_primary_key) { "#{archived_table}_pkey" } + + before do + connection.execute(<<~SQL) + CREATE TABLE #{original_table} ( + id serial NOT NULL PRIMARY KEY, + original_column text NOT NULL, + created_at timestamptz NOT NULL); + + CREATE TABLE #{replacement_table} ( + id int NOT NULL, + replacement_column text NOT NULL, + created_at timestamptz NOT NULL, + PRIMARY KEY (id, created_at)) + PARTITION BY RANGE (created_at); + + CREATE TABLE #{replacement_table}_202001 PARTITION OF #{replacement_table} + FOR VALUES FROM ('2020-01-01') TO ('2020-02-01'); + SQL + end + + it 'replaces the current table, archiving the old' do + expect_table_to_be_replaced { replace_table } + end + + it 'transfers the primary key sequence to the replacement table' do + expect(sequence_owned_by(original_table, 'id')).to eq(original_sequence) + expect(default_expression_for(original_table, 'id')).to eq("nextval('#{original_sequence}'::regclass)") + + expect(sequence_owned_by(replacement_table, 'id')).to be_nil + expect(default_expression_for(replacement_table, 'id')).to be_nil + + expect_table_to_be_replaced { replace_table } + + expect(sequence_owned_by(original_table, 'id')).to eq(original_sequence) + expect(default_expression_for(original_table, 'id')).to eq("nextval('#{original_sequence}'::regclass)") + expect(sequence_owned_by(archived_table, 'id')).to be_nil + expect(default_expression_for(archived_table, 'id')).to be_nil + end + + it 'renames the primary key constraints to match the new table names' do + expect(primary_key_constraint_name(original_table)).to eq(original_primary_key) + expect(primary_key_constraint_name(replacement_table)).to eq(replacement_primary_key) + + expect_table_to_be_replaced { replace_table } + + expect(primary_key_constraint_name(original_table)).to eq(original_primary_key) + expect(primary_key_constraint_name(archived_table)).to eq(archived_primary_key) + end + + def expect_table_to_be_replaced(&block) + super(original_table: original_table, replacement_table: replacement_table, archived_table: archived_table, &block) + end +end diff --git a/spec/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers_spec.rb b/spec/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers_spec.rb index 147637cf471..f10ff704c17 100644 --- a/spec/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers_spec.rb +++ b/spec/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers_spec.rb @@ -5,6 +5,7 @@ require 'spec_helper' RSpec.describe Gitlab::Database::PartitioningMigrationHelpers::TableManagementHelpers do include PartitioningHelpers include TriggerHelpers + include TableSchemaHelpers let(:migration) do ActiveRecord::Migration.new.extend(described_class) @@ -629,6 +630,76 @@ RSpec.describe Gitlab::Database::PartitioningMigrationHelpers::TableManagementHe end end + describe '#replace_with_partitioned_table' do + let(:archived_table) { "#{source_table}_archived" } + + before do + migration.partition_table_by_date source_table, partition_column, min_date: min_date, max_date: max_date + end + + it 'replaces the original table with the partitioned table' do + expect(table_type(source_table)).to eq('normal') + expect(table_type(partitioned_table)).to eq('partitioned') + expect(table_type(archived_table)).to be_nil + + expect_table_to_be_replaced { migration.replace_with_partitioned_table(source_table) } + + expect(table_type(source_table)).to eq('partitioned') + expect(table_type(archived_table)).to eq('normal') + expect(table_type(partitioned_table)).to be_nil + end + + it 'moves the trigger from the original table to the new table' do + expect_function_to_exist(function_name) + expect_valid_function_trigger(source_table, trigger_name, function_name, after: %w[delete insert update]) + + expect_table_to_be_replaced { migration.replace_with_partitioned_table(source_table) } + + expect_function_to_exist(function_name) + expect_valid_function_trigger(source_table, trigger_name, function_name, after: %w[delete insert update]) + end + + def expect_table_to_be_replaced(&block) + super(original_table: source_table, replacement_table: partitioned_table, archived_table: archived_table, &block) + end + end + + describe '#rollback_replace_with_partitioned_table' do + let(:archived_table) { "#{source_table}_archived" } + + before do + migration.partition_table_by_date source_table, partition_column, min_date: min_date, max_date: max_date + + migration.replace_with_partitioned_table source_table + end + + it 'replaces the partitioned table with the non-partitioned table' do + expect(table_type(source_table)).to eq('partitioned') + expect(table_type(archived_table)).to eq('normal') + expect(table_type(partitioned_table)).to be_nil + + expect_table_to_be_replaced { migration.rollback_replace_with_partitioned_table(source_table) } + + expect(table_type(source_table)).to eq('normal') + expect(table_type(partitioned_table)).to eq('partitioned') + expect(table_type(archived_table)).to be_nil + end + + it 'moves the trigger from the partitioned table to the non-partitioned table' do + expect_function_to_exist(function_name) + expect_valid_function_trigger(source_table, trigger_name, function_name, after: %w[delete insert update]) + + expect_table_to_be_replaced { migration.rollback_replace_with_partitioned_table(source_table) } + + expect_function_to_exist(function_name) + expect_valid_function_trigger(source_table, trigger_name, function_name, after: %w[delete insert update]) + end + + def expect_table_to_be_replaced(&block) + super(original_table: source_table, replacement_table: archived_table, archived_table: partitioned_table, &block) + end + end + def filter_columns_by_name(columns, names) columns.reject { |c| names.include?(c.name) } end diff --git a/spec/lib/gitlab/error_tracking_spec.rb b/spec/lib/gitlab/error_tracking_spec.rb index 2cc9ff36c99..68a46b11487 100644 --- a/spec/lib/gitlab/error_tracking_spec.rb +++ b/spec/lib/gitlab/error_tracking_spec.rb @@ -198,47 +198,39 @@ RSpec.describe Gitlab::ErrorTracking do end describe '.track_exception' do - it 'calls Raven.capture_exception' do - expected_extras = { - some_other_info: 'info', - issue_url: issue_url - } + let(:extra) { { issue_url: issue_url, some_other_info: 'info' } } - expected_tags = { - correlation_id: 'cid' - } + subject(:track_exception) { described_class.track_exception(exception, extra) } - expect(Raven).to receive(:capture_exception) - .with(exception, - tags: a_hash_including(expected_tags), - extra: a_hash_including(expected_extras)) - - described_class.track_exception( - exception, - issue_url: issue_url, - some_other_info: 'info' - ) + before do + allow(Raven).to receive(:capture_exception).and_call_original + allow(Gitlab::ErrorTracking::Logger).to receive(:error) + end + + it 'calls Raven.capture_exception' do + track_exception + + expect(Raven).to have_received(:capture_exception) + .with(exception, + tags: a_hash_including(correlation_id: 'cid'), + extra: a_hash_including(some_other_info: 'info', issue_url: issue_url)) end it 'calls Gitlab::ErrorTracking::Logger.error with formatted payload' do - expect(Gitlab::ErrorTracking::Logger).to receive(:error) - .with(a_hash_including(*expected_payload_includes)) + track_exception - described_class.track_exception( - exception, - issue_url: issue_url, - some_other_info: 'info' - ) + expect(Gitlab::ErrorTracking::Logger).to have_received(:error) + .with(a_hash_including(*expected_payload_includes)) end context 'with filterable parameters' do let(:extra) { { test: 1, my_token: 'test' } } it 'filters parameters' do - expect(Gitlab::ErrorTracking::Logger).to receive(:error).with( - hash_including({ 'extra.test' => 1, 'extra.my_token' => '[FILTERED]' })) + track_exception - described_class.track_exception(exception, extra) + expect(Gitlab::ErrorTracking::Logger).to have_received(:error) + .with(hash_including({ 'extra.test' => 1, 'extra.my_token' => '[FILTERED]' })) end end @@ -247,44 +239,58 @@ RSpec.describe Gitlab::ErrorTracking do let(:exception) { double(message: 'bang!', sentry_extra_data: extra_info, backtrace: caller) } it 'includes the extra data from the exception in the tracking information' do - expect(Raven).to receive(:capture_exception) - .with(exception, a_hash_including(extra: a_hash_including(extra_info))) + track_exception - described_class.track_exception(exception) + expect(Raven).to have_received(:capture_exception) + .with(exception, a_hash_including(extra: a_hash_including(extra_info))) end end context 'the exception implements :sentry_extra_data, which returns nil' do let(:exception) { double(message: 'bang!', sentry_extra_data: nil, backtrace: caller) } + let(:extra) { { issue_url: issue_url } } it 'just includes the other extra info' do - extra_info = { issue_url: issue_url } - expect(Raven).to receive(:capture_exception) - .with(exception, a_hash_including(extra: a_hash_including(extra_info))) + track_exception - described_class.track_exception(exception, extra_info) + expect(Raven).to have_received(:capture_exception) + .with(exception, a_hash_including(extra: a_hash_including(extra))) end end context 'with sidekiq args' do - it 'ensures extra.sidekiq.args is a string' do - extra = { sidekiq: { 'class' => 'PostReceive', 'args' => [1, { 'id' => 2, 'name' => 'hello' }, 'some-value', 'another-value'] } } + context 'when the args does not have anything sensitive' do + let(:extra) { { sidekiq: { 'class' => 'PostReceive', 'args' => [1, { 'id' => 2, 'name' => 'hello' }, 'some-value', 'another-value'] } } } - expect(Gitlab::ErrorTracking::Logger).to receive(:error).with( - hash_including({ 'extra.sidekiq' => { 'class' => 'PostReceive', 'args' => ['1', '{"id"=>2, "name"=>"hello"}', 'some-value', 'another-value'] } })) + it 'ensures extra.sidekiq.args is a string' do + track_exception - described_class.track_exception(exception, extra) + expect(Gitlab::ErrorTracking::Logger).to have_received(:error).with( + hash_including({ 'extra.sidekiq' => { 'class' => 'PostReceive', 'args' => ['1', '{"id"=>2, "name"=>"hello"}', 'some-value', 'another-value'] } })) + end end - it 'filters sensitive arguments before sending' do - extra = { sidekiq: { 'class' => 'UnknownWorker', 'args' => ['sensitive string', 1, 2] } } + context 'when the args has sensitive information' do + let(:extra) { { sidekiq: { 'class' => 'UnknownWorker', 'args' => ['sensitive string', 1, 2] } } } + + it 'filters sensitive arguments before sending' do + track_exception + + expect(sentry_event.dig('extra', 'sidekiq', 'args')).to eq(['[FILTERED]', 1, 2]) + expect(Gitlab::ErrorTracking::Logger).to have_received(:error).with( + hash_including('extra.sidekiq' => { 'class' => 'UnknownWorker', 'args' => ['[FILTERED]', '1', '2'] })) + end + end + end - expect(Gitlab::ErrorTracking::Logger).to receive(:error).with( - hash_including('extra.sidekiq' => { 'class' => 'UnknownWorker', 'args' => ['[FILTERED]', '1', '2'] })) + context 'when the error is kind of an `ActiveRecord::StatementInvalid`' do + let(:exception) { ActiveRecord::StatementInvalid.new(sql: 'SELECT "users".* FROM "users" WHERE "users"."id" = 1 AND "users"."foo" = $1') } - described_class.track_exception(exception, extra) + it 'injects the normalized sql query into extra' do + track_exception - expect(sentry_event.dig('extra', 'sidekiq', 'args')).to eq(['[FILTERED]', 1, 2]) + expect(Raven).to have_received(:capture_exception) + .with(exception, a_hash_including(extra: a_hash_including(sql: 'SELECT "users".* FROM "users" WHERE "users"."id" = $2 AND "users"."foo" = $1'))) end end end diff --git a/spec/lib/gitlab/usage_data_counters/issue_activity_unique_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/issue_activity_unique_counter_spec.rb index e08dc41d0cc..a53ab6ff97b 100644 --- a/spec/lib/gitlab/usage_data_counters/issue_activity_unique_counter_spec.rb +++ b/spec/lib/gitlab/usage_data_counters/issue_activity_unique_counter_spec.rb @@ -292,6 +292,36 @@ RSpec.describe Gitlab::UsageDataCounters::IssueActivityUniqueCounter, :clean_git end end + context 'for Issue comment added actions' do + it_behaves_like 'tracks and counts action' do + let(:action) { described_class::ISSUE_COMMENT_ADDED } + + def track_action(params) + described_class.track_issue_comment_added_action(**params) + end + end + end + + context 'for Issue comment edited actions' do + it_behaves_like 'tracks and counts action' do + let(:action) { described_class::ISSUE_COMMENT_EDITED } + + def track_action(params) + described_class.track_issue_comment_edited_action(**params) + end + end + end + + context 'for Issue comment removed actions' do + it_behaves_like 'tracks and counts action' do + let(:action) { described_class::ISSUE_COMMENT_REMOVED } + + def track_action(params) + described_class.track_issue_comment_removed_action(**params) + end + end + end + it 'can return the count of actions per user deduplicated', :aggregate_failures do described_class.track_issue_title_changed_action(author: user1) described_class.track_issue_description_changed_action(author: user1) |