multi_mail 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +57 -34
  3. data/bin/multi_mail_post +71 -0
  4. data/lib/mail_ext/message.rb +11 -0
  5. data/lib/multi_mail.rb +25 -8
  6. data/lib/multi_mail/cloudmailin/receiver.rb +24 -3
  7. data/lib/multi_mail/mailgun/message.rb +6 -1
  8. data/lib/multi_mail/mailgun/sender.rb +4 -4
  9. data/lib/multi_mail/mandrill/message.rb +4 -3
  10. data/lib/multi_mail/mandrill/sender.rb +9 -2
  11. data/lib/multi_mail/message/base.rb +14 -0
  12. data/lib/multi_mail/postmark/message.rb +74 -0
  13. data/lib/multi_mail/postmark/receiver.rb +2 -1
  14. data/lib/multi_mail/postmark/sender.rb +54 -24
  15. data/lib/multi_mail/sendgrid/message.rb +4 -4
  16. data/lib/multi_mail/sendgrid/sender.rb +2 -2
  17. data/lib/multi_mail/simple/receiver.rb +31 -1
  18. data/lib/multi_mail/version.rb +1 -1
  19. data/multi_mail.gemspec +2 -1
  20. data/spec/cloudmailin/receiver_spec.rb +89 -84
  21. data/spec/fixtures/cloudmailin/json/attachment_store.txt +65 -0
  22. data/spec/fixtures/cloudmailin/multipart/attachment_store.txt +174 -0
  23. data/spec/fixtures/cloudmailin/raw/attachment_store.txt +162 -0
  24. data/spec/fixtures/mailgun/parsed/valid.txt +107 -101
  25. data/spec/fixtures/simple/invalid.txt +4 -0
  26. data/spec/fixtures/simple/missing.txt +4 -0
  27. data/spec/fixtures/simple/valid.txt +1 -1
  28. data/spec/mail_ext/message_spec.rb +45 -0
  29. data/spec/mailgun/message_spec.rb +38 -8
  30. data/spec/mailgun/receiver_spec.rb +104 -110
  31. data/spec/mailgun/sender_spec.rb +13 -7
  32. data/spec/mandrill/message_spec.rb +25 -1
  33. data/spec/mandrill/receiver_spec.rb +81 -83
  34. data/spec/mandrill/sender_spec.rb +13 -6
  35. data/spec/message/base_spec.rb +33 -1
  36. data/spec/postmark/message_spec.rb +292 -0
  37. data/spec/postmark/receiver_spec.rb +46 -48
  38. data/spec/postmark/sender_spec.rb +10 -10
  39. data/spec/sendgrid/message_spec.rb +6 -1
  40. data/spec/sendgrid/receiver_spec.rb +56 -58
  41. data/spec/sendgrid/sender_spec.rb +9 -7
  42. data/spec/service_spec.rb +1 -1
  43. data/spec/simple/receiver_spec.rb +38 -25
  44. data/spec/spec_helper.rb +6 -8
  45. metadata +185 -203
@@ -1,5 +1,3 @@
1
- require 'multi_mail/mailgun/message'
2
-
3
1
  module MultiMail
4
2
  module Sender
5
3
  # Mailgun's outgoing mail sender.
@@ -14,6 +12,8 @@ module MultiMail
14
12
  #
15
13
  # @param [Hash] options required and optional arguments
16
14
  # @option options [String] :api_key a Mailgun API key
15
+ # @option options [String] :domain the Mailgun email domain
16
+ # @see http://documentation.mailgun.com/api-intro.html#base-url
17
17
  def initialize(options = {})
18
18
  super
19
19
  @api_key = settings.delete(:api_key)
@@ -62,7 +62,7 @@ module MultiMail
62
62
  case response.status
63
63
  when 401
64
64
  raise InvalidAPIKey, response.body
65
- when 400
65
+ when 400, 404
66
66
  body = JSON.load(response.body)
67
67
  case body['message']
68
68
  when "'from' parameter is missing"
@@ -72,7 +72,7 @@ module MultiMail
72
72
  when "Need at least one of 'text' or 'html' parameters specified"
73
73
  raise MissingBody, body['message']
74
74
  else
