# frozen_string_literal: true require 'spec_helper' RSpec.describe IncidentManagement::CreateIssueService do let(:project) { create(:project, :repository, :private) } let_it_be(:user) { User.alert_bot } let(:service) { described_class.new(project, alert_payload) } let(:alert_starts_at) { Time.current } let(:alert_title) { 'TITLE' } let(:alert_annotations) { { title: alert_title } } let(:alert_payload) do build_alert_payload( annotations: alert_annotations, starts_at: alert_starts_at ) end let(:alert_presenter) do Gitlab::Alerting::Alert.new(project: project, payload: alert_payload).present end let!(:setting) do create(:project_incident_management_setting, project: project) end subject { service.execute } context 'when create_issue enabled' do let(:issue) { subject[:issue] } before do setting.update!(create_issue: true) end context 'without issue_template_content' do it 'creates an issue with alert summary only' do expect(subject).to include(status: :success) expect(issue.author).to eq(user) expect(issue.title).to eq(alert_title) expect(issue.description).to include(alert_presenter.issue_summary_markdown.strip) expect(separator_count(issue.description)).to eq(0) end end context 'with erroneous issue service' do let(:invalid_issue) do build(:issue, project: project, title: nil).tap(&:valid?) end let(:issue_error) { invalid_issue.errors.full_messages.to_sentence } it 'returns and logs the issue error' do expect_next_instance_of(Issues::CreateService) do |issue_service| expect(issue_service).to receive(:execute).and_return(invalid_issue) end expect(service) .to receive(:log_error) .with(error_message(issue_error)) expect(subject).to include(status: :error, message: issue_error) end end shared_examples 'GFM template' do context 'plain content' do let(:template_content) { 'some content' } it 'creates an issue appending issue template' do expect(subject).to include(status: :success) expect(issue.description).to include(alert_presenter.issue_summary_markdown) expect(separator_count(issue.description)).to eq(1) expect(issue.description).to include(template_content) end end context 'quick actions' do let(:user) { create(:user) } let(:plain_text) { 'some content' } let(:template_content) do <<~CONTENT #{plain_text} /due tomorrow /assign @#{user.username} CONTENT end before do project.add_maintainer(user) end it 'creates an issue interpreting quick actions' do expect(subject).to include(status: :success) expect(issue.description).to include(plain_text) expect(issue.due_date).to be_present expect(issue.assignees).to eq([user]) end end end context 'with gitlab_incident_markdown' do let(:alert_annotations) do { title: alert_title, gitlab_incident_markdown: template_content } end it_behaves_like 'GFM template' end context 'with issue_template_content' do before do create_issue_template('bug', template_content) setting.update!(issue_template_key: 'bug') end it_behaves_like 'GFM template' context 'and gitlab_incident_markdown' do let(:template_content) { 'plain text'} let(:alt_template) { 'alternate text' } let(:alert_annotations) do { title: alert_title, gitlab_incident_markdown: alt_template } end it 'includes both templates' do expect(subject).to include(status: :success) expect(issue.description).to include(alert_presenter.issue_summary_markdown) expect(issue.description).to include(template_content) expect(issue.description).to include(alt_template) expect(separator_count(issue.description)).to eq(2) end end private def create_issue_template(name, content) project.repository.create_file( project.creator, ".gitlab/issue_templates/#{name}.md", content, message: 'message', branch_name: 'master' ) end end context 'with gitlab alert' do let(:gitlab_alert) { create(:prometheus_alert, project: project) } before do alert_payload['labels'] = { 'gitlab_alert_id' => gitlab_alert.prometheus_metric_id.to_s } end it 'creates an issue' do query_title = "#{gitlab_alert.title} #{gitlab_alert.computed_operator} #{gitlab_alert.threshold}" expect(subject).to include(status: :success) expect(issue.author).to eq(user) expect(issue.title).to eq(alert_presenter.full_title) expect(issue.title).to include(gitlab_alert.environment.name) expect(issue.title).to include(query_title) expect(issue.title).to include('for 5 minutes') expect(issue.description).to include(alert_presenter.issue_summary_markdown.strip) expect(separator_count(issue.description)).to eq(0) end end describe 'with invalid alert payload' do shared_examples 'invalid alert' do it 'does not create an issue' do expect(service) .to receive(:log_error) .with(error_message('invalid alert')) expect(subject).to eq(status: :error, message: 'invalid alert') end end context 'without title' do let(:alert_annotations) { {} } it_behaves_like 'invalid alert' end context 'without startsAt' do let(:alert_starts_at) { nil } it_behaves_like 'invalid alert' end end describe "label `incident`" do it_behaves_like 'create alert issue sets issue labels' end end context 'when create_issue disabled' do before do setting.update!(create_issue: false) end it 'returns an error' do expect(service) .to receive(:log_error) .with(error_message('setting disabled')) expect(subject).to eq(status: :error, message: 'setting disabled') end end private def build_alert_payload(annotations: {}, starts_at: Time.current) { 'annotations' => annotations.stringify_keys }.tap do |payload| payload['startsAt'] = starts_at.rfc3339 if starts_at end end def error_message(message) %{Cannot create incident issue for "#{project.full_name}": #{message}} end def separator_count(text) summary_separator = "\n\n---\n\n" text.scan(summary_separator).size end end