diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-05-19 18:44:42 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-05-19 18:44:42 +0300 |
commit | 4555e1b21c365ed8303ffb7a3325d773c9b8bf31 (patch) | |
tree | 5423a1c7516cffe36384133ade12572cf709398d /spec/support/shared_examples/services | |
parent | e570267f2f6b326480d284e0164a6464ba4081bc (diff) |
Add latest changes from gitlab-org/gitlab@13-12-stable-eev13.12.0-rc42
Diffstat (limited to 'spec/support/shared_examples/services')
23 files changed, 729 insertions, 122 deletions
diff --git a/spec/support/shared_examples/services/alert_management/alert_processing/alert_firing_shared_examples.rb b/spec/support/shared_examples/services/alert_management/alert_processing/alert_firing_shared_examples.rb new file mode 100644 index 00000000000..218a3462c35 --- /dev/null +++ b/spec/support/shared_examples/services/alert_management/alert_processing/alert_firing_shared_examples.rb @@ -0,0 +1,161 @@ +# frozen_string_literal: true + +# This shared_example requires the following variables: +# - `service`, the service which includes AlertManagement::AlertProcessing +RSpec.shared_examples 'creates an alert management alert or errors' do + it { is_expected.to be_success } + + it 'creates AlertManagement::Alert' do + expect(Gitlab::AppLogger).not_to receive(:warn) + + expect { subject }.to change(AlertManagement::Alert, :count).by(1) + end + + it 'executes the alert service hooks' do + expect_next_instance_of(AlertManagement::Alert) do |alert| + expect(alert).to receive(:execute_services) + end + + subject + end + + context 'and fails to save' do + let(:errors) { double(messages: { hosts: ['hosts array is over 255 chars'] })} + + before do + allow(service).to receive(:alert).and_call_original + allow(service).to receive_message_chain(:alert, :save).and_return(false) + allow(service).to receive_message_chain(:alert, :errors).and_return(errors) + end + + it_behaves_like 'alerts service responds with an error', :bad_request + + it 'writes a warning to the log' do + expect(Gitlab::AppLogger).to receive(:warn).with( + message: "Unable to create AlertManagement::Alert from #{source}", + project_id: project.id, + alert_errors: { hosts: ['hosts array is over 255 chars'] } + ) + + subject + end + end +end + +# This shared_example requires the following variables: +# - last_alert_attributes, last created alert +# - project, project that alert created +# - payload_raw, hash representation of payload +# - environment, project's environment +# - fingerprint, fingerprint hash +RSpec.shared_examples 'properly assigns the alert properties' do + specify do + subject + + expect(last_alert_attributes).to match({ + project_id: project.id, + title: payload_raw.fetch(:title), + started_at: Time.zone.parse(payload_raw.fetch(:start_time)), + severity: payload_raw.fetch(:severity, nil), + status: AlertManagement::Alert.status_value(:triggered), + events: 1, + domain: domain, + hosts: payload_raw.fetch(:hosts, nil), + payload: payload_raw.with_indifferent_access, + issue_id: nil, + description: payload_raw.fetch(:description, nil), + monitoring_tool: payload_raw.fetch(:monitoring_tool, nil), + service: payload_raw.fetch(:service, nil), + fingerprint: Digest::SHA1.hexdigest(fingerprint), + environment_id: environment.id, + ended_at: nil, + prometheus_alert_id: nil + }.with_indifferent_access) + end +end + +RSpec.shared_examples 'does not create an alert management alert' do + specify do + expect { subject }.not_to change(AlertManagement::Alert, :count) + end +end + +# This shared_example requires the following variables: +# - `alert`, the alert for which events should be incremented +RSpec.shared_examples 'adds an alert management alert event' do + specify do + expect(alert).not_to receive(:execute_services) + + expect { subject }.to change { alert.reload.events }.by(1) + + expect(subject).to be_success + end + + it_behaves_like 'does not create an alert management alert' +end + +# This shared_example requires the following variables: +# - `alert`, the alert for which events should not be incremented +RSpec.shared_examples 'does not add an alert management alert event' do + specify do + expect { subject }.not_to change { alert.reload.events } + end +end + +RSpec.shared_examples 'processes new firing alert' do + include_examples 'processes never-before-seen alert' + + context 'for an existing alert with the same fingerprint' do + let_it_be(:gitlab_fingerprint) { Digest::SHA1.hexdigest(fingerprint) } + + context 'which is triggered' do + let_it_be(:alert) { create(:alert_management_alert, :triggered, fingerprint: gitlab_fingerprint, project: project) } + + it_behaves_like 'adds an alert management alert event' + it_behaves_like 'sends alert notification emails if enabled' + it_behaves_like 'processes incident issues if enabled', with_issue: true + + it_behaves_like 'does not create an alert management alert' + it_behaves_like 'does not create a system note for alert' + + context 'with an existing resolved alert as well' do + let_it_be(:resolved_alert) { create(:alert_management_alert, :resolved, project: project, fingerprint: gitlab_fingerprint) } + + it_behaves_like 'adds an alert management alert event' + it_behaves_like 'sends alert notification emails if enabled' + it_behaves_like 'processes incident issues if enabled', with_issue: true + + it_behaves_like 'does not create an alert management alert' + it_behaves_like 'does not create a system note for alert' + end + end + + context 'which is acknowledged' do + let_it_be(:alert) { create(:alert_management_alert, :acknowledged, fingerprint: gitlab_fingerprint, project: project) } + + it_behaves_like 'adds an alert management alert event' + it_behaves_like 'processes incident issues if enabled', with_issue: true + + it_behaves_like 'does not create an alert management alert' + it_behaves_like 'does not create a system note for alert' + it_behaves_like 'does not send alert notification emails' + end + + context 'which is ignored' do + let_it_be(:alert) { create(:alert_management_alert, :ignored, fingerprint: gitlab_fingerprint, project: project) } + + it_behaves_like 'adds an alert management alert event' + it_behaves_like 'processes incident issues if enabled', with_issue: true + + it_behaves_like 'does not create an alert management alert' + it_behaves_like 'does not create a system note for alert' + it_behaves_like 'does not send alert notification emails' + end + + context 'which is resolved' do + let_it_be(:alert) { create(:alert_management_alert, :resolved, fingerprint: gitlab_fingerprint, project: project) } + + include_examples 'processes never-before-seen alert' + end + end +end diff --git a/spec/support/shared_examples/services/alert_management/alert_processing/alert_recovery_shared_examples.rb b/spec/support/shared_examples/services/alert_management/alert_processing/alert_recovery_shared_examples.rb new file mode 100644 index 00000000000..86e7da5bcbe --- /dev/null +++ b/spec/support/shared_examples/services/alert_management/alert_processing/alert_recovery_shared_examples.rb @@ -0,0 +1,113 @@ +# frozen_string_literal: true + +# This shared_example requires the following variables: +# - `alert`, the alert to be resolved +RSpec.shared_examples 'resolves an existing alert management alert' do + it 'sets the end time and status' do + expect(Gitlab::AppLogger).not_to receive(:warn) + + expect { subject } + .to change { alert.reload.resolved? }.to(true) + .and change { alert.ended_at.present? }.to(true) + + expect(subject).to be_success + end +end + +# This shared_example requires the following variables: +# - `alert`, the alert not to be updated +RSpec.shared_examples 'does not change the alert end time' do + specify do + expect { subject }.not_to change { alert.reload.ended_at } + end +end + +# This shared_example requires the following variables: +# - `project`, expected project for an incoming alert +# - `service`, a service which includes AlertManagement::AlertProcessing +# - `alert` (optional), the alert which should fail to resolve. If not +# included, the log is expected to correspond to a new alert +RSpec.shared_examples 'writes a warning to the log for a failed alert status update' do + before do + allow(service).to receive(:alert).and_call_original + allow(service).to receive_message_chain(:alert, :resolve).and_return(false) + end + + specify do + expect(Gitlab::AppLogger).to receive(:warn).with( + message: 'Unable to update AlertManagement::Alert status to resolved', + project_id: project.id, + alert_id: alert ? alert.id : (last_alert_id + 1) + ) + + # Failure to resolve a recovery alert is not a critical failure + expect(subject).to be_success + end + + private + + def last_alert_id + AlertManagement::Alert.connection + .select_value("SELECT nextval('#{AlertManagement::Alert.sequence_name}')") + end +end + +RSpec.shared_examples 'processes recovery alert' do + context 'seen for the first time' do + let(:alert) { AlertManagement::Alert.last } + + include_examples 'processes never-before-seen recovery alert' + end + + context 'for an existing alert with the same fingerprint' do + let_it_be(:gitlab_fingerprint) { Digest::SHA1.hexdigest(fingerprint) } + + context 'which is triggered' do + let_it_be(:alert) { create(:alert_management_alert, :triggered, project: project, fingerprint: gitlab_fingerprint, monitoring_tool: source) } + + it_behaves_like 'resolves an existing alert management alert' + it_behaves_like 'creates expected system notes for alert', :recovery_alert, :resolve_alert + it_behaves_like 'sends alert notification emails if enabled' + it_behaves_like 'closes related incident if enabled' + it_behaves_like 'writes a warning to the log for a failed alert status update' + + it_behaves_like 'does not create an alert management alert' + it_behaves_like 'does not process incident issues' + it_behaves_like 'does not add an alert management alert event' + end + + context 'which is ignored' do + let_it_be(:alert) { create(:alert_management_alert, :ignored, project: project, fingerprint: gitlab_fingerprint, monitoring_tool: source) } + + it_behaves_like 'resolves an existing alert management alert' + it_behaves_like 'creates expected system notes for alert', :recovery_alert, :resolve_alert + it_behaves_like 'sends alert notification emails if enabled' + it_behaves_like 'closes related incident if enabled' + it_behaves_like 'writes a warning to the log for a failed alert status update' + + it_behaves_like 'does not create an alert management alert' + it_behaves_like 'does not process incident issues' + it_behaves_like 'does not add an alert management alert event' + end + + context 'which is acknowledged' do + let_it_be(:alert) { create(:alert_management_alert, :acknowledged, project: project, fingerprint: gitlab_fingerprint, monitoring_tool: source) } + + it_behaves_like 'resolves an existing alert management alert' + it_behaves_like 'creates expected system notes for alert', :recovery_alert, :resolve_alert + it_behaves_like 'sends alert notification emails if enabled' + it_behaves_like 'closes related incident if enabled' + it_behaves_like 'writes a warning to the log for a failed alert status update' + + it_behaves_like 'does not create an alert management alert' + it_behaves_like 'does not process incident issues' + it_behaves_like 'does not add an alert management alert event' + end + + context 'which is resolved' do + let_it_be(:alert) { create(:alert_management_alert, :resolved, project: project, fingerprint: gitlab_fingerprint, monitoring_tool: source) } + + include_examples 'processes never-before-seen recovery alert' + end + end +end diff --git a/spec/support/shared_examples/services/alert_management/alert_processing/incident_creation_shared_examples.rb b/spec/support/shared_examples/services/alert_management/alert_processing/incident_creation_shared_examples.rb new file mode 100644 index 00000000000..c6ac07b6dd5 --- /dev/null +++ b/spec/support/shared_examples/services/alert_management/alert_processing/incident_creation_shared_examples.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +# Expects usage of 'incident settings enabled' context. +# +# This shared_example includes the following option: +# - with_issue: includes a test for when the defined `alert` has an associated issue +# +# This shared_example requires the following variables: +# - `alert`, required if :with_issue is true +RSpec.shared_examples 'processes incident issues if enabled' do |with_issue: false| + include_examples 'processes incident issues', with_issue + + context 'with incident setting disabled' do + let(:create_issue) { false } + + it_behaves_like 'does not process incident issues' + end +end + +RSpec.shared_examples 'processes incident issues' do |with_issue: false| + before do + allow_next_instance_of(AlertManagement::Alert) do |alert| + allow(alert).to receive(:execute_services) + end + end + + specify do + expect(IncidentManagement::ProcessAlertWorkerV2) + .to receive(:perform_async) + .with(kind_of(Integer)) + + Sidekiq::Testing.inline! do + expect(subject).to be_success + end + end + + context 'with issue', if: with_issue do + before do + alert.update!(issue: create(:issue, project: project)) + end + + it_behaves_like 'does not process incident issues' + end +end + +RSpec.shared_examples 'does not process incident issues' do + specify do + expect(IncidentManagement::ProcessAlertWorkerV2).not_to receive(:perform_async) + + subject + end +end diff --git a/spec/support/shared_examples/services/alert_management/alert_processing/incident_resolution_shared_examples.rb b/spec/support/shared_examples/services/alert_management/alert_processing/incident_resolution_shared_examples.rb new file mode 100644 index 00000000000..132f1e0422e --- /dev/null +++ b/spec/support/shared_examples/services/alert_management/alert_processing/incident_resolution_shared_examples.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +# Expects usage of 'incident settings enabled' context. +# +# This shared_example requires the following variables: +# - `alert`, alert for which related incidents should be closed +# - `project`, project of the alert +RSpec.shared_examples 'closes related incident if enabled' do + context 'with issue' do + before do + alert.update!(issue: create(:issue, project: project)) + end + + it { expect { subject }.to change { alert.issue.reload.closed? }.from(false).to(true) } + it { expect { subject }.to change(ResourceStateEvent, :count).by(1) } + end + + context 'without issue' do + it { expect { subject }.not_to change { alert.reload.issue } } + it { expect { subject }.not_to change(ResourceStateEvent, :count) } + end + + context 'with incident setting disabled' do + let(:auto_close_incident) { false } + + it_behaves_like 'does not close related incident' + end +end + +RSpec.shared_examples 'does not close related incident' do + context 'with issue' do + before do + alert.update!(issue: create(:issue, project: project)) + end + + it { expect { subject }.not_to change { alert.issue.reload.state } } + it { expect { subject }.not_to change(ResourceStateEvent, :count) } + end + + context 'without issue' do + it { expect { subject }.not_to change { alert.reload.issue } } + it { expect { subject }.not_to change(ResourceStateEvent, :count) } + end +end diff --git a/spec/support/shared_examples/services/alert_management/alert_processing/notifications_shared_examples.rb b/spec/support/shared_examples/services/alert_management/alert_processing/notifications_shared_examples.rb new file mode 100644 index 00000000000..5f30b58176b --- /dev/null +++ b/spec/support/shared_examples/services/alert_management/alert_processing/notifications_shared_examples.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +# Expects usage of 'incident settings enabled' context. +# +# This shared_example includes the following option: +# - count: number of notifications expected to be sent +RSpec.shared_examples 'sends alert notification emails if enabled' do |count: 1| + include_examples 'sends alert notification emails', count + + context 'with email setting disabled' do + let(:send_email) { false } + + it_behaves_like 'does not send alert notification emails' + end +end + +RSpec.shared_examples 'sends alert notification emails' do |count: 1| + let(:notification_async) { double(NotificationService::Async) } + + specify do + allow(NotificationService).to receive_message_chain(:new, :async).and_return(notification_async) + expect(notification_async).to receive(:prometheus_alerts_fired).exactly(count).times + + subject + end +end + +RSpec.shared_examples 'does not send alert notification emails' do + specify do + expect(NotificationService).not_to receive(:new) + + subject + end +end diff --git a/spec/support/shared_examples/services/alert_management/alert_processing/system_notes_shared_examples.rb b/spec/support/shared_examples/services/alert_management/alert_processing/system_notes_shared_examples.rb new file mode 100644 index 00000000000..57d598c0259 --- /dev/null +++ b/spec/support/shared_examples/services/alert_management/alert_processing/system_notes_shared_examples.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +# This shared_example includes the following option: +# - notes: any of [:new_alert, :recovery_alert, :resolve_alert]. +# Represents which notes are expected to be created. +# +# This shared_example requires the following variables: +# - `source` (optional), the monitoring tool or integration name +# expected in the applicable system notes +RSpec.shared_examples 'creates expected system notes for alert' do |*notes| + let(:expected_note_count) { expected_notes.length } + let(:new_notes) { Note.last(expected_note_count).pluck(:note) } + let(:expected_notes) do + { + new_alert: source, + recovery_alert: source, + resolve_alert: 'Resolved' + }.slice(*notes) + end + + it "for #{notes.join(', ')}" do + expect { subject }.to change(Note, :count).by(expected_note_count) + + expected_notes.each_value.with_index do |value, index| + expect(new_notes[index]).to include(value) + end + end +end + +RSpec.shared_examples 'does not create a system note for alert' do + specify do + expect { subject }.not_to change(Note, :count) + end +end diff --git a/spec/support/shared_examples/services/alert_management_shared_examples.rb b/spec/support/shared_examples/services/alert_management_shared_examples.rb index d9f28a97a0f..827ae42f970 100644 --- a/spec/support/shared_examples/services/alert_management_shared_examples.rb +++ b/spec/support/shared_examples/services/alert_management_shared_examples.rb @@ -1,111 +1,77 @@ # frozen_string_literal: true -RSpec.shared_examples 'creates an alert management alert' do - it { is_expected.to be_success } +RSpec.shared_examples 'alerts service responds with an error and takes no actions' do |http_status| + include_examples 'alerts service responds with an error', http_status - it 'creates AlertManagement::Alert' do - expect { subject }.to change(AlertManagement::Alert, :count).by(1) - end - - it 'executes the alert service hooks' do - expect_next_instance_of(AlertManagement::Alert) do |alert| - expect(alert).to receive(:execute_services) - end + it_behaves_like 'does not create an alert management alert' + it_behaves_like 'does not create a system note for alert' + it_behaves_like 'does not process incident issues' + it_behaves_like 'does not send alert notification emails' +end - subject +RSpec.shared_examples 'alerts service responds with an error' do |http_status| + specify do + expect(subject).to be_error + expect(subject.http_status).to eq(http_status) end end # This shared_example requires the following variables: -# - last_alert_attributes, last created alert -# - project, project that alert created -# - payload_raw, hash representation of payload -# - environment, project's environment -# - fingerprint, fingerprint hash -RSpec.shared_examples 'assigns the alert properties' do - it 'ensures that created alert has all data properly assigned' do - subject - - expect(last_alert_attributes).to match( - project_id: project.id, - title: payload_raw.fetch(:title), - started_at: Time.zone.parse(payload_raw.fetch(:start_time)), - severity: payload_raw.fetch(:severity), - status: AlertManagement::Alert.status_value(:triggered), - events: 1, - domain: domain, - hosts: payload_raw.fetch(:hosts), - payload: payload_raw.with_indifferent_access, - issue_id: nil, - description: payload_raw.fetch(:description), - monitoring_tool: payload_raw.fetch(:monitoring_tool), - service: payload_raw.fetch(:service), - fingerprint: Digest::SHA1.hexdigest(fingerprint), - environment_id: environment.id, - ended_at: nil, - prometheus_alert_id: nil +# - `service`, a service which includes ::IncidentManagement::Settings +RSpec.shared_context 'incident management settings enabled' do + let(:auto_close_incident) { true } + let(:create_issue) { true } + let(:send_email) { true } + + let(:incident_management_setting) do + double( + auto_close_incident?: auto_close_incident, + create_issue?: create_issue, + send_email?: send_email ) end -end -RSpec.shared_examples 'does not an create alert management alert' do - it 'does not create alert' do - expect { subject }.not_to change(AlertManagement::Alert, :count) + before do + allow(ProjectServiceWorker).to receive(:perform_async) + allow(service) + .to receive(:incident_management_setting) + .and_return(incident_management_setting) end end -RSpec.shared_examples 'adds an alert management alert event' do - it { is_expected.to be_success } - - it 'does not create an alert' do - expect { subject }.not_to change(AlertManagement::Alert, :count) - end - - it 'increases alert events count' do - expect { subject }.to change { alert.reload.events }.by(1) - end - - it 'does not executes the alert service hooks' do - expect(alert).not_to receive(:execute_services) - - subject - end +RSpec.shared_examples 'processes never-before-seen alert' do + it_behaves_like 'creates an alert management alert or errors' + it_behaves_like 'creates expected system notes for alert', :new_alert + it_behaves_like 'processes incident issues if enabled' + it_behaves_like 'sends alert notification emails if enabled' end -RSpec.shared_examples 'processes incident issues' do - let(:create_incident_service) { spy } - - before do - allow_any_instance_of(AlertManagement::Alert).to receive(:execute_services) +RSpec.shared_examples 'processes never-before-seen recovery alert' do + it_behaves_like 'creates an alert management alert or errors' + it_behaves_like 'creates expected system notes for alert', :new_alert, :recovery_alert, :resolve_alert + it_behaves_like 'sends alert notification emails if enabled' + it_behaves_like 'does not process incident issues' + it_behaves_like 'writes a warning to the log for a failed alert status update' do + let(:alert) { nil } # Ensure the next alert id is used end - it 'processes issues' do - expect(IncidentManagement::ProcessAlertWorker) - .to receive(:perform_async) - .with(nil, nil, kind_of(Integer)) - .once + it 'resolves the alert' do + subject - Sidekiq::Testing.inline! do - expect(subject).to be_success - end + expect(AlertManagement::Alert.last.ended_at).to be_present + expect(AlertManagement::Alert.last.resolved?).to be(true) end end -RSpec.shared_examples 'does not process incident issues' do - it 'does not process issues' do - expect(IncidentManagement::ProcessAlertWorker) - .not_to receive(:perform_async) +RSpec.shared_examples 'processes one firing and one resolved prometheus alerts' do + it 'creates AlertManagement::Alert' do + expect(Gitlab::AppLogger).not_to receive(:warn) - expect(subject).to be_success + expect { subject } + .to change(AlertManagement::Alert, :count).by(2) + .and change(Note, :count).by(4) end -end - -RSpec.shared_examples 'does not process incident issues due to error' do |http_status:| - it 'does not process issues' do - expect(IncidentManagement::ProcessAlertWorker) - .not_to receive(:perform_async) - expect(subject).to be_error - expect(subject.http_status).to eq(http_status) - end + it_behaves_like 'processes incident issues' + it_behaves_like 'sends alert notification emails', count: 2 end diff --git a/spec/support/shared_examples/services/boards/boards_recent_visit_shared_examples.rb b/spec/support/shared_examples/services/boards/boards_recent_visit_shared_examples.rb new file mode 100644 index 00000000000..68ea460dabc --- /dev/null +++ b/spec/support/shared_examples/services/boards/boards_recent_visit_shared_examples.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'boards recent visit' do + let_it_be(:user) { create(:user) } + + describe '#visited' do + it 'creates a visit if one does not exists' do + expect { described_class.visited!(user, board) }.to change(described_class, :count).by(1) + end + + shared_examples 'was visited previously' do + let_it_be(:visit) do + create(visit_relation, + board_parent_relation => board_parent, + board_relation => board, + user: user, + updated_at: 7.days.ago + ) + end + + it 'updates the timestamp' do + freeze_time do + described_class.visited!(user, board) + + expect(described_class.count).to eq 1 + expect(described_class.first.updated_at).to be_like_time(Time.zone.now) + end + end + end + + it_behaves_like 'was visited previously' + + context 'when we try to create a visit that is not unique' do + before do + expect(described_class).to receive(:find_or_create_by).and_raise(ActiveRecord::RecordNotUnique, 'record not unique') + expect(described_class).to receive(:find_or_create_by).and_return(visit) + end + + it_behaves_like 'was visited previously' + end + end + + describe '#latest' do + def create_visit(time) + create(visit_relation, board_parent_relation => board_parent, user: user, updated_at: time) + end + + it 'returns the most recent visited' do + create_visit(7.days.ago) + create_visit(5.days.ago) + recent = create_visit(1.day.ago) + + expect(described_class.latest(user, board_parent)).to eq recent + end + + it 'returns last 3 visited boards' do + create_visit(7.days.ago) + visit1 = create_visit(3.days.ago) + visit2 = create_visit(2.days.ago) + visit3 = create_visit(5.days.ago) + + expect(described_class.latest(user, board_parent, count: 3)).to eq([visit2, visit1, visit3]) + end + end +end diff --git a/spec/support/shared_examples/services/boards/create_service_shared_examples.rb b/spec/support/shared_examples/services/boards/create_service_shared_examples.rb new file mode 100644 index 00000000000..63b5e3a5a84 --- /dev/null +++ b/spec/support/shared_examples/services/boards/create_service_shared_examples.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'boards recent visit create service' do + let_it_be(:user) { create(:user) } + + subject(:service) { described_class.new(board.resource_parent, user) } + + it 'returns nil when there is no user' do + service.current_user = nil + + expect(service.execute(board)).to be_nil + end + + it 'returns nil when database is read only' do + allow(Gitlab::Database).to receive(:read_only?) { true } + + expect(service.execute(board)).to be_nil + end + + it 'records the visit' do + expect(model).to receive(:visited!).once + + service.execute(board) + end +end diff --git a/spec/support/shared_examples/services/boards/issues_move_service_shared_examples.rb b/spec/support/shared_examples/services/boards/issues_move_service_shared_examples.rb index 4aa5d7d890b..7d4fbeea0dc 100644 --- a/spec/support/shared_examples/services/boards/issues_move_service_shared_examples.rb +++ b/spec/support/shared_examples/services/boards/issues_move_service_shared_examples.rb @@ -146,7 +146,7 @@ RSpec.shared_examples 'issues move service' do |group| params.merge!(move_after_id: issue1.id, move_before_id: issue2.id) match_params = { move_between_ids: [issue1.id, issue2.id], board_group_id: parent.id } - expect(Issues::UpdateService).to receive(:new).with(issue.project, user, match_params).and_return(double(execute: build(:issue))) + expect(Issues::UpdateService).to receive(:new).with(project: issue.project, current_user: user, params: match_params).and_return(double(execute: build(:issue))) described_class.new(parent, user, params).execute(issue) end diff --git a/spec/support/shared_examples/services/boards/lists_destroy_service_shared_examples.rb b/spec/support/shared_examples/services/boards/lists_destroy_service_shared_examples.rb index 94da405e491..af88644ced7 100644 --- a/spec/support/shared_examples/services/boards/lists_destroy_service_shared_examples.rb +++ b/spec/support/shared_examples/services/boards/lists_destroy_service_shared_examples.rb @@ -3,30 +3,27 @@ RSpec.shared_examples 'lists destroy service' do context 'when list type is label' do it 'removes list from board' do - list = create(:list, board: board) service = described_class.new(parent, user) expect { service.execute(list) }.to change(board.lists, :count).by(-1) end it 'decrements position of higher lists' do - development = create(:list, board: board, position: 0) - review = create(:list, board: board, position: 1) - staging = create(:list, board: board, position: 2) - closed = board.lists.closed.first + development = create(list_type, params.merge(position: 0)) + review = create(list_type, params.merge(position: 1)) + staging = create(list_type, params.merge(position: 2)) described_class.new(parent, user).execute(development) expect(review.reload.position).to eq 0 expect(staging.reload.position).to eq 1 - expect(closed.reload.position).to be_nil + expect(closed_list.reload.position).to be_nil end end it 'does not remove list from board when list type is closed' do - list = board.lists.closed.first service = described_class.new(parent, user) - expect { service.execute(list) }.not_to change(board.lists, :count) + expect { service.execute(closed_list) }.not_to change(board.lists, :count) end end diff --git a/spec/support/shared_examples/services/common_system_notes_shared_examples.rb b/spec/support/shared_examples/services/common_system_notes_shared_examples.rb index 7b277d4bede..ce412ef55de 100644 --- a/spec/support/shared_examples/services/common_system_notes_shared_examples.rb +++ b/spec/support/shared_examples/services/common_system_notes_shared_examples.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true RSpec.shared_examples 'system note creation' do |update_params, note_text| - subject { described_class.new(project, user).execute(issuable, old_labels: []) } + subject { described_class.new(project: project, current_user: user).execute(issuable, old_labels: []) } before do issuable.assign_attributes(update_params) @@ -18,7 +18,7 @@ RSpec.shared_examples 'system note creation' do |update_params, note_text| end RSpec.shared_examples 'draft notes creation' do |action| - subject { described_class.new(project, user).execute(issuable, old_labels: []) } + subject { described_class.new(project: project, current_user: user).execute(issuable, old_labels: []) } it 'creates Draft toggle and title change notes' do expect { subject }.to change { Note.count }.from(0).to(2) diff --git a/spec/support/shared_examples/services/destroy_label_links_shared_examples.rb b/spec/support/shared_examples/services/destroy_label_links_shared_examples.rb new file mode 100644 index 00000000000..d2b52468c25 --- /dev/null +++ b/spec/support/shared_examples/services/destroy_label_links_shared_examples.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +RSpec.shared_examples_for 'service deleting label links of an issuable' do + let_it_be(:label_link) { create(:label_link, target: target) } + + def execute + described_class.new(target.id, target.class.name).execute + end + + it 'deletes label links for specified target ID and type' do + control_count = ActiveRecord::QueryRecorder.new { execute }.count + + # Create more label links for the target + create(:label_link, target: target) + create(:label_link, target: target) + + expect { execute }.not_to exceed_query_limit(control_count) + expect(target.reload.label_links.count).to eq(0) + end +end diff --git a/spec/support/shared_examples/services/issuable/destroy_service_shared_examples.rb b/spec/support/shared_examples/services/issuable/destroy_service_shared_examples.rb index ccc287c10de..e776c098fa0 100644 --- a/spec/support/shared_examples/services/issuable/destroy_service_shared_examples.rb +++ b/spec/support/shared_examples/services/issuable/destroy_service_shared_examples.rb @@ -1,10 +1,6 @@ # frozen_string_literal: true shared_examples_for 'service deleting todos' do - before do - stub_feature_flags(destroy_issuable_todos_async: group) - end - it 'destroys associated todos asynchronously' do expect(TodosDestroyer::DestroyedIssuableWorker) .to receive(:perform_async) @@ -12,20 +8,14 @@ shared_examples_for 'service deleting todos' do subject.execute(issuable) end +end - context 'when destroy_issuable_todos_async feature is disabled for group' do - before do - stub_feature_flags(destroy_issuable_todos_async: false) - end - - it 'destroy associated todos synchronously' do - expect_next_instance_of(TodosDestroyer::DestroyedIssuableWorker) do |worker| - expect(worker) - .to receive(:perform) - .with(issuable.id, issuable.class.name) - end +shared_examples_for 'service deleting label links' do + it 'destroys associated label links asynchronously' do + expect(Issuable::LabelLinksDestroyWorker) + .to receive(:perform_async) + .with(issuable.id, issuable.class.name) - subject.execute(issuable) - end + subject.execute(issuable) end end diff --git a/spec/support/shared_examples/services/issuable_shared_examples.rb b/spec/support/shared_examples/services/issuable_shared_examples.rb index 5b3e0f9e0b9..a50a386afe1 100644 --- a/spec/support/shared_examples/services/issuable_shared_examples.rb +++ b/spec/support/shared_examples/services/issuable_shared_examples.rb @@ -4,14 +4,14 @@ RSpec.shared_examples 'cache counters invalidator' do it 'invalidates counter cache for assignees' do expect_any_instance_of(User).to receive(:invalidate_merge_request_cache_counts) - described_class.new(project, user, {}).execute(merge_request) + described_class.new(project: project, current_user: user).execute(merge_request) end end RSpec.shared_examples 'updating a single task' do def update_issuable(opts) issuable = try(:issue) || try(:merge_request) - described_class.new(project, user, opts).execute(issuable) + described_class.new(project: project, current_user: user, params: opts).execute(issuable) end before do diff --git a/spec/support/shared_examples/services/merge_request_shared_examples.rb b/spec/support/shared_examples/services/merge_request_shared_examples.rb index 178b6bc47e1..d2595b92cbc 100644 --- a/spec/support/shared_examples/services/merge_request_shared_examples.rb +++ b/spec/support/shared_examples/services/merge_request_shared_examples.rb @@ -70,7 +70,7 @@ RSpec.shared_examples 'merge request reviewers cache counters invalidator' do it 'invalidates counter cache for reviewers' do expect(merge_request.reviewers).to all(receive(:invalidate_merge_request_cache_counts)) - described_class.new(project, user, {}).execute(merge_request) + described_class.new(project: project, current_user: user).execute(merge_request) end end @@ -86,7 +86,7 @@ RSpec.shared_examples_for 'a service that can create a merge request' do context 'when project has been forked', :sidekiq_might_not_need_inline do let(:forked_project) { fork_project(project, user1, repository: true) } - let(:service) { described_class.new(forked_project, user1, changes, push_options) } + let(:service) { described_class.new(project: forked_project, current_user: user1, changes: changes, push_options: push_options) } before do allow(forked_project).to receive(:empty_repo?).and_return(false) diff --git a/spec/support/shared_examples/services/metrics/dashboard_shared_examples.rb b/spec/support/shared_examples/services/metrics/dashboard_shared_examples.rb index b6c33eac7b4..4df12f7849b 100644 --- a/spec/support/shared_examples/services/metrics/dashboard_shared_examples.rb +++ b/spec/support/shared_examples/services/metrics/dashboard_shared_examples.rb @@ -12,13 +12,22 @@ RSpec.shared_examples 'misconfigured dashboard service response' do |status_code end RSpec.shared_examples 'valid dashboard service response for schema' do + file_ref_resolver = proc do |uri| + file = Rails.root.join(uri.path) + raise StandardError, "Ref file #{uri.path} must be json" unless uri.path.ends_with?('.json') + raise StandardError, "File #{file.to_path} doesn't exists" unless file.exist? + + Gitlab::Json.parse(File.read(file)) + end + it 'returns a json representation of the dashboard' do result = service_call expect(result.keys).to contain_exactly(:dashboard, :status) expect(result[:status]).to eq(:success) - expect(JSON::Validator.fully_validate(dashboard_schema, result[:dashboard])).to be_empty + validator = JSONSchemer.schema(dashboard_schema, ref_resolver: file_ref_resolver) + expect(validator.valid?(result[:dashboard].with_indifferent_access)).to be true end end diff --git a/spec/support/shared_examples/services/namespace_package_settings_shared_examples.rb b/spec/support/shared_examples/services/namespace_package_settings_shared_examples.rb index 8398dd3c453..f7a6bd3676a 100644 --- a/spec/support/shared_examples/services/namespace_package_settings_shared_examples.rb +++ b/spec/support/shared_examples/services/namespace_package_settings_shared_examples.rb @@ -7,6 +7,8 @@ RSpec.shared_examples 'updating the namespace package setting attributes' do |fr expect { subject } .to change { namespace.package_settings.reload.maven_duplicates_allowed }.from(from[:maven_duplicates_allowed]).to(to[:maven_duplicates_allowed]) .and change { namespace.package_settings.reload.maven_duplicate_exception_regex }.from(from[:maven_duplicate_exception_regex]).to(to[:maven_duplicate_exception_regex]) + .and change { namespace.package_settings.reload.generic_duplicates_allowed }.from(from[:generic_duplicates_allowed]).to(to[:generic_duplicates_allowed]) + .and change { namespace.package_settings.reload.generic_duplicate_exception_regex }.from(from[:generic_duplicate_exception_regex]).to(to[:generic_duplicate_exception_regex]) end end @@ -26,6 +28,8 @@ RSpec.shared_examples 'creating the namespace package setting' do expect(namespace.package_setting_relation.maven_duplicates_allowed).to eq(package_settings[:maven_duplicates_allowed]) expect(namespace.package_setting_relation.maven_duplicate_exception_regex).to eq(package_settings[:maven_duplicate_exception_regex]) + expect(namespace.package_setting_relation.generic_duplicates_allowed).to eq(package_settings[:generic_duplicates_allowed]) + expect(namespace.package_setting_relation.generic_duplicate_exception_regex).to eq(package_settings[:generic_duplicate_exception_regex]) end it_behaves_like 'returning a success' diff --git a/spec/support/shared_examples/services/packages_shared_examples.rb b/spec/support/shared_examples/services/packages_shared_examples.rb index 4e34c191306..72878e925dc 100644 --- a/spec/support/shared_examples/services/packages_shared_examples.rb +++ b/spec/support/shared_examples/services/packages_shared_examples.rb @@ -203,7 +203,9 @@ RSpec.shared_examples 'filters on each package_type' do |is_project: false| let_it_be(:package7) { create(:generic_package, project: project) } let_it_be(:package8) { create(:golang_package, project: project) } let_it_be(:package9) { create(:debian_package, project: project) } - let_it_be(:package9) { create(:rubygems_package, project: project) } + let_it_be(:package10) { create(:rubygems_package, project: project) } + let_it_be(:package11) { create(:helm_package, project: project) } + let_it_be(:package12) { create(:terraform_module_package, project: project) } Packages::Package.package_types.keys.each do |package_type| context "for package type #{package_type}" do diff --git a/spec/support/shared_examples/services/projects/update_repository_storage_service_shared_examples.rb b/spec/support/shared_examples/services/projects/update_repository_storage_service_shared_examples.rb index 1fb1b9f79b2..275ddebc18c 100644 --- a/spec/support/shared_examples/services/projects/update_repository_storage_service_shared_examples.rb +++ b/spec/support/shared_examples/services/projects/update_repository_storage_service_shared_examples.rb @@ -47,7 +47,7 @@ RSpec.shared_examples 'moves repository to another storage' do |repository_type| expect(original_repository_double).to receive(:remove) end - it "moves the project and its #{repository_type} repository to the new storage and unmarks the repository as read only" do + it "moves the project and its #{repository_type} repository to the new storage and unmarks the repository as read-only" do old_project_repository_path = Gitlab::GitalyClient::StorageSettings.allow_disk_access do project.repository.path_to_repo end diff --git a/spec/support/shared_examples/services/schedule_bulk_repository_shard_moves_shared_examples.rb b/spec/support/shared_examples/services/schedule_bulk_repository_shard_moves_shared_examples.rb index e67fc4ab04a..97304680316 100644 --- a/spec/support/shared_examples/services/schedule_bulk_repository_shard_moves_shared_examples.rb +++ b/spec/support/shared_examples/services/schedule_bulk_repository_shard_moves_shared_examples.rb @@ -27,7 +27,7 @@ RSpec.shared_examples 'moves repository shard in bulk' do container.set_repository_read_only! expect(subject).to receive(:log_info) - .with(/Container #{container.full_path} \(#{container.id}\) was skipped: #{container.class} is read only/) + .with(/Container #{container.full_path} \(#{container.id}\) was skipped: #{container.class} is read-only/) expect { subject.execute(source_storage_name, destination_storage_name) } .to change(move_service_klass, :count).by(0) end diff --git a/spec/support/shared_examples/services/security/ci_configuration/create_service_shared_examples.rb b/spec/support/shared_examples/services/security/ci_configuration/create_service_shared_examples.rb new file mode 100644 index 00000000000..538fd2bb513 --- /dev/null +++ b/spec/support/shared_examples/services/security/ci_configuration/create_service_shared_examples.rb @@ -0,0 +1,91 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.shared_examples_for 'services security ci configuration create service' do |skip_w_params| + let_it_be(:project) { create(:project, :repository) } + let_it_be(:user) { create(:user) } + + describe '#execute' do + let(:params) { {} } + + context 'user does not belong to project' do + it 'returns an error status' do + expect(result.status).to eq(:error) + expect(result.payload[:success_path]).to be_nil + end + + it 'does not track a snowplow event' do + subject + + expect_no_snowplow_event + end + end + + context 'user belongs to project' do + before do + project.add_developer(user) + end + + it 'does track the snowplow event' do + subject + + expect_snowplow_event(**snowplow_event) + end + + it 'raises exception if the user does not have permission to create a new branch' do + allow(project).to receive(:repository).and_raise(Gitlab::Git::PreReceiveError, "You are not allowed to create protected branches on this project.") + + expect { subject }.to raise_error(Gitlab::Git::PreReceiveError) + end + + context 'when exception is raised' do + let_it_be(:project) { create(:project, :repository) } + + before do + allow(project.repository).to receive(:add_branch).and_raise(StandardError, "The unexpected happened!") + end + + context 'when branch was created' do + before do + allow(project.repository).to receive(:branch_exists?).and_return(true) + end + + it 'tries to rm branch' do + expect(project.repository).to receive(:rm_branch).with(user, branch_name) + expect { subject }.to raise_error(StandardError) + end + end + + context 'when branch was not created' do + before do + allow(project.repository).to receive(:branch_exists?).and_return(false) + end + + it 'does not try to rm branch' do + expect(project.repository).not_to receive(:rm_branch) + expect { subject }.to raise_error(StandardError) + end + end + end + + context 'with no parameters' do + it 'returns the path to create a new merge request' do + expect(result.status).to eq(:success) + expect(result.payload[:success_path]).to match(/#{Gitlab::Routing.url_helpers.project_new_merge_request_url(project, {})}(.*)description(.*)source_branch/) + end + end + + unless skip_w_params + context 'with parameters' do + let(:params) { non_empty_params } + + it 'returns the path to create a new merge request' do + expect(result.status).to eq(:success) + expect(result.payload[:success_path]).to match(/#{Gitlab::Routing.url_helpers.project_new_merge_request_url(project, {})}(.*)description(.*)source_branch/) + end + end + end + end + end +end diff --git a/spec/support/shared_examples/services/updating_mentions_shared_examples.rb b/spec/support/shared_examples/services/updating_mentions_shared_examples.rb index 84f6c4d136a..13a2aa9ddac 100644 --- a/spec/support/shared_examples/services/updating_mentions_shared_examples.rb +++ b/spec/support/shared_examples/services/updating_mentions_shared_examples.rb @@ -15,7 +15,7 @@ RSpec.shared_examples 'updating mentions' do |service_class| def update_mentionable(opts) perform_enqueued_jobs do - service_class.new(project, user, opts).execute(mentionable) + service_class.new(project: project, current_user: user, params: opts).execute(mentionable) end mentionable.reload |