tweetwine 0.3.2 → 0.4.0

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.
Files changed (46) hide show
  1. data/.gitignore +6 -0
  2. data/CHANGELOG.rdoc +9 -0
  3. data/Gemfile +5 -13
  4. data/LICENSE.txt +1 -1
  5. data/README.md +3 -2
  6. data/Rakefile +8 -2
  7. data/lib/tweetwine/character_encoding.rb +1 -1
  8. data/lib/tweetwine/cli.rb +9 -3
  9. data/lib/tweetwine/config.rb +3 -3
  10. data/lib/tweetwine/exceptions.rb +54 -0
  11. data/lib/tweetwine/http.rb +1 -1
  12. data/lib/tweetwine/{util.rb → support.rb} +19 -12
  13. data/lib/tweetwine/tweet.rb +69 -0
  14. data/lib/tweetwine/twitter.rb +70 -72
  15. data/lib/tweetwine/ui.rb +36 -41
  16. data/lib/tweetwine/uri.rb +31 -0
  17. data/lib/tweetwine/version.rb +15 -0
  18. data/lib/tweetwine.rb +6 -64
  19. data/man/tweetwine.7 +4 -3
  20. data/man/tweetwine.7.ronn +3 -2
  21. data/release-script.txt +10 -0
  22. data/test/example/authorization_example.rb +40 -0
  23. data/test/example/example_helper.rb +1 -1
  24. data/test/example/global_options_example.rb +64 -0
  25. data/test/example/search_statuses_example.rb +36 -31
  26. data/test/example/show_followers_example.rb +1 -1
  27. data/test/example/show_friends_example.rb +1 -1
  28. data/test/example/show_home_example.rb +17 -29
  29. data/test/example/show_mentions_example.rb +2 -2
  30. data/test/example/show_user_example.rb +14 -12
  31. data/test/example/update_status_example.rb +9 -9
  32. data/test/example/use_http_proxy_example.rb +7 -6
  33. data/test/example/{application_behavior_example.rb → user_help_example.rb} +6 -39
  34. data/test/unit/config_test.rb +1 -1
  35. data/test/unit/http_test.rb +1 -21
  36. data/test/unit/oauth_test.rb +11 -11
  37. data/test/unit/{util_test.rb → support_test.rb} +37 -38
  38. data/test/unit/tweet_helper.rb +83 -0
  39. data/test/unit/tweet_test.rb +153 -0
  40. data/test/unit/twitter_test.rb +240 -248
  41. data/test/unit/ui_test.rb +174 -78
  42. data/test/unit/unit_helper.rb +18 -6
  43. data/test/unit/uri_test.rb +41 -0
  44. data/test/unit/url_shortener_test.rb +7 -7
  45. data/tweetwine.gemspec +12 -22
  46. metadata +52 -73
@@ -3,9 +3,9 @@
3
3
  require "example_helper"
4
4
 
5
5
  Feature "show user's tweets" do
6
- in_order_to "to see what's going on with a specific user"
7
6
  as_a "authenticated user"
8
- i_want_to "see that user's tweets"
7
+ i_want_to "see a specific user's tweets"
8
+ in_order_to "to know what the user has been doing"
9
9
 
10
10
  USER_URL = "https://api.twitter.com/1/statuses/user_timeline.json?count=20&page=1&screen_name=%s"
11
11
  USER_FIXTURE = fixture_file('user.json')
@@ -17,11 +17,7 @@ Feature "show user's tweets" do
17
17
  end
18
18
 
19
19
  Then "the application shows my tweets" do
20
- @output[0].should == "jillv, in reply to chris, 9 hours ago:"
21
- @output[1].should == "@chris wait me until the garden"
22
- @output[2].should == ""
23
- @output[3].should == "jillv, 3 days ago:"
24
- @output[4].should == "so boring to wait"
20
+ should_output_tweets
25
21
  end
26
22
  end
27
23
 
@@ -32,11 +28,17 @@ Feature "show user's tweets" do
32
28
  end
33
29
 
34
30
  Then "the application shows the user's tweets" do
