From 8f9beefac3774b30e911fb00a68f4c7a5244cf27 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Mon, 23 Mar 2020 12:09:47 +0000 Subject: Add latest changes from gitlab-org/gitlab@master --- spec/factories/prometheus_alert_event.rb | 22 ++ .../self_managed_prometheus_alert_event.rb | 23 ++ spec/features/groups/issues_spec.rb | 2 + .../autocomplete/move_to_project_finder_spec.rb | 13 +- spec/finders/group_descendants_finder_spec.rb | 2 +- spec/finders/projects_finder_spec.rb | 18 +- spec/fixtures/api/schemas/entities/discussion.json | 3 +- spec/fixtures/api/schemas/public_api/v4/notes.json | 1 + .../repository/components/table/parent_row_spec.js | 9 +- spec/models/concerns/optionally_search_spec.rb | 12 +- spec/models/project_spec.rb | 12 +- spec/models/prometheus_alert_event_spec.rb | 103 +++++++ spec/models/snippet_repository_spec.rb | 35 +++ spec/requests/api/groups_spec.rb | 2 +- spec/requests/api/notes_spec.rb | 50 +++- spec/requests/api/pages/internal_access_spec.rb | 2 +- spec/requests/api/pages/private_access_spec.rb | 2 +- spec/requests/api/pages/public_access_spec.rb | 2 +- spec/requests/api/projects_spec.rb | 15 + spec/serializers/prometheus_alert_entity_spec.rb | 27 ++ .../dashboard/update_dashboard_service_spec.rb | 46 +++ spec/services/notification_service_spec.rb | 2 +- .../alerts/create_events_service_spec.rb | 312 +++++++++++++++++++++ spec/services/snippets/create_service_spec.rb | 6 + .../requests/api/notes_shared_examples.rb | 2 + spec/tasks/gitlab/cleanup_rake_spec.rb | 65 +++++ .../projects/artifacts/_artifact.html.haml_spec.rb | 2 +- .../process_prometheus_alert_worker_spec.rb | 155 ++++++++++ 28 files changed, 915 insertions(+), 30 deletions(-) create mode 100644 spec/factories/prometheus_alert_event.rb create mode 100644 spec/factories/self_managed_prometheus_alert_event.rb create mode 100644 spec/models/prometheus_alert_event_spec.rb create mode 100644 spec/serializers/prometheus_alert_entity_spec.rb create mode 100644 spec/services/projects/prometheus/alerts/create_events_service_spec.rb create mode 100644 spec/workers/incident_management/process_prometheus_alert_worker_spec.rb (limited to 'spec') diff --git a/spec/factories/prometheus_alert_event.rb b/spec/factories/prometheus_alert_event.rb new file mode 100644 index 00000000000..281fbacabe2 --- /dev/null +++ b/spec/factories/prometheus_alert_event.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :prometheus_alert_event do + project { prometheus_alert.project } + prometheus_alert + sequence(:payload_key) { |n| "hash payload key #{n}" } + status { PrometheusAlertEvent.status_value_for(:firing) } + started_at { Time.now } + + trait :resolved do + status { PrometheusAlertEvent.status_value_for(:resolved) } + ended_at { Time.now } + payload_key { nil } + end + + trait :none do + status { nil } + started_at { nil } + end + end +end diff --git a/spec/factories/self_managed_prometheus_alert_event.rb b/spec/factories/self_managed_prometheus_alert_event.rb new file mode 100644 index 00000000000..238942e2c46 --- /dev/null +++ b/spec/factories/self_managed_prometheus_alert_event.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :self_managed_prometheus_alert_event do + project + sequence(:payload_key) { |n| "hash payload key #{n}" } + status { SelfManagedPrometheusAlertEvent.status_value_for(:firing) } + title { 'alert' } + query_expression { 'vector(2)' } + started_at { Time.now } + + trait :resolved do + status { SelfManagedPrometheusAlertEvent.status_value_for(:resolved) } + ended_at { Time.now } + payload_key { nil } + end + + trait :none do + status { nil } + started_at { nil } + end + end +end diff --git a/spec/features/groups/issues_spec.rb b/spec/features/groups/issues_spec.rb index 5b2e98804b0..e03d7b6d1f7 100644 --- a/spec/features/groups/issues_spec.rb +++ b/spec/features/groups/issues_spec.rb @@ -100,6 +100,8 @@ describe 'Group issues page' do find('.empty-state .js-lazy-loaded') find('.new-project-item-link').click + find('.select2-input').set(group.name) + page.within('.select2-results') do expect(page).to have_content(project.full_name) expect(page).not_to have_content(project_with_issues_disabled.full_name) diff --git a/spec/finders/autocomplete/move_to_project_finder_spec.rb b/spec/finders/autocomplete/move_to_project_finder_spec.rb index f997dd32c40..9129a3b65be 100644 --- a/spec/finders/autocomplete/move_to_project_finder_spec.rb +++ b/spec/finders/autocomplete/move_to_project_finder_spec.rb @@ -3,8 +3,8 @@ require 'spec_helper' describe Autocomplete::MoveToProjectFinder do - let(:user) { create(:user) } - let(:project) { create(:project) } + let_it_be(:user) { create(:user) } + let_it_be(:project) { create(:project) } let(:no_access_project) { create(:project) } let(:guest_project) { create(:project) } @@ -92,6 +92,15 @@ describe Autocomplete::MoveToProjectFinder do expect(described_class.new(user, project_id: project.id, search: 'wadus').execute.to_a) .to eq([wadus_project]) end + + it 'allows searching by parent namespace' do + group = create(:group) + other_project = create(:project, group: group) + other_project.add_maintainer(user) + + expect(described_class.new(user, project_id: project.id, search: group.name).execute.to_a) + .to contain_exactly(other_project) + end end end end diff --git a/spec/finders/group_descendants_finder_spec.rb b/spec/finders/group_descendants_finder_spec.rb index ee8606e474e..8d3564ca3c0 100644 --- a/spec/finders/group_descendants_finder_spec.rb +++ b/spec/finders/group_descendants_finder_spec.rb @@ -123,7 +123,7 @@ describe GroupDescendantsFinder do project = create(:project, namespace: group) other_project = create(:project) other_project.project_group_links.create(group: group, - group_access: ProjectGroupLink::MASTER) + group_access: ProjectGroupLink::MAINTAINER) expect(finder.execute).to contain_exactly(project) end diff --git a/spec/finders/projects_finder_spec.rb b/spec/finders/projects_finder_spec.rb index 6a04ca0eb67..eb3e28d1668 100644 --- a/spec/finders/projects_finder_spec.rb +++ b/spec/finders/projects_finder_spec.rb @@ -6,22 +6,22 @@ describe ProjectsFinder, :do_not_mock_admin_mode do include AdminModeHelper describe '#execute' do - let(:user) { create(:user) } - let(:group) { create(:group, :public) } + let_it_be(:user) { create(:user) } + let_it_be(:group) { create(:group, :public) } - let!(:private_project) do + let_it_be(:private_project) do create(:project, :private, name: 'A', path: 'A') end - let!(:internal_project) do + let_it_be(:internal_project) do create(:project, :internal, group: group, name: 'B', path: 'B') end - let!(:public_project) do + let_it_be(:public_project) do create(:project, :public, group: group, name: 'C', path: 'C') end - let!(:shared_project) do + let_it_be(:shared_project) do create(:project, :private, name: 'D', path: 'D') end @@ -139,6 +139,12 @@ describe ProjectsFinder, :do_not_mock_admin_mode do it { is_expected.to eq([public_project]) } end + describe 'filter by group name' do + let(:params) { { name: group.name, search_namespaces: true } } + + it { is_expected.to eq([public_project, internal_project]) } + end + describe 'filter by archived' do let!(:archived_project) { create(:project, :public, :archived, name: 'E', path: 'E') } diff --git a/spec/fixtures/api/schemas/entities/discussion.json b/spec/fixtures/api/schemas/entities/discussion.json index bcc1db79e83..16622ef6887 100644 --- a/spec/fixtures/api/schemas/entities/discussion.json +++ b/spec/fixtures/api/schemas/entities/discussion.json @@ -54,7 +54,8 @@ "cached_markdown_version": { "type": "integer" }, "human_access": { "type": ["string", "null"] }, "toggle_award_path": { "type": "string" }, - "path": { "type": "string" } + "path": { "type": "string" }, + "commands_changes": { "type": "object", "additionalProperties": true } }, "required": [ "id", "attachment", "author", "created_at", "updated_at", diff --git a/spec/fixtures/api/schemas/public_api/v4/notes.json b/spec/fixtures/api/schemas/public_api/v4/notes.json index 0f9c221fd6d..9668327adc4 100644 --- a/spec/fixtures/api/schemas/public_api/v4/notes.json +++ b/spec/fixtures/api/schemas/public_api/v4/notes.json @@ -19,6 +19,7 @@ }, "additionalProperties": false }, + "commands_changes": { "type": "object", "additionalProperties": true }, "created_at": { "type": "date" }, "updated_at": { "type": "date" }, "system": { "type": "boolean" }, diff --git a/spec/frontend/repository/components/table/parent_row_spec.js b/spec/frontend/repository/components/table/parent_row_spec.js index 904798e0b83..b4800112fee 100644 --- a/spec/frontend/repository/components/table/parent_row_spec.js +++ b/spec/frontend/repository/components/table/parent_row_spec.js @@ -31,10 +31,11 @@ describe('Repository parent row component', () => { }); it.each` - path | to - ${'app'} | ${'/-/tree/master/'} - ${'app/assets'} | ${'/-/tree/master/app'} - ${'app/assets#/test'} | ${'/-/tree/master/app/assets%23'} + path | to + ${'app'} | ${'/-/tree/master/'} + ${'app/assets'} | ${'/-/tree/master/app'} + ${'app/assets#/test'} | ${'/-/tree/master/app/assets%23'} + ${'app/assets#/test/world'} | ${'/-/tree/master/app/assets%23/test'} `('renders link in $path to $to', ({ path, to }) => { factory(path); diff --git a/spec/models/concerns/optionally_search_spec.rb b/spec/models/concerns/optionally_search_spec.rb index ff4212ddf18..71cf536db89 100644 --- a/spec/models/concerns/optionally_search_spec.rb +++ b/spec/models/concerns/optionally_search_spec.rb @@ -22,12 +22,22 @@ describe OptionallySearch do it 'delegates to the search method' do expect(model) .to receive(:search) - .with('foo') + .with('foo', {}) model.optionally_search('foo') end end + context 'when an option is provided' do + it 'delegates to the search method' do + expect(model) + .to receive(:search) + .with('foo', some_option: true) + + model.optionally_search('foo', some_option: true) + end + end + context 'when no query is given' do it 'returns the current relation' do expect(model.optionally_search).to be_a_kind_of(ActiveRecord::Relation) diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 44be4985439..ceb6382eb6c 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -108,6 +108,8 @@ describe Project do it { is_expected.to have_many(:external_pull_requests) } it { is_expected.to have_many(:sourced_pipelines) } it { is_expected.to have_many(:source_pipelines) } + it { is_expected.to have_many(:prometheus_alert_events) } + it { is_expected.to have_many(:self_managed_prometheus_alert_events) } it_behaves_like 'model with repository' do let_it_be(:container) { create(:project, :repository, path: 'somewhere') } @@ -1757,7 +1759,7 @@ describe Project do expect(described_class.search(project.path.upcase)).to eq([project]) end - context 'by full path' do + context 'when include_namespace is true' do let_it_be(:group) { create(:group) } let_it_be(:project) { create(:project, group: group) } @@ -1767,11 +1769,11 @@ describe Project do end it 'returns projects that match the group path' do - expect(described_class.search(group.path)).to eq([project]) + expect(described_class.search(group.path, include_namespace: true)).to eq([project]) end it 'returns projects that match the full path' do - expect(described_class.search(project.full_path)).to eq([project]) + expect(described_class.search(project.full_path, include_namespace: true)).to eq([project]) end end @@ -1781,11 +1783,11 @@ describe Project do end it 'returns no results when searching by group path' do - expect(described_class.search(group.path)).to be_empty + expect(described_class.search(group.path, include_namespace: true)).to be_empty end it 'returns no results when searching by full path' do - expect(described_class.search(project.full_path)).to be_empty + expect(described_class.search(project.full_path, include_namespace: true)).to be_empty end end end diff --git a/spec/models/prometheus_alert_event_spec.rb b/spec/models/prometheus_alert_event_spec.rb new file mode 100644 index 00000000000..040113643dd --- /dev/null +++ b/spec/models/prometheus_alert_event_spec.rb @@ -0,0 +1,103 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe PrometheusAlertEvent do + subject { build(:prometheus_alert_event) } + + let(:alert) { subject.prometheus_alert } + + describe 'associations' do + it { is_expected.to belong_to(:prometheus_alert).required } + end + + describe 'validations' do + it { is_expected.to be_valid } + + it { is_expected.to validate_presence_of(:prometheus_alert).with_message(:required) } + it { is_expected.to validate_uniqueness_of(:payload_key).scoped_to(:prometheus_alert_id) } + it { is_expected.to validate_presence_of(:started_at) } + + describe 'payload_key & ended_at' do + context 'absent if firing?' do + subject { build(:prometheus_alert_event) } + + it { is_expected.to validate_presence_of(:payload_key) } + it { is_expected.not_to validate_presence_of(:ended_at) } + end + + context 'present if resolved?' do + subject { build(:prometheus_alert_event, :resolved) } + + it { is_expected.not_to validate_presence_of(:payload_key) } + it { is_expected.to validate_presence_of(:ended_at) } + end + end + end + + describe '#title' do + it 'delegates to alert' do + expect(subject.title).to eq(alert.title) + end + end + + describe 'prometheus_metric_id' do + it 'delegates to alert' do + expect(subject.prometheus_metric_id).to eq(alert.prometheus_metric_id) + end + end + + describe 'transaction' do + describe 'fire' do + let(:started_at) { Time.now } + + context 'when status is none' do + subject { build(:prometheus_alert_event, :none) } + + it 'fires an event' do + result = subject.fire(started_at) + + expect(result).to eq(true) + expect(subject).to be_firing + expect(subject.started_at).to be_like_time(started_at) + end + end + + context 'when firing' do + subject { build(:prometheus_alert_event) } + + it 'cannot fire again' do + result = subject.fire(started_at) + + expect(result).to eq(false) + end + end + end + + describe 'resolve' do + let(:ended_at) { Time.now } + + context 'when firing' do + subject { build(:prometheus_alert_event) } + + it 'resolves an event' do + result = subject.resolve!(ended_at) + + expect(result).to eq(true) + expect(subject).to be_resolved + expect(subject.ended_at).to be_like_time(ended_at) + end + end + + context 'when resolved' do + subject { build(:prometheus_alert_event, :resolved) } + + it 'cannot resolve again' do + result = subject.resolve(ended_at) + + expect(result).to eq(false) + end + end + end + end +end diff --git a/spec/models/snippet_repository_spec.rb b/spec/models/snippet_repository_spec.rb index 6861e03282a..c31fe192367 100644 --- a/spec/models/snippet_repository_spec.rb +++ b/spec/models/snippet_repository_spec.rb @@ -140,6 +140,41 @@ describe SnippetRepository do let_it_be(:named_snippet) { { file_path: 'fee.txt', content: 'bar', action: :create } } let_it_be(:unnamed_snippet) { { file_path: '', content: 'dummy', action: :create } } + context 'when existing file has a default name' do + let(:default_name) { 'snippetfile1.txt' } + let(:new_file) { { file_path: '', content: 'bar' } } + let(:existing_file) { { previous_path: default_name, file_path: '', content: 'new_content' } } + + before do + expect(blob_at(snippet, default_name)).to be_nil + + snippet_repository.multi_files_action(user, [new_file], commit_opts) + + expect(blob_at(snippet, default_name)).to be + end + + it 'reuses the existing file name' do + snippet_repository.multi_files_action(user, [existing_file], commit_opts) + + blob = blob_at(snippet, default_name) + expect(blob.data).to eq existing_file[:content] + end + end + + context 'when file name consists of one or several whitespaces' do + let(:default_name) { 'snippetfile1.txt' } + let(:new_file) { { file_path: ' ', content: 'bar' } } + + it 'assigns a new name to the file' do + expect(blob_at(snippet, default_name)).to be_nil + + snippet_repository.multi_files_action(user, [new_file], commit_opts) + + blob = blob_at(snippet, default_name) + expect(blob.data).to eq new_file[:content] + end + end + context 'when some files are not named' do let(:data) { [named_snippet] + Array.new(2) { unnamed_snippet.clone } } diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb index 54bb2e670da..cec4995c620 100644 --- a/spec/requests/api/groups_spec.rb +++ b/spec/requests/api/groups_spec.rb @@ -302,7 +302,7 @@ describe API::Groups do before do group1.add_developer(user2) - group3.add_master(user2) + group3.add_maintainer(user2) end it 'returns an array of groups the user has at least master access' do diff --git a/spec/requests/api/notes_spec.rb b/spec/requests/api/notes_spec.rb index 6cf978e717e..3fb14eb9d5a 100644 --- a/spec/requests/api/notes_spec.rb +++ b/spec/requests/api/notes_spec.rb @@ -3,8 +3,8 @@ require 'spec_helper' describe API::Notes do - let(:user) { create(:user) } - let!(:project) { create(:project, :public, namespace: user.namespace) } + let!(:user) { create(:user) } + let!(:project) { create(:project, :public) } let(:private_user) { create(:user) } before do @@ -226,14 +226,56 @@ describe API::Notes do let(:note) { merge_request_note } end + let(:request_body) { 'Hi!' } + let(:request_path) { "/projects/#{project.id}/merge_requests/#{merge_request.iid}/notes" } + + subject { post api(request_path, user), params: { body: request_body } } + + context 'a command only note' do + let(:assignee) { create(:user) } + let(:request_body) { "/assign #{assignee.to_reference}" } + + before do + project.add_developer(assignee) + project.add_developer(user) + end + + it 'returns 202 Accepted status' do + subject + + expect(response).to have_gitlab_http_status(:accepted) + end + + it 'does not actually create a new note' do + expect { subject }.not_to change { Note.where(system: false).count } + end + + it 'does however create a system note about the change' do + expect { subject }.to change { Note.system.count }.by(1) + end + + it 'applies the commands' do + expect { subject }.to change { merge_request.reset.assignees } + end + + it 'reports the changes' do + subject + + expect(json_response).to include( + 'commands_changes' => include( + 'assignee_ids' => [Integer] + ), + 'summary' => include("Assigned #{assignee.to_reference}.") + ) + end + end + context 'when the merge request discussion is locked' do before do merge_request.update_attribute(:discussion_locked, true) end context 'when a user is a team member' do - subject { post api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/notes", user), params: { body: 'Hi!' } } - it 'returns 200 status' do subject diff --git a/spec/requests/api/pages/internal_access_spec.rb b/spec/requests/api/pages/internal_access_spec.rb index 91139b987df..ee55d1c54b7 100644 --- a/spec/requests/api/pages/internal_access_spec.rb +++ b/spec/requests/api/pages/internal_access_spec.rb @@ -19,7 +19,7 @@ describe "Internal Project Pages Access" do before do allow(Gitlab.config.pages).to receive(:access_control).and_return(true) group.add_owner(owner) - project.add_master(master) + project.add_maintainer(master) project.add_developer(developer) project.add_reporter(reporter) project.add_guest(guest) diff --git a/spec/requests/api/pages/private_access_spec.rb b/spec/requests/api/pages/private_access_spec.rb index 7c592ccfd43..146c6a389f3 100644 --- a/spec/requests/api/pages/private_access_spec.rb +++ b/spec/requests/api/pages/private_access_spec.rb @@ -19,7 +19,7 @@ describe "Private Project Pages Access" do before do allow(Gitlab.config.pages).to receive(:access_control).and_return(true) group.add_owner(owner) - project.add_master(master) + project.add_maintainer(master) project.add_developer(developer) project.add_reporter(reporter) project.add_guest(guest) diff --git a/spec/requests/api/pages/public_access_spec.rb b/spec/requests/api/pages/public_access_spec.rb index f2fe64434c6..7d929e2a287 100644 --- a/spec/requests/api/pages/public_access_spec.rb +++ b/spec/requests/api/pages/public_access_spec.rb @@ -19,7 +19,7 @@ describe "Public Project Pages Access" do before do allow(Gitlab.config.pages).to receive(:access_control).and_return(true) group.add_owner(owner) - project.add_master(master) + project.add_maintainer(master) project.add_developer(developer) project.add_reporter(reporter) project.add_guest(guest) diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index 8706b941e4f..c4f4801e372 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -362,6 +362,21 @@ describe API::Projects do end end + context 'and using search and search_namespaces is true' do + let(:group) { create(:group) } + let!(:project_in_group) { create(:project, group: group) } + + before do + group.add_guest(user) + end + + it_behaves_like 'projects response' do + let(:filter) { { search: group.name, search_namespaces: true } } + let(:current_user) { user } + let(:projects) { [project_in_group] } + end + end + context 'and using id_after' do it_behaves_like 'projects response' do let(:filter) { { id_after: project2.id } } diff --git a/spec/serializers/prometheus_alert_entity_spec.rb b/spec/serializers/prometheus_alert_entity_spec.rb new file mode 100644 index 00000000000..5121c62a0e0 --- /dev/null +++ b/spec/serializers/prometheus_alert_entity_spec.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe PrometheusAlertEntity do + let(:user) { create(:user) } + let(:prometheus_alert) { create(:prometheus_alert) } + let(:request) { double('prometheus_alert', current_user: user) } + let(:entity) { described_class.new(prometheus_alert, request: request) } + + subject { entity.as_json } + + context 'when user can read prometheus alerts' do + before do + prometheus_alert.project.add_maintainer(user) + stub_licensed_features(prometheus_alerts: true) + end + + it 'exposes prometheus_alert attributes' do + expect(subject).to include(:id, :title, :query, :operator, :threshold) + end + + it 'exposes alert_path' do + expect(subject).to include(:alert_path) + end + end +end diff --git a/spec/services/metrics/dashboard/update_dashboard_service_spec.rb b/spec/services/metrics/dashboard/update_dashboard_service_spec.rb index 227041344d7..6ba4b4035e4 100644 --- a/spec/services/metrics/dashboard/update_dashboard_service_spec.rb +++ b/spec/services/metrics/dashboard/update_dashboard_service_spec.rb @@ -92,6 +92,8 @@ describe Metrics::Dashboard::UpdateDashboardService, :use_clean_rails_memory_sto end context 'Files::UpdateService success' do + let(:merge_request) { project.merge_requests.last } + before do allow(::Files::UpdateService).to receive(:new).and_return(double(execute: { status: :success })) end @@ -107,6 +109,31 @@ describe Metrics::Dashboard::UpdateDashboardService, :use_clean_rails_memory_sto expect(service_call[:status]).to be :success expect(service_call[:http_status]).to be :created expect(service_call[:dashboard]).to match dashboard_details + expect(service_call[:merge_request]).to eq(Gitlab::UrlBuilder.build(merge_request)) + end + + context 'when the merge request does not succeed' do + let(:error_message) { 'There was an error' } + + let(:merge_request) do + build(:merge_request, target_project: project, source_project: project, author: user) + end + + before do + merge_request.errors.add(:base, error_message) + allow_next_instance_of(::MergeRequests::CreateService) do |mr| + allow(mr).to receive(:execute).and_return(merge_request) + end + end + + it 'returns an appropriate message and status code', :aggregate_failures do + result = service_call + + expect(result.keys).to contain_exactly(:message, :http_status, :status, :last_step) + expect(result[:status]).to eq(:error) + expect(result[:http_status]).to eq(:bad_request) + expect(result[:message]).to eq(error_message) + end end context 'with escaped characters in file name' do @@ -125,6 +152,25 @@ describe Metrics::Dashboard::UpdateDashboardService, :use_clean_rails_memory_sto expect(service_call[:dashboard]).to match dashboard_details end end + + context 'when pushing to the default branch' do + let(:branch) { 'master' } + + it 'does not create a merge request', :aggregate_failures do + dashboard_details = { + path: '.gitlab/dashboards/custom_dashboard.yml', + display_name: 'custom_dashboard.yml', + default: false, + system_dashboard: false + } + + expect(::MergeRequests::CreateService).not_to receive(:new) + expect(service_call.keys).to contain_exactly(:dashboard, :http_status, :status) + expect(service_call[:status]).to be :success + expect(service_call[:http_status]).to be :created + expect(service_call[:dashboard]).to match dashboard_details + end + end end context 'Files::UpdateService fails' do diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb index 8b43844eb96..9fa8f807330 100644 --- a/spec/services/notification_service_spec.rb +++ b/spec/services/notification_service_spec.rb @@ -2790,7 +2790,7 @@ describe NotificationService, :mailer do let!(:developer) { create(:user) } before do - project.add_master(master) + project.add_maintainer(master) end it 'sends the email to owners and masters' do diff --git a/spec/services/projects/prometheus/alerts/create_events_service_spec.rb b/spec/services/projects/prometheus/alerts/create_events_service_spec.rb new file mode 100644 index 00000000000..1d726db6ce3 --- /dev/null +++ b/spec/services/projects/prometheus/alerts/create_events_service_spec.rb @@ -0,0 +1,312 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Projects::Prometheus::Alerts::CreateEventsService do + let(:user) { create(:user) } + let_it_be(:project) { create(:project) } + let(:metric) { create(:prometheus_metric, project: project) } + let(:service) { described_class.new(project, user, alerts_payload) } + + shared_examples 'events persisted' do |expected_count| + subject { service.execute } + + it 'returns proper amount of created events' do + expect(subject.size).to eq(expected_count) + end + + it 'increments event count' do + expect { subject }.to change { PrometheusAlertEvent.count }.to(expected_count) + end + end + + shared_examples 'no events persisted' do + subject { service.execute } + + it 'returns no created events' do + expect(subject).to be_empty + end + + it 'does not change event count' do + expect { subject }.not_to change { PrometheusAlertEvent.count } + end + end + + shared_examples 'self managed events persisted' do + subject { service.execute } + + it 'returns created events' do + expect(subject).not_to be_empty + end + + it 'does change self managed event count' do + expect { subject }.to change { SelfManagedPrometheusAlertEvent.count } + end + end + + context 'with valid alerts_payload' do + let!(:alert) { create(:prometheus_alert, prometheus_metric: metric, project: project) } + + let(:events) { service.execute } + + context 'with a firing payload' do + let(:started_at) { truncate_to_second(Time.now) } + let(:firing_event) { alert_payload(status: 'firing', started_at: started_at) } + let(:alerts_payload) { { 'alerts' => [firing_event] } } + + it_behaves_like 'events persisted', 1 + + it 'returns created event' do + event = events.first + + expect(event).to be_firing + expect(event.started_at).to eq(started_at) + expect(event.ended_at).to be_nil + end + + context 'with 2 different firing events' do + let(:another_firing_event) { alert_payload(status: 'firing', started_at: started_at + 1) } + let(:alerts_payload) { { 'alerts' => [firing_event, another_firing_event] } } + + it_behaves_like 'events persisted', 2 + end + + context 'with already persisted firing event' do + before do + service.execute + end + + it_behaves_like 'no events persisted' + end + + context 'with duplicate payload' do + let(:alerts_payload) { { 'alerts' => [firing_event, firing_event] } } + + it_behaves_like 'events persisted', 1 + end + end + + context 'with a resolved payload' do + let(:started_at) { truncate_to_second(Time.now) } + let(:ended_at) { started_at + 1 } + let(:payload_key) { PrometheusAlertEvent.payload_key_for(alert.prometheus_metric_id, utc_rfc3339(started_at)) } + let(:resolved_event) { alert_payload(status: 'resolved', started_at: started_at, ended_at: ended_at) } + let(:alerts_payload) { { 'alerts' => [resolved_event] } } + + context 'with a matching firing event' do + before do + create(:prometheus_alert_event, + prometheus_alert: alert, + payload_key: payload_key, + started_at: started_at) + end + + it 'does not create an additional event' do + expect { service.execute }.not_to change { PrometheusAlertEvent.count } + end + + it 'marks firing event as `resolved`' do + expect(events.size).to eq(1) + + event = events.first + expect(event).to be_resolved + expect(event.started_at).to eq(started_at) + expect(event.ended_at).to eq(ended_at) + end + + context 'with duplicate payload' do + let(:alerts_payload) { { 'alerts' => [resolved_event, resolved_event] } } + + it 'does not create an additional event' do + expect { service.execute }.not_to change { PrometheusAlertEvent.count } + end + + it 'marks firing event as `resolved` only once' do + expect(events.size).to eq(1) + end + end + end + + context 'without a matching firing event' do + context 'due to payload_key' do + let(:payload_key) { 'some other payload_key' } + + before do + create(:prometheus_alert_event, + prometheus_alert: alert, + payload_key: payload_key, + started_at: started_at) + end + + it_behaves_like 'no events persisted' + end + + context 'due to status' do + before do + create(:prometheus_alert_event, :resolved, + prometheus_alert: alert, + started_at: started_at) + end + + it_behaves_like 'no events persisted' + end + end + + context 'with already resolved event' do + before do + service.execute + end + + it_behaves_like 'no events persisted' + end + end + + context 'with a metric from another project' do + let(:another_project) { create(:project) } + let(:metric) { create(:prometheus_metric, project: another_project) } + let(:alerts_payload) { { 'alerts' => [alert_payload] } } + + let!(:alert) do + create(:prometheus_alert, + prometheus_metric: metric, + project: another_project) + end + + it_behaves_like 'no events persisted' + end + end + + context 'with invalid payload' do + let(:alert) { create(:prometheus_alert, prometheus_metric: metric, project: project) } + + describe '`alerts` key' do + context 'is missing' do + let(:alerts_payload) { {} } + + it_behaves_like 'no events persisted' + end + + context 'is nil' do + let(:alerts_payload) { { 'alerts' => nil } } + + it_behaves_like 'no events persisted' + end + + context 'is empty' do + let(:alerts_payload) { { 'alerts' => [] } } + + it_behaves_like 'no events persisted' + end + + context 'is not a Hash' do + let(:alerts_payload) { { 'alerts' => [:not_a_hash] } } + + it_behaves_like 'no events persisted' + end + + describe '`status`' do + context 'is missing' do + let(:alerts_payload) { { 'alerts' => [alert_payload(status: nil)] } } + + it_behaves_like 'no events persisted' + end + + context 'is invalid' do + let(:alerts_payload) { { 'alerts' => [alert_payload(status: 'invalid')] } } + + it_behaves_like 'no events persisted' + end + end + + describe '`started_at`' do + context 'is missing' do + let(:alerts_payload) { { 'alerts' => [alert_payload(started_at: nil)] } } + + it_behaves_like 'no events persisted' + end + + context 'is invalid' do + let(:alerts_payload) { { 'alerts' => [alert_payload(started_at: 'invalid date')] } } + + it_behaves_like 'no events persisted' + end + end + + describe '`ended_at`' do + context 'is missing and status is resolved' do + let(:alerts_payload) { { 'alerts' => [alert_payload(ended_at: nil, status: 'resolved')] } } + + it_behaves_like 'no events persisted' + end + + context 'is invalid and status is resolved' do + let(:alerts_payload) { { 'alerts' => [alert_payload(ended_at: 'invalid date', status: 'resolved')] } } + + it_behaves_like 'no events persisted' + end + end + + describe '`labels`' do + describe '`gitlab_alert_id`' do + context 'is missing' do + let(:alerts_payload) { { 'alerts' => [alert_payload(gitlab_alert_id: nil)] } } + + it_behaves_like 'no events persisted' + end + + context 'is missing but title is given' do + let(:alerts_payload) { { 'alerts' => [alert_payload(gitlab_alert_id: nil, title: 'alert')] } } + + it_behaves_like 'self managed events persisted' + end + + context 'is missing and environment name is given' do + let(:environment) { create(:environment, project: project) } + let(:alerts_payload) { { 'alerts' => [alert_payload(gitlab_alert_id: nil, title: 'alert', environment: environment.name)] } } + + it_behaves_like 'self managed events persisted' + + it 'associates the environment to the alert event' do + service.execute + + expect(SelfManagedPrometheusAlertEvent.last.environment).to eq environment + end + end + + context 'is invalid' do + let(:alerts_payload) { { 'alerts' => [alert_payload(gitlab_alert_id: '-1')] } } + + it_behaves_like 'no events persisted' + end + end + end + end + end + + private + + def alert_payload(status: 'firing', started_at: Time.now, ended_at: Time.now, gitlab_alert_id: alert.prometheus_metric_id, title: nil, environment: nil) + payload = {} + + payload['status'] = status if status + payload['startsAt'] = utc_rfc3339(started_at) if started_at + payload['endsAt'] = utc_rfc3339(ended_at) if ended_at + payload['labels'] = {} + payload['labels']['gitlab_alert_id'] = gitlab_alert_id.to_s if gitlab_alert_id + payload['labels']['alertname'] = title if title + payload['labels']['gitlab_environment_name'] = environment if environment + + payload + end + + # Example: 2018-09-27T18:25:31.079079416Z + def utc_rfc3339(date) + date.utc.rfc3339 + rescue + date + end + + def truncate_to_second(date) + date.change(usec: 0) + end +end diff --git a/spec/services/snippets/create_service_spec.rb b/spec/services/snippets/create_service_spec.rb index ffad3c8b8e5..26c80ee05b3 100644 --- a/spec/services/snippets/create_service_spec.rb +++ b/spec/services/snippets/create_service_spec.rb @@ -193,6 +193,12 @@ describe Snippets::CreateService do subject end + it 'destroys the snippet_repository' do + subject + + expect(SnippetRepository.count).to be_zero + end + it 'returns the error' do response = subject diff --git a/spec/support/shared_examples/requests/api/notes_shared_examples.rb b/spec/support/shared_examples/requests/api/notes_shared_examples.rb index 0c52af43465..72e8b920192 100644 --- a/spec/support/shared_examples/requests/api/notes_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/notes_shared_examples.rb @@ -172,6 +172,8 @@ RSpec.shared_examples 'noteable API' do |parent_type, noteable_type, id_name| if parent_type == 'projects' context 'by a project owner' do + let(:user) { project.owner } + it 'sets the creation time on the new note' do post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user), params: params diff --git a/spec/tasks/gitlab/cleanup_rake_spec.rb b/spec/tasks/gitlab/cleanup_rake_spec.rb index 92ccc195a9a..8db18895c24 100644 --- a/spec/tasks/gitlab/cleanup_rake_spec.rb +++ b/spec/tasks/gitlab/cleanup_rake_spec.rb @@ -120,6 +120,71 @@ describe 'gitlab:cleanup rake tasks' do end end + describe 'gitlab:cleanup:orphan_lfs_file_references' do + subject(:rake_task) { run_rake_task('gitlab:cleanup:orphan_lfs_file_references') } + + let(:project) { create(:project, :repository) } + + before do + stub_env('PROJECT_ID', project.id) + end + + it 'runs the task without errors' do + expect(Gitlab::Cleanup::OrphanLfsFileReferences) + .to receive(:new).and_call_original + + expect { rake_task }.not_to raise_error + end + + context 'with DRY_RUN set to false' do + before do + stub_env('DRY_RUN', 'false') + end + + it 'passes dry_run correctly' do + expect(Gitlab::Cleanup::OrphanLfsFileReferences) + .to receive(:new) + .with(project, + limit: anything, + dry_run: false, + logger: anything) + .and_call_original + + rake_task + end + end + + context 'with LIMIT set to 100' do + before do + stub_env('LIMIT', '100') + end + + it 'passes limit as integer' do + expect(Gitlab::Cleanup::OrphanLfsFileReferences) + .to receive(:new) + .with(project, + limit: 100, + dry_run: true, + logger: anything) + .and_call_original + + rake_task + end + end + end + + describe 'gitlab:cleanup:orphan_lfs_files' do + subject(:rake_task) { run_rake_task('gitlab:cleanup:orphan_lfs_files') } + + it 'runs RemoveUnreferencedLfsObjectsWorker' do + expect_any_instance_of(RemoveUnreferencedLfsObjectsWorker) + .to receive(:perform) + .and_call_original + + rake_task + end + end + context 'sessions' do describe 'gitlab:cleanup:sessions:active_sessions_lookup_keys', :clean_gitlab_redis_shared_state do subject(:rake_task) { run_rake_task('gitlab:cleanup:sessions:active_sessions_lookup_keys') } diff --git a/spec/views/projects/artifacts/_artifact.html.haml_spec.rb b/spec/views/projects/artifacts/_artifact.html.haml_spec.rb index 460b63efa2f..b3bf54e143a 100644 --- a/spec/views/projects/artifacts/_artifact.html.haml_spec.rb +++ b/spec/views/projects/artifacts/_artifact.html.haml_spec.rb @@ -38,7 +38,7 @@ RSpec.describe "projects/artifacts/_artifact.html.haml" do let(:user) { create(:user) } it 'has a delete button' do - allow_any_instance_of(ProjectTeam).to receive(:max_member_access).and_return(Gitlab::Access::MASTER) + allow_any_instance_of(ProjectTeam).to receive(:max_member_access).and_return(Gitlab::Access::MAINTAINER) render_partial expect(rendered).to have_link('Delete artifacts', href: project_artifact_path(project, project.job_artifacts.first)) diff --git a/spec/workers/incident_management/process_prometheus_alert_worker_spec.rb b/spec/workers/incident_management/process_prometheus_alert_worker_spec.rb new file mode 100644 index 00000000000..19ef2635882 --- /dev/null +++ b/spec/workers/incident_management/process_prometheus_alert_worker_spec.rb @@ -0,0 +1,155 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe IncidentManagement::ProcessPrometheusAlertWorker do + describe '#perform' do + let_it_be(:project) { create(:project) } + let_it_be(:prometheus_alert) { create(:prometheus_alert, project: project) } + let_it_be(:payload_key) { PrometheusAlertEvent.payload_key_for(prometheus_alert.prometheus_metric_id, prometheus_alert.created_at.rfc3339) } + let!(:prometheus_alert_event) { create(:prometheus_alert_event, prometheus_alert: prometheus_alert, payload_key: payload_key) } + + let(:alert_params) do + { + startsAt: prometheus_alert.created_at.rfc3339, + labels: { + gitlab_alert_id: prometheus_alert.prometheus_metric_id + } + }.with_indifferent_access + end + + it 'creates an issue' do + expect { subject.perform(project.id, alert_params) } + .to change(Issue, :count) + .by(1) + end + + it 'relates issue to an event' do + expect { subject.perform(project.id, alert_params) } + .to change(prometheus_alert.related_issues, :count) + .from(0) + .to(1) + end + + context 'resolved event' do + let(:issue) { create(:issue, project: project) } + + before do + prometheus_alert_event.related_issues << issue + prometheus_alert_event.resolve + end + + it 'does not create an issue' do + expect { subject.perform(project.id, alert_params) } + .not_to change(Issue, :count) + end + + it 'closes the existing issue' do + expect { subject.perform(project.id, alert_params) } + .to change { issue.reload.state } + .from('opened') + .to('closed') + end + + it 'leaves a system note on the issue' do + expect(SystemNoteService) + .to receive(:auto_resolve_prometheus_alert) + + subject.perform(project.id, alert_params) + end + end + + context 'when project could not be found' do + let(:non_existing_project_id) { (Project.maximum(:id) || 0) + 1 } + + it 'does not create an issue' do + expect { subject.perform(non_existing_project_id, alert_params) } + .not_to change(Issue, :count) + end + + it 'does not relate issue to an event' do + expect { subject.perform(non_existing_project_id, alert_params) } + .not_to change(prometheus_alert.related_issues, :count) + end + end + + context 'when event could not be found' do + before do + alert_params[:labels][:gitlab_alert_id] = (PrometheusAlertEvent.maximum(:id) || 0) + 1 + end + + it 'does not create an issue' do + expect { subject.perform(project.id, alert_params) } + .not_to change(Issue, :count) + end + + it 'does not relate issue to an event' do + expect { subject.perform(project.id, alert_params) } + .not_to change(prometheus_alert.related_issues, :count) + end + end + + context 'when issue could not be created' do + before do + allow_next_instance_of(IncidentManagement::CreateIssueService) do |instance| + allow(instance).to receive(:execute).and_return( { error: true } ) + end + end + + it 'does not relate issue to an event' do + expect { subject.perform(project.id, alert_params) } + .not_to change(prometheus_alert.related_issues, :count) + end + end + + context 'self-managed alert' do + let(:alert_name) { 'alert' } + let(:starts_at) { Time.now.rfc3339 } + + let!(:prometheus_alert_event) do + payload_key = SelfManagedPrometheusAlertEvent.payload_key_for(starts_at, alert_name, 'vector(1)') + create(:self_managed_prometheus_alert_event, project: project, payload_key: payload_key) + end + + let(:alert_params) do + { + startsAt: starts_at, + generatorURL: 'http://localhost:9090/graph?g0.expr=vector%281%29&g0.tab=1', + labels: { + alertname: alert_name + } + }.with_indifferent_access + end + + it 'creates an issue' do + expect { subject.perform(project.id, alert_params) } + .to change(Issue, :count) + .by(1) + end + + it 'relates issue to an event' do + expect { subject.perform(project.id, alert_params) } + .to change(prometheus_alert_event.related_issues, :count) + .from(0) + .to(1) + end + + context 'when event could not be found' do + before do + alert_params[:generatorURL] = 'http://somethingelse.com' + end + + it 'creates an issue' do + expect { subject.perform(project.id, alert_params) } + .to change(Issue, :count) + .by(1) + end + + it 'does not relate issue to an event' do + expect { subject.perform(project.id, alert_params) } + .not_to change(prometheus_alert.related_issues, :count) + end + end + end + end +end -- cgit v1.2.3