postmark 1.10.0 → 1.11.0
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/CHANGELOG.rdoc +7 -0
- data/CONTRIBUTING.md +18 -0
- data/Gemfile +6 -5
- data/LICENSE +1 -1
- data/README.md +62 -9
- data/VERSION +1 -1
- data/gemfiles/Gemfile.legacy +4 -3
- data/lib/postmark.rb +1 -18
- data/lib/postmark/api_client.rb +32 -1
- data/lib/postmark/client.rb +8 -4
- data/lib/postmark/error.rb +117 -0
- data/lib/postmark/http_client.rb +7 -25
- data/lib/postmark/mail_message_converter.rb +1 -1
- data/lib/postmark/version.rb +1 -1
- data/spec/integration/account_api_client_spec.rb +2 -2
- data/spec/integration/api_client_messages_spec.rb +3 -3
- data/spec/spec_helper.rb +4 -1
- data/spec/support/custom_matchers.rb +30 -0
- data/spec/unit/postmark/account_api_client_spec.rb +22 -22
- data/spec/unit/postmark/api_client_spec.rb +164 -54
- data/spec/unit/postmark/bounce_spec.rb +10 -10
- data/spec/unit/postmark/error_spec.rb +218 -0
- data/spec/unit/postmark/handlers/mail_spec.rb +5 -5
- data/spec/unit/postmark/http_client_spec.rb +4 -5
- data/spec/unit/postmark/mail_message_converter_spec.rb +6 -0
- data/spec/unit/postmark/message_extensions/mail_spec.rb +6 -6
- data/spec/unit/postmark_spec.rb +5 -5
- metadata +33 -4
@@ -43,11 +43,11 @@ describe Postmark::Bounce do
|
|
43
43
|
end
|
44
44
|
|
45
45
|
it 'allows to activate the bounce' do
|
46
|
-
subject.can_activate?.should
|
46
|
+
subject.can_activate?.should be true
|
47
47
|
end
|
48
48
|
|
49
49
|
it 'has an available dump' do
|
50
|
-
subject.dump_available?.should
|
50
|
+
subject.dump_available?.should be true
|
51
51
|
end
|
52
52
|
|
53
53
|
its(:type) { should eq bounce_data[:type] }
|
@@ -69,11 +69,11 @@ describe Postmark::Bounce do
|
|
69
69
|
end
|
70
70
|
|
71
71
|
it 'allows to activate the bounce' do
|
72
|
-
subject.can_activate?.should
|
72
|
+
subject.can_activate?.should be true
|
73
73
|
end
|
74
74
|
|
75
75
|
it 'has an available dump' do
|
76
|
-
subject.dump_available?.should
|
76
|
+
subject.dump_available?.should be true
|
77
77
|
end
|
78
78
|
|
79
79
|
its(:type) { should eq bounce_data[:type] }
|
@@ -93,7 +93,7 @@ describe Postmark::Bounce do
|
|
93
93
|
let(:api_client) { Postmark.api_client }
|
94
94
|
|
95
95
|
it "calls #dump_bounce on shared api_client instance" do
|
96
|
-
Postmark.api_client.
|
96
|
+
expect(Postmark.api_client).to receive(:dump_bounce).with(bounce.id) { response }
|
97
97
|
bounce.dump.should == bounce_body
|
98
98
|
end
|
99
99
|
|
@@ -104,7 +104,7 @@ describe Postmark::Bounce do
|
|
104
104
|
let(:api_client) { Postmark.api_client }
|
105
105
|
|
106
106
|
it "calls #activate_bounce on shared api_client instance" do
|
107
|
-
api_client.
|
107
|
+
expect(api_client).to receive(:activate_bounce).with(bounce.id) { bounce_data }
|
108
108
|
bounce.activate.should be_a Postmark::Bounce
|
109
109
|
end
|
110
110
|
|
@@ -114,7 +114,7 @@ describe Postmark::Bounce do
|
|
114
114
|
let(:api_client) { Postmark.api_client }
|
115
115
|
|
116
116
|
it "calls #get_bounce on shared api_client instance" do
|
117
|
-
api_client.
|
117
|
+
expect(api_client).to receive(:get_bounce).with(42) { bounce_data }
|
118
118
|
Postmark::Bounce.find(42).should be_a Postmark::Bounce
|
119
119
|
end
|
120
120
|
end
|
@@ -125,8 +125,8 @@ describe Postmark::Bounce do
|
|
125
125
|
let(:api_client) { Postmark.api_client }
|
126
126
|
|
127
127
|
it "calls #get_bounces on shared api_client instance" do
|
128
|
-
api_client.
|
129
|
-
Postmark::Bounce.all.should
|
128
|
+
expect(api_client).to receive(:get_bounces) { response }
|
129
|
+
Postmark::Bounce.all.count.should eq(3)
|
130
130
|
end
|
131
131
|
|
132
132
|
end
|
@@ -137,7 +137,7 @@ describe Postmark::Bounce do
|
|
137
137
|
let(:tags) { ["tag1", "tag2"] }
|
138
138
|
|
139
139
|
it "calls #get_bounced_tags on shared api_client instance" do
|
140
|
-
api_client.
|
140
|
+
expect(api_client).to receive(:get_bounced_tags) { tags }
|
141
141
|
Postmark::Bounce.tags.should == tags
|
142
142
|
end
|
143
143
|
end
|
@@ -0,0 +1,218 @@
|
|
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
|
+
specify { 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
|
+
specify { 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::TimeoutError) do
|
110
|
+
it { is_expected.to be_a(Postmark::Error) }
|
111
|
+
specify { expect(subject.retry?).to be true }
|
112
|
+
end
|
113
|
+
|
114
|
+
describe(Postmark::UnknownMessageType) do
|
115
|
+
it 'exists for backward compatibility' do
|
116
|
+
is_expected.to be_a(Postmark::Error)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
describe(Postmark::InvalidApiKeyError) do
|
121
|
+
it { is_expected.to be_a(Postmark::Error) }
|
122
|
+
end
|
123
|
+
|
124
|
+
describe(Postmark::InternalServerError) do
|
125
|
+
it { is_expected.to be_a(Postmark::Error) }
|
126
|
+
end
|
127
|
+
|
128
|
+
describe(Postmark::UnexpectedHttpResponseError) do
|
129
|
+
it { is_expected.to be_a(Postmark::Error) }
|
130
|
+
end
|
131
|
+
|
132
|
+
describe(Postmark::InactiveRecipientError) do
|
133
|
+
describe '.parse_recipients' do
|
134
|
+
let(:recipients) do
|
135
|
+
%w(nothing@wildbit.com noth.ing+2@wildbit.com noth.ing+2-1@wildbit.com)
|
136
|
+
end
|
137
|
+
|
138
|
+
subject { Postmark::InactiveRecipientError.parse_recipients(message) }
|
139
|
+
|
140
|
+
context '1/1 inactive' do
|
141
|
+
let(:message) do
|
142
|
+
'You tried to send to a recipient that has been marked as ' \
|
143
|
+
"inactive.\nFound inactive addresses: #{recipients[0]}.\n" \
|
144
|
+
'Inactive recipients are ones that have generated a hard ' \
|
145
|
+
'bounce or a spam complaint.'
|
146
|
+
end
|
147
|
+
|
148
|
+
it { is_expected.to eq(recipients.take(1)) }
|
149
|
+
end
|
150
|
+
|
151
|
+
context 'i/n inactive, n > 1, i < n' do
|
152
|
+
let(:message) do
|
153
|
+
'Message OK, but will not deliver to these inactive addresses: ' \
|
154
|
+
"#{recipients[0...2].join(', ')}. Inactive recipients are ones that " \
|
155
|
+
'have generated a hard bounce or a spam complaint.'
|
156
|
+
end
|
157
|
+
|
158
|
+
it { is_expected.to eq(recipients.take(2)) }
|
159
|
+
end
|
160
|
+
|
161
|
+
context 'n/n inactive, n > 1' do
|
162
|
+
let(:message) do
|
163
|
+
'You tried to send to recipients that have all been marked as ' \
|
164
|
+
"inactive.\nFound inactive addresses: #{recipients.join(', ')}.\n" \
|
165
|
+
'Inactive recipients are ones that have generated a hard bounce or a spam complaint.'
|
166
|
+
end
|
167
|
+
|
168
|
+
it { is_expected.to eq(recipients) }
|
169
|
+
end
|
170
|
+
|
171
|
+
context 'unknown error format' do
|
172
|
+
let(:message) { recipients.join(', ') }
|
173
|
+
|
174
|
+
it { is_expected.to eq([]) }
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
describe '.new' do
|
179
|
+
let(:address) { 'user@example.org' }
|
180
|
+
let(:response) { { 'Message' => message } }
|
181
|
+
|
182
|
+
subject do
|
183
|
+
Postmark::InactiveRecipientError.new(
|
184
|
+
Postmark::ApiInputError::INACTIVE_RECIPIENT,
|
185
|
+
Postmark::Json.encode(response),
|
186
|
+
response)
|
187
|
+
end
|
188
|
+
|
189
|
+
let(:message) do
|
190
|
+
'You tried to send to a recipient that has been marked as ' \
|
191
|
+
"inactive.\nFound inactive addresses: #{address}.\n" \
|
192
|
+
'Inactive recipients are ones that have generated a hard ' \
|
193
|
+
'bounce or a spam complaint.'
|
194
|
+
end
|
195
|
+
|
196
|
+
it 'parses recipients from json payload' do
|
197
|
+
expect(subject.recipients).to eq([address])
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
describe(Postmark::DeliveryError) do
|
203
|
+
it 'is an alias to Error for backwards compatibility' do
|
204
|
+
expect(subject.class).to eq(Postmark::Error)
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
describe(Postmark::InvalidMessageError) do
|
209
|
+
it 'is an alias to Error for backwards compatibility' do
|
210
|
+
expect(subject.class).to eq(Postmark::ApiInputError)
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
describe(Postmark::UnknownError) do
|
215
|
+
it 'is an alias for backwards compatibility' do
|
216
|
+
expect(subject.class).to eq(Postmark::UnexpectedHttpResponseError)
|
217
|
+
end
|
218
|
+
end
|
@@ -15,19 +15,19 @@ describe Mail::Postmark do
|
|
15
15
|
end
|
16
16
|
|
17
17
|
it "wraps Postmark.send_through_postmark" do
|
18
|
-
Postmark::ApiClient.
|
18
|
+
allow_any_instance_of(Postmark::ApiClient).to receive(:deliver_message).with(message)
|
19
19
|
message.delivery_method Mail::Postmark
|
20
20
|
message.deliver
|
21
21
|
end
|
22
22
|
|
23
23
|
it "returns self by default" do
|
24
|
-
Postmark::ApiClient.
|
24
|
+
allow_any_instance_of(Postmark::ApiClient).to receive(:deliver_message).with(message)
|
25
25
|
message.delivery_method Mail::Postmark
|
26
26
|
message.deliver.should eq message
|
27
27
|
end
|
28
28
|
|
29
29
|
it "returns the actual response if :return_response setting is present" do
|
30
|
-
Postmark::ApiClient.
|
30
|
+
allow_any_instance_of(Postmark::ApiClient).to receive(:deliver_message).with(message)
|
31
31
|
message.delivery_method Mail::Postmark, :return_response => true
|
32
32
|
message.deliver.should eq message
|
33
33
|
end
|
@@ -39,13 +39,13 @@ describe Mail::Postmark do
|
|
39
39
|
|
40
40
|
it 'uses provided API token' do
|
41
41
|
message.delivery_method Mail::Postmark, :api_token => 'api-token'
|
42
|
-
Postmark::ApiClient.
|
42
|
+
expect(Postmark::ApiClient).to receive(:new).with('api-token', {}).and_return(double(:deliver_message => true))
|
43
43
|
message.deliver
|
44
44
|
end
|
45
45
|
|
46
46
|
it 'uses API token provided as legacy api_key' do
|
47
47
|
message.delivery_method Mail::Postmark, :api_key => 'api-token'
|
48
|
-
Postmark::ApiClient.
|
48
|
+
expect(Postmark::ApiClient).to receive(:new).with('api-token', {}).and_return(double(:deliver_message => true))
|
49
49
|
message.deliver
|
50
50
|
end
|
51
51
|
end
|
@@ -36,7 +36,7 @@ describe Postmark::HttpClient do
|
|
36
36
|
its(:api_key) { should eq api_token }
|
37
37
|
its(:host) { should eq 'api.postmarkapp.com' }
|
38
38
|
its(:port) { should eq 443 }
|
39
|
-
its(:secure) { should
|
39
|
+
its(:secure) { should be true }
|
40
40
|
its(:path_prefix) { should eq '/' }
|
41
41
|
its(:http_read_timeout) { should eq 15 }
|
42
42
|
its(:http_open_timeout) { should eq 5 }
|
@@ -128,8 +128,7 @@ describe Postmark::HttpClient do
|
|
128
128
|
end
|
129
129
|
|
130
130
|
it "raises a custom error when the request times out" do
|
131
|
-
subject.http.
|
132
|
-
and_raise(Timeout::Error)
|
131
|
+
expect(subject.http).to receive(:post).at_least(:once).and_raise(Timeout::Error)
|
133
132
|
expect { subject.post(target_path) }.to raise_error Postmark::TimeoutError
|
134
133
|
end
|
135
134
|
|
@@ -170,7 +169,7 @@ describe Postmark::HttpClient do
|
|
170
169
|
end
|
171
170
|
|
172
171
|
it "raises a custom error when the request times out" do
|
173
|
-
subject.http.
|
172
|
+
expect(subject.http).to receive(:get).at_least(:once).and_raise(Timeout::Error)
|
174
173
|
expect { subject.get(target_path) }.to raise_error Postmark::TimeoutError
|
175
174
|
end
|
176
175
|
|
@@ -211,7 +210,7 @@ describe Postmark::HttpClient do
|
|
211
210
|
end
|
212
211
|
|
213
212
|
it "raises a custom error when the request times out" do
|
214
|
-
subject.http.
|
213
|
+
expect(subject.http).to receive(:put).at_least(:once).and_raise(Timeout::Error)
|
215
214
|
expect { subject.put(target_path) }.to raise_error Postmark::TimeoutError
|
216
215
|
end
|
217
216
|
|
@@ -311,6 +311,12 @@ describe Postmark::MailMessageConverter do
|
|
311
311
|
"TrackLinks" => 'None'})
|
312
312
|
end
|
313
313
|
|
314
|
+
it 'converts link tracking options when set via header' do
|
315
|
+
msg = mail_html_message
|
316
|
+
msg[:track_links] = :html_and_text
|
317
|
+
expect(subject.new(msg).run).to include('TrackLinks' => 'HtmlAndText')
|
318
|
+
end
|
319
|
+
|
314
320
|
end
|
315
321
|
|
316
322
|
it 'correctly decodes unicode in messages transfered as quoted-printable' do
|
@@ -2,7 +2,7 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
describe Mail::Message do
|
4
4
|
before do
|
5
|
-
Kernel.
|
5
|
+
allow(Kernel).to receive(:warn)
|
6
6
|
end
|
7
7
|
|
8
8
|
let(:mail_message) do
|
@@ -147,7 +147,7 @@ describe Mail::Message do
|
|
147
147
|
end
|
148
148
|
|
149
149
|
it "is deprecated" do
|
150
|
-
Kernel.
|
150
|
+
expect(Kernel).to receive(:warn).with(/deprecated/)
|
151
151
|
mail_message.postmark_attachments = attached_hash
|
152
152
|
end
|
153
153
|
end
|
@@ -161,12 +161,12 @@ describe Mail::Message do
|
|
161
161
|
'Content' => ''} }
|
162
162
|
|
163
163
|
before do
|
164
|
-
attached_file.
|
165
|
-
attached_file.
|
164
|
+
allow(attached_file).to receive(:is_a?) { |arg| arg == File ? true : false }
|
165
|
+
allow(attached_file).to receive(:path) { '/tmp/file.jpeg' }
|
166
166
|
end
|
167
167
|
|
168
168
|
it "supports multiple attachment formats" do
|
169
|
-
IO.
|
169
|
+
expect(IO).to receive(:read).with("/tmp/file.jpeg").and_return("")
|
170
170
|
|
171
171
|
mail_message.postmark_attachments = [attached_hash, attached_file]
|
172
172
|
attachments = mail_message.export_attachments
|
@@ -177,7 +177,7 @@ describe Mail::Message do
|
|
177
177
|
|
178
178
|
it "is deprecated" do
|
179
179
|
mail_message.postmark_attachments = attached_hash
|
180
|
-
Kernel.
|
180
|
+
expect(Kernel).to receive(:warn).with(/deprecated/)
|
181
181
|
mail_message.postmark_attachments
|
182
182
|
end
|
183
183
|
end
|
data/spec/unit/postmark_spec.rb
CHANGED
@@ -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.
|
102
|
+
allow(Postmark::ApiClient).to receive(:new).
|
103
103
|
with(api_token,
|
104
104
|
:secure => secure,
|
105
105
|
:proxy_host => proxy_host,
|
@@ -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.
|
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.
|
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.
|
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.
|
162
|
+
allow(api_client).to receive(:delivery_stats)
|
163
163
|
subject.delivery_stats
|
164
164
|
end
|
165
165
|
end
|