leanplum_api 3.1.0 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +63 -32
  3. data/lib/leanplum_api/api.rb +107 -156
  4. data/lib/leanplum_api/configuration.rb +9 -1
  5. data/lib/leanplum_api/connection.rb +2 -2
  6. data/lib/leanplum_api/data_export_api.rb +78 -0
  7. data/lib/leanplum_api/faraday_middleware/response_validation.rb +23 -19
  8. data/lib/leanplum_api/version.rb +1 -1
  9. data/spec/api_spec.rb +142 -98
  10. data/spec/configuration_spec.rb +2 -2
  11. data/spec/data_export_api_spec.rb +57 -0
  12. data/spec/fixtures/vcr/delete_user.yml +129 -0
  13. data/spec/fixtures/vcr/export_data.yml +5 -5
  14. data/spec/fixtures/vcr/export_data_dates.yml +6 -6
  15. data/spec/fixtures/vcr/export_user.yml +7 -7
  16. data/spec/fixtures/vcr/export_users.yml +44 -0
  17. data/spec/fixtures/vcr/get_ab_test.yml +5 -5
  18. data/spec/fixtures/vcr/get_ab_tests.yml +5 -5
  19. data/spec/fixtures/vcr/get_export_results.yml +5 -5
  20. data/spec/fixtures/vcr/get_messages.yml +5 -5
  21. data/spec/fixtures/vcr/get_vars.yml +5 -5
  22. data/spec/fixtures/vcr/missing_message.yml +4 -4
  23. data/spec/fixtures/vcr/reset_anomalous_user.yml +6 -6
  24. data/spec/fixtures/vcr/set_device_attributes.yml +46 -0
  25. data/spec/fixtures/vcr/set_user_attributes.yml +7 -7
  26. data/spec/fixtures/vcr/set_user_attributes_with_devices.yml +46 -0
  27. data/spec/fixtures/vcr/set_user_attributes_with_devices_and_events.yml +46 -0
  28. data/spec/fixtures/vcr/set_user_attributes_with_events.yml +46 -0
  29. data/spec/fixtures/vcr/track_events.yml +8 -8
  30. data/spec/fixtures/vcr/track_events_and_attributes.yml +9 -9
  31. data/spec/fixtures/vcr/track_events_anomaly_overrider.yml +20 -19
  32. data/spec/fixtures/vcr/track_offline_events.yml +8 -8
  33. data/spec/http_spec.rb +6 -5
  34. data/spec/spec_helper.rb +11 -8
  35. metadata +40 -3
@@ -29,6 +29,9 @@ module LeanplumApi
29
29
  attr_accessor :logger
30
30
  attr_accessor :timeout_seconds
31
31
 
32
+ # Override validations for leanplum response. On by default.
33
+ attr_accessor :validate_response
34
+
32
35
  # Optional configuration for exporting raw data to S3.
33
36
  # If s3_bucket_name is provided, s3_access_id and s3_access_key must also be provided.
34
37
  attr_accessor :s3_bucket_name
@@ -39,9 +42,14 @@ module LeanplumApi
39
42
  def initialize
40
43
  @api_version = DEFAULT_LEANPLUM_API_VERSION
41
44
  @developer_mode = false
45
+ @validate_response = true
42
46
  @timeout_seconds = 600
43
47
  @logger = LeanplumApi::Logger.new(STDOUT)
44
- @api_debug = ENV['LEANPLUM_API_DEBUG'].to_s =~ /^(true|t|yes|y|1)$/i
48
+ @api_debug = debug_mode?
49
+ end
50
+
51
+ def debug_mode?
52
+ ENV['LEANPLUM_API_DEBUG'].to_s =~ /^(true|t|yes|y|1)$/i
45
53
  end
46
54
  end
47
55
  end
@@ -7,13 +7,13 @@ module LeanplumApi
7
7
  end
8
8
 
9
9
  def get(query)
10
- connection.get(LEANPLUM_API_PATH, query.merge(authentication_params))
10
+ connection.get(LEANPLUM_API_PATH, query.merge(authentication_params)).body['response']
11
11
  end
12
12
 
13
13
  def multi(payload)
14
14
  connection.post("#{LEANPLUM_API_PATH}?#{authed_multi_param_string}") do |request|
15
15
  request.body = { data: payload }
16
- end
16
+ end.body['response']
17
17
  end
18
18
 
19
19
  private
