embiggen 0.2.0 → 1.0.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e042af897bb6b3d3f859ef7f8b98b2baa82a549c
4
- data.tar.gz: de3ac88d70c05591727ba4cbd291dea762add074
3
+ metadata.gz: f799848928c0c97fed7139de5465eaf62b564c35
4
+ data.tar.gz: 6da8a48878868a89f5837b15c0cceca531eece18
5
5
  SHA512:
6
- metadata.gz: 52ea1b1bdd91f6d958ff730e94dafa63475794b28803743fce061980288bd9d75d80f1937eaad6c998d7b6b967daf9c274500e3fa6979f590a5044ec121f20b7
7
- data.tar.gz: 920c7a714f80a7d75ade6a8982a68c6791bc4bfda7aca81e7956469a2d8ec0e1d92c9c71995577bf6ef319d2a45c62e352b8f4fe8ef415e1f88b39e33023c3c1
6
+ metadata.gz: 185ef0720cd5c8a438aad74c4f15f68c68a5d37934ae829d777377330a57c2cfb070ac880945f5e85277a1359bad9070c45be273e567529377ce033a1e316acf
7
+ data.tar.gz: 84d6bc662878057e2dfe1f8a0e212947d41f53b4d0cd664810a47a2f96d7617de1957d79005f1cc3e700b74cfb270770f6da19cb492f279f7451a92303b1672d
data/README.md CHANGED
@@ -2,19 +2,19 @@
2
2
 
3
3
  A Ruby library to expand shortened URLs.
4
4
 
5
- **Current version:** 0.2.0
5
+ **Current version:** 1.0.0
6
6
  **Supported Ruby versions:** 1.8.7, 1.9.2, 1.9.3, 2.0, 2.1, 2.2
7
7
 
8
8
  ## Installation
9
9
 
10
10
  ```
11
- gem install embiggen -v '~> 0.2'
11
+ gem install embiggen -v '~> 1.0'
12
12
  ```
13
13
 
14
14
  Or, in your `Gemfile`:
15
15
 
16
16
  ```ruby
17
- gem 'embiggen', '~> 0.2'
17
+ gem 'embiggen', '~> 1.0'
18
18
  ```
19
19
 
20
20
  ## Usage
@@ -24,24 +24,24 @@ require 'embiggen'
24
24
 
25
25
  # Basic usage
26
26
  Embiggen::URI('https://youtu.be/dQw4w9WgXcQ').expand
27
- #=> #<URI:HTTPS https://www.youtube.com/watch?v=dQw4w9WgXcQ&feature=youtu.be>
27
+ #=> #<URI::HTTPS https://www.youtube.com/watch?v=dQw4w9WgXcQ&feature=youtu.be>
28
28
 
29
29
  # Longer-form usage
30
30
  uri = Embiggen::URI.new(URI('https://youtu.be/dQw4w9WgXcQ'))
31
31
  uri.shortened?
32
32
  #=> true
33
33
  uri.expand
34
- #=> #<URI:HTTPS https://www.youtube.com/watch?v=dQw4w9WgXcQ&feature=youtu.be>
34
+ #=> #<URI::HTTPS https://www.youtube.com/watch?v=dQw4w9WgXcQ&feature=youtu.be>
35
35
 
36
36
  # Gracefully deals with unshortened URIs
37
37
  uri = Embiggen::URI('http://www.altmetric.com')
38
38
  uri.shortened?
39
39
  #=> false
40
40
  uri.expand
41
- #=> #<URI:HTTP http://www.altmetric.com>
41
+ #=> #<URI::HTTP http://www.altmetric.com>
42
42
 
43
- # Noisier expand! for explicit error handling
44
- Embiggen::URI('http://bit.ly/bad').expand!
43
+ # Raises errors with bad shortened URIs
44
+ Embiggen::URI('http://bit.ly/bad').expand
45
45
  #=> TooManyRedirects: http://bit.ly/bad redirected too many times
46
46
  # or...
47
47
  #=> BadShortenedURI: following http://bit.ly/bad did not redirect
