multi_mail 0.0.1 → 0.0.2

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 (59) hide show
  1. data/.travis.yml +4 -0
  2. data/.yardopts +4 -0
  3. data/Gemfile +1 -1
  4. data/README.md +107 -12
  5. data/Rakefile +75 -52
  6. data/lib/multi_mail/cloudmailin/receiver.rb +100 -0
  7. data/lib/multi_mail/mailgun/receiver.rb +74 -36
  8. data/lib/multi_mail/mailgun/sender.rb +14 -0
  9. data/lib/multi_mail/mandrill/receiver.rb +77 -35
  10. data/lib/multi_mail/mandrill/sender.rb +14 -0
  11. data/lib/multi_mail/postmark/receiver.rb +68 -0
  12. data/lib/multi_mail/postmark/sender.rb +14 -0
  13. data/lib/multi_mail/receiver/base.rb +125 -8
  14. data/lib/multi_mail/receiver.rb +10 -4
  15. data/lib/multi_mail/sender/base.rb +7 -0
  16. data/lib/multi_mail/sender.rb +10 -4
  17. data/lib/multi_mail/sendgrid/receiver.rb +42 -0
  18. data/lib/multi_mail/sendgrid/sender.rb +11 -0
  19. data/lib/multi_mail/service.rb +15 -4
  20. data/lib/multi_mail/simple/receiver.rb +15 -0
  21. data/lib/multi_mail/simple/sender.rb +14 -0
  22. data/lib/multi_mail/version.rb +1 -1
  23. data/lib/multi_mail.rb +71 -3
  24. data/multi_mail.gemspec +12 -5
  25. data/spec/cloudmailin/receiver_spec.rb +112 -0
  26. data/spec/fixtures/cloudmailin/json/spam.txt +59 -0
  27. data/spec/fixtures/cloudmailin/json/valid.txt +59 -0
  28. data/spec/fixtures/cloudmailin/multipart/spam.txt +135 -0
  29. data/spec/fixtures/cloudmailin/multipart/valid.txt +135 -0
  30. data/spec/fixtures/cloudmailin/raw/spam.txt +137 -0
  31. data/spec/fixtures/cloudmailin/raw/valid.txt +137 -0
  32. data/spec/fixtures/mailgun/parsed/invalid.txt +8 -0
  33. data/spec/fixtures/mailgun/parsed/missing.txt +8 -0
  34. data/spec/fixtures/mailgun/parsed/spam.txt +8 -0
  35. data/spec/fixtures/mailgun/parsed/valid.txt +187 -0
  36. data/spec/fixtures/mandrill/spam.txt +9 -0
  37. data/spec/fixtures/mandrill/valid.txt +10 -0
  38. data/spec/fixtures/multipart.txt +99 -0
  39. data/spec/fixtures/postmark/spam.txt +83 -0
  40. data/spec/fixtures/postmark/valid.txt +92 -0
  41. data/spec/fixtures/simple/valid.txt +4 -0
  42. data/spec/mailgun/receiver_spec.rb +105 -50
  43. data/spec/mailgun/sender_spec.rb +0 -0
  44. data/spec/mandrill/receiver_spec.rb +35 -35
  45. data/spec/mandrill/sender_spec.rb +0 -0
  46. data/spec/multi_mail_spec.rb +63 -0
  47. data/spec/postmark/receiver_spec.rb +60 -0
  48. data/spec/postmark/sender_spec.rb +0 -0
  49. data/spec/receiver/base_spec.rb +73 -8
  50. data/spec/sender/base_spec.rb +21 -0
  51. data/spec/service_spec.rb +2 -2
  52. data/spec/simple/receiver_spec.rb +36 -0
  53. data/spec/simple/sender_spec.rb +0 -0
  54. data/spec/spec_helper.rb +123 -10
  55. metadata +141 -21
  56. data/spec/fixtures/mailgun/invalid.txt +0 -8
  57. data/spec/fixtures/mailgun/missing.txt +0 -8
  58. data/spec/fixtures/mailgun/spam.txt +0 -8
  59. data/spec/fixtures/mailgun/valid.txt +0 -8
