customerio 2.2.1 → 4.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,11 @@
1
+ require 'net/http'
2
+ require 'multi_json'
3
+
4
+ module Customerio
5
+ module Regions
6
+ Region = Struct.new(:track_url, :api_url)
7
+
8
+ US = Customerio::Regions::Region.new('https://track.customer.io', 'https://api.customer.io').freeze
9
+ EU = Customerio::Regions::Region.new('https://track-eu.customer.io', 'https://api-eu.customer.io').freeze
10
+ end
11
+ end
@@ -0,0 +1,49 @@
1
+ require 'base64'
2
+
3
+ module Customerio
4
+ class SendEmailRequest
5
+ attr_reader :message
6
+
7
+ def initialize(opts)
8
+ @message = opts.delete_if { |field| invalid_field?(field) }
9
+ @message[:attachments] = {}
10
+ @message[:headers] = {}
11
+ end
12
+
13
+ def attach(name, data, encode: true)
14
+ raise "attachment #{name} already exists" if @message[:attachments].has_key?(name)
15
+ @message[:attachments][name] = encode ? Base64.strict_encode64(data) : data
16
+ end
17
+
18
+ private
19
+
20
+ REQUIRED_FIELDS = %i(to identifiers)
21
+
22
+ OPTIONAL_FIELDS = %i(
23
+ transactional_message_id
24
+ message_data
25
+ headers
26
+ preheader
27
+ from
28
+ reply_to
29
+ bcc
30
+ subject
31
+ body
32
+ plaintext_body
33
+ amp_body
34
+ fake_bcc
35
+ disable_message_retention
36
+ send_to_unsubscribed
37
+ tracked
38
+ queue_draft
39
+ )
40
+
41
+ def invalid_field?(field)
42
+ !REQUIRED_FIELDS.include?(field) && !OPTIONAL_FIELDS.include?(field)
43
+ end
44
+
45
+ def encode(data)
46
+ Base64.strict_encode64(data)
47
+ end
48
+ end
49
+ end
@@ -1,3 +1,3 @@
1
1
  module Customerio
2
- VERSION = "2.2.1"
2
+ VERSION = "4.0.1"
3
3
  end
