Commit deafbf11 authored by Jan Provaznik's avatar Jan Provaznik

Merge branch '349683-add-crm-system-notes' into 'master'

Create system note when issue contacts are changed

See merge request gitlab-org/gitlab!77816
parents 76e27733 2729386b
...@@ -42,7 +42,8 @@ module SystemNoteHelper ...@@ -42,7 +42,8 @@ module SystemNoteHelper
'cloned' => 'documents', 'cloned' => 'documents',
'issue_type' => 'pencil-square', 'issue_type' => 'pencil-square',
'attention_requested' => 'user', 'attention_requested' => 'user',
'attention_request_removed' => 'user' 'attention_request_removed' => 'user',
'contact' => 'users'
}.freeze }.freeze
def system_note_icon_name(note) def system_note_icon_name(note)
......
...@@ -25,7 +25,7 @@ class Discussion ...@@ -25,7 +25,7 @@ class Discussion
:to_ability_name, :to_ability_name,
:editable?, :editable?,
:resolved_by_id, :resolved_by_id,
:system_note_with_references_visible_for?, :system_note_visible_for?,
:resource_parent, :resource_parent,
:save, :save,
to: :first_note to: :first_note
......
...@@ -27,10 +27,14 @@ class Note < ApplicationRecord ...@@ -27,10 +27,14 @@ class Note < ApplicationRecord
redact_field :note redact_field :note
TYPES_RESTRICTED_BY_ABILITY = { TYPES_RESTRICTED_BY_PROJECT_ABILITY = {
branch: :download_code branch: :download_code
}.freeze }.freeze
TYPES_RESTRICTED_BY_GROUP_ABILITY = {
contact: :read_crm_contact
}.freeze
# Aliases to make application_helper#edited_time_ago_with_tooltip helper work properly with notes. # Aliases to make application_helper#edited_time_ago_with_tooltip helper work properly with notes.
# See https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/10392/diffs#note_28719102 # See https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/10392/diffs#note_28719102
alias_attribute :last_edited_by, :updated_by alias_attribute :last_edited_by, :updated_by
...@@ -119,7 +123,7 @@ class Note < ApplicationRecord ...@@ -119,7 +123,7 @@ class Note < ApplicationRecord
scope :inc_author, -> { includes(:author) } scope :inc_author, -> { includes(:author) }
scope :with_api_entity_associations, -> { preload(:note_diff_file, :author) } scope :with_api_entity_associations, -> { preload(:note_diff_file, :author) }
scope :inc_relations_for_view, -> do scope :inc_relations_for_view, -> do
includes(:project, { author: :status }, :updated_by, :resolved_by, :award_emoji, includes({ project: :group }, { author: :status }, :updated_by, :resolved_by, :award_emoji,
{ system_note_metadata: :description_version }, :note_diff_file, :diff_note_positions, :suggestions) { system_note_metadata: :description_version }, :note_diff_file, :diff_note_positions, :suggestions)
end end
...@@ -565,10 +569,10 @@ class Note < ApplicationRecord ...@@ -565,10 +569,10 @@ class Note < ApplicationRecord
noteable.user_mentions.where(note: self) noteable.user_mentions.where(note: self)
end end
def system_note_with_references_visible_for?(user) def system_note_visible_for?(user)
return true unless system? return true unless system?
(!system_note_with_references? || all_referenced_mentionables_allowed?(user)) && system_note_viewable_by?(user) system_note_viewable_by?(user) && all_referenced_mentionables_allowed?(user)
end end
def parent_user def parent_user
...@@ -617,10 +621,17 @@ class Note < ApplicationRecord ...@@ -617,10 +621,17 @@ class Note < ApplicationRecord
def system_note_viewable_by?(user) def system_note_viewable_by?(user)
return true unless system_note_metadata return true unless system_note_metadata
restriction = TYPES_RESTRICTED_BY_ABILITY[system_note_metadata.action.to_sym] system_note_viewable_by_project_ability?(user) && system_note_viewable_by_group_ability?(user)
return Ability.allowed?(user, restriction, project) if restriction end
true def system_note_viewable_by_project_ability?(user)
project_restriction = TYPES_RESTRICTED_BY_PROJECT_ABILITY[system_note_metadata.action.to_sym]
!project_restriction || Ability.allowed?(user, project_restriction, project)
end
def system_note_viewable_by_group_ability?(user)
group_restriction = TYPES_RESTRICTED_BY_GROUP_ABILITY[system_note_metadata.action.to_sym]
!group_restriction || Ability.allowed?(user, group_restriction, project&.group)
end end
def keep_around_commit def keep_around_commit
...@@ -646,6 +657,8 @@ class Note < ApplicationRecord ...@@ -646,6 +657,8 @@ class Note < ApplicationRecord
end end
def all_referenced_mentionables_allowed?(user) def all_referenced_mentionables_allowed?(user)
return true unless system_note_with_references?
if user_visible_reference_count.present? && total_reference_count.present? if user_visible_reference_count.present? && total_reference_count.present?
# if they are not equal, then there are private/confidential references as well # if they are not equal, then there are private/confidential references as well
user_visible_reference_count > 0 && user_visible_reference_count == total_reference_count user_visible_reference_count > 0 && user_visible_reference_count == total_reference_count
......
...@@ -24,7 +24,7 @@ class SystemNoteMetadata < ApplicationRecord ...@@ -24,7 +24,7 @@ class SystemNoteMetadata < ApplicationRecord
opened closed merged duplicate locked unlocked outdated reviewer opened closed merged duplicate locked unlocked outdated reviewer
tag due_date pinned_embed cherry_pick health_status approved unapproved tag due_date pinned_embed cherry_pick health_status approved unapproved
status alert_issue_added relate unrelate new_alert_added severity status alert_issue_added relate unrelate new_alert_added severity
attention_requested attention_request_removed attention_requested attention_request_removed contact
].freeze ].freeze
validates :note, presence: true, unless: :importing? validates :note, presence: true, unless: :importing?
......
...@@ -16,7 +16,7 @@ class NotePolicy < BasePolicy ...@@ -16,7 +16,7 @@ class NotePolicy < BasePolicy
condition(:for_design) { @subject.for_design? } condition(:for_design) { @subject.for_design? }
condition(:is_visible) { @subject.system_note_with_references_visible_for?(@user) } condition(:is_visible) { @subject.system_note_visible_for?(@user) }
condition(:confidential, scope: :subject) { @subject.confidential? } condition(:confidential, scope: :subject) { @subject.confidential? }
......
...@@ -16,6 +16,9 @@ module Issues ...@@ -16,6 +16,9 @@ module Issues
determine_changes if params[:replace_ids].present? determine_changes if params[:replace_ids].present?
return error_too_many if too_many? return error_too_many if too_many?
@added_count = 0
@removed_count = 0
add if params[:add_ids].present? add if params[:add_ids].present?
remove if params[:remove_ids].present? remove if params[:remove_ids].present?
...@@ -25,6 +28,7 @@ module Issues ...@@ -25,6 +28,7 @@ module Issues
if issue.valid? if issue.valid?
GraphqlTriggers.issue_crm_contacts_updated(issue) GraphqlTriggers.issue_crm_contacts_updated(issue)
issue.touch issue.touch
create_system_note
ServiceResponse.success(payload: issue) ServiceResponse.success(payload: issue)
else else
# The default error isn't very helpful: "Issue customer relations contacts is invalid" # The default error isn't very helpful: "Issue customer relations contacts is invalid"
...@@ -36,7 +40,7 @@ module Issues ...@@ -36,7 +40,7 @@ module Issues
private private
attr_accessor :issue, :errors, :existing_ids attr_accessor :issue, :errors, :existing_ids, :added_count, :removed_count
def determine_changes def determine_changes
params[:add_ids] = params[:replace_ids] - existing_ids params[:add_ids] = params[:replace_ids] - existing_ids
...@@ -63,7 +67,9 @@ module Issues ...@@ -63,7 +67,9 @@ module Issues
contact_ids.uniq.each do |contact_id| contact_ids.uniq.each do |contact_id|
issue_contact = issue.issue_customer_relations_contacts.create(contact_id: contact_id) issue_contact = issue.issue_customer_relations_contacts.create(contact_id: contact_id)
unless issue_contact.persisted? if issue_contact.persisted?
@added_count += 1
else
# The validation ensures that the id exists and the user has permission # The validation ensures that the id exists and the user has permission
errors << "#{contact_id}: The resource that you are attempting to access does not exist or you don't have permission to perform this action" errors << "#{contact_id}: The resource that you are attempting to access does not exist or you don't have permission to perform this action"
end end
...@@ -81,7 +87,7 @@ module Issues ...@@ -81,7 +87,7 @@ module Issues
def remove_by_id(contact_ids) def remove_by_id(contact_ids)
contact_ids &= existing_ids contact_ids &= existing_ids
issue.issue_customer_relations_contacts @removed_count += issue.issue_customer_relations_contacts
.where(contact_id: contact_ids) # rubocop: disable CodeReuse/ActiveRecord .where(contact_id: contact_ids) # rubocop: disable CodeReuse/ActiveRecord
.delete_all .delete_all
end end
...@@ -129,6 +135,11 @@ module Issues ...@@ -129,6 +135,11 @@ module Issues
params[:add_emails] && params[:add_emails].length > MAX_ADDITIONAL_CONTACTS params[:add_emails] && params[:add_emails].length > MAX_ADDITIONAL_CONTACTS
end end
def create_system_note
SystemNoteService.change_issuable_contacts(
issue, issue.project, current_user, added_count, removed_count)
end
def error_no_permissions def error_no_permissions
ServiceResponse.error(message: _('You have insufficient permissions to set customer relations contacts for this issue')) ServiceResponse.error(message: _('You have insufficient permissions to set customer relations contacts for this issue'))
end end
......
...@@ -45,6 +45,10 @@ module SystemNoteService ...@@ -45,6 +45,10 @@ module SystemNoteService
::SystemNotes::IssuablesService.new(noteable: issuable, project: project, author: author).change_issuable_reviewers(old_reviewers) ::SystemNotes::IssuablesService.new(noteable: issuable, project: project, author: author).change_issuable_reviewers(old_reviewers)
end end
def change_issuable_contacts(issuable, project, author, added_count, removed_count)
::SystemNotes::IssuablesService.new(noteable: issuable, project: project, author: author).change_issuable_contacts(added_count, removed_count)
end
def relate_issue(noteable, noteable_ref, user) def relate_issue(noteable, noteable_ref, user)
::SystemNotes::IssuablesService.new(noteable: noteable, project: noteable.project, author: user).relate_issue(noteable_ref) ::SystemNotes::IssuablesService.new(noteable: noteable, project: noteable.project, author: user).relate_issue(noteable_ref)
end end
......
...@@ -111,6 +111,35 @@ module SystemNotes ...@@ -111,6 +111,35 @@ module SystemNotes
create_note(NoteSummary.new(noteable, project, author, body, action: 'reviewer')) create_note(NoteSummary.new(noteable, project, author, body, action: 'reviewer'))
end end
# Called when the contacts of an issuable are changed or removed
# We intend to reference the contacts but for security we are just
# going to state how many were added/removed for now. See discussion:
# https://gitlab.com/gitlab-org/gitlab/-/merge_requests/77816#note_806114273
#
# added_count - number of contacts added, or 0
# removed_count - number of contacts removed, or 0
#
# Example Note text:
#
# "added 2 contacts"
#
# "added 3 contacts and removed one contact"
#
# Returns the created Note object
def change_issuable_contacts(added_count, removed_count)
text_parts = []
Gitlab::I18n.with_default_locale do
text_parts << "added #{added_count} #{'contact'.pluralize(added_count)}" if added_count > 0
text_parts << "removed #{removed_count} #{'contact'.pluralize(removed_count)}" if removed_count > 0
end
return if text_parts.empty?
body = text_parts.join(' and ')
create_note(NoteSummary.new(noteable, project, author, body, action: 'contact'))
end
# Called when the title of a Noteable is changed # Called when the title of a Noteable is changed
# #
# old_title - Previous String title # old_title - Previous String title
......
...@@ -68,8 +68,8 @@ module EE ...@@ -68,8 +68,8 @@ module EE
for_epic? ? noteable.group : super for_epic? ? noteable.group : super
end end
override :system_note_with_references_visible_for? override :system_note_visible_for?
def system_note_with_references_visible_for?(user) def system_note_visible_for?(user)
return false unless super return false unless super
return true unless system_note_for_epic? && created_before_noteable? return true unless system_note_for_epic? && created_before_noteable?
......
...@@ -42,12 +42,12 @@ RSpec.describe Note do ...@@ -42,12 +42,12 @@ RSpec.describe Note do
end end
it 'returns visible but not readable for a non-member user' do it 'returns visible but not readable for a non-member user' do
expect(note.system_note_with_references_visible_for?(non_member)).to be_truthy expect(note.system_note_visible_for?(non_member)).to be_truthy
expect(note.readable_by?(non_member)).to be_falsy expect(note.readable_by?(non_member)).to be_falsy
end end
it 'returns visible but not readable for a nil user' do it 'returns visible but not readable for a nil user' do
expect(note.system_note_with_references_visible_for?(nil)).to be_truthy expect(note.system_note_visible_for?(nil)).to be_truthy
expect(note.readable_by?(nil)).to be_falsy expect(note.readable_by?(nil)).to be_falsy
end end
end end
......
...@@ -252,7 +252,7 @@ module API ...@@ -252,7 +252,7 @@ module API
.fresh .fresh
# Without RendersActions#prepare_notes_for_rendering, # Without RendersActions#prepare_notes_for_rendering,
# Note#system_note_with_references_visible_for? will attempt to render # Note#system_note_visible_for? will attempt to render
# Markdown references mentioned in the note to see whether they # Markdown references mentioned in the note to see whether they
# should be redacted. For notes that reference a commit, this # should be redacted. For notes that reference a commit, this
# would also incur a Gitaly call to verify the commit exists. # would also incur a Gitaly call to verify the commit exists.
......
...@@ -445,7 +445,7 @@ RSpec.describe Note do ...@@ -445,7 +445,7 @@ RSpec.describe Note do
end end
end end
describe "#system_note_with_references_visible_for?" do describe "#system_note_visible_for?" do
let(:project) { create(:project, :public) } let(:project) { create(:project, :public) }
let(:user) { create(:user) } let(:user) { create(:user) }
let(:guest) { create(:project_member, :guest, project: project, user: create(:user)).user } let(:guest) { create(:project_member, :guest, project: project, user: create(:user)).user }
...@@ -469,22 +469,25 @@ RSpec.describe Note do ...@@ -469,22 +469,25 @@ RSpec.describe Note do
end end
it 'returns visible but not readable for non-member user' do it 'returns visible but not readable for non-member user' do
expect(note.system_note_with_references_visible_for?(non_member)).to be_truthy expect(note.system_note_visible_for?(non_member)).to be_truthy
expect(note.readable_by?(non_member)).to be_falsy expect(note.readable_by?(non_member)).to be_falsy
end end
it 'returns visible but not readable for a nil user' do it 'returns visible but not readable for a nil user' do
expect(note.system_note_with_references_visible_for?(nil)).to be_truthy expect(note.system_note_visible_for?(nil)).to be_truthy
expect(note.readable_by?(nil)).to be_falsy expect(note.readable_by?(nil)).to be_falsy
end end
end end
end end
describe "#system_note_viewable_by?(user)" do describe "#system_note_viewable_by?(user)" do
let_it_be(:note) { create(:note) } let_it_be(:group) { create(:group, :private) }
let_it_be(:project) { create(:project, group: group) }
let_it_be(:note) { create(:note, project: project) }
let_it_be(:user) { create(:user) } let_it_be(:user) { create(:user) }
let!(:metadata) { create(:system_note_metadata, note: note, action: "branch") } let(:action) { "commit" }
let!(:metadata) { create(:system_note_metadata, note: note, action: action) }
context "when system_note_metadata is not present" do context "when system_note_metadata is not present" do
it "returns true" do it "returns true" do
...@@ -494,32 +497,50 @@ RSpec.describe Note do ...@@ -494,32 +497,50 @@ RSpec.describe Note do
end end
end end
context "system_note_metadata isn't of type 'branch'" do context "system_note_metadata isn't of type 'branch' or 'contact'" do
before do
metadata.action = "not_a_branch"
end
it "returns true" do it "returns true" do
expect(note.send(:system_note_viewable_by?, user)).to be_truthy expect(note.send(:system_note_viewable_by?, user)).to be_truthy
end end
end end
context "user doesn't have :download_code ability" do context "system_note_metadata is of type 'branch'" do
it "returns false" do let(:action) { "branch" }
expect(note.send(:system_note_viewable_by?, user)).to be_falsey
context "user doesn't have :download_code ability" do
it "returns false" do
expect(note.send(:system_note_viewable_by?, user)).to be_falsey
end
end
context "user has the :download_code ability" do
it "returns true" do
expect(Ability).to receive(:allowed?).with(user, :download_code, note.project).and_return(true)
expect(note.send(:system_note_viewable_by?, user)).to be_truthy
end
end end
end end
context "user has the :download_code ability" do context "system_note_metadata is of type 'contact'" do
it "returns true" do let(:action) { "contact" }
expect(Ability).to receive(:allowed?).with(user, :download_code, note.project).and_return(true)
expect(note.send(:system_note_viewable_by?, user)).to be_truthy context "user doesn't have :read_crm_contact ability" do
it "returns false" do
expect(note.send(:system_note_viewable_by?, user)).to be_falsey
end
end
context "user has the :read_crm_contact ability" do
it "returns true" do
expect(Ability).to receive(:allowed?).with(user, :read_crm_contact, note.project.group).and_return(true)
expect(note.send(:system_note_viewable_by?, user)).to be_truthy
end
end end
end end
end end
describe "system_note_with_references_visible_for?" do describe "system_note_visible_for?" do
let_it_be(:private_user) { create(:user) } let_it_be(:private_user) { create(:user) }
let_it_be(:private_project) { create(:project, namespace: private_user.namespace) { |p| p.add_maintainer(private_user) } } let_it_be(:private_project) { create(:project, namespace: private_user.namespace) { |p| p.add_maintainer(private_user) } }
let_it_be(:private_issue) { create(:issue, project: private_project) } let_it_be(:private_issue) { create(:issue, project: private_project) }
...@@ -529,11 +550,11 @@ RSpec.describe Note do ...@@ -529,11 +550,11 @@ RSpec.describe Note do
shared_examples "checks references" do shared_examples "checks references" do
it "returns false" do it "returns false" do
expect(note.system_note_with_references_visible_for?(ext_issue.author)).to be_falsy expect(note.system_note_visible_for?(ext_issue.author)).to be_falsy
end end
it "returns true" do it "returns true" do
expect(note.system_note_with_references_visible_for?(private_user)).to be_truthy expect(note.system_note_visible_for?(private_user)).to be_truthy
end end
it "returns true if user visible reference count set" do it "returns true if user visible reference count set" do
...@@ -541,7 +562,7 @@ RSpec.describe Note do ...@@ -541,7 +562,7 @@ RSpec.describe Note do
note.total_reference_count = 1 note.total_reference_count = 1
expect(note).not_to receive(:reference_mentionables) expect(note).not_to receive(:reference_mentionables)
expect(note.system_note_with_references_visible_for?(ext_issue.author)).to be_truthy expect(note.system_note_visible_for?(ext_issue.author)).to be_truthy
end end
it "returns false if user visible reference count set but does not match total reference count" do it "returns false if user visible reference count set but does not match total reference count" do
...@@ -549,14 +570,14 @@ RSpec.describe Note do ...@@ -549,14 +570,14 @@ RSpec.describe Note do
note.total_reference_count = 2 note.total_reference_count = 2
expect(note).not_to receive(:reference_mentionables) expect(note).not_to receive(:reference_mentionables)
expect(note.system_note_with_references_visible_for?(ext_issue.author)).to be_falsy expect(note.system_note_visible_for?(ext_issue.author)).to be_falsy
end end
it "returns false if ref count is 0" do it "returns false if ref count is 0" do
note.user_visible_reference_count = 0 note.user_visible_reference_count = 0
expect(note).not_to receive(:reference_mentionables) expect(note).not_to receive(:reference_mentionables)
expect(note.system_note_with_references_visible_for?(ext_issue.author)).to be_falsy expect(note.system_note_visible_for?(ext_issue.author)).to be_falsy
end end
end end
...@@ -622,11 +643,11 @@ RSpec.describe Note do ...@@ -622,11 +643,11 @@ RSpec.describe Note do
end end
it "returns true for other users" do it "returns true for other users" do
expect(note.system_note_with_references_visible_for?(ext_issue.author)).to be_truthy expect(note.system_note_visible_for?(ext_issue.author)).to be_truthy
end end
it "returns true for anonymous users" do it "returns true for anonymous users" do
expect(note.system_note_with_references_visible_for?(nil)).to be_truthy expect(note.system_note_visible_for?(nil)).to be_truthy
end end
end end
end end
......
...@@ -32,6 +32,16 @@ RSpec.describe Issues::SetCrmContactsService do ...@@ -32,6 +32,16 @@ RSpec.describe Issues::SetCrmContactsService do
end end
end end
shared_examples 'adds system note' do |added_count, removed_count|
it 'calls SystemNoteService.change_issuable_contacts with correct counts' do
expect(SystemNoteService)
.to receive(:change_issuable_contacts)
.with(issue, project, user, added_count, removed_count)
set_crm_contacts
end
end
context 'when the user has no permission' do context 'when the user has no permission' do
let(:params) { { replace_ids: [contacts[1].id, contacts[2].id] } } let(:params) { { replace_ids: [contacts[1].id, contacts[2].id] } }
...@@ -81,6 +91,7 @@ RSpec.describe Issues::SetCrmContactsService do ...@@ -81,6 +91,7 @@ RSpec.describe Issues::SetCrmContactsService do
let(:expected_contacts) { [contacts[1], contacts[2]] } let(:expected_contacts) { [contacts[1], contacts[2]] }
it_behaves_like 'setting contacts' it_behaves_like 'setting contacts'
it_behaves_like 'adds system note', 1, 1
end end
context 'add' do context 'add' do
...@@ -89,6 +100,7 @@ RSpec.describe Issues::SetCrmContactsService do ...@@ -89,6 +100,7 @@ RSpec.describe Issues::SetCrmContactsService do
let(:expected_contacts) { [issue_contact_1, issue_contact_2, added_contact] } let(:expected_contacts) { [issue_contact_1, issue_contact_2, added_contact] }
it_behaves_like 'setting contacts' it_behaves_like 'setting contacts'
it_behaves_like 'adds system note', 1, 0
end end
context 'add by email' do context 'add by email' do
...@@ -99,12 +111,14 @@ RSpec.describe Issues::SetCrmContactsService do ...@@ -99,12 +111,14 @@ RSpec.describe Issues::SetCrmContactsService do
let(:params) { { add_emails: [contacts[3].email] } } let(:params) { { add_emails: [contacts[3].email] } }
it_behaves_like 'setting contacts' it_behaves_like 'setting contacts'
it_behaves_like 'adds system note', 1, 0
end end
context 'with autocomplete prefix emails in params' do context 'with autocomplete prefix emails in params' do
let(:params) { { add_emails: ["[\"contact:\"#{contacts[3].email}\"]"] } } let(:params) { { add_emails: ["[\"contact:\"#{contacts[3].email}\"]"] } }
it_behaves_like 'setting contacts' it_behaves_like 'setting contacts'
it_behaves_like 'adds system note', 1, 0
end end
end end
...@@ -113,6 +127,7 @@ RSpec.describe Issues::SetCrmContactsService do ...@@ -113,6 +127,7 @@ RSpec.describe Issues::SetCrmContactsService do
let(:expected_contacts) { [contacts[1]] } let(:expected_contacts) { [contacts[1]] }
it_behaves_like 'setting contacts' it_behaves_like 'setting contacts'
it_behaves_like 'adds system note', 0, 1
end end
context 'remove by email' do context 'remove by email' do
...@@ -122,12 +137,14 @@ RSpec.describe Issues::SetCrmContactsService do ...@@ -122,12 +137,14 @@ RSpec.describe Issues::SetCrmContactsService do
let(:params) { { remove_emails: [contacts[0].email] } } let(:params) { { remove_emails: [contacts[0].email] } }
it_behaves_like 'setting contacts' it_behaves_like 'setting contacts'
it_behaves_like 'adds system note', 0, 1
end end
context 'with autocomplete prefix and suffix email in params' do context 'with autocomplete prefix and suffix email in params' do
let(:params) { { remove_emails: ["[contact:#{contacts[0].email}]"] } } let(:params) { { remove_emails: ["[contact:#{contacts[0].email}]"] } }
it_behaves_like 'setting contacts' it_behaves_like 'setting contacts'
it_behaves_like 'adds system note', 0, 1
end end
end end
......
...@@ -77,6 +77,19 @@ RSpec.describe SystemNoteService do ...@@ -77,6 +77,19 @@ RSpec.describe SystemNoteService do
end end
end end
describe '.change_issuable_contacts' do
let(:added_count) { 5 }
let(:removed_count) { 3 }
it 'calls IssuableService' do
expect_next_instance_of(::SystemNotes::IssuablesService) do |service|
expect(service).to receive(:change_issuable_contacts).with(added_count, removed_count)
end
described_class.change_issuable_contacts(noteable, project, author, added_count, removed_count)
end
end
describe '.close_after_error_tracking_resolve' do describe '.close_after_error_tracking_resolve' do
it 'calls IssuableService' do it 'calls IssuableService' do
expect_next_instance_of(::SystemNotes::IssuablesService) do |service| expect_next_instance_of(::SystemNotes::IssuablesService) do |service|
......
...@@ -188,6 +188,54 @@ RSpec.describe ::SystemNotes::IssuablesService do ...@@ -188,6 +188,54 @@ RSpec.describe ::SystemNotes::IssuablesService do
end end
end end
describe '#change_issuable_contacts' do
subject { service.change_issuable_contacts(1, 1) }
let_it_be(:noteable) { create(:issue, project: project) }
it_behaves_like 'a system note' do
let(:action) { 'contact' }
end
def build_note(added_count, removed_count)
service.change_issuable_contacts(added_count, removed_count).note
end
it 'builds a correct phrase when one contact is added' do
expect(build_note(1, 0)).to eq "added 1 contact"
end
it 'builds a correct phrase when one contact is removed' do
expect(build_note(0, 1)).to eq "removed 1 contact"
end
it 'builds a correct phrase when one contact is added and one contact is removed' do
expect(build_note(1, 1)).to(
eq("added 1 contact and removed 1 contact")
)
end
it 'builds a correct phrase when three contacts are added and one removed' do
expect(build_note(3, 1)).to(
eq("added 3 contacts and removed 1 contact")
)
end
it 'builds a correct phrase when three contacts are removed and one added' do
expect(build_note(1, 3)).to(
eq("added 1 contact and removed 3 contacts")
)
end
it 'builds a correct phrase when the locale is different' do
Gitlab::I18n.with_locale('pt-BR') do
expect(build_note(1, 3)).to(
eq("added 1 contact and removed 3 contacts")
)
end
end
end
describe '#change_status' do describe '#change_status' do
subject { service.change_status(status, source) } subject { service.change_status(status, source) }
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
RSpec.shared_examples 'users with note access' do RSpec.shared_examples 'users with note access' do
it 'returns true' do it 'returns true' do
users.each do |user| users.each do |user|
expect(note.system_note_with_references_visible_for?(user)).to be_truthy expect(note.system_note_visible_for?(user)).to be_truthy
expect(note.readable_by?(user)).to be_truthy expect(note.readable_by?(user)).to be_truthy
end end
end end
...@@ -12,7 +12,7 @@ end ...@@ -12,7 +12,7 @@ end
RSpec.shared_examples 'users without note access' do RSpec.shared_examples 'users without note access' do
it 'returns false' do it 'returns false' do
users.each do |user| users.each do |user|
expect(note.system_note_with_references_visible_for?(user)).to be_falsy expect(note.system_note_visible_for?(user)).to be_falsy
expect(note.readable_by?(user)).to be_falsy expect(note.readable_by?(user)).to be_falsy
end end
end end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment