diff --git a/lib/puppet/network/http/handler.rb b/lib/puppet/network/http/handler.rb index 7f6bd4111..1e5281695 100644 --- a/lib/puppet/network/http/handler.rb +++ b/lib/puppet/network/http/handler.rb @@ -1,203 +1,249 @@ module Puppet::Network::HTTP end module Puppet::Network::HTTP::Handler + + # How we map http methods and the indirection name in the URI + # to an indirection method. + METHOD_MAP = { + "GET" => { + :plural => :search, + :singular => :find + }, + "PUT" => { + :singular => :save + }, + "DELETE" => { + :singular => :destroy + } + } + 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) unless header = accept_header(request) raise ArgumentError, "An Accept header must be provided to pick the right format" end format = nil header.split(/,\s*/).each do |name| next unless format = Puppet::Network::FormatHandler.format(name) next unless format.suitable? return name end raise "No specified acceptable formats (%s) are functional on this machine" % header 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 + def uri2indirection(http_method, uri, params) + environment, indirection, key = uri.split("/", 4)[1..-1] # the first field is always nil because of the leading slash + + raise ArgumentError, "The environment must be purely alphanumeric, not '%s'" % environment unless environment =~ /^\w+$/ + raise ArgumentError, "The indirection name must be purely alphanumeric, not '%s'" % indirection unless indirection =~ /^\w+$/ + + plurality = (indirection == handler.to_s + "s") ? :plural : :singular + + unless METHOD_MAP[http_method] + raise ArgumentError, "No support for http method %s" % http_method + end + + unless method = METHOD_MAP[http_method][plurality] + raise ArgumentError, "No support for plural %s operations" % http_method + end + + indirection.sub!(/s$/, '') if plurality == :plural + + params[:environment] = environment + + key = URI.unescape(key) + + Puppet::Indirector::Request.new(indirection, method, key, params) + end + + def indirection2uri(request) + indirection = request.method == :search ? request.indirection_name.to_s + "s" : request.indirection_name.to_s + "/#{request.environment.to_s}/#{indirection}/#{request.escaped_key}#{request.query_string}" + 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] Puppet.err(exception) 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)}]") key = URI.unescape(key) 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) if key = request_key(request) key = URI.unescape(key) result = model.search(key, args) else result = model.search(args) end 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 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)}]") key = URI.unescape(key) 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 def decode_params(params) params.inject({}) do |result, ary| param, value = ary value = URI.unescape(value) if value =~ /^---/ value = YAML.load(value) else value = true if value == "true" value = false if value == "false" value = Integer(value) if value =~ /^\d+$/ value = value.to_f if value =~ /^\d+\.\d+$/ end result[param.to_sym] = value result end end end diff --git a/lib/puppet/node/environment.rb b/lib/puppet/node/environment.rb index 57dc23e9d..445439aa3 100644 --- a/lib/puppet/node/environment.rb +++ b/lib/puppet/node/environment.rb @@ -1,64 +1,68 @@ # Model the environment that a node can operate in. This class just # provides a simple wrapper for the functionality around environments. class Puppet::Node::Environment @seen = {} # Return an existing environment instance, or create a new one. def self.new(name = nil) name ||= Puppet.settings.value(:environment) raise ArgumentError, "Environment name must be specified" unless name symbol = name.to_sym return @seen[symbol] if @seen[symbol] obj = self.allocate obj.send :initialize, symbol @seen[symbol] = obj end attr_reader :name # Return an environment-specific setting. def [](param) Puppet.settings.value(param, self.name) end def initialize(name) @name = name end def module(name) Puppet::Module.each_module(modulepath) do |mod| return mod if mod.name == name end return nil end def modulepath dirs = self[:modulepath].split(File::PATH_SEPARATOR) if ENV["PUPPETLIB"] dirs = ENV["PUPPETLIB"].split(File::PATH_SEPARATOR) + dirs end dirs.collect do |dir| if dir !~ /^#{File::SEPARATOR}/ File.join(Dir.getwd, dir) else dir end end.find_all do |p| p =~ /^#{File::SEPARATOR}/ && FileTest.directory?(p) end end # Return all modules from this environment. def modules result = [] Puppet::Module.each_module(modulepath) do |mod| result << mod end result end + + def to_s + name.to_s + end end diff --git a/spec/unit/network/http/handler.rb b/spec/unit/network/http/handler.rb index 6f1a57b8b..fc73cc131 100755 --- a/spec/unit/network/http/handler.rb +++ b/spec/unit/network/http/handler.rb @@ -1,499 +1,600 @@ #!/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 be able to convert a URI into a request" do + @handler.should respond_to(:uri2indirection) + end + + it "should be able to convert a request into a URI" do + @handler.should respond_to(:indirection2uri) + end + + describe "when converting a URI into a request" do + before do + @handler.stubs(:handler).returns "foo" + end + + it "should require the http method, the URI, and the query parameters" do + # Not a terribly useful test, but an important statement for the spec + lambda { @handler.uri2indirection("/foo") }.should raise_error(ArgumentError) + end + + it "should use the first field of the URI as the environment" do + @handler.uri2indirection("GET", "/env/foo/bar", {}).environment.should == Puppet::Node::Environment.new("env") + end + + it "should fail if the environment is not alphanumeric" do + lambda { @handler.uri2indirection("GET", "/env ness/foo/bar", {}) }.should raise_error(ArgumentError) + end + + it "should use the environment from the URI even if one is specified in the parameters" do + @handler.uri2indirection("GET", "/env/foo/bar", {:environment => "otherenv"}).environment.should == Puppet::Node::Environment.new("env") + end + + it "should use the second field of the URI as the indirection name" do + @handler.uri2indirection("GET", "/env/foo/bar", {}).indirection_name.should == :foo + end + + it "should fail if the indirection name is not alphanumeric" do + lambda { @handler.uri2indirection("GET", "/env/foo ness/bar", {}) }.should raise_error(ArgumentError) + end + + it "should use the remainder of the URI as the indirection key" do + @handler.uri2indirection("GET", "/env/foo/bar", {}).key.should == "bar" + end + + it "should support the indirection key being a /-separated file path" do + @handler.uri2indirection("GET", "/env/foo/bee/baz/bomb", {}).key.should == "bee/baz/bomb" + end + + it "should choose 'find' as the indirection method if the http method is a GET and the indirection name is singular" do + @handler.uri2indirection("GET", "/env/foo/bar", {}).method.should == :find + end + + it "should choose 'search' as the indirection method if the http method is a GET and the indirection name is plural" do + @handler.uri2indirection("GET", "/env/foos/bar", {}).method.should == :search + end + + it "should choose 'delete' as the indirection method if the http method is a DELETE and the indirection name is singular" do + @handler.uri2indirection("DELETE", "/env/foo/bar", {}).method.should == :destroy + end + + it "should choose 'save' as the indirection method if the http method is a PUT and the indirection name is singular" do + @handler.uri2indirection("PUT", "/env/foo/bar", {}).method.should == :save + end + + it "should fail if an indirection method cannot be picked" do + lambda { @handler.uri2indirection("UPDATE", "/env/foo/bar", {}) }.should raise_error(ArgumentError) + end + + it "should URI unescape the indirection key" do + escaped = URI.escape("foo bar") + @handler.uri2indirection("GET", "/env/foo/#{escaped}", {}).key.should == "foo bar" + end + end + + describe "when converting a request into a URI" do + before do + @request = Puppet::Indirector::Request.new(:foo, :find, "with spaces", :foo => :bar, :environment => "myenv") + end + + it "should use the environment as the first field of the URI" do + @handler.indirection2uri(@request).split("/")[1].should == "myenv" + end + + it "should use the indirection as the second field of the URI" do + @handler.indirection2uri(@request).split("/")[2].should == "foo" + end + + it "should pluralize the indirection name if the method is 'search'" do + @request.stubs(:method).returns :search + @handler.indirection2uri(@request).split("/")[2].should == "foos" + end + + it "should use the escaped key as the remainder of the URI" do + escaped = URI.escape("with spaces") + @handler.indirection2uri(@request).split("/")[3].sub(/\?.+/, '').should == escaped + end + + it "should add the query string to the URI" do + @request.expects(:query_string).returns "?query" + @handler.indirection2uri(@request).should =~ /\?query$/ + end + 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 @format = stub 'format', :suitable? => true Puppet::Network::FormatHandler.stubs(:format).returns @format end it "should fail if the 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 the escaped request key" do @handler.stubs(:request_key).returns URI.escape("my key") @model_class.expects(:find).with do |key, args| key == "my key" end.returns @result @handler.do_find(@request, @response) 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 fail if no accept header is provided" do @handler.expects(:accept_header).with(@request).returns nil lambda { @handler.do_find(@request, @response) }.should raise_error(ArgumentError) end it "should fail if the accept header does not contain a valid format" do @handler.expects(:accept_header).with(@request).returns "" lambda { @handler.do_find(@request, @response) }.should raise_error(RuntimeError) end it "should not use an unsuitable format" do @handler.expects(:accept_header).with(@request).returns "foo,bar" foo = mock 'foo', :suitable? => false bar = mock 'bar', :suitable? => true Puppet::Network::FormatHandler.expects(:format).with("foo").returns foo Puppet::Network::FormatHandler.expects(:format).with("bar").returns bar @handler.expects(:set_content_type).with(@response, "bar") # the suitable 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) @format = stub 'format', :suitable? => true Puppet::Network::FormatHandler.stubs(:format).returns @format 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 |key, args| args[:foo] == :baz and args[:bar] == :xyzzy end.returns @result @handler.do_search(@request, @response) end it "should use an escaped request key if one is provided" do @handler.expects(:request_key).with(@request).returns URI.escape("foo bar") @model_class.expects(:search).with { |key, args| key == "foo bar" }.returns @result @handler.do_search(@request, @response) end it "should work with no request key if none is provided" do @handler.expects(:request_key).with(@request).returns nil @model_class.expects(:search).with { |args| args.is_a?(Hash) }.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 the escaped request key to destroy the instance in the model" do @handler.expects(:request_key).returns URI.escape("foo bar") @model_class.expects(:destroy).with do |key, args| key == "foo bar" end @handler.do_destroy(@request, @response) 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) @format = stub 'format', :suitable? => true Puppet::Network::FormatHandler.stubs(:format).returns @format 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 diff --git a/spec/unit/node/environment.rb b/spec/unit/node/environment.rb index 20889f071..dd6745f1e 100755 --- a/spec/unit/node/environment.rb +++ b/spec/unit/node/environment.rb @@ -1,117 +1,121 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../spec_helper' require 'puppet/node/environment' require 'puppet/util/execution' describe Puppet::Node::Environment do it "should use the default environment if no name is provided while initializing an environment" do Puppet.settings.expects(:value).with(:environment).returns("one") Puppet::Node::Environment.new().name.should == :one end it "should treat environment instances as singletons" do Puppet::Node::Environment.new("one").should equal(Puppet::Node::Environment.new("one")) end it "should treat an environment specified as names or strings as equivalent" do Puppet::Node::Environment.new(:one).should equal(Puppet::Node::Environment.new("one")) end + it "should return its name when converted to a string" do + Puppet::Node::Environment.new(:one).to_s.should == "one" + end + it "should consider its module path to be the environment-specific modulepath setting" do FileTest.stubs(:directory?).returns true env = Puppet::Node::Environment.new("testing") module_path = %w{/one /two}.join(File::PATH_SEPARATOR) env.expects(:[]).with(:modulepath).returns module_path env.modulepath.should == %w{/one /two} end it "should prefix the value of the 'PUPPETLIB' environment variable to the module path if present" do FileTest.stubs(:directory?).returns true Puppet::Util::Execution.withenv("PUPPETLIB" => %w{/l1 /l2}.join(File::PATH_SEPARATOR)) do env = Puppet::Node::Environment.new("testing") module_path = %w{/one /two}.join(File::PATH_SEPARATOR) env.expects(:[]).with(:modulepath).returns module_path env.modulepath.should == %w{/l1 /l2 /one /two} end end it "should not return non-directories in the module path" do env = Puppet::Node::Environment.new("testing") module_path = %w{/one /two}.join(File::PATH_SEPARATOR) env.expects(:[]).with(:modulepath).returns module_path FileTest.expects(:directory?).with("/one").returns true FileTest.expects(:directory?).with("/two").returns false env.modulepath.should == %w{/one} end it "should use the current working directory to fully-qualify unqualified paths" do FileTest.stubs(:directory?).returns true env = Puppet::Node::Environment.new("testing") module_path = %w{/one two}.join(File::PATH_SEPARATOR) env.expects(:[]).with(:modulepath).returns module_path two = File.join(Dir.getwd, "two") env.modulepath.should == ["/one", two] end describe "when modeling a specific environment" do it "should have a method for returning the environment name" do Puppet::Node::Environment.new("testing").name.should == :testing end it "should provide an array-like accessor method for returning any environment-specific setting" do env = Puppet::Node::Environment.new("testing") env.should respond_to(:[]) end it "should ask the Puppet settings instance for the setting qualified with the environment name" do Puppet.settings.expects(:value).with("myvar", :testing).returns("myval") env = Puppet::Node::Environment.new("testing") env["myvar"].should == "myval" end it "should be able to return its modules" do Puppet::Node::Environment.new("testing").should respond_to(:modules) end it "should return each module from the environment-specific module path when asked for its modules" do env = Puppet::Node::Environment.new("testing") module_path = %w{/one /two}.join(File::PATH_SEPARATOR) env.expects(:modulepath).returns module_path Puppet::Module.expects(:each_module).with(module_path).multiple_yields("mod1", "mod2") env.modules.should == %w{mod1 mod2} end it "should be able to return an individual module by matching the module name" do env = Puppet::Node::Environment.new("testing") module_path = %w{/one /two}.join(File::PATH_SEPARATOR) env.expects(:modulepath).returns module_path one = stub 'one', :name => 'one' two = stub 'two', :name => 'two' Puppet::Module.expects(:each_module).with(module_path).multiple_yields(one, two) env.module("two").should equal(two) end it "should return nil if asked for a module that is not in its path" do env = Puppet::Node::Environment.new("testing") module_path = %w{/one /two}.join(File::PATH_SEPARATOR) env.expects(:modulepath).returns module_path one = stub 'one', :name => 'one' two = stub 'two', :name => 'two' Puppet::Module.expects(:each_module).with(module_path).multiple_yields(one, two) env.module("three").should be_nil end end end