diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-11-18 13:16:36 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-11-18 13:16:36 +0000 |
commit | 311b0269b4eb9839fa63f80c8d7a58f32b8138a0 (patch) | |
tree | 07e7870bca8aed6d61fdcc810731c50d2c40af47 /spec/services/issues | |
parent | 27909cef6c4170ed9205afa7426b8d3de47cbb0c (diff) | |
download | gitlab-ce-311b0269b4eb9839fa63f80c8d7a58f32b8138a0.tar.gz |
Add latest changes from gitlab-org/gitlab@14-5-stable-eev14.5.0-rc42
Diffstat (limited to 'spec/services/issues')
-rw-r--r-- | spec/services/issues/build_service_spec.rb | 96 | ||||
-rw-r--r-- | spec/services/issues/close_service_spec.rb | 44 | ||||
-rw-r--r-- | spec/services/issues/create_service_spec.rb | 79 | ||||
-rw-r--r-- | spec/services/issues/set_crm_contacts_service_spec.rb | 162 | ||||
-rw-r--r-- | spec/services/issues/update_service_spec.rb | 42 |
5 files changed, 339 insertions, 84 deletions
diff --git a/spec/services/issues/build_service_spec.rb b/spec/services/issues/build_service_spec.rb index b96dd981e0f..cf75efb5c57 100644 --- a/spec/services/issues/build_service_spec.rb +++ b/spec/services/issues/build_service_spec.rb @@ -7,12 +7,14 @@ RSpec.describe Issues::BuildService do let_it_be(:project) { create(:project, :repository) } let_it_be(:developer) { create(:user) } + let_it_be(:reporter) { create(:user) } let_it_be(:guest) { create(:user) } let(:user) { developer } before_all do project.add_developer(developer) + project.add_reporter(reporter) project.add_guest(guest) end @@ -140,76 +142,64 @@ RSpec.describe Issues::BuildService do end describe '#execute' do - context 'as developer' do - it 'builds a new issues with given params' do - milestone = create(:milestone, project: project) - issue = build_issue(milestone_id: milestone.id) - - expect(issue.milestone).to eq(milestone) - expect(issue.issue_type).to eq('issue') - expect(issue.work_item_type.base_type).to eq('issue') - end + describe 'setting milestone' do + context 'when developer' do + it 'builds a new issues with given params' do + milestone = create(:milestone, project: project) + issue = build_issue(milestone_id: milestone.id) + + expect(issue.milestone).to eq(milestone) + end - it 'sets milestone to nil if it is not available for the project' do - milestone = create(:milestone, project: create(:project)) - issue = build_issue(milestone_id: milestone.id) + it 'sets milestone to nil if it is not available for the project' do + milestone = create(:milestone, project: create(:project)) + issue = build_issue(milestone_id: milestone.id) - expect(issue.milestone).to be_nil + expect(issue.milestone).to be_nil + end end - context 'when issue_type is incident' do - it 'sets the correct issue type' do - issue = build_issue(issue_type: 'incident') + context 'when guest' do + let(:user) { guest } - expect(issue.issue_type).to eq('incident') - expect(issue.work_item_type.base_type).to eq('incident') + it 'cannot set milestone' do + milestone = create(:milestone, project: project) + issue = build_issue(milestone_id: milestone.id) + + expect(issue.milestone).to be_nil end end end - context 'as guest' do - let(:user) { guest } - - it 'cannot set milestone' do - milestone = create(:milestone, project: project) - issue = build_issue(milestone_id: milestone.id) + describe 'setting issue type' do + context 'with a corresponding WorkItem::Type' do + let_it_be(:type_issue_id) { WorkItem::Type.default_issue_type.id } + let_it_be(:type_incident_id) { WorkItem::Type.default_by_type(:incident).id } + + where(:issue_type, :current_user, :work_item_type_id, :resulting_issue_type) do + nil | ref(:guest) | ref(:type_issue_id) | 'issue' + 'issue' | ref(:guest) | ref(:type_issue_id) | 'issue' + 'incident' | ref(:guest) | ref(:type_issue_id) | 'issue' + 'incident' | ref(:reporter) | ref(:type_incident_id) | 'incident' + # update once support for test_case is enabled + 'test_case' | ref(:guest) | ref(:type_issue_id) | 'issue' + # update once support for requirement is enabled + 'requirement' | ref(:guest) | ref(:type_issue_id) | 'issue' + 'invalid' | ref(:guest) | ref(:type_issue_id) | 'issue' + # ensure that we don't set a value which has a permission check but is an invalid issue type + 'project' | ref(:guest) | ref(:type_issue_id) | 'issue' + end - expect(issue.milestone).to be_nil - end + with_them do + let(:user) { current_user } - context 'setting issue type' do - shared_examples 'builds an issue' do - specify do + it 'builds an issue' do issue = build_issue(issue_type: issue_type) expect(issue.issue_type).to eq(resulting_issue_type) expect(issue.work_item_type_id).to eq(work_item_type_id) end end - - it 'cannot set invalid issue type' do - issue = build_issue(issue_type: 'project') - - expect(issue).to be_issue - end - - context 'with a corresponding WorkItem::Type' do - let_it_be(:type_issue_id) { WorkItem::Type.default_issue_type.id } - let_it_be(:type_incident_id) { WorkItem::Type.default_by_type(:incident).id } - - where(:issue_type, :work_item_type_id, :resulting_issue_type) do - nil | ref(:type_issue_id) | 'issue' - 'issue' | ref(:type_issue_id) | 'issue' - 'incident' | ref(:type_incident_id) | 'incident' - 'test_case' | ref(:type_issue_id) | 'issue' # update once support for test_case is enabled - 'requirement' | ref(:type_issue_id) | 'issue' # update once support for requirement is enabled - 'invalid' | ref(:type_issue_id) | 'issue' - end - - with_them do - it_behaves_like 'builds an issue' - end - end end end end diff --git a/spec/services/issues/close_service_spec.rb b/spec/services/issues/close_service_spec.rb index 93ef046a632..158f9dec83e 100644 --- a/spec/services/issues/close_service_spec.rb +++ b/spec/services/issues/close_service_spec.rb @@ -83,6 +83,14 @@ RSpec.describe Issues::CloseService do service.execute(issue) end + it 'does not change escalation status' do + resolved = IncidentManagement::Escalatable::STATUSES[:resolved] + + expect { service.execute(issue) } + .to not_change { IncidentManagement::IssuableEscalationStatus.where(issue: issue).count } + .and not_change { IncidentManagement::IssuableEscalationStatus.where(status: resolved).count } + end + context 'issue is incident type' do let(:issue) { create(:incident, project: project) } let(:current_user) { user } @@ -90,6 +98,40 @@ RSpec.describe Issues::CloseService do subject { service.execute(issue) } it_behaves_like 'an incident management tracked event', :incident_management_incident_closed + + it 'creates a new escalation resolved escalation status', :aggregate_failures do + expect { service.execute(issue) }.to change { IncidentManagement::IssuableEscalationStatus.where(issue: issue).count }.by(1) + + expect(issue.incident_management_issuable_escalation_status).to be_resolved + end + + context 'when there is an escalation status' do + before do + create(:incident_management_issuable_escalation_status, issue: issue) + end + + it 'changes escalations status to resolved' do + expect { service.execute(issue) }.to change { issue.incident_management_issuable_escalation_status.reload.resolved? }.to(true) + end + + it 'adds a system note', :aggregate_failures do + expect { service.execute(issue) }.to change { issue.notes.count }.by(1) + + new_note = issue.notes.last + expect(new_note.note).to eq('changed the status to **Resolved** by closing the incident') + expect(new_note.author).to eq(user) + end + + context 'when the escalation status did not change to resolved' do + let(:escalation_status) { instance_double('IncidentManagement::IssuableEscalationStatus', resolve: false) } + + it 'does not create a system note' do + allow(issue).to receive(:incident_management_issuable_escalation_status).and_return(escalation_status) + + expect { service.execute(issue) }.not_to change { issue.notes.count } + end + end + end end end @@ -237,7 +279,7 @@ RSpec.describe Issues::CloseService do it 'verifies the number of queries' do recorded = ActiveRecord::QueryRecorder.new { close_issue } - expected_queries = 27 + expected_queries = 32 expect(recorded.count).to be <= expected_queries expect(recorded.cached_count).to eq(0) diff --git a/spec/services/issues/create_service_spec.rb b/spec/services/issues/create_service_spec.rb index 1887be4896e..18e03db11dc 100644 --- a/spec/services/issues/create_service_spec.rb +++ b/spec/services/issues/create_service_spec.rb @@ -15,8 +15,7 @@ RSpec.describe Issues::CreateService do expect(described_class.rate_limiter_scoped_and_keyed).to be_a(RateLimitedService::RateLimiterScopedAndKeyed) expect(described_class.rate_limiter_scoped_and_keyed.key).to eq(:issues_create) - expect(described_class.rate_limiter_scoped_and_keyed.opts[:scope]).to eq(%i[project current_user]) - expect(described_class.rate_limiter_scoped_and_keyed.opts[:users_allowlist].call).to eq(%w[support-bot]) + expect(described_class.rate_limiter_scoped_and_keyed.opts[:scope]).to eq(%i[project current_user external_author]) expect(described_class.rate_limiter_scoped_and_keyed.rate_limiter_klass).to eq(Gitlab::ApplicationRateLimiter) end end @@ -81,7 +80,7 @@ RSpec.describe Issues::CreateService do it_behaves_like 'not an incident issue' - context 'issue is incident type' do + context 'when issue is incident type' do before do opts.merge!(issue_type: 'incident') end @@ -91,23 +90,37 @@ RSpec.describe Issues::CreateService do subject { issue } - it_behaves_like 'incident issue' - it_behaves_like 'has incident label' + context 'as reporter' do + let_it_be(:reporter) { create(:user) } - it 'does create an incident label' do - expect { subject } - .to change { Label.where(incident_label_attributes).count }.by(1) - end + let(:user) { reporter } - context 'when invalid' do - before do - opts.merge!(title: '') + before_all do + project.add_reporter(reporter) end - it 'does not apply an incident label prematurely' do - expect { subject }.to not_change(LabelLink, :count).and not_change(Issue, :count) + it_behaves_like 'incident issue' + it_behaves_like 'has incident label' + + it 'does create an incident label' do + expect { subject } + .to change { Label.where(incident_label_attributes).count }.by(1) + end + + context 'when invalid' do + before do + opts.merge!(title: '') + end + + it 'does not apply an incident label prematurely' do + expect { subject }.to not_change(LabelLink, :count).and not_change(Issue, :count) + end end end + + context 'as guest' do + it_behaves_like 'not an incident issue' + end end it 'refreshes the number of open issues', :use_clean_rails_memory_store_caching do @@ -289,6 +302,44 @@ RSpec.describe Issues::CreateService do described_class.new(project: project, current_user: user, params: opts, spam_params: spam_params).execute end + context 'when rate limiting is in effect', :freeze_time, :clean_gitlab_redis_rate_limiting do + let(:user) { create(:user) } + + before do + stub_feature_flags(rate_limited_service_issues_create: true) + stub_application_setting(issues_create_limit: 1) + end + + subject do + 2.times { described_class.new(project: project, current_user: user, params: opts, spam_params: double).execute } + end + + context 'when too many requests are sent by one user' do + it 'raises an error' do + expect do + subject + end.to raise_error(RateLimitedService::RateLimitedError) + end + + it 'creates 1 issue' do + expect do + subject + rescue RateLimitedService::RateLimitedError + end.to change { Issue.count }.by(1) + end + end + + context 'when limit is higher than count of issues being created' do + before do + stub_application_setting(issues_create_limit: 2) + end + + it 'creates 2 issues' do + expect { subject }.to change { Issue.count }.by(2) + end + end + end + context 'after_save callback to store_mentions' do context 'when mentionable attributes change' do let(:opts) { { title: 'Title', description: "Description with #{user.to_reference}" } } diff --git a/spec/services/issues/set_crm_contacts_service_spec.rb b/spec/services/issues/set_crm_contacts_service_spec.rb new file mode 100644 index 00000000000..65b22fe3b35 --- /dev/null +++ b/spec/services/issues/set_crm_contacts_service_spec.rb @@ -0,0 +1,162 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Issues::SetCrmContactsService do + let_it_be(:user) { create(:user) } + let_it_be(:group) { create(:group) } + let_it_be(:project) { create(:project, group: group) } + let_it_be(:contacts) { create_list(:contact, 4, group: group) } + + let(:issue) { create(:issue, project: project) } + let(:does_not_exist_or_no_permission) { "The resource that you are attempting to access does not exist or you don't have permission to perform this action" } + + before do + create(:issue_customer_relations_contact, issue: issue, contact: contacts[0]) + create(:issue_customer_relations_contact, issue: issue, contact: contacts[1]) + end + + subject(:set_crm_contacts) do + described_class.new(project: project, current_user: user, params: params).execute(issue) + end + + describe '#execute' do + context 'when the user has no permission' do + let(:params) { { crm_contact_ids: [contacts[1].id, contacts[2].id] } } + + it 'returns expected error response' do + response = set_crm_contacts + + expect(response).to be_error + expect(response.message).to match_array(['You have insufficient permissions to set customer relations contacts for this issue']) + end + end + + context 'when user has permission' do + before do + group.add_reporter(user) + end + + context 'when the contact does not exist' do + let(:params) { { crm_contact_ids: [non_existing_record_id] } } + + it 'returns expected error response' do + response = set_crm_contacts + + expect(response).to be_error + expect(response.message).to match_array(["Issue customer relations contacts #{non_existing_record_id}: #{does_not_exist_or_no_permission}"]) + end + end + + context 'when the contact belongs to a different group' do + let(:group2) { create(:group) } + let(:contact) { create(:contact, group: group2) } + let(:params) { { crm_contact_ids: [contact.id] } } + + before do + group2.add_reporter(user) + end + + it 'returns expected error response' do + response = set_crm_contacts + + expect(response).to be_error + expect(response.message).to match_array(["Issue customer relations contacts #{contact.id}: #{does_not_exist_or_no_permission}"]) + end + end + + context 'replace' do + let(:params) { { crm_contact_ids: [contacts[1].id, contacts[2].id] } } + + it 'updates the issue with correct contacts' do + response = set_crm_contacts + + expect(response).to be_success + expect(issue.customer_relations_contacts).to match_array([contacts[1], contacts[2]]) + end + end + + context 'add' do + let(:params) { { add_crm_contact_ids: [contacts[3].id] } } + + it 'updates the issue with correct contacts' do + response = set_crm_contacts + + expect(response).to be_success + expect(issue.customer_relations_contacts).to match_array([contacts[0], contacts[1], contacts[3]]) + end + end + + context 'remove' do + let(:params) { { remove_crm_contact_ids: [contacts[0].id] } } + + it 'updates the issue with correct contacts' do + response = set_crm_contacts + + expect(response).to be_success + expect(issue.customer_relations_contacts).to match_array([contacts[1]]) + end + end + + context 'when attempting to add more than 6' do + let(:id) { contacts[0].id } + let(:params) { { add_crm_contact_ids: [id, id, id, id, id, id, id] } } + + it 'returns expected error message' do + response = set_crm_contacts + + expect(response).to be_error + expect(response.message).to match_array(['You can only add up to 6 contacts at one time']) + end + end + + context 'when trying to remove non-existent contact' do + let(:params) { { remove_crm_contact_ids: [non_existing_record_id] } } + + it 'returns expected error message' do + response = set_crm_contacts + + expect(response).to be_success + expect(response.message).to be_nil + end + end + + context 'when combining params' do + let(:error_invalid_params) { 'You cannot combine crm_contact_ids with add_crm_contact_ids or remove_crm_contact_ids' } + + context 'add and remove' do + let(:params) { { remove_crm_contact_ids: [contacts[1].id], add_crm_contact_ids: [contacts[3].id] } } + + it 'updates the issue with correct contacts' do + response = set_crm_contacts + + expect(response).to be_success + expect(issue.customer_relations_contacts).to match_array([contacts[0], contacts[3]]) + end + end + + context 'replace and remove' do + let(:params) { { crm_contact_ids: [contacts[3].id], remove_crm_contact_ids: [contacts[0].id] } } + + it 'returns expected error response' do + response = set_crm_contacts + + expect(response).to be_error + expect(response.message).to match_array([error_invalid_params]) + end + end + + context 'replace and add' do + let(:params) { { crm_contact_ids: [contacts[3].id], add_crm_contact_ids: [contacts[1].id] } } + + it 'returns expected error response' do + response = set_crm_contacts + + expect(response).to be_error + expect(response.message).to match_array([error_invalid_params]) + end + end + end + end + end +end diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb index 83c17f051eb..85b8fef685e 100644 --- a/spec/services/issues/update_service_spec.rb +++ b/spec/services/issues/update_service_spec.rb @@ -1256,28 +1256,38 @@ RSpec.describe Issues::UpdateService, :mailer do let(:closed_issuable) { create(:closed_issue, project: project) } end - context 'real-time updates' do - using RSpec::Parameterized::TableSyntax - + context 'broadcasting issue assignee updates' do let(:update_params) { { assignee_ids: [user2.id] } } - where(:action_cable_in_app_enabled, :feature_flag_enabled, :should_broadcast) do - true | true | true - true | false | true - false | true | true - false | false | false - end + context 'when feature flag is enabled' do + before do + stub_feature_flags(broadcast_issue_updates: true) + end + + it 'triggers the GraphQL subscription' do + expect(GraphqlTriggers).to receive(:issuable_assignees_updated).with(issue) + + update_issue(update_params) + end - with_them do - it 'broadcasts to the issues channel based on ActionCable and feature flag values' do - allow(Gitlab::ActionCable::Config).to receive(:in_app?).and_return(action_cable_in_app_enabled) - stub_feature_flags(broadcast_issue_updates: feature_flag_enabled) + context 'when assignee is not updated' do + let(:update_params) { { title: 'Some other title' } } - if should_broadcast - expect(GraphqlTriggers).to receive(:issuable_assignees_updated).with(issue) - else + it 'does not trigger the GraphQL subscription' do expect(GraphqlTriggers).not_to receive(:issuable_assignees_updated).with(issue) + + update_issue(update_params) end + end + end + + context 'when feature flag is disabled' do + before do + stub_feature_flags(broadcast_issue_updates: false) + end + + it 'does not trigger the GraphQL subscription' do + expect(GraphqlTriggers).not_to receive(:issuable_assignees_updated).with(issue) update_issue(update_params) end |