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