@@ -0,0 +1,60 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+ require 'multi_mail/postmark/receiver'
3
+
4
+ describe MultiMail::Receiver::Postmark do
5
+ context 'after initialization' do
6
+ let :service do
7
+ MultiMail::Receiver.new(:provider => :postmark)
8
+ end
9
+
10
+ def params(fixture)
11
+ MultiMail::Receiver::Postmark.parse(response('postmark', fixture))
12
+ end
13
+
14
+ describe '#transform' do
15
+ it 'should return a mail message' do
16
+ messages = service.transform(params('valid'))
17
+ messages.size.should == 1
18
+ message = messages[0]
19
+
20
+ # Headers
21
+ message.date.should == DateTime.parse('Mon, 15 Apr 2013 20:20:12 -0400')
22
+ message.from.should == ['james@opennorth.ca']
23
+ message.to.should == ['4354473e2e6ab001fa836f627a54004e@inbound.postmarkapp.com']
24
+ message.subject.should == 'Test'
25
+
26
+ # Body
27
+ message.multipart?.should == true
28
+ message.parts.size.should == 4
29
+ message.parts[0].content_type.should == 'text/plain'
30
+ message.parts[1].content_type.should == 'text/html; charset=UTF-8'
31
+ message.parts[0].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"
32
+ # @note Due to a Postmark bug, the HTML part is missing content.
33
+ message.parts[1].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>)
34
+
35
+ # Attachments
36
+ attachment0 = message.attachments.find{|attachment| attachment.filename == 'foo.txt'}
37
+ attachment1 = message.attachments.find{|attachment| attachment.filename == 'bar.txt'}
38
+ attachment0.read.should == "Lorem ipsum dolor sit amet, consectetur adipiscing elit.\n"
39
+ attachment1.read.should == "Nam accumsan euismod eros et rhoncus.\n"
40
+
41
+ # Extra Postmark parameters
42
+ message['MailboxHash'].should be_nil
43
+ message['MessageID'].value.should == '61c7c8b8-ba7e-43c3-b9ad-0ba865e8caa2'
44
+ message['Tag'].should be_nil
45
+ end
46
+ end
47
+
48
+ describe '#spam?' do
49
+ it 'should return true if the response is spam' do
50
+ message = service.transform(params('spam'))[0]
51
+ service.spam?(message).should == true
52
+ end
53
+
54
+ it 'should return false if the response is ham' do
55
+ message = service.transform(params('valid'))[0]
56
+ service.spam?(message).should == false
57
+ end
58
+ end
59
+ end
60
+ end
File without changes
@@ -15,31 +15,43 @@ describe MultiMail::Receiver::Base do
15
15
  end
16
16
  end
17
17
 
18
- describe '#process' do
19
- before :all do
20
- @service = klass.new
21
- end
18
+ let :service do
19
+ klass.new
20
+ end
22
21
 
22
+ describe '#process' do
23
23
  it 'should parse the request' do
24
24
  klass.should_receive(:parse).with('foo' => 1).once.and_return('foo' => 1)
25
- @service.process('foo' => 1)
25
+ service.process('foo' => 1)
26
26
  end
27
27
 
28
28
  it 'should transform the request if the request is valid' do
29
- @service.should_receive(:transform).with('foo' => 1).once
30
- @service.process('foo' => 1)
29
+ service.should_receive(:transform).with('foo' => 1).once
30
+ service.process('foo' => 1)
31
31
  end
32
32
 
33
33
  it 'raise an error if the request is invalid' do
34
- expect{ @service.process('foo' => 0) }.to raise_error(MultiMail::ForgedRequest)
34
+ expect{ service.process('foo' => 0) }.to raise_error(MultiMail::ForgedRequest)
35
35
  end
36
36
  end
37
37
 
38
38
  describe '#parse' do
39
+ it 'should parse JSON' do
40
+ klass.parse('{"foo":1,"bar":1,"bar":1}').should == {'foo' => 1, 'bar' => 1}
41
+ end
42
+
39
43
  it 'should parse raw POST data' do
40
44
  klass.parse('foo=1&bar=1&bar=1').should == {'foo' => '1', 'bar' => ['1', '1']}
41
45
  end
42
46
 
