restify 1.13.0 → 1.15.2

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 (40) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +98 -6
  3. data/lib/restify/adapter/em.rb +6 -13
  4. data/lib/restify/adapter/pooled_em.rb +35 -40
  5. data/lib/restify/adapter/typhoeus.rb +57 -51
  6. data/lib/restify/context.rb +5 -9
  7. data/lib/restify/error.rb +24 -0
  8. data/lib/restify/global.rb +1 -0
  9. data/lib/restify/logging.rb +1 -1
  10. data/lib/restify/processors/base/parsing.rb +5 -9
  11. data/lib/restify/processors/base.rb +2 -6
  12. data/lib/restify/promise.rb +1 -3
  13. data/lib/restify/request.rb +13 -5
  14. data/lib/restify/resource.rb +2 -2
  15. data/lib/restify/response.rb +0 -2
  16. data/lib/restify/timeout.rb +1 -3
  17. data/lib/restify/version.rb +3 -3
  18. data/spec/restify/cache_spec.rb +2 -2
  19. data/spec/restify/context_spec.rb +10 -7
  20. data/spec/restify/error_spec.rb +10 -0
  21. data/spec/restify/features/head_requests_spec.rb +7 -7
  22. data/spec/restify/features/request_bodies_spec.rb +84 -0
  23. data/spec/restify/features/request_errors_spec.rb +19 -0
  24. data/spec/restify/features/request_headers_spec.rb +16 -17
  25. data/spec/restify/features/response_errors_spec.rb +127 -0
  26. data/spec/restify/global_spec.rb +6 -6
  27. data/spec/restify/link_spec.rb +9 -9
  28. data/spec/restify/processors/base_spec.rb +1 -0
  29. data/spec/restify/processors/json_spec.rb +2 -1
  30. data/spec/restify/processors/msgpack_spec.rb +8 -7
  31. data/spec/restify/promise_spec.rb +8 -4
  32. data/spec/restify/registry_spec.rb +2 -2
  33. data/spec/restify/relation_spec.rb +18 -17
  34. data/spec/restify/resource_spec.rb +9 -8
  35. data/spec/restify/timeout_spec.rb +4 -4
  36. data/spec/restify_spec.rb +52 -57
  37. data/spec/spec_helper.rb +11 -8
  38. data/spec/support/stub_server.rb +106 -0
  39. metadata +30 -23
  40. data/spec/restify/features/response_errors.rb +0 -79
@@ -20,6 +20,7 @@ module Restify
20
20
 
21
21
  def cache
22
22
  @cache ||= begin
23
+ require 'active_support'
23
24
  require 'active_support/cache'
24
25
  Restify::Cache.new store: ActiveSupport::Cache::MemoryStore.new
25
26
  end
@@ -14,7 +14,7 @@ module Restify
14
14
  _log_prefix,
15
15
  *Array(tag),
16
16
  message,
17
- _fmt(**kwargs)
17
+ _fmt(**kwargs),
18
18
  ].map(&:to_s).reject(&:empty?).join(' ')
19
19
  end
20
20
  end
@@ -21,12 +21,10 @@ module Restify
21
21
  def parse(object, root: false)
22
22
  case object
23
23
  when Hash
24
- data = object.each_with_object({}, &method(:parse_data))
25
- relations = object.each_with_object({}, &method(:parse_rels))
24
+ data = object.each_with_object({}) {|each, obj| parse_data(each, obj) }
25
+ relations = object.each_with_object({}) {|each, obj| parse_rels(each, obj) }
26
26
 
27
- if self.class.indifferent_access?
28
- data = with_indifferent_access(data)
29
- end
27
+ data = with_indifferent_access(data) if self.class.indifferent_access?
30
28
 
31
29
  Resource.new context,
32
30
  data: data,
@@ -34,7 +32,7 @@ module Restify
34
32
  relations: relations
35
33
 
36
34
  when Array
37
- object.map(&method(:parse))
35
+ object.map {|each| parse(each) }
38
36
  else
39
37
  object
40
38
  end
@@ -56,9 +54,7 @@ module Restify
56
54
  return
57
55
  end
58
56
 
59
- if relations.key?(name) || pair[1].nil? || pair[1].to_s =~ /\A\w*\z/
60
- return
61
- end
57
+ return if relations.key?(name) || pair[1].nil? || pair[1].to_s =~ /\A\w*\z/
62
58
 
