expresspigeon-ruby 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,19 @@
1
+ *.rbc
2
+ *.sassc
3
+ .sass-cache
4
+ capybara-*.html
5
+ .rspec
6
+ /.bundle
7
+ /vendor/bundle
8
+ /log/*
9
+ /tmp/*
10
+ /db/*.sqlite3
11
+ /public/system/*
12
+ /coverage/
13
+ /spec/tmp/*
14
+ **.orig
15
+ rerun.txt
16
+ pickle-email-*.html
17
+ doc/structures.csv
18
+ *.iml
19
+ .idea/
data/.rvmrc ADDED
@@ -0,0 +1,8 @@
1
+ USER_RVMRC='.rvmrc.local'
2
+
3
+ if [ -f $USER_RVMRC ]; then
4
+ source $USER_RVMRC
5
+ else
6
+ rvm use 1.9.3@expresspigeon-ruby --create
7
+ fi
8
+
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in expresspigeon-ruby.gemspec
4
+ gemspec
5
+
data/Gemfile.lock ADDED
@@ -0,0 +1,24 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ expresspigeon-ruby (0.0.1)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ diff-lcs (1.2.5)
10
+ rspec (2.14.1)
11
+ rspec-core (~> 2.14.0)
12
+ rspec-expectations (~> 2.14.0)
13
+ rspec-mocks (~> 2.14.0)
14
+ rspec-core (2.14.5)
15
+ rspec-expectations (2.14.3)
16
+ diff-lcs (>= 1.1.3, < 2.0)
17
+ rspec-mocks (2.14.3)
18
+
19
+ PLATFORMS
20
+ ruby
21
+
22
+ DEPENDENCIES
23
+ expresspigeon-ruby!
24
+ rspec (~> 2.6)
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 ExpressPigeon
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,29 @@
1
+ # Expresspigeon::Ruby
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'expresspigeon-ruby'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install expresspigeon-ruby
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+ ## Contributing
24
+
25
+ 1. Fork it
26
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
27
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
28
+ 4. Push to the branch (`git push origin my-new-feature`)
29
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,20 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'expresspigeon-ruby/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "expresspigeon-ruby"
8
+ gem.version = Expresspigeon::API::VERSION
9
+ gem.authors = ["ipolevoy"]
10
+ gem.email = ["ipolevoy@groupon.com"]
11
+ gem.description = %q{ExpressPigeon Ruby API for sending transactional messages, manipulating lists, contacts nad more}
12
+ gem.summary = %q{ExpressPigeon API Ruby Wrapper}
13
+ gem.homepage = "https://github.com/expresspigeon/expresspigeon-ruby"
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ["lib"]
19
+ gem.add_development_dependency "rspec", "~> 2.6"
20
+ end
@@ -0,0 +1,5 @@
1
+ module Expresspigeon
2
+ module API
3
+ VERSION = "0.0.1"
4
+ end
5
+ end
@@ -0,0 +1,306 @@
1
+ require "expresspigeon-ruby/version"
2
+
3
+ require 'net/http'
4
+ require 'json'
5
+ require 'uri'
6
+
7
+ AUTH_KEY = ENV['EXPRESSPIGEON_AUTH_KEY']
8
+ ROOT = 'https://api.expresspigeon.com/'
9
+ #ROOT = 'http://localhost:8888/api/'
10
+ USE_SSL = true
11
+
12
+ module ExpressPigeon
13
+ module API
14
+ unless AUTH_KEY
15
+ raise 'Provide EXPRESSPIGEON_AUTH_KEY environment variable'
16
+ end
17
+
18
+ def http(path, method, params = {})
19
+ uri = URI.parse "#{ROOT}#{path}"
20
+ req = Net::HTTP.const_get("#{method}").new "#{ROOT}#{path}"
21
+ req['X-auth-key'] = AUTH_KEY
22
+ if params
23
+ req.body = params.to_json
24
+ req['Content-type'] = 'application/json'
25
+ end
26
+
27
+ if block_given?
28
+ Net::HTTP.start(uri.host, uri.port, :use_ssl => USE_SSL) do |http|
29
+ http.request req do |res|
30
+ res.read_body do |seg|
31
+ yield seg
32
+ end
33
+ end
34
+ end
35
+ else
36
+ resp = Net::HTTP.start(uri.host, uri.port, :use_ssl => USE_SSL) do |http|
37
+ http.request req
38
+ end
39
+ parsed = JSON.parse(resp.body)
40
+ if parsed.kind_of? Hash
41
+ MetaHash.new parsed
42
+ else
43
+ parsed
44
+ end
45
+ end
46
+ end
47
+
48
+ def get(path, &block)
49
+ http path, 'Get', nil, &block
50
+ end
51
+
52
+ def post(path, params = {})
53
+ http path, 'Post', params
54
+ end
55
+
56
+ def del(path, params = {})
57
+ http path, 'Delete', params
58
+ end
59
+
60
+ def self.campaigns
61
+ Campaigns.new
62
+ end
63
+
64
+ def self.lists
65
+ Lists.new
66
+ end
67
+
68
+ def self.contacts
69
+ Contacts.new
70
+ end
71
+
72
+ def self.messages
73
+ Messages.new
74
+ end
75
+ end
76
+
77
+ end
78
+
79
+ class MetaHash < Hash
80
+
81
+ def initialize(delegate)
82
+ super
83
+ @delegate = delegate
84
+ @delegate.each_key do |k|
85
+ v = @delegate[k] # lets go only one level down for now
86
+ if v.kind_of? Hash
87
+ @delegate[k] = MetaHash.new(v)
88
+ end
89
+ end
90
+ end
91
+
92
+ def method_missing(m, *args, &block)
93
+ @delegate[m.to_s]
94
+ end
95
+
96
+ def to_s
97
+ @delegate.to_s
98
+ end
99
+
100
+ end
101
+
102
+ class Lists
103
+
104
+ include ExpressPigeon::API
105
+
106
+ def initialize
107
+ @endpoint = 'lists'
108
+ end
109
+
110
+ def create(list_name, from_name, reply_to)
111
+ post @endpoint, {:name => list_name, :from_name => from_name, :reply_to => reply_to}
112
+ end
113
+
114
+
115
+ # Query all lists.
116
+ # returns: array of hashes each representing a list for this user
117
+ def all
118
+ get @endpoint
119
+ end
120
+
121
+
122
+ #Updates existing list
123
+ #
124
+ #:param list_id: Id of list to be updated
125
+ #:type list_id: int
126
+ #
127
+ #:param params: JSON object represents a list to be updated
128
+
129
+ #
130
+ #:returns: EpResponse with status, code, message, and updated list
131
+ #:rtype: EpResponse
132
+ #TODO: resolve API on Python side, then implement this
133
+ #def update(list_id, params = {})
134
+ # params['id'] = list_id
135
+ # return self.ep.put(self.endpoint, params=params)
136
+ #end
137
+
138
+
139
+ # Removes a list with a given id. A list must be enabled and has no dependent subscriptions and/or scheduled campaigns.
140
+ #
141
+ # param list_id: Id of list to be removed.
142
+ # returns response hash with status, code, and message
143
+ def delete(list_id)
144
+ del "#{@endpoint}/#{list_id}"
145
+ end
146
+
147
+ def csv(list_id, &block)
148
+ get "#{@endpoint}/#{list_id}/csv", &block
149
+ end
150
+
151
+
152
+ end
153
+
154
+ class Campaigns
155
+ include ExpressPigeon::API
156
+
157
+ def initialize
158
+ @endpoint = 'campaigns'
159
+ end
160
+
161
+ def all
162
+ get @endpoint
163
+ end
164
+
165
+ def report(campaign_id)
166
+ get "#{@endpoint}/#{campaign_id}"
167
+ end
168
+
169
+ def bounced(campaign_id)
170
+ get "#{@endpoint}/#{campaign_id}/bounced"
171
+ end
172
+
173
+ def unsubscribed(campaign_id)
174
+ get "#{@endpoint}/#{campaign_id}/unsubscribed"
175
+ end
176
+
177
+ def spam(campaign_id)
178
+ get "#{@endpoint}/#{campaign_id}/spam"
179
+ end
180
+
181
+ #
182
+ # Schedules a new campaign to be sent.
183
+ # Parameters:
184
+ # * *list_id* - id of list to send to
185
+ # * *template_id* - id of template to send
186
+ # * *name* - name of a newly created campaign
187
+ # * *from_name* - from name
188
+ # * *reply_to* - reply to
189
+ # * *subject* - subject of campaign
190
+ # * *google_analytics* - true to turn Google Analytics on
191
+ def send(params = {})
192
+ post @endpoint, params
193
+ end
194
+
195
+ #
196
+ # Schedules a new campaign to be sent.
197
+ # Parameters:
198
+ # * *list_id* - id of list to send to
199
+ # * *template_id* - id of template to send
200
+ # * *name* - name of a newly created campaign
201
+ # * *from_name* - from name
202
+ # * *reply_to* - reply to
203
+ # * *subject* - subject of campaign
204
+ # * *google_analytics* - true to turn Google Analytics on
205
+ # * *schedule_for* - Specifies what time a campaign should be sent. If it is provided the campaign will
206
+ # be scheduled to this time, otherwise campaign is sent immediately. The schedule_for
207
+ # must be in ISO date format and should be in the future.
208
+ def schedule(params = {})
209
+ post @endpoint, params
210
+ end
211
+ end
212
+
213
+
214
+ class Contacts
215
+ include ExpressPigeon::API
216
+
217
+ def initialize
218
+ @endpoint = 'contacts'
219
+ end
220
+
221
+ def find_by_email(email)
222
+ get "#{@endpoint}?email=#{email}"
223
+ end
224
+
225
+
226
+ # JSON document represents a contact to be created or updated.
227
+ # The email field is required.
228
+ # When updating a contact, list_id is optional,
229
+ # since the contact is uniquely identified by email across all lists.
230
+ #
231
+ # :param list_id: Contact list ID (Fixnum) the contact will be added to
232
+ #
233
+ # :param contact: Hash describes new contact. The "email" field is required.
234
+ #
235
+ # :returns: representation of a contact
236
+ #
237
+ def upsert(list_id, contact)
238
+ post @endpoint, params = {:list_id => list_id, :contact => contact}
239
+ end
240
+
241
+ # Delete single contact. If list_id is not provided, contact will be deleted from system.
242
+ # :param email: contact email to be deleted.
243
+ # :param list_id: list id to remove contact from, if not provided, contact will be deleted from system.
244
+ def delete(email, list_id = nil)
245
+ if list_id
246
+ query = "email=#{email}&list_id=#{list_id}"
247
+ else
248
+ query = "email=#{email}"
249
+ end
250
+ del "#{@endpoint}?#{query}", nil
251
+ end
252
+
253
+
254
+ end
255
+ class Messages
256
+
257
+ include ExpressPigeon::API
258
+
259
+ def initialize
260
+ @endpoint = 'messages'
261
+ end
262
+
263
+ def send_message(template_id, to, reply_to, from_name, subject, merge_fields = nil, view_online = false, click_tracking = true)
264
+ post @endpoint, params = {template_id: template_id, :to => to, reply_to: reply_to, :from => from_name, :subject => subject,
265
+ :merge_fields => merge_fields, :view_online => view_online, :click_tracking => click_tracking}
266
+ end
267
+
268
+ def report(message_id)
269
+ get "#{@endpoint}/#{message_id}"
270
+ end
271
+
272
+ #
273
+ #
274
+ # start_date is instance of Time
275
+ # end_date is instance of Time
276
+ def reports(from_id, start_date = nil, end_date = nil)
277
+ params = []
278
+
279
+ if from_id
280
+ params << "from_id=#{from_id}"
281
+ end
282
+
283
+ if start_date and not end_date
284
+ raise 'must include both start_date and end_date'
285
+ end
286
+ if end_date and not start_date
287
+ raise 'must include both start_date and end_date'
288
+ end
289
+
290
+ if start_date and end_date
291
+ params << "start_date=#{start_date.strftime('%FT%T.%L%z')}"
292
+ params << "end_date=#{end_date.strftime('%FT%T.%L%z')}"
293
+ end
294
+
295
+ query = "#{@endpoint}?"
296
+
297
+ if params.size > 0
298
+ query << params.join('&')
299
+ end
300
+
301
+ puts "calling: #{query}"
302
+ get query
303
+
304
+ end
305
+
306
+ end
@@ -0,0 +1,109 @@
1
+ require './lib/expresspigeon-ruby'
2
+ require 'pigeon_helper'
3
+
4
+ describe 'campaigns integration test' do
5
+
6
+ include PigeonSpecHelper
7
+ it 'should return more than 0 campaign ids' do
8
+ res = PIGEON.campaigns.all
9
+ res.class.should == Array
10
+ res.size.should > 0
11
+ end
12
+
13
+ it 'cannot send with missing parameters' do
14
+ res = PIGEON.campaigns.send(:template_id => 15233, :name => 'API Test campaign',
15
+ :from_name => 'Igor Polevoy', :reply_to => 'igor@polevoy.org',
16
+ :subject => 'API test', :google_analytics => true)
17
+ validate_response res, 400, 'error', /required parameters: list_id, template_id, name, from_name, reply_to, subject, google_analytics/
18
+ end
19
+
20
+ it 'cannot send with bad reply_to' do
21
+ res = PIGEON.campaigns.send(:list_id => -1, :template_id => -1, :name => 'My Campaign', :from_name => 'John', :reply_to => 'j',
22
+ :subject => 'Hi', :google_analytics => false)
23
+ validate_response res, 400, 'error', /reply_to should be valid email address/
24
+ end
25
+
26
+ it 'cannot send with non-existing template' do
27
+ res = PIGEON.campaigns.send(:list_id => -1, :template_id => -1, :name => 'My Campaign', :from_name => 'John',
28
+ :reply_to => 'j@j.j',
29
+ :subject => 'Hi', :google_analytics => false)
30
+ validate_response res, 400, 'error', /template=-1 is not found/
31
+ end
32
+
33
+ it 'cannot send to non-existing list' do
34
+
35
+ res = PIGEON.campaigns.send(:list_id => -1, :template_id => TEMPLATE_ID, :name => 'My Campaign', :from_name => 'John',
36
+ :reply_to => 'j@j.j',
37
+ :subject => 'Hi', :google_analytics => false)
38
+ validate_response res, 400, 'error', /list=-1 is not found/
39
+ end
40
+
41
+ it 'cannot send to disabled list' do
42
+ res = PIGEON.campaigns.send(:list_id => LIST_ID, :template_id => TEMPLATE_ID, :name => 'My Campaign', :from_name => 'John',
43
+ :reply_to => 'j@j.j',
44
+ :subject => 'Hi', :google_analytics => false)
45
+ validate_response res, 400, 'error', /list=#{DISABLED_LIST} is disabled/
46
+ end
47
+
48
+ it 'should create new list, add contact and send successful campaign' do
49
+
50
+ list_resp = PIGEON.lists.create('My list', 'John', API_USER)
51
+ list_id = list_resp.list.id
52
+ PIGEON.contacts.upsert(list_id, {:email => API_USER})
53
+ resp = PIGEON.campaigns.send(:list_id => list_id, :template_id => TEMPLATE_ID, :name => 'My Campaign', :from_name => 'John',
54
+ :reply_to => API_USER,
55
+ :subject => 'Hi', :google_analytics => false)
56
+ validate_response resp, 200, 'success', /new campaign created successfully/
57
+ report = PIGEON.campaigns.report(resp.campaign_id)
58
+ (report.delivered == 0 or report.delivered == 1).should be_true
59
+ report.clicked.should eq 0
60
+ report.opened.should eq 0
61
+ report.spam.should eq 0
62
+ (report.in_transit == 0 or report.in_transit == 1).should be_true
63
+ report.unsubscribed.should eq 0
64
+ report.bounced.should eq 0
65
+ bounced = PIGEON.campaigns.bounced(resp.campaign_id)
66
+ unsubscribed = PIGEON.campaigns.unsubscribed(resp.campaign_id)
67
+ spam = PIGEON.campaigns.spam(resp.campaign_id)
68
+
69
+ bounced.size.should eq 0
70
+ unsubscribed.size.should eq 0
71
+ spam.size.should eq 0
72
+
73
+ resp = PIGEON.contacts.delete(API_USER)
74
+ validate_response resp, 200, 'success', /contact=non@non.non deleted successfully/
75
+
76
+ resp = PIGEON.contacts.find_by_email(API_USER)
77
+ validate_response resp, 404, 'error', /contact=non@non.non not found/
78
+
79
+ resp = PIGEON.lists.delete(list_id)
80
+ validate_response resp, 200, 'success', /deleted successfully/
81
+ end
82
+
83
+
84
+ it 'cannot send campaign if scheduling with bad date' do
85
+
86
+ list_resp = PIGEON.lists.create "My list", "John", API_USER
87
+ resp = PIGEON.campaigns.schedule :list_id => list_resp.list.id, :template_id => TEMPLATE_ID, :name => 'My Campaign',
88
+
89
+ :from_name => 'John',
90
+ :reply_to => API_USER, :subject => 'Hi',
91
+ :google_analytics => false, :schedule_for => "2013-05-28"
92
+
93
+ validate_response resp, 400, 'error', /schedule_for is not in ISO date format, example: 2013-05-28T17:19:50.779/
94
+ resp = PIGEON.lists.delete(list_resp.list.id)
95
+ validate_response resp, 200, 'success', /deleted successfully/
96
+ end
97
+
98
+ it 'should not schedule campaign with date in the past' do
99
+ list_resp = PIGEON.lists.create('My list', 'John', API_USER)
100
+ resp = PIGEON.campaigns.schedule :list_id => list_resp.list.id, :template_id => TEMPLATE_ID, :name => 'My Campaign',
101
+ :from_name => 'John',
102
+ :reply_to => API_USER, :subject => 'Hi',
103
+ :google_analytics => false, :schedule_for => '2010-05-28T17:19:50.779+0300'
104
+
105
+ validate_response resp, 400, 'error', /schedule_for should be in the future/
106
+ end
107
+
108
+ end
109
+
@@ -0,0 +1,152 @@
1
+ require './lib/expresspigeon-ruby'
2
+ require 'pigeon_helper'
3
+
4
+ describe 'contacts integration test' do
5
+
6
+ include PigeonSpecHelper
7
+
8
+ it 'should not create contact without contact data' do
9
+ resp = PIGEON.contacts.upsert(-1, {})
10
+ validate_response resp, 400, 'error', /contact and contact.email are required/
11
+ end
12
+ it 'should not create contact without email' do
13
+ resp = PIGEON.contacts.upsert -1, :email => '', :first_name => 'Marylin', :last_name => 'Monroe'
14
+ validate_response resp, 400, 'error', /contact and contact.email are required/
15
+ end
16
+
17
+ it 'should not add contact with too many custom fields' do
18
+ custom_fields = {}
19
+ (1..25).each { |n| custom_fields["custom_field_#{n}"] = n }
20
+ resp = PIGEON.contacts.upsert -1, :email => "mary@e.e", :custom_fields => custom_fields
21
+ validate_response resp, 400, 'error', /You cannot create more than 20 custom fields. Use one of the 'custom_fields'./
22
+ end
23
+
24
+ it 'should not create new contact without list_id' do
25
+ resp = PIGEON.contacts.upsert '', :email => 'ee@e.e', :first_name => 'Marylin', :last_name => 'Monroe'
26
+ validate_response resp, 404, 'error', /contact=ee@e.e not found/
27
+ end
28
+
29
+ it 'test_create_with_suppressed_contact' do
30
+ resp = PIGEON.contacts.upsert -1, :email => 'suppressed@e.e'
31
+ validate_response resp, 400, 'error', /contact=suppressed@e.e is in suppress list/
32
+ end
33
+
34
+ it 'cannot create with non-existent_list' do
35
+ resp = PIGEON.contacts.upsert -1, :email => "e@e.e"
36
+ validate_response resp, 404, 'error', /list=-1 not found/
37
+ end
38
+
39
+ it 'creates list with contacts' do
40
+ list_response = PIGEON.lists.create 'My List', 'John Doe', 'john@doe.com'
41
+ list_id = list_response.list.id
42
+ resp = PIGEON.contacts.upsert list_id, email: "mary@e.e",
43
+ :custom_fields => {:custom_field_1 => "custom_value_1", }
44
+ validate_response resp, 200, 'success', /contact=mary@e.e created\/updated successfully/
45
+ resp.contact.custom_fields.custom_field_1.should eq 'custom_value_1'
46
+ resp.contact.email.should eq 'mary@e.e'
47
+ resp.contact.email_format.should eq 'html'
48
+ resp.contact.status.should eq 'ACTIVE'
49
+ PIGEON.lists.delete(list_id)
50
+ end
51
+
52
+ it 'creates list non-existent custom field' do
53
+ list_response = PIGEON.lists.create 'My List', 'John Doe', 'a@a.a'
54
+ list_id = list_response.list.id
55
+ resp = PIGEON.contacts.upsert(list_id, {:email => "mary@e.e", :custom_fields => {:c => "c", }})
56
+ validate_response resp, 200, 'success', nil
57
+ PIGEON.lists.delete(list_id)
58
+ end
59
+
60
+ it 'cannot export contacts from list without list_id' do
61
+ content = ''
62
+ PIGEON.lists.csv "-1" do |c|
63
+ content << c
64
+ end
65
+ resp = JSON.parse(content)
66
+ validate_response MetaHash.new(resp), 404, 'error', /list=-1 not found/
67
+ end
68
+
69
+ it 'should get contacts from suppressed list' do
70
+ content =''
71
+ PIGEON.lists.csv "suppress_list" do |c|
72
+ content += c
73
+ end
74
+ resp = content.split /\n/
75
+ resp.size.should eq 2
76
+ resp[1].should =~ /"suppressed@e.e","Suppressed","Doe"/
77
+ end
78
+
79
+ it 'should get single contact' do
80
+ resp = PIGEON.contacts.find_by_email 'suppressed@e.e'
81
+ resp.email.should eq 'suppressed@e.e'
82
+ end
83
+
84
+ it 'should not find non existent contact' do
85
+ resp = PIGEON.contacts.find_by_email 'a@a.a'
86
+ validate_response resp, 404, 'error', /contact=a@a.a not found/
87
+ end
88
+
89
+ it 'should update contact' do
90
+
91
+ list_response = PIGEON.lists.create('My List', 'John Doe', "a@a.a")
92
+ resp = PIGEON.contacts.upsert list_response.list.id,
93
+ :email => "mary@e.e", :first_name => "Mary", :last_name => "Doe"
94
+ validate_response resp, 200, 'success', /contact=mary@e.e created\/updated successfully/
95
+ PIGEON.contacts.find_by_email("mary@e.e").last_name.should eq 'Doe'
96
+
97
+ resp = PIGEON.contacts.upsert list_response.list.id,
98
+ :email => 'mary@e.e', :first_name => 'Mary', :last_name => 'Johns'
99
+ validate_response resp, 200, 'success', /contact=mary@e.e created\/updated successfully/
100
+ PIGEON.contacts.find_by_email("mary@e.e").last_name.should eq 'Johns'
101
+ end
102
+
103
+ it 'cannot delete contact with non-existent email' do
104
+ res = PIGEON.contacts.delete("g@g.g")
105
+ validate_response res, 404, 'error', /contact=g@g.g not found/
106
+ end
107
+
108
+ it 'should not delete suppressed contact' do
109
+ res = PIGEON.contacts.delete("suppressed@e.e")
110
+ validate_response res, 400, 'error', /contact=suppressed@e.e is in suppress list/
111
+ end
112
+
113
+ it 'should delete single contact from all lists' do
114
+ list_response = PIGEON.lists.create 'My List', 'Jane Doe', 'a@a.a'
115
+ PIGEON.contacts.upsert list_response.list.id, :email => 'mary@e.e'
116
+ res = PIGEON.contacts.delete 'mary@e.e'
117
+ validate_response res, 200, 'success', /contact=mary@e.e deleted successfully/
118
+ PIGEON.lists.delete list_response.list.id
119
+ end
120
+
121
+ it 'deletes single contact from single list' do
122
+ list_response = PIGEON.lists.create 'My List', 'John D.', 'a@a.a'
123
+ list_response_2 = PIGEON.lists.create('My List2', "Jane D.", 'a@a.a')
124
+ PIGEON.contacts.upsert(list_response.list.id, {:email => 'mary@e.e'})
125
+ PIGEON.contacts.upsert(list_response_2.list.id, {:email => 'mary@e.e'})
126
+
127
+ res = PIGEON.contacts.delete 'mary@e.e', list_response.list.id
128
+
129
+ validate_response res, 200, 'success', /contact=mary@e.e deleted successfully/
130
+
131
+ contacts_exported = ''
132
+ PIGEON.lists.csv list_response.list.id do |c|
133
+ contacts_exported << c
134
+ end
135
+ contacts_exported = contacts_exported.split /\n/
136
+ contacts_exported.size.should eq 1
137
+
138
+ contacts_exported_2 = ''
139
+ PIGEON.lists.csv list_response_2.list.id do |c|
140
+ contacts_exported_2 << c
141
+ end
142
+
143
+ contacts_exported_2 = contacts_exported_2.split /\n/
144
+ contacts_exported_2.size.should eq 2
145
+ contacts_exported_2[1].should =~ /"mary@e.e"/
146
+
147
+ PIGEON.lists.delete(list_response.list.id)
148
+ PIGEON.lists.delete(list_response_2.list.id)
149
+ PIGEON.contacts.delete('mary@e.e')
150
+ end
151
+ end
152
+
@@ -0,0 +1,149 @@
1
+ require './lib/expresspigeon-ruby'
2
+ require 'pigeon_helper'
3
+
4
+ describe 'contacts integration test' do
5
+
6
+ include PigeonSpecHelper
7
+
8
+ it 'test_create_and_delete_new_list(self):' do
9
+ contact_list = PIGEON.lists.create 'Active customers', 'Bob', 'bob@acmetools.com'
10
+
11
+ puts "*****************************"
12
+ puts contact_list
13
+
14
+ validate_response contact_list, 200, 'success', /list=#{contact_list.list.id} created\/updated successfully/
15
+ contact_list.list.name.should eq "Active customers"
16
+ contact_list.list.from_name.should eq "Bob"
17
+ contact_list.list.reply_to.should eq "bob@acmetools.com"
18
+ contact_list.list.contact_count.should eq 0
19
+
20
+ #TODO: uncomment when running against real test env
21
+ #contact_list.list.zip.should eq '220000'
22
+ #contact_list.list.state.should eq "AL"
23
+ #contact_list.list.address1.should eq "Coolman 11"
24
+ #contact_list.list.city.should eq "Minsk"
25
+ #contact_list.list.country.should eq "Belarus"
26
+ #contact_list.list.organization.should eq "ExpressPigeon"
27
+
28
+ res = PIGEON.lists.delete(contact_list.list.id)
29
+ validate_response res, 200, 'success', /list=#{contact_list.list.id} deleted successfully/
30
+ end
31
+
32
+ #TODO: implement Lists.update method
33
+ it 'should update existing list' do
34
+
35
+ existing_list = PIGEON.lists.create("Update", "Bob", "bob@acmetools.com")
36
+ #res = PIGEON.lists.update existing_list.list.id, :name => 'Updated Name', :from_name => 'Bill'
37
+ #
38
+ #validate_response res, 200, 'success', /list=#{res.list.id} created\/updated successfully/
39
+ #res.list.name.should eq "Updated Name"
40
+ #res.list.from_name.should eq 'Bill'
41
+ #PIGEON.lists.delete res.list.id
42
+ end
43
+
44
+
45
+ it 'should upload contacts'
46
+
47
+ list_name = "Upload_#{Kernel.rand(9999).to_s}"
48
+ existing_list = PIGEON.lists.create(list_name, 'Bob', 'bob@acmetools.com')
49
+
50
+ #res = PIGEON.lists.upload(existing_list.list.id, self.file_to_upload)
51
+
52
+
53
+ #self.assertEqual(res.status, "success")
54
+ #self.assertEqual(res.code, 200)
55
+ #self.assertEquals(res.message, "file uploaded successfully")
56
+ #self.assertTrue(res.upload_id is not None)
57
+ #
58
+ #sleep(5)
59
+ #
60
+ #res = self.api.lists.upload_status(res.upload_id)
61
+ #self.assertEqual(res.message, "file upload completed")
62
+ #self.assertEqual(res.status, "success")
63
+ #self.assertEqual(res.code, 200)
64
+ #report = res.report
65
+ #self.assertTrue(report.completed)
66
+ #self.assertFalse(report.failed)
67
+ #self.assertEqual(report.suppressed, 0)
68
+ #self.assertEqual(report.skipped, 0)
69
+ #self.assertEqual(report.list_name, list_name)
70
+ #self.assertEqual(report.imported, 2)
71
+
72
+ # def test_upsert_list_with_non_existent_id(self):
73
+ # res = self.api.lists.update(-1, {"name": "Updated Name", "from_name": "Bill"})
74
+ # self.assertEqual(res.status, "error")
75
+ # self.assertEqual(res.code, 404)
76
+ # self.assertEqual(res.message, "list=-1 not found")
77
+ #
78
+ # def test_delete_list_with_non_existent_id(self):
79
+ # res = self.api.lists.delete(-1)
80
+ # self.assertEqual(res.status, "error")
81
+ # self.assertEqual(res.code, 404)
82
+ # self.assertEqual(res.message, "list=-1 not found")
83
+ #
84
+ # def test_remove_disabled_list(self):
85
+ # res = self.api.lists.delete(130)
86
+ # self.assertEqual(res.code, 400)
87
+ # self.assertEqual(res.status, "error")
88
+ # self.assertEqual(res.message, "could not delete disabled list=130")
89
+ #
90
+ # def test_upload_without_id(self):
91
+ # res = self.api.lists.upload("", self.file_to_upload)
92
+ # self.assertEqual(res.code, 400)
93
+ # self.assertEqual(res.status, "error")
94
+ # self.assertEqual(res.message, "you must provide list_id in URL")
95
+ #
96
+ # def test_upload_with_non_existent_id(self):
97
+ # res = self.api.lists.upload(-1, self.file_to_upload)
98
+ # self.assertEqual(res.code, 404)
99
+ # self.assertEqual(res.message, "list=-1 not found")
100
+ #
101
+ # def test_upload_status_without_upload_id(self):
102
+ # res = self.api.lists.upload_status("")
103
+ # self.assertEqual(res.code, 400)
104
+ # self.assertEqual(res.status, "error")
105
+ # self.assertEqual(res.message, "you must provide upload id")
106
+ #
107
+ # def test_enabled_list_removal(self):
108
+ # list_resp = self.api.lists.create("My list", "John", os.environ['EXPRESSPIGEON_API_USER'])
109
+ # self.api.contacts.upsert(list_resp.list.id, {"email": os.environ['EXPRESSPIGEON_API_USER']})
110
+ #
111
+ # now = datetime.datetime.now(pytz.UTC)
112
+ # schedule = self.format_date(now + datetime.timedelta(hours=1))
113
+ #
114
+ # res = self.api.campaigns.schedule(list_id=list_resp.list.id, template_id=self.template_id, name="My Campaign",
115
+ # from_name="John",
116
+ # reply_to=os.environ['EXPRESSPIGEON_API_USER'], subject="Hi",
117
+ # google_analytics=False,
118
+ # schedule_for=schedule)
119
+ # self.assertEqual(res.code, 200)
120
+ # self.assertEqual(res.status, "success")
121
+ # self.assertEqual(res.message, "new campaign created successfully")
122
+ # self.assertTrue(res.campaign_id is not None)
123
+ #
124
+ # res = self.api.lists.delete(list_resp.list.id)
125
+ # self.assertEqual(res.code, 400)
126
+ # self.assertEqual(res.status, "error")
127
+ # self.assertEqual(res.message,
128
+ # "could not delete list={0}, it has dependent subscriptions and/or scheduled campaigns".format(
129
+ # list_resp.list.id))
130
+ #
131
+ # def test_export_csv(self):
132
+ # list_response = self.api.lists.create("My List", "a@a.a", "a@a.a")
133
+ # self.api.contacts.upsert(list_response.list.id, {"email": "mary@a.a"})
134
+ #
135
+ # res = self.api.lists.csv(list_response.list.id).split("\n")
136
+ # self.assertEquals(len(res), 2)
137
+ # headers = '"Email", "First name", "Last name", "City", "Phone", "Company", "Title", "Address 1", "Address 2", ' \
138
+ # '"State", "Zip", "Country", "Date of birth", "custom_field_1", "custom_field_10", "custom_field_11", ' \
139
+ # '"custom_field_12", "custom_field_13", "custom_field_18", "custom_field_19", "custom_field_2", ' \
140
+ # '"custom_field_20", "custom_field_21", "custom_field_22", "custom_field_23", "custom_field_24", ' \
141
+ # '"custom_field_3", "custom_field_4", "custom_field_5", "custom_field_6", "custom_field_7", ' \
142
+ # '"custom_field_8", "custom_field_9"'
143
+ # self.assertEquals(res[0], headers)
144
+ # self.assertEquals(res[1], '"mary@a.a",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,')
145
+ #
146
+ # self.api.lists.delete(list_response.list.id)
147
+ # self.api.contacts.delete("mary@a.a")
148
+
149
+ end
@@ -0,0 +1,127 @@
1
+ require './lib/expresspigeon-ruby'
2
+ require 'pigeon_helper'
3
+
4
+ describe 'transactional messages integration test' do
5
+
6
+ include PigeonSpecHelper
7
+
8
+ #def test_sending_message_and_report_without_params(self):
9
+ # res = self.api.messages.send_message(template_id=-1, to="", reply_to="", from_name="", subject="")
10
+ # self.assertEqual(res.code, 400)
11
+ # self.assertEqual(res.status, "error")
12
+ # self.assertEqual(res.message, "Required fields: template_id, reply_to, from, to, and subject")
13
+ #
14
+ #def test_sending_message_and_report_with_wrong_email_in_to(self):
15
+ # res = self.api.messages.send_message(template_id=-1, to="e", reply_to="a@a.a", from_name="me", subject="Hi")
16
+ # self.assertEqual(res.code, 400)
17
+ # self.assertEqual(res.status, "error")
18
+ # self.assertEqual(res.message, "Email in the 'to' field is not valid")
19
+ #
20
+ #def test_sending_message_and_report_with_wrong_email_in_reply_to(self):
21
+ # res = self.api.messages.send_message(template_id=-1, to="e@e.e", reply_to="a", from_name="me", subject="Hi")
22
+ # self.assertEqual(res.code, 400)
23
+ # self.assertEqual(res.status, "error")
24
+ # self.assertEqual(res.message, "Email in the 'reply_to' field is not valid")
25
+ #
26
+ #def test_sending_message_and_report_with_wrong_template_id(self):
27
+ # res = self.api.messages.send_message(template_id=-1, to="e@e.e", reply_to="a@a.a", from_name="me", subject="Hi")
28
+ # self.assertEqual(res.code, 400)
29
+ # self.assertEqual(res.status, "error")
30
+ # self.assertEqual(res.message, "template=-1 not found")
31
+
32
+
33
+ #TODO: complete the spec
34
+ it 'sends a single transactional message' do
35
+ message_response = PIGEON.messages.send_message 4905, ENV['TARGET_EMAIL'], ENV['TARGET_EMAIL'], "Team ExpressPigeon", "Hi there!",
36
+ :first_name => "Igor"
37
+ validate_response message_response, 200, 'success', /email queued/
38
+ report = PIGEON.messages.report(message_response.id)
39
+ report.id.should eq message_response.id
40
+ end
41
+
42
+
43
+ #
44
+ #def test_reports_with_bad_dates(self):
45
+ # res = self.api.messages.reports("abc", "")
46
+ # self.assertEquals(res.code, 400)
47
+ # self.assertEquals(res.status, "error")
48
+ # self.assertEquals(res.message, "invalid 'start_date' or 'end_date'")
49
+ #
50
+ #def test_reports_with_start_date_only(self):
51
+ # res = self.api.messages.reports("2013-03-16T11:22:23.210+0000", "")
52
+ # self.assertEquals(res.code, 400)
53
+ # self.assertEquals(res.status, "error")
54
+ # self.assertEquals(res.message, "'start_date' and 'end_date' should be provided together")
55
+ #
56
+ #def test_reports_with_end_date_only(self):
57
+ # res = self.api.messages.reports("", "2013-03-16T11:22:23.210+0000")
58
+ # self.assertEquals(res.code, 400)
59
+ # self.assertEquals(res.status, "error")
60
+ # self.assertEquals(res.message, "'start_date' and 'end_date' should be provided together")
61
+ #
62
+ #def test_sending_multiple_messages_and_get_reports(self):
63
+ # message_response = self.api.messages.send_message(template_id=self.template_id,
64
+ # to=os.environ['EXPRESSPIGEON_API_USER'],
65
+ # reply_to="a@a.a", from_name="me", subject="Hi",
66
+ # merge_fields={"first_name": "Gleb"})
67
+ # self.assertEqual(message_response.code, 200)
68
+ # self.assertEqual(message_response.status, "success")
69
+ # self.assertEqual(message_response.message, "email queued")
70
+ # self.assertTrue(message_response.id is not None and message_response.id != "")
71
+ #
72
+ # message_response_2 = self.api.messages.send_message(template_id=self.template_id,
73
+ # to=os.environ['EXPRESSPIGEON_API_USER'],
74
+ # reply_to="a@a.a", from_name="me", subject="Hi 2",
75
+ # merge_fields={"first_name": "Gleb"})
76
+ # self.assertEqual(message_response_2.code, 200)
77
+ # self.assertEqual(message_response_2.status, "success")
78
+ # self.assertEqual(message_response_2.message, "email queued")
79
+ # self.assertTrue(message_response_2.id is not None and message_response.id != "")
80
+ #
81
+ # report = self.__get_report_by_id__(message_response.id)
82
+ #
83
+ # self.assertEquals(report.id, message_response.id)
84
+ # self.assertEquals(report.email, os.environ['EXPRESSPIGEON_API_USER'])
85
+ # self.assertTrue(report.in_transit is not None)
86
+ #
87
+ # report2 = self.__get_report_by_id__(message_response_2.id)
88
+ #
89
+ # self.assertEquals(report2.id, message_response_2.id)
90
+ # self.assertEquals(report2.email, os.environ['EXPRESSPIGEON_API_USER'])
91
+ # self.assertTrue(report2.in_transit is not None)
92
+ #
93
+ it 'test_sending_multiple_messages_and_get_reports_for_today(self):' do
94
+
95
+ start = Time.now.utc - 60 # one minute ago
96
+
97
+ message_response = PIGEON.messages.send_message 4905, ENV['TARGET_EMAIL'], ENV['TARGET_EMAIL'],
98
+ 'Team EP', "Hi, there!", :first_name => "Bob"
99
+
100
+ validate_response message_response, 200, 'success', /email queued/
101
+ message_response.id should_not be_nil
102
+
103
+ message_response2 = PIGEON.messages.send_message 4905, ENV['TARGET_EMAIL'], ENV['TARGET_EMAIL'],
104
+ 'Team EP', "Hi, there!", :first_name => "Bob"
105
+ validate_response message_response2, 200, 'success', /email queued/
106
+ message_response2.id should_not be_nil
107
+
108
+ finish = start + 120 # two minutes after start
109
+ reports = PIGEON.messages.reports (message_response.id - 1), start, finish
110
+
111
+ reports.size.should eq 2
112
+ reports[0]['id'].should eq message_response.id
113
+ reports[1]['id'].should eq message_response2.id
114
+
115
+ reports[0]['email'].should eq ENV['TARGET_EMAIL']
116
+ reports[1]['email'].should eq ENV['TARGET_EMAIL']
117
+ end
118
+ #
119
+ #def __get_report_by_id__(self, message_id, start_date=None, end_date=None):
120
+ # reports = self.api.messages.reports() if start_date is None and end_date is None else \
121
+ # self.api.messages.reports(start_date, end_date)
122
+ # report = [r for r in reports if r.id == message_id]
123
+ # self.assertEquals(len(report), 1)
124
+ # return report[0]
125
+
126
+
127
+ end
@@ -0,0 +1,16 @@
1
+ PIGEON ||= ExpressPigeon::API
2
+ TEMPLATE_ID ||= 1
3
+ LIST_ID ||= 12
4
+ API_USER ||= "non@non.non"
5
+ DISABLED_LIST ||= 12
6
+
7
+ module PigeonSpecHelper
8
+ def validate_response res, code, status, message
9
+ res.code.should eq code
10
+ res.status.should eq status
11
+ if message
12
+ (res.message =~ message).should_not be_nil
13
+ end
14
+
15
+ end
16
+ end
metadata ADDED
@@ -0,0 +1,82 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: expresspigeon-ruby
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - ipolevoy
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2014-01-28 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '2.6'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '2.6'
30
+ description: ExpressPigeon Ruby API for sending transactional messages, manipulating
31
+ lists, contacts nad more
32
+ email:
33
+ - ipolevoy@groupon.com
34
+ executables: []
35
+ extensions: []
36
+ extra_rdoc_files: []
37
+ files:
38
+ - .gitignore
39
+ - .rvmrc
40
+ - Gemfile
41
+ - Gemfile.lock
42
+ - LICENSE.txt
43
+ - README.md
44
+ - Rakefile
45
+ - expresspigeon-ruby.gemspec
46
+ - lib/expresspigeon-ruby.rb
47
+ - lib/expresspigeon-ruby/version.rb
48
+ - spec/campaigns_spec.rb
49
+ - spec/contacts_spec.rb
50
+ - spec/lists_spec.rb
51
+ - spec/messages_spec.rb
52
+ - spec/pigeon_helper.rb
53
+ homepage: https://github.com/expresspigeon/expresspigeon-ruby
54
+ licenses: []
55
+ post_install_message:
56
+ rdoc_options: []
57
+ require_paths:
58
+ - lib
59
+ required_ruby_version: !ruby/object:Gem::Requirement
60
+ none: false
61
+ requirements:
62
+ - - ! '>='
63
+ - !ruby/object:Gem::Version
64
+ version: '0'
65
+ required_rubygems_version: !ruby/object:Gem::Requirement
66
+ none: false
67
+ requirements:
68
+ - - ! '>='
69
+ - !ruby/object:Gem::Version
70
+ version: '0'
71
+ requirements: []
72
+ rubyforge_project:
73
+ rubygems_version: 1.8.25
74
+ signing_key:
75
+ specification_version: 3
76
+ summary: ExpressPigeon API Ruby Wrapper
77
+ test_files:
78
+ - spec/campaigns_spec.rb
79
+ - spec/contacts_spec.rb
80
+ - spec/lists_spec.rb
81
+ - spec/messages_spec.rb
82
+ - spec/pigeon_helper.rb