daad7144ec
This refactors the Markdown pipeline so it supports the rendering of multiple documents that may belong to different projects. An example of where this happens is when displaying the event feed of a group. In this case we retrieve events for all projects in the group. Previously we would group events per project and render these chunks separately, but this would result in many SQL queries being executed. By extending the Markdown pipeline to support this out of the box we can drastically reduce the number of SQL queries. To achieve this we introduce a new object to the pipeline: Banzai::RenderContext. This object simply wraps two other objects: an optional Project instance, and an optional User instance. On its own this wouldn't be very helpful, but a RenderContext can also be used to associate HTML documents with specific Project instances. This work is done in Banzai::ObjectRenderer and allows us to reuse as many queries (and results) as possible.
143 lines
4.5 KiB
Ruby
143 lines
4.5 KiB
Ruby
require 'spec_helper'
|
|
|
|
describe Banzai::ReferenceParser::IssueParser do
|
|
include ReferenceParserHelpers
|
|
|
|
let(:project) { create(:project, :public) }
|
|
let(:user) { create(:user) }
|
|
let(:issue) { create(:issue, project: project) }
|
|
let(:link) { empty_html_link }
|
|
subject { described_class.new(Banzai::RenderContext.new(project, user)) }
|
|
|
|
describe '#nodes_visible_to_user' do
|
|
context 'when the link has a data-issue attribute' do
|
|
before do
|
|
link['data-issue'] = issue.id.to_s
|
|
end
|
|
|
|
it_behaves_like "referenced feature visibility", "issues"
|
|
|
|
it 'returns the nodes when the user can read the issue' do
|
|
expect(Ability).to receive(:issues_readable_by_user)
|
|
.with([issue], user)
|
|
.and_return([issue])
|
|
|
|
expect(subject.nodes_visible_to_user(user, [link])).to eq([link])
|
|
end
|
|
|
|
it 'returns an empty Array when the user can not read the issue' do
|
|
expect(Ability).to receive(:issues_readable_by_user)
|
|
.with([issue], user)
|
|
.and_return([])
|
|
|
|
expect(subject.nodes_visible_to_user(user, [link])).to eq([])
|
|
end
|
|
|
|
context 'when the user cannot read cross project' do
|
|
let(:issue) { create(:issue) }
|
|
|
|
before do
|
|
allow(Ability).to receive(:allowed?).with(user, :read_cross_project) { false }
|
|
allow(Ability).to receive(:allowed?).with(user, :read_cross_project, :global) { false }
|
|
end
|
|
|
|
it 'returns the nodes when the user can read the issue' do
|
|
expect(Ability).to receive(:allowed?)
|
|
.with(user, :read_issue_iid, issue)
|
|
.and_return(true)
|
|
|
|
expect(subject.nodes_visible_to_user(user, [link])).to eq([link])
|
|
end
|
|
|
|
it 'returns an empty Array when the user can not read the issue' do
|
|
expect(Ability).to receive(:allowed?)
|
|
.with(user, :read_issue_iid, issue)
|
|
.and_return(false)
|
|
|
|
expect(subject.nodes_visible_to_user(user, [link])).to eq([])
|
|
end
|
|
|
|
context 'when the issue is not cross project' do
|
|
let(:issue) { create(:issue, project: project) }
|
|
|
|
it 'does not check `can_read_reference` if the issue is not cross project' do
|
|
expect(Ability).to receive(:issues_readable_by_user)
|
|
.with([issue], user)
|
|
.and_return([])
|
|
|
|
expect(subject).not_to receive(:can_read_reference?).with(user, issue)
|
|
|
|
expect(subject.nodes_visible_to_user(user, [link])).to eq([])
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'when the link does not have a data-issue attribute' do
|
|
it 'returns an empty Array' do
|
|
expect(subject.nodes_visible_to_user(user, [link])).to eq([])
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#referenced_by' do
|
|
context 'when the link has a data-issue attribute' do
|
|
context 'using an existing issue ID' do
|
|
before do
|
|
link['data-issue'] = issue.id.to_s
|
|
end
|
|
|
|
it 'returns an Array of issues' do
|
|
expect(subject.referenced_by([link])).to eq([issue])
|
|
end
|
|
|
|
it 'returns an empty Array when the list of nodes is empty' do
|
|
expect(subject.referenced_by([link])).to eq([issue])
|
|
expect(subject.referenced_by([])).to eq([])
|
|
end
|
|
end
|
|
|
|
context 'when issue with given ID does not exist' do
|
|
before do
|
|
link['data-issue'] = '-1'
|
|
end
|
|
|
|
it 'returns an empty Array' do
|
|
expect(subject.referenced_by([link])).to eq([])
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#records_for_nodes' do
|
|
it 'returns a Hash containing the issues for a list of nodes' do
|
|
link['data-issue'] = issue.id.to_s
|
|
nodes = [link]
|
|
|
|
expect(subject.records_for_nodes(nodes)).to eq({ link => issue })
|
|
end
|
|
end
|
|
|
|
context 'when checking multiple merge requests on another project' do
|
|
let(:other_project) { create(:project, :public) }
|
|
let(:other_issue) { create(:issue, project: other_project) }
|
|
|
|
let(:control_links) do
|
|
[issue_link(other_issue)]
|
|
end
|
|
|
|
let(:actual_links) do
|
|
control_links + [issue_link(create(:issue, project: other_project))]
|
|
end
|
|
|
|
def issue_link(issue)
|
|
Nokogiri::HTML.fragment(%Q{<a data-issue="#{issue.id}"></a>}).children[0]
|
|
end
|
|
|
|
before do
|
|
project.add_developer(user)
|
|
end
|
|
|
|
it_behaves_like 'no N+1 queries'
|
|
end
|
|
end
|