75
- raise InvalidMessage, body['message']
75
+ raise InvalidRequest, body['message']
76
76
  end
77
77
  when 200
78
78
  body = JSON.load(response.body)
@@ -22,16 +22,16 @@ module MultiMail
22
22
  #
23
23
  # @return [Hash] the message headers in Mandrill format
24
24
  def mandrill_headers
25
- headers = {}
25
+ hash = {}
26
26
  header_fields.each do |field|
27
27
  key = field.name.downcase
28
28
  # Mandrill only allows Reply-To and X-* headers currently.
29
29
  # https://mandrillapp.com/api/docs/messages.ruby.html
30
30
  if key == 'reply-to' || key.start_with?('x-')
31
- headers[field.name] = field.value
31
+ hash[field.name] = field.value
32
32
  end
33
33
  end
34
- headers
34
+ hash
35
35
  end
36
36
 
37
37
  # Returns the message's attachments in Mandrill format.
@@ -65,6 +65,7 @@ module MultiMail
65
65
  'headers' => mandrill_headers,
66
66
  'attachments' => attachments,
67
67
  'images' => images,
68
+ 'tags' => tags,
68
69
  }
69
70
 
70
71
  normalize(hash)
@@ -1,5 +1,3 @@
1
- require 'multi_mail/mandrill/message'
2
-
3
1
  module MultiMail
4
2
  module Sender
5
3
  # Mandrill's outgoing mail sender.
@@ -14,13 +12,22 @@ module MultiMail
14
12
  #
15
13
  # @param [Hash] options required and optional arguments
16
14
  # @option options [String] :api_key a Mandrill API key
15
+ # @option options [Boolean] :async whether to enable a background sending
16
+ # mode optimized for bulk sending
17
+ # @option options [String] :ip_pool the name of the dedicated IP pool that
18
+ # should be used to send the message
19
+ # @option options [Time,String] :send_at when this message should be sent
17
20
  # @see https://mandrillapp.com/api/docs/index.ruby.html
21
+ # @see https://mandrillapp.com/api/docs/messages.JSON.html#method-send
18
22
  def initialize(options = {})
19
23
  super
20
24
  @api_key = settings.delete(:api_key)
21
25
  @async = settings.delete(:async) || false
22
26
  @ip_pool = settings.delete(:ip_pool)
23
27
  @send_at = settings.delete(:send_at)
28
+ unless @send_at.nil? or String === @send_at
29
+ @send_at = @send_at.utc.strftime('%Y-%m-%d %T')
30
+ end
24
31
  end
25
32
 
26
33
  # Returns the additional parameters for the API call.
@@ -22,6 +22,20 @@ module MultiMail
22
22
  end
23
23
  end
24
24
 
25
+ def tags
26
+ if self['tag']
27
+ if self['tag'].respond_to?(:map)
28
+ self['tag'].map do |field|
29
+ field.value
30
+ end
31
+ else
32
+ [self['tag'].value]
33
+ end
34
+ else
35
+ []
36
+ end
37
+ end
38
+
25
39
  private
26
40
 
27
41
  def normalize(hash)
