twitter4r 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/twitter.rb +14 -0
- data/lib/twitter/core.rb +179 -0
- data/lib/twitter/meta.rb +42 -0
- data/lib/twitter/version.rb +14 -0
- data/spec/spec_helper.rb +86 -0
- data/spec/twitter/core_spec.rb +430 -0
- data/spec/twitter/meta_spec.rb +96 -0
- data/spec/twitter/version_spec.rb +19 -0
- metadata +53 -0
data/lib/twitter.rb
ADDED
@@ -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'))
|
data/lib/twitter/core.rb
ADDED
@@ -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
|
data/lib/twitter/meta.rb
ADDED
@@ -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
|
+
|
data/spec/spec_helper.rb
ADDED
@@ -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
|
+
|