twitter4r 0.1.1 → 0.2.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,331 @@
1
+ # Contains Twitter4R Model API.
2
+
3
+ module Twitter
4
+ # Mixin module for model classes. Includes generic class methods like
5
+ # unmarshal.
6
+ #
7
+ # To create a new model that includes this mixin's features simply:
8
+ # class NewModel
9
+ # include Twitter::ModelMixin
10
+ # end
11
+ #
12
+ # This mixin module automatically includes <tt>Twitter::ClassUtilMixin</tt>
13
+ # features.
14
+ #
15
+ # The contract for models to use this mixin correctly is that the class
16
+ # including this mixin must provide an class method named <tt>attributes</tt>
17
+ # that will return an Array of attribute symbols that will be checked
18
+ # in #eql? override method. The following would be sufficient:
19
+ # def self.attributes; @@ATTRIBUTES; end
20
+ module ModelMixin #:nodoc:
21
+ def self.included(base) #:nodoc:
22
+ base.send(:include, Twitter::ClassUtilMixin)
23
+ base.send(:include, InstanceMethods)
24
+ base.extend(ClassMethods)
25
+ end
26
+
27
+ # Class methods defined for <tt>Twitter::ModelMixin</tt> module.
28
+ module ClassMethods #:nodoc:
29
+ # Unmarshal object singular or plural array of model objects
30
+ # from JSON serialization. Currently JSON is only supported
31
+ # since this is all <tt>Twitter4R</tt> needs.
32
+ def unmarshal(raw)
33
+ input = JSON.parse(raw)
34
+ def unmarshal_model(hash)
35
+ self.new(hash)
36
+ end
37
+ return unmarshal_model(input) if input.is_a?(Hash) # singular case
38
+ result = []
39
+ input.each do |hash|
40
+ model = unmarshal_model(hash) if hash.is_a?(Hash)
41
+ result << model
42
+ end if input.is_a?(Array)
43
+ result # plural case
44
+ end
45
+ end
46
+
47
+ # Instance methods defined for <tt>Twitter::ModelMixin</tt> module.
48
+ module InstanceMethods #:nodoc:
49
+ attr_accessor :client
50
+ # Equality method override of Object#eql? default.
51
+ #
52
+ # Relies on the class using this mixin to provide a <tt>attributes</tt>
53
+ # class method that will return an Array of attributes to check are
54
+ # equivalent in this #eql? override.
55
+ #
56
+ # It is by design that the #eql? method will raise a NoMethodError
57
+ # if no <tt>attributes</tt> class method exists, to alert you that
58
+ # you must provide it for a meaningful result from this #eql? override.
59
+ # Otherwise this will return a meaningless result.
60
+ def eql?(other)
61
+ attrs = self.class.attributes
62
+ attrs.each do |att|
63
+ return false unless self.send(att).eql?(other.send(att))
64
+ end
65
+ true
66
+ end
67
+
68
+ # Returns integer representation of model object instance.
69
+ #
70
+ # For example,
71
+ # status = Twitter::Status.new(:id => 234343)
72
+ # status.to_i #=> 234343
73
+ def to_i
74
+ @id
75
+ end
76
+
77
+ # Returns string representation of model object instance.
78
+ #
79
+ # For example,
80
+ # status = Twitter::Status.new(:text => 'my status message')
81
+ # status.to_s #=> 'my status message'
82
+ #
83
+ # If a model class doesn't have a @text attribute defined
84
+ # the default Object#to_s will be returned as the result.
85
+ def to_s
86
+ self.respond_to(:text) ? @text : super.to_s
87
+ end
88
+
89
+ # Returns hash representation of model object instance.
90
+ #
91
+ # For example,
92
+ # u = Twitter::User.new(:id => 2342342, :screen_name => 'tony_blair_is_the_devil')
93
+ # u.to_hash #=> {:id => 2342342, :screen_name => 'tony_blair_is_the_devil'}
94
+ #
95
+ # This method also requires that the class method <tt>attributes</tt> be
96
+ # defined to return an Array of attributes for the class.
97
+ def to_hash
98
+ attrs = self.class.attributes
99
+ result = {}
100
+ attrs.each do |att|
101
+ value = self.send(att)
102
+ value = value.to_hash if value.respond_to?(:to_hash)
103
+ result[att] = value if value
104
+ end
105
+ result
106
+ end
107
+
108
+ # "Blesses" model object.
109
+ #
110
+ # Should be overridden by model class if special behavior is expected
111
+ #
112
+ # Expected to return blessed object (usually <tt>self</tt>)
113
+ def bless(client)
114
+ self.basic_bless(client)
115
+ end
116
+
117
+ protected
118
+ # Basic "blessing" of model object
119
+ def basic_bless(client)
120
+ self.client = client
121
+ self
122
+ end
123
+ end
124
+ end
125
+
126
+ module AuthenticatedUserMixin
127
+ def self.included(base)
128
+ base.send(:include, InstanceMethods)
129
+ end
130
+
131
+ module InstanceMethods
132
+ # Returns an Array of user objects that represents the authenticated
133
+ # user's friends on Twitter.
134
+ def followers
135
+ @client.user(@id, :followers)
136
+ end
137
+
138
+ # Adds given user as a friend. Returns user object as given by
139
+ # <tt>Twitter</tt> REST server response.
140
+ #
141
+ # For <tt>user</tt> argument you may pass in the unique integer
142
+ # user ID, screen name or Twitter::User object representation.
143
+ def befriend(user)
144
+ @client.friend(:add, user)
145
+ end
146
+
147
+ # Removes given user as a friend. Returns user object as given by
148
+ # <tt>Twitter</tt> REST server response.
149
+ #
150
+ # For <tt>user</tt> argument you may pass in the unique integer
151
+ # user ID, screen name or Twitter::User object representation.
152
+ def defriend(user)
153
+ @client.friend(:remove, user)
154
+ end
155
+ end
156
+ end
157
+
158
+ # Represents a <tt>Twitter</tt> user
159
+ class User
160
+ include ModelMixin
161
+ @@ATTRIBUTES = [:id, :name, :description, :location, :screen_name, :url]
162
+ attr_accessor *@@ATTRIBUTES
163
+
164
+ class << self
165
+ # Used as factory method callback
166
+ def attributes; @@ATTRIBUTES; end
167
+
168
+ # Returns user model object with given <tt>id</tt> using the configuration
169
+ # and credentials of the <tt>client</tt> object passed in.
170
+ #
171
+ # You can pass in either the user's unique integer ID or the user's
172
+ # screen name.
173
+ def find(id, client)
174
+ client.user(id)
175
+ end
176
+ end
177
+
178
+ # Override of ModelMixin#bless method.
179
+ #
180
+ # Adds #followers instance method when user object represents
181
+ # authenticated user. Otherwise just do basic bless.
182
+ #
183
+ # This permits applications using <tt>Twitter4R</tt> to write
184
+ # Rubyish code like this:
185
+ # followers = user.followers if user.is_me?
186
+ # Or:
187
+ # followers = user.followers if user.respond_to?(:followers)
188
+ def bless(client)
189
+ basic_bless(client)
190
+ self.instance_eval(%{
191
+ self.class.send(:include, Twitter::AuthenticatedUserMixin)
192
+ }) if self.is_me? and not self.respond_to?(:followers)
193
+ self
194
+ end
195
+
196
+ # Returns whether this <tt>Twitter::User</tt> model object
197
+ # represents the authenticated user of the <tt>client</tt>
198
+ # that blessed it.
199
+ def is_me?
200
+ # TODO: Determine whether we should cache this or not?
201
+ # Might be dangerous to do so, but do we want to support
202
+ # the edge case where this would cause a problem? i.e.
203
+ # changing authenticated user after initial use of
204
+ # authenticated API.
205
+ # TBD: To cache or not to cache. That is the question!
206
+ # Since this is an implementation detail we can leave this for
207
+ # subsequent 0.2.x releases. It doesn't have to be decided before
208
+ # the 0.2.0 launch.
209
+ @screen_name == @client.instance_eval("@login")
210
+ end
211
+
212
+ # Returns an Array of user objects that represents the authenticated
213
+ # user's friends on Twitter.
214
+ def friends
215
+ @client.user(@id, :friends)
216
+ end
217
+ end # User
218
+
219
+ # Represents a status posted to <tt>Twitter</tt> by a <tt>Twitter</tt> user.
220
+ class Status
221
+ include ModelMixin
222
+ @@ATTRIBUTES = [:id, :text, :created_at, :user]
223
+ attr_accessor *@@ATTRIBUTES
224
+
225
+ class << self
226
+ # Used as factory method callback
227
+ def attributes; @@ATTRIBUTES; end
228
+
229
+ # Returns status model object with given <tt>status</tt> using the
230
+ # configuration and credentials of the <tt>client</tt> object passed in.
231
+ def find(id, client)
232
+ client.status(:get, id)
233
+ end
234
+
235
+ # Creates a new status for the authenticated user of the given
236
+ # <tt>client</tt> context.
237
+ #
238
+ # You MUST include a valid/authenticated <tt>client</tt> context
239
+ # in the given <tt>params</tt> argument.
240
+ #
241
+ # For example:
242
+ # status = Twitter::Status.create(
243
+ # :text => 'I am shopping for flip flops',
244
+ # :client => client)
245
+ #
246
+ # An <tt>ArgumentError</tt> will be raised if no valid client context
247
+ # is given in the <tt>params</tt> Hash. For example,
248
+ # status = Twitter::Status.create(:text => 'I am shopping for flip flops')
249
+ # The above line of code will raise an <tt>ArgumentError</tt>.
250
+ #
251
+ # The same is true when you do not provide a <tt>:text</tt> key-value
252
+ # pair in the <tt>params</tt> argument given.
253
+ #
254
+ # The Twitter::Status object returned after the status successfully
255
+ # updates on the Twitter server side is returned from this method.
256
+ def create(params)
257
+ client, text = params[:client], params[:text]
258
+ raise ArgumentError, 'Valid client context must be provided' unless client.is_a?(Twitter::Client)
259
+ raise ArgumentError, 'Must provide text for the status to update' unless text.is_a?(String)
260
+ client.status(:post, text)
261
+ end
262
+ end
263
+
264
+ protected
265
+ # Constructor callback
266
+ def init
267
+ @user = User.new(@user) if @user.is_a?(Hash)
268
+ @created_at = Time.parse(@created_at) if @created_at.is_a?(String)
269
+ end
270
+ end # Status
271
+
272
+ # Represents a direct message on <tt>Twitter</tt> between <tt>Twitter</tt> users.
273
+ class Message
274
+ include ModelMixin
275
+ @@ATTRIBUTES = [:id, :recipient, :sender, :text, :created_at]
276
+ attr_accessor *@@ATTRIBUTES
277
+
278
+ class << self
279
+ # Used as factory method callback
280
+ def attributes; @@ATTRIBUTES; end
281
+
282
+ # Raises <tt>NotImplementedError</tt> because currently
283
+ # <tt>Twitter</tt> doesn't provide a facility to retrieve
284
+ # one message by unique ID.
285
+ def find(id, client)
286
+ raise NotImplementedError, 'Twitter has yet to implement a REST API for this. This is not a Twitter4R library limitation.'
287
+ end
288
+
289
+ # Creates a new direct message from the authenticated user of the
290
+ # given <tt>client</tt> context.
291
+ #
292
+ # You MUST include a valid/authenticated <tt>client</tt> context
293
+ # in the given <tt>params</tt> argument.
294
+ #
295
+ # For example:
296
+ # status = Twitter::Message.create(
297
+ # :text => 'I am shopping for flip flops',
298
+ # :receipient => 'anotherlogin',
299
+ # :client => client)
300
+ #
301
+ # An <tt>ArgumentError</tt> will be raised if no valid client context
302
+ # is given in the <tt>params</tt> Hash. For example,
303
+ # status = Twitter::Status.create(:text => 'I am shopping for flip flops')
304
+ # The above line of code will raise an <tt>ArgumentError</tt>.
305
+ #
306
+ # The same is true when you do not provide any of the following
307
+ # key-value pairs in the <tt>params</tt> argument given:
308
+ # * <tt>text</tt> - the String that will be the message text to send to <tt>user</tt>
309
+ # * <tt>recipient</tt> - the user ID, screen_name or Twitter::User object representation of the recipient of the direct message
310
+ #
311
+ # The Twitter::Message object returned after the direct message is
312
+ # successfully sent on the Twitter server side is returned from
313
+ # this method.
314
+ def create(params)
315
+ client, text, recipient = params[:client], params[:text], params[:recipient]
316
+ raise ArgumentError, 'Valid client context must be given' unless client.is_a?(Twitter::Client)
317
+ raise ArgumentError, 'Message text must be supplied to send direct message' unless text.is_a?(String)
318
+ raise ArgumentError, 'Recipient user must be specified to send direct message' unless [Twitter::User, Integer, String].member?(recipient.class)
319
+ client.message(:post, text, recipient)
320
+ end
321
+ end
322
+
323
+ protected
324
+ # Constructor callback
325
+ def init
326
+ @sender = User.new(@sender) if @sender.is_a?(Hash)
327
+ @recipient = User.new(@recipient) if @recipient.is_a?(Hash)
328
+ @created_at = Time.parse(@created_at) if @created_at.is_a?(String)
329
+ end
330
+ end # Message
331
+ end # Twitter
@@ -1,12 +1,17 @@
1
+ # version.rb contains <tt>Twitter::Version</tt> that provides helper
2
+ # methods related to versioning of the <tt>Twitter4R</tt> project.
3
+
1
4
  module Twitter::Version #:nodoc:
