FEATURE: simpler and friendlier unsubscribe workflow

- All unsubscribes go to the exact same page
- You may unsubscribe from watching a category on that page
- You no longer need to be logged in to unsubscribe from a topic
- Simplified footer on emails
This commit is contained in:
Sam 2016-06-17 11:27:52 +10:00
parent 78818551ef
commit 852860de66
18 changed files with 499 additions and 234 deletions

View file

@ -1,45 +1,123 @@
class EmailController < ApplicationController class EmailController < ApplicationController
skip_before_filter :check_xhr, :preload_json skip_before_filter :check_xhr, :preload_json, :redirect_to_login_if_required
layout 'no_ember' layout 'no_ember'
before_filter :ensure_logged_in, only: :preferences_redirect before_filter :ensure_logged_in, only: :preferences_redirect
skip_before_filter :redirect_to_login_if_required
def preferences_redirect def preferences_redirect
redirect_to(email_preferences_path(current_user.username_lower)) redirect_to(email_preferences_path(current_user.username_lower))
end end
def unsubscribe def unsubscribe
@user = DigestUnsubscribeKey.user_for_key(params[:key]) key = UnsubscribeKey.find_by(key: params[:key])
RateLimiter.new(@user, "unsubscribe_via_email", 3, 1.day).performed! unless @user && @user.staff?
if key
@user = key.user
post = key.post
@topic = (post && post.topic) || key.topic
@type = key.unsubscribe_key_type
# Don't allow the use of a key while logged in as a different user
if current_user.present? && (@user != current_user) if current_user.present? && (@user != current_user)
@different_user = true @different_user = @user.name
return @return_url = request.original_url
end
@watching_topic = @topic && TopicUser.exists?(user_id: @user.id,
notification_level: TopicUser.notification_levels[:watching],
topic_id: @topic.id)
@watched_count = nil
if @topic && @topic.category_id
if CategoryUser.exists?(user_id: @user.id,
notification_level: CategoryUser.notification_levels[:watching],
category_id: @topic.category_id)
@watched_count = TopicUser.joins(:topic)
.where(:user => @user,
:notification_level => TopicUser.notification_levels[:watching],
"topics.category_id" => @topic.category_id
).count
end
end
end end
if @user.blank? if @user.blank?
@not_found = true @not_found = true
return end
end end
if params[:from_all] def perform_unsubscribe
@user.user_option.update_columns(email_always: false,
key = UnsubscribeKey.find_by(key: params[:key])
unless key && key.user
raise Discourse::NotFound
end
topic = (key.post && key.post.topic) || key.topic
user = key.user
updated = false
if topic
if params["unwatch_topic"]
TopicUser.where(topic_id: topic.id, user_id: user.id)
.update_all(notification_level: TopicUser.notification_levels[:tracking])
updated = true
end
if params["unwatch_category"] && topic.category_id
TopicUser.joins(:topic)
.where(:user => user,
:notification_level => TopicUser.notification_levels[:watching],
"topics.category_id" => topic.category_id)
.update_all(notification_level: TopicUser.notification_levels[:tracking])
CategoryUser.where(user_id: user.id,
category_id: topic.category_id,
notification_level: CategoryUser.notification_levels[:watching]
)
.destroy_all
updated = true
end
if params["mute_topic"]
TopicUser.where(topic_id: topic.id, user_id: user.id)
.update_all(notification_level: TopicUser.notification_levels[:muted])
updated = true
end
end
if params["disable_mailing_list"]
user.user_option.update_columns(email_always: false)
updated = true
end
if params["disable_digest_emails"]
user.user_option.update_columns(email_digests: false)
updated = true
end
if params["unsubscribe_all"]
user.user_option.update_columns(email_always: false,
email_digests: false, email_digests: false,
email_direct: false, email_direct: false,
email_private_messages: false) email_private_messages: false)
updated = true
end
unless updated
redirect_to :back
else else
@user.user_option.update_column(:email_digests, false) if topic
redirect_to path("/email/unsubscribed?topic_id=#{topic.id}")
else
redirect_to path("/email/unsubscribed")
end end
end
@success = true
end end
def resubscribe def unsubscribed
@user = DigestUnsubscribeKey.user_for_key(params[:key]) @topic = Topic.find_by(id: params[:topic_id].to_i) if params[:topic_id]
raise Discourse::NotFound unless @user.present?
@user.user_option.update_column(:email_digests, true)
end end
end end