@@ -66,13 +66,55 @@ end
66
66
  Embiggen ships with a default list of URL shortening service domains (c.f.
67
67
  [Acknowledgements](#acknowledgements)) but as it is likely to be outdated and
68
68
  incomplete, you are strongly encouraged to supply your own via
69
- `Embiggen.configure`:
69
+ `Embiggen.configure`.
70
+
71
+ The list of shorteners is an object that responds to `include?` and will be
72
+ passed a URI. By default, Embiggen ships with a `ShortenerList` class which
73
+ takes a list of string domains and will return `true` if given a URI with a
74
+ matching host.
75
+
76
+ You can supply your own values and logic like so:
70
77
 
71
78
  ```ruby
72
79
  Embiggen.configure do |config|
73
- config.shorteners = %w(myshorten.er anoth.er)
80
+ config.shorteners = Embiggen::ShortenerList.new(%w(myshorten.er anoth.er))
74
81
  # or load from a file...
75
- config.shorteners = File.readlines('shorteners.txt').map(&:chomp)
82
+ config.shorteners = Embiggen::ShortenerList.new(File.readlines('shorteners.txt').map(&:chomp))
83
+ end
84
+
85
+ # Custom logic to attempt to expand every URI
86
+ class ExpandEverything
87
+ def self.include?(_uri)
88
+ true
89
+ end
90
+ end
91
+
92
+ Embiggen.configure do |config|
93
+ config.shorteners = ExpandEverything
94
+ end
95
+
96
+ # Use the Bitly API to only expand URIs on Bitly Pro domains
97
+ require 'bitly'
98
+ require 'forwardable'
99
+
100
+ class BitlyDomains
101
+ extend Forwardable
102
+ attr_reader :client
103
+ def_delegator :client, :pro?, :include?
104
+
105
+ def initialize(client)
106
+ @client = client
107
+ end
108
+ end
109
+
110
+ Bitly.use_api_version_3
111
+ Bitly.configure do |config|
112
+ config.api_version = 3
113
+ config.access_token = ENV.fetch('BITLY_ACCESS_TOKEN')
114
+ end
115
+
116
+ Embiggen.configure do |config|
117
+ config.shorteners = BitlyDomains.new(Bitly.client)
76
118
  end
77
119
  ```
78
120
 
@@ -97,7 +139,7 @@ valid URI.
97
139
 
98
140
  ```ruby
99
141
  Embiggen::URI('https://youtu.be/dQw4w9WgXcQ').expand
100
- #=> #<URI:HTTPS https://www.youtube.com/watch?v=dQw4w9WgXcQ&feature=youtu.be>
142
+ #=> #<URI::HTTPS https://www.youtube.com/watch?v=dQw4w9WgXcQ&feature=youtu.be>
101
143
 
102
144
  Embiggen::URI('http://www.altmetric.com/').expand
103
145
  #=> #<URI::HTTP http://www.altmetric.com/>
@@ -107,9 +149,18 @@ Embiggen::URI('https://youtu.be/dQw4w9WgXcQ').expand(:redirects => 2)
107
149
  ```
108
150
 
109
151
  Expand the given URI, returning the full version as a [`URI`][URI] if it is
110
- shortened or the original if it cannot be expanded. Will not raise any
111
- exceptions thrown during expansion (e.g. timeouts, network errors, invalid
112
- return URIs); see `expand!` for an alternative.
152
+ shortened or the original if it is not. Can raise the following exceptions
153
+ during expansion:
154
+
155
+ * `Embiggen::TooManyRedirects`: when a URI redirects more than the configured
156
+ number of times;
157
+ * `Embiggen::BadShortenedURI`: when a URI appears to be shortened but
158
+ following it does not result in a redirect;
159
+ * `Embiggen::NetworkError`: when an error occurs during expansion (e.g. a
160
+ network timeout, connection reset, unreachable host, etc.).
161
+
162
+ All of the above inherit from `Embiggen::Error` and have a `uri` method for
163
+ determining the problematic URI.
113
164
 
114
165
  Takes an optional hash of options for expansion:
115
166
 
@@ -120,30 +171,6 @@ Uses a whitelist of shortening domains (as configured through
120
171
  `Embiggen.configure`) to determine whether a URI is shortened or not. Be sure
121
172
  to [configure this to suit your needs](#shorteners).
122
173
 
123
- ### `Embiggen::URI#expand!`
124
-
125
- ```ruby
126
- Embiggen::URI('https://youtu.be/dQw4w9WgXcQ').expand!
127
- #=> #<URI:HTTPS https://www.youtube.com/watch?v=dQw4w9WgXcQ&feature=youtu.be>
128
-
129
- Embiggen::URI('http://bit.ly/some-bad-link').expand!
130
- # TooManyRedirects: http://bit.ly/some-bad-link redirected too many times
131
- ```
132
-
133
- Expand the given URI as with `Embiggen::URI#expand` but don't suppress any
134
- exceptions raised during expansion (including timeouts, network errors,
135
- invalid return URIs, too many redirects or no redirects whatsoever).
136
-
137
- Takes the same options as `Embiggen::URI#expand`.
138
-
139
- Two Embiggen-specific errors (both inheriting from `Embiggen::Error`) can be
140
- raised:
141
-
142
- * `Embiggen::TooManyRedirects`: when a URI redirects more than the configured
143
- number of times;
144
- * `Embiggen::BadShortenedURI`: when a URI appears to be shortened but
145
- following it does not result in a redirect.
146
-
147
174
  ### `Embiggen::URI#shortened?`
148
175
 
149
176
  ```ruby
@@ -170,10 +197,9 @@ end
170
197
  Override the following settings:
171
198
 
172
199
  * `timeout`: the default timeout for following any redirects (can be
173
- overridden by passing options to `Embiggen::URI#expand` or
174
- `Embiggen::URI#expand!`);
200
+ overridden by passing options to `Embiggen::URI#expand`);
175
201
  * `redirects`: the default number of redirects to follow (can be overridden by
176
- passing options to `Embiggen::URI#expand` or `Embiggen::URI#expand!`);
202
+ passing options to `Embiggen::URI#expand`);
177
203
  * `shorteners`: the list of domains of shortening services, c.f.
