leanplum_api 1.3.1

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.
@@ -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