UNPKG

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