jls-tweetstream 2.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,18 @@
1
+ {
2
+ "info":
3
+ {
4
+ "users":
5
+ [
6
+ {
7
+ "id":119476949,
8
+ "name":"oauth_dancer",
9
+ "dm":false
10
+ }
11
+ ],
12
+ "delimited":"none",
13
+ "include_followings_activity":false,
14
+ "include_user_changes":false,
15
+ "replies":"none",
16
+ "with":"user"
17
+ }
18
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "limit": {
3
+ "track": 1234
4
+ }
5
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "scrub_geo": {
3
+ "user_id": 1234,
4
+ "user_id_str": "1234",
5
+ "up_to_status_id":9876,
6
+ "up_to_status_id_string": "9876"
7
+ }
8
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "warning":{
3
+ "code":"FALLING_BEHIND",
4
+ "message":"Your connection is falling behind and messages are being queued for delivery to you. Your queue is now over 60% full. You will be disconnected when the queue is full.",
5
+ "percent_full": 60
6
+ }
7
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "status_withheld":{
3
+ "id":1234567890,
4
+ "user_id":123456,
5
+ "withheld_in_countries":["de", "ar"]
6
+ }
7
+ }
@@ -0,0 +1 @@
1
+ {"favorited":false,"text":"listening to Where U Headed by Universal Playaz. http://iLike.com/s/9zpOZ #musicmonday something for the ladies","in_reply_to_user_id":null,"in_reply_to_screen_name":null,"source":"<a href=\"http://www.iLike.com\" rel=\"nofollow\">iLike</a>","truncated":false,"created_at":"Tue Sep 22 01:29:13 +0000 2009","user":{"statuses_count":378,"favourites_count":1,"profile_text_color":"666666","location":"Atlanta, Ga","profile_background_image_url":"http://a3.twimg.com/profile_background_images/36516125/Universal_Playaz.jpg","profile_link_color":"2FC2EF","description":"Paper Chaser","following":null,"verified":false,"notifications":null,"profile_sidebar_fill_color":"252429","profile_image_url":"http://a1.twimg.com/profile_images/413331530/DIESELSTATScopy_normal.jpg","url":"http://www.myspace.com/DieselDtheg","profile_sidebar_border_color":"181A1E","screen_name":"DieselD2143","profile_background_tile":true,"followers_count":75,"protected":false,"time_zone":"Eastern Time (US & Canada)","created_at":"Thu Jun 18 15:56:32 +0000 2009","name":"Diesel D","friends_count":119,"profile_background_color":"1A1B1F","id":48392351,"utc_offset":-18000},"in_reply_to_status_id":null,"id":4161231023} {"favorited":false,"text":"David Bowie and Nine Inch Nails perform \"Hurt\" http://bit.ly/AOaWG #musicmonday #nineinchnails #nin","in_reply_to_user_id":null,"in_reply_to_screen_name":null,"source":"web","truncated":false,"created_at":"Tue Sep 22 01:29:16 +0000 2009","user":{"statuses_count":668,"favourites_count":25,"profile_text_color":"445d85","location":"S\u00e3o Paulo, Brazil","profile_background_image_url":"http://a3.twimg.com/profile_background_images/38174991/GeorgeRomero-oil-400.jpg","profile_link_color":"555757","description":"You think I ain't worth a dollar, but I feel like a millionaire","following":null,"verified":false,"notifications":null,"profile_sidebar_fill_color":"a3a7ad","profile_image_url":"http://a1.twimg.com/profile_images/96034368/n1076431955_30001395_7912_normal.jpg","url":null,"profile_sidebar_border_color":"c7d1ed","screen_name":"RenatonMiranda","profile_background_tile":true,"followers_count":111,"protected":false,"time_zone":"Santiago","created_at":"Sat Mar 14 15:03:59 +0000 2009","name":"Renato Miranda","friends_count":143,"profile_background_color":"287356","id":24379310,"utc_offset":-14400},"in_reply_to_status_id":null,"id":4161232008} {"favorited":false,"text":"#musicmonday ,time to download some songs today!! :)","in_reply_to_user_id":null,"in_reply_to_screen_name":null,"source":"web","truncated":false,"created_at":"Tue Sep 22 01:29:19 +0000 2009","user":{"statuses_count":188,"favourites_count":0,"profile_text_color":"3D1957","location":"under the water","profile_background_image_url":"http://s.twimg.com/a/1253562286/images/themes/theme10/bg.gif","profile_link_color":"FF0000","description":"ask me ","following":null,"verified":false,"notifications":null,"profile_sidebar_fill_color":"7AC3EE","profile_image_url":"http://a1.twimg.com/profile_images/421281292/twit_pic_normal.jpg","url":"http://www.exploretalent.com/contest_video.php?talentnum=2053105&cm_id=3398","profile_sidebar_border_color":"65B0DA","screen_name":"julieanne11343","profile_background_tile":true,"followers_count":9,"protected":false,"time_zone":"Pacific Time (US & Canada)","created_at":"Mon Jul 20 21:08:22 +0000 2009","name":"Julieanne","friends_count":17,"profile_background_color":"642D8B","id":58591151,"utc_offset":-28800},"in_reply_to_status_id":null,"id":4161233120} {"text":"#Musicmonday \"Dont be tardy f0r the party\"","truncated":false,"source":"<a href=\"http://twitterhelp.blogspot.com/2008/05/twitter-via-mobile-web-mtwittercom.html\" rel=\"nofollow\">mobile web</a>","in_reply_to_status_id":null,"favorited":false,"created_at":"Tue Sep 22 01:29:19 +0000 2009","user":{"verified":false,"notifications":null,"profile_sidebar_fill_color":"e0ff92","location":"Dope Girl Island","profile_sidebar_border_color":"87bc44","description":"","following":null,"profile_background_tile":false,"followers_count":29,"profile_image_url":"http://a3.twimg.com/profile_images/217487577/badbad_normal.jpg","time_zone":"Eastern Time (US & Canada)","url":null,"friends_count":65,"profile_background_color":"9ae4e8","screen_name":"SwagGirlOnDeck","protected":false,"statuses_count":847,"favourites_count":0,"created_at":"Fri May 01 16:59:15 +0000 2009","profile_text_color":"000000","name":"Mariah Reta","id":36987168,"profile_background_image_url":"http://s.twimg.com/a/1253301564/images/themes/theme1/bg.png","utc_offset":-18000,"profile_link_color":"0000ff"},"in_reply_to_user_id":null,"id":4161233317,"in_reply_to_screen_name":null}
@@ -0,0 +1,6 @@
1
+ {
2
+ "user_withheld":{
3
+ "id":123456,
4
+ "withheld_in_countries":["de","ar"]
5
+ }
6
+ }
@@ -0,0 +1,58 @@
1
+ require 'simplecov'
2
+ require 'coveralls'
3
+
4
+ SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
5
+ SimpleCov::Formatter::HTMLFormatter,
6
+ Coveralls::SimpleCov::Formatter
7
+ ]
8
+ SimpleCov.start
9
+
10
+ require 'tweetstream'
11
+ require 'tweetstream/site_stream_client'
12
+ require 'json'
13
+ require 'rspec'
14
+ require 'webmock/rspec'
15
+ require 'yajl'
16
+
17
+ WebMock.disable_net_connect!(:allow => 'coveralls.io')
18
+
19
+ RSpec.configure do |config|
20
+ config.expect_with :rspec do |c|
21
+ c.syntax = :expect
22
+ end
23
+
24
+ config.after(:each) do
25
+ TweetStream.reset
26
+ end
27
+ end
28
+
29
+ def samples(fixture)
30
+ samples = []
31
+ Yajl::Parser.parse(fixture(fixture), :symbolize_keys => true) do |hash|
32
+ samples << hash
33
+ end
34
+ samples
35
+ end
36
+
37
+ def sample_tweets
38
+ return @tweets if @tweets
39
+ @tweets = samples('statuses.json')
40
+ end
41
+
42
+ def sample_direct_messages
43
+ return @direct_messages if @direct_messages
44
+ @direct_messages = samples('direct_messages.json')
45
+ end
46
+
47
+ def fixture_path
48
+ File.expand_path("../fixtures", __FILE__)
49
+ end
50
+
51
+ def fixture(file)
52
+ File.new(fixture_path + '/' + file)
53
+ end
54
+
55
+ FakeHttp = Class.new do
56
+ def callback; end
57
+ def errback; end
58
+ end
@@ -0,0 +1,83 @@
1
+ require 'helper'
2
+
3
+ describe TweetStream::Client do
4
+ before do
5
+ @stream = stub("EM::Twitter::Client",
6
+ :connect => true,
7
+ :unbind => true,
8
+ :each => true,
9
+ :on_error => true,
10
+ :on_max_reconnects => true,
11
+ :on_reconnect => true,
12
+ :connection_completed => true,
13
+ :on_no_data_received => true,
14
+ :on_unauthorized => true,
15
+ :on_enhance_your_calm => true
16
+ )
17
+ EM.stub!(:run).and_yield
18
+ EM::Twitter::Client.stub!(:connect).and_return(@stream)
19
+ end
20
+
21
+ describe "basic auth" do
22
+ before do
23
+ TweetStream.configure do |config|
24
+ config.username = 'tweetstream'
25
+ config.password = 'rubygem'
26
+ config.auth_method = :basic
27
+ end
28
+
29
+ @client = TweetStream::Client.new
30
+ end
31
+
32
+ it "tries to connect via a JSON stream with basic auth" do
33
+ EM::Twitter::Client.should_receive(:connect).with(
34
+ :path => '/1.1/statuses/filter.json',
35
+ :method => 'POST',
36
+ :user_agent => TweetStream::Configuration::DEFAULT_USER_AGENT,
37
+ :on_inited => nil,
38
+ :params => {:track => 'monday'},
39
+ :basic => {
40
+ :username => 'tweetstream',
41
+ :password => 'rubygem'
42
+ },
43
+ :proxy => nil
44
+ ).and_return(@stream)
45
+
46
+ @client.track('monday')
47
+ end
48
+ end
49
+
50
+ describe "oauth" do
51
+ before do
52
+ TweetStream.configure do |config|
53
+ config.consumer_key = '123456789'
54
+ config.consumer_secret = 'abcdefghijklmnopqrstuvwxyz'
55
+ config.oauth_token = '123456789'
56
+ config.oauth_token_secret = 'abcdefghijklmnopqrstuvwxyz'
57
+ config.auth_method = :oauth
58
+ end
59
+
60
+ @client = TweetStream::Client.new
61
+ end
62
+
63
+ it "tries to connect via a JSON stream with oauth" do
64
+ EM::Twitter::Client.should_receive(:connect).with(
65
+ :path => '/1.1/statuses/filter.json',
66
+ :method => 'POST',
67
+ :user_agent => TweetStream::Configuration::DEFAULT_USER_AGENT,
68
+ :on_inited => nil,
69
+ :params => {:track => 'monday'},
70
+ :oauth => {
71
+ :consumer_key => '123456789',
72
+ :consumer_secret => 'abcdefghijklmnopqrstuvwxyz',
73
+ :token => '123456789',
74
+ :token_secret => 'abcdefghijklmnopqrstuvwxyz'
75
+ },
76
+ :proxy => nil
77
+ ).and_return(@stream)
78
+
79
+ @client.track('monday')
80
+ end
81
+ end
82
+
83
+ end
@@ -0,0 +1,145 @@
1
+ require 'helper'
2
+
3
+ describe TweetStream::Client do
4
+ before(:each) do
5
+ TweetStream.configure do |config|
6
+ config.consumer_key = 'abc'
7
+ config.consumer_secret = 'def'
8
+ config.oauth_token = '123'
9
+ config.oauth_token_secret = '456'
10
+ end
11
+ @client = TweetStream::Client.new
12
+
13
+ @stream = stub("EM::Twitter::Client",
14
+ :connect => true,
15
+ :unbind => true,
16
+ :each => true,
17
+ :on_error => true,
18
+ :on_max_reconnects => true,
19
+ :on_reconnect => true,
20
+ :connection_completed => true,
21
+ :on_no_data_received => true,
22
+ :on_unauthorized => true,
23
+ :on_enhance_your_calm => true
24
+ )
25
+ EM.stub!(:run).and_yield
26
+ EM::Twitter::Client.stub!(:connect).and_return(@stream)
27
+ end
28
+
29
+ describe "Site Stream support" do
30
+ context "when calling #sitestream" do
31
+ it "sends the sitestream host" do
32
+ EM::Twitter::Client.should_receive(:connect).with(hash_including(:host => "sitestream.twitter.com")).and_return(@stream)
33
+ @client.sitestream
34
+ end
35
+
36
+ it "uses the sitestream uri" do
37
+ @client.should_receive(:start).once.with('/1.1/site.json', an_instance_of(Hash)).and_return(@stream)
38
+ @client.sitestream
39
+ end
40
+
41
+ it "supports :followings => true" do
42
+ @client.should_receive(:start).once.with('/1.1/site.json', hash_including(:with => 'followings')).and_return(@stream)
43
+ @client.sitestream(['115192457'], :followings => true)
44
+ end
45
+
46
+ it "supports :with => 'followings'" do
47
+ @client.should_receive(:start).once.with('/1.1/site.json', hash_including(:with => 'followings')).and_return(@stream)
48
+ @client.sitestream(['115192457'], :with => 'followings')
49
+ end
50
+
51
+ it "supports :with => 'user'" do
52
+ @client.should_receive(:start).once.with('/1.1/site.json', hash_including(:with => 'user')).and_return(@stream)
53
+ @client.sitestream(['115192457'], :with => 'user')
54
+ end
55
+
56
+ it "supports :replies => 'all'" do
57
+ @client.should_receive(:start).once.with('/1.1/site.json', hash_including(:replies => 'all')).and_return(@stream)
58
+ @client.sitestream(['115192457'], :replies => 'all')
59
+ end
60
+
61
+ describe "control management" do
62
+ before do
63
+ @control_response = {"control" =>
64
+ {
65
+ "control_uri" =>"/1.1/site/c/01_225167_334389048B872A533002B34D73F8C29FD09EFC50"
66
+ }
67
+ }
68
+ end
69
+ it "assigns the control_uri" do
70
+ @stream.should_receive(:each).and_yield(@control_response.to_json)
71
+ @client.sitestream
72
+
73
+ expect(@client.control_uri).to eq("/1.1/site/c/01_225167_334389048B872A533002B34D73F8C29FD09EFC50")
74
+ end
75
+
76
+ it "instantiates a SiteStreamClient" do
77
+ @stream.should_receive(:each).and_yield(@control_response.to_json)
78
+ @client.sitestream
79
+
80
+ expect(@client.control).to be_kind_of(TweetStream::SiteStreamClient)
81
+ end
82
+
83
+ it "passes the client's on_error to the SiteStreamClient" do
84
+ called = false
85
+ @client.on_error { |err| called = true }
86
+ @stream.should_receive(:each).and_yield(@control_response.to_json)
87
+ @client.sitestream
88
+
89
+ @client.control.on_error.call
90
+
91
+ expect(called).to be_true
92
+ end
93
+ end
94
+
95
+ describe "data handling" do
96
+ context "messages" do
97
+ before do
98
+ @ss_message = {'for_user' => '12345', 'message' => {'id' => 123, 'user' => {'screen_name' => 'monkey'}, 'text' => 'Oo oo aa aa'}}
99
+ end
100
+
101
+ it "yields a site stream message" do
102
+ @stream.should_receive(:each).and_yield(@ss_message.to_json)
103
+ yielded_status = nil
104
+ @client.sitestream do |message|
105
+ yielded_status = message
106
+ end
107
+ expect(yielded_status).not_to be_nil
108
+ expect(yielded_status[:for_user]).to eq('12345')
109
+ expect(yielded_status[:message][:user][:screen_name]).to eq('monkey')
110
+ expect(yielded_status[:message][:text]).to eq('Oo oo aa aa')
111
+ end
112
+ it "yields itself if block has an arity of 2" do
113
+ @stream.should_receive(:each).and_yield(@ss_message.to_json)
114
+ yielded_client = nil
115
+ @client.sitestream do |_, client|
116
+ yielded_client = client
117
+ end
118
+ expect(yielded_client).not_to be_nil
119
+ expect(yielded_client).to eq(@client)
120
+ end
121
+ end
122
+
123
+ context "friends list" do
124
+ before do
125
+ @friends_list = { 'friends' => [123, 456] }
126
+ end
127
+
128
+ it "yields a friends list array" do
129
+ @stream.should_receive(:each).and_yield(@friends_list.to_json)
130
+ yielded_list = nil
131
+ @client.on_friends do |friends|
132
+ yielded_list = friends
133
+ end
134
+ @client.sitestream
135
+
136
+ expect(yielded_list).not_to be_nil
137
+ expect(yielded_list).to be_an(Array)
138
+ expect(yielded_list.first).to eq(123)
139
+ end
140
+ end
141
+ end
142
+ end
143
+ end
144
+
145
+ end
@@ -0,0 +1,391 @@
1
+ require 'helper'
2
+
3
+ describe TweetStream::Client do
4
+ before(:each) do
5
+ TweetStream.configure do |config|
6
+ config.consumer_key = 'abc'
7
+ config.consumer_secret = 'def'
8
+ config.oauth_token = '123'
9
+ config.oauth_token_secret = '456'
10
+ end
11
+ @client = TweetStream::Client.new
12
+ end
13
+
14
+ describe "#start" do
15
+ before do
16
+ @stream = stub("EM::Twitter::Client",
17
+ :connect => true,
18
+ :unbind => true,
19
+ :each => true,
20
+ :on_error => true,
21
+ :on_max_reconnects => true,
22
+ :on_reconnect => true,
23
+ :connection_completed => true,
24
+ :on_no_data_received => true,
25
+ :on_unauthorized => true,
26
+ :on_enhance_your_calm => true
27
+ )
28
+ EM.stub!(:run).and_yield
29
+ EM::Twitter::Client.stub!(:connect).and_return(@stream)
30
+ end
31
+
32
+ it "connects if the reactor is already running" do
33
+ EM.stub!(:reactor_running?).and_return(true)
34
+ @client.should_receive(:connect)
35
+ @client.track('abc')
36
+ end
37
+
38
+ it "starts the reactor if not already running" do
39
+ EM.should_receive(:run).once
40
+ @client.track('abc')
41
+ end
42
+
43
+ it "warns when callbacks are passed as options" do
44
+ @stream.stub(:each).and_yield(nil)
45
+ Kernel.should_receive(:warn).with(/Passing callbacks via the options hash is deprecated and will be removed in TweetStream 3.0/)
46
+ @client.track('abc', :inited => Proc.new { })
47
+ end
48
+
49
+ describe 'proxy usage' do
50
+ it 'connects with a proxy' do
51
+ @client = TweetStream::Client.new(:proxy => { :uri => 'http://someproxy:8081'})
52
+ EM::Twitter::Client.should_receive(:connect).
53
+ with(hash_including(:proxy => { :uri => 'http://someproxy:8081'})).and_return(@stream)
54
+ @stream.should_receive(:each).and_return
55
+ @client.track('abc')
56
+ end
57
+ end
58
+
59
+ describe "#each" do
60
+ it "calls the appropriate parser" do
61
+ @client = TweetStream::Client.new
62
+ Yajl::Parser.should_receive(:parse).twice.and_return({})
63
+ @stream.should_receive(:each).and_yield(sample_tweets[0].to_json)
64
+ @client.track('abc','def')
65
+ end
66
+
67
+ it "yields a Twitter::Tweet" do
68
+ @stream.should_receive(:each).and_yield(sample_tweets[0].to_json)
69
+ @client.track('abc'){|s| expect(s).to be_kind_of(Twitter::Tweet)}
70
+ end
71
+
72
+ it "yields the client if a block with arity 2 is given" do
73
+ @stream.should_receive(:each).and_yield(sample_tweets[0].to_json)
74
+ @client.track('abc'){|s,c| expect(c).to eq(@client)}
75
+ end
76
+
77
+ it "includes the proper values" do
78
+ tweet = sample_tweets[0]
79
+ tweet[:id] = 123
80
+ tweet[:user][:screen_name] = 'monkey'
81
+ tweet[:text] = "Oo oo aa aa"
82
+ @stream.should_receive(:each).and_yield(tweet.to_json)
83
+ @client.track('abc') do |s|
84
+ expect(s[:id]).to eq(123)
85
+ expect(s.user.screen_name).to eq('monkey')
86
+ expect(s.text).to eq('Oo oo aa aa')
87
+ end
88
+ end
89
+
90
+ it "calls the on_stall_warning callback if specified" do
91
+ @stream.should_receive(:each).and_yield(fixture('stall_warning.json'))
92
+ @client.on_stall_warning do |warning|
93
+ expect(warning[:code]).to eq('FALLING_BEHIND')
94
+ end.track('abc')
95
+ end
96
+
97
+ it "calls the on_scrub_geo callback if specified" do
98
+ @stream.should_receive(:each).and_yield(fixture('scrub_geo.json'))
99
+ @client.on_scrub_geo do |up_to_status_id, user_id|
100
+ expect(up_to_status_id).to eq(9876)
101
+ expect(user_id).to eq(1234)
102
+ end.track('abc')
103
+ end
104
+
105
+ it "calls the on_delete callback" do
106
+ @stream.should_receive(:each).and_yield(fixture('delete.json'))
107
+ @client.on_delete do |id, user_id|
108
+ expect(id).to eq(1234)
109
+ expect(user_id).to eq(3)
110
+ end.track('abc')
111
+ end
112
+
113
+ it "calls the on_limit callback" do
114
+ limit = nil
115
+ @stream.should_receive(:each).and_yield(fixture('limit.json'))
116
+ @client.on_limit do |l|
117
+ limit = l
118
+ end.track('abc')
119
+
120
+ expect(limit).to eq(1234)
121
+ end
122
+
123
+ it "calls the on_status_withheld callback" do
124
+ status = nil
125
+ @stream.should_receive(:each).and_yield(fixture('status_withheld.json'))
126
+ @client.on_status_withheld do |s|
127
+ status = s
128
+ end.track('abc')
129
+
130
+ expect(status[:user_id]).to eq(123456)
131
+ end
132
+
133
+ it "calls the on_user_withheld callback" do
134
+ status = nil
135
+ @stream.should_receive(:each).and_yield(fixture('user_withheld.json'))
136
+ @client.on_user_withheld do |s|
137
+ status = s
138
+ end.track('abc')
139
+
140
+ expect(status[:id]).to eq(123456)
141
+ end
142
+
143
+ context "using on_anything" do
144
+ it "yields the raw hash" do
145
+ hash = {:id => 1234}
146
+ @stream.should_receive(:each).and_yield(hash.to_json)
147
+ yielded_hash = nil
148
+ @client.on_anything do |h|
149
+ yielded_hash = h
150
+ end.track('abc')
151
+
152
+ expect(yielded_hash).not_to be_nil
153
+ expect(yielded_hash[:id]).to eq(1234)
154
+ end
155
+ it "yields itself if block has an arity of 2" do
156
+ hash = {:id => 1234}
157
+ @stream.should_receive(:each).and_yield(hash.to_json)
158
+ yielded_client = nil
159
+ @client.on_anything do |_, client|
160
+ yielded_client = client
161
+ end.track('abc')
162
+ expect(yielded_client).not_to be_nil
163
+ expect(yielded_client).to eq(@client)
164
+ end
165
+ end
166
+
167
+ context "using on_timeline_status" do
168
+ it "yields a Status" do
169
+ tweet = sample_tweets[0]
170
+ tweet[:id] = 123
171
+ tweet[:user][:screen_name] = 'monkey'
172
+ tweet[:text] = "Oo oo aa aa"
173
+ @stream.should_receive(:each).and_yield(tweet.to_json)
174
+ yielded_status = nil
175
+ @client.on_timeline_status do |status|
176
+ yielded_status = status
177
+ end.track('abc')
178
+ expect(yielded_status).not_to be_nil
179
+ expect(yielded_status[:id]).to eq(123)
180
+ expect(yielded_status.user.screen_name).to eq('monkey')
181
+ expect(yielded_status.text).to eq('Oo oo aa aa')
182
+ end
183
+ it "yields itself if block has an arity of 2" do
184
+ @stream.should_receive(:each).and_yield(sample_tweets[0].to_json)
185
+ yielded_client = nil
186
+ @client.on_timeline_status do |_, client|
187
+ yielded_client = client
188
+ end.track('abc')
189
+ expect(yielded_client).not_to be_nil
190
+ expect(yielded_client).to eq(@client)
191
+ end
192
+ end
193
+
194
+ context "using on_direct_message" do
195
+ it "yields a DirectMessage" do
196
+ direct_message = sample_direct_messages[0]
197
+ direct_message[:direct_message][:id] = 1234
198
+ direct_message[:direct_message][:sender][:screen_name] = "coder"
199
+ @stream.should_receive(:each).and_yield(direct_message.to_json)
200
+ yielded_dm = nil
201
+ @client.on_direct_message do |dm|
202
+ yielded_dm = dm
203
+ end.userstream
204
+ expect(yielded_dm).not_to be_nil
205
+ expect(yielded_dm.id).to eq(1234)
206
+ expect(yielded_dm.sender.screen_name).to eq("coder")
207
+ end
208
+
209
+ it "yields itself if block has an arity of 2" do
210
+ @stream.should_receive(:each).and_yield(sample_direct_messages[0].to_json)
211
+ yielded_client = nil
212
+ @client.on_direct_message do |_, client|
213
+ yielded_client = client
214
+ end.userstream
215
+ expect(yielded_client).to eq(@client)
216
+ end
217
+ end
218
+
219
+ it "calls on_error if a non-hash response is received" do
220
+ @stream.should_receive(:each).and_yield('["favorited"]')
221
+ @client.on_error do |message|
222
+ expect(message).to eq('Unexpected JSON object in stream: ["favorited"]')
223
+ end.track('abc')
224
+ end
225
+
226
+ it "calls on_error if a json parse error occurs" do
227
+ @stream.should_receive(:each).and_yield("{'a_key':}")
228
+ @client.on_error do |message|
229
+ expect(message).to eq("Yajl::ParseError occured in stream: {'a_key':}")
230
+ end.track('abc')
231
+ end
232
+ end
233
+
234
+ describe "#on_error" do
235
+ it "passes the message on to the error block" do
236
+ @stream.should_receive(:on_error).and_yield('Uh oh')
237
+ @client.on_error do |m|
238
+ expect(m).to eq('Uh oh')
239
+ end.track('abc')
240
+ end
241
+
242
+ it "returns the block when defined" do
243
+ @client.on_error { |m| true; }
244
+ expect(@client.on_error).to be_kind_of(Proc)
245
+ end
246
+
247
+ it "returns nil when undefined" do
248
+ expect(@client.on_error).to be_nil
249
+ end
250
+ end
251
+
252
+ describe "#on_max_reconnects" do
253
+ it "raises a ReconnectError" do
254
+ @stream.should_receive(:on_max_reconnects).and_yield(30, 20)
255
+ expect(lambda{ @client.track('abc') }).to raise_error(TweetStream::ReconnectError, "Failed to reconnect after 20 tries.")
256
+ end
257
+ end
258
+ end
259
+
260
+ describe "API methods" do
261
+ %w(firehose retweet sample links).each do |method|
262
+ it "##{method} should make a call to start with \"statuses/#{method}\"" do
263
+ @client.should_receive(:start).once.with('/1.1/statuses/' + method + '.json', {})
264
+ @client.send(method)
265
+ end
266
+ end
267
+
268
+ describe "#filter" do
269
+ it "makes a call to 'statuses/filter' with the query params provided" do
270
+ @client.should_receive(:start).once.with('/1.1/statuses/filter.json', :follow => 123, :method => :post)
271
+ @client.filter(:follow => 123)
272
+ end
273
+ it "makes a call to 'statuses/filter' with the query params provided longitude/latitude pairs, separated by commas " do
274
+ @client.should_receive(:start).once.with('/1.1/statuses/filter.json', :locations => '-122.75,36.8,-121.75,37.8,-74,40,-73,41', :method => :post)
275
+ @client.filter(:locations => '-122.75,36.8,-121.75,37.8,-74,40,-73,41')
276
+ end
277
+ end
278
+
279
+ describe "#follow" do
280
+ it "makes a call to start with 'statuses/filter' and a follow query parameter" do
281
+ @client.should_receive(:start).once.with('/1.1/statuses/filter.json', :follow => [123], :method => :post)
282
+ @client.follow(123)
283
+ end
284
+
285
+ it "comma-joins multiple arguments" do
286
+ @client.should_receive(:start).once.with('/1.1/statuses/filter.json', :follow => [123,456], :method => :post)
287
+ @client.follow(123, 456)
288
+ end
289
+ end
290
+
291
+ describe "#locations" do
292
+ it "calls #start with 'statuses/filter' with the query params provided longitude/latitude pairs" do
293
+ @client.should_receive(:start).once.with('/1.1/statuses/filter.json', :locations => ['-122.75,36.8,-121.75,37.8,-74,40,-73,41'], :method => :post)
294
+ @client.locations('-122.75,36.8,-121.75,37.8,-74,40,-73,41')
295
+ end
296
+
297
+ it "calls #start with 'statuses/filter' with the query params provided longitude/latitude pairs and additional filter" do
298
+ @client.should_receive(:start).once.with('/1.1/statuses/filter.json', :locations => ['-122.75,36.8,-121.75,37.8,-74,40,-73,41'], :track => 'rock', :method => :post)
299
+ @client.locations('-122.75,36.8,-121.75,37.8,-74,40,-73,41', :track => 'rock')
300
+ end
301
+ end
302
+
303
+ describe "#track" do
304
+ it "makes a call to start with 'statuses/filter' and a track query parameter" do
305
+ @client.should_receive(:start).once.with('/1.1/statuses/filter.json', :track => ['test'], :method => :post)
306
+ @client.track('test')
307
+ end
308
+
309
+ it "comma-joins multiple arguments" do
310
+ @client.should_receive(:start).once.with('/1.1/statuses/filter.json', :track => ['foo', 'bar', 'baz'], :method => :post)
311
+ @client.track('foo', 'bar', 'baz')
312
+ end
313
+
314
+ it "comma-joins an array of arguments" do
315
+ @client.should_receive(:start).once.with('/1.1/statuses/filter.json', :track => [['foo','bar','baz']], :method => :post)
316
+ @client.track(['foo','bar','baz'])
317
+ end
318
+
319
+ it "calls #start with 'statuses/filter' and the provided queries" do
320
+ @client.should_receive(:start).once.with('/1.1/statuses/filter.json', :track => ['rock'], :method => :post)
321
+ @client.track('rock')
322
+ end
323
+ end
324
+ end
325
+
326
+ %w(on_delete on_limit on_inited on_reconnect on_no_data_received on_unauthorized on_enhance_your_calm).each do |proc_setter|
327
+ describe "##{proc_setter}" do
328
+ it "sets when a block is given" do
329
+ proc = Proc.new{|a,b| puts a }
330
+ @client.send(proc_setter, &proc)
331
+ expect(@client.send(proc_setter)).to eq(proc)
332
+ end
333
+
334
+ it "returns nil when undefined" do
335
+ expect(@client.send(proc_setter)).to be_nil
336
+ end
337
+ end
338
+ end
339
+
340
+ describe "#stop" do
341
+ it "calls EventMachine::stop_event_loop" do
342
+ EventMachine.should_receive :stop_event_loop
343
+ expect(TweetStream::Client.new.stop).to be_nil
344
+ end
345
+
346
+ it "returns the last status yielded" do
347
+ EventMachine.should_receive :stop_event_loop
348
+ client = TweetStream::Client.new
349
+ client.send(:instance_variable_set, :@last_status, {})
350
+ expect(client.stop).to eq({})
351
+ end
352
+ end
353
+
354
+ describe "#close_connection" do
355
+ it "does not call EventMachine::stop_event_loop" do
356
+ EventMachine.should_not_receive :stop_event_loop
357
+ expect(TweetStream::Client.new.close_connection).to be_nil
358
+ end
359
+ end
360
+
361
+ describe "#stop_stream" do
362
+ before(:each) do
363
+ @stream = stub("EM::Twitter::Client",
364
+ :connect => true,
365
+ :unbind => true,
366
+ :each => true,
367
+ :on_error => true,
368
+ :on_max_reconnects => true,
369
+ :on_reconnect => true,
370
+ :connection_completed => true,
371
+ :on_no_data_received => true,
372
+ :on_unauthorized => true,
373
+ :on_enhance_your_calm => true,
374
+ :stop => true
375
+ )
376
+ EM::Twitter::Client.stub!(:connect).and_return(@stream)
377
+ @client = TweetStream::Client.new
378
+ @client.connect('/')
379
+ end
380
+
381
+ it "calls stream.stop to cleanly stop the current stream" do
382
+ @stream.should_receive(:stop)
383
+ @client.stop_stream
384
+ end
385
+
386
+ it "does not stop eventmachine" do
387
+ EventMachine.should_not_receive :stop_event_loop
388
+ @client.stop_stream
389
+ end
390
+ end
391
+ end