mailgun-ruby 1.1.8 → 1.2.5

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 (39) hide show
  1. checksums.yaml +5 -5
  2. data/.travis.yml +6 -5
  3. data/Gemfile +1 -1
  4. data/README.md +24 -2
  5. data/docs/Domains.md +0 -0
  6. data/docs/Webhooks.md +1 -1
  7. data/docs/railgun/Overview.md +11 -0
  8. data/docs/railgun/Parameters.md +83 -0
  9. data/lib/mailgun/client.rb +27 -6
  10. data/lib/mailgun/events/events.rb +1 -1
  11. data/lib/mailgun/exceptions/exceptions.rb +2 -0
  12. data/lib/mailgun/messages/batch_message.rb +1 -0
  13. data/lib/mailgun/messages/message_builder.rb +61 -6
  14. data/lib/mailgun/suppressions.rb +4 -1
  15. data/lib/mailgun/version.rb +1 -1
  16. data/lib/mailgun/webhooks/webhooks.rb +1 -1
  17. data/lib/railgun/mailer.rb +105 -13
  18. data/lib/railgun/message.rb +2 -1
  19. data/lib/railgun/railtie.rb +3 -2
  20. data/mailgun.gemspec +11 -11
  21. data/spec/integration/email_validation_spec.rb +8 -0
  22. data/spec/integration/events_spec.rb +1 -1
  23. data/spec/integration/mailer_spec.rb +67 -0
  24. data/spec/integration/mailgun_spec.rb +4 -1
  25. data/spec/integration/suppressions_spec.rb +18 -2
  26. data/spec/spec_helper.rb +3 -1
  27. data/spec/unit/connection/test_client.rb +16 -0
  28. data/spec/unit/events/events_spec.rb +19 -0
  29. data/spec/unit/mailgun_spec.rb +24 -2
  30. data/spec/unit/messages/batch_message_spec.rb +56 -40
  31. data/spec/unit/messages/message_builder_spec.rb +165 -17
  32. data/spec/unit/messages/sample_data/unknown.type +0 -0
  33. data/spec/unit/railgun/content_type_spec.rb +71 -0
  34. data/spec/unit/railgun/mailer_spec.rb +388 -0
  35. data/vcr_cassettes/mailer_invalid_domain.yml +109 -0
  36. data/vcr_cassettes/message_deliver.yml +149 -0
  37. data/vcr_cassettes/suppressions.yml +66 -15
  38. metadata +50 -26
  39. data/.ruby-version +0 -1
data/mailgun.gemspec CHANGED
@@ -22,16 +22,16 @@ Gem::Specification.new do |spec|
22
22
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
23
23
  spec.require_paths = ["lib"]
24
24
 
25
- spec.required_ruby_version = '>= 2.0.0'
26
-
27
- spec.add_development_dependency 'bundler', '~> 1.5'
28
- spec.add_development_dependency 'rspec', '~> 3.0'
29
- spec.add_development_dependency 'rake', '~> 10.0'
30
- spec.add_development_dependency 'webmock', '~> 1.22'
31
- spec.add_development_dependency 'pry', '~> 0.9'
32
- spec.add_development_dependency 'vcr', '~> 3.0'
33
- spec.add_development_dependency 'simplecov', '~> 0.11'
34
-
35
- spec.add_dependency 'rest-client', '~> 2.0'
25
+ spec.required_ruby_version = '>= 2.2.2'
26
+
27
+ spec.add_development_dependency 'bundler', '>= 1.16.2'
28
+ spec.add_development_dependency 'rspec', '~> 3.8.0'
29
+ spec.add_development_dependency 'rake', '~> 12.3.2'
30
+ spec.add_development_dependency 'webmock', '~> 3.4.2'
31
+ spec.add_development_dependency 'pry', '~> 0.11.3'
32
+ spec.add_development_dependency 'vcr', '~> 3.0.3'
33
+ spec.add_development_dependency 'simplecov', '~> 0.16.1'
34
+ spec.add_development_dependency 'rails'
35
+ spec.add_dependency 'rest-client', '>= 2.0.2'
36
36
 
