twitter4r 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
+