http 0.7.4 → 0.8.0.pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +0 -1
  3. data/.rubocop.yml +5 -2
  4. data/CHANGES.md +24 -7
  5. data/CONTRIBUTING.md +25 -0
  6. data/Gemfile +24 -22
  7. data/Guardfile +2 -2
  8. data/README.md +34 -4
  9. data/Rakefile +7 -7
  10. data/examples/parallel_requests_with_celluloid.rb +2 -2
  11. data/http.gemspec +12 -12
  12. data/lib/http.rb +11 -10
  13. data/lib/http/cache.rb +146 -0
  14. data/lib/http/cache/headers.rb +100 -0
  15. data/lib/http/cache/null_cache.rb +13 -0
  16. data/lib/http/chainable.rb +14 -3
  17. data/lib/http/client.rb +64 -80
  18. data/lib/http/connection.rb +139 -0
  19. data/lib/http/content_type.rb +2 -2
  20. data/lib/http/errors.rb +7 -1
  21. data/lib/http/headers.rb +21 -8
  22. data/lib/http/headers/mixin.rb +1 -1
  23. data/lib/http/mime_type.rb +2 -2
  24. data/lib/http/mime_type/adapter.rb +2 -2
  25. data/lib/http/mime_type/json.rb +4 -4
  26. data/lib/http/options.rb +65 -74
  27. data/lib/http/redirector.rb +3 -3
  28. data/lib/http/request.rb +20 -13
  29. data/lib/http/request/caching.rb +95 -0
  30. data/lib/http/request/writer.rb +5 -5
  31. data/lib/http/response.rb +15 -9
  32. data/lib/http/response/body.rb +21 -8
  33. data/lib/http/response/caching.rb +142 -0
  34. data/lib/http/response/io_body.rb +63 -0
  35. data/lib/http/response/parser.rb +1 -1
  36. data/lib/http/response/status.rb +4 -12
  37. data/lib/http/response/status/reasons.rb +53 -53
  38. data/lib/http/response/string_body.rb +53 -0
  39. data/lib/http/version.rb +1 -1
  40. data/spec/lib/http/cache/headers_spec.rb +77 -0
  41. data/spec/lib/http/cache_spec.rb +182 -0
  42. data/spec/lib/http/client_spec.rb +123 -95
  43. data/spec/lib/http/content_type_spec.rb +25 -25
  44. data/spec/lib/http/headers/mixin_spec.rb +8 -8
  45. data/spec/lib/http/headers_spec.rb +213 -173
  46. data/spec/lib/http/options/body_spec.rb +5 -5
  47. data/spec/lib/http/options/form_spec.rb +3 -3
  48. data/spec/lib/http/options/headers_spec.rb +7 -7
  49. data/spec/lib/http/options/json_spec.rb +3 -3
  50. data/spec/lib/http/options/merge_spec.rb +26 -22
  51. data/spec/lib/http/options/new_spec.rb +10 -10
  52. data/spec/lib/http/options/proxy_spec.rb +8 -8
  53. data/spec/lib/http/options_spec.rb +2 -2
  54. data/spec/lib/http/redirector_spec.rb +32 -32
  55. data/spec/lib/http/request/caching_spec.rb +133 -0
  56. data/spec/lib/http/request/writer_spec.rb +26 -26
  57. data/spec/lib/http/request_spec.rb +63 -58
  58. data/spec/lib/http/response/body_spec.rb +13 -13
  59. data/spec/lib/http/response/caching_spec.rb +201 -0
  60. data/spec/lib/http/response/io_body_spec.rb +35 -0
  61. data/spec/lib/http/response/status_spec.rb +25 -25
  62. data/spec/lib/http/response/string_body_spec.rb +35 -0
  63. data/spec/lib/http/response_spec.rb +64 -45
  64. data/spec/lib/http_spec.rb +103 -76
  65. data/spec/spec_helper.rb +10 -12
  66. data/spec/support/connection_reuse_shared.rb +100 -0
  67. data/spec/support/create_certs.rb +12 -12
  68. data/spec/support/dummy_server.rb +11 -11
  69. data/spec/support/dummy_server/servlet.rb +43 -31
  70. data/spec/support/proxy_server.rb +31 -25
  71. metadata +57 -8
  72. data/spec/support/example_server.rb +0 -30
  73. data/spec/support/example_server/servlet.rb +0 -102
