postmark 1.8.1 → 1.21.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +2 -0
  3. data/.travis.yml +8 -5
  4. data/CHANGELOG.rdoc +86 -0
  5. data/CONTRIBUTING.md +18 -0
  6. data/Gemfile +6 -5
  7. data/LICENSE +1 -1
  8. data/README.md +34 -607
  9. data/RELEASE.md +12 -0
  10. data/VERSION +1 -1
  11. data/gemfiles/Gemfile.legacy +5 -4
  12. data/lib/postmark.rb +1 -18
  13. data/lib/postmark/account_api_client.rb +55 -1
  14. data/lib/postmark/api_client.rb +145 -17
  15. data/lib/postmark/bounce.rb +0 -4
  16. data/lib/postmark/client.rb +12 -4
  17. data/lib/postmark/error.rb +127 -0
  18. data/lib/postmark/handlers/mail.rb +10 -4
  19. data/lib/postmark/helpers/message_helper.rb +4 -0
  20. data/lib/postmark/http_client.rb +20 -32
  21. data/lib/postmark/mail_message_converter.rb +18 -5
  22. data/lib/postmark/message_extensions/mail.rb +83 -8
  23. data/lib/postmark/version.rb +1 -1
  24. data/postmark.gemspec +1 -1
  25. data/postmark.png +0 -0
  26. data/spec/integration/account_api_client_spec.rb +42 -10
  27. data/spec/integration/api_client_hashes_spec.rb +32 -49
  28. data/spec/integration/api_client_messages_spec.rb +33 -52
  29. data/spec/integration/api_client_resources_spec.rb +12 -44
  30. data/spec/integration/mail_delivery_method_spec.rb +21 -23
  31. data/spec/spec_helper.rb +4 -7
  32. data/spec/support/custom_matchers.rb +44 -0
  33. data/spec/support/shared_examples.rb +16 -16
  34. data/spec/unit/postmark/account_api_client_spec.rb +239 -45
  35. data/spec/unit/postmark/api_client_spec.rb +792 -406
  36. data/spec/unit/postmark/bounce_spec.rb +40 -62
  37. data/spec/unit/postmark/client_spec.rb +0 -6
  38. data/spec/unit/postmark/error_spec.rb +231 -0
  39. data/spec/unit/postmark/handlers/mail_spec.rb +59 -27
  40. data/spec/unit/postmark/helpers/hash_helper_spec.rb +5 -6
  41. data/spec/unit/postmark/helpers/message_helper_spec.rb +60 -11
  42. data/spec/unit/postmark/http_client_spec.rb +76 -61
  43. data/spec/unit/postmark/inbound_spec.rb +34 -34
  44. data/spec/unit/postmark/inflector_spec.rb +11 -13
  45. data/spec/unit/postmark/json_spec.rb +2 -2
  46. data/spec/unit/postmark/mail_message_converter_spec.rb +250 -81
  47. data/spec/unit/postmark/message_extensions/mail_spec.rb +249 -38
  48. data/spec/unit/postmark_spec.rb +37 -37
  49. metadata +41 -11
@@ -2,7 +2,7 @@ require 'spec_helper'
2
2
 
3
3
  describe Mail::Message do
4
4
  before do
5
- Kernel.stub(:warn)
5
+ allow(Kernel).to receive(:warn)
6
6
  end
7
7
 
8
8
  let(:mail_message) do
@@ -15,7 +15,7 @@ describe Mail::Message do
15
15
  end
16
16
 
17
17
  let(:mail_html_message) do
18
- mail = Mail.new do
18
+ Mail.new do
19
19
  from "sheldon@bigbangtheory.com"
20
20
  to "lenard@bigbangtheory.com"
21
21
  subject "Hello!"
@@ -24,38 +24,112 @@ describe Mail::Message do
24
24
  end
25
25
  end
26
26
 