2
5
  MAJOR = 0
3
- MINOR = 1
4
- REVISION = 1
6
+ MINOR = 2
7
+ REVISION = 0
5
8
  class << self
9
+ # Returns X.Y.Z formatted version string
6
10
  def to_version
7
11
  "#{MAJOR}.#{MINOR}.#{REVISION}"
8
12
  end
9
13
 
14
+ # Returns X-Y-Z formatted version name
10
15
  def to_name
11
16
  "#{MAJOR}_#{MINOR}_#{REVISION}"
12
17
  end
data/lib/twitter.rb CHANGED
@@ -1,14 +1,25 @@
1
+ #
2
+
1
3
  module Twitter; end
2
4
 
3
- def resolve_path(suffix)
4
- File.expand_path(File.join(File.dirname(__FILE__), suffix))
5
+ def require_local(suffix)
6
+ require(File.expand_path(File.join(File.dirname(__FILE__), suffix)))
5
7
  end
6
8
 
7
9
  # For better unicode support
8
10
  $KCODE = 'u'
9
11
  require 'jcode'
10
12
 
13
+ # External requires
14
+ require('net/https')
15
+ require('uri')
16
+ require('json')
17
+
11
18
  # Ordering matters...pay attention here!
12
- require(resolve_path('twitter/version'))
13
- require(resolve_path('twitter/meta'))
14
- require(resolve_path('twitter/core'))
19
+ require_local('twitter/ext')
20
+ require_local('twitter/version')
21
+ require_local('twitter/meta')
22
+ require_local('twitter/core')
23
+ require_local('twitter/model')
24
+ require_local('twitter/config')
25
+ require_local('twitter/client')
data/spec/spec_helper.rb CHANGED
@@ -1,8 +1,52 @@
1
1
  require 'spec'
