diff --git a/app/jobs/scheduled/poll_mailbox.rb b/app/jobs/scheduled/poll_mailbox.rb index 2b621771284..82bdc679559 100644 --- a/app/jobs/scheduled/poll_mailbox.rb +++ b/app/jobs/scheduled/poll_mailbox.rb @@ -38,6 +38,9 @@ module Jobs message_template = :email_reject_reply_key when ActiveRecord::Rollback message_template = :email_reject_post_error + when Email::Receiver::InvalidPost + # TODO there is a message in this exception, place it in email + message_template = :email_reject_post_error else message_template = nil end diff --git a/lib/email/receiver.rb b/lib/email/receiver.rb index b452214f737..13a36939263 100644 --- a/lib/email/receiver.rb +++ b/lib/email/receiver.rb @@ -14,6 +14,7 @@ module Email class UserNotFoundError < ProcessingError; end class UserNotSufficientTrustLevelError < ProcessingError; end class EmailLogNotFound < ProcessingError; end + class InvalidPost < ProcessingError; end attr_reader :body, :reply_key, :email_log @@ -40,6 +41,8 @@ module Email @user = User.find_by_email(@message.from.first) if @user.blank? && @allow_strangers wrap_body_in_quote + # TODO This is WRONG it should register an account + # and email the user details on how to log in / activate @user = Discourse.system_user end @@ -213,7 +216,12 @@ module Email end def create_post(user, options) - PostCreator.new(user, options).create + creator = PostCreator.new(user, options) + post = creator.create + if creator.errors.present? + raise InvalidPost, creator.errors.full_messages.join("\n") + end + post end end diff --git a/spec/components/email/receiver_spec.rb b/spec/components/email/receiver_spec.rb index 95d757dcbf1..58a1f26aade 100644 --- a/spec/components/email/receiver_spec.rb +++ b/spec/components/email/receiver_spec.rb @@ -6,8 +6,8 @@ require 'email/receiver' describe Email::Receiver do before do - SiteSetting.stubs(:reply_by_email_address).returns("reply+%{reply_key}@appmail.adventuretime.ooo") - SiteSetting.stubs(:email_in).returns(false) + SiteSetting.reply_by_email_address = "reply+%{reply_key}@appmail.adventuretime.ooo" + SiteSetting.email_in = false end describe 'invalid emails' do @@ -126,70 +126,66 @@ Thanks for listening.") end end + def fill_email(mail, from, to, body = nil, subject = nil) + result = mail.gsub("FROM", from).gsub("TO", to) + if body + result.gsub!(/Hey.*/m, body) + end + if subject + result.sub!(/We .*/, subject) + end + result + 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]) + Email::Receiver.new(email).process + end + describe "with a valid email" do let(:reply_key) { "59d8df8370b7e95c5a49fbf86aeb2c93" } - let(:valid_reply) { fixture_file("emails/valid_reply.eml") } + let(:to) { SiteSetting.reply_by_email_address.gsub("%{reply_key}", reply_key) } + + let(:valid_reply) { + reply = fixture_file("emails/valid_reply.eml") + to = SiteSetting.reply_by_email_address.gsub("%{reply_key}", reply_key) + fill_email(reply, "test@test.com", to) + } + let(:receiver) { Email::Receiver.new(valid_reply) } - let(:post) { Fabricate.build(:post) } - let(:user) { Fabricate.build(:user) } - let(:email_log) { EmailLog.new(reply_key: reply_key, post_id: 1234, topic_id: 4567, user_id: 6677, post: post, user: user ) } + let(:post) { create_post } + let(:user) { post.user } + 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: 'test@test.com' + ) } 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 "email with non-existant email log" do - - before do - EmailLog.expects(:for).returns(nil) - end - - it "raises EmailLogNotFoundError" do - expect{ receiver.process }.to raise_error(Email::Receiver::EmailLogNotFound) - end - - end - describe "with an email log" do - before do - EmailLog.expects(:for).with(reply_key).returns(email_log) + it "extracts data" do + expect{ receiver.process }.to raise_error(Email::Receiver::EmailLogNotFound) - creator = mock - PostCreator.expects(:new).with(instance_of(User), - has_entries(raw: reply_body, - cooking_options: {traditional_markdown_linebreaks: true})) - .returns(creator) + email_log.save! + receiver.process - creator.expects(:create) - end - - let!(:result) { receiver.process } - - it "extracts the body" do expect(receiver.body).to eq(reply_body) - end - - it "looks up the email log" do expect(receiver.email_log).to eq(email_log) - end - - it "extracts the key" do expect(receiver.reply_key).to eq(reply_key) - end - end - - describe "email with attachments" do - - it "can find the message and create a post" do - user.id = -1 - User.stubs(:find_by_email).returns(user) - EmailLog.stubs(:for).returns(email_log) attachment_email = fixture_file("emails/attachment.eml") + attachment_email = fill_email(attachment_email, "test@test.com", to) r = Email::Receiver.new(attachment_email) - r.expects(:create_post) expect { r.process }.to_not raise_error expect(r.body).to match(/here is an image attachment\n<img src='\/uploads\/default\/\d+\/\w{16}\.png' width='289' height='126'>\n/) end @@ -200,106 +196,48 @@ greatest show ever created. Everyone should watch it. describe "processes an email to a category" do before do - SiteSetting.stubs(:email_in).returns(true) + SiteSetting.email_in = true end - let(:incoming_email) { fixture_file("emails/valid_incoming.eml") } - let(:receiver) { Email::Receiver.new(incoming_email) } - let(:user) { Fabricate.build(:user, id: 3456) } - let(:category) { Fabricate.build(:category, id: 10) } - let(:subject) { "We should have a post-by-email-feature." } - let(:email_body) { -"Hey folks, -I was thinking. Wouldn't it be great if we could post topics via email? Yes it would! + it "correctly can target categories" do + to = "some@email.com" -Jakie" } + Fabricate(:category, email_in_allow_strangers: false, email_in: to) + SiteSetting.email_in_min_trust = TrustLevel.levels[:elder].to_s - describe "category not found" do + # no email in for user + expect{ + process_email(from: "cobb@dob.com", to: "invalid@address.com") + }.to raise_error(Email::Receiver::EmailLogNotFound) - before do - Category.expects(:find_by_email).returns(nil) - end - - it "raises EmailLogNotFoundError" do - expect{ receiver.process }.to raise_error(Email::Receiver::EmailLogNotFound) - end - - end - - describe "email from non user" do - - before do - User.expects(:find_by_email).returns(nil) - Category.expects(:find_by_email).with( - "discourse-in@appmail.adventuretime.ooo").returns(category) - end - - it "raises UserNotFoundError" do - expect{ receiver.process }.to raise_error(Email::Receiver::UserNotFoundError) - end - - end - - describe "email from untrusted user" do - before do - User.expects(:find_by_email).with( - "jake@adventuretime.ooo").returns(user) - Category.expects(:find_by_email).with( - "discourse-in@appmail.adventuretime.ooo").returns(category) - SiteSetting.stubs(:email_in_min_trust).returns(TrustLevel.levels[:elder].to_s) - end - - it "raises untrusted user error" do - expect { receiver.process }.to raise_error(Email::Receiver::UserNotSufficientTrustLevelError) - end - - end - - describe "with proper user" do - - before do - SiteSetting.stubs(:email_in_min_trust).returns( - TrustLevel.levels[:newuser].to_s) - User.expects(:find_by_email).with( - "jake@adventuretime.ooo").returns(user) - Category.expects(:find_by_email).with( - "discourse-in@appmail.adventuretime.ooo").returns(category) - - topic_creator = mock() - TopicCreator.expects(:new).with(instance_of(User), - instance_of(Guardian), - has_entries(title: subject, - category: 10)) # Make sure it is posted to the right category - .returns(topic_creator) - - topic_creator.expects(:create).returns(topic_creator) - topic_creator.expects(:id).twice.returns(12345) - - - post_creator = mock - PostCreator.expects(:new).with(instance_of(User), - has_entries(raw: email_body, - topic_id: 12345, - cooking_options: {traditional_markdown_linebreaks: true})) - .returns(post_creator) - - post_creator.expects(:create) - - EmailLog.expects(:create).with(has_entries( - email_type: 'topic_via_incoming_email', - to_address: "discourse-in@appmail.adventuretime.ooo", - user_id: 3456, - topic_id: 12345 - )) - end - - let!(:result) { receiver.process } - - it "extracts the body" do - expect(receiver.body).to eq(email_body) + # 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) + user.posts.count.should == 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 + e.message.should include("too short") end end @@ -307,77 +245,21 @@ Jakie" } describe "processes an unknown email sender to category" do before do - SiteSetting.stubs(:email_in).returns(true) + SiteSetting.email_in = true end - let(:incoming_email) { fixture_file("emails/valid_incoming.eml") } - let(:receiver) { Email::Receiver.new(incoming_email) } - let(:non_inbox_email_category) { Fabricate.build(:category, id: 20, email_in_allow_strangers: false) } - let(:public_inbox_email_category) { Fabricate.build(:category, id: 25, email_in_allow_strangers: true) } - let(:subject) { "We should have a post-by-email-feature." } - let(:email_body) { "[quote=\"jake@adventuretime.ooo\"] -Hey folks, - -I was thinking. Wouldn't it be great if we could post topics via email? Yes it would! - -Jakie -[/quote]" } - - describe "to disabled category" do - before do - User.expects(:find_by_email).with( - "jake@adventuretime.ooo").returns(nil) - Category.expects(:find_by_email).with( - "discourse-in@appmail.adventuretime.ooo").returns(non_inbox_email_category) - end - - it "raises UserNotFoundError" do - expect{ receiver.process }.to raise_error(Email::Receiver::UserNotFoundError) - end + it "rejects anon email" do + Fabricate(:category, email_in_allow_strangers: false, email_in: "bob@bob.com") + expect { process_email(from: "test@test.com", to: "bob@bob.com") }.to raise_error(Email::Receiver::UserNotFoundError) end - describe "to enabled category" do + it "creates a topic for allowed category" do + Fabricate(:category, email_in_allow_strangers: true, email_in: "bob@bob.com") + process_email(from: "test@test.com", to: "bob@bob.com") - before do - User.expects(:find_by_email).with( - "jake@adventuretime.ooo").returns(nil) - Category.expects(:find_by_email).with( - "discourse-in@appmail.adventuretime.ooo").returns(public_inbox_email_category) - - topic_creator = mock() - TopicCreator.expects(:new).with(Discourse.system_user, - instance_of(Guardian), - has_entries(title: subject, - category: 25)) # Make sure it is posted to the right category - .returns(topic_creator) - - topic_creator.expects(:create).returns(topic_creator) - topic_creator.expects(:id).twice.returns(135) - - - post_creator = mock - PostCreator.expects(:new).with(Discourse.system_user, - has_entries(raw: email_body, - topic_id: 135, - cooking_options: {traditional_markdown_linebreaks: true})) - .returns(post_creator) - - post_creator.expects(:create) - - EmailLog.expects(:create).with(has_entries( - email_type: 'topic_via_incoming_email', - to_address: "discourse-in@appmail.adventuretime.ooo", - user_id: Discourse.system_user.id, - topic_id: 135 - )) - end - - let!(:result) { receiver.process } - - it "extracts the body" do - expect(receiver.body).to eq(email_body) - end + # This is the current implementation but it is wrong, it should register an account + Discourse.system_user.posts.order("id desc").limit(1).pluck(:raw).first.should include("Hey folks") end diff --git a/spec/fixtures/emails/attachment.eml b/spec/fixtures/emails/attachment.eml index 608d1632bab..3b9c59450b3 100644 --- a/spec/fixtures/emails/attachment.eml +++ b/spec/fixtures/emails/attachment.eml @@ -1,9 +1,9 @@ Message-ID: <51C22E52.1030509@darthvader.ca> Date: Wed, 19 Jun 2013 18:18:58 -0400 -From: Anakin Skywalker <evildad@darthvader.ca> +From: Anakin Skywalker <FROM> User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:17.0) Gecko/20130510 Thunderbird/17.0.6 MIME-Version: 1.0 -To: Han Solo via Death Star <discourse+603775f8f5f68006461890a3eadf94cf@darthvader.ca> +To: Han Solo via Death Star <TO> Subject: Re: [Death Star] [PM] re: Regarding your post in "Site Customization not working" References: <51d23d33f41fb_5f4e4b35d7d60798@xwing.mail> diff --git a/spec/fixtures/emails/valid_incoming.eml b/spec/fixtures/emails/valid_incoming.eml index 329e6cc6486..5e8db53319e 100644 --- a/spec/fixtures/emails/valid_incoming.eml +++ b/spec/fixtures/emails/valid_incoming.eml @@ -1,11 +1,11 @@ -Return-Path: <jake@adventuretime.ooo> +Return-Path: <FROM> Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400 -Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for <discourse-in@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 17:03:50 -0400 -Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for <discourse-in@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 14:03:48 -0700 +Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for <TO>; Thu, 13 Jun 2013 17:03:50 -0400 +Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for <TO>; Thu, 13 Jun 2013 14:03:48 -0700 Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700 Date: Thu, 13 Jun 2013 17:03:48 -0400 -From: Jake the Dog <jake@adventuretime.ooo> -To: discourse-in@appmail.adventuretime.ooo +From: Jake the Dog <FROM> +To: <TO> Message-ID: <CADkmRc+rNGAGGbV2iE5p918UVy4UyJqVcXRO2=otppgzduJSg@mail.gmail.com> Subject: We should have a post-by-email-feature. Mime-Version: 1.0 diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 6ed30789fa4..6dfc3e4abf5 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -57,6 +57,8 @@ Spork.prefork do config.before(:suite) do + Sidekiq.error_handlers.clear + # Ugly, but needed until we have a user creator User.skip_callback(:create, :after, :ensure_in_trust_level_group)