@@ -0,0 +1,78 @@
1
+ require 'leanplum_api/api'
2
+
3
+ # Support for data export features are semi-deprecated in the gem, because the data they give has historically
4
+ # been inaccurate. The automated S3 export has better accuracy with a fraction of the headaches.
5
+ # Use these methods at your own risk.
6
+
7
+ module LeanplumApi
8
+ class DataExportAPI < API
9
+ # Returns the jobId
10
+ # Leanplum has confirmed that using startTime and endTime, especially trying to be relatively up to the minute,
11
+ # leads to sort of unprocessed information that can be incomplete.
12
+ # They recommend using the automatic export to S3 if possible.
13
+ def export_data(start_time, end_time = nil)
14
+ LeanplumApi.configuration.logger.warn("You should probably use the direct S3 export instead of exportData")
15
+ fail "Start time #{start_time} after end time #{end_time}" if end_time && start_time > end_time
16
+ LeanplumApi.configuration.logger.info("Requesting data export from #{start_time} to #{end_time}...")
17
+
18
+ # Because of open questions about how startTime and endTime work (or don't work, as the case may be), we
19
+ # only want to pass the dates unless start and end times are specifically requested.
20
+ params = { action: 'exportData', startDate: start_time.strftime('%Y%m%d') }
21
+ params[:startTime] = start_time.strftime('%s') if start_time.is_a?(DateTime) || start_time.is_a?(Time)
22
+
23
+ if end_time
24
+ params[:endDate] = end_time.strftime('%Y%m%d')
25
+ params[:endTime] = end_time.strftime('%s') if end_time.is_a?(DateTime) || end_time.is_a?(Time)
26
+ end
27
+
28
+ # Handle optional S3 export params
29
+ if LeanplumApi.configuration.s3_bucket_name
30
+ fail 's3_bucket_name set but s3_access_id not configured!' unless LeanplumApi.configuration.s3_access_id
31
+ fail 's3_bucket_name set but s3_access_key not configured!' unless LeanplumApi.configuration.s3_access_key
32
+
33
+ params.merge!(
34
+ s3BucketName: LeanplumApi.configuration.s3_bucket_name,
35
+ s3AccessId: LeanplumApi.configuration.s3_access_id,
36
+ s3AccessKey: LeanplumApi.configuration.s3_access_key
37
+ )
38
+ params.merge!(s3ObjectPrefix: LeanplumApi.configuration.s3_object_prefix) if LeanplumApi.configuration.s3_object_prefix
39
+ end
40
+
41
+ data_export_connection.get(params).first['jobId']
42
+ end
43
+
44
+ def get_export_results(job_id)
45
+ response = data_export_connection.get(action: 'getExportResults', jobId: job_id).first
46
+
47
+ if response['state'] == EXPORT_FINISHED
48
+ LeanplumApi.configuration.logger.info("Export finished.")
49
+ LeanplumApi.configuration.logger.debug(" Response: #{response}")
50
+ {
51
+ files: response['files'],
52
+ number_of_sessions: response['numSessions'],
53
+ number_of_bytes: response['numBytes'],
54
+ state: response['state'],
55
+ s3_copy_status: response['s3CopyStatus']
56
+ }
57
+ else
58
+ { state: response['state'] }
59
+ end
60
+ end
61
+
62
+ # See leanplum docs.
63
+ # The segment syntax is identical to that produced by the "Insert Value" feature on the dashboard.
64
+ # Examples: 'Country = "US"', '{Country = "US"} and {App version = 1}'.
65
+ def export_users(ab_test_id = nil, segment = nil)
66
+ data_export_connection.get(action: 'exportUsers', segment: segment, ab_test_id: ab_test_id).first['jobId']
67
+ end
68
+
69
+ def wait_for_export_job(job_id, polling_interval = 60)
70
+ while get_export_results(job_id)[:state] != EXPORT_FINISHED
71
+ LeanplumApi.configuration.logger.debug("Polling job #{job_id}: #{get_export_results(job_id)}")
72
+ sleep(polling_interval)
73
+ end
74
+
75
+ get_export_results(job_id)
76
+ end
77
+ end
78
+ end
@@ -5,42 +5,46 @@ module LeanplumApi
5
5
  class ResponseValidation < Faraday::Middleware
6
6
  Faraday::Request.register_middleware(leanplum_response_validation: self)
7
7
 
8
- def call(environment)
9
- operations = nil
8
+ SUCCESS = 'success'.freeze
9
+ WARN = 'warning'.freeze
10
10
 
11
+ def call(environment)
11
12
  if environment.body