63
59
  relations[name] = pair[1].to_s
64
60
  end
@@ -5,9 +5,7 @@ module Restify
5
5
  class Base
6
6
  extend Forwardable
7
7
 
8
- attr_reader :context
9
-
10
- attr_reader :response
8
+ attr_reader :context, :response
11
9
 
12
10
  def initialize(context, response)
13
11
  @context = context
@@ -18,9 +16,7 @@ module Restify
18
16
  @resource ||= begin
19
17
  resource = load
20
18
 
21
- unless resource.is_a? Restify::Resource
22
- resource = Resource.new context, response: response, data: resource
23
- end
19
+ resource = Resource.new context, response: response, data: resource unless resource.is_a? Restify::Resource
24
20
 
25
21
  resource._restify_response = response
26
22
  merge_relations! resource._restify_relations
@@ -11,9 +11,7 @@ module Restify
11
11
  # When dependencies were passed in, but none are left after flattening,
12
12
  # then we don't have to wait for explicit dependencies or resolution
13
13
  # through a writer.
14
- if !@task && @dependencies.empty? && dependencies.any?
15
- complete true, [], nil
16
- end
14
+ complete true, [], nil if !@task && @dependencies.empty? && dependencies.any?
17
15
  end
18
16
 
19
17
  def wait(timeout = nil)
@@ -34,18 +34,26 @@ module Restify
34
34
  @uri = opts.fetch(:uri) { raise ArgumentError.new ':uri required.' }
35
35
  @data = opts.fetch(:data, nil)
36
36
  @timeout = opts.fetch(:timeout, 300)
37
- @headers = opts.fetch(:headers, {}).merge \
38
- 'Content-Type' => 'application/json'
37
+ @headers = opts.fetch(:headers, {})
38
+
39
+ @headers['Content-Type'] ||= 'application/json' if json?
39
40
  end
40
41
 
41
42
  def body
42
- @body ||= begin
43
- JSON.dump(data) unless data.nil?
44
- end
43
+ @body ||= json? ? JSON.dump(@data) : @data
45
44
  end
46
45
 
47
46
  def to_s
48
47
  "#<#{self.class} #{method.upcase} #{uri}>"
49
48
  end
49
+
50
+ private
51
+
52
+ def json?
53
+ return false if @data.nil?
54
+ return false if @data.is_a? String
55
+
56
+ true
57
+ end
50
58
  end
51
59
  end
@@ -96,8 +96,8 @@ module Restify
96
96
  def inspect
97
97
  text = {
98
98
  '@data' => data,
99
- '@relations' => @relations
100
- }.map {|k, v| k + '=' + v.inspect }.join(' ')
99
+ '@relations' => @relations,
100
+ }.map {|k, v| "#{k}=#{v.inspect}" }.join(' ')
101
101
 
102
102
  "#<#{self.class} #{text}>"
103
103
  end
@@ -89,7 +89,6 @@ module Restify
89
89
  #
90
90
  # @return [Array<Link>] Links.
91
91
  #
92
- # rubocop:disable Metrics/MethodLength
93
92
  def links
94
93
  @links ||= begin
95
94
  if headers['LINK']
@@ -104,7 +103,6 @@ module Restify
104
103
  end
105
104
  end
106
105
  end
107
- # rubocop:enable all
108
106
 
109
107
  # Return content type header from response headers.
110
108
  #
@@ -51,9 +51,7 @@ module Restify
51
51
  "Timeout must be an number but is #{value}"
52
52
  end
53
53
 
54
- unless value > 0
55
- raise ArgumentError.new "Timeout must be > 0 but is #{value.inspect}."
56
- end
54
+ raise ArgumentError.new "Timeout must be > 0 but is #{value.inspect}." unless value.positive?
57
55
 
58
56
  value
59
57
  end
@@ -3,10 +3,10 @@
3
3
  module Restify
4
4
  module VERSION
5
5
  MAJOR = 1
6
- MINOR = 13
7
- PATCH = 0
6
+ MINOR = 15
7
+ PATCH = 2
8
8
  STAGE = nil
9
- STRING = [MAJOR, MINOR, PATCH, STAGE].reject(&:nil?).join('.').freeze
9
+ STRING = [MAJOR, MINOR, PATCH, STAGE].compact.join('.').freeze
10
10
 
11
11
  def self.to_s
12
12
  STRING
@@ -3,11 +3,11 @@
3
3
  require 'spec_helper'
