diff options
-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 |