meta_events 1.0.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,93 @@
1
+ describe ::MetaEvents::Definition::Version do
2
+ let(:definition_set) do
3
+ out = double("definition_set")
4
+ allow(out).to receive(:kind_of?).with(::MetaEvents::Definition::DefinitionSet).and_return(true)
5
+ allow(out).to receive(:global_events_prefix).with().and_return("gep")
6
+ out
7
+ end
8
+
9
+ let(:klass) { ::MetaEvents::Definition::Version }
10
+ let(:instance) { klass.new(definition_set, 3, "2014-02-03") }
11
+
12
+ it "should require valid parameters for construction" do
13
+ expect { klass.new(double("not-a-definition-set"), 1, "2014-01-01") }.to raise_error(ArgumentError)
14
+ expect { klass.new(definition_set, "foo", "2014-01-01") }.to raise_error(ArgumentError)
15
+
16
+ # Ruby 1.8.x's Time.parse method will accept "foo" for Time.parse and just return Time.now --
17
+ # which is deeply unfortunate, but there's really no good way around it.
18
+ unless RUBY_VERSION =~ /^1\.8\./
19
+ expect { klass.new(definition_set, 1, "foo") }.to raise_error(ArgumentError)
20
+ end
21
+
22
+ expect { klass.new(definition_set, 1, nil) }.to raise_error
23
+ expect { klass.new(definition_set, 1, "2014-01-01", :foo => :bar) }.to raise_error(ArgumentError, /foo/i)
24
+ end
25
+
26
+ it "should return the definition set, number, and introduction time" do
27
+ expect(instance.definition_set).to be(definition_set)
28
+ expect(instance.number).to eq(3)
29
+ expect(instance.introduced).to eq(Time.parse("2014-02-03"))
30
+ end
31
+
32
+ it "should evaluate its block in its own context" do
33
+ expect_any_instance_of(klass).to receive(:foobar).once.with(:bonk)
34
+ klass.new(definition_set, 3, "2014-02-03") { foobar(:bonk) }
35
+ end
36
+
37
+ it "should return the prefix correctly" do
38
+ instance.prefix.should == "gep3_"
39
+ end
40
+
41
+ context "with one category" do
42
+ let(:category) do
43
+ out = double("category")
44
+ allow(out).to receive(:name).with().and_return(:quux)
45
+ out
46
+ end
47
+
48
+ it "should be able to create a new category, and retrieve it" do
49
+ blk = lambda { :whatever }
50
+ expect(::MetaEvents::Definition::Category).to receive(:new).once.with(instance, ' FooBar ', :bar => :baz, &blk).and_return(category)
51
+ instance.category(' FooBar ', :bar => :baz, &blk)
52
+
53
+ expect(instance.category_named(:quux)).to be(category)
54
+ end
55
+
56
+ it "should not allow creating duplicate categories" do
57
+ expect(::MetaEvents::Definition::Category).to receive(:new).once.with(instance, :quux, { }).and_return(category)
58
+ instance.category(:quux)
59
+
60
+ category_2 = double("category-2")
61
+ allow(category_2).to receive(:name).with().and_return(:quux)
62
+ expect(::MetaEvents::Definition::Category).to receive(:new).once.with(instance, :baz, { }).and_return(category_2)
63
+ expect { instance.category(:baz) }.to raise_error(ArgumentError, /baz/i)
64
+ end
65
+
66
+ it "should allow retrieving the category, and normalize the name" do
67
+ expect(::MetaEvents::Definition::Category).to receive(:new).once.with(instance, :quux, { }).and_return(category)
68
+ instance.category(:quux)
69
+
70
+ instance.category_named(' QuuX ').should be(category)
71
+ end
72
+
73
+ it "should delegate to the category on #fetch_event" do
74
+ expect(::MetaEvents::Definition::Category).to receive(:new).once.with(instance, :quux, { }).and_return(category)
75
+ instance.category(:quux)
76
+
77
+ expect(category).to receive(:event_named).once.with(:foobar).and_return(:bonk)
78
+ instance.fetch_event(:quux, :foobar).should == :bonk
79
+ end
80
+ end
81
+
82
+ it "should return the #retired_at properly" do
83
+ expect(instance.retired_at).to be_nil
84
+
85
+ new_instance = klass.new(definition_set, 4, "2014-01-01", :retired_at => "2015-01-01")
86
+ expect(new_instance.retired_at).to eq(Time.parse("2015-01-01"))
87
+ end
88
+
89
+ it "should turn itself into a string reasonably" do
90
+ expect(instance.to_s).to match(/Version/)
91
+ expect(instance.to_s).to match(/3/)
92
+ end
93
+ end
@@ -0,0 +1,466 @@
1
+ require 'active_support/core_ext/numeric/time'
2
+ require 'ipaddr'
3
+
4
+ describe MetaEvents::Tracker do
5
+ subject(:klass) { MetaEvents::Tracker }
6
+ let(:definition_set) do
7
+ MetaEvents::Definition::DefinitionSet.new(:global_events_prefix => "xy") do
8
+ version 1, '2014-01-31' do
9
+ category :foo do
10
+ event :bar, '2014-01-31', 'this is bar'
11
+ event :baz, '2014-01-31', 'this is baz'
12
+ event :nolonger, '2014-01-31', 'should be retired', :retired_at => '2020-01-01'
13
+ end
14
+ end
15
+
16
+ version 2, '2014-06-01' do
17
+ category :foo do
18
+ event :quux, '2014-01-31', 'this is quux'
19
+ end
20
+ end
21
+ end
22
+ end
23
+
24
+ let(:receiver_1) do
25
+ out = double("receiver_1")
26
+ @receiver_1_calls = [ ]
27
+ rc1c = @receiver_1_calls
28
+ allow(out).to receive(:track) { |*args| rc1c << args }
29
+ out
30
+ end
31
+
32
+ let(:receiver_2) do
33
+ out = double("receiver_2")
34
+ @receiver_2_calls = [ ]
35
+ rc2c = @receiver_2_calls
36
+ allow(out).to receive(:track) { |*args| rc2c << args }
37
+ out
38
+ end
39
+
40
+ def new_instance(*args)
41
+ out = klass.new(*args)
42
+ out.event_receivers << receiver_1
43
+ out
44
+ end
45
+
46
+ before :each do
47
+ ::MetaEvents::Tracker.default_event_receivers = [ ]
48
+ @user = tep_object(:name => 'wilfred', :hometown => 'Fridley')
49
+ @distinct_id = rand(1_000_000_000)
50
+ @remote_ip = "128.12.34.56"
51
+ @instance = new_instance(@distinct_id, nil, :definitions => definition_set, :version => 1, :implicit_properties => { :user => @user } )
52
+ end
53
+
54
+ after :each do
55
+ expect(@receiver_1_calls).to eq([ ])
56
+ expect((@receiver_2_calls || [ ])).to eq([ ])
57
+ end
58
+
59
+ def expect_event(event_name, event_properties, options = { })
60
+ receiver_name = options[:receiver_name] || "receiver_1"
61
+ expected_distinct_id = if options.has_key?(:distinct_id) then options[:distinct_id] else @distinct_id end
62
+
63
+ calls = instance_variable_get("@#{receiver_name}_calls")
64
+
65
+ expect(calls.length).to be > 0
66
+ next_event = calls.shift
67
+ expect(next_event[0]).to eq(expected_distinct_id)
68
+ expect(next_event[1]).to eq(event_name)
69
+ expect(next_event[2]).to eq(event_properties)
70
+ end
71
+
72
+ def tep_object(props)
73
+ out = double("tep_object")
74
+ allow(out).to receive(:to_event_properties).with().and_return(props)
75
+ out
76
+ end
77
+
78
+ describe "#initialize" do
79
+ it "should validate its arguments" do
80
+ expect { new_instance(@distinct_id, nil, :version => 1, :definitions => definition_set, :foo => :bar) }.to raise_error(ArgumentError, /foo/i)
81
+ expect { new_instance(@distinct_id, "whatever", :version => 1, :definitions => definition_set) }.to raise_error(ArgumentError)
82
+ expect { new_instance(@distinct_id, /foobar/, :version => 1, :definitions => definition_set) }.to raise_error(ArgumentError, /foobar/i)
83
+ end
84
+
85
+ it "should allow a nil distinct_id" do
86
+ expect { new_instance(nil, nil, :definitions => definition_set, :version => 1) }.not_to raise_error
87
+ end
88
+
89
+ it "should not allow an invalid distinct_id" do
90
+ expect { new_instance(/foobar/, nil, :definitions => definition_set, :version => 1) }.to raise_error(ArgumentError)
91
+ end
92
+
93
+ it "should be able to read definitions from a file" do
94
+ require 'tempfile'
95
+
96
+ f = Tempfile.new('tracker_events')
97
+ begin
98
+ f.puts <<-EOS
99
+ global_events_prefix :fb
100
+
101
+ version 1, '2014-02-19' do
102
+ category :baz do
103
+ event :quux, '2014-01-31', 'this is quux'
104
+ end
105
+ end
106
+ EOS
107
+ f.close
108
+
109
+ the_instance = new_instance(@distinct_id, nil, :definitions => f.path, :version => 1)
110
+ expect { the_instance.event!(:baz, :quux, :foo => :bar) }.not_to raise_error
111
+ expect_event("fb1_baz_quux", { 'foo' => 'bar' })
112
+ expect { the_instance.event!(:baz, :foo, :foo => :bar) }.to raise_error
113
+ ensure
114
+ f.close
115
+ f.unlink
116
+ end
117
+ end
118
+ end
119
+
120
+ describe "#distinct_id" do
121
+ it "should allow reading the distinct ID" do
122
+ expect(@instance.distinct_id).to eq(@distinct_id)
123
+ end
124
+
125
+ it "should allow changing the distinct ID at runtime" do
126
+ i = new_instance(@distinct_id, nil, :definitions => definition_set, :version => 1)
127
+ i.event!(:foo, :bar, { })
128
+ expect_event('xy1_foo_bar', { }, :distinct_id => @distinct_id)
129
+ i.distinct_id = '12345yoyoyo'
130
+ expect(i.distinct_id).to eq('12345yoyoyo')
131
+ i.event!(:foo, :bar, { })
132
+ expect_event('xy1_foo_bar', { }, :distinct_id => '12345yoyoyo')
133
+ end
134
+
135
+ it "should allow changing the distinct ID to nil at runtime" do
136
+ i = new_instance(@distinct_id, nil, :definitions => definition_set, :version => 1)
137
+ expect(i.distinct_id).to eq(@distinct_id)
138
+ i.distinct_id = nil
139
+ expect(i.distinct_id).to eq(nil)
140
+ i.event!(:foo, :bar, { })
141
+ expect_event('xy1_foo_bar', { }, :distinct_id => nil)
142
+ end
143
+
144
+ it "should not allow setting the distinct ID to an unsupported object" do
145
+ i = new_instance(@distinct_id, nil, :definitions => definition_set, :version => 1)
146
+ expect { i.distinct_id = /foobar/ }.to raise_error(ArgumentError)
147
+ expect(i.distinct_id).to eq(@distinct_id)
148
+ end
149
+ end
150
+
151
+ describe "#track_event" do
152
+ it "should allow firing a valid event" do
153
+ i = new_instance(@distinct_id, nil, :definitions => definition_set, :version => 1)
154
+ i.event!(:foo, :bar, { })
155
+ expect_event('xy1_foo_bar', { })
156
+ end
157
+
158
+ describe "ip-address passing" do
159
+ it "should pass through an IPv4 String IP" do
160
+ i = new_instance(@distinct_id, "138.93.206.193", :definitions => definition_set, :version => 1)
161
+ i.event!(:foo, :bar, { })
162
+ expect_event('xy1_foo_bar', { 'ip' => "138.93.206.193" })
163
+ end
164
+
165
+ it "should pass through an IPv6 String IP" do
166
+ i = new_instance(@distinct_id, "2607:f0d0:1002:0051:0000:0000:0000:0004", :definitions => definition_set, :version => 1)
167
+ i.event!(:foo, :bar, { })
168
+ expect_event('xy1_foo_bar', { 'ip' => "2607:f0d0:1002:51::4" })
169
+ end
170
+
171
+ it "should pass through an Integer IP" do
172
+ i = new_instance(@distinct_id, 2321403585, :definitions => definition_set, :version => 1)
173
+ i.event!(:foo, :bar, { })
174
+ expect_event('xy1_foo_bar', { 'ip' => "138.93.206.193" })
175
+ end
176
+
177
+ it "should pass through an IPv4 IPAddr" do
178
+ i = new_instance(@distinct_id, IPAddr.new("138.93.206.193"), :definitions => definition_set, :version => 1)
179
+ i.event!(:foo, :bar, { })
180
+ expect_event('xy1_foo_bar', { 'ip' => "138.93.206.193" })
181
+ end
182
+
183
+ it "should pass through an IPv6 IPAddr" do
184
+ i = new_instance(@distinct_id, IPAddr.new("2607:f0d0:1002:0051:0000:0000:0000:0004"), :definitions => definition_set, :version => 1)
185
+ i.event!(:foo, :bar, { })
186
+ expect_event('xy1_foo_bar', { 'ip' => "2607:f0d0:1002:51::4" })
187
+ end
188
+ end
189
+
190
+ it "should include the distinct_id set in the constructor" do
191
+ i = new_instance(@distinct_id, nil, :definitions => definition_set, :version => 1)
192
+ i.event!(:foo, :bar, { })
193
+ expect_event('xy1_foo_bar', { }, :distinct_id => @distinct_id)
194
+ end
195
+
196
+ it "should let you override the IP address on a per-event basis" do
197
+ i = new_instance(@distinct_id, "138.93.206.193", :definitions => definition_set, :version => 1)
198
+ i.event!(:foo, :bar, { 'ip' => '203.196.4.32' })
199
+ expect_event('xy1_foo_bar', { 'ip' => '203.196.4.32' }, :distinct_id => @distinct_id)
200
+ end
201
+
202
+ it "should allow overriding the distinct_id set in the constructor" do
203
+ i = new_instance(@distinct_id, nil, :definitions => definition_set, :version => 1)
204
+ i.event!(:foo, :bar, { :distinct_id => 12345 })
205
+ expect_event('xy1_foo_bar', { }, :distinct_id => 12345)
206
+ end
207
+
208
+ it "should allow a nil distinct_id" do
209
+ i = new_instance(nil, nil, :definitions => definition_set, :version => 1)
210
+ i.event!(:foo, :bar, { })
211
+ expect_event('xy1_foo_bar', { }, :distinct_id => nil)
212
+ end
213
+
214
+ it "should allow a String distinct_id" do
215
+ i = new_instance("foobarfoobar", nil, :definitions => definition_set, :version => 1)
216
+ i.event!(:foo, :bar, { })
217
+ expect_event('xy1_foo_bar', { }, :distinct_id => 'foobarfoobar')
218
+ end
219
+
220
+ it "should send the event to both receivers if asked" do
221
+ @instance.event_receivers = [ receiver_1, receiver_2 ]
222
+ @instance.event!(:foo, :bar, { })
223
+
224
+ expect_event("xy1_foo_bar", { 'user_name' => 'wilfred', 'user_hometown' => 'Fridley' }, :receiver_name => :receiver_1)
225
+ expect_event("xy1_foo_bar", { 'user_name' => 'wilfred', 'user_hometown' => 'Fridley' }, :receiver_name => :receiver_2)
226
+ end
227
+
228
+ it "should clone the class's default-receiver list on creation" do
229
+ expect(klass.default_event_receivers).to eq([ ])
230
+
231
+ begin
232
+ klass.default_event_receivers = [ receiver_2 ]
233
+ i = klass.new(@distinct_id, nil, :definitions => definition_set, :version => 1)
234
+ i.event!(:foo, :bar, { })
235
+ expect_event('xy1_foo_bar', { }, :receiver_name => :receiver_2)
236
+ ensure
237
+ klass.default_event_receivers = [ ]
238
+ end
239
+ end
240
+
241
+ it "should allow overriding the list of receivers in the constructor" do
242
+ i = klass.new(@distinct_id, nil, :definitions => definition_set, :version => 1, :event_receivers => [ receiver_2 ])
243
+ i.event!(:foo, :bar, { })
244
+ expect_event('xy1_foo_bar', { }, :receiver_name => :receiver_2)
245
+ end
246
+
247
+ it "should use the class's list of default definitions by default" do
248
+ klass.default_definitions = MetaEvents::Definition::DefinitionSet.new(:global_events_prefix => "zz") do
249
+ version 1, '2014-01-31' do
250
+ category :marph do
251
+ event :bonk, '2014-01-31', 'this is bar'
252
+ end
253
+ end
254
+ end
255
+
256
+ i = klass.new(@distinct_id, nil, :version => 1, :event_receivers => receiver_1)
257
+ i.event!(:marph, :bonk)
258
+ expect_event('zz1_marph_bonk', { })
259
+ end
260
+
261
+ it "should pick up the default version from the class" do
262
+ klass.default_version = 2
263
+
264
+ i = klass.new(@distinct_id, nil, :event_receivers => receiver_1, :definitions => definition_set)
265
+ i.event!(:foo, :quux)
266
+ expect_event("xy2_foo_quux", { })
267
+ end
268
+
269
+ it "should allow firing a valid event, and include implicit properties" do
270
+ @instance.event!(:foo, :bar, { })
271
+ expect_event('xy1_foo_bar', { 'user_name' => 'wilfred', 'user_hometown' => 'Fridley' })
272
+ end
273
+
274
+ it "should not allow firing an event that doesn't exist" do
275
+ expect { @instance.event!(:foo, :quux, { }) }.to raise_error(ArgumentError, /quux/i)
276
+ end
277
+
278
+ it "should not allow firing a category that doesn't exist" do
279
+ expect { @instance.event!(:bogus, :signed_up, { }) }.to raise_error(ArgumentError, /bogus/i)
280
+ end
281
+
282
+ it "should include defined properties with the event" do
283
+ @instance.event!(:foo, :bar, { :awesomeness => 123, :foo => 'bar' })
284
+ expect_event('xy1_foo_bar', { 'user_name' => 'wilfred', 'user_hometown' => 'Fridley', 'awesomeness' => 123, 'foo' => 'bar' })
285
+ end
286
+
287
+ it "should fail if there's a circular reference" do
288
+ circular = { :foo => :bar }
289
+ circular[:baz] = circular
290
+
291
+ expect { @instance.event!(:foo, :bar, circular) }.to raise_error(/circular/i)
292
+ end
293
+
294
+ it "should pass through lots of different kinds of properties" do
295
+ the_time = Time.parse("2008-09-04 3:46:12 PM -08:00")
296
+ @instance.event!(:foo, :bar, {
297
+ :num1 => 42,
298
+ :num2 => 6.0221413e+23,
299
+ :true => true,
300
+ :false => false,
301
+ :string => 'foobar',
302
+ :symbol => :bazbar,
303
+ :time_interval => 3.months,
304
+ :pos_infinity => (+1.0/0.0),
305
+ :nan => (0.0/0.0),
306
+ :neg_infinity => (-1.0/0.0),
307
+ :time => the_time,
308
+ :stuff => [ "foo", :bar, 123, -9.45e+17, the_time, false, nil, true, " BoNk " ]
309
+ })
310
+ expect_event('xy1_foo_bar', {
311
+ 'user_name' => 'wilfred',
312
+ 'user_hometown' => 'Fridley',
313
+ 'num1' => 42,
314
+ 'num2' => 6.0221413e+23,
315
+ 'true' => true,
316
+ 'false' => false,
317
+ 'string' => 'foobar',
318
+ 'symbol' => 'bazbar',
319
+ 'time_interval' => 7776000,
320
+ 'pos_infinity' => "+infinity",
321
+ 'nan' => 'NaN',
322
+ 'neg_infinity' => "-infinity",
323
+ 'time' => "2008-09-04T23:46:12",
324
+ 'stuff' => [ "foo", "bar", 123, -9.45e+17, "2008-09-04T23:46:12", false, nil, true, "BoNk" ],
325
+ })
326
+ end
327
+
328
+ it "should let me override implicit properties with user-defined ones" do
329
+ @instance.event!(:foo, :bar, { :user_name => 'bongo' })
330
+ expect_event('xy1_foo_bar', { 'user_name' => 'bongo', 'user_hometown' => 'Fridley' })
331
+ end
332
+
333
+ it "should allow nested properties in the event, and expand them out" do
334
+ @instance.event!(:foo, :bar, { :location => { :city => 'Edina', :zip => 55343 } })
335
+ expect_event('xy1_foo_bar', { 'user_name' => 'wilfred', 'user_hometown' => 'Fridley', 'location_city' => 'Edina', 'location_zip' => 55343 })
336
+ end
337
+
338
+ it "should accept nested properties from #to_event_properties, and expand them out" do
339
+ @instance.event!(:foo, :bar, { :location => tep_object(:city => 'Edina', :zip => 55343 )})
340
+ expect_event('xy1_foo_bar', { 'user_name' => 'wilfred', 'user_hometown' => 'Fridley', 'location_city' => 'Edina', 'location_zip' => 55343 })
341
+ end
342
+
343
+ it "should not allow firing a retired event" do
344
+ expect { @instance.event!(:foo, :nolonger, { }) }.to raise_error(::MetaEvents::Definition::DefinitionSet::RetiredEventError)
345
+ end
346
+ end
347
+
348
+ describe "#effective_properties" do
349
+ it "should validate the event" do
350
+ expect { @instance.effective_properties(:foo, :whatever) }.to raise_error(ArgumentError)
351
+ expect { @instance.effective_properties(:foo, :nolonger) }.to raise_error(::MetaEvents::Definition::DefinitionSet::RetiredEventError)
352
+ end
353
+
354
+ it "should include the fully-qualified event name" do
355
+ expect(@instance.effective_properties(:foo, :bar)[:event_name]).to eq('xy1_foo_bar')
356
+ end
357
+
358
+ it "should include the distinct ID" do
359
+ expect(@instance.effective_properties(:foo, :bar)[:distinct_id]).to eq(@distinct_id)
360
+ end
361
+
362
+ it "should include a distinct ID of nil if there is none" do
363
+ i = new_instance(nil, nil, :definitions => definition_set, :version => 1)
364
+ h = i.effective_properties(:foo, :bar)
365
+ expect(h.has_key?(:distinct_id)).to be_true
366
+ expect(h[:distinct_id]).to be_nil
367
+ end
368
+
369
+ it "should include the set of implicit and explicit properties" do
370
+ expect(@instance.effective_properties(:foo, :bar)[:properties]).to eq('user_name' => 'wilfred', 'user_hometown' => 'Fridley')
371
+ props = @instance.effective_properties(:foo, :bar, :baz => { :a => 1, :b => 'hoo' }, :user_name => 'bongo')[:properties]
372
+ expect(props).to eq('user_name' => 'bongo', 'user_hometown' => 'Fridley', 'baz_a' => 1, 'baz_b' => 'hoo')
373
+ end
374
+
375
+ it "should include no additional keys" do
376
+ expect(@instance.effective_properties(:foo, :bar).keys.sort_by(&:to_s)).to eq(%w{distinct_id event_name properties}.map(&:to_sym).sort_by(&:to_s))
377
+ end
378
+ end
379
+
380
+ describe "#merge_properties" do
381
+ def expand(hash)
382
+ out = { }
383
+ klass.merge_properties(out, hash, "", 0)
384
+ out
385
+ end
386
+
387
+ def expand_scalar(value)
388
+ expand({ 'foo' => value })['foo']
389
+ end
390
+
391
+ it "should return proper values for scalars" do
392
+ expect(expand_scalar('foo')).to eq('foo')
393
+ expect(expand_scalar(' FoO ')).to eq('FoO')
394
+
395
+ expect(expand_scalar(:foo)).to eq('foo')
396
+ expect(expand_scalar(:' FoOO ')).to eq('FoOO')
397
+
398
+ expect(expand_scalar(123)).to eq(123)
399
+ expect(expand_scalar(4.2867)).to eq(4.2867)
400
+ expect(expand_scalar(1234567890123456789012345678901234567890123456789012345678901234567890)).to eq(1234567890123456789012345678901234567890123456789012345678901234567890)
401
+ expect(expand_scalar(true)).to eq(true)
402
+ expect(expand_scalar(false)).to eq(false)
403
+ expect(expand_scalar(nil)).to eq(nil)
404
+ end
405
+
406
+ it "should raise an error for unknown data, including Hashes" do
407
+ expect { expand_scalar(/foobar/) }.to raise_error(ArgumentError)
408
+ expect { expand_scalar([ "a", "b", /foo/ ]) }.to raise_error(ArgumentError)
409
+ end
410
+
411
+ it "should stringify symbols on both keys and values" do
412
+ expect(expand({ :foo => :bar })).to eq({ 'foo' => 'bar' })
413
+ end
414
+
415
+ it "should correctly expand simple hashes" do
416
+ expect(expand({ :foo => 'bar' })).to eq({ 'foo' => 'bar' })
417
+ expect(expand({ :zzz => ' bonkO ', 'bar' => :' baZZ ' })).to eq({ 'zzz' => 'bonkO', 'bar' => 'baZZ' })
418
+ end
419
+
420
+ it "should recursively expand hashes" do
421
+ expect(expand({ :foo => { :bar => ' whatEVs '} })).to eq({ 'foo_bar' => 'whatEVs' })
422
+ expect { expand({ :foo => { :bar => [ 1, 2, /foo/ ] } }) }.to raise_error(ArgumentError)
423
+ end
424
+
425
+ it "should call #to_event_properties for any object, and recursively expand that" do
426
+ expect(expand(:baz => tep_object({ :foo => ' BaR '}))).to eq({ 'baz_foo' => 'BaR' })
427
+ expect(expand(:baz => tep_object({ :foo => { :bar => ' yo yo yo '} }))).to eq({ 'baz_foo_bar' => 'yo yo yo' })
428
+
429
+ subsidiary = tep_object({ :bar => :baz })
430
+ expect(expand(:baz => tep_object({ :foo => subsidiary }))).to eq({ 'baz_foo_bar' => 'baz' })
431
+ end
432
+
433
+ it "should raise if it detects a property-name conflict" do
434
+ expect { expand(:foo_bar => :quux1, :foo => { :bar => :quux }) }.to raise_error(MetaEvents::Tracker::PropertyCollisionError)
435
+ end
436
+ end
437
+
438
+ describe "#normalize_scalar_property_value" do
439
+ it "should return the correct results for scalars" do
440
+ infinity = (1.0 / 0.0)
441
+ nan = (0.0 / 0.0)
442
+
443
+ t = Time.parse("2008-09-04 3:46:12 PM -08:00")
444
+ {
445
+ nil => nil,
446
+ true => true,
447
+ false => false,
448
+ 3 => 3,
449
+ 42.5e+17 => 42.5e+17,
450
+ 3.months => 7776000,
451
+ infinity => "+infinity",
452
+ nan => "NaN",
453
+ -infinity => "-infinity",
454
+ :foobar => 'foobar',
455
+ :' FooBar ' => 'FooBar',
456
+ ' FooBar ' => 'FooBar',
457
+ t => "2008-09-04T23:46:12",
458
+ [ "foo", :bar, 123, -9.45e+17, t, false, nil, true, " BoNk " ] => [ "foo", "bar", 123, -9.45e+17, "2008-09-04T23:46:12", false, nil, true, "BoNk" ],
459
+ /foobar/ => :invalid_property_value,
460
+ Object.new => :invalid_property_value
461
+ }.each do |input, output|
462
+ expect(klass.normalize_scalar_property_value(input)).to eq(output)
463
+ end
464
+ end
465
+ end
466
+ end