postmark 1.8.1 → 1.21.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.
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