chef 11.10.0.alpha.1-x86-mingw32 → 11.10.0.rc.0-x86-mingw32

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 (131) hide show
  1. data/README.md +57 -36
  2. data/distro/common/html/chef-client.8.html +4 -4
  3. data/distro/common/html/chef-expander.8.html +4 -4
  4. data/distro/common/html/chef-expanderctl.8.html +4 -4
  5. data/distro/common/html/chef-server-webui.8.html +4 -4
  6. data/distro/common/html/chef-server.8.html +4 -4
  7. data/distro/common/html/chef-shell.1.html +4 -4
  8. data/distro/common/html/chef-solo.8.html +4 -4
  9. data/distro/common/html/chef-solr.8.html +5 -5
  10. data/distro/common/html/knife-bootstrap.1.html +4 -4
  11. data/distro/common/html/knife-client.1.html +4 -4
  12. data/distro/common/html/knife-configure.1.html +4 -4
  13. data/distro/common/html/knife-cookbook-site.1.html +4 -4
  14. data/distro/common/html/knife-cookbook.1.html +4 -4
  15. data/distro/common/html/knife-data-bag.1.html +4 -4
  16. data/distro/common/html/knife-environment.1.html +4 -4
  17. data/distro/common/html/knife-exec.1.html +4 -4
  18. data/distro/common/html/knife-index.1.html +4 -4
  19. data/distro/common/html/knife-node.1.html +4 -4
  20. data/distro/common/html/knife-role.1.html +4 -4
  21. data/distro/common/html/knife-search.1.html +4 -4
  22. data/distro/common/html/knife-ssh.1.html +4 -4
  23. data/distro/common/html/knife-status.1.html +4 -4
  24. data/distro/common/html/knife-tag.1.html +4 -4
  25. data/distro/common/html/knife.1.html +4 -4
  26. data/distro/common/man/man1/knife-bootstrap.1 +58 -64
  27. data/distro/common/man/man1/knife-client.1 +19 -22
  28. data/distro/common/man/man1/knife-configure.1 +37 -46
  29. data/distro/common/man/man1/knife-cookbook-site.1 +14 -17
  30. data/distro/common/man/man1/knife-cookbook.1 +15 -18
  31. data/distro/common/man/man1/knife-data-bag.1 +14 -17
  32. data/distro/common/man/man1/knife-delete.1 +38 -47
  33. data/distro/common/man/man1/knife-deps.1 +39 -48
  34. data/distro/common/man/man1/knife-diff.1 +43 -52
  35. data/distro/common/man/man1/knife-download.1 +47 -53
  36. data/distro/common/man/man1/knife-edit.1 +32 -41
  37. data/distro/common/man/man1/knife-environment.1 +14 -17
  38. data/distro/common/man/man1/knife-exec.1 +52 -61
  39. data/distro/common/man/man1/knife-index-rebuild.1 +1 -61
  40. data/distro/common/man/man1/knife-list.1 +47 -59
  41. data/distro/common/man/man1/knife-node.1 +15 -18
  42. data/distro/common/man/man1/knife-raw.1 +28 -46
  43. data/distro/common/man/man1/knife-recipe-list.1 +1 -61
  44. data/distro/common/man/man1/knife-role.1 +19 -25
  45. data/distro/common/man/man1/knife-search.1 +53 -62
  46. data/distro/common/man/man1/knife-show.1 +36 -28
  47. data/distro/common/man/man1/knife-ssh.1 +55 -61
  48. data/distro/common/man/man1/knife-status.1 +34 -43
  49. data/distro/common/man/man1/knife-tag.1 +14 -17
  50. data/distro/common/man/man1/knife-upload.1 +47 -56
  51. data/distro/common/man/man1/knife-user.1 +17 -20
  52. data/distro/common/man/man1/knife-xargs.1 +60 -69
  53. data/lib/chef/application.rb +3 -1
  54. data/lib/chef/application/windows_service.rb +0 -1
  55. data/lib/chef/client.rb +41 -152
  56. data/lib/chef/config.rb +19 -23
  57. data/lib/chef/data_bag.rb +1 -1
  58. data/lib/chef/data_bag_item.rb +1 -1
  59. data/lib/chef/exceptions.rb +8 -0
  60. data/lib/chef/formatters/doc.rb +15 -0
  61. data/lib/chef/formatters/error_inspectors/api_error_formatting.rb +2 -1
  62. data/lib/chef/http.rb +18 -8
  63. data/lib/chef/http/authenticator.rb +4 -0
  64. data/lib/chef/http/cookie_manager.rb +3 -0
  65. data/lib/chef/http/decompressor.rb +4 -0
  66. data/lib/chef/http/json_input.rb +4 -0
  67. data/lib/chef/http/json_output.rb +4 -0
  68. data/lib/chef/http/validate_content_length.rb +94 -0
  69. data/lib/chef/knife.rb +0 -1
  70. data/lib/chef/knife/configure.rb +6 -6
  71. data/lib/chef/knife/cookbook_create.rb +2 -2
  72. data/lib/chef/knife/core/subcommand_loader.rb +49 -3
  73. data/lib/chef/knife/ssh.rb +34 -4
  74. data/lib/chef/mixin/path_sanity.rb +1 -0
  75. data/lib/chef/monologger.rb +1 -2
  76. data/lib/chef/node.rb +7 -0
  77. data/lib/chef/policy_builder.rb +49 -0
  78. data/lib/chef/policy_builder/expand_node_object.rb +230 -0
  79. data/lib/chef/policy_builder/policyfile.rb +338 -0
  80. data/lib/chef/provider/file.rb +15 -5
  81. data/lib/chef/provider/group.rb +6 -2
  82. data/lib/chef/provider/group/windows.rb +12 -2
  83. data/lib/chef/provider/http_request.rb +3 -2
  84. data/lib/chef/provider/package.rb +1 -0
  85. data/lib/chef/provider/package/aix.rb +1 -1
  86. data/lib/chef/provider/service/debian.rb +7 -2
  87. data/lib/chef/resource/file.rb +8 -1
  88. data/lib/chef/resource/package.rb +9 -0
  89. data/lib/chef/resource/service.rb +0 -1
  90. data/lib/chef/rest.rb +2 -0
  91. data/lib/chef/run_context.rb +1 -1
  92. data/lib/chef/util/file_edit.rb +1 -1
  93. data/lib/chef/util/windows/net_group.rb +7 -6
  94. data/lib/chef/version.rb +1 -1
  95. data/lib/chef/win32/version.rb +31 -18
  96. data/spec/data/cookbooks/preseed/templates/default/preseed-template-variables.seed +1 -0
  97. data/spec/functional/resource/file_spec.rb +0 -1
  98. data/spec/functional/resource/group_spec.rb +96 -16
  99. data/spec/functional/resource/package_spec.rb +17 -0
  100. data/spec/functional/resource/user_spec.rb +2 -2
  101. data/spec/functional/win32/versions_spec.rb +39 -0
  102. data/spec/integration/client/client_spec.rb +27 -28
  103. data/spec/spec_helper.rb +2 -0
  104. data/spec/support/platform_helpers.rb +7 -1
  105. data/spec/support/shared/functional/file_resource.rb +83 -43
  106. data/spec/unit/application_spec.rb +7 -5
  107. data/spec/unit/client_spec.rb +10 -3
  108. data/spec/unit/config_spec.rb +0 -30
  109. data/spec/unit/cookbook_spec.rb +1 -0
  110. data/spec/unit/data_bag_item_spec.rb +8 -0
  111. data/spec/unit/data_bag_spec.rb +6 -0
  112. data/spec/unit/http_spec.rb +48 -0
  113. data/spec/unit/knife/core/subcommand_loader_spec.rb +77 -1
  114. data/spec/unit/knife/ssh_spec.rb +107 -0
  115. data/spec/unit/mixin/path_sanity_spec.rb +6 -0
  116. data/spec/unit/mixin/securable_spec.rb +77 -3
  117. data/spec/unit/monologger_spec.rb +45 -0
  118. data/spec/unit/node_spec.rb +16 -0
  119. data/spec/unit/policy_builder/expand_node_object_spec.rb +320 -0
  120. data/spec/unit/policy_builder/policyfile_spec.rb +399 -0
  121. data/spec/unit/policy_builder_spec.rb +26 -0
  122. data/spec/unit/provider/deploy_spec.rb +3 -0
  123. data/spec/unit/provider/group/windows_spec.rb +1 -0
  124. data/spec/unit/provider/http_request_spec.rb +23 -1
  125. data/spec/unit/provider/service/debian_service_spec.rb +50 -19
  126. data/spec/unit/recipe_spec.rb +4 -0
  127. data/spec/unit/resource/package_spec.rb +5 -0
  128. data/spec/unit/rest_spec.rb +375 -278
  129. data/spec/unit/run_context_spec.rb +4 -0
  130. metadata +120 -75
  131. checksums.yaml +0 -7
@@ -0,0 +1,26 @@
1
+ #
2
+ # Author:: Daniel DeLeo (<dan@getchef.com>)
3
+ # Copyright:: Copyright 2014 Chef Software, Inc.
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ require 'spec_helper'
20
+ require 'chef/policy_builder'
21
+
22
+ describe Chef::PolicyBuilder do
23
+
24
+ # TODO: test the strategy method
25
+
26
+ end
@@ -69,6 +69,7 @@ describe Chef::Provider::Deploy do
69
69
 