47
+ it 'should accept an array' do
48
+ klass.parse([['foo', 1], ['bar', [1, 1]]]).should == {'foo' => 1, 'bar' => [1, 1]}
49
+ end
50
+
51
+ it 'should accept a Rack::Request object' do
52
+ klass.parse(Rack::Request.new(Rack::MockRequest.env_for('/?foo=1&bar[]=1&bar[]=1'))).should == {'foo' => '1', 'bar' => ['1', '1']}
53
+ end
54
+
43
55
  it 'should pass-through a hash' do
44
56
  klass.parse('foo' => 1).should == {'foo' => 1}
45
57
  end
@@ -48,4 +60,57 @@ describe MultiMail::Receiver::Base do
48
60
  expect{ klass.parse(1) }.to raise_error(ArgumentError, "Can't handle Fixnum input")
49
61
  end
50
62
  end
63
+
64
+ describe '#add_file_arguments' do
65
+ it 'should handle a regular attachment' do
66
+ params = klass.parse(response('mailgun/parsed', 'valid', false))
67
+ klass.add_file_arguments(params['attachment-1']).should == {:filename => 'foo.txt', :content => "Lorem ipsum dolor sit amet, consectetur adipiscing elit.\r\n"}
68
+ end
69
+
70
+ it 'should handle a Rails-like attachment' do
71
+ params = klass.parse(response('mailgun/parsed', 'valid', true))
72
+ klass.add_file_arguments(params['attachment-1']).should == {:filename => 'foo.txt', :content => "Lorem ipsum dolor sit amet, consectetur adipiscing elit.\r\n"}
73
+ end
74
+ end
75
+
76
+ describe '#condense' do
77
+ it "should condense a message's HTML parts to a single HTML part" do
78
+ message = Mail.new(File.read(File.expand_path('../../fixtures/multipart.txt', __FILE__)))
79
+ result = klass.condense(message.dup)
80
+
81
+ result.parts.size.should == 4
82
+
83
+ result.text_part.content_type.should == 'text/plain'
84
+ result.html_part.content_type.should == 'text/html; charset=UTF-8'
85
+
86
+ result.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"
87
+ result.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>"
88
+
89
+ [ "Lorem ipsum dolor sit amet, consectetur adipiscing elit.\n",
90
+ "Nam accumsan euismod eros et rhoncus.\n",
91
+ ].each_with_index do |body,i|
92
+ result.attachments[i].body.decoded.should == body
93
+ end
94
+ end
95
+ end
96
+
97
+ describe '#flatten' do
98
+ it 'should flatten a hierarchy of message parts' do
99
+ message = Mail.new(File.read(File.expand_path('../../fixtures/multipart.txt', __FILE__)))
100
+ result = klass.flatten(Mail.new, message.parts.dup)
101
+
102
+ result.parts.size.should == 6
103
+ result.parts.none?(&:multipart?).should == true
104
+
105
+ [ "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",
106
+ "<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>",
107
+ "Lorem ipsum dolor sit amet, consectetur adipiscing elit.\n",
108
+ "<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>",
109
+ "Nam accumsan euismod eros et rhoncus.\n",
110
+ "<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>",
111
+ ].each_with_index do |body,i|
112
+ result.parts[i].body.decoded.should == body
113
+ end
114
+ end
115
+ end
51
116
  end
@@ -0,0 +1,21 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe MultiMail::Sender::Base do
4
+ let :klass do
5
+ Class.new(MultiMail::Service) do
6
+ include MultiMail::Sender::Base
7
+
8
+ def valid?(params)
9
+ params['foo'] == 1
10
+ end
11
+
12
+ def transform(params)
13
+ [Mail.new]
14
+ end
15
+ end
16
+ end
17
+
18
+ let :service do
19
+ klass.new
20
+ end
21
+ end
data/spec/service_spec.rb CHANGED
@@ -51,10 +51,10 @@ describe MultiMail::Service do
51
51
  klass.validate_options({
52
52
  :required_argument1 => 1,
53
53
  :required_argument2 => 1,
54
- :foo => 1,
55
54
  :bar => 1,
55
+ :foo => 1,
56
56
  })
57
- }.to raise_error(ArgumentError, "Unrecognized arguments: foo, bar")
57
+ }.to raise_error(ArgumentError, "Unrecognized arguments: bar, foo")
58
58
  end
59
59
  end
60
60
  end
