# -*- encoding : utf-8 -*- require 'spec_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" end describe 'parse_body' do def test_parse_body(mail_string) Email::Receiver.new(nil).parse_body(Mail::Message.new mail_string) end it "raises EmptyEmailError if the message is blank" do expect { test_parse_body("") }.to raise_error(Email::Receiver::EmptyEmailError) end it "raises EmptyEmailError if the message is not an email" do expect { test_parse_body("asdf" * 30) }.to raise_error(Email::Receiver::EmptyEmailError) 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) end skip "raises EmailUnparsableError if the headers are corrupted" do expect { ; }.to raise_error(Email::Receiver::EmailUnparsableError) end it "can parse the html section" do test_parse_body(fixture_file("emails/html_only.eml")).should == "The EC2 instance - I've seen that there tends to be odd and " + "unrecommended settings on the Bitnami installs that I've checked out." end it "supports a Dutch reply" do test_parse_body(fixture_file("emails/dutch.eml")).should == "Dit is een antwoord in het Nederlands." end it "supports a Hebrew reply" do I18n.expects(: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 test_parse_body(fixture_file("emails/hebrew.eml")).should == "שלום" end it "supports a BIG5-encoded reply" do I18n.expects(: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 test_parse_body(fixture_file("emails/big5.eml")).should == "媽!我上電視了!" end it "removes 'via' lines if they match the site title" do SiteSetting.title = "Discourse" test_parse_body(fixture_file("emails/via_line.eml")).should == "Hello this email has content!" end it "removes an 'on date wrote' quoting line" do test_parse_body(fixture_file("emails/on_wrote.eml")).should == "Sure, all you need to do is frobnicate the foobar and you'll be all set!" end it "removes the 'Previous Discussion' marker" do test_parse_body(fixture_file("emails/previous.eml")).should == "This will not include the previous discussion that is present in this email." end it "handles multiple paragraphs" do test_parse_body(fixture_file("emails/paragraphs.eml")). should == ( "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 test_parse_body(fixture_file("emails/html_paragraphs.eml")). should == ( "Awesome! Pleasure to have you here! :boom:" ) end it "properly renders email reply from MS Outlook client" do test_parse_body(fixture_file("emails/outlook.eml")).should == "Microsoft Outlook 2010" end it "converts back to UTF-8 at the end" do result = test_parse_body(fixture_file("emails/big5.eml")) result.encoding.should == 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(: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) { fixture_file("emails/valid_reply.eml") } it "creates a post with the correct content" do start_count = topic.posts.count receiver.process topic.posts.count.should == (start_count + 1) created_post = topic.posts.last created_post.via_email.should == true created_post.raw_email.should == fixture_file("emails/valid_reply.eml") created_post.cooked.strip.should == 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 topic.posts.count.should == (start_count + 1) topic.posts.last.cooked.strip.should == fixture_file("emails/paragraphs.cooked").strip topic.posts.last.cooked.should_not match /
/ Upload.find_by(sha1: upload_sha).should_not == nil 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 end describe "posting a new topic" 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) Topic.count.should == before_topic_count end 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(: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) { 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 "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 end end describe "processes an email to a category" do before do SiteSetting.email_in = true end it "correctly can target categories" do to = "some@email.com" Fabricate(:category, email_in_allow_strangers: false, email_in: to) SiteSetting.email_in_min_trust = TrustLevel[4].to_s # 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) 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 describe "processes an unknown email sender to category" do before do SiteSetting.email_in = true 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 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") # 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 end end