mailgun-ruby 1.4.1 → 1.4.3
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.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +30 -8
- data/.rubocop.yml +64 -4
- data/Gemfile +3 -1
- data/README.md +1 -1
- data/Rakefile +5 -8
- data/docs/AnalyticsTags.md +63 -0
- data/lib/mailgun/address.rb +5 -5
- data/lib/mailgun/chains.rb +2 -3
- data/lib/mailgun/client.rb +56 -56
- data/lib/mailgun/domains/domains.rb +11 -10
- data/lib/mailgun/events/events.rb +4 -3
- data/lib/mailgun/exceptions/exceptions.rb +12 -15
- data/lib/mailgun/helpers/api_version_checker.rb +6 -1
- data/lib/mailgun/lists/opt_in_handler.rb +6 -10
- data/lib/mailgun/logs/logs.rb +4 -2
- data/lib/mailgun/messages/batch_message.rb +10 -10
- data/lib/mailgun/messages/message_builder.rb +40 -56
- data/lib/mailgun/metrics/metrics.rb +12 -6
- data/lib/mailgun/response.rb +12 -10
- data/lib/mailgun/subaccounts/subaccounts.rb +13 -8
- data/lib/mailgun/suppressions.rb +36 -43
- data/lib/mailgun/tags/analytics_tags.rb +37 -2
- data/lib/mailgun/tags/tags.rb +29 -19
- data/lib/mailgun/templates/templates.rb +40 -29
- data/lib/mailgun/version.rb +3 -1
- data/lib/mailgun/webhooks/webhooks.rb +22 -19
- data/lib/mailgun-ruby.rb +2 -0
- data/lib/mailgun.rb +4 -4
- data/lib/railgun/attachment.rb +12 -19
- data/lib/railgun/errors.rb +2 -3
- data/lib/railgun/mailer.rb +37 -41
- data/lib/railgun/railtie.rb +2 -0
- data/lib/railgun.rb +2 -0
- data/mailgun.gemspec +15 -11
- data/spec/integration/analytics_tags_spec.rb +54 -0
- data/spec/integration/bounces_spec.rb +12 -11
- data/spec/integration/campaign_spec.rb +20 -18
- data/spec/integration/complaints_spec.rb +8 -6
- data/spec/integration/domains_spec.rb +12 -18
- data/spec/integration/email_validation_spec.rb +35 -34
- data/spec/integration/events_spec.rb +8 -8
- data/spec/integration/list_members_spec.rb +27 -26
- data/spec/integration/list_spec.rb +22 -21
- data/spec/integration/logs_spec.rb +49 -47
- data/spec/integration/mailer_spec.rb +7 -3
- data/spec/integration/mailgun_spec.rb +85 -92
- data/spec/integration/metrics_spec.rb +137 -131
- data/spec/integration/routes_spec.rb +41 -40
- data/spec/integration/stats_spec.rb +4 -2
- data/spec/integration/subaccounts_spec.rb +9 -10
- data/spec/integration/suppressions_spec.rb +222 -44
- data/spec/integration/templates_spec.rb +14 -12
- data/spec/integration/unsubscribes_spec.rb +8 -6
- data/spec/integration/webhook_spec.rb +18 -12
- data/spec/spec_helper.rb +15 -8
- data/spec/unit/client_spec.rb +424 -0
- data/spec/unit/connection/test_client.rb +108 -55
- data/spec/unit/events/events_spec.rb +48 -29
- data/spec/unit/exceptions/exceptions_spec.rb +8 -7
- data/spec/unit/helpers/api_version_checker_spec.rb +206 -0
- data/spec/unit/lists/opt_in_handler_spec.rb +11 -7
- data/spec/unit/mailgun_spec.rb +71 -68
- data/spec/unit/messages/batch_message_spec.rb +37 -36
- data/spec/unit/messages/message_builder_spec.rb +170 -169
- data/spec/unit/railgun/content_type_spec.rb +31 -30
- data/spec/unit/railgun/mailer_spec.rb +62 -59
- data/spec/unit/response_spec.rb +225 -0
- data/vcr_cassettes/For_the_suppressions_handling_class/creates_a_single_bounce.yml +55 -0
- data/vcr_cassettes/analytics_tags.yml +187 -0
- data/vcr_cassettes/suppressions.yml +1053 -170
- metadata +95 -29
- data/.rubocop_todo.yml +0 -22
|
@@ -1,50 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'spec_helper'
|
|
2
4
|
require 'mailgun'
|
|
3
5
|
require 'railgun'
|
|
4
6
|
|
|
5
7
|
describe 'extract_body' do
|
|
6
|
-
|
|
7
|
-
let(:text_mail_option) {
|
|
8
|
+
let(:text_mail_option) do
|
|
8
9
|
{
|
|
9
|
-
from:
|
|
10
|
-
to:
|
|
11
|
-
subject:
|
|
12
|
-
body:
|
|
13
|
-
content_type: 'text/plain'
|
|
10
|
+
from: 'bob@example.com',
|
|
11
|
+
to: 'sally@example.com',
|
|
12
|
+
subject: 'RAILGUN TEST SAMPLE',
|
|
13
|
+
body: text_content,
|
|
14
|
+
content_type: 'text/plain'
|
|
14
15
|
}
|
|
15
|
-
|
|
16
|
+
end
|
|
16
17
|
let(:text_content) { '[TEST] Hello, world.' }
|
|
17
18
|
|
|
18
|
-
let(:html_mail_option)
|
|
19
|
+
let(:html_mail_option) do
|
|
19
20
|
{
|
|
20
|
-
from:
|
|
21
|
-
to:
|
|
22
|
-
subject:
|
|
23
|
-
body:
|
|
24
|
-
content_type: 'text/html'
|
|
21
|
+
from: 'bob@example.com',
|
|
22
|
+
to: 'sally@example.com',
|
|
23
|
+
subject: 'RAILGUN TEST SAMPLE',
|
|
24
|
+
body: html_content,
|
|
25
|
+
content_type: 'text/html'
|
|
25
26
|
}
|
|
26
|
-
|
|
27
|
+
end
|
|
27
28
|
let(:html_content) { '<h3> [TEST] </h3> <br/> Hello, world!' }
|
|
28
29
|
|
|
29
|
-
let(:amp_mail_option)
|
|
30
|
+
let(:amp_mail_option) do
|
|
30
31
|
{
|
|
31
|
-
from:
|
|
32
|
-
to:
|
|
33
|
-
subject:
|
|
34
|
-
body:
|
|
35
|
-
content_type: 'text/x-amp-html'
|
|
32
|
+
from: 'bob@example.com',
|
|
33
|
+
to: 'sally@example.com',
|
|
34
|
+
subject: 'RAILGUN TEST SAMPLE',
|
|
35
|
+
body: amp_content,
|
|
36
|
+
content_type: 'text/x-amp-html'
|
|
36
37
|
}
|
|
37
|
-
|
|
38
|
+
end
|
|
38
39
|
let(:amp_content) { '<h3> [TEST] </h3> <br/> Hello from AMP!' }
|
|
39
40
|
|
|
40
41
|
context 'with <Content-Type: text/plain>' do
|
|
41
42
|
let(:sample_mail) { Mail.new(text_mail_option) }
|
|
42
43
|
|
|
43
|
-
it '
|
|
44
|
+
it 'returns body text' do
|
|
44
45
|
expect(Railgun.extract_body_text(sample_mail)).to eq(text_content)
|
|
45
46
|
end
|
|
46
47
|
|
|
47
|
-
it '
|
|
48
|
+
it 'does not return body html' do
|
|
48
49
|
expect(Railgun.extract_body_html(sample_mail)).to be_nil
|
|
49
50
|
end
|
|
50
51
|
end
|
|
@@ -52,11 +53,11 @@ describe 'extract_body' do
|
|
|
52
53
|
context 'with <Content-Type: text/html>' do
|
|
53
54
|
let(:sample_mail) { Mail.new(html_mail_option) }
|
|
54
55
|
|
|
55
|
-
it '
|
|
56
|
+
it 'does not return body text' do
|
|
56
57
|
expect(Railgun.extract_body_text(sample_mail)).to be_nil
|
|
57
58
|
end
|
|
58
59
|
|
|
59
|
-
it '
|
|
60
|
+
it 'returns body html' do
|
|
60
61
|
expect(Railgun.extract_body_html(sample_mail)).to eq(html_content)
|
|
61
62
|
end
|
|
62
63
|
end
|
|
@@ -67,21 +68,21 @@ describe 'extract_body' do
|
|
|
67
68
|
let(:amp_mail) { Mail.new(amp_mail_option) }
|
|
68
69
|
|
|
69
70
|
before do
|
|
70
|
-
@sample_mail = Mail::Part.new(content_type:
|
|
71
|
+
@sample_mail = Mail::Part.new(content_type: 'multipart/alternative')
|
|
71
72
|
@sample_mail.add_part text_mail
|
|
72
73
|
@sample_mail.add_part amp_mail
|
|
73
74
|
@sample_mail.add_part html_mail
|
|
74
75
|
end
|
|
75
76
|
|
|
76
|
-
it '
|
|
77
|
+
it 'returns body text' do
|
|
77
78
|
expect(Railgun.extract_body_text(@sample_mail)).to eq(text_content)
|
|
78
79
|
end
|
|
79
80
|
|
|
80
|
-
it '
|
|
81
|
+
it 'returns body html' do
|
|
81
82
|
expect(Railgun.extract_body_html(@sample_mail)).to eq(html_content)
|
|
82
83
|
end
|
|
83
84
|
|
|
84
|
-
it '
|
|
85
|
+
it 'returns AMP html' do
|
|
85
86
|
expect(Railgun.extract_amp_html(@sample_mail)).to eq(amp_content)
|
|
86
87
|
end
|
|
87
88
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'json'
|
|
2
4
|
require 'logger'
|
|
3
5
|
require 'spec_helper'
|
|
@@ -15,42 +17,40 @@ class UnitTestMailer < ActionMailer::Base
|
|
|
15
17
|
def plain_message(address, subject, headers)
|
|
16
18
|
headers(headers)
|
|
17
19
|
mail(to: address, subject: subject) do |format|
|
|
18
|
-
format.text { render plain:
|
|
19
|
-
format.html { render html:
|
|
20
|
+
format.text { render plain: 'Test!' }
|
|
21
|
+
format.html { render html: '<p>Test!</p>'.html_safe }
|
|
20
22
|
end
|
|
21
23
|
end
|
|
22
24
|
|
|
23
25
|
def message_with_attachment(address, subject)
|
|
24
26
|
attachments['info.txt'] = {
|
|
25
|
-
:
|
|
26
|
-
:
|
|
27
|
+
content: File.read('docs/railgun/Overview.md'),
|
|
28
|
+
mime_type: 'text/plain'
|
|
27
29
|
}
|
|
28
30
|
mail(to: address, subject: subject) do |format|
|
|
29
|
-
format.text { render plain:
|
|
30
|
-
format.html { render html:
|
|
31
|
+
format.text { render plain: 'Test!' }
|
|
32
|
+
format.html { render html: '<p>Test!</p>'.html_safe }
|
|
31
33
|
end
|
|
32
34
|
end
|
|
33
35
|
|
|
34
36
|
def message_with_template(address, subject, template_name)
|
|
35
37
|
mail(to: address, subject: subject, template: template_name) do |format|
|
|
36
|
-
format.text { render plain:
|
|
38
|
+
format.text { render plain: 'Test!' }
|
|
37
39
|
end
|
|
38
40
|
end
|
|
39
41
|
|
|
40
42
|
def message_with_domain(address, subject, domain)
|
|
41
43
|
mail(to: address, subject: subject, domain: domain) do |format|
|
|
42
|
-
format.text { render plain:
|
|
44
|
+
format.text { render plain: 'Test!' }
|
|
43
45
|
end
|
|
44
46
|
end
|
|
45
|
-
|
|
46
47
|
end
|
|
47
48
|
|
|
48
49
|
describe 'Railgun::Mailer' do
|
|
49
|
-
|
|
50
50
|
it 'has a mailgun_client property which returns a Mailgun::Client' do
|
|
51
51
|
config = {
|
|
52
|
-
api_key:
|
|
53
|
-
domain:
|
|
52
|
+
api_key: {},
|
|
53
|
+
domain: {}
|
|
54
54
|
}
|
|
55
55
|
@mailer_obj = Railgun::Mailer.new(config)
|
|
56
56
|
|
|
@@ -60,18 +60,18 @@ describe 'Railgun::Mailer' do
|
|
|
60
60
|
context 'when config does not have api_key or domain' do
|
|
61
61
|
it 'raises configuration error' do
|
|
62
62
|
config = {
|
|
63
|
-
api_key:
|
|
63
|
+
api_key: {}
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
|
|
66
|
+
expect { Railgun::Mailer.new(config) }.to raise_error(Railgun::ConfigurationError)
|
|
67
67
|
end
|
|
68
68
|
end
|
|
69
69
|
|
|
70
70
|
context 'when fake_message_send is present in config' do
|
|
71
71
|
it 'enables test mode' do
|
|
72
72
|
config = {
|
|
73
|
-
api_key:
|
|
74
|
-
domain:
|
|
73
|
+
api_key: {},
|
|
74
|
+
domain: {},
|
|
75
75
|
fake_message_send: true
|
|
76
76
|
}
|
|
77
77
|
client_double = double(Mailgun::Client)
|
|
@@ -100,7 +100,7 @@ describe 'Railgun::Mailer' do
|
|
|
100
100
|
it 'adds options to message body' do
|
|
101
101
|
message = UnitTestMailer.plain_message('test@example.org', '', {})
|
|
102
102
|
message.mailgun_options ||= {
|
|
103
|
-
'tracking-opens' => 'true'
|
|
103
|
+
'tracking-opens' => 'true'
|
|
104
104
|
}
|
|
105
105
|
|
|
106
106
|
body = Railgun.transform_for_mailgun(message)
|
|
@@ -124,14 +124,14 @@ describe 'Railgun::Mailer' do
|
|
|
124
124
|
it 'adds variables to message body' do
|
|
125
125
|
message = UnitTestMailer.plain_message('test@example.org', '', {})
|
|
126
126
|
message.mailgun_variables ||= {
|
|
127
|
-
'user' => {:
|
|
127
|
+
'user' => { id: '1', name: 'tstark' }
|
|
128
128
|
}
|
|
129
129
|
|
|
130
130
|
body = Railgun.transform_for_mailgun(message)
|
|
131
131
|
|
|
132
132
|
expect(body).to include('v:user')
|
|
133
133
|
|
|
134
|
-
var_body = JSON.
|
|
134
|
+
var_body = JSON.parse(body['v:user'])
|
|
135
135
|
expect(var_body).to include('id')
|
|
136
136
|
expect(var_body).to include('name')
|
|
137
137
|
expect(var_body['id']).to eq('1')
|
|
@@ -141,7 +141,7 @@ describe 'Railgun::Mailer' do
|
|
|
141
141
|
it 'adds headers to message body' do
|
|
142
142
|
message = UnitTestMailer.plain_message('test@example.org', '', {})
|
|
143
143
|
message.mailgun_headers ||= {
|
|
144
|
-
'x-unit-test' => 'true'
|
|
144
|
+
'x-unit-test' => 'true'
|
|
145
145
|
}
|
|
146
146
|
|
|
147
147
|
body = Railgun.transform_for_mailgun(message)
|
|
@@ -152,8 +152,8 @@ describe 'Railgun::Mailer' do
|
|
|
152
152
|
|
|
153
153
|
it 'adds headers to message body from mailer' do
|
|
154
154
|
message = UnitTestMailer.plain_message('test@example.org', '', {
|
|
155
|
-
|
|
156
|
-
|
|
155
|
+
'x-unit-test-2' => 'true'
|
|
156
|
+
})
|
|
157
157
|
|
|
158
158
|
body = Railgun.transform_for_mailgun(message)
|
|
159
159
|
|
|
@@ -162,21 +162,24 @@ describe 'Railgun::Mailer' do
|
|
|
162
162
|
end
|
|
163
163
|
|
|
164
164
|
it 'properly handles headers that are passed as separate POST params' do
|
|
165
|
-
message = UnitTestMailer.plain_message(
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
165
|
+
message = UnitTestMailer.plain_message(
|
|
166
|
+
'test@example.org', 'Test!',
|
|
167
|
+
{
|
|
168
|
+
# `From`, `To`, and `Subject` are set on the envelope, so they should be ignored as headers
|
|
169
|
+
'From' => 'units@example.net',
|
|
170
|
+
'To' => 'user@example.com',
|
|
171
|
+
'Subject' => 'This should disappear',
|
|
172
|
+
# If `Bcc` or `Cc` are set as headers, they should be carried over as POST params, not headers
|
|
173
|
+
'Bcc' => ['list@example.org'],
|
|
174
|
+
'Cc' => ['admin@example.com'],
|
|
175
|
+
# This is an arbitrary header and should be carried over properly
|
|
176
|
+
'X-Source' => 'unit tests'
|
|
177
|
+
}
|
|
178
|
+
)
|
|
176
179
|
|
|
177
180
|
body = Railgun.transform_for_mailgun(message)
|
|
178
181
|
|
|
179
|
-
[
|
|
182
|
+
%w[From To Subject].each do |header|
|
|
180
183
|
expect(body).not_to include("h:#{header}")
|
|
181
184
|
end
|
|
182
185
|
|
|
@@ -202,20 +205,20 @@ describe 'Railgun::Mailer' do
|
|
|
202
205
|
}
|
|
203
206
|
end
|
|
204
207
|
body = Railgun.transform_for_mailgun(message)
|
|
205
|
-
expect(body[
|
|
206
|
-
expect(body[
|
|
208
|
+
expect(body['v:my-data']).to be_a(String)
|
|
209
|
+
expect(body['v:my-data'].to_s).to eq('{"key":"value"}')
|
|
207
210
|
end
|
|
208
211
|
|
|
209
212
|
it 'accepts a hash and appends as data to the message.' do
|
|
210
213
|
message = UnitTestMailer.plain_message('test@example.org', '', {}).tap do |message|
|
|
211
214
|
message.mailgun_variables = {
|
|
212
|
-
'my-data' => {'key' => 'value'}
|
|
215
|
+
'my-data' => { 'key' => 'value' }
|
|
213
216
|
}
|
|
214
217
|
end
|
|
215
218
|
body = Railgun.transform_for_mailgun(message)
|
|
216
219
|
|
|
217
|
-
expect(body[
|
|
218
|
-
expect(body[
|
|
220
|
+
expect(body['v:my-data']).to be_a(String)
|
|
221
|
+
expect(body['v:my-data'].to_s).to eq('{"key":"value"}')
|
|
219
222
|
end
|
|
220
223
|
|
|
221
224
|
it 'accepts string values' do
|
|
@@ -226,8 +229,8 @@ describe 'Railgun::Mailer' do
|
|
|
226
229
|
end
|
|
227
230
|
body = Railgun.transform_for_mailgun(message)
|
|
228
231
|
|
|
229
|
-
expect(body[
|
|
230
|
-
expect(body[
|
|
232
|
+
expect(body['v:my-data']).to be_a(String)
|
|
233
|
+
expect(body['v:my-data'].to_s).to eq('String Value.')
|
|
231
234
|
end
|
|
232
235
|
end
|
|
233
236
|
|
|
@@ -251,13 +254,13 @@ describe 'Railgun::Mailer' do
|
|
|
251
254
|
|
|
252
255
|
it 'ignores `reply-to` in headers' do
|
|
253
256
|
message = UnitTestMailer.plain_message('test@example.org', '', {
|
|
254
|
-
|
|
255
|
-
|
|
257
|
+
'reply-to' => 'user@example.com'
|
|
258
|
+
})
|
|
256
259
|
message.mailgun_headers = {
|
|
257
|
-
'Reply-To' => 'administrator@example.org'
|
|
260
|
+
'Reply-To' => 'administrator@example.org'
|
|
258
261
|
}
|
|
259
|
-
message.headers({'REPLY-TO' => 'admin@example.net'})
|
|
260
|
-
message.reply_to =
|
|
262
|
+
message.headers({ 'REPLY-TO' => 'admin@example.net' })
|
|
263
|
+
message.reply_to = 'dude@example.com.au'
|
|
261
264
|
|
|
262
265
|
body = Railgun.transform_for_mailgun(message)
|
|
263
266
|
expect(body).to include('h:reply-to')
|
|
@@ -267,12 +270,12 @@ describe 'Railgun::Mailer' do
|
|
|
267
270
|
|
|
268
271
|
it 'ignores `mime-version` in headers' do
|
|
269
272
|
message = UnitTestMailer.plain_message('test@example.org', '', {
|
|
270
|
-
|
|
271
|
-
|
|
273
|
+
'mime-version' => '1.0'
|
|
274
|
+
})
|
|
272
275
|
message.mailgun_headers = {
|
|
273
|
-
'Mime-Version' => '1.1'
|
|
276
|
+
'Mime-Version' => '1.1'
|
|
274
277
|
}
|
|
275
|
-
message.headers({'MIME-VERSION' => '1.2'})
|
|
278
|
+
message.headers({ 'MIME-VERSION' => '1.2' })
|
|
276
279
|
|
|
277
280
|
body = Railgun.transform_for_mailgun(message)
|
|
278
281
|
expect(body).not_to include('h:mime-version')
|
|
@@ -280,32 +283,32 @@ describe 'Railgun::Mailer' do
|
|
|
280
283
|
|
|
281
284
|
it 'treats `headers()` names as case-insensitve' do
|
|
282
285
|
message = UnitTestMailer.plain_message('test@example.org', '', {
|
|
283
|
-
|
|
284
|
-
|
|
286
|
+
'X-BIG-VALUE' => 1
|
|
287
|
+
})
|
|
285
288
|
|
|
286
289
|
body = Railgun.transform_for_mailgun(message)
|
|
287
290
|
expect(body).to include('h:x-big-value')
|
|
288
|
-
expect(body['h:x-big-value']).to eq(
|
|
291
|
+
expect(body['h:x-big-value']).to eq('1')
|
|
289
292
|
end
|
|
290
293
|
|
|
291
294
|
it 'treats `mailgun_headers` names as case-insensitive' do
|
|
292
295
|
message = UnitTestMailer.plain_message('test@example.org', '', {})
|
|
293
296
|
message.mailgun_headers = {
|
|
294
|
-
'X-BIG-VALUE' => 1
|
|
297
|
+
'X-BIG-VALUE' => 1
|
|
295
298
|
}
|
|
296
299
|
|
|
297
300
|
body = Railgun.transform_for_mailgun(message)
|
|
298
301
|
expect(body).to include('h:x-big-value')
|
|
299
|
-
expect(body['h:x-big-value']).to eq(
|
|
302
|
+
expect(body['h:x-big-value']).to eq('1')
|
|
300
303
|
end
|
|
301
304
|
|
|
302
305
|
it 'handles multi-value, mixed case headers correctly' do
|
|
303
306
|
message = UnitTestMailer.plain_message('test@example.org', '', {})
|
|
304
307
|
message.headers({
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
308
|
+
'x-neat-header' => 'foo',
|
|
309
|
+
'X-Neat-Header' => 'bar',
|
|
310
|
+
'X-NEAT-HEADER' => 'zoop'
|
|
311
|
+
})
|
|
309
312
|
|
|
310
313
|
body = Railgun.transform_for_mailgun(message)
|
|
311
314
|
expect(body).to include('h:x-neat-header')
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
require 'json'
|
|
5
|
+
require 'yaml'
|
|
6
|
+
require 'mailgun/response'
|
|
7
|
+
require 'mailgun/exceptions/exceptions'
|
|
8
|
+
|
|
9
|
+
describe Mailgun::Response do
|
|
10
|
+
subject(:response) { described_class.new(http_response) }
|
|
11
|
+
|
|
12
|
+
let(:json_body) { '{"id":"<message-id@example.com>","message":"Queued. Thank you."}' }
|
|
13
|
+
let(:status200) { 200 }
|
|
14
|
+
|
|
15
|
+
# Minimal double that mimics a RestClient::Response
|
|
16
|
+
let(:http_response) do
|
|
17
|
+
double('http_response', body: json_body, status: status200)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# ---------------------------------------------------------------------------
|
|
21
|
+
# .new
|
|
22
|
+
# ---------------------------------------------------------------------------
|
|
23
|
+
describe '#initialize' do
|
|
24
|
+
it 'sets body from the underlying response' do
|
|
25
|
+
expect(response.body).to eq(json_body)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it 'sets status from the underlying response' do
|
|
29
|
+
expect(response.status).to eq(status200)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
it 'aliases code to status' do
|
|
33
|
+
expect(response.code).to eq(status200)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# ---------------------------------------------------------------------------
|
|
38
|
+
# .from_hash
|
|
39
|
+
# ---------------------------------------------------------------------------
|
|
40
|
+
describe '.from_hash' do
|
|
41
|
+
subject(:from_hash_response) do
|
|
42
|
+
described_class.from_hash(body: json_body, status: status200)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
it 'returns a Response instance' do
|
|
46
|
+
expect(from_hash_response).to be_a(described_class)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
it 'sets body from the hash' do
|
|
50
|
+
expect(from_hash_response.body).to eq(json_body)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
it 'sets status from the hash' do
|
|
54
|
+
expect(from_hash_response.status).to eq(status200)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
it 'aliases code to status' do
|
|
58
|
+
expect(from_hash_response.code).to eq(status200)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# ---------------------------------------------------------------------------
|
|
63
|
+
# #to_h
|
|
64
|
+
# ---------------------------------------------------------------------------
|
|
65
|
+
describe '#to_h' do
|
|
66
|
+
it 'returns a Hash' do
|
|
67
|
+
expect(response.to_h).to be_a(Hash)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
it 'correctly parses the JSON body' do
|
|
71
|
+
result = response.to_h
|
|
72
|
+
expect(result['id']).to eq('<message-id@example.com>')
|
|
73
|
+
expect(result['message']).to eq('Queued. Thank you.')
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
it 'does not mutate the body attribute' do
|
|
77
|
+
expect { response.to_h }.not_to(change(response, :body))
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
context 'when the body is invalid JSON' do
|
|
81
|
+
let(:http_response) { double('http_response', body: 'not-json', status: 200) }
|
|
82
|
+
|
|
83
|
+
it 'raises a Mailgun::ParseError' do
|
|
84
|
+
expect { response.to_h }.to raise_error(Mailgun::ParseError)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# ---------------------------------------------------------------------------
|
|
90
|
+
# #to_h!
|
|
91
|
+
# ---------------------------------------------------------------------------
|
|
92
|
+
describe '#to_h!' do
|
|
93
|
+
it 'returns a Hash' do
|
|
94
|
+
expect(response.to_h!).to be_a(Hash)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
it 'correctly parses the JSON body' do
|
|
98
|
+
result = response.to_h!
|
|
99
|
+
expect(result['message']).to eq('Queued. Thank you.')
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
it 'replaces body with the parsed Hash' do
|
|
103
|
+
response.to_h!
|
|
104
|
+
expect(response.body).to be_a(Hash)
|
|
105
|
+
expect(response.body['id']).to eq('<message-id@example.com>')
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
context 'when the body is invalid JSON' do
|
|
109
|
+
let(:http_response) { double('http_response', body: 'not-json', status: 200) }
|
|
110
|
+
|
|
111
|
+
it 'raises a Mailgun::ParseError' do
|
|
112
|
+
expect { response.to_h! }.to raise_error(Mailgun::ParseError)
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# ---------------------------------------------------------------------------
|
|
118
|
+
# #to_yaml
|
|
119
|
+
# ---------------------------------------------------------------------------
|
|
120
|
+
describe '#to_yaml' do
|
|
121
|
+
it 'returns a String' do
|
|
122
|
+
expect(response.to_yaml).to be_a(String)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
it 'returns valid YAML that round-trips back to the expected hash' do
|
|
126
|
+
parsed = YAML.safe_load(response.to_yaml)
|
|
127
|
+
expect(parsed['id']).to eq('<message-id@example.com>')
|
|
128
|
+
expect(parsed['message']).to eq('Queued. Thank you.')
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
it 'does not mutate the body attribute' do
|
|
132
|
+
expect { response.to_yaml }.not_to(change(response, :body))
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
context 'when the body is invalid JSON' do
|
|
136
|
+
let(:http_response) { double('http_response', body: 'not-json', status: 200) }
|
|
137
|
+
|
|
138
|
+
it 'raises a Mailgun::ParseError' do
|
|
139
|
+
expect { response.to_yaml }.to raise_error(Mailgun::ParseError)
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# ---------------------------------------------------------------------------
|
|
145
|
+
# #to_yaml!
|
|
146
|
+
# ---------------------------------------------------------------------------
|
|
147
|
+
describe '#to_yaml!' do
|
|
148
|
+
it 'returns a String' do
|
|
149
|
+
expect(response.to_yaml!).to be_a(String)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
it 'returns valid YAML that round-trips back to the expected hash' do
|
|
153
|
+
yaml_str = response.to_yaml!
|
|
154
|
+
parsed = YAML.safe_load(yaml_str)
|
|
155
|
+
expect(parsed['message']).to eq('Queued. Thank you.')
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
it 'replaces body with the YAML string' do
|
|
159
|
+
response.to_yaml!
|
|
160
|
+
expect(response.body).to be_a(String)
|
|
161
|
+
expect(response.body).to include('Queued. Thank you.')
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
context 'when the body is invalid JSON' do
|
|
165
|
+
let(:http_response) { double('http_response', body: 'not-json', status: 200) }
|
|
166
|
+
|
|
167
|
+
it 'raises a Mailgun::ParseError' do
|
|
168
|
+
expect { response.to_yaml! }.to raise_error(Mailgun::ParseError)
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# ---------------------------------------------------------------------------
|
|
174
|
+
# #success?
|
|
175
|
+
# ---------------------------------------------------------------------------
|
|
176
|
+
describe '#success?' do
|
|
177
|
+
context 'with a 2xx status code' do
|
|
178
|
+
[200, 201, 204, 299].each do |code|
|
|
179
|
+
it "returns true for status #{code}" do
|
|
180
|
+
allow(http_response).to receive(:status).and_return(code)
|
|
181
|
+
expect(described_class.new(http_response).success?).to be(true)
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
context 'with a non-2xx status code' do
|
|
187
|
+
[400, 401, 403, 404, 422, 500, 503].each do |code|
|
|
188
|
+
it "returns false for status #{code}" do
|
|
189
|
+
allow(http_response).to receive(:status).and_return(code)
|
|
190
|
+
expect(described_class.new(http_response).success?).to be(false)
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
it 'returns false for status 199 (just below 2xx range)' do
|
|
196
|
+
allow(http_response).to receive(:status).and_return(199)
|
|
197
|
+
expect(described_class.new(http_response).success?).to be(false)
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
it 'returns false for status 300 (just above 2xx range)' do
|
|
201
|
+
allow(http_response).to receive(:status).and_return(300)
|
|
202
|
+
expect(described_class.new(http_response).success?).to be(false)
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
# ---------------------------------------------------------------------------
|
|
207
|
+
# attr_accessor sanity checks
|
|
208
|
+
# ---------------------------------------------------------------------------
|
|
209
|
+
describe 'attribute accessors' do
|
|
210
|
+
it 'allows body to be overwritten' do
|
|
211
|
+
response.body = 'new body'
|
|
212
|
+
expect(response.body).to eq('new body')
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
it 'allows status to be overwritten' do
|
|
216
|
+
response.status = 404
|
|
217
|
+
expect(response.status).to eq(404)
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
it 'allows code to be overwritten' do
|
|
221
|
+
response.code = 500
|
|
222
|
+
expect(response.code).to eq(500)
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
---
|
|
2
|
+
http_interactions:
|
|
3
|
+
- request:
|
|
4
|
+
method: post
|
|
5
|
+
uri: https://api.mailgun.net/bounces
|
|
6
|
+
body:
|
|
7
|
+
encoding: UTF-8
|
|
8
|
+
string: address=test5%40example.info&code=500&error=integration+testing
|
|
9
|
+
headers:
|
|
10
|
+
User-Agent:
|
|
11
|
+
- mailgun-sdk-ruby/1.4.2
|
|
12
|
+
Accept:
|
|
13
|
+
- "*/*"
|
|
14
|
+
Authorization:
|
|
15
|
+
- Basic XXX
|
|
16
|
+
Content-Type:
|
|
17
|
+
- application/x-www-form-urlencoded
|
|
18
|
+
Accept-Encoding:
|
|
19
|
+
- gzip;q=1.0,deflate;q=0.6,identity;q=0.3
|
|
20
|
+
response:
|
|
21
|
+
status:
|
|
22
|
+
code: 200
|
|
23
|
+
message: OK
|
|
24
|
+
headers:
|
|
25
|
+
Access-Control-Allow-Credentials:
|
|
26
|
+
- 'true'
|
|
27
|
+
Access-Control-Allow-Origin:
|
|
28
|
+
- "*"
|
|
29
|
+
Cache-Control:
|
|
30
|
+
- no-store
|
|
31
|
+
Content-Length:
|
|
32
|
+
- '89'
|
|
33
|
+
Content-Type:
|
|
34
|
+
- application/json; charset=utf-8
|
|
35
|
+
Date:
|
|
36
|
+
- Wed, 25 Mar 2026 18:09:17 GMT
|
|
37
|
+
Strict-Transport-Security:
|
|
38
|
+
- max-age=63072000; includeSubDomains
|
|
39
|
+
X-Mailgun-Key-Id:
|
|
40
|
+
- XXX
|
|
41
|
+
X-Request-Limit:
|
|
42
|
+
- '2500'
|
|
43
|
+
X-Request-Remaining:
|
|
44
|
+
- '2498'
|
|
45
|
+
X-Request-Reset:
|
|
46
|
+
- '1774462171770'
|
|
47
|
+
X-Xss-Protection:
|
|
48
|
+
- 1; mode=block
|
|
49
|
+
body:
|
|
50
|
+
encoding: UTF-8
|
|
51
|
+
string: '{"message":"Address has been added to the bounces table","address":"test5@example.info"}
|
|
52
|
+
|
|
53
|
+
'
|
|
54
|
+
recorded_at: Wed, 25 Mar 2026 18:09:17 GMT
|
|
55
|
+
recorded_with: VCR 6.4.0
|