multi_mail 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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