Ensure spam doesn't hit logged-out /latest (#19110)

* Ensure spam doesn't hit logged-out /latest

* Restore load-bearing constant

* Narrow selector avoids the new 'latest' banner

* Test scenario for signed-in low-score content

* Remove false-negative test

* Actual test for the logged-in scenario

* Signed-in users get poor quality content

* Feeds (RSS) use an entirely different query

* Pass @stories

* Update tests with new minimum score
This commit is contained in:
Joshua Wehner 2023-02-17 09:29:59 +01:00 committed by GitHub
parent f14b0868f8
commit 1374ab98e6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 83 additions and 10 deletions

View file

@ -231,7 +231,7 @@ class StoriesController < ApplicationController
if params[:timeframe].in?(Timeframe::FILTER_TIMEFRAMES)
@stories = Articles::Feeds::Timeframe.call(params[:timeframe])
elsif params[:timeframe] == Timeframe::LATEST_TIMEFRAME
@stories = Articles::Feeds::Latest.call
@stories = Articles::Feeds::Latest.call(minimum_score: Settings::UserExperience.home_feed_minimum_score)
else
@default_home_feed = true
feed = Articles::Feeds::LargeForemExperimental.new(page: @page, tag: params[:tag])

View file

@ -1,4 +1,11 @@
module ArticlesHelper
def should_show_latest_spam_suppression?(stories)
return false if user_signed_in?
return false unless stories.size > 1
params[:timeframe] == Timeframe::LATEST_TIMEFRAME
end
def sort_options
[
[I18n.t("helpers.articles_helper.recently_created"), "creation-desc"],

View file

@ -3,10 +3,13 @@ module Articles
module Latest
MINIMUM_SCORE = -20
def self.call(tag: nil, number_of_articles: Article::DEFAULT_FEED_PAGINATION_WINDOW_SIZE, page: 1)
def self.call(tag: nil, number_of_articles: nil, page: 1, minimum_score: nil)
number_of_articles ||= Article::DEFAULT_FEED_PAGINATION_WINDOW_SIZE
minimum_score ||= MINIMUM_SCORE
Articles::Feeds::Tag.call(tag)
.order(published_at: :desc)
.where("score > ?", MINIMUM_SCORE)
.where("score > ?", minimum_score)
.page(page)
.per(number_of_articles)
end

View file

@ -1,3 +1,12 @@
<% if should_show_latest_spam_suppression?(@stories) %>
<div class="crayons-story">
<p class="crayons-story__body">
<%= t "views.articles.sign_in_for_full_latest_html",
link: sign_up_path(state: "new-user") %>
</p>
</div>
<% end %>
<% if @pinned_article %>
<%= render partial: "articles/single_story", locals: { story: @pinned_article, pinned: true, featured: @pinned_article.id == @featured_story.id } %>
<% end %>

View file

@ -105,6 +105,7 @@ en:
missing: Article No Longer Available
published_html: Posted on %{date}
scheduled_html: Scheduled for %{date}
sign_in_for_full_latest_html: Some latest posts are only visible for members. <a href="%{link}">Sign in</a> to see all latest.
read_next: Read next
reading_time:
one: "1 min read"

View file

@ -105,6 +105,7 @@ fr:
missing: Article No Longer Available
published_html: Posted on %{date}
scheduled_html: Scheduled for %{date}
sign_in_for_full_latest_html: Some latest posts are only visible for members. <a href="%{link}">Sign in</a> to see all latest.
read_next: Lire ensuite
reading_time:
one: "1 min read"

View file

@ -191,12 +191,19 @@ RSpec.describe "Articles" do
let!(:article_with_low_score) do
create(:article, score: Articles::Feeds::Latest::MINIMUM_SCORE)
end
let!(:article_with_mid_score) do
minimum = Articles::Feeds::Latest::MINIMUM_SCORE
maximum = Settings::UserExperience.home_feed_minimum_score
score = (minimum..maximum).to_a.sample
create(:article, score: score)
end
before { get "/feed/latest" }
it "contains latest articles" do
expect(response.body).to include(last_article.title)
expect(response.body).to include(not_featured_article.title)
expect(response.body).to include(article_with_mid_score.title)
expect(response.body).not_to include(article_with_low_score.title)
end
end

View file

@ -199,5 +199,25 @@ RSpec.describe "Stories::Feeds" do
expect(response_article["top_comments"].first["username"]).not_to be_nil
end
end
context "when there are low-scoring articles" do
let!(:article) { create(:article, featured: false) }
let!(:article_with_mid_score) do
minimum = Articles::Feeds::Latest::MINIMUM_SCORE
maximum = Settings::UserExperience.home_feed_minimum_score
score = (minimum..maximum).to_a.sample
create(:article, score: score)
end
let!(:article_with_low_score) do
create(:article, score: Articles::Feeds::Latest::MINIMUM_SCORE)
end
it "excludes low-score article but not mid-score" do
get timeframe_stories_feed_path(:latest)
expect(response.body).to include(article.title)
expect(response.body).to include(article_with_mid_score.title)
expect(response.body).not_to include(article_with_low_score.title)
end
end
end
end

View file

@ -289,6 +289,13 @@ RSpec.describe "StoriesIndex" do
describe "GET stories index with timeframe" do
describe "/latest" do
let(:user) { create(:user) }
let!(:low_score) { create(:article, score: -10) }
before do
create_list(:article, 3, score: Settings::UserExperience.home_feed_minimum_score + 1)
end
it "includes a link to Relevant", :aggregate_failures do
get "/latest"
@ -296,6 +303,17 @@ RSpec.describe "StoriesIndex" do
expected_tag = "<a data-text=\"Relevant\" href=\"/\""
expect(response.body).to include(expected_tag)
end
it "includes message and a link to sign in for signed-out" do
get "/latest"
expect(response.body).to include("Some latest posts are only visible for members")
expect(response.body).to match(/Sign in.*to see all latest/)
end
it "excludes low-score content for signed-out" do
get "/latest"
expect(response.body).not_to include(low_score.title)
end
end
describe "/top/week" do

View file

@ -1,15 +1,22 @@
require "rails_helper"
RSpec.describe "User visits articles by timeframe", type: :system do
RSpec.describe "User visits articles by timeframe" do
let(:author) { create(:user) }
let!(:article) { create(:article, user: author, published_at: Time.current) }
let!(:days_old_article) { create(:article, :past, past_published_at: 2.days.ago, user: author) }
let!(:weeks_old_article) { create(:article, :past, past_published_at: 2.weeks.ago, user: author) }
let!(:months_old_article) { create(:article, :past, past_published_at: 2.months.ago, user: author) }
let!(:years_old_article) { create(:article, :past, past_published_at: 2.years.ago, user: author) }
let(:minimum_score) { Settings::UserExperience.home_feed_minimum_score + 1 }
let!(:article) { create(:article, score: minimum_score, user: author, published_at: Time.current) }
let!(:days_old_article) { create(:article, :past, past_published_at: 2.days.ago, score: minimum_score, user: author) }
let!(:weeks_old_article) do
create(:article, :past, past_published_at: 2.weeks.ago, score: minimum_score, user: author)
end
let!(:months_old_article) do
create(:article, :past, past_published_at: 2.months.ago, score: minimum_score, user: author)
end
let!(:years_old_article) do
create(:article, :past, past_published_at: 2.years.ago, score: minimum_score, user: author)
end
def shows_correct_articles_count(count)
expect(page).to have_selector(".crayons-story", visible: :visible, count: count)
expect(page).to have_selector(".crayons-story__title", visible: :visible, count: count)
end
def shows_correct_articles_count_via_xpath(count)