27
- let(:mail_message_with_bogus_headers) do
28
- mail_message.header['Return-Path'] = 'bounce@wildbit.com'
29
- mail_message.header['From'] = 'info@wildbit.com'
30
- mail_message.header['Sender'] = 'info@wildbit.com'
31
- mail_message.header['Received'] = 'from mta.pstmrk.it ([72.14.252.155]:54907)'
32
- mail_message.header['Date'] = 'January 25, 2013 3:30:58 PM PDT'
33
- mail_message.header['Content-Type'] = 'application/json'
34
- mail_message.header['To'] = 'lenard@bigbangtheory.com'
35
- mail_message.header['Cc'] = 'sheldon@bigbangtheory.com'
36
- mail_message.header['Bcc'] = 'penny@bigbangtheory.com'
37
- mail_message.header['Subject'] = 'You want not to use a bogus header'
38
- mail_message.header['Tag'] = 'bogus-tag'
39
- mail_message.header['Attachment'] = 'anydatahere'
40
- mail_message.header['Allowed-Header'] = 'value'
41
- mail_message
27
+ let(:templated_message) do
28
+ Mail.new do
29
+ from "sheldon@bigbangtheory.com"
30
+ to "lenard@bigbangtheory.com"
31
+ template_alias "Hello!"
32
+ template_model :name => "Sheldon"
33
+ end
34
+ end
35
+
36
+ describe '#tag' do
37
+
38
+ it 'value set on tag=' do
39
+ mail_message.tag='value'
40
+ expect(mail_message.tag).to eq 'value'
41
+ end
42
+
43
+ it 'value set on tag()' do
44
+ mail_message.tag('value')
45
+ expect(mail_message.tag).to eq 'value'
46
+ end
47
+
48
+ end
49
+
50
+ describe '#track_opens' do
51
+ it 'returns nil if unset' do
52
+ expect(mail_message.track_opens).to eq ''
53
+ end
54
+
55
+ context 'when assigned via #track_opens=' do
56
+ it 'returns assigned value to track opens' do
57
+ mail_message.track_opens = true
58
+ expect(mail_message.track_opens).to eq 'true'
59
+ end
60
+
61
+ it 'returns assigned value to not track opens' do
62
+ mail_message.track_opens = false
63
+ expect(mail_message.track_opens).to eq 'false'
64
+ end
65
+ end
66
+
67
+ context 'flag set on track_opens()' do
68
+ it 'true' do
69
+ mail_message.track_opens(true)
70
+ expect(mail_message.track_opens).to eq 'true'
71
+ end
72
+
73
+ it 'false' do
74
+ mail_message.track_opens(false)
75
+ expect(mail_message.track_opens).to eq 'false'
76
+ end
77
+ end
78
+ end
79
+
80
+ describe '#metadata' do
81
+ let(:metadata) { { :test => 'test' } }
82
+
83
+ it 'returns a mutable empty hash if unset' do
84
+ expect(mail_message.metadata).to eq({})
85
+ expect(mail_message.metadata.equal?(mail_message.metadata)).to be true
86
+ end
87
+
88
+ it 'supports assigning non-null values (for the builder DSL)' do
89
+ expect { mail_message.metadata(metadata) }.to change { mail_message.metadata }.to(metadata)
90
+ expect { mail_message.metadata(nil) }.to_not change { mail_message.metadata }
91
+ end
92
+
93
+ it 'returns value assigned via metadata=' do
94
+ expect { mail_message.metadata = metadata }.to change { mail_message.metadata }.to(metadata)
95
+ end
96
+ end
97
+
98
+ describe '#track_links' do
99
+ it 'return empty string when if unset' do
100
+ expect(mail_message.track_links).to eq ''
101
+ end
102
+
103
+ context 'when assigned via #track_links=' do
104
+ it 'returns track html only body value in Postmark format' do
105
+ mail_message.track_links=:html_only
106
+ expect(mail_message.track_links).to eq 'HtmlOnly'
107
+ end
108
+ end
109
+
110
+ context 'when assigned via track_links()' do
111
+ it 'returns track html only body value in Postmark format' do
112
+ mail_message.track_links(:html_only)
113
+ expect(mail_message.track_links).to eq 'HtmlOnly'
114
+ end
115
+ end
42
116
  end
43
117
 
44
118
  describe "#html?" do
45
119
  it 'is true for html only email' do
46
- mail_html_message.should be_html
120
+ expect(mail_html_message).to be_html
47
121
  end
48
122
  end
49
123
 
50
124
  describe "#body_html" do
51
125
  it 'returns html body if present' do