35
- @output[0].should == "jillv, in reply to chris, 9 hours ago:"
36
- @output[1].should == "@chris wait me until the garden"
37
- @output[2].should == ""
38
- @output[3].should == "jillv, 3 days ago:"
39
- @output[4].should == "so boring to wait"
31
+ should_output_tweets
40
32
  end
41
33
  end
34
+
35
+ private
36
+
37
+ def should_output_tweets
38
+ @output[0].should == "jillv, in reply to chris, 9 hours ago:"
39
+ @output[1].should == "@chris wait me until the garden"
40
+ @output[2].should == ""
41
+ @output[3].should == "jillv, 3 days ago:"
42
+ @output[4].should == "so boring to wait"
43
+ end
42
44
  end
@@ -4,9 +4,9 @@ require "example_helper"
4
4
  require "yaml"
5
5
 
6
6
  Feature "update my status (send new tweet)" do
7
- in_order_to "tell something about me to the world"
8
7
  as_a "authenticated user"
9
8
  i_want_to "update my status"
9
+ in_order_to "tell something about me to the world"
10
10
 
11
11
  RUBYGEMS_FIXTURE = fixture_file('shorten_rubygems.html')
12
12
  RUBYGEMS_FULL_URL = 'http://rubygems.org/'
@@ -146,14 +146,14 @@ Feature "update my status (send new tweet)" do
146
146
  @shorten_rubygems_body = "#{SHORTEN_CONFIG[:url_param_name]}=#{RUBYGEMS_FULL_URL_ENC}"
147
147
  @shorten_rubylang_body = "#{SHORTEN_CONFIG[:url_param_name]}=#{RUBYLANG_FULL_URL_ENC}"
148
148
  stub_http_request(SHORTEN_METHOD, SHORTEN_CONFIG[:service_url]).
149
- with(:body => @shorten_rubygems_body).
150
- to_return(:body => RUBYGEMS_FIXTURE)
149
+ with(:body => @shorten_rubygems_body).
150
+ to_return(:body => RUBYGEMS_FIXTURE)
151
151
  stub_http_request(SHORTEN_METHOD, SHORTEN_CONFIG[:service_url]).
152
- with(:body => @shorten_rubylang_body).
153
- to_return(:body => RUBYLANG_FIXTURE)
152
+ with(:body => @shorten_rubylang_body).
153
+ to_return(:body => RUBYLANG_FIXTURE)
154
154
  stub_http_request(:post, UPDATE_URL).