12
- operations = environment.body[:data] if environment.body[:data] && environment.body[:data].is_a?(Array)
13
+ requests = environment.body[:data] if environment.body[:data] && environment.body[:data].is_a?(Array)
13
14
  environment.body = environment.body.to_json
14
15
  end
15
16
 
16
17
  @app.call(environment).on_complete do |response|
17
18
  fail ResourceNotFoundError, response.inspect if response.status == 404
18
- fail BadResponseError, response.inspect unless response.status == 200 && response.body['response']
19
- fail BadResponseError, "No :success key in #{response.inspect}!" unless response.body['response'].is_a?(Array) && response.body['response'].first.has_key?('success')
20
- fail BadResponseError, "Not a success! Response: #{response.inspect}" unless response.body['response'].first['success'] == true
19
+ fail BadResponseError, response.inspect unless response.status == 200 && (responses = response.body['response']).is_a?(Array)
20
+ fail BadResponseError, "No :success key in #{responses.inspect}!" unless responses.all? { |r| r.key?(SUCCESS) }
21
21
 
22
- validate_operation_success(operations, response) if operations
22
+ validate_request_success(responses, requests) if LeanplumApi.configuration.validate_response
23
23
  end
24
24
  end
25
25
 
26
26
  private
27
27
 
28
- def validate_operation_success(operations, response)
29
- success_indicators = response.body['response']
30
- if success_indicators.size != operations.size
31
- fail "Attempted to do #{operations.size} operations but only received confirmation for #{success_indicators.size}!"
28
+ def validate_request_success(success_indicators, requests)
29
+ if requests && success_indicators.size != requests.size
30
+ fail BadResponseError, "Attempted #{requests.size} operations but received confirmation for #{success_indicators.size}!"
32
31
  end
33
32
 
34
- failures = []
35
- success_indicators.each_with_index do |s, i|
36
- if s['success'].to_s != 'true'
37
- LeanplumApi.configuration.logger.error("Unsuccessful request at position #{i}: #{operations[i]}")
38
- failures << { operation: operations[i], error: s }
33
+ failures = success_indicators.map.with_index do |indicator, i|
34
+ if indicator[WARN]
35
+ LeanplumApi.configuration.logger.warn((requests ? "Warning for #{requests[i]}: " : '') + indicator[WARN].to_s)
39
36
  end
40
- LeanplumApi.configuration.logger.warn("Warning for operation #{operations[i]}: #{s['warning']}") if s['warning']
41
- end
42
37
 
43
- fail LeanplumValidationException.new("Operation failures: #{failures}") if failures.size > 0
38
+ next nil if indicator[SUCCESS].to_s == 'true'
39
+
40
+ requests ? { operation: requests[i], error: indicator } : { error: indicator }
41
+ end.compact
42
+
43
+ unless failures.empty?
44
+ error_message = "Operation failures: #{failures}"
45
+ LeanplumApi.configuration.logger.error(error_message)
46
+ fail BadResponseError, error_message
47
+ end
44
48
  end
45
49
  end
46
50
  end
@@ -1,3 +1,3 @@
1
1
  module LeanplumApi
2
- VERSION = '3.1.0'
2
+ VERSION = '4.0.0'
3
3
  end
data/spec/api_spec.rb CHANGED
@@ -1,59 +1,109 @@
1
- require 'spec_helper'
2
-
3
1
  describe LeanplumApi::API do
4
2
  let(:api) { described_class.new }
5
3
  let(:first_user_id) { 123456 }
6
4
  let(:first_event_time) { Time.now.utc - 1.day }
7
5
  let(:last_event_time) { Time.now.utc }
8
- let(:users) do
9
- [{
6
+ let(:users) { [user] }
7
+ let(:devices) { [device] }
8
+ let(:user) do
9
+ {
10
10
  user_id: first_user_id,
11
11
  first_name: 'Mike',
12
12
  last_name: 'Jones',
13
13
  gender: 'm',
14
14
  email: 'still_tippin@test.com',
15
15
  create_date: '2010-01-01'.to_date,
16
- is_tipping: true,
17
- events: {
18
- eventName1: {
19
- count: 1,
20
- firstTime: first_event_time,
21
- lastTime: last_event_time
22
- }
23
- }
24
- }]
16
+ is_tipping: true
17
+ }
18
+ end
19
+ let(:device) do
20
+ {
21
+ device_id: 'fu123',
22
+ appVersion: 'x42x',
23
+ deviceModel: 'p0d',
24
+ create_date: '2018-01-01'.to_date
25
+ }
26
+ end
27
+
28
+ context 'devices' do
29
+ it 'build_device_attributes_hash' do
30
+ expect(api.send(:build_device_attributes_hash, device)).to eq(
31
+ deviceId: device[:device_id],
32
+ action: described_class::SET_DEVICE_ATTRIBUTES,
33
+ deviceAttributes: api.send(:fix_iso8601, device.except(:device_id))
34
+ )
35
+ end
36
+
37
+ context 'set_device_attributes' do
38
+ it 'sets device attributes without error' do
39
+ VCR.use_cassette('set_device_attributes') do
40
+ expect { api.set_device_attributes(devices) }.to_not raise_error
41
+ end
42
+ end
43
+ end
25
44
  end
