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.
178 lines
5.9 KiB
Ruby
178 lines
5.9 KiB
Ruby
require 'spec_helper'
|
|
|
|
describe Banzai::Redactor do
|
|
let(:user) { create(:user) }
|
|
let(:project) { build(:project) }
|
|
let(:redactor) { described_class.new(Banzai::RenderContext.new(project, user)) }
|
|
|
|
describe '#redact' do
|
|
context 'when reference not visible to user' do
|
|
before do
|
|
expect(redactor).to receive(:nodes_visible_to_user).and_return([])
|
|
end
|
|
|
|
it 'redacts an array of documents' do
|
|
doc1 = Nokogiri::HTML
|
|
.fragment('<a class="gfm" data-reference-type="issue">foo</a>')
|
|
|
|
doc2 = Nokogiri::HTML
|
|
.fragment('<a class="gfm" data-reference-type="issue">bar</a>')
|
|
|
|
redacted_data = redactor.redact([doc1, doc2])
|
|
|
|
expect(redacted_data.map { |data| data[:document] }).to eq([doc1, doc2])
|
|
expect(redacted_data.map { |data| data[:visible_reference_count] }).to eq([0, 0])
|
|
expect(doc1.to_html).to eq('foo')
|
|
expect(doc2.to_html).to eq('bar')
|
|
end
|
|
|
|
it 'replaces redacted reference with inner HTML' do
|
|
doc = Nokogiri::HTML.fragment("<a class='gfm' data-reference-type='issue'>foo</a>")
|
|
redactor.redact([doc])
|
|
expect(doc.to_html).to eq('foo')
|
|
end
|
|
|
|
context 'when data-original attribute provided' do
|
|
let(:original_content) { '<code>foo</code>' }
|
|
it 'replaces redacted reference with original content' do
|
|
doc = Nokogiri::HTML.fragment("<a class='gfm' data-reference-type='issue' data-original='#{original_content}'>bar</a>")
|
|
redactor.redact([doc])
|
|
expect(doc.to_html).to eq(original_content)
|
|
end
|
|
end
|
|
|
|
it 'returns <a> tag with original href if it is originally a link reference' do
|
|
href = 'http://localhost:3000'
|
|
doc = Nokogiri::HTML
|
|
.fragment("<a class='gfm' data-reference-type='issue' data-original=#{href} data-link-reference='true'>#{href}</a>")
|
|
|
|
redactor.redact([doc])
|
|
|
|
expect(doc.to_html).to eq('<a href="http://localhost:3000">http://localhost:3000</a>')
|
|
end
|
|
end
|
|
|
|
context 'when project is in pending delete' do
|
|
let!(:issue) { create(:issue, project: project) }
|
|
let(:redactor) { described_class.new(Banzai::RenderContext.new(project, user)) }
|
|
|
|
before do
|
|
project.update(pending_delete: true)
|
|
end
|
|
|
|
it 'redacts an issue attached' do
|
|
doc = Nokogiri::HTML.fragment("<a class='gfm' data-reference-type='issue' data-issue='#{issue.id}'>foo</a>")
|
|
|
|
redactor.redact([doc])
|
|
|
|
expect(doc.to_html).to eq('foo')
|
|
end
|
|
|
|
it 'redacts an external issue' do
|
|
doc = Nokogiri::HTML.fragment("<a class='gfm' data-reference-type='issue' data-external-issue='#{issue.id}' data-project='#{project.id}'>foo</a>")
|
|
|
|
redactor.redact([doc])
|
|
|
|
expect(doc.to_html).to eq('foo')
|
|
end
|
|
end
|
|
|
|
context 'when reference visible to user' do
|
|
it 'does not redact an array of documents' do
|
|
doc1_html = '<a class="gfm" data-reference-type="issue">foo</a>'
|
|
doc1 = Nokogiri::HTML.fragment(doc1_html)
|
|
|
|
doc2_html = '<a class="gfm" data-reference-type="issue">bar</a>'
|
|
doc2 = Nokogiri::HTML.fragment(doc2_html)
|
|
|
|
nodes = redactor.document_nodes([doc1, doc2]).map { |x| x[:nodes] }
|
|
expect(redactor).to receive(:nodes_visible_to_user).and_return(nodes.flatten)
|
|
|
|
redacted_data = redactor.redact([doc1, doc2])
|
|
|
|
expect(redacted_data.map { |data| data[:document] }).to eq([doc1, doc2])
|
|
expect(redacted_data.map { |data| data[:visible_reference_count] }).to eq([1, 1])
|
|
expect(doc1.to_html).to eq(doc1_html)
|
|
expect(doc2.to_html).to eq(doc2_html)
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'when the user cannot read cross project' do
|
|
include ActionView::Helpers::UrlHelper
|
|
let(:project) { create(:project) }
|
|
let(:other_project) { create(:project, :public) }
|
|
|
|
def create_link(issuable)
|
|
type = issuable.class.name.underscore.downcase
|
|
link_to(issuable.to_reference, '',
|
|
class: 'gfm has-tooltip',
|
|
title: issuable.title,
|
|
data: {
|
|
reference_type: type,
|
|
"#{type}": issuable.id
|
|
})
|
|
end
|
|
|
|
before do
|
|
project.add_developer(user)
|
|
|
|
allow(Ability).to receive(:allowed?).and_call_original
|
|
allow(Ability).to receive(:allowed?).with(user, :read_cross_project, :global) { false }
|
|
allow(Ability).to receive(:allowed?).with(user, :read_cross_project) { false }
|
|
end
|
|
|
|
it 'skips links to issues within the same project' do
|
|
issue = create(:issue, project: project)
|
|
link = create_link(issue)
|
|
doc = Nokogiri::HTML.fragment(link)
|
|
|
|
redactor.redact([doc])
|
|
result = doc.css('a').last
|
|
|
|
expect(result['class']).to include('has-tooltip')
|
|
expect(result['title']).to eq(issue.title)
|
|
end
|
|
|
|
it 'removes info from a cross project reference' do
|
|
issue = create(:issue, project: other_project)
|
|
link = create_link(issue)
|
|
doc = Nokogiri::HTML.fragment(link)
|
|
|
|
redactor.redact([doc])
|
|
result = doc.css('a').last
|
|
|
|
expect(result['class']).not_to include('has-tooltip')
|
|
expect(result['title']).to be_empty
|
|
end
|
|
end
|
|
|
|
describe '#redact_nodes' do
|
|
it 'redacts an Array of nodes' do
|
|
doc = Nokogiri::HTML.fragment('<a href="foo">foo</a>')
|
|
node = doc.children[0]
|
|
|
|
expect(redactor).to receive(:nodes_visible_to_user)
|
|
.with([node])
|
|
.and_return(Set.new)
|
|
|
|
redactor.redact_document_nodes([{ document: doc, nodes: [node] }])
|
|
|
|
expect(doc.to_html).to eq('foo')
|
|
end
|
|
end
|
|
|
|
describe '#nodes_visible_to_user' do
|
|
it 'returns a Set containing the visible nodes' do
|
|
doc = Nokogiri::HTML.fragment('<a data-reference-type="issue"></a>')
|
|
node = doc.children[0]
|
|
|
|
expect_any_instance_of(Banzai::ReferenceParser::IssueParser)
|
|
.to receive(:nodes_visible_to_user)
|
|
.with(user, [node])
|
|
.and_return([node])
|
|
|
|
expect(redactor.nodes_visible_to_user([node])).to eq(Set.new([node]))
|
|
end
|
|
end
|
|
end
|