3
require 'puppet/network/http/handler'
4
require 'puppet/network/rest_authorization'
7
include Puppet::Network::HTTP::Handler
10
describe Puppet::Network::HTTP::Handler do
12
@handler = HttpHandled.new
15
it "should include the v1 REST API" do
16
Puppet::Network::HTTP::Handler.ancestors.should be_include(Puppet::Network::HTTP::API::V1)
19
it "should include the Rest Authorization system" do
20
Puppet::Network::HTTP::Handler.ancestors.should be_include(Puppet::Network::RestAuthorization)
23
it "should have a method for initializing" do
24
@handler.should respond_to(:initialize_for_puppet)
27
describe "when initializing" do
28
it "should fail when no server type has been provided" do
29
lambda { @handler.initialize_for_puppet }.should raise_error(ArgumentError)
32
it "should set server type" do
33
@handler.initialize_for_puppet("foo")
34
@handler.server.should == "foo"
38
it "should be able to process requests" do
39
@handler.should respond_to(:process)
42
describe "when processing a request" do
44
@request = stub('http request')
45
@request.stubs(:[]).returns "foo"
46
@response = stub('http response')
47
@model_class = stub('indirected model class')
48
@indirection = stub('indirection')
49
@model_class.stubs(:indirection).returns(@indirection)
51
@result = stub 'result', :render => "mytext"
53
@handler.stubs(:check_authorization)
58
# Stub out the interface we require our including classes to
60
def stub_server_interface
61
@handler.stubs(:accept_header ).returns "format_one,format_two"
62
@handler.stubs(:content_type_header).returns "text/yaml"
63
@handler.stubs(:set_content_type ).returns "my_result"
64
@handler.stubs(:set_response ).returns "my_result"
65
@handler.stubs(:path ).returns "/my_handler/my_result"
66
@handler.stubs(:http_method ).returns("GET")
67
@handler.stubs(:params ).returns({})
68
@handler.stubs(:content_type ).returns("text/plain")
71
it "should create an indirection request from the path, parameters, and http method" do
72
@handler.expects(:path).with(@request).returns "mypath"
73
@handler.expects(:http_method).with(@request).returns "mymethod"
74
@handler.expects(:params).with(@request).returns "myparams"
76
@handler.expects(:uri2indirection).with("mymethod", "mypath", "myparams").returns stub("request", :method => :find)
78
@handler.stubs(:do_find)
80
@handler.process(@request, @response)
83
it "should call the 'do' method and delegate authorization to the RestAuthorization layer" do
84
@handler.expects(:uri2indirection).returns(["facts", :mymethod, "key", {:node => "name"}])
86
@handler.expects(:do_mymethod).with("facts", "key", {:node => "name"}, @request, @response)
88
@handler.expects(:check_authorization).with("facts", :mymethod, "key", {:node => "name"})
90
@handler.process(@request, @response)
93
it "should return 403 if the request is not authorized" do
94
@handler.expects(:uri2indirection).returns(["facts", :mymethod, "key", {:node => "name"}])
96
@handler.expects(:do_mymethod).never
98
@handler.expects(:check_authorization).with("facts", :mymethod, "key", {:node => "name"}).raises(Puppet::Network::AuthorizationError.new("forbidden"))
100
@handler.expects(:set_response).with { |response, body, status| status == 403 }
102
@handler.process(@request, @response)
105
it "should serialize a controller exception when an exception is thrown while finding the model instance" do
106
@handler.expects(:uri2indirection).returns(["facts", :find, "key", {:node => "name"}])
108
@handler.expects(:do_find).raises(ArgumentError, "The exception")
109
@handler.expects(:set_response).with { |response, body, status| body == "The exception" and status == 400 }
110
@handler.process(@request, @response)
113
it "should set the format to text/plain when serializing an exception" do
114
@handler.expects(:set_content_type).with(@response, "text/plain")
115
@handler.do_exception(@response, "A test", 404)
118
it "should raise an error if the request is formatted in an unknown format" do
119
@handler.stubs(:content_type_header).returns "unknown format"
120
lambda { @handler.request_format(@request) }.should raise_error
123
it "should still find the correct format if content type contains charset information" do
124
@handler.stubs(:content_type_header).returns "text/plain; charset=UTF-8"
125
@handler.request_format(@request).should == "s"
128
it "should deserialize YAML parameters" do
129
params = {'my_param' => [1,2,3].to_yaml}
131
decoded_params = @handler.send(:decode_params, params)
133
decoded_params.should == {:my_param => [1,2,3]}
136
it "should accept YAML parameters with !ruby/hash tags on Ruby 1.8", :if => RUBY_VERSION =~ /^1\.8/ do
137
params = {'my_param' => "--- !ruby/hash:Array {}"}
139
decoded_params = @handler.send(:decode_params, params)
141
decoded_params[:my_param].should be_an(Array)
144
# These are only dangerous with Psych, which is Ruby 1.9-only. Since
145
# there's no real way to change the yamler in Puppet, assume that 1.9 means
146
# Psych, especially in tests.
147
it "should fail if YAML parameters have !ruby/hash tags on Ruby 1.9", :unless => RUBY_VERSION =~ /^1\.8/ do
148
params = {'my_param' => "--- !ruby/hash:Array {}"}
150
expect { @handler.send(:decode_params, params) }.to raise_error(ArgumentError, /Illegal YAML mapping found/)
153
describe "when finding a model instance" do
155
@indirection.stubs(:find).returns @result
156
Puppet::Indirector::Indirection.expects(:instance).with(:my_handler).returns( stub "indirection", :model => @model_class )
158
@format = stub 'format', :suitable? => true, :mime => "text/format", :name => "format"
159
Puppet::Network::FormatHandler.stubs(:format).returns @format
161
@oneformat = stub 'one', :suitable? => true, :mime => "text/one", :name => "one"
162
Puppet::Network::FormatHandler.stubs(:format).with("one").returns @oneformat
165
it "should use the indirection request to find the model class" do
166
@handler.do_find("my_handler", "my_result", {}, @request, @response)
169
it "should use the escaped request key" do
170
@indirection.expects(:find).with do |key, args|
173
@handler.do_find("my_handler", "my_result", {}, @request, @response)
176
it "should use a common method for determining the request parameters" do
177
@indirection.expects(:find).with do |key, args|
178
args[:foo] == :baz and args[:bar] == :xyzzy
180
@handler.do_find("my_handler", "my_result", {:foo => :baz, :bar => :xyzzy}, @request, @response)
183
it "should set the content type to the first format specified in the accept header" do
184
@handler.expects(:accept_header).with(@request).returns "one,two"
185
@handler.expects(:set_content_type).with(@response, @oneformat)
186
@handler.do_find("my_handler", "my_result", {}, @request, @response)
189
it "should fail if no accept header is provided" do
190
@handler.expects(:accept_header).with(@request).returns nil
191
lambda { @handler.do_find("my_handler", "my_result", {}, @request, @response) }.should raise_error(ArgumentError)
194
it "should fail if the accept header does not contain a valid format" do
195
@handler.expects(:accept_header).with(@request).returns ""
196
lambda { @handler.do_find("my_handler", "my_result", {}, @request, @response) }.should raise_error(RuntimeError)
199
it "should not use an unsuitable format" do
200
@handler.expects(:accept_header).with(@request).returns "foo,bar"
201
foo = mock 'foo', :suitable? => false
202
bar = mock 'bar', :suitable? => true
203
Puppet::Network::FormatHandler.expects(:format).with("foo").returns foo
204
Puppet::Network::FormatHandler.expects(:format).with("bar").returns bar
206
@handler.expects(:set_content_type).with(@response, bar) # the suitable one
208
@handler.do_find("my_handler", "my_result", {}, @request, @response)
211
it "should render the result using the first format specified in the accept header" do
213
@handler.expects(:accept_header).with(@request).returns "one,two"
214
@result.expects(:render).with(@oneformat)
216
@handler.do_find("my_handler", "my_result", {}, @request, @response)
219
it "should pass the result through without rendering it if the result is a string" do
220
@indirection.stubs(:find).returns "foo"
221
@handler.expects(:set_response).with(@response, "foo")
222
@handler.do_find("my_handler", "my_result", {}, @request, @response)
225
it "should use the default status when a model find call succeeds" do
226
@handler.expects(:set_response).with { |response, body, status| status.nil? }
227
@handler.do_find("my_handler", "my_result", {}, @request, @response)
230
it "should return a serialized object when a model find call succeeds" do
231
@model_instance = stub('model instance')
232
@model_instance.expects(:render).returns "my_rendered_object"
234
@handler.expects(:set_response).with { |response, body, status| body == "my_rendered_object" }
235
@indirection.stubs(:find).returns(@model_instance)
236
@handler.do_find("my_handler", "my_result", {}, @request, @response)
239
it "should return a 404 when no model instance can be found" do
240
@model_class.stubs(:name).returns "my name"
241
@handler.expects(:set_response).with { |response, body, status| status == 404 }
242
@indirection.stubs(:find).returns(nil)
243
@handler.do_find("my_handler", "my_result", {}, @request, @response)
246
it "should write a log message when no model instance can be found" do
247
@model_class.stubs(:name).returns "my name"
248
@indirection.stubs(:find).returns(nil)
250
Puppet.expects(:info).with("Could not find my_handler for 'my_result'")
252
@handler.do_find("my_handler", "my_result", {}, @request, @response)
256
it "should serialize the result in with the appropriate format" do
257
@model_instance = stub('model instance')
259
@handler.expects(:format_to_use).returns(@oneformat)
260
@model_instance.expects(:render).with(@oneformat).returns "my_rendered_object"
261
@indirection.stubs(:find).returns(@model_instance)
262
@handler.do_find("my_handler", "my_result", {}, @request, @response)
266
describe "when performing head operation" do
268
@handler.stubs(:model).with("my_handler").returns(stub 'model', :indirection => @model_class)
269
@handler.stubs(:http_method).with(@request).returns("HEAD")
270
@handler.stubs(:path).with(@request).returns("/production/my_handler/my_result")
271
@handler.stubs(:params).with(@request).returns({})
273
@model_class.stubs(:head).returns true
276
it "should use the escaped request key" do
277
@model_class.expects(:head).with do |key, args|
280
@handler.process(@request, @response)
283
it "should not generate a response when a model head call succeeds" do
284
@handler.expects(:set_response).never
285
@handler.process(@request, @response)
288
it "should return a 404 when the model head call returns false" do
289
@handler.expects(:set_response).with { |response, body, status| status == 404 }
290
@model_class.stubs(:head).returns(false)
291
@handler.process(@request, @response)
295
describe "when searching for model instances" do
297
Puppet::Indirector::Indirection.expects(:instance).with(:my_handler).returns( stub "indirection", :model => @model_class )
299
@result1 = mock 'result1'
300
@result2 = mock 'results'
302
@result = [@result1, @result2]
303
@model_class.stubs(:render_multiple).returns "my rendered instances"
304
@indirection.stubs(:search).returns(@result)
306
@format = stub 'format', :suitable? => true, :mime => "text/format", :name => "format"
307
Puppet::Network::FormatHandler.stubs(:format).returns @format
309
@oneformat = stub 'one', :suitable? => true, :mime => "text/one", :name => "one"
310
Puppet::Network::FormatHandler.stubs(:format).with("one").returns @oneformat
313
it "should use the indirection request to find the model" do
314
@handler.do_search("my_handler", "my_result", {}, @request, @response)
317
it "should use a common method for determining the request parameters" do
318
@indirection.expects(:search).with do |key, args|
319
args[:foo] == :baz and args[:bar] == :xyzzy
321
@handler.do_search("my_handler", "my_result", {:foo => :baz, :bar => :xyzzy}, @request, @response)
324
it "should use the default status when a model search call succeeds" do
325
@indirection.stubs(:search).returns(@result)
326
@handler.do_search("my_handler", "my_result", {}, @request, @response)
329
it "should set the content type to the first format returned by the accept header" do
330
@handler.expects(:accept_header).with(@request).returns "one,two"
331
@handler.expects(:set_content_type).with(@response, @oneformat)
333
@handler.do_search("my_handler", "my_result", {}, @request, @response)
336
it "should return a list of serialized objects when a model search call succeeds" do
337
@handler.expects(:accept_header).with(@request).returns "one,two"
339
@indirection.stubs(:search).returns(@result)
341
@model_class.expects(:render_multiple).with(@oneformat, @result).returns "my rendered instances"
343
@handler.expects(:set_response).with { |response, data| data == "my rendered instances" }
344
@handler.do_search("my_handler", "my_result", {}, @request, @response)
347
it "should return [] when searching returns an empty array" do
348
@handler.expects(:accept_header).with(@request).returns "one,two"
349
@indirection.stubs(:search).returns([])
350
@model_class.expects(:render_multiple).with(@oneformat, []).returns "[]"
353
@handler.expects(:set_response).with { |response, data| data == "[]" }
354
@handler.do_search("my_handler", "my_result", {}, @request, @response)
357
it "should return a 404 when searching returns nil" do
358
@model_class.stubs(:name).returns "my name"
359
@handler.expects(:set_response).with { |response, body, status| status == 404 }
360
@indirection.stubs(:search).returns(nil)
361
@handler.do_search("my_handler", "my_result", {}, @request, @response)
365
describe "when destroying a model instance" do
367
Puppet::Indirector::Indirection.expects(:instance).with(:my_handler).returns( stub "indirection", :model => @model_class )
369
@result = stub 'result', :render => "the result"
370
@indirection.stubs(:destroy).returns @result
373
it "should use the indirection request to find the model" do
374
@handler.do_destroy("my_handler", "my_result", {}, @request, @response)
377
it "should use the escaped request key to destroy the instance in the model" do
378
@indirection.expects(:destroy).with do |key, args|
381
@handler.do_destroy("my_handler", "foo bar", {}, @request, @response)
384
it "should use a common method for determining the request parameters" do
385
@indirection.expects(:destroy).with do |key, args|
386
args[:foo] == :baz and args[:bar] == :xyzzy
388
@handler.do_destroy("my_handler", "my_result", {:foo => :baz, :bar => :xyzzy}, @request, @response)
391
it "should use the default status code a model destroy call succeeds" do
392
@handler.expects(:set_response).with { |response, body, status| status.nil? }
393
@handler.do_destroy("my_handler", "my_result", {}, @request, @response)
396
it "should return a yaml-encoded result when a model destroy call succeeds" do
397
@result = stub 'result', :to_yaml => "the result"
398
@indirection.expects(:destroy).returns(@result)
400
@handler.expects(:set_response).with { |response, body, status| body == "the result" }
402
@handler.do_destroy("my_handler", "my_result", {}, @request, @response)
406
describe "when saving a model instance" do
408
Puppet::Indirector::Indirection.stubs(:instance).with(:my_handler).returns( stub "indirection", :model => @model_class )
409
@handler.stubs(:body).returns('my stuff')
410
@handler.stubs(:content_type_header).returns("text/yaml")
412
@result = stub 'result', :render => "the result"
414
@model_instance = stub('indirected model instance')
415
@model_class.stubs(:convert_from).returns(@model_instance)
416
@indirection.stubs(:save)
418
@format = stub 'format', :suitable? => true, :name => "format", :mime => "text/format"
419
Puppet::Network::FormatHandler.stubs(:format).returns @format
420
@yamlformat = stub 'yaml', :suitable? => true, :name => "yaml", :mime => "text/yaml"
421
Puppet::Network::FormatHandler.stubs(:format).with("yaml").returns @yamlformat
424
it "should use the indirection request to find the model" do
425
@handler.do_save("my_handler", "my_result", {}, @request, @response)
428
it "should use the 'body' hook to retrieve the body of the request" do
429
@handler.expects(:body).returns "my body"
430
@model_class.expects(:convert_from).with { |format, body| body == "my body" }.returns @model_instance
432
@handler.do_save("my_handler", "my_result", {}, @request, @response)
435
it "should fail to save model if data is not specified" do
436
@handler.stubs(:body).returns('')
438
lambda { @handler.do_save("my_handler", "my_result", {}, @request, @response) }.should raise_error(ArgumentError)
441
it "should use a common method for determining the request parameters" do
442
@indirection.expects(:save).with(@model_instance, 'key').once
443
@handler.do_save("my_handler", "key", {}, @request, @response)
446
it "should use the default status when a model save call succeeds" do
447
@handler.expects(:set_response).with { |response, body, status| status.nil? }
448
@handler.do_save("my_handler", "my_result", {}, @request, @response)
451
it "should return the yaml-serialized result when a model save call succeeds" do
452
@indirection.stubs(:save).returns(@model_instance)
453
@model_instance.expects(:to_yaml).returns('foo')
454
@handler.do_save("my_handler", "my_result", {}, @request, @response)
457
it "should set the content to yaml" do
458
@handler.expects(:set_content_type).with(@response, @yamlformat)
459
@handler.do_save("my_handler", "my_result", {}, @request, @response)
462
it "should use the content-type header to know the body format" do
463
@handler.expects(:content_type_header).returns("text/format")
464
Puppet::Network::FormatHandler.stubs(:mime).with("text/format").returns @format
466
@model_class.expects(:convert_from).with { |format, body| format == "format" }.returns @model_instance
468
@handler.do_save("my_handler", "my_result", {}, @request, @response)
473
describe "when resolving node" do
474
it "should use a look-up from the ip address" do
475
Resolv.expects(:getname).with("1.2.3.4").returns("host.domain.com")
477
@handler.resolve_node(:ip => "1.2.3.4")
480
it "should return the look-up result" do
481
Resolv.stubs(:getname).with("1.2.3.4").returns("host.domain.com")
483
@handler.resolve_node(:ip => "1.2.3.4").should == "host.domain.com"
486
it "should return the ip address if resolving fails" do
487
Resolv.stubs(:getname).with("1.2.3.4").raises(RuntimeError, "no such host")
489
@handler.resolve_node(:ip => "1.2.3.4").should == "1.2.3.4"