26
45
 
27
46
  context 'users' do
28
- it 'build_user_attributes_hash' do
29
- expect(api.send(:build_user_attributes_hash, users.first)).to eq({
30
- userId: first_user_id,
31
- action: 'setUserAttributes',
32
- events: {
33
- 'eventName1' => {
34
- 'count' => 1,
35
- 'firstTime' => first_event_time.strftime('%s').to_i,
36
- 'lastTime' => last_event_time.strftime('%s').to_i
37
- }
38
- },
39
- userAttributes: HashWithIndifferentAccess.new(
40
- first_name: 'Mike',
41
- last_name: 'Jones',
42
- gender: 'm',
43
- email: 'still_tippin@test.com',
44
- create_date: '2010-01-01',
45
- is_tipping: true
46
- )
47
- })
47
+ let(:events) { { eventName1: { count: 1, firstTime: first_event_time, lastTime: last_event_time } } }
48
+ let(:events_with_timestamps) { Hash[events.map { |k, v| [k, api.send(:fix_seconds_since_epoch, v)] }] }
49
+ let(:user_with_devices) { user.merge(devices: devices) }
50
+ let(:user_with_events) { user.merge(events: events) }
51
+
52
+ context '#build_user_attributes_hash' do
53
+ let(:built_attributes) do
54
+ {
55
+ userId: first_user_id,
56
+ action: described_class::SET_USER_ATTRIBUTES,
57
+ userAttributes: api.send(:fix_iso8601, user.except(:user_id))
58
+ }
59
+ end
60
+
61
+ it 'builds the right hash' do
62
+ expect(api.send(:build_user_attributes_hash, user)).to eq(built_attributes)
63
+ end
64
+
65
+ context 'with events' do
66
+ it 'builds the right hash' do
67
+ expect(api.send(:build_user_attributes_hash, user_with_events)).to eq(
68
+ built_attributes.merge(events: events_with_timestamps)
69
+ )
70
+ end
71
+ end
72
+
73
+ context 'with devices' do
74
+ it 'builds the right hash' do
75
+ expect(api.send(:build_user_attributes_hash, user_with_devices)).to eq(
76
+ built_attributes.merge(devices: devices)
77
+ )
78
+ end
79
+ end
48
80
  end
49
81
 
50
- context 'set_user_attributes' do
82
+ context '#set_user_attributes' do
51
83
  context 'valid request' do
52
84
  it 'should successfully set user attributes' do
53
85
  VCR.use_cassette('set_user_attributes') do
54
86
  expect { api.set_user_attributes(users) }.to_not raise_error
55
87
  end
56
88
  end
89
+
90
+ it 'should successfully set user attributes and events' do
91
+ VCR.use_cassette('set_user_attributes_with_events') do
92
+ expect { api.set_user_attributes([user_with_events]) }.to_not raise_error
93
+ end
94
+ end
95
+
96
+ it 'should successfully set user attributes and devices' do
97
+ VCR.use_cassette('set_user_attributes_with_devices') do
98
+ expect { api.set_user_attributes([user_with_devices]) }.to_not raise_error
99
+ end
100
+ end
101
+
102
+ it 'should successfully set user attributes and devices and events' do
103
+ VCR.use_cassette('set_user_attributes_with_devices_and_events') do
104
+ expect { api.set_user_attributes([user_with_devices.merge(events: events)]) }.to_not raise_error
105
+ end
106
+ end
57
107
  end
58
108
 
59
109
  context 'invalid request' do
@@ -65,34 +115,43 @@ describe LeanplumApi::API do
65
115
  end
66
116
  end
67
117
 
68
- context 'user_attributes' do
118
+ context '#user_attributes' do
69
119
  it 'should get user attributes for this user' do
70
120
  VCR.use_cassette('export_user') do
71
121
  api.user_attributes(first_user_id).each do |k, v|