70
70
  it "creates deploy_to dir" do
71
71
  ::Dir.should_receive(:chdir).with(@expected_release_dir).exactly(4).times
72
+ @provider.should_receive(:enforce_ownership).twice
72
73
  @provider.stub(:update_cached_repo)
73
74
  @provider.deploy
74
75
  end
@@ -80,6 +81,7 @@ describe Chef::Provider::Deploy do
80
81
  ::Dir.should_receive(:chdir).with(@expected_release_dir).exactly(4).times
81
82
  FileUtils.should_not_receive(:mkdir_p).with(@resource.deploy_to)
82
83
  FileUtils.should_not_receive(:mkdir_p).with(@resource.shared_path)
84
+ @provider.should_receive(:enforce_ownership).twice
83
85
  @provider.stub(:copy_cached_repo)
84
86
  @provider.stub(:update_cached_repo)
85
87
  @provider.stub(:symlink)
@@ -94,6 +96,7 @@ describe Chef::Provider::Deploy do
94
96
  @provider.stub(:copy_cached_repo)
95
97
  @provider.stub(:update_cached_repo)
96
98
  @provider.stub(:install_gems)
99
+ @provider.should_receive(:enforce_ownership).ordered
97
100
  @provider.stub(:enforce_ownership)
98
101
  @provider.stub(:symlink)
99
102
  @provider.stub(:migrate)
@@ -55,6 +55,7 @@ describe Chef::Provider::Group::Windows do
55
55
  Chef::Util::Windows::NetGroup.stub!(:new).and_return(@net_group)
56
56
  @net_group.stub!(:local_add_members)
57
57
  @net_group.stub!(:local_set_members)
58
+ @provider.stub(:local_group_name_to_sid)
58
59
  @provider.current_resource = @current_resource
59
60
  end
60
61
 
@@ -103,6 +103,8 @@ describe Chef::Provider::HttpRequest do
103
103
  end
104
104
  end
105
105
 
106
+ # CHEF-4762: we expect a nil return value for a "200 Success" response
107
+ # and false for a "304 Not Modified" response
106
108
  describe "action_head" do
107
109
  before do
108
110
  @provider.http = @http
@@ -110,17 +112,37 @@ describe Chef::Provider::HttpRequest do
110
112
 
111
113
  it "should inflate a message block at runtime" do
112
114
  @new_resource.message { "return" }
113
- @http.should_receive(:head).with("http://www.opscode.com/?message=return", {}).and_return("")
115
+ @http.should_receive(:head).with("http://www.opscode.com/?message=return", {}).and_return(nil)
114
116
  @provider.run_action(:head)
115
117
  @new_resource.should be_updated
116
118
  end
117
119
 
118
120
  it "should run a HEAD request" do
121
+ @http.should_receive(:head).with("http://www.opscode.com/?message=is cool", {}).and_return(nil)
122
+ @provider.run_action(:head)
123
+ @new_resource.should be_updated
124
+ end
125
+
126
+ it "should update a HEAD request with empty string response body (CHEF-4762)" do
119
127
  @http.should_receive(:head).with("http://www.opscode.com/?message=is cool", {}).and_return("")
120
128
  @provider.run_action(:head)
121
129
  @new_resource.should be_updated
122
130
  end
123
131
 
132
+ it "should update a HEAD request with nil response body (CHEF-4762)" do
133
+ @http.should_receive(:head).with("http://www.opscode.com/?message=is cool", {}).and_return(nil)
134
+ @provider.run_action(:head)
135
+ @new_resource.should be_updated
136
+ end
137
+
138
+ it "should not update a HEAD request if a not modified response (CHEF-4762)" do
139
+ if_modified_since = File.mtime(__FILE__).httpdate
140
+ @new_resource.headers "If-Modified-Since" => if_modified_since
141
+ @http.should_receive(:head).with("http://www.opscode.com/?message=is cool", {"If-Modified-Since" => if_modified_since}).and_return(false)
142
+ @provider.run_action(:head)
143
+ @new_resource.should_not be_updated
144
+ end
145
+
124
146
  it "should run a HEAD request with If-Modified-Since header" do
125
147
  @new_resource.headers "If-Modified-Since" => File.mtime(__FILE__).httpdate
126
148
  @http.should_receive(:head).with("http://www.opscode.com/?message=is cool", @new_resource.headers)
@@ -126,11 +126,19 @@ describe Chef::Provider::Service::Debian do
126
126
  /etc/rc5.d/S20chef
127
127
  /etc/rc6.d/K20chef
128
128
  STDOUT
129
- "stderr" => ""
129
+ "stderr" => "",
130
+ "priorities" => {
131
+ "0"=>[:stop, "20"],
132
+ "1"=>[:stop, "20"],
133
+ "2"=>[:start, "20"],
134
+ "3"=>[:start, "20"],
135
+ "4"=>[:start, "20"],
136
+ "5"=>[:start, "20"],
137
+ "6"=>[:stop, "20"]
138
+ }
130
139
  },
131
140
  "not linked" => {
132
- "stdout" =>
133
- " Removing any system startup links for /etc/init.d/chef ...",
141
+ "stdout" => " Removing any system startup links for /etc/init.d/chef ...",
134
142
  "stderr" => ""
135
143
  },
136
144
  },
@@ -147,20 +155,51 @@ insserv: remove service /etc/init.d/../rc0.d/K20chef-client
147
155
  insserv: remove service /etc/init.d/../rc6.d/K20chef-client
148
156
  insserv: dryrun, not creating .depend.boot, .depend.start, and .depend.stop
149
157
  STDERR
158
+ "priorities" => {
159
+ "0"=>[:stop, "20"],
160
+ "1"=>[:stop, "20"],
161
+ "2"=>[:start, "20"],
162
+ "3"=>[:start, "20"],
163
+ "4"=>[:start, "20"],
164
+ "5"=>[:start, "20"],
165
+ "6"=>[:stop, "20"]
166
+ }
150
167
  },
151
168
  "not linked" => {
152
169
  "stdout" => "update-rc.d: using dependency based boot sequencing",
153
170
  "stderr" => ""
154
171
  }
172
+ },
173
+ "Debian/Wheezy and earlier, a service only starting at run level S" => {
174
+ "linked" => {
175
+ "stdout" => "",
176
+ "stderr" => <<-STDERR,
177
+ insserv: remove service /etc/init.d/../rc0.d/K06rpcbind
178
+ insserv: remove service /etc/init.d/../rc1.d/K06rpcbind
179
+ insserv: remove service /etc/init.d/../rc6.d/K06rpcbind
180
+ insserv: remove service /etc/init.d/../rcS.d/S13rpcbind
181
+ insserv: dryrun, not creating .depend.boot, .depend.start, and .depend.stop
182
+ STDERR
183
+ "priorities" => {
184
+ "0"=>[:stop, "06"],
185
+ "1"=>[:stop, "06"],
186
+ "6"=>[:stop, "06"],
187
+ "S"=>[:start, "13"]
188
+ }
189
+ },
190
+ "not linked" => {
191
+ "stdout" => "",
192
+ "stderr" => "insserv: dryrun, not creating .depend.boot, .depend.start, and .depend.stop"
193
+ }
155
194
  }
156
- }.each do |model, streams|
195
+ }.each do |model, expected_results|
157
196
  context "on #{model}" do
158
197
  context "when update-rc.d shows init linked to rc*.d/" do
159
198
  before do
160
199
  @provider.stub!(:assert_update_rcd_available)
161
200
 
162
- @stdout = StringIO.new(streams["linked"]["stdout"])
163
- @stderr = StringIO.new(streams["linked"]["stderr"])
201
+ @stdout = StringIO.new(expected_results["linked"]["stdout"])
202
+ @stderr = StringIO.new(expected_results["linked"]["stderr"])
164
203
  @status = mock("Status", :exitstatus => 0, :stdout => @stdout)
165
204
  @provider.stub!(:shell_out!).and_return(@status)
166
205
  @provider.stub!(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status)
@@ -178,22 +217,15 @@ insserv: remove service /etc/init.d/../rc0.d/K20chef-client
178
217
 
179
218
  it "stores the start/stop priorities of the service" do
180
219
  @provider.load_current_resource
181
- expected_priorities = {"6"=>[:stop, "20"],
182
- "0"=>[:stop, "20"],
183
- "1"=>[:stop, "20"],
184
- "2"=>[:start, "20"],
185
- "3"=>[:start, "20"],
186
- "4"=>[:start, "20"],
187
- "5"=>[:start, "20"]}
188
- @provider.current_resource.priority.should == expected_priorities
220
+ @provider.current_resource.priority.should == expected_results["linked"]["priorities"]
189
221
  end
190
222
  end
191
223
 
192
224
  context "when update-rc.d shows init isn't linked to rc*.d/" do
193
225
  before do
194
226
  @provider.stub!(:assert_update_rcd_available)
