2019-10-12 21:52:04 +05:30
# frozen_string_literal: true
2014-09-02 18:07:02 +05:30
# Specifications for behavior common to all Mentionable implementations.
# Requires a shared context containing:
2015-09-11 14:41:01 +05:30
# - subject { "the mentionable implementation" }
2014-09-02 18:07:02 +05:30
# - 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-23 02:04:40 +05:30
shared_context 'mentionable context' do
2015-10-24 18:46:33 +05:30
let ( :project ) { subject . project }
2015-09-11 14:41:01 +05:30
let ( :author ) { subject . author }
2014-09-02 18:07:02 +05:30
2019-03-02 22:35:43 +05:30
let ( :mentioned_issue ) { create ( :issue , project : project ) }
let! ( :mentioned_mr ) { create ( :merge_request , source_project : project ) }
2015-12-23 02:04:40 +05:30
let ( :mentioned_commit ) { project . commit ( " HEAD~1 " ) }
2014-09-02 18:07:02 +05:30
2017-08-17 22:00:37 +05:30
let ( :ext_proj ) { create ( :project , :public , :repository ) }
2015-09-11 14:41:01 +05:30
let ( :ext_issue ) { create ( :issue , project : ext_proj ) }
let ( :ext_mr ) { create ( :merge_request , :simple , source_project : ext_proj ) }
2015-12-23 02:04:40 +05:30
let ( :ext_commit ) { ext_proj . commit ( " HEAD~2 " ) }
2015-04-26 12:48:37 +05:30
2014-09-02 18:07:02 +05:30
# 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-09-11 14:41:01 +05:30
<<-MSG.strip_heredoc
These references are new :
Issue : #{mentioned_issue.to_reference}
Merge : #{mentioned_mr.to_reference}
Commit : #{mentioned_commit.to_reference}
This reference is a repeat and should only be mentioned once :
Repeat : #{mentioned_issue.to_reference}
These references are cross - referenced :
Issue : #{ext_issue.to_reference(project)}
Merge : #{ext_mr.to_reference(project)}
Commit : #{ext_commit.to_reference(project)}
This is a self - reference and should not be mentioned at all :
Self : #{backref_text}
MSG
2014-09-02 18:07:02 +05:30
end
before do
2015-09-11 14:41:01 +05:30
# Wire the project's repository to return the mentioned commit, and +nil+
# for any unrecognized commits.
2015-12-23 02:04:40 +05:30
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-09-11 14:41:01 +05:30
2014-09-02 18:07:02 +05:30
set_mentionable_text . call ( ref_string )
2016-06-02 11:05:42 +05:30
2018-03-17 18:26:18 +05:30
project . add_developer ( author )
2014-09-02 18:07:02 +05:30
end
end
shared_examples 'a mentionable' do
2015-12-23 02:04:40 +05:30
include_context 'mentionable context'
2014-09-02 18:07:02 +05:30
it 'generates a descriptive back-reference' do
2015-04-26 12:48:37 +05:30
expect ( subject . gfm_reference ) . to eq ( backref_text )
2014-09-02 18:07:02 +05:30
end
it " extracts references from its reference property " do
# De-duplicate and omit itself
2015-10-24 18:46:33 +05:30
refs = subject . referenced_mentionables
2015-04-26 12:48:37 +05:30
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 )
2014-09-02 18:07:02 +05:30
end
2019-09-30 21:07:59 +05:30
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
2014-09-02 18:07:02 +05:30
it 'creates cross-reference notes' do
2015-04-26 12:48:37 +05:30
mentioned_objects = [ mentioned_issue , mentioned_mr , mentioned_commit ,
ext_issue , ext_mr , ext_commit ]
mentioned_objects . each do | referenced |
2017-09-10 17:25:29 +05:30
expect ( SystemNoteService ) . to receive ( :cross_reference )
. with ( referenced , subject . local_reference , author )
2014-09-02 18:07:02 +05:30
end
2015-10-24 18:46:33 +05:30
subject . create_cross_references!
2014-09-02 18:07:02 +05:30
end
end
shared_examples 'an editable mentionable' do
2015-12-23 02:04:40 +05:30
include_context 'mentionable context'
2014-09-02 18:07:02 +05:30
it_behaves_like 'a mentionable'
2015-09-11 14:41:01 +05:30
let ( :new_issues ) do
[ create ( :issue , project : project ) , create ( :issue , project : ext_proj ) ]
end
2019-09-30 21:07:59 +05:30
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
2014-09-02 18:07:02 +05:30
it 'creates new cross-reference notes when the mentionable text is edited' do
2015-09-11 14:41:01 +05:30
subject . save
2016-11-03 12:29:30 +05:30
subject . create_cross_references!
2015-09-11 14:41:01 +05:30
new_text = <<-MSG.strip_heredoc
These references already existed :
Issue : #{mentioned_issue.to_reference}
Commit : #{mentioned_commit.to_reference}
2015-04-26 12:48:37 +05:30
2015-09-11 14:41:01 +05:30
- - -
This cross - project reference already existed :
Issue : #{ext_issue.to_reference(project)}
- - -
These two references are introduced in an edit :
Issue : #{new_issues[0].to_reference}
Cross : #{new_issues[1].to_reference(project)}
MSG
# These three objects were already referenced, and should not receive new
# notes
2015-04-26 12:48:37 +05:30
[ mentioned_issue , mentioned_commit , ext_issue ] . each do | oldref |
2017-09-10 17:25:29 +05:30
expect ( SystemNoteService ) . not_to receive ( :cross_reference )
. with ( oldref , any_args )
2014-09-02 18:07:02 +05:30
end
2015-09-11 14:41:01 +05:30
# These two issues are new and should receive reference notes
2016-11-03 12:29:30 +05:30
# In the case of MergeRequests remember that cannot mention commits included in the MergeRequest
2015-09-11 14:41:01 +05:30
new_issues . each do | newref |
2017-09-10 17:25:29 +05:30
expect ( SystemNoteService ) . to receive ( :cross_reference )
. with ( newref , subject . local_reference , author )
2015-04-26 12:48:37 +05:30
end
2014-09-02 18:07:02 +05:30
set_mentionable_text . call ( new_text )
2015-10-24 18:46:33 +05:30
subject . create_new_cross_references! ( author )
2014-09-02 18:07:02 +05:30
end
end
2020-01-01 13:55:28 +05:30
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