All requests should now have an ipaddress add to them, they
should always be marked authenticated or not, and they should
have the certificate name set as their 'node' if a certificate
is present.
They both use the same methods they use for xmlrpc,
although there's no common code, to facilitate deprecation
of xmlrpc.
Signed-off-by: Luke Kanies <[EMAIL PROTECTED]>
---
lib/puppet/network/http/handler.rb | 56 +++++------
lib/puppet/network/http/mongrel/rest.rb | 49 +++++++---
lib/puppet/network/http/webrick/rest.rb | 51 +++++++---
spec/unit/network/http/mongrel/rest.rb | 164 ++++++++++++++++++++++++-------
spec/unit/network/http/webrick/rest.rb | 165 ++++++++++++++++++++-----------
5 files changed, 332 insertions(+), 153 deletions(-)
diff --git a/lib/puppet/network/http/handler.rb
b/lib/puppet/network/http/handler.rb
index 7113c92..8c6abde 100644
--- a/lib/puppet/network/http/handler.rb
+++ b/lib/puppet/network/http/handler.rb
@@ -1,30 +1,30 @@
module Puppet::Network::HTTP::Handler
-
+
def initialize_for_puppet(args = {})
raise ArgumentError unless @server = args[:server]
raise ArgumentError unless @handler = args[:handler]
@model = find_model_for_handler(@handler)
end
-
+
# handle an HTTP request
def process(request, response)
return do_find(request, response) if get?(request) and
singular?(request)
return do_search(request, response) if get?(request) and
plural?(request)
return do_destroy(request, response) if delete?(request) and
singular?(request)
- return do_save(request, response) if put?(request) and
singular?(request)
+ return do_save(request, response) if put?(request) and
singular?(request)
raise ArgumentError, "Did not understand HTTP #{http_method(request)}
request for '#{path(request)}'"
rescue Exception => e
return do_exception(request, response, e)
end
-
+
private
def model
- @model
+ @model
end
-
+
def do_find(request, response)
- key = request_key(request) || raise(ArgumentError, "Could not locate
lookup key in request path [#{path}]")
+ key = request_key(request) || raise(ArgumentError, "Could not locate
lookup key in request path [#{path(request)}]")
args = params(request)
result = model.find(key, args).to_yaml
encode_result(request, response, result)
@@ -37,7 +37,7 @@ module Puppet::Network::HTTP::Handler
end
def do_destroy(request, response)
- key = request_key(request) || raise(ArgumentError, "Could not locate
lookup key in request path [#{path}]")
+ key = request_key(request) || raise(ArgumentError, "Could not locate
lookup key in request path [#{path(request)}]")
args = params(request)
result = model.destroy(key, args)
encode_result(request, response, YAML.dump(result))
@@ -46,71 +46,67 @@ module Puppet::Network::HTTP::Handler
def do_save(request, response)
data = body(request).to_s
raise ArgumentError, "No data to save" if !data or data.empty?
- # args = params(request)
+ args = params(request)
obj = model.from_yaml(data)
- result = save_object(obj).to_yaml
+ result = obj.save(args).to_yaml
encode_result(request, response, result)
end
-
- def save_object(obj)
- obj.save
- end
-
+
def do_exception(request, response, exception, status=404)
encode_result(request, response, exception.to_yaml, status)
end
-
+
def find_model_for_handler(handler)
Puppet::Indirector::Indirection.model(handler) ||
raise(ArgumentError, "Cannot locate indirection [#{handler}].")
end
-
+
def get?(request)
http_method(request) == 'GET'
end
-
+
def put?(request)
http_method(request) == 'PUT'
end
-
+
def delete?(request)
http_method(request) == 'DELETE'
end
-
+
def singular?(request)
%r{/[EMAIL PROTECTED](path(request))
end
-
+
def plural?(request)
%r{/[EMAIL PROTECTED](path(request))
end
-
- # methods to be overridden by the including web server class
-
+
+ # methods to be overridden by the including web server class
+
def register_handler
raise NotImplementedError
end
-
+
def http_method(request)
raise NotImplementedError
end
-
+
def path(request)
raise NotImplementedError
end
-
+
def request_key(request)
raise NotImplementedError
end
-
+
def body(request)
raise NotImplementedError
end
-
+
def params(request)
raise NotImplementedError
end
-
+
def encode_result(request, response, result, status = 200)
raise NotImplementedError
end
diff --git a/lib/puppet/network/http/mongrel/rest.rb
b/lib/puppet/network/http/mongrel/rest.rb
index 2a3d4f1..d1257d5 100644
--- a/lib/puppet/network/http/mongrel/rest.rb
+++ b/lib/puppet/network/http/mongrel/rest.rb
@@ -3,45 +3,64 @@ require 'puppet/network/http/handler'
class Puppet::Network::HTTP::MongrelREST < Mongrel::HttpHandler
include Puppet::Network::HTTP::Handler
-
- def initialize(args={})
- super()
- initialize_for_puppet(args)
- end
+
+ 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))
+ end
private
-
+
# which HTTP verb was used in this request
def http_method(request)
request.params[Mongrel::Const::REQUEST_METHOD]
end
-
+
# what path was requested?
def path(request)
# LAK:NOTE See http://snurl.com/21zf8 [groups_google_com]
x = '/' + request.params[Mongrel::Const::REQUEST_PATH].split('/')[1]
end
-
+
# return the key included in the request path
def request_key(request)
# LAK:NOTE See http://snurl.com/21zf8 [groups_google_com]
x = request.params[Mongrel::Const::REQUEST_PATH].split('/')[2]
end
-
+
# return the request body
def body(request)
request.body
end
-
- # return the query params for this request
- def params(request)
- Mongrel::HttpRequest.query_parse(request.params["QUERY_STRING"])
- end
-
+
# produce the body of the response
def encode_result(request, response, result, status = 200)
response.start(status) do |head, body|
body.write(result)
end
end
+
+ def client_info(request)
+ result = {}
+ params = request.params
+ result[:ip] = params["REMOTE_ADDR"]
+
+ # JJM #906 The following dn.match regular expression is forgiving
+ # enough to match the two Distinguished Name string contents
+ # coming from Apache, Pound or other reverse SSL proxies.
+ if dn = params[Puppet[:ssl_client_header]] and dn_matchdata =
dn.match(/^.*?CN\s*=\s*(.*)/)
+ result[:node] = dn_matchdata[1].to_str
+ result[:authenticated] =
(params[Puppet[:ssl_client_verify_header]] == 'SUCCESS')
+ else
+ result[:authenticated] = false
+ end
+
+ return result
+ end
end
diff --git a/lib/puppet/network/http/webrick/rest.rb
b/lib/puppet/network/http/webrick/rest.rb
index b439121..a235fb4 100644
--- a/lib/puppet/network/http/webrick/rest.rb
+++ b/lib/puppet/network/http/webrick/rest.rb
@@ -1,46 +1,67 @@
require 'puppet/network/http/handler'
class Puppet::Network::HTTP::WEBrickREST <
WEBrick::HTTPServlet::AbstractServlet
-
+
include Puppet::Network::HTTP::Handler
-
+
def initialize(server, handler)
- raise ArgumentError, "server is required" unless server
- super(server)
- initialize_for_puppet(:server => server, :handler => handler)
+ raise ArgumentError, "server is required" unless server
+ super(server)
+ initialize_for_puppet(:server => server, :handler => handler)
+ end
+
+ # We had to expose this method for testing purposes.
+ def params(request)
+ result = request.query
+ result.merge(client_information(request))
end
# WEBrick uses a service() method to respond to requests. Simply delegate
to the handler response() method.
def service(request, response)
process(request, response)
end
-
+
private
-
+
def http_method(request)
request.request_method
end
-
+
def path(request)
# LAK:NOTE See http://snurl.com/21zf8 [groups_google_com]
x = '/' + request.path.split('/')[1]
end
-
+
def request_key(request)
# LAK:NOTE See http://snurl.com/21zf8 [groups_google_com]
x = request.path.split('/')[2]
end
-
+
def body(request)
request.body
end
-
- def params(request)
- request.query
- end
-
+
def encode_result(request, response, result, status = 200)
response.status = status
response.body = result
end
+
+ # Retrieve node/cert/ip information from the request object.
+ def client_information(request)
+ result = {}
+ if peer = request.peeraddr and ip = peer[3]
+ result[:ip] = ip
+ end
+
+ # If they have a certificate (which will almost always be true)
+ # then we get the hostname from the cert, instead of via IP
+ # info
+ result[:authenticated] = false
+ if cert = request.client_cert and nameary = cert.subject.to_a.find {
|ary| ary[0] == "CN" }
+ result[:node] = nameary[1]
+ result[:authenticated] = true
+ end
+
+ result
+ end
end
diff --git a/spec/unit/network/http/mongrel/rest.rb
b/spec/unit/network/http/mongrel/rest.rb
index b483bbd..1dd7de4 100644
--- a/spec/unit/network/http/mongrel/rest.rb
+++ b/spec/unit/network/http/mongrel/rest.rb
@@ -3,7 +3,7 @@ require 'puppet/network/http'
describe Puppet::Network::HTTP::MongrelREST, "when initializing" do
confine "Mongrel is not available" => Puppet.features.mongrel?
-
+
before do
@mock_mongrel = mock('Mongrel server')
@mock_mongrel.stubs(:register)
@@ -11,20 +11,20 @@ describe Puppet::Network::HTTP::MongrelREST, "when
initializing" do
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)
@@ -33,7 +33,7 @@ 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')
@@ -45,28 +45,28 @@ describe Puppet::Network::HTTP::MongrelREST, "when
receiving a request" do
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',
@@ -75,26 +75,26 @@ describe Puppet::Network::HTTP::MongrelREST, "when
receiving a request" do
@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', {})
+ @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).with({}).returns([])
+ @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', {})
+ @mock_model_class.expects(:destroy).with { |key, args| key == 'key' }
@handler.process(@mock_request, @mock_response)
end
@@ -103,13 +103,13 @@ describe Puppet::Network::HTTP::MongrelREST, "when
receiving a request" do
@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(:params).returns({ Mongrel::Const::REQUEST_METHOD
=> 'POST', Mongrel::Const::REQUEST_PATH => '/foo'})
@mock_response.expects(:start).with(404)
@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(404)
@@ -127,8 +127,74 @@ describe Puppet::Network::HTTP::MongrelREST, "when
receiving a request" do
@mock_response.expects(:start).with(404)
@handler.process(@mock_request, @mock_response)
end
-
-
+
+ describe "and determining the request parameters", :shared => true 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
+
+ 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 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 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 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
+
describe "when finding a model instance" do |variable|
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'})
@@ -136,20 +202,21 @@ describe Puppet::Network::HTTP::MongrelREST, "when
receiving a request" do
@handler.process(@mock_request, @mock_response)
end
- it "should pass HTTP request parameters to model find" do
+ 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|
- key == 'key' and args['foo'] == 'baz' and args['bar'] ==
'xyzzy'
+ args[:foo] == :baz and args[:bar] == :xyzzy
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 return a serialized object when a model find call succeeds"
do
setup_find_request
@mock_model_instance = stub('model instance')
@@ -157,7 +224,7 @@ describe Puppet::Network::HTTP::MongrelREST, "when
receiving a request" do
@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)
@@ -172,7 +239,16 @@ describe Puppet::Network::HTTP::MongrelREST, "when
receiving a request" do
@mock_response.expects(:start).with(404)
@handler.process(@mock_request, @mock_response)
end
-
+
+ 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
+ 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|
@@ -180,20 +256,20 @@ describe Puppet::Network::HTTP::MongrelREST, "when
receiving a request" do
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)
@@ -201,7 +277,7 @@ describe Puppet::Network::HTTP::MongrelREST, "when
receiving a request" do
@handler.process(@mock_request, @mock_response)
end
end
-
+
describe "when saving a model instance" do |variable|
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'})
@@ -209,20 +285,29 @@ describe Puppet::Network::HTTP::MongrelREST, "when
receiving a request" do
@mock_response.expects(:start).with(404)
@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
+ 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 serialize a controller exception when an exception is
thrown by save" do
setup_save_request
@mock_model_instance.expects(:save).raises(ArgumentError)
@@ -230,8 +315,17 @@ describe Puppet::Network::HTTP::MongrelREST, "when
receiving a request" do
@handler.process(@mock_request, @mock_response)
end
end
-
+
describe "when searching for model instances" do |variable|
+ 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
+ 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|
@@ -245,7 +339,7 @@ describe Puppet::Network::HTTP::MongrelREST, "when
receiving a request" do
@mock_response.expects(:start).with(200)
@handler.process(@mock_request, @mock_response)
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}") }
@@ -260,7 +354,7 @@ describe Puppet::Network::HTTP::MongrelREST, "when
receiving a request" do
@handler.process(@mock_request, @mock_response)
end
end
-
+
it "should serialize a controller exception if the request fails" do
setup_bad_request
@mock_response.expects(:start).with(404)
diff --git a/spec/unit/network/http/webrick/rest.rb
b/spec/unit/network/http/webrick/rest.rb
index b7bd338..1e0166d 100644
--- a/spec/unit/network/http/webrick/rest.rb
+++ b/spec/unit/network/http/webrick/rest.rb
@@ -8,23 +8,23 @@ describe Puppet::Network::HTTP::WEBrickREST, "when
initializing" do
Puppet::Indirector::Indirection.stubs(:model).returns(@mock_model)
@params = [ @mock_webrick, :foo ]
end
-
+
it "should require access to a WEBrick server" do
Proc.new {
@params[0] = nil
Puppet::Network::HTTP::WEBrickREST.new([EMAIL PROTECTED])
}.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([EMAIL PROTECTED])
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([EMAIL PROTECTED])
}.should raise_error(ArgumentError)
@@ -33,7 +33,7 @@ end
describe Puppet::Network::HTTP::WEBrickREST, "when receiving a request" do
before do
- @mock_request = stub('webrick http request', :query => {})
+ @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, :[] =>
{})
@@ -46,19 +46,19 @@ describe Puppet::Network::HTTP::WEBrickREST, "when
receiving a request" do
@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')
@@ -66,44 +66,48 @@ describe Puppet::Network::HTTP::WEBrickREST, "when
receiving a request" do
@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', {})
- @handler.service(@mock_request, @mock_response)
+ @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.service(@mock_request, @mock_response)
+ @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', {})
- @handler.service(@mock_request, @mock_response)
+ @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.service(@mock_request, @mock_response)
+ @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(404)
@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')
@@ -125,6 +129,42 @@ describe Puppet::Network::HTTP::WEBrickREST, "when
receiving a request" do
@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)
+ 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')
@@ -132,22 +172,22 @@ describe Puppet::Network::HTTP::WEBrickREST, "when
receiving a request" do
@mock_response.expects(:status=).with(404)
@handler.process(@mock_request, @mock_response)
end
-
- it "should pass HTTP request parameters to model find" do
+
+ it "should use a common method for determining the request parameters"
do
setup_find_request
- @mock_request.stubs(:query).returns(:foo => :baz, :bar => :xyzzy)
+ @handler.stubs(:params).returns(:foo => :baz, :bar => :xyzzy)
@mock_model_class.expects(:find).with do |key, args|
- key == 'key' and args[:foo] == :baz and args[:bar] == :xyzzy
+ args[:foo] == :baz and args[:bar] == :xyzzy
end
- @handler.service(@mock_request, @mock_response)
+ @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(: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')
@@ -155,7 +195,7 @@ describe Puppet::Network::HTTP::WEBrickREST, "when
receiving a request" do
@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)
@@ -163,7 +203,7 @@ describe Puppet::Network::HTTP::WEBrickREST, "when
receiving a request" do
@handler.process(@mock_request, @mock_response)
end
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')
@@ -171,37 +211,37 @@ describe Puppet::Network::HTTP::WEBrickREST, "when
receiving a request" do
@mock_response.expects(:status=).with(404)
@handler.process(@mock_request, @mock_response)
end
-
- it "should pass HTTP request parameters to model destroy" do
+
+ it "should use a common method for determining the request parameters"
do
setup_destroy_request
- @mock_request.stubs(:query).returns(:foo => :baz, :bar => :xyzzy)
+ @handler.stubs(:params).returns(:foo => :baz, :bar => :xyzzy)
@mock_model_class.expects(:destroy).with do |key, args|
- key == 'key' and args[:foo] == :baz and args[:bar] == :xyzzy
+ args[:foo] == :baz and args[:bar] == :xyzzy
end
- @handler.service(@mock_request, @mock_response)
+ @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 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 serialize a controller exception when an exception is
thrown by search" do
- setup_search_request
- @mock_model_class.expects(:search).raises(ArgumentError)
+
+ 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(404)
- @handler.process(@mock_request, @mock_response)
- end
+ @handler.process(@mock_request, @mock_response)
+ end
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')
@@ -210,20 +250,29 @@ describe Puppet::Network::HTTP::WEBrickREST, "when
receiving a request" do
@mock_response.expects(:status=).with(404)
@handler.process(@mock_request, @mock_response)
end
-
+
+ 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
+ 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
-
+
it "should serialize a controller exception when an exception is
thrown by save" do
setup_save_request
@mock_model_instance.expects(:save).raises(ArgumentError)
@@ -231,15 +280,15 @@ describe Puppet::Network::HTTP::WEBrickREST, "when
receiving a request" do
@handler.process(@mock_request, @mock_response)
end
end
-
+
describe "when searching for model instances" do
- it "should pass HTTP request parameters to model search" do
+ it "should use a common method for determining the request parameters"
do
setup_search_request
- @mock_request.stubs(:query).returns(:foo => :baz, :bar => :xyzzy)
+ @handler.stubs(:params).returns(:foo => :baz, :bar => :xyzzy)
@mock_model_class.expects(:search).with do |args|
args[:foo] == :baz and args[:bar] == :xyzzy
- end.returns([])
- @handler.service(@mock_request, @mock_response)
+ end
+ @handler.process(@mock_request, @mock_response)
end
it "should generate a 200 response when a model search call succeeds"
do
@@ -247,20 +296,20 @@ describe Puppet::Network::HTTP::WEBrickREST, "when
receiving a request" do
@mock_response.expects(:status=).with(200)
@handler.process(@mock_request, @mock_response)
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 serialize a controller exception when an exception is
thrown by destroy" do
- setup_destroy_request
- @mock_model_class.expects(:destroy).raises(ArgumentError)
+
+ 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(404)
- @handler.process(@mock_request, @mock_response)
- end
+ @handler.process(@mock_request, @mock_response)
+ end
end
it "should serialize a controller exception if the request fails" do
--
1.5.3.7
--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups
"Puppet Developers" group.
To post to this group, send email to [email protected]
To unsubscribe from this group, send email to [EMAIL PROTECTED]
For more options, visit this group at
http://groups.google.com/group/puppet-dev?hl=en
-~----------~----~----~----~------~----~------~--~---