@@ -0,0 +1,172 @@
1
+ require 'spec_helper'
2
+ require 'multi_json'
3
+ require 'base64'
4
+ require 'tempfile'
5
+
6
+ describe Customerio::APIClient do
7
+ let(:app_key) { "appkey" }
8
+
9
+ let(:client) { Customerio::APIClient.new(app_key) }
10
+ let(:response) { double("Response", code: 200) }
11
+
12
+ def api_uri(path)
13
+ "https://api.customer.io#{path}"
14
+ end
15
+
16
+ def request_headers
17
+ { 'Authorization': "Bearer #{app_key}", 'Content-Type': 'application/json' }
18
+ end
19
+
20
+ def json(data)
21
+ MultiJson.dump(data)
22
+ end
23
+
24
+ it "the base client is initialised with the correct values when no region is passed in" do
25
+ app_key = "appkey"
26
+
27
+ expect(Customerio::BaseClient).to(
28
+ receive(:new)
29
+ .with(
30
+ { app_key: app_key },
31
+ {
32
+ region: Customerio::Regions::US,
33
+ url: Customerio::Regions::US.api_url
34
+ }
35
+ )
36
+ )
37
+
38
+ client = Customerio::APIClient.new(app_key)
39
+ end
40
+
41
+ it "raises an error when an incorrect region is passed in" do
42
+ expect {
43
+ Customerio::APIClient.new("appkey", region: :au)
44
+ }.to raise_error /region must be an instance of Customerio::Regions::Region/
45
+ end
46
+
47
+ [Customerio::Regions::US, Customerio::Regions::EU].each do |region|
48
+ it "the base client is initialised with the correct values when the region \"#{region}\" is passed in" do
49
+ app_key = "appkey"
50
+
51
+ expect(Customerio::BaseClient).to(
52
+ receive(:new)
53
+ .with(
54
+ { app_key: app_key },
55
+ {
56
+ region: region,
57
+ url: region.api_url
58
+ }
59
+ )
60
+ )
61
+
62
+ client = Customerio::APIClient.new(app_key, { region: region })
63
+ end
64
+ end
65
+
66
+ describe "#send_email" do
67
+ it "sends a POST request to the /api/send/email path" do
68
+ req = Customerio::SendEmailRequest.new(
69
+ identifiers: {
70
+ id: 'c1',
71
+ },
72
+ transactional_message_id: 1,
73
+ )
74
+
75
+ stub_request(:post, api_uri('/v1/send/email'))
76
+ .with(headers: request_headers, body: req.message)
77
+ .to_return(status: 200, body: { delivery_id: 1 }.to_json, headers: {})
78
+
79
+ client.send_email(req).should eq({ "delivery_id" => 1 })
80
+ end
81
+
82
+ it "handles validation failures (400)" do
83
+ req = Customerio::SendEmailRequest.new(
84
+ identifiers: {
85
+ id: 'c1',
86
+ },
87
+ transactional_message_id: 1,
88
+ )
89
+
90
+ err_json = { meta: { error: "example error" } }.to_json
91
+
92
+ stub_request(:post, api_uri('/v1/send/email'))
93
+ .with(headers: request_headers, body: req.message)
94
+ .to_return(status: 400, body: err_json, headers: {})
95
+
96
+ lambda { client.send_email(req) }.should(
97
+ raise_error(Customerio::InvalidResponse) { |error|
98
+ error.message.should eq "example error"
99
+ error.code.should eq "400"
100
+ }
101
+ )
102
+ end
103
+
104
+ it "handles other failures (5xx)" do
105
+ req = Customerio::SendEmailRequest.new(
106
+ identifiers: {
107
+ id: 'c1',
108
+ },
109
+ transactional_message_id: 1,
110
+ )
111
+
112
+ stub_request(:post, api_uri('/v1/send/email'))
113
+ .with(headers: request_headers, body: req.message)
114
+ .to_return(status: 500, body: "Server unavailable", headers: {})
115
+
116
+ lambda { client.send_email(req) }.should(
117
+ raise_error(Customerio::InvalidResponse) { |error|
118
+ error.message.should eq "Server unavailable"
119
+ error.code.should eq "500"
120
+ }
121
+ )
122
+ end
123
+
124
+ it "allows attaching file content without encoding" do
125
+ content = 'sample content'
126
+
127
+ req = Customerio::SendEmailRequest.new(
128
+ customer_id: 'c1',
129
+ transactional_message_id: 1,
130
+ )
131
+
132
+ req.attach('test', content, encode: false)
133
+ req.message[:attachments]['test'].should eq content
134
+
135
+ stub_request(:post, api_uri('/v1/send/email'))
136
+ .with(headers: request_headers, body: req.message)
137
+ .to_return(status: 200, body: { delivery_id: 1 }.to_json, headers: {})
138
+
139
+ client.send_email(req)
140
+ end
141
+
142
+ it "allows attaching files with encoding (default)" do
143
+ content = 'sample content'
144
+
145
+ req = Customerio::SendEmailRequest.new(
146
+ customer_id: 'c1',
147
+ transactional_message_id: 1,
148
+ )
149
+
150
+ req.attach('test', content)
151
+ req.message[:attachments]['test'].should eq Base64.strict_encode64(content)
152
+
153
+ stub_request(:post, api_uri('/v1/send/email'))
154
+ .with(headers: request_headers, body: req.message)
155
+ .to_return(status: 200, body: { delivery_id: 1 }.to_json, headers: {})
156
+
157
+ client.send_email(req)
158
+ end
159
+
160
+ it "raises error when attaching the same key again" do
161
+ req = Customerio::SendEmailRequest.new(
162
+ customer_id: 'c1',
163
+ transactional_message_id: 1,
164
+ )
165
+
166
+ req.attach('test', 'test-content')
167
+
168
+ lambda { req.attach('test', '') }.should raise_error(/attachment test already exists/)
169
+ req.message[:attachments].should eq({ "test" => Base64.strict_encode64("test-content") })
170
+ end
171
+ end
172
+ end
@@ -0,0 +1,67 @@
1
+ require 'spec_helper'
2
+ require 'multi_json'
3
+ require 'base64'
4
+
5
+ describe Customerio::BaseClient do
6
+ let(:url) { "https://test.customer.io" }
7
+
8
+ let(:site_id) { "SITE_ID" }
9
+ let(:api_key) { "API_KEY" }
10
+ let(:track_client) { Customerio::BaseClient.new({ site_id: site_id, api_key: api_key }, { url: url }) }
11
+
12
+ let(:app_key) { "APP_KEY" }
13
+ let(:api_client) { Customerio::BaseClient.new({ app_key: app_key }, { url: url }) }
14
+
15
+ def api_uri(path)
16
+ "#{url}#{path}"
17
+ end
18
+
19
+ def track_client_request_headers
20
+ token = Base64.strict_encode64("#{site_id}:#{api_key}")
21
+ { 'Authorization': "Basic #{token}", 'Content-Type': 'application/json' }
22
+ end
23
+
24
+ def api_client_request_headers
25
+ { 'Authorization': "Bearer #{app_key}", 'Content-Type': 'application/json' }
26
+ end
27
+
28
+ describe "with a site ID and API key" do
29
+ it "uses the correct basic auth" do
30
+ stub_request(:put, api_uri('/some/path')).
31
+ with(headers: track_client_request_headers).
32
+ to_return(status: 200, body: "", headers: {})
33
+
34
+ track_client.request(:put, '/some/path', "")
35
+ end
36
+ end
37
+
38
+ describe "with an app key" do
39
+ it "uses the correct bearer token" do
40
+ stub_request(:put, api_uri('/some/path')).
41
+ with(headers: api_client_request_headers).
42
+ to_return(status: 200, body: "", headers: {})
43
+
44
+ api_client.request(:put, '/some/path', "")
45
+ end
46
+ end
47
+
48
+ describe "#verify_response" do
49
+ it "throws an error when the response isn't between 200 and 300" do
50
+ stub_request(:put, api_uri('/some/path')).
51
+ with(headers: api_client_request_headers).
52
+ to_return(status: 400, body: "", headers: {})
53
+
54
+ lambda { api_client.request_and_verify_response(:put, '/some/path', "") }.should(
55
+ raise_error(Customerio::InvalidResponse)
56
+ )
57
+ end
58
+
59
+ it "returns the response when the status is 200" do
60
+ stub_request(:put, api_uri('/some/path')).
61
+ with(headers: api_client_request_headers).
62
+ to_return(status: 200, body: "Test", headers: {})
63
+
64
+ api_client.request_and_verify_response(:put, '/some/path', "").body.should eq("Test")
65
+ end
66
+ end
67
+ end
data/spec/client_spec.rb CHANGED
@@ -1,39 +1,92 @@
1
1
  require 'spec_helper'
2
2
  require 'multi_json'
3
-
3
+ require 'base64'
4
4
 
5
5
  describe Customerio::Client do
6
- let(:client) { Customerio::Client.new("SITE_ID", "API_KEY", :json => false) }
7
- let(:response) { double("Response", :code => 200) }
6
+ let(:site_id) { "SITE_ID" }
7
+ let(:api_key) { "API_KEY" }
8
+
9
+ let(:client) { Customerio::Client.new(site_id, api_key) }
10
+ let(:response) { double("Response", code: 200) }
8
11
 
9
12
  def api_uri(path)
10
- "https://SITE_ID:API_KEY@track.customer.io#{path}"
13
+ "https://track.customer.io#{path}"
14
+ end
15
+
16
+ def request_headers
17
+ token = Base64.strict_encode64("#{site_id}:#{api_key}")
18
+ { 'Authorization': "Basic #{token}", 'Content-Type': 'application/json' }
11
19
  end
12
20
 
13
21
  def json(data)
14
22
  MultiJson.dump(data)
15
23
  end
16
24
 
