UNPKG

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