diff options
Diffstat (limited to 'spec/lib/banzai/filter')
5 files changed, 318 insertions, 5 deletions
diff --git a/spec/lib/banzai/filter/asset_proxy_filter_spec.rb b/spec/lib/banzai/filter/asset_proxy_filter_spec.rb index 1f886059bf6..81aa8d35ebc 100644 --- a/spec/lib/banzai/filter/asset_proxy_filter_spec.rb +++ b/spec/lib/banzai/filter/asset_proxy_filter_spec.rb @@ -28,7 +28,7 @@ RSpec.describe Banzai::Filter::AssetProxyFilter do stub_application_setting(asset_proxy_enabled: true) stub_application_setting(asset_proxy_secret_key: 'shared-secret') stub_application_setting(asset_proxy_url: 'https://assets.example.com') - stub_application_setting(asset_proxy_whitelist: %w(gitlab.com *.mydomain.com)) + stub_application_setting(asset_proxy_allowlist: %w(gitlab.com *.mydomain.com)) described_class.initialize_settings @@ -39,16 +39,26 @@ RSpec.describe Banzai::Filter::AssetProxyFilter do expect(Gitlab.config.asset_proxy.domain_regexp).to eq(/^(gitlab\.com|.*?\.mydomain\.com)$/i) end - context 'when whitelist is empty' do + context 'when allowlist is empty' do it 'defaults to the install domain' do stub_application_setting(asset_proxy_enabled: true) - stub_application_setting(asset_proxy_whitelist: []) + stub_application_setting(asset_proxy_allowlist: []) described_class.initialize_settings expect(Gitlab.config.asset_proxy.allowlist).to eq [Gitlab.config.gitlab.host] end end + + it 'supports deprecated whitelist settings' do + stub_application_setting(asset_proxy_enabled: true) + stub_application_setting(asset_proxy_whitelist: %w(foo.com bar.com)) + stub_application_setting(asset_proxy_allowlist: []) + + described_class.initialize_settings + + expect(Gitlab.config.asset_proxy.allowlist).to eq %w(foo.com bar.com) + end end context 'when properly configured' do diff --git a/spec/lib/banzai/filter/custom_emoji_filter_spec.rb b/spec/lib/banzai/filter/custom_emoji_filter_spec.rb new file mode 100644 index 00000000000..ca8c9750e7f --- /dev/null +++ b/spec/lib/banzai/filter/custom_emoji_filter_spec.rb @@ -0,0 +1,80 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Banzai::Filter::CustomEmojiFilter do + include FilterSpecHelper + + let_it_be(:group) { create(:group) } + let_it_be(:project) { create(:project, group: group) } + let_it_be(:custom_emoji) { create(:custom_emoji, name: 'tanuki', group: group) } + let_it_be(:custom_emoji2) { create(:custom_emoji, name: 'happy_tanuki', group: group, file: 'https://foo.bar/happy.png') } + + it 'replaces supported name custom emoji' do + doc = filter('<p>:tanuki:</p>', project: project) + + expect(doc.css('gl-emoji').first.attributes['title'].value).to eq('tanuki') + expect(doc.css('gl-emoji img').size).to eq 1 + end + + it 'ignores non existent custom emoji' do + exp = act = '<p>:foo:</p>' + doc = filter(act) + + expect(doc.to_html).to match Regexp.escape(exp) + end + + it 'correctly uses the custom emoji URL' do + doc = filter('<p>:tanuki:</p>') + + expect(doc.css('img').first.attributes['src'].value).to eq(custom_emoji.file) + end + + it 'matches with adjacent text' do + doc = filter('tanuki (:tanuki:)') + + expect(doc.css('img').size).to eq 1 + end + + it 'matches multiple same custom emoji' do + doc = filter(':tanuki: :tanuki:') + + expect(doc.css('img').size).to eq 2 + end + + it 'matches multiple custom emoji' do + doc = filter(':tanuki: (:happy_tanuki:)') + + expect(doc.css('img').size).to eq 2 + end + + it 'does not match enclosed colons' do + doc = filter('tanuki:tanuki:') + + expect(doc.css('img').size).to be 0 + end + + it 'keeps whitespace intact' do + doc = filter('This deserves a :tanuki:, big time.') + + expect(doc.to_html).to match(/^This deserves a <gl-emoji.+>, big time\.\z/) + end + + it 'does not match emoji in a string' do + doc = filter("'2a00:tanuki:100::1'") + + expect(doc.css('gl-emoji').size).to eq 0 + end + + it 'does not do N+1 query' do + create(:custom_emoji, name: 'party-parrot', group: group) + + control_count = ActiveRecord::QueryRecorder.new(skip_cached: false) do + filter('<p>:tanuki:</p>') + end + + expect do + filter('<p>:tanuki: :party-parrot:</p>') + end.not_to exceed_all_query_limit(control_count.count) + end +end diff --git a/spec/lib/banzai/filter/feature_flag_reference_filter_spec.rb b/spec/lib/banzai/filter/feature_flag_reference_filter_spec.rb new file mode 100644 index 00000000000..2d7089853cf --- /dev/null +++ b/spec/lib/banzai/filter/feature_flag_reference_filter_spec.rb @@ -0,0 +1,223 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Banzai::Filter::FeatureFlagReferenceFilter do + include FilterSpecHelper + + let_it_be(:project) { create(:project, :public) } + let_it_be(:feature_flag) { create(:operations_feature_flag, project: project) } + let_it_be(:reference) { feature_flag.to_reference } + + it 'requires project context' do + expect { described_class.call('') }.to raise_error(ArgumentError, /:project/) + end + + %w(pre code a style).each do |elem| + it "ignores valid references contained inside '#{elem}' element" do + exp = act = "<#{elem}>Feature Flag #{reference}</#{elem}>" + + expect(reference_filter(act).to_html).to eq exp + end + end + + context 'with internal reference' do + it 'links to a valid reference' do + doc = reference_filter("See #{reference}") + + expect(doc.css('a').first.attr('href')).to eq urls.edit_project_feature_flag_url(project, feature_flag) + end + + it 'links with adjacent text' do + doc = reference_filter("Feature Flag (#{reference}.)") + + expect(doc.to_html).to match(%r{\(<a.+>#{Regexp.escape(reference)}</a>\.\)}) + end + + it 'ignores invalid feature flag IIDs' do + exp = act = "Check [feature_flag:#{non_existing_record_id}]" + + expect(reference_filter(act).to_html).to eq exp + end + + it 'includes a title attribute' do + doc = reference_filter("Feature Flag #{reference}") + + expect(doc.css('a').first.attr('title')).to eq feature_flag.name + end + + it 'escapes the title attribute' do + allow(feature_flag).to receive(:name).and_return(%{"></a>whatever<a title="}) + doc = reference_filter("Feature Flag #{reference}") + + expect(doc.text).to eq "Feature Flag #{reference}" + end + + it 'includes default classes' do + doc = reference_filter("Feature Flag #{reference}") + + expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-feature_flag has-tooltip' + end + + it 'includes a data-project attribute' do + doc = reference_filter("Feature Flag #{reference}") + 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-feature-flag attribute' do + doc = reference_filter("See #{reference}") + link = doc.css('a').first + + expect(link).to have_attribute('data-feature-flag') + expect(link.attr('data-feature-flag')).to eq feature_flag.id.to_s + end + + it 'supports an :only_path context' do + doc = reference_filter("Feature Flag #{reference}", only_path: true) + link = doc.css('a').first.attr('href') + + expect(link).not_to match %r(https?://) + expect(link).to eq urls.edit_project_feature_flag_url(project, feature_flag.iid, only_path: true) + end + end + + context 'with cross-project / cross-namespace complete reference' do + let_it_be(:namespace) { create(:namespace) } + let_it_be(:project2) { create(:project, :public, namespace: namespace) } + let_it_be(:feature_flag) { create(:operations_feature_flag, project: project2) } + let_it_be(:reference) { "[feature_flag:#{project2.full_path}/#{feature_flag.iid}]" } + + it 'links to a valid reference' do + doc = reference_filter("See #{reference}") + + expect(doc.css('a').first.attr('href')).to eq urls.edit_project_feature_flag_url(project2, feature_flag) + end + + it 'produces a valid text in a link' do + doc = reference_filter("See (#{reference}.)") + + expect(doc.css('a').first.text).to eql(reference) + end + + it 'produces a valid text' do + doc = reference_filter("See (#{reference}.)") + + expect(doc.text).to eql("See (#{reference}.)") + end + + it 'ignores invalid feature flag IIDs on the referenced project' do + exp = act = "Check [feature_flag:#{non_existing_record_id}]" + + expect(reference_filter(act).to_html).to eq exp + end + end + + context 'with cross-project / same-namespace complete reference' do + let_it_be(:namespace) { create(:namespace) } + let_it_be(:project) { create(:project, :public, namespace: namespace) } + let_it_be(:project2) { create(:project, :public, namespace: namespace) } + let_it_be(:feature_flag) { create(:operations_feature_flag, project: project2) } + let_it_be(:reference) { "[feature_flag:#{project2.full_path}/#{feature_flag.iid}]" } + + it 'links to a valid reference' do + doc = reference_filter("See #{reference}") + + expect(doc.css('a').first.attr('href')).to eq urls.edit_project_feature_flag_url(project2, feature_flag) + end + + it 'produces a valid text in a link' do + doc = reference_filter("See ([feature_flag:#{project2.path}/#{feature_flag.iid}].)") + + expect(doc.css('a').first.text).to eql("[feature_flag:#{project2.path}/#{feature_flag.iid}]") + end + + it 'produces a valid text' do + doc = reference_filter("See ([feature_flag:#{project2.path}/#{feature_flag.iid}].)") + + expect(doc.text).to eql("See ([feature_flag:#{project2.path}/#{feature_flag.iid}].)") + end + + it 'ignores invalid feature flag IIDs on the referenced project' do + exp = act = "Check [feature_flag:#{non_existing_record_id}]" + + expect(reference_filter(act).to_html).to eq exp + end + end + + context 'with cross-project shorthand reference' do + let_it_be(:namespace) { create(:namespace) } + let_it_be(:project) { create(:project, :public, namespace: namespace) } + let_it_be(:project2) { create(:project, :public, namespace: namespace) } + let_it_be(:feature_flag) { create(:operations_feature_flag, project: project2) } + let_it_be(:reference) { "[feature_flag:#{project2.path}/#{feature_flag.iid}]" } + + it 'links to a valid reference' do + doc = reference_filter("See #{reference}") + + expect(doc.css('a').first.attr('href')).to eq urls.edit_project_feature_flag_url(project2, feature_flag) + end + + it 'produces a valid text in a link' do + doc = reference_filter("See ([feature_flag:#{project2.path}/#{feature_flag.iid}].)") + + expect(doc.css('a').first.text).to eql("[feature_flag:#{project2.path}/#{feature_flag.iid}]") + end + + it 'produces a valid text' do + doc = reference_filter("See ([feature_flag:#{project2.path}/#{feature_flag.iid}].)") + + expect(doc.text).to eql("See ([feature_flag:#{project2.path}/#{feature_flag.iid}].)") + end + + it 'ignores invalid feature flag IDs on the referenced project' do + exp = act = "Check [feature_flag:#{non_existing_record_id}]" + + expect(reference_filter(act).to_html).to eq exp + end + end + + context 'with cross-project URL reference' do + let_it_be(:namespace) { create(:namespace, name: 'cross-reference') } + let_it_be(:project2) { create(:project, :public, namespace: namespace) } + let_it_be(:feature_flag) { create(:operations_feature_flag, project: project2) } + let_it_be(:reference) { urls.edit_project_feature_flag_url(project2, feature_flag) } + + it 'links to a valid reference' do + doc = reference_filter("See #{reference}") + + expect(doc.css('a').first.attr('href')).to eq urls.edit_project_feature_flag_url(project2, feature_flag) + end + + it 'links with adjacent text' do + doc = reference_filter("See (#{reference}.)") + + expect(doc.to_html).to match(%r{\(<a.+>#{Regexp.escape(feature_flag.to_reference(project))}</a>\.\)}) + end + + it 'ignores invalid feature flag IIDs on the referenced project' do + act = "See #{invalidate_reference(reference)}" + + expect(reference_filter(act).to_html).to match(%r{<a.+>#{Regexp.escape(invalidate_reference(reference))}</a>}) + end + end + + context 'with group context' do + let_it_be(:group) { create(:group) } + + it 'links to a valid reference' do + reference = "[feature_flag:#{project.full_path}/#{feature_flag.iid}]" + result = reference_filter("See #{reference}", { project: nil, group: group } ) + + expect(result.css('a').first.attr('href')).to eq(urls.edit_project_feature_flag_url(project, feature_flag)) + end + + it 'ignores internal references' do + exp = act = "See [feature_flag:#{feature_flag.iid}]" + + expect(reference_filter(act, project: nil, group: group).to_html).to eq exp + end + end +end diff --git a/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb b/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb index df78a3321ba..811c2aca342 100644 --- a/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb @@ -216,7 +216,7 @@ RSpec.describe Banzai::Filter::MergeRequestReferenceFilter do end context 'URL reference for a commit' do - let(:mr) { create(:merge_request, :with_diffs) } + let(:mr) { create(:merge_request) } let(:reference) do urls.project_merge_request_url(mr.project, mr) + "/diffs?commit_id=#{mr.diff_head_sha}" end diff --git a/spec/lib/banzai/filter/truncate_source_filter_spec.rb b/spec/lib/banzai/filter/truncate_source_filter_spec.rb index b0c6d91daa8..d5eb8b738b1 100644 --- a/spec/lib/banzai/filter/truncate_source_filter_spec.rb +++ b/spec/lib/banzai/filter/truncate_source_filter_spec.rb @@ -22,7 +22,7 @@ RSpec.describe Banzai::Filter::TruncateSourceFilter do it 'truncates UTF-8 text by bytes, on a character boundary' do utf8_text = '日本語の文字が大きい' - truncated = '日…' + truncated = '日...' expect(filter(utf8_text, limit: truncated.bytesize)).to eq(truncated) expect(filter(utf8_text, limit: utf8_text.bytesize)).to eq(utf8_text) |