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.
- 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
|
+
|