52
- mail_html_message.body_html.should == "<b>Hello Sheldon!</b>"
126
+ expect(mail_html_message.body_html).to eq "<b>Hello Sheldon!</b>"
53
127
  end
54
128
  end
55
129
 
56
130
  describe "#body_text" do
57
131
  it 'returns text body if present' do
58
- mail_message.body_text.should == "Hello Sheldon!"
132
+ expect(mail_message.body_text).to eq "Hello Sheldon!"
59
133
  end
60
134
  end
61
135
 
@@ -65,11 +139,11 @@ describe Mail::Message do
65
139
 
66
140
  it "stores attachments as an array" do
67
141
  mail_message.postmark_attachments = attached_hash
68
- mail_message.instance_variable_get(:@_attachments).should include(attached_hash)
142
+ expect(mail_message.instance_variable_get(:@_attachments)).to include(attached_hash)
69
143
  end
70
144
 
71
145
  it "is deprecated" do
72
- Kernel.should_receive(:warn).with(/deprecated/)
146
+ expect(Kernel).to receive(:warn).with(/deprecated/)
73
147
  mail_message.postmark_attachments = attached_hash
74
148
  end
75
149
  end
@@ -83,23 +157,23 @@ describe Mail::Message do
83
157
  'Content' => ''} }
84
158
 
85
159
  before do
86
- attached_file.stub(:is_a?) { |arg| arg == File ? true : false }
87
- attached_file.stub(:path) { '/tmp/file.jpeg' }
160
+ allow(attached_file).to receive(:is_a?) { |arg| arg == File ? true : false }
161
+ allow(attached_file).to receive(:path) { '/tmp/file.jpeg' }
88
162
  end
89
163
 
90
164
  it "supports multiple attachment formats" do
91
- IO.should_receive(:read).with("/tmp/file.jpeg").and_return("")
165
+ expect(IO).to receive(:read).with("/tmp/file.jpeg").and_return("")
92
166
 
93
167
  mail_message.postmark_attachments = [attached_hash, attached_file]
94
168
  attachments = mail_message.export_attachments
95
169
 
96
- attachments.should include(attached_hash)
97
- attachments.should include(exported_file)
170
+ expect(attachments).to include(attached_hash)
171
+ expect(attachments).to include(exported_file)
98
172
  end
99
173
 
100
174
  it "is deprecated" do
101
175
  mail_message.postmark_attachments = attached_hash
102
- Kernel.should_receive(:warn).with(/deprecated/)
176
+ expect(Kernel).to receive(:warn).with(/deprecated/)
103
177
  mail_message.postmark_attachments
104
178
  end
105
179
  end
@@ -116,13 +190,13 @@ describe Mail::Message do
116
190
 
117
191
  it "exports native attachments" do
118
192
  mail_message.attachments["face.jpeg"] = file_data
119
- mail_message.export_attachments.should include(exported_data)
193
+ expect(mail_message.export_attachments).to include(exported_data)
120
194
  end
121
195
 
122
196
  it "still supports the deprecated attachments API" do
123
197
  mail_message.attachments["face.jpeg"] = file_data
124
198
  mail_message.postmark_attachments = exported_data
125
- mail_message.export_attachments.should == [exported_data, exported_data]
199
+ expect(mail_message.export_attachments).to eq [exported_data, exported_data]
126
200
  end
127
201
 
128
202
  end
@@ -132,10 +206,11 @@ describe Mail::Message do
132
206
  it "exports the attachment with related content id" do
133
207
  mail_message.attachments.inline["face.jpeg"] = file_data
134
208
  attachments = mail_message.export_attachments
135
- attachments.count.should_not be_zero
136
- attachments.first.should include(exported_data)
137
- attachments.first.should have_key('ContentID')
138
- attachments.first['ContentID'].should start_with('cid:')
209
+
210
+ expect(attachments.count).to_not be_zero
211
+ expect(attachments.first).to include(exported_data)
212
+ expect(attachments.first).to have_key('ContentID')
213
+ expect(attachments.first['ContentID']).to start_with('cid:')
139
214
  end
140
215
 
141
216
  end
@@ -143,15 +218,151 @@ describe Mail::Message do
143
218
  end
144
219
 
