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
data/test/io_test.rb
ADDED
@@ -0,0 +1,265 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
module Tweetwine
|
4
|
+
|
5
|
+
class IOTest < Test::Unit::TestCase
|
6
|
+
context "An IO instance" do
|
7
|
+
setup do
|
8
|
+
@input = mock()
|
9
|
+
@output = mock()
|
10
|
+
@io = IO.new({ :input => @input, :output => @output })
|
11
|
+
end
|
12
|
+
|
13
|
+
should "output prompt and return input as trimmed" do
|
14
|
+
@output.expects(:print).with("The answer: ")
|
15
|
+
@input.expects(:gets).returns(" 42 ")
|
16
|
+
assert_equal "42", @io.prompt("The answer")
|
17
|
+
end
|
18
|
+
|
19
|
+
should "output info message" do
|
20
|
+
@output.expects(:puts).with("foo")
|
21
|
+
@io.info("foo")
|
22
|
+
end
|
23
|
+
|
24
|
+
should "output warning message" do
|
25
|
+
@output.expects(:puts).with("Warning: monkey patching ahead")
|
26
|
+
@io.warn("monkey patching ahead")
|
27
|
+
end
|
28
|
+
|
29
|
+
should "confirm action, with positive answer" do
|
30
|
+
@output.expects(:print).with("Fire nukes? [yN] ")
|
31
|
+
@input.expects(:gets).returns("y")
|
32
|
+
assert_equal true, @io.confirm("Fire nukes?")
|
33
|
+
end
|
34
|
+
|
35
|
+
should "confirm action, with negative answer" do
|
36
|
+
@output.expects(:print).with("Fire nukes? [yN] ")
|
37
|
+
@input.expects(:gets).returns("n")
|
38
|
+
assert_equal false, @io.confirm("Fire nukes?")
|
39
|
+
end
|
40
|
+
|
41
|
+
should "confirm action, with default answer" do
|
42
|
+
@output.expects(:print).with("Fire nukes? [yN] ")
|
43
|
+
@input.expects(:gets).returns("")
|
44
|
+
assert_equal false, @io.confirm("Fire nukes?")
|
45
|
+
end
|
46
|
+
|
47
|
+
context "with colorization disabled" do
|
48
|
+
setup do
|
49
|
+
@io = IO.new({ :input => @input, :output => @output, :colorize => false })
|
50
|
+
end
|
51
|
+
|
52
|
+
should "output a record as user info when no status is given" do
|
53
|
+
record = { :user => "fooman" }
|
54
|
+
@output.expects(:puts).with(<<-END
|
55
|
+
fooman
|
56
|
+
|
57
|
+
END
|
58
|
+
)
|
59
|
+
@io.show_record(record)
|
60
|
+
end
|
61
|
+
|
62
|
+
should "output a record as status info when status is given, without in-reply info" do
|
63
|
+
status = "Hi, @barman! Lulz woo! #hellos"
|
64
|
+
record = {
|
65
|
+
:user => "fooman",
|
66
|
+
:status => {
|
67
|
+
:created_at => Time.at(1),
|
68
|
+
:text => status
|
69
|
+
}
|
70
|
+
}
|
71
|
+
Util.expects(:humanize_time_diff).returns([2, "secs"])
|
72
|
+
@output.expects(:puts).with(<<-END
|
73
|
+
fooman, 2 secs ago:
|
74
|
+
#{status}
|
75
|
+
|
76
|
+
END
|
77
|
+
)
|
78
|
+
@io.show_record(record)
|
79
|
+
end
|
80
|
+
|
81
|
+
should "output a record as status info when status is given, with in-reply info" do
|
82
|
+
status = "Hi, @fooman! How are you doing?"
|
83
|
+
record = {
|
84
|
+
:user => "barman",
|
85
|
+
:status => {
|
86
|
+
:created_at => Time.at(1),
|
87
|
+
:text => status,
|
88
|
+
:in_reply_to => "fooman"
|
89
|
+
}
|
90
|
+
}
|
91
|
+
Util.expects(:humanize_time_diff).returns([2, "secs"])
|
92
|
+
@output.expects(:puts).with(<<-END
|
93
|
+
barman, in reply to fooman, 2 secs ago:
|
94
|
+
#{status}
|
95
|
+
|
96
|
+
END
|
97
|
+
)
|
98
|
+
@io.show_record(record)
|
99
|
+
end
|
100
|
+
|
101
|
+
should "output a preview of a status" do
|
102
|
+
status = "@nick, check http://bit.ly/18rU_Vx"
|
103
|
+
@output.expects(:puts).with(<<-END
|
104
|
+
|
105
|
+
#{status}
|
106
|
+
|
107
|
+
END
|
108
|
+
)
|
109
|
+
@io.show_status_preview(status)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
context "with colorization enabled" do
|
114
|
+
setup do
|
115
|
+
@io = IO.new({ :input => @input, :output => @output, :colorize => true })
|
116
|
+
end
|
117
|
+
|
118
|
+
should "output a record as user info when no status is given" do
|
119
|
+
record = { :user => "fooman" }
|
120
|
+
@output.expects(:puts).with(<<-END
|
121
|
+
\033[32mfooman\033[0m
|
122
|
+
|
123
|
+
END
|
124
|
+
)
|
125
|
+
@io.show_record(record)
|
126
|
+
end
|
127
|
+
|
128
|
+
should "output a record as status info when status is given, without in-reply info" do
|
129
|
+
record = {
|
130
|
+
:user => "fooman",
|
131
|
+
:status => {
|
132
|
+
:created_at => Time.at(1),
|
133
|
+
:text => "Wondering the meaning of life."
|
134
|
+
}
|
135
|
+
}
|
136
|
+
Util.expects(:humanize_time_diff).returns([2, "secs"])
|
137
|
+
@output.expects(:puts).with(<<-END
|
138
|
+
\033[32mfooman\033[0m, 2 secs ago:
|
139
|
+
Wondering the meaning of life.
|
140
|
+
|
141
|
+
END
|
142
|
+
)
|
143
|
+
@io.show_record(record)
|
144
|
+
end
|
145
|
+
|
146
|
+
should "output a record as status info when status is given, with in-reply info" do
|
147
|
+
record = {
|
148
|
+
:user => "barman",
|
149
|
+
:status => {
|
150
|
+
:created_at => Time.at(1),
|
151
|
+
:text => "Hi, @fooman! How are you doing? #hellos",
|
152
|
+
:in_reply_to => "fooman"
|
153
|
+
}
|
154
|
+
}
|
155
|
+
Util.expects(:humanize_time_diff).returns([2, "secs"])
|
156
|
+
@output.expects(:puts).with(<<-END
|
157
|
+
\033[32mbarman\033[0m, in reply to \033[32mfooman\033[0m, 2 secs ago:
|
158
|
+
Hi, \033[33m@fooman\033[0m! How are you doing? \033[35m#hellos\033[0m
|
159
|
+
|
160
|
+
END
|
161
|
+
)
|
162
|
+
@io.show_record(record)
|
163
|
+
end
|
164
|
+
|
165
|
+
should "output a preview of a status" do
|
166
|
+
status = "@nick, check http://bit.ly/18rU_Vx"
|
167
|
+
@output.expects(:puts).with(<<-END
|
168
|
+
|
169
|
+
\033[33m@nick\033[0m, check \033[36mhttp://bit.ly/18rU_Vx\033[0m
|
170
|
+
|
171
|
+
END
|
172
|
+
)
|
173
|
+
@io.show_status_preview(status)
|
174
|
+
end
|
175
|
+
|
176
|
+
should "highlight HTTP and HTTPS URLs in a status" do
|
177
|
+
record = {
|
178
|
+
:user => "barman",
|
179
|
+
:status => {
|
180
|
+
:created_at => Time.at(1),
|
181
|
+
:text => "Three links: http://bit.ly/18rU_Vx http://is.gd/1qLk3 and https://is.gd/2rLk4",
|
182
|
+
}
|
183
|
+
}
|
184
|
+
Util.expects(:humanize_time_diff).returns([2, "secs"])
|
185
|
+
@output.expects(:puts).with(<<-END
|
186
|
+
\033[32mbarman\033[0m, 2 secs ago:
|
187
|
+
Three links: \033[36mhttp://bit.ly/18rU_Vx\033[0m \033[36mhttp://is.gd/1qLk3\033[0m and \033[36mhttps://is.gd/2rLk4\033[0m
|
188
|
+
|
189
|
+
END
|
190
|
+
)
|
191
|
+
@io.show_record(record)
|
192
|
+
end
|
193
|
+
|
194
|
+
should "highlight HTTP and HTTPS URLs in a status, even if duplicates" do
|
195
|
+
record = {
|
196
|
+
:user => "barman",
|
197
|
+
:status => {
|
198
|
+
:created_at => Time.at(1),
|
199
|
+
:text => "Duplicate links: http://is.gd/1qLk3 and http://is.gd/1qLk3",
|
200
|
+
}
|
201
|
+
}
|
202
|
+
Util.expects(:humanize_time_diff).returns([2, "secs"])
|
203
|
+
@output.expects(:puts).with(<<-END
|
204
|
+
\033[32mbarman\033[0m, 2 secs ago:
|
205
|
+
Duplicate links: \033[36mhttp://is.gd/1qLk3\033[0m and \033[36mhttp://is.gd/1qLk3\033[0m
|
206
|
+
|
207
|
+
END
|
208
|
+
)
|
209
|
+
@io.show_record(record)
|
210
|
+
end
|
211
|
+
|
212
|
+
should "highlight usernames in a status" do
|
213
|
+
record = {
|
214
|
+
:user => "barman",
|
215
|
+
:status => {
|
216
|
+
:created_at => Time.at(1),
|
217
|
+
:text => "I salute you @fooman, @barbaz, and @spoonman!",
|
218
|
+
}
|
219
|
+
}
|
220
|
+
Util.expects(:humanize_time_diff).returns([2, "secs"])
|
221
|
+
@output.expects(:puts).with(<<-END
|
222
|
+
\033[32mbarman\033[0m, 2 secs ago:
|
223
|
+
I salute you \033[33m@fooman\033[0m, \033[33m@barbaz\033[0m, and \033[33m@spoonman\033[0m!
|
224
|
+
|
225
|
+
END
|
226
|
+
)
|
227
|
+
@io.show_record(record)
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
context "Username regex" do
|
233
|
+
should "match a proper username reference" do
|
234
|
+
assert_full_match IO::USERNAME_REGEX, "@nick"
|
235
|
+
assert_full_match IO::USERNAME_REGEX, "@nick_man"
|
236
|
+
end
|
237
|
+
|
238
|
+
should "not match an inproper username reference" do
|
239
|
+
assert_no_full_match IO::USERNAME_REGEX, "@"
|
240
|
+
assert_no_full_match IO::USERNAME_REGEX, "nick"
|
241
|
+
assert_no_full_match IO::USERNAME_REGEX, "@nick-man"
|
242
|
+
assert_no_full_match IO::USERNAME_REGEX, " @nick"
|
243
|
+
assert_no_full_match IO::USERNAME_REGEX, "@nick "
|
244
|
+
assert_no_full_match IO::USERNAME_REGEX, " @nick "
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
context "Hashtag regex" do
|
249
|
+
should "match a proper hashtag reference" do
|
250
|
+
assert_full_match IO::HASHTAG_REGEX, "#mayhem"
|
251
|
+
assert_full_match IO::HASHTAG_REGEX, "#friday_mayhem"
|
252
|
+
assert_full_match IO::HASHTAG_REGEX, "#friday-mayhem"
|
253
|
+
end
|
254
|
+
|
255
|
+
should "not match an inproper hashtag reference" do
|
256
|
+
assert_no_full_match IO::USERNAME_REGEX, "#"
|
257
|
+
assert_no_full_match IO::USERNAME_REGEX, "mayhem"
|
258
|
+
assert_no_full_match IO::USERNAME_REGEX, " #mayhem"
|
259
|
+
assert_no_full_match IO::USERNAME_REGEX, "#mayhem "
|
260
|
+
assert_no_full_match IO::USERNAME_REGEX, " #mayhem "
|
261
|
+
end
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
module Tweetwine
|
4
|
+
|
5
|
+
class OptionsTest < Test::Unit::TestCase
|
6
|
+
context "An Options instance" do
|
7
|
+
should "get the value corresponding to a key or nil (the default value)" do
|
8
|
+
assert_equal "alpha", Options.new({:a => "alpha"})[:a]
|
9
|
+
assert_equal nil, Options.new({})[:a]
|
10
|
+
end
|
11
|
+
|
12
|
+
context "for requiring options" do
|
13
|
+
should "raise ArgumentError if there's no value for the required option (a value that is nil)" do
|
14
|
+
assert_equal "alpha", Options.new({:a => "alpha"}).require(:a)
|
15
|
+
assert_raise(ArgumentError) { Options.new({}).require(:a) }
|
16
|
+
end
|
17
|
+
|
18
|
+
should "indicate the required option upon failure" do
|
19
|
+
error = nil
|
20
|
+
begin
|
21
|
+
Options.new({}).require(:a)
|
22
|
+
flunk "should have raised ArgumentError"
|
23
|
+
rescue ArgumentError => e
|
24
|
+
error = e
|
25
|
+
end
|
26
|
+
assert_equal("Option a is required", e.to_s)
|
27
|
+
end
|
28
|
+
|
29
|
+
should "indicate the required option upon failure, with optional error source" do
|
30
|
+
error = nil
|
31
|
+
begin
|
32
|
+
Options.new({}, "test options").require(:a)
|
33
|
+
flunk "should have raised ArgumentError"
|
34
|
+
rescue ArgumentError => e
|
35
|
+
error = e
|
36
|
+
end
|
37
|
+
assert_equal("Option a is required for test options", e.to_s)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
require "rest_client"
|
3
|
+
|
4
|
+
class Object
|
5
|
+
def sleep(timeout); end # speed up tests
|
6
|
+
end
|
7
|
+
|
8
|
+
module Tweetwine
|
9
|
+
|
10
|
+
class RestClientWrapperTest < Test::Unit::TestCase
|
11
|
+
context "A RestClientWrapper instance" do
|
12
|
+
setup do
|
13
|
+
@io = mock()
|
14
|
+
@rest_client = RestClientWrapper.new(@io)
|
15
|
+
end
|
16
|
+
|
17
|
+
should "raise ClientError for an invalid request" do
|
18
|
+
RestClient.expects(:get) \
|
19
|
+
.with("https://secret:agent@hushhush.net") \
|
20
|
+
.raises(RestClient::Unauthorized)
|
21
|
+
assert_raise(ClientError) { @rest_client.get("https://secret:agent@hushhush.net") }
|
22
|
+
end
|
23
|
+
|
24
|
+
should "raise ClientError when connection cannot be established" do
|
25
|
+
RestClient.expects(:get) \
|
26
|
+
.with("http://www.invalid.net") \
|
27
|
+
.raises(Errno::ECONNABORTED)
|
28
|
+
assert_raise(ClientError) { @rest_client.get("http://www.invalid.net") }
|
29
|
+
end
|
30
|
+
|
31
|
+
should "raise ClientError when host cannot be resolved" do
|
32
|
+
RestClient.expects(:get) \
|
33
|
+
.with("http://unknown.net") \
|
34
|
+
.raises(SocketError)
|
35
|
+
assert_raise(ClientError) { @rest_client.get("http://unknown.net") }
|
36
|
+
end
|
37
|
+
|
38
|
+
should "retry connection upon connection reset" do
|
39
|
+
rest_client_calls = sequence("RestClient")
|
40
|
+
RestClient.expects(:get) \
|
41
|
+
.with("http://www.heavilyloaded.net") \
|
42
|
+
.in_sequence(rest_client_calls) \
|
43
|
+
.raises(Errno::ECONNRESET)
|
44
|
+
RestClient.expects(:get) \
|
45
|
+
.with("http://www.heavilyloaded.net") \
|
46
|
+
.in_sequence(rest_client_calls)
|
47
|
+
@io.expects(:warn).with("Could not connect -- retrying in 4 seconds")
|
48
|
+
@rest_client.get("http://www.heavilyloaded.net")
|
49
|
+
end
|
50
|
+
|
51
|
+
should "retry connection a maximum of certain number of times" do
|
52
|
+
rest_client_calls = sequence("RestClient")
|
53
|
+
io_calls = sequence("IO")
|
54
|
+
RestClientWrapper::MAX_RETRIES.times do
|
55
|
+
RestClient.expects(:get) \
|
56
|
+
.with("http://www.heavilyloaded.net") \
|
57
|
+
.in_sequence(rest_client_calls) \
|
58
|
+
.raises(Errno::ECONNRESET)
|
59
|
+
end
|
60
|
+
(RestClientWrapper::MAX_RETRIES - 1).times do
|
61
|
+
@io.expects(:warn).in_sequence(io_calls)
|
62
|
+
end
|
63
|
+
assert_raise(ClientError) { @rest_client.get("http://www.heavilyloaded.net") }
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
module Tweetwine
|
4
|
+
|
5
|
+
class StartupConfigTest < Test::Unit::TestCase
|
6
|
+
TEST_CONFIG_FILE = File.dirname(__FILE__) << "/test_config.yaml"
|
7
|
+
|
8
|
+
context "A StartupConfig instance" do
|
9
|
+
context "upon initialization" do
|
10
|
+
should "require at least one supported command" do
|
11
|
+
assert_raise(ArgumentError) { StartupConfig.new([]) }
|
12
|
+
assert_nothing_raised { StartupConfig.new([:default_action]) }
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
context "at runtime" do
|
17
|
+
setup do
|
18
|
+
@config = StartupConfig.new([:default_action, :another_action])
|
19
|
+
end
|
20
|
+
|
21
|
+
should "use the first supported command as a default command when given no command as a cmdline argument" do
|
22
|
+
@config.parse
|
23
|
+
assert_equal :default_action, @config.command
|
24
|
+
end
|
25
|
+
|
26
|
+
should "check that given command is supported" do
|
27
|
+
@config.parse(%w{default_action}) { |args| {} }
|
28
|
+
assert_equal :default_action, @config.command
|
29
|
+
|
30
|
+
@config.parse(%w{another_action}) { |args| {} }
|
31
|
+
assert_equal :another_action, @config.command
|
32
|
+
end
|
33
|
+
|
34
|
+
should "parse cmdline args, command, and leftover args" do
|
35
|
+
@config.parse(%w{--opt foo another_action left overs}) do |args|
|
36
|
+
args.slice!(0..1)
|
37
|
+
{:opt => "foo"}
|
38
|
+
end
|
39
|
+
assert_equal({:opt => "foo"}, @config.options)
|
40
|
+
assert_equal :another_action, @config.command
|
41
|
+
assert_equal %w{left overs}, @config.args
|
42
|
+
end
|
43
|
+
|
44
|
+
context "when given no cmdline args and a config file" do
|
45
|
+
setup do
|
46
|
+
@config.parse([], TEST_CONFIG_FILE)
|
47
|
+
end
|
48
|
+
|
49
|
+
should "have the parsed option defined" do
|
50
|
+
assert_equal false, @config.options[:colorize]
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
context "when given cmdline args and no config file" do
|
55
|
+
setup do
|
56
|
+
@config.parse(%w{--opt foo}) do |args|
|
57
|
+
args.clear
|
58
|
+
{:opt => "foo"}
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
should "have the parsed option defined" do
|
63
|
+
assert_equal "foo", @config.options[:opt]
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
context "when given an option both as a cmdline option and in a config file" do
|
68
|
+
setup do
|
69
|
+
@config.parse(%w{--colorize}, TEST_CONFIG_FILE) do |args|
|
70
|
+
args.clear
|
71
|
+
{:colorize => true}
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
should "the command line option should override the config file option" do
|
76
|
+
assert_equal true, @config.options[:colorize]
|
77
|
+
end
|
78
|
+
|
79
|
+
should "have nil for an undefined option" do
|
80
|
+
assert_nil @config.options[:num_statuses]
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
require "tweetwine"
|
2
|
+
require "test/unit"
|
3
|
+
require "shoulda"
|
4
|
+
require "mocha"
|
5
|
+
|
6
|
+
module Tweetwine
|
7
|
+
module TestHelpers
|
8
|
+
def create_test_statuses(*gen_records)
|
9
|
+
status_records = gen_records.map do |gen_record|
|
10
|
+
{
|
11
|
+
"user" => { "screen_name" => gen_record[:user] },
|
12
|
+
"created_at" => gen_record[:status][:created_at],
|
13
|
+
"text" => gen_record[:status][:text],
|
14
|
+
"in_reply_to_screen_name" => gen_record[:status][:in_reply_to]
|
15
|
+
}
|
16
|
+
end
|
17
|
+
[status_records, gen_records]
|
18
|
+
end
|
19
|
+
|
20
|
+
def create_test_users(*gen_records)
|
21
|
+
user_records = gen_records.map do |gen_record|
|
22
|
+
user_record = { "screen_name" => gen_record[:user] }
|
23
|
+
if gen_record[:status]
|
24
|
+
user_record.merge!({
|
25
|
+
"status" => {
|
26
|
+
"created_at" => gen_record[:status][:created_at],
|
27
|
+
"text" => gen_record[:status][:text],
|
28
|
+
"in_reply_to_screen_name" => gen_record[:status][:in_reply_to],
|
29
|
+
}
|
30
|
+
})
|
31
|
+
end
|
32
|
+
user_record
|
33
|
+
end
|
34
|
+
[user_records, gen_records]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
Mocha::Configuration.prevent(:stubbing_non_existent_method)
|
40
|
+
|
41
|
+
module Test
|
42
|
+
module Unit
|
43
|
+
class TestCase
|
44
|
+
include Tweetwine::TestHelpers
|
45
|
+
|
46
|
+
def assert_full_match(regex, str, msg = "")
|
47
|
+
match_data = regex.match(str)
|
48
|
+
assert(str == match_data.to_s, msg)
|
49
|
+
end
|
50
|
+
|
51
|
+
def assert_no_full_match(regex, str, msg = "")
|
52
|
+
match_data = regex.match(str)
|
53
|
+
assert(str != match_data.to_s, msg)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,161 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
module Tweetwine
|
4
|
+
|
5
|
+
class UrlShortenerTest < Test::Unit::TestCase
|
6
|
+
context "An UrlShortener instance" do
|
7
|
+
setup do
|
8
|
+
@rest_client = mock()
|
9
|
+
end
|
10
|
+
|
11
|
+
context "upon initialization" do
|
12
|
+
should "raise exception if service URL is not given" do
|
13
|
+
assert_raise(ArgumentError) do
|
14
|
+
UrlShortener.new(
|
15
|
+
@rest_client,
|
16
|
+
{
|
17
|
+
:service_url => nil,
|
18
|
+
:url_param_name => "url",
|
19
|
+
:xpath_selector => "//input[@id='short_url']/@value"
|
20
|
+
}
|
21
|
+
)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
should "raise exception if URL parameter name is not given" do
|
26
|
+
assert_raise(ArgumentError) do
|
27
|
+
UrlShortener.new(
|
28
|
+
@rest_client,
|
29
|
+
{
|
30
|
+
:service_url => "http://shorten.it/create",
|
31
|
+
:url_param_name => nil,
|
32
|
+
:xpath_selector => "//input[@id='short_url']/@value"
|
33
|
+
}
|
34
|
+
)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
should "raise exception if XPath selector is not given" do
|
39
|
+
assert_raise(ArgumentError) do
|
40
|
+
UrlShortener.new(
|
41
|
+
@rest_client,
|
42
|
+
{
|
43
|
+
:service_url => "http://shorten.it/create",
|
44
|
+
:url_param_name => "url",
|
45
|
+
:xpath_selector => nil
|
46
|
+
}
|
47
|
+
)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
should "fallback to use GET method if not given explicitly" do
|
52
|
+
url_shortener = UrlShortener.new(
|
53
|
+
@rest_client,
|
54
|
+
{
|
55
|
+
:service_url => "http://shorten.it/create",
|
56
|
+
:url_param_name => "url",
|
57
|
+
:xpath_selector => "//input[@id='short_url']/@value"
|
58
|
+
}
|
59
|
+
)
|
60
|
+
assert_equal(:get, url_shortener.instance_variable_get(:@method))
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
context "at runtime" do
|
65
|
+
context "configured for HTTP GET" do
|
66
|
+
should "use parameters as URL query parameters, with just the URL parameter" do
|
67
|
+
url_shortener = UrlShortener.new(
|
68
|
+
@rest_client,
|
69
|
+
{
|
70
|
+
:method => "get",
|
71
|
+
:service_url => "http://shorten.it/create",
|
72
|
+
:url_param_name => "url",
|
73
|
+
:xpath_selector => "//input[@id='short_url']/@value"
|
74
|
+
}
|
75
|
+
)
|
76
|
+
@rest_client.expects(:get) \
|
77
|
+
.with("http://shorten.it/create?url=http://www.ruby-doc.org/core/")
|
78
|
+
url_shortener.shorten("http://www.ruby-doc.org/core/")
|
79
|
+
end
|
80
|
+
|
81
|
+
should "use parameters as URL query parameters, with additional extra parameters" do
|
82
|
+
url_shortener = UrlShortener.new(
|
83
|
+
@rest_client,
|
84
|
+
{
|
85
|
+
:method => "get",
|
86
|
+
:service_url => "http://shorten.it/create",
|
87
|
+
:url_param_name => "url",
|
88
|
+
:extra_params => {
|
89
|
+
:token => "xyz"
|
90
|
+
},
|
91
|
+
:xpath_selector => "//input[@id='short_url']/@value"
|
92
|
+
}
|
93
|
+
)
|
94
|
+
@rest_client.expects(:get) \
|
95
|
+
.with("http://shorten.it/create?token=xyz&url=http://www.ruby-doc.org/core/")
|
96
|
+
url_shortener.shorten("http://www.ruby-doc.org/core/")
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
context "configured for HTTP POST" do
|
101
|
+
should "use parameters as payload, with just the URL parameter" do
|
102
|
+
url_shortener = UrlShortener.new(
|
103
|
+
@rest_client,
|
104
|
+
{
|
105
|
+
:method => "post",
|
106
|
+
:service_url => "http://shorten.it/create",
|
107
|
+
:url_param_name => "url",
|
108
|
+
:xpath_selector => "//input[@id='short_url']/@value"
|
109
|
+
}
|
110
|
+
)
|
111
|
+
@rest_client.expects(:post) \
|
112
|
+
.with("http://shorten.it/create", {:url => "http://www.ruby-doc.org/core/"})
|
113
|
+
url_shortener.shorten("http://www.ruby-doc.org/core/")
|
114
|
+
end
|
115
|
+
|
116
|
+
should "use parameters as payload, with additional extra parameters" do
|
117
|
+
url_shortener = UrlShortener.new(
|
118
|
+
@rest_client,
|
119
|
+
{
|
120
|
+
:method => "post",
|
121
|
+
:service_url => "http://shorten.it/create",
|
122
|
+
:url_param_name => "url",
|
123
|
+
:extra_params => {
|
124
|
+
:token => "xyz"
|
125
|
+
},
|
126
|
+
:xpath_selector => "//input[@id='short_url']/@value"
|
127
|
+
}
|
128
|
+
)
|
129
|
+
@rest_client.expects(:post) \
|
130
|
+
.with("http://shorten.it/create", {
|
131
|
+
:token => "xyz",
|
132
|
+
:url => "http://www.ruby-doc.org/core/"
|
133
|
+
})
|
134
|
+
url_shortener.shorten("http://www.ruby-doc.org/core/")
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
context "in erroenous situations" do
|
139
|
+
should "raise ClientError upon connection error" do
|
140
|
+
url_shortener = UrlShortener.new(
|
141
|
+
@rest_client,
|
142
|
+
{
|
143
|
+
:method => "post",
|
144
|
+
:service_url => "http://shorten.it/create",
|
145
|
+
:url_param_name => "url",
|
146
|
+
:xpath_selector => "//input[@id='short_url']/@value"
|
147
|
+
}
|
148
|
+
)
|
149
|
+
@rest_client.expects(:post) \
|
150
|
+
.with("http://shorten.it/create", {
|
151
|
+
:url => "http://www.ruby-doc.org/core/"
|
152
|
+
}) \
|
153
|
+
.raises(ClientError, "connection error")
|
154
|
+
assert_raise(ClientError) { url_shortener.shorten("http://www.ruby-doc.org/core/") }
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
end
|