2
2
  require 'twitter'
3
+ require 'twitter/console'
4
+ require 'twitter/extras'
3
5
 
4
6
  # Add helper methods here if relevant to multiple _spec.rb files
5
7
 
8
+ # Spec helper that sets attribute <tt>att</tt> for given objects <tt>obj</tt>
9
+ # and <tt>other</tt> to given <tt>value</tt>.
10
+ def equalizer(obj, other, att, value)
11
+ setter = "#{att}="
12
+ obj.send(setter, value)
13
+ other.send(setter, value)
14
+ end
15
+
16
+ # Spec helper that nil-izes objects passed in
17
+ def nilize(*objects)
18
+ objects.each {|obj| obj = nil }
19
+ end
20
+
21
+ # Returns default <tt>client</tt> context object
22
+ def client_context(file = 'config/twitter.yml')
23
+ Twitter::Client.from_config(file)
24
+ end
25
+
26
+ # Spec helper that returns a mocked <tt>Twitter::Config</tt> object
27
+ # with stubbed attributes and <tt>attrs</tt> for overriding attribute
28
+ # values.
29
+ def stubbed_twitter_config(config, attrs = {})
30
+ opts = {
31
+ :protocol => :ssl,
32
+ :host => 'twitter.com',
33
+ :port => 443,
34
+ :proxy_host => 'proxy.host',
35
+ :proxy_port => 8080,
36
+ }.merge(attrs)
37
+ config.stub!(:protocol).and_return(opts[:protocol])
38
+ config.stub!(:host).and_return(opts[:host])
39
+ config.stub!(:port).and_return(opts[:port])
40
+ config.stub!(:proxy_host).and_return(opts[:proxy_host])
41
+ config.stub!(:proxy_port).and_return(opts[:proxy_port])
42
+ config
43
+ end
44
+
45
+ def mas_twitter_config(attrs = {})
46
+ config = mock(Twitter::Config)
47
+ stubbed_twitter_conf(config, attrs)
48
+ end
49
+
6
50
  # Spec helper that returns the project root directory as absolute path string
