dotmailer 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -73,6 +73,13 @@ module DotMailer
73
73
  end
74
74
  end
75
75
 
76
+ def delete(path)
77
+ rescue_api_errors do
78
+ endpoint = endpoint_for(path)
79
+ RestClient.delete endpoint, :accept => :json
80
+ end
81
+ end
82
+
76
83
  def to_s
77
84
  "#{self.class.name} api_user: #{api_user}"
78
85
  end
@@ -104,6 +104,10 @@ module DotMailer
104
104
  client.put_json "/contacts/#{id}", attributes.merge('dataFields' => data_fields_for_api)
105
105
  end
106
106
 
107
+ def delete
108
+ client.delete "/contacts/#{id}"
109
+ end
110
+
107
111
  def subscribed?
108
112
  status == SUBSCRIBED_STATUS
109
113
  end
@@ -2,11 +2,15 @@ require 'csv'
2
2
  require 'active_support/core_ext/object/blank'
3
3
 
4
4
  module DotMailer
5
+ # This is the maximum number of times we will poll the dotMailer
6
+ # API to see if an import has finished.
7
+ MAX_TRIES = 10
8
+
5
9
  class ContactImport
6
- def self.import(account, contacts)
10
+ def self.import(account, contacts, wait_for_finish = false)
7
11
  contact_import = new(account, contacts)
8
12
 
9
- contact_import.start
13
+ contact_import.start(wait_for_finish)
10
14
 
11
15
  contact_import
12
16
  end
@@ -18,12 +22,14 @@ module DotMailer
18
22
  self.contacts = contacts
19
23
  end
20
24
 
21
- def start
25
+ def start(wait_for_finish)
22
26
  validate_headers
23
27
 
24
28
  response = client.post_csv '/contacts/import', contacts_csv
25
29
 
26
30
  self.id = response['id']
31
+
32
+ wait_until_finished if wait_for_finish
27
33
  end
28
34
 
29
35
  def status
@@ -90,5 +96,17 @@ module DotMailer
90
96
  def valid_headers
91
97
  @valid_headers ||= %w(id email optInType emailType) + account.data_fields.map(&:name)
92
98
  end
99
+
100
+ def wait_until_finished
101
+ # Wait for the import to finish, backing off in incremental powers
102
+ # of 2, a maximum of MAX_TRIES times.
103
+ #
104
+ # (i.e. 1s, 4s, 9s, 16s, ..., MAX_TRIES ** 2)
105
+ #
106
+ # A MAX_TRIES of 10 means we will wait a total of 385 seconds before
107
+ # giving up.
108
+ finished = (1..MAX_TRIES).detect { |i| sleep(i ** 2) && finished? }
109
+ raise ImportNotFinished unless finished
110
+ end
93
111
  end
94
112
  end
@@ -1,7 +1,13 @@
1
1
  module DotMailer
2
+ class Exception < ::Exception
3
+ end
4
+
2
5
  class ImportNotFinished < Exception
3
6
  end
4
7
 
8
+ class InvalidFromAddress < Exception
9
+ end
10
+
5
11
  class InvalidRequest < Exception
6
12
  end
7
13
 