@@ -0,0 +1,74 @@
1
+ module MultiMail
2
+ module Message
3
+ # @see http://developer.postmarkapp.com/developer-build.html#message-format
4
+ class Postmark < MultiMail::Message::Base
5
+ # Returns the message headers in Postmark format.
6
+ #
7
+ # @return [Multimap] the message headers in Postmark format
8
+ def postmark_headers
9
+ array = []
10
+ header_fields.each do |field|
11
+ key = field.name.downcase
12
+ # @see https://github.com/wildbit/postmark-gem/blob/master/lib/postmark/message_extensions/mail.rb#L74
13
+ # @see https://github.com/wildbit/postmark-gem/pull/36#issuecomment-22298955
14
+ unless %w(from to cc bcc reply-to subject tag content-type date).include?(key) || (Array === field.value && field.value.size > 1)
15
+ array << {'Name' => field.name, 'Value' => field.value}
16
+ end
17
+ end
18
+ array
19
+ end
20
+
21
+ # Returns the message's attachments in Postmark format.
22
+ #
23
+ # @return [Multimap] the attachments in Postmark format
24
+ # @see http://developer.postmarkapp.com/developer-build.html#attachments
25
+ def postmark_attachments
26
+ attachments.map do |attachment|
27
+ hash = {
28
+ 'ContentType' => attachment.content_type,
29
+ 'Name' => attachment.filename,
30
+ 'Content' => Base64.encode64(attachment.body.decoded)
31
+ }
32
+ if attachment.content_type.start_with?('image/')
33
+ hash['ContentID'] = attachment.filename
34
+ end
35
+ hash
36
+ end
37
+ end
38
+
39
+ # Returns the message as parameters to POST to Postmark.
40
+ #
41
+ # @return [Hash] the message as parameters to POST to Postmark
42
+ def to_postmark_hash
43
+ hash = {}
44
+
45
+ %w(from subject).each do |field|
46
+ if self[field]
47
+ hash[postmark_key(field)] = self[field].value
48
+ end
49
+ end
50
+
51
+ %w(to cc bcc reply_to).each do |field|
52
+ if self[field]
53
+ value = self[field].value
54
+ hash[postmark_key(field)] = value.respond_to?(:join) ? value.join(', ') : value
55
+ end
56
+ end
57
+
58
+ hash['TextBody'] = body_text
59
+ hash['HtmlBody'] = body_html
60
+ hash['Headers'] = postmark_headers
61
+ hash['Attachments'] = postmark_attachments
62
+ hash['Tag'] = tags.first
63
+
64
+ normalize(hash)
65
+ end
66
+
67
+ private
68
+
69
+ def postmark_key(string)
70
+ string.downcase.split(/[_-]/).map(&:capitalize).join
71
+ end
72
+ end
73
+ end
74
+ end
@@ -16,7 +16,8 @@ module MultiMail
16
16
  cc = params['CcFull'].map{|hash| transform_address(hash)}
17
17
 
18
18
  message = Mail.new do
19
- headers headers
19
+ headers headers
20
+ message_id params['MessageID']
20
21
 
21
22
  from from
22
23
  to to
@@ -1,42 +1,72 @@
1
- begin
2
- require 'postmark'
3
- rescue LoadError
4
- raise 'The postmark gem is not available. In order to use the Postmark sender, you must: gem install postmark'
5
- end
6
-
7
1
  module MultiMail
8
2
  module Sender
9
3
  # Postmark's outgoing mail sender.
10
4
  class Postmark
11
5
  include MultiMail::Sender::Base
12
6
 
13
- # @see https://github.com/wildbit/postmark-gem#communicating-with-the-api
14
7
  requires :api_key
15
8
 
9
+ attr_reader :api_key
10
+
11
+ # Initializes a Postmark outgoing email sender.
12
+ #
13
+ # @param [Hash] options required and optional arguments
14
+ # @option options [String] :api_key a Postmark API key
15
+ # @see http://developer.postmarkapp.com/developer-build.html#authentication-headers
16
+ def initialize(options = {})
17
+ super
18
+ @api_key = settings.delete(:api_key)
19
+ end
20
+
16
21
  # Delivers a message via the Postmark API.
17
22
  #
18
23
  # @param [Mail::Message] mail a message
19
- # @see https://github.com/wildbit/postmark-gem#using-postmark-with-the-mail-library
24
+ # @see http://developer.postmarkapp.com/developer-build.html
25
+ # @see http://developer.postmarkapp.com/developer-build.html#http-response-codes
26
+ # @see http://developer.postmarkapp.com/developer-build.html#api-error-codes
20
27
  def deliver!(mail)
21
- mail.delivery_method Mail::Postmark, settings
28
+ parameters = settings.dup
29
+ parameters.delete(:return_response)
30
+ message = MultiMail::Message::Postmark.new(mail).to_postmark_hash.merge(parameters)
22
31
 
