leanplum_api 1.3.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,45 @@
1
+ module LeanplumApi
2
+ class << self
3
+ attr_accessor :configuration
4
+ end
5
+
6
+ def self.configure
7
+ self.configuration ||= Configuration.new
8
+ yield(configuration) if block_given?
9
+ end
10
+
11
+ def self.reset!
12
+ self.configuration = Configuration.new
13
+ end
14
+
15
+ class Configuration
16
+ DEFAULT_LEANPLUM_API_VERSION = '1.0.6'
17
+
18
+ # Required IDs and access keys provided by leanplum
19
+ attr_accessor :app_id
20
+ attr_accessor :production_key
21
+ attr_accessor :content_read_only_key
22
+ attr_accessor :data_export_key
23
+ attr_accessor :development_key
24
+
25
+ # Optional
26
+ attr_accessor :api_version
27
+ attr_accessor :developer_mode
28
+ attr_accessor :log_path
29
+ attr_accessor :timeout_seconds
30
+
31
+ # Optional configuration for exporting raw data to S3.
32
+ # If s3_bucket_name is provided, s3_access_id and s3_access_key must also be provided.
33
+ attr_accessor :s3_bucket_name
34
+ attr_accessor :s3_access_id
35
+ attr_accessor :s3_access_key
36
+ attr_accessor :s3_object_prefix
37
+
38
+ def initialize
39
+ @log_path = 'log'
40
+ @api_version = DEFAULT_LEANPLUM_API_VERSION
41
+ @developer_mode = false
42
+ @timeout_seconds = 600
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,15 @@
1
+ require 'leanplum_api/http'
2
+
3
+ module LeanplumApi
4
+ class ContentReadOnly < HTTP
5
+ def initialize(options = {})
6
+ raise 'Content read only key not configured!' unless LeanplumApi.configuration.content_read_only_key
7
+ super
8
+ end
9
+
10
+ # Data export API requests need to use the Data Export key
11
+ def authentication_params
12
+ super.merge(clientKey: LeanplumApi.configuration.content_read_only_key)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ require 'leanplum_api/http'
2
+
3
+ module LeanplumApi
4
+ class DataExport < HTTP
5
+ def initialize(options = {})
6
+ raise 'Data export key not configured' unless LeanplumApi.configuration.data_export_key
7
+ super
8
+ end
9
+
10
+ # Data export API requests need to use the Data Export key
11
+ def authentication_params
12
+ super.merge(clientKey: LeanplumApi.configuration.data_export_key)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,14 @@
1
+ require 'leanplum_api/http'
2
+
3
+ module LeanplumApi
4
+ class Development < HTTP
5
+ def initialize(options = {})
6
+ raise 'Development key not configured!' unless LeanplumApi.configuration.development_key
7
+ super
8
+ end
9
+
10
+ def authentication_params
11
+ super.merge(clientKey: LeanplumApi.configuration.development_key)
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,4 @@
1
+ module LeanplumApi
2
+ class LeanplumValidationException < Exception
3
+ end
4
+ end
@@ -0,0 +1,68 @@
1
+ require 'faraday'
2
+ require 'faraday_middleware'
3
+ require 'uri'
4
+
5
+ module LeanplumApi
6
+ class HTTP
7
+ LEANPLUM_API_PATH = '/api'
8
+
9
+ def initialize(options = {})
10
+ @logger = options[:logger] || Logger.new(STDERR)
11
+ end
12
+
13
+ def post(payload)
14
+ connection.post("#{LEANPLUM_API_PATH}?#{authed_multi_param_string}") do |request|
15
+ request.body = { data: payload }.to_json
16
+ end
17
+ end
18
+
19
+ def get(query)
20
+ connection.get(LEANPLUM_API_PATH, query.merge(authentication_params))
21
+ end
22
+
23
+ def authentication_params
24
+ {
25
+ appId: LeanplumApi.configuration.app_id,
26
+ clientKey: LeanplumApi.configuration.production_key,
27
+ apiVersion: LeanplumApi.configuration.api_version,
28
+ devMode: LeanplumApi.configuration.developer_mode
29
+ }
30
+ end
31
+
32
+ private
33
+
34
+ def connection
35
+ fail 'APP_ID not configured!' unless LeanplumApi.configuration.app_id
36
+ fail 'PRODUCTION_KEY not configured!' unless LeanplumApi.configuration.production_key
37
+
38
+ options = {
39
+ url: 'https://www.leanplum.com',
40
+ request: {
41
+ timeout: LeanplumApi.configuration.timeout_seconds,
42
+ open_timeout: LeanplumApi.configuration.timeout_seconds
43
+ }
44
+ }
45
+
46
+ @connection ||= Faraday.new(options) do |connection|
47
+ connection.request :json
48
+
49
+ connection.response :logger, @logger, bodies: true if api_debug?
50
+ connection.response :json, :content_type => /\bjson$/
51
+
52
+ connection.adapter Faraday.default_adapter
53
+ end
54
+ end
55
+
56
+ def api_debug?
57
+ ENV['LEANPLUM_API_DEBUG'].to_s =~ /^(true|1)$/i
58
+ end
59
+
60
+ def authed_multi_param_string
61
+ if LeanplumApi.configuration.developer_mode
62
+ URI.encode_www_form(authentication_params.merge(action: 'multi', time: Time.now.utc.strftime('%s'), devMode: true))
63
+ else
64
+ URI.encode_www_form(authentication_params.merge(action: 'multi', time: Time.now.utc.strftime('%s')))
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,23 @@
1
+ require 'logger'
2
+
3
+ module LeanplumApi
4
+ class LeanplumApiLogger < Logger
5
+ def format_message(severity, timestamp, progname, msg)
6
+ @keys ||= [
7
+ LeanplumApi.configuration.production_key,
8
+ LeanplumApi.configuration.app_id,
9
+ LeanplumApi.configuration.data_export_key,
10
+ LeanplumApi.configuration.content_read_only_key,
11
+ LeanplumApi.configuration.development_key,
12
+ LeanplumApi.configuration.s3_access_key,
13
+ LeanplumApi.configuration.s3_access_id
14
+ ].compact
15
+
16
+ if @keys.empty?
17
+ "#{timestamp.strftime('%Y-%m-%d %H:%M:%S')} #{severity} #{msg}\n"
18
+ else
19
+ "#{timestamp.strftime('%Y-%m-%d %H:%M:%S')} #{severity} #{msg.gsub(/#{@keys.map { |k| Regexp.quote(k) }.join('|')}/, '<HIDDEN_KEY>')}\n"
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,3 @@
1
+ module LeanplumApi
2
+ VERSION = '1.3.1'
3
+ end
data/spec/api_spec.rb ADDED
@@ -0,0 +1,230 @@
1
+ require 'spec_helper'
2
+
3
+ describe LeanplumApi::API do
4
+ let(:api) { LeanplumApi::API.new }
5
+ let(:users) do
6
+ [{
7
+ user_id: 123456,
8
+ first_name: 'Mike',
9
+ last_name: 'Jones',
10
+ gender: 'm',
11
+ email: 'still_tippin@test.com',
12
+ create_date: '2010-01-01'.to_date
13
+ }]
14
+ end
15
+ let(:first_user_id) { users.first[:user_id] }
16
+
17
+ context 'users' do
18
+ it 'build_user_attributes_hash' do
19
+ expect(api.send(:build_user_attributes_hash, users.first)).to eq({
20
+ 'userId' => 123456,
21
+ 'action' => 'setUserAttributes',
22
+ 'userAttributes' => {
23
+ first_name: 'Mike',
24
+ last_name: 'Jones',
25
+ gender: 'm',
26
+ email: 'still_tippin@test.com',
27
+ create_date: '2010-01-01'
28
+ }
29
+ })
30
+ end
31
+
32
+ context 'set_user_attributes' do
33
+ context 'valid request' do
34
+ it 'should successfully set user attributes' do
35
+ VCR.use_cassette('set_user_attributes') do
36
+ expect { api.set_user_attributes(users) }.to_not raise_error
37
+ end
38
+ end
39
+ end
40
+
41
+ context 'invalid request' do
42
+ let(:broken_users) { users + [{ first_name: 'Moe' }] }
43
+
44
+ it 'should raise an error' do
45
+ VCR.use_cassette('set_user_attributes_broken') do
46
+ expect{ api.set_user_attributes(broken_users) }.to raise_error(/No device_id or user_id in hash/)
47
+ end
48
+ end
49
+ end
50
+ end
51
+
52
+ context 'export_user' do
53
+ it 'should get user attributes for this user' do
54
+ VCR.use_cassette('export_user') do
55
+ user_info = api.export_user(first_user_id)
56
+ user_info.keys.each do |k|
57
+ if users.first[k.to_sym].is_a?(Date) || users.first[k.to_sym].is_a?(DateTime)
58
+ expect(user_info[k]).to eq(users.first[k.to_sym].strftime('%Y-%m-%d'))
59
+ else
60
+ expect(user_info[k]).to eq(users.first[k.to_sym])
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+
67
+ context 'export_users' do
68
+ it 'should export users'
69
+ end
70
+
71
+ context 'reset_anomalous_users' do
72
+ it 'should successfully call setUserAttributes with resetAnomalies' do
73
+ VCR.use_cassette('reset_anomalous_user') do
74
+ expect { api.reset_anomalous_users(first_user_id) }.to_not raise_error
75
+ end
76
+ end
77
+ end
78
+ end
79
+
80
+ context 'events' do
81
+ let(:events) do
82
+ [
83
+ {
84
+ user_id: 12345,
85
+ event: 'purchase',
86
+ time: Time.now.utc,
87
+ params: {
88
+ 'some_timestamp' => '2015-05-01 01:02:03'
89
+ }
90
+ },
91
+ {
92
+ user_id: 54321,
93
+ event: 'purchase_page_view',
94
+ time: Time.now.utc - 10.minutes,
95
+ }
96
+ ]
97
+ end
98
+
99
+ it 'build_event_attributes_hash' do
100
+ expect(api.send(:build_event_attributes_hash, events.first)).to eq({
101
+ 'userId' => 12345,
102
+ 'time' => Time.now.utc.strftime('%s'),
103
+ 'action' => 'track',
104
+ 'event' => 'purchase',
105
+ 'params' => { params: { 'some_timestamp'=>'2015-05-01 01:02:03' } }
106
+ })
107
+ end
108
+
109
+ context 'without user attributes' do
110
+ context 'valid request' do
111
+ it 'should successfully track events' do
112
+ VCR.use_cassette('track_events') do
113
+ expect { api.track_events(events) }.to_not raise_error
114
+ end
115
+ end
116
+ end
117
+
118
+ context 'invalid request' do
119
+ let(:broken_events) { events + [{ event: 'no_user_id_event' }] }
120
+
121
+ it 'should raise an error' do
122
+ VCR.use_cassette('track_events_broken') do
123
+ expect { api.track_events(broken_events) }.to raise_error(/No device_id or user_id in hash/)
124
+ end
125
+ end
126
+ end
127
+
128
+ context 'anomalous data force_anomalous_override' do
129
+ it 'should successfully force the anomalous data override events' do
130
+ VCR.use_cassette('track_events_anomaly_overrider') do
131
+ expect { api.track_events(events, force_anomalous_override: true) }.to_not raise_error
132
+ end
133
+ end
134
+ end
135
+ end
136
+
137
+ context 'along with user attributes' do
138
+ it 'should work' do
139
+ VCR.use_cassette('track_events_and_attributes') do
140
+ expect { api.track_multi(events, users) }.to_not raise_error
141
+ end
142
+ end
143
+ end
144
+ end
145
+
146
+ # Data export and content read only endpoints forbid use of devMode
147
+ context 'non devMode methods' do
148
+ around(:all) do |example|
149
+ LeanplumApi.configure { |c| c.developer_mode = false }
150
+ example.run
151
+ LeanplumApi.configure { |c| c.developer_mode = true }
152
+ end
153
+
154
+ context 'data export methods' do
155
+ let(:s3_bucket_name) { 'bucket' }
156
+ let(:s3_access_key) { 's3_access_key' }
157
+ let(:s3_access_id) { 's3_access_id' }
158
+
159
+ around(:all) do |example|
160
+ LeanplumApi.configure do |c|
161
+ c.developer_mode = false
162
+ end
163
+ example.run
164
+ LeanplumApi.configure { |c| c.developer_mode = true }
165
+ end
166
+
167
+ context 'export_data' do
168
+ it 'should request a data export job with a starttime' do
169
+ VCR.use_cassette('export_data') do
170
+ expect { api.export_data(Time.at(1438660800).utc) }.to raise_error(/No matching data found/)
171
+ end
172
+ end
173
+
174
+ it 'should request a data export job with start and end dates' do
175
+ VCR.use_cassette('export_data_dates') do
176
+ expect { api.export_data(Date.new(2015, 9, 5), Date.new(2015, 9, 6)) }.to raise_error(/No matching data found/)
177
+ end
178
+ end
179
+ end
180
+
181
+ context 'get_export_results' do
182
+ it 'should get a status for a data export job' do
183
+ VCR.use_cassette('get_export_results') do
184
+ response = api.get_export_results('export_4727756026281984_2904941266315269120')
185
+ expect(response).to eq({
186
+ files: ['https://leanplum_export.storage.googleapis.com/export-4727756026281984-d5969d55-f242-48a6-85a3-165af08e2306-output-0'],
187
+ number_of_bytes: 36590,
188
+ number_of_sessions: 101,
189
+ state: LeanplumApi::API::EXPORT_FINISHED,
190
+ s3_copy_status: nil
191
+ })
192
+ end
193
+ end
194
+ end
195
+ end
196
+
197
+ context 'content read only methods' do
198
+ it 'gets ab tests' do
199
+ VCR.use_cassette('get_ab_tests') do
200
+ expect(api.get_ab_tests).to eq([])
201
+ end
202
+ end
203
+
204
+ it 'gets an ab test' do
205
+ VCR.use_cassette('get_ab_test') do
206
+ expect(api.get_ab_tests(1)).to eq([])
207
+ end
208
+ end
209
+
210
+ it 'gets messages' do
211
+ VCR.use_cassette('get_messages') do
212
+ expect(api.get_messages).to eq([{
213
+ "id" => 5670583287676928,
214
+ "created" => 1440091595.799,
215
+ "name" => "New Message",
216
+ "active" => false,
217
+ "messageType" => "Push Notification"
218
+ }])
219
+ end
220
+ end
221
+
222
+ it 'gets vars' do
223
+ VCR.use_cassette('get_vars') do
224
+ vars = api.get_vars(first_user_id)
225
+ expect(vars).to eq({'test_var' => 1})
226
+ end
227
+ end
228
+ end
229
+ end
230
+ end
@@ -0,0 +1,26 @@
1
+ require 'spec_helper'
2
+
3
+ describe LeanplumApi do
4
+ context 'configuration' do
5
+ after(:each) do
6
+ LeanplumApi.reset!
7
+ end
8
+
9
+ it 'should have a default configuration' do
10
+ expect(LeanplumApi.configuration.log_path.is_a?(String)).to eq(true)
11
+ end
12
+
13
+ it 'should allow configuration' do
14
+ LeanplumApi.configure do |config|
15
+ config.log_path = 'test/path'
16
+ config.production_key = 'new_client_key'
17
+ config.app_id = 'new_app_id'
18
+ end
19
+
20
+ expect(LeanplumApi.configuration.log_path).to eq('test/path')
21
+ expect(LeanplumApi.configuration.production_key).to eq('new_client_key')
22
+ expect(LeanplumApi.configuration.app_id).to eq('new_app_id')
23
+ expect(LeanplumApi.configuration.api_version).to eq(LeanplumApi::Configuration::DEFAULT_LEANPLUM_API_VERSION)
24
+ end
25
+ end
26
+ end