# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Banzai::Filter::References::VulnerabilityReferenceFilter do
  include FilterSpecHelper

  let(:urls) { Gitlab::Routing.url_helpers }

  let(:project) { create(:project) }
  let(:another_project) { create(:project) }
  let(:vulnerability) { create(:vulnerability, project: project) }
  let(:full_ref_text) { "Check [vulnerability:#{vulnerability.project.full_path}/#{vulnerability.id}]" }

  def doc(reference = nil)
    reference ||= "Check [vulnerability:#{vulnerability.id}]"
    context = { project: project, group: nil }

    reference_filter(reference, context)
  end

  context 'internal reference' do
    let(:reference) { "[vulnerability:#{vulnerability.id}]" }

    it 'links to a valid reference' do
      expect(doc.css('a').first.attr('href')).to eq(urls.project_security_vulnerability_url(project, vulnerability))
    end

    it 'links with adjacent text' do
      expect(doc.text).to eq("Check #{reference}")
    end

    it 'includes a title attribute' do
      expect(doc.css('a').first.attr('title')).to eq(vulnerability.title)
    end

    it 'escapes the title attribute' do
      vulnerability.update_column(:title, %{"></a>whatever<a title="})

      expect(doc.text).to eq("Check #{reference}")
    end

    it 'includes default classes' do
      expect(doc.css('a').first.attr('class')).to eq('gfm gfm-vulnerability has-tooltip')
    end

    it 'includes a data-project attribute' do
      link = doc.css('a').first

      expect(link).to have_attribute('data-project')
      expect(link.attr('data-project')).to eq(project.id.to_s)
    end

    it 'includes a data-vulnerability attribute' do
      link = doc.css('a').first

      expect(link).to have_attribute('data-vulnerability')
      expect(link.attr('data-vulnerability')).to eq(vulnerability.id.to_s)
    end

    it 'includes a data-original attribute' do
      link = doc.css('a').first

      expect(link).to have_attribute('data-original')
      expect(link.attr('data-original')).to eq(CGI.escapeHTML(reference))
    end

    it 'ignores invalid vulnerability IDs' do
      text = "Check [vulnerability:#{non_existing_record_id}]"

      expect(doc(text).to_s).to eq(ERB::Util.html_escape_once(text))
    end

    it 'ignores out of range vulnerability IDs' do
      text = "Check &1161452270761535925900804973910297"

      expect(doc(text).to_s).to eq(ERB::Util.html_escape_once(text))
    end

    it 'does not process links containing vulnerability numbers followed by text' do
      href = "#{reference}st"
      link = doc("<a href='#{href}'></a>").css('a').first.attr('href')

      expect(link).to eq(href)
    end
  end

  context 'cross-reference' do
    before do
      vulnerability.update_column(:project_id, another_project.id)
    end

    it 'ignores a shorthand reference from another group' do
      text = "Check [vulnerability:#{vulnerability.id}]"

      expect(doc(text).to_s).to eq(ERB::Util.html_escape_once(text))
    end

    it 'links to a valid reference for full reference' do
      expect(doc(full_ref_text).css('a').first.attr('href')).to eq(urls.project_security_vulnerability_url(another_project, vulnerability))
    end

    it 'link has valid text' do
      expect(doc(full_ref_text).css('a').first.text).to eq("[vulnerability:#{vulnerability.project.full_path}/#{vulnerability.id}]")
    end

    it 'includes default classes' do
      expect(doc(full_ref_text).css('a').first.attr('class')).to eq('gfm gfm-vulnerability has-tooltip')
    end
  end

  context 'escaped cross-reference' do
    before do
      vulnerability.update_column(:project_id, another_project.id)
    end

    it 'ignores a shorthand reference from another group' do
      text = "Check [vulnerability:#{vulnerability.id}]"

      expect(doc(text).to_s).to eq(ERB::Util.html_escape_once(text))
    end

    it 'links to a valid reference for full reference' do
      expect(doc(full_ref_text).css('a').first.attr('href')).to eq(urls.project_security_vulnerability_url(another_project, vulnerability))
    end

    it 'link has valid text' do
      expect(doc(full_ref_text).css('a').first.text).to eq("[vulnerability:#{vulnerability.project.full_path}/#{vulnerability.id}]")
    end

    it 'includes default classes' do
      expect(doc(full_ref_text).css('a').first.attr('class')).to eq('gfm gfm-vulnerability has-tooltip')
    end
  end

  context 'url reference' do
    let(:link) { urls.project_security_vulnerability_url(vulnerability.project, vulnerability) }
    let(:text) { "Check #{link}" }
    let(:project) { create(:project) }

    before do
      vulnerability.update_column(:project_id, another_project.id)
    end

    it 'links to a valid reference' do
      expect(doc(text).css('a').first.attr('href')).to eq(urls.project_security_vulnerability_url(another_project, vulnerability))
    end

    it 'link has valid text' do
      expect(doc(text).css('a').first.text).to eq(vulnerability.to_reference(project))
    end

    it 'includes default classes' do
      expect(doc(text).css('a').first.attr('class')).to eq('gfm gfm-vulnerability has-tooltip')
    end

    it 'matches link reference with trailing slash' do
      doc2 = reference_filter("Fixed (#{link}/.)")

      expect(doc2).to match(%r{\(#{Regexp.escape(vulnerability.to_reference(project))}\.\)})
    end
  end

  context 'url in a link href' do
    let(:link) { urls.project_security_vulnerability_url(vulnerability.project, vulnerability) }
    let(:text) do
      ref = %{<a href="#{link}">Reference</a>}
      "Check #{ref}"
    end

    before do
      vulnerability.update_column(:project_id, another_project.id)
    end

    it 'links to a valid reference for link href' do
      expect(doc(text).css('a').first.attr('href')).to eq(urls.project_security_vulnerability_url(another_project, vulnerability))
    end

    it 'link has valid text' do
      expect(doc(text).css('a').first.text).to eq('Reference')
    end

    it 'includes default classes' do
      expect(doc(text).css('a').first.attr('class')).to eq('gfm gfm-vulnerability has-tooltip')
    end
  end
end