72
- if users.first[k.to_sym].is_a?(Date) || users.first[k.to_sym].is_a?(DateTime)
73
- expect(v).to eq(users.first[k.to_sym].strftime('%Y-%m-%d'))
122
+ if user[k.to_sym].is_a?(Date) || user[k.to_sym].is_a?(DateTime)
123
+ expect(v).to eq(user[k.to_sym].strftime('%Y-%m-%d'))
74
124
  else
75
- expect(v).to eq(users.first[k.to_sym])
125
+ expect(v).to eq(user[k.to_sym])
76
126
  end
77
127
  end
78
128
  end
79
129
  end
80
130
  end
81
131
 
82
- context 'export_users' do
83
- it 'should export users'
84
- end
85
-
86
- context 'reset_anomalous_users' do
132
+ context '#reset_anomalous_users' do
87
133
  it 'should successfully call setUserAttributes with resetAnomalies' do
88
134
  VCR.use_cassette('reset_anomalous_user') do
89
135
  expect { api.reset_anomalous_users(first_user_id) }.to_not raise_error
90
136
  end
91
137
  end
92
138
  end
139
+
140
+ context '#delete_user' do
141
+ let(:user_id) { 'delete_yourself_123' }
142
+ let(:deletable_user) { user.merge(user_id: user_id) }
143
+
144
+ it 'should delete a user' do
145
+ VCR.use_cassette('delete_user') do
146
+ expect { api.set_user_attributes(deletable_user) }.to_not raise_error
147
+ expect { api.delete_user(user_id) }.to_not raise_error
148
+ expect { api.user_attributes(user_id) }.to raise_error(LeanplumApi::ResourceNotFoundError)
149
+ end
150
+ end
151
+ end
93
152
  end
94
153
 
95
- context 'events' do
154
+ context 'event tracking' do
96
155
  let(:timestamp) { '2015-05-01 01:02:03' }
97
156
  let(:purchase) { 'purchase' }
98
157
  let(:events) do
@@ -100,13 +159,13 @@ describe LeanplumApi::API do
100
159
  {
101
160
  user_id: first_user_id,
102
161
  event: purchase,
103
- time: Time.now.utc,
162
+ time: last_event_time,
104
163
  some_timestamp: timestamp
105
164
  },
106
165
  {
107
166
  user_id: 54321,
108
167
  event: 'purchase_page_view',
109
- time: Time.now.utc - 10.minutes
168
+ time: last_event_time - 10.minutes
110
169
  }
111
170
  ]
112
171
  end
@@ -115,9 +174,9 @@ describe LeanplumApi::API do
115
174
  let(:event_hash) do
116
175
  {
117
176
  userId: first_user_id,
118
- time: Time.now.utc.strftime('%s').to_i,
119
- action: 'track',
120
177
  event: purchase,
178
+ time: last_event_time.strftime('%s').to_i,
179
+ action: described_class::TRACK,
121
180
  params: { some_timestamp: timestamp }
122
181
  }
123
182
  end
@@ -127,7 +186,7 @@ describe LeanplumApi::API do
127
186
  end
128
187
  end
129
188
 
130
- context 'without user attributes' do
189
+ context '#track_events' do
131
190
  context 'valid request' do
132
191
  it 'should successfully track session events' do
133
192
  VCR.use_cassette('track_events') do
@@ -137,7 +196,10 @@ describe LeanplumApi::API do
137
196
 
138
197
  it 'should successfully track non session events' do
139
198
  VCR.use_cassette('track_offline_events') do
140
- expect { api.track_events(events, allow_offline: true) }.to_not raise_error
199
+ expect do
200
+ response = api.track_events(events, allow_offline: true)
201
+ expect(response.map { |r| r['success'] && r['isOffline'] }.all?).to be_truthy
202
+ end.to_not raise_error
141
203
  end
142
204
  end
143
205
  end
@@ -153,28 +215,34 @@ describe LeanplumApi::API do
153
215
  end
154
216
 
155
217
  context 'anomalous data force_anomalous_override' do
156
- let(:old_events) { events.map { |e| e[:time] -= 1.year; e } }
218
+ let(:old_events) { events.map { |e| e[:time] -= 2.years; e } }
157
219
 
158
220
  it 'should successfully force the anomalous data override events' do
159
221
  VCR.use_cassette('track_events_anomaly_overrider') do