@@ -0,0 +1,36 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+ require 'multi_mail/simple/receiver'
3
+
4
+ describe MultiMail::Receiver::Simple do
5
+ context 'after initialization' do
6
+ let :service do
7
+ MultiMail::Receiver.new({
8
+ :provider => :simple,
9
+ })
10
+ end
11
+
12
+ def params(fixture)
13
+ MultiMail::Receiver::Simple.parse(response('simple', fixture))
14
+ end
15
+
16
+ describe '#transform' do
17
+ it 'should return a mail message' do
18
+ message = service.transform(params('valid'))[0]
19
+
20
+ # Headers
21
+ message.date.should == DateTime.parse('Thu, 27 Dec 2012 15:25:37 -0500')
22
+ message.from.should == ['james@opennorth.ca']
23
+ message.to.should == ['foo+bar@govkit.org']
24
+ message.subject.should == 'Test'
25
+
26
+ # Body
27
+ message.multipart?.should == true
28
+ message.parts.size.should == 2
29
+ message.parts[0].content_type.should == 'text/plain; charset=us-ascii'
30
+ message.parts[0].body.should == "bold text\n\n\n> multiline\n> quoted\n> text\n\n--\nSignature block"
31
+ message.parts[1].content_type.should == 'text/html; charset=us-ascii'
32
+ message.parts[1].body.should == %(<html><head></head><body style="word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space; "><div><b>bold text</b></div><div><br></div><div><blockquote type="cite"></blockquote></div><div><blockquote type="cite">multiline</blockquote></div><blockquote type="cite"><div>quoted</div><div>text</div></blockquote><br><div>--</div><div>Signature block</div></body></html>)
33
+ end
34
+ end
35
+ end
36
+ end
File without changes
data/spec/spec_helper.rb CHANGED
@@ -1,27 +1,140 @@
1
+ require 'rubygems'
2
+
3
+ require 'coveralls'
4
+ Coveralls.wear!
5
+
1
6
  require 'net/http'
2
7
  require 'yaml'
3
8
 
4
- require 'rubygems'
5
9
  require 'rspec'
10
+ require 'rack'
6
11
  require File.dirname(__FILE__) + '/../lib/multi_mail'
7
12
 
8
13
  # Requires supporting ruby files with custom matchers and macros, etc,
9
14
  # in spec/support/ and its subdirectories.
10
15
  Dir[File.expand_path("../support/**/*.rb", __FILE__)].each {|f| require f}
11
16
 
12
- # @return [Hash] required arguments to initialize services
13
- # @todo warn if unable to authenticate with details in api_keys.yml
14
- def credentials
15
- @credentials ||= YAML.load_file(File.expand_path('../../api_keys.yml', __FILE__))
16
- end
17
-
17
+ # Use requestb.in. Copy the content from the "Raw" tab and replace the first
18
+ # line with "HTTP/1.1 200 OK". Note that messages cannot exceed 10kb. All
19
+ # fixtures are modified to have the same Date header.
20
+ #
21
+ # Sign up for all services, and, in all cases except Cloudmailin, add an API key
22
+ # to `api_keys.yml`, which will look like:
23
+ #
24
+ # ---
25
+ # :mailgun_api_key: ...
26
+ # :mandrill_api_key: ...
27
+ # :postmark_api_key: ...
28
+ #
29
+ # For Postmark, you must create a server to get an API key.
30
+ #
31
+ # # Cloudmailin
32
+ #
33
+ # Change the HTTP POST format on Cloudmailin and wait a few minutes. Run
34
+ # `unix2dos` on the fixtures to fix line endings.
35
+ #
36
+ # spam.txt Change the SPF result to "fail"
37
+ # valid.txt Send a complex multipart message
38
+ #
39
+ # # Mailgun
40
+ #
41
+ # Run `bundle exec rake mailgun` to set up Mailgun.
42
+ #
43
+ # invalid.txt Send a blank message and change the signature parameter value to "xxx"
44
+ # missing.txt Send a blank message and remove the signature parameter
45
+ # spam.txt Send a subject-less message with message body XJS*C4JDBQADN1.NSBN3*2IDNEN*GTUBE-STANDARD-ANTI-UBE-TEST-EMAIL*C.34X
46
+ # valid.txt Send a complex multipart message, recalculate the signature parameter value for an API key of "foo"
47
+ #
48
+ # # Mandrill
49
+ #
50
+ # Run `bundle exec rake mandrill` to ensure Mandrill is properly set up.
51
+ #
52
+ # invalid.txt Send a blank message and change the event parameter value to "xxx"
53
+ # missing.txt Send a blank message and remove the event parameter
54
+ # spam.txt Send a subject-less message with message body XJS*C4JDBQADN1.NSBN3*2IDNEN*GTUBE-STANDARD-ANTI-UBE-TEST-EMAIL*C.34X
55
+ # valid.txt Send a complex multipart message
56
+ #
57
+ # # Postmark
58
+ #
59
+ # Run `bundle exec rake postmark` to set up Postmark.
60
+ #
61
+ # spam.txt Send a subject-less message with message body XJS*C4JDBQADN1.NSBN3*2IDNEN*GTUBE-STANDARD-ANTI-UBE-TEST-EMAIL*C.34X
62
+ # valid.txt Send a complex multipart message
63
+ #
18
64
  # @param [String] provider a provider
