customerio 0.7.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +8 -8
- data/.travis.yml +0 -2
- data/CHANGELOG.markdown +8 -0
- data/customerio.gemspec +3 -2
- data/lib/customerio.rb +1 -0
- data/lib/customerio/client.rb +53 -20
- data/lib/customerio/param_encoder.rb +68 -0
- data/lib/customerio/version.rb +1 -1
- data/spec/client_spec.rb +209 -192
- data/spec/spec_helper.rb +4 -2
- metadata +31 -22
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
MWFhMDk1ZmMzOTBjZTZhMzYxNGYyZTJmZDFlMTdiZWI1NjdhMTY4YQ==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
YmMyODY3OTIxMjhkN2MxYzVlNzdlOWRiOTgxNzc2OWUyYWJjNDc0Ng==
|
7
7
|
SHA512:
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
NjA5OWUwZDU4ZmJmYjA3YmQ4ZTY0ZDhlNWQzMjU2MDViNzk3NjkxNzI2Zjcw
|
10
|
+
YjFjNDg4N2Q5YzQwMWIxZGJkZWFmNzNlYTEzYjVkNWZlY2Q4MjgyNjM3MWNm
|
11
|
+
M2ZjMzU5MGUyMzljOTg0NTA0OWM5NDZkMDU4MTVlMzViNDEzYWU=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
YTMxNjA2NzJjNzU2ZDExNzBjZDYxODBhMDRiMTg1MWE3ZDIzMmI5YzcxYzE5
|
14
|
+
MGNhNTc5YTJmYjRmNjBlYTljODU3YzhiMGZiZGNkM2UyOTZhYzkyMWE0ZTBl
|
15
|
+
MGY2Mzg1YzMyMGYxNzg2NGY1YjY4YmFhZjkwYzVjN2IxYjNkYTE=
|
data/.travis.yml
CHANGED
data/CHANGELOG.markdown
CHANGED
@@ -1,3 +1,11 @@
|
|
1
|
+
## Customerio 1.0.0 - Mar 15, 2016 ##
|
2
|
+
|
3
|
+
There is a slight breaking change in this release. If you are depending on the HTTP response object included in the InvalidResponse exception (introduced in 0.6.1), note that it is now a `Net::HTTPBadRequest`.
|
4
|
+
|
5
|
+
* Remove HTTParty dependency, use Net::HTTP instead. [#42](https://github.com/customerio/customerio-ruby/pull/42)
|
6
|
+
|
7
|
+
* Update test suite to use webmock, to increase confidence that we're not changing our HTTP requests [#41](https://github.com/customerio/customerio-ruby/pull/41)
|
8
|
+
|
1
9
|
## Customerio 0.7.0 - Mar 2, 2016 ##
|
2
10
|
|
3
11
|
* Add new method for tracking anonymous events: `anonymous_track`. See README for more details. Many thanks to @sdawson for this contribution! [#36](https://github.com/customerio/customerio-ruby/pull/36)
|
data/customerio.gemspec
CHANGED
@@ -15,10 +15,11 @@ Gem::Specification.new do |gem|
|
|
15
15
|
gem.require_paths = ["lib"]
|
16
16
|
gem.version = Customerio::VERSION
|
17
17
|
|
18
|
-
gem.add_dependency('httparty', ["< 0.12", ">= 0.5"])
|
19
18
|
gem.add_dependency('multi_json', "~> 1.0")
|
20
19
|
|
21
20
|
gem.add_development_dependency('rake')
|
22
21
|
gem.add_development_dependency('rspec')
|
23
|
-
gem.add_development_dependency('
|
22
|
+
gem.add_development_dependency('webmock')
|
23
|
+
gem.add_development_dependency('addressable', '~> 2.3.6')
|
24
|
+
gem.add_development_dependency('json')
|
24
25
|
end
|
data/lib/customerio.rb
CHANGED
data/lib/customerio/client.rb
CHANGED
@@ -1,13 +1,13 @@
|
|
1
|
-
require '
|
1
|
+
require 'net/http'
|
2
2
|
require 'multi_json'
|
3
3
|
|
4
4
|
module Customerio
|
5
|
-
|
6
|
-
|
7
|
-
base_uri 'https://track.customer.io'
|
8
|
-
default_timeout 10
|
5
|
+
DEFAULT_BASE_URI = 'https://track.customer.io'
|
6
|
+
DEFAULT_TIMEOUT = 10
|
9
7
|
|
8
|
+
class Client
|
10
9
|
class MissingIdAttributeError < RuntimeError; end
|
10
|
+
class InvalidRequest < RuntimeError; end
|
11
11
|
class InvalidResponse < RuntimeError
|
12
12
|
attr_reader :response
|
13
13
|
|
@@ -18,8 +18,11 @@ module Customerio
|
|
18
18
|
end
|
19
19
|
|
20
20
|
def initialize(site_id, secret_key, options = {})
|
21
|
-
@
|
21
|
+
@username = site_id
|
22
|
+
@password = secret_key
|
22
23
|
@json = options.has_key?(:json) ? options[:json] : true
|
24
|
+
@base_uri = options[:base_uri] || DEFAULT_BASE_URI
|
25
|
+
@timeout = options[:timeout] || DEFAULT_TIMEOUT
|
23
26
|
end
|
24
27
|
|
25
28
|
def identify(attributes)
|
@@ -27,7 +30,7 @@ module Customerio
|
|
27
30
|
end
|
28
31
|
|
29
32
|
def delete(customer_id)
|
30
|
-
verify_response(
|
33
|
+
verify_response(request(:delete, customer_path(customer_id)))
|
31
34
|
end
|
32
35
|
|
33
36
|
def track(*args)
|
@@ -59,11 +62,7 @@ module Customerio
|
|
59
62
|
|
60
63
|
url = customer_path(attributes[:id])
|
61
64
|
|
62
|
-
|
63
|
-
verify_response(self.class.put(url, options.merge(:body => MultiJson.dump(attributes), :headers => {'Content-Type' => 'application/json'})))
|
64
|
-
else
|
65
|
-
verify_response(self.class.put(url, options.merge(:body => attributes)))
|
66
|
-
end
|
65
|
+
verify_response(request(:put, url, attributes))
|
67
66
|
end
|
68
67
|
|
69
68
|
def create_customer_event(customer_id, event_name, attributes = {})
|
@@ -77,11 +76,7 @@ module Customerio
|
|
77
76
|
def create_event(url, event_name, attributes = {})
|
78
77
|
body = { :name => event_name, :data => attributes }
|
79
78
|
body[:timestamp] = attributes[:timestamp] if valid_timestamp?(attributes[:timestamp])
|
80
|
-
|
81
|
-
verify_response(self.class.post(url, options.merge(:body => MultiJson.dump(body), :headers => {'Content-Type' => 'application/json'})))
|
82
|
-
else
|
83
|
-
verify_response(self.class.post(url, options.merge(:body => body)))
|
84
|
-
end
|
79
|
+
verify_response(request(:post, url, body))
|
85
80
|
end
|
86
81
|
|
87
82
|
def customer_path(id)
|
@@ -94,7 +89,7 @@ module Customerio
|
|
94
89
|
|
95
90
|
|
96
91
|
def verify_response(response)
|
97
|
-
if response.code >= 200 && response.code < 300
|
92
|
+
if response.code.to_i >= 200 && response.code.to_i < 300
|
98
93
|
response
|
99
94
|
else
|
100
95
|
raise InvalidResponse.new("Customer.io API returned an invalid response: #{response.code}", response)
|
@@ -106,8 +101,46 @@ module Customerio
|
|
106
101
|
hash.inject({}){ |hash, (k,v)| hash[k.to_sym] = v; hash }
|
107
102
|
end
|
108
103
|
|
109
|
-
def
|
110
|
-
|
104
|
+
def request(method, path, body = nil, headers = {})
|
105
|
+
uri = URI.join(@base_uri, path)
|
106
|
+
|
107
|
+
session = Net::HTTP.new(uri.host, uri.port)
|
108
|
+
session.use_ssl = (uri.scheme == 'https')
|
109
|
+
session.open_timeout = @timeout
|
110
|
+
session.read_timeout = @timeout
|
111
|
+
|
112
|
+
req = request_class(method).new(uri.path)
|
113
|
+
req.initialize_http_header(headers)
|
114
|
+
req.basic_auth @username, @password
|
115
|
+
|
116
|
+
add_request_body(req, body) unless body.nil?
|
117
|
+
|
118
|
+
session.start do |http|
|
119
|
+
http.request(req)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def request_class(method)
|
124
|
+
case method
|
125
|
+
when :post
|
126
|
+
Net::HTTP::Post
|
127
|
+
when :put
|
128
|
+
Net::HTTP::Put
|
129
|
+
when :delete
|
130
|
+
Net::HTTP::Delete
|
131
|
+
else
|
132
|
+
raise InvalidRequest.new("Invalid request method #{method.inspect}")
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def add_request_body(req, body)
|
137
|
+
if @json
|
138
|
+
req.add_field('Content-Type', 'application/json')
|
139
|
+
req.body = MultiJson.dump(body)
|
140
|
+
else
|
141
|
+
req.add_field('Content-Type', 'application/x-www-form-urlencoded')
|
142
|
+
req.body = ParamEncoder.to_params(body)
|
143
|
+
end
|
111
144
|
end
|
112
145
|
end
|
113
146
|
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# Based on HTTParty's HashConversions:
|
2
|
+
#
|
3
|
+
# https://github.com/jnunemaker/httparty/blob/master/lib/httparty/hash_conversions.rb
|
4
|
+
#
|
5
|
+
# License: MIT https://github.com/jnunemaker/httparty/blob/master/MIT-LICENSE
|
6
|
+
|
7
|
+
require 'erb'
|
8
|
+
require 'uri'
|
9
|
+
|
10
|
+
module Customerio
|
11
|
+
class ParamEncoder
|
12
|
+
# @return <String> This hash as a query string
|
13
|
+
#
|
14
|
+
# @example
|
15
|
+
# { name: "Bob",
|
16
|
+
# address: {
|
17
|
+
# street: '111 Ruby Ave.',
|
18
|
+
# city: 'Ruby Central',
|
19
|
+
# phones: ['111-111-1111', '222-222-2222']
|
20
|
+
# }
|
21
|
+
# }.to_params
|
22
|
+
# #=> "name=Bob&address[city]=Ruby Central&address[phones][]=111-111-1111&address[phones][]=222-222-2222&address[street]=111 Ruby Ave."
|
23
|
+
def self.to_params(hash)
|
24
|
+
hash.to_hash.map { |k, v| normalize_param(k, v) }.join.chop
|
25
|
+
end
|
26
|
+
|
27
|
+
# @param key<Object> The key for the param.
|
28
|
+
# @param value<Object> The value for the param.
|
29
|
+
#
|
30
|
+
# @return <String> This key value pair as a param
|
31
|
+
#
|
32
|
+
# @example normalize_param(:name, "Bob Jones") #=> "name=Bob%20Jones&"
|
33
|
+
def self.normalize_param(key, value)
|
34
|
+
param = ''
|
35
|
+
stack = []
|
36
|
+
|
37
|
+
if value.respond_to?(:to_ary)
|
38
|
+
param << value.to_ary.map { |element| normalize_param("#{key}[]", element) }.join
|
39
|
+
elsif value.respond_to?(:to_hash)
|
40
|
+
stack << [key, value.to_hash]
|
41
|
+
else
|
42
|
+
param << "#{key}=#{escape_value(value)}&"
|
43
|
+
end
|
44
|
+
|
45
|
+
stack.each do |parent, hash|
|
46
|
+
hash.each do |k, v|
|
47
|
+
if v.respond_to?(:to_hash)
|
48
|
+
stack << ["#{parent}[#{k}]", v.to_hash]
|
49
|
+
else
|
50
|
+
param << normalize_param("#{parent}[#{k}]", v)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
param
|
56
|
+
end
|
57
|
+
|
58
|
+
# Prefer ERB::Util.url_encode, fall baack to deprecation URI.encode for
|
59
|
+
# old Ruby support
|
60
|
+
def self.escape_value(value)
|
61
|
+
if ERB::Util.respond_to? :url_encode
|
62
|
+
ERB::Util.url_encode(value.to_s)
|
63
|
+
else
|
64
|
+
URI.encode(value.to_s, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
data/lib/customerio/version.rb
CHANGED
data/spec/client_spec.rb
CHANGED
@@ -1,14 +1,17 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
require 'multi_json'
|
3
3
|
|
4
|
+
|
4
5
|
describe Customerio::Client do
|
5
6
|
let(:client) { Customerio::Client.new("SITE_ID", "API_KEY", :json => false) }
|
6
7
|
let(:response) { double("Response", :code => 200) }
|
7
8
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
def api_uri(path)
|
10
|
+
"https://SITE_ID:API_KEY@track.customer.io#{path}"
|
11
|
+
end
|
12
|
+
|
13
|
+
def json(data)
|
14
|
+
MultiJson.dump(data)
|
12
15
|
end
|
13
16
|
|
14
17
|
describe "json option" do
|
@@ -17,90 +20,85 @@ describe Customerio::Client do
|
|
17
20
|
it "uses json by default" do
|
18
21
|
client = Customerio::Client.new("SITE_ID", "API_KEY")
|
19
22
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
{
|
24
|
-
|
25
|
-
:body=>json,
|
26
|
-
:headers=>{"Content-Type"=>"application/json"}
|
27
|
-
}).and_return(response)
|
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 => {})
|
27
|
+
|
28
28
|
client.identify(body)
|
29
29
|
end
|
30
30
|
|
31
31
|
it "allows disabling json" do
|
32
32
|
client = Customerio::Client.new("SITE_ID", "API_KEY", :json => false)
|
33
33
|
|
34
|
-
|
35
|
-
"
|
36
|
-
{
|
37
|
-
|
38
|
-
:body=>body
|
39
|
-
}).and_return(response)
|
34
|
+
stub_request(:put, api_uri('/api/v1/customers/5')).
|
35
|
+
with(:body => { :id => "5", :name => "Bob" }).
|
36
|
+
to_return(:status => 200, :body => "", :headers => {})
|
37
|
+
|
40
38
|
client.identify(body)
|
41
39
|
end
|
42
40
|
end
|
43
41
|
|
44
|
-
describe ".base_uri" do
|
45
|
-
it "should be set to customer.io's api" do
|
46
|
-
Customerio::Client.base_uri.should == "https://track.customer.io"
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
42
|
describe "#identify" do
|
51
43
|
it "sends a PUT request to customer.io's customer API" do
|
52
|
-
|
44
|
+
stub_request(:put, api_uri('/api/v1/customers/5')).
|
45
|
+
with(:body => "id=5").
|
46
|
+
to_return(:status => 200, :body => "", :headers => {})
|
47
|
+
|
53
48
|
client.identify(:id => 5)
|
54
49
|
end
|
55
50
|
|
56
51
|
it "sends a PUT request to customer.io's customer API using json headers" do
|
57
52
|
client = Customerio::Client.new("SITE_ID", "API_KEY", :json => true)
|
58
53
|
body = { :id => 5, :name => "Bob" }
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
54
|
+
|
55
|
+
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 => {})
|
59
|
+
|
65
60
|
client.identify(body)
|
66
61
|
end
|
67
62
|
|
68
63
|
it "raises an error if PUT doesn't return a 2xx response code" do
|
69
|
-
|
64
|
+
stub_request(:put, api_uri('/api/v1/customers/5')).
|
65
|
+
with(:body => "id=5").
|
66
|
+
to_return(:status => 500, :body => "", :headers => {})
|
67
|
+
|
70
68
|
lambda { client.identify(:id => 5) }.should raise_error(Customerio::Client::InvalidResponse)
|
71
69
|
end
|
72
70
|
|
73
71
|
it "includes the HTTP response with raised errors" do
|
74
|
-
|
75
|
-
|
72
|
+
stub_request(:put, api_uri('/api/v1/customers/5')).
|
73
|
+
with(:body => "id=5").
|
74
|
+
to_return(:status => 500, :body => "whatever", :headers => {})
|
75
|
+
|
76
76
|
lambda { client.identify(:id => 5) }.should raise_error {|error|
|
77
77
|
error.should be_a Customerio::Client::InvalidResponse
|
78
|
-
error.response.should eq
|
78
|
+
error.response.code.should eq "500"
|
79
|
+
error.response.body.should eq "whatever"
|
79
80
|
}
|
80
81
|
end
|
81
82
|
|
82
|
-
it "uses the site_id and api key for basic auth" do
|
83
|
-
Customerio::Client.should_receive(:put).with("/api/v1/customers/5", {
|
84
|
-
:basic_auth => { :username => "SITE_ID", :password => "API_KEY" },
|
85
|
-
:body => anything()
|
86
|
-
}).and_return(response)
|
87
|
-
|
88
|
-
client.identify(:id => 5)
|
89
|
-
end
|
90
|
-
|
91
83
|
it "sends along all attributes" do
|
92
|
-
|
93
|
-
|
84
|
+
time = Time.now.to_i
|
85
|
+
|
86
|
+
stub_request(:put, api_uri('/api/v1/customers/5')).with(
|
94
87
|
:body => {
|
95
|
-
:id => 5,
|
88
|
+
:id => "5",
|
96
89
|
:email => "customer@example.com",
|
97
|
-
:created_at =>
|
90
|
+
:created_at => time.to_s,
|
98
91
|
:first_name => "Bob",
|
99
92
|
:plan => "basic"
|
100
|
-
}
|
101
|
-
|
102
|
-
|
103
|
-
|
93
|
+
}).to_return(:status => 200, :body => "", :headers => {})
|
94
|
+
|
95
|
+
client.identify({
|
96
|
+
:id => 5,
|
97
|
+
:email => "customer@example.com",
|
98
|
+
:created_at => time,
|
99
|
+
:first_name => "Bob",
|
100
|
+
:plan => "basic"
|
101
|
+
})
|
104
102
|
end
|
105
103
|
|
106
104
|
it "requires an id attribute" do
|
@@ -108,6 +106,10 @@ describe Customerio::Client do
|
|
108
106
|
end
|
109
107
|
|
110
108
|
it 'should not raise errors when attribute keys are strings' do
|
109
|
+
stub_request(:put, api_uri('/api/v1/customers/5')).
|
110
|
+
with(:body => "id=5").
|
111
|
+
to_return(:status => 200, :body => "", :headers => {})
|
112
|
+
|
111
113
|
attributes = { "id" => 5 }
|
112
114
|
|
113
115
|
lambda { client.identify(attributes) }.should_not raise_error()
|
@@ -115,88 +117,111 @@ describe Customerio::Client do
|
|
115
117
|
end
|
116
118
|
|
117
119
|
describe "#delete" do
|
118
|
-
|
119
|
-
|
120
|
+
it "sends a DELETE request to the customer.io's event API" do
|
121
|
+
stub_request(:delete, api_uri('/api/v1/customers/5')).
|
122
|
+
to_return(:status => 200, :body => "", :headers => {})
|
123
|
+
|
120
124
|
client.delete(5)
|
121
|
-
|
125
|
+
end
|
122
126
|
end
|
123
127
|
|
124
128
|
describe "#track" do
|
125
|
-
it "sends a POST request to the customer.io's event API" do
|
126
|
-
Customerio::Client.should_receive(:post).with("/api/v1/customers/5/events", anything()).and_return(response)
|
127
|
-
client.track(5, "purchase")
|
128
|
-
end
|
129
|
-
|
130
129
|
it "raises an error if POST doesn't return a 2xx response code" do
|
131
|
-
|
130
|
+
stub_request(:post, api_uri('/api/v1/customers/5/events')).
|
131
|
+
with(:body => "name=purchase").
|
132
|
+
to_return(:status => 500, :body => "", :headers => {})
|
133
|
+
|
132
134
|
lambda { client.track(5, "purchase") }.should raise_error(Customerio::Client::InvalidResponse)
|
133
135
|
end
|
134
136
|
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
})
|
137
|
+
it "uses the site_id and api key for basic auth and sends the event name" do
|
138
|
+
stub_request(:post, api_uri('/api/v1/customers/5/events')).
|
139
|
+
with(:body => "name=purchase").
|
140
|
+
to_return(:status => 200, :body => "", :headers => {})
|
140
141
|
|
141
142
|
client.track(5, "purchase")
|
142
|
-
|
143
|
+
end
|
143
144
|
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
145
|
+
it "sends any optional event attributes" do
|
146
|
+
stub_request(:post, api_uri('/api/v1/customers/5/events')).
|
147
|
+
with(:body => {
|
148
|
+
:name => "purchase",
|
149
|
+
:data => {
|
150
|
+
:type => "socks",
|
151
|
+
:price => "13.99"
|
152
|
+
}
|
153
|
+
}).
|
154
|
+
to_return(:status => 200, :body => "", :headers => {})
|
149
155
|
|
150
|
-
client.track(5, "purchase")
|
151
|
-
|
156
|
+
client.track(5, "purchase", :type => "socks", :price => "13.99")
|
157
|
+
end
|
152
158
|
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
159
|
+
it "copes with arrays" do
|
160
|
+
stub_request(:post, api_uri('/api/v1/customers/5/events')).
|
161
|
+
with(:body => {
|
162
|
+
:name => "event",
|
163
|
+
:data => {
|
164
|
+
:things => ["a", "b", "c"]
|
165
|
+
}
|
166
|
+
}).
|
167
|
+
to_return(:status => 200, :body => "", :headers => {})
|
161
168
|
|
162
|
-
client.track(5, "
|
163
|
-
|
169
|
+
client.track(5, "event", :things => ["a", "b", "c"])
|
170
|
+
end
|
171
|
+
|
172
|
+
it "copes with hashes" do
|
173
|
+
stub_request(:post, api_uri('/api/v1/customers/5/events')).
|
174
|
+
with(:body => {
|
175
|
+
:name => "event",
|
176
|
+
:data => {
|
177
|
+
:stuff => { :a => "b" }
|
178
|
+
}
|
179
|
+
}).
|
180
|
+
to_return(:status => 200, :body => "", :headers => {})
|
181
|
+
|
182
|
+
client.track(5, "event", :stuff => { :a => "b" })
|
183
|
+
end
|
164
184
|
|
165
185
|
it "sends a POST request as json using json headers" do
|
166
186
|
client = Customerio::Client.new("SITE_ID", "API_KEY", :json => true)
|
167
|
-
|
168
|
-
|
169
|
-
:basic_auth => anything(),
|
170
|
-
:body => MultiJson.dump({
|
171
|
-
:name => "purchase",
|
172
|
-
:data => { :type => "socks", :price => "13.99" }
|
173
|
-
}),
|
174
|
-
:headers=>{"Content-Type"=>"application/json"}}).and_return(response)
|
175
|
-
client.track(5, "purchase", :type => "socks", :price => "13.99")
|
176
|
-
end
|
187
|
+
data = { :type => "socks", :price => "13.99" }
|
188
|
+
body = { :name => "purchase", :data => data }
|
177
189
|
|
190
|
+
stub_request(:post, api_uri('/api/v1/customers/5/events')).
|
191
|
+
with(:body => json(body),
|
192
|
+
:headers => {'Content-Type'=>'application/json'}).
|
193
|
+
to_return(:status => 200, :body => "", :headers => {})
|
178
194
|
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
:
|
186
|
-
|
187
|
-
|
195
|
+
client.track(5, "purchase", data)
|
196
|
+
end
|
197
|
+
|
198
|
+
it "allows sending of a timestamp" do
|
199
|
+
stub_request(:post, api_uri('/api/v1/customers/5/events')).
|
200
|
+
with(:body => {
|
201
|
+
:name => "purchase",
|
202
|
+
:data => {
|
203
|
+
:type => "socks",
|
204
|
+
:price => "13.99",
|
205
|
+
:timestamp => "1561231234"
|
206
|
+
},
|
207
|
+
:timestamp => "1561231234"
|
208
|
+
}).
|
209
|
+
to_return(:status => 200, :body => "", :headers => {})
|
188
210
|
|
189
211
|
client.track(5, "purchase", :type => "socks", :price => "13.99", :timestamp => 1561231234)
|
190
|
-
|
212
|
+
end
|
191
213
|
|
192
214
|
it "doesn't send timestamp if timestamp is in milliseconds" do
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
215
|
+
stub_request(:post, api_uri('/api/v1/customers/5/events')).
|
216
|
+
with(:body => {
|
217
|
+
:name => "purchase",
|
218
|
+
:data => {
|
219
|
+
:type => "socks",
|
220
|
+
:price => "13.99",
|
221
|
+
:timestamp => "1561231234000"
|
222
|
+
}
|
223
|
+
}).
|
224
|
+
to_return(:status => 200, :body => "", :headers => {})
|
200
225
|
|
201
226
|
client.track(5, "purchase", :type => "socks", :price => "13.99", :timestamp => 1561231234000)
|
202
227
|
end
|
@@ -204,74 +229,71 @@ describe Customerio::Client do
|
|
204
229
|
it "doesn't send timestamp if timestamp is a date" do
|
205
230
|
date = Time.now
|
206
231
|
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
232
|
+
stub_request(:post, api_uri('/api/v1/customers/5/events')).
|
233
|
+
with(:body => {
|
234
|
+
:name => "purchase",
|
235
|
+
:data => {
|
236
|
+
:type => "socks",
|
237
|
+
:price => "13.99",
|
238
|
+
:timestamp => Time.now.to_s
|
239
|
+
}
|
240
|
+
}).
|
241
|
+
to_return(:status => 200, :body => "", :headers => {})
|
214
242
|
|
215
243
|
client.track(5, "purchase", :type => "socks", :price => "13.99", :timestamp => date)
|
216
244
|
end
|
217
245
|
|
218
|
-
it "doesn't send timestamp if timestamp isn't
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
246
|
+
it "doesn't send timestamp if timestamp isn't an integer" do
|
247
|
+
stub_request(:post, api_uri('/api/v1/customers/5/events')).
|
248
|
+
with(:body => {
|
249
|
+
:name => "purchase",
|
250
|
+
:data => {
|
251
|
+
:type => "socks",
|
252
|
+
:price => "13.99",
|
253
|
+
:timestamp => "Hello world"
|
254
|
+
}
|
255
|
+
}).
|
256
|
+
|
257
|
+
to_return(:status => 200, :body => "", :headers => {})
|
226
258
|
|
227
259
|
client.track(5, "purchase", :type => "socks", :price => "13.99", :timestamp => "Hello world")
|
228
260
|
end
|
229
261
|
|
230
262
|
context "tracking an anonymous event" do
|
231
263
|
it "sends a POST request to the customer.io's anonymous event API" do
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
it "uses the site_id and api key for basic auth" do
|
237
|
-
Customerio::Client.should_receive(:post).with("/api/v1/events", {
|
238
|
-
:basic_auth => { :username => "SITE_ID", :password => "API_KEY" },
|
239
|
-
:body => anything()
|
240
|
-
}).and_return(response)
|
241
|
-
|
242
|
-
client.track("purchase")
|
243
|
-
end
|
244
|
-
|
245
|
-
it "sends the event name" do
|
246
|
-
Customerio::Client.should_receive(:post).with("/api/v1/events", {
|
247
|
-
:basic_auth => anything(),
|
248
|
-
:body => { :name => "purchase", :data => {} }
|
249
|
-
}).and_return(response)
|
264
|
+
stub_request(:post, api_uri('/api/v1/events')).
|
265
|
+
with(:body => "name=purchase").
|
266
|
+
to_return(:status => 200, :body => "", :headers => {})
|
250
267
|
|
251
268
|
client.track("purchase")
|
252
269
|
end
|
253
270
|
|
254
271
|
it "sends any optional event attributes" do
|
255
|
-
|
256
|
-
:
|
257
|
-
:body => {
|
272
|
+
stub_request(:post, api_uri('/api/v1/events')).
|
273
|
+
with(:body => {
|
258
274
|
:name => "purchase",
|
259
|
-
:data => {
|
260
|
-
|
261
|
-
|
275
|
+
:data => {
|
276
|
+
:type => "socks",
|
277
|
+
:price => "13.99"
|
278
|
+
}
|
279
|
+
}).
|
280
|
+
to_return(:status => 200, :body => "", :headers => {})
|
262
281
|
|
263
282
|
client.track("purchase", :type => "socks", :price => "13.99")
|
264
283
|
end
|
265
284
|
|
266
285
|
it "allows sending of a timestamp" do
|
267
|
-
|
268
|
-
:
|
269
|
-
:body => {
|
286
|
+
stub_request(:post, api_uri('/api/v1/events')).
|
287
|
+
with(:body => {
|
270
288
|
:name => "purchase",
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
289
|
+
:data => {
|
290
|
+
:type => "socks",
|
291
|
+
:price => "13.99",
|
292
|
+
:timestamp => "1561231234"
|
293
|
+
},
|
294
|
+
:timestamp => "1561231234"
|
295
|
+
}).
|
296
|
+
to_return(:status => 200, :body => "", :headers => {})
|
275
297
|
|
276
298
|
client.track("purchase", :type => "socks", :price => "13.99", :timestamp => 1561231234)
|
277
299
|
end
|
@@ -279,55 +301,50 @@ describe Customerio::Client do
|
|
279
301
|
end
|
280
302
|
|
281
303
|
describe "#anonymous_track" do
|
282
|
-
it "sends a POST request to the customer.io event API" do
|
283
|
-
Customerio::Client.should_receive(:post).with("/api/v1/events", anything()).and_return(response)
|
284
|
-
client.anonymous_track("purchase")
|
285
|
-
end
|
286
|
-
|
287
304
|
it "raises an error if POST doesn't return a 2xx response code" do
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
it "uses the site_id and api key for basic auth" do
|
293
|
-
Customerio::Client.should_receive(:post).with("/api/v1/events", {
|
294
|
-
:basic_auth => { :username => "SITE_ID", :password => "API_KEY" },
|
295
|
-
:body => anything()
|
296
|
-
})
|
305
|
+
stub_request(:post, api_uri('/api/v1/events')).
|
306
|
+
with(:body => "name=purchase").
|
307
|
+
to_return(:status => 500, :body => "", :headers => {})
|
297
308
|
|
298
|
-
client.anonymous_track("purchase")
|
309
|
+
lambda { client.anonymous_track("purchase") }.should raise_error(Customerio::Client::InvalidResponse)
|
299
310
|
end
|
300
311
|
|
301
|
-
it "sends the event name" do
|
302
|
-
|
303
|
-
:
|
304
|
-
:
|
305
|
-
}).and_return(response)
|
312
|
+
it "uses the site_id and api key for basic auth and sends the event name" do
|
313
|
+
stub_request(:post, api_uri('/api/v1/events')).
|
314
|
+
with(:body => "name=purchase").
|
315
|
+
to_return(:status => 200, :body => "", :headers => {})
|
306
316
|
|
307
317
|
client.anonymous_track("purchase")
|
308
318
|
end
|
309
319
|
|
310
320
|
it "sends any optional event attributes" do
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
321
|
+
stub_request(:post, api_uri('/api/v1/events')).
|
322
|
+
with(:body => {
|
323
|
+
:name => "purchase",
|
324
|
+
:data => {
|
325
|
+
:type => "socks",
|
326
|
+
:price => "27.99"
|
327
|
+
},
|
328
|
+
}).
|
329
|
+
|
330
|
+
to_return(:status => 200, :body => "", :headers => {})
|
318
331
|
|
319
332
|
client.anonymous_track("purchase", :type => "socks", :price => "27.99")
|
320
333
|
end
|
321
334
|
|
322
335
|
it "allows sending of a timestamp" do
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
336
|
+
stub_request(:post, api_uri('/api/v1/events')).
|
337
|
+
with(:body => {
|
338
|
+
:name => "purchase",
|
339
|
+
:data => {
|
340
|
+
:type => "socks",
|
341
|
+
:price => "27.99",
|
342
|
+
:timestamp => "1561235678"
|
343
|
+
},
|
344
|
+
:timestamp => "1561235678"
|
345
|
+
}).
|
346
|
+
|
347
|
+
to_return(:status => 200, :body => "", :headers => {})
|
331
348
|
|
332
349
|
client.anonymous_track("purchase", :type => "socks", :price => "27.99", :timestamp => 1561235678)
|
333
350
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -2,11 +2,13 @@ require 'rubygems'
|
|
2
2
|
require 'bundler/setup'
|
3
3
|
|
4
4
|
require 'customerio'
|
5
|
-
require '
|
5
|
+
require 'webmock'
|
6
6
|
|
7
|
-
|
7
|
+
WebMock.disable_net_connect!
|
8
8
|
|
9
9
|
require 'rspec'
|
10
|
+
require 'webmock/rspec'
|
11
|
+
|
10
12
|
RSpec.configure do |config|
|
11
13
|
config.expect_with(:rspec) { |c| c.syntax = :should }
|
12
14
|
config.mock_with(:rspec) { |c| c.syntax = :should }
|
metadata
CHANGED
@@ -1,51 +1,45 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: customerio
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- John Allison
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-03-
|
11
|
+
date: 2016-03-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: multi_json
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- -
|
18
|
-
- !ruby/object:Gem::Version
|
19
|
-
version: '0.12'
|
20
|
-
- - ! '>='
|
17
|
+
- - ~>
|
21
18
|
- !ruby/object:Gem::Version
|
22
|
-
version: '0
|
19
|
+
version: '1.0'
|
23
20
|
type: :runtime
|
24
21
|
prerelease: false
|
25
22
|
version_requirements: !ruby/object:Gem::Requirement
|
26
23
|
requirements:
|
27
|
-
- -
|
28
|
-
- !ruby/object:Gem::Version
|
29
|
-
version: '0.12'
|
30
|
-
- - ! '>='
|
24
|
+
- - ~>
|
31
25
|
- !ruby/object:Gem::Version
|
32
|
-
version: '0
|
26
|
+
version: '1.0'
|
33
27
|
- !ruby/object:Gem::Dependency
|
34
|
-
name:
|
28
|
+
name: rake
|
35
29
|
requirement: !ruby/object:Gem::Requirement
|
36
30
|
requirements:
|
37
|
-
- -
|
31
|
+
- - ! '>='
|
38
32
|
- !ruby/object:Gem::Version
|
39
|
-
version: '
|
40
|
-
type: :
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
41
35
|
prerelease: false
|
42
36
|
version_requirements: !ruby/object:Gem::Requirement
|
43
37
|
requirements:
|
44
|
-
- -
|
38
|
+
- - ! '>='
|
45
39
|
- !ruby/object:Gem::Version
|
46
|
-
version: '
|
40
|
+
version: '0'
|
47
41
|
- !ruby/object:Gem::Dependency
|
48
|
-
name:
|
42
|
+
name: rspec
|
49
43
|
requirement: !ruby/object:Gem::Requirement
|
50
44
|
requirements:
|
51
45
|
- - ! '>='
|
@@ -59,7 +53,7 @@ dependencies:
|
|
59
53
|
- !ruby/object:Gem::Version
|
60
54
|
version: '0'
|
61
55
|
- !ruby/object:Gem::Dependency
|
62
|
-
name:
|
56
|
+
name: webmock
|
63
57
|
requirement: !ruby/object:Gem::Requirement
|
64
58
|
requirements:
|
65
59
|
- - ! '>='
|
@@ -73,7 +67,21 @@ dependencies:
|
|
73
67
|
- !ruby/object:Gem::Version
|
74
68
|
version: '0'
|
75
69
|
- !ruby/object:Gem::Dependency
|
76
|
-
name:
|
70
|
+
name: addressable
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ~>
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 2.3.6
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ~>
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 2.3.6
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: json
|
77
85
|
requirement: !ruby/object:Gem::Requirement
|
78
86
|
requirements:
|
79
87
|
- - ! '>='
|
@@ -103,6 +111,7 @@ files:
|
|
103
111
|
- customerio.gemspec
|
104
112
|
- lib/customerio.rb
|
105
113
|
- lib/customerio/client.rb
|
114
|
+
- lib/customerio/param_encoder.rb
|
106
115
|
- lib/customerio/version.rb
|
107
116
|
- spec/client_spec.rb
|
108
117
|
- spec/spec_helper.rb
|