145
220
  describe "#export_headers" do
146
- let(:headers) { mail_message_with_bogus_headers.export_headers }
147
- let(:header_names) { headers.map { |h| h['Name'] } }
221
+ let(:mail_message_with_reserved_headers) do
222
+ mail_message.header['Return-Path'] = 'bounce@wildbit.com'
223
+ mail_message.header['From'] = 'info@wildbit.com'
224
+ mail_message.header['Sender'] = 'info@wildbit.com'
225
+ mail_message.header['Received'] = 'from mta.pstmrk.it ([72.14.252.155]:54907)'
226
+ mail_message.header['Date'] = 'January 25, 2013 3:30:58 PM PDT'
227
+ mail_message.header['Content-Type'] = 'application/json'
228
+ mail_message.header['To'] = 'lenard@bigbangtheory.com'
229
+ mail_message.header['Cc'] = 'sheldon@bigbangtheory.com'
230
+ mail_message.header['Bcc'] = 'penny@bigbangtheory.com'
231
+ mail_message.header['Subject'] = 'You want not to use a bogus header'
232
+ mail_message.header['Tag'] = 'bogus-tag'
233
+ mail_message.header['Attachment'] = 'anydatahere'
234
+ mail_message.header['Allowed-Header'] = 'value'
235
+ mail_message.header['TRACK-OPENS'] = 'true'
236
+ mail_message.header['TRACK-LINKS'] = 'HtmlOnly'
237
+ mail_message
238
+ end
239
+
240
+
241
+ it 'only allowed headers' do
242
+ headers = mail_message_with_reserved_headers.export_headers
243
+ header_names = headers.map { |h| h['Name'] }
148
244
 
149
- specify { header_names.should include('Allowed-Header') }
150
- specify { header_names.count.should == 1 }
245
+ aggregate_failures do
246
+ expect(header_names).to include('Allowed-Header')
247
+ expect(header_names.count).to eq 1
248
+ end
249
+ end
250
+
251
+ it 'custom header character case preserved' do
252
+ custom_header = {"Name"=>"custom-Header", "Value"=>"cUsTomHeaderValue"}
253
+ mail_message.header[custom_header['Name']] = custom_header['Value']
254
+
255
+ expect(mail_message.export_headers.first).to match(custom_header)
256
+ end
151
257
  end
152
258
 
153
259
  describe "#to_postmark_hash" do
154
260
  # See mail_message_converter_spec.rb
155
261
  end
156
262
 