23
- if settings[:return_response]
24
- mail.deliver!
25
- else
26
- mail.deliver
32
+ response = Faraday.post do |request|
33
+ request.url 'https://api.postmarkapp.com/email'
34
+ request.headers['Accept'] = 'application/json'
35
+ request.headers['Content-Type'] = 'application/json'
36
+ request.headers['X-Postmark-Server-Token'] = @api_key
37
+ request.body = JSON.dump(message)
38
+ end
39
+
40
+ body = JSON.load(response.body)
41
+
42
+ unless response.status == 200
43
+ case body['ErrorCode']
44
+ when 0
45
+ raise InvalidAPIKey, body['Message']
46
+ when 300
47
+ case body['Message']
48
+ when "Header 'Content-Type' not allowed."
49
+ raise InvalidHeader, body['Message']
50
+ when "Header 'Date' not allowed."
51
+ raise InvalidHeader, body['Message']
52
+ when "Invalid 'From' value."
53
+ raise MissingSender, body['Message']
54
+ when 'Zero recipients specified'
55
+ raise MissingRecipients, body['Message']
56
+ when 'Provide either email TextBody or HtmlBody or both.'
57
+ raise MissingBody, body['Message']
58
+ else
59
+ raise InvalidMessage, body['Message']
60
+ end
61
+ else
62
+ raise InvalidRequest, body['Message']
63
+ end
27
64
  end
28
- rescue ::Postmark::InvalidApiKeyError => e
29
- raise InvalidAPIKey, e.message
30
- rescue ::Postmark::InvalidMessageError => e
31
- case e.message
32
- when "Invalid 'From' value."
33
- raise MissingSender, e.message
34
- when 'Zero recipients specified'
35
- raise MissingRecipients, e.message
36
- when 'Provide either email TextBody or HtmlBody or both.'
37
- raise MissingBody, e.message
65
+
66
+ if settings[:return_response]
67
+ body
38
68
  else
39
- raise InvalidMessage, e.message
69
+ self
40
70
  end
41
71
  end
42
72
  end
@@ -6,15 +6,15 @@ module MultiMail
6
6
  #
7
7
  # @return [Hash] the message headers in SendGrid format
8
8
  def sendgrid_headers
9
- headers = {}
9
+ hash = {}
10
10
  header_fields.each do |field|
11
11
  key = field.name.downcase
12
- unless %w(to subject from bcc reply-to date message-id).include?(key)
12
+ unless %w(to subject from bcc reply-to date).include?(key)
13
13
  # The JSON must not contain integers.
14
- headers[field.name] = field.value.to_s
14
+ hash[field.name] = field.value.to_s
15
15
  end
16
16
  end
17
- headers
17
+ hash
18
18
  end
19
19
 
20
20
  # Returns the message's attachments in SendGrid format.
@@ -1,5 +1,3 @@
1
- require 'multi_mail/sendgrid/message'
2
-
3
1
  module MultiMail
4
2
  module Sender
5
3
  # SendGrid's outgoing mail sender.
@@ -14,6 +12,8 @@ module MultiMail
14
12
  # @param [Hash] options required and optional arguments
15
13
  # @option options [String] :api_user a SendGrid API user
16
14
  # @option options [String] :api_key a SendGrid API key
15
+ # @option options [Hash,String] the X-SMTPAPI SendGrid header
16
+ # @see http://sendgrid.com/docs/API_Reference/SMTP_API/index.html
17
17
  def initialize(options = {})
18
18
  super
19
19
  if Hash === settings[:'x-smtpapi']
@@ -1,8 +1,33 @@
1
1
  module MultiMail
2
2
  module Receiver
3
+ # A simple incoming email receiver.
3
4
  class Simple
4
5
  include MultiMail::Receiver::Base
5
6
 
7
+ recognizes :secret
8
+
9
+ # Initializes a simple incoming email receiver.
10
+ #
11
+ # @param [Hash] options required and optional arguments
12
+ # @option options [String] :secret a secret key
13
+ def initialize(options = {})
14
+ super
15
+ @secret = options[:secret]
16
+ end
17
+
18
+ # Returns whether a request is authentic.
19
+ #
20
+ # @param [Hash] params the content of the webhook
21
+ # @return [Boolean] whether the request is authentic
22
+ # @raise [IndexError] if the request is missing parameters
23
+ def valid?(params)
24
+ if @secret
25
+ params.fetch('signature') == signature(params)
26
+ else
27
+ super
28
+ end
29
+ end
30
+
6
31
  # Expects a raw email message parsable by the Mail gem.
7
32
  #