4
4
 
5
5
  describe Restify::Cache do
6
+ subject { cache }
7
+
6
8
  let(:store) { double 'store' }
7
9
  let(:cache) { described_class.new store }
8
10
 
9
- subject { cache }
10
-
11
11
  describe '#call' do
12
12
  let(:request) { double 'request' }
13
13
  let(:promise0) { double 'promise0' }
@@ -18,27 +18,30 @@ describe Restify::Context do
18
18
  end
19
19
 
20
20
  describe '#adapter' do
21
- let(:kwargs) { {adapter: double('adapter')} }
22
21
  subject { super().options[:adapter] }
23
22
 
23
+ let(:kwargs) { {adapter: double('adapter')} }
24
+
24
25
  it 'adapter is not serialized' do
25
26
  expect(subject).to equal nil
26
27
  end
27
28
  end
28
29
 
29
30
  describe '#cache' do
30
- let(:kwargs) { {adapter: double('cache')} }
31
31
  subject { super().options[:cache] }
32
32
 
33
+ let(:kwargs) { {adapter: double('cache')} }
34
+
33
35
  it 'cache is not serialized' do
34
36
  expect(subject).to equal nil
35
37
  end
36
38
  end
37
39
 
38
40
  describe '#headers' do
39
- let(:kwargs) { {headers: {'Accept' => 'application/json'}} }
40
41
  subject { super().options[:headers] }
41
42
 
43
+ let(:kwargs) { {headers: {'Accept' => 'application/json'}} }
44
+
42
45
  it 'all headers are serialized' do
43
46
  expect(subject).to eq('Accept' => 'application/json')
44
47
  end
@@ -46,20 +49,20 @@ describe Restify::Context do
46
49
  end
47
50
 
48
51
  context 'YAML' do
52
+ subject { load }
53
+
49
54
  let(:dump) { YAML.dump(context) }
50
55
  let(:load) { YAML.load(dump) } # rubocop:disable Security/YAMLLoad
51
56
 
52
- subject { load }
53
-
54
57
  include_examples 'serialization'
55
58
  end
56
59
 
57
60
  context 'Marshall' do
61
+ subject { load }
62
+
58
63
  let(:dump) { Marshal.dump(context) }
59
64
  let(:load) { Marshal.load(dump) } # rubocop:disable Security/MarshalLoad
60
65
 
61
- subject { load }
62
-
63
66
  include_examples 'serialization'
64
67
  end
65
68
  end
@@ -19,51 +19,61 @@ describe Restify::ResponseError do
19
19
 
20
20
  context 'with 400 Bad Request' do
21
21
  let(:code) { 400 }
22
+
22
23
  it { is_expected.to be_a ::Restify::BadRequest }
23
24
  end
24
25
 
25
26
  context 'with 401 Unauthorized' do
26
27
  let(:code) { 401 }
28
+
27
29
  it { is_expected.to be_a ::Restify::Unauthorized }
28
30
  end
29
31
 
30
32
  context 'with 404 Unauthorized' do
31
33
  let(:code) { 404 }
34
+
32
35
  it { is_expected.to be_a ::Restify::NotFound }
33
36
  end
34
37
 
35
38
  context 'with 406 Not Acceptable' do
36
39
  let(:code) { 406 }
40
+
37
41
  it { is_expected.to be_a ::Restify::NotAcceptable }
38
42
  end
39
43
 
40
44
  context 'with 410 Gone' do
41
45
  let(:code) { 410 }
46
+
42
47
  it { is_expected.to be_a ::Restify::Gone }
43
48
  end
44
49
 
45
50
  context 'with 422 Unprocessable Entity' do
46
51
  let(:code) { 422 }
52
+
47
53
  it { is_expected.to be_a ::Restify::UnprocessableEntity }
48
54
  end
49
55
 
50
56
  context 'with 500 Internal Server Error' do
51
57
  let(:code) { 500 }
58
+
52
59
  it { is_expected.to be_a ::Restify::InternalServerError }
53
60
  end
54
61
 
55
62
  context 'with 502 Bad Gateway' do
56
63
  let(:code) { 502 }
64
+
57
65
  it { is_expected.to be_a ::Restify::BadGateway }
58
66
  end
59
67
 
60
68
  context 'with 503 Service Unavailable' do
61
69
  let(:code) { 503 }
70
+
62
71
  it { is_expected.to be_a ::Restify::ServiceUnavailable }