7
51
  def project_root_dir
8
52
  File.expand_path(File.join(File.dirname(__FILE__), '..'))
@@ -16,16 +60,14 @@ def stubbed_net_http(response, obj_stubs = {}, host = 'twitter.com', port = 80)
16
60
  http = Net::HTTP.new(host, port)
17
61
  Net::HTTP.stub!(:new).and_return(http)
18
62
  http.stub!(:request).and_return(response)
19
- http.stub!(:use_ssl=)
20
- http.stub!(:verify_mode=)
21
63
  http
22
64
  end
23
65
 
24
66
  # Spec helper that returns a mocked <tt>Net::HTTP</tt> object and
25
67
  # stubs out the <tt>request</tt> method to return the given
26
68
  # <tt>response</tt>
27
- def mas_net_http(response)
28
- http = mock(Net::HTTP)
69
+ def mas_net_http(response, obj_stubs = {})
70
+ http = mock(Net::HTTP, obj_stubs)
29
71
  Net::HTTP.stub!(:new).and_return(http)
30
72
  http.stub!(:request).and_return(response)
31
73
  http.stub!(:start).and_yield(http)
@@ -87,4 +129,5 @@ end
87
129
  def _create_http_response(mock_response, code, message)
88
130
  mock_response.stub!(:code).and_return(code)
