postmark 0.9.19 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. data/.travis.yml +8 -0
  2. data/CHANGELOG.rdoc +20 -0
  3. data/Gemfile +6 -0
  4. data/README.md +351 -91
  5. data/VERSION +1 -1
  6. data/lib/postmark.rb +40 -132
  7. data/lib/postmark/api_client.rb +162 -0
  8. data/lib/postmark/bounce.rb +20 -17
  9. data/lib/postmark/handlers/mail.rb +10 -3
  10. data/lib/postmark/helpers/hash_helper.rb +35 -0
  11. data/lib/postmark/helpers/message_helper.rb +62 -0
  12. data/lib/postmark/http_client.rb +44 -28
  13. data/lib/postmark/inbound.rb +21 -0
  14. data/lib/postmark/inflector.rb +28 -0
  15. data/lib/postmark/message_extensions/mail.rb +50 -5
  16. data/lib/postmark/message_extensions/shared.rb +23 -28
  17. data/lib/postmark/version.rb +1 -1
  18. data/postmark.gemspec +4 -7
  19. data/spec/data/empty.gif +0 -0
  20. data/spec/integration/api_client_hashes_spec.rb +101 -0
  21. data/spec/integration/api_client_messages_spec.rb +127 -0
  22. data/spec/integration/mail_delivery_method_spec.rb +80 -0
  23. data/spec/spec_helper.rb +15 -5
  24. data/spec/support/helpers.rb +11 -0
  25. data/spec/{shared_examples.rb → support/shared_examples.rb} +0 -0
  26. data/spec/unit/postmark/api_client_spec.rb +246 -0
  27. data/spec/unit/postmark/bounce_spec.rb +142 -0
  28. data/spec/unit/postmark/handlers/mail_spec.rb +39 -0
  29. data/spec/unit/postmark/helpers/hash_helper_spec.rb +34 -0
  30. data/spec/unit/postmark/helpers/message_helper_spec.rb +115 -0
  31. data/spec/unit/postmark/http_client_spec.rb +204 -0
  32. data/spec/unit/postmark/inbound_spec.rb +88 -0
  33. data/spec/unit/postmark/inflector_spec.rb +35 -0
  34. data/spec/unit/postmark/json_spec.rb +37 -0
  35. data/spec/unit/postmark/message_extensions/mail_spec.rb +205 -0
  36. data/spec/unit/postmark_spec.rb +164 -0
  37. metadata +45 -93
  38. data/lib/postmark/attachments_fix_for_mail.rb +0 -48
  39. data/lib/postmark/message_extensions/tmail.rb +0 -115
  40. data/spec/bounce_spec.rb +0 -53
  41. data/spec/postmark_spec.rb +0 -253