63
72
  end
64
73
 
65
74
  context 'with 504 Gateway Timeout' do
66
75
  let(:code) { 504 }
76
+
67
77
  it { is_expected.to be_a ::Restify::GatewayTimeout }
68
78
  end
69
79
  end
@@ -4,20 +4,20 @@ require 'spec_helper'
4
4
 
5
5
  describe Restify do
6
6
  let!(:request_stub) do
7
- stub_request(:head, 'http://localhost/base')
7
+ stub_request(:head, 'http://stubserver/base')
8
8
  .with(query: hash_including({}))
9
9
  .to_return do
10
- <<-RESPONSE.gsub(/^ {8}/, '')
10
+ <<~HTTP
11
11
  HTTP/1.1 200 OK
12
12
  Content-Length: 333
13
- Transfer-Encoding: chunked
14
- Link: <http://localhost/other>; rel="neat"
15
- RESPONSE
13
+ Link: <http://localhost:9292/other>; rel="neat"
14
+ HTTP
16
15
  end
17
16
  end
18
17
 
19
18
  describe 'HEAD requests' do
20
- subject { Restify.new('http://localhost/base').head(params).value! }
19
+ subject { Restify.new('http://localhost:9292/base').head(params).value! }
20
+
21
21
  let(:params) { {} }
22
22
 
23
23
  it 'returns a resource with access to headers' do
@@ -34,7 +34,7 @@ describe Restify do
34
34
  it 'adds them to the query string' do
35
35
  subject
36
36
  expect(
37
- request_stub.with(query: {foo: 'bar'})
37
+ request_stub.with(query: {foo: 'bar'}),
38
38
  ).to have_been_requested
39
39
  end
40
40
  end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Restify do
6
+ let!(:request_stub) do
7
+ stub_request(:post, 'http://stubserver/base').to_return do
8
+ <<~HTTP
9
+ HTTP/1.1 200 OK
10
+ Link: <http://localhost:9292/other>; rel="neat"
11
+ HTTP
12
+ end
13
+ end
14
+
15
+ describe 'Request body' do
16
+ subject { Restify.new('http://localhost:9292/base').post(body, {}, {headers: headers}).value! }
17
+
18
+ let(:headers) { {} }
19
+
20
+ context 'with JSON-like data structures' do
21
+ let(:body) { {a: 'b', c: 'd'} }
22
+
23
+ it 'is serialized as JSON' do
24
+ subject
25
+
26
+ expect(
27
+ request_stub.with(body: '{"a":"b","c":"d"}'),
28
+ ).to have_been_requested
29
+ end
30
+
31
+ it 'gets a JSON media type for free' do
32
+ subject
33
+
34
+ expect(
35
+ request_stub.with(headers: {'Content-Type' => 'application/json'}),
36
+ ).to have_been_requested
37
+ end
38
+
39
+ context 'with overridden media type' do
40
+ let(:headers) { {'Content-Type' => 'application/vnd.api+json'} }
41
+
42
+ it 'respects the override' do
43
+ subject
44
+
45
+ expect(
46
+ request_stub.with(headers: {'Content-Type' => 'application/vnd.api+json'}),
47
+ ).to have_been_requested
48
+ end
49
+ end
50
+ end
51
+
52
+ context 'with strings' do
53
+ let(:body) { 'a=b&c=d' }
54
+
55
+ it 'is sent as provided' do
56
+ subject
57
+
58
+ expect(
59
+ request_stub.with(body: 'a=b&c=d'),
60
+ ).to have_been_requested
61
+ end
62
+
63
+ it 'does not get a JSON media type' do
64
+ subject
65
+
66
+ expect(
67
+ request_stub.with {|req| req.headers['Content-Type'] !~ /json/ },
68
+ ).to have_been_requested
69
+ end
70
+
71
+ context 'with overridden media type' do
72
+ let(:headers) { {'Content-Type' => 'application/text'} }
73
+
74
+ it 'respects the override' do
75
+ subject
76
+
77
+ expect(
78
+ request_stub.with(headers: {'Content-Type' => 'application/text'}),
79
+ ).to have_been_requested
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Restify, adapter: ::Restify::Adapter::Typhoeus do
6
+ before do
7
+ stub_request(:get, 'http://stubserver/base').to_timeout
8
+ end
9
+
10
+ describe 'Timeout' do
11
+ subject(:request) { Restify.new('http://localhost:9292/base').get({}, timeout: 0.1).value! }
12
+
13
+ it 'throws a network error' do
14
+ expect { request }.to raise_error Restify::NetworkError do |error|
15
+ expect(error.message).to match(/timeout/i)
16
+ end
17
+ end
18
+ end
19
+ end
@@ -4,32 +4,31 @@ require 'spec_helper'
4
4
 