195
- @stdout = StringIO.new(streams["not linked"]["stdout"])
196
- @stderr = StringIO.new(streams["not linked"]["stderr"])
227
+ @stdout = StringIO.new(expected_results["not linked"]["stdout"])
228
+ @stderr = StringIO.new(expected_results["not linked"]["stderr"])
197
229
  @status = mock("Status", :exitstatus => 0, :stdout => @stdout)
198
230
  @provider.stub!(:shell_out!).and_return(@status)
199
231
  @provider.stub!(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status)
@@ -244,6 +276,7 @@ insserv: remove service /etc/init.d/../rc0.d/K20chef-client
244
276
  context "when the service is enabled" do
245
277
  before do
246
278
  @current_resource.enabled(true)
279
+ @current_resource.priority(80)
247
280
  end
248
281
 
249
282
  context "and the service sets no priority" do
@@ -252,7 +285,6 @@ insserv: remove service /etc/init.d/../rc0.d/K20chef-client
252
285
 
253
286
  context "and the service requests the same priority as is set" do
254
287
  before do
255
- @current_resource.priority(80)
256
288
  @new_resource.priority(80)
257
289
  end
258
290
  it_behaves_like "the service is up to date"
@@ -260,8 +292,7 @@ insserv: remove service /etc/init.d/../rc0.d/K20chef-client
260
292
 
261
293
  context "and the service requests a different priority than is set" do
262
294
  before do
263
- @current_resource.priority(20)
264
- @new_resource.priority(80)
295
+ @new_resource.priority(20)
265
296
  end
266
297
  it_behaves_like "the service is not up to date"
267
298
  end
@@ -193,6 +193,7 @@ describe Chef::Recipe do
193
193
 
194
194
  describe "include_recipe" do
195
195
  it "should evaluate another recipe with include_recipe" do
196
+ @node.should_receive(:loaded_recipe).with(:openldap, "gigantor")
196
197
  @run_context.include_recipe "openldap::gigantor"
197
198
  res = @run_context.resource_collection.resources(:cat => "blanket")
198
199
  res.name.should eql("blanket")
@@ -200,6 +201,7 @@ describe Chef::Recipe do
200
201
  end
201
202
 
202
203
  it "should load the default recipe for a cookbook if include_recipe is called without a ::" do
204
+ @node.should_receive(:loaded_recipe).with(:openldap, "default")
203
205
  @run_context.include_recipe "openldap"
204
206
  res = @run_context.resource_collection.resources(:cat => "blanket")
205
207
  res.name.should eql("blanket")
@@ -207,11 +209,13 @@ describe Chef::Recipe do
207
209
  end
208
210
 
209
211
  it "should store that it has seen a recipe in the run_context" do
212
+ @node.should_receive(:loaded_recipe).with(:openldap, "default")
210
213
  @run_context.include_recipe "openldap"
211
214
  @run_context.loaded_recipe?("openldap").should be_true
212
215
  end
213
216
 
214
217
  it "should not include the same recipe twice" do
218
+ @node.should_receive(:loaded_recipe).with(:openldap, "default").exactly(:once)
215
219
  @cookbook_collection[:openldap].should_receive(:load_recipe).with("default", @run_context)
216
220
  @recipe.include_recipe "openldap"
217
221
  @cookbook_collection[:openldap].should_not_receive(:load_recipe).with("default", @run_context)
@@ -49,6 +49,11 @@ describe Chef::Resource::Package do
49
49
  @resource.response_file.should eql("something")
50
50
  end
51
51
 
52
+ it "should accept a hash for response file template variables" do
53
+ @resource.response_file_variables({:variables => true})
54
+ @resource.response_file_variables.should eql({:variables => true})
55
+ end
56
+
52
57
  it "should accept a string for the source" do
53
58
  @resource.source "something"
54
59
  @resource.source.should eql("something")
@@ -53,70 +53,115 @@ Y6S6MeZ69Rp89ma4ttMZ+kwi1+XyHqC/dlcVRW42Zl5Dc7BALRlJjQ==
53
53
  -----END RSA PRIVATE KEY-----"
54
54
 
55
55
  describe Chef::REST do
56
- before(:each) do
57
- @log_stringio = StringIO.new
58
- Chef::Log.init(@log_stringio)
56
+ let(:base_url) { "http://chef.example.com:4000" }
59
57
 
60
- Chef::REST::CookieJar.stub(:instance).and_return({})
61
- @base_url = "http://chef.example.com:4000"
62
- @monkey_uri = URI.parse("http://chef.example.com:4000/monkey")
63
- @rest = Chef::REST.new(@base_url, nil, nil)
58
+ let(:monkey_uri) { URI.parse("http://chef.example.com:4000/monkey") }
64
59
 
60
+ let(:log_stringio) { StringIO.new }
61
+
62
+ let(:rest) do
63
+ Chef::REST::CookieJar.stub(:instance).and_return({})
64
+ rest = Chef::REST.new(base_url, nil, nil)
65
65
  Chef::REST::CookieJar.instance.clear
66
+ rest
66
67
  end
67
68
 
69
+ before(:each) do
70
+ Chef::Log.init(log_stringio)
71
+ end
68
72
 
69
73
  describe "calling an HTTP verb on a path or absolute URL" do
70
74
  it "adds a relative URL to the base url it was initialized with" do
71
- @rest.create_url("foo/bar/baz").should == URI.parse(@base_url + "/foo/bar/baz")
75
+ expect(rest.create_url("foo/bar/baz")).to eq(URI.parse(base_url + "/foo/bar/baz"))
72
76
  end
73
77
 
74
78
  it "replaces the base URL when given an absolute URL" do
75
- @rest.create_url("http://chef-rulez.example.com:9000").should == URI.parse("http://chef-rulez.example.com:9000")
79
+ expect(rest.create_url("http://chef-rulez.example.com:9000")).to eq(URI.parse("http://chef-rulez.example.com:9000"))
76
80
  end
77
81
 
78
82
  it "makes a :GET request with the composed url object" do
79
- @rest.should_receive(:send_http_request).
80
- with(:GET, @monkey_uri, STANDARD_READ_HEADERS, false).
83
+ rest.should_receive(:send_http_request).
84
+ with(:GET, monkey_uri, STANDARD_READ_HEADERS, false).
81
85
  and_return([1,2,3])
82
- @rest.should_receive(:apply_response_middleware).with(1,2,3).and_return([1,2,3])
83
- @rest.should_receive('success_response?'.to_sym).with(1).and_return(true)
84
- @rest.get_rest("monkey")
86
+ rest.should_receive(:apply_response_middleware).with(1,2,3).and_return([1,2,3])
87
+ rest.should_receive('success_response?'.to_sym).with(1).and_return(true)
88
+ rest.get_rest("monkey")
85
89
  end
86
90
 
87
91
  it "makes a :GET reqest for a streaming download with the composed url" do
88
- @rest.should_receive(:streaming_request).with('monkey', {})
89
- @rest.get_rest("monkey", true)
92
+ rest.should_receive(:streaming_request).with('monkey', {})
93
+ rest.get_rest("monkey", true)
90
94
  end
91
95
 
92
96
  STANDARD_READ_HEADERS = {"Accept"=>"application/json", "Accept"=>"application/json", "Accept-Encoding"=>"gzip;q=1.0,deflate;q=0.6,identity;q=0.3"}
93
97
  STANDARD_WRITE_HEADERS = {"Accept"=>"application/json", "Content-Type"=>"application/json", "Accept"=>"application/json", "Accept-Encoding"=>"gzip;q=1.0,deflate;q=0.6,identity;q=0.3"}
94
98
 
95
99
  it "makes a :DELETE request with the composed url object" do
96
- @rest.should_receive(:send_http_request).
97
- with(:DELETE, @monkey_uri, STANDARD_READ_HEADERS, false).
100
+ rest.should_receive(:send_http_request).
101
+ with(:DELETE, monkey_uri, STANDARD_READ_HEADERS, false).
98
102
  and_return([1,2,3])
99
- @rest.should_receive(:apply_response_middleware).with(1,2,3).and_return([1,2,3])
100
- @rest.should_receive('success_response?'.to_sym).with(1).and_return(true)
101
- @rest.delete_rest("monkey")
103
+ rest.should_receive(:apply_response_middleware).with(1,2,3).and_return([1,2,3])
104
+ rest.should_receive('success_response?'.to_sym).with(1).and_return(true)
105
+ rest.delete_rest("monkey")
102
106
  end
103
107
 
104
108
  it "makes a :POST request with the composed url object and data" do
105
- @rest.should_receive(:send_http_request).
106
- with(:POST, @monkey_uri, STANDARD_WRITE_HEADERS, "\"data\"").
109
+ rest.should_receive(:send_http_request).
110
+ with(:POST, monkey_uri, STANDARD_WRITE_HEADERS, "\"data\"").
107
111
  and_return([1,2,3])
108
- @rest.should_receive(:apply_response_middleware).with(1,2,3).and_return([1,2,3])
109
- @rest.should_receive('success_response?'.to_sym).with(1).and_return(true)
110
- @rest.post_rest("monkey", "data")
112
+ rest.should_receive(:apply_response_middleware).with(1,2,3).and_return([1,2,3])
113
+ rest.should_receive('success_response?'.to_sym).with(1).and_return(true)
114
+ rest.post_rest("monkey", "data")
111
115
  end