160
- expect { api.track_events(old_events, force_anomalous_override: true) }.to_not raise_error
222
+ expect do
223
+ response = api.track_events(old_events, force_anomalous_override: true)
224
+ expect(response.map { |r| r['warning']['message'] }.all? { |w| w =~ /Past event detected/ }).to be true
225
+ end.to_not raise_error
161
226
  end
162
227
  end
163
228
  end
164
229
  end
165
230
 
166
- context 'along with user attributes' do
167
- it 'should work' do
231
+ context '#track_multi' do
232
+ it 'tracks users and events at the same time' do
168
233
  VCR.use_cassette('track_events_and_attributes') do
169
- expect { api.track_multi(events, users) }.to_not raise_error
234
+ expect do
235
+ response = api.track_multi(events: events, user_attributes: users)
236
+ expect(response.first['success']).to be true
237
+ end.to_not raise_error
170
238
  end
171
239
  end
172
240
  end
173
241
 
174
- context 'user_events' do
242
+ context '#user_events' do
175
243
  it 'should get user events for this user' do
176
244
  VCR.use_cassette('export_user') do
177
- expect(api.user_events(first_user_id)[purchase].keys).to eq(%w(firstTime lastTime count))
245
+ expect(api.user_events(first_user_id)[purchase].keys.sort).to eq(%w(firstTime lastTime count).sort)
178
246
  end
179
247
  end
180
248
  end
@@ -188,46 +256,6 @@ describe LeanplumApi::API do
188
256
  LeanplumApi.configure { |c| c.developer_mode = true }
189
257
  end
190
258
 
191
- context 'data export methods' do
192
- context 'export_data' do
193
- context 'regular export' do
194
- it 'should request a data export job with a starttime' do
195
- VCR.use_cassette('export_data') do
196
- expect { api.export_data(Time.at(1438660800).utc) }.to raise_error LeanplumApi::BadResponseError
197
- end
198
- end
199
-
200
- it 'should request a data export job with start and end dates' do
201
- VCR.use_cassette('export_data_dates') do
202
- expect { api.export_data(Date.new(2017, 8, 5), Date.new(2017, 8, 6)) }.to_not raise_error
203
- end
204
- end
205
- end
206
-
207
- context 's3 export' do
208
- let(:s3_bucket_name) { 'bucket' }
209
- let(:s3_access_key) { 's3_access_key' }
210
- let(:s3_access_id) { 's3_access_id' }
211
-
212
- it 'should request an S3 export'
213
- end
214
- end
215
-
216
- context 'get_export_results' do
217
- it 'should get a status for a data export job' do
218
- VCR.use_cassette('get_export_results') do
219
- expect(api.get_export_results('export_4727756026281984_2904941266315269120')).to eq({
220
- files: ['https://leanplum_export.storage.googleapis.com/export-4727756026281984-d5969d55-f242-48a6-85a3-165af08e2306-output-0'],
221
- number_of_bytes: 36590,
222
- number_of_sessions: 101,
223
- state: LeanplumApi::API::EXPORT_FINISHED,
224
- s3_copy_status: nil
225
- })
226
- end
227
- end
228
- end
229
- end
230
-
231
259
  context 'content read only methods' do
232
260
  context 'ab tests' do
233
261
  it 'gets ab tests' do
@@ -267,9 +295,25 @@ describe LeanplumApi::API do
267
295
  pending 'Docs are extremely unclear about what getVars and setVars even do'
268
296
 
269
297
  VCR.use_cassette('get_vars') do
270
- expect(api.get_vars(users.first[:user_id])).to eq({ 'test_var' => 1 })
298
+ expect(api.get_vars(user[:user_id])).to eq({ 'test_var' => 1 })
271
299
  end
272
300
  end
273
301
  end
274
302
  end
303
+
304
+ context 'hash utility methods' do
305
+ let(:hash_with_times) { { not_time: 'grippin', time: Time.now.utc, date: Time.now.utc.to_date } }
306
+
307
+ it 'turns datetimes into seconds from the epoch' do
308
+ expect(api.send(:fix_seconds_since_epoch, hash_with_times)).to eq(
309
+ hash_with_times.merge(time: Time.now.utc.strftime('%s').to_i, date: Time.now.utc.to_date.strftime('%s').to_i)
310
+ )
311
+ end
312
+
313
+ it 'turns datetimes into iso8601 format' do
314
+ expect(api.send(:fix_iso8601, hash_with_times)).to eq(
315
+ hash_with_times.merge(time: Time.now.utc.iso8601, date: Time.now.utc.to_date.iso8601)
316
+ )
317
+ end
318
+ end
275
319
  end