5
5
  describe Restify do
6
6
  let!(:request_stub) do
7
- stub_request(:get, 'http://localhost/base').to_return do
8
- <<-RESPONSE.gsub(/^ {8}/, '')
7
+ stub_request(:get, 'http://stubserver/base').to_return do
8
+ <<~HTTP
9
9
  HTTP/1.1 200 OK
10
10
  Content-Type: application/json
11
- Transfer-Encoding: chunked
12
- Link: <http://localhost/base>; rel="self"
11
+ Link: <http://localhost:9292/base>; rel="self"
13
12
 
14
13
  { "response": "success" }
15
- RESPONSE
14
+ HTTP
16
15
  end
17
16
  end
18
17
 
19
18
  context 'with request headers configured for a single request' do
20
- let(:context) { Restify.new('http://localhost/base') }
19
+ let(:context) { Restify.new('http://localhost:9292/base') }
21
20
 
22
21
  it 'sends the headers only for that request' do
23
22
  root = context.get(
24
23
  {},
25
- {headers: {'Accept' => 'application/msgpack, application/json'}}
24
+ {headers: {'Accept' => 'application/msgpack, application/json'}},
26
25
  ).value!
27
26
 
28
27
  root.rel(:self).get.value!
29
28
 
30
29
  expect(request_stub).to have_been_requested.twice
31
30
  expect(
32
- request_stub.with(headers: {'Accept' => 'application/msgpack, application/json'})
31
+ request_stub.with(headers: {'Accept' => 'application/msgpack, application/json'}),
33
32
  ).to have_been_requested.once
34
33
  end
35
34
  end
@@ -37,8 +36,8 @@ describe Restify do
37
36
  context 'with request headers configured for context' do
38
37
  let(:context) do
39
38
  Restify.new(
40
- 'http://localhost/base',
41
- headers: {'Accept' => 'application/msgpack, application/json'}
39
+ 'http://localhost:9292/base',
40
+ headers: {'Accept' => 'application/msgpack, application/json'},
42
41
  )
43
42
  end
44
43
 
@@ -48,39 +47,39 @@ describe Restify do
48
47
  root.rel(:self).get.value!
49
48
 
50
49
  expect(
51
- request_stub.with(headers: {'Accept' => 'application/msgpack, application/json'})
50
+ request_stub.with(headers: {'Accept' => 'application/msgpack, application/json'}),
52
51
  ).to have_been_requested.twice
53
52
  end
54
53
 
55
54
  it 'can overwrite headers for single requests' do
56
55
  root = context.get(
57
56
  {},
58
- {headers: {'Accept' => 'application/xml'}}
57
+ {headers: {'Accept' => 'application/xml'}},
59
58
  ).value!
60
59
 
61
60
  root.rel(:self).get.value!
62
61
 
63
62
  expect(
64
- request_stub.with(headers: {'Accept' => 'application/xml'})
63
+ request_stub.with(headers: {'Accept' => 'application/xml'}),
65
64
  ).to have_been_requested.once
66
65
  expect(
67
- request_stub.with(headers: {'Accept' => 'application/msgpack, application/json'})
66
+ request_stub.with(headers: {'Accept' => 'application/msgpack, application/json'}),
68
67
  ).to have_been_requested.once
69
68
  end
70
69
 
71
70
  it 'can add additional headers for single requests' do
72
71
  root = context.get(
73
72
  {},
74
- {headers: {'X-Custom' => 'foobar'}}
73
+ {headers: {'X-Custom' => 'foobar'}},
75
74
  ).value!
76
75
 
77
76
  root.rel(:self).get.value!
78
77
 
79
78
  expect(
80
- request_stub.with(headers: {'Accept' => 'application/msgpack, application/json'})
79
+ request_stub.with(headers: {'Accept' => 'application/msgpack, application/json'}),
81
80
  ).to have_been_requested.twice
82
81
  expect(
83
- request_stub.with(headers: {'Accept' => 'application/msgpack, application/json', 'X-Custom' => 'foobar'})
82
+ request_stub.with(headers: {'Accept' => 'application/msgpack, application/json', 'X-Custom' => 'foobar'}),
84
83
  ).to have_been_requested.once
85
84
  end
86
85
  end
@@ -0,0 +1,127 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Restify do
6
+ before do
7
+ stub_request(:get, 'http://stubserver/base')
8
+ .to_return(status: http_status, headers: headers)
9
+ end
10
+
11
+ let(:http_status) { '200 OK' }
12
+ let(:headers) { {} }
13
+
14
+ describe 'Error handling' do
15
+ subject(:request) { Restify.new('http://localhost:9292/base').get.value! }
16
+
17
+ context 'for 400 status codes' do
18
+ let(:http_status) { '400 Bad Request' }
19
+
20
+ it 'throws a BadRequest exception' do
21
+ expect { request }.to raise_error Restify::BadRequest
22
+ end
23
+ end
24
+
25
+ context 'for 401 status codes' do
26
+ let(:http_status) { '401 Unauthorized' }
27
+
28
+ it 'throws an Unauthorized exception' do
29
+ expect { request }.to raise_error Restify::Unauthorized
30
+ end
31
+ end
32
+
33
+ context 'for 404 status codes' do
34
+ let(:http_status) { '404 Not Found' }
35
+
36
+ it 'throws a ClientError exception' do
37
+ expect { request }.to raise_error Restify::NotFound
38
+ end
39
+ end
40
+
41
+ context 'for 406 status codes' do
42
+ let(:http_status) { '406 Not Acceptable' }
43
+
44
+ it 'throws a NotAcceptable exception' do
45
+ expect { request }.to raise_error Restify::NotAcceptable
46
+ end
47
+ end
48
+
49
+ context 'for 422 status codes' do
50
+ let(:http_status) { '422 Unprocessable Entity' }
51
+
52
+ it 'throws a UnprocessableEntity exception' do
53
+ expect { request }.to raise_error Restify::UnprocessableEntity
54
+ end
55
+ end
56
+
57
+ context 'for 429 status codes' do
58
+ let(:http_status) { '429 Too Many Requests' }
59
+
60
+ it 'throws a TooManyRequests exception' do
61
+ expect { request }.to raise_error Restify::TooManyRequests
62
+ end
63
+
64
+ describe 'the exception' do
65
+ subject(:exception) do
66
+ exception = nil
67
+ begin
68
+ request
69
+ rescue Restify::TooManyRequests => e
70
+ exception = e
71
+ end
72
+ exception
73
+ end
74
+
75
+ context 'by default' do
76
+ it 'does not know when to retry again' do
77
+ expect(exception.retry_after).to be_nil
78
+ end
79
+ end
80
+
81
+ context 'with Retry-After header containing seconds' do
82
+ let(:headers) { {'Retry-After' => '120'} }
83
+
84
+ it 'determines the date correctly' do
85
+ now = DateTime.now
86
+ lower = now + Rational(119, 86_400)
87
+ upper = now + Rational(121, 86_400)
88
+
89
+ expect(exception.retry_after).to be_between(lower, upper)
90
+ end
91
+ end
92
+
93
+ context 'with Retry-After header containing HTTP date' do
94
+ let(:headers) { {'Retry-After' => 'Sun, 13 Mar 2033 13:03:33 GMT'} }
95
+
96
+ it 'parses the date correctly' do
97
+ expect(exception.retry_after.to_s).to eq '2033-03-13T13:03:33+00:00'
98
+ end
99
+ end
100
+
101
+ context 'with Retry-After header containing invalid date string' do
102
+ let(:headers) { {'Retry-After' => 'tomorrow 12:00:00'} }
103
+
104
+ it 'does not know when to retry again' do
105
+ expect(exception.retry_after).to be_nil
106
+ end
107
+ end
108
+ end
109
+ end
110
+
111
+ context 'for any other 4xx status codes' do
112
+ let(:http_status) { '415 Unsupported Media Type' }
113
+
114
+ it 'throws a generic ClientError exception' do
115
+ expect { request }.to raise_error Restify::ClientError
116
+ end
117
+ end
118
+
119
+ context 'for any 5xx status codes' do
120
+ let(:http_status) { '500 Internal Server Error' }
121
+
122
+ it 'throws a generic ServerError exception' do
123
+ expect { request }.to raise_error Restify::ServerError
124
+ end
125
+ end
126
+ end
127
+ end