View file

@ -4,7 +4,7 @@ require_dependency 'single_sign_on'
class SessionController < ApplicationController class SessionController < ApplicationController
skip_before_filter :redirect_to_login_if_required skip_before_filter :redirect_to_login_if_required
skip_before_filter :preload_json, :check_xhr, only: ['sso', 'sso_login', 'become', 'sso_provider'] skip_before_filter :preload_json, :check_xhr, only: ['sso', 'sso_login', 'become', 'sso_provider', 'destroy']
def csrf def csrf
render json: {csrf: form_authenticity_token } render json: {csrf: form_authenticity_token }
@ -237,7 +237,11 @@ class SessionController < ApplicationController
def destroy def destroy
reset_session reset_session
log_off_user log_off_user
if request.xhr?
render nothing: true render nothing: true
else
redirect_to (params[:return_url] || path("/"))
end
end end
private private

View file

@ -1,13 +0,0 @@
module Jobs
class CleanUpDigestKeys < Jobs::Scheduled
every 1.day
def execute(args)
DigestUnsubscribeKey.where('created_at < ?', 2.months.ago).delete_all
end
end
end

View file

@ -0,0 +1,13 @@
module Jobs
class CleanUpUnsubscribeKeys < Jobs::Scheduled
every 1.day
def execute(args)
UnsubscribeKey.where('created_at < ?', 2.months.ago).delete_all
end
end
end

View file

@ -4,11 +4,11 @@ class SubscriptionMailer < ActionMailer::Base
include Email::BuildEmailHelper include Email::BuildEmailHelper
def confirm_unsubscribe(user, opts={}) def confirm_unsubscribe(user, opts={})
unsubscribe_key = DigestUnsubscribeKey.create_key_for(user) unsubscribe_key = UnsubscribeKey.create_key_for(user, "all")
build_email user.email, build_email user.email,
template: "unsubscribe_mailer", template: "unsubscribe_mailer",
site_title: SiteSetting.title, site_title: SiteSetting.title,
site_domain_name: Discourse.current_hostname, site_domain_name: Discourse.current_hostname,
confirm_unsubscribe_link: "#{Discourse.base_url}/unsubscribe/#{unsubscribe_key}?from_all=true" confirm_unsubscribe_link: "#{Discourse.base_url}/unsubscribe/#{unsubscribe_key}"
end end
end end

View file

@ -382,7 +382,7 @@ class UserNotifications < ActionMailer::Base
username: username, username: username,
add_unsubscribe_link: !user.staged, add_unsubscribe_link: !user.staged,
mailing_list_mode: user.user_option.mailing_list_mode, mailing_list_mode: user.user_option.mailing_list_mode,
unsubscribe_url: post.topic.unsubscribe_url, unsubscribe_url: post.unsubscribe_url(user),
allow_reply_by_email: allow_reply_by_email, allow_reply_by_email: allow_reply_by_email,
only_reply_by_email: allow_reply_by_email && user.staged, only_reply_by_email: allow_reply_by_email && user.staged,
use_site_subject: use_site_subject, use_site_subject: use_site_subject,
@ -416,6 +416,6 @@ class UserNotifications < ActionMailer::Base
@header_color = ColorScheme.hex_for_name('header_background') @header_color = ColorScheme.hex_for_name('header_background')
@anchor_color = ColorScheme.hex_for_name('tertiary') @anchor_color = ColorScheme.hex_for_name('tertiary')
@markdown_linker = MarkdownLinker.new(@base_url) @markdown_linker = MarkdownLinker.new(@base_url)
@unsubscribe_key = DigestUnsubscribeKey.create_key_for(@user) @unsubscribe_key = UnsubscribeKey.create_key_for(@user, "digest")
end end
end end

View file

