# frozen_string_literal: true

require "email/receiver"

RSpec.describe Email::Receiver do
  before do
    SiteSetting.email_in = true
    SiteSetting.reply_by_email_address = "reply+%{reply_key}@bar.com"
    SiteSetting.alternative_reply_by_email_addresses = "alt+%{reply_key}@bar.com"
  end

  def process(email_name, opts = {})
    Email::Receiver.new(email(email_name), opts).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 a ScreenedEmailError when email address is screened" do
    ScreenedEmail.expects(:should_block?).with("screened@mail.com").returns(true)
    expect { process(:screened_email) }.to raise_error(Email::Receiver::ScreenedEmailError)
  end

  it "raises EmailNotAllowed when email address is not on allowlist" do
    SiteSetting.allowed_email_domains = "example.com|bar.com"
    Fabricate(:group, incoming_email: "some_group@bar.com")
    expect { process(:blocklist_allowlist_email) }.to raise_error(Email::Receiver::EmailNotAllowed)
  end

  it "raises EmailNotAllowed when email address is on blocklist" do
    SiteSetting.blocked_email_domains = "email.com|mail.com"
    Fabricate(:group, incoming_email: "some_group@bar.com")
    expect { process(:blocklist_allowlist_email) }.to raise_error(Email::Receiver::EmailNotAllowed)
  end

  it "raises an UserNotFoundError when staged users are disabled" do
    SiteSetting.enable_staged_users = false
    expect { process(:user_not_found) }.to raise_error(Email::Receiver::UserNotFoundError)
  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 a NoSenderDetectedError when the From header is missing" do
    expect { process(:no_from) }.to raise_error(Email::Receiver::NoSenderDetectedError)
  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

  it "raises a SilencedUserError when the sender has been silenced" do
    Fabricate(:user, email: "silenced@bar.com", silenced_till: 1.year.from_now)
    expect { process(:silenced_sender) }.to raise_error(Email::Receiver::SilencedUserError)
  end

  it "doesn't raise an InactiveUserError when the sender is staged" do
    user = Fabricate(:user, email: "staged@bar.com", active: false, staged: true)
    post = Fabricate(:post)

    post_reply_key =
      Fabricate(
        :post_reply_key,
        user: user,
        post: post,
        reply_key: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
      )

    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

  it "raises an OldDestinationError when notification is too old" do
    SiteSetting.disallow_reply_by_email_after_days = 2

    topic = Fabricate(:topic)
    post = Fabricate(:post, topic: topic)
    user = Fabricate(:user, email: "discourse@bar.com")

    mail = email(:old_destination).gsub("424242", topic.id.to_s)
    expect { Email::Receiver.new(mail).process! }.to raise_error(
      Email::Receiver::BadDestinationAddress,
    )

    IncomingEmail.destroy_all
    post.update!(created_at: 3.days.ago)

    expect { Email::Receiver.new(mail).process! }.to raise_error(
      Email::Receiver::OldDestinationError,
    )
    expect(IncomingEmail.last.error).to eq("Email::Receiver::OldDestinationError")

    SiteSetting.disallow_reply_by_email_after_days = 0
    IncomingEmail.destroy_all

    expect { Email::Receiver.new(mail).process! }.to raise_error(
      Email::Receiver::BadDestinationAddress,
    )
  end

  describe "bounces" do
    it "raises a BouncerEmailError" do
      expect { process(:bounced_email) }.to raise_error(Email::Receiver::BouncedEmailError)
      expect(IncomingEmail.last.is_bounce).to eq(true)

      expect { process(:bounced_email_multiple_status_codes) }.to raise_error(
        Email::Receiver::BouncedEmailError,
      )
      expect(IncomingEmail.last.is_bounce).to eq(true)
    end

    describe "creating whisper post in PMs for staged users" do
      let(:email_address) { "linux-admin@b-s-c.co.jp" }
      fab!(:user1) { Fabricate(:user) }
      let(:user2) { Fabricate(:staged, email: email_address) }
      let(:topic) do
        Fabricate(
          :topic,
          archetype: "private_message",
          category_id: nil,
          user: user1,
          allowed_users: [user1, user2],
        )
      end
      let(:post) { create_post(topic: topic, user: user1) }

      before do
        SiteSetting.enable_staged_users = true
        SiteSetting.whispers_allowed_groups = "#{Group::AUTO_GROUPS[:staff]}"
      end

      def create_post_reply_key(value)
        Fabricate(:post_reply_key, reply_key: value, user: user2, post: post)
      end

      it "when bounce without verp" do
        create_post_reply_key("4f97315cc828096c9cb34c6f1a0d6fe8")

        expect { process(:bounced_email) }.to raise_error(Email::Receiver::BouncedEmailError)
        post = Post.last
        expect(post.whisper?).to eq(true)
        expect(post.raw).to eq(
          I18n.t(
            "system_messages.email_bounced",
            email: email_address,
            raw: "Your email bounced",
          ).strip,
        )
        expect(IncomingEmail.last.is_bounce).to eq(true)
      end

      context "when bounce with verp" do
        let(:bounce_key) { "14b08c855160d67f2e0c2f8ef36e251e" }

        before do
          SiteSetting.reply_by_email_address = "foo+%{reply_key}@discourse.org"
          create_post_reply_key(bounce_key)
          Fabricate(
            :email_log,
            to_address: email_address,
            user: user2,
            bounce_key: bounce_key,
            post: post,
          )
        end

        it "creates a post with the bounce error" do
          expect { process(:hard_bounce_via_verp) }.to raise_error(
            Email::Receiver::BouncedEmailError,
          )
          post = Post.last
          expect(post.whisper?).to eq(true)
          expect(post.raw).to eq(
            I18n.t(
              "system_messages.email_bounced",
              email: email_address,
              raw: "Your email bounced",
            ).strip,
          )
          expect(IncomingEmail.last.is_bounce).to eq(true)
        end

        it "updates the email log with the bounce error message" do
          expect { process(:hard_bounce_via_verp) }.to raise_error(
            Email::Receiver::BouncedEmailError,
          )
          email_log = EmailLog.find_by(bounce_key: bounce_key)
          expect(email_log.bounced).to eq(true)
          expect(email_log.bounce_error_code).to eq("5.1.1")
        end
      end
    end
  end

  it "logs a blank error" do
    Email::Receiver.any_instance.stubs(:process_internal).raises(RuntimeError, "")
    begin
      process(:existing_user)
    rescue StandardError
      RuntimeError
    end
    expect(IncomingEmail.last.error).to eq("RuntimeError")
  end

  it "strips null bytes from the subject" do
    expect do process(:null_byte_in_subject) end.to raise_error(
      Email::Receiver::BadDestinationAddress,
    )
  end

  describe "bounces to VERP" do
    let(:bounce_key) { "14b08c855160d67f2e0c2f8ef36e251e" }
    let(:bounce_key_2) { "b542fb5a9bacda6d28cc061d18e4eb83" }
    fab!(:user) { Fabricate(:user, email: "linux-admin@b-s-c.co.jp") }
    let!(:email_log) do
      Fabricate(:email_log, to_address: user.email, user: user, bounce_key: bounce_key)
    end
    let!(:email_log_2) do
      Fabricate(:email_log, to_address: user.email, user: user, bounce_key: bounce_key_2)
    end

    it "deals with soft bounces" do
      expect { process(:soft_bounce_via_verp) }.to raise_error(Email::Receiver::BouncedEmailError)

      email_log.reload
      expect(email_log.bounced).to eq(true)
      expect(email_log.user.user_stat.bounce_score).to eq(SiteSetting.soft_bounce_score)
    end

    it "deals with hard bounces" do
      expect { process(:hard_bounce_via_verp) }.to raise_error(Email::Receiver::BouncedEmailError)

      email_log.reload
      expect(email_log.bounced).to eq(true)
      expect(email_log.user.user_stat.bounce_score).to eq(SiteSetting.hard_bounce_score)

      expect { process(:hard_bounce_via_verp_2) }.to raise_error(Email::Receiver::BouncedEmailError)

      email_log_2.reload
      expect(email_log_2.user.user_stat.bounce_score).to eq(SiteSetting.hard_bounce_score * 2)
      expect(email_log_2.bounced).to eq(true)
    end

    it "works when the final recipient is different" do
      expect { process(:verp_bounce_different_final_recipient) }.to raise_error(
        Email::Receiver::BouncedEmailError,
      )

      email_log.reload
      expect(email_log.bounced).to eq(true)
      expect(email_log.user.user_stat.bounce_score).to eq(SiteSetting.soft_bounce_score)
    end

    it "sends a system message once they reach the 'bounce_score_threshold'" do
      expect(user.active).to eq(true)

      user.user_stat.bounce_score = SiteSetting.bounce_score_threshold - 1
      user.user_stat.save!

      SystemMessage.expects(:create_from_system_user).with(user, :email_revoked)

      expect { process(:hard_bounce_via_verp) }.to raise_error(Email::Receiver::BouncedEmailError)
    end
  end

  describe "reply" do
    let(:reply_key) { "4f97315cc828096c9cb34c6f1a0d6fe8" }
    fab!(:category) { Fabricate(:category) }
    fab!(:user) { Fabricate(:user, email: "discourse@bar.com") }
    fab!(:topic) { create_topic(category: category, user: user) }
    fab!(:post) { create_post(topic: topic) }

    let!(:post_reply_key) do
      Fabricate(:post_reply_key, reply_key: reply_key, user: user, post: post)
    end

    let :topic_user do
      TopicUser.find_by(topic_id: topic.id, user_id: user.id)
    end

    it "uses MD5 of 'mail_string' there is no message_id" do
      mail_string = email(:missing_message_id)
      expect { Email::Receiver.new(mail_string).process! }.to change { IncomingEmail.count }
      expect(IncomingEmail.last.message_id).to eq(Digest::MD5.hexdigest(mail_string))
    end

    it "raises a ReplyUserNotMatchingError when the email address isn't matching the one we sent the notification to" do
      Fabricate(:user, email: "someone_else@bar.com")
      expect { process(:reply_user_not_matching) }.to raise_error(
        Email::Receiver::ReplyUserNotMatchingError,
      )
    end

    it "raises a FromReplyByAddressError when the email is from the reply by email address" do
      expect { process(:from_reply_by_email_address) }.to raise_error(
        Email::Receiver::FromReplyByAddressError,
      )
    end

    it "accepts reply from secondary email address" do
      Fabricate(:secondary_email, email: "someone_else@bar.com", user: user)

      expect { process(:reply_user_not_matching) }.to change { topic.posts.count }

      post = Post.last

      expect(post.raw).to eq("Lorem ipsum dolor sit amet, consectetur adipiscing elit.")

      expect(post.user).to eq(user)
    end

    it "raises a ReplyNotAllowedError when user without permissions is replying" do
      Fabricate(:user, email: "bob@bar.com")
      category.set_permissions(admins: :full)
      category.save
      expect { process(:reply_user_not_matching_but_known) }.to raise_error(
        Email::Receiver::ReplyNotAllowedError,
      )
    end

    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

    context "with a closed topic" do
      before { topic.update_columns(closed: true) }

      it "raises a TopicClosedError when the topic was closed" do
        expect { process(:reply_user_matching) }.to raise_error(Email::Receiver::TopicClosedError)
      end

      it "Can watch topics via the watch command" do
        # TODO support other locales as well, the tricky thing is that these string live in
        # client.yml not on server yml so it is a bit tricky to find

        topic.update_columns(closed: true)
        process(:watch)
        expect(topic_user.notification_level).to eq(NotificationLevels.topic_levels[:watching])
      end

      it "Can mute topics via the mute command" do
        process(:mute)
        expect(topic_user.notification_level).to eq(NotificationLevels.topic_levels[:muted])
      end

      it "can track a topic via the track command" do
        process(:track)
        expect(topic_user.notification_level).to eq(NotificationLevels.topic_levels[:tracking])
      end
    end

    it "raises an InvalidPost when there was an error while creating the post" do
      expect { process(:too_small) }.to raise_error(Email::Receiver::TooShortPost)
    end

    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 "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 "creates a new reply post" do
      handler_calls = 0
      handler = proc { |_| handler_calls += 1 }

      DiscourseEvent.on(:topic_created, &handler)

      expect { process(:text_reply) }.to change { topic.posts.count }
      expect(topic.posts.last.raw).to eq(
        "This is a text reply :)\n\nEmail parsing should not break because of a UTF-8 character: ’",
      )
      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 **HTML** reply ;)")

      DiscourseEvent.off(:topic_created, &handler)
      expect(handler_calls).to eq(0)
    end

    it "stores the created_via source against the incoming email" do
      process(:text_reply, source: :handle_mail)
      expect(IncomingEmail.last.created_via).to eq(IncomingEmail.created_via_types[:handle_mail])
      process(:text_and_html_reply, source: :imap)
      expect(IncomingEmail.last.created_via).to eq(IncomingEmail.created_via_types[:imap])
    end

    it "stores the message_id of the incoming email against the post as outbound_message_id" do
      expect { process(:text_reply, source: :handle_mail) }.to change(Post, :count)
      message_id = IncomingEmail.last.message_id
      expect(Post.last.outbound_message_id).to eq(message_id)
    end

    it "automatically elides gmail quotes" do
      SiteSetting.always_show_trimmed_content = true
      expect { process(:gmail_html_reply) }.to change { topic.posts.count }
      expect(topic.posts.last.raw).to eq(
        "This is a **GMAIL** reply ;)\n\n<details class='elided'>\n<summary title='Show trimmed content'>&#183;&#183;&#183;</summary>\n\nThis is the *elided* part!\n\n</details>",
      )
    end

    it "doesn't process email with same message-id more than once" do
      expect do
        process(:text_reply)
        process(:text_reply)
      end.to change { topic.posts.count }.by(1)
    end

    it "handles different encodings correctly" do
      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("您好! 你今天好吗?")

      expect { process(:reply_with_weird_encoding) }.to change { topic.posts.count }
      expect(topic.posts.last.raw).to eq("This is a reply with a weird encoding.")

      expect { process(:reply_with_8bit_encoding) }.to change { topic.posts.count }
      expect(topic.posts.last.raw).to eq("hab vergessen kritische zeichen einzufügen:\näöüÄÖÜß")
    end

    it "prefers text over html when site setting is disabled" do
      SiteSetting.incoming_email_prefer_html = false
      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 "prefers html over text when site setting is enabled" do
      SiteSetting.incoming_email_prefer_html = true
      expect { process(:text_and_html_reply) }.to change { topic.posts.count }
      expect(topic.posts.last.raw).to eq("This is the **html** part.")
    end

    it "uses text when prefer_html site setting is enabled but no html is available" do
      SiteSetting.incoming_email_prefer_html = true
      expect { process(:text_reply) }.to change { topic.posts.count }
      expect(topic.posts.last.raw).to eq(
        "This is a text reply :)\n\nEmail parsing should not break because of a UTF-8 character: ’",
      )
    end

    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 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 "removes the translated 'Previous Replies' marker" do
      expect { process(:previous_replies_de) }.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 "removes the 'type reply above' marker" do
      expect { process(:reply_above) }.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 "removes the translated 'Previous Replies' marker" do
      expect { process(:reply_above_de) }.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 { 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. And if\nyou can mix it up with some anise, then I'm in heaven ;)",
      )
    end

    it "raises a NoSenderDetectedError when the From header can't be parsed" do
      expect { process(:invalid_from_1) }.to raise_error(Email::Receiver::NoSenderDetectedError)
    end

    it "raises a NoSenderDetectedError when the From header doesn't contain an email address" do
      expect { process(:invalid_from_2) }.to raise_error(Email::Receiver::NoSenderDetectedError)
    end

    it "doesn't raise an AutoGeneratedEmailError due to an X-Auto-Response-Suppress header" do
      expect { process(:quirks_exchange_xars) }.to change { topic.posts.count }
    end

    it "doesn't raise an AutoGeneratedEmailError when the mail is auto generated but is allowlisted" do
      SiteSetting.auto_generated_allowlist = "foo@bar.com|discourse@bar.com"
      expect { process(:auto_generated_allowlisted) }.to change { topic.posts.count }
    end

    it "doesn't raise an AutoGeneratedEmailError when block_auto_generated_emails is disabled" do
      SiteSetting.block_auto_generated_emails = false
      expect { process(:auto_generated_unblocked) }.to change { topic.posts.count }
    end

    it "allows staged users to reply to a restricted category" do
      user.update_columns(staged: true)

      category.email_in = "category@bar.com"
      category.email_in_allow_strangers = true
      category.set_permissions(Group[:trust_level_4] => :full)
      category.save!

      expect { process(:staged_reply_restricted) }.to change { topic.posts.count }
    end

    it "posts a reply to the topic when the post was deleted" do
      post.update_columns(deleted_at: 1.day.ago)
      expect { process(:reply_user_matching) }.to change { topic.posts.count }
      expect(topic.ordered_posts.last.reply_to_post_number).to be_nil
    end

    describe "Unsubscribing via email" do
      let(:last_email) { ActionMailer::Base.deliveries.last }

      describe "unsubscribe_subject.eml" do
        it "sends an email asking the user to confirm the unsubscription" do
          expect { process("unsubscribe_subject") }.to change {
            ActionMailer::Base.deliveries.count
          }.by(1)
          expect(last_email.to.length).to eq 1
          expect(last_email.from.length).to eq 1
          expect(last_email.from).to include "noreply@#{Discourse.current_hostname}"
          expect(last_email.to).to include "discourse@bar.com"
          expect(last_email.subject).to eq I18n.t(:"unsubscribe_mailer.subject_template").gsub(
               "%{site_title}",
               SiteSetting.title,
             )
        end

        it "does nothing unless unsubscribe_via_email is turned on" do
          SiteSetting.unsubscribe_via_email = false
          before_deliveries = ActionMailer::Base.deliveries.count
          expect { process("unsubscribe_subject") }.to raise_error {
            Email::Receiver::BadDestinationAddress
          }
          expect(before_deliveries).to eq ActionMailer::Base.deliveries.count
        end
      end

      describe "unsubscribe_body.eml" do
        it "sends an email asking the user to confirm the unsubscription" do
          expect { process("unsubscribe_body") }.to change {
            ActionMailer::Base.deliveries.count
          }.by(1)
          expect(last_email.to.length).to eq 1
          expect(last_email.from.length).to eq 1
          expect(last_email.from).to include "noreply@#{Discourse.current_hostname}"
          expect(last_email.to).to include "discourse@bar.com"
          expect(last_email.subject).to eq I18n.t(:"unsubscribe_mailer.subject_template").gsub(
               "%{site_title}",
               SiteSetting.title,
             )
        end

        it "does nothing unless unsubscribe_via_email is turned on" do
          SiteSetting.unsubscribe_via_email = false
          before_deliveries = ActionMailer::Base.deliveries.count
          expect { process("unsubscribe_body") }.to raise_error { Email::Receiver::InvalidPost }
          expect(before_deliveries).to eq ActionMailer::Base.deliveries.count
        end
      end

      it "raises an UnsubscribeNotAllowed and does not send an unsubscribe email" do
        before_deliveries = ActionMailer::Base.deliveries.count
        expect { process(:unsubscribe_new_user) }.to raise_error {
          Email::Receiver::UnsubscribeNotAllowed
        }
        expect(before_deliveries).to eq ActionMailer::Base.deliveries.count
      end
    end

    it "handles inline reply" do
      expect { process(:inline_reply) }.to change { topic.posts.count }
      expect(topic.posts.last.raw).to eq("And this is *my* reply :+1:")
    end

    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(
        "> 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 "strips mobile/webmail 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.")
    end

    it "strips 'original message' context" do
      expect { process(:original_message) }.to change { topic.posts.count }
      expect(topic.posts.last.raw).to eq("This is a reply :)")
    end

    it "add the 'elided' part of the original message only for private messages" do
      topic.update_columns(category_id: nil, archetype: Archetype.private_message)
      topic.allowed_users << user
      topic.save

      expect { process(:original_message) }.to change { topic.posts.count }
      expect(topic.posts.last.raw).to eq(
        "This is a reply :)\n\n<details class='elided'>\n<summary title='Show trimmed content'>&#183;&#183;&#183;</summary>\n\n---Original Message---\nThis part should not be included\n\n</details>",
      )
    end

    it "doesn't include the 'elided' part of the original message when always_show_trimmed_content is disabled" do
      SiteSetting.always_show_trimmed_content = false
      expect { process(:original_message) }.to change { topic.posts.count }
      expect(topic.posts.last.raw).to eq("This is a reply :)")
    end

    it "adds the 'elided' part of the original message for public replies when always_show_trimmed_content is enabled" do
      SiteSetting.always_show_trimmed_content = true
      expect { process(:original_message) }.to change { topic.posts.count }
      expect(topic.posts.last.raw).to eq(
        "This is a reply :)\n\n<details class='elided'>\n<summary title='Show trimmed content'>&#183;&#183;&#183;</summary>\n\n---Original Message---\nThis part should not be included\n\n</details>",
      )
    end

    it "doesn't trim the message when trim_incoming_emails is disabled" do
      SiteSetting.trim_incoming_emails = false
      expect { process(:original_message) }.to change { topic.posts.count }
      expect(topic.posts.last.raw).to eq(
        "This is a reply :)\n\n---Original Message---\nThis part should not be included",
      )
    end

    it "supports attached images in TEXT part" do
      SiteSetting.incoming_email_prefer_html = false

      expect { process(:no_body_with_image) }.to change { topic.posts.count }

      post = topic.posts.last
      upload = post.uploads.first

      expect(post.raw).to include(
        "![#{upload.original_filename}|#{upload.width}x#{upload.height}](#{upload.short_url})",
      )

      expect { process(:inline_image) }.to change { topic.posts.count }

      post = topic.posts.last
      upload = post.uploads.first

      expect(post.raw).to include(
        "![#{upload.original_filename}|#{upload.width}x#{upload.height}](#{upload.short_url})",
      )
    end

    it "supports attached images in HTML part" do
      SiteSetting.incoming_email_prefer_html = true

      expect { process(:inline_image) }.to change { topic.posts.count }

      post = topic.posts.last
      upload = post.uploads.last

      expect(post.raw).to eq(<<~MD.chomp)
      **Before**

      <img src="#{upload.short_url}" alt="内嵌图片 1">

      *After*
      MD
    end

    it "gracefully handles malformed images in HTML part" do
      expect { process(:inline_image_2) }.to change { topic.posts.count }

      post = topic.posts.last
      upload = post.uploads.last

      expect(post.raw).to eq(<<~MD.chomp)
      [image:#{"0" * 5000}

      ![#{upload.original_filename}|#{upload.width}x#{upload.height}](#{upload.short_url})
      MD
    end

    it "supports attached images in signature" do
      SiteSetting.incoming_email_prefer_html = true
      SiteSetting.always_show_trimmed_content = true

      expect { process(:body_with_image) }.to change { topic.posts.count }

      post = topic.posts.last
      upload = post.uploads.last

      expect(post.raw).to eq(<<~MD.chomp)
      This is a **GMAIL** reply ;)

      <details class='elided'>
      <summary title='Show trimmed content'>&#183;&#183;&#183;</summary>

      <img src="upload://qUm0DGR49PAZshIi7HxMd3cAlzn.png" width="300" height="200">

      </details>
      MD
    end

    it "supports attachments" do
      SiteSetting.authorized_extensions = "txt|jpg"
      expect { process(:attached_txt_file) }.to change { topic.posts.count }
      post = topic.posts.last
      upload = post.uploads.first

      expect(post.raw).to eq(<<~MD.chomp)
      Please find some text file attached.

      [#{upload.original_filename}|attachment](#{upload.short_url}) (20 Bytes)
      MD

      expect { process(:apple_mail_attachment) }.to change { topic.posts.count }
      post = topic.posts.last
      upload = post.uploads.first

      expect(post.raw).to eq(<<~MD.chomp)
      Picture below.

      <img apple-inline="yes" id="06C04C58-783E-4753-9B6B-D57403903060" src="#{upload.short_url}" class="">

      Picture above.
      MD
    end

    it "works with removed attachments" do
      SiteSetting.authorized_extensions = "jpg"

      expect { process(:removed_attachments) }.to change { topic.posts.count }
      post = topic.posts.last
      expect(post.uploads).to be_empty
    end

    it "supports eml attachments" do
      SiteSetting.authorized_extensions = "eml"
      expect { process(:attached_eml_file) }.to change { topic.posts.count }
      post = topic.posts.last
      upload = post.uploads.first

      expect(post.raw).to eq(<<~MD.chomp)
      Please find the eml file attached.

      [#{upload.original_filename}|attachment](#{upload.short_url}) (193 Bytes)
      MD
    end

    it "can decode attachments" do
      SiteSetting.authorized_extensions = "pdf"
      Fabricate(:group, incoming_email: "one@foo.com")

      process(:encoded_filename)
      expect(Upload.last.original_filename).to eq("This is a test.pdf")
    end

    context "when attachment is rejected" do
      it "sends out the warning email" do
        expect { process(:attached_txt_file) }.to change { EmailLog.count }.by(1)
        expect(EmailLog.last.email_type).to eq("email_reject_attachment")
        expect(topic.posts.last.uploads.size).to eq 0
      end

      it "doesn't send out the warning email if sender is staged user" do
        user.update_columns(staged: true)
        expect { process(:attached_txt_file) }.not_to change { EmailLog.count }
        expect(topic.posts.last.uploads.size).to eq 0
      end

      it "creates the post with attachment missing message" do
        missing_attachment_regex =
          Regexp.escape(I18n.t("emails.incoming.missing_attachment", filename: "text.txt"))
        expect { process(:attached_txt_file) }.to change { topic.posts.count }
        post = topic.posts.last
        expect(post.raw).to match(/#{missing_attachment_regex}/)
        expect(post.uploads.size).to eq 0
      end
    end

    it "supports emails with just an attachment" do
      SiteSetting.authorized_extensions = "pdf"
      expect { process(:attached_pdf_file) }.to change { topic.posts.count }
      post = topic.posts.last
      upload = post.uploads.last

      expect(post.raw).to include(
        "[#{upload.original_filename}|attachment](#{upload.short_url}) (64 KB)",
      )
    end

    it "supports liking via email" do
      expect { process(:like) }.to change(PostAction, :count)
    end

    it "ensures posts aren't dated in the future" do
      # PostCreator doesn't provide sub-second accuracy for created_at
      now = freeze_time Time.zone.now.round

      expect { process(:from_the_future) }.to change { topic.posts.count }
      expect(topic.posts.last.created_at).to eq_time(now)
    end

    it "accepts emails with wrong reply key if the system knows about the forwarded email" do
      Fabricate(:user, email: "bob@bar.com")
      Fabricate(
        :incoming_email,
        raw: <<~RAW,
                  Return-Path: <discourse@bar.com>
                  From: Alice <discourse@bar.com>
                  To: dave@bar.com, reply+4f97315cc828096c9cb34c6f1a0d6fe8@bar.com
                  CC: carol@bar.com, bob@bar.com
                  Subject: Hello world
                  Date: Fri, 15 Jan 2016 00:12:43 +0100
                  Message-ID: <10@foo.bar.mail>
                  Mime-Version: 1.0
                  Content-Type: text/plain; charset=UTF-8
                  Content-Transfer-Encoding: quoted-printable

                  This post was created by email.
                RAW
        from_address: "discourse@bar.com",
        to_addresses: "dave@bar.com;reply+4f97315cc828096c9cb34c6f1a0d6fe8@bar.com",
        cc_addresses: "carol@bar.com;bob@bar.com",
        topic: topic,
        post: post,
        user: user,
      )

      expect { process(:reply_user_not_matching_but_known) }.to change { topic.posts.count }
    end

    it "re-enables user's PM email notifications when user replies to a private topic" do
      topic.update_columns(category_id: nil, archetype: Archetype.private_message)
      topic.allowed_users << user
      topic.save

      user.user_option.update_columns(email_messages_level: UserOption.email_level_types[:never])
      expect { process(:reply_user_matching) }.to change { topic.posts.count }
      user.reload
      expect(user.user_option.email_messages_level).to eq(UserOption.email_level_types[:always])
    end
  end

  shared_examples "creates topic with forwarded message as quote" do |destination, address|
    it "creates topic with forwarded message as quote" do
      expect { process(:forwarded_email_1) }.to change(Topic, :count)

      topic = Topic.last
      if destination == :category
        expect(topic.category).to eq(Category.where(email_in: address).first)
      else
        expect(topic.archetype).to eq(Archetype.private_message)
        expect(topic.allowed_groups).to eq(Group.where(incoming_email: address))
      end

      post = Post.last

      expect(post.user.email).to eq("ba@bar.com")
      expect(post.raw).to eq(<<~RAW.chomp)
        @team, can you have a look at this email below?

        [quote]
        From: Some One &lt;some@one\\.com&gt;
        To: Ba Bar &lt;ba@bar\\.com&gt;
        Date: Mon, 1 Dec 2016 00:13:37 \\+0100
        Subject: Discoursing much?

        Hello Ba Bar,

        Discoursing much today?

        XoXo
        [/quote]
      RAW
    end
  end

  describe "new message to a group" do
    fab!(:group) { Fabricate(:group, incoming_email: "team@bar.com|meat@bar.com") }

    it "handles encoded display names" do
      expect { process(:encoded_display_name) }.to change(Topic, :count)

      topic = Topic.last
      expect(topic.title).to eq("I need help")
      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

    it "handles email with no subject" do
      expect { process(:no_subject) }.to change(Topic, :count)
      expect(Topic.last.title).to eq("This topic needs a title")
    end

    it "invites everyone in the chain but emails configured as 'incoming' (via reply, group or category)" do
      expect { process(:cc) }.to change(Topic, :count)

      topic = Topic.last

      emails = topic.allowed_users.joins(:user_emails).pluck(:"user_emails.email")
      expect(emails).to contain_exactly("someone@else.com", "discourse@bar.com", "wat@bar.com")

      expect(topic.topic_users.count).to eq(3)
    end

    it "invites users with a secondary email in the chain" do
      user1 =
        Fabricate(
          :user,
          trust_level: SiteSetting.email_in_min_trust,
          user_emails: [
            Fabricate.build(:secondary_email, email: "discourse@bar.com"),
            Fabricate.build(:secondary_email, email: "someone@else.com"),
          ],
        )

      user2 =
        Fabricate(
          :user,
          trust_level: SiteSetting.email_in_min_trust,
          user_emails: [
            Fabricate.build(:secondary_email, email: "team@bar.com"),
            Fabricate.build(:secondary_email, email: "wat@bar.com"),
          ],
        )

      expect { process(:cc) }.to change(Topic, :count)
      expect(Topic.last.allowed_users).to contain_exactly(user1, user2)
    end

    it "cap the number of staged users created per email" do
      SiteSetting.maximum_staged_users_per_email = 1
      expect { process(:cc) }.to change(Topic, :count).by(1).and change(User, :count).by(1)
      expect(Topic.last.ordered_posts[-1].post_type).to eq(Post.types[:moderator_action])
    end

    it "cap the number of staged users existing per email" do
      Fabricate(:user, email: "discourse@bar.com", staged: true) # from
      Fabricate(:user, email: "someone@else.com", staged: true) # to

      SiteSetting.maximum_staged_users_per_email = 1
      expect { process(:cc) }.to change(Topic, :count).and not_change(User, :count)
      expect(Topic.last.ordered_posts[-1].post_type).to eq(Post.types[:moderator_action])
    end

    it "rejects messages with too many recipients" do
      SiteSetting.maximum_recipients_per_new_group_email = 3
      expect { process(:cc) }.to raise_error(Email::Receiver::TooManyRecipientsError)
    end

    it "uses the incoming_email message-id as the new post's outbound_message_id" do
      expect { process(:cc) }.to change(Topic, :count)
      message_id = IncomingEmail.last.message_id
      expect(Topic.last.first_post.outbound_message_id).to eq(message_id)
    end

    describe "reply-to header" do
      before { SiteSetting.block_auto_generated_emails = false }

      it "extracts address and uses it for comparison" do
        expect { process(:reply_to_whitespaces) }.to change(Topic, :count).by(1)
        user = User.last
        incoming = IncomingEmail.find_by(message_id: "TXULO4v6YU0TzeL2buFAJNU2MK21c7t4@example.com")
        topic = incoming.topic
        expect(incoming.from_address).to eq("johndoe@example.com")
        expect(user.email).to eq("johndoe@example.com")
      end

      it "handles emails where there is a Reply-To address, using that instead of the from address, if X-Original-From is present" do
        expect { process(:reply_to_different_to_from) }.to change(Topic, :count).by(1)
        user = User.last
        incoming = IncomingEmail.find_by(message_id: "3848c3m98r439c348mc349@test.mailinglist.com")
        topic = incoming.topic
        expect(incoming.from_address).to eq("arthurmorgan@reddeadtest.com")
        expect(user.email).to eq("arthurmorgan@reddeadtest.com")
      end

      it "allows for quotes around the display name of the Reply-To address" do
        expect { process(:reply_to_different_to_from_quoted_display_name) }.to change(
          Topic,
          :count,
        ).by(1)
        user = User.last
        incoming = IncomingEmail.find_by(message_id: "3848c3m98r439c348mc349@test.mailinglist.com")
        topic = incoming.topic
        expect(incoming.from_address).to eq("johnmarston@reddeadtest.com")
        expect(user.email).to eq("johnmarston@reddeadtest.com")
      end

      it "does not use the reply-to address if an X-Original-From header is not present" do
        expect { process(:reply_to_different_to_from_no_x_original) }.to change(Topic, :count).by(1)
        user = User.last
        incoming = IncomingEmail.find_by(message_id: "3848c3m98r439c348mc349@test.mailinglist.com")
        topic = incoming.topic
        expect(incoming.from_address).to eq("westernsupport@test.mailinglist.com")
        expect(user.email).to eq("westernsupport@test.mailinglist.com")
      end

      it "does not use the reply-to address if the X-Original-From header is different from the reply-to address" do
        expect { process(:reply_to_different_to_from_x_original_different) }.to change(
          Topic,
          :count,
        ).by(1)
        user = User.last
        incoming = IncomingEmail.find_by(message_id: "3848c3m98r439c348mc349@test.mailinglist.com")
        topic = incoming.topic
        expect(incoming.from_address).to eq("westernsupport@test.mailinglist.com")
        expect(user.email).to eq("westernsupport@test.mailinglist.com")
      end
    end

    describe "when 'find_related_post_with_key' is disabled" do
      before { SiteSetting.find_related_post_with_key = false }

      it "associates email replies using both 'In-Reply-To' and 'References' headers" do
        expect { process(:email_reply_1) }.to change(Topic, :count).by(1) &
          change(Post, :count).by(3)

        topic = Topic.last
        ordered_posts = topic.ordered_posts

        expect(ordered_posts.first.raw).to eq("This is email reply **1**.")

        ordered_posts[1..-1].each do |post|
          expect(post.action_code).to eq("invited_user")
          expect(post.user.email).to eq("one@foo.com")

          expect(%w[two three].include?(post.custom_fields["action_code_who"])).to eq(true)
        end

        expect { process(:email_reply_2) }.to change { topic.posts.count }.by(1)
        expect { process(:email_reply_3) }.to change { topic.posts.count }.by(1)
        ordered_posts[1..-1].each(&:trash!)
        expect { process(:email_reply_4) }.to change { topic.posts.count }.by(1)
      end

      describe "replying with various message-id formats using In-Reply-To header" do
        let!(:topic) do
          process(:email_reply_1)
          Topic.last
        end
        let!(:post) { Fabricate(:post, topic: topic) }

        def process_mail_with_message_id(message_id)
          mail_string = <<~EMAIL
          Return-Path: <two@foo.com>
          From: Two <two@foo.com>
          To: one@foo.com
          Subject: RE: Testing email threading
          Date: Fri, 15 Jan 2016 00:12:43 +0100
          Message-ID: <44@foo.bar.mail>
          In-Reply-To: <#{message_id}>
          Mime-Version: 1.0
          Content-Type: text/plain
          Content-Transfer-Encoding: 7bit

          This is email reply testing with Message-ID formats.
          EMAIL
          Email::Receiver.new(mail_string).process!
        end

        it "posts a reply using a message-id in the format topic/TOPIC_ID/POST_ID@HOST" do
          expect {
            process_mail_with_message_id("topic/#{topic.id}/#{post.id}@test.localhost")
          }.to change { Post.count }.by(1)
          expect(topic.reload.posts.last.raw).to include(
            "This is email reply testing with Message-ID formats",
          )
        end

        it "posts a reply using a message-id in the format topic/TOPIC_ID@HOST" do
          expect { process_mail_with_message_id("topic/#{topic.id}@test.localhost") }.to change {
            Post.count
          }.by(1)
          expect(topic.reload.posts.last.raw).to include(
            "This is email reply testing with Message-ID formats",
          )
        end

        it "posts a reply using a message-id in the format topic/TOPIC_ID/POST_ID.RANDOM_SUFFIX@HOST" do
          expect {
            process_mail_with_message_id("topic/#{topic.id}/#{post.id}.rjc3yr79834y@test.localhost")
          }.to change { Post.count }.by(1)
          expect(topic.reload.posts.last.raw).to include(
            "This is email reply testing with Message-ID formats",
          )
        end

        it "posts a reply using a message-id in the format topic/TOPIC_ID.RANDOM_SUFFIX@HOST" do
          expect {
            process_mail_with_message_id(
              "topic/#{topic.id}/#{post.id}.x3487nxy877843x@test.localhost",
            )
          }.to change { Post.count }.by(1)
          expect(topic.reload.posts.last.raw).to include(
            "This is email reply testing with Message-ID formats",
          )
        end

        it "posts a reply using a message-id in the format discourse/post/POST_ID@HOST" do
          expect {
            process_mail_with_message_id("discourse/post/#{post.id}@test.localhost")
          }.to change { Post.count }.by(1)
          expect(topic.reload.posts.last.raw).to include(
            "This is email reply testing with Message-ID formats",
          )
        end
      end
    end

    it "supports any kind of attachments when 'allow_all_attachments_for_group_messages' is enabled" do
      SiteSetting.allow_all_attachments_for_group_messages = true
      expect { process(:attached_rb_file) }.to change(Topic, :count)

      post = Topic.last.first_post
      upload = post.uploads.first

      expect(post.raw).to include(
        "[#{upload.original_filename}|attachment](#{upload.short_url}) (#{upload.filesize} Bytes)",
      )
    end

    it "reenables user's PM email notifications when user emails new topic to group" do
      user = Fabricate(:user, email: "existing@bar.com")
      user.user_option.update_columns(email_messages_level: UserOption.email_level_types[:never])
      expect { process(:group_existing_user) }.to change(Topic, :count)
      user.reload
      expect(user.user_option.email_messages_level).to eq(UserOption.email_level_types[:always])
    end

    context "with forwarded emails behaviour set to create replies" do
      before do
        Fabricate(:group, incoming_email: "some_group@bar.com")
        SiteSetting.forwarded_emails_behaviour = "create_replies"
      end

      it "handles forwarded emails" do
        expect { process(:forwarded_email_1) }.to change(Topic, :count)

        forwarded_post, last_post = *Post.last(2)

        expect(forwarded_post.user.email).to eq("some@one.com")
        expect(last_post.user.email).to eq("ba@bar.com")

        expect(forwarded_post.raw).to match(/XoXo/)
        expect(last_post.raw).to match(/can you have a look at this email below/)

        expect(last_post.post_type).to eq(Post.types[:regular])
      end

      it "handles weirdly forwarded emails" do
        group.add(Fabricate(:user, email: "ba@bar.com"))
        group.save

        SiteSetting.forwarded_emails_behaviour = "create_replies"
        expect { process(:forwarded_email_2) }.to change(Topic, :count)

        forwarded_post, last_post = *Post.last(2)

        expect(forwarded_post.user.email).to eq("some@one.com")
        expect(last_post.user.email).to eq("ba@bar.com")

        expect(forwarded_post.raw).to match(/XoXo/)
        expect(last_post.raw).to match(/can you have a look at this email below/)

        expect(last_post.post_type).to eq(Post.types[:whisper])
      end

      # Who thought this was a good idea?!
      it "doesn't blow up with localized email headers" do
        expect { process(:forwarded_email_3) }.to change(Topic, :count)
      end

      it "adds a small action post to explain who forwarded the email when the sender didn't write anything" do
        expect { process(:forwarded_email_4) }.to change(Topic, :count)

        forwarded_post, last_post = *Post.last(2)

        expect(forwarded_post.user.email).to eq("some@one.com")
        expect(forwarded_post.raw).to match(/XoXo/)

        expect(last_post.user.email).to eq("ba@bar.com")
        expect(last_post.post_type).to eq(Post.types[:small_action])
        expect(last_post.action_code).to eq("forwarded")
      end
    end

    context "with forwarded emails behaviour set to quote" do
      before { SiteSetting.forwarded_emails_behaviour = "quote" }

      include_examples "creates topic with forwarded message as quote",
                       :group,
                       "team@bar.com|meat@bar.com"
    end

    context "when a reply is sent to a group's email_username" do
      let!(:topic) do
        group.update(email_username: "team@somesmtpaddress.com")
        process(:email_reply_1)
        Topic.last
      end

      it "does not invite the group email_username as a staged user" do
        process(:email_reply_to_group_email_username)
        expect(User.find_by_email("team@somesmtpaddress.com")).to eq(nil)
      end

      it "creates the reply when the sender and referenced messsage id are known" do
        expect { process(:email_reply_to_group_email_username) }.to change { topic.posts.count }.by(
          1,
        ).and not_change { Topic.count }
      end
    end

    context "when a group forwards an email to its inbox" do
      before do
        group.update!(
          email_username: "team@somesmtpaddress.com",
          incoming_email: "team@somesmtpaddress.com|support+team@bar.com",
          smtp_server: "smtp.test.com",
          smtp_port: 587,
          smtp_ssl: true,
          smtp_enabled: true,
        )
      end

      it "does not use the team's address as the from_address; it uses the original sender address" do
        process(:forwarded_by_group_to_inbox)
        topic = Topic.last
        expect(topic.incoming_email.first.to_addresses).to include("support+team@bar.com")
        expect(topic.incoming_email.first.from_address).to eq("fred@bedrock.com")
      end

      context "with forwarded emails behaviour set to create replies" do
        before { SiteSetting.forwarded_emails_behaviour = "create_replies" }

        it "does not use the team's address as the from_address; it uses the original sender address" do
          process(:forwarded_by_group_to_inbox)
          topic = Topic.last
          expect(topic.incoming_email.first.to_addresses).to include("support+team@bar.com")
          expect(topic.incoming_email.first.from_address).to eq("fred@bedrock.com")
        end

        it "does not say the email was forwarded by the original sender, it says the email is forwarded by the group" do
          expect { process(:forwarded_by_group_to_inbox) }.to change {
            User.where(staged: true).count
          }.by(4)
          topic = Topic.last
          forwarded_small_post = topic.ordered_posts.last
          expect(forwarded_small_post.action_code).to eq("forwarded")
          expect(forwarded_small_post.user).to eq(User.find_by_email("team@somesmtpaddress.com"))
        end

        it "keeps track of the cc addresses of the forwarded email and creates staged users for them" do
          expect { process(:forwarded_by_group_to_inbox) }.to change {
            User.where(staged: true).count
          }.by(4)
          topic = Topic.last
          cc_user1 = User.find_by_email("terry@ccland.com")
          cc_user2 = User.find_by_email("don@ccland.com")
          fred_user = User.find_by_email("fred@bedrock.com")
          team_user = User.find_by_email("team@somesmtpaddress.com")
          expect(topic.incoming_email.first.cc_addresses).to eq("terry@ccland.com;don@ccland.com")
          expect(topic.topic_allowed_users.pluck(:user_id)).to match_array(
            [fred_user.id, team_user.id, cc_user1.id, cc_user2.id],
          )
        end

        it "keeps track of the cc addresses of the final forwarded email as well" do
          expect { process(:forwarded_by_group_to_inbox_double_cc) }.to change {
            User.where(staged: true).count
          }.by(5)
          topic = Topic.last
          cc_user1 = User.find_by_email("terry@ccland.com")
          cc_user2 = User.find_by_email("don@ccland.com")
          fred_user = User.find_by_email("fred@bedrock.com")
          team_user = User.find_by_email("team@somesmtpaddress.com")
          someother_user = User.find_by_email("someotherparty@test.com")
          expect(topic.incoming_email.first.cc_addresses).to eq(
            "someotherparty@test.com;terry@ccland.com;don@ccland.com",
          )
          expect(topic.topic_allowed_users.pluck(:user_id)).to match_array(
            [fred_user.id, team_user.id, someother_user.id, cc_user1.id, cc_user2.id],
          )
        end

        context "when staged user for the team email already exists" do
          let!(:staged_team_user) do
            User.create!(
              email: "team@somesmtpaddress.com",
              username: UserNameSuggester.suggest("team@somesmtpaddress.com"),
              name: "team teamson",
              staged: true,
            )
          end

          it "uses that and does not create another staged user" do
            expect { process(:forwarded_by_group_to_inbox) }.to change {
              User.where(staged: true).count
            }.by(3)
            topic = Topic.last
            forwarded_small_post = topic.ordered_posts.last
            expect(forwarded_small_post.action_code).to eq("forwarded")
            expect(forwarded_small_post.user).to eq(staged_team_user)
          end
        end
      end
    end

    context "when emailing a group by email_username and following reply flow" do
      let!(:original_inbound_email_topic) do
        group.update!(
          email_username: "team@somesmtpaddress.com",
          incoming_email: "team@somesmtpaddress.com|suppor+team@bar.com",
          smtp_server: "smtp.test.com",
          smtp_port: 587,
          smtp_ssl: true,
          smtp_enabled: true,
        )
        process(:email_to_group_email_username_1)
        Topic.last
      end
      fab!(:user_in_group) do
        u = Fabricate(:user)
        Fabricate(:group_user, user: u, group: group)
        u
      end

      before do
        NotificationEmailer.enable
        SiteSetting.disallow_reply_by_email_after_days = 10_000
        Jobs.run_immediately!
      end

      def reply_as_group_user
        group_post =
          PostCreator.create(
            user_in_group,
            raw: "Thanks for your request. Please try to restart.",
            topic_id: original_inbound_email_topic.id,
          )
        email_log = EmailLog.last
        [email_log, group_post]
      end

      it "the inbound processed email creates an incoming email and topic record correctly, and adds the group to the topic" do
        incoming = IncomingEmail.find_by(topic: original_inbound_email_topic)
        user = User.find_by_email("two@foo.com")
        expect(original_inbound_email_topic.topic_allowed_users.first.user_id).to eq(user.id)
        expect(original_inbound_email_topic.topic_allowed_groups.first.group_id).to eq(group.id)
        expect(incoming.to_addresses).to eq("team@somesmtpaddress.com")
        expect(incoming.from_address).to eq("two@foo.com")
        expect(incoming.message_id).to eq("u4w8c9r4y984yh98r3h69873@foo.bar.mail")
      end

      it "creates an EmailLog when someone from the group replies, and does not create an IncomingEmail record for the reply" do
        email_log, group_post = reply_as_group_user
        expect(email_log.message_id).to eq("discourse/post/#{group_post.id}@test.localhost")
        expect(email_log.to_address).to eq("two@foo.com")
        expect(email_log.email_type).to eq("user_private_message")
        expect(email_log.post_id).to eq(group_post.id)
        expect(IncomingEmail.exists?(post_id: group_post.id)).to eq(false)
      end

      it "processes a reply from the OP user to the group SMTP username, linking the reply_to_post_number correctly by
      matching in_reply_to to the email log" do
        email_log, group_post = reply_as_group_user

        reply_email = email(:email_to_group_email_username_2)
        reply_email.gsub!("MESSAGE_ID_REPLY_TO", email_log.message_id)
        expect do Email::Receiver.new(reply_email).process! end.to not_change {
          Topic.count
        }.and change { Post.count }.by(1)

        reply_post = Post.last
        expect(reply_post.reply_to_user).to eq(user_in_group)
        expect(reply_post.reply_to_post_number).to eq(group_post.post_number)
      end

      it "processes the reply from the user as a brand new topic if they have replied from a different address (e.g. auto forward) and allow_unknown_sender_topic_replies is disabled" do
        email_log, group_post = reply_as_group_user

        reply_email = email(:email_to_group_email_username_2_as_unknown_sender)
        reply_email.gsub!("MESSAGE_ID_REPLY_TO", email_log.message_id)
        expect do Email::Receiver.new(reply_email).process! end.to change { Topic.count }.by(
          1,
        ).and change { Post.count }.by(1)

        reply_post = Post.last
        expect(reply_post.topic_id).not_to eq(original_inbound_email_topic.id)
      end

      it "processes the reply from the user as a reply if they have replied from a different address (e.g. auto forward) and allow_unknown_sender_topic_replies is enabled" do
        group.update!(allow_unknown_sender_topic_replies: true)
        email_log, group_post = reply_as_group_user

        reply_email = email(:email_to_group_email_username_2_as_unknown_sender)
        reply_email.gsub!("MESSAGE_ID_REPLY_TO", email_log.message_id)
        expect do Email::Receiver.new(reply_email).process! end.to not_change {
          Topic.count
        }.and change { Post.count }.by(1)

        reply_post = Post.last
        expect(reply_post.topic_id).to eq(original_inbound_email_topic.id)
      end

      it "creates a new topic with a reference back to the original if replying to a too old topic" do
        SiteSetting.disallow_reply_by_email_after_days = 2
        email_log, group_post = reply_as_group_user

        group_post.update(created_at: 10.days.ago)
        group_post.topic.update(created_at: 10.days.ago)

        reply_email = email(:email_to_group_email_username_2)
        reply_email.gsub!("MESSAGE_ID_REPLY_TO", email_log.message_id)
        expect do Email::Receiver.new(reply_email).process! end.to change { Topic.count }.by(
          1,
        ).and change { Post.count }.by(1)

        reply_post = Post.last
        new_topic = Topic.last

        expect(reply_post.topic).to eq(new_topic)
        expect(reply_post.raw).to include(
          I18n.t(
            "emails.incoming.continuing_old_discussion",
            url: group_post.topic.url,
            title: group_post.topic.title,
            count: SiteSetting.disallow_reply_by_email_after_days,
          ),
        )
      end
    end

    context "when message sent to a group has no key and find_related_post_with_key is enabled" do
      let!(:topic) do
        process(:email_reply_1)
        Topic.last
      end

      it "creates a reply when the sender and referenced message id are known" do
        expect { process(:email_reply_2) }.to change { topic.posts.count }.by(1).and not_change {
                Topic.count
              }
      end

      it "creates a new topic when the sender is not known and the group does not allow unknown senders to reply to topics" do
        IncomingEmail.where(message_id: "34@foo.bar.mail").update(cc_addresses: "three@foo.com")
        group.update(allow_unknown_sender_topic_replies: false)
        expect { process(:email_reply_2) }.to not_change { topic.posts.count }.and change {
                Topic.count
              }.by(1)
      end

      it "creates a new topic when the referenced message id is not known" do
        IncomingEmail.where(message_id: "34@foo.bar.mail").update(message_id: "99@foo.bar.mail")
        expect { process(:email_reply_2) }.to not_change { topic.posts.count }.and change {
                Topic.count
              }.by(1)
      end

      it "includes the sender on the topic when the message id is known, the sender is not known, and the group allows unknown senders to reply to topics" do
        IncomingEmail.where(message_id: "34@foo.bar.mail").update(cc_addresses: "three@foo.com")
        group.update(allow_unknown_sender_topic_replies: true)
        expect { process(:email_reply_2) }.to change { topic.posts.count }.by(1).and not_change {
                Topic.count
              }
      end

      context "when the sender is not in the topic allowed users" do
        before do
          user = User.find_by_email("two@foo.com")
          topic.topic_allowed_users.find_by(user: user).destroy
        end

        it "adds them to the topic at the same time" do
          IncomingEmail.where(message_id: "34@foo.bar.mail").update(cc_addresses: "three@foo.com")
          group.update(allow_unknown_sender_topic_replies: true)
          expect { process(:email_reply_2) }.to change { topic.posts.count }.by(1).and not_change {
                  Topic.count
                }
        end
      end
    end
  end

  describe "new topic in a category" do
    fab!(:category) do
      Fabricate(
        :category,
        email_in: "category@bar.com|category@foo.com",
        email_in_allow_strangers: false,
      )
    end

    it "raises a StrangersNotAllowedError when 'email_in_allow_strangers' is disabled" do
      expect { process(:new_user) }.to raise_error(Email::Receiver::StrangersNotAllowedError)
    end

    it "raises an InsufficientTrustLevelError when user's trust level isn't enough" do
      Fabricate(:user, email: "existing@bar.com", trust_level: 3)
      SiteSetting.email_in_min_trust = 4
      expect { process(:existing_user) }.to raise_error(
        Email::Receiver::InsufficientTrustLevelError,
      )
    end

    it "works" do
      handler_calls = 0
      handler =
        proc do |topic|
          expect(topic.incoming_email_addresses).to contain_exactly(
            "discourse@bar.com",
            "category@foo.com",
          )
          handler_calls += 1
        end

      DiscourseEvent.on(:topic_created, &handler)

      user =
        Fabricate(:user, email: "existing@bar.com", trust_level: SiteSetting.email_in_min_trust)
      group = Fabricate(:group)

      group.add(user)
      group.save

      category.set_permissions(group => :create_post)
      category.save!

      # raises an InvalidAccess when the user doesn't have the privileges to create a topic
      expect { process(:existing_user) }.to raise_error(Discourse::InvalidAccess)

      category.update_columns(email_in_allow_strangers: true)

      # allows new user to create a topic
      expect { process(:new_user) }.to change(Topic, :count)

      DiscourseEvent.off(:topic_created, &handler)
      expect(handler_calls).to eq(1)
    end

    it "creates visible topic for ham" do
      SiteSetting.email_in_spam_header = "none"

      Fabricate(:user, email: "existing@bar.com", trust_level: SiteSetting.email_in_min_trust)
      expect { process(:existing_user) }.to change { Topic.count }.by(1) # Topic created

      topic = Topic.last
      expect(topic.visible).to eq(true)

      post = Post.last
      expect(post.hidden).to eq(false)
      expect(post.hidden_at).to eq(nil)
      expect(post.hidden_reason_id).to eq(nil)
    end

    it "creates hidden topic for X-Spam-Flag" do
      SiteSetting.email_in_spam_header = "X-Spam-Flag"

      user =
        Fabricate(:user, email: "existing@bar.com", trust_level: SiteSetting.email_in_min_trust)
      expect { process(:spam_x_spam_flag) }.to change { ReviewableQueuedPost.count }.by(1)
      expect(user.reload.silenced?).to be(true)
    end

    it "creates hidden topic for X-Spam-Status" do
      SiteSetting.email_in_spam_header = "X-Spam-Status"

      user =
        Fabricate(:user, email: "existing@bar.com", trust_level: SiteSetting.email_in_min_trust)
      expect { process(:spam_x_spam_status) }.to change { ReviewableQueuedPost.count }.by(1)
      expect(user.reload.silenced?).to be(true)
    end

    it "creates hidden topic for X-SES-Spam-Verdict" do
      SiteSetting.email_in_spam_header = "X-SES-Spam-Verdict"

      user =
        Fabricate(:user, email: "existing@bar.com", trust_level: SiteSetting.email_in_min_trust)
      expect { process(:spam_x_ses_spam_verdict) }.to change { ReviewableQueuedPost.count }.by(1)
      expect(user.reload.silenced?).to be(true)
    end

    it "creates hidden topic for failed Authentication-Results header" do
      SiteSetting.email_in_authserv_id = "example.com"

      user =
        Fabricate(:user, email: "existing@bar.com", trust_level: SiteSetting.email_in_min_trust)
      expect { process(:dmarc_fail) }.to change { ReviewableQueuedPost.count }.by(1)
      expect(user.reload.silenced?).to be(false)
    end

    it "adds the 'elided' part of the original message when always_show_trimmed_content is enabled" do
      SiteSetting.always_show_trimmed_content = true

      Fabricate(:user, email: "existing@bar.com", trust_level: SiteSetting.email_in_min_trust)
      expect { process(:forwarded_email_to_category) }.to change { Topic.count }.by(1) # Topic created

      new_post, = Post.last
      expect(new_post.raw).to include(
        "Hi everyone, can you have a look at the email below?",
        "<summary title='Show trimmed content'>&#183;&#183;&#183;</summary>",
        "Discoursing much today?",
      )
    end

    it "works when approving is enabled" do
      SiteSetting.approve_unless_trust_level = 4

      Fabricate(:user, email: "tl3@bar.com", trust_level: TrustLevel[3])
      Fabricate(:user, email: "tl4@bar.com", trust_level: TrustLevel[4])

      category.set_permissions(Group[:trust_level_4] => :full)
      category.save!

      Group.refresh_automatic_group!(:trust_level_4)

      expect { process(:tl3_user) }.to raise_error(Email::Receiver::InvalidPost)
      expect { process(:tl4_user) }.to change(Topic, :count)
    end

    it "ignores by case-insensitive title" do
      SiteSetting.ignore_by_title = "foo"
      expect { process(:ignored) }.to_not change(Topic, :count)
    end

    it "associates email from a secondary address with user" do
      user =
        Fabricate(
          :user,
          trust_level: SiteSetting.email_in_min_trust,
          user_emails: [Fabricate.build(:secondary_email, email: "existing@bar.com")],
        )

      expect { process(:existing_user) }.to change(Topic, :count).by(1)

      topic = Topic.last

      expect(topic.posts.last.raw).to eq("Hey, this is a topic from an existing user ;)")

      expect(topic.user).to eq(user)
    end
  end

  describe "new topic in a category that allows strangers" do
    fab!(:category) do
      Fabricate(
        :category,
        email_in: "category@bar.com|category@foo.com",
        email_in_allow_strangers: true,
      )
    end

    it "lets an email in from a stranger" do
      expect { process(:new_user) }.to change(Topic, :count)
    end

    it "lets an email in from a high-TL user" do
      Fabricate(:user, email: "tl4@bar.com", trust_level: TrustLevel[4])
      expect { process(:tl4_user) }.to change(Topic, :count)
    end

    it "fails on email from a low-TL user" do
      SiteSetting.email_in_min_trust = 4
      Fabricate(:user, email: "tl3@bar.com", trust_level: TrustLevel[3])
      expect { process(:tl3_user) }.to raise_error(Email::Receiver::InsufficientTrustLevelError)
    end
  end

  describe "#reply_by_email_address_regex" do
    before do
      SiteSetting.reply_by_email_address = nil
      SiteSetting.alternative_reply_by_email_addresses = nil
    end

    it "it matches nothing if there is not reply_by_email_address" do
      expect(Email::Receiver.reply_by_email_address_regex).to eq(/$a/)
    end

    it "uses 'reply_by_email_address' site setting" do
      SiteSetting.reply_by_email_address = "foo+%{reply_key}@bar.com"
      expect(Email::Receiver.reply_by_email_address_regex).to eq(/foo\+?(\h{32})?@bar\.com/)
    end

    it "uses 'alternative_reply_by_email_addresses' site setting" do
      SiteSetting.alternative_reply_by_email_addresses = "alt.foo+%{reply_key}@bar.com"
      expect(Email::Receiver.reply_by_email_address_regex).to eq(/alt\.foo\+?(\h{32})?@bar\.com/)
    end

    it "combines both 'reply_by_email' settings" do
      SiteSetting.reply_by_email_address = "foo+%{reply_key}@bar.com"
      SiteSetting.alternative_reply_by_email_addresses = "alt.foo+%{reply_key}@bar.com"
      expect(Email::Receiver.reply_by_email_address_regex).to eq(
        /foo\+?(\h{32})?@bar\.com|alt\.foo\+?(\h{32})?@bar\.com/,
      )
    end
  end

  describe "check_address" do
    before { SiteSetting.reply_by_email_address = "foo+%{reply_key}@bar.com" }

    it "returns nil when the key is invalid" do
      expect(Email::Receiver.check_address("fake@fake.com")).to be_nil
      expect(
        Email::Receiver.check_address("foo+4f97315cc828096c9cb34c6f1a0d6fe8@bar.com"),
      ).to be_nil
    end

    context "with a valid reply" do
      it "returns the destination when the key is valid" do
        post_reply_key = Fabricate(:post_reply_key, reply_key: "4f97315cc828096c9cb34c6f1a0d6fe8")

        dest = Email::Receiver.check_address("foo+4f97315cc828096c9cb34c6f1a0d6fe8@bar.com")

        expect(dest).to eq(post_reply_key)
      end
    end
  end

  describe "staged users" do
    before { SiteSetting.enable_staged_users = true }

    shared_examples "does not create staged users" do |email_name, expected_exception|
      it "does not create staged users" do
        staged_user_count = User.where(staged: true).count
        User.expects(:create).never
        User.expects(:create!).never

        if expected_exception
          expect { process(email_name) }.to raise_error(expected_exception)
        else
          process(email_name)
        end

        expect(User.where(staged: true).count).to eq(staged_user_count)
      end
    end

    shared_examples "cleans up staged users" do |email_name, expected_exception|
      it "cleans up staged users" do
        staged_user_count = User.where(staged: true).count
        expect { process(email_name) }.to raise_error(expected_exception)
        expect(User.where(staged: true).count).to eq(staged_user_count)
      end
    end

    context "when email address is screened" do
      before { ScreenedEmail.expects(:should_block?).with("screened@mail.com").returns(true) }

      include_examples "does not create staged users",
                       :screened_email,
                       Email::Receiver::ScreenedEmailError
    end

    context "when the mail is auto generated" do
      include_examples "does not create staged users",
                       :auto_generated_header,
                       Email::Receiver::AutoGeneratedEmailError
    end

    context "when email is a bounced email" do
      include_examples "does not create staged users",
                       :bounced_email,
                       Email::Receiver::BouncedEmailError
    end

    context "when the body is blank" do
      include_examples "does not create staged users",
                       :no_body,
                       Email::Receiver::NoBodyDetectedError
    end

    context "when unsubscribe via email is not allowed" do
      include_examples "does not create staged users",
                       :unsubscribe_new_user,
                       Email::Receiver::UnsubscribeNotAllowed
    end

    context "when From email address is not on allowlist" do
      before do
        SiteSetting.allowed_email_domains = "example.com|bar.com"
        Fabricate(:group, incoming_email: "some_group@bar.com")
      end

      include_examples "does not create staged users",
                       :blocklist_allowlist_email,
                       Email::Receiver::EmailNotAllowed
    end

    context "when From email address is on blocklist" do
      before do
        SiteSetting.blocked_email_domains = "email.com|mail.com"
        Fabricate(:group, incoming_email: "some_group@bar.com")
      end

      include_examples "does not create staged users",
                       :blocklist_allowlist_email,
                       Email::Receiver::EmailNotAllowed
    end

    context "with blocklist and allowlist for To and Cc" do
      before { Fabricate(:group, incoming_email: "some_group@bar.com") }

      it "does not create staged users for email addresses not on allowlist" do
        SiteSetting.allowed_email_domains = "mail.com|example.com"
        process(:blocklist_allowlist_email)

        expect(User.find_by_email("alice@foo.com")).to be_nil
        expect(User.find_by_email("bob@foo.com")).to be_nil
        expect(User.find_by_email("carol@example.com")).to be_present
      end

      it "does not create staged users for email addresses on blocklist" do
        SiteSetting.blocked_email_domains = "email.com|foo.com"
        process(:blocklist_allowlist_email)

        expect(User.find_by_email("alice@foo.com")).to be_nil
        expect(User.find_by_email("bob@foo.com")).to be_nil
        expect(User.find_by_email("carol@example.com")).to be_present
      end
    end

    context "when destinations aren't matching any of the incoming emails" do
      include_examples "does not create staged users",
                       :bad_destinations,
                       Email::Receiver::BadDestinationAddress
    end

    context "when email is sent to category" do
      context "when email is sent by a new user and category does not allow strangers" do
        fab!(:category) do
          Fabricate(:category, email_in: "category@foo.com", email_in_allow_strangers: false)
        end

        include_examples "does not create staged users",
                         :new_user,
                         Email::Receiver::StrangersNotAllowedError
      end

      context "when email has no date" do
        fab!(:category) do
          Fabricate(:category, email_in: "category@foo.com", email_in_allow_strangers: true)
        end

        it "includes the translated string in the error" do
          expect { process(:no_date) }.to raise_error(Email::Receiver::InvalidPost).with_message(
            I18n.t("system_messages.email_reject_invalid_post_specified.date_invalid"),
          )
        end

        include_examples "does not create staged users", :no_date, Email::Receiver::InvalidPost
      end

      context "with forwarded emails behaviour set to quote" do
        before { SiteSetting.forwarded_emails_behaviour = "quote" }

        context "with a category which allows strangers" do
          fab!(:category) do
            Fabricate(:category, email_in: "team@bar.com", email_in_allow_strangers: true)
          end
          include_examples "creates topic with forwarded message as quote",
                           :category,
                           "team@bar.com"
        end

        context "with a category which doesn't allow strangers" do
          fab!(:category) do
            Fabricate(:category, email_in: "team@bar.com", email_in_allow_strangers: false)
          end
          include_examples "cleans up staged users",
                           :forwarded_email_1,
                           Email::Receiver::StrangersNotAllowedError
        end
      end
    end

    context "when email is a reply" do
      let(:reply_key) { "4f97315cc828096c9cb34c6f1a0d6fe8" }
      fab!(:category) { Fabricate(:category) }
      fab!(:user) { Fabricate(:user, email: "discourse@bar.com") }
      fab!(:user2) { Fabricate(:user, email: "someone_else@bar.com") }
      fab!(:topic) { create_topic(category: category, user: user) }
      fab!(:post) { create_post(topic: topic, user: user) }

      let!(:post_reply_key) do
        Fabricate(:post_reply_key, reply_key: reply_key, user: user, post: post)
      end

      context "when the email address isn't matching the one we sent the notification to" do
        include_examples "does not create staged users",
                         :reply_user_not_matching,
                         Email::Receiver::ReplyUserNotMatchingError
      end

      context "with forwarded emails behaviour set to create replies" do
        before { SiteSetting.forwarded_emails_behaviour = "create_replies" }

        context "when a reply contains a forwarded email" do
          include_examples "does not create staged users", :reply_and_forwarded
        end

        context "with forwarded email to category that doesn't allow strangers" do
          before { category.update!(email_in: "team@bar.com", email_in_allow_strangers: false) }

          include_examples "cleans up staged users",
                           :forwarded_email_1,
                           Email::Receiver::StrangersNotAllowedError
        end
      end
    end

    context "when replying without key is allowed" do
      fab!(:group) { Fabricate(:group, incoming_email: "team@bar.com") }
      let!(:topic) do
        SiteSetting.find_related_post_with_key = false
        process(:email_reply_1)
        Topic.last
      end

      context "when the topic was deleted" do
        before { topic.update_columns(deleted_at: 1.day.ago) }

        include_examples "cleans up staged users",
                         :email_reply_staged,
                         Email::Receiver::TopicNotFoundError
      end

      context "when the topic was closed" do
        before { topic.update_columns(closed: true) }

        include_examples "cleans up staged users",
                         :email_reply_staged,
                         Email::Receiver::TopicClosedError
      end

      context "when they aren't allowed to like a post" do
        before { topic.update_columns(archived: true) }

        include_examples "cleans up staged users",
                         :email_reply_like,
                         Email::Receiver::InvalidPostAction
      end
    end

    it "does not remove the incoming email record when staged users are deleted" do
      expect { process(:bad_destinations) }.to change { IncomingEmail.count }.and raise_error(
              Email::Receiver::BadDestinationAddress,
            )
      expect(IncomingEmail.last.message_id).to eq("9@foo.bar.mail")
    end
  end

  describe "mailman mirror" do
    fab!(:category) { Fabricate(:mailinglist_mirror_category) }

    it "uses 'from' email address" do
      expect { process(:mailman_1) }.to change { Topic.count }
      user = Topic.last.user
      expect(user.email).to eq("some@one.com")
      expect(user.name).to eq("Some One")
    end

    it "uses 'reply-to' email address" do
      expect { process(:mailman_2) }.to change { Topic.count }
      user = Topic.last.user
      expect(user.email).to eq("some@one.com")
      expect(user.name).to eq("Some")
    end

    it "uses 'x-mailfrom' email address and name from CC" do
      expect { process(:mailman_3) }.to change { Topic.count }
      user = Topic.last.user
      expect(user.email).to eq("some@one.com")
      expect(user.name).to eq("Some One")
    end

    it "uses 'x-original-from' email address" do
      expect { process(:mailman_4) }.to change { Topic.count }
      user = Topic.last.user
      expect(user.email).to eq("some@one.com")
      expect(user.name).to eq("Some")
    end
  end

  describe "mailing list mirror" do
    fab!(:category) { Fabricate(:mailinglist_mirror_category) }

    before { SiteSetting.block_auto_generated_emails = true }

    it "should allow creating topic even when email is autogenerated" do
      expect { process(:mailinglist) }.to change { Topic.count }
      expect(IncomingEmail.last.is_auto_generated).to eq(false)
    end

    it "should allow replying without reply key" do
      process(:mailinglist)
      topic = Topic.last

      expect { process(:mailinglist_reply) }.to change { topic.posts.count }
    end

    it "should skip validations for staged users" do
      Fabricate(:user, email: "alice@foo.com", staged: true)
      expect { process(:mailinglist_short_message) }.to change { Topic.count }
    end

    it "should skip validations for regular users" do
      Fabricate(:user, email: "alice@foo.com")
      expect { process(:mailinglist_short_message) }.to change { Topic.count }
    end

    context "with read-only category" do
      before do
        category.set_permissions(everyone: :readonly)
        category.save!

        Fabricate(:user, email: "alice@foo.com")
        Fabricate(:user, email: "bob@bar.com")
      end

      it "should allow creating topic within read-only category" do
        expect { process(:mailinglist) }.to change { Topic.count }
      end

      it "should allow replying within read-only category" do
        process(:mailinglist)
        topic = Topic.last

        expect { process(:mailinglist_reply) }.to change { topic.posts.count }
      end
    end

    it "ignores unsubscribe email" do
      SiteSetting.unsubscribe_via_email = true
      Fabricate(:user, email: "alice@foo.com")

      expect { process("mailinglist_unsubscribe") }.to_not change {
        ActionMailer::Base.deliveries.count
      }
    end

    it "ignores dmarc fails" do
      expect { process("mailinglist_dmarc_fail") }.to change { Topic.count }

      post = Topic.last.first_post
      expect(post.hidden).to eq(false)
      expect(post.hidden_reason_id).to be_nil
    end
  end

  it "tries to fix unparsable email addresses in To and CC headers" do
    expect { process(:unparsable_email_addresses) }.to raise_error(
      Email::Receiver::BadDestinationAddress,
    )

    email = IncomingEmail.last
    expect(email.to_addresses).to eq("foo@bar.com")
    expect(email.cc_addresses).to eq("bob@example.com;carol@example.com")
  end

  describe "#select_body" do
    let(:email) { <<~EMAIL }
      MIME-Version: 1.0
      Date: Tue, 01 Jan 2019 00:00:00 +0300
      Subject: An email with whitespaces
      From: Foo <foo@discourse.org>
      To: bar@discourse.org
      Content-Type: text/plain; charset="UTF-8"

          This is a line that will be stripped
          This is another line that will be stripped

      This is a line that will not be touched.
      This is another line that will not be touched.

      * list

        * sub-list

      - list

        - sub-list

      + list

        + sub-list

      [code]
        1.upto(10).each do |i|
          puts i
        end

      ```
        # comment
      [/code]

          This is going to be stripped too.

      ```
        1.upto(10).each do |i|
          puts i
        end

      [/code]
        # comment
      ```

              This is going to be stripped too.

      Bye!
      EMAIL

    let(:stripped_text) { <<~MD }
      This is a line that will be stripped
      This is another line that will be stripped

      This is a line that will not be touched.
      This is another line that will not be touched.

      * list

        * sub-list

      - list

        - sub-list

      + list

        + sub-list

      [code]
        1.upto(10).each do |i|
          puts i
        end

      ```
        # comment
      [/code]

      This is going to be stripped too.

      ```
        1.upto(10).each do |i|
          puts i
        end

      [/code]
        # comment
      ```

      This is going to be stripped too.

      Bye!
      MD

    it "strips lines if strip_incoming_email_lines is enabled" do
      SiteSetting.strip_incoming_email_lines = true

      receiver = Email::Receiver.new(email)
      text, elided, format = receiver.select_body
      expect(text).to eq(stripped_text)
    end

    it "works with empty mail body" do
      SiteSetting.strip_incoming_email_lines = true

      email = <<~EMAIL
        Date: Tue, 01 Jan 2019 00:00:00 +0300
        Subject: An email with whitespaces
        From: Foo <foo@discourse.org>
        To: bar@discourse.org
        Content-Type: text/plain; charset="UTF-8"

        --
        my signature

      EMAIL

      receiver = Email::Receiver.new(email)
      text, _elided, _format = receiver.select_body
      expect(text).to be_blank
    end
  end

  describe "replying to digest" do
    fab!(:user) { Fabricate(:user) }
    fab!(:digest_message_id) { "7402d8ae-1c6e-44bc-9948-48e007839bcc@localhost" }
    fab!(:email_log) do
      Fabricate(
        :email_log,
        user: user,
        email_type: "digest",
        to_address: user.email,
        message_id: digest_message_id,
      )
    end
    let(:email) { <<~EMAIL }
      MIME-Version: 1.0
      Date: Tue, 01 Jan 2019 00:00:00 +0300
      From: someone <#{user.email}>
      To: Discourse <#{SiteSetting.notification_email}>
      Message-ID: <CANtGPwC3ZmWSxnnEuJHfosbtc9d0-ZV02b_7KuyircDt4peDC2@mail.gmail.com>
      In-Reply-To: <#{digest_message_id}>
      Subject: Re: [Discourse] Summary
      References: <#{digest_message_id}>
      Content-Type: text/plain; charset="UTF-8"

      hello there! I like the digest!

      EMAIL

    before { Jobs.run_immediately! }

    it "returns a ReplyToDigestError" do
      expect { Email::Receiver.new(email).process! }.to raise_error(
        Email::Receiver::ReplyToDigestError,
      )
    end
  end

  describe "find_related_post" do
    let(:user) { Fabricate(:user) }
    let(:group) { Fabricate(:group, users: [user]) }

    let (:email_1) {
      <<~EMAIL
      MIME-Version: 1.0
      Date: Wed, 01 Jan 2019 12:00:00 +0200
      Message-ID: <7aN1uwcokt2xkfG3iYrpKmiuVhy4w9b5@mail.gmail.com>
      Subject: Lorem ipsum dolor sit amet
      From: Dan Ungureanu <dan@discourse.org>
      To: team-test@discourse.org
      Content-Type: text/plain; charset="UTF-8"

      Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas
      semper, erat tempor sodales commodo, mi diam tempus lorem, in vehicula
      leo libero quis lacus. Nullam justo nunc, sagittis nec metus placerat,
      auctor condimentum neque. Sed risus purus, fermentum eget purus
      porttitor, finibus efficitur orci. Integer tempus mi nec odio
      elementum pulvinar. Pellentesque sed fringilla nulla, ac mollis quam.
      Vivamus semper lacinia scelerisque. Cras urna magna, porttitor nec
      libero quis, congue viverra sapien. Nulla sodales ac tellus a
      suscipit.
      EMAIL
    }

    let (:post_2) {
      incoming_email =
        IncomingEmail.find_by(message_id: "7aN1uwcokt2xkfG3iYrpKmiuVhy4w9b5@mail.gmail.com")

      PostCreator.create(
        user,
        raw:
          "Vestibulum rutrum tortor vitae arcu varius, non vestibulum ipsum tempor. Integer nibh libero, dignissim eu velit vel, interdum posuere mi. Aliquam erat volutpat. Pellentesque id nulla ultricies, eleifend ipsum non, fringilla purus. Aliquam pretium dolor lobortis urna volutpat, vel consectetur arcu porta. In non erat quis nibh gravida pharetra consequat vel risus. Aliquam rutrum consectetur est ac posuere. Praesent mattis nunc risus, a molestie lectus accumsan porta.",
        topic_id: incoming_email.topic_id,
      )
    }

    let (:email_3) {
      <<~EMAIL
      MIME-Version: 1.0
      Date: Wed, 01 Jan 2019 12:00:00 +0200
      References: <7aN1uwcokt2xkfG3iYrpKmiuVhy4w9b5@mail.gmail.com> <topic/#{post_2.topic_id}/#{post_2.id}@test.localhost>
      In-Reply-To: <topic/#{post_2.topic_id}/#{post_2.id}@test.localhost>
      Message-ID: <w1vdxT8ebJjZQQp7XyDdEJaSscE9qRjr@mail.gmail.com>
      Subject: Re: Lorem ipsum dolor sit amet
      From: Dan Ungureanu <dan@discourse.org>
      To: team-test@discourse.org
      Content-Type: text/plain; charset="UTF-8"

      Integer mattis suscipit facilisis. Ut ullamcorper libero at faucibus
      sodales. Ut suscipit elit ac dui porta consequat. Suspendisse potenti.
      Nam ut accumsan dui, eget commodo sapien. Etiam ultrices elementum
      cursus. Vivamus et diam et orci lobortis porttitor. Aliquam
      scelerisque ex a imperdiet ornare. Donec interdum laoreet posuere.
      Nulla sagittis, velit id posuere sollicitudin, elit nunc laoreet
      libero, vitae aliquet tortor eros at est. Donec vitae massa vehicula,
      aliquet libero non, porttitor ipsum. Phasellus pellentesque sodales
      lacus eu sagittis. Aliquam ut condimentum nisi. Nulla in placerat
      felis. Sed pellentesque, massa auctor venenatis gravida, risus lorem
      iaculis mi, at hendrerit nisi turpis sit amet metus. Nulla egestas
      ante eget nisi luctus consectetur.
      EMAIL
    }

    def receive(email_string)
      Email::Receiver.new(email_string, destinations: [group]).process!
    end

    it "makes all posts in same topic" do
      expect { receive(email_1) }.to change { Topic.count }.by(1).and change {
              Post.where(post_type: Post.types[:regular]).count
            }.by(1)
      expect { post_2 }.to not_change { Topic.count }.and change {
              Post.where(post_type: Post.types[:regular]).count
            }.by(1)
      expect { receive(email_3) }.to not_change { Topic.count }.and change {
              Post.where(post_type: Post.types[:regular]).count
            }.by(1)
    end
  end

  it "fixes valid addresses in embedded emails" do
    Fabricate(:group, incoming_email: "group-fwd@example.com")
    process(:long_embedded_email_headers)
    incoming_email = IncomingEmail.find_by(message_id: "example1@mail.gmail.com")
    expect(incoming_email.cc_addresses).to include("a@example.com")
    expect(incoming_email.cc_addresses).to include("c@example.com")
  end
end