Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F16570641
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
60 KB
Referenced Files
None
Subscribers
None
View Options
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
Details
Attached
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)
Attached To
Mode
rPU puppet
Attached
Detach File
Event Timeline
Log In to Comment