2020-05-24 23:13:21 +05:30
|
|
|
# frozen_string_literal: true
|
|
|
|
|
|
|
|
require 'spec_helper'
|
|
|
|
|
2020-07-28 23:09:34 +05:30
|
|
|
RSpec.describe AlertManagement::Alert do
|
2020-05-24 23:13:21 +05:30
|
|
|
describe 'associations' do
|
|
|
|
it { is_expected.to belong_to(:project) }
|
2020-07-28 23:09:34 +05:30
|
|
|
it { is_expected.to belong_to(:issue).optional }
|
|
|
|
it { is_expected.to belong_to(:prometheus_alert).optional }
|
|
|
|
it { is_expected.to belong_to(:environment).optional }
|
2020-06-23 00:09:42 +05:30
|
|
|
it { is_expected.to have_many(:assignees).through(:alert_assignees) }
|
|
|
|
it { is_expected.to have_many(:notes) }
|
|
|
|
it { is_expected.to have_many(:ordered_notes) }
|
|
|
|
it { is_expected.to have_many(:user_mentions) }
|
2020-05-24 23:13:21 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
describe 'validations' do
|
|
|
|
it { is_expected.to validate_presence_of(:title) }
|
|
|
|
it { is_expected.to validate_presence_of(:events) }
|
|
|
|
it { is_expected.to validate_presence_of(:severity) }
|
|
|
|
it { is_expected.to validate_presence_of(:status) }
|
|
|
|
it { is_expected.to validate_presence_of(:started_at) }
|
|
|
|
|
|
|
|
it { is_expected.to validate_length_of(:title).is_at_most(200) }
|
|
|
|
it { is_expected.to validate_length_of(:description).is_at_most(1000) }
|
|
|
|
it { is_expected.to validate_length_of(:service).is_at_most(100) }
|
|
|
|
it { is_expected.to validate_length_of(:monitoring_tool).is_at_most(100) }
|
|
|
|
|
|
|
|
context 'when status is triggered' do
|
|
|
|
context 'when ended_at is blank' do
|
|
|
|
subject { build(:alert_management_alert) }
|
|
|
|
|
|
|
|
it { is_expected.to be_valid }
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when ended_at is present' do
|
|
|
|
subject { build(:alert_management_alert, ended_at: Time.current) }
|
|
|
|
|
|
|
|
it { is_expected.to be_invalid }
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when status is acknowledged' do
|
|
|
|
context 'when ended_at is blank' do
|
|
|
|
subject { build(:alert_management_alert, :acknowledged) }
|
|
|
|
|
|
|
|
it { is_expected.to be_valid }
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when ended_at is present' do
|
|
|
|
subject { build(:alert_management_alert, :acknowledged, ended_at: Time.current) }
|
|
|
|
|
|
|
|
it { is_expected.to be_invalid }
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when status is resolved' do
|
|
|
|
context 'when ended_at is blank' do
|
|
|
|
subject { build(:alert_management_alert, :resolved, ended_at: nil) }
|
|
|
|
|
|
|
|
it { is_expected.to be_invalid }
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when ended_at is present' do
|
|
|
|
subject { build(:alert_management_alert, :resolved, ended_at: Time.current) }
|
|
|
|
|
|
|
|
it { is_expected.to be_valid }
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when status is ignored' do
|
|
|
|
context 'when ended_at is blank' do
|
|
|
|
subject { build(:alert_management_alert, :ignored) }
|
|
|
|
|
|
|
|
it { is_expected.to be_valid }
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when ended_at is present' do
|
|
|
|
subject { build(:alert_management_alert, :ignored, ended_at: Time.current) }
|
|
|
|
|
|
|
|
it { is_expected.to be_invalid }
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe 'fingerprint' do
|
2020-07-28 23:09:34 +05:30
|
|
|
let_it_be(:project) { create(:project) }
|
2020-05-24 23:13:21 +05:30
|
|
|
let_it_be(:fingerprint) { 'fingerprint' }
|
|
|
|
let(:new_alert) { build(:alert_management_alert, fingerprint: fingerprint, project: project) }
|
|
|
|
|
|
|
|
subject { new_alert }
|
|
|
|
|
|
|
|
context 'adding an alert with the same fingerprint' do
|
2020-07-28 23:09:34 +05:30
|
|
|
context 'same project, various states' do
|
|
|
|
using RSpec::Parameterized::TableSyntax
|
|
|
|
|
|
|
|
# We are only validating uniqueness for non-resolved alerts
|
|
|
|
where(:existing_status, :new_status, :valid) do
|
|
|
|
:resolved | :triggered | true
|
|
|
|
:resolved | :acknowledged | true
|
|
|
|
:resolved | :ignored | true
|
|
|
|
:resolved | :resolved | true
|
|
|
|
:triggered | :triggered | false
|
|
|
|
:triggered | :acknowledged | false
|
|
|
|
:triggered | :ignored | false
|
|
|
|
:triggered | :resolved | true
|
|
|
|
:acknowledged | :triggered | false
|
|
|
|
:acknowledged | :acknowledged | false
|
|
|
|
:acknowledged | :ignored | false
|
|
|
|
:acknowledged | :resolved | true
|
|
|
|
:ignored | :triggered | false
|
|
|
|
:ignored | :acknowledged | false
|
|
|
|
:ignored | :ignored | false
|
|
|
|
:ignored | :resolved | true
|
|
|
|
end
|
|
|
|
|
|
|
|
with_them do
|
|
|
|
let!(:existing_alert) { create(:alert_management_alert, existing_status, fingerprint: fingerprint, project: project) }
|
|
|
|
let(:new_alert) { build(:alert_management_alert, new_status, fingerprint: fingerprint, project: project) }
|
|
|
|
|
|
|
|
if params[:valid]
|
|
|
|
it { is_expected.to be_valid }
|
|
|
|
else
|
|
|
|
it { is_expected.to be_invalid }
|
|
|
|
end
|
|
|
|
end
|
2020-05-24 23:13:21 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
context 'different project' do
|
2020-07-28 23:09:34 +05:30
|
|
|
let!(:existing_alert) { create(:alert_management_alert, fingerprint: fingerprint) }
|
2020-05-24 23:13:21 +05:30
|
|
|
|
|
|
|
it { is_expected.to be_valid }
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe 'hosts' do
|
|
|
|
subject(:alert) { build(:alert_management_alert, hosts: hosts) }
|
|
|
|
|
|
|
|
context 'over 255 total chars' do
|
|
|
|
let(:hosts) { ['111.111.111.111'] * 18 }
|
|
|
|
|
|
|
|
it { is_expected.not_to be_valid }
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'under 255 chars' do
|
|
|
|
let(:hosts) { ['111.111.111.111'] * 17 }
|
|
|
|
|
|
|
|
it { is_expected.to be_valid }
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe 'enums' do
|
|
|
|
let(:severity_values) do
|
|
|
|
{ critical: 0, high: 1, medium: 2, low: 3, info: 4, unknown: 5 }
|
|
|
|
end
|
|
|
|
|
|
|
|
it { is_expected.to define_enum_for(:severity).with_values(severity_values) }
|
|
|
|
end
|
|
|
|
|
|
|
|
describe 'scopes' do
|
|
|
|
let_it_be(:project) { create(:project) }
|
|
|
|
let_it_be(:triggered_alert) { create(:alert_management_alert, project: project) }
|
|
|
|
let_it_be(:resolved_alert) { create(:alert_management_alert, :resolved, project: project) }
|
|
|
|
let_it_be(:ignored_alert) { create(:alert_management_alert, :ignored, project: project) }
|
|
|
|
|
|
|
|
describe '.for_iid' do
|
|
|
|
subject { AlertManagement::Alert.for_iid(triggered_alert.iid) }
|
|
|
|
|
|
|
|
it { is_expected.to match_array(triggered_alert) }
|
|
|
|
end
|
|
|
|
|
|
|
|
describe '.for_status' do
|
|
|
|
let(:status) { AlertManagement::Alert::STATUSES[:resolved] }
|
|
|
|
|
|
|
|
subject { AlertManagement::Alert.for_status(status) }
|
|
|
|
|
|
|
|
it { is_expected.to match_array(resolved_alert) }
|
|
|
|
|
|
|
|
context 'with multiple statuses' do
|
|
|
|
let(:status) { AlertManagement::Alert::STATUSES.values_at(:resolved, :ignored) }
|
|
|
|
|
|
|
|
it { is_expected.to match_array([resolved_alert, ignored_alert]) }
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe '.for_fingerprint' do
|
|
|
|
let_it_be(:fingerprint) { SecureRandom.hex }
|
|
|
|
let_it_be(:alert_with_fingerprint) { create(:alert_management_alert, project: project, fingerprint: fingerprint) }
|
|
|
|
let_it_be(:unrelated_alert_with_finger_print) { create(:alert_management_alert, fingerprint: fingerprint) }
|
|
|
|
|
|
|
|
subject { described_class.for_fingerprint(project, fingerprint) }
|
|
|
|
|
|
|
|
it { is_expected.to contain_exactly(alert_with_fingerprint) }
|
|
|
|
end
|
|
|
|
|
2020-07-28 23:09:34 +05:30
|
|
|
describe '.for_environment' do
|
|
|
|
let(:environment) { create(:environment, project: project) }
|
|
|
|
let!(:env_alert) { create(:alert_management_alert, project: project, environment: environment) }
|
|
|
|
|
|
|
|
subject { described_class.for_environment(environment) }
|
|
|
|
|
|
|
|
it { is_expected.to match_array(env_alert) }
|
|
|
|
end
|
|
|
|
|
2020-05-24 23:13:21 +05:30
|
|
|
describe '.counts_by_status' do
|
|
|
|
subject { described_class.counts_by_status }
|
|
|
|
|
|
|
|
it do
|
|
|
|
is_expected.to eq(
|
|
|
|
triggered_alert.status => 1,
|
|
|
|
resolved_alert.status => 1,
|
|
|
|
ignored_alert.status => 1
|
|
|
|
)
|
|
|
|
end
|
|
|
|
end
|
2020-07-28 23:09:34 +05:30
|
|
|
|
|
|
|
describe '.counts_by_project_id' do
|
|
|
|
subject { described_class.counts_by_project_id }
|
|
|
|
|
|
|
|
let!(:alert_other_project) { create(:alert_management_alert) }
|
|
|
|
|
|
|
|
it do
|
|
|
|
is_expected.to eq(
|
|
|
|
project.id => 3,
|
|
|
|
alert_other_project.project.id => 1
|
|
|
|
)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe '.open' do
|
|
|
|
subject { described_class.open }
|
|
|
|
|
|
|
|
let!(:acknowledged_alert) { create(:alert_management_alert, :acknowledged, project: project)}
|
|
|
|
|
|
|
|
it { is_expected.to contain_exactly(acknowledged_alert, triggered_alert) }
|
|
|
|
end
|
|
|
|
|
|
|
|
describe '.not_resolved' do
|
|
|
|
subject { described_class.not_resolved }
|
|
|
|
|
|
|
|
let!(:acknowledged_alert) { create(:alert_management_alert, :acknowledged, project: project) }
|
|
|
|
|
|
|
|
it { is_expected.to contain_exactly(acknowledged_alert, triggered_alert, ignored_alert) }
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe '.last_prometheus_alert_by_project_id' do
|
|
|
|
subject { described_class.last_prometheus_alert_by_project_id }
|
|
|
|
|
|
|
|
let(:project_1) { create(:project) }
|
|
|
|
let!(:alert_1) { create(:alert_management_alert, project: project_1) }
|
|
|
|
let!(:alert_2) { create(:alert_management_alert, project: project_1) }
|
|
|
|
|
|
|
|
let(:project_2) { create(:project) }
|
|
|
|
let!(:alert_3) { create(:alert_management_alert, project: project_2) }
|
|
|
|
let!(:alert_4) { create(:alert_management_alert, project: project_2) }
|
|
|
|
|
|
|
|
it 'returns the latest alert for each project' do
|
|
|
|
expect(subject).to contain_exactly(alert_2, alert_4)
|
|
|
|
end
|
2020-05-24 23:13:21 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
describe '.search' do
|
|
|
|
let_it_be(:alert) do
|
|
|
|
create(:alert_management_alert,
|
|
|
|
title: 'Title',
|
|
|
|
description: 'Desc',
|
|
|
|
service: 'Service',
|
|
|
|
monitoring_tool: 'Monitor'
|
|
|
|
)
|
|
|
|
end
|
|
|
|
|
|
|
|
subject { AlertManagement::Alert.search(query) }
|
|
|
|
|
|
|
|
context 'does not contain search string' do
|
|
|
|
let(:query) { 'something else' }
|
|
|
|
|
|
|
|
it { is_expected.to be_empty }
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'title includes query' do
|
|
|
|
let(:query) { alert.title.upcase }
|
|
|
|
|
|
|
|
it { is_expected.to contain_exactly(alert) }
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'description includes query' do
|
|
|
|
let(:query) { alert.description.upcase }
|
|
|
|
|
|
|
|
it { is_expected.to contain_exactly(alert) }
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'service includes query' do
|
|
|
|
let(:query) { alert.service.upcase }
|
|
|
|
|
|
|
|
it { is_expected.to contain_exactly(alert) }
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'monitoring tool includes query' do
|
|
|
|
let(:query) { alert.monitoring_tool.upcase }
|
|
|
|
|
|
|
|
it { is_expected.to contain_exactly(alert) }
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe '#details' do
|
|
|
|
let(:payload) do
|
|
|
|
{
|
|
|
|
'title' => 'Details title',
|
|
|
|
'custom' => {
|
|
|
|
'alert' => {
|
|
|
|
'fields' => %w[one two]
|
|
|
|
}
|
|
|
|
},
|
|
|
|
'yet' => {
|
|
|
|
'another' => 'field'
|
|
|
|
}
|
|
|
|
}
|
|
|
|
end
|
|
|
|
let(:alert) { build(:alert_management_alert, title: 'Details title', payload: payload) }
|
|
|
|
|
|
|
|
subject { alert.details }
|
|
|
|
|
|
|
|
it 'renders the payload as inline hash' do
|
|
|
|
is_expected.to eq(
|
|
|
|
'custom.alert.fields' => %w[one two],
|
|
|
|
'yet.another' => 'field'
|
|
|
|
)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2020-06-23 00:09:42 +05:30
|
|
|
describe '#to_reference' do
|
|
|
|
let(:alert) { build(:alert_management_alert) }
|
|
|
|
|
|
|
|
it { expect(alert.to_reference).to eq('') }
|
|
|
|
end
|
|
|
|
|
2020-05-24 23:13:21 +05:30
|
|
|
describe '#trigger' do
|
|
|
|
subject { alert.trigger }
|
|
|
|
|
|
|
|
context 'when alert is in triggered state' do
|
|
|
|
let(:alert) { create(:alert_management_alert) }
|
|
|
|
|
|
|
|
it 'does not change the alert status' do
|
|
|
|
expect { subject }.not_to change { alert.reload.status }
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when alert not in triggered state' do
|
|
|
|
let(:alert) { create(:alert_management_alert, :resolved) }
|
|
|
|
|
|
|
|
it 'changes the alert status to triggered' do
|
|
|
|
expect { subject }.to change { alert.triggered? }.to(true)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'resets ended at' do
|
|
|
|
expect { subject }.to change { alert.reload.ended_at }.to nil
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe '#acknowledge' do
|
|
|
|
subject { alert.acknowledge }
|
|
|
|
|
|
|
|
let(:alert) { create(:alert_management_alert, :resolved) }
|
|
|
|
|
|
|
|
it 'changes the alert status to acknowledged' do
|
|
|
|
expect { subject }.to change { alert.acknowledged? }.to(true)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'resets ended at' do
|
|
|
|
expect { subject }.to change { alert.reload.ended_at }.to nil
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe '#resolve' do
|
|
|
|
let!(:ended_at) { Time.current }
|
|
|
|
|
|
|
|
subject do
|
|
|
|
alert.ended_at = ended_at
|
|
|
|
alert.resolve
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when alert already resolved' do
|
|
|
|
let(:alert) { create(:alert_management_alert, :resolved) }
|
|
|
|
|
|
|
|
it 'does not change the alert status' do
|
|
|
|
expect { subject }.not_to change { alert.reload.status }
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when alert is not resolved' do
|
|
|
|
let(:alert) { create(:alert_management_alert) }
|
|
|
|
|
|
|
|
it 'changes alert status to "resolved"' do
|
|
|
|
expect { subject }.to change { alert.resolved? }.to(true)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe '#ignore' do
|
|
|
|
subject { alert.ignore }
|
|
|
|
|
|
|
|
let(:alert) { create(:alert_management_alert, :resolved) }
|
|
|
|
|
|
|
|
it 'changes the alert status to ignored' do
|
|
|
|
expect { subject }.to change { alert.ignored? }.to(true)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'resets ended at' do
|
|
|
|
expect { subject }.to change { alert.reload.ended_at }.to nil
|
|
|
|
end
|
|
|
|
end
|
2020-06-23 00:09:42 +05:30
|
|
|
|
|
|
|
describe '#register_new_event!' do
|
|
|
|
subject { alert.register_new_event! }
|
|
|
|
|
|
|
|
let(:alert) { create(:alert_management_alert) }
|
|
|
|
|
|
|
|
it 'increments the events count by 1' do
|
|
|
|
expect { subject }.to change { alert.events }.by(1)
|
|
|
|
end
|
|
|
|
end
|
2020-07-28 23:09:34 +05:30
|
|
|
|
|
|
|
describe '#present' do
|
|
|
|
context 'when alert is generic' do
|
|
|
|
let(:alert) { build(:alert_management_alert) }
|
|
|
|
|
|
|
|
it 'uses generic alert presenter' do
|
|
|
|
expect(alert.present).to be_kind_of(AlertManagement::AlertPresenter)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when alert is Prometheus specific' do
|
|
|
|
let(:alert) { build(:alert_management_alert, :prometheus) }
|
|
|
|
|
|
|
|
it 'uses Prometheus Alert presenter' do
|
|
|
|
expect(alert.present).to be_kind_of(AlertManagement::PrometheusAlertPresenter)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2020-05-24 23:13:21 +05:30
|
|
|
end
|