@@ -2,84 +2,84 @@
2
2
 
3
3
  RSpec.describe HTTP::Request::Writer do
4
4
  let(:io) { StringIO.new }
5
- let(:body) { '' }
5
+ let(:body) { "" }
6
6
  let(:headers) { HTTP::Headers.new }
7
- let(:headerstart) { 'GET /test HTTP/1.1' }
7
+ let(:headerstart) { "GET /test HTTP/1.1" }
8
8
 
9
9
  subject(:writer) { described_class.new(io, body, headers, headerstart) }
10
10
 
11
- describe '#initalize' do
12
- context 'when body is nil' do
11
+ describe "#initalize" do
12
+ context "when body is nil" do
13
13
  let(:body) { nil }
14
14
 
15
- it 'does not raise an error' do
15
+ it "does not raise an error" do
16
16
  expect { writer }.not_to raise_error
17
17
  end
18
18
  end
19
19
 
20
- context 'when body is a string' do
21
- let(:body) { 'string body' }
20
+ context "when body is a string" do
21
+ let(:body) { "string body" }
22
22
 
23
- it 'does not raise an error' do
23
+ it "does not raise an error" do
24
24
  expect { writer }.not_to raise_error
25
25
  end
26
26
  end
27
27
 
28
- context 'when body is an Enumerable' do
28
+ context "when body is an Enumerable" do
29
29
  let(:body) { %w(bees cows) }
30
30
 
31
- it 'does not raise an error' do
31
+ it "does not raise an error" do
32
32
  expect { writer }.not_to raise_error
33
33
  end
34
34
  end
35
35
 
36
- context 'when body is not string, enumerable or nil' do
36
+ context "when body is not string, enumerable or nil" do
37
37
  let(:body) { 123 }
38
38
 
39
- it 'raises an error' do
39
+ it "raises an error" do
40
40
  expect { writer }.to raise_error
41
41
  end
42
42
  end
43
43
  end
44
44
 
45
- describe '#stream' do
46
- context 'when body is Enumerable' do
45
+ describe "#stream" do
46
+ context "when body is Enumerable" do
47
47
  let(:body) { %w(bees cows) }
48
- let(:headers) { HTTP::Headers.coerce 'Transfer-Encoding' => 'chunked' }
48
+ let(:headers) { HTTP::Headers.coerce "Transfer-Encoding" => "chunked" }
49
49
 
50
- it 'writes a chunked request from an Enumerable correctly' do
50
+ it "writes a chunked request from an Enumerable correctly" do
51
51
  writer.stream
52
52
  expect(io.string).to end_with "\r\n4\r\nbees\r\n4\r\ncows\r\n0\r\n\r\n"
53
53
  end
54
54
 
55
- it 'writes Transfer-Encoding header only once' do
55
+ it "writes Transfer-Encoding header only once" do
56
56
  writer.stream
57
57
  expect(io.string).to start_with "#{headerstart}\r\nTransfer-Encoding: chunked\r\n\r\n"
58
58
  end
59
59
 
60
- context 'when Transfer-Encoding not set' do
60
+ context "when Transfer-Encoding not set" do
61
61
  let(:headers) { HTTP::Headers.new }
62
62
  specify { expect { writer.stream }.to raise_error }
63
63
  end
64
64
 
