Page MenuHomePhorge

No OneTemporary

diff --git a/lib/puppet/indirector/rest.rb b/lib/puppet/indirector/rest.rb
index e59b332c0..a2767d05b 100644
--- a/lib/puppet/indirector/rest.rb
+++ b/lib/puppet/indirector/rest.rb
@@ -1,59 +1,62 @@
require 'net/http'
require 'uri'
# Access objects via REST
class Puppet::Indirector::REST < Puppet::Indirector::Terminus
# Figure out the content type, turn that into a format, and use the format
# to extract the body of the response.
def deserialize(response, multiple = false)
case response.code
when "404"
return nil
when /^2/
unless response['content-type']
raise "No content type in http response; cannot parse"
end
# Convert the response to a deserialized object.
if multiple
model.convert_from_multiple(response['content-type'], response.body)
else
model.convert_from(response['content-type'], response.body)
end
else
# Raise the http error if we didn't get a 'success' of some kind.
message = "Server returned %s: %s" % [response.code, response.message]
raise Net::HTTPError.new(message, response)
end
end
# Provide appropriate headers.
def headers
{"Accept" => model.supported_formats.join(", ")}
end
def network
Puppet::Network::HttpPool.http_instance(Puppet[:server], Puppet[:masterport].to_i)
end
def find(request)
deserialize network.get("/#{indirection.name}/#{request.key}", headers)
end
def search(request)
if request.key
path = "/#{indirection.name}s/#{request.key}"
else
path = "/#{indirection.name}s"
end
- deserialize(network.get(path, headers), true)
+ unless result = deserialize(network.get(path, headers), true)
+ return []
+ end
+ return result
end
def destroy(request)
deserialize network.delete("/#{indirection.name}/#{request.key}", headers)
end
def save(request)
deserialize network.put("/#{indirection.name}/", request.instance.render, headers)
end
end
diff --git a/lib/puppet/network/http/handler.rb b/lib/puppet/network/http/handler.rb
index 0069933bd..291481acd 100644
--- a/lib/puppet/network/http/handler.rb
+++ b/lib/puppet/network/http/handler.rb
@@ -1,166 +1,168 @@
module Puppet::Network::HTTP
end
module Puppet::Network::HTTP::Handler
attr_reader :model, :server, :handler
# Retrieve the accept header from the http request.
def accept_header(request)
raise NotImplementedError
end
# Which format to use when serializing our response. Just picks
# the first value in the accept header, at this point.
def format_to_use(request)
accept_header(request).split(/,\s*/)[0]
end
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)
raise ArgumentError, "Did not understand HTTP #{http_method(request)} request for '#{path(request)}'"
rescue Exception => e
return do_exception(response, e)
end
# Are we interacting with a singular instance?
def singular?(request)
%r{/#{handler.to_s}$}.match(path(request))
end
# Are we interacting with multiple instances?
def plural?(request)
%r{/#{handler.to_s}s$}.match(path(request))
end
# Set the response up, with the body and status.
def set_response(response, body, status = 200)
raise NotImplementedError
end
# Set the specified format as the content type of the response.
def set_content_type(response, format)
raise NotImplementedError
end
def do_exception(response, exception, status=400)
if exception.is_a?(Exception)
puts exception.backtrace if Puppet[:trace]
puts exception if Puppet[:trace]
end
set_content_type(response, "text/plain")
set_response(response, exception.to_s, status)
end
# Execute our find.
def do_find(request, response)
key = request_key(request) || raise(ArgumentError, "Could not locate lookup key in request path [#{path(request)}]")
args = params(request)
unless result = model.find(key, args)
return do_exception(response, "Could not find %s %s" % [model.name, key], 404)
end
# The encoding of the result must include the format to use,
# and it needs to be used for both the rendering and as
# the content type.
format = format_to_use(request)
set_content_type(response, format)
set_response(response, result.render(format))
end
# Execute our search.
def do_search(request, response)
args = params(request)
result = model.search(args)
+ if result.nil? or (result.is_a?(Array) and result.empty?)
+ return do_exception(response, "Could not find instances in %s with '%s'" % [model.name, args.inspect], 404)
+ end
- # LAK:FAIL This doesn't work.
format = format_to_use(request)
set_content_type(response, format)
set_response(response, model.render_multiple(format, result))
end
# Execute our destroy.
def do_destroy(request, response)
key = request_key(request) || raise(ArgumentError, "Could not locate lookup key in request path [#{path(request)}]")
args = params(request)
result = model.destroy(key, args)
set_content_type(response, "yaml")
set_response(response, result.to_yaml)
end
# Execute our save.
def do_save(request, response)
data = body(request).to_s
raise ArgumentError, "No data to save" if !data or data.empty?
args = params(request)
format = format_to_use(request)
obj = model.convert_from(format_to_use(request), data)
result = save_object(obj, args)
set_content_type(response, "yaml")
set_response(response, result.to_yaml)
end
private
# LAK:NOTE This has to be here for testing; it's a stub-point so
# we keep infinite recursion from happening.
def save_object(object, args)
object.save(args)
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
# methods to be overridden by the including web server class
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
end
diff --git a/spec/integration/indirector/rest.rb b/spec/integration/indirector/rest.rb
index 86d4d4291..b307e3cab 100755
--- a/spec/integration/indirector/rest.rb
+++ b/spec/integration/indirector/rest.rb
@@ -1,492 +1,499 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../../spec_helper'
require 'puppet/network/server'
require 'puppet/indirector'
require 'puppet/indirector/rest'
# a fake class that will be indirected via REST
class Puppet::TestIndirectedFoo
extend Puppet::Indirector
indirects :test_indirected_foo, :terminus_setting => :test_indirected_foo_terminus
attr_reader :value
def initialize(value = 0)
@value = value
end
def self.from_yaml(yaml)
YAML.load(yaml)
end
def name
"bob"
end
end
# empty Terminus class -- this would normally have to be in a directory findable by the autoloader, but we short-circuit that below
class Puppet::TestIndirectedFoo::Rest < Puppet::Indirector::REST
end
describe Puppet::Indirector::REST do
before do
Puppet[:masterport] = 34343
Puppet[:server] = "localhost"
# Get a safe temporary file
@tmpfile = Tempfile.new("webrick_integration_testing")
@dir = @tmpfile.path + "_dir"
Puppet.settings[:confdir] = @dir
Puppet.settings[:vardir] = @dir
Puppet.settings[:server] = "127.0.0.1"
Puppet.settings[:masterport] = "34343"
Puppet.settings[:http_enable_post_connection_check] = false
Puppet::SSL::Host.ca_location = :local
Puppet::TestIndirectedFoo.terminus_class = :rest
end
after do
Puppet::Network::HttpPool.instance_variable_set("@ssl_host", nil)
Puppet.settings.clear
end
describe "when using webrick" do
before :each do
Puppet::Util::Cacher.invalidate
Puppet[:servertype] = 'webrick'
Puppet[:server] = '127.0.0.1'
Puppet[:certname] = '127.0.0.1'
ca = Puppet::SSL::CertificateAuthority.new
ca.generate(Puppet[:certname]) unless Puppet::SSL::Certificate.find(Puppet[:certname])
@params = { :address => "127.0.0.1", :port => 34343, :handlers => [ :test_indirected_foo ], :xmlrpc_handlers => [ :status ] }
@server = Puppet::Network::Server.new(@params)
@server.listen
# LAK:NOTE We need to have a fake model here so that our indirected methods get
# passed through REST; otherwise we'd be stubbing 'find', which would cause an immediate
# return.
@mock_model = stub('faked model', :name => "foo")
Puppet::Network::HTTP::WEBrickREST.any_instance.stubs(:model).returns(@mock_model)
end
describe "when finding a model instance over REST" do
describe "when a matching model instance can be found" do
before :each do
@model_instance = Puppet::TestIndirectedFoo.new(23)
@mock_model.stubs(:find).returns @model_instance
end
it "should not fail" do
lambda { Puppet::TestIndirectedFoo.find('bar') }.should_not raise_error
end
it 'should return an instance of the model class' do
Puppet::TestIndirectedFoo.find('bar').class.should == Puppet::TestIndirectedFoo
end
it 'should return the instance of the model class associated with the provided lookup key' do
Puppet::TestIndirectedFoo.find('bar').value.should == @model_instance.value
end
it 'should set an expiration on model instance' do
Puppet::TestIndirectedFoo.find('bar').expiration.should_not be_nil
end
it "should use a supported format" do
Puppet::TestIndirectedFoo.expects(:supported_formats).returns ["marshal"]
text = Marshal.dump(@model_instance)
@model_instance.expects(:render).with("marshal").returns text
Puppet::TestIndirectedFoo.find('bar')
end
end
describe "when no matching model instance can be found" do
before :each do
@mock_model = stub('faked model', :name => "foo", :find => nil)
Puppet::Network::HTTP::WEBrickREST.any_instance.stubs(:model).returns(@mock_model)
end
it "should return nil" do
Puppet::TestIndirectedFoo.find('bar').should be_nil
end
end
describe "when an exception is encountered in looking up a model instance" do
before :each do
@mock_model = stub('faked model', :name => "foo")
@mock_model.stubs(:find).raises(RuntimeError)
Puppet::Network::HTTP::WEBrickREST.any_instance.stubs(:model).returns(@mock_model)
end
it "should raise an exception" do
lambda { Puppet::TestIndirectedFoo.find('bar') }.should raise_error(Net::HTTPError)
end
end
end
describe "when searching for model instances over REST" do
describe "when matching model instances can be found" do
before :each do
@model_instances = [ Puppet::TestIndirectedFoo.new(23), Puppet::TestIndirectedFoo.new(24) ]
@mock_model.stubs(:search).returns @model_instances
+ # Force yaml, because otherwise our mocks can't work correctly
+ Puppet::TestIndirectedFoo.stubs(:supported_formats).returns %w{yaml}
+
@mock_model.stubs(:render_multiple).returns @model_instances.to_yaml
end
it "should not fail" do
lambda { Puppet::TestIndirectedFoo.search('bar') }.should_not raise_error
end
it 'should return all matching results' do
Puppet::TestIndirectedFoo.search('bar').length.should == @model_instances.length
end
it 'should return model instances' do
Puppet::TestIndirectedFoo.search('bar').each do |result|
result.class.should == Puppet::TestIndirectedFoo
end
end
it 'should return the instance of the model class associated with the provided lookup key' do
Puppet::TestIndirectedFoo.search('bar').collect { |i| i.value }.should == @model_instances.collect { |i| i.value }
end
end
describe "when no matching model instance can be found" do
before :each do
@mock_model = stub('faked model', :name => "foo", :find => nil)
Puppet::Network::HTTP::WEBrickREST.any_instance.stubs(:model).returns(@mock_model)
end
it "should return nil" do
Puppet::TestIndirectedFoo.find('bar').should be_nil
end
end
describe "when an exception is encountered in looking up a model instance" do
before :each do
@mock_model = stub('faked model')
@mock_model.stubs(:find).raises(RuntimeError)
Puppet::Network::HTTP::WEBrickREST.any_instance.stubs(:model).returns(@mock_model)
end
it "should raise an exception" do
lambda { Puppet::TestIndirectedFoo.find('bar') }.should raise_error(Net::HTTPError)
end
end
end
describe "when destroying a model instance over REST" do
describe "when a matching model instance can be found" do
before :each do
@mock_model.stubs(:destroy).returns true
end
it "should not fail" do
lambda { Puppet::TestIndirectedFoo.destroy('bar') }.should_not raise_error
end
it 'should return success' do
Puppet::TestIndirectedFoo.destroy('bar').should == true
end
end
describe "when no matching model instance can be found" do
before :each do
@mock_model.stubs(:destroy).returns false
end
it "should return failure" do
Puppet::TestIndirectedFoo.destroy('bar').should == false
end
end
describe "when an exception is encountered in destroying a model instance" do
before :each do
@mock_model.stubs(:destroy).raises(RuntimeError)
end
it "should raise an exception" do
lambda { Puppet::TestIndirectedFoo.destroy('bar') }.should raise_error(Net::HTTPError)
end
end
end
describe "when saving a model instance over REST" do
before :each do
@instance = Puppet::TestIndirectedFoo.new(42)
@mock_model.stubs(:save_object).returns @instance
@mock_model.stubs(:convert_from).returns @instance
Puppet::Network::HTTP::WEBrickREST.any_instance.stubs(:save_object).returns(@instance)
end
describe "when a successful save can be performed" do
before :each do
end
it "should not fail" do
lambda { @instance.save }.should_not raise_error
end
it 'should return an instance of the model class' do
@instance.save.class.should == Puppet::TestIndirectedFoo
end
it 'should return a matching instance of the model class' do
@instance.save.value.should == @instance.value
end
end
describe "when a save cannot be completed" do
before :each do
Puppet::Network::HTTP::WEBrickREST.any_instance.stubs(:save_object).returns(false)
end
it "should return failure" do
@instance.save.should == false
end
end
describe "when an exception is encountered in performing a save" do
before :each do
Puppet::Network::HTTP::WEBrickREST.any_instance.stubs(:save_object).raises(RuntimeError)
end
it "should raise an exception" do
lambda { @instance.save }.should raise_error(Net::HTTPError)
end
end
end
after :each do
@server.unlisten
end
end
describe "when using mongrel" do
confine "Mongrel is not available" => Puppet.features.mongrel?
before :each do
Puppet[:servertype] = 'mongrel'
@params = { :address => "127.0.0.1", :port => 34343, :handlers => [ :test_indirected_foo ] }
# Make sure we never get a cert, since mongrel can't speak ssl
Puppet::SSL::Certificate.stubs(:find).returns nil
# We stub ssl to be off, since mongrel can't speak ssl
Net::HTTP.any_instance.stubs(:use_ssl?).returns false
@server = Puppet::Network::Server.new(@params)
@server.listen
# LAK:NOTE We need to have a fake model here so that our indirected methods get
# passed through REST; otherwise we'd be stubbing 'find', which would cause an immediate
# return.
@mock_model = stub('faked model', :name => "foo")
Puppet::Network::HTTP::MongrelREST.any_instance.stubs(:model).returns(@mock_model)
end
after do
@server.unlisten
end
describe "when finding a model instance over REST" do
describe "when a matching model instance can be found" do
before :each do
@model_instance = Puppet::TestIndirectedFoo.new(23)
@mock_model.stubs(:find).returns @model_instance
end
it "should not fail" do
lambda { Puppet::TestIndirectedFoo.find('bar') }.should_not raise_error
end
it 'should return an instance of the model class' do
Puppet::TestIndirectedFoo.find('bar').class.should == Puppet::TestIndirectedFoo
end
it 'should return the instance of the model class associated with the provided lookup key' do
Puppet::TestIndirectedFoo.find('bar').value.should == @model_instance.value
end
it 'should set an expiration on model instance' do
Puppet::TestIndirectedFoo.find('bar').expiration.should_not be_nil
end
it "should use a supported format" do
Puppet::TestIndirectedFoo.expects(:supported_formats).returns ["marshal"]
text = Marshal.dump(@model_instance)
@model_instance.expects(:render).with("marshal").returns text
Puppet::TestIndirectedFoo.find('bar')
end
end
describe "when no matching model instance can be found" do
before :each do
@mock_model.stubs(:find).returns nil
end
it "should return nil" do
Puppet::TestIndirectedFoo.find('bar').should be_nil
end
end
describe "when an exception is encountered in looking up a model instance" do
before :each do
@mock_model.stubs(:find).raises(RuntimeError)
end
it "should raise an exception" do
lambda { Puppet::TestIndirectedFoo.find('bar') }.should raise_error(Net::HTTPError)
end
end
end
describe "when searching for model instances over REST" do
describe "when matching model instances can be found" do
before :each do
@model_instances = [ Puppet::TestIndirectedFoo.new(23), Puppet::TestIndirectedFoo.new(24) ]
+
+ # Force yaml, because otherwise our mocks can't work correctly
+ Puppet::TestIndirectedFoo.stubs(:supported_formats).returns %w{yaml}
+
@mock_model.stubs(:search).returns @model_instances
@mock_model.stubs(:render_multiple).returns @model_instances.to_yaml
end
it "should not fail" do
lambda { Puppet::TestIndirectedFoo.search('bar') }.should_not raise_error
end
it 'should return all matching results' do
Puppet::TestIndirectedFoo.search('bar').length.should == @model_instances.length
end
it 'should return model instances' do
Puppet::TestIndirectedFoo.search('bar').each do |result|
result.class.should == Puppet::TestIndirectedFoo
end
end
it 'should return the instance of the model class associated with the provided lookup key' do
Puppet::TestIndirectedFoo.search('bar').collect { |i| i.value }.should == @model_instances.collect { |i| i.value }
end
it 'should set an expiration on model instances' do
Puppet::TestIndirectedFoo.search('bar').each do |result|
result.expiration.should_not be_nil
end
end
end
describe "when no matching model instance can be found" do
before :each do
@mock_model.stubs(:search).returns nil
@mock_model.stubs(:render_multiple).returns nil.to_yaml
end
it "should return nil" do
- Puppet::TestIndirectedFoo.search('bar').should be_nil
+ Puppet::TestIndirectedFoo.search('bar').should == []
end
end
describe "when an exception is encountered in looking up a model instance" do
before :each do
@mock_model.stubs(:find).raises(RuntimeError)
end
it "should raise an exception" do
lambda { Puppet::TestIndirectedFoo.find('bar') }.should raise_error(Net::HTTPError)
end
end
end
describe "when destroying a model instance over REST" do
describe "when a matching model instance can be found" do
before :each do
@mock_model.stubs(:destroy).returns true
end
it "should not fail" do
lambda { Puppet::TestIndirectedFoo.destroy('bar') }.should_not raise_error
end
it 'should return success' do
Puppet::TestIndirectedFoo.destroy('bar').should == true
end
end
describe "when no matching model instance can be found" do
before :each do
@mock_model.stubs(:destroy).returns false
end
it "should return failure" do
Puppet::TestIndirectedFoo.destroy('bar').should == false
end
end
describe "when an exception is encountered in destroying a model instance" do
before :each do
@mock_model.stubs(:destroy).raises(RuntimeError)
end
it "should raise an exception" do
lambda { Puppet::TestIndirectedFoo.destroy('bar') }.should raise_error(Net::HTTPError)
end
end
end
describe "when saving a model instance over REST" do
before :each do
@instance = Puppet::TestIndirectedFoo.new(42)
@mock_model.stubs(:convert_from).returns @instance
# LAK:NOTE This stub is necessary to prevent the REST call from calling
# REST.save again, thus producing painful infinite recursion.
Puppet::Network::HTTP::MongrelREST.any_instance.stubs(:save_object).returns(@instance)
end
describe "when a successful save can be performed" do
before :each do
end
it "should not fail" do
lambda { @instance.save }.should_not raise_error
end
it 'should return an instance of the model class' do
@instance.save.class.should == Puppet::TestIndirectedFoo
end
it 'should return a matching instance of the model class' do
@instance.save.value.should == @instance.value
end
end
describe "when a save cannot be completed" do
before :each do
Puppet::Network::HTTP::MongrelREST.any_instance.stubs(:save_object).returns(false)
end
it "should return failure" do
@instance.save.should == false
end
end
describe "when an exception is encountered in performing a save" do
before :each do
Puppet::Network::HTTP::MongrelREST.any_instance.stubs(:save_object).raises(RuntimeError)
end
it "should raise an exception" do
lambda { @instance.save }.should raise_error(Net::HTTPError)
end
end
end
end
end
diff --git a/spec/unit/indirector/rest.rb b/spec/unit/indirector/rest.rb
index 801e8888e..28ceb693a 100755
--- a/spec/unit/indirector/rest.rb
+++ b/spec/unit/indirector/rest.rb
@@ -1,293 +1,299 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../../spec_helper'
require 'puppet/indirector/rest'
describe "a REST http call", :shared => true do
it "should accept a path" do
lambda { @search.send(@method, *@arguments) }.should_not raise_error(ArgumentError)
end
it "should require a path" do
lambda { @searcher.send(@method) }.should raise_error(ArgumentError)
end
it "should return the results of deserializing the response to the request" do
conn = mock 'connection'
conn.stubs(:put).returns @response
conn.stubs(:delete).returns @response
conn.stubs(:get).returns @response
Puppet::Network::HttpPool.stubs(:http_instance).returns conn
@searcher.expects(:deserialize).with(@response).returns "myobject"
@searcher.send(@method, *@arguments).should == 'myobject'
end
end
describe Puppet::Indirector::REST do
before do
Puppet::Indirector::Terminus.stubs(:register_terminus_class)
@model = stub('model', :supported_formats => %w{}, :convert_from => nil)
@instance = stub('model instance')
@indirection = stub('indirection', :name => :mystuff, :register_terminus_type => nil, :model => @model)
Puppet::Indirector::Indirection.stubs(:instance).returns(@indirection)
@rest_class = Class.new(Puppet::Indirector::REST) do
def self.to_s
"This::Is::A::Test::Class"
end
end
@response = stub('mock response', :body => 'result', :code => "200")
@response.stubs(:[]).with('content-type').returns "text/plain"
@searcher = @rest_class.new
end
describe "when deserializing responses" do
it "should return nil if the response code is 404" do
response = mock 'response'
response.expects(:code).returns "404"
@searcher.deserialize(response).should be_nil
end
it "should fail if the response code is not in the 200s" do
@model.expects(:convert_from).never
response = mock 'response'
response.stubs(:code).returns "300"
response.stubs(:message).returns "There was a problem"
lambda { @searcher.deserialize(response) }.should raise_error(Net::HTTPError)
end
it "should return the results of converting from the format specified by the content-type header if the response code is in the 200s" do
@model.expects(:convert_from).with("myformat", "mydata").returns "myobject"
response = mock 'response'
response.stubs(:[]).with("content-type").returns "myformat"
response.stubs(:body).returns "mydata"
response.stubs(:code).returns "200"
@searcher.deserialize(response).should == "myobject"
end
it "should convert and return multiple instances if the return code is in the 200s and 'multiple' is specified" do
@model.expects(:convert_from_multiple).with("myformat", "mydata").returns "myobjects"
response = mock 'response'
response.stubs(:[]).with("content-type").returns "myformat"
response.stubs(:body).returns "mydata"
response.stubs(:code).returns "200"
@searcher.deserialize(response, true).should == "myobjects"
end
end
describe "when creating an HTTP client" do
before do
Puppet.settings.stubs(:value).returns("rest_testing")
end
it "should use the :server setting as the host and the :masterport setting (as an Integer) as the port" do
Puppet.settings.expects(:value).with(:server).returns "myserver"
Puppet.settings.expects(:value).with(:masterport).returns "1234"
Puppet::Network::HttpPool.expects(:http_instance).with("myserver", 1234).returns "myconn"
@searcher.network.should == "myconn"
end
end
describe "when doing a find" do
before :each do
@connection = stub('mock http connection', :get => @response)
@searcher.stubs(:network).returns(@connection) # neuter the network connection
@request = stub 'request', :key => 'foo'
end
it "should call the GET http method on a network connection" do
@searcher.expects(:network).returns @connection
@connection.expects(:get).returns @response
@searcher.find(@request)
end
it "should deserialize and return the http response" do
@connection.expects(:get).returns @response
@searcher.expects(:deserialize).with(@response).returns "myobject"
@searcher.find(@request).should == 'myobject'
end
it "should use the indirection name and request key to create the path" do
should_path = "/%s/%s" % [@indirection.name.to_s, "foo"]
@connection.expects(:get).with { |path, args| path == should_path }.returns(@response)
@searcher.find(@request)
end
it "should provide an Accept header containing the list of supported formats joined with commas" do
@connection.expects(:get).with { |path, args| args["Accept"] == "supported, formats" }.returns(@response)
@searcher.model.expects(:supported_formats).returns %w{supported formats}
@searcher.find(@request)
end
it "should deserialize and return the network response" do
@searcher.expects(:deserialize).with(@response).returns @instance
@searcher.find(@request).should equal(@instance)
end
it "should generate an error when result data deserializes fails" do
@searcher.expects(:deserialize).raises(ArgumentError)
lambda { @searcher.find(@request) }.should raise_error(ArgumentError)
end
end
describe "when doing a search" do
before :each do
@connection = stub('mock http connection', :get => @response)
@searcher.stubs(:network).returns(@connection) # neuter the network connection
@model.stubs(:convert_from_multiple)
@request = stub 'request', :key => 'foo'
end
it "should call the GET http method on a network connection" do
@searcher.expects(:network).returns @connection
@connection.expects(:get).returns @response
@searcher.search(@request)
end
it "should deserialize as multiple instances and return the http response" do
@connection.expects(:get).returns @response
@searcher.expects(:deserialize).with(@response, true).returns "myobject"
@searcher.search(@request).should == 'myobject'
end
it "should use the plural indirection name as the path if there is no request key" do
should_path = "/%ss" % [@indirection.name.to_s]
@request.stubs(:key).returns nil
@connection.expects(:get).with { |path, args| path == should_path }.returns(@response)
@searcher.search(@request)
end
it "should use the plural indirection name and request key to create the path if the request key is set" do
should_path = "/%ss/%s" % [@indirection.name.to_s, "foo"]
@connection.expects(:get).with { |path, args| path == should_path }.returns(@response)
@searcher.search(@request)
end
it "should provide an Accept header containing the list of supported formats joined with commas" do
@connection.expects(:get).with { |path, args| args["Accept"] == "supported, formats" }.returns(@response)
@searcher.model.expects(:supported_formats).returns %w{supported formats}
@searcher.search(@request)
end
+ it "should return an empty array if serialization returns nil" do
+ @model.stubs(:convert_from_multiple).returns nil
+
+ @searcher.search(@request).should == []
+ end
+
it "should generate an error when result data deserializes fails" do
@searcher.expects(:deserialize).raises(ArgumentError)
lambda { @searcher.search(@request) }.should raise_error(ArgumentError)
end
end
describe "when doing a destroy" do
before :each do
@connection = stub('mock http connection', :delete => @response)
@searcher.stubs(:network).returns(@connection) # neuter the network connection
@request = stub 'request', :key => 'foo'
end
it "should call the DELETE http method on a network connection" do
@searcher.expects(:network).returns @connection
@connection.expects(:delete).returns @response
@searcher.destroy(@request)
end
it "should deserialize and return the http response" do
@connection.expects(:delete).returns @response
@searcher.expects(:deserialize).with(@response).returns "myobject"
@searcher.destroy(@request).should == 'myobject'
end
it "should use the indirection name and request key to create the path" do
should_path = "/%s/%s" % [@indirection.name.to_s, "foo"]
@connection.expects(:delete).with { |path, args| path == should_path }.returns(@response)
@searcher.destroy(@request)
end
it "should provide an Accept header containing the list of supported formats joined with commas" do
@connection.expects(:delete).with { |path, args| args["Accept"] == "supported, formats" }.returns(@response)
@searcher.model.expects(:supported_formats).returns %w{supported formats}
@searcher.destroy(@request)
end
it "should deserialize and return the network response" do
@searcher.expects(:deserialize).with(@response).returns @instance
@searcher.destroy(@request).should equal(@instance)
end
it "should generate an error when result data deserializes fails" do
@searcher.expects(:deserialize).raises(ArgumentError)
lambda { @searcher.destroy(@request) }.should raise_error(ArgumentError)
end
end
describe "when doing a save" do
before :each do
@connection = stub('mock http connection', :put => @response)
@searcher.stubs(:network).returns(@connection) # neuter the network connection
@instance = stub 'instance', :render => "mydata"
@request = stub 'request', :instance => @instance
end
it "should call the PUT http method on a network connection" do
@searcher.expects(:network).returns @connection
@connection.expects(:put).returns @response
@searcher.save(@request)
end
it "should use the indirection name as the path for the request" do
@connection.expects(:put).with { |path, data, args| path == "/#{@indirection.name.to_s}/" }.returns @response
@searcher.save(@request)
end
it "should serialize the instance using the default format and pass the result as the body of the request" do
@instance.expects(:render).returns "serial_instance"
@connection.expects(:put).with { |path, data, args| data == "serial_instance" }.returns @response
@searcher.save(@request)
end
it "should deserialize and return the http response" do
@connection.expects(:put).returns @response
@searcher.expects(:deserialize).with(@response).returns "myobject"
@searcher.save(@request).should == 'myobject'
end
it "should provide an Accept header containing the list of supported formats joined with commas" do
@connection.expects(:put).with { |path, data, args| args["Accept"] == "supported, formats" }.returns(@response)
@searcher.model.expects(:supported_formats).returns %w{supported formats}
@searcher.save(@request)
end
it "should deserialize and return the network response" do
@searcher.expects(:deserialize).with(@response).returns @instance
@searcher.save(@request).should equal(@instance)
end
it "should generate an error when result data deserializes fails" do
@searcher.expects(:deserialize).raises(ArgumentError)
lambda { @searcher.save(@request) }.should raise_error(ArgumentError)
end
end
end
diff --git a/spec/unit/network/http/handler.rb b/spec/unit/network/http/handler.rb
index 1ed816d97..816c0ea2e 100755
--- a/spec/unit/network/http/handler.rb
+++ b/spec/unit/network/http/handler.rb
@@ -1,426 +1,440 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../../../spec_helper'
require 'puppet/network/http/handler'
class HttpHandled
include Puppet::Network::HTTP::Handler
end
describe Puppet::Network::HTTP::Handler do
before do
@handler = HttpHandled.new
end
it "should have a method for initializing" do
@handler.should respond_to(:initialize_for_puppet)
end
describe "when initializing" do
before do
Puppet::Indirector::Indirection.stubs(:model).returns "eh"
end
it "should fail when no server type has been provided" do
lambda { @handler.initialize_for_puppet :handler => "foo" }.should raise_error(ArgumentError)
end
it "should fail when no handler has been provided" do
lambda { @handler.initialize_for_puppet :server => "foo" }.should raise_error(ArgumentError)
end
it "should set the handler and server type" do
@handler.initialize_for_puppet :server => "foo", :handler => "bar"
@handler.server.should == "foo"
@handler.handler.should == "bar"
end
it "should use the indirector to find the appropriate model" do
Puppet::Indirector::Indirection.expects(:model).with("bar").returns "mymodel"
@handler.initialize_for_puppet :server => "foo", :handler => "bar"
@handler.model.should == "mymodel"
end
end
it "should be able to process requests" do
@handler.should respond_to(:process)
end
describe "when processing a request" do
before do
@request = stub('http request')
@request.stubs(:[]).returns "foo"
@response = stub('http response')
@model_class = stub('indirected model class')
@result = stub 'result', :render => "mytext"
@handler.stubs(:model).returns @model_class
@handler.stubs(:handler).returns :my_handler
stub_server_interface
end
# Stub out the interface we require our including classes to
# implement.
def stub_server_interface
@handler.stubs(:accept_header ).returns "format_one,format_two"
@handler.stubs(:set_content_type).returns "my_result"
@handler.stubs(:set_response ).returns "my_result"
@handler.stubs(:path ).returns "/my_handler"
@handler.stubs(:request_key ).returns "my_result"
@handler.stubs(:params ).returns({})
@handler.stubs(:content_type ).returns("text/plain")
end
it "should consider the request singular if the path is equal to '/' plus the handler name" do
@handler.expects(:path).with(@request).returns "/foo"
@handler.expects(:handler).returns "foo"
@handler.should be_singular(@request)
end
it "should not consider the request singular unless the path is equal to '/' plus the handler name" do
@handler.expects(:path).with(@request).returns "/foo"
@handler.expects(:handler).returns "bar"
@handler.should_not be_singular(@request)
end
it "should consider the request plural if the path is equal to '/' plus the handler name plus 's'" do
@handler.expects(:path).with(@request).returns "/foos"
@handler.expects(:handler).returns "foo"
@handler.should be_plural(@request)
end
it "should not consider the request plural unless the path is equal to '/' plus the handler name plus 's'" do
@handler.expects(:path).with(@request).returns "/foos"
@handler.expects(:handler).returns "bar"
@handler.should_not be_plural(@request)
end
it "should call the model find method if the request represents a singular HTTP GET" do
@handler.expects(:http_method).returns('GET')
@handler.expects(:singular?).returns(true)
@handler.expects(:do_find).with(@request, @response)
@handler.process(@request, @response)
end
it "should serialize a controller exception when an exception is thrown while finding the model instance" do
@handler.expects(:http_method).returns('GET')
@handler.expects(:singular?).returns(true)
@handler.expects(:do_find).raises(ArgumentError, "The exception")
@handler.expects(:set_response).with { |response, body, status| body == "The exception" and status == 400 }
@handler.process(@request, @response)
end
it "should call the model search method if the request represents a plural HTTP GET" do
@handler.stubs(:http_method).returns('GET')
@handler.stubs(:singular?).returns(false)
@handler.stubs(:plural?).returns(true)
@handler.expects(:do_search).with(@request, @response)
@handler.process(@request, @response)
end
it "should serialize a controller exception when an exception is thrown by search" do
@handler.stubs(:http_method).returns('GET')
@handler.stubs(:singular?).returns(false)
@handler.stubs(:plural?).returns(true)
@model_class.expects(:search).raises(ArgumentError)
@handler.expects(:set_response).with { |response, data, status| status == 400 }
@handler.process(@request, @response)
end
it "should call the model destroy method if the request represents an HTTP DELETE" do
@handler.stubs(:http_method).returns('DELETE')
@handler.stubs(:singular?).returns(true)
@handler.stubs(:plural?).returns(false)
@handler.expects(:do_destroy).with(@request, @response)
@handler.process(@request, @response)
end
it "should serialize a controller exception when an exception is thrown by destroy" do
@handler.stubs(:http_method).returns('DELETE')
@handler.stubs(:singular?).returns(true)
@handler.stubs(:plural?).returns(false)
@handler.expects(:do_destroy).with(@request, @response).raises(ArgumentError, "The exception")
@handler.expects(:set_response).with { |response, body, status| body == "The exception" and status == 400 }
@handler.process(@request, @response)
end
it "should call the model save method if the request represents an HTTP PUT" do
@handler.stubs(:http_method).returns('PUT')
@handler.stubs(:singular?).returns(true)
@handler.expects(:do_save).with(@request, @response)
@handler.process(@request, @response)
end
it "should serialize a controller exception when an exception is thrown by save" do
@handler.stubs(:http_method).returns('PUT')
@handler.stubs(:singular?).returns(true)
@handler.stubs(:body).raises(ArgumentError)
@handler.expects(:set_response).with { |response, body, status| status == 400 }
@handler.process(@request, @response)
end
it "should fail if the HTTP method isn't supported" do
@handler.stubs(:http_method).returns('POST')
@handler.stubs(:singular?).returns(true)
@handler.stubs(:plural?).returns(false)
@handler.expects(:set_response).with { |response, body, status| status == 400 }
@handler.process(@request, @response)
end
it "should fail if delete request's pluralization is wrong" do
@handler.stubs(:http_method).returns('DELETE')
@handler.stubs(:singular?).returns(false)
@handler.stubs(:plural?).returns(true)
@handler.expects(:set_response).with { |response, body, status| status == 400 }
@handler.process(@request, @response)
end
it "should fail if put request's pluralization is wrong" do
@handler.stubs(:http_method).returns('PUT')
@handler.stubs(:singular?).returns(false)
@handler.stubs(:plural?).returns(true)
@handler.expects(:set_response).with { |response, body, status| status == 400 }
@handler.process(@request, @response)
end
it "should fail if the request is for an unknown path" do
@handler.stubs(:http_method).returns('GET')
@handler.expects(:singular?).returns false
@handler.expects(:plural?).returns false
@handler.expects(:set_response).with { |response, body, status| status == 400 }
@handler.process(@request, @response)
end
it "should set the format to text/plain when serializing an exception" do
@handler.expects(:set_content_type).with(@response, "text/plain")
@handler.do_exception(@response, "A test", 404)
end
describe "when finding a model instance" do
before do
@handler.stubs(:http_method).returns('GET')
@handler.stubs(:path).returns('/my_handler')
@handler.stubs(:singular?).returns(true)
@handler.stubs(:request_key).returns('key')
@model_class.stubs(:find).returns @result
end
it "should fail to find model if key is not specified" do
@handler.stubs(:request_key).returns(nil)
lambda { @handler.do_find(@request, @response) }.should raise_error(ArgumentError)
end
it "should use a common method for determining the request parameters" do
@handler.stubs(:params).returns(:foo => :baz, :bar => :xyzzy)
@model_class.expects(:find).with do |key, args|
args[:foo] == :baz and args[:bar] == :xyzzy
end.returns @result
@handler.do_find(@request, @response)
end
it "should set the content type to the first format specified in the accept header" do
@handler.expects(:accept_header).with(@request).returns "one,two"
@handler.expects(:set_content_type).with(@response, "one")
@handler.do_find(@request, @response)
end
it "should render the result using the first format specified in the accept header" do
@handler.expects(:accept_header).with(@request).returns "one,two"
@result.expects(:render).with("one")
@handler.do_find(@request, @response)
end
it "should use the default status when a model find call succeeds" do
@handler.expects(:set_response).with { |response, body, status| status.nil? }
@handler.do_find(@request, @response)
end
it "should return a serialized object when a model find call succeeds" do
@model_instance = stub('model instance')
@model_instance.expects(:render).returns "my_rendered_object"
@handler.expects(:set_response).with { |response, body, status| body == "my_rendered_object" }
@model_class.stubs(:find).returns(@model_instance)
@handler.do_find(@request, @response)
end
it "should return a 404 when no model instance can be found" do
@model_class.stubs(:name).returns "my name"
@handler.expects(:set_response).with { |response, body, status| status == 404 }
@model_class.stubs(:find).returns(nil)
@handler.do_find(@request, @response)
end
it "should serialize the result in with the appropriate format" do
@model_instance = stub('model instance')
@handler.expects(:format_to_use).returns "one"
@model_instance.expects(:render).with("one").returns "my_rendered_object"
@model_class.stubs(:find).returns(@model_instance)
@handler.do_find(@request, @response)
end
end
describe "when searching for model instances" do
before do
@handler.stubs(:http_method).returns('GET')
@handler.stubs(:path).returns('/my_handlers')
@handler.stubs(:singular?).returns(false)
@handler.stubs(:plural?).returns(true)
@handler.stubs(:request_key).returns('key')
@result1 = mock 'result1'
@result2 = mock 'results'
@result = [@result1, @result2]
@model_class.stubs(:render_multiple).returns "my rendered instances"
@model_class.stubs(:search).returns(@result)
end
it "should use a common method for determining the request parameters" do
@handler.stubs(:params).returns(:foo => :baz, :bar => :xyzzy)
@model_class.expects(:search).with do |args|
args[:foo] == :baz and args[:bar] == :xyzzy
end.returns @result
@handler.do_search(@request, @response)
end
it "should use the default status when a model search call succeeds" do
@model_class.stubs(:search).returns(@result)
@handler.do_search(@request, @response)
end
it "should set the content type to the first format returned by the accept header" do
@handler.expects(:accept_header).with(@request).returns "one,two"
@handler.expects(:set_content_type).with(@response, "one")
@handler.do_search(@request, @response)
end
it "should return a list of serialized objects when a model search call succeeds" do
@handler.expects(:accept_header).with(@request).returns "one,two"
@model_class.stubs(:search).returns(@result)
@model_class.expects(:render_multiple).with("one", @result).returns "my rendered instances"
@handler.expects(:set_response).with { |response, data| data == "my rendered instances" }
@handler.do_search(@request, @response)
end
+
+ it "should return a 404 when searching returns an empty array" do
+ @model_class.stubs(:name).returns "my name"
+ @handler.expects(:set_response).with { |response, body, status| status == 404 }
+ @model_class.stubs(:search).returns([])
+ @handler.do_search(@request, @response)
+ end
+
+ it "should return a 404 when searching returns nil" do
+ @model_class.stubs(:name).returns "my name"
+ @handler.expects(:set_response).with { |response, body, status| status == 404 }
+ @model_class.stubs(:search).returns([])
+ @handler.do_search(@request, @response)
+ end
end
describe "when destroying a model instance" do
before do
@handler.stubs(:http_method).returns('DELETE')
@handler.stubs(:path).returns('/my_handler/key')
@handler.stubs(:singular?).returns(true)
@handler.stubs(:request_key).returns('key')
@result = stub 'result', :render => "the result"
@model_class.stubs(:destroy).returns @result
end
it "should fail to destroy model if key is not specified" do
@handler.expects(:request_key).returns nil
lambda { @handler.do_destroy(@request, @response) }.should raise_error(ArgumentError)
end
it "should use a common method for determining the request parameters" do
@handler.stubs(:params).returns(:foo => :baz, :bar => :xyzzy)
@model_class.expects(:destroy).with do |key, args|
args[:foo] == :baz and args[:bar] == :xyzzy
end
@handler.do_destroy(@request, @response)
end
it "should use the default status code a model destroy call succeeds" do
@handler.expects(:set_response).with { |response, body, status| status.nil? }
@handler.do_destroy(@request, @response)
end
it "should return a yaml-encoded result when a model destroy call succeeds" do
@result = stub 'result', :to_yaml => "the result"
@model_class.expects(:destroy).returns(@result)
@handler.expects(:set_response).with { |response, body, status| body == "the result" }
@handler.do_destroy(@request, @response)
end
end
describe "when saving a model instance" do
before do
@handler.stubs(:http_method).returns('PUT')
@handler.stubs(:path).returns('/my_handler/key')
@handler.stubs(:singular?).returns(true)
@handler.stubs(:request_key).returns('key')
@handler.stubs(:body).returns('my stuff')
@result = stub 'result', :render => "the result"
@model_instance = stub('indirected model instance', :save => true)
@model_class.stubs(:convert_from).returns(@model_instance)
end
it "should use the 'body' hook to retrieve the body of the request" do
@handler.expects(:body).returns "my body"
@model_class.expects(:convert_from).with { |format, body| body == "my body" }.returns @model_instance
@handler.do_save(@request, @response)
end
it "should fail to save model if data is not specified" do
@handler.stubs(:body).returns('')
lambda { @handler.do_save(@request, @response) }.should raise_error(ArgumentError)
end
it "should use a common method for determining the request parameters" do
@handler.stubs(:params).returns(:foo => :baz, :bar => :xyzzy)
@model_instance.expects(:save).with do |args|
args[:foo] == :baz and args[:bar] == :xyzzy
end
@handler.do_save(@request, @response)
end
it "should use the default status when a model save call succeeds" do
@handler.expects(:set_response).with { |response, body, status| status.nil? }
@handler.do_save(@request, @response)
end
it "should return the yaml-serialized result when a model save call succeeds" do
@model_instance.stubs(:save).returns(@model_instance)
@model_instance.expects(:to_yaml).returns('foo')
@handler.do_save(@request, @response)
end
it "should set the content to yaml" do
@handler.expects(:set_content_type).with(@response, "yaml")
@handler.do_save(@request, @response)
end
end
end
end

File Metadata

Mime Type
text/x-diff
Expires
Fri, Nov 1, 9:30 AM (1 d, 17 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
10075920
Default Alt Text
(60 KB)

Event Timeline