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
@@ -15,49 +15,46 @@ describe Postmark::Bounce do
15
15
  :subject => "Hello from our app!"} }
16
16
  let(:bounce_data_postmark) { Postmark::HashHelper.to_postmark(bounce_data) }
17
17
  let(:bounces_data) { [bounce_data, bounce_data, bounce_data] }
18
-
19
18
  let(:bounce) { Postmark::Bounce.new(bounce_data) }
20
19
 
21
20
  subject { bounce }
22
21
 
23
22
  context "attr readers" do
24
-
25
- it { should respond_to(:email) }
26
- it { should respond_to(:bounced_at) }
27
- it { should respond_to(:type) }
28
- it { should respond_to(:description) }
29
- it { should respond_to(:details) }
30
- it { should respond_to(:name) }
31
- it { should respond_to(:id) }
32
- it { should respond_to(:server_id) }
33
- it { should respond_to(:tag) }
34
- it { should respond_to(:message_id) }
35
- it { should respond_to(:subject) }
36
-
23
+ it { expect(subject).to respond_to(:email) }
24
+ it { expect(subject).to respond_to(:bounced_at) }
25
+ it { expect(subject).to respond_to(:type) }
26
+ it { expect(subject).to respond_to(:description) }
27
+ it { expect(subject).to respond_to(:details) }
28
+ it { expect(subject).to respond_to(:name) }
29
+ it { expect(subject).to respond_to(:id) }
30
+ it { expect(subject).to respond_to(:server_id) }
31
+ it { expect(subject).to respond_to(:tag) }
32
+ it { expect(subject).to respond_to(:message_id) }
33
+ it { expect(subject).to respond_to(:subject) }
37
34
  end
38
35
 
39
36
  context "given a bounce created from bounce_data" do
40
37
 
41
38
  it 'is not inactive' do
42
- should_not be_inactive
39
+ expect(subject).not_to be_inactive
43
40
  end
44
41
 
45
42
  it 'allows to activate the bounce' do
46
- subject.can_activate?.should be_true
43
+ expect(subject.can_activate?).to be true
47
44
  end
48
45
 
49
46
  it 'has an available dump' do
50
- subject.dump_available?.should be_true
47
+ expect(subject.dump_available?).to be true
51
48
  end
52
49
 
53
- its(:type) { should eq bounce_data[:type] }
54
- its(:message_id) { should eq bounce_data[:message_id] }
55
- its(:description) { should eq bounce_data[:description] }
56
- its(:details) { should eq bounce_data[:details] }
57
- its(:email) { should eq bounce_data[:email] }
58
- its(:bounced_at) { should == Time.parse(bounce_data[:bounced_at]) }
59
- its(:id) { should eq bounce_data[:id] }
60
- its(:subject) { should eq bounce_data[:subject] }
50
+ its(:type) { is_expected.to eq bounce_data[:type] }
51
+ its(:message_id) { is_expected.to eq bounce_data[:message_id] }
52
+ its(:description) { is_expected.to eq bounce_data[:description] }
53
+ its(:details) { is_expected.to eq bounce_data[:details] }
54
+ its(:email) { is_expected.to eq bounce_data[:email] }
55
+ its(:bounced_at) { is_expected.to eq Time.parse(bounce_data[:bounced_at]) }
56
+ its(:id) { is_expected.to eq bounce_data[:id] }
57
+ its(:subject) { is_expected.to eq bounce_data[:subject] }
61
58
 
62
59
  end
63
60
 
@@ -65,81 +62,62 @@ describe Postmark::Bounce do
65
62
  subject { Postmark::Bounce.new(bounce_data_postmark) }
66
63
 
67
64
  it 'is not inactive' do
68
- should_not be_inactive
65
+ expect(subject).not_to be_inactive
69
66
  end
70
67
 
71
68
  it 'allows to activate the bounce' do
72
- subject.can_activate?.should be_true
69
+ expect(subject.can_activate?).to be true
73
70
  end
74
71
 
75
72
  it 'has an available dump' do
76
- subject.dump_available?.should be_true
73
+ expect(subject.dump_available?).to be true
77
74
  end
78
75
 