@ -1,33 +0,0 @@
class DigestUnsubscribeKey < ActiveRecord::Base
belongs_to :user
before_create :generate_random_key
def self.create_key_for(user)
DigestUnsubscribeKey.create(user_id: user.id).key
end
def self.user_for_key(key)
where(key: key).first.try(:user)
end
private
def generate_random_key
self.key = SecureRandom.hex(32)
end
end
# == Schema Information
#
# Table name: digest_unsubscribe_keys
#
# key :string(64) not null, primary key
# user_id :integer not null
# created_at :datetime
# updated_at :datetime
#
# Indexes
#
# index_digest_unsubscribe_keys_on_created_at (created_at)
#

View file

@ -385,6 +385,10 @@ class Post < ActiveRecord::Base
end end
end end
def unsubscribe_url(user)
"#{Discourse.base_url}/email/unsubscribe/#{UnsubscribeKey.create_key_for(user, self)}"
end
def self.url(slug, topic_id, post_number) def self.url(slug, topic_id, post_number)
"/t/#{slug}/#{topic_id}/#{post_number}" "/t/#{slug}/#{topic_id}/#{post_number}"
end end

View file

@ -0,0 +1,41 @@
class UnsubscribeKey < ActiveRecord::Base
belongs_to :user
belongs_to :post
belongs_to :topic
before_create :generate_random_key
def self.create_key_for(user, type)
if Post === type
create(user_id: user.id, unsubscribe_key_type: "topic", topic_id: type.topic_id, post_id: type.id).key
else
create(user_id: user.id, unsubscribe_key_type: type).key
end
end
def self.user_for_key(key)
where(key: key).first.try(:user)
end
private
def generate_random_key
self.key = SecureRandom.hex(32)
end
end
# == Schema Information
#
# Table name: unsubscribe_keys
#
# key :string(64) not null, primary key
# user_id :integer not null
# created_at :datetime
# updated_at :datetime
# unsubscribe_key_type :string
# topic_id :integer
#
# Indexes
#
# index_unsubscribe_keys_on_created_at (created_at)
#

View file

@ -1,28 +1,79 @@
<div class='container'> <div class='container unsubscribe'>
<%- if @not_found || @different_user %>
<%- if @success %> <%if @not_found%>
<h2><%= t :'unsubscribed.title' %></h2> <p><%= t "unsubscribe.not_found_description" %></p>
<br/> <%- else %>
<p><%= t("unsubscribe.different_user_description").html_safe %></p>
<p><%= t :'unsubscribed.description' %></p> <%= form_tag(session_path(id: current_user.username_lower), method: :delete) do %>
<%= hidden_field_tag(:return_url, @return_url) %>
<p><%= t :'unsubscribed.oops' %></p> <%= submit_tag t('unsubscribe.log_out'), class: 'btn btn-danger' %>
<%- end%>
<%= form_tag(email_resubscribe_path(key: params[:key])) do %> <%- end %>
<%= submit_tag t(:'resubscribe.action'), class: 'btn btn-danger' %>
<% end %>
<%- else %> <%- else %>
<h2><%= t :'unsubscribed.error' %></h2>
<br/> <br/>
<%- if @different_user %> <h2><%= t 'unsubscribe.title'%></h2>
<p><%= t :'unsubscribed.different_user_description' %></p> <br/>
<%= form_tag(email_perform_unsubscribe_path(key: params[:key])) do %>
<%if @topic %>
<% if @watching_topic %>
<p>
<label>
<%= check_box_tag 'unwatch_topic', 1, true %>
<%= t('unsubscribe.stop_watching_topic', link: render_topic_title(@topic)).html_safe %>
</label>
</p>
<% else %>
<p>
<label>
<%= check_box_tag 'mute_topic', 1, true %>
<%= t('unsubscribe.mute_topic', link: render_topic_title(@topic)).html_safe %>
</label>
</p>
<% end %>
<% if @watched_count %>
<p>
<label>
<%= check_box_tag 'unwatch_category' %>
<%= t('unsubscribe.unwatch_category', category: category_badge(@topic.category)).html_safe %>
</label>
</p>
<% end %>
<% end %>
<% if @user.user_option.email_always && !SiteSetting.disable_mailing_list_mode %>
<p>
<label>
<%= check_box_tag 'disable_mailing_list' %>
<%= t 'unsubscribe.mailing_list_mode' %>
</label>
</p>
<% end %>
<% if !@topic %>
<% unless SiteSetting.disable_digest_emails %>
<p>
<label>
<%= check_box_tag 'disable_digest_emails', 1, @type=="digest" %>
<%= t 'unsubscribe.disable_digest_emails' %>
</label>
</p>
<% end %>
<% end %>
<p>
<label>
<%= check_box_tag 'unsubscribe_all', 1, @type=="all" %>
<%= t 'unsubscribe.all', sitename: SiteSetting.title %>
</label>
</p>
<br/>
<%= submit_tag t('unsubscribe.title'), class: 'btn btn-danger' %>
<%- end %> <%- end %>
<%- if @not_found %>
<p><%= t :'unsubscribed.not_found_description' %></p>
<%- end %>
<p><%=raw(t :'unsubscribed.preferences_link') %></p>
<%- end %> <%- end %>

