twitter4r 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,14 @@
1
+ module Twitter; end
2
+
3
+ def resolve_path(suffix)
4
+ File.expand_path(File.join(File.dirname(__FILE__), suffix))
5
+ end
6
+
7
+ # For better unicode support
8
+ $KCODE = 'u'
9
+ require 'jcode'
10
+
11
+ # Ordering matters...pay attention here!
12
+ require(resolve_path('twitter/version'))
13
+ require(resolve_path('twitter/meta'))
14
+ require(resolve_path('twitter/core'))
@@ -0,0 +1,179 @@
1
+ # The Twitter4R API provides a nicer Ruby object API to work with
2
+ # instead of coding around the REST API.
3
+
4
+ require('net/https')
5
+ require('uri')
6
+ require('json')
7
+
8
+ # Encapsules the Twitter4R API.
9
+ module Twitter
10
+ module ClassUtilMixin #:nodoc:
11
+ def self.included(base)
12
+ base.send(:include, InstanceMethods)
13
+ end
14
+
15
+ module InstanceMethods #:nodoc:
16
+ def initialize(params = {})
17
+ params.each do |key,val|
18
+ self.send("#{key}=", val) if self.respond_to? key
19
+ end
20
+ end
21
+ end
22
+ end # ClassUtilMixin
23
+
24
+ # Error subclass raised when there is an error encountered upon
25
+ # querying or posting to the REST API.
26
+ class RESTError < Exception
27
+ include ClassUtilMixin
28
+ attr_accessor :code, :message, :uri
29
+
30
+ def to_s
31
+ "HTTP #{code}: #{message} at #{uri}"
32
+ end
33
+ end # RESTError
34
+
35
+ # Represents a user of Twitter
36
+ class User
37
+ include ClassUtilMixin
38
+ attr_accessor :id, :name, :description, :location, :screen_name, :url
39
+
40
+ def eql?(obj)
41
+ [:id, :name, :description, :location,
42
+ :screen_name, :url].each do |att|
43
+ return false if self.send(att).eql?(obj.send(att))
44
+ end or true
45
+ end
46
+ end # User
47
+
48
+ # Represents a status posted to Twitter by a user of Twitter.
49
+ class Status
50
+ include ClassUtilMixin
51
+ attr_accessor :id, :text, :created_at, :user
52
+
53
+ def eql?(obj)
54
+ [:id, :text, :created_at, :user].each do |att|
55
+ return false if self.send(att).eql?(obj.send(att))
56
+ end or true
57
+ end
58
+ end # Status
59
+
60
+ # Used to query or post to the Twitter REST API to simplify code.
61
+ class Client
62
+ include ClassUtilMixin
63
+ attr_accessor :login, :password
64
+
65
+ @@HOST = 'twitter.com'
66
+ @@PORT = 80
67
+ @@SSL = false
68
+ @@URIS = {:public => '/statuses/public_timeline.json',
69
+ :friends => '/statuses/friends_timeline.json',
70
+ :friends_statues => '/statuses/friends.json',
71
+ :followers => '/statuses/followers.json',
72
+ :update => '/statuses/update.json',
73
+ :send_direct_message => '/direct_messages/new.json', }
74
+
75
+ class << self
76
+ def config(conf)
77
+ @@HOST = conf[:host] if conf[:host]
78
+ @@PORT = conf[:port] if conf[:port]
79
+ @@SSL = conf[:use_ssl] if conf[:use_ssl] # getting ready for SSL support
80
+ end
81
+
82
+ # Mostly helper method for irb shell prototyping
83
+ # TODO: move this out as class extension for twitter4r console script
84
+ def from_config(config_file, env = 'test')
85
+ yaml_hash = YAML.load(File.read(config_file))
86
+ self.new yaml_hash[env]
87
+ end
88
+ end # class << self
89
+
90
+ def timeline(type = :public)
91
+ http = Net::HTTP.new(@@HOST, @@PORT)
92
+ http.start do |http|
93
+ timeline_request(type, http)
94
+ end # http.start block
95
+ end # timeline
96
+
97
+ def public_timeline
98
+ timeline :public
99
+ end
100
+
101
+ def friend_timeline
102
+ timeline :friends
103
+ end
104
+
105
+ def friend_statuses
106
+ timeline :friends_statuses
107
+ end
108
+
109
+ def follower_statuses
110
+ timeline :followers
111
+ end
112
+
113
+ def update(message)
114
+ uri = @@URIS[:update]
115
+ http = Net::HTTP.new(@@HOST, @@PORT)
116
+ http.start do |http|
117
+ request = Net::HTTP::Post.new(uri)
118
+ request.basic_auth(@login, @password)
119
+ response = http.request(request, "status=#{URI.escape(message)}")
120
+ handle_rest_response(response, uri)
121
+ end
122
+ end
123
+
124
+ def send_direct_message(user, message)
125
+ login = user.respond_to?(:screen_name) ? user.screen_name : user
126
+ uri = @@URIS[:send_direct_message]
127
+ http = Net::HTTP.new(@@HOST, @@PORT)
128
+ http.start do |http|
129
+ request = Net::HTTP::Post.new(uri)
130
+ request.basic_auth(@login, @password)
131
+ response = http.request(request, "user=#{login}&text=#{URI.escape(message)}")
132
+ handle_rest_response(response, uri)
133
+ end
134
+ end
135
+
136
+ protected
137
+ attr_accessor :login, :password, :host, :port
138
+
139
+ def unmarshall_statuses(status_array)
140
+ status_array.collect do |status|
141
+ Status.new(:id => status['id'],
142
+ :text => status['text'],
143
+ :user => unmarshall_user(status['user']),
144
+ :created_at => Time.parse(status['created_at'])
145
+ )
146
+ end
147
+ end
148
+
149
+ def unmarshall_user(map)
150
+ User.new(:id => map['id'], :name => map['name'],
151
+ :screen_name => map['screen_name'],
152
+ :description => map['description'],
153
+ :location => map['location'],
154
+ :url => map['url'])
155
+ end
156
+
157
+ def timeline_request(type, http)
158
+ uri = @@URIS[type]
159
+ request = Net::HTTP::Get.new(uri)
160
+ request.basic_auth(@login, @password)
161
+ response = http.request(request)
162
+
163
+ handle_rest_response(response, uri)
164
+ unmarshall_statuses(JSON.parse(response.body))
165
+ end
166
+
167
+ def raise_rest_error(response, uri = nil)
168
+ raise RESTError.new(:code => response.code,
169
+ :message => response.message,
170
+ :uri => uri)
171
+ end
172
+
173
+ def handle_rest_response(response, uri)
174
+ unless ["200", "201"].member?(response.code)
175
+ raise_rest_error(response, uri)
176
+ end
177
+ end
178
+ end
179
+ end
@@ -0,0 +1,42 @@
1
+ require('rubygems')
2
+ require('erb')
3
+
4
+ class Twitter::Meta #:nodoc:
5
+ attr_accessor :root_dir
6
+ attr_reader :pkg_info, :gem_spec, :project_files, :spec_files
7
+
8
+ def initialize(root_dir)
9
+ @root_dir = root_dir
10
+ end
11
+
12
+ def pkg_info
13
+ yaml_file = File.join(@root_dir, 'pkg-info.yml')
14
+ ryaml = ERB.new(File.read(yaml_file), 0)
15
+ s = ryaml.result(binding)
16
+ YAML.load(s)
17
+ end
18
+
19
+ def spec_info
20
+ self.pkg_info['spec'] if self.pkg_info
21
+ end
22
+
23
+ def project_files
24
+ @project_files ||= Dir.glob(File.join(@root_dir, 'lib/**/*.rb'))
25
+ @project_files
26
+ end
27
+
28
+ def spec_files
29
+ @spec_files ||= Dir.glob(File.join(@root_dir, 'spec/**/*.rb'))
30
+ @spec_files
31
+ end
32
+
33
+ def gem_spec
34
+ @gem_spec ||= Gem::Specification.new do |spec|
35
+ self.spec_info.each do |key, val|
36
+ spec.send("#{key}=", val)
37
+ end
38
+ end
39
+ @gem_spec
40
+ end
41
+ end
42
+
@@ -0,0 +1,14 @@
1
+ module Twitter::Version #:nodoc:
2
+ MAJOR = 0
3
+ MINOR = 1
4
+ REVISION = 0
5
+ class << self
6
+ def to_version
7
+ "#{MAJOR}.#{MINOR}.#{REVISION}"
8
+ end
9
+
10
+ def to_name
11
+ "#{MAJOR}_#{MINOR}_#{REVISION}"
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,86 @@
1
+ require 'spec'
2
+ require 'twitter'
3
+
4
+ # Add helper methods here if relevant to multiple _spec.rb files
5
+
6
+ # Spec helper that returns the project root directory as absolute path string
7
+ def project_root_dir
8
+ File.expand_path(File.join(File.dirname(__FILE__), '..'))
9
+ end
10
+
11
+ # Spec helper that returns stubbed <tt>Net::HTTP</tt> object
12
+ # with given <tt>response</tt> and <tt>obj_stubs</tt>.
13
+ # The <tt>host</tt> and <tt>port</tt> are used to initialize
14
+ # the Net::HTTP object.
15
+ def stubbed_net_http(response, obj_stubs = {}, host = 'twitter.com', port = 80)
16
+ http = Net::HTTP.new(host, port)
17
+ Net::HTTP.stub!(:new).and_return(http)
18
+ http.stub!(:request).and_return(response)
19
+ http
20
+ end
21
+
22
+ # Spec helper that returns a mocked <tt>Net::HTTP</tt> object and
23
+ # stubs out the <tt>request</tt> method to return the given
24
+ # <tt>response</tt>
25
+ def mas_net_http(response)
26
+ http = mock(Net::HTTP)
27
+ Net::HTTP.stub!(:new).and_return(http)
28
+ http.stub!(:request).and_return(response)
29
+ http.stub!(:start).and_yield(http)
30
+ http
31
+ end
32
+
33
+ # Spec helper that returns a mocked <tt>Net::HTTP::Get</tt> object and
34
+ # stubs relevant class methods and given <tt>obj_stubs</tt>
35
+ # for endo-specing
36
+ def mas_net_http_get(obj_stubs = {})
37
+ request = Spec::Mocks::Mock.new(Net::HTTP::Get)
38
+ Net::HTTP::Get.stub!(:new).and_return(request)
39
+ obj_stubs.each do |method, value|
40
+ request.stub!(method).and_return(value)
41
+ end
42
+ request
43
+ end
44
+
45
+ # Spec helper that returns a mocked <tt>Net::HTTP::Post</tt> object and
46
+ # stubs relevant class methods and given <tt>obj_stubs</tt>
47
+ # for endo-specing
48
+ def mas_net_http_post(obj_stubs = {})
49
+ request = Spec::Mocks::Mock.new(Net::HTTP::Post)
50
+ Net::HTTP::Post.stub!(:new).and_return(request)
51
+ obj_stubs.each do |method, value|
52
+ request.stub!(method).and_return(value)
53
+ end
54
+ request
55
+ end
56
+
57
+ # Spec helper that returns a mocked <tt>Net::HTTPResponse</tt> object and
58
+ # stubs given <tt>obj_stubs</tt> for endo-specing.
59
+ #
60
+ def mas_net_http_response(status = :success,
61
+ body = '',
62
+ obj_stubs = {})
63
+ response = Spec::Mocks::Mock.new(Net::HTTPResponse)
64
+ response.stub!(:body).and_return(body)
65
+ case status
66
+ when :success || 200
67
+ _create_http_response(response, "200", "OK")
68
+ when :created || 201
69
+ _create_http_response(response, "201", "Created")
70
+ when :redirect || 301
71
+ _create_http_response(response, "301", "Redirect")
72
+ when :not_authorized || 403
73
+ _create_http_response(response, "403", "Not Authorized")
74
+ when :file_not_found || 404
75
+ _create_http_response(response, "404", "File Not Found")
76
+ when :server_error || 500
77
+ _create_http_response(response, "500", "Server Error")
78
+ end
79
+ response
80
+ end
81
+
82
+ # Local helper method to DRY up code.
83
+ def _create_http_response(mock_response, code, message)
84
+ mock_response.stub!(:code).and_return(code)
85
+ mock_response.stub!(:message).and_return(message)
86
+ end
@@ -0,0 +1,430 @@
1
+ require File.join(File.dirname(__FILE__), '..', 'spec_helper')
2
+
3
+ context "Twitter::ClassUtilMixin mixed-in class" do
4
+ setup do
5
+ class TestClass
6
+ include Twitter::ClassUtilMixin
7
+ attr_accessor :var1, :var2, :var3
8
+ end
9
+ @init_hash = { :var1 => 'val1', :var2 => 'val2', :var3 => 'val3' }
10
+ end
11
+
12
+ specify "should have Twitter::ClassUtilMixin as an included module" do
13
+ TestClass.included_modules.member?(Twitter::ClassUtilMixin).should.eql true
14
+ end
15
+
16
+ specify "should set attributes passed in the hash to TestClass.new" do
17
+ test = TestClass.new(@init_hash)
18
+ @init_hash.each do |key, val|
19
+ test.send(key).should.eql val
20
+ end
21
+ end
22
+
23
+ specify "should not set attributes passed in the hash that are not attributes in TestClass.new" do
24
+ test = nil
25
+ lambda { test = TestClass.new(@init_hash.merge(:var4 => 'val4')) }.should_not_raise
26
+ test.respond_to?(:var4).should.eql false
27
+ end
28
+ end
29
+
30
+ context "Twitter::RESTError#to_s" do
31
+ setup do
32
+ @hash = { :code => 200, :message => 'OK', :uri => 'http://test.host/bla' }
33
+ @error = Twitter::RESTError.new(@hash)
34
+ @expected_message = "HTTP #{@hash[:code]}: #{@hash[:message]} at #{@hash[:uri]}"
35
+ end
36
+
37
+ specify "should return @expected_message" do
38
+ @error.to_s.should.eql @expected_message
39
+ end
40
+ end
41
+
42
+ context "Twitter::Client" do
43
+ setup do
44
+ @init_hash = { :login => 'user', :password => 'pass' }
45
+ end
46
+
47
+ specify ".new should accept login and password as initializer hash keys and set the values to instance values" do
48
+ client = nil
49
+ lambda do
50
+ client = Twitter::Client.new(@init_hash)
51
+ end.should_not_raise
52
+ client.send(:login).should.eql @init_hash[:login]
53
+ client.send(:password).should.eql @init_hash[:password]
54
+ end
55
+ end
56
+
57
+ context "Twitter::Client.config" do
58
+ setup do
59
+ @config_hash = { :host => 'test.host',
60
+ :port => 443,
61
+ :use_ssl => true, }
62
+ end
63
+
64
+ specify "should override @@HOST and @@PORT if supplied" do
65
+ Twitter::Client.config @config_hash
66
+ host = Twitter::Client.class_eval("@@HOST")
67
+ host.should.eql @config_hash[:host]
68
+ port = Twitter::Client.class_eval("@@PORT")
69
+ port.should.eql @config_hash[:port]
70
+ ssl = Twitter::Client.class_eval("@@SSL")
71
+ ssl.should.eql @config_hash[:use_ssl]
72
+ end
73
+
74
+ teardown do
75
+ Twitter::Client.config :host => 'twitter.com', :port => 80, :use_ssl => false
76
+ end
77
+ end
78
+
79
+ context "Twitter::Client#timeline(:public)" do
80
+ setup do
81
+ Twitter::Client.config(:port => 443, :use_ssl => false)
82
+ @host = Twitter::Client.class_eval("@@HOST")
83
+ @port = Twitter::Client.class_eval("@@PORT")
84
+
85
+ @request = mas_net_http_get(:basic_auth => nil)
86
+ @response = mas_net_http_response(:success, '[]')
87
+
88
+ @http = mas_net_http(@response)
89
+ @client = Twitter::Client.from_config 'config/twitter.yml'
90
+ @login = @client.instance_eval("@login")
91
+ @password = @client.instance_eval("@password")
92
+ end
93
+
94
+ specify "should connect to the Twitter service via HTTP connection" do
95
+ Net::HTTP.should_receive(:new).with(@host, @port).once.and_return(@http)
96
+ @client.timeline(:public)
97
+ end
98
+
99
+ specify " should send HTTP Basic Authentication credentials" do
100
+ @request.should_receive(:basic_auth).with(@login, @password).once
101
+ @client.timeline(:public)
102
+ end
103
+ end
104
+
105
+ context "Twitter::Client#unmarshall_statuses" do
106
+ setup do
107
+ @json_hash = { "text" => "Thinking Zipcar is lame...",
108
+ "id" => 46672912,
109
+ "user" => {"name" => "Angie",
110
+ "description" => "TV junkie...",
111
+ "location" => "NoVA",
112
+ "profile_image_url" => "http:\/\/assets0.twitter.com\/system\/user\/profile_image\/5483072\/normal\/eye.jpg?1177462492",
113
+ "url" => nil,
114
+ "id" => 5483072,
115
+ "protected" => false,
116
+ "screen_name" => "ang_410"},
117
+ "created_at" => "Wed May 02 03:04:54 +0000 2007"}
118
+ @user = Twitter::User.new @json_hash["user"]
119
+ @status = Twitter::Status.new @json_hash
120
+ @status.user = @user
121
+ @client = Twitter::Client.from_config 'config/twitter.yml'
122
+ end
123
+
124
+ specify "should return expected populated Twitter::Status object values in an Array" do
125
+ statuses = @client.send(:unmarshall_statuses, [@json_hash])
126
+ statuses.should.have(1)
127
+ statuses.first.should.eql? @status
128
+ end
129
+ end
130
+
131
+ context "Twitter::Client#unmarshall_user" do
132
+ setup do
133
+ @json_hash = { "name" => "Lucy Snowe",
134
+ "description" => "School Mistress Entrepreneur",
135
+ "location" => "Villette",
136
+ "url" => "http://villetteschoolforgirls.com",
137
+ "id" => 859303,
138
+ "protected" => true,
139
+ "screen_name" => "LucyDominatrix", }
140
+ @user = Twitter::User.new @json_hash
141
+ @client = Twitter::Client.from_config 'config/twitter.yml'
142
+ end
143
+
144
+ specify "should return expected populated Twitter::User object value" do
145
+ user = @client.send(:unmarshall_user, @json_hash)
146
+ user.should.eql? @user
147
+ end
148
+ end
149
+
150
+ context "Twitter::Client#timeline_request upon 200 HTTP response" do
151
+ setup do
152
+ @request = mas_net_http_get :basic_auth => nil
153
+ @response = mas_net_http_response # defaults to :success
154
+
155
+ @http = mas_net_http(@response)
156
+ @client = Twitter::Client.from_config 'config/twitter.yml'
157
+ @uris = Twitter::Client.class_eval("@@URIS")
158
+
159
+ JSON.stub!(:parse).and_return({})
160
+ end
161
+
162
+ specify "should make GET HTTP request to appropriate URL" do
163
+ @uris.keys.each do |type|
164
+ Net::HTTP::Get.should_receive(:new).with(@uris[type]).and_return(@request)
165
+ @client.send(:timeline_request, type, @http)
166
+ end
167
+ end
168
+ end
169
+
170
+ context "Twitter::Client#timeline_request upon 403 HTTP response" do
171
+ setup do
172
+ @request = mas_net_http_get :basic_auth => nil
173
+ @response = mas_net_http_response :not_authorized
174
+
175
+ @http = mas_net_http(@response)
176
+ @client = Twitter::Client.from_config 'config/twitter.yml'
177
+ @uris = Twitter::Client.class_eval("@@URIS")
178
+ end
179
+
180
+ specify "should make GET HTTP request to appropriate URL" do
181
+ @uris.keys.each do |type|
182
+ lambda do
183
+ Net::HTTP::Get.should_receive(:new).with(@uris[type]).and_return(@request)
184
+ @client.send(:timeline_request, type, @http)
185
+ end.should_raise(Twitter::RESTError)
186
+ end
187
+ end
188
+ end
189
+
190
+ context "Twitter::Client#timeline_request upon 500 HTTP response" do
191
+ setup do
192
+ @request = mas_net_http_get(:basic_auth => nil)
193
+ @response = mas_net_http_response(:server_error)
194
+
195
+ @http = mas_net_http(@response)
196
+ @client = Twitter::Client.from_config 'config/twitter.yml'
197
+ @uris = Twitter::Client.class_eval("@@URIS")
198
+ end
199
+
200
+ specify "should make GET HTTP request to appropriate URL" do
201
+ @uris.keys.each do |type|
202
+ lambda do
203
+ Net::HTTP::Get.should_receive(:new).with(@uris[type]).and_return(@request)
204
+ @client.send(:timeline_request, type, @http)
205
+ end.should_raise(Twitter::RESTError)
206
+ end
207
+ end
208
+ end
209
+
210
+ context "Twitter::Client#timeline_request upon 404 HTTP response" do
211
+ setup do
212
+ @request = mas_net_http_get(:basic_auth => nil)
213
+ @response = mas_net_http_response(:file_not_found)
214
+
215
+ @http = mas_net_http(@response)
216
+ @client = Twitter::Client.from_config 'config/twitter.yml'
217
+ @uris = Twitter::Client.class_eval("@@URIS")
218
+ end
219
+
220
+ specify "should make GET HTTP request to appropriate URL" do
221
+ @uris.keys.each do |type|
222
+ lambda do
223
+ Net::HTTP::Get.should_receive(:new).with(@uris[type]).and_return(@request)
224
+ @client.send(:timeline_request, type, @http)
225
+ end.should_raise(Twitter::RESTError)
226
+ end
227
+ end
228
+ end
229
+
230
+ context "Twitter::Client#update(msg) upon 200 HTTP response" do
231
+ setup do
232
+ @request = mas_net_http_post(:basic_auth => nil)
233
+ @response = mas_net_http_response
234
+
235
+ @http = mas_net_http(@response)
236
+ @client = Twitter::Client.from_config 'config/twitter.yml'
237
+ @expected_uri = Twitter::Client.class_eval("@@URIS[:update]")
238
+
239
+ @message = "We love Jodhi May!"
240
+ end
241
+
242
+ specify "should make POST HTTP request to appropriate URL" do
243
+ Net::HTTP::Post.should_receive(:new).with(@expected_uri).and_return(@request)
244
+ @client.update(@message)
245
+ end
246
+ end
247
+
248
+ context "Twitter::Client#update(msg) upon 500 HTTP response" do
249
+ setup do
250
+ @request = mas_net_http_post(:basic_auth => nil)
251
+ @response = mas_net_http_response(:server_error)
252
+
253
+ @http = mas_net_http(@response)
254
+ @client = Twitter::Client.from_config 'config/twitter.yml'
255
+ @expected_uri = Twitter::Client.class_eval("@@URIS[:update]")
256
+
257
+ @message = "We love Jodhi May!"
258
+ end
259
+
260
+ specify "should make POST HTTP request to appropriate URL" do
261
+ lambda do
262
+ Net::HTTP::Post.should_receive(:new).with(@expected_uri).and_return(@request)
263
+ @client.update(@message)
264
+ end.should_raise(Twitter::RESTError)
265
+ end
266
+ end
267
+
268
+ context "Twitter::Client#public_timeline" do
269
+ setup do
270
+ @request = mas_net_http_get(:basic_auth => nil)
271
+ @response = mas_net_http_response
272
+
273
+ @http = mas_net_http(@response)
274
+ @client = Twitter::Client.from_config 'config/twitter.yml'
275
+ end
276
+
277
+ specify "should delegate work to Twitter::Client#public(:public)" do
278
+ @client.should_receive(:timeline).with(:public).once
279
+ @client.public_timeline
280
+ end
281
+ end
282
+
283
+ context "Twitter::Client#friend_timeline" do
284
+ setup do
285
+ @request = mas_net_http_get(:basic_auth => nil)
286
+ @response = mas_net_http_response
287
+
288
+ @http = mas_net_http(@response)
289
+ @client = Twitter::Client.from_config 'config/twitter.yml'
290
+ end
291
+
292
+ specify "should delegate work to Twitter::Client#public(:friends)" do
293
+ @client.should_receive(:timeline).with(:friends).once
294
+ @client.friend_timeline
295
+ end
296
+ end
297
+
298
+ context "Twitter::Client#friend_statuses" do
299
+ setup do
300
+ @request = mas_net_http_get(:basic_auth => nil)
301
+ @response = mas_net_http_response
302
+
303
+ @http = mas_net_http(@response)
304
+ @client = Twitter::Client.from_config 'config/twitter.yml'
305
+ end
306
+
307
+ specify "should delegate work to Twitter::Client#public(:friends_statuses)" do
308
+ @client.should_receive(:timeline).with(:friends_statuses).once
309
+ @client.friend_statuses
310
+ end
311
+ end
312
+
313
+ context "Twitter::Client#follower_statuses" do
314
+ setup do
315
+ @request = mas_net_http_get(:basic_auth => nil)
316
+ @response = mas_net_http_response
317
+
318
+ @http = mas_net_http(@response)
319
+ @client = Twitter::Client.from_config 'config/twitter.yml'
320
+ end
321
+
322
+ specify "should delegate work to Twitter::Client#public(:followers)" do
323
+ @client.should_receive(:timeline).with(:followers).once
324
+ @client.follower_statuses
325
+ end
326
+ end
327
+
328
+ context "Twitter::Client#send_direct_message" do
329
+ setup do
330
+ @request = mas_net_http_post(:basic_auth => nil)
331
+ @response = mas_net_http_response
332
+
333
+ @http = mas_net_http(@response)
334
+ @client = Twitter::Client.from_config 'config/twitter.yml'
335
+
336
+ @login = @client.instance_eval("@login")
337
+ @password = @client.instance_eval("@password")
338
+
339
+ @user = mock(Twitter::User)
340
+ @user.stub!(:screen_name).and_return("twitter4r")
341
+
342
+ @message = "This is a test direct message from twitter4r RSpec specifications"
343
+ @expected_uri = '/direct_messages/new.json'
344
+ @expected_params = "user=#{@user.screen_name}&text=#{URI.escape(@message)}"
345
+ end
346
+
347
+ specify "should convert given Twitter::User object to screen name" do
348
+ @user.should_receive(:screen_name).once
349
+ @client.send_direct_message(@user, @message)
350
+ end
351
+
352
+ specify "should POST to expected URI" do
353
+ Net::HTTP::Post.should_receive(:new).with(@expected_uri).once.and_return(@request)
354
+ @client.send_direct_message(@user, @message)
355
+ end
356
+
357
+ specify "should login via HTTP Basic Authentication using expected credentials" do
358
+ @request.should_receive(:basic_auth).with(@login, @password).once
359
+ @client.send_direct_message(@user, @message)
360
+ end
361
+
362
+ specify "should make POST request with expected URI escaped parameters" do
363
+ @http.should_receive(:request).with(@request, @expected_params).once.and_return(@response)
364
+ @client.send_direct_message(@user, @message)
365
+ end
366
+ end
367
+
368
+ context "Twitter::Status#eql?" do
369
+ setup do
370
+ @attr_hash = { :text => 'Status', :id => 34329594003,
371
+ :user => { :name => 'Tess',
372
+ :description => "Unfortunate D'Urberville",
373
+ :location => 'Dorset',
374
+ :url => nil,
375
+ :id => 34320304,
376
+ :screen_name => 'maiden_no_more' },
377
+ :created_at => 'Wed May 02 03:04:54 +0000 2007'}
378
+ @obj = Twitter::Status.new @attr_hash
379
+ @other = Twitter::Status.new @attr_hash
380
+ end
381
+
382
+ specify "should return true when non-transient object attributes are eql?" do
383
+ @obj.should.eql? @other
384
+ @obj.eql?(@other).should.eql? true # for the sake of getting rcov to recognize this method is covered in the specs
385
+ end
386
+
387
+ specify "should return false when not all non-transient object attributes are eql?" do
388
+ @other.created_at = Time.now.to_s
389
+ @obj.should.not.eql? @other
390
+ @obj.eql?(@other).should.eql? false # for the sake of getting rcov to recognize this method is covered in the specs
391
+ end
392
+
393
+ specify "should return true when comparing same object to itself" do
394
+ @obj.should.eql? @obj
395
+ @obj.eql?(@obj).should.eql? true # for the sake of getting rcov to recognize this method is covered in the specs
396
+ @other.should.eql? @other
397
+ @other.eql?(@other).should.eql? true # for the sake of getting rcov to recognize this method is covered in the specs
398
+ end
399
+ end
400
+
401
+ context "Twitter::User#eql?" do
402
+ setup do
403
+ @attr_hash = { :name => 'Elizabeth Jane Newson-Henshard',
404
+ :description => "Wronged 'Daughter'",
405
+ :location => 'Casterbridge',
406
+ :url => nil,
407
+ :id => 6748302,
408
+ :screen_name => 'mayors_daughter_or_was_she?' }
409
+ @obj = Twitter::User.new @attr_hash
410
+ @other = Twitter::User.new @attr_hash
411
+ end
412
+
413
+ specify "should return true when non-transient object attributes are eql?" do
414
+ @obj.should.eql? @other
415
+ @obj.eql?(@other).should.eql? true
416
+ end
417
+
418
+ specify "should return false when not all non-transient object attributes are eql?" do
419
+ @other.id = 1
420
+ @obj.should.not.eql? @other
421
+ @obj.eql?(@other).should.eql? false
422
+ end
423
+
424
+ specify "should return true when comparing same object to itself" do
425
+ @obj.should.eql? @obj
426
+ @obj.eql?(@obj).should.eql? true
427
+ @other.should.eql? @other
428
+ @obj.eql?(@obj).should.eql? true
429
+ end
430
+ end
@@ -0,0 +1,96 @@
1
+ require File.join(File.dirname(__FILE__), '..', 'spec_helper')
2
+
3
+ def glob_files(*path_elements)
4
+ Dir.glob(File.join(*path_elements))
5
+ end
6
+
7
+ def load_erb_yaml(path, context)
8
+ ryaml = ERB.new(File.read(path), 0)
9
+ YAML.load(ryaml.result(context))
10
+ end
11
+
12
+ module ERBMetaMixin
13
+ # Needed to make the YAML load work...
14
+ def project_files
15
+ glob_files(@root_dir, 'lib', '**/*.rb')
16
+ end
17
+
18
+ # Needed to make the YAML load work...
19
+ def spec_files
20
+ glob_files(@root_dir, 'spec', '**/*.rb')
21
+ end
22
+ end
23
+
24
+ context "Twitter::Meta cache policy" do
25
+ include ERBMetaMixin
26
+ setup do
27
+ @root_dir = project_root_dir
28
+ @meta = Twitter::Meta.new(@root_dir)
29
+ @expected_pkg_info = load_erb_yaml(File.join(@root_dir, 'pkg-info.yml'), binding)
30
+ @expected_project_files = project_files
31
+ @expected_spec_files = spec_files
32
+ end
33
+
34
+ specify "should store value returned from pkg_info in @pkg_info after first YAML load" do
35
+ @meta.instance_eval("@pkg_info").should.eql?(nil)
36
+ @meta.pkg_info
37
+ @meta.instance_eval("@pkg_info").should_eql?(@expected_pkg_info)
38
+ @meta.pkg_info
39
+ @meta.instance_eval("@pkg_info").should_eql?(@expected_pkg_info)
40
+ end
41
+
42
+ specify "should store value returned from project_files in @project_files after first glob" do
43
+ @meta.instance_eval("@project_files").should.eql?(nil)
44
+ @meta.project_files
45
+ @meta.instance_eval("@project_files").should.eql?(@expected_project_files)
46
+ @meta.project_files
47
+ @meta.instance_eval("@project_files").should.eql?(@expected_project_files)
48
+ end
49
+
50
+ specify "should store value returned from spec_files in @spec_files after first glob" do
51
+ @meta.instance_eval("@spec_files").should.eql?(nil)
52
+ @meta.spec_files
53
+ @meta.instance_eval("@spec_files").should.eql?(@expected_spec_files)
54
+ @meta.spec_files
55
+ @meta.instance_eval("@spec_files").should.eql?(@expected_spec_files)
56
+ end
57
+ end
58
+
59
+ context "Twitter::Meta" do
60
+ include ERBMetaMixin
61
+ setup do
62
+ @root_dir = project_root_dir
63
+ @meta = Twitter::Meta.new(@root_dir)
64
+ @expected_yaml_hash = load_erb_yaml(File.join(@root_dir, 'pkg-info.yml'), binding)
65
+ @expected_project_files = project_files
66
+ @expected_spec_files = spec_files
67
+ end
68
+
69
+ specify "should load and return YAML file into Hash object upon #pkg_info call" do
70
+ yaml_hash = @meta.pkg_info
71
+ yaml_hash.should.eql? @expected_yaml_hash
72
+ end
73
+
74
+ specify "should return the embedded hash responding to key 'spec' of #pkg_info call upon #spec_info call" do
75
+ yaml_hash = @meta.spec_info
76
+ yaml_hash.should.eql? @expected_yaml_hash['spec']
77
+ end
78
+
79
+ specify "should return list of files matching ROOT_DIR/lib/**/*.rb upon #project_files call" do
80
+ project_files = @meta.project_files
81
+ project_files.should.eql? @expected_project_files
82
+ end
83
+
84
+ specify "should return list of files matching ROOT_DIR/spec/**/*.rb upon #spec_files call" do
85
+ spec_files = @meta.spec_files
86
+ spec_files.should.eql? @expected_spec_files
87
+ end
88
+
89
+ specify "should return Gem specification based on YAML file contents and #project_files and #spec_files return values" do
90
+ spec = @meta.gem_spec
91
+ expected_spec_hash = @expected_yaml_hash['spec']
92
+ expected_spec_hash.each do |key, val|
93
+ spec.send(key).should.eql? expected_spec_hash[key]
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,19 @@
1
+ require File.join(File.dirname(__FILE__), '..', 'spec_helper')
2
+
3
+ VERSION_LIST = [Twitter::Version::MAJOR, Twitter::Version::MINOR, Twitter::Version::REVISION]
4
+
5
+ EXPECTED_VERSION = VERSION_LIST.join('.')
6
+ EXPECTED_NAME = VERSION_LIST.join('_')
7
+
8
+ context "Twitter::Version.to_version" do
9
+ specify "should return #{EXPECTED_VERSION}" do
10
+ Twitter::Version.to_version.should.eql EXPECTED_VERSION
11
+ end
12
+ end
13
+
14
+ context "Twitter::Version.to_name" do
15
+ specify "should return #{EXPECTED_NAME}" do
16
+ Twitter::Version.to_name.should.eql EXPECTED_NAME
17
+ end
18
+ end
19
+
metadata ADDED
@@ -0,0 +1,53 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.9.2
3
+ specification_version: 1
4
+ name: twitter4r
5
+ version: !ruby/object:Gem::Version
6
+ version: 0.1.0
7
+ date: 2007-05-06 00:00:00 -05:00
8
+ summary: A clean Twitter client API in pure Ruby (not command-line client). Will include Twitter add-ons also in Ruby.
9
+ require_paths:
10
+ - lib
11
+ email: twitter4r-devel@rubyforge.org
12
+ homepage: http://twitter4r.rubyforge.org
13
+ rubyforge_project: twitter4r
14
+ description:
15
+ autorequire: twitter
16
+ default_executable:
17
+ bindir: bin
18
+ has_rdoc: true
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ - - ">"
22
+ - !ruby/object:Gem::Version
23
+ version: 0.0.0
24
+ version:
25
+ platform: ruby
26
+ signing_key:
27
+ cert_chain:
28
+ post_install_message:
29
+ authors:
30
+ - Susan Potter
31
+ files:
32
+ - lib/twitter.rb
33
+ - lib/twitter/version.rb
34
+ - lib/twitter/meta.rb
35
+ - lib/twitter/core.rb
36
+ - spec/spec_helper.rb
37
+ - spec/twitter/core_spec.rb
38
+ - spec/twitter/version_spec.rb
39
+ - spec/twitter/meta_spec.rb
40
+ test_files: []
41
+
42
+ rdoc_options: []
43
+
44
+ extra_rdoc_files: []
45
+
46
+ executables: []
47
+
48
+ extensions: []
49
+
50
+ requirements: []
51
+
52
+ dependencies: []
53
+