178
204
  [Shorteners](#shorteners).
179
205
 
@@ -1,5 +1,6 @@
1
1
  # encoding: utf-8
2
2
  require 'set'
3
+ require 'embiggen/shortener_list'
3
4
 
4
5
  module Embiggen
5
6
  class Configuration
@@ -17,7 +18,7 @@ module Embiggen
17
18
 
18
19
  # From http://longurl.org/services
19
20
  def self.shorteners
20
- @shorteners ||= Set.new(%w(
21
+ @shorteners ||= ShortenerList.new(%w(
21
22
  0rz.tw 1link.in 1url.com 2.gp 2big.at 2tu.us 3.ly 307.to 4ms.me
22
23
  4sq.com 4url.cc 6url.com 7.ly a.gg a.nf aa.cx abcurl.net ad.vu adf.ly
23
24
  adjix.com afx.cc all.fuseurl.com alturl.com amzn.to ar.gy arst.ch
@@ -0,0 +1,14 @@
1
+ module Embiggen
2
+ class Error < ::StandardError
3
+ attr_reader :uri
4
+
5
+ def initialize(message, uri)
6
+ super(message)
7
+ @uri = uri
8
+ end
9
+ end
10
+
11
+ class BadShortenedURI < Error; end
12
+ class NetworkError < Error; end
13
+ class TooManyRedirects < Error; end
14
+ end
@@ -0,0 +1,33 @@
1
+ require 'embiggen/error'
2
+ require 'net/http'
3
+
4
+ module Embiggen
5
+ class HttpClient
6
+ attr_reader :uri, :http
7
+
8
+ def initialize(uri)
9
+ @uri = uri
10
+ @http = ::Net::HTTP.new(uri.host, uri.port)
11
+ @http.use_ssl = true if uri.scheme == 'https'
12
+ end
13
+
14
+ def follow(timeout)
15
+ response = request(timeout)
16
+ return unless response.is_a?(::Net::HTTPRedirection)
17
+
18
+ response.fetch('Location')
19
+ rescue StandardError, ::Timeout::Error => e
20
+ raise NetworkError.new(
21
+ "could not follow #{uri}: #{e.message}", uri)
22
+ end
23
+
24
+ private
25
+
26
+ def request(timeout)
27
+ http.open_timeout = timeout
28
+ http.read_timeout = timeout
29
+
30
+ http.head(uri.request_uri)
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,25 @@
1
+ require 'forwardable'
2
+ require 'set'
3
+
4
+ module Embiggen
5
+ class ShortenerList
6
+ extend Forwardable
7
+ include Enumerable
8
+
9
+ attr_reader :domains
10
+
11
+ def initialize(domains)
12
+ @domains = Set.new(domains)
13
+ end
14
+
15
+ def include?(uri)
16
+ domains.any? { |domain| uri.host =~ /\b#{domain}\z/i }
17
+ end
18
+
19
+ def +(other)
20
+ self.class.new(domains + other)
21
+ end
22
+
23
+ def_delegators :domains, :<<, :size, :delete, :empty?, :each
24
+ end
25
+ end
data/lib/embiggen/uri.rb CHANGED
@@ -1,82 +1,59 @@
1
1
  require 'embiggen/configuration'
2
+ require 'embiggen/error'
3
+ require 'embiggen/http_client'
2
4
  require 'addressable/uri'
3
- require 'net/http'
5
+ require 'uri'
4
6
 
5
7
  module Embiggen
6
8
  class URI
7
- attr_reader :uri
9
+ attr_reader :uri, :http_client
8
10
 
9
11
  def initialize(uri)
10
12
  @uri = URI(::Addressable::URI.parse(uri).normalize.to_s)
13
+ @http_client = HttpClient.new(@uri)
11
14
  end
12
15
 
13
16
  def expand(request_options = {})
14
- expand!(request_options)
15
- rescue TooManyRedirects => error
16
- error.uri
17
- rescue StandardError, ::Timeout::Error
18
- uri
19
- end
20
-
21
- def expand!(request_options = {})
22
17
  return uri unless shortened?
23
18
 
24
- redirects = request_options.fetch(:redirects) { Configuration.redirects }
25
- check_redirects(redirects)
26
-
27
- location = head_location(request_options)
28
- check_location(location)
19
+ redirects = extract_redirects(request_options)
20
+ location = follow(request_options)
29
21
 
30
- URI.new(location).
31
- expand!(request_options.merge(:redirects => redirects - 1))
22
+ self.class.new(location).
23
+ expand(request_options.merge(:redirects => redirects - 1))
32
24
  end
33
25
 
34
26
  def shortened?
35
- Configuration.shorteners.any? { |domain| uri.host =~ /\b#{domain}\z/i }
27
+ Configuration.shorteners.include?(uri)
36
28
  end
37
29
 
38
30
  private
39
31
 
40
- def check_redirects(redirects)
41
- return unless redirects.zero?
42
-
43
- fail TooManyRedirects.new("#{uri} redirected too many times", uri)
44
- end
45
-
46
- def check_location(location)
47
- return if location
32
+ def extract_redirects(request_options = {})
33
+ redirects = request_options.fetch(:redirects) { Configuration.redirects }
34
+ fail TooManyRedirects.new(
35
+ "following #{uri} reached the redirect limit", uri) if redirects.zero?
48
36
 
49
- fail BadShortenedURI, "following #{uri} did not redirect"
37
+ redirects
50
38
  end
51
39
 
52
- def head_location(request_options = {})
40
+ def follow(request_options = {})
53
41
  timeout = request_options.fetch(:timeout) { Configuration.timeout }
54
42
 
55
- http.open_timeout = timeout
56
- http.read_timeout = timeout
57
-
58
- response = http.head(uri.request_uri)
43
+ location = http_client.follow(timeout)
44
+ fail BadShortenedURI.new(
45
+ "following #{uri} did not redirect", uri) unless followable?(location)
59
46
 
60
- response.fetch('Location') if response.is_a?(::Net::HTTPRedirection)
47
+ location
61
48
  end
62
49
 
63
- def http
64
- http = ::Net::HTTP.new(uri.host, uri.port)
65
- http.use_ssl = true if uri.scheme == 'https'
66
-
67
- http
68
- end
69
- end
70
-
71
- class Error < ::StandardError; end
72
- class BadShortenedURI < Error; end
73
-
74
- class TooManyRedirects < Error
75
- attr_reader :uri
50
+ def followable?(location)
51
+ return unless location
76
52
 
77
- def initialize(message, uri)
78
- super(message)
79
- @uri = uri
53
+ Addressable::URI.parse(location).absolute?
54
+ rescue Addressable::URI::InvalidURIError => e
55
+ raise BadShortenedURI.new(
56
+ "following #{uri} returns an invalid URI: #{e}", uri)
80
57
  end
81
58
  end
82
59
  end
@@ -0,0 +1,86 @@
1
+ require 'embiggen/shortener_list'
2
+
3
+ RSpec.describe Embiggen::ShortenerList do
4
+ describe '#new' do
5
+ it 'can be given a set' do
6
+ list = described_class.new(Set['a.com', 'b.com', 'b.com'])
7
+
8
+ expect(list.size).to eq(2)
9
+ end
10
+
11
+ it 'converts a given enumerable to a set' do
12
+ list = described_class.new(%w(a.com a.com))
13
+
14
+ expect(list.size).to eq(1)
15
+ end
16
+ end
17
+
18
+ describe '#include?' do
19
+ it 'returns true if a URL host is on the whitelist' do
20
+ list = described_class.new(%w(bit.ly))
21
+
22
+ expect(list).to include(URI('http://bit.ly/foo'))
23
+ end
24
+
25
+ it 'returns false if a URL host is not on the whitelist' do
26
+ list = described_class.new(%w(bit.ly))
27
+
28
+ expect(list).to_not include(URI('http://www.altmetric.com'))
29
+ end
30
+ end
31
+
32
+ describe '#<<' do
33
+ it 'appends domains to the list' do
34
+ list = described_class.new([])
35
+ list << 'bit.ly'
36
+
37
+ expect(list).to include(URI('http://bit.ly/foo'))
38
+ end
39
+
40
+ it 'can be chained' do
41
+ list = described_class.new([])
42
+ list << 'bit.ly' << 'a.com'
43
+
44
+ expect(list).to include(URI('http://bit.ly/foo'), URI('http://a.com/bar'))
45
+ end
46
+ end
47
+
48
+ describe '#size' do
49
+ it 'returns the number of domains in the list' do
50
+ list = described_class.new(%w(bit.ly ow.ly))
51
+
52
+ expect(list.size).to eq(2)
53
+ end
54
+ end
55
+
56
+ describe '#delete' do
57
+ it 'removes domains from the list' do
58
+ list = described_class.new(%w(bit.ly))
59
+ list.delete('bit.ly')
60
+
61
+ expect(list).to be_empty
62
+ end
63
+ end
64
+
65
+ describe '#+' do
66
+ it 'appends a list of domains to the existing one' do
67
+ list = described_class.new(%w(bit.ly))
68
+ list += %w(a.com)
69
+
70
+ expect(list).to include(URI('http://a.com/foo'))
71
+ end
72
+
73
+ it 'can combine two lists' do
74
+ list = described_class.new(%w(bit.ly))
75
+ list += described_class.new(%w(a.com))
76
+
77
+ expect(list).to include(URI('http://a.com/foo'))
78
+ end
79
+ end
80
+
81
+ it 'is enumerable for 1.8 compatiblity' do
82
+ list = described_class.new([])
83
+
84
+ expect(list).to be_kind_of(Enumerable)
85
+ end
86
+ end
@@ -61,46 +61,103 @@ module Embiggen
61
61
  expect { uri.expand }.to_not raise_error
62
62
  end
63
63
 
64
- it 'does not expand erroring URIs' do
65
- stub_request(:head, 'http://bit.ly/bad').to_return(:status => 500)
66
- uri = described_class.new(URI('http://bit.ly/bad'))
64
+ it 'raises an error if the URI redirects too many times' do
65
+ stub_redirect('http://bit.ly/1', 'http://bit.ly/2')
66
+ stub_redirect('http://bit.ly/2', 'http://bit.ly/3')
67
+ stub_redirect('http://bit.ly/3', 'http://bit.ly/4')
68
+ uri = described_class.new('http://bit.ly/1')
67
69
 
68
- expect(uri.expand).to eq(URI('http://bit.ly/bad'))
70
+ expect { uri.expand(:redirects => 2) }.
71
+ to raise_error(TooManyRedirects)
69
72
  end
70
73
 
71
- it 'does not expand URIs that time out' do
72
- stub_request(:head, 'http://bit.ly/bad').to_timeout
73
- uri = described_class.new(URI('http://bit.ly/bad'))
74
+ it 'retains the last URI when redirecting too many times' do
75
+ stub_redirect('http://bit.ly/1', 'http://bit.ly/2')
76
+ stub_redirect('http://bit.ly/2', 'http://bit.ly/3')
77
+ stub_redirect('http://bit.ly/3', 'http://bit.ly/4')
78
+ uri = described_class.new('http://bit.ly/1')
79
+
80
+ last_uri = nil
81
+
82
+ begin
83
+ uri.expand(:redirects => 2)
84
+ rescue TooManyRedirects => ex
85
+ last_uri = ex.uri
86
+ end
74
87
 
75
- expect(uri.expand).to eq(URI('http://bit.ly/bad'))
88
+ expect(last_uri).to eq(URI('http://bit.ly/3'))
76
89
  end
77
90
 
78
- it 'does not expand URIs whose connection resets' do
79
- stub_request(:head, 'http://bit.ly/bad').to_raise(Errno::ECONNRESET)
80
- uri = described_class.new(URI('http://bit.ly/bad'))
91
+ it 'raises an error if a shortened URI does not redirect' do
92
+ stub_request(:head, 'http://bit.ly/bad').to_return(:status => 500)
93
+ uri = described_class.new('http://bit.ly/bad')
81
94
 
82
- expect(uri.expand).to eq(URI('http://bit.ly/bad'))
95
+ expect { uri.expand }.to raise_error(BadShortenedURI)
83
96
  end
84
97
 
85
- it 'does not expand URIs whose host is unreachable' do
86
- stub_request(:head, 'http://bit.ly/bad').to_raise(Errno::EHOSTUNREACH)
87
- uri = described_class.new(URI('http://bit.ly/bad'))
98
+ it 'raises an error if the URI returned is not valid' do
99
+ stub_redirect('http://bit.ly/suspicious', '|cat /etc/passwd')
100
+ uri = described_class.new('http://bit.ly/suspicious')
88
101
 
89
- expect(uri.expand).to eq(URI('http://bit.ly/bad'))
102
+ expect { uri.expand }.to raise_error(BadShortenedURI)
90
103
  end
91
104
 
92
- it 'does not expand URIs whose name or service is not known' do
93
- stub_request(:head, 'http://bit.ly/bad').to_raise(SocketError)
94
- uri = described_class.new(URI('http://bit.ly/bad'))
105
+ it 'raises an error if the URI returned is not valid' do
106
+ stub_redirect('http://bit.ly/suspicious', 'http:')
107
+ uri = described_class.new('http://bit.ly/suspicious')
95
108
 
96
- expect(uri.expand).to eq(URI('http://bit.ly/bad'))
109
+ expect { uri.expand }.to raise_error(BadShortenedURI)
97
110
  end
98
111
 
99
- it 'takes an optional timeout' do
112
+ it 'retains the last URI if a shortened URI does not redirect' do
113
+ stub_redirect('http://bit.ly/bad', 'http://bit.ly/bad2')
114
+ stub_request(:head, 'http://bit.ly/bad2').to_return(:status => 500)
115
+ uri = described_class.new('http://bit.ly/bad')
116
+
117
+ last_uri = nil
118
+
119
+ begin
120
+ uri.expand
121
+ rescue BadShortenedURI => ex
122
+ last_uri = ex.uri
123
+ end
124
+
125
+ expect(last_uri).to eq(URI('http://bit.ly/bad2'))
126
+ end
127
+
128
+ it 'raises a network error if the URI times out' do
100
129
  stub_request(:head, 'http://bit.ly/bad').to_timeout
101
- uri = described_class.new(URI('http://bit.ly/bad'))
130
+ uri = described_class.new('http://bit.ly/bad')
131
+
132
+ expect { uri.expand }.to raise_error(NetworkError)
133
+ end
134
+
135
+ it 'raises a network error if the connection resets' do
136
+ stub_request(:head, 'http://bit.ly/bad').to_raise(::Errno::ECONNRESET)
137
+ uri = described_class.new('http://bit.ly/bad')
102
138
 
103
- expect(uri.expand(:timeout => 5)).to eq(URI('http://bit.ly/bad'))
139
+ expect { uri.expand }.to raise_error(NetworkError)
140
+ end
141
+
142
+ it 'raises a network error if the host cannot be reached' do
143
+ stub_request(:head, 'http://bit.ly/bad').to_raise(::Errno::EHOSTUNREACH)
144
+ uri = described_class.new('http://bit.ly/bad')
145
+
146
+ expect { uri.expand }.to raise_error(NetworkError)
147
+ end
148
+
149
+ it 'retains the last URI if there is a network error' do
150
+ stub_redirect('http://bit.ly/bad', 'http://bit.ly/bad2')
151
+ stub_request(:head, 'http://bit.ly/bad2').to_timeout
152
+ uri = described_class.new('http://bit.ly/bad')
153
+
154
+ begin
155
+ uri.expand
156
+ rescue NetworkError => ex
157
+ last_uri = ex.uri
158
+ end
159
+
160
+ expect(last_uri).to eq(URI('http://bit.ly/bad2'))
104
161
  end
105
162
 
106
163
  it 'expands redirects to other shorteners' do
@@ -122,7 +179,7 @@ module Embiggen
122
179
  stub_redirect('http://bit.ly/6', 'http://bit.ly/7')
123
180
  uri = described_class.new(URI('http://bit.ly/1'))
124
181
 
125
- expect(uri.expand).to eq(URI('http://bit.ly/6'))
182
+ expect { uri.expand }.to raise_error(TooManyRedirects)
126
183
  end
127
184
 
128
185
  it 'takes an optional redirect threshold' do
@@ -131,7 +188,7 @@ module Embiggen
131
188
  stub_redirect('http://bit.ly/3', 'http://bit.ly/4')
132
189
  uri = described_class.new(URI('http://bit.ly/1'))
133
190
 
134
- expect(uri.expand(:redirects => 2)).to eq(URI('http://bit.ly/3'))
191
+ expect { uri.expand(:redirects => 2) }.to raise_error(TooManyRedirects)
135
192
  end
136
193
 
137
194
  it 'uses the threshold from the configuration' do
@@ -141,7 +198,7 @@ module Embiggen
141
198
  uri = described_class.new(URI('http://bit.ly/1'))
142
199
  Configuration.redirects = 2
143
200
 
144
- expect(uri.expand).to eq(URI('http://bit.ly/3'))
201
+ expect { uri.expand }.to raise_error(TooManyRedirects)
145
202
  end
146
203
 
147
204
  it 'uses shorteners from the configuration' do
@@ -158,55 +215,8 @@ module Embiggen
158
215
  end
159
216
  end
160
217
 
161
- describe '#expand!' do
162
- it 'expands shortened URLs' do
163
- stub_redirect('https://youtu.be/dQw4w9WgXcQ',
164
- 'https://www.youtube.com/watch?v=dQw4w9WgXcQ&feature=youtu.be')
165
- uri = described_class.new(URI('https://youtu.be/dQw4w9WgXcQ'))
166
-
167
- expect(uri.expand!).to eq(URI('https://www.youtube.com/watch?v=dQw4w9WgXcQ&feature=youtu.be'))
168
- end
169
-
170
- it 'does not expand unshortened URIs' do
171
- uri = described_class.new(URI('http://www.altmetric.com'))
172
-
173
- expect(uri.expand!).to eq(URI('http://www.altmetric.com'))
174
- end
175
-
176
- it 'raises an error if the URI redirects too many times' do
177
- stub_redirect('http://bit.ly/1', 'http://bit.ly/2')
178
- stub_redirect('http://bit.ly/2', 'http://bit.ly/3')
179
- stub_redirect('http://bit.ly/3', 'http://bit.ly/4')
180
- uri = described_class.new(URI('http://bit.ly/1'))
181
-
182
- expect { uri.expand!(:redirects => 2) }.
183
- to raise_error(TooManyRedirects)
184
- end
185
-
186
- it 'raises an error if a shortened URI does not redirect' do
187
- stub_request(:head, 'http://bit.ly/bad').to_return(:status => 500)
188
- uri = described_class.new(URI('http://bit.ly/bad'))
189
-
190
- expect { uri.expand! }.to raise_error(BadShortenedURI)
191
- end
192
-
193
- it 'raises an error if the URI times out' do
194
- stub_request(:head, 'http://bit.ly/bad').to_timeout
195
- uri = described_class.new(URI('http://bit.ly/bad'))
196
-
197
- expect { uri.expand! }.to raise_error(::Timeout::Error)
198
- end
199
-
200
- it 'raises an error if the URI errors' do
201
- stub_request(:head, 'http://bit.ly/bad').to_raise(::Errno::ECONNRESET)
202
- uri = described_class.new(URI('http://bit.ly/bad'))
203
-
204
- expect { uri.expand! }.to raise_error(::Errno::ECONNRESET)
205
- end
206
- end
207
-
208
218
  describe '#uri' do
209
- it 'returns the original URI' do
219
+ it 'returns a URI' do
210
220
  uri = described_class.new(URI('http://www.altmetric.com'))
211
221
 
212
222
  expect(uri.uri).to eq(URI('http://www.altmetric.com'))
@@ -246,6 +256,14 @@ module Embiggen
246
256
  end
247
257
  end
248
258
 
259
+ describe '#http_client' do
260
+ it 'returns the HTTP client for the given URI' do
261
+ uri = described_class.new('http://www.altmetric.com')
262
+
263
+ expect(uri.http_client.uri).to eq(URI('http://www.altmetric.com'))
264
+ end
265
+ end
266
+
249
267
  def stub_redirect(short_url, expanded_url, status = 301)
250
268
  stub_request(:head, short_url).
251
269
  to_return(:status => status, :headers => { 'Location' => expanded_url })
metadata CHANGED
@@ -1,14 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: embiggen
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Paul Mucur
8
+ - Jonathan Hernandez
8
9
  autorequire:
9
10
  bindir: bin
10
11
  cert_chain: []
11
- date: 2015-05-31 00:00:00.000000000 Z
12
+ date: 2015-11-21 00:00:00.000000000 Z
12
13
  dependencies:
13
14
  - !ruby/object:Gem::Dependency
14
15
  name: addressable
@@ -64,8 +65,12 @@ files:
64
65
  - README.md
65
66
  - lib/embiggen.rb
66
67
  - lib/embiggen/configuration.rb
68
+ - lib/embiggen/error.rb
69
+ - lib/embiggen/http_client.rb
70
+ - lib/embiggen/shortener_list.rb
67
71
  - lib/embiggen/uri.rb
68
72
  - spec/embiggen/configuration_spec.rb
73
+ - spec/embiggen/shortener_list_spec.rb
69
74
  - spec/embiggen/uri_spec.rb
70
75
  - spec/embiggen_spec.rb
71
76
  - spec/spec_helper.rb
@@ -95,7 +100,7 @@ specification_version: 4
95
100
  summary: A library to expand shortened URLs
96
101
  test_files:
97
102
  - spec/embiggen/configuration_spec.rb
103
+ - spec/embiggen/shortener_list_spec.rb
98
104
  - spec/embiggen/uri_spec.rb
99
105
  - spec/embiggen_spec.rb
100
106
  - spec/spec_helper.rb
101
- has_rdoc: