restify 1.13.0 → 1.15.2

Sign up to get free protection for your applications and to get access to all the features.
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