112
116
 
113
117
  it "makes a :PUT request with the composed url object and data" do
114
- @rest.should_receive(:send_http_request).
115
- with(:PUT, @monkey_uri, STANDARD_WRITE_HEADERS, "\"data\"").
118
+ rest.should_receive(:send_http_request).
119
+ with(:PUT, monkey_uri, STANDARD_WRITE_HEADERS, "\"data\"").
116
120
  and_return([1,2,3])
117
- @rest.should_receive(:apply_response_middleware).with(1,2,3).and_return([1,2,3])
118
- @rest.should_receive('success_response?'.to_sym).with(1).and_return(true)
119
- @rest.put_rest("monkey", "data")
121
+ rest.should_receive(:apply_response_middleware).with(1,2,3).and_return([1,2,3])
122
+ rest.should_receive('success_response?'.to_sym).with(1).and_return(true)
123
+ rest.put_rest("monkey", "data")
124
+ end
125
+ end
126
+
127
+ describe "legacy API" do
128
+ let(:rest) do
129
+ Chef::REST.new(base_url)
130
+ end
131
+
132
+ before(:each) do
133
+ Chef::Config[:node_name] = "webmonkey.example.com"
134
+ Chef::Config[:client_key] = CHEF_SPEC_DATA + "/ssl/private_key.pem"
135
+ end
136
+
137
+ it 'responds to raw_http_request as a public method' do
138
+ expect(rest.public_methods.map(&:to_s)).to include("raw_http_request")
139
+ end
140
+
141
+ it 'calls the authn middleware' do
142
+ data = "\"secure data\""
143
+
144
+ auth_headers = STANDARD_WRITE_HEADERS.merge({"auth_done"=>"yep"})
145
+
146
+ rest.authenticator.should_receive(:handle_request).
147
+ with(:POST, monkey_uri, STANDARD_WRITE_HEADERS, data).
148
+ and_return([:POST, monkey_uri, auth_headers, data])
149
+ rest.should_receive(:send_http_request).
150
+ with(:POST, monkey_uri, auth_headers, data).
151
+ and_return([1,2,3])
152
+ rest.should_receive('success_response?'.to_sym).with(1).and_return(true)
153
+ rest.raw_http_request(:POST, monkey_uri, STANDARD_WRITE_HEADERS, data)
154
+ end
155
+
156
+ it 'sets correct authn headers' do
157
+ data = "\"secure data\""
158
+ method, uri, auth_headers, d = rest.authenticator.handle_request(:POST, monkey_uri, STANDARD_WRITE_HEADERS, data)
159
+
160
+ rest.should_receive(:send_http_request).
161
+ with(:POST, monkey_uri, auth_headers, data).
162
+ and_return([1,2,3])
163
+ rest.should_receive('success_response?'.to_sym).with(1).and_return(true)
164
+ rest.raw_http_request(:POST, monkey_uri, STANDARD_WRITE_HEADERS, data)
120
165
  end
121
166
  end
122
167
 
@@ -137,202 +182,185 @@ describe Chef::REST do
137
182
  auth_headers = STANDARD_WRITE_HEADERS.merge({"auth_done"=>"yep"})
138
183
 
139
184
  @rest.authenticator.should_receive(:handle_request).
140
- with(:POST, @monkey_uri, STANDARD_WRITE_HEADERS, data).
141
- and_return([:POST, @monkey_uri, auth_headers, data])
185
+ with(:POST, monkey_uri, STANDARD_WRITE_HEADERS, data).
186
+ and_return([:POST, monkey_uri, auth_headers, data])
142
187
  @rest.should_receive(:send_http_request).
143
- with(:POST, @monkey_uri, auth_headers, data).
188
+ with(:POST, monkey_uri, auth_headers, data).
144
189
  and_return([1,2,3])
145
190
  @rest.should_receive('success_response?'.to_sym).with(1).and_return(true)
146
- @rest.raw_http_request(:POST, @monkey_uri, STANDARD_WRITE_HEADERS, data)
191
+ @rest.raw_http_request(:POST, monkey_uri, STANDARD_WRITE_HEADERS, data)
147
192
  end
148
193
 
149
194
  it 'sets correct authn headers' do
150
195
  data = "\"secure data\""
151
- method, uri, auth_headers, d = @rest.authenticator.handle_request(:POST, @monkey_uri, STANDARD_WRITE_HEADERS, data)
196
+ method, uri, auth_headers, d = @rest.authenticator.handle_request(:POST, monkey_uri, STANDARD_WRITE_HEADERS, data)
152
197
 
153
198
  @rest.should_receive(:send_http_request).
154
- with(:POST, @monkey_uri, auth_headers, data).
199
+ with(:POST, monkey_uri, auth_headers, data).
155
200
  and_return([1,2,3])
156
201
  @rest.should_receive('success_response?'.to_sym).with(1).and_return(true)
157
- @rest.raw_http_request(:POST, @monkey_uri, STANDARD_WRITE_HEADERS, data)
202
+ @rest.raw_http_request(:POST, monkey_uri, STANDARD_WRITE_HEADERS, data)
158
203
  end
159
204
  end
160
205
 
161
206
 
162
207
  describe "when configured to authenticate to the Chef server" do
208
+ let(:base_url) { URI.parse("http://chef.example.com:4000") }
209
+
210
+ let(:rest) do
211
+ Chef::REST.new(base_url)
212
+ end
213
+
163
214
  before do
164
- @url = URI.parse("http://chef.example.com:4000")
165
215
  Chef::Config[:node_name] = "webmonkey.example.com"
166
216
  Chef::Config[:client_key] = CHEF_SPEC_DATA + "/ssl/private_key.pem"
167
- @rest = Chef::REST.new(@url)
168
217
  end
169
218
 
170
219
  it "configures itself to use the node_name and client_key in the config by default" do
171
- @rest.client_name.should == "webmonkey.example.com"
172
- @rest.signing_key_filename.should == CHEF_SPEC_DATA + "/ssl/private_key.pem"
220
+ expect(rest.client_name).to eq("webmonkey.example.com")
221
+ expect(rest.signing_key_filename).to eq(CHEF_SPEC_DATA + "/ssl/private_key.pem")
173
222
  end
174
223
 
175
224
  it "provides access to the raw key data" do
176
- @rest.signing_key.should == SIGNING_KEY_DOT_PEM
225
+ expect(rest.signing_key).to eq(SIGNING_KEY_DOT_PEM)
177
226
  end
178
227
 
179
228
  it "does not error out when initialized without credentials" do
180
- @rest = Chef::REST.new(@url, nil, nil) #should_not raise_error hides the bt from you, so screw it.
181
- @rest.client_name.should be_nil
182
- @rest.signing_key.should be_nil
229
+ rest = Chef::REST.new(base_url, nil, nil) #should_not raise_error hides the bt from you, so screw it.
230
+ expect(rest.client_name).to be_nil
231
+ expect(rest.signing_key).to be_nil
183
232
  end
184
233
 
185
234
  it "indicates that requests should not be signed when it has no credentials" do
186
- @rest = Chef::REST.new(@url, nil, nil)
187
- @rest.sign_requests?.should be_false
235
+ rest = Chef::REST.new(base_url, nil, nil)
236
+ expect(rest.sign_requests?).to be_false
188
237
  end
189
238
 
190
239
  it "raises PrivateKeyMissing when the key file doesn't exist" do
191
- lambda {Chef::REST.new(@url, "client-name", "/dev/null/nothing_here")}.should raise_error(Chef::Exceptions::PrivateKeyMissing)
240
+ expect {Chef::REST.new(base_url, "client-name", "/dev/null/nothing_here")}.to raise_error(Chef::Exceptions::PrivateKeyMissing)
192
241
  end
193
242
 
194
243
  it "raises InvalidPrivateKey when the key file doesnt' look like a key" do
195
244
  invalid_key_file = CHEF_SPEC_DATA + "/bad-config.rb"
196
- lambda {Chef::REST.new(@url, "client-name", invalid_key_file)}.should raise_error(Chef::Exceptions::InvalidPrivateKey)
245
+ expect {Chef::REST.new(base_url, "client-name", invalid_key_file)}.to raise_error(Chef::Exceptions::InvalidPrivateKey)
197
246
  end
198
247
 
199
248
  it "can take private key as a sting :raw_key in options during initializaton" do
200
- Chef::REST.new(@url, "client-name", nil, :raw_key => SIGNING_KEY_DOT_PEM).signing_key.should == SIGNING_KEY_DOT_PEM
249
+ expect(Chef::REST.new(base_url, "client-name", nil, :raw_key => SIGNING_KEY_DOT_PEM).signing_key).to eq(SIGNING_KEY_DOT_PEM)
201
250
  end
202
251
 
203
252
  it "raises InvalidPrivateKey when the key passed as string :raw_key in options doesnt' look like a key" do
204
- lambda {Chef::REST.new(@url, "client-name", nil, :raw_key => "bad key string")}.should raise_error(Chef::Exceptions::InvalidPrivateKey)
253
+ expect {Chef::REST.new(base_url, "client-name", nil, :raw_key => "bad key string")}.to raise_error(Chef::Exceptions::InvalidPrivateKey)
205
254
  end
