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
@@ -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