keen 0.5.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -12,6 +12,12 @@ module Keen
12
12
  :path, :headers, :body)
13
13
  @http.post(path, body, headers)
14
14
  end
15
+
16
+ def get(options)
17
+ path, headers = options.values_at(
18
+ :path, :headers)
19
+ @http.get(path, headers)
20
+ end
15
21
  end
16
22
 
17
23
  class Async
@@ -1,3 +1,3 @@
1
1
  module Keen
2
- VERSION = "0.5.0"
2
+ VERSION = "0.6.0"
3
3
  end
@@ -3,51 +3,129 @@ require File.expand_path("../spec_helper", __FILE__)
3
3
  describe "Keen IO API" do
4
4
  let(:project_id) { ENV['KEEN_PROJECT_ID'] }
5
5
 
6
- let(:collection) { "users" }
7
- let(:event_properties) { { "name" => "Bob" } }
8
- let(:api_success) { { "created" => true } }
6
+ describe "publishing" do
7
+ let(:collection) { "users" }
8
+ let(:event_properties) { { "name" => "Bob" } }
9
+ let(:api_success) { { "created" => true } }
9
10
 
10
- describe "success" do
11
-
12
- it "should return a created status for a valid post" do
13
- Keen.publish(collection, event_properties).should == api_success
11
+ describe "success" do
12
+ it "should return a created status for a valid post" do
13
+ Keen.publish(collection, event_properties).should == api_success
14
+ end
14
15
  end
15
- end
16
16
 
17
- describe "failure" do
18
- it "should raise a not found error if an invalid project id" do
19
- client = Keen::Client.new(:project_id => "riker")
20
- expect {
21
- client.publish(collection, event_properties)
22
- }.to raise_error(Keen::NotFoundError)
23
- end
17
+ describe "failure" do
18
+ it "should raise a not found error if an invalid project id" do
19
+ client = Keen::Client.new(:project_id => "riker")
20
+ expect {
21
+ client.publish(collection, event_properties)
22
+ }.to raise_error(Keen::NotFoundError)
23
+ end
24
24
 
25
- it "should success if a non-url-safe event collection is specified" do
26
- Keen.publish("infinite possibilities", event_properties).should == api_success
25
+ it "should succeed if a non-url-safe event collection is specified" do
26
+ Keen.publish("infinite possibilities", event_properties).should == api_success
27
+ end
27
28
  end
28
- end
29
29
 
30
- describe "async" do
31
- # no TLS support in EventMachine on jRuby
32
- unless defined?(JRUBY_VERSION)
30
+ describe "async" do
31
+ # no TLS support in EventMachine on jRuby
32
+ unless defined?(JRUBY_VERSION)
33
33
 
