diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-03-25 21:08:10 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-03-25 21:08:10 +0300 |
commit | 5d75b2b9a9d11c20667895e6aa68ea4e76658c5d (patch) | |
tree | 2aa529b0a153c805f5f4ecb357321a4e4f4c59cb /spec | |
parent | 6f2065c468b05658125b746169c56764a8ccddb1 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
-rw-r--r-- | spec/lib/gitlab/database/postgresql_adapter/schema_versions_copy_mixin_spec.rb | 42 | ||||
-rw-r--r-- | spec/lib/uploaded_file_spec.rb | 10 | ||||
-rw-r--r-- | spec/models/concerns/bulk_insert_safe_spec.rb | 31 | ||||
-rw-r--r-- | spec/requests/api/releases_spec.rb | 20 | ||||
-rw-r--r-- | spec/services/projects/prometheus/alerts/notify_service_spec.rb | 343 | ||||
-rw-r--r-- | spec/support/helpers/test_env.rb | 53 | ||||
-rw-r--r-- | spec/support/praefect.rb | 11 | ||||
-rw-r--r-- | spec/support/shared_examples/models/concerns/bulk_insert_safe_shared_examples.rb | 6 |
8 files changed, 489 insertions, 27 deletions
diff --git a/spec/lib/gitlab/database/postgresql_adapter/schema_versions_copy_mixin_spec.rb b/spec/lib/gitlab/database/postgresql_adapter/schema_versions_copy_mixin_spec.rb new file mode 100644 index 00000000000..968dfc1ea43 --- /dev/null +++ b/spec/lib/gitlab/database/postgresql_adapter/schema_versions_copy_mixin_spec.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Database::PostgresqlAdapter::SchemaVersionsCopyMixin do + let(:schema_migration) { double('schem_migration', table_name: table_name, all_versions: versions) } + let(:versions) { %w(5 2 1000 200 4 93 2) } + let(:table_name) { "schema_migrations" } + + let(:instance) do + Object.new.extend(described_class) + end + + before do + allow(instance).to receive(:schema_migration).and_return(schema_migration) + allow(instance).to receive(:quote_table_name).with(table_name).and_return("\"#{table_name}\"") + end + + subject { instance.dump_schema_information } + + it 'uses COPY FROM STDIN' do + expect(subject.split("\n").first).to match(/COPY "schema_migrations" \(version\) FROM STDIN;/) + end + + it 'contains a sorted list of versions by their numeric value' do + version_lines = subject.split("\n")[1..-2].map(&:to_i) + + expect(version_lines).to eq(versions.map(&:to_i).sort) + end + + it 'contains a end-of-data marker' do + expect(subject).to end_with("\\.\n") + end + + context 'with non-Integer versions' do + let(:versions) { %w(5 2 4 abc) } + + it 'raises an error' do + expect { subject }.to raise_error(/invalid value for Integer/) + end + end +end diff --git a/spec/lib/uploaded_file_spec.rb b/spec/lib/uploaded_file_spec.rb index 2bbbd67b13c..25536c07dd9 100644 --- a/spec/lib/uploaded_file_spec.rb +++ b/spec/lib/uploaded_file_spec.rb @@ -59,6 +59,16 @@ describe UploadedFile do expect(subject.sha256).to eq('sha256') expect(subject.remote_id).to eq('remote_id') end + + it 'handles a blank path' do + params['file.path'] = '' + + # Not a real file, so can't determine size itself + params['file.size'] = 1.byte + + expect { described_class.from_params(params, :file, upload_path) } + .not_to raise_error + end end end diff --git a/spec/models/concerns/bulk_insert_safe_spec.rb b/spec/models/concerns/bulk_insert_safe_spec.rb index a8e56cb8bdd..5ed1d6b9967 100644 --- a/spec/models/concerns/bulk_insert_safe_spec.rb +++ b/spec/models/concerns/bulk_insert_safe_spec.rb @@ -129,10 +129,37 @@ describe BulkInsertSafe do end.not_to change { described_class.count } end - it 'does nothing and returns true when items are empty' do - expect(described_class.bulk_insert!([])).to be(true) + it 'does nothing and returns an empty array when items are empty' do + expect(described_class.bulk_insert!([])).to eq([]) expect(described_class.count).to eq(0) end + + context 'with returns option set' do + context 'when is set to :ids' do + it 'return an array with the primary key values for all inserted records' do + items = described_class.valid_list(1) + + expect(described_class.bulk_insert!(items, returns: :ids)).to contain_exactly(a_kind_of(Integer)) + end + end + + context 'when is set to nil' do + it 'returns an empty array' do + items = described_class.valid_list(1) + + expect(described_class.bulk_insert!(items, returns: nil)).to eq([]) + end + end + + context 'when is set to anything else' do + it 'raises an error' do + items = described_class.valid_list(1) + + expect { described_class.bulk_insert!([items], returns: [:id, :name]) } + .to raise_error(ArgumentError, "returns needs to be :ids or nil") + end + end + end end context 'when duplicate items are to be inserted' do diff --git a/spec/requests/api/releases_spec.rb b/spec/requests/api/releases_spec.rb index e66e999dc27..0589554bf44 100644 --- a/spec/requests/api/releases_spec.rb +++ b/spec/requests/api/releases_spec.rb @@ -6,6 +6,7 @@ describe API::Releases do let(:project) { create(:project, :repository, :private) } let(:maintainer) { create(:user) } let(:reporter) { create(:user) } + let(:developer) { create(:user) } let(:guest) { create(:user) } let(:non_project_member) { create(:user) } let(:commit) { create(:commit, project: project) } @@ -15,6 +16,7 @@ describe API::Releases do project.add_maintainer(maintainer) project.add_reporter(reporter) project.add_guest(guest) + project.add_developer(developer) project.repository.add_tag(maintainer, 'v0.1', commit.id) project.repository.add_tag(maintainer, 'v0.2', commit.id) @@ -248,6 +250,24 @@ describe API::Releases do .to match_array(release.sources.map(&:url)) end + context 'with evidence' do + let!(:evidence) { create(:evidence, release: release) } + + it 'returns the evidence' do + get api("/projects/#{project.id}/releases/v0.1", maintainer) + + expect(json_response['evidences'].count).to eq(1) + end + + it '#collected_at' do + Timecop.freeze(Time.now.round) do + get api("/projects/#{project.id}/releases/v0.1", maintainer) + + expect(json_response['evidences'].first['collected_at'].to_datetime.to_i).to be_within(1.minute).of(release.evidences.first.created_at.to_i) + end + end + end + context 'when release has link asset' do let!(:link) do create(:release_link, diff --git a/spec/services/projects/prometheus/alerts/notify_service_spec.rb b/spec/services/projects/prometheus/alerts/notify_service_spec.rb new file mode 100644 index 00000000000..ce850e65329 --- /dev/null +++ b/spec/services/projects/prometheus/alerts/notify_service_spec.rb @@ -0,0 +1,343 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Projects::Prometheus::Alerts::NotifyService do + let_it_be(:project, reload: true) { create(:project) } + + let(:service) { described_class.new(project, nil, payload) } + let(:token_input) { 'token' } + + let!(:setting) do + create(:project_incident_management_setting, project: project, send_email: true, create_issue: true) + end + + let(:subject) { service.execute(token_input) } + + before do + # We use `let_it_be(:project)` so we make sure to clear caches + project.clear_memoization(:licensed_feature_available) + end + + shared_examples 'sends notification email' do + let(:notification_service) { spy } + + it 'sends a notification for firing alerts only' do + expect(NotificationService) + .to receive(:new) + .and_return(notification_service) + + expect(notification_service) + .to receive_message_chain(:async, :prometheus_alerts_fired) + + expect(subject).to eq(true) + end + end + + shared_examples 'processes incident issues' do |amount| + let(:create_incident_service) { spy } + + it 'processes issues' do + expect(IncidentManagement::ProcessPrometheusAlertWorker) + .to receive(:perform_async) + .with(project.id, kind_of(Hash)) + .exactly(amount).times + + Sidekiq::Testing.inline! do + expect(subject).to eq(true) + end + end + end + + shared_examples 'does not process incident issues' do + it 'does not process issues' do + expect(IncidentManagement::ProcessPrometheusAlertWorker) + .not_to receive(:perform_async) + + expect(subject).to eq(true) + end + end + + shared_examples 'persists events' do + let(:create_events_service) { spy } + + it 'persists events' do + expect(Projects::Prometheus::Alerts::CreateEventsService) + .to receive(:new) + .and_return(create_events_service) + + expect(create_events_service) + .to receive(:execute) + + expect(subject).to eq(true) + end + end + + shared_examples 'notifies alerts' do + it_behaves_like 'sends notification email' + it_behaves_like 'persists events' + end + + shared_examples 'no notifications' do + let(:notification_service) { spy } + let(:create_events_service) { spy } + + it 'does not notify' do + expect(notification_service).not_to receive(:async) + expect(create_events_service).not_to receive(:execute) + + expect(subject).to eq(false) + end + end + + context 'with valid payload' do + let(:alert_firing) { create(:prometheus_alert, project: project) } + let(:alert_resolved) { create(:prometheus_alert, project: project) } + let(:payload_raw) { payload_for(firing: [alert_firing], resolved: [alert_resolved]) } + let(:payload) { ActionController::Parameters.new(payload_raw).permit! } + let(:payload_alert_firing) { payload_raw['alerts'].first } + let(:token) { 'token' } + + context 'with project specific cluster' do + using RSpec::Parameterized::TableSyntax + + where(:cluster_enabled, :status, :configured_token, :token_input, :result) do + true | :installed | token | token | :success + true | :installed | nil | nil | :success + true | :updated | token | token | :success + true | :updating | token | token | :failure + true | :installed | token | 'x' | :failure + true | :installed | nil | token | :failure + true | :installed | token | nil | :failure + true | nil | token | token | :failure + false | :installed | token | token | :failure + end + + with_them do + before do + cluster = create(:cluster, :provided_by_user, + projects: [project], + enabled: cluster_enabled) + + if status + create(:clusters_applications_prometheus, status, + cluster: cluster, + alert_manager_token: configured_token) + end + end + + case result = params[:result] + when :success + it_behaves_like 'notifies alerts' + when :failure + it_behaves_like 'no notifications' + else + raise "invalid result: #{result.inspect}" + end + end + end + + context 'without project specific cluster' do + let!(:cluster) { create(:cluster, enabled: true) } + + it_behaves_like 'no notifications' + end + + context 'with manual prometheus installation' do + using RSpec::Parameterized::TableSyntax + + where(:alerting_setting, :configured_token, :token_input, :result) do + true | token | token | :success + true | token | 'x' | :failure + true | token | nil | :failure + false | nil | nil | :success + false | nil | token | :failure + end + + with_them do + let(:alert_manager_token) { token_input } + + before do + create(:prometheus_service, project: project) + + if alerting_setting + create(:project_alerting_setting, + project: project, + token: configured_token) + end + end + + case result = params[:result] + when :success + it_behaves_like 'notifies alerts' + when :failure + it_behaves_like 'no notifications' + else + raise "invalid result: #{result.inspect}" + end + end + end + + context 'alert emails' do + before do + create(:prometheus_service, project: project) + create(:project_alerting_setting, project: project, token: token) + end + + context 'when incident_management_setting does not exist' do + let!(:setting) { nil } + + it_behaves_like 'persists events' + + it 'does not send notification email', :sidekiq_might_not_need_inline do + expect_any_instance_of(NotificationService) + .not_to receive(:async) + + expect(subject).to eq(true) + end + end + + context 'when incident_management_setting.send_email is true' do + it_behaves_like 'notifies alerts' + end + + context 'incident_management_setting.send_email is false' do + let!(:setting) do + create(:project_incident_management_setting, send_email: false, project: project) + end + + it_behaves_like 'persists events' + + it 'does not send notification' do + expect(NotificationService).not_to receive(:new) + + expect(subject).to eq(true) + end + end + end + + context 'process incident issues' do + before do + create(:prometheus_service, project: project) + create(:project_alerting_setting, project: project, token: token) + end + + context 'with create_issue setting enabled' do + before do + setting.update!(create_issue: true) + end + + it_behaves_like 'processes incident issues', 2 + + context 'multiple firing alerts' do + let(:payload_raw) do + payload_for(firing: [alert_firing, alert_firing], resolved: []) + end + + it_behaves_like 'processes incident issues', 2 + end + + context 'without firing alerts' do + let(:payload_raw) do + payload_for(firing: [], resolved: [alert_resolved]) + end + + it_behaves_like 'processes incident issues', 1 + end + end + + context 'with create_issue setting disabled' do + before do + setting.update!(create_issue: false) + end + + it_behaves_like 'does not process incident issues' + end + end + end + + context 'with invalid payload' do + context 'without version' do + let(:payload) { {} } + + it_behaves_like 'no notifications' + end + + context 'when version is not "4"' do + let(:payload) { { 'version' => '5' } } + + it_behaves_like 'no notifications' + end + + context 'with missing alerts' do + let(:payload) { { 'version' => '4' } } + + it_behaves_like 'no notifications' + end + + context 'when the payload is too big' do + let(:payload) { { 'the-payload-is-too-big' => true } } + let(:deep_size_object) { instance_double(Gitlab::Utils::DeepSize, valid?: false) } + + before do + allow(Gitlab::Utils::DeepSize).to receive(:new).and_return(deep_size_object) + end + + it_behaves_like 'no notifications' + + it 'does not process issues' do + expect(IncidentManagement::ProcessPrometheusAlertWorker) + .not_to receive(:perform_async) + + subject + end + end + end + + private + + def payload_for(firing: [], resolved: []) + status = firing.any? ? 'firing' : 'resolved' + alerts = firing + resolved + alert_name = alerts.first.title + prometheus_metric_id = alerts.first.prometheus_metric_id.to_s + + alerts_map = \ + firing.map { |alert| map_alert_payload('firing', alert) } + + resolved.map { |alert| map_alert_payload('resolved', alert) } + + # See https://prometheus.io/docs/alerting/configuration/#%3Cwebhook_config%3E + { + 'version' => '4', + 'receiver' => 'gitlab', + 'status' => status, + 'alerts' => alerts_map, + 'groupLabels' => { + 'alertname' => alert_name + }, + 'commonLabels' => { + 'alertname' => alert_name, + 'gitlab' => 'hook', + 'gitlab_alert_id' => prometheus_metric_id + }, + 'commonAnnotations' => {}, + 'externalURL' => '', + 'groupKey' => "{}:{alertname=\'#{alert_name}\'}" + } + end + + def map_alert_payload(status, alert) + { + 'status' => status, + 'labels' => { + 'alertname' => alert.title, + 'gitlab' => 'hook', + 'gitlab_alert_id' => alert.prometheus_metric_id.to_s + }, + 'annotations' => {}, + 'startsAt' => '2018-09-24T08:57:31.095725221Z', + 'endsAt' => '0001-01-01T00:00:00Z', + 'generatorURL' => 'http://prometheus-prometheus-server-URL' + } + end +end diff --git a/spec/support/helpers/test_env.rb b/spec/support/helpers/test_env.rb index 66c2faac2dd..2e69c59c80a 100644 --- a/spec/support/helpers/test_env.rb +++ b/spec/support/helpers/test_env.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true require 'rspec/mocks' -require 'toml-rb' module TestEnv extend ActiveSupport::Concern @@ -87,7 +86,7 @@ module TestEnv 'conflict-resolvable-fork' => '404fa3f' }.freeze - TMP_TEST_PATH = Rails.root.join('tmp', 'tests', '**') + TMP_TEST_PATH = Rails.root.join('tmp', 'tests').freeze REPOS_STORAGE = 'default'.freeze SECOND_STORAGE_PATH = Rails.root.join('tmp', 'tests', 'second_storage') @@ -140,7 +139,7 @@ module TestEnv # # Keeps gitlab-shell and gitlab-test def clean_test_path - Dir[TMP_TEST_PATH].each do |entry| + Dir[File.join(TMP_TEST_PATH, '**')].each do |entry| unless test_dirs.include?(File.basename(entry)) FileUtils.rm_rf(entry) end @@ -164,7 +163,8 @@ module TestEnv install_dir: gitaly_dir, version: Gitlab::GitalyClient.expected_server_version, task: "gitlab:gitaly:install[#{install_gitaly_args}]") do - Gitlab::SetupHelper.create_gitaly_configuration(gitaly_dir, { 'default' => repos_path }, force: true) + Gitlab::SetupHelper::Gitaly.create_configuration(gitaly_dir, { 'default' => repos_path }, force: true) + Gitlab::SetupHelper::Praefect.create_configuration(gitaly_dir, { 'praefect' => repos_path }, force: true) start_gitaly(gitaly_dir) end end @@ -192,17 +192,38 @@ module TestEnv end end - @gitaly_pid = Integer(File.read('tmp/tests/gitaly.pid')) + gitaly_pid = Integer(File.read(TMP_TEST_PATH.join('gitaly.pid'))) + praefect_pid = Integer(File.read(TMP_TEST_PATH.join('praefect.pid'))) - Kernel.at_exit { stop_gitaly } + Kernel.at_exit { stop(gitaly_pid) } + Kernel.at_exit { stop(praefect_pid) } - wait_gitaly + wait('gitaly') + wait('praefect') end - def wait_gitaly + def stop(pid) + Process.kill('KILL', pid) + rescue Errno::ESRCH + # The process can already be gone if the test run was INTerrupted. + end + + def gitaly_url + ENV.fetch('GITALY_REPO_URL', nil) + end + + def socket_path(service) + TMP_TEST_PATH.join('gitaly', "#{service}.socket").to_s + end + + def praefect_socket_path + "unix:" + socket_path(:praefect) + end + + def wait(service) sleep_time = 10 sleep_interval = 0.1 - socket = Gitlab::GitalyClient.address('default').sub('unix:', '') + socket = socket_path(service) Integer(sleep_time / sleep_interval).times do Socket.unix(socket) @@ -211,19 +232,7 @@ module TestEnv sleep sleep_interval end - raise "could not connect to gitaly at #{socket.inspect} after #{sleep_time} seconds" - end - - def stop_gitaly - return unless @gitaly_pid - - Process.kill('KILL', @gitaly_pid) - rescue Errno::ESRCH - # The process can already be gone if the test run was INTerrupted. - end - - def gitaly_url - ENV.fetch('GITALY_REPO_URL', nil) + raise "could not connect to #{service} at #{socket.inspect} after #{sleep_time} seconds" end def setup_workhorse diff --git a/spec/support/praefect.rb b/spec/support/praefect.rb new file mode 100644 index 00000000000..3218275c2aa --- /dev/null +++ b/spec/support/praefect.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +require_relative 'helpers/test_env' + +RSpec.configure do |config| + config.before(:each, :praefect) do + allow(Gitlab.config.repositories.storages['default']).to receive(:[]).and_call_original + allow(Gitlab.config.repositories.storages['default']).to receive(:[]).with('gitaly_address') + .and_return(TestEnv.praefect_socket_path) + end +end diff --git a/spec/support/shared_examples/models/concerns/bulk_insert_safe_shared_examples.rb b/spec/support/shared_examples/models/concerns/bulk_insert_safe_shared_examples.rb index c6180a5a196..7bcd6191f1d 100644 --- a/spec/support/shared_examples/models/concerns/bulk_insert_safe_shared_examples.rb +++ b/spec/support/shared_examples/models/concerns/bulk_insert_safe_shared_examples.rb @@ -45,11 +45,11 @@ RSpec.shared_examples 'a BulkInsertSafe model' do |klass| expect { target_class.bulk_insert!(items) }.to change { target_class.count }.by(items.size) end - it 'returns true' do + it 'returns an empty array' do items = valid_items_for_bulk_insertion expect(items).not_to be_empty - expect(target_class.bulk_insert!(items)).to be true + expect(target_class.bulk_insert!(items)).to eq([]) end end @@ -69,7 +69,7 @@ RSpec.shared_examples 'a BulkInsertSafe model' do |klass| # it is not always possible to create invalid items if items.any? - expect(target_class.bulk_insert!(items, validate: false)).to be(true) + expect(target_class.bulk_insert!(items, validate: false)).to eq([]) expect(target_class.count).to eq(items.size) end end |