diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-05-19 18:44:42 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-05-19 18:44:42 +0300 |
commit | 4555e1b21c365ed8303ffb7a3325d773c9b8bf31 (patch) | |
tree | 5423a1c7516cffe36384133ade12572cf709398d /spec/lib/banzai | |
parent | e570267f2f6b326480d284e0164a6464ba4081bc (diff) |
Add latest changes from gitlab-org/gitlab@13-12-stable-eev13.12.0-rc42
Diffstat (limited to 'spec/lib/banzai')
13 files changed, 348 insertions, 118 deletions
diff --git a/spec/lib/banzai/cross_project_reference_spec.rb b/spec/lib/banzai/cross_project_reference_spec.rb index 95b78ceb5d5..60ff15a88e0 100644 --- a/spec/lib/banzai/cross_project_reference_spec.rb +++ b/spec/lib/banzai/cross_project_reference_spec.rb @@ -4,10 +4,12 @@ require 'spec_helper' RSpec.describe Banzai::CrossProjectReference do let(:including_class) { Class.new.include(described_class).new } + let(:reference_cache) { Banzai::Filter::References::ReferenceCache.new(including_class, {})} before do allow(including_class).to receive(:context).and_return({}) allow(including_class).to receive(:parent_from_ref).and_call_original + allow(including_class).to receive(:reference_cache).and_return(reference_cache) end describe '#parent_from_ref' do @@ -47,5 +49,18 @@ RSpec.describe Banzai::CrossProjectReference do expect(including_class.parent_from_ref('cross/reference')).to eq project2 end end + + context 'when reference cache is loaded' do + let(:project2) { double('referenced project') } + + before do + allow(reference_cache).to receive(:cache_loaded?).and_return(true) + allow(reference_cache).to receive(:parent_per_reference).and_return({ 'cross/reference' => project2 }) + end + + it 'pulls from the reference cache' do + expect(including_class.parent_from_ref('cross/reference')).to eq project2 + end + end end end diff --git a/spec/lib/banzai/filter/custom_emoji_filter_spec.rb b/spec/lib/banzai/filter/custom_emoji_filter_spec.rb index 5e76e8164dd..94e77663d0f 100644 --- a/spec/lib/banzai/filter/custom_emoji_filter_spec.rb +++ b/spec/lib/banzai/filter/custom_emoji_filter_spec.rb @@ -53,7 +53,7 @@ RSpec.describe Banzai::Filter::CustomEmojiFilter do end expect do - filter('<p>:tanuki: :party-parrot:</p>') + filter('<p>:tanuki:</p> <p>:party-parrot:</p>') end.not_to exceed_all_query_limit(control_count.count) end end diff --git a/spec/lib/banzai/filter/references/abstract_reference_filter_spec.rb b/spec/lib/banzai/filter/references/abstract_reference_filter_spec.rb index 076c112ac87..3cb3ebc42a6 100644 --- a/spec/lib/banzai/filter/references/abstract_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/references/abstract_reference_filter_spec.rb @@ -8,18 +8,6 @@ RSpec.describe Banzai::Filter::References::AbstractReferenceFilter do let(:doc) { Nokogiri::HTML.fragment('') } let(:filter) { described_class.new(doc, project: project) } - describe '#references_per_parent' do - let(:doc) { Nokogiri::HTML.fragment("#1 #{project.full_path}#2 #2") } - - it 'returns a Hash containing references grouped per parent paths' do - expect(described_class).to receive(:object_class).exactly(6).times.and_return(Issue) - - refs = filter.references_per_parent - - expect(refs).to match(a_hash_including(project.full_path => contain_exactly(1, 2))) - end - end - describe '#data_attributes_for' do let_it_be(:issue) { create(:issue, project: project) } @@ -32,71 +20,17 @@ RSpec.describe Banzai::Filter::References::AbstractReferenceFilter do end end - describe '#parent_per_reference' do - it 'returns a Hash containing projects grouped per parent paths' do - expect(filter).to receive(:references_per_parent) - .and_return({ project.full_path => Set.new([1]) }) - - expect(filter.parent_per_reference) - .to eq({ project.full_path => project }) - end - end - - describe '#find_for_paths' do - context 'with RequestStore disabled' do - it 'returns a list of Projects for a list of paths' do - expect(filter.find_for_paths([project.full_path])) - .to eq([project]) - end - - it "return an empty array for paths that don't exist" do - expect(filter.find_for_paths(['nonexistent/project'])) - .to eq([]) + context 'abstract methods' do + describe '#find_object' do + it 'raises NotImplementedError' do + expect { filter.find_object(nil, nil) }.to raise_error(NotImplementedError) end end - context 'with RequestStore enabled', :request_store do - it 'returns a list of Projects for a list of paths' do - expect(filter.find_for_paths([project.full_path])) - .to eq([project]) + describe '#url_for_object' do + it 'raises NotImplementedError' do + expect { filter.url_for_object(nil, nil) }.to raise_error(NotImplementedError) end - - context "when no project with that path exists" do - it "returns no value" do - expect(filter.find_for_paths(['nonexistent/project'])) - .to eq([]) - end - - it "adds the ref to the project refs cache" do - project_refs_cache = {} - allow(filter).to receive(:refs_cache).and_return(project_refs_cache) - - filter.find_for_paths(['nonexistent/project']) - - expect(project_refs_cache).to eq({ 'nonexistent/project' => nil }) - end - - context 'when the project refs cache includes nil values' do - before do - # adds { 'nonexistent/project' => nil } to cache - filter.from_ref_cached('nonexistent/project') - end - - it "return an empty array for paths that don't exist" do - expect(filter.find_for_paths(['nonexistent/project'])) - .to eq([]) - end - end - end - end - end - - describe '#current_parent_path' do - it 'returns the path of the current parent' do - doc = Nokogiri::HTML.fragment('') - filter = described_class.new(doc, project: project) - - expect(filter.current_parent_path).to eq(project.full_path) end end end diff --git a/spec/lib/banzai/filter/references/design_reference_filter_spec.rb b/spec/lib/banzai/filter/references/design_reference_filter_spec.rb index 52514ad17fc..cb1f3d520a4 100644 --- a/spec/lib/banzai/filter/references/design_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/references/design_reference_filter_spec.rb @@ -104,7 +104,7 @@ RSpec.describe Banzai::Filter::References::DesignReferenceFilter do let(:pattern) { described_class.object_class.link_reference_pattern } let(:parsed) do m = pattern.match(url) - described_class.identifier(m) if m + described_class.new('', project: nil).identifier(m) if m end it 'can parse the reference' do @@ -119,9 +119,11 @@ RSpec.describe Banzai::Filter::References::DesignReferenceFilter do describe 'static properties' do specify do expect(described_class).to have_attributes( - object_sym: :design, + reference_type: :design, object_class: ::DesignManagement::Design ) + + expect(described_class.new('', project: nil).object_sym).to eq :design end end diff --git a/spec/lib/banzai/filter/references/issue_reference_filter_spec.rb b/spec/lib/banzai/filter/references/issue_reference_filter_spec.rb index b849355f6db..88c2494b243 100644 --- a/spec/lib/banzai/filter/references/issue_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/references/issue_reference_filter_spec.rb @@ -470,42 +470,24 @@ RSpec.describe Banzai::Filter::References::IssueReferenceFilter do end end - describe '#records_per_parent' do - context 'using an internal issue tracker' do - it 'returns a Hash containing the issues per project' do - doc = Nokogiri::HTML.fragment('') - filter = described_class.new(doc, project: project) - - expect(filter).to receive(:parent_per_reference) - .and_return({ project.full_path => project }) - - expect(filter).to receive(:references_per_parent) - .and_return({ project.full_path => Set.new([issue.iid]) }) - - expect(filter.records_per_parent) - .to eq({ project => { issue.iid => issue } }) - end - end - end - describe '.references_in' do let(:merge_request) { create(:merge_request) } it 'yields valid references' do expect do |b| - described_class.references_in(issue.to_reference, &b) + described_class.new('', project: nil).references_in(issue.to_reference, &b) end.to yield_with_args(issue.to_reference, issue.iid, nil, nil, MatchData) end it "doesn't yield invalid references" do expect do |b| - described_class.references_in('#0', &b) + described_class.new('', project: nil).references_in('#0', &b) end.not_to yield_control end it "doesn't yield unsupported references" do expect do |b| - described_class.references_in(merge_request.to_reference, &b) + described_class.new('', project: nil).references_in(merge_request.to_reference, &b) end.not_to yield_control end end diff --git a/spec/lib/banzai/filter/references/merge_request_reference_filter_spec.rb b/spec/lib/banzai/filter/references/merge_request_reference_filter_spec.rb index 7a634b0b513..ee2ce967a47 100644 --- a/spec/lib/banzai/filter/references/merge_request_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/references/merge_request_reference_filter_spec.rb @@ -142,6 +142,17 @@ RSpec.describe Banzai::Filter::References::MergeRequestReferenceFilter do expect(doc.text).to eq("Merge (#{reference}.)") end + it 'has correct data attributes' do + doc = reference_filter("Merge (#{reference}.)") + + link = doc.css('a').first + + expect(link.attr('data-project')).to eq project2.id.to_s + expect(link.attr('data-project-path')).to eq project2.full_path + expect(link.attr('data-iid')).to eq merge.iid.to_s + expect(link.attr('data-mr-title')).to eq merge.title + end + it 'ignores invalid merge IDs on the referenced project' do exp = act = "Merge #{invalidate_reference(reference)}" diff --git a/spec/lib/banzai/filter/references/project_reference_filter_spec.rb b/spec/lib/banzai/filter/references/project_reference_filter_spec.rb index 7a77d57cd42..63a5a9184c1 100644 --- a/spec/lib/banzai/filter/references/project_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/references/project_reference_filter_spec.rb @@ -85,7 +85,7 @@ RSpec.describe Banzai::Filter::References::ProjectReferenceFilter do document = Nokogiri::HTML.fragment("<p>#{get_reference(project)}</p>") filter = described_class.new(document, project: project) - expect(filter.projects_hash).to eq({ project.full_path => project }) + expect(filter.send(:projects_hash)).to eq({ project.full_path => project }) end end @@ -94,7 +94,7 @@ RSpec.describe Banzai::Filter::References::ProjectReferenceFilter do document = Nokogiri::HTML.fragment("<p>#{get_reference(project)}</p>") filter = described_class.new(document, project: project) - expect(filter.projects).to eq([project.full_path]) + expect(filter.send(:projects)).to eq([project.full_path]) end end end diff --git a/spec/lib/banzai/filter/references/reference_cache_spec.rb b/spec/lib/banzai/filter/references/reference_cache_spec.rb new file mode 100644 index 00000000000..9e2a6f35910 --- /dev/null +++ b/spec/lib/banzai/filter/references/reference_cache_spec.rb @@ -0,0 +1,144 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Banzai::Filter::References::ReferenceCache do + let_it_be(:project) { create(:project) } + let_it_be(:project2) { create(:project) } + let_it_be(:issue1) { create(:issue, project: project) } + let_it_be(:issue2) { create(:issue, project: project) } + let_it_be(:issue3) { create(:issue, project: project2) } + let_it_be(:doc) { Nokogiri::HTML.fragment("#{issue1.to_reference} #{issue2.to_reference} #{issue3.to_reference(full: true)}") } + + let(:filter_class) { Banzai::Filter::References::IssueReferenceFilter } + let(:filter) { filter_class.new(doc, project: project) } + let(:cache) { described_class.new(filter, { project: project }) } + + describe '#load_references_per_parent' do + it 'loads references grouped per parent paths' do + cache.load_references_per_parent(filter.nodes) + + expect(cache.references_per_parent).to eq({ project.full_path => [issue1.iid, issue2.iid].to_set, + project2.full_path => [issue3.iid].to_set }) + end + end + + describe '#load_parent_per_reference' do + it 'returns a Hash containing projects grouped per parent paths' do + cache.load_references_per_parent(filter.nodes) + cache.load_parent_per_reference + + expect(cache.parent_per_reference).to match({ project.full_path => project, project2.full_path => project2 }) + end + end + + describe '#load_records_per_parent' do + it 'returns a Hash containing projects grouped per parent paths' do + cache.load_references_per_parent(filter.nodes) + cache.load_parent_per_reference + cache.load_records_per_parent + + expect(cache.records_per_parent).to match({ project => { issue1.iid => issue1, issue2.iid => issue2 }, + project2 => { issue3.iid => issue3 } }) + end + end + + describe '#initialize_reference_cache' do + it 'does not have an N+1 query problem with cross projects' do + doc_single = Nokogiri::HTML.fragment("#1") + filter_single = filter_class.new(doc_single, project: project) + cache_single = described_class.new(filter_single, { project: project }) + + control_count = ActiveRecord::QueryRecorder.new do + cache_single.load_references_per_parent(filter_single.nodes) + cache_single.load_parent_per_reference + cache_single.load_records_per_parent + end.count + + # Since this is an issue filter that is not batching issue queries + # across projects, we have to account for that. + # 1 for both projects, 1 for issues in each project == 3 + # TODO: https://gitlab.com/gitlab-org/gitlab/-/issues/330359 + max_count = control_count + 1 + + expect do + cache.load_references_per_parent(filter.nodes) + cache.load_parent_per_reference + cache.load_records_per_parent + end.not_to exceed_query_limit(max_count) + end + end + + describe '#find_for_paths' do + context 'with RequestStore disabled' do + it 'returns a list of Projects for a list of paths' do + expect(cache.find_for_paths([project.full_path])) + .to eq([project]) + end + + it 'return an empty array for paths that do not exist' do + expect(cache.find_for_paths(['nonexistent/project'])) + .to eq([]) + end + end + + context 'with RequestStore enabled', :request_store do + it 'returns a list of Projects for a list of paths' do + expect(cache.find_for_paths([project.full_path])) + .to eq([project]) + end + + context 'when no project with that path exists' do + it 'returns no value' do + expect(cache.find_for_paths(['nonexistent/project'])) + .to eq([]) + end + + it 'adds the ref to the project refs cache' do + project_refs_cache = {} + allow(cache).to receive(:refs_cache).and_return(project_refs_cache) + + cache.find_for_paths(['nonexistent/project']) + + expect(project_refs_cache).to eq({ 'nonexistent/project' => nil }) + end + end + end + end + + describe '#current_parent_path' do + it 'returns the path of the current parent' do + expect(cache.current_parent_path).to eq project.full_path + end + end + + describe '#current_project_namespace_path' do + it 'returns the path of the current project namespace' do + expect(cache.current_project_namespace_path).to eq project.namespace.full_path + end + end + + describe '#full_project_path' do + it 'returns current parent path when no ref specified' do + expect(cache.full_project_path('something', nil)).to eq cache.current_parent_path + end + + it 'returns combined namespace and project ref' do + expect(cache.full_project_path('something', 'cool')).to eq 'something/cool' + end + + it 'returns uses default namespace and project ref when namespace nil' do + expect(cache.full_project_path(nil, 'cool')).to eq "#{project.namespace.full_path}/cool" + end + end + + describe '#full_group_path' do + it 'returns current parent path when no group ref specified' do + expect(cache.full_group_path(nil)).to eq cache.current_parent_path + end + + it 'returns group ref' do + expect(cache.full_group_path('cool_group')).to eq 'cool_group' + end + end +end diff --git a/spec/lib/banzai/filter/references/reference_filter_spec.rb b/spec/lib/banzai/filter/references/reference_filter_spec.rb index 4bcb41ef2a9..b14b9374364 100644 --- a/spec/lib/banzai/filter/references/reference_filter_spec.rb +++ b/spec/lib/banzai/filter/references/reference_filter_spec.rb @@ -155,7 +155,7 @@ RSpec.describe Banzai::Filter::References::ReferenceFilter do let(:nodes) { [node] } it 'skips node' do - expect { |b| filter.replace_text_when_pattern_matches(filter.nodes[0], 0, ref_pattern, &b) }.not_to yield_control + expect { |b| filter.send(:replace_text_when_pattern_matches, filter.nodes[0], 0, ref_pattern, &b) }.not_to yield_control end end @@ -183,12 +183,12 @@ RSpec.describe Banzai::Filter::References::ReferenceFilter do end end - describe "#call_and_update_nodes" do + describe '#call_and_update_nodes' do include_context 'new nodes' let(:document) { Nokogiri::HTML.fragment('<a href="foo">foo</a>') } let(:filter) { described_class.new(document, project: project) } - it "updates all new nodes", :aggregate_failures do + it 'updates all new nodes', :aggregate_failures do filter.instance_variable_set('@nodes', nodes) expect(filter).to receive(:call) { filter.instance_variable_set('@new_nodes', new_nodes) } @@ -201,14 +201,14 @@ RSpec.describe Banzai::Filter::References::ReferenceFilter do end end - describe ".call" do + describe '.call' do include_context 'new nodes' let(:document) { Nokogiri::HTML.fragment('<a href="foo">foo</a>') } let(:result) { { reference_filter_nodes: nodes } } - it "updates all nodes", :aggregate_failures do + it 'updates all nodes', :aggregate_failures do expect_next_instance_of(described_class) do |filter| expect(filter).to receive(:call_and_update_nodes).and_call_original expect(filter).to receive(:with_update_nodes).and_call_original @@ -221,4 +221,21 @@ RSpec.describe Banzai::Filter::References::ReferenceFilter do expect(result[:reference_filter_nodes]).to eq(expected_nodes) end end + + context 'abstract methods' do + let(:document) { Nokogiri::HTML.fragment('<a href="foo">foo</a>') } + let(:filter) { described_class.new(document, project: project) } + + describe '#references_in' do + it 'raises NotImplementedError' do + expect { filter.references_in('foo', %r{(?<!\w)}) }.to raise_error(NotImplementedError) + end + end + + describe '#object_link_filter' do + it 'raises NotImplementedError' do + expect { filter.send(:object_link_filter, 'foo', %r{(?<!\w)}) }.to raise_error(NotImplementedError) + end + end + end end diff --git a/spec/lib/banzai/filter/references/snippet_reference_filter_spec.rb b/spec/lib/banzai/filter/references/snippet_reference_filter_spec.rb index 32a706925ba..7ab3b24b1c2 100644 --- a/spec/lib/banzai/filter/references/snippet_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/references/snippet_reference_filter_spec.rb @@ -219,4 +219,31 @@ RSpec.describe Banzai::Filter::References::SnippetReferenceFilter do expect(reference_filter(act, project: nil, group: create(:group)).to_html).to eq exp end end + + context 'checking N+1' do + let(:namespace2) { create(:namespace) } + let(:project2) { create(:project, :public, namespace: namespace2) } + let(:snippet2) { create(:project_snippet, project: project2) } + let(:reference2) { "#{project2.full_path}$#{snippet2.id}" } + + it 'does not have N+1 per multiple references per project', :use_sql_query_cache do + markdown = "#{reference} $9999990" + + control_count = ActiveRecord::QueryRecorder.new(skip_cached: false) do + reference_filter(markdown) + end.count + + markdown = "#{reference} $9999990 $9999991 $9999992 $9999993 #{reference2} something/cool$12" + + # Since we're not batching snippet queries across projects, + # we have to account for that. + # 1 for both projects, 1 for snippets in each project == 3 + # TODO: https://gitlab.com/gitlab-org/gitlab/-/issues/330359 + max_count = control_count + 1 + + expect do + reference_filter(markdown) + end.not_to exceed_all_query_limit(max_count) + end + end end diff --git a/spec/lib/banzai/filter/references/user_reference_filter_spec.rb b/spec/lib/banzai/filter/references/user_reference_filter_spec.rb index e4703606b47..70cbdb080a4 100644 --- a/spec/lib/banzai/filter/references/user_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/references/user_reference_filter_spec.rb @@ -189,7 +189,7 @@ RSpec.describe Banzai::Filter::References::UserReferenceFilter do filter = described_class.new(document, project: project) ns = user.namespace - expect(filter.namespaces).to eq({ ns.path => ns }) + expect(filter.send(:namespaces)).to eq({ ns.path => ns }) end end @@ -198,7 +198,28 @@ RSpec.describe Banzai::Filter::References::UserReferenceFilter do document = Nokogiri::HTML.fragment("<p>#{get_reference(user)}</p>") filter = described_class.new(document, project: project) - expect(filter.usernames).to eq([user.username]) + expect(filter.send(:usernames)).to eq([user.username]) + end + end + + context 'checking N+1' do + let(:user2) { create(:user) } + let(:group) { create(:group) } + let(:reference2) { user2.to_reference } + let(:reference3) { group.to_reference } + + it 'does not have N+1 per multiple user references', :use_sql_query_cache do + markdown = "#{reference}" + + control_count = ActiveRecord::QueryRecorder.new(skip_cached: false) do + reference_filter(markdown) + end.count + + markdown = "#{reference} @qwertyuiopzx @wertyuio @ertyu @rtyui #{reference2} #{reference3}" + + expect do + reference_filter(markdown) + end.not_to exceed_all_query_limit(control_count) end end end diff --git a/spec/lib/banzai/pipeline/post_process_pipeline_spec.rb b/spec/lib/banzai/pipeline/post_process_pipeline_spec.rb index d9f45769550..ebe1ca4d403 100644 --- a/spec/lib/banzai/pipeline/post_process_pipeline_spec.rb +++ b/spec/lib/banzai/pipeline/post_process_pipeline_spec.rb @@ -3,24 +3,56 @@ require 'spec_helper' RSpec.describe Banzai::Pipeline::PostProcessPipeline do - context 'when a document only has upload links' do - it 'does not make any Gitaly calls', :request_store do - markdown = <<-MARKDOWN.strip_heredoc - [Relative Upload Link](/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg) + subject { described_class.call(doc, context) } + + let_it_be(:project) { create(:project, :public, :repository) } - ![Relative Upload Image](/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg) - MARKDOWN + let(:context) { { project: project, ref: 'master' } } - context = { - project: create(:project, :public, :repository), - ref: 'master' - } + context 'when a document only has upload links' do + let(:doc) do + <<-HTML.strip_heredoc + <a href="/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg">Relative Upload Link</a> + <img src="/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg"> + HTML + end + it 'does not make any Gitaly calls', :request_store do Gitlab::GitalyClient.reset_counts - described_class.call(markdown, context) + subject expect(Gitlab::GitalyClient.get_request_count).to eq(0) end end + + context 'when both upload and repository links are present' do + let(:html) do + <<-HTML.strip_heredoc + <a href="/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg">Relative Upload Link</a> + <img src="/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg"> + <a href="/test.jpg">Just a link</a> + HTML + end + + let(:doc) { HTML::Pipeline.parse(html) } + + it 'searches for attributes only once' do + expect(doc).to receive(:search).once.and_call_original + + subject + end + + context 'when "optimize_linkable_attributes" is disabled' do + before do + stub_feature_flags(optimize_linkable_attributes: false) + end + + it 'searches for attributes twice' do + expect(doc).to receive(:search).twice.and_call_original + + subject + end + end + end end diff --git a/spec/lib/banzai/reference_parser/merge_request_parser_spec.rb b/spec/lib/banzai/reference_parser/merge_request_parser_spec.rb index 32a9f09c3f6..1820141c898 100644 --- a/spec/lib/banzai/reference_parser/merge_request_parser_spec.rb +++ b/spec/lib/banzai/reference_parser/merge_request_parser_spec.rb @@ -8,7 +8,7 @@ RSpec.describe Banzai::ReferenceParser::MergeRequestParser do let(:user) { create(:user) } let(:project) { create(:project, :public) } let(:merge_request) { create(:merge_request, source_project: project) } - subject { described_class.new(Banzai::RenderContext.new(merge_request.target_project, user)) } + subject(:parser) { described_class.new(Banzai::RenderContext.new(merge_request.target_project, user)) } let(:link) { empty_html_link } @@ -65,4 +65,49 @@ RSpec.describe Banzai::ReferenceParser::MergeRequestParser do it_behaves_like 'no N+1 queries' end + + describe '#can_read_reference?' do + subject { parser.can_read_reference?(user, merge_request) } + + it { is_expected.to be_truthy } + + context 'when merge request belongs to the private project' do + let(:project) { create(:project, :private) } + + it 'prevents user from reading merge request references' do + is_expected.to be_falsey + end + + context 'when user has access to the project' do + before do + project.add_developer(user) + end + + it { is_expected.to be_truthy } + end + end + + context 'with memoization' do + context 'when project is the same' do + it 'calls #can? only once' do + expect(parser).to receive(:can?).once + + 2.times { parser.can_read_reference?(user, merge_request) } + end + end + + context 'when merge requests belong to different projects' do + it 'calls #can? for each project' do + expect(parser).to receive(:can?).twice + + another_merge_request = create(:merge_request) + + 2.times do + parser.can_read_reference?(user, merge_request) + parser.can_read_reference?(user, another_merge_request) + end + end + end + end + end end |