mixpanel-ruby-with-pixel-tracking 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,3 @@
1
+ module Mixpanel
2
+ VERSION = '1.4.0'
3
+ end
@@ -0,0 +1,17 @@
1
+ require File.join(File.dirname(__FILE__), 'lib/mixpanel-ruby/version.rb')
2
+
3
+ spec = Gem::Specification.new do |spec|
4
+ spec.name = 'mixpanel-ruby-with-pixel-tracking'
5
+ spec.version = Mixpanel::VERSION
6
+ spec.files = Dir.glob(`git ls-files`.split("\n"))
7
+ spec.require_paths = ['lib']
8
+ spec.summary = 'Temporary fork of the Official Mixpanel tracking library'
9
+ spec.description = 'Enables pixel-based event tracking'
10
+ spec.authors = [ 'Mixpanel', 'Pius Uzamere' ]
11
+ spec.email = 'support@mixpanel.com'
12
+ spec.homepage = 'https://mixpanel.com/help/reference/ruby'
13
+
14
+ spec.add_development_dependency('rake')
15
+ spec.add_development_dependency('rspec')
16
+ spec.add_development_dependency('webmock')
17
+ end
@@ -0,0 +1,135 @@
1
+ require 'spec_helper'
2
+ require 'webmock'
3
+ require 'base64'
4
+ require 'mixpanel-ruby/consumer'
5
+
6
+ describe Mixpanel::Consumer do
7
+ before { WebMock.reset! }
8
+
9
+ shared_examples_for 'consumer' do
10
+ it 'should send a request to api.mixpanel.com/track on events' do
11
+ stub_request(:any, 'https://api.mixpanel.com/track').to_return({:body => '{"status": 1, "error": null}'})
12
+ subject.send(:event, {'data' => 'TEST EVENT MESSAGE'}.to_json)
13
+ WebMock.should have_requested(:post, 'https://api.mixpanel.com/track').
14
+ with(:body => {'data' => 'IlRFU1QgRVZFTlQgTUVTU0FHRSI=', 'verbose' => '1' })
15
+ end
16
+
17
+ it 'should send a request to api.mixpanel.com/people on profile updates' do
18
+ stub_request(:any, 'https://api.mixpanel.com/engage').to_return({:body => '{"status": 1, "error": null}'})
19
+ subject.send(:profile_update, {'data' => 'TEST EVENT MESSAGE'}.to_json)
20
+ WebMock.should have_requested(:post, 'https://api.mixpanel.com/engage').
21
+ with(:body => {'data' => 'IlRFU1QgRVZFTlQgTUVTU0FHRSI=', 'verbose' => '1' })
22
+ end
23
+
24
+ it 'should send a request to api.mixpanel.com/import on event imports' do
25
+ stub_request(:any, 'https://api.mixpanel.com/import').to_return({:body => '{"status": 1, "error": null}'})
26
+ subject.send(:import, {'data' => 'TEST EVENT MESSAGE', 'api_key' => 'API_KEY','verbose' => '1' }.to_json)
27
+ WebMock.should have_requested(:post, 'https://api.mixpanel.com/import').
28
+ with(:body => {'data' => 'IlRFU1QgRVZFTlQgTUVTU0FHRSI=', 'api_key' => 'API_KEY', 'verbose' => '1' })
29
+ end
30
+
31
+ it 'should encode long messages without newlines' do
32
+ stub_request(:any, 'https://api.mixpanel.com/track').to_return({:body => '{"status": 1, "error": null}'})
33
+ subject.send(:event, {'data' => 'BASE64-ENCODED VERSION OF BIN. THIS METHOD COMPLIES WITH RFC 2045. LINE FEEDS ARE ADDED TO EVERY 60 ENCODED CHARACTORS. IN RUBY 1.8 WE NEED TO JUST CALL ENCODE64 AND REMOVE THE LINE FEEDS, IN RUBY 1.9 WE CALL STRIC_ENCODED64 METHOD INSTEAD'}.to_json)
34
+ WebMock.should have_requested(:post, 'https://api.mixpanel.com/track').
35
+ with(:body => {'data' => 'IkJBU0U2NC1FTkNPREVEIFZFUlNJT04gT0YgQklOLiBUSElTIE1FVEhPRCBDT01QTElFUyBXSVRIIFJGQyAyMDQ1LiBMSU5FIEZFRURTIEFSRSBBRERFRCBUTyBFVkVSWSA2MCBFTkNPREVEIENIQVJBQ1RPUlMuIElOIFJVQlkgMS44IFdFIE5FRUQgVE8gSlVTVCBDQUxMIEVOQ09ERTY0IEFORCBSRU1PVkUgVEhFIExJTkUgRkVFRFMsIElOIFJVQlkgMS45IFdFIENBTEwgU1RSSUNfRU5DT0RFRDY0IE1FVEhPRCBJTlNURUFEIg==', 'verbose' => '1'})
36
+ end
37
+
38
+ it 'should provide thorough information in case mixpanel fails' do
39
+ stub_request(:any, 'https://api.mixpanel.com/track').to_return({:status => 401, :body => "nutcakes"})
40
+ expect { subject.send(:event, {'data' => 'TEST EVENT MESSAGE'}.to_json) }.to raise_exception('Could not write to Mixpanel, server responded with 401 returning: \'nutcakes\'')
41
+ end
42
+ end
43
+
44
+ context 'raw consumer' do
45
+ it_behaves_like 'consumer'
46
+ end
47
+
48
+ context 'custom request consumer' do
49
+ subject do
50
+ ret = Mixpanel::Consumer.new
51
+ class << ret
52
+ attr_reader :called
53
+ def request(*args)
54
+ @called = true
55
+ super(*args)
56
+ end
57
+ end
58
+
59
+ ret
60
+ end
61
+
62
+ after(:each) do
63
+ subject.called.should be_true
64
+ end
65
+
66
+ it_behaves_like 'consumer'
67
+ end
68
+
69
+ end
70
+
71
+ describe Mixpanel::BufferedConsumer do
72
+ let(:max_length) { 10 }
73
+ before { WebMock.reset! }
74
+
75
+ context 'Default BufferedConsumer' do
76
+ subject { Mixpanel::BufferedConsumer.new(nil, nil, nil, max_length) }
77
+
78
+ it 'should not send a request for a single message until flush is called' do
79
+ stub_request(:any, 'https://api.mixpanel.com/track').to_return({:body => '{"status": 1, "error": null}'})
80
+ subject.send(:event, {'data' => 'TEST EVENT 1'}.to_json)
81
+ WebMock.should have_not_requested(:post, 'https://api.mixpanel.com/track')
82
+
83
+ subject.flush()
84
+ WebMock.should have_requested(:post, 'https://api.mixpanel.com/track').
85
+ with(:body => {'data' => 'WyJURVNUIEVWRU5UIDEiXQ==', 'verbose' => '1' })
86
+ end
87
+
88
+ it 'should send one message when max_length events are tracked' do
89
+ stub_request(:any, 'https://api.mixpanel.com/track').to_return({:body => '{"status": 1, "error": null}'})
90
+
91
+ max_length.times do |i|
92
+ subject.send(:event, {'data' => "x #{i}"}.to_json)
93
+ end
94
+
95
+ WebMock.should have_requested(:post, 'https://api.mixpanel.com/track').
96
+ with(:body => {'data' => 'WyJ4IDAiLCJ4IDEiLCJ4IDIiLCJ4IDMiLCJ4IDQiLCJ4IDUiLCJ4IDYiLCJ4IDciLCJ4IDgiLCJ4IDkiXQ==', 'verbose' => '1' })
97
+ end
98
+
99
+ it 'should send one message per api key on import' do
100
+ stub_request(:any, 'https://api.mixpanel.com/import').to_return({:body => '{"status": 1, "error": null}'})
101
+ subject.send(:import, {'data' => 'TEST EVENT 1', 'api_key' => 'KEY 1'}.to_json)
102
+ subject.send(:import, {'data' => 'TEST EVENT 1', 'api_key' => 'KEY 2'}.to_json)
103
+ subject.send(:import, {'data' => 'TEST EVENT 2', 'api_key' => 'KEY 1'}.to_json)
104
+ subject.send(:import, {'data' => 'TEST EVENT 2', 'api_key' => 'KEY 2'}.to_json)
105
+ subject.flush
106
+
107
+ WebMock.should have_requested(:post, 'https://api.mixpanel.com/import').
108
+ with(:body => {'data' => 'IlRFU1QgRVZFTlQgMSI=', 'api_key' => 'KEY 1', 'verbose' => '1' })
109
+
110
+ WebMock.should have_requested(:post, 'https://api.mixpanel.com/import').
111
+ with(:body => {'data' => 'IlRFU1QgRVZFTlQgMSI=', 'api_key' => 'KEY 2', 'verbose' => '1' })
112
+ end
113
+ end
114
+
115
+ context 'BufferedConsumer with block' do
116
+ let(:messages_seen) { [] }
117
+ subject do
118
+ Mixpanel::BufferedConsumer.new(nil, nil, nil, 3) do |type, message|
119
+ messages_seen << [type, message]
120
+ end
121
+ end
122
+
123
+ it 'should call block instead of making default requests on flush' do
124
+ 3.times do |i|
125
+ subject.send(:event, {'data' => "x #{i}"}.to_json)
126
+ end
127
+
128
+ expect(messages_seen).to match_array(
129
+ [[:event, "{\"data\":[\"x 0\",\"x 1\",\"x 2\"]}"]]
130
+ )
131
+ end
132
+
133
+ end
134
+
135
+ end
@@ -0,0 +1,72 @@
1
+ require 'spec_helper'
2
+ require 'mixpanel-ruby/events.rb'
3
+ require 'mixpanel-ruby/version.rb'
4
+ require 'time'
5
+
6
+ describe Mixpanel::Events do
7
+ before(:each) do
8
+ @time_now = Time.parse('Jun 6 1972, 16:23:04')
9
+ Time.stub(:now).and_return(@time_now)
10
+
11
+ @log = []
12
+ @events = Mixpanel::Events.new('TEST TOKEN') do |type, message|
13
+ @log << [type, JSON.load(message)]
14
+ end
15
+ end
16
+
17
+ it 'should send a well formed track/ message' do
18
+ @events.track('TEST ID', 'Test Event', {
19
+ 'Circumstances' => 'During a test'
20
+ })
21
+ @log.should eq([[:event, 'data' => {
22
+ 'event' => 'Test Event',
23
+ 'properties' => {
24
+ 'Circumstances' => 'During a test',
25
+ 'distinct_id' => 'TEST ID',
26
+ 'mp_lib' => 'ruby',
27
+ '$lib_version' => Mixpanel::VERSION,
28
+ 'token' => 'TEST TOKEN',
29
+ 'time' => @time_now.to_i
30
+ }
31
+ }]])
32
+ end
33
+
34
+
35
+ it 'should return a well formed pixel tracking uri if asked' do
36
+ @events.track('TEST ID', 'Test Event', {
37
+ 'Circumstances' => 'During a test'
38
+ }, nil, true)
39
+ @log.should eq([[:event, 'data' => {
40
+ 'event' => 'Test Event',
41
+ 'properties' => {
42
+ 'Circumstances' => 'During a test',
43
+ 'distinct_id' => 'TEST ID',
44
+ 'mp_lib' => 'ruby',
45
+ '$lib_version' => Mixpanel::VERSION,
46
+ 'token' => 'TEST TOKEN',
47
+ 'time' => @time_now.to_i
48
+ }
49
+ }]])
50
+ end
51
+
52
+
53
+ it 'should send a well formed import/ message' do
54
+ @events.import('API_KEY', 'TEST ID', 'Test Event', {
55
+ 'Circumstances' => 'During a test'
56
+ })
57
+ @log.should eq([[:import, {
58
+ 'api_key' => 'API_KEY',
59
+ 'data' => {
60
+ 'event' => 'Test Event',
61
+ 'properties' => {
62
+ 'Circumstances' => 'During a test',
63
+ 'distinct_id' => 'TEST ID',
64
+ 'mp_lib' => 'ruby',
65
+ '$lib_version' => Mixpanel::VERSION,
66
+ 'token' => 'TEST TOKEN',
67
+ 'time' => @time_now.to_i
68
+ }
69
+ }
70
+ } ]])
71
+ end
72
+ end
@@ -0,0 +1,167 @@
1
+ require 'spec_helper'
2
+ require 'mixpanel-ruby/people'
3
+
4
+ describe Mixpanel::People do
5
+ before(:each) do
6
+ @time_now = Time.parse('Jun 6 1972, 16:23:04')
7
+ Time.stub(:now).and_return(@time_now)
8
+
9
+ @log = []
10
+ @people = Mixpanel::People.new('TEST TOKEN') do |type, message|
11
+ @log << [type, JSON.load(message)]
12
+ end
13
+ end
14
+
15
+ it 'should send a well formed engage/set message' do
16
+ @people.set("TEST ID", {
17
+ '$firstname' => 'David',
18
+ '$lastname' => 'Bowie',
19
+ })
20
+ @log.should eq([[:profile_update, 'data' => {
21
+ '$token' => 'TEST TOKEN',
22
+ '$distinct_id' => 'TEST ID',
23
+ '$time' => @time_now.to_i * 1000,
24
+ '$set' => {
25
+ '$firstname' => 'David',
26
+ '$lastname' => 'Bowie'
27
+ }
28
+ }]])
29
+ end
30
+
31
+ it 'should properly cast dates' do
32
+ @people.set("TEST ID", {
33
+ 'created_at' => DateTime.new(2013, 1, 2, 3, 4, 5)
34
+ })
35
+ @log.should eq([[:profile_update, 'data' => {
36
+ '$token' => 'TEST TOKEN',
37
+ '$distinct_id' => 'TEST ID',
38
+ '$time' => @time_now.to_i * 1000,
39
+ '$set' => {
40
+ 'created_at' => '2013-01-02T03:04:05'
41
+ }
42
+ }]])
43
+ end
44
+
45
+ it 'should send a well formed engage/set_once message' do
46
+ @people.set_once("TEST ID", {
47
+ '$firstname' => 'David',
48
+ '$lastname' => 'Bowie',
49
+ })
50
+ @log.should eq([[:profile_update, 'data' => {
51
+ '$token' => 'TEST TOKEN',
52
+ '$distinct_id' => 'TEST ID',
53
+ '$time' => @time_now.to_i * 1000,
54
+ '$set_once' => {
55
+ '$firstname' => 'David',
56
+ '$lastname' => 'Bowie'
57
+ }
58
+ }]])
59
+ end
60
+
61
+ it 'should send a well formed engage/add message' do
62
+ @people.increment("TEST ID", {'Albums Released' => 10})
63
+ @log.should eq([[:profile_update, 'data' => {
64
+ '$token' => 'TEST TOKEN',
65
+ '$distinct_id' => 'TEST ID',
66
+ '$time' => @time_now.to_i * 1000,
67
+ '$add' => {
68
+ 'Albums Released' => 10
69
+ }
70
+ }]])
71
+ end
72
+
73
+ it 'should send an engage/add message with a value of 1' do
74
+ @people.plus_one("TEST ID", 'Albums Released')
75
+ @log.should eq([[:profile_update, 'data' => {
76
+ '$token' => 'TEST TOKEN',
77
+ '$distinct_id' => 'TEST ID',
78
+ '$time' => @time_now.to_i * 1000,
79
+ '$add' => {
80
+ 'Albums Released' => 1
81
+ }
82
+ }]])
83
+ end
84
+
85
+ it 'should send a well formed engage/append message' do
86
+ @people.append("TEST ID", {'Albums' => 'Diamond Dogs'})
87
+ @log.should eq([[:profile_update, 'data' => {
88
+ '$token' => 'TEST TOKEN',
89
+ '$distinct_id' => 'TEST ID',
90
+ '$time' => @time_now.to_i * 1000,
91
+ '$append' => {
92
+ 'Albums' => 'Diamond Dogs'
93
+ }
94
+ }]])
95
+ end
96
+
97
+ it 'should send a well formed engage/union message' do
98
+ @people.union("TEST ID", {'Albums' => ['Diamond Dogs']})
99
+ @log.should eq([[:profile_update, 'data' => {
100
+ '$token' => 'TEST TOKEN',
101
+ '$distinct_id' => 'TEST ID',
102
+ '$time' => @time_now.to_i * 1000,
103
+ '$union' => {
104
+ 'Albums' => ['Diamond Dogs']
105
+ }
106
+ }]])
107
+ end
108
+
109
+ it 'should send a well formed unset message' do
110
+ @people.unset('TEST ID', 'Albums')
111
+ @log.should eq([[:profile_update, 'data' => {
112
+ '$token' => 'TEST TOKEN',
113
+ '$distinct_id' => 'TEST ID',
114
+ '$time' => @time_now.to_i * 1000,
115
+ '$unset' => ['Albums']
116
+ }]])
117
+ end
118
+
119
+ it 'should send a well formed unset message with multiple properties' do
120
+ @people.unset('TEST ID', ['Albums', 'Vinyls'])
121
+ @log.should eq([[:profile_update, 'data' => {
122
+ '$token' => 'TEST TOKEN',
123
+ '$distinct_id' => 'TEST ID',
124
+ '$time' => @time_now.to_i * 1000,
125
+ '$unset' => ['Albums', 'Vinyls']
126
+ }]])
127
+ end
128
+
129
+ it 'should send an engage/append with the right $transaction stuff' do
130
+ @people.track_charge("TEST ID", 25.42, {
131
+ '$time' => DateTime.new(1999,12,24,14, 02, 53),
132
+ 'SKU' => '1234567'
133
+ })
134
+ @log.should eq([[:profile_update, 'data' => {
135
+ '$token' => 'TEST TOKEN',
136
+ '$distinct_id' => 'TEST ID',
137
+ '$time' => @time_now.to_i * 1000,
138
+ '$append' => {
139
+ '$transactions' => {
140
+ '$time' => '1999-12-24T14:02:53',
141
+ 'SKU' => '1234567',
142
+ '$amount' => 25.42
143
+ }
144
+ }
145
+ }]])
146
+ end
147
+
148
+ it 'should send a well formed engage/unset message for $transaction' do
149
+ @people.clear_charges("TEST ID")
150
+ @log.should eq([[:profile_update, 'data' => {
151
+ '$token' => 'TEST TOKEN',
152
+ '$distinct_id' => 'TEST ID',
153
+ '$time' => @time_now.to_i * 1000,
154
+ '$unset' => ['$transactions']
155
+ }]])
156
+ end
157
+
158
+ it 'should send a well formed engage/delete message' do
159
+ @people.delete_user("TEST ID")
160
+ @log.should eq([[:profile_update, 'data' => {
161
+ '$token' => 'TEST TOKEN',
162
+ '$distinct_id' => 'TEST ID',
163
+ '$time' => @time_now.to_i * 1000,
164
+ '$delete' => ''
165
+ }]])
166
+ end
167
+ end
@@ -0,0 +1,135 @@
1
+ require 'mixpanel-ruby'
2
+ require 'base64'
3
+ require 'json'
4
+ require 'uri'
5
+
6
+ describe Mixpanel::Tracker do
7
+ before(:each) do
8
+ @time_now = Time.parse('Jun 6 1972, 16:23:04')
9
+ Time.stub(:now).and_return(@time_now)
10
+ end
11
+
12
+ it 'should send an alias message to mixpanel no matter what the consumer is' do
13
+ WebMock.reset!
14
+ stub_request(:any, 'https://api.mixpanel.com/track').to_return({:body => '{"status": 1, "error": null}'})
15
+ mixpanel = Mixpanel::Tracker.new('TEST TOKEN') {|*args| }
16
+ mixpanel.alias('TEST ALIAS', 'TEST ID')
17
+
18
+ WebMock.should have_requested(:post, 'https://api.mixpanel.com/track').
19
+ with(:body => {:data => 'eyJldmVudCI6IiRjcmVhdGVfYWxpYXMiLCJwcm9wZXJ0aWVzIjp7ImRpc3RpbmN0X2lkIjoiVEVTVCBJRCIsImFsaWFzIjoiVEVTVCBBTElBUyIsInRva2VuIjoiVEVTVCBUT0tFTiJ9fQ==', 'verbose' => '1'})
20
+ end
21
+
22
+ it 'should send a request to the track api with the default consumer' do
23
+ WebMock.reset!
24
+ stub_request(:any, 'https://api.mixpanel.com/track').to_return({:body => '{"status": 1, "error": null}'})
25
+ stub_request(:any, 'https://api.mixpanel.com/engage').to_return({:body => '{"status": 1, "error": null}'})
26
+ mixpanel = Mixpanel::Tracker.new('TEST TOKEN')
27
+
28
+ mixpanel.track('TEST ID', 'TEST EVENT', {'Circumstances' => 'During test'})
29
+
30
+ body = nil
31
+ WebMock.should have_requested(:post, 'https://api.mixpanel.com/track').
32
+ with { |req| body = req.body }
33
+
34
+ message_urlencoded = body[/^data=(.*?)(?:&|$)/, 1]
35
+ message_json = Base64.strict_decode64(URI.unescape(message_urlencoded))
36
+ message = JSON.load(message_json)
37
+ message.should eq({
38
+ 'event' => 'TEST EVENT',
39
+ 'properties' => {
40
+ 'Circumstances' => 'During test',
41
+ 'distinct_id' => 'TEST ID',
42
+ 'mp_lib' => 'ruby',
43
+ '$lib_version' => Mixpanel::VERSION,
44
+ 'token' => 'TEST TOKEN',
45
+ 'time' => @time_now.to_i
46
+ }
47
+ })
48
+ end
49
+
50
+ it 'should return a tracking uri if asked and not execute any requests' do
51
+ mixpanel = Mixpanel::Tracker.new('TEST TOKEN')
52
+ stub_request(:any, 'https://api.mixpanel.com/track').to_return({:body => '{"status": 1, "error": null}'})
53
+
54
+
55
+ a = mixpanel.track('TEST ID', 'TEST EVENT', {'Circumstances' => 'During test'}, nil, true)
56
+ a.should eq "https://api.mixpanel.com/track?data=eyJldmVudCI6IlRFU1QgRVZFTlQiLCJwcm9wZXJ0aWVzIjp7ImRpc3RpbmN0X2lkIjoiVEVTVCBJRCIsInRva2VuIjoiVEVTVCBUT0tFTiIsInRpbWUiOjc2NzIwOTg0LCJtcF9saWIiOiJydWJ5IiwiJGxpYl92ZXJzaW9uIjoiMS40LjAiLCJDaXJjdW1zdGFuY2VzIjoiRHVyaW5nIHRlc3QifX0%3D&verbose=1&img=1"
57
+
58
+ WebMock.should_not have_requested(:post, 'https://api.mixpanel.com/track')
59
+ WebMock.should_not have_requested(:get, 'https://api.mixpanel.com/track')
60
+ end
61
+
62
+
63
+
64
+
65
+
66
+
67
+
68
+
69
+
70
+
71
+
72
+
73
+
74
+
75
+
76
+
77
+
78
+
79
+
80
+ it 'should call a consumer block if one is given' do
81
+ messages = []
82
+ mixpanel = Mixpanel::Tracker.new('TEST TOKEN') do |type, message|
83
+ messages << [type, JSON.load(message)]
84
+ end
85
+ mixpanel.track('ID', 'Event')
86
+ mixpanel.import('API_KEY', 'ID', 'Import')
87
+ mixpanel.people.set('ID', {'k' => 'v'})
88
+ mixpanel.people.append('ID', {'k' => 'v'})
89
+
90
+ expect = [
91
+ [ :event, 'data' =>
92
+ { 'event' => 'Event',
93
+ 'properties' => {
94
+ 'distinct_id' => 'ID',
95
+ 'mp_lib' => 'ruby',
96
+ '$lib_version' => Mixpanel::VERSION,
97
+ 'token' => 'TEST TOKEN',
98
+ 'time' => @time_now.to_i
99
+ }
100
+ }
101
+ ],
102
+ [ :import, {
103
+ 'data' => {
104
+ 'event' => 'Import',
105
+ 'properties' => {
106
+ 'distinct_id' => 'ID',
107
+ 'mp_lib' => 'ruby',
108
+ '$lib_version' => Mixpanel::VERSION,
109
+ 'token' => 'TEST TOKEN',
110
+ 'time' => @time_now.to_i
111
+ }
112
+ },
113
+ 'api_key' => 'API_KEY',
114
+ }
115
+ ],
116
+ [ :profile_update, 'data' =>
117
+ { '$token' => 'TEST TOKEN',
118
+ '$distinct_id' => 'ID',
119
+ '$time' => @time_now.to_i * 1000,
120
+ '$set' => {'k' => 'v'}
121
+ }
122
+ ],
123
+ [ :profile_update, 'data' =>
124
+ { '$token' => 'TEST TOKEN',
125
+ '$distinct_id' => 'ID',
126
+ '$time' => @time_now.to_i * 1000,
127
+ '$append' => {'k' => 'v'}
128
+ }
129
+ ]
130
+ ]
131
+ expect.zip(messages).each do |expect, found|
132
+ expect.should eq(found)
133
+ end
134
+ end
135
+ end