@@ -0,0 +1,142 @@
1
+ require 'spec_helper'
2
+
3
+ describe Postmark::Bounce do
4
+ let(:bounce_data) { {:type => "HardBounce",
5
+ :message_id => "d12c2f1c-60f3-4258-b163-d17052546ae4",
6
+ :type_code => 1,
7
+ :details => "test bounce",
8
+ :email => "jim@test.com",
9
+ :bounced_at => "2013-04-22 18:06:48 +0800",
10
+ :dump_available => true,
11
+ :inactive => false,
12
+ :can_activate => true,
13
+ :id => 42,
14
+ :subject => "Hello from our app!"} }
15
+ let(:bounce_data_postmark) { Postmark::HashHelper.to_postmark(bounce_data) }
16
+ let(:bounces_data) { [bounce_data, bounce_data, bounce_data] }
17
+
18
+ let(:bounce) { Postmark::Bounce.new(bounce_data) }
19
+
20
+ subject { bounce }
21
+
22
+ context "attr readers" do
23
+
24
+ it { should respond_to(:email) }
25
+ it { should respond_to(:bounced_at) }
26
+ it { should respond_to(:type) }
27
+ it { should respond_to(:details) }
28
+ it { should respond_to(:name) }
29
+ it { should respond_to(:id) }
30
+ it { should respond_to(:server_id) }
31
+ it { should respond_to(:tag) }
32
+ it { should respond_to(:message_id) }
33
+ it { should respond_to(:subject) }
34
+
35
+ end
36
+
37
+ context "given a bounce created from bounce_data" do
38
+
39
+ it 'is not inactive' do
40
+ should_not be_inactive
41
+ end
42
+
43
+ it 'allows to activate the bounce' do
44
+ subject.can_activate?.should be_true
45
+ end
46
+
47
+ it 'has an available dump' do
48
+ subject.dump_available?.should be_true
49
+ end
50
+
51
+ its(:type) { should eq bounce_data[:type] }
52
+ its(:message_id) { should eq bounce_data[:message_id] }
53
+ its(:details) { should eq bounce_data[:details] }
54
+ its(:email) { should eq bounce_data[:email] }
55
+ its(:bounced_at) { should == Time.parse(bounce_data[:bounced_at]) }
56
+ its(:id) { should eq bounce_data[:id] }
57
+ its(:subject) { should eq bounce_data[:subject] }
58
+
59
+ end
60
+
61
+ context "given a bounce created from bounce_data_postmark" do
62
+ subject { Postmark::Bounce.new(bounce_data_postmark) }
63
+
64
+ it 'is not inactive' do
65
+ should_not be_inactive
66
+ end
67
+
68
+ it 'allows to activate the bounce' do
69
+ subject.can_activate?.should be_true
70
+ end
71
+
72
+ it 'has an available dump' do
73
+ subject.dump_available?.should be_true
74
+ end
75
+
76
+ its(:type) { should eq bounce_data[:type] }
77
+ its(:message_id) { should eq bounce_data[:message_id] }
78
+ its(:details) { should eq bounce_data[:details] }
79
+ its(:email) { should eq bounce_data[:email] }
80
+ its(:bounced_at) { should == Time.parse(bounce_data[:bounced_at]) }
81
+ its(:id) { should eq bounce_data[:id] }
82
+ its(:subject) { should eq bounce_data[:subject] }
83
+
84
+ end
85
+
86
+ describe "#dump" do
87
+
88
+ let(:bounce_body) { mock }
89
+ let(:response) { {:body => bounce_body} }
90
+ let(:api_client) { Postmark.api_client }
91
+
92
+ it "calls #dump_bounce on shared api_client instance" do
93
+ Postmark.api_client.should_receive(:dump_bounce).with(bounce.id) { response }
94
+ bounce.dump.should == bounce_body
95
+ end
96
+
97
+ end
98
+
99
+ describe "#activate" do
100
+
101
+ let(:api_client) { Postmark.api_client }
102
+
103
+ it "calls #activate_bounce on shared api_client instance" do
104
+ api_client.should_receive(:activate_bounce).with(bounce.id) { bounce_data }
105
+ bounce.activate.should be_a Postmark::Bounce
106
+ end
107
+
108
+ end
109
+
110
+ describe ".find" do
111
+ let(:api_client) { Postmark.api_client }
112
+
113
+ it "calls #get_bounce on shared api_client instance" do
114
+ api_client.should_receive(:get_bounce).with(42) { bounce_data }
115
+ Postmark::Bounce.find(42).should be_a Postmark::Bounce
116
+ end
117
+ end
118
+
119
+ describe ".all" do
120
+
121
+ let(:response) { bounces_data }
122
+ let(:api_client) { Postmark.api_client }
123
+
124
+ it "calls #get_bounces on shared api_client instance" do
125
+ api_client.should_receive(:get_bounces) { response }
126
+ Postmark::Bounce.all.should have(3).bounces
127
+ end
128
+
129
+ end
130
+
131
+ describe ".tags" do
132
+
133
+ let(:api_client) { Postmark.api_client }
134
+ let(:tags) { ["tag1", "tag2"] }
135
+
136
+ it "calls #get_bounced_tags on shared api_client instance" do
137
+ api_client.should_receive(:get_bounced_tags) { tags }
138
+ Postmark::Bounce.tags.should == tags
139
+ end
140
+ end
141
+
142
+ end
@@ -0,0 +1,39 @@
1
+ require 'spec_helper'
2
+
3
+ describe Mail::Postmark do
4
+ let(:message) do
5
+ Mail.new do
6
+ from "sheldon@bigbangtheory.com"
7
+ to "lenard@bigbangtheory.com"
8
+ subject "Hello!"
9
+ body "Hello Sheldon!"
10
+ end
11
+ end
12
+
13
+ it "can be set as delivery_method" do
14
+ message.delivery_method Mail::Postmark
15
+ end
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
22
+
23
+ it "returns self by default" do
24
+ Postmark::ApiClient.any_instance.should_receive(:deliver_message).with(message)
25
+ message.delivery_method Mail::Postmark
26
+ message.deliver.should eq message
27
+ end
28
+
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
33
+ end
34
+
35
+ it "allows to set the api key" do
36
+ message.delivery_method Mail::Postmark, {:api_key => 'api-key'}
37
+ message.delivery_method.settings[:api_key].should == 'api-key'
38
+ end
39
+ end
@@ -0,0 +1,34 @@
1
+ require 'spec_helper'
2
+
3
+ describe Postmark::HashHelper do
4
+ describe ".to_postmark" do
5
+ let(:source) { {:from => "support@postmarkapp.com", :reply_to => "contact@wildbit.com"} }
6
+ let(:target) { {"From" => "support@postmarkapp.com", "ReplyTo" => "contact@wildbit.com"} }
7
+
8
+ it 'converts Hash keys to Postmark format' do
9
+ subject.to_postmark(source).should == target
10
+ end
11
+
12
+ it 'acts idempotentely' do
13
+ subject.to_postmark(target).should == target
14
+ end
15
+ end
16
+
17
+ describe ".to_ruby" do
18
+ let(:source) { {"From" => "support@postmarkapp.com", "ReplyTo" => "contact@wildbit.com"} }
19
+ let(:target) { {:from => "support@postmarkapp.com", :reply_to => "contact@wildbit.com"} }
20
+
21
+ it 'converts Hash keys to Ruby format' do
22
+ subject.to_ruby(source).should == target
23
+ end
24
+
25
+ it 'has compatible mode' do
26
+ subject.to_ruby(source, true).should == target.merge(source)
27
+ end
28
+
29
+ it 'acts idempotentely' do
30
+ subject.to_ruby(target).should == target
31
+ end
32
+ end
33
+
34
+ end
@@ -0,0 +1,115 @@
1
+ require 'spec_helper'
2
+
3
+ describe Postmark::MessageHelper do
4
+ let(:attachments) {
5
+ [
6
+ File.open(empty_gif_path),
7
+ {:name => "img2.gif",
8
+ :content => Postmark::MessageHelper.encode_in_base64(File.read(empty_gif_path)),
9
+ :content_type => "application/octet-stream"}
10
+ ]
11
+ }
12
+
13
+ let(:postmark_attachments) {
14
+ content = Postmark::MessageHelper.encode_in_base64(File.read(empty_gif_path))
15
+ [
16
+ {"Name" => "empty.gif",
17
+ "Content" => content,
18
+ "ContentType" => "application/octet-stream"},
19
+ {"Name" => "img2.gif",
20
+ "Content" => content,
21
+ "ContentType" => "application/octet-stream"}
22
+ ]
23
+ }
24
+
25
+ let(:headers) {
26
+ [{:name => "CUSTOM-HEADER", :value => "value"}]
27
+ }
28
+
29
+ let(:postmark_headers) {
30
+ [{"Name" => "CUSTOM-HEADER", "Value" => "value"}]
31
+ }
32
+
33
+
34
+ describe ".to_postmark" do
35
+ let(:message) {
36
+ {
37
+ :from => "sender@example.com",
38
+ :to => "receiver@example.com",
39
+ :cc => "copied@example.com",
40
+ :bcc => "blank-copied@example.com",
41
+ :subject => "Test",
42
+ :tag => "Invitation",
43
+ :html_body => "<b>Hello</b>",
44
+ :text_body => "Hello",
45
+ :reply_to => "reply@example.com"
46
+ }
47
+ }
48
+
49
+ let(:postmark_message) {
50
+ {
51
+ "From" => "sender@example.com",
52
+ "To" => "receiver@example.com",
53
+ "Cc" => "copied@example.com",
54
+ "Bcc"=> "blank-copied@example.com",
55
+ "Subject" => "Test",
56
+ "Tag" => "Invitation",
57
+ "HtmlBody" => "<b>Hello</b>",
58
+ "TextBody" => "Hello",
59
+ "ReplyTo" => "reply@example.com"
60
+ }
61
+ }
62
+
63
+ let(:message_with_headers) {
64
+ message.merge(:headers => headers)
65
+ }
66
+
67
+ let(:postmark_message_with_headers) {
68
+ postmark_message.merge("Headers" => postmark_headers)
69
+ }
70
+
71
+ let(:message_with_headers_and_attachments) {
72
+ message_with_headers.merge(:attachments => attachments)
73
+ }
74
+
75
+ let(:postmark_message_with_headers_and_attachments) {
76
+ postmark_message_with_headers.merge("Attachments" => postmark_attachments)
77
+ }
78
+
79
+ it 'converts messages without custom headers and attachments correctly' do
80
+ subject.to_postmark(message).should == postmark_message
81
+ end
82
+
83
+ it 'converts messages with custom headers and without attachments correctly' do
84
+ subject.to_postmark(message_with_headers).should == postmark_message_with_headers
85
+ end
86
+
87
+ it 'converts messages with custom headers and attachments correctly' do
88
+ subject.to_postmark(message_with_headers_and_attachments).should == postmark_message_with_headers_and_attachments
89
+ end
90
+
91
+ end
92
+
93
+ describe ".headers_to_postmark" do
94
+ it 'converts headers to Postmark format' do
95
+ subject.headers_to_postmark(headers).should == postmark_headers
96
+ end
97
+
98
+ it 'accepts single header as a non-array' do
99
+ subject.headers_to_postmark(headers.first).should == [postmark_headers.first]
100
+ end
101
+ end
102
+
103
+ describe ".attachments_to_postmark" do
104
+
105
+ it 'converts attachments to Postmark format' do
106
+ subject.attachments_to_postmark(attachments).should == postmark_attachments
107
+ end
108
+
109
+ it 'accepts single attachment as a non-array' do
110
+ subject.attachments_to_postmark(attachments.first).should == [postmark_attachments.first]
111
+ end
112
+
113
+ end
114
+
115
+ end
@@ -0,0 +1,204 @@
1
+ require 'spec_helper'
2
+
3
+ describe Postmark::HttpClient do
4
+
5
+ def response_body(status, message = "")
6
+ body = {"ErrorCode" => status, "Message" => message}.to_json
7
+ end
8
+
9
+ let(:api_key) { "provided-postmark-api-key" }
10
+ let(:secure) { true }
11
+ let(:proxy_host) { "providedproxyhostname.com" }
12
+ let(:proxy_port) { 42 }
13
+ let(:proxy_user) { "provided proxy user" }
14
+ let(:proxy_pass) { "provided proxy pass" }
15
+ let(:host) { "providedhostname.org" }
16
+ let(:port) { 443 }
17
+ let(:path_prefix) { "/provided/path/prefix" }
18
+ let(:http_open_timeout) { 42 }
19
+ let(:http_read_timeout) { 42 }
20
+
21
+ let(:http_client) { Postmark::HttpClient.new(api_key) }
22
+ subject { http_client }
23
+
24
+ context "attr writers" do
25
+ it { should respond_to(:api_key=) }
26
+ end
27
+
28
+ context "attr readers" do
29
+ it { should respond_to(:http) }
30
+ it { should respond_to(:secure) }
31
+ it { should respond_to(:api_key) }
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
+ end
42
+
43
+ context "when it is created without options" do
44
+ its(:api_key) { should eq api_key }
45
+ its(:host) { should eq 'api.postmarkapp.com' }
46
+ its(:port) { should eq 80 }
47
+ its(:secure) { should be_false }
48
+ its(:path_prefix) { should eq '/' }
49
+ its(:http_read_timeout) { should eq 15 }
50
+ its(:http_open_timeout) { should eq 5 }
51
+ end
52
+
53
+ context "when it is created with options" do
54
+ subject { Postmark::HttpClient.new(api_key,
55
+ :secure => secure,
56
+ :proxy_host => proxy_host,
57
+ :proxy_port => proxy_port,
58
+ :proxy_user => proxy_user,
59
+ :proxy_pass => proxy_pass,
60
+ :host => host,
61
+ :port => port,
62
+ :path_prefix => path_prefix,
63
+ :http_open_timeout => http_open_timeout,
64
+ :http_read_timeout => http_read_timeout) }
65
+
66
+ its(:api_key) { should eq api_key }
67
+ its(:secure) { should == secure }
68
+ its(:proxy_host) { should == proxy_host }
69
+ its(:proxy_port) { should == proxy_port }
70
+ its(:proxy_user) { should == proxy_user }
71
+ its(:proxy_pass) { should == proxy_pass }
72
+ its(:host) { should == host }
73
+ its(:port) { should == port }
74
+ its(:path_prefix) { should == path_prefix }
75
+ its(:http_open_timeout) { should == http_open_timeout }
76
+ its(:http_read_timeout) { should == http_read_timeout }
77
+ end
78
+
79
+ describe "#post" do
80
+ let(:target_path) { "path/on/server" }
81
+ let(:target_url) { "http://api.postmarkapp.com/#{target_path}" }
82
+
83
+ it "sends a POST request to provided URI" do
84
+ FakeWeb.register_uri(:post, target_url, :body => response_body(200))
85
+ subject.post(target_path)
86
+ FakeWeb.should have_requested(:post, target_url)
87
+ end
88
+
89
+ it "raises a custom error when API key authorization fails" do
90
+ FakeWeb.register_uri(:post, target_url, :body => response_body(401),
91
+ :status => [ "401", "Unauthorized" ])
92
+ expect { subject.post(target_path) }.to raise_error Postmark::InvalidApiKeyError
93
+ end
94
+
95
+ it "raises a custom error when sent JSON was not valid" do
96
+ FakeWeb.register_uri(:post, target_url, :body => response_body(422),
97
+ :status => [ "422", "Invalid" ])
98
+ expect { subject.post(target_path) }.to raise_error Postmark::InvalidMessageError
99
+ end
100
+
101
+ it "raises a custom error when server fails to process the request" do
102
+ FakeWeb.register_uri(:post, target_url, :body => response_body(500),
103
+ :status => [ "500", "Internal Server Error" ])
104
+ expect { subject.post(target_path) }.to raise_error Postmark::InternalServerError
105
+ end
106
+
107
+ it "raises a custom error when the request times out" do
108
+ subject.http.should_receive(:post).at_least(:once).
109
+ and_raise(Timeout::Error)
110
+ expect { subject.post(target_path) }.to raise_error Postmark::TimeoutError
111
+ end
112
+
113
+ it "raises a default error when unknown issue occurs" do
114
+ FakeWeb.register_uri(:post, target_url, :body => response_body(485),
115
+ :status => [ "485", "Custom HTTP response status" ])
116
+ expect { subject.post(target_path) }.to raise_error Postmark::UnknownError
117
+ end
118
+
119
+ end
120
+
121
+ describe "#get" do
122
+ let(:target_path) { "path/on/server" }
123
+ let(:target_url) { "http://api.postmarkapp.com/#{target_path}" }
124
+
125
+ it "sends a GET request to provided URI" do
126
+ FakeWeb.register_uri(:get, target_url, :body => response_body(200))
127
+ subject.get(target_path)
128
+ FakeWeb.should have_requested(:get, target_url)
129
+ end
130
+
131
+ it "raises a custom error when API key authorization fails" do
132
+ FakeWeb.register_uri(:get, target_url, :body => response_body(401),
133
+ :status => [ "401", "Unauthorized" ])
134
+ expect { subject.get(target_path) }.to raise_error Postmark::InvalidApiKeyError
135
+ end
136
+
137
+ it "raises a custom error when sent JSON was not valid" do
138
+ FakeWeb.register_uri(:get, target_url, :body => response_body(422),
139
+ :status => [ "422", "Invalid" ])
140
+ expect { subject.get(target_path) }.to raise_error Postmark::InvalidMessageError
141
+ end
142
+
143
+ it "raises a custom error when server fails to process the request" do
144
+ FakeWeb.register_uri(:get, target_url, :body => response_body(500),
145
+ :status => [ "500", "Internal Server Error" ])
146
+ expect { subject.get(target_path) }.to raise_error Postmark::InternalServerError
147
+ end
148
+
149
+ it "raises a custom error when the request times out" do
150
+ subject.http.should_receive(:get).at_least(:once).
151
+ and_raise(Timeout::Error)
152
+ expect { subject.get(target_path) }.to raise_error Postmark::TimeoutError
153
+ end
154
+
155
+ it "raises a default error when unknown issue occurs" do
156
+ FakeWeb.register_uri(:get, target_url, :body => response_body(485),
157
+ :status => [ "485", "Custom HTTP response status" ])
158
+ expect { subject.get(target_path) }.to raise_error Postmark::UnknownError
159
+ end
160
+
161
+ end
162
+
163
+ describe "#put" do
164
+ let(:target_path) { "path/on/server" }
165
+ let(:target_url) { "http://api.postmarkapp.com/#{target_path}" }
166
+
167
+ it "sends a PUT request to provided URI" do
168
+ FakeWeb.register_uri(:put, target_url, :body => response_body(200))
169
+ subject.put(target_path)
170
+ FakeWeb.should have_requested(:put, target_url)
171
+ end
172
+
173
+ it "raises a custom error when API key authorization fails" do
174
+ FakeWeb.register_uri(:put, target_url, :body => response_body(401),
175
+ :status => [ "401", "Unauthorized" ])
176
+ expect { subject.put(target_path) }.to raise_error Postmark::InvalidApiKeyError
177
+ end
178
+
179
+ it "raises a custom error when sent JSON was not valid" do
180
+ FakeWeb.register_uri(:put, target_url, :body => response_body(422),
181
+ :status => [ "422", "Invalid" ])
182
+ expect { subject.put(target_path) }.to raise_error Postmark::InvalidMessageError
183
+ end
184
+
185
+ it "raises a custom error when server fails to process the request" do
186
+ FakeWeb.register_uri(:put, target_url, :body => response_body(500),
187
+ :status => [ "500", "Internal Server Error" ])
188
+ expect { subject.put(target_path) }.to raise_error Postmark::InternalServerError
189
+ end
190
+
191
+ it "raises a custom error when the request times out" do
192
+ subject.http.should_receive(:put).at_least(:once).
193
+ and_raise(Timeout::Error)
194
+ expect { subject.put(target_path) }.to raise_error Postmark::TimeoutError
195
+ end
196
+
197
+ it "raises a default error when unknown issue occurs" do
198
+ FakeWeb.register_uri(:put, target_url, :body => response_body(485),
199
+ :status => [ "485", "Custom HTTP response status" ])
200
+ expect { subject.put(target_path) }.to raise_error Postmark::UnknownError
201
+ end
202
+
203
+ end
204
+ end