206
255
 
207
256
  end
208
257
 
209
258
  context "when making REST requests" do
210
- before(:each) do
211
- Chef::Config[:ssl_client_cert] = nil
212
- Chef::Config[:ssl_client_key] = nil
213
- @url = URI.parse("https://one:80/?foo=bar")
214
-
215
- @host_header = "one:80"
216
-
217
- @http_response = Net::HTTPSuccess.new("1.1", "200", "successful rest req")
218
- @http_response.stub(:read_body)
219
- @http_response.stub(:body).and_return("ninja")
220
- @http_response.add_field("Content-Length", "5")
221
-
222
- @http_client = Net::HTTP.new(@url.host, @url.port)
223
- Net::HTTP.stub(:new).and_return(@http_client)
224
- @http_client.stub(:request).and_yield(@http_response).and_return(@http_response)
225
-
226
- @base_headers = { 'Accept' => 'application/json',
227
- 'X-Chef-Version' => Chef::VERSION,
228
- 'Accept-Encoding' => Chef::REST::RESTRequest::ENCODING_GZIP_DEFLATE}
229
- @req_with_body_headers = @base_headers.merge("Content-Type" => "application/json", "Content-Length" => '13')
259
+ let(:body) { "ninja" }
260
+
261
+ let(:http_response) do
262
+ http_response = Net::HTTPSuccess.new("1.1", "200", "successful rest req")
263
+ http_response.stub(:read_body)
264
+ http_response.stub(:body).and_return(body)
265
+ http_response["Content-Length"] = body.bytesize.to_s
266
+ http_response
230
267
  end
231
268
 
232
- describe "streaming downloads to a tempfile" do
233
- before do
234
- @tempfile = StringIO.new
235
- @tempfile.stub(:close!)
236
- @tempfile.stub(:path).and_return("/a-temporary-file")
237
- Tempfile.stub(:new).with("chef-rest").and_return(@tempfile)
238
- Tempfile.stub(:open).and_return(@tempfile)
239
-
240
- @request_mock = {}
241
- Net::HTTP::Get.stub(:new).and_return(@request_mock)
242
- end
243
-
244
- it "should build a new HTTP GET request without the application/json accept header" do
245
- expected_headers = {'Accept' => "*/*",
246
- 'X-Chef-Version' => Chef::VERSION,
247
- 'Accept-Encoding' => Chef::REST::RESTRequest::ENCODING_GZIP_DEFLATE,
248
- 'Host' => @host_header}
249
- Net::HTTP::Get.should_receive(:new).with("/?foo=bar", expected_headers).and_return(@request_mock)
250
- @rest.streaming_request(@url, {})
251
- end
269
+ let(:host_header) { "one:80" }
252
270
 
253
- it "should create a tempfile for the output of a raw request" do
254
- @rest.streaming_request(@url, {}).should equal(@tempfile)
255
- end
271
+ let(:url) { URI.parse("https://one:80/?foo=bar") }
256
272
 
257
- it "should read the body of the response in chunks on a raw request" do
258
- @http_response.should_receive(:read_body).and_return(true)
259
- @rest.streaming_request(@url, {})
260
- end
273
+ let(:base_url) { "http://chef.example.com:4000" }
261
274
 
262
- it "should populate the tempfile with the value of the raw request" do
263
- @http_response.should_receive(:read_body).and_yield("ninja")
264
- @rest.streaming_request(@url, {})
265
- #@tempfile.string.should include("ninja")
266
- end
275
+ let!(:http_client) do
276
+ http_client = Net::HTTP.new(url.host, url.port)
277
+ http_client.stub(:request).and_yield(http_response).and_return(http_response)
278
+ http_client
279
+ end
267
280
 
268
- it "should close the tempfile if we're doing a raw request" do
269
- @tempfile.should_receive(:close).once.and_return(true)
270
- @rest.streaming_request(@url, {})
271
- end
281
+ let(:rest) do
282
+ Net::HTTP.stub(:new).and_return(http_client)
283
+ Chef::REST::CookieJar.stub(:instance).and_return({})
284
+ rest = Chef::REST.new(base_url, nil, nil)
285
+ Chef::REST::CookieJar.instance.clear
286
+ rest
287
+ end
272
288
 
273
- it "should not raise a divide by zero exception if the size is 0" do
274
- @http_response.stub(:header).and_return({ 'Content-Length' => "5" })
275
- @http_response.stub(:read_body).and_yield('')
276
- lambda { @rest.streaming_request(@url, {}) }.should_not raise_error
277
- end
289
+ let(:base_headers) do
290
+ {
291
+ 'Accept' => 'application/json',
292
+ 'X-Chef-Version' => Chef::VERSION,
293
+ 'Accept-Encoding' => Chef::REST::RESTRequest::ENCODING_GZIP_DEFLATE,
294
+ }
295
+ end
278
296
 
279
- it "should not raise a divide by zero exception if the Content-Length is 0" do
280
- @http_response.stub(:header).and_return({ 'Content-Length' => "0" })
281
- @http_response.stub(:read_body).and_yield("ninja")
282
- lambda { @rest.streaming_request(@url, {}) }.should_not raise_error
283
- end
297
+ let (:req_with_body_headers) do
298
+ base_headers.merge("Content-Type" => "application/json", "Content-Length" => '13')
299
+ end
284
300
 
301
+ before(:each) do
302
+ Chef::Config[:ssl_client_cert] = nil
303
+ Chef::Config[:ssl_client_key] = nil
285
304
  end
286
305
 
287
306
  describe "as JSON API requests" do