@@ -0,0 +1,36 @@
1
+ require 'active_support/core_ext/hash/slice'
2
+
3
+ module DotMailer
4
+ class FromAddress
5
+ def initialize(attributes)
6
+ self.attributes = attributes
7
+ end
8
+
9
+ def id
10
+ attributes['id']
11
+ end
12
+
13
+ def email
14
+ attributes['email']
15
+ end
16
+
17
+ def to_hash
18
+ attributes.slice('id', 'email')
19
+ end
20
+
21
+ def ==(other)
22
+ attributes == other.attributes
23
+ end
24
+
25
+ def to_s
26
+ %{#{self.class.name} id: #{id}, email: #{email}}
27
+ end
28
+
29
+ def inspect
30
+ to_s
31
+ end
32
+
33
+ protected
34
+ attr_accessor :attributes
35
+ end
36
+ end
@@ -0,0 +1,36 @@
1
+ module DotMailer
2
+ class Segment
3
+ def initialize(account, attributes)
4
+ self.account = account
5
+ self.attributes = attributes
6
+ end
7
+
8
+ def self.find_by_id(account, id)
9
+ response = account.client.get "/segments"
10
+ response = response.detect {|segment| segment['id'] == id}
11
+
12
+ new(account, response)
13
+ end
14
+
15
+ def id
16
+ attributes['id']
17
+ end
18
+
19
+ def refresh!
20
+ client.post_json "/segments/refresh/#{self.id}", {}
21
+ end
22
+
23
+ def refresh_progress
24
+ response = client.get "/segments/refresh/#{self.id}"
25
+ return response["status"]
26
+ end
27
+
28
+ private
29
+ attr_accessor :attributes, :account
30
+
31
+ def client
32
+ account.client
33
+ end
34
+ end
35
+ end
36
+
@@ -1,3 +1,3 @@
1
1
  module DotMailer
2
- VERSION = "0.0.3"
2
+ VERSION = "0.0.4"
3
3
  end
@@ -4,11 +4,15 @@ describe DotMailer::Account do
4
4
  let(:api_user) { double 'api user' }
5
5
  let(:api_pass) { double 'api pass' }
6
6
  let(:client) { double 'client' }
7
+ let(:cache) { double 'cache' }
7
8
 
8
9
  subject { DotMailer::Account.new(api_user, api_pass) }
9
10
 
10
11
  before(:each) do
11
- subject.stub :client => client
12
+ subject.stub(
13
+ :client => client,
14
+ :cache => cache
15
+ )
12
16
  end
13
17
 
14
18
  describe '#initialize' do
@@ -51,11 +55,6 @@ describe DotMailer::Account do
51
55
 
52
56
  describe '#data_fields' do
53
57
  let(:data_fields) { double 'data fields' }
54
- let(:cache) { double 'cache' }
55
-
56
- before(:each) do
57
- subject.stub :cache => cache
58
- end
59
58
 
60
59
  context 'when the cache is empty' do
61
60
  before(:each) do
@@ -90,10 +89,8 @@ describe DotMailer::Account do
90
89
  describe '#create_data_field' do
91
90
  let(:name) { double 'name' }
92
91
  let(:options) { double 'options' }
93
- let(:cache) { double 'cache' }
94
92
 
95
93
  before(:each) do
96
- subject.stub :cache => cache
97
94
  DotMailer::DataField.stub :create
98
95
  cache.stub :delete
99
96
  end
@@ -110,4 +107,56 @@ describe DotMailer::Account do
110
107
  subject.create_data_field(name, options)
111
108
  end
112
109
  end
110
+
111
+ describe '#from_addresses' do
112
+ let(:attributes) { double 'attributes' }
113
+ let(:response) { 3.times.map { attributes } }
114
+ let(:from_address) { double 'from address' }
115
+ let(:from_addresses) { 3.times.map { from_address } }
116
+
117
+ before(:each) do
118
+ DotMailer::FromAddress.stub :new => from_address
119
+ end
120
+
121
+ context 'when the cache is empty' do
122
+ before(:each) do
123
+ cache.stub(:fetch).with('from_addresses').and_yield
124
+ client.stub :get => response
125
+ end
126
+
127
+ it 'should call get on the client with the correct path' do
128
+ client.should_receive(:get).with('/custom-from-addresses')
129
+
130
+ subject.from_addresses
131
+ end
132
+
133
+ it 'should initialize 3 FromAddresses' do
134
+ DotMailer::FromAddress.should_receive(:new).with(attributes).exactly(3).times
135
+
136
+ subject.from_addresses
137
+ end
138
+
139
+ its(:from_addresses) { should == from_addresses }
140
+ end
141
+
142
+ context 'when the cache is not empty' do
143
+ before(:each) do
144
+ cache.stub(:fetch).with('from_addresses').and_return(response)
145
+ end
146
+
147
+ it 'should not call get on the client' do
148
+ client.should_not_receive(:get)
149
+
150
+ subject.from_addresses
151
+ end
152
+
153
+ it 'should initialize 3 FromAddresses' do
154
+ DotMailer::FromAddress.should_receive(:new).with(attributes).exactly(3).times
155
+
156
+ subject.from_addresses
157
+ end
158
+
159
+ its(:from_addresses) { should == from_addresses }
160
+ end
161
+ end
113
162
  end
@@ -0,0 +1,206 @@
1
+ require 'spec_helper'
2
+
3
+ describe DotMailer::Campaign do
4
+ let(:client) { double 'client' }
5
+ let(:account) { double 'account', :client => client }
6
+
7
+ let(:id) { 123 }
8
+ let(:name) { 'my_campaign' }
9
+ let(:campaign_subject) { 'My Campaign' }
10
+ let(:from_name) { 'Me' }
11
+ let(:from_email) { 'me@example.com' }
12
+ let(:html_content) { '<h1>Hello!</h1><a href="http://$UNSUB$">Unsubscribe</a>' }
13
+ let(:plain_text_content) { "Hello!\n======\nhttp://$UNSUB$" }
14
+
15
+ let(:from_address) do
16
+ DotMailer::FromAddress.new 'id' => 123, 'email' => from_email
17
+ end
18
+
19
+ subject do
20
+ DotMailer::Campaign.new(account, {
21
+ 'id' => id,
22
+ 'name' => name,
23
+ 'subject' => campaign_subject,
24
+ 'fromName' => from_name,
25
+ 'fromAddress' => from_address.to_hash,
26
+ 'htmlContent' => html_content,
27
+ 'plainTextContent' => plain_text_content
28
+ })
29
+ end
30
+
31
+ describe 'Class' do
32
+ subject { DotMailer::Campaign }
33
+
34
+ describe '.create' do
35
+ let(:response) { double 'response' }
36
+ let(:campaign) { double 'campaign' }
37
+
38
+ # We define a method so we can override keys within
39
+ # context blocks without redefining other keys
40
+ def attributes
41
+ {
42
+ :name => name,
43
+ :subject => campaign_subject,
44
+ :from_name => from_name,
45
+ :from_email => from_email,
46
+ :html_content => html_content,
47
+ :plain_text_content => plain_text_content
48
+ }
49
+ end
50
+
51
+ before(:each) do
52
+ account.stub :from_addresses => [from_address]
53
+ client.stub :post_json => response
54
+ subject.stub :new => campaign
55
+ end
56
+
57
+ [
58
+ :name,
59
+ :subject,
60
+ :from_name,
61
+ :from_email,
62
+ :html_content,
63
+ :plain_text_content
64
+ ].each do |attribute|
65
+ context "without specifying #{attribute}" do
66
+ define_method :attributes do
67
+ super().except(attribute)
68
+ end
69
+
70
+ it 'should raise an error' do
71
+ expect { subject.create(account, attributes) }.to \
72
+ raise_error(RuntimeError, "missing :#{attribute}")
73
+ end
74
+ end
75
+ end
76
+
77
+ context 'when the fromAddress is not a valid from address' do
78
+ let(:unknown_email) { 'unknown@example.com' }
79
+
80
+ def attributes
81
+ super.merge(:from_email => unknown_email)
82
+ end
83
+
84
+ it 'should raise an error' do
85
+ expect { subject.create(account, attributes) }.to \
86
+ raise_error(DotMailer::InvalidFromAddress, unknown_email)
87
+ end
88
+ end
89
+
90
+ it 'should call post_json on the client with the correct path' do
91
+ client.should_receive(:post_json).with('/campaigns', anything)
92
+
93
+ subject.create(account, attributes)
94
+ end
95
+
96
+ it 'should call post_json on the client with the correct parameters' do
97
+ client.should_receive(:post_json).with(anything, {
98
+ 'Name' => name,
99
+ 'Subject' => campaign_subject,
100
+ 'FromName' => from_name,
101
+ 'FromAddress' => from_address.to_hash,
102
+ 'HtmlContent' => html_content,
103
+ 'PlainTextContent' => plain_text_content
104
+ })
105
+
106
+ subject.create(account, attributes)
107
+ end
108
+
109
+ it 'should instantiate a new Campaign object with the account and response' do
110
+ subject.should_receive(:new).with(account, response)
111
+
112
+ subject.create(account, attributes)
113
+ end
114
+
115
+ it 'should return the new Campaign object' do
116
+ subject.create(account, attributes).should == campaign
117
+ end
118
+ end
119
+
120
+ describe '.find_by_id' do
121
+ let(:id) { 123 }
122
+ let(:response) { double 'response' }
123
+ let(:campaign) { double 'campaign' }
124
+
125
+ before(:each) do
126
+ subject.stub :new => campaign
127
+ client.stub :get => response
128
+ end
129
+
130
+ it 'should call get on the client with the correct parameters' do
131
+ client.should_receive(:get).with("/campaigns/#{id}")
132
+
133
+ subject.find_by_id(account, id)
134
+ end
135
+
136
+ it 'should initialize a Campaign with the response' do
137
+ subject.should_receive(:new).with(account, response)
138
+
139
+ subject.find_by_id(account, id)
140
+ end
141
+
142
+ it 'should return the new Campaign object' do
143
+ subject.find_by_id(account, id).should == campaign
144
+ end
145
+ end
146
+ end
147
+
148
+ its(:id) { should == id }
149
+ its(:name) { should == name }
150
+ its(:from_name) { should == from_name }
151
+ its(:from_address) { should == from_address }
152
+ its(:html_content) { should == html_content }
153
+ its(:plain_text_content) { should == plain_text_content }
154
+
155
+ describe '#send_to_contact_ids' do
156
+ let(:contact_ids) { double 'contact ids' }
157
+
158
+ it 'should call post_json on the client with the correct path' do
159
+ client.should_receive(:post_json).with('/campaigns/send', anything)
160
+
161
+ subject.send_to_contact_ids contact_ids
162
+ end
163
+
164
+ it 'should call post_json on the client with the contact ids' do
165
+ client.should_receive(:post_json).with(anything, {
166
+ 'campaignId' => id,
167
+ 'contactIds' => contact_ids
168
+ })
169
+
170
+ subject.send_to_contact_ids contact_ids
171
+ end
172
+ end
173
+
174
+ describe '#send_to_segment' do
175
+ let(:segment) { double 'segment', :id => 123 }
176
+
177
+ it 'should call post_json on the client with the correct path' do
178
+ client.should_receive(:post_json).with('/campaigns/send', anything)
179
+
180
+ subject.send_to_segment segment
181
+ end
182
+
183
+ it 'should call post_json on the client with the contact ids' do
184
+ client.should_receive(:post_json).with(anything, {
185
+ 'campaignId' => id,
186
+ 'addressBookIds' => [segment.id]
187
+ })
188
+
189
+ subject.send_to_segment segment
190
+ end
191
+ end
192
+
193
+ describe '#summary' do
194
+ let(:summary) { double 'campaign_summary'}
195
+
196
+ it 'should call get on the client with the correct path' do
197
+ client.should_receive(:get).with("/campaigns/#{id}/summary")
198
+ subject.summary
199
+ end
200
+
201
+ it 'should return a CampaignSummary object' do
202
+ client.should_receive(:get).with(anything).and_return(summary)
203
+ subject.summary.class.should be(DotMailer::CampaignSummary)
204
+ end
205
+ end
206
+ end