diff --git a/Gemfile b/Gemfile index 6d08b12a2d..18abcbd68c 100644 --- a/Gemfile +++ b/Gemfile @@ -30,7 +30,6 @@ gem 'resque-scheduler', '~> 4.3.1' gem 'cocaine', '~> 0.5.8' gem 'paperclip', '~> 5.2.0' gem 'googlecharts', '~> 1.6.12' -gem 'sanitize', '~> 4.6.4' gem 'tweetstream', '~> 2.6.1' # no longer maintained? gem 'twitter', '~> 5.5' gem 'flickraw', '~> 0.9.9' @@ -86,7 +85,7 @@ gem 'dogstatsd-ruby', '~> 3.2.0' gem 'test-unit', '~> 3.2.7' gem 'http', '~> 1.0' gem 'robots_tag_parser', '~> 0.1.0', git: 'https://github.com/GSA/robots_tag_parser' -gem 'loofah', '~> 2.3.1' +gem 'loofah', '~> 2.6.0' # Using custom branch until https://github.com/brutuscat/medusa/issues/10 is merged gem 'medusa', git: 'https://github.com/MothOnMars/medusa', branch: 'clean_urls' # Robotex is required by Medusa. Specifying fork until https://github.com/chriskite/robotex/issues/4 @@ -129,7 +128,6 @@ gem 'font-awesome-grunticon-rails', # put test-only gems in this group so their generators # and rake tasks are available in development mode: group :development do - gem 'web-console', '~> 2.0' gem 'spring', '~> 2.0' gem 'listen', '~> 3.1.5' end @@ -165,7 +163,7 @@ group :test do gem 'shoulda-matchers', '~> 4.1.1' gem 'shoulda-kept-assign-to', '~> 1.1.0' gem 'vcr', '~> 4.0' - gem 'webmock', '~> 3.1.1' + gem 'webmock', '~> 3.8.3' gem 'rspec-activemodel-mocks', '~> 1.0.3' gem 'rspec_junit_formatter', '~> 0.3.0' gem 'rails-controller-testing', '~> 1.0.4' diff --git a/Gemfile.lock b/Gemfile.lock index 40e8b322a2..3f60027e48 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -185,8 +185,6 @@ GEM bcrypt-ruby (3.1.5) bcrypt (>= 3.1.3) bindata (2.4.4) - binding_of_caller (0.8.0) - debug_inspector (>= 0.0.1) buftok (0.2.0) builder (3.2.4) byebug (11.0.1) @@ -271,7 +269,6 @@ GEM curb (0.9.10) daemons (1.3.1) database_cleaner (1.7.0) - debug_inspector (0.0.3) declarative (0.0.10) declarative-option (0.1.0) descendants_tracker (0.0.4) @@ -390,7 +387,7 @@ GEM haml (5.0.4) temple (>= 0.8.0) tilt - hashdiff (0.4.0) + hashdiff (1.0.1) hashie (3.3.2) highline (2.0.2) html_truncator (0.4.2) @@ -457,7 +454,7 @@ GEM logging (2.2.2) little-plugger (~> 1.1) multi_json (~> 1.10) - loofah (2.3.1) + loofah (2.6.0) crass (~> 1.0.2) nokogiri (>= 1.5.9) mail (2.7.1) @@ -494,10 +491,8 @@ GEM net-http-persistent (2.9.4) newrelic_rpm (5.0.0.342) nio4r (2.5.2) - nokogiri (1.10.9) + nokogiri (1.10.10) mini_portile2 (~> 2.4.0) - nokogumbo (1.5.0) - nokogiri oj (3.3.10) omniauth (1.4.3) hashie (>= 1.2, < 4) @@ -531,9 +526,9 @@ GEM pry-rails (0.3.9) pry (>= 0.10.4) public_suffix (3.1.1) - puma (3.12.4) + puma (3.12.6) raabro (1.1.6) - rack (2.2.2) + rack (2.2.3) rack-accept (0.4.5) rack (>= 0.4) rack-contrib (2.1.0) @@ -678,10 +673,6 @@ GEM rufus-scheduler (3.6.0) fugit (~> 1.1, >= 1.1.6) safe_yaml (1.0.5) - sanitize (4.6.6) - crass (~> 1.0.2) - nokogiri (>= 1.4.4) - nokogumbo (~> 1.4) sass (3.3.14) sass-rails (5.0.7) railties (>= 4.0.0, < 6) @@ -782,18 +773,13 @@ GEM coercible (~> 1.0) descendants_tracker (~> 0.0, >= 0.0.3) equalizer (~> 0.0, >= 0.0.9) - web-console (2.3.0) - activemodel (>= 4.0) - binding_of_caller (>= 0.7.2) - railties (>= 4.0) - sprockets-rails (>= 2.0, < 4.0) - webmock (3.1.1) + webmock (3.8.3) addressable (>= 2.3.6) crack (>= 0.3.2) - hashdiff + hashdiff (>= 0.4.0, < 2.0.0) websocket-driver (0.7.2) websocket-extensions (>= 0.1.0) - websocket-extensions (0.1.4) + websocket-extensions (0.1.5) will_paginate (3.1.7) will_paginate-bootstrap (1.0.2) will_paginate (>= 3.0.3) @@ -863,7 +849,7 @@ DEPENDENCIES launchy (~> 2.4.3) less-rails-bootstrap! listen (~> 3.1.5) - loofah (~> 2.3.1) + loofah (~> 2.6.0) medusa! mobile-fu (~> 1.4.0) mry (= 0.59.0.0) @@ -907,7 +893,6 @@ DEPENDENCIES rspec-rails (~> 3.8.2) rspec_junit_formatter (~> 0.3.0) rubocop (= 0.60.0) - sanitize (~> 4.6.4) sass (~> 3.3.0) sass-rails (~> 5.0.7) saxerator (~> 0.9.9) @@ -931,8 +916,7 @@ DEPENDENCIES validate_url (= 0.2.0) vcr (~> 4.0) virtus (~> 1.0.5) - web-console (~> 2.0) - webmock (~> 3.1.1) + webmock (~> 3.8.3) will_paginate (~> 3.1.6) will_paginate-bootstrap (~> 1.0.1) yajl-ruby (~> 1.3.1) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 94ad4587ec..6f1ec173cb 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -124,7 +124,7 @@ def query_search_options end def sanitize_query(query) - QuerySanitizer.sanitize(query) + Sanitizer.sanitize(query, encode: false) end def highlighting_option diff --git a/app/controllers/image_searches_controller.rb b/app/controllers/image_searches_controller.rb index 4550f544cb..5464bf5be9 100644 --- a/app/controllers/image_searches_controller.rb +++ b/app/controllers/image_searches_controller.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class ImageSearchesController < ApplicationController include MobileFriendlyController diff --git a/app/models/boosted_content.rb b/app/models/boosted_content.rb index 74b58e3a6a..d0ecb1227f 100644 --- a/app/models/boosted_content.rb +++ b/app/models/boosted_content.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class BoostedContent < ApplicationRecord extend HumanAttributeName include BestBet diff --git a/app/models/elastic_featured_collection_results.rb b/app/models/elastic_featured_collection_results.rb index cc5a496df5..b4d8705622 100644 --- a/app/models/elastic_featured_collection_results.rb +++ b/app/models/elastic_featured_collection_results.rb @@ -10,8 +10,11 @@ def highlight_instance(highlight, instance) def highlight_link_titles(highlighted_link_titles, instance) highlighted_link_titles.each do |link_title| - fcl = instance.featured_collection_links.detect { |fcl| fcl.title == Sanitize.clean(link_title) } + fcl = instance.featured_collection_links.detect do |fcl| + fcl.title == Sanitizer.sanitize(link_title) + end + fcl.title = link_title if fcl end end -end \ No newline at end of file +end diff --git a/app/models/indexed_document.rb b/app/models/indexed_document.rb index 482976d0de..49961687b2 100644 --- a/app/models/indexed_document.rb +++ b/app/models/indexed_document.rb @@ -109,7 +109,7 @@ def index_application_file(file_path, doctype) end def extract_body_from(nokogiri_doc) - body = scrub_inner_text(Sanitize.clean(nokogiri_doc.at('body').inner_html.encode('utf-8'))) rescue '' + body = scrub_inner_text(Sanitizer.sanitize(nokogiri_doc.at('body').inner_html.encode('utf-8'))) rescue '' raise IndexedDocumentError.new(EMPTY_BODY_STATUS) if body.blank? body end diff --git a/app/models/med_topic.rb b/app/models/med_topic.rb index d0fd4187e0..689306d654 100644 --- a/app/models/med_topic.rb +++ b/app/models/med_topic.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class MedTopic < ApplicationRecord MAX_MED_TOPIC_SUMMARY_LENGTH = 200 MEDLINE_BASE_URL = 'https://medlineplus.gov/' @@ -148,8 +150,8 @@ def self.search_for(title, locale = 'en') end def truncated_summary - sentences = Sanitize.clean(summary_html).gsub(/[[:space:]]/, ' ').squish.split(/\.\s*/) - summary = '' + sentences = Sanitizer.sanitize(summary_html).gsub(/[[:space:]]/, ' ').split(/\.\s*/) + summary = +'' sentences.slice(0,3).each do |sentence| break if (summary.length + sentence.length + 1) > MAX_MED_TOPIC_SUMMARY_LENGTH diff --git a/app/models/tweet.rb b/app/models/tweet.rb index e087a94199..8b403733f0 100644 --- a/app/models/tweet.rb +++ b/app/models/tweet.rb @@ -6,7 +6,7 @@ class Tweet < ApplicationRecord serialize :urls, Array def sanitize_tweet_text - self.tweet_text = Sanitize.clean(tweet_text).squish if tweet_text + self.tweet_text = Sanitizer.sanitize(tweet_text).squish if tweet_text end def url_to_tweet diff --git a/features/support/paths.rb b/features/support/paths.rb index 926eef518b..d0926c758f 100644 --- a/features/support/paths.rb +++ b/features/support/paths.rb @@ -23,7 +23,7 @@ def path_to(page_name) when /^(.*)'s strictui search page$/ search_path(:affiliate => $1, :strictui => "1") when /^(.*)'s search page with unsanitized "([^\"]*)" query$/ - search_path(:affiliate => $1, :query => "") + search_path(:affiliate => $1, :query => "#{$2}") when /^(.*)'s search page with site limited to "([^\"]*)"$/ search_path(:affiliate => $1, :sitelimit => $2) when /^(.*)'s image search page$/ diff --git a/lib/api/search_options.rb b/lib/api/search_options.rb index e419b04116..75390bfb76 100644 --- a/lib/api/search_options.rb +++ b/lib/api/search_options.rb @@ -1,9 +1,9 @@ -require 'sanitize' +# frozen_string_literal: true class Api::SearchOptions include ActiveModel::Validations - LIMIT_ERROR_MESSAGE_TEMPLATE = 'must be between %s and %s'.freeze + LIMIT_ERROR_MESSAGE_TEMPLATE = 'must be between %s and %s' class_attribute :default_limit, :limit_range @@ -12,7 +12,7 @@ class Api::SearchOptions OFFSET_RANGE = (0..1000).freeze DEFAULT_OFFSET = 0 - OFFSET_ERROR_MESSAGE = "must be between #{OFFSET_RANGE.first} and #{OFFSET_RANGE.last}".freeze + OFFSET_ERROR_MESSAGE = "must be between #{OFFSET_RANGE.first} and #{OFFSET_RANGE.last}" QUERY_PARAMS = %i(query query_not query_or query_quote) attr_accessor :access_key, @@ -29,7 +29,7 @@ class Api::SearchOptions :filter validates_presence_of :access_key, - :affiliate , + :affiliate, message: 'must be present' validates_length_of :query, @@ -68,7 +68,7 @@ def initialize(params = {}) self.filter = params[:filter] QUERY_PARAMS.each do |param| - self.send("#{param}=", QuerySanitizer.sanitize(params[param])) + self.send("#{param}=", Sanitizer.sanitize(params[param], encode: false)) end end diff --git a/lib/attribute_processor.rb b/lib/attribute_processor.rb index e73b9d0463..2492bd2aa7 100644 --- a/lib/attribute_processor.rb +++ b/lib/attribute_processor.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module AttributeProcessor def self.prepend_attributes_with_http(record, *url_attribute_names) url_attribute_names.each do |attr_name| @@ -9,7 +11,7 @@ def self.prepend_attributes_with_http(record, *url_attribute_names) def self.sanitize_attributes(record, *attribute_names) attribute_names.each do |attr_name| value = record.send :"#{attr_name}" - record.send :"#{attr_name}=", Sanitize.clean(value) + record.send :"#{attr_name}=", Sanitizer.sanitize(value) end end diff --git a/lib/importers/rss_feed_data.rb b/lib/importers/rss_feed_data.rb index 4bb7f84bf5..3324228999 100644 --- a/lib/importers/rss_feed_data.rb +++ b/lib/importers/rss_feed_data.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class RssFeedData include RssFeedParser @@ -166,11 +168,11 @@ def link_status_code_404?(link) def extract_body(item) body = nil raw_body = extract_element_content item, elements[:body] - body = Sanitize.clean(raw_body) if raw_body + body = Sanitizer.sanitize(raw_body) if raw_body if body.blank? and @has_media_ns media_body = extract_element(item, :media_text) - body = Sanitize.clean(media_body) if media_body + body = Sanitizer.sanitize(media_body) if media_body end body end @@ -180,11 +182,11 @@ def extract_description(item) elements[:description].each do |description_path| break if (raw_description = extract_element_content item, description_path) end - description = Sanitize.clean(raw_description) if raw_description + description = Sanitizer.sanitize(raw_description) if raw_description if description.blank? and @has_media_ns media_description = extract_element(item, :media_description) - description = Sanitize.clean(media_description) if media_description + description = Sanitizer.sanitize(media_description) if media_description end description end diff --git a/lib/query_sanitizer.rb b/lib/query_sanitizer.rb deleted file mode 100644 index f165e44481..0000000000 --- a/lib/query_sanitizer.rb +++ /dev/null @@ -1,7 +0,0 @@ -module QuerySanitizer - def self.sanitize(query) - Sanitize.clean(query.to_s).gsub('&', '&').squish if query - rescue ArgumentError => e - nil - end -end \ No newline at end of file diff --git a/lib/sanitizer.rb b/lib/sanitizer.rb new file mode 100644 index 0000000000..2a5351c03a --- /dev/null +++ b/lib/sanitizer.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +module Sanitizer + def self.sanitize(str, encode: true) + Loofah.fragment(str).scrub!(:prune).text(encode_special_chars: encode).squish + rescue ArgumentError => e + Rails.logger.error("Error sanitizing string #{str}: #{e}") + nil + end +end diff --git a/spec/controllers/image_searches_controller_spec.rb b/spec/controllers/image_searches_controller_spec.rb index 1237d198d3..2d9c296fc8 100644 --- a/spec/controllers/image_searches_controller_spec.rb +++ b/spec/controllers/image_searches_controller_spec.rb @@ -7,7 +7,7 @@ describe '#index' do context 'when searching on legacy affiliate and the query is present' do let(:affiliate) { affiliates(:basic_affiliate) } - let(:query) { '' } + let(:query) { 'thunder & lightning' } let(:image_search) do double(LegacyImageSearch, query: 'thunder & lightning', @@ -27,7 +27,7 @@ context 'for a live search' do before do get :index, params: { affiliate: 'nps.gov', - query: '' } + query: query } end it { is_expected.to assign_to(:search).with(image_search) } @@ -51,7 +51,7 @@ context 'for a staged search' do before do get :index, params: { affiliate: 'nps.gov', - query: '', + query: query, staged: 'true' } end @@ -67,7 +67,7 @@ expect(image_search).to receive(:to_json).and_return(search_results_json) get :index, params: { affiliate: 'nps.gov', - query: '' }, + query: query }, format: :json end @@ -113,7 +113,7 @@ context 'when searching normally' do before do get :index, - params: { query: '', + params: { query: 'weather', affiliate: 'usagov' }, format: 'json' @search = assigns[:search] diff --git a/spec/controllers/searches_controller_spec.rb b/spec/controllers/searches_controller_spec.rb index e83f207bcc..b3d48959c8 100644 --- a/spec/controllers/searches_controller_spec.rb +++ b/spec/controllers/searches_controller_spec.rb @@ -208,7 +208,7 @@ get :index, params: { affiliate: affiliate.name, - query: '' + query: 'thunder & lightning' } @search = assigns[:search] @page_title = assigns[:page_title] diff --git a/spec/lib/api/search_options_spec.rb b/spec/lib/api/search_options_spec.rb index 9cac018e32..614d36514f 100644 --- a/spec/lib/api/search_options_spec.rb +++ b/spec/lib/api/search_options_spec.rb @@ -1,6 +1,38 @@ require 'spec_helper' describe Api::SearchOptions, type: :model do + subject(:options) do + described_class.new(params) + end + let(:params) do + {} + end + + describe 'initialization' do + context 'when the query params include HTML tags' do + let(:unsanitized_query) { 'thunder & lightning' } + let(:params) do + { + query: unsanitized_query, + query_not: unsanitized_query, + query_or: unsanitized_query, + query_quote: unsanitized_query + } + end + + it 'sanitizes the query params' do + expect( + [ + options.query, + options.query_not, + options.query_or, + options.query_quote + ] + ).to all eq 'thunder & lightning' + end + end + end + describe '#valid?' do before { expect(Affiliate).not_to receive(:find_by_name) } diff --git a/spec/lib/query_sanitizer_spec.rb b/spec/lib/query_sanitizer_spec.rb deleted file mode 100644 index b65e1054be..0000000000 --- a/spec/lib/query_sanitizer_spec.rb +++ /dev/null @@ -1,13 +0,0 @@ -require 'spec_helper' - -describe QuerySanitizer, ".sanitize" do - context 'when UTF-8 query is malformed' do - before do - allow(Sanitize).to receive(:clean).and_raise ArgumentError - end - - it 'should gracefully handle it' do - expect(QuerySanitizer.sanitize('bad query')).to be_nil - end - end -end \ No newline at end of file diff --git a/spec/lib/sanitizer_spec.rb b/spec/lib/sanitizer_spec.rb new file mode 100644 index 0000000000..9faecef597 --- /dev/null +++ b/spec/lib/sanitizer_spec.rb @@ -0,0 +1,56 @@ +require 'spec_helper' + +describe Sanitizer, ".sanitize" do + subject(:sanitize) { Sanitizer.sanitize(string) } + + context 'when the string contains HTML tags' do + let(:string) { 'foo bar' } + + it 'sanitizes the string' do + expect(sanitize).to eq 'foo bar' + end + end + + context 'when the string is nil' do + let(:string) { nil } + + it { is_expected.to eq '' } + end + + context 'when the string is not valid UTF-8' do + let(:string) { "foo\xF3" } + + it 'gracefully handles it' do + expect(sanitize).to be_nil + end + + it 'logs the error' do + expect(Rails.logger).to receive(:error).with( + "Error sanitizing string foo\xF3: invalid byte sequence in UTF-8" + ) + sanitize + end + end + + context 'when the string contains additional whitespace' do + let(:string) { 'foo bar' } + + it { is_expected.to eq 'foo bar' } + end + + context 'when the string contains HTML entities' do + let(:string) { 'foo & bar & baz' } + + it 'encodes them by default' do + expect(sanitize).to eq 'foo & bar & baz' + end + + context 'when not encoding the entities' do + let(:sanitize) { Sanitizer.sanitize(string, encode: false) } + + it 'does not encode them' do + expect(sanitize).to eq 'foo & bar & baz' + end + end + end +end diff --git a/spec/mailers/emailer_spec.rb b/spec/mailers/emailer_spec.rb index c86660ca9c..28583e83d5 100644 --- a/spec/mailers/emailer_spec.rb +++ b/spec/mailers/emailer_spec.rb @@ -216,7 +216,7 @@ it { is_expected.to have_subject(/Today's Snapshot for #{membership.affiliate.name} on #{Date.yesterday}/) } it "should contain the daily shapshot tables for yesterday" do - body = Sanitize.clean(email.default_part_body.to_s).squish + body = Sanitizer.sanitize(email.default_part_body) expect(body).to include('Top Queries') expect(body).to include('Search Term Total Queries (Bots + Humans) Real Queries') expect(body).to include('1. query1 100 80') @@ -271,7 +271,7 @@ it { is_expected.to have_subject(/April 2012/) } it "should show per-affiliate and total stats for the month" do - body = Sanitize.clean(email.default_part_body.to_s).squish + body = Sanitizer.sanitize(email.default_part_body) expect(body).to include('102 100 33.33% -33.33% 100') expect(body).to include('50 40 12.00% -9.00% 35') expect(body).to include('0 0 0.00% 0.00% 0') @@ -306,7 +306,7 @@ it { is_expected.to have_subject(/2012 Year in Review/) } it "show stats for the year" do - body = Sanitize.clean(email.default_part_body.to_s).squish + body = Sanitizer.sanitize(email.default_part_body) expect(body).to include('Most Popular Queries for 2012') expect(body).to include('NPEspanol Site Not enough historic data to compute most popular') expect(body).to include('query5 54 53') diff --git a/spec/models/boosted_content_spec.rb b/spec/models/boosted_content_spec.rb index 5b6cb88542..263bd6b974 100644 --- a/spec/models/boosted_content_spec.rb +++ b/spec/models/boosted_content_spec.rb @@ -17,6 +17,7 @@ dependent(:destroy).inverse_of(:boosted_content) end + it_behaves_like 'a record that sanitizes attributes', [:title, :description] describe "Creating new instance of BoostedContent" do it { is_expected.to validate_presence_of :url } @@ -24,6 +25,13 @@ it { is_expected.to validate_presence_of :description } it { is_expected.to validate_presence_of :affiliate } it { is_expected.to validate_presence_of :publish_start_on } + it do + is_expected.to validate_uniqueness_of(:url). + case_insensitive. + scoped_to(:affiliate_id). + with_message('has already been boosted') + end + it 'validates the url format' do boosted_content = BoostedContent.new(url: 'blah') @@ -66,16 +74,6 @@ expect(boosted_content.errors.full_messages.join).to match(/Publish end date can't be before publish start date/) end - it 'should not allow duplicate url' do - url = 'http://search.gov/post/9866782725/did-you-mean-roes-or-rose' - BoostedContent.create!(valid_attributes.merge(url: url)) - expect { BoostedContent.create!(valid_attributes.merge(url: url)) }.to raise_error - end - - it 'should not allow blank description' do - expect { BoostedContent.create!(valid_attributes.merge(description: ' ')) }.to raise_error - end - it "should save URL as is when it starts with http(s)://" do url = 'search.gov/post/9866782725/did-you-mean-roes-or-rose' prefixes = %w( http:// HTTPS:// ) diff --git a/spec/models/featured_collection_link_spec.rb b/spec/models/featured_collection_link_spec.rb index 64932dab77..dcfc6ab375 100644 --- a/spec/models/featured_collection_link_spec.rb +++ b/spec/models/featured_collection_link_spec.rb @@ -10,6 +10,8 @@ it { is_expected.to validate_presence_of :url } it { is_expected.to belong_to :featured_collection } + it_behaves_like 'a record that sanitizes attributes', [:title] + it 'squishes title and url' do fc = FeaturedCollection.new(title: 'Search USA Blog', status: 'active', diff --git a/spec/models/featured_collection_spec.rb b/spec/models/featured_collection_spec.rb index 4190283096..299de17e08 100644 --- a/spec/models/featured_collection_spec.rb +++ b/spec/models/featured_collection_spec.rb @@ -25,6 +25,8 @@ it { is_expected.to allow_value(status).for(:status) } end it { is_expected.not_to allow_value("bogus status").for(:status) } + + it_behaves_like 'a record that sanitizes attributes', [:title] end specify { expect(FeaturedCollection.new(:status => 'active')).to be_is_active } diff --git a/spec/models/med_topic_spec.rb b/spec/models/med_topic_spec.rb index d1e1c8762c..82caae92cf 100644 --- a/spec/models/med_topic_spec.rb +++ b/spec/models/med_topic_spec.rb @@ -294,7 +294,7 @@ end end - describe '#search_for' do + describe '.search_for' do before do MedTopic.create! do |t| t.medline_tid = 3061 @@ -354,4 +354,38 @@ } end end + + describe '#truncated_summary' do + subject(:truncated_summary) { med_topic.truncated_summary } + let(:summary_html) { '

