tweetwine 0.2.4
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/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
|