89
131
  mock_response.stub!(:message).and_return(message)
132
+ mock_response.stub!(:is_a?).and_return(true) if ["200", "201"].member?(code)
90
133
  end
@@ -0,0 +1,232 @@
1
+ require File.join(File.dirname(__FILE__), '..', '..', 'spec_helper')
2
+
3
+ describe "Twitter::Client" do
4
+ before(:each) do
5
+ @init_hash = { :login => 'user', :password => 'pass' }
6
+ end
7
+
8
+ it ".new should accept login and password as initializer hash keys and set the values to instance values" do
9
+ client = nil
10
+ lambda do
11
+ client = Twitter::Client.new(@init_hash)
12
+ end.should_not raise_error
13
+ client.send(:login).should eql(@init_hash[:login])
14
+ client.send(:password).should eql(@init_hash[:password])
15
+ end
16
+ end
17
+
18
+ describe Twitter::Client, "#http_header" do
19
+ before(:each) do
20
+ @user_agent = 'myapp'
21
+ @application_name = @user_agent
22
+ @application_version = '1.2.3'
23
+ @application_url = 'http://myapp.url'
24
+ Twitter::Client.configure do |conf|
25
+ conf.user_agent = @user_agent
26
+ conf.application_name = @application_name
27
+ conf.application_version = @application_version
28
+ conf.application_url = @application_url
29
+ end
30
+ @expected_headers = {
31
+ 'Accept' => 'text/x-json',
32
+ 'X-Twitter-Client' => @application_name,
33
+ 'X-Twitter-Client-Version' => @application_version,
34
+ 'X-Twitter-Client-URL' => @application_url,
35
+ 'User-Agent' => "Twitter4R v#{Twitter::Version.to_version} [#{@user_agent}]",
36
+ }
37
+ @twitter = client_context
38
+ # resent @@http_header class variable in Twitter::Client class
39
+ Twitter::Client.class_eval("@@http_header = nil")
40
+ end
41
+
42
+ it "should always return expected HTTP headers" do
43
+ headers = @twitter.send(:http_header)
44
+ headers.should eql(@expected_headers)
45
+ end
46
+
47
+ it "should cache HTTP headers Hash in class variable after first invocation" do
48
+ cache = Twitter::Client.class_eval("@@http_header")
49
+ cache.should be_nil
50
+ @twitter.send(:http_header)
51
+ cache = Twitter::Client.class_eval("@@http_header")
52
+ cache.should_not be_nil
53
+ cache.should eql(@expected_headers)
54
+ end
55
+
56
+ after(:each) do
57
+ nilize(@user_agent, @application_name, @application_version, @application_url, @twitter, @expected_headers)
58
+ end
59
+ end
60
+
61
+ describe Twitter::Client, "#create_http_get_request" do
62
+ before(:each) do
63
+ @uri = '/some/path'
64
+ @expected_get_request = mock(Net::HTTP::Get)
65
+ @twitter = client_context
66
+ @default_header = @twitter.send(:http_header)
67
+ end
68
+
69
+ it "should create new Net::HTTP::Get object with expected initialization arguments" do
70
+ Net::HTTP::Get.should_receive(:new).with(@uri, @default_header).and_return(@expected_get_request)
71
+ @twitter.send(:create_http_get_request, @uri)
72
+ end
73
+
74
+ after(:each) do
75
+ nilize(@twitter, @uri, @expected_get_request, @default_header)
76
+ end
77
+ end
78
+
79
+ describe Twitter::Client, "#create_http_post_request" do
80
+ before(:each) do
81
+ @uri = '/some/path'
82
+ @expected_post_request = mock(Net::HTTP::Post)
83
+ @twitter = client_context
84
+ @default_header = @twitter.send(:http_header)
85
+ end
86
+
87
+ it "should create new Net::HTTP::Post object with expected initialization arguments" do
88
+ Net::HTTP::Post.should_receive(:new).with(@uri, @default_header).and_return(@expected_post_request)
89
+ @twitter.send(:create_http_post_request, @uri)
90
+ end
91
+
92
+ after(:each) do
93
+ nilize(@twitter, @uri, @expected_post_request, @default_header)
94
+ end
95
+ end
96
+
97
+ describe Twitter::Client, "#create_http_delete_request" do
98
+ before(:each) do
99
+ @uri = '/a/stupid/path/that/is/not/restful/since/twitter.com/cannot/do/consistent/restful/apis'
100
+ @expected_delete_request = mock(Net::HTTP::Delete)
101
+ @twitter = client_context
102
+ @default_header = @twitter.send(:http_header)
103
+ end
104
+
105
+ it "should create new Net::HTTP::Delete object with expected initialization arguments" do
106
+ Net::HTTP::Delete.should_receive(:new).with(@uri, @default_header).and_return(@expected_delete_request)
107
+ @twitter.send(:create_http_delete_request, @uri)
108
+ end
109
+
110
+ after(:each) do
111
+ nilize(@twitter, @uri, @expected_delete_request, @default_header)
112
+ end
113
+ end
114
+
115
+ describe Twitter::Client, "#http_connect" do
116
+ before(:each) do
117
+ @request = mas_net_http_get(:basic_auth => nil)
118
+ @good_response = mas_net_http_response(:success)
119
+ @bad_response = mas_net_http_response(:server_error)
120
+ @http_stubs = {:is_a? => true}
121
+ @block = Proc.new do |conn|
122
+ conn.is_a?(Net::HTTP).should be(true)
123
+ @has_yielded = true
124
+ @request
125
+ end
126
+ @twitter = client_context
127
+ @has_yielded = false
128
+ end
129
+
130
+ def generate_bad_response
131
+ @http = mas_net_http(@bad_response, @http_stubs)
132
+ Net::HTTP.stub!(:new).and_return(@http)
133
+ end
134
+
135
+ def generate_good_response
136
+ @http = mas_net_http(@good_response, @http_stubs)
137
+ Net::HTTP.stub!(:new).and_return(@http)
138
+ end
139
+
140
+ it "should yield HTTP connection when response is good" do
141
+ generate_good_response
142
+ @http.should_receive(:is_a?).with(Net::HTTP).and_return(true)
143
+ lambda do
144
+ @twitter.send(:http_connect, &@block)
145
+ end.should_not raise_error
146
+ @has_yielded.should be(true)
147
+ end
148
+
149
+ it "should yield HTTP connection when response is bad" do
150
+ generate_bad_response
151
+ @http.should_receive(:is_a?).with(Net::HTTP).and_return(true)
152
+ lambda {
153
+ @twitter.send(:http_connect, &@block)
154
+ }.should raise_error(Twitter::RESTError)
155
+ @has_yielded.should be(true)
156
+ end
157
+
158
+ after(:each) do
159
+ nilize(@good_response, @bad_response, @http)
160
+ end
161
+ end
162
+
163
+ describe Twitter::Client, "#bless_model" do
164
+ before(:each) do
165
+ @twitter = client_context
166
+ @model = Twitter::User.new
167
+ end
168
+
169
+ it "should recieve #client= message on given model to self" do
170
+ @model.should_receive(:client=).with(@twitter)
171
+ model = @twitter.send(:bless_model, @model)
172
+ end
173
+
174
+ it "should set client attribute on given model to self" do
175
+ model = @twitter.send(:bless_model, @model)
176
+ model.client.should eql(@twitter)
177
+ end
178
+
179
+ # if model is nil, it doesn't not necessarily signify an exceptional case for this method's usage.
180
+ it "should return nil when receiving nil and not raise any exceptions" do
181
+ model = @twitter.send(:bless_model, nil)
182
+ model.should be_nil
183
+ end
184
+
185
+ # needed to alert developer that the model needs to respond to #client= messages appropriately.
186
+ it "should raise an error if passing in a non-nil object that doesn't not respond to the :client= message" do
187
+ lambda {
188
+ @twitter.send(:bless_model, Object.new)
189
+ }.should raise_error(NoMethodError)
190
+ end
191
+
192
+ after(:each) do
193
+ nilize(@twitter)
194
+ end
195
+ end
196
+
197
+ describe Twitter::Client, "#bless_models" do
198
+ before(:each) do
199
+ @twitter = client_context
200
+ @models = [
201
+ Twitter::Status.new(:text => 'message #1'),
202
+ Twitter::Status.new(:text => 'message #2'),
203
+ ]
204
+ end
205
+
206
+ it "should set client attributes for each model in given Array to self" do
207
+ models = @twitter.send(:bless_models, @models)
208
+ models.each {|model| model.client.should eql(@twitter) }
209
+ end
210
+
211
+ it "should set client attribute for singular model given to self" do
212
+ model = @twitter.send(:bless_models, @models[0])
213
+ model.client.should eql(@twitter)
214
+ end
215
+
216
+ it "should delegate to bless_model for singular model case" do
217
+ model = @models[0]
218
+ @twitter.should_receive(:bless_model).with(model).and_return(model)
219
+ @twitter.send(:bless_models, model)
220
+ end
221
+
222
+ it "should return nil when receiving nil and not raise any exceptions" do
223
+ lambda {
224
+ value = @twitter.send(:bless_models, nil)
225
+ value.should be_nil
226
+ }.should_not raise_error
227
+ end
228
+
229
+ after(:each) do
230
+ nilize(@twitter, @models)
231
+ end
232
+ end