8
33
  # @param [Hash] params the content of the webhook
@@ -10,6 +35,11 @@ module MultiMail
10
35
  def transform(params)
11
36
  [Mail.new(params)]
12
37
  end
38
+
39
+ def signature(params)
40
+ data = "#{params.fetch('timestamp')}#{params.fetch('token')}"
41
+ OpenSSL::HMAC.hexdigest('sha256', @secret, data)
42
+ end
13
43
  end
14
44
  end
15
- end
45
+ end
@@ -1,3 +1,3 @@
1
1
  module MultiMail
2
- VERSION = "0.1.0"
2
+ VERSION = "0.1.1"
3
3
  end
data/multi_mail.gemspec CHANGED
@@ -9,6 +9,7 @@ Gem::Specification.new do |s|
9
9
  s.email = ["info@opennorth.ca"]
10
10
  s.homepage = "http://github.com/opennorth/multi_mail"
11
11
  s.summary = %q{Easily switch between email APIs}
12
+ s.license = 'MIT'
12
13
 
13
14
  s.files = `git ls-files`.split("\n")
14
15
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
@@ -25,7 +26,7 @@ Gem::Specification.new do |s|
25
26
  s.add_development_dependency 'json', '~> 1.7.7' # to silence coveralls warning
26
27
  s.add_development_dependency 'rake'
27
28
  s.add_development_dependency 'rspec', '~> 2.10'
28
- s.add_development_dependency 'vcr', '~> 2.0'
29
+ s.add_development_dependency 'vcr', '~> 2.4.0'
29
30
 
30
31
  # For Rake tasks
31
32
  s.add_development_dependency 'mandrill-api', '~> 1.0.35'
@@ -2,108 +2,113 @@ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
2
  require 'multi_mail/cloudmailin/receiver'
3
3
 
4
4
  describe MultiMail::Receiver::Cloudmailin do
5
- context 'after initialization' do
6
- context 'with invalid HTTP POST format' do
7
- let :service do
8
- MultiMail::Receiver.new({
9
- :provider => :cloudmailin,
10
- :http_post_format => 'invalid',
11
- })
12
- end
5
+ context 'with invalid HTTP POST format' do
6
+ let :service do
7
+ MultiMail::Receiver.new({
8
+ :provider => :cloudmailin,
9
+ :http_post_format => 'invalid',
10
+ })
11
+ end
13
12
 
14
- describe '#transform' do
15
- it 'should raise an error if :http_post_format is invalid' do
16
- expect{ service.transform({}) }.to raise_error(ArgumentError)
17
- end
13
+ describe '#transform' do
14
+ it 'should raise an error if :http_post_format is invalid' do
15
+ expect{ service.transform({}) }.to raise_error(ArgumentError)
18
16
  end
19
17
  end
18
+ end
20
19
 
21
- [false, true].each do |action_dispatch|
22
- let :action_dispatch do
23
- action_dispatch
24
- end
20
+ [false, true].each do |action_dispatch|
21
+ let :action_dispatch do
22
+ action_dispatch
23
+ end
25
24
 
26
- ['raw', 'json', 'multipart', '', nil].each do |http_post_format|
27
- context "with #{http_post_format.inspect} format and #{action_dispatch ? 'ActionDispatch' : 'Rack'}" do
28
- let :http_post_format do
29
- http_post_format
30
- end
25
+ ['raw', 'json', 'multipart', '', nil].each do |http_post_format|
26
+ context "with #{http_post_format.inspect} format and #{action_dispatch ? 'ActionDispatch' : 'Rack'}" do
27
+ let :actual_http_post_format do
28
+ http_post_format.to_s.empty? ? 'raw' : http_post_format
29
+ end
31
30
 
32
- let :actual_http_post_format do
33
- http_post_format.to_s.empty? ? 'raw' : http_post_format
31
+ let :service do
32
+ MultiMail::Receiver.new({
33
+ :provider => :cloudmailin,
34
+ :http_post_format => http_post_format,
35
+ })
36
+ end
37
+
38
+ def params(fixture)
39
+ MultiMail::Receiver::Cloudmailin.parse(response("cloudmailin/#{actual_http_post_format}", fixture, action_dispatch))
40
+ end
41
+
42
+ describe '#transform' do
43
+ it 'should return a mail message' do
44
+ helper(service.transform(params('valid')))
34
45
  end