17
- describe "json option" do
18
- let(:body) { { :id => 5, :name => "Bob" } }
25
+ it "the base client is initialised with the correct values when no region is passed in" do
26
+ site_id = "SITE_ID"
27
+ api_key = "API_KEY"
28
+
29
+ expect(Customerio::BaseClient).to(
30
+ receive(:new)
31
+ .with(
32
+ { site_id: site_id, api_key: api_key },
33
+ {
34
+ region: Customerio::Regions::US,
35
+ url: Customerio::Regions::US.track_url
36
+ }
37
+ )
38
+ )
19
39
 
20
- it "uses json by default" do
21
- client = Customerio::Client.new("SITE_ID", "API_KEY")
40
+ client = Customerio::Client.new(site_id, api_key)
41
+ end
22
42
 
23
- stub_request(:put, api_uri('/api/v1/customers/5')).
24
- with(:body => json(body),
25
- :headers => {'Content-Type'=>'application/json'}).
26
- to_return(:status => 200, :body => "", :headers => {})
43
+ it "raises an error when an incorrect region is passed in" do
44
+ expect {
45
+ Customerio::Client.new("siteid", "apikey", region: :au)
46
+ }.to raise_error /region must be an instance of Customerio::Regions::Region/
47
+ end
27
48
 
28
- client.identify(body)
49
+ [Customerio::Regions::US, Customerio::Regions::EU].each do |region|
50
+ it "the base client is initialised with the correct values when the region \"#{region}\" is passed in" do
51
+ site_id = "SITE_ID"
52
+ api_key = "API_KEY"
53
+
54
+ expect(Customerio::BaseClient).to(
55
+ receive(:new)
56
+ .with(
57
+ { site_id: site_id, api_key: api_key },
58
+ {
59
+ region: region,
60
+ url: region.track_url
61
+ }
62
+ )
63
+ )
64
+
65
+ client = Customerio::Client.new(site_id, api_key, { region: region })
29
66
  end
67
+ end
30
68
 
31
- it "allows disabling json" do
32
- client = Customerio::Client.new("SITE_ID", "API_KEY", :json => false)
69
+ it "uses json by default" do
70
+ body = { id: 5, name: "Bob" }
71
+ client = Customerio::Client.new("SITE_ID", "API_KEY")
33
72
 
34
- stub_request(:put, api_uri('/api/v1/customers/5')).
35
- with(:body => { :id => "5", :name => "Bob" }).
36
- to_return(:status => 200, :body => "", :headers => {})
73
+ stub_request(:put, api_uri('/api/v1/customers/5')).
74
+ with(body: json(body),
75
+ headers: {'Content-Type'=>'application/json'}).
76
+ to_return(status: 200, body: "", headers: {})
77
+
78
+ client.identify(body)
79
+ end
80
+
81
+ describe "headers" do
82
+ let(:body) { { id: 1, token: :test } }
83
+
84
+ it "sends the basic headers, base64 encoded with the request" do
85
+ client = Customerio::Client.new("SITE_ID", "API_KEY")
86
+
87
+ stub_request(:put, api_uri('/api/v1/customers/1')).
88
+ with(body: json(body), headers: request_headers).
89
+ to_return(status: 200, body: "", headers: {})
37
90
 
38
91
  client.identify(body)
39
92
  end
@@ -42,41 +95,54 @@ describe Customerio::Client do
42
95
  describe "#identify" do
43
96
  it "sends a PUT request to customer.io's customer API" do
44
97
  stub_request(:put, api_uri('/api/v1/customers/5')).
45
- with(:body => "id=5").
46
- to_return(:status => 200, :body => "", :headers => {})
98
+ with(body: json(id: "5")).
99
+ to_return(status: 200, body: "", headers: {})
47
100
 
48
- client.identify(:id => 5)
101
+ client.identify(id: "5")
102
+ end
103
+
104
+ it "escapes customer IDs" do
105
+ stub_request(:put, api_uri('/api/v1/customers/5%20')).
106
+ with(body: json({ id: "5 " })).
107
+ to_return(status: 200, body: "", headers: {})
108
+
109
+ client.identify(id: "5 ")
110
+
111
+ stub_request(:put, api_uri('/api/v1/customers/5%2F')).
112
+ with(body: { id: "5/" }).
113
+ to_return(status: 200, body: "", headers: {})
114
+ client.identify(id: "5/")
49
115
  end
50
116
 
51
117
  it "sends a PUT request to customer.io's customer API using json headers" do
52
- client = Customerio::Client.new("SITE_ID", "API_KEY", :json => true)
53
- body = { :id => 5, :name => "Bob" }
118
+ client = Customerio::Client.new("SITE_ID", "API_KEY", json: true)
119
+ body = { id: 5, name: "Bob" }
54
120
 
55
121
  stub_request(:put, api_uri('/api/v1/customers/5')).
56
- with(:body => json(body),
57
- :headers => {'Content-Type'=>'application/json'}).
58
- to_return(:status => 200, :body => "", :headers => {})
122
+ with(body: json(body),
123
+ headers: {'Content-Type'=>'application/json'}).
124
+ to_return(status: 200, body: "", headers: {})
59
125
 
60
126
  client.identify(body)
61
127
  end
62
128
 
63
129
  it "raises an error if PUT doesn't return a 2xx response code" do
64
130
  stub_request(:put, api_uri('/api/v1/customers/5')).
65
- with(:body => "id=5").
66
- to_return(:status => 500, :body => "", :headers => {})
131
+ with(body: json(id: 5)).
132
+ to_return(status: 500, body: "", headers: {})
67
133
 
68
- lambda { client.identify(:id => 5) }.should raise_error(Customerio::Client::InvalidResponse)
134
+ lambda { client.identify(id: 5) }.should raise_error(Customerio::InvalidResponse)
69
135
  end
70
136
 
71
137
  it "includes the HTTP response with raised errors" do
72
138
  stub_request(:put, api_uri('/api/v1/customers/5')).
73
- with(:body => "id=5").
74
- to_return(:status => 500, :body => "whatever", :headers => {})
139
+ with(body: json(id: 5)).
140
+ to_return(status: 500, body: "Server unavailable", headers: {})
75
141
 