19
65
  # @param [String] fixture one of "valid", "invalid" or "spam"
66
+ # @param [Boolean] action_dispatch whether uploaded files should be
67
+ # `ActionDispatch::Http::UploadedFile` objects
20
68
  # @return [String] the provider's baked response
21
69
  # @see FakeWeb::Responder#baked_response
22
- def response(provider, fixture)
23
- io = File.open(File.expand_path("../fixtures/#{provider}/#{fixture}.txt", __FILE__), 'r')
70
+ # @see https://github.com/rack/rack/blob/master/test/spec_multipart.rb
71
+ def response(provider, fixture, action_dispatch = false)
72
+ contents = File.read(File.expand_path("../fixtures/#{provider}/#{fixture}.txt", __FILE__))
73
+ io = StringIO.new(contents)
24
74
  socket = Net::BufferedIO.new(io)
25
75
  response = Net::HTTPResponse.read_new(socket)
26
- response.reading_body(socket, true) {}
76
+
77
+ # `response.reading_body(socket, true) {}`, for whatever reason, fails to read
78
+ # all of the body in files like `cloudmailin/multipart/valid.txt`.
79
+ body = contents[/(?:\r?\n){2,}(.+)\z/m, 1]
80
+
81
+ # It's kind of crazy that no library has an easier way of doing this.
82
+ if response.header['content-type']['multipart/form-data']
83
+ body = Rack::Multipart.parse_multipart(Rack::MockRequest.env_for('/', {
84
+ 'CONTENT_TYPE' => response.header['content-type'],
85
+ :input => body,
86
+ }))
87
+ end
88
+
89
+ if action_dispatch
90
+ # ActionDispatch would parse the request into a parameters hash.
91
+ klass = Class.new(MultiMail::Service) do
92
+ include MultiMail::Receiver::Base
93
+ end
94
+ normalize_encode_params(klass.parse(body))
95
+ else
96
+ body
97
+ end
98
+ end
99
+
100
+ # @see https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/http/upload.rb
101
+ class UploadedFile
102
+ attr_accessor :original_filename, :content_type, :tempfile, :headers
103
+
104
+ def initialize(hash)
105
+ @original_filename = hash[:filename]
106
+ @content_type = hash[:type]
107
+ @headers = hash[:head]
108
+ @tempfile = hash[:tempfile]
109
+ raise(ArgumentError, ':tempfile is required') unless @tempfile
110
+ end
111
+
112
+ def read(*args)
113
+ @tempfile.read(*args)
114
+ end
115
+ end
116
+
117
+ # @see https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/http/parameters.rb
118
+ # @see https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/http/upload.rb
119
+ def normalize_encode_params(params)
120
+ if Hash === params
121
+ if params.has_key?(:tempfile)
122
+ UploadedFile.new(params)
123
+ else
124
+ new_hash = {}
125
+ params.each do |k, v|
126
+ new_hash[k] = case v
127
+ when Hash
128
+ normalize_encode_params(v)
129
+ when Array
130
+ v.map! {|el| normalize_encode_params(el) }
131
+ else
132
+ v
133
+ end
134
+ end
135
+ new_hash
136
+ end
137
+ else
138
+ params
139
+ end
27
140
  end