35
46
 
36
- let :service do
37
- MultiMail::Receiver.new({
47
+ it 'should return a mail message with URL attachments' do
48
+ helper(MultiMail::Receiver.new({
38
49
  :provider => :cloudmailin,
39
50
  :http_post_format => http_post_format,
40
- })
51
+ :attachment_store => true,
52
+ }).transform(params('attachment_store')), true)
41
53
  end
42
54
 
43
- def params(fixture)
44
- MultiMail::Receiver::Cloudmailin.parse(response("cloudmailin/#{actual_http_post_format}", fixture, action_dispatch))
45
- end
55
+ def helper(messages, attachment_store = false)
56
+ messages.size.should == 1
57
+ message = messages[0]
58
+
59
+ # Headers
60
+ message.date.should == DateTime.parse('Mon, 15 Apr 2013 20:20:12 -04:00')
61
+ message.from.should == ['james@opennorth.ca']
62
+ message.to.should == ['5dae6f85cd65d30d384a@cloudmailin.net']
63
+ message.subject.should == 'Test'
46
64
 
47
- describe '#transform' do
48
- it 'should return a mail message' do
49
- messages = service.transform(params('valid'))
50
- messages.size.should == 1
51
- message = messages[0]
52
-
53
- # Headers
54
- message.date.should == DateTime.parse('Mon, 15 Apr 2013 20:20:12 -04:00')
55
- message.from.should == ['james@opennorth.ca']
56
- message.to.should == ['5dae6f85cd65d30d384a@cloudmailin.net']
57
- message.subject.should == 'Test'
58
-
59
- # Body
60
- message.multipart?.should == true
61
- message.parts.size.should == 4
62
- text_part = message.parts.find{|part| part.content_type == 'text/plain'}
63
- html_part = message.parts.find{|part| part.content_type == 'text/html; charset=UTF-8'}
64
- text_part.body.decoded.should == "bold text\n\n\n\nsome more bold text\n\n\n\nsome italic text\n\n> multiline\n> quoted\n> text\n\n\n--\nSignature block"
65
-
66
- # @note Due to a Cloudmailin bug, the HTML part is missing content
67
- # unless you use the "raw" HTTP POST format.
68
- if actual_http_post_format == 'raw'
69
- html_part.body.decoded.should == %(<html><head></head><body style="word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space; "><b>bold text</b><div><br></div><div></div></body></html><html><body style="word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space; "><head></head><br><div></div><div><br></div><div><b>some more bold text</b></div><div><b><br></b></div><div><b></b></div></body></html><html><head></head><body style="word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space; "><br><div><b></b></div><div><b><span class="Apple-style-span" style="font-weight: normal; "><br></span></b></div><div><b><span class="Apple-style-span" style="font-weight: normal; "><i>some italic text</i></span></b></div><div><b><span class="Apple-style-span" style="font-weight: normal; "><br></span></b></div><div><blockquote type="cite">multiline</blockquote><blockquote type="cite">quoted</blockquote><blockquote type="cite">text</blockquote></div><div><br></div><div>--</div><div>Signature block</div></body></html>)
70
- else
71
- html_part.body.decoded.should == %(<html><head></head><body style="word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space; "><b>bold text</b><div><br></div><div></div></body></html>)
72
- end
73
-
74
- # Attachments
75
- attachment0 = message.attachments.find{|attachment| attachment.filename == 'foo.txt'}
76
- attachment1 = message.attachments.find{|attachment| attachment.filename == 'bar.txt'}
77
- # @note Cloudmailin removes the newline at the end of the file,
78
- # unless you use the "raw" HTTP POST format.
79
- if actual_http_post_format == 'raw'
80
- attachment0.read.should == "Lorem ipsum dolor sit amet, consectetur adipiscing elit.\n"
81
- attachment1.read.should == "Nam accumsan euismod eros et rhoncus.\n"
82
- else
83
- attachment0.read.should == "Lorem ipsum dolor sit amet, consectetur adipiscing elit."
84
- attachment1.read.should == "Nam accumsan euismod eros et rhoncus."
85
- end
86
-
87
- # Extra Cloudmailin parameters
88
- if actual_http_post_format == 'raw'
89
- message['reply_plain'].should be_nil
90
- else
91
- message['reply_plain'].value.should == "bold text\n\n\n\nsome more bold text\n\n\n\nsome italic text\n"
92
- end
93
- message['spf-result'].value.should == 'pass'
65
+ # Body
66
+ message.multipart?.should == true
67
+ message.parts.size.should == 4
68
+ text_part = message.parts.find{|part| part.content_type == 'text/plain'}
69
+ html_part = message.parts.find{|part| part.content_type == 'text/html; charset=UTF-8'}
70
+ text_part.body.decoded.should == "bold text\n\n\n\nsome more bold text\n\n\n\nsome italic text\n\n> multiline\n> quoted\n> text\n\n\n--\nSignature block"
71
+
72
+ # @note Due to a Cloudmailin bug, the HTML part is missing content
73
+ # unless you use the "raw" HTTP POST format or URL attachments.
74
+ if actual_http_post_format == 'raw' || attachment_store
75
+ html_part.body.decoded.should == %(<html><head></head><body style="word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space; "><b>bold text</b><div><br></div><div></div></body></html><html><body style="word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space; "><head></head><br><div></div><div><br></div><div><b>some more bold text</b></div><div><b><br></b></div><div><b></b></div></body></html><html><head></head><body style="word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space; "><br><div><b></b></div><div><b><span class="Apple-style-span" style="font-weight: normal; "><br></span></b></div><div><b><span class="Apple-style-span" style="font-weight: normal; "><i>some italic text</i></span></b></div><div><b><span class="Apple-style-span" style="font-weight: normal; "><br></span></b></div><div><blockquote type="cite">multiline</blockquote><blockquote type="cite">quoted</blockquote><blockquote type="cite">text</blockquote></div><div><br></div><div>--</div><div>Signature block</div></body></html>)
76
+ else
77
+ html_part.body.decoded.should == %(<html><head></head><body style="word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space; "><b>bold text</b><div><br></div><div></div></body></html>)
94
78
  end