34
- it "should publish the event and trigger callbacks" do
35
- EM.run {
36
- Keen.publish_async(collection, event_properties).callback { |response|
37
- response.should == api_success
38
- EM.stop
34
+ it "should publish the event and trigger callbacks" do
35
+ EM.run {
36
+ Keen.publish_async(collection, event_properties).callback { |response|
37
+ response.should == api_success
38
+ EM.stop
39
+ }
39
40
  }
40
- }
41
- end
41
+ end
42
42
 
43
- it "should publish to non-url-safe collections" do
44
- EM.run {
45
- Keen.publish_async("foo bar", event_properties).callback { |response|
46
- response.should == api_success
47
- EM.stop
43
+ it "should publish to non-url-safe collections" do
44
+ EM.run {
45
+ Keen.publish_async("foo bar", event_properties).callback { |response|
46
+ response.should == api_success
47
+ EM.stop
48
+ }
48
49
  }
49
- }
50
+ end
50
51
  end
51
52
  end
52
53
  end
54
+
55
+ describe "queries" do
56
+ let(:api_key) { ENV['KEEN_API_KEY'] }
57
+ let(:event_collection) { "purchases_" + rand(100000).to_s }
58
+ let(:returns_event_collection) { "returns_" + rand(100000).to_s }
59
+
60
+ before(:all) do
61
+ Keen.publish(event_collection, {
62
+ :username => "bob",
63
+ :price => 10
64
+ })
65
+ Keen.publish(event_collection, {
66
+ :username => "ted",
67
+ :price => 20
68
+ })
69
+ Keen.publish(returns_event_collection, {
70
+ :username => "bob",
71
+ :price => 30
72
+ })
73
+ sleep(1)
74
+ end
75
+
76
+ it "should return a valid count" do
77
+ Keen.count(event_collection).should == 2
78
+ end
79
+
80
+ it "should return a valid count_unique" do
81
+ Keen.count_unique(event_collection, :target_property => "price").should == 2
82
+ end
83
+
84
+ it "should return a valid sum" do
85
+ Keen.sum(event_collection, :target_property => "price").should == 30
86
+ end
87
+
88
+ it "should return a valid minimum" do
89
+ Keen.minimum(event_collection, :target_property => "price").should == 10
90
+ end
91
+
92
+ it "should return a valid maximum" do
93
+ Keen.maximum(event_collection, :target_property => "price").should == 20
94
+ end
95
+
96
+ it "should return a valid average" do
97
+ Keen.average(event_collection, :target_property => "price").should == 15
98
+ end
99
+
100
+ it "should return a valid select_unique" do
101
+ results = Keen.select_unique(event_collection, :target_property => "price")
102
+ results.sort.should == [10, 20].sort
103
+ end
104
+
105
+ it "should return a valid extraction" do
106
+ results = Keen.extraction(event_collection)
107
+ results.length.should == 2
108
+ results.all? { |result| result["keen"] }.should be_true
109
+ end
110
+
111
+ it "should return a valid funnel" do
112
+ steps = [{
113
+ :event_collection => event_collection,
114
+ :actor_property => "username"
115
+ }, {
116
+ :event_collection => returns_event_collection,
117
+ :actor_property => "username"
118
+ }]
119
+ results = Keen.funnel(:steps => steps)
120
+ results.should == [2, 1]
121
+ end
122
+
123
+ it "should apply filters" do
124
+ Keen.count(event_collection, :filters => [{
125
+ :property_name => "username",
126
+ :operator => "eq",
127
+ :property_value => "ted"
128
+ }]).should == 1
129
+ end
130
+ end
53
131
  end
@@ -0,0 +1,161 @@
1
+ require File.expand_path("../../spec_helper", __FILE__)
2
+
3
+ describe Keen::Client::PublishingMethods do
4
+ let(:project_id) { "12345" }
5
+ let(:api_host) { "api.keen.io" }
6
+ let(:collection) { "users" }
7
+ let(:event_properties) { { "name" => "Bob" } }
8
+ let(:api_success) { { "created" => true } }
9
+ let(:client) { Keen::Client.new(:project_id => project_id) }
10
+
11
+ describe "publish" do
12
+ it "should post using the collection and properties" do
13
+ stub_keen_post(api_event_resource_url(collection), 201, "")
14
+ client.publish(collection, event_properties)
15
+ expect_keen_post(api_event_resource_url(collection), event_properties, "sync")
16
+ end
17
+
18
+ it "should return the proper response" do
19
+ api_response = { "created" => true }
20
+ stub_keen_post(api_event_resource_url(collection), 201, api_response)
21
+ client.publish(collection, event_properties).should == api_response
22
+ end
23
+
24
+ it "should raise an argument error if no event collection is specified" do
25
+ expect {
26
+ client.publish(nil, {})
27
+ }.to raise_error(ArgumentError)
28
+ end
29
+
30
+ it "should raise an argument error if no properties are specified" do
31
+ expect {
32
+ client.publish(collection, nil)
33
+ }.to raise_error(ArgumentError)
34
+ end
35
+
36
+ it "should url encode the event collection" do
37
+ stub_keen_post(api_event_resource_url("foo%20bar"), 201, "")
38
+ client.publish("foo bar", event_properties)
39
+ expect_keen_post(api_event_resource_url("foo%20bar"), event_properties, "sync")
40
+ end
41
+
42
+ it "should wrap exceptions" do
43
+ stub_request(:post, api_event_resource_url(collection)).to_timeout
44
+ e = nil
45
+ begin
46
+ client.publish(collection, event_properties)
47
+ rescue Exception => exception
48
+ e = exception
49
+ end
50
+
51
+ e.class.should == Keen::HttpError
52
+ e.original_error.class.should == Timeout::Error
53
+ e.message.should == "Couldn't connect to Keen IO: execution expired"
54
+ end
55
+
56
+ it "should raise an exception if client has no project_id" do
57
+ expect {
58
+ Keen::Client.new.publish(collection, event_properties)
59
+ }.to raise_error(Keen::ConfigurationError)
60
+ end
61
+ end
62
+
63
+ describe "publish_async" do
64
+ # no TLS support in EventMachine on jRuby
65
+ unless defined?(JRUBY_VERSION)
66
+ it "should require a running event loop" do
67
+ expect {
68
+ client.publish_async(collection, event_properties)
69
+ }.to raise_error(Keen::Error)
70
+ end
71
+
72
+ it "should post the event data" do
73
+ stub_keen_post(api_event_resource_url(collection), 201, api_success)
74
+ EM.run {
75
+ client.publish_async(collection, event_properties).callback {
76
+ begin
77
+ expect_keen_post(api_event_resource_url(collection), event_properties, "async")
78
+ ensure
79
+ EM.stop
80
+ end
81
+ }
82
+ }
83
+ end
84
+
85
+ it "should uri encode the event collection" do
86
+ stub_keen_post(api_event_resource_url("foo%20bar"), 201, api_success)
87
+ EM.run {
88
+ client.publish_async("foo bar", event_properties).callback {
89
+ begin
90
+ expect_post(api_event_resource_url("foo%20bar"), event_properties, "async")
91
+ ensure
92
+ EM.stop
93
+ end
94
+ }
95
+ }
96
+ end
97
+
98
+ it "should raise an argument error if no event collection is specified" do
99
+ expect {
100
+ client.publish_async(nil, {})
101
+ }.to raise_error(ArgumentError)
102
+ end
103
+
104
+ it "should raise an argument error if no properties are specified" do
105
+ expect {
106
+ client.publish_async(collection, nil)
107
+ }.to raise_error(ArgumentError)
108
+ end
109
+
110
+ describe "deferrable callbacks" do
111
+ it "should trigger callbacks" do
112
+ stub_keen_post(api_event_resource_url(collection), 201, api_success)
113
+ EM.run {
114
+ client.publish_async(collection, event_properties).callback { |response|
115
+ begin
116
+ response.should == api_success
117
+ ensure
118
+ EM.stop
119
+ end
120
+ }
121
+ }
122
+ end
123
+
124
+ it "should trigger errbacks" do
125
+ stub_request(:post, api_event_resource_url(collection)).to_timeout
126
+ EM.run {
127
+ client.publish_async(collection, event_properties).errback { |error|
128
+ begin
129
+ error.should_not be_nil
130
+ error.message.should == "Couldn't connect to Keen IO: WebMock timeout error"
131
+ ensure
132
+ EM.stop
133
+ end
134
+ }
135
+ }
136
+ end
137
+ end
138
+ end
139
+ end
140
+
141
+ it "should raise an exception if client has no project_id" do
142
+ expect {
143
+ Keen::Client.new.publish_async(collection, event_properties)
144
+ }.to raise_error(Keen::ConfigurationError)
145
+ end
146
+
147
+ describe "#add_event" do
148
+ it "should alias to publish" do
149
+ client.should_receive(:publish).with("users", {:a => 1}, {:b => 2})
150
+ client.add_event("users", {:a => 1}, {:b => 2})
151
+ end
152
+ end
153
+
154
+ describe "beacon_url" do
155
+ it "should return a url with a base-64 encoded json param" do
156
+ client = Keen::Client.new(project_id)
157
+ client.beacon_url("sign_ups", { :name => "Bob" }).should ==
158
+ "https://api.keen.io/3.0/projects/12345/events/sign_ups?data=eyJuYW1lIjoiQm9iIn0="
159
+ end
160
+ end
161
+ end
@@ -0,0 +1,132 @@
1
+ require File.expand_path("../../spec_helper", __FILE__)
2
+
3
+ describe Keen::Client do
4
+ let(:project_id) { "12345" }
5
+ let(:api_key) { "abcde" }
6
+ let(:api_host) { "api.keen.io" }
7
+ let(:api_version) { "3.0" }
8
+ let(:event_collection) { "users" }
9
+ let(:client) { Keen::Client.new(:project_id => project_id, :api_key => api_key) }
10
+
11
+ def query_url(query_name, query_params)
12
+ "https://#{api_host}/#{api_version}/projects/#{project_id}/queries/#{query_name}#{query_params}"
13
+ end
14
+
15
+ describe "querying names" do
16
+ let(:params) { { :event_collection => "signups" } }
17
+
18
+ ["minimum", "maximum", "sum", "average", "count", "count_unique", "select_unique", "extraction"].each do |query_name|
19
+ it "should call keen query passing the query name" do
20
+ client.should_receive(:query).with(query_name.to_sym, event_collection, params)
21
+ client.send(query_name, event_collection, params)
22
+ end
23
+ end
24
+
25
+ describe "funnel" do
26
+ it "should call keen query w/o event collection" do
27
+ client.should_receive(:query).with(:funnel, nil, params)
28
+ client.funnel(params)
29
+ end
30
+ end
31
+ end
32
+
33
+ describe "#query" do
34
+ describe "with an improperly configured client" do
35
+ it "should require a project id" do
36
+ expect {
37
+ Keen::Client.new(:api_key => api_key).count("users", {})
38
+ }.to raise_error(Keen::ConfigurationError)
39
+ end
40
+
41
+ it "should require an api key" do
42
+ expect {
43
+ Keen::Client.new(:project_id => project_id).count("users", {})
44
+ }.to raise_error(Keen::ConfigurationError)
45
+ end
46
+ end
47
+
48
+ describe "with a valid client" do
49
+ let(:query) { client.method(:query) }
50
+ let(:query_name) { "count" }
51
+ let(:api_response) { { "result" => 1 } }
52
+
53
+ def test_query(extra_query_params="", extra_query_hash={})
54
+ expected_query_params = "?api_key=#{api_key}&event_collection=#{event_collection}"
55
+ expected_query_params += extra_query_params
56
+ expected_url = query_url(query_name, expected_query_params)
57
+ stub_keen_get(expected_url, 200, :result => 1)
58
+ response = query.call(query_name, event_collection, extra_query_hash)
59
+ response.should == api_response["result"]
60
+ expect_keen_get(expected_url, "sync")
61
+ end
62
+
63
+ it "should call the API w/ proper headers and return the processed json response" do
64
+ test_query
65
+ end
66
+
67
+ it "should encode filters properly" do
68
+ filters = [{
69
+ :property_name => "animal",
70
+ :operator => "eq",
71
+ :property_value => "dogs"
72
+ }]
73
+ filter_str = MultiJson.encode(filters)
74
+ test_query("&filters=#{filter_str}", :filters => filters)
75
+ end
76
+
77
+ it "should encode absolute timeframes properly" do
78
+ timeframe = {
79
+ :start => "2012-08-13T19:00Z",
80
+ :end => "2012-08-13T19:00Z",
81
+ }
82
+ timeframe_str = MultiJson.encode(timeframe)
83
+ test_query("&timeframe=#{timeframe_str}", :timeframe => timeframe)
84
+ end
85
+
86
+ it "should encode steps properly" do
87
+ steps = [{
88
+ :event_collection => "signups",
89
+ :actor_property => "user.id"
90
+ }]
91
+ steps_str = MultiJson.encode(steps)
92
+ test_query("&steps=#{steps_str}", :steps => steps)
93
+ end
94
+
95
+ it "should not encode relative timeframes" do
96
+ timeframe = "last_10_days"
97
+ test_query("&timeframe=#{timeframe}", :timeframe => timeframe)
98
+ end
99
+
100
+ it "should raise a failed responses" do
101
+ query_params = "?api_key=#{api_key}&event_collection=#{event_collection}"
102
+ url = query_url(query_name, query_params)
103
+
104
+ stub_keen_get(url, 401, :error => {})
105
+ expect {
106
+ query.call(query_name, event_collection, {})
107
+ }.to raise_error(Keen::AuthenticationError)
108
+ expect_keen_get(url, "sync")
109
+ end
110
+ end
111
+ end
112
+
113
+ describe "#count" do
114
+ it "should not require params" do
115
+ query_params = "?api_key=#{api_key}&event_collection=#{event_collection}"
116
+ url = query_url("count", query_params)
117
+ stub_keen_get(url, 200, :result => 10)
118
+ client.count(event_collection).should == 10
119
+ expect_keen_get(url, "sync")
120
+ end
121
+ end
122
+
123
+ describe "#extraction" do
124
+ it "should not require params" do
125
+ query_params = "?api_key=#{api_key}&event_collection=#{event_collection}"
126
+ url = query_url("extraction", query_params)
127
+ stub_keen_get(url, 200, :result => { "a" => 1 } )
128
+ client.extraction(event_collection).should == { "a" => 1 }
129
+ expect_keen_get(url, "sync")
130
+ end
131
+ end
132
+ end