288
- before do
289
- @request_mock = {}
290
- Net::HTTP::Get.stub(:new).and_return(@request_mock)
291
-
292
- @base_headers = {"Accept" => "application/json",
293
- "X-Chef-Version" => Chef::VERSION,
294
- "Accept-Encoding" => Chef::REST::RESTRequest::ENCODING_GZIP_DEFLATE,
295
- "Host" => @host_header
307
+ let(:request_mock) { {} }
308
+
309
+ let(:base_headers) do #FIXME: huh?
310
+ {
311
+ 'Accept' => 'application/json',
312
+ 'X-Chef-Version' => Chef::VERSION,
313
+ 'Accept-Encoding' => Chef::REST::RESTRequest::ENCODING_GZIP_DEFLATE,
314
+ 'Host' => host_header,
296
315
  }
297
316
  end
298
317
 
318
+ before do
319
+ Net::HTTP::Get.stub(:new).and_return(request_mock)
320
+ end
321
+
299
322
  it "should always include the X-Chef-Version header" do
300
- Net::HTTP::Get.should_receive(:new).with("/?foo=bar", @base_headers).and_return(@request_mock)
301
- @rest.request(:GET, @url, {})
323
+ Net::HTTP::Get.should_receive(:new).with("/?foo=bar", base_headers).and_return(request_mock)
324
+ rest.request(:GET, url, {})
302
325
  end
303
326
 
304
327
  it "sets the user agent to chef-client" do
305
- # must reset to default b/c knife changes the UA
328
+ # XXX: must reset to default b/c knife changes the UA
306
329
  Chef::REST::RESTRequest.user_agent = Chef::REST::RESTRequest::DEFAULT_UA
307
- @rest.request(:GET, @url, {})
308
- @request_mock['User-Agent'].should match(/^Chef Client\/#{Chef::VERSION}/)
330
+ rest.request(:GET, url, {})
331
+ expect(request_mock['User-Agent']).to match(/^Chef Client\/#{Chef::VERSION}/)
309
332
  end
310
333
 
311
334
  # CHEF-3140
312
335
  context "when configured to disable compression" do
313
- before do
314
- @rest = Chef::REST.new(@base_url, nil, nil, :disable_gzip => true)
336
+ let(:rest) do
337
+ Net::HTTP.stub(:new).and_return(http_client)
338
+ Chef::REST.new(base_url, nil, nil, :disable_gzip => true)
315
339
  end
316
340
 
317
341
  it "does not accept encoding gzip" do
318
- @rest.send(:build_headers, :GET, @url, {}).should_not have_key("Accept-Encoding")
342
+ expect(rest.send(:build_headers, :GET, url, {})).not_to have_key("Accept-Encoding")
319
343
  end
320
344
 
321
345
  it "does not decompress a response encoded as gzip" do
322
- @http_response.add_field("content-encoding", "gzip")
323
- request = Net::HTTP::Get.new(@url.path)
346
+ http_response.add_field("content-encoding", "gzip")
347
+ request = Net::HTTP::Get.new(url.path)
324
348
  Net::HTTP::Get.should_receive(:new).and_return(request)
325
349
  # will raise a Zlib error if incorrect
326
- @rest.request(:GET, @url, {}).should == "ninja"
350
+ expect(rest.request(:GET, url, {})).to eq("ninja")
327
351
  end
328
352
  end
353
+
329
354
  context "when configured with custom http headers" do
330
- before(:each) do
331
- @custom_headers = {
355
+ let(:custom_headers) do
356
+ {
332
357
  'X-Custom-ChefSecret' => 'sharpknives',
333
358
  'X-Custom-RequestPriority' => 'extremely low'
334
359
  }
335
- Chef::Config[:custom_http_headers] = @custom_headers
360
+ end
361
+
362
+ before(:each) do
363
+ Chef::Config[:custom_http_headers] = custom_headers
336
364
  end
337
365
 
338
366
  after(:each) do
@@ -341,84 +369,111 @@ describe Chef::REST do
341
369
 
342
370
  it "should set them on the http request" do
343
371
  url_string = an_instance_of(String)
344
- header_hash = hash_including(@custom_headers)
372
+ header_hash = hash_including(custom_headers)
345
373
  Net::HTTP::Get.should_receive(:new).with(url_string, header_hash)
346
- @rest.request(:GET, @url, {})
374
+ rest.request(:GET, url, {})
347
375
  end
348
376
  end
349
377
 
350
- it "should set the cookie for this request if one exists for the given host:port" do
351
- Chef::REST::CookieJar.instance["#{@url.host}:#{@url.port}"] = "cookie monster"
352
- Net::HTTP::Get.should_receive(:new).with("/?foo=bar", @base_headers.merge('Cookie' => "cookie monster")).and_return(@request_mock)
353
- @rest.request(:GET, @url, {})
378
+ context "when setting cookies" do
379
+ let(:rest) do
380
+ Net::HTTP.stub(:new).and_return(http_client)
381
+ Chef::REST::CookieJar.instance["#{url.host}:#{url.port}"] = "cookie monster"
382
+ rest = Chef::REST.new(base_url, nil, nil)
383
+ rest
384
+ end
385
+
386
+ it "should set the cookie for this request if one exists for the given host:port" do
387
+ Net::HTTP::Get.should_receive(:new).with("/?foo=bar", base_headers.merge('Cookie' => "cookie monster")).and_return(request_mock)
388
+ rest.request(:GET, url, {})
389
+ end
354
390
  end
355
391
 
356
392
  it "should build a new HTTP GET request" do
357
- Net::HTTP::Get.should_receive(:new).with("/?foo=bar", @base_headers).and_return(@request_mock)
358
- @rest.request(:GET, @url, {})
393
+ Net::HTTP::Get.should_receive(:new).with("/?foo=bar", base_headers).and_return(request_mock)
394
+ rest.request(:GET, url, {})
359
395
  end
360
396
 
361
397
  it "should build a new HTTP POST request" do
362
- request = Net::HTTP::Post.new(@url.path)
363
- expected_headers = @base_headers.merge("Content-Type" => 'application/json', 'Content-Length' => '13')
398
+ request = Net::HTTP::Post.new(url.path)
399
+ expected_headers = base_headers.merge("Content-Type" => 'application/json', 'Content-Length' => '13')
364
400
 
365
401
  Net::HTTP::Post.should_receive(:new).with("/?foo=bar", expected_headers).and_return(request)
366
- @rest.request(:POST, @url, {}, {:one=>:two})
367
- request.body.should == '{"one":"two"}'
402
+ rest.request(:POST, url, {}, {:one=>:two})
403
+ expect(request.body).to eq('{"one":"two"}')
368
404
  end
369
405
 
370
406
  it "should build a new HTTP PUT request" do
371
- request = Net::HTTP::Put.new(@url.path)
372
- expected_headers = @base_headers.merge("Content-Type" => 'application/json', 'Content-Length' => '13')
407
+ request = Net::HTTP::Put.new(url.path)
408
+ expected_headers = base_headers.merge("Content-Type" => 'application/json', 'Content-Length' => '13')
373
409
  Net::HTTP::Put.should_receive(:new).with("/?foo=bar",expected_headers).and_return(request)
374
- @rest.request(:PUT, @url, {}, {:one=>:two})
375
- request.body.should == '{"one":"two"}'
410
+ rest.request(:PUT, url, {}, {:one=>:two})
411
+ expect(request.body).to eq('{"one":"two"}')
376
412
  end
377
413
 
378
414
  it "should build a new HTTP DELETE request" do
379
- Net::HTTP::Delete.should_receive(:new).with("/?foo=bar", @base_headers).and_return(@request_mock)
380
- @rest.request(:DELETE, @url)
415
+ Net::HTTP::Delete.should_receive(:new).with("/?foo=bar", base_headers).and_return(request_mock)
416
+ rest.request(:DELETE, url)
381
417
  end
382
418
 
383
419
  it "should raise an error if the method is not GET/PUT/POST/DELETE" do
384
- lambda { @rest.request(:MONKEY, @url) }.should raise_error(ArgumentError)
420
+ expect { rest.request(:MONKEY, url) }.to raise_error(ArgumentError)
385
421
  end
386
422
 
387
423
  it "returns nil when the response is successful but content-type is not JSON" do
388
- @rest.request(:GET, @url).should == "ninja"
424
+ expect(rest.request(:GET, url)).to eq("ninja")
389
425
  end
390
426
 
391
- it "should inflate the body as to an object if JSON is returned" do
392
- @http_response.add_field('content-type', "application/json")
393
- @http_response.stub(:body).and_return('{"ohai2u":"json_api"}')
394
- @rest.request(:GET, @url, {}).should == {"ohai2u"=>"json_api"}
427
+ it "should fail if the response is truncated" do
428
+ http_response["Content-Length"] = (body.bytesize + 99).to_s
429
+ expect { rest.request(:GET, url) }.to raise_error(Chef::Exceptions::ContentLengthMismatch)
395
430
  end
396
431
 
397
- %w[ HTTPFound HTTPMovedPermanently HTTPSeeOther HTTPUseProxy HTTPTemporaryRedirect HTTPMultipleChoice ].each do |resp_name|
398
- it "should call request again on a #{resp_name} response" do
399
- resp_cls = Net.const_get(resp_name)
400
- resp_code = Net::HTTPResponse::CODE_TO_OBJ.keys.detect { |k| Net::HTTPResponse::CODE_TO_OBJ[k] == resp_cls }
401
- http_response = Net::HTTPFound.new("1.1", resp_code, "bob is somewhere else again")
402
- http_response.add_field("location", @url.path)
403
- http_response.stub(:read_body)
432
+ context "when JSON is returned" do
433
+ let(:body) { '{"ohai2u":"json_api"}' }
434
+ it "should inflate the body as to an object" do
435
+ http_response.add_field('content-type', "application/json")
436
+ expect(rest.request(:GET, url, {})).to eq({"ohai2u"=>"json_api"})
437
+ end
404
438
 
405
- @http_client.stub(:request).and_yield(http_response).and_return(http_response)
439
+ it "should fail if the response is truncated" do
440
+ http_response.add_field('content-type', "application/json")
441
+ http_response["Content-Length"] = (body.bytesize + 99).to_s
442
+ expect { rest.request(:GET, url, {}) }.to raise_error(Chef::Exceptions::ContentLengthMismatch)
443
+ end
444
+ end
445
+
446
+ %w[ HTTPFound HTTPMovedPermanently HTTPSeeOther HTTPUseProxy HTTPTemporaryRedirect HTTPMultipleChoice ].each do |resp_name|
447
+ describe "when encountering a #{resp_name} redirect" do
448
+ let(:http_response) do
449
+ resp_cls = Net.const_get(resp_name)
450
+ resp_code = Net::HTTPResponse::CODE_TO_OBJ.keys.detect { |k| Net::HTTPResponse::CODE_TO_OBJ[k] == resp_cls }
451
+ http_response = Net::HTTPFound.new("1.1", resp_code, "bob is somewhere else again")
452
+ http_response.add_field("location", url.path)
453
+ http_response.stub(:read_body)
454
+ http_response
455
+ end
456
+ it "should call request again" do
406
457
 
407
- lambda { @rest.request(:GET, @url) }.should raise_error(Chef::Exceptions::RedirectLimitExceeded)
458
+ expect { rest.request(:GET, url) }.to raise_error(Chef::Exceptions::RedirectLimitExceeded)
408
459
 
409
- [:PUT, :POST, :DELETE].each do |method|
410
- lambda { @rest.request(method, @url) }.should raise_error(Chef::Exceptions::InvalidRedirect)
460
+ [:PUT, :POST, :DELETE].each do |method|
461
+ expect { rest.request(method, url) }.to raise_error(Chef::Exceptions::InvalidRedirect)
462
+ end
411
463
  end
412
464
  end
413
465
  end
414
466
 
415
- it "should return `false` when response is 304 NotModified" do
416
- http_response = Net::HTTPNotModified.new("1.1", "304", "it's the same as when you asked 5 minutes ago")
417
- http_response.stub(:read_body)
418
-
419
- @http_client.stub(:request).and_yield(http_response).and_return(http_response)
467
+ context "when the response is 304 NotModified" do
468
+ let (:http_response) do
469
+ http_response = Net::HTTPNotModified.new("1.1", "304", "it's the same as when you asked 5 minutes ago")
470
+ http_response.stub(:read_body)
471
+ http_response
472
+ end
420
473
 
421
- @rest.request(:GET, @url).should be_false
474
+ it "should return `false`" do
475
+ expect(rest.request(:GET, url)).to be_false
476
+ end
422
477
  end
423
478
 
424
479
  describe "when the request fails" do
@@ -431,128 +486,167 @@ describe Chef::REST do
431
486
  Chef::Log.level = @original_log_level
432
487
  end
433
488
 
434
- it "should show the JSON error message on an unsuccessful request" do
435
- http_response = Net::HTTPServerError.new("1.1", "500", "drooling from inside of mouth")
436
- http_response.add_field("content-type", "application/json")
437
- http_response.stub(:body).and_return('{ "error":[ "Ears get sore!", "Not even four" ] }')
438
- http_response.stub(:read_body)
439
- @rest.stub(:sleep)
440
- @http_client.stub(:request).and_yield(http_response).and_return(http_response)
489
+ context "on an unsuccessful response with a JSON error" do
490
+ let(:http_response) do
491
+ http_response = Net::HTTPServerError.new("1.1", "500", "drooling from inside of mouth")
492
+ http_response.add_field("content-type", "application/json")
493
+ http_response.stub(:body).and_return('{ "error":[ "Ears get sore!", "Not even four" ] }')
494
+ http_response.stub(:read_body)
495
+ http_response
496
+ end
441
497
 
442
- lambda {@rest.request(:GET, @url)}.should raise_error(Net::HTTPFatalError)
443
- @log_stringio.string.should match(Regexp.escape('INFO: HTTP Request Returned 500 drooling from inside of mouth: Ears get sore!, Not even four'))
444
- end
498
+ it "should show the JSON error message" do
499
+ rest.stub(:sleep)
445
500
 
446
- it "decompresses the JSON error message on an unsuccessful request" do
447
- http_response = Net::HTTPServerError.new("1.1", "500", "drooling from inside of mouth")
448
- http_response.add_field("content-type", "application/json")
449
- http_response.add_field("content-encoding", "deflate")
450
- unzipped_body = '{ "error":[ "Ears get sore!", "Not even four" ] }'
451
- gzipped_body = Zlib::Deflate.deflate(unzipped_body)
452
- gzipped_body.force_encoding(Encoding::BINARY) if "strings".respond_to?(:force_encoding)
501
+ expect {rest.request(:GET, url)}.to raise_error(Net::HTTPFatalError)
502
+ expect(log_stringio.string).to match(Regexp.escape('INFO: HTTP Request Returned 500 drooling from inside of mouth: Ears get sore!, Not even four'))
503
+ end
504
+ end
453
505
 
454
- http_response.stub(:body).and_return gzipped_body
455
- http_response.stub(:read_body)
456
- @rest.stub(:sleep)
457
- @rest.stub(:http_retry_count).and_return(0)
458
- @http_client.stub(:request).and_yield(http_response).and_return(http_response)
506
+ context "on an unsuccessful response with a JSON error that is compressed" do
507
+ let(:http_response) do
508
+ http_response = Net::HTTPServerError.new("1.1", "500", "drooling from inside of mouth")
509
+ http_response.add_field("content-type", "application/json")
510
+ http_response.add_field("content-encoding", "deflate")
511
+ unzipped_body = '{ "error":[ "Ears get sore!", "Not even four" ] }'
512
+ gzipped_body = Zlib::Deflate.deflate(unzipped_body)
513
+ gzipped_body.force_encoding(Encoding::BINARY) if "strings".respond_to?(:force_encoding)
514
+
515
+ http_response.stub(:body).and_return gzipped_body
516
+ http_response.stub(:read_body)
517
+ http_response
518
+ end
519
+ it "decompresses the JSON error message" do
520
+ rest.stub(:sleep)
521
+ rest.stub(:http_retry_count).and_return(0)
459
522
 
460
- lambda {@rest.request(:GET, @url)}.should raise_error(Net::HTTPFatalError)
461
- @log_stringio.string.should match(Regexp.escape('INFO: HTTP Request Returned 500 drooling from inside of mouth: Ears get sore!, Not even four'))
523
+ expect {rest.request(:GET, url)}.to raise_error(Net::HTTPFatalError)
524
+ expect(log_stringio.string).to match(Regexp.escape('INFO: HTTP Request Returned 500 drooling from inside of mouth: Ears get sore!, Not even four'))
525
+ end
526
+ it "fails when the compressed body is truncated" do
527
+ http_response["Content-Length"] = (body.bytesize + 99).to_s
528
+ expect {rest.request(:GET, url)}.to raise_error(Chef::Exceptions::ContentLengthMismatch)
529
+ end
462
530
  end
463
531
 
464
- it "should raise an exception on an unsuccessful request" do
465
- http_response = Net::HTTPServerError.new("1.1", "500", "drooling from inside of mouth")
466
- http_response.stub(:body)
467
- http_response.stub(:read_body)
468
- @rest.stub(:sleep)
469
- @http_client.stub(:request).and_yield(http_response).and_return(http_response)
470
- lambda {@rest.request(:GET, @url)}.should raise_error(Net::HTTPFatalError)
532
+ context "on a generic unsuccessful request" do
533
+ let(:http_response) do
534
+ http_response = Net::HTTPServerError.new("1.1", "500", "drooling from inside of mouth")
535
+ http_response.stub(:body)
536
+ http_response.stub(:read_body)
537
+ http_response
538
+ end
539
+ it "throws an exception" do
540
+ rest.stub(:sleep)
541
+ expect {rest.request(:GET, url)}.to raise_error(Net::HTTPFatalError)
542
+ end
471
543
  end
472
544
  end
473
-
474
-
475
545
  end
476
546
 
477
547
  context "when streaming downloads to a tempfile" do
478
- before do
479
- @tempfile = Tempfile.open("chef-rspec-rest_spec-line-#{__LINE__}--")
480
- Tempfile.stub(:new).with("chef-rest").and_return(@tempfile)
481
- @request_mock = {}
482
- Net::HTTP::Get.stub(:new).and_return(@request_mock)
548
+ let!(:tempfile) { Tempfile.open("chef-rspec-rest_spec-line-@{__LINE__}--") }
549
+
550
+ let(:request_mock) { {} }
551
+
552
+ let(:http_response) do
553
+ http_response = Net::HTTPSuccess.new("1.1",200, "it-works")
554
+
555
+ http_response.stub(:read_body)
556
+ http_response.should_not_receive(:body)
557
+ http_response["Content-Length"] = "0" # call set_content_length (in test), if otherwise
558
+ http_response
559
+ end
560
+
561
+ def set_content_length
562
+ content_length = 0
563
+ http_response.read_body do |chunk|
564
+ content_length += chunk.bytesize
565
+ end
566
+ http_response["Content-Length"] = content_length.to_s
567
+ end
483
568
 
484
- @http_response = Net::HTTPSuccess.new("1.1",200, "it-works")
485
- @http_response.stub(:read_body)
486
- @http_client.stub(:request).and_yield(@http_response).and_return(@http_response)
569
+ before do
570
+ Tempfile.stub(:new).with("chef-rest").and_return(tempfile)
571
+ Net::HTTP::Get.stub(:new).and_return(request_mock)
487
572
  end
488
573
 
489
574
  after do
490
- @tempfile.close!
575
+ tempfile.close!
491
576
  end
492
577
 
493
578
  it " build a new HTTP GET request without the application/json accept header" do
494
579
  expected_headers = {'Accept' => "*/*",
495
580
  'X-Chef-Version' => Chef::VERSION,
496
581
  'Accept-Encoding' => Chef::REST::RESTRequest::ENCODING_GZIP_DEFLATE,
497
- 'Host' => @host_header}
498
- Net::HTTP::Get.should_receive(:new).with("/?foo=bar", expected_headers).and_return(@request_mock)
499
- @rest.streaming_request(@url, {})
582
+ 'Host' => host_header}
583
+ Net::HTTP::Get.should_receive(:new).with("/?foo=bar", expected_headers).and_return(request_mock)
584
+ rest.streaming_request(url, {})
500
585
  end
501
586
 
502
587
  it "returns a tempfile containing the streamed response body" do
503
- @rest.streaming_request(@url, {}).should equal(@tempfile)
588
+ expect(rest.streaming_request(url, {})).to equal(tempfile)
504
589
  end
505
590
 
506
591
  it "writes the response body to a tempfile" do
507
- @http_response.stub(:read_body).and_yield("real").and_yield("ultimate").and_yield("power")
508
- @rest.streaming_request(@url, {})
509
- IO.read(@tempfile.path).chomp.should == "realultimatepower"
592
+ http_response.stub(:read_body).and_yield("real").and_yield("ultimate").and_yield("power")
593
+ set_content_length
594
+ rest.streaming_request(url, {})
595
+ expect(IO.read(tempfile.path).chomp).to eq("realultimatepower")
510
596
  end
511
597
 
512
598
  it "closes the tempfile" do
513
- @rest.streaming_request(@url, {})
514
- @tempfile.should be_closed
599
+ rest.streaming_request(url, {})
600
+ expect(tempfile).to be_closed
515
601
  end
516
602
 
517
603
  it "yields the tempfile containing the streamed response body and then unlinks it when given a block" do
518
- @http_response.stub(:read_body).and_yield("real").and_yield("ultimate").and_yield("power")
604
+ http_response.stub(:read_body).and_yield("real").and_yield("ultimate").and_yield("power")
605
+ set_content_length
519
606
  tempfile_path = nil
520
- @rest.streaming_request(@url, {}) do |tempfile|
607
+ rest.streaming_request(url, {}) do |tempfile|
521
608
  tempfile_path = tempfile.path
522
- File.exist?(tempfile.path).should be_true
523
- IO.read(@tempfile.path).chomp.should == "realultimatepower"
609
+ expect(File.exist?(tempfile.path)).to be_true
610
+ expect(IO.read(tempfile.path).chomp).to eq("realultimatepower")
524
611
  end
525
- File.exist?(tempfile_path).should be_false
612
+ expect(File.exist?(tempfile_path)).to be_false
526
613
  end
527
614
 
528
615
  it "does not raise a divide by zero exception if the content's actual size is 0" do
529
- @http_response.add_field('Content-Length', "5")
530
- @http_response.stub(:read_body).and_yield('')
531
- lambda { @rest.streaming_request(@url, {}) }.should_not raise_error
616
+ http_response['Content-Length'] = "5"
617
+ http_response.stub(:read_body).and_yield('')
618
+ expect { rest.streaming_request(url, {}) }.to_not raise_error(ZeroDivisionError)
532
619
  end
533
620
 
534
621
  it "does not raise a divide by zero exception when the Content-Length is 0" do
535
- @http_response.add_field('Content-Length', "0")
536
- @http_response.stub(:read_body).and_yield("ninja")
537
- lambda { @rest.streaming_request(@url, {}) }.should_not raise_error
622
+ http_response['Content-Length'] = "0"
623
+ http_response.stub(:read_body).and_yield("ninja")
624
+ expect { rest.streaming_request(url, {}) }.to_not raise_error(ZeroDivisionError)
625
+ end
626
+
627
+ it "it raises an exception when the download is truncated" do
628
+ http_response["Content-Length"] = (body.bytesize + 99).to_s
629
+ http_response.stub(:read_body).and_yield("ninja")
630
+ expect { rest.streaming_request(url, {}) }.to raise_error(Chef::Exceptions::ContentLengthMismatch)
538
631
  end
539
632
 
540
633
  it "fetches a file and yields the tempfile it is streamed to" do
541
- @http_response.stub(:read_body).and_yield("real").and_yield("ultimate").and_yield("power")
634
+ http_response.stub(:read_body).and_yield("real").and_yield("ultimate").and_yield("power")
635
+ set_content_length
542
636
  tempfile_path = nil
543
- @rest.fetch("cookbooks/a_cookbook") do |tempfile|
637
+ rest.fetch("cookbooks/a_cookbook") do |tempfile|
544
638
  tempfile_path = tempfile.path
545
- IO.read(@tempfile.path).chomp.should == "realultimatepower"
639
+ expect(IO.read(tempfile.path).chomp).to eq("realultimatepower")
546
640
  end
547
- File.exist?(tempfile_path).should be_false
641
+ expect(File.exist?(tempfile_path)).to be_false
548
642
  end
549
643
 
550
644
  it "closes and unlinks the tempfile if there is an error while streaming the content to the tempfile" do
551
- path = @tempfile.path
552
- path.should_not be_nil
553
- @tempfile.stub(:write).and_raise(IOError)
554
- @rest.fetch("cookbooks/a_cookbook") {|tmpfile| "shouldn't get here"}
555
- File.exists?(path).should be_false
645
+ path = tempfile.path
646
+ expect(path).not_to be_nil
647
+ tempfile.stub(:write).and_raise(IOError)
648
+ rest.fetch("cookbooks/a_cookbook") {|tmpfile| "shouldn't get here"}
649
+ expect(File.exists?(path)).to be_false
556
650
  end
557
651
 
558
652
  it "closes and unlinks the tempfile when the response is a redirect" do
@@ -561,80 +655,83 @@ describe Chef::REST do
561
655
  Tempfile.stub(:new).with("chef-rest").and_return(tempfile)
562
656
 
563
657
  redirect = Net::HTTPFound.new("1.1", "302", "bob is taking care of that one for me today")
564
- redirect.add_field("location", @url.path)
658
+ redirect.add_field("location", url.path)
565
659
  redirect.stub(:read_body)
566
660
 
567
- @http_client.should_receive(:request).and_yield(redirect).and_return(redirect)
568
- @http_client.should_receive(:request).and_yield(@http_response).and_return(@http_response)
569
- @rest.fetch("cookbooks/a_cookbook") {|tmpfile| "shouldn't get here"}
661
+ http_client.should_receive(:request).and_yield(redirect).and_return(redirect)
662
+ http_client.should_receive(:request).and_yield(http_response).and_return(http_response)
663
+ rest.fetch("cookbooks/a_cookbook") {|tmpfile| "shouldn't get here"}
570
664
  end
571
665
 
572
666
  it "passes the original block to the redirected request" do
573
- http_response = Net::HTTPFound.new("1.1", "302", "bob is taking care of that one for me today")
574
- http_response.add_field("location","/that-thing-is-here-now")
575
- http_response.stub(:read_body)
667
+ http_redirect = Net::HTTPFound.new("1.1", "302", "bob is taking care of that one for me today")
668
+ http_redirect.add_field("location","/that-thing-is-here-now")
669
+ http_redirect.stub(:read_body)
576
670
 
577
671
  block_called = false
578
- @http_client.stub(:request).and_yield(@http_response).and_return(http_response, @http_response)
579
- @rest.fetch("cookbooks/a_cookbook") do |tmpfile|
672
+ http_client.stub(:request).and_yield(http_response).and_return(http_redirect, http_response)
673
+ rest.fetch("cookbooks/a_cookbook") do |tmpfile|
580
674
  block_called = true
581
675
  end
582
- block_called.should be_true
676
+ expect(block_called).to be_true
583
677
  end
584
678
  end
585
679
  end
586
680
 
587
681
  context "when following redirects" do
682
+ let(:rest) do
683
+ Chef::REST.new(base_url)
684
+ end
685
+
588
686
  before do
589
687
  Chef::Config[:node_name] = "webmonkey.example.com"
590
688
  Chef::Config[:client_key] = CHEF_SPEC_DATA + "/ssl/private_key.pem"
591
- @rest = Chef::REST.new(@url)
592
689
  end
593
690
 
594
691
  it "raises a RedirectLimitExceeded when redirected more than 10 times" do
595
- redirected = lambda {@rest.follow_redirect { redirected.call }}
596
- lambda {redirected.call}.should raise_error(Chef::Exceptions::RedirectLimitExceeded)
692
+ redirected = lambda {rest.follow_redirect { redirected.call }}
693
+ expect {redirected.call}.to raise_error(Chef::Exceptions::RedirectLimitExceeded)
597
694
  end
598
695
 
599
696
  it "does not count redirects from previous calls against the redirect limit" do
600
697
  total_redirects = 0
601
698
  redirected = lambda do
602
- @rest.follow_redirect do
699
+ rest.follow_redirect do
603
700
  total_redirects += 1
604
701
  redirected.call unless total_redirects >= 9
605
702
  end
606
703
  end
607
- lambda {redirected.call}.should_not raise_error
704
+ expect {redirected.call}.not_to raise_error
608
705
  total_redirects = 0
609
- lambda {redirected.call}.should_not raise_error
706
+ expect {redirected.call}.not_to raise_error
610
707
  end
611
708
 
612
709
  it "does not sign the redirected request when sign_on_redirect is false" do
613
- @rest.sign_on_redirect = false
614
- @rest.follow_redirect { @rest.sign_requests?.should be_false }
710
+ rest.sign_on_redirect = false
711
+ rest.follow_redirect { expect(rest.sign_requests?).to be_false }
615
712
  end
616
713
 
617
714
  it "resets sign_requests to the original value after following an unsigned redirect" do
618
- @rest.sign_on_redirect = false
619
- @rest.sign_requests?.should be_true
715
+ rest.sign_on_redirect = false
716
+ expect(rest.sign_requests?).to be_true
620
717
 
621
- @rest.follow_redirect { @rest.sign_requests?.should be_false }
622
- @rest.sign_requests?.should be_true
718
+ rest.follow_redirect { expect(rest.sign_requests?).to be_false }
719
+ expect(rest.sign_requests?).to be_true
623
720
  end
624
721
 
625
722
  it "configures the redirect limit" do
626
723
  total_redirects = 0
627
724
  redirected = lambda do
628
- @rest.follow_redirect do
725
+ rest.follow_redirect do
629
726
  total_redirects += 1
630
727
  redirected.call unless total_redirects >= 9
631
728
  end
632
729
  end
633
- lambda {redirected.call}.should_not raise_error
730
+ expect {redirected.call}.not_to raise_error
634
731
 
635
732
  total_redirects = 0
636
- @rest.redirect_limit = 3
637
- lambda {redirected.call}.should raise_error(Chef::Exceptions::RedirectLimitExceeded)
733
+ rest.redirect_limit = 3
734
+ expect {redirected.call}.to raise_error(Chef::Exceptions::RedirectLimitExceeded)
638
735
  end
639
736
 
640
737
  end