95
- end
96
79
 
97
- describe '#spam?' do
98
- it 'should return true if the response is spam' do
99
- message = service.transform(params('spam'))[0]
100
- service.spam?(message).should == true
80
+ # Attachments
81
+ attachment0 = message.attachments.find{|attachment| attachment.filename == 'foo.txt'}
82
+ attachment1 = message.attachments.find{|attachment| attachment.filename == 'bar.txt'}
83
+ # @note Cloudmailin removes the newline at the end of the file,
84
+ # unless you use the "raw" HTTP POST format or URL attachments.
85
+ if actual_http_post_format == 'raw' || attachment_store
86
+ attachment0.read.should == "Lorem ipsum dolor sit amet, consectetur adipiscing elit.\n"
87
+ attachment1.read.should == "Nam accumsan euismod eros et rhoncus.\n"
88
+ else
89
+ attachment0.read.should == "Lorem ipsum dolor sit amet, consectetur adipiscing elit."
90
+ attachment1.read.should == "Nam accumsan euismod eros et rhoncus."
101
91
  end
102
92
 
103
- it 'should return false if the response is ham' do
104
- message = service.transform(params('valid'))[0]
105
- service.spam?(message).should == false
93
+ # Extra Cloudmailin parameters
94
+ if actual_http_post_format == 'raw'
95
+ message['reply_plain'].should be_nil
96
+ else
97
+ message['reply_plain'].value.should == "bold text\n\n\n\nsome more bold text\n\n\n\nsome italic text\n"
106
98
  end
99
+ message['spf-result'].value.should == 'pass'
100
+ end
101
+ end
102
+
103
+ describe '#spam?' do
104
+ it 'should return true if the response is spam' do
105
+ message = service.transform(params('spam'))[0]
106
+ service.spam?(message).should == true
107
+ end
108
+
109
+ it 'should return false if the response is ham' do
110
+ message = service.transform(params('valid'))[0]
111
+ service.spam?(message).should == false
107
112
  end
108
113
  end
109
114
  end