76
- lambda { client.identify(:id => 5) }.should raise_error {|error|
77
- error.should be_a Customerio::Client::InvalidResponse
78
- error.response.code.should eq "500"
79
- error.response.body.should eq "whatever"
142
+ lambda { client.identify(id: 5) }.should raise_error {|error|
143
+ error.should be_a Customerio::InvalidResponse
144
+ error.code.should eq "500"
145
+ error.message.should eq "Server unavailable"
80
146
  }
81
147
  end
82
148
 
@@ -84,31 +150,34 @@ describe Customerio::Client do
84
150
  time = Time.now.to_i
85
151
 
86
152
  stub_request(:put, api_uri('/api/v1/customers/5')).with(
87
- :body => {
88
- :id => "5",
89
- :email => "customer@example.com",
90
- :created_at => time.to_s,
91
- :first_name => "Bob",
92
- :plan => "basic"
93
- }).to_return(:status => 200, :body => "", :headers => {})
153
+ body: {
154
+ id: 5,
155
+ email: "customer@example.com",
156
+ created_at: time,
157
+ first_name: "Bob",
158
+ plan: "basic",
159
+ anonymous_id: "anon-id"
160
+ }).to_return(status: 200, body: "", headers: {})
94
161
 
95
162
  client.identify({
96
- :id => 5,
97
- :email => "customer@example.com",
98
- :created_at => time,
99
- :first_name => "Bob",
100
- :plan => "basic"
163
+ id: 5,
164
+ anonymous_id: "anon-id",
165
+ email: "customer@example.com",
166
+ created_at: time,
167
+ first_name: "Bob",
168
+ plan: "basic"
101
169
  })
102
170
  end
103
171
 
104
172
  it "requires an id attribute" do
105
- lambda { client.identify(:email => "customer@example.com") }.should raise_error(Customerio::Client::MissingIdAttributeError)
173
+ lambda { client.identify(email: "customer@example.com") }.should raise_error(Customerio::Client::MissingIdAttributeError)
174
+ lambda { client.identify(id: "") }.should raise_error(Customerio::Client::MissingIdAttributeError)
106
175
  end
107
176
 
108
177
  it 'should not raise errors when attribute keys are strings' do
109
178
  stub_request(:put, api_uri('/api/v1/customers/5')).
110
- with(:body => "id=5").
111
- to_return(:status => 200, :body => "", :headers => {})
179
+ with(body: json(id: 5)).
180
+ to_return(status: 200, body: "", headers: {})
112
181
 
113
182
  attributes = { "id" => 5 }
114
183
 
@@ -119,257 +188,265 @@ describe Customerio::Client do
119
188
  describe "#delete" do
120
189
  it "sends a DELETE request to the customer.io's event API" do
121
190
  stub_request(:delete, api_uri('/api/v1/customers/5')).
122
- to_return(:status => 200, :body => "", :headers => {})
191
+ to_return(status: 200, body: "", headers: {})
123
192
 
124
193
  client.delete(5)
125
194
  end
195
+
196
+ it "throws an error when customer_id is missing" do
197
+ stub_request(:put, /track.customer.io/)
198
+ .to_return(status: 200, body: "", headers: {})
199
+
200
+ lambda { client.delete(" ") }.should raise_error(Customerio::Client::ParamError, "customer_id must be a non-empty string")
201
+ end
202
+
203
+ it "escapes customer IDs" do
204
+ stub_request(:delete, api_uri('/api/v1/customers/5%20')).
205
+ to_return(status: 200, body: "", headers: {})
206
+
207
+ client.delete("5 ")
208
+ end
126
209
  end
127
210
 
128
211
  describe "#suppress" do
129
212
  it "sends a POST request to the customer.io's suppress API" do
130
213
  stub_request(:post, api_uri('/api/v1/customers/5/suppress')).
131
- to_return(:status => 200, :body => "", :headers => {})
214
+ to_return(status: 200, body: "", headers: {})
132
215
 
133
216
  client.suppress(5)
134
217
  end
218
+
219
+ it "throws an error when customer_id is missing" do
220
+ stub_request(:put, /track.customer.io/)
221
+ .to_return(status: 200, body: "", headers: {})
222
+
223
+ lambda { client.suppress(" ") }.should raise_error(Customerio::Client::ParamError, "customer_id must be a non-empty string")
224
+ end
135
225
  end
136
226
 
137
227
  describe "#unsuppress" do
138
228
  it "sends a POST request to the customer.io's unsuppress API" do
139
229
  stub_request(:post, api_uri('/api/v1/customers/5/unsuppress')).
140
- to_return(:status => 200, :body => "", :headers => {})
230
+ to_return(status: 200, body: "", headers: {})
141
231
 
142
232
  client.unsuppress(5)
143
233
  end
234
+
235
+ it "throws an error when customer_id is missing" do
236
+ stub_request(:put, /track.customer.io/)
237
+ .to_return(status: 200, body: "", headers: {})
238
+
239
+ lambda { client.suppress(" ") }.should raise_error(Customerio::Client::ParamError, "customer_id must be a non-empty string")
240
+ end
144
241
  end
145
242
 
146
243
  describe "#track" do
147
244
  it "raises an error if POST doesn't return a 2xx response code" do
148
245
  stub_request(:post, api_uri('/api/v1/customers/5/events')).
149
- with(:body => "name=purchase").
150
- to_return(:status => 500, :body => "", :headers => {})
246
+ with(body: json(name: "purchase", data: {})).
247
+ to_return(status: 500, body: "", headers: {})
248
+
249
+ lambda { client.track(5, "purchase") }.should raise_error(Customerio::InvalidResponse)
250
+ end
251
+
252
+ it "throws an error when customer_id or event_name is missing" do
253
+ stub_request(:put, /track.customer.io/)
254
+ .to_return(status: 200, body: "", headers: {})
151
255
 
152
- lambda { client.track(5, "purchase") }.should raise_error(Customerio::Client::InvalidResponse)
256
+ lambda { client.track(" ", "test_event") }.should raise_error(Customerio::Client::ParamError, "customer_id must be a non-empty string")
257
+ lambda { client.track(5, " ") }.should raise_error(Customerio::Client::ParamError, "event_name must be a non-empty string")
153
258
  end