View file

@ -0,0 +1,14 @@
<div class='container unsubscribe'>
<br>
<h2><%=t "unsubscribed.title"%></h2>
<br>
<p>
<%=t("unsubscribed.description", url: path("/my/prefrences")).html_safe %>
</p>
<% if @topic %>
<p>
<%=t("unsubscribed.topic_description", link: render_topic_title(@topic)).html_safe%>
</p>
<% end %>
</div>

View file

@ -621,18 +621,21 @@ en:
remove: "This topic is no longer a banner. It will no longer appear at the top of every page." remove: "This topic is no longer a banner. It will no longer appear at the top of every page."
unsubscribed: unsubscribed:
title: 'Unsubscribed' title: "Unsubscribed!"
description: "You have been unsubscribed. We won't contact you again!" description: "You have been unsubscribed. If you wish to change your email settings visit your <a href='%{url}'>user preferences</a>."
oops: "In case you didn't mean to do this, click below." topic_description: "To re-subscribe to %{link}, use the notification control at the bottom or right of the topic."
error: "Error Unsubscribing"
preferences_link: "You can also unsubscribe from summary emails on your <a href='/my/preferences'>preferences page</a>"
different_user_description: "You are currently logged in as a different user than the one who the summary was mailed to. Please log out and try again."
not_found_description: "Sorry, we couldn't unsubscribe you. It's possible the link in your email has expired."
resubscribe: unsubscribe:
action: "Re-Subscribe" title: "Unsubscribe"
title: "Re-Subscribed!" stop_watching_topic: "Stop watching this topic, %{link}"
description: "You have been re-subscribed." mute_topic: "Mute all notifications for this topic, %{link}"
unwatch_category: "Stop watching any topics in %{category}"
mailing_list_mode: "Disable mailing list mode"
disable_digest_emails: "Stop sending me digest emails"
all: "Don't send me any mail from %{sitename}"
different_user_description: "You are currently logged in as a different user than the one we emailed. Please log out and try again."
not_found_description: "Sorry, we couldn't find the unsubscription. It's possible the link in your email has expired."
log_out: "Log Out"
reports: reports:
visits: visits:
@ -2164,19 +2167,19 @@ en:
[Please review and fix them](%{base_url}/admin). [Please review and fix them](%{base_url}/admin).
unsubscribe_link: | unsubscribe_link: |
To stop receiving notifications for this particular topic, [click here](%{unsubscribe_url}). To unsubscribe from these emails, [click here](%{unsubscribe_url}).
To unsubscribe from these emails, change your [user preferences](%{user_preferences_url}).
unsubscribe_link_and_mail: | unsubscribe_link_and_mail: |
To stop receiving notifications for this particular topic, [click here](%{unsubscribe_url}). To unsubscribe from these emails, [click here](%{unsubscribe_url}).
To unsubscribe from these emails, change your [user preferences](%{user_preferences_url}) or [click here](mailto:reply@%{hostname}?subject=unsubscribe) to unsubscribe via email. Alternarively you may [click here](mailto:reply@%{hostname}?subject=unsubscribe) to unsubscribe via email.
unsubscribe_mailing_list: | unsubscribe_mailing_list: |
You have mailing list mode enabled. To stop receiving notifications for this particular topic, [click here](%{unsubscribe_url}). You are receiving these emails cause you have enabled mailing list mode.
To unsubscribe from these emails, change your [user preferences](%{user_preferences_url}) or [click here](mailto:reply@%{hostname}?subject=unsubscribe) to unsubscribe via email. To unsubscribe from these emails, [click here](%{unsubscribe_url}).
Alternatively you may [click here](mailto:reply@%{hostname}?subject=unsubscribe) to unsubscribe via email.
subject_re: "Re: " subject_re: "Re: "
subject_pm: "[PM] " subject_pm: "[PM] "

