segment 2.2.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,38 @@
1
+ require 'faraday'
2
+ require 'pmap'
3
+
4
+ class RunscopeClient
5
+ def initialize(api_token)
6
+ headers = { 'Authorization' => "Bearer #{api_token}" }
7
+ @conn = Faraday.new('https://api.runscope.com', headers: headers)
8
+ end
9
+
10
+ def requests(bucket_key)
11
+ with_retries(3) do
12
+ response = @conn.get("/buckets/#{bucket_key}/messages", count: 20)
13
+
14
+ raise "Runscope error. #{response.body}" unless response.status == 200
15
+
16
+ message_uuids = JSON.parse(response.body)['data'].map { |message|
17
+ message.fetch('uuid')
18
+ }
19
+
20
+ message_uuids.pmap { |uuid|
21
+ response = @conn.get("/buckets/#{bucket_key}/messages/#{uuid}")
22
+ raise "Runscope error. #{response.body}" unless response.status == 200
23
+ JSON.parse(response.body).fetch('data').fetch('request')
24
+ }
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ def with_retries(max_retries)
31
+ retries ||= 0
32
+ yield
33
+ rescue StandardError => e
34
+ retries += 1
35
+ retry if retries < max_retries
36
+ raise e
37
+ end
38
+ end
@@ -0,0 +1,92 @@
1
+ require 'spec_helper'
2
+
3
+ module Segment
4
+ class Analytics
5
+ describe BackoffPolicy do
6
+ describe '#initialize' do
7
+ context 'no options are given' do
8
+ it 'sets default min_timeout_ms' do
9
+ actual = subject.instance_variable_get(:@min_timeout_ms)
10
+ expect(actual).to eq(described_class::MIN_TIMEOUT_MS)
11
+ end
12
+
13
+ it 'sets default max_timeout_ms' do
14
+ actual = subject.instance_variable_get(:@max_timeout_ms)
15
+ expect(actual).to eq(described_class::MAX_TIMEOUT_MS)
16
+ end
17
+
18
+ it 'sets default multiplier' do
19
+ actual = subject.instance_variable_get(:@multiplier)
20
+ expect(actual).to eq(described_class::MULTIPLIER)
21
+ end
22
+
23
+ it 'sets default randomization factor' do
24
+ actual = subject.instance_variable_get(:@randomization_factor)
25
+ expect(actual).to eq(described_class::RANDOMIZATION_FACTOR)
26
+ end
27
+ end
28
+
29
+ context 'options are given' do
30
+ let(:min_timeout_ms) { 1234 }
31
+ let(:max_timeout_ms) { 5678 }
32
+ let(:multiplier) { 24 }
33
+ let(:randomization_factor) { 0.4 }
34
+
35
+ let(:options) do
36
+ {
37
+ min_timeout_ms: min_timeout_ms,
38
+ max_timeout_ms: max_timeout_ms,
39
+ multiplier: multiplier,
40
+ randomization_factor: randomization_factor
41
+ }
42
+ end
43
+
44
+ subject { described_class.new(options) }
45
+
46
+ it 'sets passed in min_timeout_ms' do
47
+ actual = subject.instance_variable_get(:@min_timeout_ms)
48
+ expect(actual).to eq(min_timeout_ms)
49
+ end
50
+
51
+ it 'sets passed in max_timeout_ms' do
52
+ actual = subject.instance_variable_get(:@max_timeout_ms)
53
+ expect(actual).to eq(max_timeout_ms)
54
+ end
55
+
56
+ it 'sets passed in multiplier' do
57
+ actual = subject.instance_variable_get(:@multiplier)
58
+ expect(actual).to eq(multiplier)
59
+ end
60
+
61
+ it 'sets passed in randomization_factor' do
62
+ actual = subject.instance_variable_get(:@randomization_factor)
63
+ expect(actual).to eq(randomization_factor)
64
+ end
65
+ end
66
+ end
67
+
68
+ describe '#next_interval' do
69
+ subject {
70
+ described_class.new(
71
+ min_timeout_ms: 1000,
72
+ max_timeout_ms: 10000,
73
+ multiplier: 2,
74
+ randomization_factor: 0.5
75
+ )
76
+ }
77
+
78
+ it 'returns exponentially increasing durations' do
79
+ expect(subject.next_interval).to be_within(500).of(1000)
80
+ expect(subject.next_interval).to be_within(1000).of(2000)
81
+ expect(subject.next_interval).to be_within(2000).of(4000)
82
+ expect(subject.next_interval).to be_within(4000).of(8000)
83
+ end
84
+
85
+ it 'caps maximum duration at max_timeout_secs' do
86
+ 10.times { subject.next_interval }
87
+ expect(subject.next_interval).to eq(10000)
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,328 @@
1
+ require 'spec_helper'
2
+
3
+ module Segment
4
+ class Analytics
5
+ describe Client do
6
+ let(:client) do
7
+ Client.new(:write_key => WRITE_KEY).tap { |client|
8
+ # Ensure that worker doesn't consume items from the queue
9
+ client.instance_variable_set(:@worker, NoopWorker.new)
10
+ }
11
+ end
12
+ let(:queue) { client.instance_variable_get :@queue }
13
+
14
+ describe '#initialize' do
15
+ it 'errors if no write_key is supplied' do
16
+ expect { Client.new }.to raise_error(ArgumentError)
17
+ end
18
+
19
+ it 'does not error if a write_key is supplied' do
20
+ expect do
21
+ Client.new :write_key => WRITE_KEY
22
+ end.to_not raise_error
23
+ end
24
+
25
+ it 'does not error if a write_key is supplied as a string' do
26
+ expect do
27
+ Client.new 'write_key' => WRITE_KEY
28
+ end.to_not raise_error
29
+ end
30
+ end
31
+
32
+ describe '#track' do
33
+ it 'errors without an event' do
34
+ expect { client.track(:user_id => 'user') }.to raise_error(ArgumentError)
35
+ end
36
+
37
+ it 'errors without a user_id' do
38
+ expect { client.track(:event => 'Event') }.to raise_error(ArgumentError)
39
+ end
40
+
41
+ it 'errors if properties is not a hash' do
42
+ expect {
43
+ client.track({
44
+ :user_id => 'user',
45
+ :event => 'Event',
46
+ :properties => [1, 2, 3]
47
+ })
48
+ }.to raise_error(ArgumentError)
49
+ end
50
+
51
+ it 'uses the timestamp given' do
52
+ time = Time.parse('1990-07-16 13:30:00.123 UTC')
53
+
54
+ client.track({
55
+ :event => 'testing the timestamp',
56
+ :user_id => 'joe',
57
+ :timestamp => time
58
+ })
59
+
60
+ msg = queue.pop
61
+
62
+ expect(Time.parse(msg[:timestamp])).to eq(time)
63
+ end
64
+
65
+ it 'does not error with the required options' do
66
+ expect do
67
+ client.track Queued::TRACK
68
+ queue.pop
69
+ end.to_not raise_error
70
+ end
71
+
72
+ it 'does not error when given string keys' do
73
+ expect do
74
+ client.track Utils.stringify_keys(Queued::TRACK)
75
+ queue.pop
76
+ end.to_not raise_error
77
+ end
78
+
79
+ it 'converts time and date traits into iso8601 format' do
80
+ client.track({
81
+ :user_id => 'user',
82
+ :event => 'Event',
83
+ :properties => {
84
+ :time => Time.utc(2013),
85
+ :time_with_zone => Time.zone.parse('2013-01-01'),
86
+ :date_time => DateTime.new(2013, 1, 1),
87
+ :date => Date.new(2013, 1, 1),
88
+ :nottime => 'x'
89
+ }
90
+ })
91
+ message = queue.pop
92
+
93
+ properties = message[:properties]
94
+ expect(properties[:time]).to eq('2013-01-01T00:00:00.000Z')
95
+ expect(properties[:time_with_zone]).to eq('2013-01-01T00:00:00.000Z')
96
+ expect(properties[:date_time]).to eq('2013-01-01T00:00:00.000+00:00')
97
+ expect(properties[:date]).to eq('2013-01-01')
98
+ expect(properties[:nottime]).to eq('x')
99
+ end
100
+ end
101
+
102
+ describe '#identify' do
103
+ it 'errors without any user id' do
104
+ expect { client.identify({}) }.to raise_error(ArgumentError)
105
+ end
106
+
107
+ it 'does not error with the required options' do
108
+ expect do
109
+ client.identify Queued::IDENTIFY
110
+ queue.pop
111
+ end.to_not raise_error
112
+ end
113
+
114
+ it 'does not error with the required options as strings' do
115
+ expect do
116
+ client.identify Utils.stringify_keys(Queued::IDENTIFY)
117
+ queue.pop
118
+ end.to_not raise_error
119
+ end
120
+
121
+ it 'converts time and date traits into iso8601 format' do
122
+ client.identify({
123
+ :user_id => 'user',
124
+ :traits => {
125
+ :time => Time.utc(2013),
126
+ :time_with_zone => Time.zone.parse('2013-01-01'),
127
+ :date_time => DateTime.new(2013, 1, 1),
128
+ :date => Date.new(2013, 1, 1),
129
+ :nottime => 'x'
130
+ }
131
+ })
132
+
133
+ message = queue.pop
134
+
135
+ traits = message[:traits]
136
+ expect(traits[:time]).to eq('2013-01-01T00:00:00.000Z')
137
+ expect(traits[:time_with_zone]).to eq('2013-01-01T00:00:00.000Z')
138
+ expect(traits[:date_time]).to eq('2013-01-01T00:00:00.000+00:00')
139
+ expect(traits[:date]).to eq('2013-01-01')
140
+ expect(traits[:nottime]).to eq('x')
141
+ end
142
+ end
143
+
144
+ describe '#alias' do
145
+ it 'errors without from' do
146
+ expect { client.alias :user_id => 1234 }.to raise_error(ArgumentError)
147
+ end
148
+
149
+ it 'errors without to' do
150
+ expect { client.alias :previous_id => 1234 }.to raise_error(ArgumentError)
151
+ end
152
+
153
+ it 'does not error with the required options' do
154
+ expect { client.alias ALIAS }.to_not raise_error
155
+ end
156
+
157
+ it 'does not error with the required options as strings' do
158
+ expect do
159
+ client.alias Utils.stringify_keys(ALIAS)
160
+ end.to_not raise_error
161
+ end
162
+ end
163
+
164
+ describe '#group' do
165
+ it 'errors without group_id' do
166
+ expect { client.group :user_id => 'foo' }.to raise_error(ArgumentError)
167
+ end
168
+
169
+ it 'errors without user_id' do
170
+ expect { client.group :group_id => 'foo' }.to raise_error(ArgumentError)
171
+ end
172
+
173
+ it 'does not error with the required options' do
174
+ client.group Queued::GROUP
175
+ end
176
+
177
+ it 'does not error with the required options as strings' do
178
+ client.group Utils.stringify_keys(Queued::GROUP)
179
+ end
180
+
181
+ it 'converts time and date traits into iso8601 format' do
182
+ client.identify({
183
+ :user_id => 'user',
184
+ :group_id => 'group',
185
+ :traits => {
186
+ :time => Time.utc(2013),
187
+ :time_with_zone => Time.zone.parse('2013-01-01'),
188
+ :date_time => DateTime.new(2013, 1, 1),
189
+ :date => Date.new(2013, 1, 1),
190
+ :nottime => 'x'
191
+ }
192
+ })
193
+
194
+ message = queue.pop
195
+
196
+ traits = message[:traits]
197
+ expect(traits[:time]).to eq('2013-01-01T00:00:00.000Z')
198
+ expect(traits[:time_with_zone]).to eq('2013-01-01T00:00:00.000Z')
199
+ expect(traits[:date_time]).to eq('2013-01-01T00:00:00.000+00:00')
200
+ expect(traits[:date]).to eq('2013-01-01')
201
+ expect(traits[:nottime]).to eq('x')
202
+ end
203
+ end
204
+
205
+ describe '#page' do
206
+ it 'errors without user_id' do
207
+ expect { client.page :name => 'foo' }.to raise_error(ArgumentError)
208
+ end
209
+
210
+ it 'does not error with the required options' do
211
+ expect { client.page Queued::PAGE }.to_not raise_error
212
+ end
213
+
214
+ it 'does not error with the required options as strings' do
215
+ expect do
216
+ client.page Utils.stringify_keys(Queued::PAGE)
217
+ end.to_not raise_error
218
+ end
219
+ end
220
+
221
+ describe '#screen' do
222
+ it 'errors without user_id' do
223
+ expect { client.screen :name => 'foo' }.to raise_error(ArgumentError)
224
+ end
225
+
226
+ it 'does not error with the required options' do
227
+ expect { client.screen Queued::SCREEN }.to_not raise_error
228
+ end
229
+
230
+ it 'does not error with the required options as strings' do
231
+ expect do
232
+ client.screen Utils.stringify_keys(Queued::SCREEN)
233
+ end.to_not raise_error
234
+ end
235
+ end
236
+
237
+ describe '#flush' do
238
+ let(:client_with_worker) { Client.new(:write_key => WRITE_KEY) }
239
+
240
+ it 'waits for the queue to finish on a flush' do
241
+ client_with_worker.identify Queued::IDENTIFY
242
+ client_with_worker.track Queued::TRACK
243
+ client_with_worker.flush
244
+
245
+ expect(client_with_worker.queued_messages).to eq(0)
246
+ end
247
+
248
+ unless defined? JRUBY_VERSION
249
+ it 'completes when the process forks' do
250
+ client_with_worker.identify Queued::IDENTIFY
251
+
252
+ Process.fork do
253
+ client_with_worker.track Queued::TRACK
254
+ client_with_worker.flush
255
+ expect(client_with_worker.queued_messages).to eq(0)
256
+ end
257
+
258
+ Process.wait
259
+ end
260
+ end
261
+ end
262
+
263
+ context 'common' do
264
+ check_property = proc { |msg, k, v| msg[k] && msg[k] == v }
265
+
266
+ let(:data) { { :user_id => 1, :group_id => 2, :previous_id => 3, :anonymous_id => 4, :message_id => 5, :event => 'coco barked', :name => 'coco' } }
267
+
268
+ it 'does not convert ids given as fixnums to strings' do
269
+ [:track, :screen, :page, :identify].each do |s|
270
+ client.send(s, data)
271
+ message = queue.pop(true)
272
+
273
+ expect(check_property.call(message, :userId, 1)).to eq(true)
274
+ expect(check_property.call(message, :anonymousId, 4)).to eq(true)
275
+ end
276
+ end
277
+
278
+ it 'returns false if queue is full' do
279
+ client.instance_variable_set(:@max_queue_size, 1)
280
+
281
+ [:track, :screen, :page, :group, :identify, :alias].each do |s|
282
+ expect(client.send(s, data)).to eq(true)
283
+ expect(client.send(s, data)).to eq(false) # Queue is full
284
+ queue.pop(true)
285
+ end
286
+ end
287
+
288
+ it 'converts message id to string' do
289
+ [:track, :screen, :page, :group, :identify, :alias].each do |s|
290
+ client.send(s, data)
291
+ message = queue.pop(true)
292
+
293
+ expect(check_property.call(message, :messageId, '5')).to eq(true)
294
+ end
295
+ end
296
+
297
+ context 'group' do
298
+ it 'does not convert ids given as fixnums to strings' do
299
+ client.group(data)
300
+ message = queue.pop(true)
301
+
302
+ expect(check_property.call(message, :userId, 1)).to eq(true)
303
+ expect(check_property.call(message, :groupId, 2)).to eq(true)
304
+ end
305
+ end
306
+
307
+ context 'alias' do
308
+ it 'does not convert ids given as fixnums to strings' do
309
+ client.alias(data)
310
+ message = queue.pop(true)
311
+
312
+ expect(check_property.call(message, :userId, 1)).to eq(true)
313
+ expect(check_property.call(message, :previousId, 3)).to eq(true)
314
+ end
315
+ end
316
+
317
+ it 'sends integrations' do
318
+ [:track, :screen, :page, :group, :identify, :alias].each do |s|
319
+ client.send s, :integrations => { :All => true, :Salesforce => false }, :user_id => 1, :group_id => 2, :previous_id => 3, :anonymous_id => 4, :event => 'coco barked', :name => 'coco'
320
+ message = queue.pop(true)
321
+ expect(message[:integrations][:All]).to eq(true)
322
+ expect(message[:integrations][:Salesforce]).to eq(false)
323
+ end
324
+ end
325
+ end
326
+ end
327
+ end
328
+ end