79
- its(:type) { should eq bounce_data[:type] }
80
- its(:message_id) { should eq bounce_data[:message_id] }
81
- its(:details) { should eq bounce_data[:details] }
82
- its(:email) { should eq bounce_data[:email] }
83
- its(:bounced_at) { should == Time.parse(bounce_data[:bounced_at]) }
84
- its(:id) { should eq bounce_data[:id] }
85
- its(:subject) { should eq bounce_data[:subject] }
86
-
76
+ its(:type) { is_expected.to eq bounce_data[:type] }
77
+ its(:message_id) { is_expected.to eq bounce_data[:message_id] }
78
+ its(:details) { is_expected.to eq bounce_data[:details] }
79
+ its(:email) { is_expected.to eq bounce_data[:email] }
80
+ its(:bounced_at) { is_expected.to eq Time.parse(bounce_data[:bounced_at]) }
81
+ its(:id) { is_expected.to eq bounce_data[:id] }
82
+ its(:subject) { is_expected.to eq bounce_data[:subject] }
87
83
  end
88
84
 
89
85
  describe "#dump" do
90
-
91
86
  let(:bounce_body) { double }
92
87
  let(:response) { {:body => bounce_body} }
93
88
  let(:api_client) { Postmark.api_client }
94
89
 
95
90
  it "calls #dump_bounce on shared api_client instance" do
96
- Postmark.api_client.should_receive(:dump_bounce).with(bounce.id) { response }
97
- bounce.dump.should == bounce_body
91
+ expect(Postmark.api_client).to receive(:dump_bounce).with(bounce.id) { response }
92
+ expect(bounce.dump).to eq bounce_body
98
93
  end
99
-
100
94
  end
101
95
 
102
96
  describe "#activate" do
103
-
104
97
  let(:api_client) { Postmark.api_client }
105
98
 
106
99
  it "calls #activate_bounce on shared api_client instance" do
107
- api_client.should_receive(:activate_bounce).with(bounce.id) { bounce_data }
108
- bounce.activate.should be_a Postmark::Bounce
100
+ expect(api_client).to receive(:activate_bounce).with(bounce.id) { bounce_data }
101
+ expect(bounce.activate).to be_a Postmark::Bounce
109
102
  end
110
-
111
103
  end
112
104
 
113
105
  describe ".find" do
114
106
  let(:api_client) { Postmark.api_client }
115
107
 
116
108
  it "calls #get_bounce on shared api_client instance" do
117
- api_client.should_receive(:get_bounce).with(42) { bounce_data }
118
- Postmark::Bounce.find(42).should be_a Postmark::Bounce
109
+ expect(api_client).to receive(:get_bounce).with(42) { bounce_data }
110
+ expect(Postmark::Bounce.find(42)).to be_a Postmark::Bounce
119
111
  end
120
112
  end
121
113
 
122
114
  describe ".all" do
123
-
124
115
  let(:response) { bounces_data }
125
116
  let(:api_client) { Postmark.api_client }
126
117
 
127
118
  it "calls #get_bounces on shared api_client instance" do
128
- api_client.should_receive(:get_bounces) { response }
129
- Postmark::Bounce.all.should have(3).bounces
130
- end
131
-
132
- end
133
-
134
- describe ".tags" do
135
-
136
- let(:api_client) { Postmark.api_client }
137
- let(:tags) { ["tag1", "tag2"] }
138
-
139
- it "calls #get_bounced_tags on shared api_client instance" do
140
- api_client.should_receive(:get_bounced_tags) { tags }
141
- Postmark::Bounce.tags.should == tags
119
+ expect(api_client).to receive(:get_bounces) { response }
120
+ expect(Postmark::Bounce.all.count).to eq(3)
142
121
  end
143
122
  end
144
-
145
123
  end
@@ -1,13 +1,10 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Postmark::Client do
4
-
5
4
  subject { Postmark::Client.new('abcd-efgh') }
6
5
 
7
6
  describe 'instance' do
8
-
9
7
  describe '#find_each' do
10
-
11
8
  let(:path) { 'resources' }
12
9
  let(:name) { 'Resources' }