View file

@ -235,8 +235,10 @@ Discourse::Application.routes.draw do
end # admin namespace end # admin namespace
get "email_preferences" => "email#preferences_redirect", :as => "email_preferences_redirect" get "email_preferences" => "email#preferences_redirect", :as => "email_preferences_redirect"
get "email/unsubscribe/:key" => "email#unsubscribe", as: "email_unsubscribe" get "email/unsubscribe/:key" => "email#unsubscribe", as: "email_unsubscribe"
post "email/resubscribe/:key" => "email#resubscribe", as: "email_resubscribe" get "email/unsubscribed" => "email#unsubscribed", as: "email_unsubscribed"
post "email/unsubscribe/:key" => "email#perform_unsubscribe", as: "email_perform_unsubscribe"
resources :session, id: USERNAME_ROUTE_FORMAT, only: [:create, :destroy, :become] do resources :session, id: USERNAME_ROUTE_FORMAT, only: [:create, :destroy, :become] do
get 'become' get 'become'

View file

@ -0,0 +1,19 @@
class RenameDigestUnsbscribeKeys < ActiveRecord::Migration
def up
rename_table :digest_unsubscribe_keys, :unsubscribe_keys
add_column :unsubscribe_keys, :unsubscribe_key_type, :string
add_column :unsubscribe_keys, :topic_id, :int
add_column :unsubscribe_keys, :post_id, :int
execute "UPDATE unsubscribe_keys SET unsubscribe_key_type = 'digest' WHERE unsubscribe_key_type IS NULL"
end
def down
remove_column :unsubscribe_keys, :unsubscribe_key_type
remove_column :unsubscribe_keys, :topic_id
remove_column :unsubscribe_keys, :post_id
rename_table :unsubscribe_keys, :digest_unsubscribe_keys
end
end

View file

@ -187,8 +187,8 @@ describe Email::MessageBuilder do
expect(message_with_unsubscribe.header_args['List-Unsubscribe']).to be_present expect(message_with_unsubscribe.header_args['List-Unsubscribe']).to be_present
end end
it "has the user preferences url in the body" do it "has the unsubscribe url in the body" do
expect(message_with_unsubscribe.body).to match(builder.template_args[:user_preferences_url]) expect(message_with_unsubscribe.body).to match('/t/1234/unsubscribe')
end end
it "can add an unsubscribe via email link" do it "can add an unsubscribe via email link" do

View file