65
- context 'when Transfer-Encoding is not chunked' do
66
- let(:headers) { HTTP::Headers.coerce 'Transfer-Encoding' => 'gzip' }
65
+ context "when Transfer-Encoding is not chunked" do
66
+ let(:headers) { HTTP::Headers.coerce "Transfer-Encoding" => "gzip" }
67
67
  specify { expect { writer.stream }.to raise_error }
68
68
  end
69
69
  end
70
70
 
71
- context 'when body is a unicode String' do
72
- let(:body) { 'Привет, мир!' }
71
+ context "when body is a unicode String" do
72
+ let(:body) { "Привет, мир!" }
73
73
 
74
- it 'properly calculates Content-Length if needed' do
74
+ it "properly calculates Content-Length if needed" do
75
75
  writer.stream
76
76
  expect(io.string).to start_with "#{headerstart}\r\nContent-Length: 21\r\n\r\n"
77
77
  end
78
78
 
79
- context 'when Content-Length explicitly set' do
80
- let(:headers) { HTTP::Headers.coerce 'Content-Length' => 12 }
79
+ context "when Content-Length explicitly set" do
80
+ let(:headers) { HTTP::Headers.coerce "Content-Length" => 12 }
81
81
 
82
- it 'keeps given value' do
82
+ it "keeps given value" do
83
83
  writer.stream
84
84
  expect(io.string).to start_with "#{headerstart}\r\nContent-Length: 12\r\n\r\n"
85
85
  end
@@ -1,141 +1,146 @@
1
1
  RSpec.describe HTTP::Request do
2
- let(:headers) { {:accept => 'text/html'} }
3
- let(:request_uri) { 'http://example.com/' }
2
+ let(:headers) { {:accept => "text/html"} }
3
+ let(:request_uri) { "http://example.com/" }
4
4
 
5
5
  subject(:request) { HTTP::Request.new(:get, request_uri, headers) }
6
6
 
7
- it 'includes HTTP::Headers::Mixin' do
7
+ it "includes HTTP::Headers::Mixin" do
8
8
  expect(described_class).to include HTTP::Headers::Mixin
9
9
  end
10
10
 
11
- it 'requires URI to have scheme part' do
12
- expect { HTTP::Request.new(:get, 'example.com/') }.to \
11
+ it "requires URI to have scheme part" do
12
+ expect { HTTP::Request.new(:get, "example.com/") }.to \
13
13
  raise_error(HTTP::Request::UnsupportedSchemeError)
14
14
  end
15
15
 
16
- it 'provides a #scheme accessor' do
16
+ it "provides a #scheme accessor" do
17
17
  expect(request.scheme).to eq(:http)
18
18
  end
19
19
 
20
- it 'provides a #verb accessor' do
20
+ it "provides a #verb accessor" do
21
21
  expect(subject.verb).to eq(:get)
22
22
  end
23
23
 
24
- it 'provides a #__method__ method that outputs a deprecation warning and delegates to Object#method' do
24
+ it "provides a #__method__ method that outputs a deprecation warning and delegates to Object#method" do
25
25
  warning = capture_warning do
26
26
  expect(subject.__method__(:verb)).to eq(subject.method(:verb))
27
27
  end
