diff options
author | Luke Kanies <luke@madstop.com> | 2008-07-26 22:46:26 -0500 |
---|---|---|
committer | Luke Kanies <luke@madstop.com> | 2008-07-29 00:51:21 -0500 |
commit | e3350caeec3a662b0b92ec2dee372563a493fa11 (patch) | |
tree | 26630fdca2c343d91b7e6b8fe8fa8e67b4b68a47 | |
parent | b3914c367f470dd37a846d01207228d6ded40c6d (diff) | |
download | puppet-e3350caeec3a662b0b92ec2dee372563a493fa11.tar.gz puppet-e3350caeec3a662b0b92ec2dee372563a493fa11.tar.xz puppet-e3350caeec3a662b0b92ec2dee372563a493fa11.zip |
Drastically simplifying the REST implementation tests.
Nearly all of the tests are now written just once, in
the Handler module. The Mongrel and Webrick tests just
validate that they provide the interface and how they do
so.
Signed-off-by: Luke Kanies <luke@madstop.com>
-rw-r--r-- | lib/puppet/network/http/mongrel/rest.rb | 22 | ||||
-rw-r--r-- | lib/puppet/network/http/webrick/rest.rb | 13 | ||||
-rwxr-xr-x | spec/unit/network/http/mongrel/rest.rb | 452 | ||||
-rwxr-xr-x | spec/unit/network/http/webrick/rest.rb | 364 |
4 files changed, 214 insertions, 637 deletions
diff --git a/lib/puppet/network/http/mongrel/rest.rb b/lib/puppet/network/http/mongrel/rest.rb index 520ad67f0..d6c2e4679 100644 --- a/lib/puppet/network/http/mongrel/rest.rb +++ b/lib/puppet/network/http/mongrel/rest.rb @@ -4,24 +4,28 @@ class Puppet::Network::HTTP::MongrelREST < Mongrel::HttpHandler include Puppet::Network::HTTP::Handler + ACCEPT_HEADER = "HTTP_ACCEPT".freeze # yay, zed's a crazy-man + def initialize(args={}) super() initialize_for_puppet(args) end - # Return the query params for this request. We had to expose this method for - # testing purposes. - def params(request) - Mongrel::HttpRequest.query_parse(request.params["QUERY_STRING"]).merge(client_info(request)) + def accept_header(request) + request.params[ACCEPT_HEADER] end - private - # which HTTP verb was used in this request def http_method(request) request.params[Mongrel::Const::REQUEST_METHOD] end + # Return the query params for this request. We had to expose this method for + # testing purposes. + def params(request) + Mongrel::HttpRequest.query_parse(request.params["QUERY_STRING"]).merge(client_info(request)) + end + # what path was requested? def path(request) # LAK:NOTE See http://snurl.com/21zf8 [groups_google_com] @@ -39,8 +43,12 @@ class Puppet::Network::HTTP::MongrelREST < Mongrel::HttpHandler request.body end + def set_content_type(response, format) + response.header['Content-Type'] = format + end + # produce the body of the response - def encode_result(request, response, result, status = 200) + def set_response(response, result, status = 200) response.start(status) do |head, body| body.write(result) end diff --git a/lib/puppet/network/http/webrick/rest.rb b/lib/puppet/network/http/webrick/rest.rb index a235fb4f3..c7cc06916 100644 --- a/lib/puppet/network/http/webrick/rest.rb +++ b/lib/puppet/network/http/webrick/rest.rb @@ -10,7 +10,7 @@ class Puppet::Network::HTTP::WEBrickREST < WEBrick::HTTPServlet::AbstractServlet initialize_for_puppet(:server => server, :handler => handler) end - # We had to expose this method for testing purposes. + # Retrieve the request parameters, including authentication information. def params(request) result = request.query result.merge(client_information(request)) @@ -21,7 +21,9 @@ class Puppet::Network::HTTP::WEBrickREST < WEBrick::HTTPServlet::AbstractServlet process(request, response) end - private + def accept_header(request) + request[:accept] + end def http_method(request) request.request_method @@ -41,7 +43,12 @@ class Puppet::Network::HTTP::WEBrickREST < WEBrick::HTTPServlet::AbstractServlet request.body end - def encode_result(request, response, result, status = 200) + # Set the specified format as the content type of the response. + def set_content_type(response, format) + response[:content_type] = format + end + + def set_response(response, result, status = 200) response.status = status response.body = result end diff --git a/spec/unit/network/http/mongrel/rest.rb b/spec/unit/network/http/mongrel/rest.rb index 9f35c199c..5435b0372 100755 --- a/spec/unit/network/http/mongrel/rest.rb +++ b/spec/unit/network/http/mongrel/rest.rb @@ -3,377 +3,155 @@ require File.dirname(__FILE__) + '/../../../../spec_helper' require 'puppet/network/http' -describe "Puppet::Network::HTTP::MongrelREST", "when initializing" do +describe "Puppet::Network::HTTP::MongrelREST" do confine "Mongrel is not available" => Puppet.features.mongrel? - before do - require 'puppet/network/http/mongrel/rest' - - @mock_mongrel = mock('Mongrel server') - @mock_mongrel.stubs(:register) - @mock_model = mock('indirected model') - Puppet::Indirector::Indirection.stubs(:model).with(:foo).returns(@mock_model) - @params = { :server => @mock_mongrel, :handler => :foo } - end - - it "should require access to a Mongrel server" do - Proc.new { Puppet::Network::HTTP::MongrelREST.new(@params.delete_if {|k,v| :server == k })}.should raise_error(ArgumentError) - end - - it "should require an indirection name" do - Proc.new { Puppet::Network::HTTP::MongrelREST.new(@params.delete_if {|k,v| :handler == k })}.should raise_error(ArgumentError) - end - - it "should look up the indirection model from the indirection name" do - Puppet::Indirector::Indirection.expects(:model).with(:foo).returns(@mock_model) - Puppet::Network::HTTP::MongrelREST.new(@params) - end - - it "should fail if the indirection is not known" do - Puppet::Indirector::Indirection.expects(:model).with(:foo).returns(nil) - Proc.new { Puppet::Network::HTTP::MongrelREST.new(@params) }.should raise_error(ArgumentError) - end -end - -describe "Puppet::Network::HTTP::MongrelREST", "when receiving a request" do - confine "Mongrel is not available" => Puppet.features.mongrel? - - before do - @mock_request = stub('mongrel http request') - @mock_head = stub('response head') - @mock_body = stub('response body', :write => true) - @mock_response = stub('mongrel http response') - @mock_response.stubs(:start).yields(@mock_head, @mock_body) - @mock_model_class = stub('indirected model class') - @mock_mongrel = stub('mongrel http server', :register => true) - Puppet::Indirector::Indirection.stubs(:model).with(:foo).returns(@mock_model_class) - @handler = Puppet::Network::HTTP::MongrelREST.new(:server => @mock_mongrel, :handler => :foo) - end - - def setup_find_request(params = {}) - @mock_request.stubs(:params).returns({ Mongrel::Const::REQUEST_METHOD => 'GET', - Mongrel::Const::REQUEST_PATH => '/foo/key', - 'QUERY_STRING' => ''}.merge(params)) - @mock_model_class.stubs(:find) - end - - def setup_search_request(params = {}) - @mock_request.stubs(:params).returns({ Mongrel::Const::REQUEST_METHOD => 'GET', - Mongrel::Const::REQUEST_PATH => '/foos', - 'QUERY_STRING' => '' }.merge(params)) - @mock_model_class.stubs(:search).returns([]) - end - - def setup_destroy_request(params = {}) - @mock_request.stubs(:params).returns({ Mongrel::Const::REQUEST_METHOD => 'DELETE', - Mongrel::Const::REQUEST_PATH => '/foo/key', - 'QUERY_STRING' => '' }.merge(params)) - @mock_model_class.stubs(:destroy) - end - - def setup_save_request(params = {}) - @mock_request.stubs(:params).returns({ Mongrel::Const::REQUEST_METHOD => 'PUT', - Mongrel::Const::REQUEST_PATH => '/foo', - 'QUERY_STRING' => '' }.merge(params)) - @mock_request.stubs(:body).returns('this is a fake request body') - @mock_model_instance = stub('indirected model instance', :save => true) - @mock_model_class.stubs(:from_yaml).returns(@mock_model_instance) - end - - def setup_bad_request - @mock_request.stubs(:params).returns({ Mongrel::Const::REQUEST_METHOD => 'POST', Mongrel::Const::REQUEST_PATH => '/foos'}) - end - - it "should call the model find method if the request represents a singular HTTP GET" do - setup_find_request - @mock_model_class.expects(:find).with { |key, args| key == 'key' } - @handler.process(@mock_request, @mock_response) - end - - it "should call the model search method if the request represents a plural HTTP GET" do - setup_search_request - @mock_model_class.expects(:search).returns([]) - @handler.process(@mock_request, @mock_response) - end - - it "should call the model destroy method if the request represents an HTTP DELETE" do - setup_destroy_request - @mock_model_class.expects(:destroy).with { |key, args| key == 'key' } - @handler.process(@mock_request, @mock_response) - end - - it "should call the model save method if the request represents an HTTP PUT" do - setup_save_request - @mock_model_instance.expects(:save) - @handler.process(@mock_request, @mock_response) + it "should include the Puppet::Network::HTTP::Handler module" do + Puppet::Network::HTTP::MongrelREST.ancestors.should be_include(Puppet::Network::HTTP::Handler) end - it "should fail if the HTTP method isn't supported" do - @mock_request.stubs(:params).returns({ Mongrel::Const::REQUEST_METHOD => 'POST', Mongrel::Const::REQUEST_PATH => '/foo'}) - @mock_response.expects(:start).with(400) - @handler.process(@mock_request, @mock_response) - end - - it "should fail if the request's pluralization is wrong" do - @mock_request.stubs(:params).returns({ Mongrel::Const::REQUEST_METHOD => 'DELETE', Mongrel::Const::REQUEST_PATH => '/foos/key'}) - @mock_response.expects(:start).with(400) - @handler.process(@mock_request, @mock_response) - - @mock_request.stubs(:params).returns({ Mongrel::Const::REQUEST_METHOD => 'PUT', Mongrel::Const::REQUEST_PATH => '/foos/key'}) - @mock_response.expects(:start).with(400) - @handler.process(@mock_request, @mock_response) - end - - it "should fail if the request is for an unknown path" do - @mock_request.stubs(:params).returns({ Mongrel::Const::REQUEST_METHOD => 'GET', - Mongrel::Const::REQUEST_PATH => '/bar/key', - 'QUERY_STRING' => '' }) - @mock_response.expects(:start).with(400) - @handler.process(@mock_request, @mock_response) - end - - it "should serialize a controller exception if the request fails" do - @mock_request.stubs(:params).raises(ArgumentError, "This is a failure") - body = mock 'body' - @mock_response.expects(:start).with(400).yields("head", body) - body.expects(:write).with("This is a failure") - @handler.process(@mock_request, @mock_response) + describe "when initializing" do + it "should call the Handler's initialization hook with its provided arguments as the server and handler" do + Puppet::Network::HTTP::MongrelREST.any_instance.expects(:initialize_for_puppet).with(:server => "my", :handler => "arguments") + Puppet::Network::HTTP::MongrelREST.new(:server => "my", :handler => "arguments") + end end - describe "and determining the request parameters", :shared => true do - confine "Mongrel is not available" => Puppet.features.mongrel? - + describe "when receiving a request" do before do - @mock_request.stubs(:params).returns({}) - end - - it "should include the HTTP request parameters" do - @mock_request.expects(:params).returns('QUERY_STRING' => 'foo=baz&bar=xyzzy') - result = @handler.params(@mock_request) - result["foo"].should == "baz" - result["bar"].should == "xyzzy" - end - - it "should pass the client's ip address to model find" do - @mock_request.stubs(:params).returns("REMOTE_ADDR" => "ipaddress") - @handler.params(@mock_request)[:ip].should == "ipaddress" - end - - it "should use the :ssl_client_header to determine the parameter when looking for the certificate" do - Puppet.settings.stubs(:value).returns "eh" - Puppet.settings.expects(:value).with(:ssl_client_header).returns "myheader" - @mock_request.stubs(:params).returns("myheader" => "/CN=host.domain.com") - @handler.params(@mock_request) - end - - it "should retrieve the hostname by matching the certificate parameter" do - Puppet.settings.stubs(:value).returns "eh" - Puppet.settings.expects(:value).with(:ssl_client_header).returns "myheader" - @mock_request.stubs(:params).returns("myheader" => "/CN=host.domain.com") - @handler.params(@mock_request)[:node].should == "host.domain.com" - end - - it "should use the :ssl_client_header to determine the parameter for checking whether the host certificate is valid" do - Puppet.settings.stubs(:value).with(:ssl_client_header).returns "certheader" - Puppet.settings.expects(:value).with(:ssl_client_verify_header).returns "myheader" - @mock_request.stubs(:params).returns("myheader" => "SUCCESS", "certheader" => "/CN=host.domain.com") - @handler.params(@mock_request) - end + @params = {} + @request = stub('mongrel http request', :params => @params) + + @head = stub('response head') + @body = stub('response body', :write => true) + @response = stub('mongrel http response') + @response.stubs(:start).yields(@head, @body) + @model_class = stub('indirected model class') + @mongrel = stub('mongrel http server', :register => true) + Puppet::Indirector::Indirection.stubs(:model).with(:foo).returns(@model_class) + @handler = Puppet::Network::HTTP::MongrelREST.new(:server => @mongrel, :handler => :foo) + end + + describe "and using the HTTP Handler interface" do + it "should return the HTTP_ACCEPT parameter as the accept header" do + @params.expects(:[]).with("HTTP_ACCEPT").returns "myaccept" + @handler.accept_header(@request).should == "myaccept" + end - it "should consider the host authenticated if the validity parameter contains 'SUCCESS'" do - Puppet.settings.stubs(:value).with(:ssl_client_header).returns "certheader" - Puppet.settings.stubs(:value).with(:ssl_client_verify_header).returns "myheader" - @mock_request.stubs(:params).returns("myheader" => "SUCCESS", "certheader" => "/CN=host.domain.com") - @handler.params(@mock_request)[:authenticated].should be_true - end + it "should use the REQUEST_METHOD as the http method" do + @params.expects(:[]).with(Mongrel::Const::REQUEST_METHOD).returns "mymethod" + @handler.http_method(@request).should == "mymethod" + end - it "should consider the host unauthenticated if the validity parameter does not contain 'SUCCESS'" do - Puppet.settings.stubs(:value).with(:ssl_client_header).returns "certheader" - Puppet.settings.stubs(:value).with(:ssl_client_verify_header).returns "myheader" - @mock_request.stubs(:params).returns("myheader" => "whatever", "certheader" => "/CN=host.domain.com") - @handler.params(@mock_request)[:authenticated].should be_false - end + it "should use the first part of the request path as the path" do + @params.expects(:[]).with(Mongrel::Const::REQUEST_PATH).returns "/foo/bar" + @handler.path(@request).should == "/foo" + end - it "should consider the host unauthenticated if no certificate information is present" do - Puppet.settings.stubs(:value).with(:ssl_client_header).returns "certheader" - Puppet.settings.stubs(:value).with(:ssl_client_verify_header).returns "myheader" - @mock_request.stubs(:params).returns("myheader" => nil, "certheader" => "SUCCESS") - @handler.params(@mock_request)[:authenticated].should be_false - end + it "should use the second part of the request path as the request key" do + @params.expects(:[]).with(Mongrel::Const::REQUEST_PATH).returns "/foo/bar" + @handler.request_key(@request).should == "bar" + end - it "should not pass a node name to model method if no certificate information is present" do - Puppet.settings.stubs(:value).returns "eh" - Puppet.settings.expects(:value).with(:ssl_client_header).returns "myheader" - @mock_request.stubs(:params).returns("myheader" => nil) - @handler.params(@mock_request).should_not be_include(:node) - end - end + it "should return the request body as the body" do + @request.expects(:body).returns "mybody" + @handler.body(@request).should == "mybody" + end - describe "when finding a model instance" do |variable| - confine "Mongrel is not available" => Puppet.features.mongrel? + it "should set the response's content-type header when setting the content type" do + @header = mock 'header' + @response.expects(:header).returns @header + @header.expects(:[]=).with('Content-Type', "mytype") - it "should fail to find model if key is not specified" do - @mock_request.stubs(:params).returns({ Mongrel::Const::REQUEST_METHOD => 'GET', Mongrel::Const::REQUEST_PATH => '/foo'}) - @mock_response.expects(:start).with(400) - @handler.process(@mock_request, @mock_response) - end - - it "should use a common method for determining the request parameters" do - setup_find_request('QUERY_STRING' => 'foo=baz&bar=xyzzy') - @handler.expects(:params).returns(:foo => :baz, :bar => :xyzzy) - @mock_model_class.expects(:find).with do |key, args| - args[:foo] == :baz and args[:bar] == :xyzzy + @handler.set_content_type(@response, "mytype") end - @handler.process(@mock_request, @mock_response) - end - it "should generate a 200 response when a model find call succeeds" do - setup_find_request - @mock_response.expects(:start).with(200) - @handler.process(@mock_request, @mock_response) - end + it "should set the status and write the body when setting the response for a successful request" do + head = mock 'head' + body = mock 'body' + @response.expects(:start).with(200).yields(head, body) - it "should return a serialized object when a model find call succeeds" do - setup_find_request - @mock_model_instance = stub('model instance') - @mock_model_instance.expects(:to_yaml) - @mock_model_class.stubs(:find).returns(@mock_model_instance) - @handler.process(@mock_request, @mock_response) - end + body.expects(:write).with("mybody") - it "should serialize a controller exception when an exception is thrown by find" do - setup_find_request - @mock_model_class.expects(:find).raises(ArgumentError) - @mock_response.expects(:start).with(400) - @handler.process(@mock_request, @mock_response) - end - end + @handler.set_response(@response, "mybody", 200) + end - describe "when destroying a model instance" do |variable| - confine "Mongrel is not available" => Puppet.features.mongrel? + it "should set the status and reason and write the body when setting the response for a successful request" do + head = mock 'head' + body = mock 'body' + @response.expects(:start).with(400, false, "mybody").yields(head, body) - it "should fail to destroy model if key is not specified" do - @mock_request.stubs(:params).returns({ Mongrel::Const::REQUEST_METHOD => 'DELETE', Mongrel::Const::REQUEST_PATH => '/foo'}) - @mock_response.expects(:start).with(400) - @handler.process(@mock_request, @mock_response) - end + body.expects(:write).with("mybody") - it "should use a common method for determining the request parameters" do - setup_destroy_request('QUERY_STRING' => 'foo=baz&bar=xyzzy') - @handler.expects(:params).returns(:foo => :baz, :bar => :xyzzy) - @mock_model_class.expects(:destroy).with do |key, args| - args[:foo] == :baz and args[:bar] == :xyzzy + @handler.set_response(@response, "mybody", 400) end - @handler.process(@mock_request, @mock_response) end - it "should pass HTTP request parameters to model destroy" do - setup_destroy_request('QUERY_STRING' => 'foo=baz&bar=xyzzy') - @mock_model_class.expects(:destroy).with do |key, args| - key == 'key' and args['foo'] == 'baz' and args['bar'] == 'xyzzy' + describe "and determining the request parameters", :shared => true do + before do + @request.stubs(:params).returns({}) end - @handler.process(@mock_request, @mock_response) - end - it "should generate a 200 response when a model destroy call succeeds" do - setup_destroy_request - @mock_response.expects(:start).with(200) - @handler.process(@mock_request, @mock_response) - end - - it "should return a serialized success result when a model destroy call succeeds" do - setup_destroy_request - @mock_model_class.stubs(:destroy).returns(true) - @mock_body.expects(:write).with("--- true\n") - @handler.process(@mock_request, @mock_response) - end - - it "should serialize a controller exception when an exception is thrown by destroy" do - setup_destroy_request - @mock_model_class.expects(:destroy).raises(ArgumentError) - @mock_response.expects(:start).with(400) - @handler.process(@mock_request, @mock_response) - end - end - - describe "when saving a model instance" do |variable| - confine "Mongrel is not available" => Puppet.features.mongrel? - - it "should fail to save model if data is not specified" do - @mock_request.stubs(:params).returns({ Mongrel::Const::REQUEST_METHOD => 'PUT', Mongrel::Const::REQUEST_PATH => '/foo'}) - @mock_request.stubs(:body).returns('') - @mock_response.expects(:start).with(400) - @handler.process(@mock_request, @mock_response) - end - - it "should use a common method for determining the request parameters" do - setup_save_request('QUERY_STRING' => 'foo=baz&bar=xyzzy') - @handler.expects(:params).returns(:foo => :baz, :bar => :xyzzy) - @mock_model_instance.expects(:save).with do |args| - args[:foo] == :baz and args[:bar] == :xyzzy + it "should include the HTTP request parameters" do + @request.expects(:params).returns('QUERY_STRING' => 'foo=baz&bar=xyzzy') + result = @handler.params(@request) + result["foo"].should == "baz" + result["bar"].should == "xyzzy" end - @handler.process(@mock_request, @mock_response) - end - - it "should generate a 200 response when a model save call succeeds" do - setup_save_request - @mock_response.expects(:start).with(200) - @handler.process(@mock_request, @mock_response) - end - it "should return a serialized object when a model save call succeeds" do - setup_save_request - @mock_model_instance.stubs(:save).returns(@mock_model_instance) - @mock_model_instance.expects(:to_yaml).returns('foo') - @handler.process(@mock_request, @mock_response) - end + it "should pass the client's ip address to model find" do + @request.stubs(:params).returns("REMOTE_ADDR" => "ipaddress") + @handler.params(@request)[:ip].should == "ipaddress" + end - it "should serialize a controller exception when an exception is thrown by save" do - setup_save_request - @mock_model_instance.expects(:save).raises(ArgumentError) - @mock_response.expects(:start).with(400) - @handler.process(@mock_request, @mock_response) - end - end + it "should use the :ssl_client_header to determine the parameter when looking for the certificate" do + Puppet.settings.stubs(:value).returns "eh" + Puppet.settings.expects(:value).with(:ssl_client_header).returns "myheader" + @request.stubs(:params).returns("myheader" => "/CN=host.domain.com") + @handler.params(@request) + end - describe "when searching for model instances" do |variable| - confine "Mongrel is not available" => Puppet.features.mongrel? + it "should retrieve the hostname by matching the certificate parameter" do + Puppet.settings.stubs(:value).returns "eh" + Puppet.settings.expects(:value).with(:ssl_client_header).returns "myheader" + @request.stubs(:params).returns("myheader" => "/CN=host.domain.com") + @handler.params(@request)[:node].should == "host.domain.com" + end - it "should use a common method for determining the request parameters" do - setup_search_request('QUERY_STRING' => 'foo=baz&bar=xyzzy') - @handler.expects(:params).returns(:foo => :baz, :bar => :xyzzy) - @mock_model_class.expects(:search).with do |args| - args[:foo] == :baz and args[:bar] == :xyzzy + it "should use the :ssl_client_header to determine the parameter for checking whether the host certificate is valid" do + Puppet.settings.stubs(:value).with(:ssl_client_header).returns "certheader" + Puppet.settings.expects(:value).with(:ssl_client_verify_header).returns "myheader" + @request.stubs(:params).returns("myheader" => "SUCCESS", "certheader" => "/CN=host.domain.com") + @handler.params(@request) end - @handler.process(@mock_request, @mock_response) - end - it "should pass HTTP request parameters to model search" do - setup_search_request('QUERY_STRING' => 'foo=baz&bar=xyzzy') - @mock_model_class.expects(:search).with do |args| - args['foo'] == 'baz' and args['bar'] == 'xyzzy' - end.returns([]) - @handler.process(@mock_request, @mock_response) - end + it "should consider the host authenticated if the validity parameter contains 'SUCCESS'" do + Puppet.settings.stubs(:value).with(:ssl_client_header).returns "certheader" + Puppet.settings.stubs(:value).with(:ssl_client_verify_header).returns "myheader" + @request.stubs(:params).returns("myheader" => "SUCCESS", "certheader" => "/CN=host.domain.com") + @handler.params(@request)[:authenticated].should be_true + end - it "should generate a 200 response when a model search call succeeds" do - setup_search_request - @mock_response.expects(:start).with(200) - @handler.process(@mock_request, @mock_response) - end + it "should consider the host unauthenticated if the validity parameter does not contain 'SUCCESS'" do + Puppet.settings.stubs(:value).with(:ssl_client_header).returns "certheader" + Puppet.settings.stubs(:value).with(:ssl_client_verify_header).returns "myheader" + @request.stubs(:params).returns("myheader" => "whatever", "certheader" => "/CN=host.domain.com") + @handler.params(@request)[:authenticated].should be_false + end - it "should return a list of serialized objects when a model search call succeeds" do - setup_search_request - mock_matches = [1..5].collect {|i| mock("model instance #{i}", :to_yaml => "model instance #{i}") } - @mock_model_class.stubs(:search).returns(mock_matches) - @handler.process(@mock_request, @mock_response) - end + it "should consider the host unauthenticated if no certificate information is present" do + Puppet.settings.stubs(:value).with(:ssl_client_header).returns "certheader" + Puppet.settings.stubs(:value).with(:ssl_client_verify_header).returns "myheader" + @request.stubs(:params).returns("myheader" => nil, "certheader" => "SUCCESS") + @handler.params(@request)[:authenticated].should be_false + end - it "should serialize a controller exception when an exception is thrown by search" do - setup_search_request - @mock_model_class.expects(:search).raises(ArgumentError) - @mock_response.expects(:start).with(400) - @handler.process(@mock_request, @mock_response) + it "should not pass a node name to model method if no certificate information is present" do + Puppet.settings.stubs(:value).returns "eh" + Puppet.settings.expects(:value).with(:ssl_client_header).returns "myheader" + @request.stubs(:params).returns("myheader" => nil) + @handler.params(@request).should_not be_include(:node) + end end - end + end end diff --git a/spec/unit/network/http/webrick/rest.rb b/spec/unit/network/http/webrick/rest.rb index 782f99045..b42053d53 100755 --- a/spec/unit/network/http/webrick/rest.rb +++ b/spec/unit/network/http/webrick/rest.rb @@ -4,322 +4,106 @@ require File.dirname(__FILE__) + '/../../../../spec_helper' require 'puppet/network/http' require 'puppet/network/http/webrick/rest' -describe Puppet::Network::HTTP::WEBrickREST, "when initializing" do - before do - @mock_webrick = stub('WEBrick server', :mount => true, :[] => {}) - @mock_model = mock('indirected model') - Puppet::Indirector::Indirection.stubs(:model).returns(@mock_model) - @params = [ @mock_webrick, :foo ] +describe Puppet::Network::HTTP::WEBrickREST do + it "should include the Puppet::Network::HTTP::Handler module" do + Puppet::Network::HTTP::WEBrickREST.ancestors.should be_include(Puppet::Network::HTTP::Handler) end - it "should require access to a WEBrick server" do - Proc.new { - @params[0] = nil - Puppet::Network::HTTP::WEBrickREST.new(*@params) - }.should raise_error(ArgumentError) - end - - it "should require an indirection name" do - Proc.new { Puppet::Network::HTTP::WEBrickREST.new(@params.first) }.should raise_error(ArgumentError) - end - - it "should look up the indirection model from the indirection name" do - Puppet::Indirector::Indirection.expects(:model).returns(@mock_model) - Puppet::Network::HTTP::WEBrickREST.new(*@params) - end - - it "should fail if the indirection is not known" do - Puppet::Indirector::Indirection.expects(:model).returns(nil) - Proc.new { Puppet::Network::HTTP::WEBrickREST.new(*@params) }.should raise_error(ArgumentError) - end -end - -describe Puppet::Network::HTTP::WEBrickREST, "when receiving a request" do - before do - @mock_request = stub('webrick http request', :query => {}, :peeraddr => %w{eh boo host ip}, :client_cert => nil) - @mock_response = stub('webrick http response', :status= => true, :body= => true) - @mock_model_class = stub('indirected model class') - @mock_webrick = stub('webrick http server', :mount => true, :[] => {}) - Puppet::Indirector::Indirection.stubs(:model).with(:foo).returns(@mock_model_class) - @handler = Puppet::Network::HTTP::WEBrickREST.new(@mock_webrick, :foo) - end - - def setup_find_request - @mock_request.stubs(:request_method).returns('GET') - @mock_request.stubs(:path).returns('/foo/key') - @mock_model_class.stubs(:find) - end - - def setup_search_request - @mock_request.stubs(:request_method).returns('GET') - @mock_request.stubs(:path).returns('/foos') - @mock_model_class.stubs(:search).returns([]) - end - - def setup_destroy_request - @mock_request.stubs(:request_method).returns('DELETE') - @mock_request.stubs(:path).returns('/foo/key') - @mock_model_class.stubs(:destroy) - end - - def setup_save_request - @mock_request.stubs(:request_method).returns('PUT') - @mock_request.stubs(:path).returns('/foo') - @mock_request.stubs(:body).returns('This is a fake request body') - @mock_model_instance = stub('indirected model instance', :save => true) - @mock_model_class.stubs(:from_yaml).returns(@mock_model_instance) - end - - def setup_bad_request - @mock_request.stubs(:request_method).returns('POST') - @mock_request.stubs(:path).returns('/foos') - end - - it "should delegate its :service method to its :process method" do - @handler.expects(:process).with(@mock_request, @mock_response).returns "stuff" - @handler.service(@mock_request, @mock_response).should == "stuff" - end - - it "should call the model find method if the request represents a singular HTTP GET" do - setup_find_request - @mock_model_class.expects(:find).with { |key, args| key == 'key' } - @handler.process(@mock_request, @mock_response) - end - - it "should call the model search method if the request represents a plural HTTP GET" do - setup_search_request - @mock_model_class.expects(:search).returns([]) - @handler.process(@mock_request, @mock_response) - end - - it "should call the model destroy method if the request represents an HTTP DELETE" do - setup_destroy_request - @mock_model_class.expects(:destroy).with { |key, args| key == 'key' } - @handler.process(@mock_request, @mock_response) - end - - it "should call the model save method if the request represents an HTTP PUT" do - setup_save_request - @mock_model_instance.expects(:save) - @handler.process(@mock_request, @mock_response) - end - - it "should fail if the HTTP method isn't supported" do - @mock_request.stubs(:request_method).returns('POST') - @mock_request.stubs(:path).returns('/foo') - @mock_response.expects(:status=).with(400) - @handler.process(@mock_request, @mock_response) - end - - it "should fail if delete request's pluralization is wrong" do - @mock_request.stubs(:request_method).returns('DELETE') - @mock_request.stubs(:path).returns('/foos/key') - @mock_response.expects(:status=).with(400) - @handler.process(@mock_request, @mock_response) - end - - it "should fail if put request's pluralization is wrong" do - @mock_request.stubs(:request_method).returns('PUT') - @mock_request.stubs(:path).returns('/foos/key') - @mock_response.expects(:status=).with(400) - @handler.process(@mock_request, @mock_response) - end - - it "should fail if the request is for an unknown path" do - @mock_request.stubs(:request_method).returns('GET') - @mock_request.stubs(:path).returns('/bar/key') - @mock_response.expects(:status=).with(400) - @handler.process(@mock_request, @mock_response) - end - - it "should set the response status to 400 and the body to the exception message if the request fails" do - @mock_request.stubs(:request_method).raises(ArgumentError, "This is a failure") - @mock_request.stubs(:path).returns('/foos') - @mock_response.expects(:status=).with(400) - @mock_response.expects(:body=).with("This is a failure") - @handler.process(@mock_request, @mock_response) - end - - describe "and determining the request parameters" do - it "should include the HTTP request parameters" do - @mock_request.stubs(:query).returns(:foo => :baz, :bar => :xyzzy) - result = @handler.params(@mock_request) - result[:foo].should == :baz - result[:bar].should == :xyzzy - end - - it "should pass the client's ip address to model find" do - @mock_request.stubs(:peeraddr).returns(%w{noidea dunno hostname ipaddress}) - @handler.params(@mock_request)[:ip].should == "ipaddress" - end - - it "should set 'authenticated' to true if a certificate is present" do - cert = stub 'cert', :subject => [%w{CN host.domain.com}] - @mock_request.stubs(:client_cert).returns cert - @handler.params(@mock_request)[:authenticated].should be_true - end - - it "should set 'authenticated' to false if no certificate is present" do - @mock_request.stubs(:client_cert).returns nil - @handler.params(@mock_request)[:authenticated].should be_false - end - - it "should pass the client's certificate name to model method if a certificate is present" do - cert = stub 'cert', :subject => [%w{CN host.domain.com}] - @mock_request.stubs(:client_cert).returns cert - @handler.params(@mock_request)[:node].should == "host.domain.com" - end - - it "should not pass a node name to model method if no certificate is present" do - @mock_request.stubs(:client_cert).returns nil - @handler.params(@mock_request).should_not be_include(:node) + describe "when initializing" do + it "should call the Handler's initialization hook with its provided arguments as the server and handler" do + Puppet::Network::HTTP::WEBrickREST.any_instance.expects(:initialize_for_puppet).with(:server => "my", :handler => "arguments") + Puppet::Network::HTTP::WEBrickREST.new("my", "arguments") end end - describe "when finding a model instance" do |variable| - it "should fail to find model if key is not specified" do - @mock_request.stubs(:request_method).returns('GET') - @mock_request.stubs(:path).returns('/foo') - @mock_response.expects(:status=).with(400) - @handler.process(@mock_request, @mock_response) + describe "when receiving a request" do + before do + @request = stub('webrick http request', :query => {}, :peeraddr => %w{eh boo host ip}, :client_cert => nil) + @response = stub('webrick http response', :status= => true, :body= => true) + @model_class = stub('indirected model class') + @webrick = stub('webrick http server', :mount => true, :[] => {}) + Puppet::Indirector::Indirection.stubs(:model).with(:foo).returns(@model_class) + @handler = Puppet::Network::HTTP::WEBrickREST.new(@webrick, :foo) end - it "should use a common method for determining the request parameters" do - setup_find_request - @handler.stubs(:params).returns(:foo => :baz, :bar => :xyzzy) - @mock_model_class.expects(:find).with do |key, args| - args[:foo] == :baz and args[:bar] == :xyzzy - end - @handler.process(@mock_request, @mock_response) + it "should delegate its :service method to its :process method" do + @handler.expects(:process).with(@request, @response).returns "stuff" + @handler.service(@request, @response).should == "stuff" end - it "should generate a 200 response when a model find call succeeds" do - setup_find_request - @mock_response.expects(:status=).with(200) - @handler.process(@mock_request, @mock_response) - end - - it "should return a serialized object when a model find call succeeds" do - setup_find_request - @mock_model_instance = stub('model instance') - @mock_model_instance.expects(:to_yaml) - @mock_model_class.stubs(:find).returns(@mock_model_instance) - @handler.process(@mock_request, @mock_response) - end - - it "should serialize a controller exception when an exception is thrown by find" do - setup_find_request - @mock_model_class.expects(:find).raises(ArgumentError) - @mock_response.expects(:status=).with(400) - @handler.process(@mock_request, @mock_response) - end - end + describe "when using the Handler interface" do + it "should use the 'accept' request parameter as the Accept header" do + @request.expects(:[]).with(:accept).returns "foobar" + @handler.accept_header(@request).should == "foobar" + end - describe "when destroying a model instance" do |variable| - it "should fail to destroy model if key is not specified" do - @mock_request.stubs(:request_method).returns('DELETE') - @mock_request.stubs(:path).returns('/foo') - @mock_response.expects(:status=).with(400) - @handler.process(@mock_request, @mock_response) - end + it "should use the request method as the http method" do + @request.expects(:request_method).returns "FOO" + @handler.http_method(@request).should == "FOO" + end - it "should use a common method for determining the request parameters" do - setup_destroy_request - @handler.stubs(:params).returns(:foo => :baz, :bar => :xyzzy) - @mock_model_class.expects(:destroy).with do |key, args| - args[:foo] == :baz and args[:bar] == :xyzzy + it "should return the first argument of the request path as the path" do + @request.expects(:path).returns "/foo/bar" + @handler.path(@request).should == "/foo" end - @handler.process(@mock_request, @mock_response) - end - it "should generate a 200 response when a model destroy call succeeds" do - setup_destroy_request - @mock_response.expects(:status=).with(200) - @handler.process(@mock_request, @mock_response) - end + it "should return the second field in the path as the request key" do + @request.expects(:path).returns "/foo/bar" + @handler.request_key(@request).should == "bar" + end - it "should return a serialized success result when a model destroy call succeeds" do - setup_destroy_request - @mock_model_class.stubs(:destroy).returns(true) - @mock_response.expects(:body=).with("--- true\n") - @handler.process(@mock_request, @mock_response) - end + it "should return the request body as the body" do + @request.expects(:body).returns "my body" + @handler.body(@request).should == "my body" + end - it "should serialize a controller exception when an exception is thrown by destroy" do - setup_destroy_request - @mock_model_class.expects(:destroy).raises(ArgumentError) - @mock_response.expects(:status=).with(400) - @handler.process(@mock_request, @mock_response) - end - end + it "should set the response's :content_type header when setting the content type" do + @response.expects(:[]=).with(:content_type, "text/html") + @handler.set_content_type(@response, "text/html") + end - describe "when saving a model instance" do - it "should fail to save model if data is not specified" do - @mock_request.stubs(:request_method).returns('PUT') - @mock_request.stubs(:path).returns('/foo') - @mock_request.stubs(:body).returns('') - @mock_response.expects(:status=).with(400) - @handler.process(@mock_request, @mock_response) - end + it "should set the status and body on the response when setting the response" do + @response.expects(:status=).with 200 + @response.expects(:body=).with "mybody" - it "should use a common method for determining the request parameters" do - setup_save_request - @handler.stubs(:params).returns(:foo => :baz, :bar => :xyzzy) - @mock_model_instance.expects(:save).with do |args| - args[:foo] == :baz and args[:bar] == :xyzzy + @handler.set_response(@response, "mybody", 200) end - @handler.process(@mock_request, @mock_response) end - it "should generate a 200 response when a model save call succeeds" do - setup_save_request - @mock_response.expects(:status=).with(200) - @handler.process(@mock_request, @mock_response) - end - - it "should return a serialized object when a model save call succeeds" do - setup_save_request - @mock_model_instance.stubs(:save).returns(@mock_model_instance) - @mock_model_instance.expects(:to_yaml).returns('foo') - @handler.process(@mock_request, @mock_response) - end + describe "and determining the request parameters" do + it "should include the HTTP request parameters" do + @request.stubs(:query).returns(:foo => :baz, :bar => :xyzzy) + result = @handler.params(@request) + result[:foo].should == :baz + result[:bar].should == :xyzzy + end - it "should serialize a controller exception when an exception is thrown by save" do - setup_save_request - @mock_model_instance.expects(:save).raises(ArgumentError) - @mock_response.expects(:status=).with(400) - @handler.process(@mock_request, @mock_response) - end - end + it "should pass the client's ip address to model find" do + @request.stubs(:peeraddr).returns(%w{noidea dunno hostname ipaddress}) + @handler.params(@request)[:ip].should == "ipaddress" + end - describe "when searching for model instances" do - it "should use a common method for determining the request parameters" do - setup_search_request - @handler.stubs(:params).returns(:foo => :baz, :bar => :xyzzy) - @mock_model_class.expects(:search).with do |args| - args[:foo] == :baz and args[:bar] == :xyzzy + it "should set 'authenticated' to true if a certificate is present" do + cert = stub 'cert', :subject => [%w{CN host.domain.com}] + @request.stubs(:client_cert).returns cert + @handler.params(@request)[:authenticated].should be_true end - @handler.process(@mock_request, @mock_response) - end - it "should generate a 200 response when a model search call succeeds" do - setup_search_request - @mock_response.expects(:status=).with(200) - @handler.process(@mock_request, @mock_response) - end + it "should set 'authenticated' to false if no certificate is present" do + @request.stubs(:client_cert).returns nil + @handler.params(@request)[:authenticated].should be_false + end - it "should return a list of serialized objects when a model search call succeeds" do - setup_search_request - mock_matches = [1..5].collect {|i| mock("model instance #{i}", :to_yaml => "model instance #{i}") } - @mock_model_class.stubs(:search).returns(mock_matches) - @handler.process(@mock_request, @mock_response) - end + it "should pass the client's certificate name to model method if a certificate is present" do + cert = stub 'cert', :subject => [%w{CN host.domain.com}] + @request.stubs(:client_cert).returns cert + @handler.params(@request)[:node].should == "host.domain.com" + end - it "should serialize a controller exception when an exception is thrown by search" do - setup_search_request - @mock_model_class.expects(:search).raises(ArgumentError) - @mock_response.expects(:status=).with(400) - @handler.process(@mock_request, @mock_response) + it "should not pass a node name to model method if no certificate is present" do + @request.stubs(:client_cert).returns nil + @handler.params(@request).should_not be_include(:node) + end end end end |