@ -19,117 +19,186 @@ describe EmailController do
end end
context '.resubscribe' do context '.perform unsubscribe' do
it 'raises not found on invalid key' do
post :perform_unsubscribe, key: "123"
expect(response.status).to eq(404)
end
let(:user) { it 'can fully unsubscribe' do
user = Fabricate(:user) user = Fabricate(:user)
user.user_option.update_columns(email_digests: false) key = UnsubscribeKey.create_key_for(user, "all")
user
} user.user_option.update_columns(email_always: true,
let(:key) { DigestUnsubscribeKey.create_key_for(user) } email_digests: true,
email_direct: true,
email_private_messages: true)
post :perform_unsubscribe, key: key, unsubscribe_all: "1"
expect(response.status).to eq(302)
user.user_option.reload
expect(user.user_option.email_always).to eq(false)
expect(user.user_option.email_digests).to eq(false)
expect(user.user_option.email_direct).to eq(false)
expect(user.user_option.email_private_messages).to eq(false)
context 'with a valid key' do
before do
get :resubscribe, key: key
user.reload
end end
it 'subscribes the user' do it 'can disable mailing list' do
expect(user.user_option.email_digests).to eq(true) user = Fabricate(:user)
end key = UnsubscribeKey.create_key_for(user, "all")
user.user_option.update_columns(email_always: true)
post :perform_unsubscribe, key: key, disable_mailing_list: "1"
expect(response.status).to eq(302)
user.user_option.reload
expect(user.user_option.email_always).to eq(false)
end end
it 'can disable digest' do
user = Fabricate(:user)
key = UnsubscribeKey.create_key_for(user, "all")
user.user_option.update_columns(email_digests: true)
post :perform_unsubscribe, key: key, disable_digest_emails: "1"
expect(response.status).to eq(302)
user.user_option.reload
expect(user.user_option.email_digests).to eq(false)
end
it 'can unwatch topic' do
p = Fabricate(:post)
key = UnsubscribeKey.create_key_for(p.user, p)
TopicUser.change(p.user_id, p.topic_id, notification_level: TopicUser.notification_levels[:watching])
post :perform_unsubscribe, key: key, unwatch_topic: "1"
expect(response.status).to eq(302)
expect(TopicUser.get(p.topic, p.user).notification_level).to eq(TopicUser.notification_levels[:tracking])
end
it 'can mute topic' do
p = Fabricate(:post)
key = UnsubscribeKey.create_key_for(p.user, p)
TopicUser.change(p.user_id, p.topic_id, notification_level: TopicUser.notification_levels[:watching])
post :perform_unsubscribe, key: key, mute_topic: "1"
expect(response.status).to eq(302)
expect(TopicUser.get(p.topic, p.user).notification_level).to eq(TopicUser.notification_levels[:muted])
end
it 'can unwatch category' do
p = Fabricate(:post)
key = UnsubscribeKey.create_key_for(p.user, p)
cu = CategoryUser.create!(user_id: p.user.id,
category_id: p.topic.category_id,
notification_level: CategoryUser.notification_levels[:watching])
post :perform_unsubscribe, key: key, unwatch_category: "1"
expect(response.status).to eq(302)
expect(CategoryUser.find_by(id: cu.id)).to eq(nil)
end
end end
context '.unsubscribe' do context '.unsubscribe' do
let(:user) { render_views
it 'displays logo ut button if wrong user logged in' do
log_in_user Fabricate(:admin)
user = Fabricate(:user) user = Fabricate(:user)
user.user_option.update_columns(email_always: true, email_digests: true, email_direct: true, email_private_messages: true) key = UnsubscribeKey.create_key_for(user, "digest")
user
}
let(:key) { DigestUnsubscribeKey.create_key_for(user) }
context 'from confirm unsubscribe email' do
before do
get :unsubscribe, key: key, from_all: true
user.reload
end
it 'unsubscribes from all emails' do
options = user.user_option
expect(options.email_digests).to eq false
expect(options.email_direct).to eq false
expect(options.email_private_messages).to eq false
expect(options.email_always).to eq false
end
end
context 'with a valid key' do
before do
get :unsubscribe, key: key get :unsubscribe, key: key
user.reload
expect(response.body).to include(I18n.t("unsubscribe.log_out"))
expect(response.body).to include(I18n.t("unsubscribe.different_user_description"))
end end
it 'unsubscribes the user' do it 'displays not found if key is not found' do
expect(user.user_option.email_digests).to eq(false) get :unsubscribe, key: SecureRandom.hex
expect(response.body).to include(CGI.escapeHTML(I18n.t("unsubscribe.not_found_description")))
end end
it "sets the appropriate instance variables" do it 'correctly handles mailing list mode' do
expect(assigns(:success)).to be_present
end
end
context "with an expired key or invalid key" do user = Fabricate(:user)
before do key = UnsubscribeKey.create_key_for(user, "digest")
get :unsubscribe, key: 'watwatwat'
end
it "sets the appropriate instance variables" do user.user_option.update_columns(email_always: true)
expect(assigns(:success)).to be_blank
end
end
context 'when logged in as a different user' do
let!(:logged_in_user) { log_in(:coding_horror) }
before do
get :unsubscribe, key: key get :unsubscribe, key: key
user.reload expect(response.body).to include(I18n.t("unsubscribe.mailing_list_mode"))
end
it 'does not unsubscribe the user' do SiteSetting.disable_mailing_list_mode = true
expect(user.user_option.email_digests).to eq(true)
end
it 'sets the appropriate instance variables' do get :unsubscribe, key: key
expect(assigns(:success)).to be_blank expect(response.body).not_to include(I18n.t("unsubscribe.mailing_list_mode"))
expect(assigns(:different_user)).to be_present
end
end
context 'when logged in as the keyed user' do user.user_option.update_columns(email_always: false)
SiteSetting.disable_mailing_list_mode = false
before do get :unsubscribe, key: key
log_in_user(user) expect(response.body).not_to include(I18n.t("unsubscribe.mailing_list_mode"))
get :unsubscribe, key: DigestUnsubscribeKey.create_key_for(user)
user.reload
end
it 'unsubscribes the user' do
expect(user.user_option.email_digests).to eq(false)
end
it 'sets the appropriate instance variables' do
expect(assigns(:success)).to be_present
end
end
it "sets not_found when the key didn't match anything" do
get :unsubscribe, key: 'asdfasdf'
expect(assigns(:not_found)).to eq(true)
end
end end
it 'correctly handles digest unsubscribe' do
user = Fabricate(:user)
user.user_option.update_columns(email_digests: false)
key = UnsubscribeKey.create_key_for(user, "digest")
# because we are type digest we will always show digest and it will be selected
get :unsubscribe, key: key
expect(response.body).to include(I18n.t("unsubscribe.disable_digest_emails"))
source = Nokogiri::HTML::fragment(response.body)
expect(source.css("#disable_digest_emails")[0]["checked"]).to eq("checked")
SiteSetting.disable_digest_emails = true
get :unsubscribe, key: key
expect(response.body).not_to include(I18n.t("unsubscribe.disable_digest_emails"))
SiteSetting.disable_digest_emails = false
key = UnsubscribeKey.create_key_for(user, "not_digest")
get :unsubscribe, key: key
expect(response.body).to include(I18n.t("unsubscribe.disable_digest_emails"))
end
it 'correctly handles watched categories' do
post = Fabricate(:post)
user = post.user
cu = CategoryUser.create!(user_id: user.id,
category_id: post.topic.category_id,
notification_level: CategoryUser.notification_levels[:watching])
key = UnsubscribeKey.create_key_for(user, post)
get :unsubscribe, key: key
expect(response.body).to include("unwatch_category")
cu.destroy!
get :unsubscribe, key: key
expect(response.body).not_to include("unwatch_category")
end
end
end end

