UNPKG

12.2 kBJavaScriptView Raw
1var proxyquire = require('proxyquire'),
2 Sinon = require('sinon'),
3 https = require('https'),
4 events = require('events'),
5 Mixpanel = require('../lib/mixpanel-node'),
6 packageInfo = require('../package.json');
7
8var mock_now_time = new Date(2016, 1, 1).getTime();
9
10exports.track = {
11 setUp: function(next) {
12 this.mixpanel = Mixpanel.init('token');
13 this.clock = Sinon.useFakeTimers(mock_now_time);
14
15 Sinon.stub(this.mixpanel, 'send_request');
16
17 next();
18 },
19
20 tearDown: function(next) {
21 this.mixpanel.send_request.restore();
22 this.clock.restore();
23
24 next();
25 },
26
27 "calls send_request with correct endpoint and data": function(test) {
28 var event = "test",
29 props = { key1: 'val1' },
30 expected_endpoint = "/track",
31 expected_data = {
32 event: 'test',
33 properties: {
34 key1: 'val1',
35 token: 'token'
36 }
37 };
38
39 this.mixpanel.track(event, props);
40
41 test.ok(
42 this.mixpanel.send_request.calledWithMatch({ endpoint: expected_endpoint, data: expected_data }),
43 "track didn't call send_request with correct arguments"
44 );
45
46 test.done();
47 },
48
49 "can be called with optional properties": function(test) {
50 var expected_endpoint = "/track",
51 expected_data = {
52 event: 'test',
53 properties: {
54 token: 'token'
55 }
56 };
57
58 this.mixpanel.track("test");
59
60 test.ok(
61 this.mixpanel.send_request.calledWithMatch({ endpoint: expected_endpoint, data: expected_data }),
62 "track didn't call send_request with correct arguments"
63 );
64 test.done();
65 },
66
67 "can be called with optional callback": function(test) {
68 var expected_endpoint = "/track",
69 expected_data = {
70 event: 'test',
71 properties: {
72 token: 'token'
73 }
74 };
75
76 this.mixpanel.send_request.callsArgWith(1, undefined);
77
78 test.expect(1);
79 this.mixpanel.track("test", function(e) {
80 test.equal(e, undefined, "error should be undefined");
81 test.done();
82 });
83 },
84
85 "supports Date object for time": function(test) {
86 var event = 'test',
87 time = new Date(mock_now_time),
88 props = { time: time },
89 expected_endpoint = "/track",
90 expected_data = {
91 event: 'test',
92 properties: {
93 token: 'token',
94 time: time.getTime(),
95 mp_lib: 'node',
96 $lib_version: packageInfo.version
97 }
98 };
99
100 this.mixpanel.track(event, props);
101
102 test.ok(
103 this.mixpanel.send_request.calledWithMatch({ endpoint: expected_endpoint, data: expected_data }),
104 "track didn't call send_request with correct arguments"
105 );
106 test.done();
107 },
108
109 "supports unix timestamp for time": function(test) {
110 var event = 'test',
111 time = mock_now_time,
112 props = { time: time },
113 expected_endpoint = "/track",
114 expected_data = {
115 event: 'test',
116 properties: {
117 token: 'token',
118 time: time,
119 mp_lib: 'node',
120 $lib_version: packageInfo.version
121 }
122 };
123
124 this.mixpanel.track(event, props);
125
126 test.ok(
127 this.mixpanel.send_request.calledWithMatch({ endpoint: expected_endpoint, data: expected_data }),
128 "track didn't call send_request with correct arguments"
129 );
130 test.done();
131 },
132
133 "throws error if time is not a number or Date": function(test) {
134 var event = 'test',
135 props = { time: 'not a number or Date' };
136
137 test.throws(
138 this.mixpanel.track.bind(this, event, props),
139 /`time` property must be a Date or Unix timestamp/,
140 "track didn't throw an error when time wasn't a number or Date"
141 );
142 test.done();
143 },
144
145 "does not require time property": function(test) {
146 var event = 'test',
147 props = {};
148
149 test.doesNotThrow(this.mixpanel.track.bind(this, event, props));
150 test.done();
151 }
152};
153
154exports.track_batch = {
155 setUp: function(next) {
156 this.mixpanel = Mixpanel.init('token');
157 this.clock = Sinon.useFakeTimers();
158 Sinon.stub(this.mixpanel, 'send_request');
159 next();
160 },
161
162 tearDown: function(next) {
163 this.mixpanel.send_request.restore();
164 this.clock.restore();
165 next();
166 },
167
168 "calls send_request with correct endpoint, data, and method": function(test) {
169 var expected_endpoint = "/track",
170 event_list = [
171 {event: 'test', properties: {key1: 'val1', time: 500 }},
172 {event: 'test', properties: {key2: 'val2', time: 1000}},
173 {event: 'test2', properties: {key2: 'val2', time: 1500}}
174 ],
175 expected_data = [
176 {event: 'test', properties: {key1: 'val1', time: 500, token: 'token'}},
177 {event: 'test', properties: {key2: 'val2', time: 1000, token: 'token'}},
178 {event: 'test2', properties: {key2: 'val2', time: 1500, token: 'token'}}
179 ];
180
181 this.mixpanel.track_batch(event_list);
182
183 test.ok(
184 this.mixpanel.send_request.calledWithMatch({
185 method: "POST",
186 endpoint: expected_endpoint,
187 data: expected_data
188 }),
189 "track_batch didn't call send_request with correct arguments"
190 );
191
192 test.done();
193 },
194
195 "does not require the time argument for every event": function(test) {
196 var event_list = [
197 {event: 'test', properties: {key1: 'val1', time: 500 }},
198 {event: 'test', properties: {key2: 'val2', time: 1000}},
199 {event: 'test2', properties: {key2: 'val2' }}
200 ];
201 test.doesNotThrow(this.mixpanel.track_batch.bind(this, event_list));
202 test.done();
203 },
204
205 "batches 50 events at a time": function(test) {
206 var event_list = [];
207 for (var ei = 0; ei < 130; ei++) { // 3 batches: 50 + 50 + 30
208 event_list.push({event: 'test', properties: {key1: 'val1', time: 500 + ei }});
209 }
210
211 this.mixpanel.track_batch(event_list);
212
213 test.equals(
214 3, this.mixpanel.send_request.callCount,
215 "track_batch didn't call send_request correct number of times"
216 );
217
218 test.done();
219 }
220};
221
222exports.track_batch_integration = {
223 setUp: function(next) {
224 this.mixpanel = Mixpanel.init('token', { key: 'key' });
225 this.clock = Sinon.useFakeTimers();
226
227 Sinon.stub(https, 'request');
228
229 this.http_emitter = new events.EventEmitter();
230
231 // stub sequence of https responses
232 this.res = [];
233 for (var ri = 0; ri < 5; ri++) {
234 this.res.push(new events.EventEmitter());
235 https.request.onCall(ri).callsArgWith(1, this.res[ri]);
236 https.request.onCall(ri).returns({
237 write: function () {},
238 end: function () {},
239 on: function(event) {}
240 });
241 }
242
243 this.event_list = [];
244 for (var ei = 0; ei < 130; ei++) { // 3 batches: 50 + 50 + 30
245 this.event_list.push({event: 'test', properties: {key1: 'val1', time: 500 + ei }});
246 }
247
248 next();
249 },
250
251 tearDown: function(next) {
252 https.request.restore();
253 this.clock.restore();
254
255 next();
256 },
257
258 "calls provided callback after all requests finish": function(test) {
259 test.expect(2);
260 this.mixpanel.track_batch(this.event_list, function(error_list) {
261 test.equals(
262 3, https.request.callCount,
263 "track_batch didn't call send_request correct number of times before callback"
264 );
265 test.equals(
266 null, error_list,
267 "track_batch returned errors in callback unexpectedly"
268 );
269 test.done();
270 });
271 for (var ri = 0; ri < 3; ri++) {
272 this.res[ri].emit('data', '1');
273 this.res[ri].emit('end');
274 }
275 },
276
277 "passes error list to callback": function(test) {
278 test.expect(1);
279 this.mixpanel.track_batch(this.event_list, function(error_list) {
280 test.equals(
281 3, error_list.length,
282 "track_batch didn't return errors in callback"
283 );
284 test.done();
285 });
286 for (var ri = 0; ri < 3; ri++) {
287 this.res[ri].emit('data', '0');
288 this.res[ri].emit('end');
289 }
290 },
291
292 "calls provided callback when options are passed": function(test) {
293 test.expect(2);
294 this.mixpanel.track_batch(this.event_list, {max_batch_size: 100}, function(error_list) {
295 test.equals(
296 3, https.request.callCount,
297 "track_batch didn't call send_request correct number of times before callback"
298 );
299 test.equals(
300 null, error_list,
301 "track_batch returned errors in callback unexpectedly"
302 );
303 test.done();
304 });
305 for (var ri = 0; ri < 3; ri++) {
306 this.res[ri].emit('data', '1');
307 this.res[ri].emit('end');
308 }
309 },
310
311 "sends more requests when max_batch_size < 50": function(test) {
312 test.expect(2);
313 this.mixpanel.track_batch(this.event_list, {max_batch_size: 30}, function(error_list) {
314 test.equals(
315 5, https.request.callCount, // 30 + 30 + 30 + 30 + 10
316 "track_batch didn't call send_request correct number of times before callback"
317 );
318 test.equals(
319 null, error_list,
320 "track_batch returned errors in callback unexpectedly"
321 );
322 test.done();
323 });
324 for (var ri = 0; ri < 5; ri++) {
325 this.res[ri].emit('data', '1');
326 this.res[ri].emit('end');
327 }
328 },
329
330 "can set max concurrent requests": function(test) {
331 var async_all_stub = Sinon.stub();
332 var PatchedMixpanel = proxyquire('../lib/mixpanel-node', {
333 './utils': {async_all: async_all_stub},
334 });
335 async_all_stub.callsArgWith(2, null);
336 this.mixpanel = PatchedMixpanel.init('token', { key: 'key' });
337
338 test.expect(2);
339 this.mixpanel.track_batch(this.event_list, {max_batch_size: 30, max_concurrent_requests: 2}, function(error_list) {
340 // should send 5 event batches over 3 request batches:
341 // request batch 1: 30 events, 30 events
342 // request batch 2: 30 events, 30 events
343 // request batch 3: 10 events
344 test.equals(
345 3, async_all_stub.callCount,
346 "track_batch didn't batch concurrent https requests correctly"
347 );
348 test.equals(
349 null, error_list,
350 "track_batch returned errors in callback unexpectedly"
351 );
352 test.done();
353 });
354 for (var ri = 0; ri < 3; ri++) {
355 this.res[ri].emit('data', '1');
356 this.res[ri].emit('end');
357 }
358 },
359
360 "behaves well without a callback": function(test) {
361 test.expect(2);
362 this.mixpanel.track_batch(this.event_list);
363 test.equals(
364 3, https.request.callCount,
365 "track_batch didn't call send_request correct number of times"
366 );
367 this.mixpanel.track_batch(this.event_list, {max_batch_size: 100});
368 test.equals(
369 5, https.request.callCount, // 3 + 100 / 50; last request starts async
370 "track_batch didn't call send_request correct number of times"
371 );
372 test.done();
373 }
374};
375
376