keen 0.5.0 → 0.6.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.
@@ -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