Make reads controller async and sanitize notification titles (#1158)

This commit is contained in:
Andy Zhao 2018-11-19 19:33:00 -05:00 committed by Ben Halpern
parent a1010201f2
commit 044de6a8b5
7 changed files with 67 additions and 54 deletions

View file

@ -11,5 +11,4 @@ class NotificationCounter
def set_to_zero def set_to_zero
@user.notifications.where(read?: false).update_all(read?: true) @user.notifications.where(read?: false).update_all(read?: true)
end end
handle_asynchronously :set_to_zero
end end

View file

@ -35,7 +35,7 @@
reacted to reacted to
<a href="<%= reactable_data["path"] %>" class="notification-comment-reacted-link"> <a href="<%= reactable_data["path"] %>" class="notification-comment-reacted-link">
<%# your article/comment or the actual title of the article/comment %> <%# your article/comment or the actual title of the article/comment %>
<%= reactable_data["title"].blank? ? "your #{reactable_data["class"]["name"].downcase}" : reactable_data["title"] %> <%= reactable_data["title"].blank? ? "your #{reactable_data["class"]["name"].downcase}" : sanitize(reactable_data["title"]) %>
</a> </a>
<span class="reaction-images"> <span class="reaction-images">
with with

View file

@ -15,7 +15,7 @@
<a href="<%= json_data["article"]["path"] %>"> <a href="<%= json_data["article"]["path"] %>">
<div class="notification-new-post"> <div class="notification-new-post">
<div class="notification-new-post-title"> <div class="notification-new-post-title">
<%= json_data["article"]["title"] %> <%= sanitize(json_data["article"]["title"]) %>
</div> </div>
<div class="notification-new-post-tags"> <div class="notification-new-post-tags">
<% json_data["article"]["cached_tag_list_array"].each do |tag| %> <% json_data["article"]["cached_tag_list_array"].each do |tag| %>

View file

@ -2,7 +2,7 @@
<div class="content notification-content badge-content"> <div class="content notification-content badge-content">
<% cache "activity-badge-reward-#{json_data["badge_achievement"]["badge_id"]}" do %> <% cache "activity-badge-reward-#{json_data["badge_achievement"]["badge_id"]}" do %>
<div class="badge-title"> <div class="badge-title">
You received the <strong><%= json_data["badge_achievement"]["badge"]["title"] %></strong> badge You received the <strong><%= sanitize(json_data["badge_achievement"]["badge"]["title"]) %></strong> badge
</div> </div>
<p class="badge-description"> <p class="badge-description">
<%= json_data["badge_achievement"]["badge"]["description"] %> <%= json_data["badge_achievement"]["badge"]["description"] %>

View file

@ -11,7 +11,7 @@
<a href="<%= json_data["user"]["path"] %>"><%= json_data["user"]["name"] %></a> <a href="<%= json_data["user"]["path"] %>"><%= json_data["user"]["name"] %></a>
commented on commented on
<a href="<%= json_data["comment"]["commentable"]["path"] %>"> <a href="<%= json_data["comment"]["commentable"]["path"] %>">
<%= json_data["comment"]["commentable"]["title"] %> <%= sanitize(json_data["comment"]["commentable"]["title"]) %>
</a> </a>
<%= render "notifications/shared/comment_box", json_data: json_data, notification: notification, context: "default" %> <%= render "notifications/shared/comment_box", json_data: json_data, notification: notification, context: "default" %>
<% elsif notification.action == "Moderation" %> <% elsif notification.action == "Moderation" %>
@ -20,7 +20,7 @@
<br/><br> <br/><br>
<b>Here's the article for context: <b>Here's the article for context:
<a href="<%= json_data["comment"]["commentable"]["path"] %>"> <a href="<%= json_data["comment"]["commentable"]["path"] %>">
<%= json_data["comment"]["commentable"]["title"] %> <%= sanitize(json_data["comment"]["commentable"]["title"]) %>
</a></b> </a></b>
<%= render "notifications/shared/comment_box", json_data: json_data, notification: notification, context: "moderation" %> <%= render "notifications/shared/comment_box", json_data: json_data, notification: notification, context: "moderation" %>
<br> <br>
@ -29,7 +29,7 @@
<a href="<%= json_data["user"]["path"] %>"><%= json_data["user"]["name"] %></a> <a href="<%= json_data["user"]["path"] %>"><%= json_data["user"]["name"] %></a>
wrote their first comment on: wrote their first comment on:
<a href="<%= json_data["comment"]["commentable"]["path"] %>"> <a href="<%= json_data["comment"]["commentable"]["path"] %>">
<%= json_data["comment"]["commentable"]["title"] %> <%= sanitize(json_data["comment"]["commentable"]["title"]) %>
</a> </a>
<%= render "notifications/activity/activities/shared/comment_box", activity: activity, context: "moderation" %> <%= render "notifications/activity/activities/shared/comment_box", activity: activity, context: "moderation" %>
Give them their first reply! 🎉 Give them their first reply! 🎉

View file

@ -27,7 +27,7 @@
<strong> <strong>
<a href="<%= first_notification.json_data["reaction"]["reactable"]["path"] %>" class="notification-comment-reacted-link"> <a href="<%= first_notification.json_data["reaction"]["reactable"]["path"] %>" class="notification-comment-reacted-link">
<%# title is blank when it's a comment with only an image, for example %> <%# title is blank when it's a comment with only an image, for example %>
<%= first_notification.json_data["reaction"]["reactable"]["title"].blank? ? "your #{first_notification.json_data["reaction"]["reactable_type"].downcase}" : first_notification.json_data["reaction"]["reactable"]["title"] %> <%= first_notification.json_data["reaction"]["reactable"]["title"].blank? ? "your #{first_notification.json_data["reaction"]["reactable_type"].downcase}" : sanitize(first_notification.json_data["reaction"]["reactable"]["title"]) %>
</a> </a>
</strong> </strong>
<span class="reaction-images"> <span class="reaction-images">

View file

@ -4,13 +4,13 @@ RSpec.describe "NotificationsIndex", type: :request do
let(:user) { create(:user) } let(:user) { create(:user) }
describe "GET notifications" do describe "GET notifications" do
xit "renders page with the proper heading" do it "renders page with the proper heading" do
get "/notifications" get "/notifications"
expect(response.body).to include("Notifications") expect(response.body).to include("Notifications")
end end
context "when signed out" do context "when signed out" do
xit "renders the signup cue" do it "renders the signup cue" do
get "/notifications" get "/notifications"
expect(response.body).to include "<div class=\"signup-cue" expect(response.body).to include "<div class=\"signup-cue"
end end
@ -19,7 +19,7 @@ RSpec.describe "NotificationsIndex", type: :request do
context "when signed in" do context "when signed in" do
before { sign_in user } before { sign_in user }
xit "does not render the signup cue" do it "does not render the signup cue" do
get "/notifications" get "/notifications"
expect(response.body).not_to include "Create your account" expect(response.body).not_to include "Create your account"
end end
@ -36,44 +36,44 @@ RSpec.describe "NotificationsIndex", type: :request do
follow_instances.each { |follow| Notification.send_new_follower_notification_without_delay(follow) } follow_instances.each { |follow| Notification.send_new_follower_notification_without_delay(follow) }
end end
xit "renders the proper message for a single notification" do it "renders the proper message for a single notification" do
mock_follow_notifications(1) mock_follow_notifications(1)
get "/notifications" get "/notifications"
follow_message = "#{User.last.name}</a> followed you!" follow_message = "#{User.last.name}</a> followed you!"
expect(response.body).to include follow_message expect(response.body).to include follow_message
end end
xit "renders the proper message for two notifications in the same day" do it "renders the proper message for two notifications in the same day" do
mock_follow_notifications(2) mock_follow_notifications(2)
get "/notifications" get "/notifications"
follow_message = "#{CGI.escapeHTML(User.last.name)}</a> and\n <a href=\"/#{CGI.escapeHTML(User.second_to_last.username)}\">#{CGI.escapeHTML(User.second_to_last.name)}</a> followed you!" follow_message = "#{CGI.escapeHTML(User.last.name)}</a> and\n <a href=\"/#{CGI.escapeHTML(User.second_to_last.username)}\">#{CGI.escapeHTML(User.second_to_last.name)}</a> followed you!"
expect(response.body).to include CGI.escapeHTML(follow_message) expect(response.body).to include CGI.escapeHTML(follow_message)
end end
xit "renders the proper message for three or more notifications in the same day" do it "renders the proper message for three or more notifications in the same day" do
mock_follow_notifications(rand(3..10)) mock_follow_notifications(rand(3..10))
get "/notifications" get "/notifications"
follow_message = "others followed you!" follow_message = "others followed you!"
expect(response.body).to include follow_message expect(response.body).to include follow_message
end end
xit "groups two notifications on the same day" do it "groups two notifications on the same day" do
mock_follow_notifications(2) mock_follow_notifications(2)
get "/notifications" get "/notifications"
notifications = controller.instance_variable_get(:@notifications) grouped_notifications = controller.instance_variable_get(:@notifications)[0].grouped_notifications
# only one notification object containing a group of notifications # only one notification object containing a group of notifications
expect(notifications.count).to eq 1 expect(grouped_notifications.count).to eq 2
end end
xit "groups three or more notifications on the same day" do it "groups three or more notifications on the same day" do
amount = rand(3..10) amount = rand(3..10)
mock_follow_notifications(amount) mock_follow_notifications(amount)
get "/notifications" get "/notifications"
notifications = controller.instance_variable_get(:@notifications) grouped_notifications = controller.instance_variable_get(:@notifications)[0].grouped_notifications
expect(notifications.count).to eq 1 expect(grouped_notifications.count).to eq amount
end end
xit "does not group notifications that occur on different days" do it "does not group notifications that occur on different days" do
mock_follow_notifications(2) mock_follow_notifications(2)
Notification.last.update(created_at: Notification.last.created_at - 1.day) Notification.last.update(created_at: Notification.last.created_at - 1.day)
get "/notifications" get "/notifications"
@ -83,8 +83,9 @@ RSpec.describe "NotificationsIndex", type: :request do
end end
context "when a user has new reaction notifications" do context "when a user has new reaction notifications" do
let(:article1) { create(:article, user_id: user.id) } let(:article1) { create(:article, user_id: user.id) }
let(:article2) { create(:article, user_id: user.id) } let(:article2) { create(:article, user_id: user.id) }
let(:special_characters_article) { create(:article, user_id: user.id, title: "Nothing like good ol' blah blah blah") }
before do before do
sign_in user sign_in user
@ -104,28 +105,28 @@ RSpec.describe "NotificationsIndex", type: :request do
reactions.each { |reaction| Notification.send_reaction_notification_without_delay(reaction) } reactions.each { |reaction| Notification.send_reaction_notification_without_delay(reaction) }
end end
xit "renders the proper message for a single public reaction" do it "renders the proper message for a single public reaction" do
mock_heart_reaction_notifications(1, %w(like unicorn)) mock_heart_reaction_notifications(1, %w(like unicorn))
get "/notifications" get "/notifications"
message = "#{CGI.escapeHTML(User.last.name)}</strong></a> reacted to" message = "#{CGI.escapeHTML(User.last.name)}</strong></a> reacted to"
expect(response.body).to include message expect(response.body).to include message
end end
xit "renders the proper message for a single private reaction" do it "renders the proper message for a single private reaction" do
mock_heart_reaction_notifications(1, %w(readinglist)) mock_heart_reaction_notifications(1, %w(readinglist))
get "/notifications" get "/notifications"
message = "Someone reacted to" message = "Someone reacted to"
expect(response.body).to include message expect(response.body).to include message
end end
xit "renders the proper message for two or more public reactions" do it "renders the proper message for two or more public reactions" do
mock_heart_reaction_notifications(2, %w(like unicorn)) mock_heart_reaction_notifications(2, %w(like unicorn))
get "/notifications" get "/notifications"
message = "#{User.last.name}</a> and <a href=\"/#{CGI.escapeHTML(User.second_to_last.username)}\">#{CGI.escapeHTML(User.second_to_last.name)}</a>\n reacted to" message = "#{User.last.name}</a> and <a href=\"/#{CGI.escapeHTML(User.second_to_last.username)}\">#{CGI.escapeHTML(User.second_to_last.name)}</a>\n reacted to"
expect(response.body).to include CGI.escapeHTML(message) expect(response.body).to include CGI.escapeHTML(message)
end end
xit "renders the proper message for two or more reactions where at least one is private" do it "renders the proper message for two or more reactions where at least one is private" do
mock_heart_reaction_notifications(1, %w(readinglist)) mock_heart_reaction_notifications(1, %w(readinglist))
mock_heart_reaction_notifications(1, %w(unicorn like)) mock_heart_reaction_notifications(1, %w(unicorn like))
get "/notifications" get "/notifications"
@ -133,29 +134,29 @@ RSpec.describe "NotificationsIndex", type: :request do
expect(response.body).to include CGI.escapeHTML(message) expect(response.body).to include CGI.escapeHTML(message)
end end
xit "renders the proper message for multiple public reactions" do it "renders the proper message for multiple public reactions" do
mock_heart_reaction_notifications(3, %w(unicorn like)) mock_heart_reaction_notifications(3, %w(unicorn like))
get "/notifications" get "/notifications"
message = "#{User.last.name}</a> and 2 others\n reacted to" message = "#{User.last.name}</a> and 2 others\n reacted to"
expect(response.body).to include CGI.escapeHTML(message) expect(response.body).to include CGI.escapeHTML(message)
end end
xit "properly groups two notifications that have the same day and reactable" do it "properly groups two notifications that have the same day and reactable" do
mock_heart_reaction_notifications(2, %w(unicorn like readinglist)) mock_heart_reaction_notifications(2, %w(unicorn like readinglist))
get "/notifications" get "/notifications"
notifications = controller.instance_variable_get(:@notifications) grouped_notifications = controller.instance_variable_get(:@notifications)[0].grouped_notifications
expect(notifications.count).to eq 1 expect(grouped_notifications.count).to eq 2
end end
xit "properly groups three or more notifications that have the same day and reactable" do it "properly groups three or more notifications that have the same day and reactable" do
amount = rand(3..10) amount = rand(3..10)
mock_heart_reaction_notifications(amount, %w(unicorn like readinglist)) mock_heart_reaction_notifications(amount, %w(unicorn like readinglist))
get "/notifications" get "/notifications"
notifications = controller.instance_variable_get(:@notifications) grouped_notifications = controller.instance_variable_get(:@notifications)[0].grouped_notifications
expect(notifications.count).to eq 1 expect(grouped_notifications.count).to eq amount
end end
xit "does not group notifications that are on different days but same reactable" do it "does not group notifications that are on different days but have the same reactable" do
mock_heart_reaction_notifications(2, %w(unicorn like readinglist)) mock_heart_reaction_notifications(2, %w(unicorn like readinglist))
Notification.last.update(created_at: Notification.last.created_at - 1.day) Notification.last.update(created_at: Notification.last.created_at - 1.day)
get "/notifications" get "/notifications"
@ -163,13 +164,26 @@ RSpec.describe "NotificationsIndex", type: :request do
expect(notifications.count).to eq 2 expect(notifications.count).to eq 2
end end
xit "does not group notifications that are on the same day but different reactables" do it "does not group notifications that are on the same day but have different reactables" do
mock_heart_reaction_notifications(1, %w(unicorn like readinglist), article1) mock_heart_reaction_notifications(1, %w(unicorn like readinglist), article1)
mock_heart_reaction_notifications(1, %w(unicorn like readinglist), article2) mock_heart_reaction_notifications(1, %w(unicorn like readinglist), article2)
get "/notifications" get "/notifications"
notifications = controller.instance_variable_get(:@notifications) notifications = controller.instance_variable_get(:@notifications)
expect(notifications.count).to eq 2 expect(notifications.count).to eq 2
end end
it "properly renders reactable titles", focus: true do
mock_heart_reaction_notifications(1, %w(unicorn like readinglist), special_characters_article)
get "/notifications"
expect(response.body).to include special_characters_article.title
end
it "properly renders reactable titles for multiple reactions", focus: true do
amount = rand(3..10)
mock_heart_reaction_notifications(amount, %w(unicorn like readinglist), special_characters_article)
get "/notifications"
expect(response.body).to include special_characters_article.title
end
end end
context "when a user has a new comment notification" do context "when a user has a new comment notification" do
@ -183,19 +197,19 @@ RSpec.describe "NotificationsIndex", type: :request do
get "/notifications" get "/notifications"
end end
xit "renders the correct message" do it "renders the correct message" do
expect(response.body).to include "commented on" expect(response.body).to include "commented on"
end end
xit "does not render the moderation message" do it "does not render the moderation message" do
expect(response.body).not_to include "As a trusted member" expect(response.body).not_to include "As a trusted member"
end end
xit "renders the original article's title" do it "renders the original article's title" do
expect(response.body).to include article.title expect(response.body).to include CGI.escapeHTML(article.title)
end end
xit "renders the comment's processed HTML" do it "renders the comment's processed HTML" do
expect(response.body).to include CGI.escapeHTML(comment.processed_html) expect(response.body).to include CGI.escapeHTML(comment.processed_html)
end end
end end
@ -213,15 +227,15 @@ RSpec.describe "NotificationsIndex", type: :request do
get "/notifications" get "/notifications"
end end
xit "renders the proper message" do it "renders the proper message" do
expect(response.body).to include "As a trusted member" expect(response.body).to include "As a trusted member"
end end
xit "renders the article's title" do it "renders the article's title" do
expect(response.body).to include CGI.escapeHTML(article.title) expect(response.body).to include CGI.escapeHTML(article.title)
end end
xit "renders the comment's processed HTML" do it "renders the comment's processed HTML" do
expect(response.body).to include CGI.escapeHTML(comment.processed_html) expect(response.body).to include CGI.escapeHTML(comment.processed_html)
end end
end end
@ -232,7 +246,7 @@ RSpec.describe "NotificationsIndex", type: :request do
sign_in user sign_in user
end end
xit "renders the welcome notification" do it "renders the welcome notification" do
broadcast = create(:broadcast, :onboarding) broadcast = create(:broadcast, :onboarding)
Notification.send_welcome_notification_without_delay(user.id) Notification.send_welcome_notification_without_delay(user.id)
get "/notifications" get "/notifications"
@ -249,20 +263,20 @@ RSpec.describe "NotificationsIndex", type: :request do
get "/notifications" get "/notifications"
end end
xit "renders the proper message with the badge's title" do it "renders the proper message with the badge's title" do
message = "You received the <strong>#{Badge.first.title}" message = "You received the <strong>#{Badge.first.title}"
expect(response.body).to include CGI.escapeHTML(message) expect(response.body).to include CGI.escapeHTML(message)
end end
xit "renders the rewarding context message" do it "renders the rewarding context message" do
expect(response.body).to include CGI.escapeHTML(user.badge_achievements.first.rewarding_context_message) expect(response.body).to include CGI.escapeHTML(user.badge_achievements.first.rewarding_context_message)
end end
xit "renders the badge's description" do it "renders the badge's description" do
expect(response.body).to include CGI.escapeHTML(Badge.first.description) expect(response.body).to include CGI.escapeHTML(Badge.first.description)
end end
xit "renders the CHECK YOUR PROFILE button" do it "renders the CHECK YOUR PROFILE button" do
expect(response.body).to include "CHECK YOUR PROFILE" expect(response.body).to include "CHECK YOUR PROFILE"
end end
end end
@ -289,11 +303,11 @@ RSpec.describe "NotificationsIndex", type: :request do
get "/notifications" get "/notifications"
end end
xit "renders the proper message" do it "renders the proper message" do
expect(response.body).to include "mentioned you in a comment" expect(response.body).to include "mentioned you in a comment"
end end
xit "renders the processed HTML of the comment where they were mentioned" do it "renders the processed HTML of the comment where they were mentioned" do
expect(response.body).to include CGI.escapeHTML(comment.processed_html) expect(response.body).to include CGI.escapeHTML(comment.processed_html)
end end
end end
@ -309,15 +323,15 @@ RSpec.describe "NotificationsIndex", type: :request do
get "/notifications" get "/notifications"
end end
xit "renders the proper message" do it "renders the proper message" do
expect(response.body).to include "made a new post:" expect(response.body).to include "made a new post:"
end end
xit "renders the article's title" do it "renders the article's title" do
expect(response.body).to include CGI.escapeHTML(article.title) expect(response.body).to include CGI.escapeHTML(article.title)
end end
xit "renders the author's name" do it "renders the author's name" do
expect(response.body).to include CGI.escapeHTML(article.user.name) expect(response.body).to include CGI.escapeHTML(article.user.name)
end end
end end