13
10
  let(:response) {
@@ -44,9 +41,6 @@ describe Postmark::Client do
44
41
  to receive(:get).exactly(5).times.and_return(response)
45
42
  expect(subject.find_each(path, name, :count => 2).count).to eq(10)
46
43
  end
47
-
48
44
  end
49
-
50
45
  end
51
-
52
46
  end
@@ -0,0 +1,231 @@
1
+ require 'spec_helper'
2
+
3
+ describe(Postmark::Error) do
4
+ it {is_expected.to be_a(StandardError)}
5
+ end
6
+
7
+ describe(Postmark::HttpClientError) do
8
+ it {is_expected.to be_a(Postmark::Error)}
9
+ it {expect(subject.retry?).to be true}
10
+ end
11
+
12
+ describe(Postmark::HttpServerError) do
13
+ it {is_expected.to be_a(Postmark::Error)}
14
+
15
+ describe '.build' do
16
+ context 'picks an appropriate subclass for code' do
17
+ subject {Postmark::HttpServerError.build(code, Postmark::Json.encode({}))}
18
+
19
+ context '401' do
20
+ let(:code) {'401'}
21
+
22
+ it {is_expected.to be_a(Postmark::InvalidApiKeyError)}
23
+ its(:status_code) {is_expected.to eq 401}
24
+ end
25
+
26
+ context '422' do
27
+ let(:code) {'422'}
28
+
29
+ it {is_expected.to be_a(Postmark::ApiInputError)}
30
+ its(:status_code) {is_expected.to eq 422}
31
+ end
32
+
33
+ context '500' do
34
+ let(:code) {'500'}
35
+
36
+ it {is_expected.to be_a(Postmark::InternalServerError)}
37
+ its(:status_code) {is_expected.to eq 500}
38
+ end
39
+
40
+ context 'others' do
41
+ let(:code) {'999'}
42
+
43
+ it {is_expected.to be_a(Postmark::UnexpectedHttpResponseError)}
44
+ its(:status_code) {is_expected.to eq code.to_i}
45
+ end
46
+ end
47
+ end
48
+
49
+ describe '#retry?' do
50
+ it 'is true for 5XX status codes' do
51
+ (500...600).each do |code|
52
+ expect(Postmark::HttpServerError.new(code).retry?).to be true
53
+ end
54
+ end
55
+
56
+ it 'is false for other codes except 5XX' do
57
+ [200, 300, 400].each do |code|
58
+ expect(Postmark::HttpServerError.new(code).retry?).to be false
59
+ end
60
+ end
61
+ end
62
+
63
+ describe '#message ' do
64
+ it 'uses "Message" field on postmark response if available' do
65
+ data = {'Message' => 'Postmark error message'}
66
+ error = Postmark::HttpServerError.new(502, Postmark::Json.encode(data), data)
67
+ expect(error.message).to eq data['Message']
68
+ end
69
+
70
+ it 'falls back to a message generated from status code' do
71
+ error = Postmark::HttpServerError.new(502, '<html>')
72
+ expect(error.message).to match(/The Postmark API responded with HTTP status \d+/)
73
+ end
74
+ end
75
+ end
76
+
77
+ describe(Postmark::ApiInputError) do
78
+ describe '.build' do
79
+ context 'picks an appropriate subclass for error code' do
80
+ let(:response) {{'ErrorCode' => code}}
81
+
82
+ subject do
83
+ Postmark::ApiInputError.build(Postmark::Json.encode(response), response)
84
+ end
85
+
86
+ shared_examples_for 'api input error' do
87
+ its(:status_code) {is_expected.to eq 422}
88
+ it {expect(subject.retry?).to be false}
89
+ it {is_expected.to be_a(Postmark::ApiInputError)}
90
+ it {is_expected.to be_a(Postmark::HttpServerError)}
91
+ end
92
+
93
+ context '406' do
94
+ let(:code) {Postmark::ApiInputError::INACTIVE_RECIPIENT}
95
+
96
+ it {is_expected.to be_a(Postmark::InactiveRecipientError)}
97
+ it_behaves_like 'api input error'
98
+ end
99
+
100
+ context 'others' do
101
+ let(:code) {'9999'}
102
+
103
+ it_behaves_like 'api input error'
104
+ end
105
+ end
106
+ end
107
+ end
108
+
109
+ describe Postmark::InvalidTemplateError do
110
+ subject(:error) {Postmark::InvalidTemplateError.new(:foo => 'bar')}
111
+
112
+ it 'is created with a response' do
113
+ expect(error.message).to start_with('Failed to render the template.')
114
+ expect(error.postmark_response).to eq(:foo => 'bar')
115
+ end
116
+ end
117
+
118
+ describe(Postmark::TimeoutError) do
119
+ it {is_expected.to be_a(Postmark::Error)}
120
+ it {expect(subject.retry?).to be true}
121
+ end
122
+
123
+ describe(Postmark::UnknownMessageType) do
124
+ it 'exists for backward compatibility' do
125
+ is_expected.to be_a(Postmark::Error)
126
+ end
127
+ end
128
+
129
+ describe(Postmark::InvalidApiKeyError) do
130
+ it {is_expected.to be_a(Postmark::Error)}
131
+ end
132
+
133
+ describe(Postmark::InternalServerError) do
134
+ it {is_expected.to be_a(Postmark::Error)}
135
+ end
136
+
137
+ describe(Postmark::UnexpectedHttpResponseError) do
138
+ it {is_expected.to be_a(Postmark::Error)}
139
+ end
140
+
141
+ describe(Postmark::MailAdapterError) do
142
+ it {is_expected.to be_a(Postmark::Error)}
143
+ end
144
+
145
+ describe(Postmark::InactiveRecipientError) do
146
+ describe '.parse_recipients' do
147
+ let(:recipients) do
148
+ %w(nothing@wildbit.com noth.ing+2@wildbit.com noth.ing+2-1@wildbit.com)
149
+ end
150
+
151
+ subject {Postmark::InactiveRecipientError.parse_recipients(message)}
152
+
153
+ context '1/1 inactive' do
154
+ let(:message) do
155
+ 'You tried to send to a recipient that has been marked as ' \
156
+ "inactive.\nFound inactive addresses: #{recipients[0]}.\n" \
157
+ 'Inactive recipients are ones that have generated a hard ' \
158
+ 'bounce or a spam complaint.'
159
+ end
160
+
161
+ it {is_expected.to eq(recipients.take(1))}
162
+ end
163
+
164
+ context 'i/n inactive, n > 1, i < n' do
165
+ let(:message) do
166
+ 'Message OK, but will not deliver to these inactive addresses: ' \
167
+ "#{recipients[0...2].join(', ')}. Inactive recipients are ones that " \
168
+ 'have generated a hard bounce or a spam complaint.'
169
+ end
170
+
171
+ it {is_expected.to eq(recipients.take(2))}
172
+ end
173
+
174
+ context 'n/n inactive, n > 1' do
175
+ let(:message) do
176
+ 'You tried to send to recipients that have all been marked as ' \
177
+ "inactive.\nFound inactive addresses: #{recipients.join(', ')}.\n" \
178
+ 'Inactive recipients are ones that have generated a hard bounce or a spam complaint.'
179
+ end
180
+
181
+ it {is_expected.to eq(recipients)}
182
+ end
183
+
184
+ context 'unknown error format' do
185
+ let(:message) {recipients.join(', ')}
186
+
187
+ it {is_expected.to eq([])}
188
+ end
189
+ end
190
+
191
+ describe '.new' do
192
+ let(:address) {'user@example.org'}
193
+ let(:response) {{'Message' => message}}
194
+
195
+ subject do
196
+ Postmark::InactiveRecipientError.new(
197
+ Postmark::ApiInputError::INACTIVE_RECIPIENT,
198
+ Postmark::Json.encode(response),
199
+ response)
200
+ end
201
+
202
+ let(:message) do
203
+ 'You tried to send to a recipient that has been marked as ' \
204
+ "inactive.\nFound inactive addresses: #{address}.\n" \
205
+ 'Inactive recipients are ones that have generated a hard ' \
206
+ 'bounce or a spam complaint.'
207
+ end
208
+
209
+ it 'parses recipients from json payload' do
210
+ expect(subject.recipients).to eq([address])
211
+ end
212
+ end
213
+ end
214
+
215
+ describe(Postmark::DeliveryError) do
216
+ it 'is an alias to Error for backwards compatibility' do
217
+ expect(subject.class).to eq(Postmark::Error)
218
+ end
219
+ end
220
+
221
+ describe(Postmark::InvalidMessageError) do
222
+ it 'is an alias to Error for backwards compatibility' do
223
+ expect(subject.class).to eq(Postmark::ApiInputError)
224
+ end
225
+ end
226
+
227
+ describe(Postmark::UnknownError) do
228
+ it 'is an alias for backwards compatibility' do
229
+ expect(subject.class).to eq(Postmark::UnexpectedHttpResponseError)
230
+ end
231
+ end
@@ -10,42 +10,74 @@ describe Mail::Postmark do
10
10
  end
11
11
  end
12
12
 
13
- it "can be set as delivery_method" do
13
+ before do
14
14
  message.delivery_method Mail::Postmark
15
15
  end
16
16
 
17
- it "wraps Postmark.send_through_postmark" do
18
- Postmark::ApiClient.any_instance.should_receive(:deliver_message).with(message)
19
- message.delivery_method Mail::Postmark
20
- message.deliver
21
- end
17
+ subject(:handler) { message.delivery_method }
22
18
 
23
- it "returns self by default" do
24
- Postmark::ApiClient.any_instance.should_receive(:deliver_message).with(message)
19
+ it "can be set as delivery_method" do
25
20
  message.delivery_method Mail::Postmark
26
- message.deliver.should eq message
27
- end
28
21
 
29
- it "returns the actual response if :return_response setting is present" do
30
- Postmark::ApiClient.any_instance.should_receive(:deliver_message).with(message)
31
- message.delivery_method Mail::Postmark, :return_response => true
32
- message.deliver.should eq message
22
+ is_expected.to be_a(Mail::Postmark)
33
23
  end
34
24
 
35
- it "allows setting the api token" do
36
- message.delivery_method Mail::Postmark, :api_token => 'api-token'
37
- message.delivery_method.settings[:api_token].should == 'api-token'
38
- end
25
+ describe '#deliver!' do
26
+ it "returns self by default" do
27
+ expect_any_instance_of(Postmark::ApiClient).to receive(:deliver_message).with(message)
28
+ expect(message.deliver).to eq message
29
+ end
30
+
31
+ it "returns the actual response if :return_response setting is present" do
32
+ expect_any_instance_of(Postmark::ApiClient).to receive(:deliver_message).with(message)
33
+ message.delivery_method Mail::Postmark, :return_response => true
34
+ expect(message.deliver).to eq message
35
+ end
36
+
37
+ it "allows setting the api token" do
38
+ message.delivery_method Mail::Postmark, :api_token => 'api-token'
39
+ expect(message.delivery_method.settings[:api_token]).to eq 'api-token'
40
+ end
41
+
42
+ it 'uses provided API token' do
43
+ message.delivery_method Mail::Postmark, :api_token => 'api-token'
44
+ expect(Postmark::ApiClient).to receive(:new).with('api-token', {}).and_return(double(:deliver_message => true))
45
+ message.deliver
46
+ end
47
+
48
+ it 'uses API token provided as legacy api_key' do
49
+ message.delivery_method Mail::Postmark, :api_key => 'api-token'
50
+ expect(Postmark::ApiClient).to receive(:new).with('api-token', {}).and_return(double(:deliver_message => true))
51
+ message.deliver
52
+ end
39
53
 
40
- it 'uses provided API token' do
41
- message.delivery_method Mail::Postmark, :api_token => 'api-token'
42
- Postmark::ApiClient.should_receive(:new).with('api-token', {}).and_return(double(:deliver_message => true))
43
- message.deliver
54
+ context 'when sending a pre-rendered message' do
55
+ it "uses ApiClient#deliver_message to send the message" do
56
+ expect_any_instance_of(Postmark::ApiClient).to receive(:deliver_message).with(message)
57
+ message.deliver
58
+ end
59
+ end
60
+
61
+ context 'when sending a Postmark template' do
62
+ let(:message) do
63
+ Mail.new do
64
+ from "sheldon@bigbangtheory.com"
65
+ to "lenard@bigbangtheory.com"
66
+ template_alias "hello"
67
+ template_model :name => "Sheldon"
68
+ end
69
+ end
70
+
71
+ it 'uses ApiClient#deliver_message_with_template to send the message' do
72
+ expect_any_instance_of(Postmark::ApiClient).to receive(:deliver_message_with_template).with(message)
73
+ message.deliver
74
+ end
75
+ end
44
76
  end
45
77
 
46
- it 'uses API token provided as legacy api_key' do
47
- message.delivery_method Mail::Postmark, :api_key => 'api-token'
48
- Postmark::ApiClient.should_receive(:new).with('api-token', {}).and_return(double(:deliver_message => true))
49
- message.deliver
78
+ describe '#api_client' do
79
+ subject { handler.api_client }
80
+
81
+ it { is_expected.to be_a(Postmark::ApiClient) }
50
82
  end
51
- end
83
+ end