mirror of
https://github.com/thewesker/bug-em.git
synced 2025-12-22 21:11:06 -05:00
lol
This commit is contained in:
46
node_modules/twit/tests/helpers.js
generated
vendored
Normal file
46
node_modules/twit/tests/helpers.js
generated
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
var stream = require('stream');
|
||||
var util = require('util');
|
||||
|
||||
// Used to stub out calls to `request`.
|
||||
exports.FakeRequest = function () {
|
||||
EventEmitter.call(this)
|
||||
}
|
||||
util.inherits(exports.FakeRequest, EventEmitter)
|
||||
exports.FakeRequest.prototype.destroy = function () {
|
||||
|
||||
}
|
||||
|
||||
// Used to stub out the http.IncomingMessage object emitted by the "response" event on `request`.
|
||||
exports.FakeResponse = function (statusCode, body) {
|
||||
if (!body) {
|
||||
body = '';
|
||||
}
|
||||
this.statusCode = statusCode;
|
||||
stream.Readable.call(this);
|
||||
this.push(body);
|
||||
this.push(null);
|
||||
}
|
||||
util.inherits(exports.FakeResponse, stream.Readable);
|
||||
|
||||
exports.FakeResponse.prototype._read = function () {
|
||||
|
||||
}
|
||||
exports.FakeResponse.prototype.destroy = function () {
|
||||
|
||||
}
|
||||
|
||||
exports.generateRandomString = function generateRandomString (length) {
|
||||
var length = length || 10
|
||||
var ret = ''
|
||||
var alphabet = 'abcdefghijklmnopqrstuvwxyz0123456789'
|
||||
for (var i = 0; i < length; i++) {
|
||||
// use an easy set of unicode as an alphabet - twitter won't reformat them
|
||||
// which makes testing easier
|
||||
ret += alphabet[Math.floor(Math.random()*alphabet.length)]
|
||||
}
|
||||
|
||||
ret = encodeURI(ret)
|
||||
|
||||
return ret
|
||||
}
|
||||
BIN
node_modules/twit/tests/img/bigbird.jpg
generated
vendored
Normal file
BIN
node_modules/twit/tests/img/bigbird.jpg
generated
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.8 KiB |
BIN
node_modules/twit/tests/img/cutebird.png
generated
vendored
Normal file
BIN
node_modules/twit/tests/img/cutebird.png
generated
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 28 KiB |
BIN
node_modules/twit/tests/img/snoopy-animated.gif
generated
vendored
Normal file
BIN
node_modules/twit/tests/img/snoopy-animated.gif
generated
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 693 B |
BIN
node_modules/twit/tests/img/twitterbird.gif
generated
vendored
Normal file
BIN
node_modules/twit/tests/img/twitterbird.gif
generated
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 KiB |
74
node_modules/twit/tests/multiple-conn.js
generated
vendored
Normal file
74
node_modules/twit/tests/multiple-conn.js
generated
vendored
Normal file
@@ -0,0 +1,74 @@
|
||||
var assert = require('assert');
|
||||
|
||||
var Twit = require('../lib/twitter');
|
||||
var config1 = require('../config1');
|
||||
var colors = require('colors');
|
||||
var restTest = require('./rest.js');
|
||||
|
||||
/*
|
||||
Don't run these tests often otherwise Twitter will rate limit you
|
||||
*/
|
||||
|
||||
describe.skip('multiple connections', function () {
|
||||
it('results in one of the streams closing', function (done) {
|
||||
var twit = new Twit(config1);
|
||||
|
||||
var streams = [
|
||||
twit.stream('statuses/sample'),
|
||||
twit.stream('statuses/sample'),
|
||||
twit.stream('statuses/sample'),
|
||||
];
|
||||
|
||||
streams.forEach(function (stream, i) {
|
||||
stream.on('disconnect', function (disconnect) {
|
||||
console.log('Disconect for stream', i)
|
||||
assert.equal(typeof disconnect, 'object');
|
||||
done();
|
||||
});
|
||||
|
||||
stream.on('error', function (errMsg) {
|
||||
console.log('error for stream', i, errMsg)
|
||||
})
|
||||
|
||||
stream.on('tweet', function (t) {
|
||||
console.log(i)
|
||||
})
|
||||
|
||||
stream.on('connected', function () {
|
||||
console.log('Stream', i, 'connected.')
|
||||
})
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
describe.skip('Managing multiple streams legally', function () {
|
||||
this.timeout(60000);
|
||||
it('updating track keywords without losing data', function (done) {
|
||||
var twit = new Twit(config1);
|
||||
var stream1 = twit.stream('statuses/filter', { track: ['#no'] });
|
||||
|
||||
stream1.once('tweet', function (tweet) {
|
||||
console.log('got tweet from first stream')
|
||||
restTest.checkTweet(tweet);
|
||||
restTest.assertTweetHasText(tweet, '#no');
|
||||
|
||||
// update our track list and initiate a new connection
|
||||
var stream2 = twit.stream('statuses/filter', { track: ['#fun'] });
|
||||
|
||||
stream2.once('connected', function (res) {
|
||||
console.log('second stream connected')
|
||||
// stop the first stream immediately
|
||||
stream1.stop();
|
||||
assert.equal(res.statusCode, 200)
|
||||
|
||||
stream2.once('tweet', function (tweet) {
|
||||
restTest.checkTweet(tweet);
|
||||
|
||||
restTest.assertTweetHasText(tweet, '#fun');
|
||||
return done();
|
||||
})
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
823
node_modules/twit/tests/rest.js
generated
vendored
Normal file
823
node_modules/twit/tests/rest.js
generated
vendored
Normal file
@@ -0,0 +1,823 @@
|
||||
var assert = require('assert')
|
||||
, EventEmitter = require('events').EventEmitter
|
||||
, fs = require('fs')
|
||||
, sinon = require('sinon')
|
||||
, Twit = require('../lib/twitter')
|
||||
, config1 = require('../config1')
|
||||
, config2 = require('../config2')
|
||||
, helpers = require('./helpers')
|
||||
, util = require('util')
|
||||
, async = require('async')
|
||||
|
||||
describe('REST API', function () {
|
||||
var twit = null
|
||||
|
||||
before(function () {
|
||||
twit = new Twit(config1);
|
||||
})
|
||||
|
||||
it('GET `account/verify_credentials`', function (done) {
|
||||
twit.get('account/verify_credentials', function (err, reply, response) {
|
||||
checkReply(err, reply)
|
||||
assert.notEqual(reply.followers_count, undefined)
|
||||
assert.notEqual(reply.friends_count, undefined)
|
||||
assert.ok(reply.id_str)
|
||||
|
||||
checkResponse(response)
|
||||
|
||||
assert(response.headers['x-rate-limit-limit'])
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('GET `account/verify_credentials` using promise API only', function (done) {
|
||||
twit
|
||||
.get('account/verify_credentials', { skip_status: true })
|
||||
.catch(function (err) {
|
||||
console.log('catch err', err.stack)
|
||||
})
|
||||
.then(function (result) {
|
||||
checkReply(null, result.data)
|
||||
assert.notEqual(result.data.followers_count, undefined)
|
||||
assert.notEqual(result.data.friends_count, undefined)
|
||||
assert.ok(result.data.id_str)
|
||||
checkResponse(result.resp)
|
||||
assert(result.resp.headers['x-rate-limit-limit'])
|
||||
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('GET `account/verify_credentials` using promise API AND callback API', function (done) {
|
||||
var i = 0;
|
||||
|
||||
var _checkDataAndResp = function (data, resp) {
|
||||
checkReply(null, data)
|
||||
assert.notEqual(data.followers_count, undefined)
|
||||
assert.notEqual(data.friends_count, undefined)
|
||||
assert.ok(data.id_str)
|
||||
checkResponse(resp)
|
||||
assert(resp.headers['x-rate-limit-limit'])
|
||||
|
||||
i++;
|
||||
if (i == 2) {
|
||||
done()
|
||||
}
|
||||
}
|
||||
|
||||
var get = twit.get('account/verify_credentials', function (err, data, resp) {
|
||||
assert(!err, err);
|
||||
_checkDataAndResp(data, resp);
|
||||
});
|
||||
get.catch(function (err) {
|
||||
console.log('Got error:', err.stack)
|
||||
});
|
||||
get.then(function (result) {
|
||||
_checkDataAndResp(result.data, result.resp);
|
||||
});
|
||||
})
|
||||
|
||||
it('POST `account/update_profile`', function (done) {
|
||||
twit.post('account/update_profile', function (err, reply, response) {
|
||||
checkReply(err, reply)
|
||||
console.log('\nscreen name:', reply.screen_name)
|
||||
checkResponse(response)
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('POST `statuses/update` and POST `statuses/destroy:id`', function (done) {
|
||||
var tweetId = null
|
||||
|
||||
var params = {
|
||||
status: '@tolga_tezel tweeting using github.com/ttezel/twit. ' + helpers.generateRandomString(7)
|
||||
}
|
||||
twit.post('statuses/update', params, function (err, reply, response) {
|
||||
checkReply(err, reply)
|
||||
console.log('\ntweeted:', reply.text)
|
||||
|
||||
tweetId = reply.id_str
|
||||
assert(tweetId)
|
||||
checkResponse(response)
|
||||
|
||||
var deleteParams = { id: tweetId }
|
||||
// Try up to 2 times to delete the tweet.
|
||||
// Even after a 200 response to statuses/update is returned, a delete might 404 so we retry.
|
||||
exports.req_with_retries(twit, 2, 'post', 'statuses/destroy/:id', deleteParams, [404], function (err, body, response) {
|
||||
checkReply(err, body)
|
||||
checkTweet(body)
|
||||
checkResponse(response)
|
||||
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('POST `statuses/update` with characters requiring escaping', function (done) {
|
||||
var params = { status: '@tolga_tezel tweeting using github.com/ttezel/twit :) !' + helpers.generateRandomString(15) }
|
||||
|
||||
twit.post('statuses/update', params, function (err, reply, response) {
|
||||
checkReply(err, reply)
|
||||
|
||||
console.log('\ntweeted:', reply.text)
|
||||
|
||||
checkResponse(response)
|
||||
|
||||
var text = reply.text
|
||||
|
||||
assert(reply.id_str)
|
||||
|
||||
twit.post('statuses/destroy/:id', { id: reply.id_str }, function (err, reply, response) {
|
||||
checkReply(err, reply)
|
||||
checkTweet(reply)
|
||||
assert.equal(reply.text, text)
|
||||
|
||||
checkResponse(response)
|
||||
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('POST `statuses/update` with \'Hi!!\' works', function (done) {
|
||||
var params = { status: 'Hi!!' }
|
||||
|
||||
twit.post('statuses/update', params, function (err, reply, response) {
|
||||
checkReply(err, reply)
|
||||
|
||||
console.log('\ntweeted:', reply.text)
|
||||
|
||||
checkResponse(response)
|
||||
|
||||
var text = reply.text
|
||||
|
||||
var destroyRoute = 'statuses/destroy/'+reply.id_str
|
||||
|
||||
twit.post(destroyRoute, function (err, reply, response) {
|
||||
checkReply(err, reply)
|
||||
checkTweet(reply)
|
||||
assert.equal(reply.text, text)
|
||||
|
||||
checkResponse(response)
|
||||
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('GET `statuses/home_timeline`', function (done) {
|
||||
twit.get('statuses/home_timeline', function (err, reply, response) {
|
||||
checkReply(err, reply)
|
||||
checkTweet(reply[0])
|
||||
|
||||
checkResponse(response)
|
||||
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('GET `statuses/mentions_timeline`', function (done) {
|
||||
twit.get('statuses/mentions_timeline', function (err, reply, response) {
|
||||
checkReply(err, reply)
|
||||
checkTweet(reply[0])
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('GET `statuses/user_timeline`', function (done) {
|
||||
var params = {
|
||||
screen_name: 'tolga_tezel'
|
||||
}
|
||||
|
||||
twit.get('statuses/user_timeline', params, function (err, reply, response) {
|
||||
checkReply(err, reply)
|
||||
checkTweet(reply[0])
|
||||
|
||||
checkResponse(response)
|
||||
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('GET `search/tweets` { q: "a", since_id: 12345 }', function (done) {
|
||||
var params = { q: 'a', since_id: 12345 }
|
||||
twit.get('search/tweets', params, function (err, reply, response) {
|
||||
checkReply(err, reply)
|
||||
assert.ok(reply.statuses)
|
||||
checkTweet(reply.statuses[0])
|
||||
|
||||
checkResponse(response)
|
||||
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('GET `search/tweets` { q: "fun since:2011-11-11" }', function (done) {
|
||||
var params = { q: 'fun since:2011-11-11', count: 100 }
|
||||
twit.get('search/tweets', params, function (err, reply, response) {
|
||||
checkReply(err, reply)
|
||||
assert.ok(reply.statuses)
|
||||
checkTweet(reply.statuses[0])
|
||||
|
||||
console.log('\nnumber of fun statuses:', reply.statuses.length)
|
||||
|
||||
checkResponse(response)
|
||||
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('GET `search/tweets`, using `q` array', function (done) {
|
||||
var params = {
|
||||
q: [ 'banana', 'mango', 'peach' ]
|
||||
}
|
||||
|
||||
twit.get('search/tweets', params, function (err, reply, response) {
|
||||
checkReply(err, reply)
|
||||
assert.ok(reply.statuses)
|
||||
checkTweet(reply.statuses[0])
|
||||
|
||||
checkResponse(response)
|
||||
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('GET `search/tweets` with count set to 100', function (done) {
|
||||
var params = {
|
||||
q: 'happy',
|
||||
count: 100
|
||||
}
|
||||
|
||||
twit.get('search/tweets', params, function (err, reply, res) {
|
||||
checkReply(err, reply)
|
||||
console.log('\nnumber of tweets from search:', reply.statuses.length)
|
||||
// twitter won't always send back 100 tweets if we ask for 100,
|
||||
// but make sure it's close to 100
|
||||
assert(reply.statuses.length > 95)
|
||||
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('GET `search/tweets` with geocode', function (done) {
|
||||
var params = {
|
||||
q: 'apple', geocode: [ '37.781157', '-122.398720', '1mi' ]
|
||||
}
|
||||
|
||||
twit.get('search/tweets', params, function (err, reply) {
|
||||
checkReply(err, reply)
|
||||
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('GET `direct_messages`', function (done) {
|
||||
twit.get('direct_messages', function (err, reply, response) {
|
||||
checkResponse(response)
|
||||
checkReply(err, reply)
|
||||
assert.ok(Array.isArray(reply))
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('GET `followers/ids`', function (done) {
|
||||
twit.get('followers/ids', function (err, reply, response) {
|
||||
checkReply(err, reply)
|
||||
assert.ok(Array.isArray(reply.ids))
|
||||
|
||||
checkResponse(response)
|
||||
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('GET `followers/ids` of screen_name tolga_tezel', function (done) {
|
||||
twit.get('followers/ids', { screen_name: 'tolga_tezel' }, function (err, reply, response) {
|
||||
checkReply(err, reply)
|
||||
assert.ok(Array.isArray(reply.ids))
|
||||
|
||||
checkResponse(response)
|
||||
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('POST `statuses/retweet`', function (done) {
|
||||
// search for a tweet to retweet
|
||||
twit.get('search/tweets', { q: 'apple' }, function (err, reply, response) {
|
||||
checkReply(err, reply)
|
||||
assert.ok(reply.statuses)
|
||||
|
||||
var tweet = reply.statuses[0]
|
||||
checkTweet(tweet)
|
||||
|
||||
var tweetId = tweet.id_str
|
||||
assert(tweetId)
|
||||
|
||||
twit.post('statuses/retweet/'+tweetId, function (err, reply, response) {
|
||||
checkReply(err, reply)
|
||||
|
||||
var retweetId = reply.id_str
|
||||
assert(retweetId)
|
||||
|
||||
twit.post('statuses/destroy/'+retweetId, function (err, reply, response) {
|
||||
checkReply(err, reply)
|
||||
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// 1.1.8 usage
|
||||
it('POST `statuses/retweet/:id` without `id` in params returns error', function (done) {
|
||||
twit.post('statuses/retweet/:id', function (err, reply, response) {
|
||||
assert(err)
|
||||
assert.equal(err.message, 'Twit: Params object is missing a required parameter for this request: `id`')
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
// 1.1.8 usage
|
||||
it('POST `statuses/retweet/:id`', function (done) {
|
||||
// search for a tweet to retweet
|
||||
twit.get('search/tweets', { q: 'banana' }, function (err, reply, response) {
|
||||
checkReply(err, reply)
|
||||
assert.ok(reply.statuses)
|
||||
|
||||
var tweet = reply.statuses[0]
|
||||
checkTweet(tweet)
|
||||
|
||||
var tweetId = tweet.id_str
|
||||
assert(tweetId)
|
||||
|
||||
twit.post('statuses/retweet/:id', { id: tweetId }, function (err, reply) {
|
||||
checkReply(err, reply)
|
||||
|
||||
var retweetId = reply.id_str
|
||||
assert(retweetId)
|
||||
|
||||
twit.post('statuses/destroy/:id', { id: retweetId }, function (err, reply, response) {
|
||||
checkReply(err, reply)
|
||||
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// 1.1.8 usage
|
||||
// skip for now since this API call is having problems on Twitter's side (404)
|
||||
it.skip('GET `users/suggestions/:slug`', function (done) {
|
||||
twit.get('users/suggestions/:slug', { slug: 'funny' }, function (err, reply, res) {
|
||||
checkReply(err, reply)
|
||||
assert.equal(reply.slug, 'funny')
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
// 1.1.8 usage
|
||||
// skip for now since this API call is having problems on Twitter's side (404)
|
||||
it.skip('GET `users/suggestions/:slug/members`', function (done) {
|
||||
twit.get('users/suggestions/:slug/members', { slug: 'funny' }, function (err, reply, res) {
|
||||
checkReply(err, reply)
|
||||
|
||||
assert(reply[0].id_str)
|
||||
assert(reply[0].screen_name)
|
||||
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
// 1.1.8 usage
|
||||
it('GET `geo/id/:place_id`', function (done) {
|
||||
var placeId = 'df51dec6f4ee2b2c'
|
||||
|
||||
twit.get('geo/id/:place_id', { place_id: placeId }, function (err, reply, res) {
|
||||
checkReply(err, reply)
|
||||
|
||||
assert(reply.country)
|
||||
assert(reply.bounding_box)
|
||||
assert.equal(reply.id, placeId)
|
||||
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('POST `direct_messages/new`', function (done) {
|
||||
var dmId
|
||||
|
||||
async.series({
|
||||
postDm: function (next) {
|
||||
|
||||
var dmParams = {
|
||||
screen_name: 'tolga_tezel',
|
||||
text: 'hey this is a direct message from twit! :) ' + helpers.generateRandomString(15)
|
||||
}
|
||||
// post a direct message from the sender's account
|
||||
twit.post('direct_messages/new', dmParams, function (err, reply) {
|
||||
assert(!err, err)
|
||||
assert(reply)
|
||||
|
||||
dmId = reply.id_str
|
||||
|
||||
exports.checkDm(reply)
|
||||
|
||||
assert.equal(reply.text, dmParams.text)
|
||||
assert(dmId)
|
||||
|
||||
return next()
|
||||
})
|
||||
},
|
||||
deleteDm: function (next) {
|
||||
twit.post('direct_messages/destroy', { id: dmId }, function (err, reply) {
|
||||
assert(!err, err)
|
||||
exports.checkDm(reply)
|
||||
assert.equal(reply.id, dmId)
|
||||
|
||||
return next()
|
||||
})
|
||||
}
|
||||
}, done);
|
||||
})
|
||||
|
||||
describe('Media Upload', function () {
|
||||
var twit = null
|
||||
|
||||
before(function () {
|
||||
twit = new Twit(config1)
|
||||
})
|
||||
|
||||
it('POST media/upload with png', function (done) {
|
||||
var b64content = fs.readFileSync(__dirname + '/img/cutebird.png', { encoding: 'base64' })
|
||||
|
||||
twit.post('media/upload', { media_data: b64content }, function (err, data, response) {
|
||||
assert.equal(response.statusCode, 200)
|
||||
assert(!err, err)
|
||||
exports.checkMediaUpload(data)
|
||||
assert(data.image.image_type == 'image/png' || data.image.image_type == 'image\/png')
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('POST media/upload with JPG', function (done) {
|
||||
var b64content = fs.readFileSync(__dirname + '/img/bigbird.jpg', { encoding: 'base64' })
|
||||
|
||||
twit.post('media/upload', { media_data: b64content }, function (err, data, response) {
|
||||
assert(!err, err)
|
||||
exports.checkMediaUpload(data)
|
||||
assert.equal(data.image.image_type, 'image/jpeg')
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('POST media/upload with static GIF', function (done) {
|
||||
var b64content = fs.readFileSync(__dirname + '/img/twitterbird.gif', { encoding: 'base64' })
|
||||
|
||||
twit.post('media/upload', { media_data: b64content }, function (err, data, response) {
|
||||
assert(!err, err)
|
||||
exports.checkMediaUpload(data)
|
||||
assert.equal(data.image.image_type, 'image/gif')
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('POST media/upload with animated GIF using `media_data` parameter', function (done) {
|
||||
var b64content = fs.readFileSync(__dirname + '/img/snoopy-animated.gif', { encoding: 'base64' })
|
||||
|
||||
twit.post('media/upload', { media_data: b64content }, function (err, data, response) {
|
||||
assert(!err, err)
|
||||
exports.checkMediaUpload(data)
|
||||
var expected_image_types = ['image/gif', 'image/animatedgif']
|
||||
var image_type = data.image.image_type
|
||||
assert.ok(expected_image_types.indexOf(image_type) !== -1, 'got unexpected image type:' + image_type)
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('POST media/upload with animated GIF, then POST a tweet referencing the media', function (done) {
|
||||
var b64content = fs.readFileSync(__dirname + '/img/snoopy-animated.gif', { encoding: 'base64' });
|
||||
|
||||
twit.post('media/upload', { media_data: b64content }, function (err, data, response) {
|
||||
assert(!err, err)
|
||||
exports.checkMediaUpload(data)
|
||||
var expected_image_types = ['image/gif', 'image/animatedgif']
|
||||
var image_type = data.image.image_type
|
||||
assert.ok(expected_image_types.indexOf(image_type) !== -1, 'got unexpected image type:' + image_type)
|
||||
|
||||
var mediaIdStr = data.media_id_string
|
||||
assert(mediaIdStr)
|
||||
var params = { status: '#nofilter', media_ids: [mediaIdStr] }
|
||||
twit.post('statuses/update', params, function (err, data, response) {
|
||||
assert(!err, err)
|
||||
var tweetIdStr = data.id_str
|
||||
assert(tweetIdStr)
|
||||
|
||||
exports.req_with_retries(twit, 3, 'post', 'statuses/destroy/:id', { id: tweetIdStr }, [404], function (err, data, response) {
|
||||
checkReply(err, data)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('POST media/upload with animated GIF using `media` parameter', function (done) {
|
||||
var b64Content = fs.readFileSync(__dirname + '/img/snoopy-animated.gif', { encoding: 'base64' });
|
||||
|
||||
twit.post('media/upload', { media: b64Content }, function (err, data, response) {
|
||||
assert(!err, err)
|
||||
exports.checkMediaUpload(data)
|
||||
var expected_image_types = ['image/gif', 'image/animatedgif']
|
||||
var image_type = data.image.image_type
|
||||
assert.ok(expected_image_types.indexOf(image_type) !== -1, 'got unexpected image type:' + image_type)
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('POST media/upload with JPG, then POST media/metadata/create with alt text', function (done) {
|
||||
var b64content = fs.readFileSync(__dirname + '/img/bigbird.jpg', { encoding: 'base64' })
|
||||
|
||||
twit.post('media/upload', { media_data: b64content }, function (err, data, response) {
|
||||
assert(!err, err)
|
||||
exports.checkMediaUpload(data)
|
||||
assert.equal(data.image.image_type, 'image/jpeg')
|
||||
|
||||
var mediaIdStr = data.media_id_string
|
||||
assert(mediaIdStr)
|
||||
var altText = 'a very small Big Bird'
|
||||
var params = { media_id: mediaIdStr, alt_text: { text: altText } }
|
||||
twit.post('media/metadata/create', params, function (err, data, response) {
|
||||
assert(!err, err)
|
||||
// data is empty on media/metadata/create success; nothing more to assert
|
||||
done();
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('POST account/update_profile_image', function (done) {
|
||||
var b64content = fs.readFileSync(__dirname + '/img/snoopy-animated.gif', { encoding: 'base64' })
|
||||
|
||||
twit.post('account/update_profile_image', { image: b64content }, function (err, data, response) {
|
||||
assert(!err, err);
|
||||
exports.checkReply(err, data);
|
||||
exports.checkUser(data);
|
||||
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('POST friendships/create', function (done) {
|
||||
var params = { screen_name: 'tolga_tezel', follow: false };
|
||||
twit.post('friendships/create', params, function (err, data, resp) {
|
||||
assert(!err, err);
|
||||
exports.checkReply(err, data);
|
||||
exports.checkUser(data);
|
||||
done();
|
||||
});
|
||||
})
|
||||
|
||||
describe('Favorites', function () {
|
||||
it('POST favorites/create and POST favorites/destroy work', function (done) {
|
||||
twit.post('favorites/create', { id: '583531943624597504' }, function (err, data, resp) {
|
||||
assert(!err, err);
|
||||
exports.checkReply(err, data);
|
||||
var tweetIdStr = data.id_str;
|
||||
assert(tweetIdStr);
|
||||
|
||||
twit.post('favorites/destroy', { id: tweetIdStr }, function (err, data, resp) {
|
||||
assert(!err, err);
|
||||
exports.checkReply(err, data);
|
||||
assert(data.id_str);
|
||||
assert(data.text);
|
||||
|
||||
done();
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('error handling', function () {
|
||||
describe('handling errors from the twitter api', function () {
|
||||
it('should callback with an Error object with all the info and a response object', function (done) {
|
||||
var twit = new Twit({
|
||||
consumer_key: 'a',
|
||||
consumer_secret: 'b',
|
||||
access_token: 'c',
|
||||
access_token_secret: 'd'
|
||||
})
|
||||
twit.get('account/verify_credentials', function (err, reply, res) {
|
||||
assert(err instanceof Error)
|
||||
assert(err.statusCode === 401)
|
||||
assert(err.code > 0)
|
||||
assert(err.message.match(/token/))
|
||||
assert(err.twitterReply)
|
||||
assert(err.allErrors)
|
||||
assert(res)
|
||||
assert(res.headers)
|
||||
assert.equal(res.statusCode, 401)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
describe('handling other errors', function () {
|
||||
it('should just forward errors raised by underlying request lib', function (done) {
|
||||
var twit = new Twit(config1);
|
||||
var fakeError = new Error('derp')
|
||||
|
||||
var FakeRequest = function () {
|
||||
EventEmitter.call(this)
|
||||
}
|
||||
util.inherits(FakeRequest, EventEmitter)
|
||||
|
||||
var stubGet = function () {
|
||||
var fakeRequest = new FakeRequest()
|
||||
process.nextTick(function () {
|
||||
fakeRequest.emit('error', fakeError)
|
||||
})
|
||||
return fakeRequest
|
||||
}
|
||||
|
||||
var request = require('request')
|
||||
var stubGet = sinon.stub(request, 'get', stubGet)
|
||||
|
||||
twit.get('account/verify_credentials', function (err, reply, res) {
|
||||
assert(err === fakeError)
|
||||
|
||||
// restore request.get
|
||||
stubGet.restore()
|
||||
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('Request timeout', function () {
|
||||
it('set to 1ms should return with a timeout error', function (done) {
|
||||
config1.timeout_ms = 1;
|
||||
var twit = new Twit(config1);
|
||||
twit.get('account/verify_credentials', function (err, reply, res) {
|
||||
assert(err)
|
||||
assert.equal(err.message, 'ETIMEDOUT')
|
||||
delete config1.timeout_ms
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
describe('Twit agent_options config', function () {
|
||||
it('config.trusted_cert_fingerprints works against cert fingerprint for api.twitter.com:443', function (done) {
|
||||
config1.trusted_cert_fingerprints = [
|
||||
'66:EA:47:62:D9:B1:4F:1A:AE:89:5F:68:BA:6B:8E:BB:F8:1D:BF:8E'
|
||||
];
|
||||
var t = new Twit(config1);
|
||||
|
||||
t.get('account/verify_credentials', function (err, data, resp) {
|
||||
assert(!err, err)
|
||||
assert(data)
|
||||
assert(data.id_str)
|
||||
assert(data.name)
|
||||
assert(data.screen_name)
|
||||
|
||||
delete config1.trusted_cert_fingerprints
|
||||
done();
|
||||
})
|
||||
})
|
||||
|
||||
it('config.trusted_cert_fingerprints responds with Error when fingerprint mismatch occurs', function (done) {
|
||||
config1.trusted_cert_fingerprints = [
|
||||
'AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA'
|
||||
];
|
||||
var t = new Twit(config1);
|
||||
|
||||
t.get('account/verify_credentials', function (err, data, resp) {
|
||||
assert(err)
|
||||
assert(err.toString().indexOf('Trusted fingerprints are: ' + config1.trusted_cert_fingerprints[0]) !== -1)
|
||||
|
||||
delete config1.trusted_cert_fingerprints
|
||||
done();
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('Local time offset compensation', function () {
|
||||
it('Compensates for local time being behind', function (done) {
|
||||
var t1 = Date.now();
|
||||
var t = new Twit(config2);
|
||||
|
||||
var stubNow = function () {
|
||||
return 0;
|
||||
}
|
||||
var stubDateNow = sinon.stub(Date, 'now', stubNow);
|
||||
|
||||
t.get('account/verify_credentials', function (err, data, resp) {
|
||||
assert(err);
|
||||
|
||||
t.get('account/verify_credentials', function (err, data, resp) {
|
||||
assert(!err, err);
|
||||
exports.checkReply(err, data);
|
||||
exports.checkUser(data);
|
||||
assert(t._twitter_time_minus_local_time_ms > 0)
|
||||
|
||||
stubDateNow.restore();
|
||||
|
||||
done();
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
/**
|
||||
* Basic validation to verify we have no error and reply is an object
|
||||
*
|
||||
* @param {error} err error object (or null)
|
||||
* @param {object} reply reply object received from twitter
|
||||
*/
|
||||
var checkReply = exports.checkReply = function (err, reply) {
|
||||
assert.equal(err, null, 'reply err:'+util.inspect(err, true, 10, true))
|
||||
assert.equal(typeof reply, 'object')
|
||||
}
|
||||
|
||||
/**
|
||||
* check the http response object and its headers
|
||||
* @param {object} response http response object
|
||||
*/
|
||||
var checkResponse = exports.checkResponse = function (response) {
|
||||
assert(response)
|
||||
assert(response.headers)
|
||||
assert.equal(response.statusCode, 200)
|
||||
}
|
||||
|
||||
/**
|
||||
* validate that @tweet is a tweet object
|
||||
*
|
||||
* @param {object} tweet `tweet` object received from twitter
|
||||
*/
|
||||
var checkTweet = exports.checkTweet = function (tweet) {
|
||||
assert.ok(tweet)
|
||||
assert.equal('string', typeof tweet.id_str, 'id_str wasnt string:'+tweet.id_str)
|
||||
assert.equal('string', typeof tweet.text)
|
||||
|
||||
assert.ok(tweet.user)
|
||||
assert.equal('string', typeof tweet.user.id_str)
|
||||
assert.equal('string', typeof tweet.user.screen_name)
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that @dm is a direct message object
|
||||
*
|
||||
* @param {object} dm `direct message` object received from twitter
|
||||
*/
|
||||
exports.checkDm = function checkDm (dm) {
|
||||
assert.ok(dm)
|
||||
assert.equal('string', typeof dm.id_str)
|
||||
assert.equal('string', typeof dm.text)
|
||||
|
||||
var recipient = dm.recipient
|
||||
|
||||
assert.ok(recipient)
|
||||
assert.equal('string', typeof recipient.id_str)
|
||||
assert.equal('string', typeof recipient.screen_name)
|
||||
|
||||
var sender = dm.sender
|
||||
|
||||
assert.ok(sender)
|
||||
assert.equal('string', typeof sender.id_str)
|
||||
assert.equal('string', typeof sender.screen_name)
|
||||
|
||||
assert.equal('string', typeof dm.text)
|
||||
}
|
||||
|
||||
exports.checkMediaUpload = function checkMediaUpload (data) {
|
||||
assert.ok(data)
|
||||
assert.ok(data.image)
|
||||
assert.ok(data.image.w)
|
||||
assert.ok(data.image.h)
|
||||
assert.ok(data.media_id)
|
||||
assert.equal('string', typeof data.media_id_string)
|
||||
assert.ok(data.size)
|
||||
}
|
||||
|
||||
exports.checkUser = function checkUser (data) {
|
||||
assert.ok(data)
|
||||
assert.ok(data.id_str)
|
||||
assert.ok(data.name)
|
||||
assert.ok(data.screen_name)
|
||||
}
|
||||
|
||||
exports.assertTweetHasText = function (tweet, text) {
|
||||
assert(tweet.text.toLowerCase().indexOf(text) !== -1, 'expected to find '+text+' in text: '+tweet.text);
|
||||
}
|
||||
|
||||
exports.req_with_retries = function (twit_instance, num_tries, verb, path, params, status_codes_to_retry, cb) {
|
||||
twit_instance[verb](path, params, function (err, data, response) {
|
||||
if (!num_tries || (status_codes_to_retry.indexOf(response.statusCode) === -1)) {
|
||||
return cb(err, data, response)
|
||||
}
|
||||
|
||||
exports.req_with_retries(twit_instance, num_tries - 1, verb, path, params, status_codes_to_retry, cb)
|
||||
})
|
||||
}
|
||||
55
node_modules/twit/tests/rest_app_only_auth.js
generated
vendored
Normal file
55
node_modules/twit/tests/rest_app_only_auth.js
generated
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
var assert = require('assert')
|
||||
|
||||
var config1 = require('../config1');
|
||||
var Twit = require('../lib/twitter');
|
||||
var checkReply = require('./rest').checkReply;
|
||||
var checkResponse = require('./rest').checkResponse;
|
||||
var checkTweet = require('./rest').checkTweet;
|
||||
|
||||
describe('REST API using app-only auth', function () {
|
||||
var twit = null
|
||||
before(function () {
|
||||
var config = {
|
||||
consumer_key: config1.consumer_key,
|
||||
consumer_secret: config1.consumer_secret,
|
||||
app_only_auth: true,
|
||||
}
|
||||
twit = new Twit(config)
|
||||
})
|
||||
|
||||
it('GET `application/rate_limit_status`', function (done) {
|
||||
twit.get('application/rate_limit_status', function (err, body, response) {
|
||||
checkReply(err, body)
|
||||
checkResponse(response)
|
||||
assert(body.rate_limit_context)
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('GET `application/rate_limit_status with specific resource`', function (done) {
|
||||
var params = { resources: [ 'users', 'search' ]}
|
||||
twit.get('application/rate_limit_status', params, function (err, body, response) {
|
||||
checkReply(err, body)
|
||||
checkResponse(response)
|
||||
assert(body.rate_limit_context)
|
||||
assert(body.resources.users)
|
||||
assert(body.resources.search)
|
||||
assert.equal(Object.keys(body.resources).length, 2)
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('GET `search/tweets` { q: "a", since_id: 12345 }', function (done) {
|
||||
var params = { q: 'a', since_id: 12345 }
|
||||
twit.get('search/tweets', params, function (err, reply, response) {
|
||||
checkReply(err, reply)
|
||||
assert.ok(reply.statuses)
|
||||
checkTweet(reply.statuses[0])
|
||||
|
||||
checkResponse(response)
|
||||
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
90
node_modules/twit/tests/rest_chunked_upload.js
generated
vendored
Normal file
90
node_modules/twit/tests/rest_chunked_upload.js
generated
vendored
Normal file
@@ -0,0 +1,90 @@
|
||||
var assert = require('assert');
|
||||
var fs = require('fs');
|
||||
var mime = require('mime');
|
||||
var path = require('path');
|
||||
|
||||
var config = require('../config1');
|
||||
var Twit = require('../lib/twitter');
|
||||
|
||||
describe('twit.postMediaChunked', function () {
|
||||
it('Posting media via twit.postMediaChunked works with .mp4', function (done) {
|
||||
var twit = new Twit(config);
|
||||
var mediaFilePath = path.join(__dirname, './video/station.mp4');
|
||||
twit.postMediaChunked({ file_path: mediaFilePath }, function (err, bodyObj, resp) {
|
||||
exports.checkUploadMedia(err, bodyObj, resp)
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('POST media/upload via manual commands works with .mp4', function (done) {
|
||||
var mediaFilePath = path.join(__dirname, './video/station.mp4');
|
||||
var mediaType = mime.lookup(mediaFilePath);
|
||||
var mediaFileSizeBytes = fs.statSync(mediaFilePath).size;
|
||||
|
||||
var twit = new Twit(config);
|
||||
twit.post('media/upload', {
|
||||
'command': 'INIT',
|
||||
'media_type': mediaType,
|
||||
'total_bytes': mediaFileSizeBytes
|
||||
}, function (err, bodyObj, resp) {
|
||||
assert(!err, err);
|
||||
var mediaIdStr = bodyObj.media_id_string;
|
||||
|
||||
var isStreamingFile = true;
|
||||
var isUploading = false;
|
||||
var segmentIndex = 0;
|
||||
var fStream = fs.createReadStream(mediaFilePath, { highWaterMark: 5 * 1024 * 1024 });
|
||||
|
||||
var _finalizeMedia = function (mediaIdStr, cb) {
|
||||
twit.post('media/upload', {
|
||||
'command': 'FINALIZE',
|
||||
'media_id': mediaIdStr
|
||||
}, cb)
|
||||
}
|
||||
|
||||
var _checkFinalizeResp = function (err, bodyObj, resp) {
|
||||
exports.checkUploadMedia(err, bodyObj, resp)
|
||||
done();
|
||||
}
|
||||
|
||||
fStream.on('data', function (buff) {
|
||||
fStream.pause();
|
||||
isStreamingFile = false;
|
||||
isUploading = true;
|
||||
|
||||
twit.post('media/upload', {
|
||||
'command': 'APPEND',
|
||||
'media_id': mediaIdStr,
|
||||
'segment_index': segmentIndex,
|
||||
'media': buff.toString('base64'),
|
||||
}, function (err, bodyObj, resp) {
|
||||
assert(!err, err);
|
||||
isUploading = false;
|
||||
|
||||
if (!isStreamingFile) {
|
||||
_finalizeMedia(mediaIdStr, _checkFinalizeResp);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
fStream.on('end', function () {
|
||||
isStreamingFile = false;
|
||||
|
||||
if (!isUploading) {
|
||||
_finalizeMedia(mediaIdStr, _checkFinalizeResp);
|
||||
}
|
||||
});
|
||||
});
|
||||
})
|
||||
})
|
||||
|
||||
exports.checkUploadMedia = function (err, bodyObj, resp) {
|
||||
assert(!err, err)
|
||||
|
||||
assert(bodyObj)
|
||||
assert(bodyObj.media_id)
|
||||
assert(bodyObj.media_id_string)
|
||||
assert(bodyObj.size)
|
||||
assert(bodyObj.video)
|
||||
assert.equal(bodyObj.video.video_type, 'video/mp4')
|
||||
}
|
||||
646
node_modules/twit/tests/streaming.js
generated
vendored
Normal file
646
node_modules/twit/tests/streaming.js
generated
vendored
Normal file
@@ -0,0 +1,646 @@
|
||||
var assert = require('assert')
|
||||
, http = require('http')
|
||||
, EventEmitter = require('events').EventEmitter
|
||||
, rewire = require('rewire')
|
||||
, sinon = require('sinon')
|
||||
, Twit = require('../lib/twitter')
|
||||
, config1 = require('../config1')
|
||||
, config2 = require('../config2')
|
||||
, colors = require('colors')
|
||||
, helpers = require('./helpers')
|
||||
, util = require('util')
|
||||
, zlib = require('zlib')
|
||||
, async = require('async')
|
||||
, restTest = require('./rest');
|
||||
|
||||
/**
|
||||
* Stop the stream and check the tweet we got back.
|
||||
* Call @done on completion.
|
||||
*
|
||||
* @param {object} stream object returned by twit.stream()
|
||||
* @param {Function} done completion callback
|
||||
*/
|
||||
exports.checkStream = function (stream, done) {
|
||||
stream.on('connected', function () {
|
||||
console.log('\nconnected'.grey)
|
||||
});
|
||||
|
||||
stream.once('tweet', function (tweet) {
|
||||
stream.stop()
|
||||
assert.ok(tweet)
|
||||
assert.equal('string', typeof tweet.text)
|
||||
assert.equal('string', typeof tweet.id_str)
|
||||
|
||||
console.log(('\ntweet: '+tweet.text).grey)
|
||||
|
||||
done()
|
||||
});
|
||||
|
||||
stream.on('reconnecting', function (req, res, connectInterval) {
|
||||
console.log('Got disconnected. Scheduling reconnect! statusCode:', res.statusCode, 'connectInterval', connectInterval)
|
||||
});
|
||||
|
||||
stream.on('error', function (err) {
|
||||
console.log('Stream emitted an error', err)
|
||||
return done(err)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the stream state is correctly set for a stopped stream.
|
||||
*
|
||||
* @param {object} stream object returned by twit.stream()
|
||||
*/
|
||||
exports.checkStreamStopState = function (stream) {
|
||||
assert.strictEqual(stream._connectInterval, 0)
|
||||
assert.strictEqual(stream._usedFirstReconnect, false)
|
||||
assert.strictEqual(stream._scheduledReconnect, undefined)
|
||||
assert.strictEqual(stream._stallAbortTimeout, undefined)
|
||||
}
|
||||
|
||||
describe('Streaming API', function () {
|
||||
|
||||
it('statuses/sample', function (done) {
|
||||
var twit = new Twit(config1);
|
||||
var stream = twit.stream('statuses/sample')
|
||||
|
||||
exports.checkStream(stream, done)
|
||||
})
|
||||
|
||||
it('statuses/filter using `track`', function (done) {
|
||||
this.timeout(120000)
|
||||
var twit = new Twit(config2);
|
||||
var stream = twit.stream('statuses/filter', { track: 'fun' })
|
||||
|
||||
exports.checkStream(stream, done)
|
||||
})
|
||||
|
||||
it('statuses/filter using `locations` string', function (done) {
|
||||
var twit = new Twit(config1);
|
||||
var world = '-180,-90,180,90';
|
||||
var stream = twit.stream('statuses/filter', { locations: world })
|
||||
|
||||
exports.checkStream(stream, done)
|
||||
})
|
||||
|
||||
it('statuses/filter using `locations` array for San Francisco and New York', function (done) {
|
||||
var twit = new Twit(config2);
|
||||
var params = {
|
||||
locations: [ '-122.75', '36.8', '121.75', '37.8', '-74', '40', '73', '41' ]
|
||||
}
|
||||
|
||||
var stream = twit.stream('statuses/filter', params)
|
||||
|
||||
exports.checkStream(stream, done)
|
||||
})
|
||||
|
||||
it('statuses/filter using `track` array', function (done) {
|
||||
var twit = new Twit(config1);
|
||||
var params = {
|
||||
track: [ 'twitter', ':)', 'fun' ]
|
||||
}
|
||||
|
||||
var stream = twit.stream('statuses/filter', params)
|
||||
|
||||
exports.checkStream(stream, done)
|
||||
})
|
||||
|
||||
it('statuses/filter using `track` and `language`', function (done) {
|
||||
var twit = new Twit(config1);
|
||||
var params = {
|
||||
track: [ 'twitter', '#apple', 'google', 'twitter', 'facebook', 'happy', 'party', ':)' ],
|
||||
language: 'en'
|
||||
}
|
||||
|
||||
var stream = twit.stream('statuses/filter', params)
|
||||
|
||||
exports.checkStream(stream, done)
|
||||
})
|
||||
|
||||
it('stopping & restarting the stream works', function (done) {
|
||||
var twit = new Twit(config2);
|
||||
var stream = twit.stream('statuses/sample')
|
||||
|
||||
//stop the stream after 2 seconds
|
||||
setTimeout(function () {
|
||||
stream.stop()
|
||||
|
||||
exports.checkStreamStopState(stream)
|
||||
|
||||
console.log('\nstopped stream')
|
||||
}, 2000)
|
||||
|
||||
//after 3 seconds, start the stream, and stop after 'connect'
|
||||
setTimeout(function () {
|
||||
stream.once('connected', function (req) {
|
||||
console.log('\nrestarted stream')
|
||||
stream.stop()
|
||||
|
||||
exports.checkStreamStopState(stream)
|
||||
|
||||
console.log('\nstopped stream')
|
||||
done()
|
||||
})
|
||||
|
||||
//restart the stream
|
||||
stream.start()
|
||||
}, 3000)
|
||||
})
|
||||
|
||||
it('stopping & restarting stream emits to previously assigned callbacks', function (done) {
|
||||
var twit = new Twit(config1);
|
||||
var stream = twit.stream('statuses/sample')
|
||||
|
||||
var started = false
|
||||
var numTweets = 0
|
||||
stream.on('tweet', function (tweet) {
|
||||
process.stdout.write('.')
|
||||
if (!started) {
|
||||
started = true
|
||||
numTweets++
|
||||
console.log('received tweet', numTweets)
|
||||
|
||||
console.log('stopping stream')
|
||||
stream.stop()
|
||||
|
||||
exports.checkStreamStopState(stream)
|
||||
|
||||
// we've successfully received a new tweet after restarting, test successful
|
||||
if (numTweets === 2) {
|
||||
done()
|
||||
} else {
|
||||
started = false
|
||||
console.log('restarting stream')
|
||||
|
||||
setTimeout(function () {
|
||||
stream.start()
|
||||
}, 1000)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
stream.on('limit', function (limitMsg) {
|
||||
console.log('limit', limitMsg)
|
||||
})
|
||||
|
||||
stream.on('disconnect', function (disconnMsg) {
|
||||
console.log('disconnect', disconnMsg)
|
||||
})
|
||||
|
||||
stream.on('reconnect', function (req, res, ival) {
|
||||
console.log('reconnect. statusCode:', res.statusCode, 'interval:', ival)
|
||||
})
|
||||
|
||||
stream.on('connect', function (req) {
|
||||
console.log('connect')
|
||||
})
|
||||
|
||||
})
|
||||
})
|
||||
|
||||
describe('streaming API direct message events', function () {
|
||||
var senderScreenName;
|
||||
var receiverScreenName;
|
||||
var twitSender;
|
||||
var twitReceiver;
|
||||
|
||||
// before we send direct messages the user receiving the DM
|
||||
// has to follow the sender. Make this so.
|
||||
before(function (done) {
|
||||
twitSender = new Twit(config1);
|
||||
twitReceiver = new Twit(config2);
|
||||
|
||||
// get sender/receiver names in parallel, then make the receiver follow the sender
|
||||
async.parallel({
|
||||
// get sender screen name and set it for tests to use
|
||||
getSenderScreenName: function (parNext) {
|
||||
console.log('getting sender user screen_name')
|
||||
|
||||
twitSender.get('account/verify_credentials', { twit_options: { retry: true } }, function (err, reply) {
|
||||
assert(!err, err)
|
||||
|
||||
assert(reply)
|
||||
assert(reply.screen_name)
|
||||
|
||||
senderScreenName = reply.screen_name
|
||||
|
||||
return parNext()
|
||||
})
|
||||
},
|
||||
// get receiver screen name and set it for tests to use
|
||||
getReceiverScreenName: function (parNext) {
|
||||
console.log('getting receiver user screen_name')
|
||||
twitReceiver.get('account/verify_credentials', { twit_options: { retry: true } }, function (err, reply) {
|
||||
assert(!err, err)
|
||||
|
||||
assert(reply)
|
||||
assert(reply.screen_name)
|
||||
|
||||
receiverScreenName = reply.screen_name
|
||||
|
||||
return parNext()
|
||||
})
|
||||
}
|
||||
}, function (err) {
|
||||
assert(!err, err)
|
||||
|
||||
var followParams = { screen_name: senderScreenName }
|
||||
console.log('making receiver user follow the sender user')
|
||||
// make receiver follow sender
|
||||
twitReceiver.post('friendships/create', followParams, function (err, reply) {
|
||||
assert(!err, err)
|
||||
assert(reply.following)
|
||||
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('user_stream `direct_message` event', function (done) {
|
||||
// User A follows User B
|
||||
// User A connects to their user stream
|
||||
// User B posts a DM to User A
|
||||
// User A receives it in their user stream
|
||||
this.timeout(0);
|
||||
|
||||
// build out DM params
|
||||
function makeDmParams () {
|
||||
return {
|
||||
screen_name: receiverScreenName,
|
||||
text: helpers.generateRandomString(10) + ' direct message streaming event test! :-) ' + helpers.generateRandomString(20),
|
||||
twit_options: {
|
||||
retry: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var dmIdsReceived = []
|
||||
var dmIdsSent = []
|
||||
var sentDmFound = false
|
||||
|
||||
// start listening for user stream events
|
||||
var receiverStream = twitReceiver.stream('user')
|
||||
|
||||
console.log('\nlistening for DMs')
|
||||
// listen for direct_message event and check DM once it's received
|
||||
receiverStream.on('direct_message', function (directMsg) {
|
||||
if (sentDmFound) {
|
||||
// don't call `done` more than once
|
||||
return
|
||||
}
|
||||
|
||||
console.log('got DM event. id:', directMsg.direct_message.id_str)
|
||||
restTest.checkDm(directMsg.direct_message)
|
||||
dmIdsReceived.push(directMsg.direct_message.id_str)
|
||||
|
||||
// make sure one of the DMs sent was found
|
||||
// (we can send multiple DMs if our stream has to reconnect)
|
||||
sentDmFound = dmIdsSent.some(function (dmId) {
|
||||
return dmId == directMsg.direct_message.id_str
|
||||
})
|
||||
|
||||
if (!sentDmFound) {
|
||||
console.log('this DM doesnt match our test DMs - still waiting for a matching one.')
|
||||
console.log('dmIdsSent', dmIdsSent)
|
||||
return
|
||||
}
|
||||
|
||||
receiverStream.stop()
|
||||
return done()
|
||||
})
|
||||
|
||||
var lastTimeSent = 0
|
||||
var msToWait = 0
|
||||
var postDmInterval = null
|
||||
|
||||
receiverStream.on('connected', function () {
|
||||
var dmParams = makeDmParams()
|
||||
|
||||
console.log('sending a new DM:', dmParams.text)
|
||||
twitSender.post('direct_messages/new', dmParams, function (err, reply) {
|
||||
assert(!err, err)
|
||||
assert(reply)
|
||||
restTest.checkDm(reply)
|
||||
assert(reply.id_str)
|
||||
// we will check this dm against the reply recieved in the message event
|
||||
dmIdsSent.push(reply.id_str)
|
||||
|
||||
console.log('successfully posted DM:', reply.text, reply.id_str)
|
||||
if (dmIdsReceived.indexOf(reply.id_str) !== -1) {
|
||||
// our response to the DM posting lost the race against the direct_message
|
||||
// listener (we already got the event). So we can finish the test.
|
||||
done()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
after(function (done) {
|
||||
console.log('cleaning up DMs:', dmIdsSent)
|
||||
// delete the DMs we posted
|
||||
var deleteDms = dmIdsSent.map(function (dmId) {
|
||||
return function (next) {
|
||||
assert.equal(typeof dmId, 'string')
|
||||
console.log('\ndeleting DM', dmId)
|
||||
var params = { id: dmId, twit_options: { retry: true } }
|
||||
twitSender.post('direct_messages/destroy', params, function (err, reply) {
|
||||
assert(!err, err)
|
||||
restTest.checkDm(reply)
|
||||
assert.equal(reply.id, dmId)
|
||||
return next()
|
||||
})
|
||||
}
|
||||
})
|
||||
async.parallel(deleteDms, done)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('streaming API friends preamble', function () {
|
||||
it('returns an array of strings if stringify_friend_ids is true', function (done) {
|
||||
var twit = new Twit(config1);
|
||||
var stream = twit.stream('user', { stringify_friend_ids: true });
|
||||
stream.on('friends', function (friendsObj) {
|
||||
assert(friendsObj)
|
||||
assert(friendsObj.friends_str)
|
||||
if (friendsObj.friends_str.length) {
|
||||
assert.equal(typeof friendsObj.friends_str[0], 'string')
|
||||
} else {
|
||||
console.log('\nEmpty friends preamble:', friendsObj, '. Make some friends on Twitter! ^_^')
|
||||
}
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('streaming API bad request', function (done) {
|
||||
it('emits an error for a 401 response', function (done) {
|
||||
var badCredentials = {
|
||||
consumer_key: 'a'
|
||||
, consumer_secret: 'b'
|
||||
, access_token: 'c'
|
||||
, access_token_secret: 'd'
|
||||
}
|
||||
|
||||
var twit = new Twit(badCredentials);
|
||||
|
||||
var stream = twit.stream('statuses/filter', { track : ['foo'] });
|
||||
|
||||
stream.on('parser-error', function (err) {
|
||||
assert.equal(err.statusCode, 401)
|
||||
assert(err.twitterReply)
|
||||
|
||||
return done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('streaming API `messages` event', function (done) {
|
||||
var request = require('request');
|
||||
var originalPost = request.post;
|
||||
var RewiredTwit = rewire('../lib/twitter');
|
||||
var RewiredStreamingApiConnection = rewire('../lib/streaming-api-connection');
|
||||
var revertParser, revertTwit;
|
||||
|
||||
var MockParser = function () {
|
||||
var self = this;
|
||||
EventEmitter.call(self);
|
||||
process.nextTick(function () {
|
||||
self.emit('element', {scrub_geo: 'bar'})
|
||||
self.emit('element', {limit: 'buzz'})
|
||||
});
|
||||
}
|
||||
util.inherits(MockParser, EventEmitter);
|
||||
|
||||
before(function () {
|
||||
revertTwit = RewiredTwit.__set__('StreamingAPIConnection', RewiredStreamingApiConnection);
|
||||
revertParser = RewiredStreamingApiConnection.__set__('Parser', MockParser);
|
||||
|
||||
request.post = function () { return new helpers.FakeRequest() }
|
||||
})
|
||||
|
||||
after(function () {
|
||||
request.post = originalPost;
|
||||
revertTwit();
|
||||
revertParser();
|
||||
})
|
||||
|
||||
it('is returned for 2 different event types', function (done) {
|
||||
var twit = new RewiredTwit(config1);
|
||||
var stream = twit.stream('statuses/sample');
|
||||
var gotScrubGeo = false;
|
||||
var gotLimit = false;
|
||||
var numMessages = 0;
|
||||
|
||||
var maybeDone = function () {
|
||||
if (gotScrubGeo && gotLimit && numMessages == 2) {
|
||||
done()
|
||||
}
|
||||
}
|
||||
|
||||
stream.on('limit', function () {
|
||||
gotLimit = true;
|
||||
maybeDone();
|
||||
});
|
||||
stream.on('scrub_geo', function () {
|
||||
gotScrubGeo = true;
|
||||
maybeDone();
|
||||
})
|
||||
|
||||
stream.on('message', function (msg) {
|
||||
numMessages++;
|
||||
maybeDone();
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('streaming reconnect', function (done) {
|
||||
it('correctly implements connection closing backoff', function (done) {
|
||||
var stubPost = function () {
|
||||
var fakeRequest = new helpers.FakeRequest()
|
||||
process.nextTick(function () {
|
||||
fakeRequest.emit('close')
|
||||
})
|
||||
return fakeRequest
|
||||
}
|
||||
|
||||
var request = require('request')
|
||||
var stubPost = sinon.stub(request, 'post', stubPost)
|
||||
|
||||
var twit = new Twit(config1);
|
||||
var stream = twit.stream('statuses/filter', { track: [ 'fun', 'yolo']});
|
||||
|
||||
var reconnects = [0, 250, 500, 750]
|
||||
var reconnectCount = -1
|
||||
|
||||
var testDone = false
|
||||
|
||||
stream.on('reconnect', function () {
|
||||
if (testDone) {
|
||||
return
|
||||
}
|
||||
reconnectCount += 1
|
||||
var expectedInterval = reconnects[reconnectCount]
|
||||
|
||||
// make sure our connect interval is correct
|
||||
assert.equal(stream._connectInterval, expectedInterval);
|
||||
|
||||
// simulate immediate reconnect by forcing a new connection (`self._connectInterval` parameter unchanged)
|
||||
stream._startPersistentConnection();
|
||||
|
||||
if (reconnectCount === reconnects.length -1) {
|
||||
// restore request.post
|
||||
stubPost.restore()
|
||||
testDone = true
|
||||
return done();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('correctly implements 420 backoff', function (done) {
|
||||
var stubPost = function () {
|
||||
var fakeRequest = new helpers.FakeRequest()
|
||||
process.nextTick(function () {
|
||||
var fakeResponse = new helpers.FakeResponse(420)
|
||||
fakeRequest.emit('response', fakeResponse)
|
||||
fakeRequest.emit('close')
|
||||
})
|
||||
return fakeRequest
|
||||
}
|
||||
|
||||
var request = require('request')
|
||||
var stubPost = sinon.stub(request, 'post', stubPost)
|
||||
|
||||
var twit = new Twit(config1);
|
||||
var stream = twit.stream('statuses/filter', { track: [ 'fun', 'yolo']});
|
||||
|
||||
var reconnects = [60000, 120000, 240000, 480000]
|
||||
var reconnectCount = -1
|
||||
var testComplete = false
|
||||
|
||||
stream.on('reconnect', function (req, res, connectInterval) {
|
||||
if (testComplete) {
|
||||
// prevent race between last connection attempt firing a reconnect and us validating the final
|
||||
// reconnect value in `reconnects`
|
||||
return
|
||||
}
|
||||
|
||||
reconnectCount += 1
|
||||
var expectedInterval = reconnects[reconnectCount]
|
||||
|
||||
// make sure our connect interval is correct
|
||||
assert.equal(stream._connectInterval, connectInterval);
|
||||
assert.equal(stream._connectInterval, expectedInterval);
|
||||
// simulate immediate reconnect by forcing a new connection (`self._connectInterval` parameter unchanged)
|
||||
stream._startPersistentConnection();
|
||||
|
||||
if (reconnectCount === reconnects.length -1) {
|
||||
// restore request.post
|
||||
stubPost.restore()
|
||||
testComplete = true
|
||||
return done();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Streaming API disconnect message', function (done) {
|
||||
it('results in stopping the stream', function (done) {
|
||||
var stubPost = function () {
|
||||
var fakeRequest = new helpers.FakeRequest()
|
||||
process.nextTick(function () {
|
||||
var body = zlib.gzipSync(JSON.stringify({disconnect: true}) + '\r\n')
|
||||
var fakeResponse = new helpers.FakeResponse(200, body)
|
||||
fakeRequest.emit('response', fakeResponse);
|
||||
fakeResponse.emit('close')
|
||||
});
|
||||
return fakeRequest
|
||||
}
|
||||
|
||||
var request = require('request')
|
||||
var origRequest = request.post
|
||||
var stubs = sinon.collection
|
||||
stubs.stub(request, 'post', stubPost)
|
||||
|
||||
var twit = new Twit(config1);
|
||||
var stream = twit.stream('statuses/filter', { track: ['fun']});
|
||||
|
||||
stream.on('disconnect', function (disconnMsg) {
|
||||
stream.stop();
|
||||
// restore stub
|
||||
request.post = origRequest
|
||||
done();
|
||||
})
|
||||
})
|
||||
});
|
||||
|
||||
describe('Streaming API Connection limit exceeded message', function (done) {
|
||||
it('results in an `error` event containing the message', function (done) {
|
||||
var errMsg = 'Exceeded connection limit for user';
|
||||
|
||||
var stubPost = function () {
|
||||
var fakeRequest = new helpers.FakeRequest();
|
||||
process.nextTick(function () {
|
||||
var body = zlib.gzipSync(errMsg + '\r\n');
|
||||
var fakeResponse = new helpers.FakeResponse(200, body);
|
||||
fakeRequest.emit('response', fakeResponse);
|
||||
fakeResponse.emit('close');
|
||||
});
|
||||
return fakeRequest
|
||||
}
|
||||
|
||||
var request = require('request');
|
||||
var origRequest = request.post;
|
||||
var stubs = sinon.collection;
|
||||
stubs.stub(request, 'post', stubPost);
|
||||
|
||||
var twit = new Twit(config1);
|
||||
var stream = twit.stream('statuses/filter');
|
||||
|
||||
stream.on('error', function (err) {
|
||||
assert(err.toString().indexOf(errMsg) !== -1, 'Unexpected error msg:' + errMsg + '.');;
|
||||
stream.stop();
|
||||
// restore stub
|
||||
request.post = origRequest;
|
||||
done();
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('Streaming API connection management', function () {
|
||||
it('.stop() works in all states', function (done) {
|
||||
var stubPost = function () {
|
||||
var fakeRequest = new helpers.FakeRequest();
|
||||
process.nextTick(function () {
|
||||
var body = zlib.gzipSync('Foobar\r\n');
|
||||
var fakeResponse = new helpers.FakeResponse(200, body);
|
||||
fakeRequest.emit('response', fakeResponse);
|
||||
});
|
||||
return fakeRequest
|
||||
}
|
||||
|
||||
var request = require('request');
|
||||
var origRequest = request.post;
|
||||
var stubs = sinon.collection;
|
||||
stubs.stub(request, 'post', stubPost);
|
||||
|
||||
var twit = new Twit(config1);
|
||||
|
||||
var stream = twit.stream('statuses/sample');
|
||||
stream.stop();
|
||||
console.log('\nStopped. Restarting..');
|
||||
stream.start();
|
||||
stream.once('connect', function(request) {
|
||||
console.log('Stream emitted `connect`. Stopping & starting stream..')
|
||||
stream.stop();
|
||||
|
||||
stream.once('connected', function () {
|
||||
console.log('Stream emitted `connected`. Stopping stream.');
|
||||
stream.stop();
|
||||
|
||||
stubs.restore();
|
||||
done();
|
||||
});
|
||||
stream.start();
|
||||
});
|
||||
})
|
||||
})
|
||||
13
node_modules/twit/tests/test_helpers.js
generated
vendored
Normal file
13
node_modules/twit/tests/test_helpers.js
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
var assert = require('assert')
|
||||
var helpers = require('../lib/helpers')
|
||||
|
||||
describe('makeQueryString', function () {
|
||||
it('correctly encodes Objects with String values', function () {
|
||||
assert.equal(helpers.makeQueryString({a: 'Ladies + Gentlemen'}), 'a=Ladies%20%2B%20Gentlemen');
|
||||
assert.equal(helpers.makeQueryString({a: 'An encoded string!'}), 'a=An%20encoded%20string%21');
|
||||
assert.equal(helpers.makeQueryString({a: 'Dogs, Cats & Mice'}), 'a=Dogs%2C%20Cats%20%26%20Mice')
|
||||
assert.equal(helpers.makeQueryString({a: '☃'}), 'a=%E2%98%83')
|
||||
assert.equal(helpers.makeQueryString({a: '#haiku #poetry'}), 'a=%23haiku%20%23poetry')
|
||||
assert.equal(helpers.makeQueryString({a: '"happy hour" :)'}), 'a=%22happy%20hour%22%20%3A%29')
|
||||
})
|
||||
})
|
||||
105
node_modules/twit/tests/twit.js
generated
vendored
Normal file
105
node_modules/twit/tests/twit.js
generated
vendored
Normal file
@@ -0,0 +1,105 @@
|
||||
var assert = require('assert')
|
||||
, Twit = require('../lib/twitter')
|
||||
, config1 = require('../config1')
|
||||
|
||||
describe('twit', function () {
|
||||
describe('instantiation', function () {
|
||||
it('works with var twit = new Twit()', function () {
|
||||
var twit = new Twit({
|
||||
consumer_key: 'a',
|
||||
consumer_secret: 'b',
|
||||
access_token: 'c',
|
||||
access_token_secret: 'd'
|
||||
});
|
||||
assert(twit.config)
|
||||
assert.equal(typeof twit.get, 'function')
|
||||
assert.equal(typeof twit.post, 'function')
|
||||
assert.equal(typeof twit.stream, 'function')
|
||||
})
|
||||
it('works with var twit = Twit()', function () {
|
||||
var twit = Twit({
|
||||
consumer_key: 'a',
|
||||
consumer_secret: 'b',
|
||||
access_token: 'c',
|
||||
access_token_secret: 'd'
|
||||
});
|
||||
assert(twit.config)
|
||||
assert.equal(typeof twit.get, 'function')
|
||||
assert.equal(typeof twit.post, 'function')
|
||||
assert.equal(typeof twit.stream, 'function')
|
||||
})
|
||||
})
|
||||
|
||||
describe('config', function () {
|
||||
it('throws when passing empty config', function (done) {
|
||||
assert.throws(function () {
|
||||
var twit = new Twit({})
|
||||
}, Error)
|
||||
|
||||
done()
|
||||
})
|
||||
|
||||
it('throws when config is missing a required key', function (done) {
|
||||
assert.throws(function () {
|
||||
var twit = new Twit({
|
||||
consumer_key: 'a'
|
||||
, consumer_secret: 'a'
|
||||
, access_token: 'a'
|
||||
})
|
||||
}, Error)
|
||||
|
||||
done()
|
||||
})
|
||||
|
||||
it('throws when config provides all keys but they\'re empty strings', function (done) {
|
||||
assert.throws(function () {
|
||||
var twit = new Twit({
|
||||
consumer_key: ''
|
||||
, consumer_secret: ''
|
||||
, access_token: ''
|
||||
, access_token_secret: ''
|
||||
})
|
||||
}, Error)
|
||||
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
describe('setAuth()', function () {
|
||||
var twit;
|
||||
|
||||
beforeEach(function () {
|
||||
twit = new Twit({
|
||||
consumer_key: 'a',
|
||||
consumer_secret: 'b',
|
||||
access_token: 'c',
|
||||
access_token_secret: 'd'
|
||||
})
|
||||
})
|
||||
|
||||
it('should update the client\'s auth config', function (done) {
|
||||
// partial update
|
||||
twit.setAuth({
|
||||
consumer_key: 'x',
|
||||
consumer_secret: 'y'
|
||||
})
|
||||
|
||||
assert(twit.config.consumer_key === 'x')
|
||||
assert(twit.config.consumer_secret === 'y')
|
||||
|
||||
// full update
|
||||
twit.setAuth(config1)
|
||||
|
||||
assert(twit.config.consumer_key === config1.consumer_key)
|
||||
assert(twit.config.consumer_secret === config1.consumer_secret)
|
||||
assert(twit.config.access_token === config1.access_token)
|
||||
assert(twit.config.access_token_secret === config1.access_token_secret)
|
||||
|
||||
twit.get('account/verify_credentials', { twit_options: { retry: true } }, function (err, reply, response) {
|
||||
assert(!err, err);
|
||||
assert(response.headers['x-rate-limit-limit'])
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
});
|
||||
38
node_modules/twit/tests/user_stream.js
generated
vendored
Normal file
38
node_modules/twit/tests/user_stream.js
generated
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
var assert = require('assert')
|
||||
var Twit = require('../lib/twitter')
|
||||
var config1 = require('../config1')
|
||||
var streaming = require('./streaming')
|
||||
|
||||
//verify `friendsMsg` is a twitter 'friends' message object
|
||||
function checkFriendsMsg (friendsMsg) {
|
||||
var friendIds = friendsMsg.friends
|
||||
|
||||
assert(friendIds)
|
||||
assert(Array.isArray(friendIds))
|
||||
assert(friendIds[0])
|
||||
}
|
||||
|
||||
describe('user events', function () {
|
||||
it('friends', function (done) {
|
||||
var twit = new Twit(config1);
|
||||
var stream = twit.stream('user');
|
||||
|
||||
//make sure we're connected to the right endpoint
|
||||
assert.equal(stream.reqOpts.url, 'https://userstream.twitter.com/1.1/user.json')
|
||||
|
||||
stream.on('friends', function (friendsMsg) {
|
||||
checkFriendsMsg(friendsMsg)
|
||||
|
||||
stream.stop()
|
||||
done()
|
||||
})
|
||||
|
||||
stream.on('connect', function () {
|
||||
console.log('\nuser stream connecting..')
|
||||
})
|
||||
|
||||
stream.on('connected', function () {
|
||||
console.log('user stream connected.')
|
||||
})
|
||||
})
|
||||
})
|
||||
BIN
node_modules/twit/tests/video/station.mp4
generated
vendored
Normal file
BIN
node_modules/twit/tests/video/station.mp4
generated
vendored
Normal file
Binary file not shown.
Reference in New Issue
Block a user