154
259
 
155
260
  it "uses the site_id and api key for basic auth and sends the event name" do
156
261
  stub_request(:post, api_uri('/api/v1/customers/5/events')).
157
- with(:body => "name=purchase").
158
- to_return(:status => 200, :body => "", :headers => {})
262
+ with(body: json(name: "purchase", data: {})).
263
+ to_return(status: 200, body: "", headers: {})
159
264
 
160
265
  client.track(5, "purchase")
161
266
  end
162
267
 
163
268
  it "sends any optional event attributes" do
164
269
  stub_request(:post, api_uri('/api/v1/customers/5/events')).
165
- with(:body => {
166
- :name => "purchase",
167
- :data => {
168
- :type => "socks",
169
- :price => "13.99"
270
+ with(body: json({
271
+ name: "purchase",
272
+ data: {
273
+ type: "socks",
274
+ price: "13.99"
170
275
  }
171
- }).
172
- to_return(:status => 200, :body => "", :headers => {})
276
+ })).
277
+ to_return(status: 200, body: "", headers: {})
173
278
 
174
- client.track(5, "purchase", :type => "socks", :price => "13.99")
279
+ client.track(5, "purchase", type: "socks", price: "13.99")
175
280
  end
176
281
 
177
282
  it "copes with arrays" do
178
283
  stub_request(:post, api_uri('/api/v1/customers/5/events')).
179
- with(:body => {
180
- :name => "event",
181
- :data => {
182
- :things => ["a", "b", "c"]
284
+ with(body: {
285
+ name: "event",
286
+ data: {
287
+ things: ["a", "b", "c"]
183
288
  }
184
289
  }).
185
- to_return(:status => 200, :body => "", :headers => {})
290
+ to_return(status: 200, body: "", headers: {})
186
291
 
187
- client.track(5, "event", :things => ["a", "b", "c"])
292
+ client.track(5, "event", things: ["a", "b", "c"])
188
293
  end
189
294
 
190
295
  it "copes with hashes" do
191
296
  stub_request(:post, api_uri('/api/v1/customers/5/events')).
192
- with(:body => {
193
- :name => "event",
194
- :data => {
195
- :stuff => { :a => "b" }
297
+ with(body: {
298
+ name: "event",
299
+ data: {
300
+ stuff: { a: "b" }
196
301
  }
197
302
  }).
198
- to_return(:status => 200, :body => "", :headers => {})
303
+ to_return(status: 200, body: "", headers: {})
199
304
 
200
- client.track(5, "event", :stuff => { :a => "b" })
305
+ client.track(5, "event", stuff: { a: "b" })
201
306
  end
202
307
 
203
308
  it "sends a POST request as json using json headers" do
204
- client = Customerio::Client.new("SITE_ID", "API_KEY", :json => true)
205
- data = { :type => "socks", :price => "13.99" }
206
- body = { :name => "purchase", :data => data }
309
+ client = Customerio::Client.new("SITE_ID", "API_KEY", json: true)
310
+ data = { type: "socks", price: "13.99" }
311
+ body = { name: "purchase", data: data }
207
312
 
208
313
  stub_request(:post, api_uri('/api/v1/customers/5/events')).
209
- with(:body => json(body),
210
- :headers => {'Content-Type'=>'application/json'}).
211
- to_return(:status => 200, :body => "", :headers => {})
314
+ with(body: json(body),
315
+ headers: {'Content-Type'=>'application/json'}).
316
+ to_return(status: 200, body: "", headers: {})
212
317
 
213
318
  client.track(5, "purchase", data)
214
319
  end
215
320
 
216
321
  it "allows sending of a timestamp" do
217
322
  stub_request(:post, api_uri('/api/v1/customers/5/events')).
218
- with(:body => {
219
- :name => "purchase",
220
- :data => {
221
- :type => "socks",
222
- :price => "13.99",
223
- :timestamp => "1561231234"
323
+ with(body: json({
324
+ name: "purchase",
325
+ data: {
326
+ type: "socks",
327
+ price: "13.99",
328
+ timestamp: 1561231234
224
329
  },
225
- :timestamp => "1561231234"
226
- }).
227
- to_return(:status => 200, :body => "", :headers => {})
330
+ timestamp: 1561231234
331
+ })).
332
+ to_return(status: 200, body: "", headers: {})
228
333
 
229
- client.track(5, "purchase", :type => "socks", :price => "13.99", :timestamp => 1561231234)
334
+ client.track(5, "purchase", type: "socks", price: "13.99", timestamp: 1561231234)
230
335
  end
231
336
 
232
337
  it "doesn't send timestamp if timestamp is in milliseconds" do
233
338
  stub_request(:post, api_uri('/api/v1/customers/5/events')).
234
- with(:body => {
235
- :name => "purchase",
236
- :data => {
237
- :type => "socks",
238
- :price => "13.99",
239
- :timestamp => "1561231234000"
339
+ with(body: json({
340
+ name: "purchase",
341
+ data: {
342
+ type: "socks",
343
+ price: "13.99",
344
+ timestamp: 1561231234000
240
345
  }
241
- }).
242
- to_return(:status => 200, :body => "", :headers => {})
346
+ })).
347
+ to_return(status: 200, body: "", headers: {})
243
348
 
244
- client.track(5, "purchase", :type => "socks", :price => "13.99", :timestamp => 1561231234000)
349
+ client.track(5, "purchase", type: "socks", price: "13.99", timestamp: 1561231234000)
245
350
  end
246
351
 
247
352
  it "doesn't send timestamp if timestamp is a date" do
248
353
  date = Time.now
249
354
 
250
355
  stub_request(:post, api_uri('/api/v1/customers/5/events')).
251
- with(:body => {
252
- :name => "purchase",
253
- :data => {
254
- :type => "socks",
255
- :price => "13.99",
256
- :timestamp => Time.now.to_s
356
+ with(body: {
357
+ name: "purchase",
358
+ data: {
359
+ type: "socks",
360
+ price: "13.99",
361
+ timestamp: Time.now.to_s
257
362
  }
258
363
  }).
259
- to_return(:status => 200, :body => "", :headers => {})
364
+ to_return(status: 200, body: "", headers: {})
260
365
 
261
- client.track(5, "purchase", :type => "socks", :price => "13.99", :timestamp => date)
366
+ client.track(5, "purchase", type: "socks", price: "13.99", timestamp: date)
262
367
  end
263
368
 
264
369
  it "doesn't send timestamp if timestamp isn't an integer" do
265
370
  stub_request(:post, api_uri('/api/v1/customers/5/events')).
266
- with(:body => {
267
- :name => "purchase",
268
- :data => {
269
- :type => "socks",
270
- :price => "13.99",
271
- :timestamp => "Hello world"
371
+ with(body: json({
372
+ name: "purchase",
373
+ data: {
374
+ type: "socks",
375
+ price: "13.99",
376
+ timestamp: "Hello world"
272
377
  }
273
- }).
378
+ })).
274
379
 
275
- to_return(:status => 200, :body => "", :headers => {})
380
+ to_return(status: 200, body: "", headers: {})
276
381
 
277
- client.track(5, "purchase", :type => "socks", :price => "13.99", :timestamp => "Hello world")
382
+ client.track(5, "purchase", type: "socks", price: "13.99", timestamp: "Hello world")
278
383
  end
279
384
 
280
385
  context "tracking an anonymous event" do
386
+ let(:anon_id) { "anon-id" }
387
+
281
388
  it "sends a POST request to the customer.io's anonymous event API" do
282
389
  stub_request(:post, api_uri('/api/v1/events')).
283
- with(:body => "name=purchase").
284
- to_return(:status => 200, :body => "", :headers => {})
390
+ with(body: { anonymous_id: anon_id, name: "purchase", data: {} }).
391
+ to_return(status: 200, body: "", headers: {})
285
392
 
286
- client.track("purchase")
393
+ client.track_anonymous(anon_id, "purchase")
287
394
  end
288
395
 
289
396
  it "sends any optional event attributes" do
290
397
  stub_request(:post, api_uri('/api/v1/events')).
291
- with(:body => {
292
- :name => "purchase",
293
- :data => {
294
- :type => "socks",
295
- :price => "13.99"
398
+ with(body: {
399
+ anonymous_id: anon_id,
400
+ name: "purchase",
401
+ data: {
402
+ type: "socks",
403
+ price: "13.99"
296
404
  }
297
405
  }).
298
- to_return(:status => 200, :body => "", :headers => {})
406
+ to_return(status: 200, body: "", headers: {})
299
407
 
300
- client.track("purchase", :type => "socks", :price => "13.99")
408
+ client.track_anonymous(anon_id, "purchase", type: "socks", price: "13.99")
301
409
  end
302
410
 
303
411
  it "allows sending of a timestamp" do
304
412
  stub_request(:post, api_uri('/api/v1/events')).
305
- with(:body => {
306
- :name => "purchase",
307
- :data => {
308
- :type => "socks",
309
- :price => "13.99",
310
- :timestamp => "1561231234"
413
+ with(body: {
414
+ anonymous_id: anon_id,
415
+ name: "purchase",
416
+ data: {
417
+ type: "socks",
418
+ price: "13.99",
419
+ timestamp: 1561231234
311
420
  },
312
- :timestamp => "1561231234"
421
+ timestamp: 1561231234
313
422
  }).
314
- to_return(:status => 200, :body => "", :headers => {})
423
+ to_return(status: 200, body: "", headers: {})
315
424
 
316
- client.track("purchase", :type => "socks", :price => "13.99", :timestamp => 1561231234)
425
+ client.track_anonymous(anon_id, "purchase", type: "socks", price: "13.99", timestamp: 1561231234)
317
426
  end
318
- end
319
- end
320
427
 
321
- describe "#anonymous_track" do
322
- it "raises an error if POST doesn't return a 2xx response code" do
323
- stub_request(:post, api_uri('/api/v1/events')).
324
- with(:body => "name=purchase").
325
- to_return(:status => 500, :body => "", :headers => {})
326
-
327
- lambda { client.anonymous_track("purchase") }.should raise_error(Customerio::Client::InvalidResponse)
328
- end
428
+ it "raises an error if POST doesn't return a 2xx response code" do
429
+ stub_request(:post, api_uri('/api/v1/events')).
430
+ with(body: { anonymous_id: anon_id, name: "purchase", data: {} }).
431
+ to_return(status: 500, body: "", headers: {})
329
432
 
330
- it "uses the site_id and api key for basic auth and sends the event name" do
331
- stub_request(:post, api_uri('/api/v1/events')).
332
- with(:body => "name=purchase").
333
- to_return(:status => 200, :body => "", :headers => {})
334
-
335
- client.anonymous_track("purchase")
336
- end
337
-
338
- it "sends any optional event attributes" do
339
- stub_request(:post, api_uri('/api/v1/events')).
340
- with(:body => {
341
- :name => "purchase",
342
- :data => {
343
- :type => "socks",
344
- :price => "27.99"
345
- },
346
- }).
347
-
348
- to_return(:status => 200, :body => "", :headers => {})
349
-
350
- client.anonymous_track("purchase", :type => "socks", :price => "27.99")
351
- end
433
+ lambda { client.track_anonymous(anon_id, "purchase") }.should raise_error(Customerio::InvalidResponse)
434
+ end
352
435
 
353
- it "allows sending of a timestamp" do
354
- stub_request(:post, api_uri('/api/v1/events')).
355
- with(:body => {
356
- :name => "purchase",
357
- :data => {
358
- :type => "socks",
359
- :price => "27.99",
360
- :timestamp => "1561235678"
361
- },
362
- :timestamp => "1561235678"
363
- }).
436
+ it "throws an error when anonymous_id is missing" do
437
+ stub_request(:post, api_uri('/api/v1/events')).
438
+ with(body: { anonymous_id: anon_id, name: "purchase", data: {} }).
439
+ to_return(status: 500, body: "", headers: {})
364
440
 
365
- to_return(:status => 200, :body => "", :headers => {})
441
+ lambda { client.track_anonymous("", "some_event") }.should raise_error(Customerio::Client::ParamError)
442
+ end
366
443
 
367
- client.anonymous_track("purchase", :type => "socks", :price => "27.99", :timestamp => 1561235678)
368
- end
444
+ it "throws an error when event_name is missing" do
445
+ stub_request(:post, api_uri('/api/v1/events')).
446
+ with(body: { anonymous_id: anon_id, name: "purchase", data: {} }).
447
+ to_return(status: 500, body: "", headers: {})
369
448
 
370
- context "too many arguments are passed" do
371
- it "throws an error" do
372
- lambda { client.anonymous_track("purchase", "text", :type => "socks", :price => "27.99") }.should raise_error(ArgumentError)
449
+ lambda { client.track_anonymous(anon_id, "") }.should raise_error(Customerio::Client::ParamError)
373
450
  end
374
451
  end
375
452
  end
@@ -377,109 +454,142 @@ describe Customerio::Client do
377
454
  describe "#devices" do
378
455
  it "allows for the creation of a new device" do
379
456
  stub_request(:put, api_uri('/api/v1/customers/5/devices')).
380
- to_return(:status => 200, :body => "", :headers => {})
457
+ to_return(status: 200, body: "", headers: {})
381
458
 
382
- client.add_device(5, "androidDeviceID", "ios", {:last_used=>1561235678})
459
+ client.add_device(5, "androidDeviceID", "ios", {last_used: 1561235678})
383
460
  client.add_device(5, "iosDeviceID", "android")
384
461
  end
385
462
  it "requires a valid customer_id when creating" do
386
463
  stub_request(:put, api_uri('/api/v1/customers/5/devices')).
387
- to_return(:status => 200, :body => "", :headers => {})
464
+ to_return(status: 200, body: "", headers: {})
388
465
 
389
466
  lambda { client.add_device("", "ios", "myDeviceID") }.should raise_error(Customerio::Client::ParamError)
390
- lambda { client.add_device(nil, "ios", "myDeviceID", {:last_used=>1561235678}) }.should raise_error(Customerio::Client::ParamError)
467
+ lambda { client.add_device(nil, "ios", "myDeviceID", {last_used: 1561235678}) }.should raise_error(Customerio::Client::ParamError)
391
468
  end
392
469
  it "requires a valid token when creating" do
393
470
  stub_request(:put, api_uri('/api/v1/customers/5/devices')).
394
- to_return(:status => 200, :body => "", :headers => {})
471
+ to_return(status: 200, body: "", headers: {})
395
472
 
396
473
  lambda { client.add_device(5, "", "ios") }.should raise_error(Customerio::Client::ParamError)
397
- lambda { client.add_device(5, nil, "ios", {:last_used=>1561235678}) }.should raise_error(Customerio::Client::ParamError)
474
+ lambda { client.add_device(5, nil, "ios", {last_used: 1561235678}) }.should raise_error(Customerio::Client::ParamError)
398
475
  end
399
476
  it "requires a valid platform when creating" do
400
477
  stub_request(:put, api_uri('/api/v1/customers/5/devices')).
401
- to_return(:status => 200, :body => "", :headers => {})
478
+ to_return(status: 200, body: "", headers: {})
402
479
 
403
480
  lambda { client.add_device(5, "token", "") }.should raise_error(Customerio::Client::ParamError)
404
- lambda { client.add_device(5, "toke", nil, {:last_used=>1561235678}) }.should raise_error(Customerio::Client::ParamError)
481
+ lambda { client.add_device(5, "toke", nil, {last_used: 1561235678}) }.should raise_error(Customerio::Client::ParamError)
405
482
  end
406
483
  it "accepts a nil data param" do
407
484
  stub_request(:put, api_uri('/api/v1/customers/5/devices')).
408
- to_return(:status => 200, :body => "", :headers => {})
485
+ to_return(status: 200, body: "", headers: {})
409
486
 
410
487
  client.add_device(5, "ios", "myDeviceID", nil)
411
488
  end
412
489
  it "fails on invalid data param" do
413
490
  stub_request(:put, api_uri('/api/v1/customers/5/devices')).
414
- to_return(:status => 200, :body => "", :headers => {})
491
+ to_return(status: 200, body: "", headers: {})
415
492
 
416
493
  lambda { client.add_device(5, "ios", "myDeviceID", 1000) }.should raise_error(Customerio::Client::ParamError)
417
494
  end
418
495
  it "supports deletion of devices by token" do
419
496
  stub_request(:delete, api_uri('/api/v1/customers/5/devices/myDeviceID')).
420
- to_return(:status => 200, :body => "", :headers => {})
497
+ to_return(status: 200, body: "", headers: {})
421
498
 
422
499
  client.delete_device(5, "myDeviceID")
423
500
  end
424
501
  it "requires a valid customer_id when deleting" do
425
502
  stub_request(:delete, api_uri('/api/v1/customers/5/devices/myDeviceID')).
426
- to_return(:status => 200, :body => "", :headers => {})
503
+ to_return(status: 200, body: "", headers: {})
427
504
 
428
505
  lambda { client.delete_device("", "myDeviceID") }.should raise_error(Customerio::Client::ParamError)
429
506
  lambda { client.delete_device(nil, "myDeviceID") }.should raise_error(Customerio::Client::ParamError)
430
507
  end
431
508
  it "requires a valid device_id when deleting" do
432
509
  stub_request(:delete, api_uri('/api/v1/customers/5/devices/myDeviceID')).
433
- to_return(:status => 200, :body => "", :headers => {})
510
+ to_return(status: 200, body: "", headers: {})
434
511
 
435
512
  lambda { client.delete_device(5, "") }.should raise_error(Customerio::Client::ParamError)
436
513
  lambda { client.delete_device(5, nil) }.should raise_error(Customerio::Client::ParamError)
437
514
  end
438
515
  end
439
516
 
440
- describe "#manual_segments" do
517
+ describe "#track_push_notification_event" do
518
+ attr_accessor :client, :attributes
441
519
 
442
- client = Customerio::Client.new("SITE_ID", "API_KEY", :json=>true)
520
+ before(:each) do
521
+ @client = Customerio::Client.new("SITE_ID", "API_KEY", :json => true)
522
+ @attributes = {
523
+ :delivery_id => 'foo',
524
+ :device_id => 'bar',
525
+ :timestamp => Time.now.to_i
526
+ }
527
+ end
443
528
 
444
- it "allows adding customers to a manual segment" do
445
- stub_request(:post, api_uri('/api/v1/segments/1/add_customers')).to_return(:status => 200, :body => "", :headers => {})
529
+ it "sends a POST request to customer.io's /push/events endpoint" do
530
+ stub_request(:post, api_uri('/push/events')).
531
+ with(
532
+ :body => json(attributes.merge({
533
+ :event => 'opened'
534
+ })),
535
+ :headers => {
536
+ 'Content-Type' => 'application/json'
537
+ }).
538
+ to_return(:status => 200, :body => "", :headers => {})
446
539
 
447
- client.add_to_segment(1, ["customer1", "customer2", "customer3"])
540
+ client.track_push_notification_event('opened', attributes)
448
541
  end
449
- it "requires a valid segment id when adding customers" do
450
- stub_request(:post, api_uri('/api/v1/segments/1/add_customers')).to_return(:status => 200, :body => "", :headers => {})
451
542
 
452
- lambda { client.add_to_segment("not_valid", ["customer1", "customer2", "customer3"]).should raise_error(Customerio::Client::ParamError) }
453
- end
454
- it "requires a valid customer list when adding customers" do
455
- stub_request(:post, api_uri('/api/v1/segments/1/add_customers')).to_return(:status => 200, :body => "", :headers => {})
543
+ it "should raise if event is invalid" do
544
+ stub_request(:post, api_uri('/push/events')).
545
+ to_return(:status => 200, :body => "", :headers => {})
456
546
 
457
- lambda { client.add_to_segment(1, "not_valid").should raise_error(Customerio::Client::ParamError) }
547
+ expect {
548
+ client.track_push_notification_event('closed', attributes.merge({ :delivery_id => nil }))
549
+ }.to raise_error(Customerio::Client::ParamError, 'event_name must be one of opened, converted, or delivered')
458
550
  end
459
- it "coerces non-string values to strings when adding customers" do
460
- stub_request(:post, api_uri('/api/v1/segments/1/add_customers')).with(:body=>json({:ids=>["1", "2", "3"]})).to_return(:status => 200, :body => "", :headers => {})
461
551
 
462
- client.add_to_segment(1, [1, 2, 3])
463
- end
464
- it "allows removing customers from a manual segment" do
465
- stub_request(:post, api_uri('/api/v1/segments/1/remove_customers')).to_return(:status => 200, :body => "", :headers => {})
552
+ it "should raise if delivery_id is invalid" do
553
+ stub_request(:post, api_uri('/push/events')).
554
+ to_return(:status => 200, :body => "", :headers => {})
466
555
 
467
- client.remove_from_segment(1, ["customer1", "customer2", "customer3"])
468
- end
469
- it "requires a valid segment id when removing customers" do
470
- stub_request(:post, api_uri('/api/v1/segments/1/remove_customers')).to_return(:status => 200, :body => "", :headers => {})
556
+ expect {
557
+ client.track_push_notification_event('opened', attributes.merge({ :delivery_id => nil }))
558
+ }.to raise_error(Customerio::Client::ParamError, 'delivery_id must be a non-empty string')
471
559
 
472
- lambda { client.remove_from_segment("not_valid", ["customer1", "customer2", "customer3"]).should raise_error(Customerio::Client::ParamError) }
560
+ expect {
561
+ client.track_push_notification_event('opened', attributes.merge({ :delivery_id => '' }))
562
+ }.to raise_error(Customerio::Client::ParamError, 'delivery_id must be a non-empty string')
473
563
  end
474
- it "requires a valid customer list when removing customers" do
475
- stub_request(:post, api_uri('/api/v1/segments/1/remove_customers')).to_return(:status => 200, :body => "", :headers => {})
476
564
 
477
- lambda { client.remove_from_segment(1, "not_valid").should raise_error(Customerio::Client::ParamError) }
565
+ it "should raise if device_id is invalid" do
566
+ stub_request(:post, api_uri('/push/events')).
567
+ to_return(:status => 200, :body => "", :headers => {})
568
+
569
+ expect {
570
+ client.track_push_notification_event('opened', attributes.merge({ :device_id => nil }))
571
+ }.to raise_error(Customerio::Client::ParamError, 'device_id must be a non-empty string')
572
+
573
+ expect {
574
+ client.track_push_notification_event('opened', attributes.merge({ :device_id => '' }))
575
+ }.to raise_error(Customerio::Client::ParamError, 'device_id must be a non-empty string')
478
576
  end
479
- it "coerces non-string values to strings when removing customers" do
480
- stub_request(:post, api_uri('/api/v1/segments/1/remove_customers')).with(:body=>json({:ids=>["1", "2", "3"]})).to_return(:status => 200, :body => "", :headers => {})
481
577
 
482
- client.remove_from_segment(1, [1, 2, 3])
578
+ it "should raise if timestamp is invalid" do
579
+ stub_request(:post, api_uri('/push/events')).
580
+ to_return(:status => 200, :body => "", :headers => {})
581
+
582
+ expect {
583
+ client.track_push_notification_event('opened', attributes.merge({ :timestamp => nil }))
584
+ }.to raise_error(Customerio::Client::ParamError, 'timestamp must be a valid timestamp')
585
+
586
+ expect {
587
+ client.track_push_notification_event('opened', attributes.merge({ :timestamp => 999999999 }))
588
+ }.to raise_error(Customerio::Client::ParamError, 'timestamp must be a valid timestamp')
589
+
590
+ expect {
591
+ client.track_push_notification_event('opened', attributes.merge({ :timestamp => 100000000000 }))
592
+ }.to raise_error(Customerio::Client::ParamError, 'timestamp must be a valid timestamp')
483
593
  end
484
594
  end
485
595
  end