1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
|
#
# Author:: Lamont Granquist (<lamont@opscode.com>)
# Copyright:: Copyright (c) 2013 Lamont Granquist
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
require 'spec_helper'
describe Chef::Provider::RemoteFile::HTTP do
let(:uri) { URI.parse("http://opscode.com/seattle.txt") }
let(:existing_file_source) { nil }
let(:current_resource_checksum) { "41e78735319af11327e9d2ca8535ea1c191e5ac1f76bb08d88fe6c3f93a8c8e5" }
let(:current_resource) do
current_resource = Chef::Resource::RemoteFile.new("/tmp/foo.txt")
current_resource.source(existing_file_source) if existing_file_source
current_resource.checksum(current_resource_checksum)
current_resource
end
let(:new_resource) do
Chef::Resource::RemoteFile.new("/tmp/foo.txt")
end
subject(:fetcher) do
Chef::Provider::RemoteFile::HTTP.new(uri, new_resource, current_resource)
end
let(:cache_control_data) { Chef::Provider::RemoteFile::CacheControlData.new(uri) }
describe "generating cache control headers" do
context "and there is no valid cache control data for this URI on disk" do
before do
Chef::Provider::RemoteFile::CacheControlData.should_receive(:load_and_validate).with(uri, current_resource_checksum).and_return(cache_control_data)
end
it "does not add conditional GET headers" do
fetcher.conditional_get_headers.should == {}
end
context "and the resource specifies custom headers" do
before do
new_resource.headers("x-myapp-header" => "custom-header-value")
end
it "has the user-specified custom headers" do
fetcher.headers.should == {"x-myapp-header" => "custom-header-value"}
end
end
end
context "and the cache control data matches the existing file" do
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.26
let(:etag) { "\"a-strong-unique-identifier\"" }
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3
let(:mtime) { "Tue, 21 May 2013 19:19:23 GMT" }
before do
cache_control_data.etag = etag
cache_control_data.mtime = mtime
Chef::Provider::RemoteFile::CacheControlData.should_receive(:load_and_validate).with(uri, current_resource_checksum).and_return(cache_control_data)
end
context "and no conditional get features are enabled" do
before do
new_resource.use_conditional_get(false)
end
it "does not add headers to the request" do
fetcher.headers.should == {}
end
end
context "and conditional get is enabled" do
before do
new_resource.use_conditional_get(true)
end
it "adds If-None-Match and If-Modified-Since headers to the request" do
headers = fetcher.headers
headers["if-none-match"].should == etag
headers["if-modified-since"].should == mtime
end
context "and custom headers are provided" do
before do
new_resource.headers("x-myapp-header" => "app-specific-header",
"if-none-match" => "custom-etag",
"if-modified-since" => "custom-last-modified")
end
it "preserves non-conflicting headers" do
fetcher.headers["x-myapp-header"].should == "app-specific-header"
end
it "prefers user-supplied cache control headers" do
headers = fetcher.headers
headers["if-none-match"].should == "custom-etag"
headers["if-modified-since"].should == "custom-last-modified"
end
end
end
context "and etag support is enabled" do
before do
new_resource.use_conditional_get(false)
new_resource.use_etags(true)
end
it "only adds If-None-Match headers to the request" do
headers = fetcher.headers
headers["if-none-match"].should == etag
headers.should_not have_key("if-modified-since")
end
end
context "and mtime support is enabled" do
before do
new_resource.use_conditional_get(false)
new_resource.use_last_modified(true)
end
it "only adds If-Modified-Since headers to the request" do
headers = fetcher.headers
headers["if-modified-since"].should == mtime
headers.should_not have_key("if-none-match")
end
end
end
end
describe "when fetching the uri" do
let(:expected_http_opts) { {} }
let(:expected_http_args) { [uri, expected_http_opts] }
let(:tempfile_path) { "/tmp/chef-mock-tempfile-abc123" }
let(:tempfile) { mock(Tempfile, :path => tempfile_path, :close => nil) }
let(:last_response) { {} }
let(:rest) do
rest = mock(Chef::HTTP::Simple)
rest.stub!(:streaming_request).and_return(tempfile)
rest.stub!(:last_response).and_return(last_response)
rest
end
before do
new_resource.headers({})
new_resource.use_last_modified(false)
Chef::Provider::RemoteFile::CacheControlData.should_receive(:load_and_validate).with(uri, current_resource_checksum).and_return(cache_control_data)
Chef::HTTP::Simple.should_receive(:new).with(*expected_http_args).and_return(rest)
end
describe "and the request does not return new content" do
it "should return a nil tempfile for a 304 HTTPNotModifed" do
# Streaming request returns nil for 304 errors
rest.stub(:streaming_request).and_return(nil)
fetcher.fetch.should be_nil
end
end
describe "and the request returns new content" do
let(:fetched_content_checksum) { "e2a8938cc31754f6c067b35aab1d0d4864272e9bf8504536ef3e79ebf8432305" }
before do
cache_control_data.should_receive(:save)
Chef::Digester.should_receive(:checksum_for_file).with(tempfile_path).and_return(fetched_content_checksum)
end
it "should return a tempfile" do
result = fetcher.fetch
result.should == tempfile
cache_control_data.etag.should be_nil
cache_control_data.mtime.should be_nil
cache_control_data.checksum.should == fetched_content_checksum
end
context "and the response does not contain an etag" do
let(:last_response) { {"etag" => nil} }
it "does not include an etag in the result" do
fetcher.fetch
cache_control_data.etag.should be_nil
cache_control_data.mtime.should be_nil
cache_control_data.checksum.should == fetched_content_checksum
end
end
context "and the response has an etag header" do
let(:last_response) { {"etag" => "abc123"} }
it "includes the etag value in the response" do
fetcher.fetch
cache_control_data.etag.should == "abc123"
cache_control_data.mtime.should be_nil
cache_control_data.checksum.should == fetched_content_checksum
end
end
context "and the response has no Date or Last-Modified header" do
let(:last_response) { {"date" => nil, "last_modified" => nil} }
it "does not set an mtime in the result" do
# RFC 2616 suggests that servers that do not set a Date header do not
# have a reliable clock, so no use in making them deal with dates.
fetcher.fetch
cache_control_data.etag.should be_nil
cache_control_data.mtime.should be_nil
cache_control_data.checksum.should == fetched_content_checksum
end
end
context "and the response has a Last-Modified header" do
let(:last_response) do
# Last-Modified should be preferred to Date if both are set
{"date" => "Fri, 17 May 2013 23:23:23 GMT", "last_modified" => "Fri, 17 May 2013 11:11:11 GMT"}
end
it "sets the mtime to the Last-Modified time in the response" do
fetcher.fetch
cache_control_data.etag.should be_nil
cache_control_data.mtime.should == last_response["last_modified"]
end
end
context "and the response has a Date header but no Last-Modified header" do
let(:last_response) do
{"date" => "Fri, 17 May 2013 23:23:23 GMT", "last_modified" => nil}
end
it "sets the mtime to the Date in the response" do
fetcher.fetch
cache_control_data.etag.should be_nil
cache_control_data.mtime.should == last_response["date"]
cache_control_data.checksum.should == fetched_content_checksum
end
end
context "and the target file is a tarball [CHEF-3140]" do
let(:uri) { URI.parse("http://opscode.com/tarball.tgz") }
let(:expected_http_opts) { {:disable_gzip => true} }
# CHEF-3140
# Some servers return tarballs as content type tar and encoding gzip, which
# is totally wrong. When this happens and gzip isn't disabled, Chef::HTTP::Simple
# will decompress the file for you, which is not at all what you expected
# to happen (you end up with an uncomressed tar archive instead of the
# gzipped tar archive you expected). To work around this behavior, we
# detect when users are fetching gzipped files and turn off gzip in
# Chef::HTTP::Simple.
it "should disable gzip compression in the client" do
# Before block in the parent context has set an expectation on
# Chef::HTTP::Simple.new() being called with expected arguments. Here we fufil
# that expectation, so that we can explicitly set it for this test.
# This is intended to provide insurance that refactoring of the parent
# context does not negate the value of this particular example.
Chef::HTTP::Simple.new(*expected_http_args)
Chef::HTTP::Simple.should_receive(:new).once.with(*expected_http_args).and_return(rest)
fetcher.fetch
cache_control_data.etag.should be_nil
cache_control_data.mtime.should be_nil
cache_control_data.checksum.should == fetched_content_checksum
end
end
end
end
end
|