chef 11.10.0.alpha.1 → 11.10.0.rc.0

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