1 | var assert = require('assert')
|
2 | , Twit = require('../lib/twitter')
|
3 | , config1 = require('../config1')
|
4 | , config2 = require('../config2')
|
5 | , colors = require('colors')
|
6 | , util = require('util')
|
7 | , async = require('async')
|
8 | , restTest = require('./rest')
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 | exports.checkStream = function (stream, done) {
|
18 | stream.on('connected', function () {
|
19 | console.log('\nconnected'.grey)
|
20 | });
|
21 |
|
22 | stream.once('tweet', function (tweet) {
|
23 | assert.equal(null, stream.abortedBy)
|
24 |
|
25 | stream.stop()
|
26 |
|
27 | assert.equal('twit-client', stream.abortedBy)
|
28 | assert.ok(tweet)
|
29 | assert.equal('string', typeof tweet.text)
|
30 | assert.equal('string', typeof tweet.id_str)
|
31 |
|
32 | console.log(('\ntweet: '+tweet.text).grey)
|
33 |
|
34 | done()
|
35 | });
|
36 |
|
37 | stream.on('reconnect', function (req, res) {
|
38 | console.log('Got disconnected. Scheduling reconnect! statusCode:', res.statusCode)
|
39 | });
|
40 | }
|
41 |
|
42 | var generateRandomString = function (length) {
|
43 | var length = length || 10
|
44 | var ret = ''
|
45 | for (var i = 0; i < length; i++) {
|
46 |
|
47 |
|
48 | ret += String.fromCharCode(Math.floor(Math.random()*90) + 33)
|
49 | }
|
50 |
|
51 | ret = encodeURI(ret)
|
52 |
|
53 | return ret
|
54 | }
|
55 |
|
56 | describe('Streaming API', function () {
|
57 |
|
58 | it('statuses/sample', function (done) {
|
59 | var twit = new Twit(config1);
|
60 | var stream = twit.stream('statuses/sample')
|
61 |
|
62 | exports.checkStream(stream, done)
|
63 | })
|
64 |
|
65 | it('statuses/filter using `track`', function (done) {
|
66 | this.timeout(120000)
|
67 | var twit = new Twit(config2);
|
68 | var stream = twit.stream('statuses/filter', { track: 'fun' })
|
69 |
|
70 | exports.checkStream(stream, done)
|
71 | })
|
72 |
|
73 | it('statuses/filter using `locations` string', function (done) {
|
74 | var twit = new Twit(config1);
|
75 | var world = '-180,-90,180,90';
|
76 | var stream = twit.stream('statuses/filter', { locations: world })
|
77 |
|
78 | exports.checkStream(stream, done)
|
79 | })
|
80 |
|
81 | it('statuses/filter using `locations` array for San Francisco and New York', function (done) {
|
82 | var twit = new Twit(config2);
|
83 | var params = {
|
84 | locations: [ '-122.75', '36.8', '121.75', '37.8', '-74', '40', '73', '41' ]
|
85 | }
|
86 |
|
87 | var stream = twit.stream('statuses/filter', params)
|
88 |
|
89 | exports.checkStream(stream, done)
|
90 | })
|
91 |
|
92 | it('statuses/filter using `track` array', function (done) {
|
93 | var twit = new Twit(config1);
|
94 | var params = {
|
95 | track: [ 'spring', 'summer', 'fall', 'winter', 'weather', 'joy', 'laugh', 'sleep' ]
|
96 | }
|
97 |
|
98 | var stream = twit.stream('statuses/filter', params)
|
99 |
|
100 | exports.checkStream(stream, done)
|
101 | })
|
102 |
|
103 | it('statuses/filter using `track` and `language`', function (done) {
|
104 | var twit = new Twit(config1);
|
105 | var params = {
|
106 | track: [ '#apple', 'google', 'twitter', 'facebook', 'happy', 'party', ':)' ],
|
107 | language: 'en'
|
108 | }
|
109 |
|
110 | var stream = twit.stream('statuses/filter', params)
|
111 |
|
112 | exports.checkStream(stream, done)
|
113 | })
|
114 |
|
115 | it('stopping & restarting the stream works', function (done) {
|
116 | var twit = new Twit(config2);
|
117 | var stream = twit.stream('statuses/sample')
|
118 |
|
119 |
|
120 | setTimeout(function () {
|
121 | assert.equal(null, stream.abortedBy)
|
122 | stream.stop()
|
123 | assert.equal('twit-client', stream.abortedBy)
|
124 | util.puts('\nstopped stream')
|
125 | }, 2000)
|
126 |
|
127 |
|
128 | setTimeout(function () {
|
129 | stream.once('connected', function (req) {
|
130 | util.puts('\nrestarted stream')
|
131 | stream.stop()
|
132 | assert.equal('twit-client', stream.abortedBy)
|
133 | util.puts('\nstopped stream')
|
134 | done()
|
135 | })
|
136 |
|
137 |
|
138 | stream.start()
|
139 | }, 3000)
|
140 | })
|
141 |
|
142 | it('stopping & restarting stream emits to previously assigned callbacks', function (done) {
|
143 | var twit = new Twit(config2);
|
144 | var stream = twit.stream('statuses/sample')
|
145 |
|
146 | var started = false
|
147 | var numTweets = 0
|
148 | stream.on('tweet', function (tweet) {
|
149 | process.stdout.write('.')
|
150 | if (!started) {
|
151 | started = true
|
152 | numTweets++
|
153 | console.log('received tweet', numTweets)
|
154 |
|
155 | console.log('stopping stream')
|
156 | stream.stop()
|
157 |
|
158 |
|
159 | if (numTweets === 2) {
|
160 | done()
|
161 | } else {
|
162 | started = false
|
163 | console.log('restarting stream')
|
164 |
|
165 | setTimeout(function () {
|
166 | stream.start()
|
167 | }, 1000)
|
168 | }
|
169 | }
|
170 | })
|
171 |
|
172 | stream.on('limit', function (limitMsg) {
|
173 | console.log('limit', limitMsg)
|
174 | })
|
175 |
|
176 | stream.on('disconnect', function (disconnMsg) {
|
177 | console.log('disconnect', disconnMsg)
|
178 | })
|
179 |
|
180 | stream.on('reconnect', function (req, res, ival) {
|
181 | console.log('reconnect. statusCode:', res.statusCode, 'interval:', ival)
|
182 | })
|
183 |
|
184 | stream.on('connect', function (req) {
|
185 | console.log('connect')
|
186 | })
|
187 |
|
188 | })
|
189 | })
|
190 |
|
191 | describe('streaming API events', function () {
|
192 | var senderScreenName
|
193 | var receiverScreenName
|
194 |
|
195 | var dmId
|
196 | var twit, twit2
|
197 |
|
198 | before(function (done) {
|
199 |
|
200 |
|
201 | twit = new Twit(config1);
|
202 | twit2 = new Twit(config2);
|
203 |
|
204 | async.parallel({
|
205 |
|
206 | getSenderScreenName: function (parNext) {
|
207 | console.log('getting first screen_name')
|
208 | twit.get('account/verify_credentials', function (err, reply) {
|
209 | assert(!err, err)
|
210 |
|
211 | assert(reply)
|
212 | assert(reply.screen_name)
|
213 |
|
214 | senderScreenName = reply.screen_name
|
215 |
|
216 | return parNext()
|
217 | })
|
218 | },
|
219 |
|
220 | getReceiverScreenName: function (parNext) {
|
221 | console.log('getting second screen_name')
|
222 | twit2.get('account/verify_credentials', function (err, reply) {
|
223 | assert(!err, err)
|
224 |
|
225 | assert(reply)
|
226 | assert(reply.screen_name)
|
227 |
|
228 | receiverScreenName = reply.screen_name
|
229 |
|
230 | return parNext()
|
231 | })
|
232 | }
|
233 | }, function (err) {
|
234 | assert(!err, err)
|
235 |
|
236 | var followParams = { screen_name: senderScreenName }
|
237 | console.log('making second user follow first one so first can DM')
|
238 |
|
239 | twit2.post('friendships/create', followParams, function (err, reply) {
|
240 | assert(!err, err)
|
241 | assert(reply.following)
|
242 |
|
243 | done()
|
244 | })
|
245 | })
|
246 | })
|
247 |
|
248 | it('direct_message event', function (done) {
|
249 | this.timeout(0);
|
250 |
|
251 | var makeDmParams = function () {
|
252 | return {
|
253 | screen_name: receiverScreenName,
|
254 | text: 'direct message streaming event test! :-) ' + generateRandomString(10),
|
255 | twit_options: {
|
256 | retry: true
|
257 | }
|
258 | }
|
259 | }
|
260 |
|
261 | var dmId = null
|
262 | var dmsSent = []
|
263 |
|
264 |
|
265 | var receiverStream = twit2.stream('user')
|
266 |
|
267 | receiverStream.on('reconnect', function (request, response, connectInterval) {
|
268 | console.log('stream reconnect: response status code', response.statusCode, 'connecting in', connectInterval)
|
269 | });
|
270 |
|
271 | console.log('\nlistening for DMs')
|
272 |
|
273 | receiverStream.once('direct_message', function (directMsg) {
|
274 | console.log('got DM', directMsg.direct_message.text)
|
275 | restTest.checkDm(directMsg.direct_message)
|
276 |
|
277 |
|
278 |
|
279 | var sentDmFound = dmsSent.some(function (dm) {
|
280 | return (
|
281 | directMsg.direct_message.text === dm.text &&
|
282 | directMsg.direct_message.sender.screen_name === dm.sender.screen_name
|
283 | )
|
284 | })
|
285 |
|
286 | if (!sentDmFound) {
|
287 | console.log('this DM doesnt match our test DMs - still waiting for a matching one.')
|
288 | }
|
289 |
|
290 | if (sentDmFound) {
|
291 | receiverStream.stop()
|
292 | return done()
|
293 | }
|
294 | })
|
295 |
|
296 | receiverStream.on('connected', function () {
|
297 | var sendDm = function () {
|
298 | var dmParams = makeDmParams()
|
299 |
|
300 | twit.post('direct_messages/new', dmParams, function (err, reply) {
|
301 | assert(!err, err)
|
302 | assert(reply)
|
303 | restTest.checkDm(reply)
|
304 |
|
305 |
|
306 | dmsSent.push(reply)
|
307 |
|
308 | console.log('posted DM, dmId:', reply.text, reply.id_str)
|
309 |
|
310 | dmId = reply.id_str
|
311 | assert(dmId)
|
312 | })
|
313 | }
|
314 |
|
315 | if (dmId) {
|
316 | console.log('stream is connected again. deleting dmId', dmId)
|
317 |
|
318 |
|
319 |
|
320 | twit.post('direct_messages/destroy', { id: dmId }, function (err, reply) {
|
321 |
|
322 | if (!err || err.statusCode !== 404) {
|
323 | assert(!err, err)
|
324 | restTest.checkDm(reply)
|
325 | assert.equal(reply.id, dmId)
|
326 | console.log('deleted DM', dmId)
|
327 | }
|
328 |
|
329 | dmId = null
|
330 |
|
331 | sendDm()
|
332 | })
|
333 | } else {
|
334 |
|
335 | sendDm()
|
336 | }
|
337 | })
|
338 |
|
339 | after(function (done) {
|
340 | console.log('\ndeleting DM')
|
341 | assert(dmId)
|
342 | assert.equal(typeof dmId, 'string')
|
343 | twit.post('direct_messages/destroy', { id: dmId }, function (err, reply) {
|
344 | assert(!err, err)
|
345 | restTest.checkDm(reply)
|
346 | assert.equal(reply.id, dmId)
|
347 |
|
348 | return done()
|
349 | })
|
350 | })
|
351 | })
|
352 | })
|
353 |
|
354 | describe('streaming API bad request', function (done) {
|
355 | it('emits an error for a 401 response', function (done) {
|
356 | var badCredentials = {
|
357 | consumer_key: 'a'
|
358 | , consumer_secret: 'b'
|
359 | , access_token: 'c'
|
360 | , access_token_secret: 'd'
|
361 | }
|
362 |
|
363 | var twit = new Twit(badCredentials);
|
364 |
|
365 | var stream = twit.stream('statuses/filter', { track : ['foo'] });
|
366 |
|
367 | stream.on('error', function (err) {
|
368 | assert.equal(err.response.statusCode, 401)
|
369 |
|
370 | return done()
|
371 | })
|
372 | })
|
373 | })
|
374 |
|
375 | describe.skip('streaming reconnect', function (done) {
|
376 |
|
377 | it('correctly implements 420 backoff', function (done) {
|
378 | var twit = new Twit(config1);
|
379 |
|
380 | var stream = twit.stream('statuses/filter', { track: [ 'fun', 'yolo']});
|
381 |
|
382 | var expectedInterval = 0;
|
383 |
|
384 | var numReconnectsTested = 0;
|
385 | var numReconnectsToTest = 3;
|
386 |
|
387 | stream.on('connected', function (res) {
|
388 |
|
389 | res.statusCode = 420;
|
390 | stream.request.abort()
|
391 |
|
392 |
|
393 | setTimeout(function () {
|
394 | expectedInterval = expectedInterval ? 2*expectedInterval : 60000;
|
395 |
|
396 |
|
397 | assert.equal(stream.connectInterval, expectedInterval);
|
398 | console.log('420 rate limiting backoff:', stream.connectInterval);
|
399 |
|
400 |
|
401 | stream.keepAlive();
|
402 | delete stream.scheduledReconnect
|
403 |
|
404 | numReconnectsTested += 1;
|
405 |
|
406 | if (numReconnectsTested === numReconnectsToTest) {
|
407 | return done();
|
408 | }
|
409 | }, 100);
|
410 | });
|
411 | });
|
412 | }) |
\ | No newline at end of file |