28
28
  expect(warning).to match(/\[DEPRECATION\] HTTP::Request#__method__ is deprecated\. Use #method instead\.$/)
29
29
  end
30
30
 
31
- it 'sets given headers' do
32
- expect(subject['Accept']).to eq('text/html')
31
+ it "sets given headers" do
32
+ expect(subject["Accept"]).to eq("text/html")
33
33
  end
34
34
 
35
- describe 'Host header' do
36
- subject { request['Host'] }
35
+ describe "Host header" do
36
+ subject { request["Host"] }
37
37
 
38
- context 'was not given' do
39
- it { is_expected.to eq 'example.com' }
38
+ context "was not given" do
39
+ it { is_expected.to eq "example.com" }
40
40
 
41
- context 'and request URI has non-standard port' do
42
- let(:request_uri) { 'http://example.com:3000/' }
43
- it { is_expected.to eq 'example.com:3000' }
41
+ context "and request URI has non-standard port" do
42
+ let(:request_uri) { "http://example.com:3000/" }
43
+ it { is_expected.to eq "example.com:3000" }
44
44
  end
45
45
  end
46
46
 
47
- context 'was explicitly given' do
48
- before { headers[:host] = 'github.com' }
49
- it { is_expected.to eq 'github.com' }
47
+ context "was explicitly given" do
48
+ before { headers[:host] = "github.com" }
49
+ it { is_expected.to eq "github.com" }
50
50
  end
51
51
  end
52
52
 
53
- describe 'User-Agent header' do
54
- subject { request['User-Agent'] }
53
+ describe "User-Agent header" do
54
+ subject { request["User-Agent"] }
55
55
 
56
- context 'was not given' do
56
+ context "was not given" do
57
57
  it { is_expected.to eq HTTP::Request::USER_AGENT }
58
58
  end
59
59
 
60
- context 'was explicitly given' do
61
- before { headers[:user_agent] = 'MrCrawly/123' }
62
- it { is_expected.to eq 'MrCrawly/123' }
60
+ context "was explicitly given" do
61
+ before { headers[:user_agent] = "MrCrawly/123" }
62
+ it { is_expected.to eq "MrCrawly/123" }
63
63
  end
64
64
  end
65
65
 
66
- describe '#redirect' do
67
- let(:headers) { {:accept => 'text/html'} }
68
- let(:proxy) { {:proxy_username => 'douglas', :proxy_password => 'adams'} }
69
- let(:body) { 'The Ultimate Question' }
70
- let(:request) { HTTP::Request.new(:post, 'http://example.com/', headers, proxy, body) }
66
+ describe "#redirect" do
67
+ let(:headers) { {:accept => "text/html"} }
68
+ let(:proxy) { {:proxy_username => "douglas", :proxy_password => "adams"} }
69
+ let(:body) { "The Ultimate Question" }
70
+ let(:request) { HTTP::Request.new(:post, "http://example.com/", headers, proxy, body) }
71
71
 
72
- subject(:redirected) { request.redirect 'http://blog.example.com/' }
72
+ subject(:redirected) { request.redirect "http://blog.example.com/" }
73
73
 
74
- its(:uri) { is_expected.to eq URI.parse 'http://blog.example.com/' }
74
+ its(:uri) { is_expected.to eq URI.parse "http://blog.example.com/" }
75
75
 
76
76
  its(:verb) { is_expected.to eq request.verb }
77
77
  its(:body) { is_expected.to eq request.body }
78
78
  its(:proxy) { is_expected.to eq request.proxy }
79
79
 
80
- it 'presets new Host header' do
81
- expect(redirected['Host']).to eq 'blog.example.com'
80
+ it "presets new Host header" do
81
+ expect(redirected["Host"]).to eq "blog.example.com"
82
82
  end
83
83
 
84
- context 'with schema-less absolute URL given' do
85
- subject(:redirected) { request.redirect '//another.example.com/blog' }
84
+ context "with schema-less absolute URL given" do
85
+ subject(:redirected) { request.redirect "//another.example.com/blog" }
86
86
 
87
- its(:uri) { is_expected.to eq URI.parse 'http://another.example.com/blog' }
87
+ its(:uri) { is_expected.to eq URI.parse "http://another.example.com/blog" }
88
88
 
89
89
  its(:verb) { is_expected.to eq request.verb }
90
90
  its(:body) { is_expected.to eq request.body }
91
91
  its(:proxy) { is_expected.to eq request.proxy }
92
92
 
93
- it 'presets new Host header' do
94
- expect(redirected['Host']).to eq 'another.example.com'
93
+ it "presets new Host header" do
94
+ expect(redirected["Host"]).to eq "another.example.com"
95
95
  end
96
96
  end
97
97
 
98
- context 'with relative URL given' do
99
- subject(:redirected) { request.redirect '/blog' }
98
+ context "with relative URL given" do
99
+ subject(:redirected) { request.redirect "/blog" }
100
100
 
101
- its(:uri) { is_expected.to eq URI.parse 'http://example.com/blog' }
101
+ its(:uri) { is_expected.to eq URI.parse "http://example.com/blog" }
102
102
 
103
103
  its(:verb) { is_expected.to eq request.verb }
104
104
  its(:body) { is_expected.to eq request.body }
105
105
  its(:proxy) { is_expected.to eq request.proxy }
106
106
 
107
- it 'keeps Host header' do
108
- expect(redirected['Host']).to eq 'example.com'
107
+ it "keeps Host header" do
108
+ expect(redirected["Host"]).to eq "example.com"
109
109
  end
110
110
 
111
- context 'with original URI having non-standard port' do
112
- let(:request) { HTTP::Request.new(:post, 'http://example.com:8080/', headers, proxy, body) }
113
- its(:uri) { is_expected.to eq URI.parse 'http://example.com:8080/blog' }
111
+ context "with original URI having non-standard port" do
112
+ let(:request) { HTTP::Request.new(:post, "http://example.com:8080/", headers, proxy, body) }
113
+ its(:uri) { is_expected.to eq URI.parse "http://example.com:8080/blog" }
114
114
  end
115
115
  end
116
116
 
117
- context 'with relative URL that misses leading slash given' do
118
- subject(:redirected) { request.redirect 'blog' }
117
+ context "with relative URL that misses leading slash given" do
118
+ subject(:redirected) { request.redirect "blog" }
119
119
 
120
- its(:uri) { is_expected.to eq URI.parse 'http://example.com/blog' }
120
+ its(:uri) { is_expected.to eq URI.parse "http://example.com/blog" }
121
121
 
122
122
  its(:verb) { is_expected.to eq request.verb }
123
123
  its(:body) { is_expected.to eq request.body }
124
124
  its(:proxy) { is_expected.to eq request.proxy }
125
125
 
126
- it 'keeps Host header' do
127
- expect(redirected['Host']).to eq 'example.com'
126
+ it "keeps Host header" do
127
+ expect(redirected["Host"]).to eq "example.com"
128
128
  end
129
129
 
130
- context 'with original URI having non-standard port' do
131
- let(:request) { HTTP::Request.new(:post, 'http://example.com:8080/', headers, proxy, body) }
132
- its(:uri) { is_expected.to eq URI.parse 'http://example.com:8080/blog' }
130
+ context "with original URI having non-standard port" do
131
+ let(:request) { HTTP::Request.new(:post, "http://example.com:8080/", headers, proxy, body) }
132
+ its(:uri) { is_expected.to eq URI.parse "http://example.com:8080/blog" }
133
133
  end
134
134
  end
135
135
 
136
- context 'with new verb given' do
137
- subject { request.redirect 'http://blog.example.com/', :get }
136
+ context "with new verb given" do
137
+ subject { request.redirect "http://blog.example.com/", :get }
138
138
  its(:verb) { is_expected.to be :get }
139
139
  end
140
140
  end
141
+
142
+ describe "#caching" do
143
+ subject { request.caching }
144
+ it { is_expected.to be_a HTTP::Request::Caching }
145
+ end
141
146
  end
@@ -1,37 +1,37 @@
1
1
  RSpec.describe HTTP::Response::Body do
2
- let(:client) { double }
3
- let(:chunks) { ['Hello, ', 'World!'] }
2
+ let(:client) { double(:sequence_id => 0) }
3
+ let(:chunks) { ["Hello, ", "World!"] }
4
4
 
5
5
  before { allow(client).to receive(:readpartial) { chunks.shift } }
6
6
 
7
7
  subject(:body) { described_class.new client }
8
8
 
9
- it 'streams bodies from responses' do
10
- expect(subject.to_s).to eq 'Hello, World!'
9
+ it "streams bodies from responses" do
10
+ expect(subject.to_s).to eq "Hello, World!"
11
11
  end
12
12
 
13
- context 'when body empty' do
14
- let(:chunks) { [''] }
13
+ context "when body empty" do
14
+ let(:chunks) { [""] }
15
15
 
16
- it 'returns responds to empty? with true' do
16
+ it "returns responds to empty? with true" do
17
17
  expect(subject).to be_empty
18
18
  end
19
19
  end
20
20
 
21
- describe '#readpartial' do
22
- context 'with size given' do
23
- it 'passes value to underlying client' do
21
+ describe "#readpartial" do
22
+ context "with size given" do
23
+ it "passes value to underlying client" do
24
24
  expect(client).to receive(:readpartial).with(42)
25
25
  body.readpartial 42
26
26
  end
27
27
  end
28
28
 
29
- context 'without size given' do
30
- it 'does not blows up' do
29
+ context "without size given" do
30
+ it "does not blows up" do
31
31
  expect { body.readpartial }.to_not raise_error
32
32
  end
33
33
 
34
- it 'calls underlying client readpartial without specific size' do
34
+ it "calls underlying client readpartial without specific size" do
35
35
  expect(client).to receive(:readpartial).with no_args
36
36
  body.readpartial
37
37
  end
@@ -0,0 +1,201 @@
1
+ RSpec.describe HTTP::Response::Caching do
2
+ let(:cache_control) { "" }
3
+ let(:headers) { {"cache-control" => cache_control} }
4
+ let(:response) { HTTP::Response.new(200, "http/1.1", headers, "") }
5
+
6
+ subject(:caching_response) { described_class.new response }
7
+
8
+ describe "#cache_headers" do
9
+ subject { caching_response.cache_headers }
10
+ it { is_expected.to be_a HTTP::Cache::Headers }
11
+ end
12
+
13
+ it "allows requested_at to be set" do
14
+ subject.requested_at = Time.now
15
+ expect(subject.requested_at).to be_within(1).of(Time.now)
16
+ end
17
+
18
+ it "allows received_at to be set" do
19
+ subject.received_at = Time.now
20
+ expect(subject.received_at).to be_within(1).of(Time.now)
21
+ end
22
+
23
+ describe "basic 200 response w/ private cache control" do
24
+ let(:cache_control) { "private" }
25
+
26
+ it "is cacheable" do
27
+ expect(subject.cacheable?).to be_truthy
28
+ end
29
+
30
+ it "is not stale" do
31
+ expect(subject.stale?).to be_falsy
32
+ end
33
+
34
+ it "is not expired" do
35
+ expect(subject.expired?).to be_falsy
36
+ end
37
+
38
+ it "is expected to be 0 seconds old" do
39
+ expect(subject.current_age).to be_within(1).of(0)
40
+ end
41
+ end
42
+
43
+ describe "basic 200 response w/ public cache control" do
44
+ let(:cache_control) { "public" }
45
+
46
+ it "is cacheable" do
47
+ expect(subject.cacheable?).to be_truthy
48
+ end
49
+
50
+ it "is not stale" do
51
+ expect(subject.stale?).to be_falsy
52
+ end
53
+
54
+ it "is not expired" do
55
+ expect(subject.expired?).to be_falsy
56
+ end
57
+
58
+ it "is expected to be 0 seconds old" do
59
+ expect(subject.current_age).to be_within(1).of(0)
60
+ end
61
+ end
62
+
63
+ describe "basic 200 response w/ no-cache" do
64
+ let(:cache_control) { "no-cache" }
65
+
66
+ it "is not cacheable" do
67
+ expect(subject.cacheable?).to be_falsy
68
+ end
69
+
70
+ it "is not stale" do
71
+ expect(subject.stale?).to be_falsy
72
+ end
73
+
74
+ it "is not expired" do
75
+ expect(subject.expired?).to be_falsy
76
+ end
77
+
78
+ it "is expected to be 0 seconds old" do
79
+ expect(subject.current_age).to be_within(1).of(0)
80
+ end
81
+ end
82
+
83
+ describe "basic 200 response w/ no-store" do
84
+ let(:cache_control) { "no-store" }
85
+
86
+ it "is not cacheable" do
87
+ expect(subject.cacheable?).to be_falsy
88
+ end
89
+
90
+ it "is not stale" do
91
+ expect(subject.stale?).to be_falsy
92
+ end
93
+
94
+ it "is not expired" do
95
+ expect(subject.expired?).to be_falsy
96
+ end
97
+
98
+ it "is expected to be 0 seconds old" do
99
+ expect(subject.current_age).to be_within(1).of(0)
100
+ end
101
+ end
102
+
103
+ describe "basic 200 response w/ max age" do
104
+ let(:cache_control) { "max-age=100" }
105
+
106
+ it "is not cacheable" do
107
+ expect(subject.cacheable?).to be_truthy
108
+ end
109
+
110
+ it "is not stale" do
111
+ expect(subject.stale?).to be_falsy
112
+ end
113
+
114
+ it "is not expired" do
115
+ expect(subject.expired?).to be_falsy
116
+ end
117
+
118
+ it "is expected to be 0 seconds old" do
119
+ expect(subject.current_age).to be_within(1).of(0)
120
+ end
121
+ end
122
+
123
+ describe "basic 200 response w/ public & max age" do
124
+ let(:cache_control) { "public, max-age=100" }
125
+
126
+ it "is not cacheable" do
127
+ expect(subject.cacheable?).to be_truthy
128
+ end
129
+
130
+ it "is not stale" do
131
+ expect(subject.stale?).to be_falsy
132
+ end
133
+
134
+ it "is not expired" do
135
+ expect(subject.expired?).to be_falsy
136
+ end
137
+
138
+ it "is expected to be 0 seconds old" do
139
+ expect(subject.current_age).to be_within(1).of(0)
140
+ end
141
+
142
+ context "with age of max-age + 1 seconds" do
143
+ let(:headers) { {"cache-control" => cache_control, "age" => "101"} }
144
+
145
+ it "is stale" do
146
+ expect(subject.stale?).to be_truthy
147
+ end
148
+
149
+ it "is expired" do
150
+ expect(subject.expired?).to be_truthy
151
+ end
152
+
153
+ it "is expected to be max-age + 1 seconds old" do
154
+ expect(subject.current_age).to be_within(1).of(101)
155
+ end
156
+ end
157
+
158
+ context "after max-age + 1 seconds" do
159
+ before do
160
+ subject.received_at = subject.requested_at = (Time.now - 101)
161
+ end
162
+
163
+ it "is stale" do
164
+ expect(subject.stale?).to be_truthy
165
+ end
166
+
167
+ it "is expired" do
168
+ expect(subject.expired?).to be_truthy
169
+ end
170
+
171
+ it "is expected to be max-age + 1 seconds old" do
172
+ expect(subject.current_age).to be_within(1).of(101)
173
+ end
174
+ end
175
+ end
176
+
177
+ describe "basic 400 response " do
178
+ let(:response) { HTTP::Response.new(400, "http/1.1", {}, "") }
179
+
180
+ it "is not cacheable" do
181
+ expect(subject.cacheable?).to be_falsy
182
+ end
183
+
184
+ it "is not stale" do
185
+ expect(subject.stale?).to be_falsy
186
+ end
187
+
188
+ it "is not expired" do
189
+ expect(subject.expired?).to be_falsy
190
+ end
191
+
192
+ it "is expected to be 0 seconds old" do
193
+ expect(subject.current_age).to be_within(1).of(0)
194
+ end
195
+ end
196
+
197
+ describe "#caching" do
198
+ subject(:caching_response) { response.caching }
199
+ it { is_expected.to be_kind_of described_class }
200
+ end
201
+ end