tweetwine 0.3.1 → 0.3.2
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.rdoc +6 -0
- data/Gemfile +12 -12
- data/README.md +7 -8
- data/Rakefile +4 -4
- data/lib/tweetwine/character_encoding.rb +1 -1
- data/lib/tweetwine/cli.rb +2 -2
- data/lib/tweetwine/twitter.rb +0 -1
- data/lib/tweetwine/url_shortener.rb +3 -3
- data/lib/tweetwine.rb +17 -1
- data/man/tweetwine.7 +4 -7
- data/man/tweetwine.7.ronn +7 -8
- data/test/example/application_behavior_example.rb +2 -2
- data/test/example/example_helper.rb +1 -7
- data/test/example/search_statuses_example.rb +1 -1
- data/test/example/show_followers_example.rb +1 -1
- data/test/example/show_friends_example.rb +1 -1
- data/test/example/show_home_example.rb +1 -1
- data/test/example/show_mentions_example.rb +1 -1
- data/test/example/show_user_example.rb +1 -1
- data/test/example/update_status_example.rb +6 -7
- data/test/example/use_http_proxy_example.rb +1 -1
- data/test/test_helper.rb +7 -1
- data/test/unit/character_encoding_test.rb +1 -1
- data/test/unit/config_test.rb +2 -2
- data/test/unit/twitter_test.rb +48 -2
- data/test/unit/unit_helper.rb +21 -60
- data/tweetwine.gemspec +44 -36
- metadata +73 -47
data/CHANGELOG.rdoc
CHANGED
data/Gemfile
CHANGED
@@ -1,17 +1,17 @@
|
|
1
1
|
source :rubygems
|
2
2
|
|
3
|
-
gem
|
4
|
-
gem
|
5
|
-
gem
|
3
|
+
gem 'json', '>= 1.0.0', :platforms => [:ruby_18, :jruby]
|
4
|
+
gem 'nokogiri', '~> 1.4.4'
|
5
|
+
gem 'oauth', '~> 0.4.4'
|
6
6
|
|
7
7
|
group :test do
|
8
|
-
gem
|
9
|
-
gem
|
10
|
-
gem
|
11
|
-
gem
|
12
|
-
gem
|
13
|
-
gem
|
14
|
-
gem
|
15
|
-
gem
|
16
|
-
gem
|
8
|
+
gem 'contest', '~> 0.1.2'
|
9
|
+
gem 'coulda', '~> 0.6.0'
|
10
|
+
gem 'gem-man', '~> 0.2.0'
|
11
|
+
gem 'mcmire-matchy', '~> 0.5.2', :require => 'matchy'
|
12
|
+
gem 'mocha', '= 0.9.8'
|
13
|
+
gem 'open4', '~> 1.0.1'
|
14
|
+
gem 'ronn', '~> 0.7.3'
|
15
|
+
gem 'timecop', '~> 0.3.5'
|
16
|
+
gem 'webmock', '~> 1.6.1'
|
17
17
|
end
|
data/README.md
CHANGED
@@ -1,10 +1,9 @@
|
|
1
|
-
tweetwine
|
2
|
-
|
1
|
+
tweetwine
|
2
|
+
=========
|
3
3
|
|
4
4
|
## DESCRIPTION
|
5
5
|
|
6
|
-
Tweetwine
|
7
|
-
quickly.
|
6
|
+
Tweetwine shows the latest tweets from the command line quickly.
|
8
7
|
|
9
8
|
The program can show the home timeline of the authenticated user, the latest
|
10
9
|
tweets of friends and followers, and the latest tweets that mention the user.
|
@@ -28,11 +27,11 @@ Install Tweetwine with RubyGems:
|
|
28
27
|
The program is tested with Ruby 1.8.7 and 1.9.2.
|
29
28
|
|
30
29
|
The program requires [oauth](http://oauth.rubyforge.org/) gem to be installed.
|
31
|
-
In addition, the program needs
|
32
|
-
1.8.
|
30
|
+
In addition, the program needs a JSON parser library, such as
|
31
|
+
[json](http://json.rubyforge.org/) gem, on Ruby 1.8.
|
33
32
|
|
34
33
|
This documentation page is also provided as a manual page. Use
|
35
|
-
[gem-man](
|
34
|
+
[gem-man](https://github.com/defunkt/gem-man) to see it:
|
36
35
|
|
37
36
|
$ gem man tweetwine
|
38
37
|
|
@@ -129,4 +128,4 @@ Tweetwine is Copyright (c) 2009-2010 Tuomas Kareinen.
|
|
129
128
|
|
130
129
|
## SEE ALSO
|
131
130
|
|
132
|
-
<
|
131
|
+
<https://github.com/tuomas/tweetwine>
|
data/Rakefile
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
# coding: utf-8
|
2
2
|
|
3
|
-
require
|
3
|
+
require 'rake/clean'
|
4
4
|
|
5
|
-
$LOAD_PATH.unshift(File.expand_path(
|
6
|
-
name =
|
5
|
+
$LOAD_PATH.unshift(File.expand_path('lib', File.dirname(__FILE__)))
|
6
|
+
name = 'tweetwine'
|
7
7
|
require name
|
8
|
-
version = Tweetwine
|
8
|
+
version = Tweetwine.version.dup
|
9
9
|
|
10
10
|
namespace :gem do
|
11
11
|
CLOBBER.include "#{name}-*.gem"
|
@@ -3,7 +3,7 @@
|
|
3
3
|
module Tweetwine
|
4
4
|
class CharacterEncoding
|
5
5
|
class << self
|
6
|
-
if
|
6
|
+
if defined? Encoding
|
7
7
|
def to_utf8(str)
|
8
8
|
result = str.encode('UTF-8')
|
9
9
|
raise TranscodeError, "invalid UTF-8 byte sequence when transcoding '#{str}'" unless result.valid_encoding?
|
data/lib/tweetwine/cli.rb
CHANGED
@@ -238,7 +238,7 @@ Usage: #{CLI::EXEC_NAME} #{name} #{usage}
|
|
238
238
|
sort { |a, b| a.first.to_s <=> b.first.to_s }.
|
239
239
|
map { |cmd, klass| [cmd, klass.about] }
|
240
240
|
CLI.ui.info <<-END
|
241
|
-
|
241
|
+
#{Tweetwine.summary}
|
242
242
|
|
243
243
|
Usage: #{CLI::EXEC_NAME} [global_options...] [<command>] [command_options...]
|
244
244
|
|
@@ -372,7 +372,7 @@ Usage: #{CLI::EXEC_NAME} [global_options...] [<command>] [command_options...]
|
|
372
372
|
about "Show program version and exit."
|
373
373
|
|
374
374
|
def run
|
375
|
-
CLI.ui.info "tweetwine #{Tweetwine
|
375
|
+
CLI.ui.info "tweetwine #{Tweetwine.version}"
|
376
376
|
end
|
377
377
|
end
|
378
378
|
end
|
data/lib/tweetwine/twitter.rb
CHANGED
@@ -3,7 +3,8 @@
|
|
3
3
|
module Tweetwine
|
4
4
|
class UrlShortener
|
5
5
|
def initialize(options)
|
6
|
-
raise
|
6
|
+
raise 'UrlShortener should be disabled' if options[:disable]
|
7
|
+
require 'nokogiri'
|
7
8
|
@method = (options[:method] || :get).to_sym
|
8
9
|
unless [:get, :post].include? @method
|
9
10
|
raise CommandLineError, "Unsupported HTTP request method for URL shortening: #{@method}"
|
@@ -20,7 +21,6 @@ module Tweetwine
|
|
20
21
|
end
|
21
22
|
|
22
23
|
def shorten(url)
|
23
|
-
require "nokogiri"
|
24
24
|
response = CLI.http.send(@method, *get_service_url_and_params(url))
|
25
25
|
doc = Nokogiri::HTML(response)
|
26
26
|
doc.xpath(@xpath_selector).first.to_s
|
@@ -44,7 +44,7 @@ module Tweetwine
|
|
44
44
|
params = @extra_params.merge({ @url_param_name.to_sym => url_to_shorten })
|
45
45
|
[service_url, params]
|
46
46
|
else
|
47
|
-
raise "Unrecognized HTTP request method; should have been supported"
|
47
|
+
raise "Unrecognized HTTP request method #{@method}; should have been supported"
|
48
48
|
end
|
49
49
|
end
|
50
50
|
end
|
data/lib/tweetwine.rb
CHANGED
@@ -1,9 +1,25 @@
|
|
1
1
|
# coding: utf-8
|
2
2
|
|
3
|
+
begin
|
4
|
+
require 'json'
|
5
|
+
rescue LoadError
|
6
|
+
raise 'Could not load JSON library; do you have one installed as a gem?'
|
7
|
+
end unless defined? JSON
|
8
|
+
|
3
9
|
gem 'oauth', '~> 0.4.4'
|
4
10
|
|
5
11
|
module Tweetwine
|
6
|
-
VERSION = '0.3.
|
12
|
+
VERSION = '0.3.2'.freeze
|
13
|
+
|
14
|
+
class << self
|
15
|
+
def version
|
16
|
+
VERSION
|
17
|
+
end
|
18
|
+
|
19
|
+
def summary
|
20
|
+
'Tweetwine shows the latest tweets from the command line quickly.'
|
21
|
+
end
|
22
|
+
end
|
7
23
|
|
8
24
|
class Error < StandardError
|
9
25
|
@status_code = 42
|
data/man/tweetwine.7
CHANGED
@@ -3,11 +3,8 @@
|
|
3
3
|
.
|
4
4
|
.TH "TWEETWINE" "7" "November 2010" "Tuomas Kareinen" "Tweetwine Manual"
|
5
5
|
.
|
6
|
-
.SH "NAME"
|
7
|
-
\fBtweetwine\fR \- a simple Twitter command line agent
|
8
|
-
.
|
9
6
|
.SH "DESCRIPTION"
|
10
|
-
Tweetwine
|
7
|
+
Tweetwine shows the latest tweets from the command line quickly\.
|
11
8
|
.
|
12
9
|
.P
|
13
10
|
The program can show the home timeline of the authenticated user, the latest tweets of friends and followers, and the latest tweets that mention the user\. If that\'s not enough, Tweetwine can search statuses with arbitrary terms and send status updates\.
|
@@ -46,10 +43,10 @@ $ gem install tweetwine
|
|
46
43
|
The program is tested with Ruby 1\.8\.7 and 1\.9\.2\.
|
47
44
|
.
|
48
45
|
.P
|
49
|
-
The program requires oauth \fIhttp://oauth\.rubyforge\.org/\fR gem to be installed\. In addition, the program needs json \fIhttp://json\.rubyforge\.org/\fR gem on Ruby 1\.8\.
|
46
|
+
The program requires oauth \fIhttp://oauth\.rubyforge\.org/\fR gem to be installed\. In addition, the program needs a JSON parser library, such as json \fIhttp://json\.rubyforge\.org/\fR gem, on Ruby 1\.8\.
|
50
47
|
.
|
51
48
|
.P
|
52
|
-
This documentation page is also provided as a manual page\. Use gem\-man \
|
49
|
+
This documentation page is also provided as a manual page\. Use gem\-man \fIhttps://github\.com/defunkt/gem\-man\fR to see it:
|
53
50
|
.
|
54
51
|
.IP "" 4
|
55
52
|
.
|
@@ -187,4 +184,4 @@ fi
|
|
187
184
|
Tweetwine is Copyright (c) 2009\-2010 Tuomas Kareinen\.
|
188
185
|
.
|
189
186
|
.SH "SEE ALSO"
|
190
|
-
\
|
187
|
+
\fIhttps://github\.com/tuomas/tweetwine\fR
|
data/man/tweetwine.7.ronn
CHANGED
@@ -1,10 +1,9 @@
|
|
1
|
-
tweetwine
|
2
|
-
|
1
|
+
tweetwine
|
2
|
+
=========
|
3
3
|
|
4
4
|
## DESCRIPTION
|
5
5
|
|
6
|
-
Tweetwine
|
7
|
-
quickly.
|
6
|
+
Tweetwine shows the latest tweets from the command line quickly.
|
8
7
|
|
9
8
|
The program can show the home timeline of the authenticated user, the latest
|
10
9
|
tweets of friends and followers, and the latest tweets that mention the user.
|
@@ -28,11 +27,11 @@ Install Tweetwine with RubyGems:
|
|
28
27
|
The program is tested with Ruby 1.8.7 and 1.9.2.
|
29
28
|
|
30
29
|
The program requires [oauth](http://oauth.rubyforge.org/) gem to be installed.
|
31
|
-
In addition, the program needs
|
32
|
-
1.8.
|
30
|
+
In addition, the program needs a JSON parser library, such as
|
31
|
+
[json](http://json.rubyforge.org/) gem, on Ruby 1.8.
|
33
32
|
|
34
33
|
This documentation page is also provided as a manual page. Use
|
35
|
-
[gem-man](
|
34
|
+
[gem-man](https://github.com/defunkt/gem-man) to see it:
|
36
35
|
|
37
36
|
$ gem man tweetwine
|
38
37
|
|
@@ -129,4 +128,4 @@ Tweetwine is Copyright (c) 2009-2010 Tuomas Kareinen.
|
|
129
128
|
|
130
129
|
## SEE ALSO
|
131
130
|
|
132
|
-
<
|
131
|
+
<https://github.com/tuomas/tweetwine>
|
@@ -35,7 +35,7 @@ Feature "application behavior" do
|
|
35
35
|
|
36
36
|
Then "the application shows help and exists with success status" do
|
37
37
|
@output.should == <<-END
|
38
|
-
|
38
|
+
#{Tweetwine.summary}
|
39
39
|
|
40
40
|
Usage: #{CLI::EXEC_NAME} [global_options...] [<command>] [command_options...]
|
41
41
|
|
@@ -147,7 +147,7 @@ Usage: tweetwine help [<command>]
|
|
147
147
|
stub_http_request(METHOD, ACCESS_TOKEN_URL).to_return(:body => ACCESS_TOKEN_RESPONSE)
|
148
148
|
stub_http_request(:get, @command_url).
|
149
149
|
to_return(:status => [401, 'Unauthorized']).then.
|
150
|
-
to_return(:body =>
|
150
|
+
to_return(:body => fixture_file('home.json'))
|
151
151
|
@output = nil
|
152
152
|
@config_contents = nil
|
153
153
|
@config_mode = nil
|
@@ -18,7 +18,7 @@ module Tweetwine::Test
|
|
18
18
|
module Helper
|
19
19
|
include WebMock::API
|
20
20
|
|
21
|
-
CONFIG_FILE =
|
21
|
+
CONFIG_FILE = fixture_path('config_example.yaml')
|
22
22
|
PROJECT_DIR = File.expand_path('../..', File.dirname(__FILE__))
|
23
23
|
PROXY_HOST = "proxy.net"
|
24
24
|
PROXY_PORT = 8123
|
@@ -39,12 +39,6 @@ module Tweetwine::Test
|
|
39
39
|
output.string.split("\n")
|
40
40
|
end
|
41
41
|
|
42
|
-
def fixture(filename)
|
43
|
-
File.open(fixture_file(filename)) do |f|
|
44
|
-
f.readlines.join("\n")
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
42
|
def in_temp_dir
|
49
43
|
Dir.mktmpdir do |tmp_dir|
|
50
44
|
Dir.chdir(tmp_dir) do |dir|
|
@@ -9,7 +9,7 @@ Feature "search tweets" do
|
|
9
9
|
|
10
10
|
SEARCH_OR_URL = "http://search.twitter.com/search.json?q=braid%20game&rpp=2&page=1"
|
11
11
|
SEARCH_AND_URL = "http://search.twitter.com/search.json?q=braid%20OR%20game&rpp=2&page=1"
|
12
|
-
SEARCH_FIXTURE =
|
12
|
+
SEARCH_FIXTURE = fixture_file('search.json')
|
13
13
|
|
14
14
|
Scenario "search words" do
|
15
15
|
When "I start the application with command 'search' and search words" do
|
@@ -9,7 +9,7 @@ Feature "show followers" do
|
|
9
9
|
|
10
10
|
Scenario "show followers" do
|
11
11
|
When "I start the application with 'followers' command" do
|
12
|
-
stub_http_request(:get, "https://api.twitter.com/1/statuses/followers.json?count=20&page=1").to_return(:body =>
|
12
|
+
stub_http_request(:get, "https://api.twitter.com/1/statuses/followers.json?count=20&page=1").to_return(:body => fixture_file('users.json'))
|
13
13
|
@output = start_cli %w{followers}
|
14
14
|
end
|
15
15
|
|
@@ -9,7 +9,7 @@ Feature "show friends" do
|
|
9
9
|
|
10
10
|
Scenario "show friends" do
|
11
11
|
When "I start the application with 'followers' command" do
|
12
|
-
stub_http_request(:get, "https://api.twitter.com/1/statuses/friends.json?count=20&page=1").to_return(:body =>
|
12
|
+
stub_http_request(:get, "https://api.twitter.com/1/statuses/friends.json?count=20&page=1").to_return(:body => fixture_file('users.json'))
|
13
13
|
@output = start_cli %w{friends}
|
14
14
|
end
|
15
15
|
|
@@ -9,7 +9,7 @@ Feature "show tweets from home timeline" do
|
|
9
9
|
|
10
10
|
def setup
|
11
11
|
super
|
12
|
-
stub_http_request(:get, "https://api.twitter.com/1/statuses/home_timeline.json?count=20&page=1").to_return(:body =>
|
12
|
+
stub_http_request(:get, "https://api.twitter.com/1/statuses/home_timeline.json?count=20&page=1").to_return(:body => fixture_file('home.json'))
|
13
13
|
end
|
14
14
|
|
15
15
|
Scenario "with colorization disabled" do
|
@@ -9,7 +9,7 @@ Feature "show tweets mentioning the user" do
|
|
9
9
|
|
10
10
|
Scenario "show tweets mentioning me" do
|
11
11
|
When "I start the application with 'mentions' command" do
|
12
|
-
stub_http_request(:get, "https://api.twitter.com/1/statuses/mentions.json?count=20&page=1").to_return(:body =>
|
12
|
+
stub_http_request(:get, "https://api.twitter.com/1/statuses/mentions.json?count=20&page=1").to_return(:body => fixture_file('mentions.json'))
|
13
13
|
@output = start_cli %w{mentions}
|
14
14
|
end
|
15
15
|
|
@@ -8,7 +8,7 @@ Feature "show user's tweets" do
|
|
8
8
|
i_want_to "see that user's tweets"
|
9
9
|
|
10
10
|
USER_URL = "https://api.twitter.com/1/statuses/user_timeline.json?count=20&page=1&screen_name=%s"
|
11
|
-
USER_FIXTURE =
|
11
|
+
USER_FIXTURE = fixture_file('user.json')
|
12
12
|
|
13
13
|
Scenario "show my tweets" do
|
14
14
|
When "I start the application with 'user' command without extra arguments" do
|
@@ -8,12 +8,12 @@ Feature "update my status (send new tweet)" do
|
|
8
8
|
as_a "authenticated user"
|
9
9
|
i_want_to "update my status"
|
10
10
|
|
11
|
-
RUBYGEMS_FIXTURE =
|
11
|
+
RUBYGEMS_FIXTURE = fixture_file('shorten_rubygems.html')
|
12
12
|
RUBYGEMS_FULL_URL = 'http://rubygems.org/'
|
13
13
|
RUBYGEMS_FULL_URL_ENC = 'http%3a%2f%2frubygems.org%2f'
|
14
14
|
RUBYGEMS_SHORT_URL = 'http://is.gd/gGazV'
|
15
15
|
RUBYGEMS_SHORT_URL_ENC = 'http%3a%2f%2fis.gd%2fgGazV'
|
16
|
-
RUBYLANG_FIXTURE =
|
16
|
+
RUBYLANG_FIXTURE = fixture_file('shorten_rubylang.html')
|
17
17
|
RUBYLANG_FULL_URL = 'http://ruby-lang.org/'
|
18
18
|
RUBYLANG_FULL_URL_ENC = 'http%3a%2f%2fruby-lang.org%2f'
|
19
19
|
RUBYLANG_SHORT_URL = 'http://is.gd/gGaM3'
|
@@ -23,9 +23,9 @@ Feature "update my status (send new tweet)" do
|
|
23
23
|
STATUS_WITH_FULL_URLS = "ruby links: #{RUBYGEMS_FULL_URL} #{RUBYLANG_FULL_URL}"
|
24
24
|
STATUS_WITH_SHORT_URLS = "ruby links: #{RUBYGEMS_SHORT_URL} #{RUBYLANG_SHORT_URL}"
|
25
25
|
STATUS_WITHOUT_URLS = "bored. going to sleep."
|
26
|
-
UPDATE_FIXTURE_WITH_URLS =
|
27
|
-
UPDATE_FIXTURE_WITHOUT_URLS =
|
28
|
-
UPDATE_FIXTURE_UTF8 =
|
26
|
+
UPDATE_FIXTURE_WITH_URLS = fixture_file('update_with_urls.json')
|
27
|
+
UPDATE_FIXTURE_WITHOUT_URLS = fixture_file('update_without_urls.json')
|
28
|
+
UPDATE_FIXTURE_UTF8 = fixture_file('update_utf8.json')
|
29
29
|
UPDATE_URL = "https://api.twitter.com/1/statuses/update.json"
|
30
30
|
|
31
31
|
BODY_WITH_SHORT_URLS = "status=ruby%20links%3a%20#{RUBYGEMS_SHORT_URL_ENC}%20#{RUBYLANG_SHORT_URL_ENC}"
|
@@ -100,7 +100,7 @@ Feature "update my status (send new tweet)" do
|
|
100
100
|
end
|
101
101
|
end
|
102
102
|
|
103
|
-
if
|
103
|
+
if defined? Encoding
|
104
104
|
Scenario "encode status in UTF-8 (String supports encoding)" do
|
105
105
|
When "I start the application with 'update' command, input latin1 encoded status, and confirm" do
|
106
106
|
@status_utf8 = "résumé"
|
@@ -136,7 +136,6 @@ Feature "update my status (send new tweet)" do
|
|
136
136
|
Then "the application sends and shows the status" do
|
137
137
|
@output[1].should == @status_latin1 # preview
|
138
138
|
@output[5].should == "#{USER}, 9 hours ago:"
|
139
|
-
# TODO: we should convert status back to latin-1
|
140
139
|
@output[6].should == @status_utf8
|
141
140
|
end
|
142
141
|
end
|
@@ -9,7 +9,7 @@ Feature "using HTTP proxy" do
|
|
9
9
|
|
10
10
|
def setup
|
11
11
|
super
|
12
|
-
stub_http_request(:get, "https://api.twitter.com/1/statuses/home_timeline.json?count=20&page=1").to_return(:body =>
|
12
|
+
stub_http_request(:get, "https://api.twitter.com/1/statuses/home_timeline.json?count=20&page=1").to_return(:body => fixture_file('home.json'))
|
13
13
|
end
|
14
14
|
|
15
15
|
Scenario "enable proxy via environment variable" do
|
data/test/test_helper.rb
CHANGED
@@ -15,10 +15,16 @@ module Tweetwine
|
|
15
15
|
File.stat(file).mode & 0777
|
16
16
|
end
|
17
17
|
|
18
|
-
def
|
18
|
+
def fixture_path(filename)
|
19
19
|
File.dirname(__FILE__) << "/fixture/" << filename
|
20
20
|
end
|
21
21
|
|
22
|
+
def fixture_file(filename)
|
23
|
+
File.open(fixture_path(filename)) do |f|
|
24
|
+
f.readlines.join("\n")
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
22
28
|
def tmp_env(vars = {})
|
23
29
|
originals = {}
|
24
30
|
vars.each_pair do |key, value|
|
@@ -5,7 +5,7 @@ require "unit_helper"
|
|
5
5
|
module Tweetwine::Test
|
6
6
|
|
7
7
|
class CharacterEncodingTest < UnitTestCase
|
8
|
-
if
|
8
|
+
if defined? Encoding
|
9
9
|
context "when transcoding to UTF-8 when String supports encoding" do
|
10
10
|
should "transcode string to UTF-8" do
|
11
11
|
str_utf8 = "groß résumé"
|
data/test/unit/config_test.rb
CHANGED
@@ -9,7 +9,7 @@ require "yaml"
|
|
9
9
|
module Tweetwine::Test
|
10
10
|
|
11
11
|
class ConfigTest < UnitTestCase
|
12
|
-
CONFIG_FILE = Helper.
|
12
|
+
CONFIG_FILE = Helper.fixture_path('config_unit.yaml')
|
13
13
|
|
14
14
|
context "when given command line arguments, no environment variables, no config file" do
|
15
15
|
setup do
|
@@ -190,7 +190,7 @@ class ConfigTest < UnitTestCase
|
|
190
190
|
end
|
191
191
|
|
192
192
|
should "ignore nonexisting config file for initial read" do
|
193
|
-
|
193
|
+
assert_contains_exactly(@default_config.keys, @config.keys) do |a, b|
|
194
194
|
# On Ruby 1.8, Symbol does not have #<=> operator for comparison.
|
195
195
|
a.to_s <=> b.to_s
|
196
196
|
end
|
data/test/unit/twitter_test.rb
CHANGED
@@ -263,7 +263,7 @@ class ClientTest < UnitTestCase
|
|
263
263
|
@twitter.update(long_status)
|
264
264
|
end
|
265
265
|
|
266
|
-
if
|
266
|
+
if defined? Encoding
|
267
267
|
should "encode status in UTF-8 (String supports encoding)" do
|
268
268
|
status_utf8, status_latin1 = "résumé", "résumé".encode('ISO-8859-1')
|
269
269
|
twitter_records, internal_records = create_test_twitter_status_records_from_rest_api({
|
@@ -447,6 +447,7 @@ class ClientTest < UnitTestCase
|
|
447
447
|
end
|
448
448
|
|
449
449
|
should "skip shortening URLs if required libraries are not found" do
|
450
|
+
Tweetwine::CLI.stubs(:url_shortener).raises(LoadError, 'gem not found')
|
450
451
|
@oauth.expects(:request_signer)
|
451
452
|
http_subresource = mock
|
452
453
|
http_subresource.expects(:post).
|
@@ -455,7 +456,6 @@ class ClientTest < UnitTestCase
|
|
455
456
|
@rest_api.expects(:[]).
|
456
457
|
with("statuses/update.json").
|
457
458
|
returns(http_subresource)
|
458
|
-
@url_shortener.expects(:shorten).with(@url).raises(LoadError, "gem not found")
|
459
459
|
@ui.expects(:warn)
|
460
460
|
@ui.expects(:show_status_preview).with(@status)
|
461
461
|
@ui.expects(:confirm).with("Really send?").returns(true)
|
@@ -619,6 +619,52 @@ class ClientTest < UnitTestCase
|
|
619
619
|
end
|
620
620
|
end
|
621
621
|
end
|
622
|
+
|
623
|
+
private
|
624
|
+
|
625
|
+
def create_test_twitter_status_records_from_rest_api(*internal_records)
|
626
|
+
twitter_records = internal_records.map do |internal_record|
|
627
|
+
{
|
628
|
+
"user" => { "screen_name" => internal_record[:from_user] },
|
629
|
+
"created_at" => internal_record[:created_at],
|
630
|
+
"text" => internal_record[:status],
|
631
|
+
"in_reply_to_screen_name" => internal_record[:to_user]
|
632
|
+
}
|
633
|
+
end
|
634
|
+
[twitter_records, internal_records]
|
635
|
+
end
|
636
|
+
|
637
|
+
def create_test_twitter_user_records_from_rest_api(*internal_records)
|
638
|
+
twitter_records = internal_records.map do |internal_record|
|
639
|
+
twitter_record = { "screen_name" => internal_record[:from_user] }
|
640
|
+
if internal_record[:status]
|
641
|
+
twitter_record.merge!({
|
642
|
+
"status" => {
|
643
|
+
"created_at" => internal_record[:created_at],
|
644
|
+
"text" => internal_record[:status],
|
645
|
+
"in_reply_to_screen_name" => internal_record[:to_user],
|
646
|
+
}
|
647
|
+
})
|
648
|
+
end
|
649
|
+
twitter_record
|
650
|
+
end
|
651
|
+
[twitter_records, internal_records]
|
652
|
+
end
|
653
|
+
|
654
|
+
def create_test_twitter_records_from_search_api(*internal_records)
|
655
|
+
twitter_search_records = internal_records.map do |internal_record|
|
656
|
+
{
|
657
|
+
"from_user" => internal_record[:from_user],
|
658
|
+
"created_at" => internal_record[:created_at],
|
659
|
+
"text" => internal_record[:status],
|
660
|
+
"to_user" => internal_record[:to_user]
|
661
|
+
}
|
662
|
+
end
|
663
|
+
twitter_records = {
|
664
|
+
"results" => twitter_search_records
|
665
|
+
}
|
666
|
+
[twitter_records, internal_records]
|
667
|
+
end
|
622
668
|
end
|
623
669
|
|
624
670
|
end
|
data/test/unit/unit_helper.rb
CHANGED
@@ -8,51 +8,30 @@ require "mocha"
|
|
8
8
|
Mocha::Configuration.prevent(:stubbing_non_existent_method)
|
9
9
|
|
10
10
|
module Tweetwine::Test
|
11
|
-
module
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
[twitter_records, internal_records]
|
11
|
+
module Assertions
|
12
|
+
# Asserts whether an Enumeration like object contains all the elements.
|
13
|
+
# Fails unless +actual+ contains the same elements as +expected+, ignoring
|
14
|
+
# the order of the elements.
|
15
|
+
#
|
16
|
+
# The method sorts +expected+ and +actual+ in order to compare them. By
|
17
|
+
# default, this sorting is done by calling #sort for each of them. If the
|
18
|
+
# method is called with a block, it is passed to the #sort calls.
|
19
|
+
def assert_contains_exactly(expected, actual, msg = '', &sorter)
|
20
|
+
expected = block_given? ? expected.sort(&sorter) : expected.sort
|
21
|
+
actual = block_given? ? actual.sort(&sorter) : actual.sort
|
22
|
+
assert_equal(expected, actual, msg)
|
24
23
|
end
|
25
24
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
twitter_record.merge!({
|
31
|
-
"status" => {
|
32
|
-
"created_at" => internal_record[:created_at],
|
33
|
-
"text" => internal_record[:status],
|
34
|
-
"in_reply_to_screen_name" => internal_record[:to_user],
|
35
|
-
}
|
36
|
-
})
|
37
|
-
end
|
38
|
-
twitter_record
|
39
|
-
end
|
40
|
-
[twitter_records, internal_records]
|
25
|
+
# Fails unless +str+ is a full match to +regex+.
|
26
|
+
def assert_full_match(regex, str, msg = '')
|
27
|
+
match_data = regex.match(str)
|
28
|
+
assert(str == match_data.to_s, msg)
|
41
29
|
end
|
42
30
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
"created_at" => internal_record[:created_at],
|
48
|
-
"text" => internal_record[:status],
|
49
|
-
"to_user" => internal_record[:to_user]
|
50
|
-
}
|
51
|
-
end
|
52
|
-
twitter_records = {
|
53
|
-
"results" => twitter_search_records
|
54
|
-
}
|
55
|
-
[twitter_records, internal_records]
|
31
|
+
# Fails if +str+ is a full match to +regex+.
|
32
|
+
def assert_no_full_match(regex, str, msg = '')
|
33
|
+
match_data = regex.match(str)
|
34
|
+
assert(str != match_data.to_s, msg)
|
56
35
|
end
|
57
36
|
end
|
58
37
|
|
@@ -88,29 +67,11 @@ module Tweetwine::Test
|
|
88
67
|
end
|
89
68
|
end
|
90
69
|
|
91
|
-
module Assertions
|
92
|
-
def assert_contains_in_order(expected, actual, msg = "", &sorter)
|
93
|
-
expected = block_given? ? expected.sort(&sorter) : expected.sort
|
94
|
-
actual = block_given? ? actual.sort(&sorter) : actual.sort
|
95
|
-
assert_equal(expected, actual, msg)
|
96
|
-
end
|
97
|
-
|
98
|
-
def assert_full_match(regex, str, msg = "")
|
99
|
-
match_data = regex.match(str)
|
100
|
-
assert(str == match_data.to_s, msg)
|
101
|
-
end
|
102
|
-
|
103
|
-
def assert_no_full_match(regex, str, msg = "")
|
104
|
-
match_data = regex.match(str)
|
105
|
-
assert(str != match_data.to_s, msg)
|
106
|
-
end
|
107
|
-
end
|
108
|
-
|
109
70
|
class UnitTestCase < ::Test::Unit::TestCase
|
110
71
|
include WebMock::API
|
111
72
|
include Tweetwine
|
112
73
|
include Helper
|
113
|
-
include Doubles
|
114
74
|
include Assertions
|
75
|
+
include Doubles
|
115
76
|
end
|
116
77
|
end
|
data/tweetwine.gemspec
CHANGED
@@ -1,52 +1,60 @@
|
|
1
1
|
# coding: utf-8
|
2
2
|
|
3
|
-
$LOAD_PATH.unshift(File.expand_path(
|
4
|
-
name =
|
3
|
+
$LOAD_PATH.unshift(File.expand_path('lib', File.dirname(__FILE__)))
|
4
|
+
name = 'tweetwine'
|
5
5
|
require name
|
6
|
-
|
6
|
+
summary = Tweetwine.summary
|
7
|
+
version = Tweetwine.version.dup
|
7
8
|
|
8
9
|
Gem::Specification.new do |s|
|
9
|
-
s.specification_version = 2 if s.respond_to? :specification_version=
|
10
|
-
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
-
|
12
10
|
s.name = name
|
13
11
|
s.version = version
|
14
|
-
s.homepage = "http://github.com/tuomas/tweetwine"
|
15
12
|
|
16
|
-
s.summary =
|
17
|
-
s.description =
|
13
|
+
s.summary = summary
|
14
|
+
s.description = <<-END
|
15
|
+
A simple but tasty Twitter agent for command line use, designed for quickly
|
16
|
+
showing the latest tweets.
|
17
|
+
END
|
18
|
+
|
19
|
+
s.email = 'tkareine@gmail.com'
|
20
|
+
s.homepage = 'https://github.com/tuomas/tweetwine'
|
18
21
|
|
19
|
-
s.authors =
|
20
|
-
s.email = "tkareine@gmail.com"
|
22
|
+
s.authors = ['Tuomas Kareinen']
|
21
23
|
|
24
|
+
s.executables = %w{tweetwine}
|
22
25
|
s.files = Dir[
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
26
|
+
'*.md',
|
27
|
+
'*.rdoc',
|
28
|
+
'LICENSE.txt',
|
29
|
+
'Gemfile',
|
30
|
+
'Rakefile',
|
31
|
+
'tweetwine.gemspec',
|
32
|
+
'{bin,contrib,lib}/**/*',
|
33
|
+
'man/**/*.[1-9]',
|
34
|
+
'man/**/*.ronn'
|
32
35
|
]
|
33
|
-
s.
|
34
|
-
|
36
|
+
s.test_files = Dir['test/**/*']
|
37
|
+
|
38
|
+
s.add_dependency 'oauth', '~> 0.4.4'
|
39
|
+
s.add_development_dependency 'contest', '~> 0.1.2'
|
40
|
+
s.add_development_dependency 'coulda', '~> 0.6.0'
|
41
|
+
s.add_development_dependency 'gem-man', '~> 0.2.0'
|
42
|
+
s.add_development_dependency 'mcmire-matchy', '~> 0.5.2'
|
43
|
+
s.add_development_dependency 'mocha', '= 0.9.8'
|
44
|
+
s.add_development_dependency 'open4', '~> 1.0.1'
|
45
|
+
s.add_development_dependency 'ronn', '~> 0.7.3'
|
46
|
+
s.add_development_dependency 'timecop', '~> 0.3.5'
|
47
|
+
s.add_development_dependency 'webmock', '~> 1.6.1'
|
48
|
+
|
49
|
+
s.post_install_message = <<-END
|
50
|
+
|
51
|
+
Tweetwine requires a JSON parser library. Ruby 1.9 comes bundled with one by
|
52
|
+
default. For Ruby 1.8, you can install 'json' gem, for example.
|
35
53
|
|
36
|
-
|
37
|
-
s.add_dependency "json", ">= 1.0.0"
|
38
|
-
s.add_development_dependency "contest", "~> 0.1.2"
|
39
|
-
s.add_development_dependency "coulda", "~> 0.6.0"
|
40
|
-
s.add_development_dependency "gem-man", "~> 0.2.0"
|
41
|
-
s.add_development_dependency "matchy", "~> 0.5.2"
|
42
|
-
s.add_development_dependency "mocha", "= 0.9.8"
|
43
|
-
s.add_development_dependency "open4", "~> 1.0.1"
|
44
|
-
s.add_development_dependency "ronn", "~> 0.7.3"
|
45
|
-
s.add_development_dependency "timecop", "~> 0.3.5"
|
46
|
-
s.add_development_dependency "webmock", "~> 1.6.1"
|
54
|
+
END
|
47
55
|
|
48
56
|
s.has_rdoc = true
|
49
|
-
s.extra_rdoc_files = Dir[
|
50
|
-
s.rdoc_options <<
|
51
|
-
<<
|
57
|
+
s.extra_rdoc_files = Dir['*.rdoc', 'LICENSE.txt']
|
58
|
+
s.rdoc_options << '--title' << "#{name} #{version}" \
|
59
|
+
<< '--exclude' << 'test'
|
52
60
|
end
|
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 0
|
7
7
|
- 3
|
8
|
-
-
|
9
|
-
version: 0.3.
|
8
|
+
- 2
|
9
|
+
version: 0.3.2
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Tuomas Kareinen
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2010-11-
|
17
|
+
date: 2010-11-17 00:00:00 +02:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
@@ -32,25 +32,10 @@ dependencies:
|
|
32
32
|
version: 0.4.4
|
33
33
|
type: :runtime
|
34
34
|
version_requirements: *id001
|
35
|
-
- !ruby/object:Gem::Dependency
|
36
|
-
name: json
|
37
|
-
prerelease: false
|
38
|
-
requirement: &id002 !ruby/object:Gem::Requirement
|
39
|
-
none: false
|
40
|
-
requirements:
|
41
|
-
- - ">="
|
42
|
-
- !ruby/object:Gem::Version
|
43
|
-
segments:
|
44
|
-
- 1
|
45
|
-
- 0
|
46
|
-
- 0
|
47
|
-
version: 1.0.0
|
48
|
-
type: :runtime
|
49
|
-
version_requirements: *id002
|
50
35
|
- !ruby/object:Gem::Dependency
|
51
36
|
name: contest
|
52
37
|
prerelease: false
|
53
|
-
requirement: &
|
38
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
54
39
|
none: false
|
55
40
|
requirements:
|
56
41
|
- - ~>
|
@@ -61,11 +46,11 @@ dependencies:
|
|
61
46
|
- 2
|
62
47
|
version: 0.1.2
|
63
48
|
type: :development
|
64
|
-
version_requirements: *
|
49
|
+
version_requirements: *id002
|
65
50
|
- !ruby/object:Gem::Dependency
|
66
51
|
name: coulda
|
67
52
|
prerelease: false
|
68
|
-
requirement: &
|
53
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
69
54
|
none: false
|
70
55
|
requirements:
|
71
56
|
- - ~>
|
@@ -76,11 +61,11 @@ dependencies:
|
|
76
61
|
- 0
|
77
62
|
version: 0.6.0
|
78
63
|
type: :development
|
79
|
-
version_requirements: *
|
64
|
+
version_requirements: *id003
|
80
65
|
- !ruby/object:Gem::Dependency
|
81
66
|
name: gem-man
|
82
67
|
prerelease: false
|
83
|
-
requirement: &
|
68
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
84
69
|
none: false
|
85
70
|
requirements:
|
86
71
|
- - ~>
|
@@ -91,11 +76,11 @@ dependencies:
|
|
91
76
|
- 0
|
92
77
|
version: 0.2.0
|
93
78
|
type: :development
|
94
|
-
version_requirements: *
|
79
|
+
version_requirements: *id004
|
95
80
|
- !ruby/object:Gem::Dependency
|
96
|
-
name: matchy
|
81
|
+
name: mcmire-matchy
|
97
82
|
prerelease: false
|
98
|
-
requirement: &
|
83
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
99
84
|
none: false
|
100
85
|
requirements:
|
101
86
|
- - ~>
|
@@ -106,11 +91,11 @@ dependencies:
|
|
106
91
|
- 2
|
107
92
|
version: 0.5.2
|
108
93
|
type: :development
|
109
|
-
version_requirements: *
|
94
|
+
version_requirements: *id005
|
110
95
|
- !ruby/object:Gem::Dependency
|
111
96
|
name: mocha
|
112
97
|
prerelease: false
|
113
|
-
requirement: &
|
98
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
114
99
|
none: false
|
115
100
|
requirements:
|
116
101
|
- - "="
|
@@ -121,11 +106,11 @@ dependencies:
|
|
121
106
|
- 8
|
122
107
|
version: 0.9.8
|
123
108
|
type: :development
|
124
|
-
version_requirements: *
|
109
|
+
version_requirements: *id006
|
125
110
|
- !ruby/object:Gem::Dependency
|
126
111
|
name: open4
|
127
112
|
prerelease: false
|
128
|
-
requirement: &
|
113
|
+
requirement: &id007 !ruby/object:Gem::Requirement
|
129
114
|
none: false
|
130
115
|
requirements:
|
131
116
|
- - ~>
|
@@ -136,11 +121,11 @@ dependencies:
|
|
136
121
|
- 1
|
137
122
|
version: 1.0.1
|
138
123
|
type: :development
|
139
|
-
version_requirements: *
|
124
|
+
version_requirements: *id007
|
140
125
|
- !ruby/object:Gem::Dependency
|
141
126
|
name: ronn
|
142
127
|
prerelease: false
|
143
|
-
requirement: &
|
128
|
+
requirement: &id008 !ruby/object:Gem::Requirement
|
144
129
|
none: false
|
145
130
|
requirements:
|
146
131
|
- - ~>
|
@@ -151,11 +136,11 @@ dependencies:
|
|
151
136
|
- 3
|
152
137
|
version: 0.7.3
|
153
138
|
type: :development
|
154
|
-
version_requirements: *
|
139
|
+
version_requirements: *id008
|
155
140
|
- !ruby/object:Gem::Dependency
|
156
141
|
name: timecop
|
157
142
|
prerelease: false
|
158
|
-
requirement: &
|
143
|
+
requirement: &id009 !ruby/object:Gem::Requirement
|
159
144
|
none: false
|
160
145
|
requirements:
|
161
146
|
- - ~>
|
@@ -166,11 +151,11 @@ dependencies:
|
|
166
151
|
- 5
|
167
152
|
version: 0.3.5
|
168
153
|
type: :development
|
169
|
-
version_requirements: *
|
154
|
+
version_requirements: *id009
|
170
155
|
- !ruby/object:Gem::Dependency
|
171
156
|
name: webmock
|
172
157
|
prerelease: false
|
173
|
-
requirement: &
|
158
|
+
requirement: &id010 !ruby/object:Gem::Requirement
|
174
159
|
none: false
|
175
160
|
requirements:
|
176
161
|
- - ~>
|
@@ -181,8 +166,11 @@ dependencies:
|
|
181
166
|
- 1
|
182
167
|
version: 1.6.1
|
183
168
|
type: :development
|
184
|
-
version_requirements: *
|
185
|
-
description:
|
169
|
+
version_requirements: *id010
|
170
|
+
description: |
|
171
|
+
A simple but tasty Twitter agent for command line use, designed for quickly
|
172
|
+
showing the latest tweets.
|
173
|
+
|
186
174
|
email: tkareine@gmail.com
|
187
175
|
executables:
|
188
176
|
- tweetwine
|
@@ -214,6 +202,8 @@ files:
|
|
214
202
|
- lib/tweetwine/url_shortener.rb
|
215
203
|
- lib/tweetwine/util.rb
|
216
204
|
- lib/tweetwine.rb
|
205
|
+
- man/tweetwine.7
|
206
|
+
- man/tweetwine.7.ronn
|
217
207
|
- test/example/application_behavior_example.rb
|
218
208
|
- test/example/example_helper.rb
|
219
209
|
- test/example/search_statuses_example.rb
|
@@ -251,16 +241,16 @@ files:
|
|
251
241
|
- test/unit/unit_helper.rb
|
252
242
|
- test/unit/url_shortener_test.rb
|
253
243
|
- test/unit/util_test.rb
|
254
|
-
- man/tweetwine.7
|
255
|
-
- man/tweetwine.7.ronn
|
256
244
|
has_rdoc: true
|
257
|
-
homepage:
|
245
|
+
homepage: https://github.com/tuomas/tweetwine
|
258
246
|
licenses: []
|
259
247
|
|
260
|
-
post_install_message:
|
248
|
+
post_install_message: "\n\
|
249
|
+
Tweetwine requires a JSON parser library. Ruby 1.9 comes bundled with one by\n\
|
250
|
+
default. For Ruby 1.8, you can install 'json' gem, for example.\n\n"
|
261
251
|
rdoc_options:
|
262
252
|
- --title
|
263
|
-
- tweetwine 0.3.
|
253
|
+
- tweetwine 0.3.2
|
264
254
|
- --exclude
|
265
255
|
- test
|
266
256
|
require_paths:
|
@@ -286,7 +276,43 @@ requirements: []
|
|
286
276
|
rubyforge_project:
|
287
277
|
rubygems_version: 1.3.7
|
288
278
|
signing_key:
|
289
|
-
specification_version:
|
290
|
-
summary:
|
291
|
-
test_files:
|
292
|
-
|
279
|
+
specification_version: 3
|
280
|
+
summary: Tweetwine shows the latest tweets from the command line quickly.
|
281
|
+
test_files:
|
282
|
+
- test/example/application_behavior_example.rb
|
283
|
+
- test/example/example_helper.rb
|
284
|
+
- test/example/search_statuses_example.rb
|
285
|
+
- test/example/show_followers_example.rb
|
286
|
+
- test/example/show_friends_example.rb
|
287
|
+
- test/example/show_home_example.rb
|
288
|
+
- test/example/show_mentions_example.rb
|
289
|
+
- test/example/show_user_example.rb
|
290
|
+
- test/example/update_status_example.rb
|
291
|
+
- test/example/use_http_proxy_example.rb
|
292
|
+
- test/fixture/config_example.yaml
|
293
|
+
- test/fixture/config_unit.yaml
|
294
|
+
- test/fixture/home.json
|
295
|
+
- test/fixture/mentions.json
|
296
|
+
- test/fixture/oauth.rb
|
297
|
+
- test/fixture/search.json
|
298
|
+
- test/fixture/shorten_rubygems.html
|
299
|
+
- test/fixture/shorten_rubylang.html
|
300
|
+
- test/fixture/update_utf8.json
|
301
|
+
- test/fixture/update_with_urls.json
|
302
|
+
- test/fixture/update_without_urls.json
|
303
|
+
- test/fixture/user.json
|
304
|
+
- test/fixture/users.json
|
305
|
+
- test/test_helper.rb
|
306
|
+
- test/unit/character_encoding_test.rb
|
307
|
+
- test/unit/cli_test.rb
|
308
|
+
- test/unit/config_test.rb
|
309
|
+
- test/unit/http_test.rb
|
310
|
+
- test/unit/oauth_test.rb
|
311
|
+
- test/unit/obfuscate_test.rb
|
312
|
+
- test/unit/option_parser_test.rb
|
313
|
+
- test/unit/promise_test.rb
|
314
|
+
- test/unit/twitter_test.rb
|
315
|
+
- test/unit/ui_test.rb
|
316
|
+
- test/unit/unit_helper.rb
|
317
|
+
- test/unit/url_shortener_test.rb
|
318
|
+
- test/unit/util_test.rb
|