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
diff options
context:
space:
mode:
Diffstat (limited to 'spec/lib/gitlab/alert_management')
-rw-r--r--spec/lib/gitlab/alert_management/alert_params_spec.rb4
-rw-r--r--spec/lib/gitlab/alert_management/payload/base_spec.rb210
-rw-r--r--spec/lib/gitlab/alert_management/payload/generic_spec.rb89
-rw-r--r--spec/lib/gitlab/alert_management/payload/managed_prometheus_spec.rb167
-rw-r--r--spec/lib/gitlab/alert_management/payload/prometheus_spec.rb240
-rw-r--r--spec/lib/gitlab/alert_management/payload_spec.rb60
6 files changed, 769 insertions, 1 deletions
diff --git a/spec/lib/gitlab/alert_management/alert_params_spec.rb b/spec/lib/gitlab/alert_management/alert_params_spec.rb
index 1fe27365c83..c3171be5e29 100644
--- a/spec/lib/gitlab/alert_management/alert_params_spec.rb
+++ b/spec/lib/gitlab/alert_management/alert_params_spec.rb
@@ -34,7 +34,9 @@ RSpec.describe Gitlab::AlertManagement::AlertParams do
hosts: ['gitlab.com'],
payload: payload,
started_at: started_at,
- fingerprint: nil
+ ended_at: nil,
+ fingerprint: nil,
+ environment: nil
)
end
diff --git a/spec/lib/gitlab/alert_management/payload/base_spec.rb b/spec/lib/gitlab/alert_management/payload/base_spec.rb
new file mode 100644
index 00000000000..e0f63bad05d
--- /dev/null
+++ b/spec/lib/gitlab/alert_management/payload/base_spec.rb
@@ -0,0 +1,210 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::AlertManagement::Payload::Base do
+ let_it_be(:project) { create(:project) }
+ let(:raw_payload) { {} }
+ let(:payload_class) { described_class }
+
+ subject(:parsed_payload) { payload_class.new(project: project, payload: raw_payload) }
+
+ describe '.attribute' do
+ subject { parsed_payload.test }
+
+ context 'with a single path provided' do
+ let(:payload_class) do
+ Class.new(described_class) do
+ attribute :test, paths: [['test']]
+ end
+ end
+
+ it { is_expected.to be_nil }
+
+ context 'and a matching value' do
+ let(:raw_payload) { { 'test' => 'value' } }
+
+ it { is_expected.to eq 'value' }
+ end
+ end
+
+ context 'with multiple paths provided' do
+ let(:payload_class) do
+ Class.new(described_class) do
+ attribute :test, paths: [['test'], %w(alt test)]
+ end
+ end
+
+ it { is_expected.to be_nil }
+
+ context 'and a matching value' do
+ let(:raw_payload) { { 'alt' => { 'test' => 'value' } } }
+
+ it { is_expected.to eq 'value' }
+ end
+ end
+
+ context 'with a fallback provided' do
+ let(:payload_class) do
+ Class.new(described_class) do
+ attribute :test, paths: [['test']], fallback: -> { 'fallback' }
+ end
+ end
+
+ it { is_expected.to eq('fallback') }
+
+ context 'and a matching value' do
+ let(:raw_payload) { { 'test' => 'value' } }
+
+ it { is_expected.to eq 'value' }
+ end
+ end
+
+ context 'with a time type provided' do
+ let(:test_time) { Time.current.change(usec: 0) }
+
+ let(:payload_class) do
+ Class.new(described_class) do
+ attribute :test, paths: [['test']], type: :time
+ end
+ end
+
+ it { is_expected.to be_nil }
+
+ context 'with a compatible matching value' do
+ let(:raw_payload) { { 'test' => test_time.to_s } }
+
+ it { is_expected.to eq test_time }
+ end
+
+ context 'with a value in rfc3339 format' do
+ let(:raw_payload) { { 'test' => test_time.rfc3339 } }
+
+ it { is_expected.to eq test_time }
+ end
+
+ context 'with an incompatible matching value' do
+ let(:raw_payload) { { 'test' => 'bad time' } }
+
+ it { is_expected.to be_nil }
+ end
+ end
+
+ context 'with an integer type provided' do
+ let(:payload_class) do
+ Class.new(described_class) do
+ attribute :test, paths: [['test']], type: :integer
+ end
+ end
+
+ it { is_expected.to be_nil }
+
+ context 'with a compatible matching value' do
+ let(:raw_payload) { { 'test' => '15' } }
+
+ it { is_expected.to eq 15 }
+ end
+
+ context 'with an incompatible matching value' do
+ let(:raw_payload) { { 'test' => String } }
+
+ it { is_expected.to be_nil }
+ end
+
+ context 'with an incompatible matching value' do
+ let(:raw_payload) { { 'test' => 'apple' } }
+
+ it { is_expected.to be_nil }
+ end
+ end
+ end
+
+ describe '#alert_params' do
+ before do
+ allow(parsed_payload).to receive(:title).and_return('title')
+ allow(parsed_payload).to receive(:description).and_return('description')
+ end
+
+ subject { parsed_payload.alert_params }
+
+ it { is_expected.to eq({ description: 'description', project_id: project.id, title: 'title' }) }
+ end
+
+ describe '#gitlab_fingerprint' do
+ subject { parsed_payload.gitlab_fingerprint }
+
+ it { is_expected.to be_nil }
+
+ context 'when plain_gitlab_fingerprint is defined' do
+ before do
+ allow(parsed_payload)
+ .to receive(:plain_gitlab_fingerprint)
+ .and_return('fingerprint')
+ end
+
+ it 'returns a fingerprint' do
+ is_expected.to eq(Digest::SHA1.hexdigest('fingerprint'))
+ end
+ end
+ end
+
+ describe '#environment' do
+ let_it_be(:environment) { create(:environment, project: project, name: 'production') }
+
+ subject { parsed_payload.environment }
+
+ before do
+ allow(parsed_payload).to receive(:environment_name).and_return(environment_name)
+ end
+
+ context 'without an environment name' do
+ let(:environment_name) { nil }
+
+ it { is_expected.to be_nil }
+ end
+
+ context 'with a non-matching environment name' do
+ let(:environment_name) { 'other_environment' }
+
+ it { is_expected.to be_nil }
+ end
+
+ context 'with a matching environment name' do
+ let(:environment_name) { 'production' }
+
+ it { is_expected.to eq(environment) }
+ end
+ end
+
+ describe '#resolved?' do
+ before do
+ allow(parsed_payload).to receive(:status).and_return(status)
+ end
+
+ subject { parsed_payload.resolved? }
+
+ context 'when status is not defined' do
+ let(:status) { nil }
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'when status is not resovled' do
+ let(:status) { 'firing' }
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'when status is resovled' do
+ let(:status) { 'resolved' }
+
+ it { is_expected.to be_truthy }
+ end
+ end
+
+ describe '#has_required_attributes?' do
+ subject { parsed_payload.has_required_attributes? }
+
+ it { is_expected.to be(true) }
+ end
+end
diff --git a/spec/lib/gitlab/alert_management/payload/generic_spec.rb b/spec/lib/gitlab/alert_management/payload/generic_spec.rb
new file mode 100644
index 00000000000..538a822503e
--- /dev/null
+++ b/spec/lib/gitlab/alert_management/payload/generic_spec.rb
@@ -0,0 +1,89 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::AlertManagement::Payload::Generic do
+ let_it_be(:project) { build_stubbed(:project) }
+ let(:raw_payload) { {} }
+
+ let(:parsed_payload) { described_class.new(project: project, payload: raw_payload) }
+
+ it_behaves_like 'subclass has expected api'
+
+ describe '#title' do
+ subject { parsed_payload.title }
+
+ it_behaves_like 'parsable alert payload field with fallback', 'New: Incident', 'title'
+ end
+
+ describe '#severity' do
+ subject { parsed_payload.severity }
+
+ it_behaves_like 'parsable alert payload field with fallback', 'critical', 'severity'
+ end
+
+ describe '#monitoring_tool' do
+ subject { parsed_payload.monitoring_tool }
+
+ it_behaves_like 'parsable alert payload field', 'monitoring_tool'
+ end
+
+ describe '#service' do
+ subject { parsed_payload.service }
+
+ it_behaves_like 'parsable alert payload field', 'service'
+ end
+
+ describe '#hosts' do
+ subject { parsed_payload.hosts }
+
+ it_behaves_like 'parsable alert payload field', 'hosts'
+ end
+
+ describe '#starts_at' do
+ let(:current_time) { Time.current.change(usec: 0).utc }
+
+ subject { parsed_payload.starts_at }
+
+ around do |example|
+ Timecop.freeze(current_time) { example.run }
+ end
+
+ context 'without start_time' do
+ it { is_expected.to eq(current_time) }
+ end
+
+ context "with start_time" do
+ let(:value) { 10.minutes.ago.change(usec: 0).utc }
+
+ before do
+ raw_payload['start_time'] = value.to_s
+ end
+
+ it { is_expected.to eq(value) }
+ end
+ end
+
+ describe '#runbook' do
+ subject { parsed_payload.runbook }
+
+ it_behaves_like 'parsable alert payload field', 'runbook'
+ end
+
+ describe '#gitlab_fingerprint' do
+ let(:plain_fingerprint) { 'fingerprint' }
+ let(:raw_payload) { { 'fingerprint' => plain_fingerprint } }
+
+ subject { parsed_payload.gitlab_fingerprint }
+
+ it 'returns a fingerprint' do
+ is_expected.to eq(Digest::SHA1.hexdigest(plain_fingerprint))
+ end
+ end
+
+ describe '#environment_name' do
+ subject { parsed_payload.environment_name }
+
+ it_behaves_like 'parsable alert payload field', 'gitlab_environment_name'
+ end
+end
diff --git a/spec/lib/gitlab/alert_management/payload/managed_prometheus_spec.rb b/spec/lib/gitlab/alert_management/payload/managed_prometheus_spec.rb
new file mode 100644
index 00000000000..862b5b2bdc3
--- /dev/null
+++ b/spec/lib/gitlab/alert_management/payload/managed_prometheus_spec.rb
@@ -0,0 +1,167 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::AlertManagement::Payload::ManagedPrometheus do
+ let_it_be(:project) { create(:project) }
+ let(:raw_payload) { {} }
+
+ let(:parsed_payload) { described_class.new(project: project, payload: raw_payload) }
+
+ it_behaves_like 'subclass has expected api'
+
+ shared_context 'with gitlab alert' do
+ let_it_be(:gitlab_alert) { create(:prometheus_alert, project: project) }
+ let(:metric_id) { gitlab_alert.prometheus_metric_id.to_s }
+ let(:alert_id) { gitlab_alert.id.to_s }
+ end
+
+ describe '#metric_id' do
+ subject { parsed_payload.metric_id }
+
+ it { is_expected.to be_nil }
+
+ context 'with gitlab_alert_id' do
+ let(:raw_payload) { { 'labels' => { 'gitlab_alert_id' => '12' } } }
+
+ it { is_expected.to eq(12) }
+ end
+ end
+
+ describe '#gitlab_prometheus_alert_id' do
+ subject { parsed_payload.gitlab_prometheus_alert_id }
+
+ it { is_expected.to be_nil }
+
+ context 'with gitlab_alert_id' do
+ let(:raw_payload) { { 'labels' => { 'gitlab_prometheus_alert_id' => '12' } } }
+
+ it { is_expected.to eq(12) }
+ end
+ end
+
+ describe '#gitlab_alert' do
+ subject { parsed_payload.gitlab_alert }
+
+ context 'without alert info in payload' do
+ it { is_expected.to be_nil }
+ end
+
+ context 'with metric id in payload' do
+ let(:raw_payload) { { 'labels' => { 'gitlab_alert_id' => metric_id } } }
+ let(:metric_id) { '-1' }
+
+ context 'without matching alert' do
+ it { is_expected.to be_nil }
+ end
+
+ context 'with matching alert' do
+ include_context 'with gitlab alert'
+
+ it { is_expected.to eq(gitlab_alert) }
+
+ context 'when unclear which alert applies' do
+ # With multiple alerts for different environments,
+ # we can't be sure which prometheus alert the payload
+ # belongs to
+ let_it_be(:another_alert) do
+ create(:prometheus_alert,
+ prometheus_metric: gitlab_alert.prometheus_metric,
+ project: project)
+ end
+
+ it { is_expected.to be_nil }
+ end
+ end
+ end
+
+ context 'with alert id' do
+ # gitlab_prometheus_alert_id is a stronger identifier,
+ # but was added after gitlab_alert_id; we won't
+ # see it without gitlab_alert_id also present
+ let(:raw_payload) do
+ {
+ 'labels' => {
+ 'gitlab_alert_id' => metric_id,
+ 'gitlab_prometheus_alert_id' => alert_id
+ }
+ }
+ end
+
+ context 'without matching alert' do
+ let(:alert_id) { '-1' }
+ let(:metric_id) { '-1' }
+
+ it { is_expected.to be_nil }
+ end
+
+ context 'with matching alerts' do
+ include_context 'with gitlab alert'
+
+ it { is_expected.to eq(gitlab_alert) }
+ end
+ end
+ end
+
+ describe '#full_query' do
+ subject { parsed_payload.full_query }
+
+ it { is_expected.to be_nil }
+
+ context 'with gitlab alert' do
+ include_context 'with gitlab alert'
+
+ let(:raw_payload) { { 'labels' => { 'gitlab_alert_id' => metric_id } } }
+
+ it { is_expected.to eq(gitlab_alert.full_query) }
+ end
+
+ context 'with sufficient fallback info' do
+ let(:raw_payload) { { 'generatorURL' => 'http://localhost:9090/graph?g0.expr=vector%281%29' } }
+
+ it { is_expected.to eq('vector(1)') }
+ end
+ end
+
+ describe '#environment' do
+ subject { parsed_payload.environment }
+
+ context 'with gitlab alert' do
+ include_context 'with gitlab alert'
+
+ let(:raw_payload) { { 'labels' => { 'gitlab_alert_id' => metric_id } } }
+
+ it { is_expected.to eq(gitlab_alert.environment) }
+ end
+
+ context 'with sufficient fallback info' do
+ let_it_be(:environment) { create(:environment, project: project, name: 'production') }
+ let(:raw_payload) do
+ {
+ 'labels' => {
+ 'gitlab_alert_id' => '-1',
+ 'gitlab_environment_name' => 'production'
+ }
+ }
+ end
+
+ it { is_expected.to eq(environment) }
+ end
+ end
+
+ describe '#metrics_dashboard_url' do
+ subject { parsed_payload.metrics_dashboard_url }
+
+ context 'without alert' do
+ it { is_expected.to be_nil }
+ end
+
+ context 'with gitlab alert' do
+ include_context 'gitlab-managed prometheus alert attributes' do
+ let(:raw_payload) { payload }
+ end
+
+ it { is_expected.to eq(dashboard_url_for_alert) }
+ end
+ end
+end
diff --git a/spec/lib/gitlab/alert_management/payload/prometheus_spec.rb b/spec/lib/gitlab/alert_management/payload/prometheus_spec.rb
new file mode 100644
index 00000000000..457db58a28b
--- /dev/null
+++ b/spec/lib/gitlab/alert_management/payload/prometheus_spec.rb
@@ -0,0 +1,240 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::AlertManagement::Payload::Prometheus do
+ let_it_be(:project) { create(:project) }
+ let(:raw_payload) { {} }
+
+ let(:parsed_payload) { described_class.new(project: project, payload: raw_payload) }
+
+ it_behaves_like 'subclass has expected api'
+
+ shared_context 'with environment' do
+ let_it_be(:environment) { create(:environment, project: project, name: 'production') }
+ end
+
+ describe '#title' do
+ subject { parsed_payload.title }
+
+ it_behaves_like 'parsable alert payload field',
+ 'annotations/title',
+ 'annotations/summary',
+ 'labels/alertname'
+ end
+
+ describe '#description' do
+ subject { parsed_payload.description }
+
+ it_behaves_like 'parsable alert payload field', 'annotations/description'
+ end
+
+ describe '#annotations' do
+ subject { parsed_payload.annotations }
+
+ it_behaves_like 'parsable alert payload field', 'annotations'
+ end
+
+ describe '#status' do
+ subject { parsed_payload.status }
+
+ it_behaves_like 'parsable alert payload field', 'status'
+ end
+
+ describe '#starts_at' do
+ let(:current_time) { Time.current.utc }
+
+ around do |example|
+ freeze_time { example.run }
+ end
+
+ subject { parsed_payload.starts_at }
+
+ context 'without payload' do
+ it { is_expected.to eq(current_time) }
+ end
+
+ context "with startsAt" do
+ let(:value) { 10.minutes.ago.change(usec: 0).utc }
+ let(:raw_payload) { { 'startsAt' => value.rfc3339 } }
+
+ it { is_expected.to eq(value) }
+ end
+ end
+
+ describe '#ends_at' do
+ subject { parsed_payload.ends_at }
+
+ context 'without payload' do
+ it { is_expected.to be_nil }
+ end
+
+ context "with endsAt" do
+ let(:value) { Time.current.change(usec: 0).utc }
+ let(:raw_payload) { { 'endsAt' => value.rfc3339 } }
+
+ it { is_expected.to eq(value) }
+ end
+ end
+
+ describe '#generator_url' do
+ subject { parsed_payload.generator_url }
+
+ it_behaves_like 'parsable alert payload field', 'generatorURL'
+ end
+
+ describe '#runbook' do
+ subject { parsed_payload.runbook }
+
+ it_behaves_like 'parsable alert payload field', 'annotations/runbook'
+ end
+
+ describe '#alert_markdown' do
+ subject { parsed_payload.alert_markdown }
+
+ it_behaves_like 'parsable alert payload field', 'annotations/gitlab_incident_markdown'
+ end
+
+ describe '#environment_name' do
+ subject { parsed_payload.environment_name }
+
+ it_behaves_like 'parsable alert payload field', 'labels/gitlab_environment_name'
+ end
+
+ describe '#gitlab_y_label' do
+ subject { parsed_payload.gitlab_y_label }
+
+ it_behaves_like 'parsable alert payload field',
+ 'annotations/gitlab_y_label',
+ 'annotations/title',
+ 'annotations/summary',
+ 'labels/alertname'
+ end
+
+ describe '#monitoring_tool' do
+ subject { parsed_payload.monitoring_tool }
+
+ it { is_expected.to eq('Prometheus') }
+ end
+
+ describe '#full_query' do
+ using RSpec::Parameterized::TableSyntax
+
+ subject { parsed_payload.full_query }
+
+ where(:generator_url, :expected_query) do
+ nil | nil
+ 'http://localhost' | nil
+ 'invalid url' | nil
+ 'http://localhost:9090/graph?g1.expr=vector%281%29' | nil
+ 'http://localhost:9090/graph?g0.expr=vector%281%29' | 'vector(1)'
+ end
+
+ with_them do
+ let(:raw_payload) { { 'generatorURL' => generator_url } }
+
+ it { is_expected.to eq(expected_query) }
+ end
+ end
+
+ describe '#environment' do
+ subject { parsed_payload.environment }
+
+ it { is_expected.to be_nil }
+
+ context 'with environment_name' do
+ let(:raw_payload) { { 'labels' => { 'gitlab_environment_name' => 'production' } } }
+
+ it { is_expected.to be_nil }
+
+ context 'with matching environment' do
+ include_context 'with environment'
+
+ it { is_expected.to eq(environment) }
+ end
+ end
+ end
+
+ describe '#gitlab_fingerprint' do
+ subject { parsed_payload.gitlab_fingerprint }
+
+ let(:raw_payload) do
+ {
+ 'startsAt' => Time.current.to_s,
+ 'generatorURL' => 'http://localhost:9090/graph?g0.expr=vector%281%29',
+ 'annotations' => { 'title' => 'title' }
+ }
+ end
+
+ it 'returns a fingerprint' do
+ plain_fingerprint = [
+ parsed_payload.send(:starts_at_raw),
+ parsed_payload.title,
+ parsed_payload.full_query
+ ].join('/')
+
+ is_expected.to eq(Digest::SHA1.hexdigest(plain_fingerprint))
+ end
+ end
+
+ describe '#metrics_dashboard_url' do
+ include_context 'self-managed prometheus alert attributes' do
+ let(:raw_payload) { payload }
+ end
+
+ subject { parsed_payload.metrics_dashboard_url }
+
+ it { is_expected.to eq(dashboard_url_for_alert) }
+
+ context 'without environment' do
+ let(:raw_payload) { payload.except('labels') }
+
+ it { is_expected.to be_nil }
+ end
+
+ context 'without full query' do
+ let(:raw_payload) { payload.except('generatorURL') }
+
+ it { is_expected.to be_nil }
+ end
+
+ context 'without title' do
+ let(:raw_payload) { payload.except('annotations') }
+
+ it { is_expected.to be_nil }
+ end
+ end
+
+ describe '#has_required_attributes?' do
+ let(:starts_at) { Time.current.change(usec: 0).utc }
+ let(:raw_payload) { { 'annotations' => { 'title' => 'title' }, 'startsAt' => starts_at.rfc3339 } }
+
+ subject { parsed_payload.has_required_attributes? }
+
+ it { is_expected.to be_truthy }
+
+ context 'without project' do
+ let(:parsed_payload) { described_class.new(project: nil, payload: raw_payload) }
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'without title' do
+ let(:raw_payload) { { 'startsAt' => starts_at.rfc3339 } }
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'without startsAt' do
+ let(:raw_payload) { { 'annotations' => { 'title' => 'title' } } }
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'without payload' do
+ let(:parsed_payload) { described_class.new(project: project, payload: nil) }
+
+ it { is_expected.to be_falsey }
+ end
+ end
+end
diff --git a/spec/lib/gitlab/alert_management/payload_spec.rb b/spec/lib/gitlab/alert_management/payload_spec.rb
new file mode 100644
index 00000000000..44b55e228c5
--- /dev/null
+++ b/spec/lib/gitlab/alert_management/payload_spec.rb
@@ -0,0 +1,60 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::AlertManagement::Payload do
+ describe '#parse' do
+ let_it_be(:project) { build_stubbed(:project) }
+ let(:payload) { {} }
+
+ context 'without a monitoring_tool specified by caller' do
+ subject { described_class.parse(project, payload) }
+
+ context 'without a monitoring tool in the payload' do
+ it { is_expected.to be_a Gitlab::AlertManagement::Payload::Generic }
+ end
+
+ context 'with the payload specifying Prometheus' do
+ let(:payload) { { 'monitoring_tool' => 'Prometheus' } }
+
+ it { is_expected.to be_a Gitlab::AlertManagement::Payload::Prometheus }
+
+ context 'with gitlab-managed attributes' do
+ let(:payload) { { 'monitoring_tool' => 'Prometheus', 'labels' => { 'gitlab_alert_id' => '12' } } }
+
+ it { is_expected.to be_a Gitlab::AlertManagement::Payload::ManagedPrometheus }
+ end
+ end
+
+ context 'with the payload specifying an unknown tool' do
+ let(:payload) { { 'monitoring_tool' => 'Custom Tool' } }
+
+ it { is_expected.to be_a Gitlab::AlertManagement::Payload::Generic }
+ end
+ end
+
+ context 'with monitoring_tool specified by caller' do
+ subject { described_class.parse(project, payload, monitoring_tool: monitoring_tool) }
+
+ context 'as Prometheus' do
+ let(:monitoring_tool) { 'Prometheus' }
+
+ context 'with an externally managed prometheus payload' do
+ it { is_expected.to be_a Gitlab::AlertManagement::Payload::Prometheus }
+ end
+
+ context 'with a self-managed prometheus payload' do
+ let(:payload) { { 'labels' => { 'gitlab_alert_id' => '14' } } }
+
+ it { is_expected.to be_a Gitlab::AlertManagement::Payload::ManagedPrometheus }
+ end
+ end
+
+ context 'as an unknown tool' do
+ let(:monitoring_tool) { 'Custom Tool' }
+
+ it { is_expected.to be_a Gitlab::AlertManagement::Payload::Generic }
+ end
+ end
+ end
+end