Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-03-23 15:09:47 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2020-03-23 15:09:47 +0300
commit8f9beefac3774b30e911fb00a68f4c7a5244cf27 (patch)
tree919c3a043f8c10bc3f78f3f6e029acfb6b972556 /spec
parente4bf776a8829e5186a0f63603c0be627b891d80e (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
-rw-r--r--spec/factories/prometheus_alert_event.rb22
-rw-r--r--spec/factories/self_managed_prometheus_alert_event.rb23
-rw-r--r--spec/features/groups/issues_spec.rb2
-rw-r--r--spec/finders/autocomplete/move_to_project_finder_spec.rb13
-rw-r--r--spec/finders/group_descendants_finder_spec.rb2
-rw-r--r--spec/finders/projects_finder_spec.rb18
-rw-r--r--spec/fixtures/api/schemas/entities/discussion.json3
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/notes.json1
-rw-r--r--spec/frontend/repository/components/table/parent_row_spec.js9
-rw-r--r--spec/models/concerns/optionally_search_spec.rb12
-rw-r--r--spec/models/project_spec.rb12
-rw-r--r--spec/models/prometheus_alert_event_spec.rb103
-rw-r--r--spec/models/snippet_repository_spec.rb35
-rw-r--r--spec/requests/api/groups_spec.rb2
-rw-r--r--spec/requests/api/notes_spec.rb50
-rw-r--r--spec/requests/api/pages/internal_access_spec.rb2
-rw-r--r--spec/requests/api/pages/private_access_spec.rb2
-rw-r--r--spec/requests/api/pages/public_access_spec.rb2
-rw-r--r--spec/requests/api/projects_spec.rb15
-rw-r--r--spec/serializers/prometheus_alert_entity_spec.rb27
-rw-r--r--spec/services/metrics/dashboard/update_dashboard_service_spec.rb46
-rw-r--r--spec/services/notification_service_spec.rb2
-rw-r--r--spec/services/projects/prometheus/alerts/create_events_service_spec.rb312
-rw-r--r--spec/services/snippets/create_service_spec.rb6
-rw-r--r--spec/support/shared_examples/requests/api/notes_shared_examples.rb2
-rw-r--r--spec/tasks/gitlab/cleanup_rake_spec.rb65
-rw-r--r--spec/views/projects/artifacts/_artifact.html.haml_spec.rb2
-rw-r--r--spec/workers/incident_management/process_prometheus_alert_worker_spec.rb155
28 files changed, 915 insertions, 30 deletions
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