37
37
  end
@@ -28,7 +28,11 @@ describe 'For the email validation endpoint', order: :defined, vcr: vcr_opts do
28
28
  expected = {
29
29
  "address" => "alice@mailgun.net",
30
30
  "did_you_mean" => nil,
31
+ "is_disposable_address" => false,
32
+ "is_role_address" => false,
31
33
  "is_valid" => true,
34
+ "mailbox_verification" => "true",
35
+ "reason" => nil,
32
36
  "parts" => {
33
37
  "display_name" => nil,
34
38
  "domain" => "mailgun.net",
@@ -50,7 +54,11 @@ describe 'For the email validation endpoint', order: :defined, vcr: vcr_opts do
50
54
  expected = {
51
55
  "address" => "example.org",
52
56
  "did_you_mean" => nil,
57
+ "is_disposable_address" => false,
58
+ "is_role_address" => false,
53
59
  "is_valid" => false,
60
+ "mailbox_verification" => "unknown",
61
+ "reason" => "Validation failed for 'example.org', reason: 'malformed address; missing @ sign'",
54
62
  "parts" => {
55
63
  "display_name" => nil,
56
64
  "domain" => nil,
@@ -22,7 +22,7 @@ describe 'For the Events endpoint', vcr: vcr_opts do
22
22
 
23
23
  it 'can iterate over all events with `each`' do
24
24
  @events.each do |e|
25
- expect(e.id).to eq("JAx9z641TuGGUyaJlD9sCQ")
25
+ expect(e["id"]).to eq("JAx9z641TuGGUyaJlD9sCQ")
26
26
  end
27
27
  end
28
28
  end
@@ -0,0 +1,67 @@
1
+ require 'spec_helper'
2
+ require 'json'
3
+ require 'logger'
4
+ require 'railgun'
5
+ require 'mailgun'
6
+ require 'mailgun/exceptions/exceptions'
7
+
8
+ ActionMailer::Base.raise_delivery_errors = true
9
+ Rails.logger = Logger.new('/dev/null')
10
+ Rails.logger.level = Logger::DEBUG
11
+
12
+ class UnitTestMailer < ActionMailer::Base
13
+ default from: 'unittest@example.org'
14
+
15
+ def plain_message(address, from, subject, headers)
16
+ headers(headers)
17
+ mail(to: address, from: from, subject: subject) do |format|
18
+ format.text { render plain: 'Test!' }
19
+ format.html { render html: '<p>Test!</p>'.html_safe }
20
+ end
21
+ end
22
+ end
23
+
24
+ vcr_opts = { :cassette_name => 'message_deliver' }
25
+
26
+ describe 'Message deliver', vcr: vcr_opts do
27
+ let(:domain) { TESTDOMAIN }
28
+ let(:config) do
29
+ {
30
+ api_key: APIKEY,
31
+ domain: domain
32
+ }
33
+ end
34
+ let(:mail) { UnitTestMailer.plain_message("bob@#{domain}", "bob@#{domain}", 'subject', {}) }
35
+
36
+ it 'successfully delivers message' do
37
+ result = Railgun::Mailer.new(config).deliver!(mail)
38
+ result.to_h!
39
+
40
+ expect(result.body['message']).to eq('Queued. Thank you.')
41
+ expect(result.body).to include('id')
42
+ expect(result.code).to eq(200)
43
+ end
44
+ end
45
+
46
+ vcr_opts = { :cassette_name => 'mailer_invalid_domain' }
47
+
48
+ describe 'Invalid domain', vcr: vcr_opts do
49
+ let(:domain) { 'not-our-doma.in' }
50
+ let(:config) do
51
+ {
52
+ api_key: APIKEY,
53
+ domain: domain
54
+ }
55
+ end
56
+ let(:mail) { UnitTestMailer.plain_message("bob@#{domain}", 'sally@not-our-doma.in' 'subject', {}) }
57
+
58
+ it 'raises expected error' do
59
+
60
+ Railgun::Mailer.new(config).deliver!(mail)
61
+ rescue Mailgun::CommunicationError => err
62
+ expect(err.message).to eq('401 Unauthorized: Forbidden - Invalid Domain or API key')
63
+ else
64
+ fail
65
+
66
+ end
67
+ end
@@ -63,6 +63,9 @@ describe 'The method send_message()', vcr: vcr_opts do
63
63
  :to => "bob@#{@domain}",
64
64
  :subject => "Test",
65
65
  :text => "Test Data" }
66
+ uuid = 'uuid'
67
+
68
+ allow(SecureRandom).to receive(:uuid).and_return(uuid)
66
69
 
67
70
  result = @mg_obj.send_message(@domain, data)
68
71
 
@@ -72,7 +75,7 @@ describe 'The method send_message()', vcr: vcr_opts do
72
75
  expect(result.body).to include("id")
73
76
 
74
77
  expect(result.code).to eq(200)
75
- expect(result.body['id']).to eq("test-mode-mail@localhost")
78
+ expect(result.body['id']).to eq("test-mode-mail-#{uuid}@localhost")
76
79
  expect(result.body['message']).to eq("Queued. Thank you.")
77
80
  end
78
81
 
@@ -52,7 +52,7 @@ describe 'For the suppressions handling class', order: :defined, vcr: vcr_opts d
52
52
  end
53
53
  end
54
54
 
55
- it 'can batch-add unsubscribes' do
55
+ it 'can batch-add unsubscribes with tags as string' do
56
56
  unsubscribes = []
57
57
  @addresses.each do |addr|
58
58
  unsubscribes.push({
@@ -69,6 +69,23 @@ describe 'For the suppressions handling class', order: :defined, vcr: vcr_opts d
69
69
  expect(nested.length).to eq(0)
70
70
  end
71
71
 
72
+ it 'can batch-add unsubscribes with tags as array' do
73
+ unsubscribes = []
74
+ @addresses.each do |addr|
75
+ unsubscribes.push({
76
+ :address => addr,
77
+ :tags => ['integration'],
78
+ })
79
+ end
80
+
81
+ response, nested = @suppress.create_unsubscribes unsubscribes
82
+ response.to_h!
83
+
84
+ expect(response.code).to eq(200)
85
+ expect(response.body['message']).to eq('4 addresses have been added to the unsubscribes table')
86
+ expect(nested.length).to eq(0)
87
+ end
88
+
72
89
  it 'raises ParameterError if no unsubscribe[:address] is present' do
73
90
  unsubscribes = []
74
91
  unsubscribes.push({
@@ -123,4 +140,3 @@ describe 'For the suppressions handling class', order: :defined, vcr: vcr_opts d
123
140
 
124
141
  # TODO: Add tests for pagination support.
125
142
  end
126
-
data/spec/spec_helper.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  require 'rubygems'
2
+ require 'base64'
2
3
  require 'bundler'
3
4
  require 'bundler/setup'
4
5
  Bundler.setup(:development)
@@ -37,9 +38,10 @@ TESTDOMAIN = envs['MAILGUN_TESTDOMAIN']
37
38
  VCR.configure do |c|
38
39
  c.cassette_library_dir = 'vcr_cassettes'
39
40
  c.hook_into :webmock
40
- c.configure_rspec_metadata!
41
41
  c.default_cassette_options = { record: :new_episodes }
42
42
  c.filter_sensitive_data('<APIKEY>') { APIKEY }
43
43
  c.filter_sensitive_data('DOMAIN.TEST') { TESTDOMAIN }
44
44
  c.filter_sensitive_data('<PUBKEY>') { PUB_APIKEY }
45
+
46
+ c.configure_rspec_metadata!
45
47
  end
@@ -23,6 +23,7 @@ module Mailgun
23
23
  end
24
24
 
25
25
  def send_message(working_domain, data)
26
+ perform_data_validation(working_domain, data)
26
27
  case data
27
28
  when Hash
28
29
  if data.has_key?(:message)
@@ -75,6 +76,21 @@ module Mailgun
75
76
 
76
77
  private
77
78
 
79
+ def perform_data_validation(working_domain, data)
80
+ fail ParameterError.new('Missing working domain', working_domain) unless working_domain
81
+ return true unless data.is_a?(Hash) && data.present?
82
+ message = data.respond_to?(:message) ? data.message : data
83
+
84
+ fail ParameterError.new(
85
+ 'Missing `to` recipient, message should containg at least 1 recipient',
86
+ working_domain
87
+ ) if message.fetch('to', []).empty? && message.fetch(:to, []).empty?
88
+ fail ParameterError.new(
89
+ 'Missing a `from` sender, message should containg at least 1 `from` sender',
90
+ working_domain
91
+ ) if message.fetch('from', []).empty? && message.fetch(:from, []).empty?
92
+ end
93
+
78
94
  def response_generator(resource_endpoint)
79
95
  if resource_endpoint == "messages"
80
96
  t = Time.now
@@ -11,6 +11,25 @@ describe 'The method get' do
11
11
  end
12
12
  end
13
13
 
14
+ describe 'Pagination' do
15
+ it 'should return a proper hash of log data.' do
16
+ @mg_obj = Mailgun::UnitClient.new('events')
17
+ events = Mailgun::Events.new(@mg_obj, "samples.mailgun.org")
18
+ result = events.get()
19
+
20
+ json = JSON.parse(result.body)
21
+ expect(json).to include("paging")
22
+ expect(json["paging"]).to include("next")
23
+ expect(json["paging"]).to include{"previous"}
24
+ end
25
+
26
+ it 'should calculate proper next-page url' do
27
+ events = Mailgun::Events.new(@mg_obj, "samples.mailgun.org")
28
+ output = events.send(:extract_endpoint_from, '/v3/samples.mailgun.org/events/W3siYiI6ICIyMDE3LTA1LTEwVDIwOjA2OjU0LjU3NiswMDowMCIsICJlIjogIjIwMTctMDUtMDhUMjA6MDY6NTQuNTc3KzAwOjAwIn0sIHsiYiI6ICIyMDE3LTA1LTEwVDIwOjA2OjU0LjU3NiswMDowMCIsICJlIjogIjIwMTctMDUtMDhUMjA6MDY6NTQuNTc3KzAwOjAwIn0sIFsiZiJdLCBudWxsLCBbWyJhY2NvdW50LmlkIiwgIjU4MDUyMTg2NzhmYTE2MTNjNzkwYjUwZiJdLCBbImRvbWFpbi5uYW1lIiwgInNhbmRib3gyOTcwMTUyYWYzZDM0NTU5YmZjN2U3MTcwM2E2Y2YyNC5tYWlsZ3VuLm9yZyJdXSwgMTAwLCBudWxsXQ==')
29
+
30
+ expect(output).to eq 'W3siYiI6ICIyMDE3LTA1LTEwVDIwOjA2OjU0LjU3NiswMDowMCIsICJlIjogIjIwMTctMDUtMDhUMjA6MDY6NTQuNTc3KzAwOjAwIn0sIHsiYiI6ICIyMDE3LTA1LTEwVDIwOjA2OjU0LjU3NiswMDowMCIsICJlIjogIjIwMTctMDUtMDhUMjA6MDY6NTQuNTc3KzAwOjAwIn0sIFsiZiJdLCBudWxsLCBbWyJhY2NvdW50LmlkIiwgIjU4MDUyMTg2NzhmYTE2MTNjNzkwYjUwZiJdLCBbImRvbWFpbi5uYW1lIiwgInNhbmRib3gyOTcwMTUyYWYzZDM0NTU5YmZjN2U3MTcwM2E2Y2YyNC5tYWlsZ3VuLm9yZyJdXSwgMTAwLCBudWxsXQ=='
31
+ end
32
+ end
14
33
 
15
34
  describe 'The method next' do
16
35
  it 'should return the next series of data.' do
@@ -37,8 +37,11 @@ describe 'The method send_message()' do
37
37
  end
38
38
 
39
39
  it 'opens the message MIME and sends the MIME message.' do
40
- data = {'to' => 'joe@test.com',
41
- 'message' => 'Sample Data/mime.txt'}
40
+ data = {
41
+ 'to' => 'joe@test.com',
42
+ 'message' => 'Sample Data/mime.txt',
43
+ 'from' => 'joe@test.com'
44
+ }
42
45
  result = @mg_obj.send_message("testdomain.com", data)
43
46
 
44
47
  result.to_h!
@@ -46,6 +49,25 @@ describe 'The method send_message()' do
46
49
  expect(result.body).to include("message")
47
50
  expect(result.body).to include("id")
48
51
  end
52
+
53
+ context 'when domain is missing' do
54
+ it 'shows failure message' do
55
+ expect(@mg_obj).to receive(:fail)
56
+ @mg_obj.send_message(nil, {})
57
+ end
58
+ end
59
+
60
+ context 'when to is missing' do
61
+ it 'shows failure message' do
62
+ data = {
63
+ 'to' => '',
64
+ 'message' => 'Sample Data/mime.txt',
65
+ 'from' => 'joe@test.com'
66
+ }
67
+ expect(@mg_obj).to receive(:fail)
68
+ @mg_obj.send_message("testdomain.com", data)
69
+ end
70
+ end
49
71
  end
50
72
 
51
73
  describe 'The method post()' do
@@ -72,60 +72,76 @@ describe 'The method add_recipient' do
72
72
  @address_3 = 'sam@example.com'
73
73
  @variables_3 = {'first' => 'Sam', 'last' => 'Doe', 'tracking' => 'GHI123'}
74
74
  end
75
-
76
- it 'adds 1,000 recipients to the message body and validates counter is incremented then reset' do
77
- recipient_type = :to
78
- 1000.times do
79
- @mb_obj.add_recipient(recipient_type, @address_1, @variables_1)
75
+ context 'when from is present' do
76
+ before(:each) do
77
+ @mb_obj.from('example@email.com')
80
78
  end
81
79
 
82
- expect(@mb_obj.counters[:recipients][recipient_type]).to eq(1000)
83
-
84
- @mb_obj.add_recipient(recipient_type, @address_1, @variables_1)
85
-
86
- expect(@mb_obj.counters[:recipients][recipient_type]).to eq(1)
87
- end
80
+ it 'adds 1,000 recipients to the message body and validates counter is incremented then reset' do
81
+ recipient_type = :to
82
+ 1000.times do
83
+ @mb_obj.add_recipient(recipient_type, @address_1, @variables_1)
84
+ end
88
85
 
89
- it 'adds recipients to the message, calls finalize, and cleans up' do
90
- recipient_type = :to
91
- 1000.times do
86
+ expect(@mb_obj.counters[:recipients][recipient_type]).to eq(1000)
87
+
92
88
  @mb_obj.add_recipient(recipient_type, @address_1, @variables_1)
89
+
90
+ expect(@mb_obj.counters[:recipients][recipient_type]).to eq(1)
93
91
  end
94
92
 
95
- expect(@mb_obj.counters[:recipients][recipient_type]).to eq(1000)
96
- @mb_obj.finalize
93
+ it 'adds recipients to the message, calls finalize, and cleans up' do
94
+ recipient_type = :to
95
+ 1000.times do
96
+ @mb_obj.add_recipient(recipient_type, @address_1, @variables_1)
97
+ end
97
98
 
98
- expect(@mb_obj.message['recipient-variables'].length).to eq(0)
99
- expect(@mb_obj.message[:to].length).to eq(0)
100
- expect(@mb_obj.counters[:recipients][recipient_type]).to eq(0)
101
- end
99
+ expect(@mb_obj.counters[:recipients][recipient_type]).to eq(1000)
100
+ @mb_obj.finalize
102
101
 
103
- it 'adds 5,005 recipients to the message body and validates we receive message_ids back' do
104
- recipient_type = :to
105
- 5005.times do
106
- @mb_obj.add_recipient(recipient_type, @address_1, @variables_1)
102
+ expect(@mb_obj.recipient_variables).to eq({})
103
+ expect(@mb_obj.message['recipient-variables'].length).to eq(0)
104
+ expect(@mb_obj.message[:to].length).to eq(0)
105
+ expect(@mb_obj.counters[:recipients][recipient_type]).to eq(0)
107
106
  end
108
- @mb_obj.finalize
109
107
 
110
- expect(@mb_obj.message_ids.length).to eq(6)
111
- end
108
+ it 'adds 5,005 recipients to the message body and validates we receive message_ids back' do
109
+ recipient_type = :to
110
+ 5005.times do
111
+ @mb_obj.add_recipient(recipient_type, @address_1, @variables_1)
112
+ end
113
+ @mb_obj.finalize
112
114
 
113
- it 'sets recipient-variables, for batch expansion' do
114
- recipient_type = :to
115
- @mb_obj.add_recipient(recipient_type, @address_1, @variables_1)
115
+ expect(@mb_obj.message_ids.length).to eq(6)
116
+ end
116
117
 
117
- expect(@mb_obj.recipient_variables[@address_1]).to eq(@variables_1)
118
+ it 'sets recipient-variables, for batch expansion' do
119
+ recipient_type = :to
120
+ @mb_obj.add_recipient(recipient_type, @address_1, @variables_1)
121
+
122
+ expect(@mb_obj.recipient_variables[@address_1]).to eq(@variables_1)
123
+ end
124
+
125
+ it 'sets multiple recipient-variables, for batch expansion' do
126
+ recipient_type = :to
127
+ @mb_obj.add_recipient(recipient_type, @address_1, @variables_1)
128
+ @mb_obj.add_recipient(recipient_type, @address_2, @variables_2)
129
+ @mb_obj.add_recipient(recipient_type, @address_3, @variables_3)
130
+
131
+ expect(@mb_obj.recipient_variables[@address_1]).to eq(@variables_1)
132
+ expect(@mb_obj.recipient_variables[@address_2]).to eq(@variables_2)
133
+ expect(@mb_obj.recipient_variables[@address_3]).to eq(@variables_3)
134
+ end
118
135
  end
119
136
 
120
- it 'sets multiple recipient-variables, for batch expansion' do
121
- recipient_type = :to
122
- @mb_obj.add_recipient(recipient_type, @address_1, @variables_1)
123
- @mb_obj.add_recipient(recipient_type, @address_2, @variables_2)
124
- @mb_obj.add_recipient(recipient_type, @address_3, @variables_3)
125
-
126
- expect(@mb_obj.recipient_variables[@address_1]).to eq(@variables_1)
127
- expect(@mb_obj.recipient_variables[@address_2]).to eq(@variables_2)
128
- expect(@mb_obj.recipient_variables[@address_3]).to eq(@variables_3)
137
+ context 'when from is empty' do
138
+ it 'shows error message' do
139
+ recipient_type = :to
140
+ @mb_obj.add_recipient(recipient_type, @address_1, @variables_1)
141
+ @mb_obj.add_recipient(recipient_type, @address_2, @variables_2)
142
+ expect(@mb_client).to receive(:fail)
143
+ @mb_obj.finalize
144
+ end
129
145
  end
130
146
 
131
147
  end