263
+ describe '#templated?' do
264
+ it { expect(mail_message).to_not be_templated }
265
+ it { expect(templated_message).to be_templated }
266
+ end
267
+
268
+ describe '#prerender' do
269
+ let(:model) { templated_message.template_model }
270
+ let(:model_text) { model[:name] }
271
+
272
+ let(:template_response) do
273
+ {
274
+ :html_body => '<html><body>{{ name }}</body></html>',
275
+ :text_body => '{{ name }}'
276
+ }
277
+ end
278
+
279
+ let(:successful_render_response) do
280
+ {
281
+ :all_content_is_valid => true,
282
+ :subject => {
283
+ :rendered_content => 'Subject'
284
+ },
285
+ :text_body => {
286
+ :rendered_content => model_text
287
+ },
288
+ :html_body => {
289
+ :rendered_content => "<html><body>#{model_text}</body></html>"
290
+ }
291
+ }
292
+ end
293
+
294
+ let(:failed_render_response) do
295
+ {
296
+ :all_content_is_valid => false,
297
+ :subject => {
298
+ :rendered_content => 'Subject'
299
+ },
300
+ :text_body => {
301
+ :rendered_content => model_text
302
+ },
303
+ :html_body => {
304
+ :rendered_content => nil,
305
+ :validation_errors => [
306
+ { :message => 'The syntax for this template is invalid.', :line => 1, :character_position => 1 }
307
+ ]
308
+ }
309
+ }
310
+ end
311
+
312
+ subject(:rendering) { message.prerender }
313
+
314
+ context 'when called on a non-templated message' do
315
+ let(:message) { mail_message }
316
+
317
+ it 'raises a Postmark::Error' do
318
+ expect { rendering }.to raise_error(Postmark::Error, /Cannot prerender/)
319
+ end
320
+ end
321
+
322
+ context 'when called on a templated message' do
323
+ let(:message) { templated_message }
324
+
325
+ before do
326
+ message.delivery_method delivery_method
327
+ end
328
+
329
+ context 'and using a non-Postmark delivery method' do
330
+ let(:delivery_method) { Mail::SMTP }
331
+
332
+ it { expect { rendering }.to raise_error(Postmark::MailAdapterError) }
333
+ end
334
+
335
+ context 'and using a Postmark delivery method' do
336
+ let(:delivery_method) { Mail::Postmark }
337
+
338
+ before do
339
+ expect_any_instance_of(Postmark::ApiClient).
340
+ to receive(:get_template).with(message.template_alias).
341
+ and_return(template_response)
342
+ expect_any_instance_of(Postmark::ApiClient).
343
+ to receive(:validate_template).with(template_response.merge(:test_render_model => model)).
344
+ and_return(render_response)
345
+ end
346
+
347
+ context 'and rendering succeeds' do
348
+ let(:render_response) { successful_render_response }
349
+
350
+ it 'sets HTML and Text parts to rendered values' do
351
+ expect { rendering }.
352
+ to change { message.subject }.to(render_response[:subject][:rendered_content]).
353
+ and change { message.body_text }.to(render_response[:text_body][:rendered_content]).
354
+ and change { message.body_html }.to(render_response[:html_body][:rendered_content])
355
+ end
356
+ end
357
+
358
+ context 'and rendering fails' do
359
+ let(:render_response) { failed_render_response }
360
+
361
+ it 'raises Postmark::InvalidTemplateError' do
362
+ expect { rendering }.to raise_error(Postmark::InvalidTemplateError)
363
+ end
364
+ end
365
+ end
366
+ end
367
+ end
157
368
  end
@@ -26,37 +26,37 @@ describe Postmark do
26
26
  end
27
27
 
28
28
  context "attr readers" do
29
- it { should respond_to(:secure) }
30
- it { should respond_to(:api_key) }
31
- it { should respond_to(:api_token) }
32
- it { should respond_to(:proxy_host) }
33
- it { should respond_to(:proxy_port) }
34
- it { should respond_to(:proxy_user) }
35
- it { should respond_to(:proxy_pass) }
36
- it { should respond_to(:host) }
37
- it { should respond_to(:port) }
38
- it { should respond_to(:path_prefix) }
39
- it { should respond_to(:http_open_timeout) }
40
- it { should respond_to(:http_read_timeout) }
41
- it { should respond_to(:max_retries) }
29
+ it { expect(subject).to respond_to(:secure) }
30
+ it { expect(subject).to respond_to(:api_key) }
31
+ it { expect(subject).to respond_to(:api_token) }
32
+ it { expect(subject).to respond_to(:proxy_host) }
33
+ it { expect(subject).to respond_to(:proxy_port) }
34
+ it { expect(subject).to respond_to(:proxy_user) }
35
+ it { expect(subject).to respond_to(:proxy_pass) }
36
+ it { expect(subject).to respond_to(:host) }
37
+ it { expect(subject).to respond_to(:port) }
38
+ it { expect(subject).to respond_to(:path_prefix) }
39
+ it { expect(subject).to respond_to(:http_open_timeout) }
40
+ it { expect(subject).to respond_to(:http_read_timeout) }
41
+ it { expect(subject).to respond_to(:max_retries) }
42
42
  end
43
43
 
44
44
  context "attr writers" do
