diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-03-03 15:11:16 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-03-03 15:11:16 +0300 |
commit | b5820a6bcd083c878a085aa288757e8dc2d35fec (patch) | |
tree | 7ce498ba4cc77c160a04eae2b8fba7474d1196d7 /spec | |
parent | 74780f24f2005d24a0e0a8fa1b3ae5391ae2928f (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
-rw-r--r-- | spec/frontend/environments/environments_app_spec.js | 12 | ||||
-rw-r--r-- | spec/frontend/environments/folder/environments_folder_view_spec.js | 14 | ||||
-rw-r--r-- | spec/lib/gitlab/error_tracking/context_payload_generator_spec.rb | 176 | ||||
-rw-r--r-- | spec/lib/gitlab/error_tracking/log_formatter_spec.rb | 71 | ||||
-rw-r--r-- | spec/lib/gitlab/error_tracking/processor/context_payload_processor_spec.rb | 45 | ||||
-rw-r--r-- | spec/lib/gitlab/error_tracking_spec.rb | 219 | ||||
-rw-r--r-- | spec/lib/gitlab/usage_data_counters/merge_request_activity_unique_counter_spec.rb | 16 | ||||
-rw-r--r-- | spec/models/upload_spec.rb | 2 | ||||
-rw-r--r-- | spec/requests/api/graphql/project/merge_requests_spec.rb | 137 | ||||
-rw-r--r-- | spec/requests/api/helpers_spec.rb | 20 | ||||
-rw-r--r-- | spec/requests/api/project_attributes.yml | 1 | ||||
-rw-r--r-- | spec/requests/api/projects_spec.rb | 11 | ||||
-rw-r--r-- | spec/services/merge_requests/update_service_spec.rb | 54 |
13 files changed, 559 insertions, 219 deletions
diff --git a/spec/frontend/environments/environments_app_spec.js b/spec/frontend/environments/environments_app_spec.js index 50d84b19ce8..542cf58b079 100644 --- a/spec/frontend/environments/environments_app_spec.js +++ b/spec/frontend/environments/environments_app_spec.js @@ -97,13 +97,21 @@ describe('Environment', () => { jest.spyOn(wrapper.vm, 'updateContent').mockImplementation(() => {}); wrapper.find('.gl-pagination li:nth-child(3) .page-link').trigger('click'); - expect(wrapper.vm.updateContent).toHaveBeenCalledWith({ scope: 'available', page: '2' }); + expect(wrapper.vm.updateContent).toHaveBeenCalledWith({ + scope: 'available', + page: '2', + nested: true, + }); }); it('should make an API request when using tabs', () => { jest.spyOn(wrapper.vm, 'updateContent').mockImplementation(() => {}); findEnvironmentsTabStopped().trigger('click'); - expect(wrapper.vm.updateContent).toHaveBeenCalledWith({ scope: 'stopped', page: '1' }); + expect(wrapper.vm.updateContent).toHaveBeenCalledWith({ + scope: 'stopped', + page: '1', + nested: true, + }); }); it('should not make the same API request when clicking on the current scope tab', () => { diff --git a/spec/frontend/environments/folder/environments_folder_view_spec.js b/spec/frontend/environments/folder/environments_folder_view_spec.js index 3943e89c6cf..d02ed8688c6 100644 --- a/spec/frontend/environments/folder/environments_folder_view_spec.js +++ b/spec/frontend/environments/folder/environments_folder_view_spec.js @@ -103,13 +103,18 @@ describe('Environments Folder View', () => { expect(wrapper.vm.updateContent).toHaveBeenCalledWith({ scope: wrapper.vm.scope, page: '10', + nested: true, }); }); it('should make an API request when using tabs', () => { jest.spyOn(wrapper.vm, 'updateContent').mockImplementation(() => {}); findEnvironmentsTabStopped().trigger('click'); - expect(wrapper.vm.updateContent).toHaveBeenCalledWith({ scope: 'stopped', page: '1' }); + expect(wrapper.vm.updateContent).toHaveBeenCalledWith({ + scope: 'stopped', + page: '1', + nested: true, + }); }); }); }); @@ -161,7 +166,11 @@ describe('Environments Folder View', () => { it('should set page to 1', () => { jest.spyOn(wrapper.vm, 'updateContent').mockImplementation(() => {}); wrapper.vm.onChangeTab('stopped'); - expect(wrapper.vm.updateContent).toHaveBeenCalledWith({ scope: 'stopped', page: '1' }); + expect(wrapper.vm.updateContent).toHaveBeenCalledWith({ + scope: 'stopped', + page: '1', + nested: true, + }); }); }); @@ -172,6 +181,7 @@ describe('Environments Folder View', () => { expect(wrapper.vm.updateContent).toHaveBeenCalledWith({ scope: wrapper.vm.scope, page: '4', + nested: true, }); }); }); diff --git a/spec/lib/gitlab/error_tracking/context_payload_generator_spec.rb b/spec/lib/gitlab/error_tracking/context_payload_generator_spec.rb new file mode 100644 index 00000000000..0e72dd7ec5e --- /dev/null +++ b/spec/lib/gitlab/error_tracking/context_payload_generator_spec.rb @@ -0,0 +1,176 @@ +# frozen_string_literal: true + +require 'fast_spec_helper' +require 'rspec-parameterized' + +RSpec.describe Gitlab::ErrorTracking::ContextPayloadGenerator do + subject(:generator) { described_class.new } + + let(:extra) do + { + some_other_info: 'info', + issue_url: 'http://gitlab.com/gitlab-org/gitlab-foss/-/issues/1' + } + end + + let(:exception) { StandardError.new("Dummy exception") } + + before do + allow(Labkit::Correlation::CorrelationId).to receive(:current_id).and_return('cid') + allow(I18n).to receive(:locale).and_return('en') + end + + context 'user metadata' do + let(:user) { create(:user) } + + it 'appends user metadata to the payload' do + payload = {} + + Gitlab::ApplicationContext.with_context(user: user) do + payload = generator.generate(exception, extra) + end + + expect(payload[:user]).to eql( + username: user.username + ) + end + end + + context 'tags metadata' do + context 'when the GITLAB_SENTRY_EXTRA_TAGS env is not set' do + before do + stub_env('GITLAB_SENTRY_EXTRA_TAGS', nil) + end + + it 'does not log into AppLogger' do + expect(Gitlab::AppLogger).not_to receive(:debug) + + generator.generate(exception, extra) + end + + it 'does not send any extra tags' do + payload = {} + + Gitlab::ApplicationContext.with_context(feature_category: 'feature_a') do + payload = generator.generate(exception, extra) + end + + expect(payload[:tags]).to eql( + correlation_id: 'cid', + locale: 'en', + program: 'test', + feature_category: 'feature_a' + ) + end + end + + context 'when the GITLAB_SENTRY_EXTRA_TAGS env is a JSON hash' do + it 'includes those tags in all events' do + stub_env('GITLAB_SENTRY_EXTRA_TAGS', { foo: 'bar', baz: 'quux' }.to_json) + payload = {} + + Gitlab::ApplicationContext.with_context(feature_category: 'feature_a') do + payload = generator.generate(exception, extra) + end + + expect(payload[:tags]).to eql( + correlation_id: 'cid', + locale: 'en', + program: 'test', + feature_category: 'feature_a', + 'foo' => 'bar', + 'baz' => 'quux' + ) + end + + it 'does not log into AppLogger' do + expect(Gitlab::AppLogger).not_to receive(:debug) + + generator.generate(exception, extra) + end + end + + context 'when the GITLAB_SENTRY_EXTRA_TAGS env is not a JSON hash' do + using RSpec::Parameterized::TableSyntax + + where(:env_var, :error) do + { foo: 'bar', baz: 'quux' }.inspect | 'JSON::ParserError' + [].to_json | 'NoMethodError' + [%w[foo bar]].to_json | 'NoMethodError' + %w[foo bar].to_json | 'NoMethodError' + '"string"' | 'NoMethodError' + end + + with_them do + before do + stub_env('GITLAB_SENTRY_EXTRA_TAGS', env_var) + end + + it 'logs into AppLogger' do + expect(Gitlab::AppLogger).to receive(:debug).with(a_string_matching(error)) + + generator.generate({}) + end + + it 'does not include any extra tags' do + payload = {} + + Gitlab::ApplicationContext.with_context(feature_category: 'feature_a') do + payload = generator.generate(exception, extra) + end + + expect(payload[:tags]).to eql( + correlation_id: 'cid', + locale: 'en', + program: 'test', + feature_category: 'feature_a' + ) + end + end + end + end + + context 'extra metadata' do + it 'appends extra metadata to the payload' do + payload = generator.generate(exception, extra) + + expect(payload[:extra]).to eql( + some_other_info: 'info', + issue_url: 'http://gitlab.com/gitlab-org/gitlab-foss/-/issues/1' + ) + end + + it 'appends exception embedded extra metadata to the payload' do + allow(exception).to receive(:sentry_extra_data).and_return( + some_other_info: 'another_info', + mr_url: 'https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/1' + ) + + payload = generator.generate(exception, extra) + + expect(payload[:extra]).to eql( + some_other_info: 'another_info', + issue_url: 'http://gitlab.com/gitlab-org/gitlab-foss/-/issues/1', + mr_url: 'https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/1' + ) + end + + it 'filters sensitive extra info' do + extra[:my_token] = '456' + allow(exception).to receive(:sentry_extra_data).and_return( + mr_url: 'https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/1', + another_token: '1234' + ) + + payload = generator.generate(exception, extra) + + expect(payload[:extra]).to eql( + some_other_info: 'info', + issue_url: 'http://gitlab.com/gitlab-org/gitlab-foss/-/issues/1', + mr_url: 'https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/1', + my_token: '[FILTERED]', + another_token: '[FILTERED]' + ) + end + end +end diff --git a/spec/lib/gitlab/error_tracking/log_formatter_spec.rb b/spec/lib/gitlab/error_tracking/log_formatter_spec.rb new file mode 100644 index 00000000000..188ccd000a1 --- /dev/null +++ b/spec/lib/gitlab/error_tracking/log_formatter_spec.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true + +require 'fast_spec_helper' + +RSpec.describe Gitlab::ErrorTracking::LogFormatter do + let(:exception) { StandardError.new('boom') } + let(:context_payload) do + { + server: 'local-hostname-of-the-server', + user: { + ip_address: '127.0.0.1', + username: 'root' + }, + tags: { + locale: 'en', + feature_category: 'category_a' + }, + extra: { + some_other_info: 'other_info', + sidekiq: { + 'class' => 'HelloWorker', + 'args' => ['senstive string', 1, 2], + 'another_field' => 'field' + } + } + } + end + + before do + Raven.context.user[:user_flag] = 'flag' + Raven.context.tags[:shard] = 'catchall' + Raven.context.extra[:some_info] = 'info' + + allow(exception).to receive(:backtrace).and_return( + [ + 'lib/gitlab/file_a.rb:1', + 'lib/gitlab/file_b.rb:2' + ] + ) + end + + after do + ::Raven::Context.clear! + end + + it 'appends error-related log fields and filters sensitive Sidekiq arguments' do + payload = described_class.new.generate_log(exception, context_payload) + + expect(payload).to eql( + 'exception.class' => 'StandardError', + 'exception.message' => 'boom', + 'exception.backtrace' => [ + 'lib/gitlab/file_a.rb:1', + 'lib/gitlab/file_b.rb:2' + ], + 'user.ip_address' => '127.0.0.1', + 'user.username' => 'root', + 'user.user_flag' => 'flag', + 'tags.locale' => 'en', + 'tags.feature_category' => 'category_a', + 'tags.shard' => 'catchall', + 'extra.some_other_info' => 'other_info', + 'extra.some_info' => 'info', + "extra.sidekiq" => { + "another_field" => "field", + "args" => ["[FILTERED]", "1", "2"], + "class" => "HelloWorker" + } + ) + end +end diff --git a/spec/lib/gitlab/error_tracking/processor/context_payload_processor_spec.rb b/spec/lib/gitlab/error_tracking/processor/context_payload_processor_spec.rb new file mode 100644 index 00000000000..0db40eca989 --- /dev/null +++ b/spec/lib/gitlab/error_tracking/processor/context_payload_processor_spec.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +require 'fast_spec_helper' + +RSpec.describe Gitlab::ErrorTracking::Processor::ContextPayloadProcessor do + subject(:processor) { described_class.new } + + before do + allow_next_instance_of(Gitlab::ErrorTracking::ContextPayloadGenerator) do |generator| + allow(generator).to receive(:generate).and_return( + user: { username: 'root' }, + tags: { locale: 'en', program: 'test', feature_category: 'feature_a', correlation_id: 'cid' }, + extra: { some_info: 'info' } + ) + end + end + + it 'merges the context payload into event payload' do + payload = { + user: { ip_address: '127.0.0.1' }, + tags: { priority: 'high' }, + extra: { sidekiq: { class: 'SomeWorker', args: ['[FILTERED]', 1, 2] } } + } + + processor.process(payload) + + expect(payload).to eql( + user: { + ip_address: '127.0.0.1', + username: 'root' + }, + tags: { + priority: 'high', + locale: 'en', + program: 'test', + feature_category: 'feature_a', + correlation_id: 'cid' + }, + extra: { + some_info: 'info', + sidekiq: { class: 'SomeWorker', args: ['[FILTERED]', 1, 2] } + } + ) + end +end diff --git a/spec/lib/gitlab/error_tracking_spec.rb b/spec/lib/gitlab/error_tracking_spec.rb index 764478ad1d7..a905b9f8d40 100644 --- a/spec/lib/gitlab/error_tracking_spec.rb +++ b/spec/lib/gitlab/error_tracking_spec.rb @@ -8,116 +8,55 @@ RSpec.describe Gitlab::ErrorTracking do let(:exception) { RuntimeError.new('boom') } let(:issue_url) { 'http://gitlab.com/gitlab-org/gitlab-foss/issues/1' } - let(:expected_payload_includes) do - [ - { 'exception.class' => 'RuntimeError' }, - { 'exception.message' => 'boom' }, - { 'tags.correlation_id' => 'cid' }, - { 'extra.some_other_info' => 'info' }, - { 'extra.issue_url' => 'http://gitlab.com/gitlab-org/gitlab-foss/issues/1' } - ] + let(:user) { create(:user) } + + let(:sentry_payload) do + { + tags: { + program: 'test', + locale: 'en', + feature_category: 'feature_a', + correlation_id: 'cid' + }, + user: { + username: user.username + }, + extra: { + some_other_info: 'info', + issue_url: 'http://gitlab.com/gitlab-org/gitlab-foss/issues/1' + } + } end - let(:sentry_event) { Gitlab::Json.parse(Raven.client.transport.events.last[1]) } + let(:logger_payload) do + { + 'exception.class' => 'RuntimeError', + 'exception.message' => 'boom', + 'tags.program' => 'test', + 'tags.locale' => 'en', + 'tags.feature_category' => 'feature_a', + 'tags.correlation_id' => 'cid', + 'user.username' => user.username, + 'extra.some_other_info' => 'info', + 'extra.issue_url' => 'http://gitlab.com/gitlab-org/gitlab-foss/issues/1' + } + end before do stub_sentry_settings allow(described_class).to receive(:sentry_dsn).and_return(Gitlab.config.sentry.dsn) allow(Labkit::Correlation::CorrelationId).to receive(:current_id).and_return('cid') + allow(I18n).to receive(:locale).and_return('en') described_class.configure do |config| config.encoding = 'json' end end - describe '.configure' do - context 'default tags from GITLAB_SENTRY_EXTRA_TAGS' do - context 'when the value is a JSON hash' do - it 'includes those tags in all events' do - stub_env('GITLAB_SENTRY_EXTRA_TAGS', { foo: 'bar', baz: 'quux' }.to_json) - - described_class.configure do |config| - config.encoding = 'json' - end - - described_class.track_exception(StandardError.new) - - expect(sentry_event['tags'].except('correlation_id', 'locale', 'program')) - .to eq('foo' => 'bar', 'baz' => 'quux') - end - end - - context 'when the value is not set' do - before do - stub_env('GITLAB_SENTRY_EXTRA_TAGS', nil) - end - - it 'does not log an error' do - expect(Gitlab::AppLogger).not_to receive(:debug) - - described_class.configure do |config| - config.encoding = 'json' - end - end - - it 'does not send any extra tags' do - described_class.configure do |config| - config.encoding = 'json' - end - - described_class.track_exception(StandardError.new) - - expect(sentry_event['tags'].keys).to contain_exactly('correlation_id', 'locale', 'program') - end - end - - context 'when the value is not a JSON hash' do - using RSpec::Parameterized::TableSyntax - - where(:env_var, :error) do - { foo: 'bar', baz: 'quux' }.inspect | 'JSON::ParserError' - [].to_json | 'NoMethodError' - [%w[foo bar]].to_json | 'NoMethodError' - %w[foo bar].to_json | 'NoMethodError' - '"string"' | 'NoMethodError' - end - - with_them do - before do - stub_env('GITLAB_SENTRY_EXTRA_TAGS', env_var) - end - - it 'does not include any extra tags' do - described_class.configure do |config| - config.encoding = 'json' - end - - described_class.track_exception(StandardError.new) - - expect(sentry_event['tags'].except('correlation_id', 'locale', 'program')) - .to be_empty - end - - it 'logs the error class' do - expect(Gitlab::AppLogger).to receive(:debug).with(a_string_matching(error)) - - described_class.configure do |config| - config.encoding = 'json' - end - end - end - end - end - end - - describe '.with_context' do - it 'adds the expected tags' do - described_class.with_context {} - - expect(Raven.tags_context[:locale].to_s).to eq(I18n.locale.to_s) - expect(Raven.tags_context[Labkit::Correlation::CorrelationId::LOG_KEY.to_sym].to_s) - .to eq('cid') + around do |example| + Gitlab::ApplicationContext.with_context(user: user, feature_category: 'feature_a') do + example.run end end @@ -128,10 +67,15 @@ RSpec.describe Gitlab::ErrorTracking do end it 'raises the exception' do - expect(Raven).to receive(:capture_exception) - - expect { described_class.track_and_raise_for_dev_exception(exception) } - .to raise_error(RuntimeError) + expect(Raven).to receive(:capture_exception).with(exception, sentry_payload) + + expect do + described_class.track_and_raise_for_dev_exception( + exception, + issue_url: issue_url, + some_other_info: 'info' + ) + end.to raise_error(RuntimeError, /boom/) end end @@ -141,19 +85,7 @@ RSpec.describe Gitlab::ErrorTracking do end it 'logs the exception with all attributes passed' do - expected_extras = { - some_other_info: 'info', - issue_url: 'http://gitlab.com/gitlab-org/gitlab-foss/issues/1' - } - - expected_tags = { - correlation_id: 'cid' - } - - expect(Raven).to receive(:capture_exception) - .with(exception, - tags: a_hash_including(expected_tags), - extra: a_hash_including(expected_extras)) + expect(Raven).to receive(:capture_exception).with(exception, sentry_payload) described_class.track_and_raise_for_dev_exception( exception, @@ -163,8 +95,7 @@ RSpec.describe Gitlab::ErrorTracking do 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)) + expect(Gitlab::ErrorTracking::Logger).to receive(:error).with(logger_payload) described_class.track_and_raise_for_dev_exception( exception, @@ -177,15 +108,19 @@ RSpec.describe Gitlab::ErrorTracking do describe '.track_and_raise_exception' do it 'always raises the exception' do - expect(Raven).to receive(:capture_exception) + expect(Raven).to receive(:capture_exception).with(exception, sentry_payload) - expect { described_class.track_and_raise_exception(exception) } - .to raise_error(RuntimeError) + expect do + described_class.track_and_raise_for_dev_exception( + exception, + issue_url: issue_url, + some_other_info: 'info' + ) + end.to raise_error(RuntimeError, /boom/) 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)) + expect(Gitlab::ErrorTracking::Logger).to receive(:error).with(logger_payload) expect do described_class.track_and_raise_exception( @@ -210,17 +145,16 @@ RSpec.describe Gitlab::ErrorTracking do 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)) + expect(Raven).to have_received(:capture_exception).with( + exception, + sentry_payload + ) end it 'calls Gitlab::ErrorTracking::Logger.error with formatted payload' do track_exception - expect(Gitlab::ErrorTracking::Logger).to have_received(:error) - .with(a_hash_including(*expected_payload_includes)) + expect(Gitlab::ErrorTracking::Logger).to have_received(:error).with(logger_payload) end context 'with filterable parameters' do @@ -229,8 +163,9 @@ RSpec.describe Gitlab::ErrorTracking do it 'filters parameters' do track_exception - expect(Gitlab::ErrorTracking::Logger).to have_received(:error) - .with(hash_including({ 'extra.test' => 1, 'extra.my_token' => '[FILTERED]' })) + expect(Gitlab::ErrorTracking::Logger).to have_received(:error).with( + hash_including({ 'extra.test' => 1, 'extra.my_token' => '[FILTERED]' }) + ) end end @@ -241,8 +176,9 @@ RSpec.describe Gitlab::ErrorTracking do it 'includes the extra data from the exception in the tracking information' do track_exception - expect(Raven).to have_received(:capture_exception) - .with(exception, a_hash_including(extra: a_hash_including(extra_info))) + expect(Raven).to have_received(:capture_exception).with( + exception, a_hash_including(extra: a_hash_including(extra_info)) + ) end end @@ -253,8 +189,9 @@ RSpec.describe Gitlab::ErrorTracking do it 'just includes the other extra info' do track_exception - expect(Raven).to have_received(:capture_exception) - .with(exception, a_hash_including(extra: a_hash_including(extra))) + expect(Raven).to have_received(:capture_exception).with( + exception, a_hash_including(extra: a_hash_including(extra)) + ) end end @@ -266,7 +203,13 @@ RSpec.describe Gitlab::ErrorTracking do track_exception 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'] } })) + hash_including( + 'extra.sidekiq' => { + 'class' => 'PostReceive', + 'args' => ['1', '{"id"=>2, "name"=>"hello"}', 'some-value', 'another-value'] + } + ) + ) end end @@ -276,9 +219,17 @@ RSpec.describe Gitlab::ErrorTracking do it 'filters sensitive arguments before sending' do track_exception + sentry_event = Gitlab::Json.parse(Raven.client.transport.events.last[1]) + 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'] })) + hash_including( + 'extra.sidekiq' => { + 'class' => 'UnknownWorker', + 'args' => ['[FILTERED]', '1', '2'] + } + ) + ) end end end diff --git a/spec/lib/gitlab/usage_data_counters/merge_request_activity_unique_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/merge_request_activity_unique_counter_spec.rb index a604de4a61f..6bc42430889 100644 --- a/spec/lib/gitlab/usage_data_counters/merge_request_activity_unique_counter_spec.rb +++ b/spec/lib/gitlab/usage_data_counters/merge_request_activity_unique_counter_spec.rb @@ -284,4 +284,20 @@ RSpec.describe Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter, :cl let(:action) { described_class::MR_CREATE_FROM_ISSUE_ACTION } end end + + describe '.track_discussion_locked_action' do + subject { described_class.track_discussion_locked_action(user: user) } + + it_behaves_like 'a tracked merge request unique event' do + let(:action) { described_class::MR_DISCUSSION_LOCKED_ACTION } + end + end + + describe '.track_discussion_unlocked_action' do + subject { described_class.track_discussion_unlocked_action(user: user) } + + it_behaves_like 'a tracked merge request unique event' do + let(:action) { described_class::MR_DISCUSSION_UNLOCKED_ACTION } + end + end end diff --git a/spec/models/upload_spec.rb b/spec/models/upload_spec.rb index 18388b4cd83..6bac5e31435 100644 --- a/spec/models/upload_spec.rb +++ b/spec/models/upload_spec.rb @@ -221,7 +221,7 @@ RSpec.describe Upload do it 'does not send a message to Sentry' do upload = described_class.new(path: "#{__FILE__}-nope", store: ObjectStorage::Store::LOCAL) - expect(Raven).not_to receive(:capture_message) + expect(Gitlab::ErrorTracking).not_to receive(:track_exception) upload.exist? end diff --git a/spec/requests/api/graphql/project/merge_requests_spec.rb b/spec/requests/api/graphql/project/merge_requests_spec.rb index d684be91dc9..12060eb51e9 100644 --- a/spec/requests/api/graphql/project/merge_requests_spec.rb +++ b/spec/requests/api/graphql/project/merge_requests_spec.rb @@ -7,13 +7,27 @@ RSpec.describe 'getting merge request listings nested in a project' do let_it_be(:project) { create(:project, :repository, :public) } let_it_be(:current_user) { create(:user) } - let_it_be(:label) { create(:label, project: project) } - let_it_be(:merge_request_a) { create(:labeled_merge_request, :unique_branches, source_project: project, labels: [label]) } - let_it_be(:merge_request_b) { create(:merge_request, :closed, :unique_branches, source_project: project) } - let_it_be(:merge_request_c) { create(:labeled_merge_request, :closed, :unique_branches, source_project: project, labels: [label]) } - let_it_be(:merge_request_d) { create(:merge_request, :locked, :unique_branches, source_project: project) } - let_it_be(:merge_request_e) { create(:merge_request, :unique_branches, source_project: project) } + + let_it_be(:merge_request_a) do + create(:labeled_merge_request, :unique_branches, source_project: project, labels: [label]) + end + + let_it_be(:merge_request_b) do + create(:merge_request, :closed, :unique_branches, source_project: project) + end + + let_it_be(:merge_request_c) do + create(:labeled_merge_request, :closed, :unique_branches, source_project: project, labels: [label]) + end + + let_it_be(:merge_request_d) do + create(:merge_request, :locked, :unique_branches, source_project: project) + end + + let_it_be(:merge_request_e) do + create(:merge_request, :unique_branches, source_project: project) + end let(:results) { graphql_data.dig('project', 'mergeRequests', 'nodes') } @@ -27,32 +41,38 @@ RSpec.describe 'getting merge request listings nested in a project' do ) end - let(:query) do - query_merge_requests(all_graphql_fields_for('MergeRequest', max_depth: 1)) - end - it_behaves_like 'a working graphql query' do + let(:query) do + query_merge_requests(all_graphql_fields_for('MergeRequest', max_depth: 2)) + end + before do - post_graphql(query, current_user: current_user) + # We cannot call the whitelist here, since the transaction does not + # begin until we enter the controller. + headers = { + 'X-GITLAB-QUERY-WHITELIST-ISSUE' => 'https://gitlab.com/gitlab-org/gitlab/-/issues/322979' + } + + post_graphql(query, current_user: current_user, headers: headers) end end # The following tests are needed to guarantee that we have correctly annotated # all the gitaly calls. Selecting combinations of fields may mask this due to # memoization. - context 'requesting a single field' do + context 'when requesting a single field' do let_it_be(:fresh_mr) { create(:merge_request, :unique_branches, source_project: project) } + let(:search_params) { { iids: [fresh_mr.iid.to_s] } } + let(:graphql_data) do + GitlabSchema.execute(query, context: { current_user: current_user }).to_h['data'] + end before do project.repository.expire_branches_cache end - let(:graphql_data) do - GitlabSchema.execute(query, context: { current_user: current_user }).to_h['data'] - end - - context 'selecting any single scalar field' do + context 'when selecting any single scalar field' do where(:field) do scalar_fields_of('MergeRequest').map { |name| [name] } end @@ -68,7 +88,7 @@ RSpec.describe 'getting merge request listings nested in a project' do end end - context 'selecting any single nested field' do + context 'when selecting any single nested field' do where(:field, :subfield, :is_connection) do nested_fields_of('MergeRequest').flat_map do |name, field| type = field_type(field) @@ -95,7 +115,11 @@ RSpec.describe 'getting merge request listings nested in a project' do end end - shared_examples 'searching with parameters' do + shared_examples 'when searching with parameters' do + let(:query) do + query_merge_requests('iid title') + end + let(:expected) do mrs.map { |mr| a_hash_including('iid' => mr.iid.to_s, 'title' => mr.title) } end @@ -107,60 +131,60 @@ RSpec.describe 'getting merge request listings nested in a project' do end end - context 'there are no search params' do + context 'when there are no search params' do let(:search_params) { nil } let(:mrs) { [merge_request_a, merge_request_b, merge_request_c, merge_request_d, merge_request_e] } - it_behaves_like 'searching with parameters' + it_behaves_like 'when searching with parameters' end - context 'the search params do not match anything' do - let(:search_params) { { iids: %w(foo bar baz) } } + context 'when the search params do not match anything' do + let(:search_params) { { iids: %w[foo bar baz] } } let(:mrs) { [] } - it_behaves_like 'searching with parameters' + it_behaves_like 'when searching with parameters' end - context 'searching by iids' do + context 'when searching by iids' do let(:search_params) { { iids: mrs.map(&:iid).map(&:to_s) } } let(:mrs) { [merge_request_a, merge_request_c] } - it_behaves_like 'searching with parameters' + it_behaves_like 'when searching with parameters' end - context 'searching by state' do + context 'when searching by state' do let(:search_params) { { state: :closed } } let(:mrs) { [merge_request_b, merge_request_c] } - it_behaves_like 'searching with parameters' + it_behaves_like 'when searching with parameters' end - context 'searching by source_branch' do + context 'when searching by source_branch' do let(:search_params) { { source_branches: mrs.map(&:source_branch) } } let(:mrs) { [merge_request_b, merge_request_c] } - it_behaves_like 'searching with parameters' + it_behaves_like 'when searching with parameters' end - context 'searching by target_branch' do + context 'when searching by target_branch' do let(:search_params) { { target_branches: mrs.map(&:target_branch) } } let(:mrs) { [merge_request_a, merge_request_d] } - it_behaves_like 'searching with parameters' + it_behaves_like 'when searching with parameters' end - context 'searching by label' do + context 'when searching by label' do let(:search_params) { { labels: [label.title] } } let(:mrs) { [merge_request_a, merge_request_c] } - it_behaves_like 'searching with parameters' + it_behaves_like 'when searching with parameters' end - context 'searching by combination' do + context 'when searching by combination' do let(:search_params) { { state: :closed, labels: [label.title] } } let(:mrs) { [merge_request_c] } - it_behaves_like 'searching with parameters' + it_behaves_like 'when searching with parameters' end context 'when requesting `approved_by`' do @@ -203,10 +227,10 @@ RSpec.describe 'getting merge request listings nested in a project' do it 'exposes `commit_count`' do execute_query - expect(results).to match_array([ + expect(results).to match_array [ { "iid" => merge_request_a.iid.to_s, "commitCount" => 0 }, { "iid" => merge_request_with_commits.iid.to_s, "commitCount" => 29 } - ]) + ] end end @@ -216,8 +240,8 @@ RSpec.describe 'getting merge request listings nested in a project' do before do # make the MRs "merged" [merge_request_a, merge_request_b, merge_request_c].each do |mr| - mr.update_column(:state_id, MergeRequest.available_states[:merged]) - mr.metrics.update_column(:merged_at, Time.now) + mr.update!(state_id: MergeRequest.available_states[:merged]) + mr.metrics.update!(merged_at: Time.now) end end @@ -256,13 +280,12 @@ RSpec.describe 'getting merge request listings nested in a project' do end it 'returns the reviewers' do + nodes = merge_request_a.reviewers.map { |r| { 'username' => r.username } } + reviewers = { 'nodes' => match_array(nodes) } + execute_query - expect(results).to include a_hash_including('reviewers' => { - 'nodes' => match_array(merge_request_a.reviewers.map do |r| - a_hash_including('username' => r.username) - end) - }) + expect(results).to include a_hash_including('reviewers' => match(reviewers)) end include_examples 'N+1 query check' @@ -309,12 +332,14 @@ RSpec.describe 'getting merge request listings nested in a project' do allow(Gitlab::Database).to receive(:read_only?).and_return(false) end + def query_context + { current_user: current_user } + end + def run_query(number) # Ensure that we have a fresh request store and batch-context between runs - result = run_with_clean_state(query, - context: { current_user: current_user }, - variables: { first: number } - ) + vars = { first: number } + result = run_with_clean_state(query, context: query_context, variables: vars) graphql_dig_at(result.to_h, :data, :project, :merge_requests, :nodes) end @@ -348,13 +373,11 @@ RSpec.describe 'getting merge request listings nested in a project' do let(:data_path) { [:project, :mergeRequests] } def pagination_query(params) - graphql_query_for(:project, { full_path: project.full_path }, - <<~QUERY + graphql_query_for(:project, { full_path: project.full_path }, <<~QUERY) mergeRequests(#{params}) { #{page_info} nodes { id } } - QUERY - ) + QUERY end context 'when sorting by merged_at DESC' do @@ -396,14 +419,12 @@ RSpec.describe 'getting merge request listings nested in a project' do let(:query) do # Note: __typename meta field is always requested by the FE - graphql_query_for(:project, { full_path: project.full_path }, - <<~QUERY + graphql_query_for(:project, { full_path: project.full_path }, <<~QUERY) mergeRequests(mergedAfter: "2020-01-01", mergedBefore: "2020-01-05", first: 0, sourceBranches: null, labels: null) { count __typename } QUERY - ) end shared_examples 'count examples' do @@ -430,14 +451,12 @@ RSpec.describe 'getting merge request listings nested in a project' do context 'when total_time_to_merge and count is queried' do let(:query) do - graphql_query_for(:project, { full_path: project.full_path }, - <<~QUERY + graphql_query_for(:project, { full_path: project.full_path }, <<~QUERY) mergeRequests(mergedAfter: "2020-01-01", mergedBefore: "2020-01-05", first: 0) { totalTimeToMerge count } QUERY - ) end it 'does not query the merge requests table for the total_time_to_merge' do diff --git a/spec/requests/api/helpers_spec.rb b/spec/requests/api/helpers_spec.rb index 91d10791541..8160a94aef2 100644 --- a/spec/requests/api/helpers_spec.rb +++ b/spec/requests/api/helpers_spec.rb @@ -314,14 +314,13 @@ RSpec.describe API::Helpers do expect(Gitlab::ErrorTracking).to receive(:sentry_dsn).and_return(Gitlab.config.sentry.dsn) Gitlab::ErrorTracking.configure - Raven.client.configuration.encoding = 'json' end it 'does not report a MethodNotAllowed exception to Sentry' do exception = Grape::Exceptions::MethodNotAllowed.new({ 'X-GitLab-Test' => '1' }) allow(exception).to receive(:backtrace).and_return(caller) - expect(Raven).not_to receive(:capture_exception).with(exception) + expect(Gitlab::ErrorTracking).not_to receive(:track_exception).with(exception) handle_api_exception(exception) end @@ -330,8 +329,7 @@ RSpec.describe API::Helpers do exception = RuntimeError.new('test error') allow(exception).to receive(:backtrace).and_return(caller) - expect(Raven).to receive(:capture_exception).with(exception, tags: - a_hash_including(correlation_id: 'new-correlation-id'), extra: {}) + expect(Gitlab::ErrorTracking).to receive(:track_exception).with(exception) Labkit::Correlation::CorrelationId.use_id('new-correlation-id') do handle_api_exception(exception) @@ -357,20 +355,6 @@ RSpec.describe API::Helpers do expect(json_response['message']).to start_with("\nRuntimeError (Runtime Error!):") end end - - context 'extra information' do - # Sentry events are an array of the form [auth_header, data, options] - let(:event_data) { Raven.client.transport.events.first[1] } - - it 'sends the params, excluding confidential values' do - expect(ProjectsFinder).to receive(:new).and_raise('Runtime Error!') - - get api('/projects', user), params: { password: 'dont_send_this', other_param: 'send_this' } - - expect(event_data).to include('other_param=send_this') - expect(event_data).to include('password=********') - end - end end describe '.authenticate_non_get!' do diff --git a/spec/requests/api/project_attributes.yml b/spec/requests/api/project_attributes.yml index 181fcafd577..104918810f8 100644 --- a/spec/requests/api/project_attributes.yml +++ b/spec/requests/api/project_attributes.yml @@ -56,6 +56,7 @@ itself: # project - can_create_merge_request_in - compliance_frameworks - container_expiration_policy + - container_registry_image_prefix - default_branch - empty_repo - forks_count diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index 8a68db6ae4b..19cab51ef34 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -1540,6 +1540,10 @@ RSpec.describe API::Projects do end context 'when authenticated as an admin' do + before do + stub_container_registry_config(enabled: true, host_port: 'registry.example.org:5000') + end + let(:project_attributes_file) { 'spec/requests/api/project_attributes.yml' } let(:project_attributes) { YAML.load_file(project_attributes_file) } @@ -1569,7 +1573,7 @@ RSpec.describe API::Projects do keys end - it 'returns a project by id' do + it 'returns a project by id', :aggregate_failures do project project_member group = create(:group) @@ -1587,6 +1591,7 @@ RSpec.describe API::Projects do expect(json_response['ssh_url_to_repo']).to be_present expect(json_response['http_url_to_repo']).to be_present expect(json_response['web_url']).to be_present + expect(json_response['container_registry_image_prefix']).to eq("registry.example.org:5000/#{project.full_path}") expect(json_response['owner']).to be_a Hash expect(json_response['name']).to eq(project.name) expect(json_response['path']).to be_present @@ -1644,9 +1649,10 @@ RSpec.describe API::Projects do before do project project_member + stub_container_registry_config(enabled: true, host_port: 'registry.example.org:5000') end - it 'returns a project by id' do + it 'returns a project by id', :aggregate_failures do group = create(:group) link = create(:project_group_link, project: project, group: group) @@ -1662,6 +1668,7 @@ RSpec.describe API::Projects do expect(json_response['ssh_url_to_repo']).to be_present expect(json_response['http_url_to_repo']).to be_present expect(json_response['web_url']).to be_present + expect(json_response['container_registry_image_prefix']).to eq("registry.example.org:5000/#{project.full_path}") expect(json_response['owner']).to be_a Hash expect(json_response['name']).to eq(project.name) expect(json_response['path']).to be_present diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb index a6bd8416e58..e9ec3bccda3 100644 --- a/spec/services/merge_requests/update_service_spec.rb +++ b/spec/services/merge_requests/update_service_spec.rb @@ -48,6 +48,8 @@ RSpec.describe MergeRequests::UpdateService, :mailer do end context 'valid params' do + let(:locked) { true } + let(:opts) do { title: 'New title', @@ -58,7 +60,7 @@ RSpec.describe MergeRequests::UpdateService, :mailer do label_ids: [label.id], target_branch: 'target', force_remove_source_branch: '1', - discussion_locked: true + discussion_locked: locked } end @@ -117,6 +119,56 @@ RSpec.describe MergeRequests::UpdateService, :mailer do MergeRequests::UpdateService.new(project, user, opts).execute(draft_merge_request) end + + context 'when MR is locked' do + context 'when locked again' do + it 'does not track discussion locking' do + expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter) + .not_to receive(:track_discussion_locked_action) + + opts[:discussion_locked] = true + + MergeRequests::UpdateService.new(project, user, opts).execute(merge_request) + end + end + + context 'when unlocked' do + it 'tracks dicussion unlocking' do + expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter) + .to receive(:track_discussion_unlocked_action).once.with(user: user) + + opts[:discussion_locked] = false + + MergeRequests::UpdateService.new(project, user, opts).execute(merge_request) + end + end + end + + context 'when MR is unlocked' do + let(:locked) { false } + + context 'when unlocked again' do + it 'does not track discussion unlocking' do + expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter) + .not_to receive(:track_discussion_unlocked_action) + + opts[:discussion_locked] = false + + MergeRequests::UpdateService.new(project, user, opts).execute(merge_request) + end + end + + context 'when locked' do + it 'tracks dicussion locking' do + expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter) + .to receive(:track_discussion_locked_action).once.with(user: user) + + opts[:discussion_locked] = true + + MergeRequests::UpdateService.new(project, user, opts).execute(merge_request) + end + end + end end context 'updating milestone' do |