155
- with(:body => BODY_WITH_SHORT_URLS).
156
- to_return(:body => UPDATE_FIXTURE_WITH_URLS)
155
+ with(:body => BODY_WITH_SHORT_URLS).
156
+ to_return(:body => UPDATE_FIXTURE_WITH_URLS)
157
157
  @output = start_cli %W{--no-colors update #{STATUS_WITH_FULL_URLS}}, %w{y}
158
158
  end
159
159
 
@@ -169,8 +169,8 @@ Feature "update my status (send new tweet)" do
169
169
  Scenario "disable URL shortening for status updates" do
170
170
  When "I have configured URL shortening, start the application with 'update' command with --no-url-shorten option, input status containing URLs, and confirm" do
171
171
  stub_http_request(:post, UPDATE_URL).
172
- with(:body => BODY_WITH_SHORT_URLS).
173
- to_return(:body => UPDATE_FIXTURE_WITH_URLS)
172
+ with(:body => BODY_WITH_SHORT_URLS).
173
+ to_return(:body => UPDATE_FIXTURE_WITH_URLS)
174
174
  @output = start_cli %W{--no-colors --no-url-shorten update #{STATUS_WITH_SHORT_URLS}}, %w{y}
175
175
  end
176
176
 
@@ -3,13 +3,15 @@
3
3
  require "example_helper"
4
4
 
5
5
  Feature "using HTTP proxy" do
6
- in_order_to "tweet behind an HTTP proxy"
7
6
  as_a "authenticated user"
8
- i_want_to "tweet as before"
7
+ i_want_to "use HTTP proxy"
8
+ in_order_to "tweet behind a firewall"
9
+
10
+ HOME_URL = "https://api.twitter.com/1/statuses/home_timeline.json?count=20&page=1"
9
11
 
10
12
  def setup
11
13
  super
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'))
14
+ stub_http_request(:get, HOME_URL).to_return(:body => fixture_file('home.json'))
13
15
  end
14
16
 
15
17
  Scenario "enable proxy via environment variable" do
@@ -20,7 +22,6 @@ Feature "using HTTP proxy" do
20
22
 
21
23
  Then "the application uses the proxy to fetch my home timeline" do
22
24
  should_use_proxy
23
- @output[0].should == "pelit, 11 days ago:"
24
25
  end
25
26
  end
26
27
 
@@ -32,7 +33,6 @@ Feature "using HTTP proxy" do
32
33
 
33
34
  Then "the application uses the proxy to fetch my home timeline" do
34
35
  should_use_proxy
35
- @output[0].should == "pelit, 11 days ago:"
36
36
  end
37
37
  end
38
38
 
@@ -44,7 +44,6 @@ Feature "using HTTP proxy" do
44
44
 
45
45
  Then "the application does not use the proxy to fetch my home timeline" do
46
46
  should_not_use_proxy
47
- @output[0].should == "pelit, 11 days ago:"
48
47
  end
49
48
  end
50
49
 
@@ -55,10 +54,12 @@ Feature "using HTTP proxy" do
55
54
  nh.proxy_class?.should == true
56
55
  nh.instance_variable_get(:@proxy_address).should == PROXY_HOST
57
56
  nh.instance_variable_get(:@proxy_port).should == PROXY_PORT
57
+ assert_requested(:get, HOME_URL)
58
58
  end
59
59
 
60
60
  def should_not_use_proxy
61
61
  net_http.proxy_class?.should == false
62
+ assert_requested(:get, HOME_URL)
62
63
  end
63
64
 
64
65
  def net_http
@@ -1,14 +1,11 @@
1
1
  # coding: utf-8
2
2
 
3
3
  require "example_helper"
4
- require "fixture/oauth"
5
4
 
6
- include Tweetwine::Test::Fixture::OAuth
7
-
8
- Feature "application behavior" do
9
- in_order_to "know about the application"
5
+ Feature "user help" do
10
6
  as_a "user"
11
- i_want_to "see helpful messages"
7
+ i_want_to "see help and error messages"
8
+ in_order_to "know how to use the application"
12
9
 
13
10
  %w{-v version ver v}.each do |arg|
14
11
  Scenario "show version with '#{arg}'" do
@@ -48,8 +45,9 @@ Usage: #{CLI::EXEC_NAME} [global_options...] [<command>] [command_options...]
48
45
  --no-colors Disable ANSI colors for output.
49
46
  --no-http-proxy Disable HTTP(S) proxy.
50
47
  --no-url-shorten Disable URL shortening.
51
- -n, --num <n> Number of statuses per page (default 20).
52
- -p, --page <p> Page number for statuses (default 1).
48
+ -n, --num <n> Number of tweets per page (default 20).
49
+ -p, --page <p> Page number for tweets (default 1).
50
+ -r, --reverse Show tweets in reverse order (default false).
53
51
  -u, --username <user> User to authenticate (default '#{USER}').
54
52
  -v, --version Show version and exit.
55
53
 
@@ -139,35 +137,4 @@ Usage: tweetwine help [<command>]
139
137
  @status.exitstatus.should == UnknownCommandError.status_code
140
138
  end
141
139
  end
142
-
143
- Scenario "authorize user with OAuth and save access token" do
144
- When "I start the application with 'home' command and the command fails due to me being unauthorized" do
145
- @command_url = "https://api.twitter.com/1/statuses/home_timeline.json?count=20&page=1"
146
- stub_http_request(METHOD, REQUEST_TOKEN_URL).to_return(:body => REQUEST_TOKEN_RESPONSE)
147
- stub_http_request(METHOD, ACCESS_TOKEN_URL).to_return(:body => ACCESS_TOKEN_RESPONSE)
148
- stub_http_request(:get, @command_url).
149
- to_return(:status => [401, 'Unauthorized']).then.
150
- to_return(:body => fixture_file('home.json'))
151
- @output = nil
152
- @config_contents = nil
153
- @config_mode = nil
154
- config_file = 'tweetwine.tmp'
155
- in_temp_dir do
156
- @output = start_cli %W{--no-colors -f #{config_file} home}, [PIN], {}
157
- @config_contents = YAML.load_file(config_file)
158
- @config_mode = file_mode(config_file)
159
- end
160
- end
161
-
162
- Then "the application authorizes me, saves access token to config file, and tries the command again" do
163
- assert_requested(METHOD, REQUEST_TOKEN_URL)
164
- assert_requested(METHOD, ACCESS_TOKEN_URL)
165
- assert_requested(:get, @command_url, :headers => {'Authorization' => /^OAuth /}, :times => 2)
166
- @output[0].should == "Please authorize: #{AUTHORIZE_URL}"
167
- @output[1].should =~ /^Enter PIN:/
168
- @output[2].should == "F1-kausi alkaa marraskuussa http://bit.ly/1qQwjQ"
169
- @config_contents['oauth_access'].empty?.should == false
170
- @config_mode.should == 0600
171
- end
172
- end
173
140
  end
@@ -186,7 +186,7 @@ class ConfigTest < UnitTestCase
186
186
  end
187
187
 
188
188
  teardown do
189
- FileUtils.remove_entry_secure @tmp_dir
189
+ FileUtils.rm_rf @tmp_dir
190
190
  end
191
191
 
192
192
  should "ignore nonexisting config file for initial read" do
@@ -14,13 +14,9 @@ class HttpTest < UnitTestCase
14
14
  PAYLOAD = {:msg => 'gloomy night'}
15
15
 
16
16
  setup do
17
- stub_sleep
18
17
  mock_ui
19
18
  @client = Http::Client.new
20
- end
21
-
22
- teardown do
23
- restore_sleep
19
+ def @client.sleep(*args); end # speed up tests
24
20
  end
25
21
 
26
22
  %w{http https}.each do |scheme|
@@ -178,22 +174,6 @@ class HttpTest < UnitTestCase
178
174
  end
179
175
  end
180
176
  end
181
-
182
- private
183
-
184
- def stub_sleep
185
- Kernel.class_eval do
186
- alias_method :__original_sleep, :sleep
187
- define_method(:sleep) { |*args| }
188
- end
189
- end
190
-
191
- def restore_sleep
192
- Kernel.class_eval do
193
- remove_method :sleep
194
- alias_method :sleep, :__original_sleep
195
- end
196
- end
197
177
  end
198
178
 
199
179
  end
@@ -27,15 +27,15 @@ class OAuthTest < UnitTestCase
27
27
 
28
28
  should "raise AuthorizationError if OAuth dance fails due to HTTP 4xx response" do
29
29
  @http.expects(:post).
30
- with(REQUEST_TOKEN_URL).
31
- raises(HttpError.new(401, 'Unauthorized'))
30
+ with(REQUEST_TOKEN_URL).
31
+ raises(HttpError.new(401, 'Unauthorized'))
32
32
  assert_raise(AuthorizationError) { @oauth.authorize }
33
33
  end
34
34
 
35
35
  should "pass other exceptions than due to HTTP 4xx responses through" do
36
36
  @http.expects(:post).
37
- with(REQUEST_TOKEN_URL).
38
- raises(HttpError.new(503, 'Service Unavailable'))
37
+ with(REQUEST_TOKEN_URL).
38
+ raises(HttpError.new(503, 'Service Unavailable'))
39
39
  assert_raise(HttpError) { @oauth.authorize }
40
40
  end
41
41
 
@@ -56,16 +56,16 @@ class OAuthTest < UnitTestCase
56
56
 
57
57
  def expect_complete_oauth_dance
58
58
  @http.expects(:post).
59
- with(REQUEST_TOKEN_URL).
60
- returns(REQUEST_TOKEN_RESPONSE)
59
+ with(REQUEST_TOKEN_URL).
60
+ returns(REQUEST_TOKEN_RESPONSE)
61
61
  @ui.expects(:info).
62
- with("Please authorize: #{AUTHORIZE_URL}")
62
+ with("Please authorize: #{AUTHORIZE_URL}")
63
63
  @ui.expects(:prompt).
64
- with('Enter PIN').
65
- returns(PIN)
64
+ with('Enter PIN').
65
+ returns(PIN)
66
66
  @http.expects(:post).
67
- with(ACCESS_TOKEN_URL).
68
- returns(ACCESS_TOKEN_RESPONSE)
67
+ with(ACCESS_TOKEN_URL).
68
+ returns(ACCESS_TOKEN_RESPONSE)
69
69
  end
70
70
 
71
71
  def fake_http_connection_and_request
@@ -6,13 +6,44 @@ require "time"
6
6
 
7
7
  module Tweetwine::Test
8
8
 
9
- class UtilTest < UnitTestCase
10
- include Util
9
+ class SupportTest < UnitTestCase
10
+ include Support
11
11
 
12
- context "for checking whether a string is blank" do
13
- should("return true for nil") { assert blank?(nil) }
14
- should("return true for empty string") { assert blank?('') }
15
- should("return false for nonempty string") { assert !blank?('a') }
12
+ context "for determining emptiness" do
13
+ [
14
+ [nil, true, "nil"],
15
+ ["", true, "empty string"],
16
+ ["foo", false, "nonempty string"],
17
+ [[], true, "empty array"],
18
+ [%w{a b}, false, "nonempty array"]
19
+ ].each do |(subject, emptiness, description)|
20
+ should "return #{emptiness} for blank? with #{description}" do
21
+ assert_equal emptiness, blank?(subject)
22
+ end
23
+
24
+ should "return #{!emptiness} for present? with #{description}" do
25
+ assert_equal !emptiness, present?(subject)
26
+ end
27
+
28
+ should "return non-empty subject for presence, when subject is #{description}" do
29
+ actual = presence(subject)
30
+ expected = present?(subject) ? subject : nil
31
+ assert_same expected, actual
32
+ end
33
+
34
+ should "return value of block for presence, when subject is #{description}" do
35
+ actual = presence(subject) { |s| s.size }
36
+ expected = present?(subject) ? subject.size : nil
37
+ assert_same expected, actual
38
+ end
39
+
40
+ should "allow presence to be used with || operator, when subject is #{description}" do
41
+ alternative = "hey"
42
+ actual = presence(subject) || alternative
43
+ expected = present?(subject) ? subject : alternative
44
+ assert_same expected, actual
45
+ end
46
+ end
16
47
  end
17
48
 
18
49
  context "for humanizing time differences" do
@@ -154,38 +185,6 @@ class UtilTest < UnitTestCase
154
185
  end
155
186
  end
156
187
 
157
- context "for percent-encoding strings" do
158
- [
159
- %w{a a},
160
- %w{B B},
161
- %w{3 3},
162
- %w{. period},
163
- %w{- dash},
164
- %w{_ underscore},
165
- ].each do |char, desc|
166
- should "not encode safe characters, case #{desc}" do
167
- assert_equal char, percent_encode(char)
168
- end
169
- end
170
-
171
- should "encode space character with percent-encoding, not with '+' character" do
172
- assert_equal "%20", percent_encode(" ")
173
- end
174
-
175
- [
176
- %w{& %26 ampersand},
177
- %w{? %3F question mark},
178
- %w{/ %2F slash},
179
- %w{: %3A colon},
180
- %w{, %2C comma}
181
- ].each do |char, expected, desc|
182
- should "encode unsafe characters that URI.encode leaves by default unencoded, case #{desc}" do
183
- assert_equal char, URI.encode(char)
184
- assert_equal expected, percent_encode(char)
185
- end
186
- end
187
- end
188
-
189
188
  context "for unescaping HTML" do
190
189
  [
191
190
  %w{a a},
@@ -0,0 +1,83 @@
1
+ # coding: utf-8
2
+
3
+ module Tweetwine::Test
4
+ module TweetHelper
5
+ FIELD_PATHS = {
6
+ :from_user => %w{screen_name},
7
+ :to_user => %w{status in_reply_to_screen_name},
8
+ :retweet => %w{retweeted_status},
9
+ :created_at => %w{status created_at},
10
+ :status => %w{status text}
11
+ }.freeze
12
+
13
+ DEFAULT_FIELD_VALUES = {
14
+ :from_user => 'fred',
15
+ :to_user => nil,
16
+ :retweet => nil,
17
+ :created_at => Time.utc(2011, 'feb', 17, 22, 28, 0).iso8601,
18
+ :status => nil
19
+ }.freeze
20
+
21
+ RECORD_SKELETON = {
22
+ 'screen_name' => nil,
23
+ 'retweeted_status' => nil,
24
+ 'status' => {
25
+ 'in_reply_to_screen_name' => nil,
26
+ 'created_at' => nil,
27
+ 'text' => nil
28
+ }.freeze
29
+ }.freeze
30
+
31
+ def create_record(fields = {})
32
+ record = create_nonrt_record(nonrt_fields(fields))
33
+ modify_to_rt_record(record, fields[:rt_user]) if fields[:rt_user]
34
+ record
35
+ end
36
+
37
+ def create_tweet(fields = {})
38
+ Tweetwine::Tweet.new(create_record(fields), FIELD_PATHS)
39
+ end
40
+
41
+ private
42
+
43
+ def find_hash_of_field_path(hash, path)
44
+ path = [path] unless path.is_a? Array
45
+ if path.size > 1
46
+ hash_path, field = path[0..-2], path.last
47
+ [Tweetwine::Support.find_hash_path(hash, hash_path), field]
48
+ else
49
+ [hash, path.first]
50
+ end
51
+ end
52
+
53
+ def nonrt_fields(fields)
54
+ DEFAULT_FIELD_VALUES.merge(fields.reject { |(k, v)| k == :rt_user })
55
+ end
56
+
57
+ def create_nonrt_record(fields)
58
+ FIELD_PATHS.inject(deep_copy(RECORD_SKELETON)) do |result, (path_name, path_actual)|
59
+ hash, field = find_hash_of_field_path(result, path_actual)
60
+ hash[field] = fields[path_name]
61
+ result
62
+ end
63
+ end
64
+
65
+ def modify_to_rt_record(record, rt_user)
66
+ rt_hash, rt_field = find_hash_of_field_path(record, FIELD_PATHS[:retweet])
67
+ fr_hash, fr_field = find_hash_of_field_path(record, FIELD_PATHS[:from_user])
68
+ st_hash, st_field = find_hash_of_field_path(record, FIELD_PATHS[:status])
69
+ rt_hash[rt_field] = {
70
+ fr_field => fr_hash[fr_field].dup,
71
+ 'status' => st_hash.dup
72
+ }
73
+ fr_hash[fr_field] = rt_user
74
+ st_hash[st_field] = 'retweeted status text. you should not see me.'
75
+ cr_hash, cr_field = find_hash_of_field_path(record, FIELD_PATHS[:created_at])
76
+ cr_hash[cr_field] = 'old created timestamp. you should not see me.'
77
+ end
78
+
79
+ def deep_copy(obj)
80
+ Marshal.load(Marshal.dump(obj))
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,153 @@
1
+ # coding: utf-8
2
+
3
+ require 'unit_helper'
4
+ require 'tweet_helper'
5
+
6
+ module Tweetwine::Test
7
+
8
+ class TweetTest < UnitTestCase
9
+ include TweetHelper
10
+
11
+ context "for initialization" do
12
+ should "raise exception if from user field is not found" do
13
+ assert_raise(ArgumentError) { create_tweet(:from_user => nil) }
14
+ end
15
+ end
16
+
17
+ context "for equivalence" do
18
+ should "equal to another tweet with same content" do
19
+ status = 'foo'
20
+ first = create_tweet(:status => status)
21
+ second = create_tweet(:status => status)
22
+ assert_equal first, second
23
+ end
24
+
25
+ should "not equal to another tweet with different content" do
26
+ first = create_tweet(:status => 'ahem')
27
+ second = create_tweet(:status => 'hmph')
28
+ assert_not_equal first, second
29
+ end
30
+ end
31
+
32
+ context "for handling regular tweet" do
33
+ setup do
34
+ @status = 'lurking'
35
+ @tweet = create_tweet(:status => @status)
36
+ end
37
+
38
+ should "detect tweet as not retweet" do
39
+ assert_equal false, @tweet.retweet?
40
+ end
41
+
42
+ should "not have retweeting user" do
43
+ assert_nil @tweet.rt_user
44
+ end
45
+
46
+ should "have originating user field" do
47
+ assert_equal(DEFAULT_FIELD_VALUES[:from_user], @tweet.from_user)
48
+ end
49
+
50
+ should "detect tweet as not being a reply" do
51
+ assert_equal false, @tweet.reply?
52
+ end
53
+
54
+ should "have no destination user" do
55
+ assert_nil @tweet.to_user
56
+ end
57
+
58
+ should "detect having creation timestamp" do
59
+ assert_equal true, @tweet.timestamped?
60
+ end
61
+
62
+ should "have creation timestamp" do
63
+ assert_equal Time.parse(DEFAULT_FIELD_VALUES[:created_at]), @tweet.created_at
64
+ end
65
+
66
+ should "detect having status" do
67
+ assert_equal true, @tweet.status?
68
+ end
69
+
70
+ should "have status" do
71
+ assert_equal @status, @tweet.status
72
+ end
73
+ end
74
+
75
+ context "for handling replying tweet" do
76
+ setup do
77
+ @to_user = 'jacko'
78
+ @tweet = create_tweet(:to_user => @to_user)
79
+ end
80
+
81
+ should "detect tweet as being a reply" do
82
+ assert_equal true, @tweet.reply?
83
+ end
84
+
85
+ should "have destination user" do
86
+ assert_equal @to_user, @tweet.to_user
87
+ end
88
+ end
89
+
90
+ context "for handling retweet" do
91
+ setup do
92
+ @rt_user = 'jonathan'
93
+ @status = 'tweet worth retweeting'
94
+ @tweet = create_tweet(:rt_user => @rt_user, :status => @status)
95
+ end
96
+
97
+ should "detect tweet as retweet" do
98
+ assert_equal true, @tweet.retweet?
99
+ end
100
+
101
+ should "have retweeting user" do
102
+ assert_equal @rt_user, @tweet.rt_user
103
+ end
104
+
105
+ should "have originating user" do
106
+ assert_equal DEFAULT_FIELD_VALUES[:from_user], @tweet.from_user
107
+ end
108
+
109
+ should "detect having creation timestamp of the original tweet" do
110
+ assert_equal true, @tweet.timestamped?
111
+ end
112
+
113
+ should "have creation timestamp of the original tweet" do
114
+ assert_equal Time.parse(DEFAULT_FIELD_VALUES[:created_at]), @tweet.created_at
115
+ end
116
+
117
+ should "detect having status of the original tweet" do
118
+ assert_equal true, @tweet.status?
119
+ end
120
+
121
+ should "have status of the original tweet" do
122
+ assert_equal @status, @tweet.status
123
+ end
124
+ end
125
+
126
+ context "for handling tweet with just user info" do
127
+ setup do
128
+ @tweet = create_tweet(:to_user => nil, :status => nil, :created_at => nil)
129
+ end
130
+
131
+ should "detect tweet as not retweet" do
132
+ assert_equal false, @tweet.retweet?
133
+ end
134
+
135
+ should "have originating user field" do
136
+ assert_equal(DEFAULT_FIELD_VALUES[:from_user], @tweet.from_user)
137
+ end
138
+
139
+ should "detect tweet as not being a reply" do
140
+ assert_equal false, @tweet.reply?
141
+ end
142
+
143
+ should "detect having no creation timestamp" do
144
+ assert_equal false, @tweet.timestamped?
145
+ end
146
+
147
+ should "detect having no status" do
148
+ assert_equal false, @tweet.status?
149
+ end
150
+ end
151
+ end
152
+
153
+ end