tweetwine 0.2.4

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,3 @@
1
+ username: foo
2
+ password: bar
3
+ colorize: false
@@ -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