tweetwine 0.2.4
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.rdoc +84 -0
- data/MIT-LICENSE.txt +19 -0
- data/README.rdoc +87 -0
- data/Rakefile +86 -0
- data/bin/tweetwine +79 -0
- data/lib/tweetwine/client.rb +174 -0
- data/lib/tweetwine/io.rb +117 -0
- data/lib/tweetwine/meta.rb +3 -0
- data/lib/tweetwine/options.rb +22 -0
- data/lib/tweetwine/rest_client_wrapper.rb +37 -0
- data/lib/tweetwine/startup_config.rb +40 -0
- data/lib/tweetwine/url_shortener.rb +38 -0
- data/lib/tweetwine/util.rb +52 -0
- data/lib/tweetwine.rb +12 -0
- data/test/client_test.rb +475 -0
- data/test/io_test.rb +265 -0
- data/test/options_test.rb +43 -0
- data/test/rest_client_wrapper_test.rb +68 -0
- data/test/startup_config_test.rb +87 -0
- data/test/test_config.yaml +3 -0
- data/test/test_helper.rb +57 -0
- data/test/url_shortener_test.rb +161 -0
- data/test/util_test.rb +55 -0
- metadata +93 -0
@@ -0,0 +1,37 @@
|
|
1
|
+
require "rest_client"
|
2
|
+
|
3
|
+
module Tweetwine
|
4
|
+
class ClientError < RuntimeError; end
|
5
|
+
|
6
|
+
class RestClientWrapper
|
7
|
+
instance_methods.each { |m| undef_method m unless m =~ /(^__|^send$|^object_id$)/ }
|
8
|
+
|
9
|
+
MAX_RETRIES = 3
|
10
|
+
RETRY_BASE_WAIT_TIMEOUT = 4
|
11
|
+
|
12
|
+
def initialize(io)
|
13
|
+
@io = io
|
14
|
+
end
|
15
|
+
|
16
|
+
protected
|
17
|
+
|
18
|
+
def method_missing(name, *args, &block)
|
19
|
+
tries = 0
|
20
|
+
begin
|
21
|
+
tries += 1
|
22
|
+
RestClient.send(name, *args, &block)
|
23
|
+
rescue Errno::ECONNRESET => e
|
24
|
+
if tries < MAX_RETRIES
|
25
|
+
timeout = RETRY_BASE_WAIT_TIMEOUT**tries
|
26
|
+
@io.warn("Could not connect -- retrying in #{timeout} seconds")
|
27
|
+
sleep timeout
|
28
|
+
retry
|
29
|
+
else
|
30
|
+
raise ClientError, e
|
31
|
+
end
|
32
|
+
rescue RestClient::Exception, SocketError, SystemCallError => e
|
33
|
+
raise ClientError, e
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require "yaml"
|
2
|
+
|
3
|
+
module Tweetwine
|
4
|
+
class StartupConfig
|
5
|
+
attr_reader :options, :command, :args, :supported_commands
|
6
|
+
|
7
|
+
def initialize(supported_commands)
|
8
|
+
@supported_commands = supported_commands.to_a
|
9
|
+
raise ArgumentError, "Must give at least one supported command" if @supported_commands.empty?
|
10
|
+
@options = {}
|
11
|
+
@command = @supported_commands.first
|
12
|
+
@args = []
|
13
|
+
end
|
14
|
+
|
15
|
+
def parse(args = [], config_file = nil, &cmd_parser)
|
16
|
+
options = parse_options(args, config_file, &cmd_parser)
|
17
|
+
command = if args.empty? then @supported_commands.first else args.shift.to_sym end
|
18
|
+
raise ArgumentError, "Unknown command" unless @supported_commands.include? command
|
19
|
+
@options, @command, @args = options, command, args
|
20
|
+
self
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def parse_options(args, config_file, &cmd_parser)
|
26
|
+
cmd_options = if cmd_parser then parse_cmdline_args(args, &cmd_parser) else {} end
|
27
|
+
config_options = if config_file && File.exists?(config_file) then parse_config_file(config_file) else {} end
|
28
|
+
config_options.merge(cmd_options)
|
29
|
+
end
|
30
|
+
|
31
|
+
def parse_cmdline_args(args, &cmd_parser)
|
32
|
+
cmd_parser.call(args)
|
33
|
+
end
|
34
|
+
|
35
|
+
def parse_config_file(config_file)
|
36
|
+
options = YAML.load(File.read(config_file))
|
37
|
+
Util.symbolize_hash_keys(options)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Tweetwine
|
2
|
+
class UrlShortener
|
3
|
+
def initialize(rest_client, options)
|
4
|
+
@rest_client = rest_client
|
5
|
+
options = Options.new(options, "URL shortening")
|
6
|
+
@method = (options[:method] || :get).to_sym
|
7
|
+
@service_url = options.require :service_url
|
8
|
+
@url_param_name = options.require :url_param_name
|
9
|
+
@extra_params = options[:extra_params] || {}
|
10
|
+
if @method == :get
|
11
|
+
tmp = []
|
12
|
+
@extra_params.each_pair { |k, v| tmp << "#{k}=#{v}" }
|
13
|
+
@extra_params = tmp
|
14
|
+
end
|
15
|
+
@xpath_selector = options.require :xpath_selector
|
16
|
+
end
|
17
|
+
|
18
|
+
def shorten(url)
|
19
|
+
require "nokogiri"
|
20
|
+
rest = case @method
|
21
|
+
when :get
|
22
|
+
tmp = @extra_params.dup
|
23
|
+
tmp << "#{@url_param_name}=#{url}"
|
24
|
+
service_url = "#{@service_url}?#{tmp.join('&')}"
|
25
|
+
[service_url]
|
26
|
+
when :post
|
27
|
+
service_url = @service_url
|
28
|
+
params = @extra_params.merge({ @url_param_name.to_sym => url })
|
29
|
+
[service_url, params]
|
30
|
+
else
|
31
|
+
raise "Unrecognized HTTP request method"
|
32
|
+
end
|
33
|
+
response = @rest_client.send(@method, *rest)
|
34
|
+
doc = Nokogiri::HTML(response)
|
35
|
+
doc.xpath(@xpath_selector).first.to_s
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require "time"
|
2
|
+
|
3
|
+
module Tweetwine
|
4
|
+
module Util
|
5
|
+
def self.humanize_time_diff(from, to)
|
6
|
+
from = Time.parse(from.to_s) unless from.is_a? Time
|
7
|
+
to = Time.parse(to.to_s) unless to.is_a? Time
|
8
|
+
|
9
|
+
difference = (to - from).to_i.abs
|
10
|
+
|
11
|
+
value, unit = case difference
|
12
|
+
when 0..59 then [difference, "sec"]
|
13
|
+
when 60..3599 then [(difference/60.0).round, "min"]
|
14
|
+
when 3600..86399 then [(difference/3600.0).round, "hour"]
|
15
|
+
else [(difference/86400.0).round, "day"]
|
16
|
+
end
|
17
|
+
|
18
|
+
[value, pluralize_unit(value, unit)]
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.symbolize_hash_keys(hash)
|
22
|
+
hash.inject({}) do |result, pair|
|
23
|
+
value = pair.last
|
24
|
+
value = symbolize_hash_keys(value) if value.is_a? Hash
|
25
|
+
result[pair.first.to_sym] = value
|
26
|
+
result
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.parse_int_gt(value, default, min, name_for_error)
|
31
|
+
if value
|
32
|
+
value = value.to_i
|
33
|
+
if value >= min
|
34
|
+
value
|
35
|
+
else
|
36
|
+
raise ArgumentError, "Invalid #{name_for_error} -- must be greater than or equal to #{min}"
|
37
|
+
end
|
38
|
+
else
|
39
|
+
default
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def self.pluralize_unit(value, unit)
|
46
|
+
if ["hour", "day"].include?(unit) && value > 1
|
47
|
+
unit = unit + "s"
|
48
|
+
end
|
49
|
+
unit
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
data/lib/tweetwine.rb
ADDED
data/test/client_test.rb
ADDED
@@ -0,0 +1,475 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
require "json"
|
3
|
+
|
4
|
+
Mocha::Configuration.allow(:stubbing_non_existent_method)
|
5
|
+
|
6
|
+
module Tweetwine
|
7
|
+
|
8
|
+
class ClientTest < Test::Unit::TestCase
|
9
|
+
context "A client instance" do
|
10
|
+
setup do
|
11
|
+
@io = mock()
|
12
|
+
@rest_client = mock()
|
13
|
+
@url_shortener = mock()
|
14
|
+
url_shortener = lambda { |options| @url_shortener }
|
15
|
+
@deps = {
|
16
|
+
:io => @io,
|
17
|
+
:rest_client => @rest_client,
|
18
|
+
:url_shortener => url_shortener
|
19
|
+
}
|
20
|
+
end
|
21
|
+
|
22
|
+
context "upon initialization" do
|
23
|
+
should "raise exception when no authentication data is given" do
|
24
|
+
assert_raise(ArgumentError) { Client.new(@deps, {}) }
|
25
|
+
assert_raise(ArgumentError) { Client.new(@deps, { :password => "bar" }) }
|
26
|
+
assert_raise(ArgumentError) { Client.new(@deps, { :username => "", :password => "bar" }) }
|
27
|
+
assert_nothing_raised { Client.new(@deps, { :username => "foo", :password => "bar" }) }
|
28
|
+
end
|
29
|
+
|
30
|
+
should "use default number of statuses if not configured otherwise" do
|
31
|
+
@client = Client.new(@deps, { :username => "foo", :password => "bar" })
|
32
|
+
assert_equal Client::DEFAULT_NUM_STATUSES, @client.num_statuses
|
33
|
+
end
|
34
|
+
|
35
|
+
should "use configured number of statuses if in allowed range" do
|
36
|
+
@client = Client.new(@deps, { :username => "foo", :password => "bar", :num_statuses => 12 })
|
37
|
+
assert_equal 12, @client.num_statuses
|
38
|
+
end
|
39
|
+
|
40
|
+
should "raise an exception for configured number of statuses if not in allowed range" do
|
41
|
+
assert_raise(ArgumentError) { Client.new(@deps, { :username => "foo", :password => "bar", :num_statuses => 0 }) }
|
42
|
+
end
|
43
|
+
|
44
|
+
should "use default page number if not configured otherwise" do
|
45
|
+
@client = Client.new(@deps, { :username => "foo", :password => "bar" })
|
46
|
+
assert_equal Client::DEFAULT_PAGE_NUM, @client.page_num
|
47
|
+
end
|
48
|
+
|
49
|
+
should "use configured page number if in allowed range" do
|
50
|
+
@client = Client.new(@deps, { :username => "foo", :password => "bar", :page_num => 12 })
|
51
|
+
assert_equal 12, @client.page_num
|
52
|
+
end
|
53
|
+
|
54
|
+
should "raise an exception for configured page number if not in allowed range" do
|
55
|
+
assert_raise(ArgumentError) { Client.new(@deps, { :username => "foo", :password => "bar", :page_num => 0 }) }
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
context "at runtime" do
|
60
|
+
setup do
|
61
|
+
@username = "spiky"
|
62
|
+
@password = "lullaby"
|
63
|
+
@client = Client.new(@deps, { :username => @username, :password => @password })
|
64
|
+
@base_url = "https://#{@username}:#{@password}@twitter.com"
|
65
|
+
@statuses_query_params = "count=#{Client::DEFAULT_NUM_STATUSES}&page=#{Client::DEFAULT_PAGE_NUM}"
|
66
|
+
@users_query_params = "page=#{Client::DEFAULT_PAGE_NUM}"
|
67
|
+
end
|
68
|
+
|
69
|
+
should "fetch friends' statuses (home view)" do
|
70
|
+
status_records, gen_records = create_test_statuses(
|
71
|
+
{
|
72
|
+
:user => "zanzibar",
|
73
|
+
:status => {
|
74
|
+
:created_at => Time.at(1).to_s,
|
75
|
+
:text => "wassup?",
|
76
|
+
:in_reply_to => nil
|
77
|
+
}
|
78
|
+
},
|
79
|
+
{
|
80
|
+
:user => "lulzwoo",
|
81
|
+
:status => {
|
82
|
+
:created_at => Time.at(1).to_s,
|
83
|
+
:text => "nuttin'",
|
84
|
+
:in_reply_to => nil
|
85
|
+
}
|
86
|
+
}
|
87
|
+
)
|
88
|
+
@rest_client.expects(:get) \
|
89
|
+
.with("#{@base_url}/statuses/friends_timeline.json?#{@statuses_query_params}") \
|
90
|
+
.returns(status_records.to_json)
|
91
|
+
@io.expects(:show_record).with(gen_records[0])
|
92
|
+
@io.expects(:show_record).with(gen_records[1])
|
93
|
+
@client.home
|
94
|
+
end
|
95
|
+
|
96
|
+
should "fetch mentions" do
|
97
|
+
status_records, gen_records = create_test_statuses(
|
98
|
+
{
|
99
|
+
:user => "zanzibar",
|
100
|
+
:status => {
|
101
|
+
:created_at => Time.at(1).to_s,
|
102
|
+
:text => "wassup, @#{@username}?",
|
103
|
+
:in_reply_to => @username
|
104
|
+
}
|
105
|
+
},
|
106
|
+
{
|
107
|
+
:user => "lulzwoo",
|
108
|
+
:status => {
|
109
|
+
:created_at => Time.at(1).to_s,
|
110
|
+
:text => "@#{@username}, doing nuttin'",
|
111
|
+
:in_reply_to => @username
|
112
|
+
}
|
113
|
+
}
|
114
|
+
)
|
115
|
+
@rest_client.expects(:get) \
|
116
|
+
.with("#{@base_url}/statuses/mentions.json?#{@statuses_query_params}") \
|
117
|
+
.returns(status_records.to_json)
|
118
|
+
@io.expects(:show_record).with(gen_records[0])
|
119
|
+
@io.expects(:show_record).with(gen_records[1])
|
120
|
+
@client.mentions
|
121
|
+
end
|
122
|
+
|
123
|
+
should "fetch a specific user's statuses, when the user identified by given argument" do
|
124
|
+
user = "spoonman"
|
125
|
+
status_records, gen_records = create_test_statuses(
|
126
|
+
{
|
127
|
+
:user => user,
|
128
|
+
:status => {
|
129
|
+
:created_at => Time.at(1).to_s,
|
130
|
+
:text => "wassup?",
|
131
|
+
:in_reply_to => nil
|
132
|
+
}
|
133
|
+
}
|
134
|
+
)
|
135
|
+
@rest_client.expects(:get) \
|
136
|
+
.with("#{@base_url}/statuses/user_timeline/#{user}.json?#{@statuses_query_params}") \
|
137
|
+
.returns(status_records.to_json)
|
138
|
+
@io.expects(:show_record).with(gen_records[0])
|
139
|
+
@client.user(user)
|
140
|
+
end
|
141
|
+
|
142
|
+
should "fetch a specific user's statuses, with the user being the authenticated user itself when given no argument" do
|
143
|
+
status_records, gen_records = create_test_statuses(
|
144
|
+
{
|
145
|
+
:user => @username,
|
146
|
+
:status => {
|
147
|
+
:created_at => Time.at(1).to_s,
|
148
|
+
:text => "wassup?",
|
149
|
+
:in_reply_to => nil
|
150
|
+
}
|
151
|
+
}
|
152
|
+
)
|
153
|
+
@rest_client.expects(:get) \
|
154
|
+
.with("#{@base_url}/statuses/user_timeline/#{@username}.json?#{@statuses_query_params}") \
|
155
|
+
.returns(status_records.to_json)
|
156
|
+
@io.expects(:show_record).with(gen_records[0])
|
157
|
+
@client.user
|
158
|
+
end
|
159
|
+
|
160
|
+
context "for posting status updates" do
|
161
|
+
should "post a status update via argument, when positive confirmation" do
|
162
|
+
status = "wondering around"
|
163
|
+
status_records, gen_records = create_test_statuses(
|
164
|
+
{
|
165
|
+
:user => @username,
|
166
|
+
:status => {
|
167
|
+
:created_at => Time.at(1).to_s,
|
168
|
+
:text => status,
|
169
|
+
:in_reply_to => nil
|
170
|
+
}
|
171
|
+
}
|
172
|
+
)
|
173
|
+
@rest_client.expects(:post) \
|
174
|
+
.with("#{@base_url}/statuses/update.json", {:status => status}) \
|
175
|
+
.returns(status_records[0].to_json)
|
176
|
+
@io.expects(:confirm).with("Really send?").returns(true)
|
177
|
+
@io.expects(:show_status_preview).with(status)
|
178
|
+
@io.expects(:info).with("Sent status update.\n\n")
|
179
|
+
@io.expects(:show_record).with(gen_records[0])
|
180
|
+
@client.update(status)
|
181
|
+
end
|
182
|
+
|
183
|
+
should "post a status update via prompt, when positive confirmation" do
|
184
|
+
status = "wondering around"
|
185
|
+
status_records, gen_records = create_test_statuses(
|
186
|
+
{ :user => @username,
|
187
|
+
:status => {
|
188
|
+
:created_at => Time.at(1).to_s,
|
189
|
+
:text => status,
|
190
|
+
:in_reply_to => nil
|
191
|
+
}
|
192
|
+
}
|
193
|
+
)
|
194
|
+
@rest_client.expects(:post) \
|
195
|
+
.with("#{@base_url}/statuses/update.json", {:status => status}) \
|
196
|
+
.returns(status_records[0].to_json)
|
197
|
+
@io.expects(:prompt).with("Status update").returns(status)
|
198
|
+
@io.expects(:show_status_preview).with(status)
|
199
|
+
@io.expects(:confirm).with("Really send?").returns(true)
|
200
|
+
@io.expects(:info).with("Sent status update.\n\n")
|
201
|
+
@io.expects(:show_record).with(gen_records[0])
|
202
|
+
@client.update
|
203
|
+
end
|
204
|
+
|
205
|
+
should "cancel a status update via argument, when negative confirmation" do
|
206
|
+
status = "wondering around"
|
207
|
+
@rest_client.expects(:post).never
|
208
|
+
@io.expects(:show_status_preview).with(status)
|
209
|
+
@io.expects(:confirm).with("Really send?").returns(false)
|
210
|
+
@io.expects(:info).with("Cancelled.")
|
211
|
+
@io.expects(:show_record).never
|
212
|
+
@client.update(status)
|
213
|
+
end
|
214
|
+
|
215
|
+
should "cancel a status update via prompt, when negative confirmation" do
|
216
|
+
status = "wondering around"
|
217
|
+
@rest_client.expects(:post).never
|
218
|
+
@io.expects(:prompt).with("Status update").returns(status)
|
219
|
+
@io.expects(:show_status_preview).with(status)
|
220
|
+
@io.expects(:confirm).with("Really send?").returns(false)
|
221
|
+
@io.expects(:info).with("Cancelled.")
|
222
|
+
@io.expects(:show_record).never
|
223
|
+
@client.update
|
224
|
+
end
|
225
|
+
|
226
|
+
should "cancel a status update via argument, when empty status" do
|
227
|
+
@rest_client.expects(:post).never
|
228
|
+
@io.expects(:confirm).never
|
229
|
+
@io.expects(:info).with("Cancelled.")
|
230
|
+
@io.expects(:show_record).never
|
231
|
+
@client.update("")
|
232
|
+
end
|
233
|
+
|
234
|
+
should "cancel a status update via prompt, when empty status" do
|
235
|
+
@rest_client.expects(:post).never
|
236
|
+
@io.expects(:prompt).with("Status update").returns("")
|
237
|
+
@io.expects(:confirm).never
|
238
|
+
@io.expects(:info).with("Cancelled.")
|
239
|
+
@io.expects(:show_record).never
|
240
|
+
@client.update
|
241
|
+
end
|
242
|
+
|
243
|
+
should "remove excess whitespace around a status update" do
|
244
|
+
whitespaced_status = " oh, i was sloppy \t "
|
245
|
+
stripped_status = "oh, i was sloppy"
|
246
|
+
status_records, gen_records = create_test_statuses(
|
247
|
+
{ :user => @username,
|
248
|
+
:status => {
|
249
|
+
:created_at => Time.at(1).to_s,
|
250
|
+
:text => stripped_status,
|
251
|
+
:in_reply_to => nil
|
252
|
+
}
|
253
|
+
}
|
254
|
+
)
|
255
|
+
@rest_client.expects(:post) \
|
256
|
+
.with("#{@base_url}/statuses/update.json", {:status => stripped_status}) \
|
257
|
+
.returns(status_records[0].to_json)
|
258
|
+
@io.expects(:show_status_preview).with(stripped_status)
|
259
|
+
@io.expects(:confirm).with("Really send?").returns(true)
|
260
|
+
@io.expects(:info).with("Sent status update.\n\n")
|
261
|
+
@io.expects(:show_record).with(gen_records[0])
|
262
|
+
@client.update(whitespaced_status)
|
263
|
+
end
|
264
|
+
|
265
|
+
should "truncate a status update with too long argument and warn the user" do
|
266
|
+
long_status = "x aaa bbb ccc ddd eee fff ggg hhh iii jjj kkk lll mmm nnn ooo ppp qqq rrr sss ttt uuu vvv www xxx yyy zzz 111 222 333 444 555 666 777 888 999 000"
|
267
|
+
truncated_status = "x aaa bbb ccc ddd eee fff ggg hhh iii jjj kkk lll mmm nnn ooo ppp qqq rrr sss ttt uuu vvv www xxx yyy zzz 111 222 333 444 555 666 777 888 99"
|
268
|
+
status_records, gen_records = create_test_statuses(
|
269
|
+
{ :user => @username,
|
270
|
+
:status => {
|
271
|
+
:created_at => Time.at(1).to_s,
|
272
|
+
:text => truncated_status,
|
273
|
+
:in_reply_to => nil
|
274
|
+
}
|
275
|
+
}
|
276
|
+
)
|
277
|
+
@rest_client.expects(:post) \
|
278
|
+
.with("#{@base_url}/statuses/update.json", {:status => truncated_status}) \
|
279
|
+
.returns(status_records[0].to_json)
|
280
|
+
@io.expects(:warn).with("Status will be truncated.")
|
281
|
+
@io.expects(:show_status_preview).with(truncated_status)
|
282
|
+
@io.expects(:confirm).with("Really send?").returns(true)
|
283
|
+
@io.expects(:info).with("Sent status update.\n\n")
|
284
|
+
@io.expects(:show_record).with(gen_records[0])
|
285
|
+
@client.update(long_status)
|
286
|
+
end
|
287
|
+
|
288
|
+
context "with URL shortening enabled" do
|
289
|
+
setup do
|
290
|
+
@client = Client.new(@deps, {
|
291
|
+
:username => @username,
|
292
|
+
:password => @password,
|
293
|
+
:shorten_urls => {
|
294
|
+
:enable => true,
|
295
|
+
:service_url => "http://shorten.it/create",
|
296
|
+
:method => "post",
|
297
|
+
:url_param_name => "url",
|
298
|
+
:xpath_selector => "//input[@id='short_url']/@value"
|
299
|
+
}
|
300
|
+
})
|
301
|
+
end
|
302
|
+
|
303
|
+
should "shorten URLs, avoiding truncation with long URLs" do
|
304
|
+
long_urls = ["http://www.google.fi/search?q=ruby+nokogiri&ie=utf-8&oe=utf-8&aq=t&rls=org.mozilla:en-US:official&client=firefox-a", "http://www.w3.org/TR/1999/REC-xpath-19991116"]
|
305
|
+
long_status = long_urls.join(" and ")
|
306
|
+
short_urls = ["http://shorten.it/2k7i8", "http://shorten.it/2k7mk"]
|
307
|
+
shortened_status = short_urls.join(" and ")
|
308
|
+
status_records, gen_records = create_test_statuses(
|
309
|
+
{ :user => @username,
|
310
|
+
:status => {
|
311
|
+
:created_at => Time.at(1).to_s,
|
312
|
+
:text => shortened_status,
|
313
|
+
:in_reply_to => nil
|
314
|
+
}
|
315
|
+
}
|
316
|
+
)
|
317
|
+
@rest_client.expects(:post) \
|
318
|
+
.with("#{@base_url}/statuses/update.json", {:status => shortened_status}) \
|
319
|
+
.returns(status_records[0].to_json)
|
320
|
+
@url_shortener.expects(:shorten).with(long_urls.first).returns(short_urls.first)
|
321
|
+
@url_shortener.expects(:shorten).with(long_urls.last).returns(short_urls.last)
|
322
|
+
@io.expects(:show_status_preview).with(shortened_status)
|
323
|
+
@io.expects(:confirm).with("Really send?").returns(true)
|
324
|
+
@io.expects(:info).with("Sent status update.\n\n")
|
325
|
+
@io.expects(:show_record).with(gen_records[0])
|
326
|
+
@client.update(long_status)
|
327
|
+
end
|
328
|
+
|
329
|
+
should "discard obviously invalid shortened URLs, using originals instead" do
|
330
|
+
long_urls = ["http://www.google.fi/", "http://www.w3.org/TR/1999/REC-xpath-19991116"]
|
331
|
+
status = long_urls.join(" and ")
|
332
|
+
short_urls = [nil, ""]
|
333
|
+
status_records, gen_records = create_test_statuses(
|
334
|
+
{ :user => @username,
|
335
|
+
:status => {
|
336
|
+
:created_at => Time.at(1).to_s,
|
337
|
+
:text => status,
|
338
|
+
:in_reply_to => nil
|
339
|
+
}
|
340
|
+
}
|
341
|
+
)
|
342
|
+
@rest_client.expects(:post) \
|
343
|
+
.with("#{@base_url}/statuses/update.json", {:status => status}) \
|
344
|
+
.returns(status_records[0].to_json)
|
345
|
+
@url_shortener.expects(:shorten).with(long_urls.first).returns(short_urls.first)
|
346
|
+
@url_shortener.expects(:shorten).with(long_urls.last).returns(short_urls.last)
|
347
|
+
@io.expects(:show_status_preview).with(status)
|
348
|
+
@io.expects(:confirm).with("Really send?").returns(true)
|
349
|
+
@io.expects(:info).with("Sent status update.\n\n")
|
350
|
+
@io.expects(:show_record).with(gen_records[0])
|
351
|
+
@client.update(status)
|
352
|
+
end
|
353
|
+
|
354
|
+
should "reuse a shortened URL for duplicate long URLs" do
|
355
|
+
long_urls = ["http://www.w3.org/TR/1999/REC-xpath-19991116"] * 2
|
356
|
+
long_status = long_urls.join(" and ")
|
357
|
+
short_url = "http://shorten.it/2k7mk"
|
358
|
+
short_status = ([short_url] * 2).join(" and ")
|
359
|
+
status_records, gen_records = create_test_statuses(
|
360
|
+
{ :user => @username,
|
361
|
+
:status => {
|
362
|
+
:created_at => Time.at(1).to_s,
|
363
|
+
:text => short_status,
|
364
|
+
:in_reply_to => nil
|
365
|
+
}
|
366
|
+
}
|
367
|
+
)
|
368
|
+
@rest_client.expects(:post) \
|
369
|
+
.with("#{@base_url}/statuses/update.json", {:status => short_status}) \
|
370
|
+
.returns(status_records[0].to_json)
|
371
|
+
@url_shortener.expects(:shorten).with(long_urls.first).returns(short_url)
|
372
|
+
@io.expects(:show_status_preview).with(short_status)
|
373
|
+
@io.expects(:confirm).with("Really send?").returns(true)
|
374
|
+
@io.expects(:info).with("Sent status update.\n\n")
|
375
|
+
@io.expects(:show_record).with(gen_records[0])
|
376
|
+
@client.update(long_status)
|
377
|
+
end
|
378
|
+
|
379
|
+
context "in erroneous situations" do
|
380
|
+
setup do
|
381
|
+
@url = "http://www.w3.org/TR/1999/REC-xpath-19991116"
|
382
|
+
@status = "skimming through #{@url}"
|
383
|
+
@status_records, @gen_records = create_test_statuses(
|
384
|
+
{ :user => @username,
|
385
|
+
:status => {
|
386
|
+
:created_at => Time.at(1).to_s,
|
387
|
+
:text => @status,
|
388
|
+
:in_reply_to => nil
|
389
|
+
}
|
390
|
+
}
|
391
|
+
)
|
392
|
+
end
|
393
|
+
|
394
|
+
should "skip shortening URLs if required libraries are not found" do
|
395
|
+
@rest_client.expects(:post) \
|
396
|
+
.with("#{@base_url}/statuses/update.json", {:status => @status}) \
|
397
|
+
.returns(@status_records[0].to_json)
|
398
|
+
@url_shortener.expects(:shorten).with(@url).raises(LoadError, "gem not found")
|
399
|
+
@io.expects(:warn)
|
400
|
+
@io.expects(:show_status_preview).with(@status)
|
401
|
+
@io.expects(:confirm).with("Really send?").returns(true)
|
402
|
+
@io.expects(:info).with("Sent status update.\n\n")
|
403
|
+
@io.expects(:show_record).with(@gen_records[0])
|
404
|
+
@client.update(@status)
|
405
|
+
end
|
406
|
+
|
407
|
+
should "skip shortening URLs upon connection error to the URL shortening service" do
|
408
|
+
@rest_client.expects(:post) \
|
409
|
+
.with("#{@base_url}/statuses/update.json", {:status => @status}) \
|
410
|
+
.returns(@status_records[0].to_json)
|
411
|
+
@url_shortener.expects(:shorten).with(@url).raises(ClientError, "connection error")
|
412
|
+
@io.expects(:warn)
|
413
|
+
@io.expects(:show_status_preview).with(@status)
|
414
|
+
@io.expects(:confirm).with("Really send?").returns(true)
|
415
|
+
@io.expects(:info).with("Sent status update.\n\n")
|
416
|
+
@io.expects(:show_record).with(@gen_records[0])
|
417
|
+
@client.update(@status)
|
418
|
+
end
|
419
|
+
end
|
420
|
+
end
|
421
|
+
end
|
422
|
+
|
423
|
+
should "fetch friends" do
|
424
|
+
user_records, gen_records = create_test_users(
|
425
|
+
{
|
426
|
+
:user => "zanzibar",
|
427
|
+
:status => {
|
428
|
+
:created_at => Time.at(1).to_s,
|
429
|
+
:text => "wassup, @foo?",
|
430
|
+
:in_reply_to => "foo"
|
431
|
+
}
|
432
|
+
},
|
433
|
+
{
|
434
|
+
:user => "lulzwoo",
|
435
|
+
:status => {
|
436
|
+
:created_at => Time.at(1).to_s,
|
437
|
+
:text => "@foo, doing nuttin'",
|
438
|
+
:in_reply_to => "foo"
|
439
|
+
}
|
440
|
+
}
|
441
|
+
)
|
442
|
+
@rest_client.expects(:get) \
|
443
|
+
.with("#{@base_url}/statuses/friends/#{@username}.json?#{@users_query_params}") \
|
444
|
+
.returns(user_records.to_json)
|
445
|
+
@io.expects(:show_record).with(gen_records[0])
|
446
|
+
@io.expects(:show_record).with(gen_records[1])
|
447
|
+
@client.friends
|
448
|
+
end
|
449
|
+
|
450
|
+
should "fetch followers" do
|
451
|
+
user_records, gen_records = create_test_users(
|
452
|
+
{
|
453
|
+
:user => "zanzibar",
|
454
|
+
:status => {
|
455
|
+
:created_at => Time.at(1).to_s,
|
456
|
+
:text => "wassup, @foo?",
|
457
|
+
:in_reply_to => "foo"
|
458
|
+
}
|
459
|
+
},
|
460
|
+
{
|
461
|
+
:user => "lulzwoo"
|
462
|
+
}
|
463
|
+
)
|
464
|
+
@rest_client.expects(:get) \
|
465
|
+
.with("#{@base_url}/statuses/followers/#{@username}.json?#{@users_query_params}") \
|
466
|
+
.returns(user_records.to_json)
|
467
|
+
@io.expects(:show_record).with(gen_records[0])
|
468
|
+
@io.expects(:show_record).with(gen_records[1])
|
469
|
+
@client.followers
|
470
|
+
end
|
471
|
+
end
|
472
|
+
end
|
473
|
+
end
|
474
|
+
|
475
|
+
end
|