View file

@ -1,31 +0,0 @@
require 'rails_helper'
require_dependency 'digest_unsubscribe_key'
describe DigestUnsubscribeKey do
it { is_expected.to belong_to :user }
describe 'key' do
let(:user) { Fabricate(:user) }
let!(:key) { DigestUnsubscribeKey.create_key_for(user) }
it 'has a temporary key' do
expect(key).to be_present
end
describe '#user_for_key' do
it 'can be used to find the user' do
expect(DigestUnsubscribeKey.user_for_key(key)).to eq(user)
end
it 'returns nil with an invalid key' do
expect(DigestUnsubscribeKey.user_for_key('asdfasdf')).to be_blank
end
end
end
end

View file

@ -0,0 +1,44 @@
require 'rails_helper'
require_dependency 'unsubscribe_key'
describe UnsubscribeKey do
describe 'post unsubscribe key' do
it 'can generate a correct url' do
post = Fabricate(:post)
url = post.unsubscribe_url(post.user)
route = Rails.application.routes.recognize_path(url)
key = UnsubscribeKey.find_by(key: route[:key])
expect(key.post_id).to eq(post.id)
expect(key.topic_id).to eq(post.topic_id)
expect(key.unsubscribe_key_type).to eq("topic")
end
end
describe 'key' do
let(:user) { Fabricate(:user) }
let!(:key) { UnsubscribeKey.create_key_for(user, "digest") }
it 'has a temporary key' do
expect(key).to be_present
end
describe '#user_for_key' do
it 'can be used to find the user' do
expect(UnsubscribeKey.user_for_key(key)).to eq(user)
end
it 'returns nil with an invalid key' do
expect(UnsubscribeKey.user_for_key('asdfasdf')).to be_blank
end
end
end
end