Lorem ipsum dolor sit amet.

' } + let(:med_topic) { MedTopic.new(summary_html: summary_html) } + + it { is_expected.to eq 'Lorem ipsum dolor sit amet.' } + + context 'when the summary html contains sentences that are too long' do + let(:summary_html) do + "This sentence is just right. This sentence is too long #{'x' * 200}." + end + + it 'omits the long sentences' do + expect(truncated_summary).to eq 'This sentence is just right.' + end + end + + context 'when the summary html contains excess whitespace' do + let(:summary_html) { " \t Extra \n   spaces. " } + + it 'squishes the whitespace' do + expect(truncated_summary).to eq 'Extra spaces.' + end + end + + context 'when the summary html is missing punctuation' do + let(:summary_html) { 'Missing punctuation' } + + it 'adds a trailing period' do + expect(truncated_summary).to eq 'Missing punctuation.' + end + end + end end diff --git a/spec/requests/clicked_spec.rb b/spec/requests/clicked_spec.rb index 192cb50120..76da9ea4c1 100644 --- a/spec/requests/clicked_spec.rb +++ b/spec/requests/clicked_spec.rb @@ -26,7 +26,7 @@ v: @vertical, l: @locale, i: @model_id } - expect(response.success?).to be(true) + expect(response.successful?).to be(true) expect(response.body).to eq('') end @@ -68,7 +68,7 @@ end it 'returns success with a blank message body' do - expect(response.success?).to be(true) + expect(response.successful?).to be(true) expect(response.body).to eq('') end diff --git a/spec/support/sanitization_behavior.rb b/spec/support/sanitization_behavior.rb new file mode 100644 index 0000000000..ae4de24b2b --- /dev/null +++ b/spec/support/sanitization_behavior.rb @@ -0,0 +1,27 @@ +shared_examples_for 'a record that sanitizes attributes' do |attributes| + let(:unsanitized_string) { 'foo' } + let(:params) do + attributes.map{ |attribute| [attribute, unsanitized_string] }.to_h + end + let(:record) { described_class.new(params) } + + context 'before validation' do + before { record.validate } + + it 'sanitizes the HTML' do + attributes.each do |attribute| + expect(record.send(attribute)).to eq 'foo' + end + end + + context 'when the attribute contains encodable entities' do + let(:unsanitized_string) { 'foo & bar' } + + it 'encodes the entities' do + attributes.each do |attribute| + expect(record.send(attribute)).to eq 'foo & bar' + end + end + end + end +end