1# frozen_string_literal: true 2 3require 'spec_helper' 4 5RSpec.describe Banzai::Filter::References::MergeRequestReferenceFilter do 6 include FilterSpecHelper 7 8 let(:project) { create(:project, :public) } 9 let(:merge) { create(:merge_request, source_project: project) } 10 11 it 'requires project context' do 12 expect { described_class.call('') }.to raise_error(ArgumentError, /:project/) 13 end 14 15 %w(pre code a style).each do |elem| 16 it "ignores valid references contained inside '#{elem}' element" do 17 exp = act = "<#{elem}>Merge #{merge.to_reference}</#{elem}>" 18 expect(reference_filter(act).to_html).to eq exp 19 end 20 end 21 22 describe 'performance' do 23 let(:another_merge) { create(:merge_request, source_project: project, source_branch: 'fix') } 24 25 it 'does not have a N+1 query problem' do 26 single_reference = "Merge request #{merge.to_reference}" 27 multiple_references = "Merge requests #{merge.to_reference} and #{another_merge.to_reference}" 28 29 control_count = ActiveRecord::QueryRecorder.new { reference_filter(single_reference).to_html }.count 30 31 expect { reference_filter(multiple_references).to_html }.not_to exceed_query_limit(control_count) 32 end 33 end 34 35 describe 'all references' do 36 let(:doc) { reference_filter(merge.to_reference) } 37 let(:tag_el) { doc.css('a').first } 38 39 it 'adds merge request iid' do 40 expect(tag_el["data-iid"]).to eq(merge.iid.to_s) 41 end 42 43 it 'adds project data attribute with project id' do 44 expect(tag_el["data-project-path"]).to eq(project.full_path) 45 end 46 47 it 'does not add `has-tooltip` class' do 48 expect(tag_el["class"]).not_to include('has-tooltip') 49 end 50 end 51 52 context 'internal reference' do 53 let(:reference) { merge.to_reference } 54 55 it 'links to a valid reference' do 56 doc = reference_filter("See #{reference}") 57 58 expect(doc.css('a').first.attr('href')).to eq urls 59 .project_merge_request_url(project, merge) 60 end 61 62 it 'links with adjacent text' do 63 doc = reference_filter("Merge (#{reference}.)") 64 expect(doc.to_html).to match(%r{\(<a.+>#{Regexp.escape(reference)}</a>\.\)}) 65 end 66 67 it 'ignores invalid merge IDs' do 68 exp = act = "Merge #{invalidate_reference(reference)}" 69 70 expect(reference_filter(act).to_html).to eq exp 71 end 72 73 it 'ignores out-of-bounds merge request IDs on the referenced project' do 74 exp = act = "Merge !#{Gitlab::Database::MAX_INT_VALUE + 1}" 75 76 expect(reference_filter(act).to_html).to eq exp 77 end 78 79 it 'has no title' do 80 doc = reference_filter("Merge #{reference}") 81 expect(doc.css('a').first.attr('title')).to eq "" 82 end 83 84 it 'escapes the title attribute' do 85 merge.update_attribute(:title, %{"></a>whatever<a title="}) 86 87 doc = reference_filter("Merge #{reference}") 88 expect(doc.text).to eq "Merge #{reference}" 89 end 90 91 it 'includes default classes, without tooltip' do 92 doc = reference_filter("Merge #{reference}") 93 expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-merge_request' 94 end 95 96 it 'includes a data-project attribute' do 97 doc = reference_filter("Merge #{reference}") 98 link = doc.css('a').first 99 100 expect(link).to have_attribute('data-project') 101 expect(link.attr('data-project')).to eq project.id.to_s 102 end 103 104 it 'includes a data-merge-request attribute' do 105 doc = reference_filter("See #{reference}") 106 link = doc.css('a').first 107 108 expect(link).to have_attribute('data-merge-request') 109 expect(link.attr('data-merge-request')).to eq merge.id.to_s 110 end 111 112 it 'includes a data-reference-format attribute' do 113 doc = reference_filter("Merge #{reference}+") 114 link = doc.css('a').first 115 116 expect(link).to have_attribute('data-reference-format') 117 expect(link.attr('data-reference-format')).to eq('+') 118 end 119 120 it 'includes a data-reference-format attribute for URL references' do 121 doc = reference_filter("Merge #{urls.project_merge_request_url(project, merge)}+") 122 link = doc.css('a').first 123 124 expect(link).to have_attribute('data-reference-format') 125 expect(link.attr('data-reference-format')).to eq('+') 126 end 127 128 it 'supports an :only_path context' do 129 doc = reference_filter("Merge #{reference}", only_path: true) 130 link = doc.css('a').first.attr('href') 131 132 expect(link).not_to match %r(https?://) 133 expect(link).to eq urls.project_merge_request_url(project, merge, only_path: true) 134 end 135 end 136 137 context 'cross-project / cross-namespace complete reference' do 138 let(:project2) { create(:project, :public) } 139 let(:merge) { create(:merge_request, source_project: project2) } 140 let(:reference) { "#{project2.full_path}!#{merge.iid}" } 141 142 it 'links to a valid reference' do 143 doc = reference_filter("See #{reference}") 144 145 expect(doc.css('a').first.attr('href')) 146 .to eq urls.project_merge_request_url(project2, merge) 147 end 148 149 it 'link has valid text' do 150 doc = reference_filter("Merge (#{reference}.)") 151 152 expect(doc.css('a').first.text).to eq(reference) 153 end 154 155 it 'has valid text' do 156 doc = reference_filter("Merge (#{reference}.)") 157 158 expect(doc.text).to eq("Merge (#{reference}.)") 159 end 160 161 it 'has correct data attributes' do 162 doc = reference_filter("Merge (#{reference}.)") 163 164 link = doc.css('a').first 165 166 expect(link.attr('data-project')).to eq project2.id.to_s 167 expect(link.attr('data-project-path')).to eq project2.full_path 168 expect(link.attr('data-iid')).to eq merge.iid.to_s 169 expect(link.attr('data-mr-title')).to eq merge.title 170 end 171 172 it 'ignores invalid merge IDs on the referenced project' do 173 exp = act = "Merge #{invalidate_reference(reference)}" 174 175 expect(reference_filter(act).to_html).to eq exp 176 end 177 end 178 179 context 'cross-project / same-namespace complete reference' do 180 let(:namespace) { create(:namespace) } 181 let(:project) { create(:project, :public, namespace: namespace) } 182 let(:project2) { create(:project, :public, namespace: namespace) } 183 let!(:merge) { create(:merge_request, source_project: project2) } 184 let(:reference) { "#{project2.full_path}!#{merge.iid}" } 185 186 it 'links to a valid reference' do 187 doc = reference_filter("See #{reference}") 188 189 expect(doc.css('a').first.attr('href')) 190 .to eq urls.project_merge_request_url(project2, merge) 191 end 192 193 it 'link has valid text' do 194 doc = reference_filter("Merge (#{reference}.)") 195 196 expect(doc.css('a').first.text).to eq("#{project2.path}!#{merge.iid}") 197 end 198 199 it 'has valid text' do 200 doc = reference_filter("Merge (#{reference}.)") 201 202 expect(doc.text).to eq("Merge (#{project2.path}!#{merge.iid}.)") 203 end 204 205 it 'ignores invalid merge IDs on the referenced project' do 206 exp = act = "Merge #{invalidate_reference(reference)}" 207 208 expect(reference_filter(act).to_html).to eq exp 209 end 210 end 211 212 context 'cross-project shorthand reference' do 213 let(:namespace) { create(:namespace) } 214 let(:project) { create(:project, :public, namespace: namespace) } 215 let(:project2) { create(:project, :public, namespace: namespace) } 216 let!(:merge) { create(:merge_request, source_project: project2) } 217 let(:reference) { "#{project2.path}!#{merge.iid}" } 218 219 it 'links to a valid reference' do 220 doc = reference_filter("See #{reference}") 221 222 expect(doc.css('a').first.attr('href')) 223 .to eq urls.project_merge_request_url(project2, merge) 224 end 225 226 it 'link has valid text' do 227 doc = reference_filter("Merge (#{reference}.)") 228 229 expect(doc.css('a').first.text).to eq("#{project2.path}!#{merge.iid}") 230 end 231 232 it 'has valid text' do 233 doc = reference_filter("Merge (#{reference}.)") 234 235 expect(doc.text).to eq("Merge (#{project2.path}!#{merge.iid}.)") 236 end 237 238 it 'ignores invalid merge IDs on the referenced project' do 239 exp = act = "Merge #{invalidate_reference(reference)}" 240 241 expect(reference_filter(act).to_html).to eq exp 242 end 243 end 244 245 context 'URL reference for a commit' do 246 let(:mr) { create(:merge_request) } 247 let(:reference) do 248 urls.project_merge_request_url(mr.project, mr) + "/diffs?commit_id=#{mr.diff_head_sha}" 249 end 250 251 let(:commit) { mr.commits.find { |commit| commit.sha == mr.diff_head_sha } } 252 253 it 'links to a valid reference' do 254 doc = reference_filter("See #{reference}") 255 256 expect(doc.css('a').first.attr('href')) 257 .to eq reference 258 end 259 260 it 'commit ref tag is valid' do 261 doc = reference_filter("See #{reference}") 262 commit_ref_tag = doc.css('a').first.css('span.gfm.gfm-commit') 263 264 expect(commit_ref_tag.text).to eq(commit.short_id) 265 end 266 267 it 'has valid text' do 268 doc = reference_filter("See #{reference}") 269 270 expect(doc.text).to eq("See #{mr.to_reference(full: true)} (#{commit.short_id})") 271 end 272 273 it 'has valid title attribute' do 274 doc = reference_filter("See #{reference}") 275 276 expect(doc.css('a').first.attr('title')).to eq(commit.title) 277 end 278 279 it 'ignores invalid commit short_ids on link text' do 280 invalidate_commit_reference = 281 urls.project_merge_request_url(mr.project, mr) + "/diffs?commit_id=12345678" 282 doc = reference_filter("See #{invalidate_commit_reference}") 283 284 expect(doc.text).to eq("See #{mr.to_reference(full: true)} (diffs)") 285 end 286 end 287 288 context 'cross-project URL reference' do 289 let(:namespace) { create(:namespace, name: 'cross-reference') } 290 let(:project2) { create(:project, :public, namespace: namespace) } 291 let(:merge) { create(:merge_request, source_project: project2, target_project: project2) } 292 let(:reference) { urls.project_merge_request_url(project2, merge) + '/diffs#note_123' } 293 294 it 'links to a valid reference' do 295 doc = reference_filter("See #{reference}") 296 297 expect(doc.css('a').first.attr('href')) 298 .to eq reference 299 end 300 301 it 'links with adjacent text' do 302 doc = reference_filter("Merge (#{reference}.)") 303 expect(doc.to_html).to match(%r{\(<a.+>#{Regexp.escape(merge.to_reference(project))} \(diffs, comment 123\)</a>\.\)}) 304 end 305 end 306 307 context 'group context' do 308 it 'links to a valid reference' do 309 reference = "#{project.full_path}!#{merge.iid}" 310 311 result = reference_filter("See #{reference}", { project: nil, group: create(:group) } ) 312 313 expect(result.css('a').first.attr('href')).to eq(urls.project_merge_request_url(project, merge)) 314 end 315 end 316end 317