2019-07-25 01:27:42 -04:00
# frozen_string_literal: true
2013-05-30 19:16:49 -04:00
# Specifications for behavior common to all Mentionable implementations.
# Requires a shared context containing:
2015-04-16 16:25:25 -04:00
# - subject { "the mentionable implementation" }
2013-05-30 19:16:49 -04:00
# - let(:backref_text) { "the way that +subject+ should refer to itself in backreferences " }
# - let(:set_mentionable_text) { lambda { |txt| "block that assigns txt to the subject's mentionable_text" } }
2015-12-04 07:07:42 -05:00
shared_context 'mentionable context' do
2015-10-12 10:23:15 -04:00
let ( :project ) { subject . project }
2015-04-16 16:26:28 -04:00
let ( :author ) { subject . author }
2019-01-16 07:09:29 -05:00
let ( :mentioned_issue ) { create ( :issue , project : project ) }
let! ( :mentioned_mr ) { create ( :merge_request , source_project : project ) }
2015-11-30 16:43:54 -05:00
let ( :mentioned_commit ) { project . commit ( " HEAD~1 " ) }
2015-04-16 16:26:28 -04:00
2017-01-26 17:44:58 -05:00
let ( :ext_proj ) { create ( :project , :public , :repository ) }
2015-04-16 16:26:28 -04:00
let ( :ext_issue ) { create ( :issue , project : ext_proj ) }
let ( :ext_mr ) { create ( :merge_request , :simple , source_project : ext_proj ) }
2015-11-30 16:43:54 -05:00
let ( :ext_commit ) { ext_proj . commit ( " HEAD~2 " ) }
2014-10-03 01:48:35 -04:00
2013-05-30 19:16:49 -04:00
# Override to add known commits to the repository stub.
let ( :extra_commits ) { [ ] }
# A string that mentions each of the +mentioned_.*+ objects above. Mentionables should add a self-reference
# to this string and place it in their +mentionable_text+.
let ( :ref_string ) do
2015-04-16 16:26:28 -04:00
<<-MSG.strip_heredoc
These references are new :
2015-05-02 23:44:46 -04:00
Issue : #{mentioned_issue.to_reference}
Merge : #{mentioned_mr.to_reference}
Commit : #{mentioned_commit.to_reference}
2015-04-16 16:26:28 -04:00
This reference is a repeat and should only be mentioned once :
2015-05-02 23:44:46 -04:00
Repeat : #{mentioned_issue.to_reference}
2015-04-16 16:26:28 -04:00
These references are cross - referenced :
2015-05-02 23:44:46 -04:00
Issue : #{ext_issue.to_reference(project)}
Merge : #{ext_mr.to_reference(project)}
Commit : #{ext_commit.to_reference(project)}
2015-04-16 16:26:28 -04:00
This is a self - reference and should not be mentioned at all :
Self : #{backref_text}
MSG
2013-05-30 19:16:49 -04:00
end
before do
2015-04-16 16:26:28 -04:00
# Wire the project's repository to return the mentioned commit, and +nil+
# for any unrecognized commits.
2015-11-30 16:43:54 -05:00
allow_any_instance_of ( :: Repository ) . to receive ( :commit ) . and_call_original
allow_any_instance_of ( :: Repository ) . to receive ( :commit ) . with ( mentioned_commit . short_id ) . and_return ( mentioned_commit )
extra_commits . each do | commit |
allow_any_instance_of ( :: Repository ) . to receive ( :commit ) . with ( commit . short_id ) . and_return ( commit )
end
2015-04-15 16:13:56 -04:00
2013-05-30 19:16:49 -04:00
set_mentionable_text . call ( ref_string )
2016-03-17 16:39:50 -04:00
2017-12-22 03:18:28 -05:00
project . add_developer ( author )
2013-05-30 19:16:49 -04:00
end
end
shared_examples 'a mentionable' do
2015-12-04 07:07:42 -05:00
include_context 'mentionable context'
2013-05-30 19:16:49 -04:00
it 'generates a descriptive back-reference' do
2015-02-12 13:17:35 -05:00
expect ( subject . gfm_reference ) . to eq ( backref_text )
2013-05-30 19:16:49 -04:00
end
it " extracts references from its reference property " do
# De-duplicate and omit itself
2015-10-12 05:54:46 -04:00
refs = subject . referenced_mentionables
2015-02-12 13:17:35 -05:00
expect ( refs . size ) . to eq ( 6 )
expect ( refs ) . to include ( mentioned_issue )
expect ( refs ) . to include ( mentioned_mr )
expect ( refs ) . to include ( mentioned_commit )
expect ( refs ) . to include ( ext_issue )
expect ( refs ) . to include ( ext_mr )
expect ( refs ) . to include ( ext_commit )
2013-05-30 19:16:49 -04:00
end
2019-07-03 19:12:02 -04:00
context 'when there are cached markdown fields' do
before do
if subject . is_a? ( CacheMarkdownField )
subject . refresh_markdown_cache
end
end
it 'sends in cached markdown fields when appropriate' do
if subject . is_a? ( CacheMarkdownField )
expect_next_instance_of ( Gitlab :: ReferenceExtractor ) do | ext |
attrs = subject . class . mentionable_attrs . collect ( & :first ) & subject . cached_markdown_fields . markdown_fields
attrs . each do | field |
expect ( ext ) . to receive ( :analyze ) . with ( subject . send ( field ) , hash_including ( rendered : anything ) )
end
end
expect ( subject ) . not_to receive ( :refresh_markdown_cache )
expect ( subject ) . to receive ( :cached_markdown_fields ) . at_least ( :once ) . and_call_original
subject . all_references ( author )
end
end
end
2013-05-30 19:16:49 -04:00
it 'creates cross-reference notes' do
2014-10-03 01:48:35 -04:00
mentioned_objects = [ mentioned_issue , mentioned_mr , mentioned_commit ,
ext_issue , ext_mr , ext_commit ]
mentioned_objects . each do | referenced |
2017-06-21 09:48:12 -04:00
expect ( SystemNoteService ) . to receive ( :cross_reference )
. with ( referenced , subject . local_reference , author )
2013-05-30 19:16:49 -04:00
end
2015-10-12 05:54:46 -04:00
subject . create_cross_references!
2013-05-30 19:16:49 -04:00
end
end
shared_examples 'an editable mentionable' do
2015-12-04 07:07:42 -05:00
include_context 'mentionable context'
2013-05-30 19:16:49 -04:00
it_behaves_like 'a mentionable'
2015-04-16 16:26:28 -04:00
let ( :new_issues ) do
[ create ( :issue , project : project ) , create ( :issue , project : ext_proj ) ]
end
2019-07-03 19:12:02 -04:00
context 'when there are cached markdown fields' do
before do
if subject . is_a? ( CacheMarkdownField )
subject . refresh_markdown_cache
end
end
it 'refreshes markdown cache if necessary' do
subject . save!
set_mentionable_text . call ( 'This is a text' )
if subject . is_a? ( CacheMarkdownField )
expect_next_instance_of ( Gitlab :: ReferenceExtractor ) do | ext |
subject . cached_markdown_fields . markdown_fields . each do | field |
expect ( ext ) . to receive ( :analyze ) . with ( subject . send ( field ) , hash_including ( rendered : anything ) )
end
end
expect ( subject ) . to receive ( :refresh_markdown_cache )
expect ( subject ) . to receive ( :cached_markdown_fields ) . at_least ( :once ) . and_call_original
subject . all_references ( author )
end
end
end
2013-05-30 19:16:49 -04:00
it 'creates new cross-reference notes when the mentionable text is edited' do
2015-04-23 12:46:52 -04:00
subject . save
2016-09-29 10:28:45 -04:00
subject . create_cross_references!
2015-04-23 12:46:52 -04:00
2015-06-02 09:00:51 -04:00
new_text = <<-MSG.strip_heredoc
2015-04-16 16:26:28 -04:00
These references already existed :
2015-06-02 09:00:51 -04:00
Issue : #{mentioned_issue.to_reference}
Commit : #{mentioned_commit.to_reference}
- - -
2015-04-16 16:26:28 -04:00
This cross - project reference already existed :
2015-06-02 09:00:51 -04:00
Issue : #{ext_issue.to_reference(project)}
- - -
2015-04-16 16:26:28 -04:00
These two references are introduced in an edit :
2015-06-02 09:00:51 -04:00
Issue : #{new_issues[0].to_reference}
Cross : #{new_issues[1].to_reference(project)}
2015-04-16 16:26:28 -04:00
MSG
2013-05-30 19:16:49 -04:00
2015-04-16 16:26:28 -04:00
# These three objects were already referenced, and should not receive new
# notes
2014-10-03 01:48:35 -04:00
[ mentioned_issue , mentioned_commit , ext_issue ] . each do | oldref |
2017-06-21 09:48:12 -04:00
expect ( SystemNoteService ) . not_to receive ( :cross_reference )
. with ( oldref , any_args )
2013-05-30 19:16:49 -04:00
end
2015-04-16 16:26:28 -04:00
# These two issues are new and should receive reference notes
2016-09-29 10:28:45 -04:00
# In the case of MergeRequests remember that cannot mention commits included in the MergeRequest
2015-04-16 16:26:28 -04:00
new_issues . each do | newref |
2017-06-21 09:48:12 -04:00
expect ( SystemNoteService ) . to receive ( :cross_reference )
. with ( newref , subject . local_reference , author )
2014-10-03 01:48:35 -04:00
end
2013-05-30 19:16:49 -04:00
set_mentionable_text . call ( new_text )
2015-10-12 05:54:46 -04:00
subject . create_new_cross_references! ( author )
2013-05-30 19:16:49 -04:00
end
end
2019-12-10 10:07:52 -05:00
shared_examples_for 'mentions in description' do | mentionable_type |
describe 'when store_mentioned_users_to_db feature disabled' do
before do
stub_feature_flags ( store_mentioned_users_to_db : false )
mentionable . store_mentions!
end
context 'when mentionable description contains mentions' do
let ( :user ) { create ( :user ) }
let ( :mentionable ) { create ( mentionable_type , description : " #{ user . to_reference } some description " ) }
it 'stores no mentions' do
expect ( mentionable . user_mentions . count ) . to eq 0
end
end
end
describe 'when store_mentioned_users_to_db feature enabled' do
before do
stub_feature_flags ( store_mentioned_users_to_db : true )
mentionable . store_mentions!
end
context 'when mentionable description has no mentions' do
let ( :mentionable ) { create ( mentionable_type , description : " just some description " ) }
it 'stores no mentions' do
expect ( mentionable . user_mentions . count ) . to eq 0
end
end
context 'when mentionable description contains mentions' do
let ( :user ) { create ( :user ) }
let ( :group ) { create ( :group ) }
let ( :mentionable_desc ) { " #{ user . to_reference } some description #{ group . to_reference ( full : true ) } and @all " }
let ( :mentionable ) { create ( mentionable_type , description : mentionable_desc ) }
it 'stores mentions' do
add_member ( user )
expect ( mentionable . user_mentions . count ) . to eq 1
expect ( mentionable . referenced_users ) . to match_array ( [ user ] )
expect ( mentionable . referenced_projects ( user ) ) . to match_array ( [ mentionable . project ] . compact ) # epic.project is nil, and we want empty []
expect ( mentionable . referenced_groups ( user ) ) . to match_array ( [ group ] )
end
end
end
end
shared_examples_for 'mentions in notes' do | mentionable_type |
context 'when mentionable notes contain mentions' do
let ( :user ) { create ( :user ) }
let ( :group ) { create ( :group ) }
let ( :note_desc ) { " #{ user . to_reference } and #{ group . to_reference ( full : true ) } and @all " }
let! ( :mentionable ) { note . noteable }
before do
note . update ( note : note_desc )
note . store_mentions!
add_member ( user )
end
it 'returns all mentionable mentions' do
expect ( mentionable . user_mentions . count ) . to eq 1
expect ( mentionable . referenced_users ) . to eq [ user ]
expect ( mentionable . referenced_projects ( user ) ) . to eq [ mentionable . project ] . compact # epic.project is nil, and we want empty []
expect ( mentionable . referenced_groups ( user ) ) . to eq [ group ]
end
end
end
shared_examples_for 'load mentions from DB' do | mentionable_type |
context 'load stored mentions' do
let_it_be ( :user ) { create ( :user ) }
let_it_be ( :mentioned_user ) { create ( :user ) }
let_it_be ( :group ) { create ( :group ) }
let_it_be ( :note_desc ) { " #{ mentioned_user . to_reference } and #{ group . to_reference ( full : true ) } and @all " }
before do
note . update ( note : note_desc )
note . store_mentions!
add_member ( user )
end
context 'when stored user mention contains ids of inexistent records' do
before do
user_mention = note . send ( :model_user_mention )
mention_ids = {
mentioned_users_ids : user_mention . mentioned_users_ids . to_a << User . maximum ( :id ) . to_i . succ ,
mentioned_projects_ids : user_mention . mentioned_projects_ids . to_a << Project . maximum ( :id ) . to_i . succ ,
mentioned_groups_ids : user_mention . mentioned_groups_ids . to_a << Group . maximum ( :id ) . to_i . succ
}
user_mention . update ( mention_ids )
end
it 'filters out inexistent mentions' do
expect ( mentionable . referenced_users ) . to match_array ( [ mentioned_user ] )
expect ( mentionable . referenced_projects ( user ) ) . to match_array ( [ mentionable . project ] . compact ) # epic.project is nil, and we want empty []
expect ( mentionable . referenced_groups ( user ) ) . to match_array ( [ group ] )
end
end
context 'when private projects and groups are mentioned' do
let ( :mega_user ) { create ( :user ) }
let ( :private_project ) { create ( :project , :private ) }
let ( :project_member ) { create ( :project_member , user : create ( :user ) , project : private_project ) }
let ( :private_group ) { create ( :group , :private ) }
let ( :group_member ) { create ( :group_member , user : create ( :user ) , group : private_group ) }
before do
user_mention = note . send ( :model_user_mention )
mention_ids = {
mentioned_projects_ids : user_mention . mentioned_projects_ids . to_a << private_project . id ,
mentioned_groups_ids : user_mention . mentioned_groups_ids . to_a << private_group . id
}
user_mention . update ( mention_ids )
add_member ( mega_user )
private_project . add_developer ( mega_user )
private_group . add_developer ( mega_user )
end
context 'when user has no access to some mentions' do
it 'filters out inaccessible mentions' do
expect ( mentionable . referenced_projects ( user ) ) . to match_array ( [ mentionable . project ] . compact ) # epic.project is nil, and we want empty []
expect ( mentionable . referenced_groups ( user ) ) . to match_array ( [ group ] )
end
end
context 'when user has access to all mentions' do
it 'returns all mentions' do
expect ( mentionable . referenced_projects ( mega_user ) ) . to match_array ( [ mentionable . project , private_project ] . compact ) # epic.project is nil, and we want empty []
expect ( mentionable . referenced_groups ( mega_user ) ) . to match_array ( [ group , private_group ] )
end
end
end
end
end
def add_member ( user )
issuable_parent = if mentionable . is_a? ( Epic )
mentionable . group
else
mentionable . project
end
issuable_parent & . add_developer ( user )
end