45
- it { should respond_to(:secure=) }
46
- it { should respond_to(:api_key=) }
47
- it { should respond_to(:api_token=) }
48
- it { should respond_to(:proxy_host=) }
49
- it { should respond_to(:proxy_port=) }
50
- it { should respond_to(:proxy_user=) }
51
- it { should respond_to(:proxy_pass=) }
52
- it { should respond_to(:host=) }
53
- it { should respond_to(:port=) }
54
- it { should respond_to(:path_prefix=) }
55
- it { should respond_to(:http_open_timeout=) }
56
- it { should respond_to(:http_read_timeout=) }
57
- it { should respond_to(:max_retries=) }
58
- it { should respond_to(:response_parser_class=) }
59
- it { should respond_to(:api_client=) }
45
+ it { expect(subject).to respond_to(:secure=) }
46
+ it { expect(subject).to respond_to(:api_key=) }
47
+ it { expect(subject).to respond_to(:api_token=) }
48
+ it { expect(subject).to respond_to(:proxy_host=) }
49
+ it { expect(subject).to respond_to(:proxy_port=) }
50
+ it { expect(subject).to respond_to(:proxy_user=) }
51
+ it { expect(subject).to respond_to(:proxy_pass=) }
52
+ it { expect(subject).to respond_to(:host=) }
53
+ it { expect(subject).to respond_to(:port=) }
54
+ it { expect(subject).to respond_to(:path_prefix=) }
55
+ it { expect(subject).to respond_to(:http_open_timeout=) }
56
+ it { expect(subject).to respond_to(:http_read_timeout=) }
57
+ it { expect(subject).to respond_to(:max_retries=) }
58
+ it { expect(subject).to respond_to(:response_parser_class=) }
59
+ it { expect(subject).to respond_to(:api_client=) }
60
60
  end
61
61
 
62
62
  describe ".response_parser_class" do
@@ -66,12 +66,12 @@ describe Postmark do
66
66
  end
67
67
 
68
68
  it "returns :ActiveSupport when ActiveSupport::JSON is available" do
69
- subject.response_parser_class.should == :ActiveSupport
69
+ expect(subject.response_parser_class).to eq :ActiveSupport
70
70
  end
71
71
 
72
72
  it "returns :Json when ActiveSupport::JSON is not available" do
73
73
  hide_const("ActiveSupport::JSON")
74
- subject.response_parser_class.should == :Json
74
+ expect(subject.response_parser_class).to eq :Json
75
75
  end
76
76
 
77
77
  end
@@ -91,7 +91,7 @@ describe Postmark do
91
91
 
92
92
  it 'returns the existing instance' do
93
93
  subject.instance_variable_set(:@api_client, api_client)
94
- subject.api_client.should == api_client
94
+ expect(subject.api_client).to eq api_client
95
95
  end
96
96
 
97
97
  end
@@ -99,7 +99,7 @@ describe Postmark do
99
99
  context "when shared client instance does not exist" do
100
100
 
101
101
  it 'creates a new instance of Postmark::ApiClient' do
102
- Postmark::ApiClient.should_receive(:new).
102
+ allow(Postmark::ApiClient).to receive(:new).
103
103
  with(api_token,
104
104
  :secure => secure,
105
105
  :proxy_host => proxy_host,
@@ -111,7 +111,7 @@ describe Postmark do
111
111
  :path_prefix => path_prefix,
112
112
  :max_retries => max_retries).
113
113
  and_return(api_client)
114
- subject.api_client.should == api_client
114
+ expect(subject.api_client).to eq api_client
115
115
  end
116
116
 
117
117
  end
@@ -127,12 +127,12 @@ describe Postmark do
127
127
  end
128
128
 
129
129
  it 'delegates the method to the shared api client instance' do
130
- api_client.should_receive(:deliver_message).with(message)
130
+ allow(api_client).to receive(:deliver_message).with(message)
131
131
  subject.deliver_message(message)
132
132
  end
133
133
 
134
134
  it 'is also accessible as .send_through_postmark' do
135
- api_client.should_receive(:deliver_message).with(message)
135
+ allow(api_client).to receive(:deliver_message).with(message)
136
136
  subject.send_through_postmark(message)
137
137
  end
138
138
  end
@@ -146,7 +146,7 @@ describe Postmark do
146
146
  end
147
147
 
148
148
  it 'delegates the method to the shared api client instance' do
149
- api_client.should_receive(:deliver_messages).with(message)
149
+ allow(api_client).to receive(:deliver_messages).with(message)
150
150
  subject.deliver_messages(message)
151
151
  end
152
152
  end
@@ -159,7 +159,7 @@ describe Postmark do
159
159
  end
160
160
 
161
161
  it 'delegates the method to the shared api client instance' do
162
- api_client.should_receive(:delivery_stats)
162
+ allow(api_client).to receive(:delivery_stats)
163
163
  subject.delivery_stats
164
164
  end
165
165
  end