FEATURE: better email in support

FEATURE: new incoming_email model
FEATURE: infinite scrolling in emails admin
FEATURE: new 'emails:import' rake task
This commit is contained in:
Régis Hanol
2016-01-19 00:57:55 +01:00
parent d0bcea3411
commit 3083657358
119 changed files with 1560 additions and 4408 deletions

View File

@ -1,791 +1,233 @@
# -*- encoding : utf-8 -*-
require 'rails_helper'
require 'email/receiver'
require "rails_helper"
require "email/receiver"
describe Email::Receiver do
before do
SiteSetting.reply_by_email_address = "reply+%{reply_key}@appmail.adventuretime.ooo"
SiteSetting.email_in = false
SiteSetting.title = "Discourse"
SiteSetting.email_in = true
SiteSetting.reply_by_email_address = "reply+%{reply_key}@bar.com"
end
describe 'parse_body' do
def test_parse_body(mail_string)
Email::Receiver.new(nil).parse_body(Mail::Message.new mail_string)
def email(email_name)
fixture_file("emails/#{email_name}.eml")
end
def process(email_name)
Email::Receiver.new(email(email_name)).process
end
it "raises an EmptyEmailError when 'mail_string' is blank" do
expect { Email::Receiver.new(nil) }.to raise_error(Email::Receiver::EmptyEmailError)
expect { Email::Receiver.new("") }.to raise_error(Email::Receiver::EmptyEmailError)
end
it "raises an NoMessageIdError when 'mail_string' is not an email" do
expect { Email::Receiver.new("wat") }.to raise_error(Email::Receiver::NoMessageIdError)
end
it "raises an NoMessageIdError when 'mail_string' is missing the message_id" do
expect { Email::Receiver.new(email(:missing_message_id)) }.to raise_error(Email::Receiver::NoMessageIdError)
end
it "raises an AutoGeneratedEmailError when the mail has no return path" do
expect { process(:no_return_path) }.to raise_error(Email::Receiver::AutoGeneratedEmailError)
end
it "raises an AutoGeneratedEmailError when the mail is auto generated" do
expect { process(:auto_generated_precedence) }.to raise_error(Email::Receiver::AutoGeneratedEmailError)
expect { process(:auto_generated_header) }.to raise_error(Email::Receiver::AutoGeneratedEmailError)
end
it "raises a NoBodyDetectedError when the body is blank" do
expect { process(:no_body) }.to raise_error(Email::Receiver::NoBodyDetectedError)
end
it "raises an InactiveUserError when the sender is inactive" do
Fabricate(:user, email: "inactive@bar.com", active: false)
expect { process(:inactive_sender) }.to raise_error(Email::Receiver::InactiveUserError)
end
skip "doesn't raise an InactiveUserError when the sender is staged" do
Fabricate(:user, email: "staged@bar.com", active: false, staged: true)
expect { process(:staged_sender) }.not_to raise_error
end
it "raises a BadDestinationAddress when destinations aren't matching any of the incoming emails" do
expect { process(:bad_destinations) }.to raise_error(Email::Receiver::BadDestinationAddress)
end
context "reply" do
let(:reply_key) { "4f97315cc828096c9cb34c6f1a0d6fe8" }
let(:user) { Fabricate(:user, email: "discourse@bar.com") }
let(:topic) { create_topic(user: user) }
let(:post) { create_post(topic: topic, user: user) }
let!(:email_log) { Fabricate(:email_log, reply_key: reply_key, user: user, topic: topic, post: post) }
it "raises a ReplyUserNotMatchingError when the email address isn't matching the one we sent the notification to" do
expect { process(:reply_user_not_matching) }.to raise_error(Email::Receiver::ReplyUserNotMatchingError)
end
it "raises EmptyEmailError if the message is blank" do
expect { test_parse_body("") }.to raise_error(Email::Receiver::EmptyEmailError)
it "raises a TopicNotFoundError when the topic was deleted" do
topic.update_columns(deleted_at: 1.day.ago)
expect { process(:reply_user_matching) }.to raise_error(Email::Receiver::TopicNotFoundError)
end
it "raises EmptyEmailError if the message is not an email" do
expect { test_parse_body("asdf" * 30) }.to raise_error(Email::Receiver::EmptyEmailError)
it "raises a TopicClosedError when the topic was closed" do
topic.update_columns(closed: true)
expect { process(:reply_user_matching) }.to raise_error(Email::Receiver::TopicClosedError)
end
it "raises EmptyEmailError if there is no reply content" do
expect { test_parse_body(fixture_file("emails/no_content_reply.eml")) }.to raise_error(Email::Receiver::EmptyEmailError)
it "raises an InvalidPost when there was an error while creating the post" do
expect { process(:too_small) }.to raise_error(Email::Receiver::InvalidPost)
end
skip "raises EmailUnparsableError if the headers are corrupted" do
expect { ; }.to raise_error(Email::Receiver::EmailUnparsableError)
it "raises an InvalidPost when there are too may mentions" do
SiteSetting.max_mentions_per_post = 1
Fabricate(:user, username: "user1")
Fabricate(:user, username: "user2")
expect { process(:too_many_mentions) }.to raise_error(Email::Receiver::InvalidPost)
end
it "can parse the html section" do
expect(test_parse_body(fixture_file("emails/html_only.eml"))).to eq("The EC2 instance - I've seen that there tends to be odd and " +
"unrecommended settings on the Bitnami installs that I've checked out.")
it "raises an InvalidPostAction when they aren't allowed to like a post" do
topic.update_columns(archived: true)
expect { process(:like) }.to raise_error(Email::Receiver::InvalidPostAction)
end
it "supports a Dutch reply" do
expect(test_parse_body(fixture_file("emails/dutch.eml"))).to eq("Dit is een antwoord in het Nederlands.")
it "works" do
expect { process(:text_reply) }.to change { topic.posts.count }
expect(topic.posts.last.raw).to eq("This is a text reply :)")
expect(topic.posts.last.via_email).to eq(true)
expect(topic.posts.last.cooked).not_to match(/<br/)
expect { process(:html_reply) }.to change { topic.posts.count }
expect(topic.posts.last.raw).to eq("This is a <b>HTML</b> reply ;)")
expect { process(:hebrew_reply) }.to change { topic.posts.count }
expect(topic.posts.last.raw).to eq("שלום! מה שלומך היום?")
expect { process(:chinese_reply) }.to change { topic.posts.count }
expect(topic.posts.last.raw).to eq("您好! 你今天好吗?")
end
it "supports a Hebrew reply" do
I18n.stubs(:t).with('user_notifications.previous_discussion').returns('כלטוב')
# The force_encoding call is only needed for the test - it is passed on fine to the cooked post
expect(test_parse_body(fixture_file("emails/hebrew.eml"))).to eq("שלום")
it "prefers text over html" do
expect { process(:text_and_html_reply) }.to change { topic.posts.count }
expect(topic.posts.last.raw).to eq("This is the *text* part.")
end
it "supports a BIG5-encoded reply" do
# The force_encoding call is only needed for the test - it is passed on fine to the cooked post
expect(test_parse_body(fixture_file("emails/big5.eml"))).to eq("媽!我上電視了!")
it "removes the 'on <date>, <contact> wrote' quoting line" do
expect { process(:on_date_contact_wrote) }.to change { topic.posts.count }
expect(topic.posts.last.raw).to eq("This is the actual reply.")
end
it "removes 'via' lines if they match the site title" do
SiteSetting.title = "Discourse"
expect(test_parse_body(fixture_file("emails/via_line.eml"))).to eq("Hello this email has content!")
end
it "removes an 'on date wrote' quoting line" do
expect(test_parse_body(fixture_file("emails/on_wrote.eml"))).to eq("Sure, all you need to do is frobnicate the foobar and you'll be all set!")
end
it "removes the 'Previous Discussion' marker" do
expect(test_parse_body(fixture_file("emails/previous.eml"))).to eq("This will not include the previous discussion that is present in this email.")
it "removes the 'Previous Replies' marker" do
expect { process(:previous_replies) }.to change { topic.posts.count }
expect(topic.posts.last.raw).to eq("This will not include the previous discussion that is present in this email.")
end
it "handles multiple paragraphs" do
expect(test_parse_body(fixture_file("emails/paragraphs.eml"))).
to eq(
"Is there any reason the *old* candy can't be be kept in silos while the new candy
is imported into *new* silos?
The thing about candy is it stays delicious for a long time -- we can just keep
it there without worrying about it too much, imo.
Thanks for listening."
)
end
it "handles multiple paragraphs when parsing html" do
expect(test_parse_body(fixture_file("emails/html_paragraphs.eml"))).
to eq(
"Awesome!
Pleasure to have you here!
:boom:"
)
end
it "handles newlines" do
expect(test_parse_body(fixture_file("emails/newlines.eml"))).
to eq(
"This is my reply.
It is my best reply.
It will also be my *only* reply."
)
expect { process(:paragraphs) }.to change { topic.posts.count }
expect(topic.posts.last.raw).to eq("Do you like liquorice?\n\nI really like them. One could even say that I am *addicted* to liquorice. Anf if\nyou can mix it up with some anise, then I'm in heaven ;)")
end
it "handles inline reply" do
expect(test_parse_body(fixture_file("emails/inline_reply.eml"))).
to eq(
"On Wed, Oct 8, 2014 at 11:12 AM, techAPJ <info@unconfigured.discourse.org> wrote:
> techAPJ <https://meta.discourse.org/users/techapj>
> November 28
>
> Test reply.
>
> First paragraph.
>
> Second paragraph.
>
> To respond, reply to this email or visit
> https://meta.discourse.org/t/testing-default-email-replies/22638/3 in
> your browser.
> ------------------------------
> Previous Replies codinghorror
> <https://meta.discourse.org/users/codinghorror>
> November 28
>
> We're testing the latest GitHub email processing library which we are
> integrating now.
>
> https://github.com/github/email_reply_parser
>
> Go ahead and reply to this topic and I'll reply from various email clients
> for testing.
> ------------------------------
>
> To respond, reply to this email or visit
> https://meta.discourse.org/t/testing-default-email-replies/22638/3 in
> your browser.
>
> To unsubscribe from these emails, visit your user preferences
> <https://meta.discourse.org/my/preferences>.
>
The quick brown fox jumps over the lazy dog. The quick brown fox jumps over
the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown
fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog. The quick brown fox jumps over
the lazy dog. The quick brown fox jumps over the lazy dog."
)
expect { process(:inline_reply) }.to change { topic.posts.count }
expect(topic.posts.last.raw).to eq("On Tue, Jan 15, 2016 at 11:12 AM, Bar Foo <info@unconfigured.discourse.org> wrote:\n\n> WAT <https://bar.com/users/wat> November 28\n>\n> This is the previous post.\n\nAnd this is *my* reply :+1:")
end
it "can retrieve the first part of multiple replies" do
expect(test_parse_body(fixture_file("emails/inline_mixed.eml"))).to eq(
"The quick brown fox jumps over the lazy dog. The quick brown fox jumps over
the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown
fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog. The quick brown fox jumps over
the lazy dog. The quick brown fox jumps over the lazy dog.
> First paragraph.
>
> Second paragraph.
The quick brown fox jumps over the lazy dog. The quick brown fox jumps over
the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown"
)
it "retrieves the first part of multiple replies" do
expect { process(:inline_mixed_replies) }.to change { topic.posts.count }
expect(topic.posts.last.raw).to eq("On Tue, Jan 15, 2016 at 11:12 AM, Bar Foo <info@unconfigured.discourse.org> wrote:\n\n> WAT <https://bar.com/users/wat> November 28\n>\n> This is the previous post.\n\nAnd this is *my* reply :+1:\n\n> This is another post.\n\nAnd this is **another** reply.")
end
it "should not include previous replies" do
expect(test_parse_body(fixture_file("emails/previous_replies.eml"))).not_to match(/Previous Replies/)
end
it "strips signatures" do
expect { process(:iphone_signature) }.to change { topic.posts.count }
expect(topic.posts.last.raw).to eq("This is not the signature you're looking for.")
it "strips iPhone signature" do
expect(test_parse_body(fixture_file("emails/iphone_signature.eml"))).not_to match(/Sent from my iPhone/)
end
it "strips regular signature" do
expect(test_parse_body(fixture_file("emails/signature.eml"))).not_to match(/Arpit/)
expect { process(:signature) }.to change { topic.posts.count }
expect(topic.posts.last.raw).to eq("You shall not sign!")
end
it "strips 'original message' context" do
expect(test_parse_body(fixture_file("emails/original_message_context.eml"))).not_to match(/Context/)
expect { process(:original_message) }.to change { topic.posts.count }
expect(topic.posts.last.raw).to eq("This is a reply :)")
end
it "properly renders email reply from gmail web client" do
expect(test_parse_body(fixture_file("emails/gmail_web.eml"))).
to eq(
"### This is a reply from standard GMail in Google Chrome.
it "supports attachments" do
expect { process(:no_body_with_attachments) }.to change { topic.posts.count }
expect(topic.posts.last.raw).to match(/<img/)
The quick brown fox jumps over the lazy dog. The quick brown fox jumps over
the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown
fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog. The quick brown fox jumps over
the lazy dog. The quick brown fox jumps over the lazy dog.
Here's some **bold** text in Markdown.
Here's a link http://example.com"
)
expect { process(:inline_attachment) }.to change { topic.posts.count }
expect(topic.posts.last.raw).to match(/Before\s+<img.+\s+After/m)
end
it "properly renders email reply from iOS default mail client" do
expect(test_parse_body(fixture_file("emails/ios_default.eml"))).
to eq(
"### this is a reply from iOS default mail
The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog.
Here's some **bold** markdown text.
Here's a link http://example.com"
)
it "supports liking via email" do
expect { process(:like) }.to change(PostAction, :count)
end
it "properly renders email reply from Android 5 gmail client" do
expect(test_parse_body(fixture_file("emails/android_gmail.eml"))).
to eq(
"### this is a reply from Android 5 gmail
The quick brown fox jumps over the lazy dog. The quick brown fox jumps over
the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown
fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.
This is **bold** in Markdown.
This is a link to http://example.com"
)
end
it "properly renders email reply from Windows 8.1 Metro default mail client" do
expect(test_parse_body(fixture_file("emails/windows_8_metro.eml"))).
to eq(
"### reply from default mail client in Windows 8.1 Metro
The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog.
This is a **bold** word in Markdown
This is a link http://example.com"
)
end
it "properly renders email reply from MS Outlook client" do
expect(test_parse_body(fixture_file("emails/outlook.eml"))).to eq("Microsoft Outlook 2010")
end
it "converts back to UTF-8 at the end" do
result = test_parse_body(fixture_file("emails/big5.eml"))
expect(result.encoding).to eq(Encoding::UTF_8)
# should not throw
TextCleaner.normalize_whitespaces(
test_parse_body(fixture_file("emails/big5.eml"))
)
end
end
describe "posting replies" do
let(:reply_key) { raise "Override this in a lower describe block" }
let(:email_raw) { raise "Override this in a lower describe block" }
# ----
let(:to) { SiteSetting.reply_by_email_address.gsub("%{reply_key}", reply_key) }
let(:receiver) { Email::Receiver.new(email_raw) }
let(:post) { create_post }
let(:topic) { post.topic }
let(:posting_user) { post.user }
let(:replying_user_email) { 'jake@adventuretime.ooo' }
let(:replying_user) { Fabricate(:user, email: replying_user_email, trust_level: 2)}
let(:email_log) { EmailLog.new(reply_key: reply_key,
post: post,
post_id: post.id,
topic_id: post.topic_id,
email_type: 'user_posted',
user: replying_user,
user_id: replying_user.id,
to_address: replying_user_email
) }
before do
email_log.save
end
# === Success Posting ===
describe "valid_reply.eml" do
let!(:reply_key) { '59d8df8370b7e95c5a49fbf86aeb2c93' }
let!(:email_raw) { fill_email(fixture_file("emails/valid_reply.eml"), replying_user_email, to) }
it "creates a post with the correct content" do
start_count = topic.posts.count
receiver.process
expect(topic.posts.count).to eq(start_count + 1)
created_post = topic.posts.last
expect(created_post.via_email).to eq(true)
expect(created_post.cooked.strip).to eq(fixture_file("emails/valid_reply.cooked").strip)
end
end
describe "paragraphs.eml" do
let!(:reply_key) { '59d8df8370b7e95c5a49fbf86aeb2c93' }
let!(:email_raw) { fixture_file("emails/paragraphs.eml") }
it "cooks multiple paragraphs with traditional Markdown linebreaks" do
start_count = topic.posts.count
receiver.process
expect(topic.posts.count).to eq(start_count + 1)
expect(topic.posts.last.cooked.strip).to eq(fixture_file("emails/paragraphs.cooked").strip)
expect(topic.posts.last.cooked).not_to match(/<br/)
end
end
describe "attachment.eml" do
let!(:reply_key) { '636ca428858779856c226bb145ef4fad' }
let!(:email_raw) {
fixture_file("emails/attachment.eml")
.gsub("TO", "reply+#{reply_key}@appmail.adventuretime.ooo")
.gsub("FROM", replying_user_email)
}
let(:upload_sha) { '04df605be528d03876685c52166d4b063aabb78a' }
it "creates a post with an attachment" do
Upload.stubs(:fix_image_orientation)
ImageOptim.any_instance.stubs(:optimize_image!)
start_count = topic.posts.count
Upload.find_by(sha1: upload_sha).try(:destroy)
receiver.process
expect(topic.posts.count).to eq(start_count + 1)
expect(topic.posts.last.cooked).to match(/<img src=['"](\/uploads\/default\/original\/.+\.png)['"] width=['"]289['"] height=['"]126['"]>/)
expect(Upload.find_by(sha1: upload_sha)).not_to eq(nil)
end
describe 'Liking via email' do
let!(:reply_key) { '636ca428858779856c226bb145ef4fad' }
let(:replied_user_like_params) { { user: replying_user, post: post, post_action_type_id: PostActionType.types[:like] } }
let(:replied_user_like) { PostAction.find_by(replied_user_like_params) }
describe "plus_one.eml" do
let!(:email_raw) {
fixture_file("emails/plus_one.eml")
.gsub("TO", "reply+#{reply_key}@appmail.adventuretime.ooo")
.gsub("FROM", replying_user_email)
}
it "adds a user like to the post" do
expect { receiver.process }.to change { PostAction.count }.by(1)
expect(replied_user_like).to be_present
end
it "does not create a duplicate like" do
PostAction.create(replied_user_like_params)
before_count = PostAction.count
expect { receiver.process }.to raise_error(Email::Receiver::InvalidPostAction)
expect(PostAction.count).to eq before_count
expect(replied_user_like).to be_present
end
it "does not allow unauthorized happiness" do
post.trash!
before_count = PostAction.count
expect { receiver.process }.to raise_error(Email::Receiver::InvalidPostAction)
expect(PostAction.count).to eq before_count
expect(replied_user_like).to_not be_present
end
end
describe "like.eml" do
let!(:email_raw) {
fixture_file("emails/like.eml")
.gsub("TO", "reply+#{reply_key}@appmail.adventuretime.ooo")
.gsub("FROM", replying_user_email)
}
it 'adds a user like to the post' do
expect { receiver.process }.to change { PostAction.count }.by(1)
expect(replied_user_like).to be_present
end
end
end
end
# === Failure Conditions ===
describe "too_short.eml" do
let!(:reply_key) { '636ca428858779856c226bb145ef4fad' }
let!(:email_raw) {
fixture_file("emails/too_short.eml")
.gsub("TO", "reply+#{reply_key}@appmail.adventuretime.ooo")
.gsub("FROM", replying_user_email)
.gsub("SUBJECT", "re: [Discourse Meta] eviltrout posted in 'Adventure Time Sux'")
}
it "raises an InvalidPost error" do
SiteSetting.min_post_length = 5
expect { receiver.process }.to raise_error(Email::Receiver::InvalidPost)
end
end
describe "too_many_mentions.eml" do
let!(:reply_key) { '636ca428858779856c226bb145ef4fad' }
let!(:email_raw) { fixture_file("emails/too_many_mentions.eml") }
it "raises an InvalidPost error" do
SiteSetting.max_mentions_per_post = 10
(1..11).each do |i|
Fabricate(:user, username: "user#{i}").save
end
expect { receiver.process }.to raise_error(Email::Receiver::InvalidPost)
end
end
describe "auto response email replies should not be accepted" do
let!(:reply_key) { '636ca428858779856c226bb145ef4fad' }
let!(:email_raw) { fixture_file("emails/auto_reply.eml") }
it "raises a AutoGeneratedEmailError" do
expect { receiver.process }.to raise_error(Email::Receiver::AutoGeneratedEmailError)
end
it "ensures posts aren't dated in the future" do
expect { process(:from_the_future) }.to change { topic.posts.count }
expect(topic.posts.last.created_at).to be_within(1.minute).of(DateTime.now)
end
end
describe "posting reply to a closed topic" do
let(:reply_key) { raise "Override this in a lower describe block" }
let(:email_raw) { raise "Override this in a lower describe block" }
let(:to) { SiteSetting.reply_by_email_address.gsub("%{reply_key}", reply_key) }
let(:receiver) { Email::Receiver.new(email_raw) }
let(:topic) { Fabricate(:topic, closed: true) }
let(:post) { Fabricate(:post, topic: topic, post_number: 1) }
let(:replying_user_email) { 'jake@adventuretime.ooo' }
let(:replying_user) { Fabricate(:user, email: replying_user_email, trust_level: 2) }
let(:email_log) { EmailLog.new(reply_key: reply_key,
post: post,
post_id: post.id,
topic_id: topic.id,
email_type: 'user_posted',
user: replying_user,
user_id: replying_user.id,
to_address: replying_user_email
) }
context "new message to a group" do
before do
email_log.save
let!(:group) { Fabricate(:group, incoming_email: "team@bar.com") }
it "handles encoded display names" do
expect { process(:encoded_display_name) }.to change(Topic, :count)
topic = Topic.last
expect(topic.private_message?).to eq(true)
expect(topic.allowed_groups).to include(group)
user = topic.user
expect(user.staged).to eq(true)
expect(user.username).to eq("random_name")
expect(user.name).to eq("Случайная Имя")
end
describe "should not create post" do
let!(:reply_key) { '59d8df8370b7e95c5a49fbf86aeb2c93' }
let!(:email_raw) { fill_email(fixture_file("emails/valid_reply.eml"), replying_user_email, to) }
it "raises a TopicClosedError" do
expect { receiver.process }.to raise_error(Email::Receiver::TopicClosedError)
end
end
end
describe "posting reply to a deleted topic" do
let(:reply_key) { raise "Override this in a lower describe block" }
let(:email_raw) { raise "Override this in a lower describe block" }
let(:to) { SiteSetting.reply_by_email_address.gsub("%{reply_key}", reply_key) }
let(:receiver) { Email::Receiver.new(email_raw) }
let(:deleted_topic) { Fabricate(:deleted_topic) }
let(:post) { Fabricate(:post, topic: deleted_topic, post_number: 1) }
let(:replying_user_email) { 'jake@adventuretime.ooo' }
let(:replying_user) { Fabricate(:user, email: replying_user_email, trust_level: 2) }
let(:email_log) { EmailLog.new(reply_key: reply_key,
post: post,
post_id: post.id,
topic_id: deleted_topic.id,
email_type: 'user_posted',
user: replying_user,
user_id: replying_user.id,
to_address: replying_user_email
) }
before do
email_log.save
end
describe "should not create post" do
let!(:reply_key) { '59d8df8370b7e95c5a49fbf86aeb2c93' }
let!(:email_raw) { fill_email(fixture_file("emails/valid_reply.eml"), replying_user_email, to) }
it "raises a TopicNotFoundError" do
expect { receiver.process }.to raise_error(Email::Receiver::TopicNotFoundError)
end
end
end
describe "posting reply as a inactive user" do
let(:reply_key) { raise "Override this in a lower describe block" }
let(:email_raw) { raise "Override this in a lower describe block" }
let(:to) { SiteSetting.reply_by_email_address.gsub("%{reply_key}", reply_key) }
let(:receiver) { Email::Receiver.new(email_raw) }
let(:topic) { Fabricate(:topic) }
let(:post) { Fabricate(:post, topic: topic, post_number: 1) }
let(:replying_user_email) { 'jake@adventuretime.ooo' }
let(:replying_user) { Fabricate(:user, email: replying_user_email, trust_level: 2, active: false) }
let(:email_log) { EmailLog.new(reply_key: reply_key,
post: post,
post_id: post.id,
topic_id: topic.id,
email_type: 'user_posted',
user: replying_user,
user_id: replying_user.id,
to_address: replying_user_email
) }
before do
email_log.save
end
describe "should not create post" do
let!(:reply_key) { '59d8df8370b7e95c5a49fbf86aeb2c93' }
let!(:email_raw) { fill_email(fixture_file("emails/valid_reply.eml"), replying_user_email, to) }
it "raises a InactiveUserError" do
expect { receiver.process }.to raise_error(Email::Receiver::InactiveUserError)
end
end
end
describe "posting a new topic in a category" do
let(:category_destination) { raise "Override this in a lower describe block" }
let(:email_raw) { raise "Override this in a lower describe block" }
let(:allow_strangers) { false }
# ----
let(:receiver) { Email::Receiver.new(email_raw) }
let(:user_email) { 'jake@adventuretime.ooo' }
let(:user) { Fabricate(:user, email: user_email, trust_level: 2)}
let(:category) { Fabricate(:category, email_in: category_destination, email_in_allow_strangers: allow_strangers) }
before do
SiteSetting.email_in = true
user.save
category.save
end
describe "too_short.eml" do
let!(:category_destination) { 'incoming+amazing@appmail.adventuretime.ooo' }
let(:email_raw) {
fixture_file("emails/too_short.eml")
.gsub("TO", category_destination)
.gsub("FROM", user_email)
.gsub("SUBJECT", "A long subject that passes the checks")
}
it "does not create a topic if the post fails" do
before_topic_count = Topic.count
expect { receiver.process }.to raise_error(Email::Receiver::InvalidPost)
expect(Topic.count).to eq(before_topic_count)
end
it "invites everyone in the chain" do
expect { process(:cc) }.to change(Topic, :count)
emails = Topic.last.allowed_users.pluck(:email)
expect(emails.size).to eq(5)
expect(emails).to include("someone@else.com", "discourse@bar.com", "team@bar.com", "wat@bar.com", "42@bar.com")
end
end
def process_email(opts)
incoming_email = fixture_file("emails/valid_incoming.eml")
email = fill_email(incoming_email, opts[:from], opts[:to], opts[:body], opts[:subject], opts[:cc])
Email::Receiver.new(email).process
end
context "new topic in a category" do
describe "with a valid email" do
let(:reply_key) { "59d8df8370b7e95c5a49fbf86aeb2c93" }
let(:to) { SiteSetting.reply_by_email_address.gsub("%{reply_key}", reply_key) }
let(:user_email) { "test@test.com" }
let(:user) { Fabricate(:user, email: user_email, trust_level: 2)}
let(:post) { create_post(user: user) }
let(:valid_reply) {
reply = fixture_file("emails/valid_reply.eml")
fill_email(reply, user.email, to)
}
let(:receiver) { Email::Receiver.new(valid_reply) }
let(:email_log) { EmailLog.new(reply_key: reply_key,
post_id: post.id,
topic_id: post.topic_id,
user_id: post.user_id,
post: post,
user: user,
email_type: 'test',
to_address: user.email
) }
let(:reply_body) {
"I could not disagree more. I am obviously biased but adventure time is the
greatest show ever created. Everyone should watch it.
- Jake out" }
describe "with an email log" do
it "extracts data" do
expect { receiver.process }.to raise_error(Email::Receiver::EmailLogNotFound)
email_log.save!
receiver.process
expect(receiver.body).to eq(reply_body)
expect(receiver.email_log).to eq(email_log)
end
let!(:category) { Fabricate(:category, email_in: "category@bar.com", email_in_allow_strangers: false) }
it "raises a StrangersNotAllowedError when 'email_in_allow_strangers' is disabled" do
expect { process(:stranger_not_allowed) }.to raise_error(Email::Receiver::StrangersNotAllowedError)
end
end
describe "with a valid email from a different user" do
let(:reply_key) { SecureRandom.hex(16) }
let(:to) { SiteSetting.reply_by_email_address.gsub("%{reply_key}", reply_key) }
let(:user) { Fabricate(:user, email: "test@test.com", trust_level: 2)}
let(:post) { create_post(user: user) }
let!(:email_log) { EmailLog.create(reply_key: reply_key,
post_id: post.id,
topic_id: post.topic_id,
user_id: post.user_id,
post: post,
user: user,
email_type: 'test',
to_address: user.email) }
it "raises ReplyUserNotFoundError when user doesn't exist" do
reply = fill_email(fixture_file("emails/valid_reply.eml"), "unknown@user.com", to)
receiver = Email::Receiver.new(reply)
expect { receiver.process }.to raise_error(Email::Receiver::ReplyUserNotFoundError)
it "raises an InsufficientTrustLevelError when user's trust level isn't enough" do
SiteSetting.email_in_min_trust = 4
Fabricate(:user, email: "insufficient@bar.com", trust_level: 3)
expect { process(:insufficient_trust_level) }.to raise_error(Email::Receiver::InsufficientTrustLevelError)
end
it "raises ReplyUserNotMatchingError when user is not matching the reply key" do
another_user = Fabricate(:user, email: "existing@user.com")
reply = fill_email(fixture_file("emails/valid_reply.eml"), another_user.email, to)
receiver = Email::Receiver.new(reply)
expect { receiver.process }.to raise_error(Email::Receiver::ReplyUserNotMatchingError)
end
end
it "raises an InvalidAccess when the user is part of a readonly group" do
user = Fabricate(:user, email: "readonly@bar.com", trust_level: SiteSetting.email_in_min_trust)
group = Fabricate(:group)
describe "processes an email to a category" do
let(:to) { "some@email.com" }
group.add(user)
group.save
before do
SiteSetting.email_in = true
SiteSetting.email_in_min_trust = TrustLevel[4].to_s
end
it "correctly can target categories" do
Fabricate(:category, email_in_allow_strangers: false, email_in: to)
# no email in for user
expect{
process_email(from: "cobb@dob.com", to: "invalid@address.com")
}.to raise_error(Email::Receiver::BadDestinationAddress)
# valid target invalid user
expect{
process_email(from: "cobb@dob.com", to: to)
}.to raise_error(Email::Receiver::UserNotFoundError)
# untrusted
user = Fabricate(:user)
expect{
process_email(from: user.email, to: to)
}.to raise_error(Email::Receiver::UserNotSufficientTrustLevelError)
# trusted
user.trust_level = 4
user.save
process_email(from: user.email, to: to)
expect(user.posts.count).to eq(1)
# email too short
message = nil
begin
process_email(from: user.email, to: to, body: "x", subject: "this is my new topic title")
rescue Email::Receiver::InvalidPost => e
message = e.message
end
expect(e.message).to include("too short")
end
it "blocks user in restricted group from creating topic" do
to = "some@email.com"
restricted_user = Fabricate(:user, trust_level: 4)
restricted_group = Fabricate(:group)
restricted_group.add(restricted_user)
restricted_group.save
category = Fabricate(:category, email_in_allow_strangers: false, email_in: to)
category.set_permissions(restricted_group => :readonly)
category.set_permissions(group => :readonly)
category.save
expect{
process_email(from: restricted_user.email, to: to)
}.to raise_error(Discourse::InvalidAccess)
expect { process(:readonly) }.to raise_error(Discourse::InvalidAccess)
end
end
describe "processes an unknown email sender to category" do
let(:email_in) { "bob@bob.com" }
let(:user_email) { "#{SecureRandom.hex(32)}@foobar.com" }
let(:body) { "This is a new topic created\n\ninside a category ! :)" }
before do
SiteSetting.email_in = true
SiteSetting.allow_staged_accounts = true
end
it "rejects anon email" do
Fabricate(:category, email_in_allow_strangers: false, email_in: email_in)
expect {
process_email(from: user_email, to: email_in, body: body)
}.to raise_error(Email::Receiver::UserNotFoundError)
end
it "creates a topic for matching category" do
Fabricate(:category, email_in_allow_strangers: true, email_in: email_in)
process_email(from: user_email, to: email_in, body: body)
staged_account = User.find_by_email(user_email)
expect(staged_account).to be
expect(staged_account.staged).to be(true)
expect(staged_account.posts.order(id: :desc).limit(1).pluck(:raw).first).to eq(body)
end
end
describe "processes an unknown email sender to group" do
let(:incoming_email) { "foo@bar.com" }
let(:user_email) { "#{SecureRandom.hex(32)}@foobar.com" }
let(:body) { "This is a message to\n\na group ;)" }
before do
SiteSetting.email_in = true
SiteSetting.allow_staged_accounts = true
end
it "creates a message for matching group" do
Fabricate(:group, incoming_email: incoming_email)
process_email(from: user_email, to: incoming_email, body: body)
staged_account = User.find_by_email(user_email)
expect(staged_account).to be
expect(staged_account.name).to eq("Jake the Dog")
expect(staged_account.staged).to be(true)
post = staged_account.posts.order(id: :desc).first
expect(post).to be
expect(post.raw).to eq(body)
expect(post.topic.private_message?).to eq(true)
end
end
describe "supports incoming mail in CC fields" do
let(:incoming_email) { "foo@bar.com" }
let(:user_email) { "#{SecureRandom.hex(32)}@foobar.com" }
let(:body) { "This is a message to\n\na group via CC ;)" }
before do
SiteSetting.email_in = true
SiteSetting.allow_staged_accounts = true
end
it "creates a message for matching group" do
Fabricate(:group, incoming_email: incoming_email)
process_email(from: user_email, to: "some@email.com", body: body, cc: incoming_email)
staged_account = User.find_by_email(user_email)
expect(staged_account).to be
expect(staged_account.staged).to be(true)
post = staged_account.posts.order(id: :desc).first
expect(post).to be
expect(post.raw).to eq(body)
expect(post.topic.private_message?).to eq(true)
it "works" do
Fabricate(:user, email: "sufficient@bar.com", trust_level: SiteSetting.email_in_min_trust)
expect { process(:sufficient_trust_level) }.to change(Topic, :count)
end
end