diff options
author | Luke Kanies <luke@madstop.com> | 2008-04-11 15:21:50 -0500 |
---|---|---|
committer | Luke Kanies <luke@madstop.com> | 2008-04-11 15:21:50 -0500 |
commit | d834242db13a827a34340c5f2e51c8df532d5196 (patch) | |
tree | cdd98ee63103082a81dac7e91e13b60458685780 | |
parent | fb05ef3c96038d67a46eb142202af186ad6cb0b3 (diff) | |
parent | d9846fc3f06f61fcb4b8806740f77747a7f6939e (diff) | |
download | puppet-d834242db13a827a34340c5f2e51c8df532d5196.tar.gz puppet-d834242db13a827a34340c5f2e51c8df532d5196.tar.xz puppet-d834242db13a827a34340c5f2e51c8df532d5196.zip |
Merge branch '0.24.x'
Conflicts:
spec/unit/network/server.rb
23 files changed, 1407 insertions, 449 deletions
diff --git a/lib/puppet/indirector/indirection.rb b/lib/puppet/indirector/indirection.rb index 15358a801..05464f8c9 100644 --- a/lib/puppet/indirector/indirection.rb +++ b/lib/puppet/indirector/indirection.rb @@ -225,14 +225,14 @@ class Puppet::Indirector::Indirection request = request(:destroy, key, *args) terminus = prepare(request) - terminus.destroy(request) + result = terminus.destroy(request) if cache? and cached = cache.find(request(:find, key, *args)) # Reuse the existing request, since it's equivalent. cache.destroy(request) end - nil + result end # Search for more than one instance. Should always return an array. @@ -242,7 +242,6 @@ class Puppet::Indirector::Indirection if result = terminus.search(request) raise Puppet::DevError, "Search results from terminus %s are not an array" % terminus.name unless result.is_a?(Array) - result.each do |instance| instance.expiration ||= self.expiration end diff --git a/lib/puppet/indirector/node/rest.rb b/lib/puppet/indirector/node/rest.rb index c5d2f97fb..d8b75f6e7 100644 --- a/lib/puppet/indirector/node/rest.rb +++ b/lib/puppet/indirector/node/rest.rb @@ -1,7 +1,7 @@ require 'puppet/node' require 'puppet/indirector/rest' -class Puppet::Node::REST < Puppet::Indirector::REST +class Puppet::Node::Rest < Puppet::Indirector::REST desc "This will eventually be a REST-based mechanism for finding nodes. It is currently non-functional." # TODO/FIXME end diff --git a/lib/puppet/indirector/rest.rb b/lib/puppet/indirector/rest.rb index 7b7c932c4..d33150fc2 100644 --- a/lib/puppet/indirector/rest.rb +++ b/lib/puppet/indirector/rest.rb @@ -1,8 +1,56 @@ -require 'puppet/indirector/rest' +require 'net/http' +require 'uri' # Access objects via REST class Puppet::Indirector::REST < Puppet::Indirector::Terminus - def find(name, options = {}) - indirection.model.new(name) + + def rest_connection_details + { :host => Puppet[:server], :port => Puppet[:masterport].to_i } + end + + def network_fetch(path) + network {|conn| conn.get("/#{path}").body } + end + + def network_delete(path) + network {|conn| conn.delete("/#{path}").body } + end + + def network_put(path, data) + network {|conn| conn.put("/#{path}", data).body } + end + + def find(request) + network_result = network_fetch("#{indirection.name}/#{request.key}") + raise YAML.load(network_result) if exception?(network_result) + indirection.model.from_yaml(network_result) + end + + def search(request) + network_results = network_fetch("#{indirection.name}s/#{request.key}") + raise YAML.load(network_results) if exception?(network_results) + YAML.load(network_results.to_s).collect {|result| indirection.model.from_yaml(result) } + end + + def destroy(request) + network_result = network_delete("#{indirection.name}/#{request.key}") + raise YAML.load(network_result) if exception?(network_result) + YAML.load(network_result.to_s) + end + + def save(request) + network_result = network_put("#{indirection.name}/", request.instance.to_yaml) + raise YAML.load(network_result) if exception?(network_result) + indirection.model.from_yaml(network_result) + end + + private + + def network(&block) + Net::HTTP.start(rest_connection_details[:host], rest_connection_details[:port]) {|conn| yield(conn) } + end + + def exception?(yaml_string) + yaml_string =~ %r{--- !ruby/exception} end end diff --git a/lib/puppet/network/http/handler.rb b/lib/puppet/network/http/handler.rb index 773381c8d..7113c92d3 100644 --- a/lib/puppet/network/http/handler.rb +++ b/lib/puppet/network/http/handler.rb @@ -1,9 +1,9 @@ -class Puppet::Network::HTTP::Handler - def initialize(args = {}) +module Puppet::Network::HTTP::Handler + + def initialize_for_puppet(args = {}) raise ArgumentError unless @server = args[:server] raise ArgumentError unless @handler = args[:handler] @model = find_model_for_handler(@handler) - register_handler end # handle an HTTP request @@ -18,38 +18,46 @@ class Puppet::Network::HTTP::Handler end private + + def model + @model + end def do_find(request, response) key = request_key(request) || raise(ArgumentError, "Could not locate lookup key in request path [#{path}]") args = params(request) - result = @model.find(key, args).to_yaml + result = model.find(key, args).to_yaml encode_result(request, response, result) end def do_search(request, response) args = params(request) - result = @model.search(args).collect {|obj| obj.to_yaml } + result = model.search(args).collect {|result| result.to_yaml }.to_yaml encode_result(request, response, result) end def do_destroy(request, response) key = request_key(request) || raise(ArgumentError, "Could not locate lookup key in request path [#{path}]") args = params(request) - result = @model.destroy(key, args) + result = model.destroy(key, args) encode_result(request, response, YAML.dump(result)) end def do_save(request, response) - data = body(request) + data = body(request).to_s raise ArgumentError, "No data to save" if !data or data.empty? - args = params(request) - obj = @model.new - result = obj.save(args.merge(:data => data)).to_yaml + # args = params(request) + obj = model.from_yaml(data) + result = save_object(obj).to_yaml encode_result(request, response, result) end + + def save_object(obj) + obj.save + end def do_exception(request, response, exception, status=404) - encode_result(request, response, exception.to_s, status) + encode_result(request, response, exception.to_yaml, status) end def find_model_for_handler(handler) @@ -77,7 +85,7 @@ class Puppet::Network::HTTP::Handler %r{/#{@handler.to_s}s$}.match(path(request)) end - # methods specific to a given web server + # methods to be overridden by the including web server class def register_handler raise NotImplementedError diff --git a/lib/puppet/network/http/mongrel.rb b/lib/puppet/network/http/mongrel.rb index 8ea669531..9a4531c7a 100644 --- a/lib/puppet/network/http/mongrel.rb +++ b/lib/puppet/network/http/mongrel.rb @@ -1,7 +1,6 @@ require 'mongrel' if Puppet.features.mongrel? require 'puppet/network/http/mongrel/rest' -require 'puppet/network/http/mongrel/xmlrpc' class Puppet::Network::HTTP::Mongrel def initialize(args = {}) @@ -14,20 +13,20 @@ class Puppet::Network::HTTP::Mongrel raise ArgumentError, ":address must be specified." unless args[:address] raise ArgumentError, ":port must be specified." unless args[:port] raise "Mongrel server is already listening" if listening? - + @protocols = args[:protocols] @handlers = args[:handlers] - @server = Mongrel::HttpServer.new(args[:address], args[:port]) - + @server = Mongrel::HttpServer.new(args[:address], args[:port]) setup_handlers - @server.run @listening = true + @server.run end def unlisten raise "Mongrel server is not listening" unless listening? - @server.graceful_shutdown + @server.stop + @server = nil @listening = false end @@ -39,16 +38,16 @@ class Puppet::Network::HTTP::Mongrel def setup_handlers @protocols.each do |protocol| + klass = class_for_protocol(protocol) @handlers.each do |handler| - class_for_protocol(protocol).new(:server => @server, :handler => handler) + @server.register('/' + handler.to_s, klass.new(:server => @server, :handler => handler)) + @server.register('/' + handler.to_s + 's', klass.new(:server => @server, :handler => handler)) end end end - # TODO/FIXME: need a spec which forces delegation to the real class def class_for_protocol(protocol) return Puppet::Network::HTTP::MongrelREST if protocol.to_sym == :rest - return Puppet::Network::HTTP::MongrelXMLRPC if protocol.to_sym == :xmlrpc raise ArgumentError, "Unknown protocol [#{protocol}]." end end diff --git a/lib/puppet/network/http/mongrel/rest.rb b/lib/puppet/network/http/mongrel/rest.rb index 6c24e360c..2a3d4f143 100644 --- a/lib/puppet/network/http/mongrel/rest.rb +++ b/lib/puppet/network/http/mongrel/rest.rb @@ -1,36 +1,44 @@ require 'puppet/network/http/handler' -class Puppet::Network::HTTP::MongrelREST < Puppet::Network::HTTP::Handler +class Puppet::Network::HTTP::MongrelREST < Mongrel::HttpHandler + + include Puppet::Network::HTTP::Handler + + def initialize(args={}) + super() + initialize_for_puppet(args) + end private - - def register_handler - @server.register('/' + @handler.to_s, self) - @server.register('/' + @handler.to_s + 's', self) - end - + + # which HTTP verb was used in this request def http_method(request) request.params[Mongrel::Const::REQUEST_METHOD] end + # what path was requested? def path(request) # LAK:NOTE See http://snurl.com/21zf8 [groups_google_com] x = '/' + request.params[Mongrel::Const::REQUEST_PATH].split('/')[1] end + # return the key included in the request path def request_key(request) # LAK:NOTE See http://snurl.com/21zf8 [groups_google_com] x = request.params[Mongrel::Const::REQUEST_PATH].split('/')[2] end + # return the request body def body(request) request.body end + # return the query params for this request def params(request) Mongrel::HttpRequest.query_parse(request.params["QUERY_STRING"]) end + # produce the body of the response def encode_result(request, response, result, status = 200) response.start(status) do |head, body| body.write(result) diff --git a/lib/puppet/network/http/mongrel/xmlrpc.rb b/lib/puppet/network/http/mongrel/xmlrpc.rb deleted file mode 100644 index 92acd4f0e..000000000 --- a/lib/puppet/network/http/mongrel/xmlrpc.rb +++ /dev/null @@ -1,4 +0,0 @@ -class Puppet::Network::HTTP::MongrelXMLRPC - def initialize(args = {}) - end -end diff --git a/lib/puppet/network/http/webrick.rb b/lib/puppet/network/http/webrick.rb index c4b2ed3c6..3a37e2071 100644 --- a/lib/puppet/network/http/webrick.rb +++ b/lib/puppet/network/http/webrick.rb @@ -1,11 +1,17 @@ require 'webrick' require 'webrick/https' require 'puppet/network/http/webrick/rest' -require 'puppet/network/http/webrick/xmlrpc' +require 'thread' class Puppet::Network::HTTP::WEBrick def initialize(args = {}) @listening = false + @mutex = Mutex.new + end + + def self.class_for_protocol(protocol) + return Puppet::Network::HTTP::WEBrickREST if protocol.to_sym == :rest + raise "Unknown protocol [#{protocol}]." end def listen(args = {}) @@ -13,39 +19,44 @@ class Puppet::Network::HTTP::WEBrick raise ArgumentError, ":protocols must be specified." if !args[:protocols] or args[:protocols].empty? raise ArgumentError, ":address must be specified." unless args[:address] raise ArgumentError, ":port must be specified." unless args[:port] - raise "WEBrick server is already listening" if listening? @protocols = args[:protocols] @handlers = args[:handlers] @server = WEBrick::HTTPServer.new(:BindAddress => args[:address], :Port => args[:port]) setup_handlers - @server.start - @listening = true + + @mutex.synchronize do + raise "WEBrick server is already listening" if @listening + @listening = true + @thread = Thread.new { @server.start } + end end def unlisten - raise "WEBrick server is not listening" unless listening? - @server.shutdown - @listening = false + @mutex.synchronize do + raise "WEBrick server is not listening" unless @listening + @server.shutdown + @thread.join + @server = nil + @listening = false + end end def listening? - @listening + @mutex.synchronize do + @listening + end end - + private def setup_handlers @protocols.each do |protocol| + klass = self.class.class_for_protocol(protocol) @handlers.each do |handler| - class_for_protocol(protocol).new(:server => @server, :handler => handler) + @server.mount('/' + handler.to_s, klass, handler) + @server.mount('/' + handler.to_s + 's', klass, handler) end end end - - def class_for_protocol(protocol) - return Puppet::Network::HTTP::WEBrickREST if protocol.to_sym == :rest - return Puppet::Network::HTTP::WEBrickXMLRPC if protocol.to_sym == :xmlrpc - raise ArgumentError, "Unknown protocol [#{protocol}]." - end end diff --git a/lib/puppet/network/http/webrick/rest.rb b/lib/puppet/network/http/webrick/rest.rb index 8cda079e2..b43912196 100644 --- a/lib/puppet/network/http/webrick/rest.rb +++ b/lib/puppet/network/http/webrick/rest.rb @@ -1,19 +1,22 @@ require 'puppet/network/http/handler' -class Puppet::Network::HTTP::WEBrickREST < Puppet::Network::HTTP::Handler +class Puppet::Network::HTTP::WEBrickREST < WEBrick::HTTPServlet::AbstractServlet + + include Puppet::Network::HTTP::Handler + + def initialize(server, handler) + raise ArgumentError, "server is required" unless server + super(server) + initialize_for_puppet(:server => server, :handler => handler) + end # WEBrick uses a service() method to respond to requests. Simply delegate to the handler response() method. def service(request, response) process(request, response) end - + private - def register_handler - @server.mount('/' + @handler.to_s, self) - @server.mount('/' + @handler.to_s + 's', self) - end - def http_method(request) request.request_method end diff --git a/lib/puppet/network/http/webrick/xmlrpc.rb b/lib/puppet/network/http/webrick/xmlrpc.rb deleted file mode 100644 index 793708f8a..000000000 --- a/lib/puppet/network/http/webrick/xmlrpc.rb +++ /dev/null @@ -1,4 +0,0 @@ -class Puppet::Network::HTTP::WEBrickXMLRPC - def initialize(args = {}) - end -end diff --git a/lib/puppet/network/server.rb b/lib/puppet/network/server.rb index f2c8dc18c..cab14519b 100644 --- a/lib/puppet/network/server.rb +++ b/lib/puppet/network/server.rb @@ -10,44 +10,44 @@ class Puppet::Network::Server raise(ArgumentError, "Must specify :address or configure Puppet :bindaddress.") @port = args[:port] || Puppet[:masterport] || raise(ArgumentError, "Must specify :port or configure Puppet :masterport") - @protocols = [] - @listening = false - @routes = {} - self.register(args[:handlers]) if args[:handlers] + @protocols = [ :rest ] + @listening = false + @routes = {} + self.register(args[:handlers]) if args[:handlers] end def register(*indirections) - raise ArgumentError, "Indirection names are required." if indirections.empty? - indirections.flatten.each { |i| @routes[i.to_sym] = true } + raise ArgumentError, "Indirection names are required." if indirections.empty? + indirections.flatten.each { |i| @routes[i.to_sym] = true } end def unregister(*indirections) - raise "Cannot unregister indirections while server is listening." if listening? - indirections = @routes.keys if indirections.empty? - - indirections.flatten.each do |i| - raise(ArgumentError, "Indirection [%s] is unknown." % i) unless @routes[i.to_sym] - end + raise "Cannot unregister indirections while server is listening." if listening? + indirections = @routes.keys if indirections.empty? + + indirections.flatten.each do |i| + raise(ArgumentError, "Indirection [%s] is unknown." % i) unless @routes[i.to_sym] + end - indirections.flatten.each do |i| - @routes.delete(i.to_sym) - end + indirections.flatten.each do |i| + @routes.delete(i.to_sym) + end end def listening? - @listening + @listening end def listen - raise "Cannot listen -- already listening." if listening? - http_server.listen(@routes.dup) - @listening = true + raise "Cannot listen -- already listening." if listening? + @listening = true + http_server.listen(:address => address, :port => port, :handlers => @routes.keys, :protocols => protocols) end def unlisten - raise "Cannot unlisten -- not currently listening." unless listening? - http_server.unlisten - @listening = false + raise "Cannot unlisten -- not currently listening." unless listening? + http_server.unlisten + @listening = false end def http_server_class diff --git a/spec/integration/indirector/rest.rb b/spec/integration/indirector/rest.rb new file mode 100644 index 000000000..cafcce713 --- /dev/null +++ b/spec/integration/indirector/rest.rb @@ -0,0 +1,460 @@ +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 + describe "when using webrick" do + before :each do + Puppet[:servertype] = 'webrick' + @params = { :address => "127.0.0.1", :port => 34343, :handlers => [ :test_indirected_foo ] } + @server = Puppet::Network::Server.new(@params) + @server.listen + + # the autoloader was clearly not written test-first. We subvert the integration test to get around its bullshit. + Puppet::Indirector::Terminus.stubs(:terminus_class).returns(Puppet::TestIndirectedFoo::Rest) + Puppet::TestIndirectedFoo.indirection.stubs(:terminus_class).returns :rest + + # Stub the connection information. + Puppet::TestIndirectedFoo.indirection.terminus(:rest).stubs(:rest_connection_details).returns(:host => "localhost", :port => 34343) + 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 = stub('faked model', :find => @model_instance) + Puppet::Network::HTTP::WEBrickREST.any_instance.stubs(:model).returns(@mock_model) + end + + it "should not fail" do + Puppet::TestIndirectedFoo.find('bar') + 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 + end + + describe "when no matching model instance can be found" do + before :each do + @mock_model = stub('faked model', :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(RuntimeError) + 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 = stub('faked model', :search => @model_instances) + Puppet::Network::HTTP::WEBrickREST.any_instance.stubs(:model).returns(@mock_model) + 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(&:value).should == @model_instances.collect(&:value) + end + + it 'should set a version timestamp on model instances' do + pending("Luke looking at why this version magic might not be working") do + Puppet::TestIndirectedFoo.search('bar').each do |result| + result.version.should_not be_nil + end + end + end + end + + describe "when no matching model instance can be found" do + before :each do + @mock_model = stub('faked model', :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(RuntimeError) + 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 = stub('faked model', :destroy => true) + Puppet::Network::HTTP::WEBrickREST.any_instance.stubs(:model).returns(@mock_model) + 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 = stub('faked model', :destroy => false) + Puppet::Network::HTTP::WEBrickREST.any_instance.stubs(:model).returns(@mock_model) + 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 = stub('faked model') + @mock_model.stubs(:destroy).raises(RuntimeError) + Puppet::Network::HTTP::WEBrickREST.any_instance.stubs(:model).returns(@mock_model) + end + + it "should raise an exception" do + lambda { Puppet::TestIndirectedFoo.destroy('bar') }.should raise_error(RuntimeError) + end + end + end + + describe "when saving a model instance over REST" do + before :each do + @instance = Puppet::TestIndirectedFoo.new(42) + @mock_model = stub('faked model', :from_yaml => @instance) + Puppet::Network::HTTP::WEBrickREST.any_instance.stubs(:model).returns(@mock_model) + 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(RuntimeError) + 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 ] } + @server = Puppet::Network::Server.new(@params) + @server.listen + + # the autoloader was clearly not written test-first. We subvert the integration test to get around its bullshit. + Puppet::Indirector::Terminus.stubs(:terminus_class).returns(Puppet::TestIndirectedFoo::Rest) + Puppet::TestIndirectedFoo.indirection.stubs(:terminus_class).returns :rest + + # Stub the connection information. + Puppet::TestIndirectedFoo.indirection.terminus(:rest).stubs(:rest_connection_details).returns(:host => "localhost", :port => 34343) + 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 = stub('faked model', :find => @model_instance) + Puppet::Network::HTTP::MongrelREST.any_instance.stubs(:model).returns(@mock_model) + 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 + end + + describe "when no matching model instance can be found" do + before :each do + @mock_model = stub('faked model', :find => nil) + Puppet::Network::HTTP::MongrelREST.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::MongrelREST.any_instance.stubs(:model).returns(@mock_model) + end + + it "should raise an exception" do + lambda { Puppet::TestIndirectedFoo.find('bar') }.should raise_error(RuntimeError) + 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 = stub('faked model', :search => @model_instances) + Puppet::Network::HTTP::MongrelREST.any_instance.stubs(:model).returns(@mock_model) + 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(&:value).should == @model_instances.collect(&: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 = stub('faked model', :find => nil) + Puppet::Network::HTTP::MongrelREST.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::MongrelREST.any_instance.stubs(:model).returns(@mock_model) + end + + it "should raise an exception" do + lambda { Puppet::TestIndirectedFoo.find('bar') }.should raise_error(RuntimeError) + 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 = stub('faked model', :destroy => true) + Puppet::Network::HTTP::MongrelREST.any_instance.stubs(:model).returns(@mock_model) + 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 = stub('faked model', :destroy => false) + Puppet::Network::HTTP::MongrelREST.any_instance.stubs(:model).returns(@mock_model) + 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 = stub('faked model') + @mock_model.stubs(:destroy).raises(RuntimeError) + Puppet::Network::HTTP::MongrelREST.any_instance.stubs(:model).returns(@mock_model) + end + + it "should raise an exception" do + lambda { Puppet::TestIndirectedFoo.destroy('bar') }.should raise_error(RuntimeError) + end + end + end + + describe "when saving a model instance over REST" do + before :each do + @instance = Puppet::TestIndirectedFoo.new(42) + @mock_model = stub('faked model', :from_yaml => @instance) + Puppet::Network::HTTP::MongrelREST.any_instance.stubs(:model).returns(@mock_model) + 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(RuntimeError) + end + end + end + + after :each do + @server.unlisten + end + end +end diff --git a/spec/integration/network/server/mongrel.rb b/spec/integration/network/server/mongrel.rb new file mode 100644 index 000000000..65caf78c9 --- /dev/null +++ b/spec/integration/network/server/mongrel.rb @@ -0,0 +1,46 @@ +require File.dirname(__FILE__) + '/../../../spec_helper' +require 'puppet/network/server' +require 'socket' + +describe Puppet::Network::Server do + 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 => 34346, :handlers => [ :node ] } + @server = Puppet::Network::Server.new(@params) + end + + describe "before listening" do + it "should not be reachable at the specified address and port" do + lambda { TCPSocket.new('127.0.0.1', 34346) }.should raise_error(Errno::ECONNREFUSED) + end + end + + describe "when listening" do + it "should be reachable on the specified address and port" do + @server.listen + lambda { TCPSocket.new('127.0.0.1', 34346) }.should_not raise_error + end + + it "should not allow multiple servers to listen on the same address and port" do + @server.listen + @server2 = Puppet::Network::Server.new(@params) + lambda { @server2.listen }.should raise_error + end + end + + describe "after unlistening" do + it "should not be reachable on the port and address assigned" do + @server.listen + @server.unlisten + lambda { TCPSocket.new('127.0.0.1', 34346) }.should raise_error(Errno::ECONNREFUSED) + end + end + + after :each do + @server.unlisten if @server.listening? + end + end +end
\ No newline at end of file diff --git a/spec/integration/network/server/webrick.rb b/spec/integration/network/server/webrick.rb new file mode 100644 index 000000000..0ab8d89e4 --- /dev/null +++ b/spec/integration/network/server/webrick.rb @@ -0,0 +1,46 @@ +require File.dirname(__FILE__) + '/../../../spec_helper' +require 'puppet/network/server' +require 'socket' + +describe Puppet::Network::Server do + describe "when using webrick" do + before :each do + Puppet[:servertype] = 'webrick' + @params = { :address => "127.0.0.1", :port => 34343, :handlers => [ :node ] } + end + + describe "before listening" do + it "should not be reachable at the specified address and port" do + lambda { TCPSocket.new('127.0.0.1', 34343) }.should raise_error + end + end + + describe "when listening" do + it "should be reachable on the specified address and port" do + @server = Puppet::Network::Server.new(@params.merge(:port => 34343)) + @server.listen + lambda { TCPSocket.new('127.0.0.1', 34343) }.should_not raise_error + end + + it "should not allow multiple servers to listen on the same address and port" do + @server = Puppet::Network::Server.new(@params.merge(:port => 34343)) + @server.listen + @server2 = Puppet::Network::Server.new(@params.merge(:port => 34343)) + lambda { @server2.listen }.should raise_error + end + + after :each do + @server.unlisten if @server.listening? + end + end + + describe "after unlistening" do + it "should not be reachable on the port and address assigned" do + @server = Puppet::Network::Server.new(@params.merge(:port => 34343)) + @server.listen + @server.unlisten + lambda { TCPSocket.new('127.0.0.1', 34343) }.should raise_error(Errno::ECONNREFUSED) + end + end + end +end
\ No newline at end of file diff --git a/spec/spec.opts b/spec/spec.opts index 2f9bf0d0a..695852c2f 100644 --- a/spec/spec.opts +++ b/spec/spec.opts @@ -1,3 +1,5 @@ +--format +s --colour --loadby mtime diff --git a/spec/unit/indirector/indirection.rb b/spec/unit/indirector/indirection.rb index e8ab9633b..cefd0557e 100755 --- a/spec/unit/indirector/indirection.rb +++ b/spec/unit/indirector/indirection.rb @@ -356,9 +356,9 @@ describe Puppet::Indirector::Indirection do it_should_behave_like "Indirection Delegator" it_should_behave_like "Delegation Authorizer" - it "should return nil" do - @terminus.stubs(:destroy) - @indirection.destroy("/my/key").should be_nil + it "should return the result of removing the instance" do + @terminus.stubs(:destroy).returns "yayness" + @indirection.destroy("/my/key").should == "yayness" end describe "when caching is enabled" do diff --git a/spec/unit/indirector/node/rest.rb b/spec/unit/indirector/node/rest.rb index 33cfda426..3b99e33c4 100755 --- a/spec/unit/indirector/node/rest.rb +++ b/spec/unit/indirector/node/rest.rb @@ -4,9 +4,9 @@ require File.dirname(__FILE__) + '/../../../spec_helper' require 'puppet/indirector/node/rest' -describe Puppet::Node::REST do +describe Puppet::Node::Rest do before do - @searcher = Puppet::Node::REST.new + @searcher = Puppet::Node::Rest.new end diff --git a/spec/unit/indirector/rest.rb b/spec/unit/indirector/rest.rb index 3c4716dc8..1a1064491 100755 --- a/spec/unit/indirector/rest.rb +++ b/spec/unit/indirector/rest.rb @@ -4,11 +4,11 @@ require File.dirname(__FILE__) + '/../../spec_helper' require 'puppet/indirector/rest' describe Puppet::Indirector::REST do - # FIXME : TODO / look through this, does this make sense? before do Puppet::Indirector::Terminus.stubs(:register_terminus_class) - @model = mock 'model' - @indirection = stub 'indirection', :name => :mystuff, :register_terminus_type => nil, :model => @model + @model = stub('model') + @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 @@ -19,12 +19,354 @@ describe Puppet::Indirector::REST do @searcher = @rest_class.new end + + describe "when locating the REST connection" do + before do + Puppet.settings.stubs(:value).returns("whatever") + end - it "should return an instance of the indirected model" - it "should deserialize result data after a call into a Model instance for find" - it "should deserialize result data after a call into a list of Model instances for search" - it "should deserialize result data after a call into a boolean for save" - it "should deserialize result data after a call into a boolean for destroy" - it "should generate an error when result data deserializes improperly" - it "should generate an error when result data specifies an error" + it "should return the :server setting as the host" do + Puppet.settings.expects(:value).with(:server).returns "myserver" + @searcher.rest_connection_details[:host].should == "myserver" + end + + it "should return the :masterport (as an Integer) as the port" do + Puppet.settings.expects(:value).with(:masterport).returns "1234" + @searcher.rest_connection_details[:port].should == 1234 + end + end + + describe "when doing a network fetch" do + before :each do + Net::HTTP.stubs(:start).returns('result') + @details = { :host => '127.0.0.1', :port => 34343 } + @searcher.stubs(:rest_connection_details).returns(@details) + end + + it "should accept a path" do + lambda { @search.network_fetch('foo') }.should_not raise_error(ArgumentError) + end + + it "should require a path" do + lambda { @searcher.network_fetch }.should raise_error(ArgumentError) + end + + it "should look up connection details" do + @searcher.expects(:rest_connection_details).returns(@details) + @searcher.network_fetch('foo') + end + + it "should use the GET http method" do + @mock_result = stub('mock result', :body => 'result') + @mock_connection = mock('mock http connection', :get => @mock_result) + @searcher.stubs(:network).yields(@mock_connection) + @searcher.network_fetch('foo') + end + + it "should use the appropriate remote server" do + Net::HTTP.expects(:start).with {|host, port| host == @details[:host] } + @searcher.network_fetch('foo') + end + + it "should use the appropriate remote port" do + Net::HTTP.expects(:start).with {|host, port| port == @details[:port] } + @searcher.network_fetch('foo') + end + + it "should use the provided path" do + @mock_result = stub('mock result', :body => 'result') + @mock_connection = stub('mock http connection') + @mock_connection.expects(:get).with('/foo').returns(@mock_result) + @searcher.stubs(:network).yields(@mock_connection) + @searcher.network_fetch('foo') + end + + it "should return the results of the GET request" do + @searcher.network_fetch('foo').should == 'result' + end + end + + describe "when doing a network delete" do + before :each do + Net::HTTP.stubs(:start).returns('result') + @details = { :host => '127.0.0.1', :port => 34343 } + @searcher.stubs(:rest_connection_details).returns(@details) + end + + it "should accept a path" do + lambda { @search.network_delete('foo') }.should_not raise_error(ArgumentError) + end + + it "should require a path" do + lambda { @searcher.network_delete }.should raise_error(ArgumentError) + end + + it "should look up connection details" do + @searcher.expects(:rest_connection_details).returns(@details) + @searcher.network_delete('foo') + end + + it "should use the DELETE http method" do + @mock_result = stub('mock result', :body => 'result') + @mock_connection = mock('mock http connection', :delete => @mock_result) + @searcher.stubs(:network).yields(@mock_connection) + @searcher.network_delete('foo') + end + + it "should use the appropriate remote server" do + Net::HTTP.expects(:start).with {|host, port| host == @details[:host] } + @searcher.network_delete('foo') + end + + it "should use the appropriate remote port" do + Net::HTTP.expects(:start).with {|host, port| port == @details[:port] } + @searcher.network_delete('foo') + end + + it "should use the provided path" do + @mock_result = stub('mock result', :body => 'result') + @mock_connection = stub('mock http connection') + @mock_connection.expects(:delete).with('/foo').returns(@mock_result) + @searcher.stubs(:network).yields(@mock_connection) + @searcher.network_delete('foo') + end + + it "should return the results of the DELETE request" do + @searcher.network_delete('foo').should == 'result' + end + end + + describe "when doing a network put" do + before :each do + Net::HTTP.stubs(:start).returns('result') + @details = { :host => '127.0.0.1', :port => 34343 } + @data = { :foo => 'bar' } + @searcher.stubs(:rest_connection_details).returns(@details) + end + + it "should accept a path and data" do + lambda { @search.network_put('foo', @data) }.should_not raise_error(ArgumentError) + end + + it "should require a path and data" do + lambda { @searcher.network_put('foo') }.should raise_error(ArgumentError) + end + + it "should look up connection details" do + @searcher.expects(:rest_connection_details).returns(@details) + @searcher.network_put('foo', @data) + end + + it "should use the appropriate remote server" do + Net::HTTP.expects(:start).with {|host, port| host == @details[:host] } + @searcher.network_put('foo', @data) + end + + it "should use the appropriate remote port" do + Net::HTTP.expects(:start).with {|host, port| port == @details[:port] } + @searcher.network_put('foo', @data) + end + + it "should use the PUT http method" do + @mock_result = stub('mock result', :body => 'result') + @mock_connection = mock('mock http connection', :put => @mock_result) + @searcher.stubs(:network).yields(@mock_connection) + @searcher.network_put('foo', @data) + end + + it "should use the provided path" do + @mock_result = stub('mock result', :body => 'result') + @mock_connection = stub('mock http connection') + @mock_connection.expects(:put).with {|path, data| path == '/foo' }.returns(@mock_result) + @searcher.stubs(:network).yields(@mock_connection) + @searcher.network_put('foo', @data) + end + + it "should use the provided data" do + @mock_result = stub('mock result', :body => 'result') + @mock_connection = stub('mock http connection') + @mock_connection.expects(:put).with {|path, data| data == @data }.returns(@mock_result) + @searcher.stubs(:network).yields(@mock_connection) + @searcher.network_put('foo', @data) + end + + it "should return the results of the PUT request" do + @searcher.network_put('foo', @data).should == 'result' + end + end + + describe "when doing a find" do + before :each do + @result = { :foo => 'bar'}.to_yaml + @searcher.stubs(:network_fetch).returns(@result) # neuter the network connection + @model.stubs(:from_yaml).returns(@instance) + + @request = stub 'request', :key => 'foo' + end + + it "should look up the model instance over the network" do + @searcher.expects(:network_fetch).returns(@result) + @searcher.find(@request) + end + + it "should look up the model instance using the named indirection" do + @searcher.expects(:network_fetch).with {|path| path =~ %r{^#{@indirection.name.to_s}/} }.returns(@result) + @searcher.find(@request) + end + + it "should look up the model instance using the provided key" do + @searcher.expects(:network_fetch).with {|path| path =~ %r{/foo$} }.returns(@result) + @searcher.find(@request) + end + + it "should deserialize result data to a Model instance" do + @model.expects(:from_yaml) + @searcher.find(@request) + end + + it "should return the deserialized Model instance" do + @searcher.find(@request).should == @instance + end + + it "should return nil when deserialized model instance is nil" do + @model.stubs(:from_yaml).returns(nil) + @searcher.find(@request).should be_nil + end + + it "should generate an error when result data deserializes improperly" do + @model.stubs(:from_yaml).raises(ArgumentError) + lambda { @searcher.find(@request) }.should raise_error(ArgumentError) + end + + it "should generate an error when result data specifies an error" do + @searcher.stubs(:network_fetch).returns(RuntimeError.new("bogus").to_yaml) + lambda { @searcher.find(@request) }.should raise_error(RuntimeError) + end + end + + describe "when doing a search" do + before :each do + @result = [1, 2].to_yaml + @searcher.stubs(:network_fetch).returns(@result) + @model.stubs(:from_yaml).returns(@instance) + + @request = stub 'request', :key => 'foo' + end + + it "should look up the model data over the network" do + @searcher.expects(:network_fetch).returns(@result) + @searcher.search(@request) + end + + it "should look up the model instance using the named indirection" do + @searcher.expects(:network_fetch).with {|path| path =~ %r{^#{@indirection.name.to_s}s/} }.returns(@result) + @searcher.search(@request) + end + + it "should look up the model instance using the provided key" do + @searcher.expects(:network_fetch).with {|path| path =~ %r{/foo$} }.returns(@result) + @searcher.search(@request) + end + + it "should deserialize result data into a list of Model instances" do + @model.expects(:from_yaml).at_least(2) + @searcher.search(@request) + end + + it "should generate an error when result data deserializes improperly" do + @model.stubs(:from_yaml).raises(ArgumentError) + lambda { @searcher.search(@request) }.should raise_error(ArgumentError) + end + + it "should generate an error when result data specifies an error" do + @searcher.stubs(:network_fetch).returns(RuntimeError.new("bogus").to_yaml) + lambda { @searcher.search(@request) }.should raise_error(RuntimeError) + end + end + + describe "when doing a destroy" do + before :each do + @result = true.to_yaml + @searcher.stubs(:network_delete).returns(@result) # neuter the network connection + @model.stubs(:from_yaml).returns(@instance) + + @request = stub 'request', :key => 'foo' + end + + it "should look up the model instance over the network" do + @searcher.expects(:network_delete).returns(@result) + @searcher.destroy(@request) + end + + it "should look up the model instance using the named indirection" do + @searcher.expects(:network_delete).with {|path| path =~ %r{^#{@indirection.name.to_s}/} }.returns(@result) + @searcher.destroy(@request) + end + + it "should look up the model instance using the provided key" do + @searcher.expects(:network_delete).with {|path| path =~ %r{/foo$} }.returns(@result) + @searcher.destroy(@request) + end + + it "should deserialize result data" do + YAML.expects(:load).with(@result) + @searcher.destroy(@request) + end + + it "should return deserialized result data" do + @searcher.destroy(@request).should == true + end + + it "should generate an error when result data specifies an error" do + @searcher.stubs(:network_delete).returns(RuntimeError.new("bogus").to_yaml) + lambda { @searcher.destroy(@request) }.should raise_error(RuntimeError) + end + end + + describe "when doing a save" do + before :each do + @result = { :foo => 'bar'}.to_yaml + @searcher.stubs(:network_put).returns(@result) # neuter the network connection + @model.stubs(:from_yaml).returns(@instance) + + @request = stub 'request', :instance => @instance + end + + it "should save the model instance over the network" do + @searcher.expects(:network_put).returns(@result) + @searcher.save(@request) + end + + it "should save the model instance using the named indirection" do + @searcher.expects(:network_put).with do |path, data| + path =~ %r{^#{@indirection.name.to_s}/} and + data == @instance.to_yaml + end.returns(@result) + @searcher.save(@request) + end + + it "should deserialize result data to a Model instance" do + @model.expects(:from_yaml) + @searcher.save(@request) + end + + it "should return the resulting deserialized Model instance" do + @searcher.save(@request).should == @instance + end + + it "should return nil when deserialized model instance is nil" do + @model.stubs(:from_yaml).returns(nil) + @searcher.save(@request).should be_nil + end + + it "should generate an error when result data deserializes improperly" do + @model.stubs(:from_yaml).raises(ArgumentError) + lambda { @searcher.save(@request) }.should raise_error(ArgumentError) + end + + it "should generate an error when result data specifies an error" do + @searcher.stubs(:network_put).returns(RuntimeError.new("bogus").to_yaml) + lambda { @searcher.save(@request) }.should raise_error(RuntimeError) + end + end end diff --git a/spec/unit/network/http/mongrel.rb b/spec/unit/network/http/mongrel.rb index a39e7354a..ccfca2f55 100644 --- a/spec/unit/network/http/mongrel.rb +++ b/spec/unit/network/http/mongrel.rb @@ -23,7 +23,7 @@ describe Puppet::Network::HTTP::Mongrel, "when turning on listening" do @mock_mongrel.stubs(:run) @mock_mongrel.stubs(:register) Mongrel::HttpServer.stubs(:new).returns(@mock_mongrel) - @listen_params = { :address => "127.0.0.1", :port => 31337, :handlers => [ :node, :catalog ], :protocols => [ :rest, :xmlrpc ] } + @listen_params = { :address => "127.0.0.1", :port => 31337, :handlers => [ :node, :catalog ], :protocols => [ :rest ] } end it "should fail if already listening" do @@ -65,32 +65,21 @@ describe Puppet::Network::HTTP::Mongrel, "when turning on listening" do it "should instantiate a handler for each protocol+handler pair to configure web server routing" do @listen_params[:protocols].each do |protocol| - mock_handler = mock("handler instance for [#{protocol}]") - mock_handler_class = mock("handler class for [#{protocol}]") @listen_params[:handlers].each do |handler| - mock_handler_class.expects(:new).with {|args| - args[:server] == @mock_mongrel and args[:handler] == handler - }.returns(mock_handler) + @mock_mongrel.expects(:register) end - @server.expects(:class_for_protocol).with(protocol).at_least_once.returns(mock_handler_class) end @server.listen(@listen_params) end it "should use a Mongrel + REST class to configure Mongrel when REST services are requested" do - Puppet::Network::HTTP::MongrelREST.expects(:new).at_least_once - @server.listen(@listen_params.merge(:protocols => [:rest])) - end - - it "should use a Mongrel + XMLRPC class to configure Mongrel when XMLRPC services are requested" do - Puppet::Network::HTTP::MongrelXMLRPC.expects(:new).at_least_once - @server.listen(@listen_params.merge(:protocols => [:xmlrpc])) + @server.expects(:class_for_protocol).with(:rest).at_least_once.returns(Puppet::Network::HTTP::MongrelREST) + @server.listen(@listen_params) end it "should fail if services from an unknown protocol are requested" do Proc.new { @server.listen(@listen_params.merge(:protocols => [ :foo ]))}.should raise_error(ArgumentError) end - end describe Puppet::Network::HTTP::Mongrel, "when turning off listening" do @@ -102,7 +91,7 @@ describe Puppet::Network::HTTP::Mongrel, "when turning off listening" do @mock_mongrel.stubs(:register) Mongrel::HttpServer.stubs(:new).returns(@mock_mongrel) @server = Puppet::Network::HTTP::Mongrel.new - @listen_params = { :address => "127.0.0.1", :port => 31337, :handlers => [ :node, :catalog ], :protocols => [ :rest, :xmlrpc ] } + @listen_params = { :address => "127.0.0.1", :port => 31337, :handlers => [ :node, :catalog ], :protocols => [ :rest ] } end it "should fail unless listening" do @@ -111,13 +100,13 @@ describe Puppet::Network::HTTP::Mongrel, "when turning off listening" do it "should order mongrel server to stop" do @server.listen(@listen_params) - @mock_mongrel.expects(:graceful_shutdown) + @mock_mongrel.expects(:stop) @server.unlisten end it "should not be listening" do @server.listen(@listen_params) - @mock_mongrel.stubs(:graceful_shutdown) + @mock_mongrel.stubs(:stop) @server.unlisten @server.should_not be_listening end diff --git a/spec/unit/network/http/mongrel/rest.rb b/spec/unit/network/http/mongrel/rest.rb index 9b2feb6ee..b483bbd46 100644 --- a/spec/unit/network/http/mongrel/rest.rb +++ b/spec/unit/network/http/mongrel/rest.rb @@ -1,8 +1,3 @@ -#!/usr/bin/env ruby -# -# Created by Rick Bradley on 2007-10-16. -# Copyright (c) 2007. All rights reserved. - require File.dirname(__FILE__) + '/../../../../spec_helper' require 'puppet/network/http' @@ -34,20 +29,6 @@ describe Puppet::Network::HTTP::MongrelREST, "when initializing" do Puppet::Indirector::Indirection.expects(:model).with(:foo).returns(nil) Proc.new { Puppet::Network::HTTP::MongrelREST.new(@params) }.should raise_error(ArgumentError) end - - it "should register itself with the mongrel server for the singular HTTP methods" do - @mock_mongrel.expects(:register).with do |*args| - args.first == '/foo' and args.last.is_a? Puppet::Network::HTTP::MongrelREST - end - Puppet::Network::HTTP::MongrelREST.new(@params) - end - - it "should register itself with the mongrel server for the plural GET method" do - @mock_mongrel.expects(:register).with do |*args| - args.first == '/foos' and args.last.is_a? Puppet::Network::HTTP::MongrelREST - end - Puppet::Network::HTTP::MongrelREST.new(@params) - end end describe Puppet::Network::HTTP::MongrelREST, "when receiving a request" do @@ -92,7 +73,7 @@ describe Puppet::Network::HTTP::MongrelREST, "when receiving a request" do 'QUERY_STRING' => '' }.merge(params)) @mock_request.stubs(:body).returns('this is a fake request body') @mock_model_instance = stub('indirected model instance', :save => true) - @mock_model_class.stubs(:new).returns(@mock_model_instance) + @mock_model_class.stubs(:from_yaml).returns(@mock_model_instance) end def setup_bad_request @@ -119,7 +100,7 @@ describe Puppet::Network::HTTP::MongrelREST, "when receiving a request" do it "should call the model save method if the request represents an HTTP PUT" do setup_save_request - @mock_model_instance.expects(:save).with(:data => 'this is a fake request body') + @mock_model_instance.expects(:save) @handler.process(@mock_request, @mock_response) end @@ -147,137 +128,138 @@ describe Puppet::Network::HTTP::MongrelREST, "when receiving a request" do @handler.process(@mock_request, @mock_response) end - it "should fail to find model if key is not specified" do - @mock_request.stubs(:params).returns({ Mongrel::Const::REQUEST_METHOD => 'GET', Mongrel::Const::REQUEST_PATH => '/foo'}) - @mock_response.expects(:start).with(404) - @handler.process(@mock_request, @mock_response) - end - - it "should fail to destroy model if key is not specified" do - @mock_request.stubs(:params).returns({ Mongrel::Const::REQUEST_METHOD => 'DELETE', Mongrel::Const::REQUEST_PATH => '/foo'}) - @mock_response.expects(:start).with(404) - @handler.process(@mock_request, @mock_response) - end - it "should fail to save model if data is not specified" do - @mock_request.stubs(:params).returns({ Mongrel::Const::REQUEST_METHOD => 'PUT', Mongrel::Const::REQUEST_PATH => '/foo'}) - @mock_request.stubs(:body).returns('') - @mock_response.expects(:start).with(404) - @handler.process(@mock_request, @mock_response) - end + describe "when finding a model instance" do |variable| + it "should fail to find model if key is not specified" do + @mock_request.stubs(:params).returns({ Mongrel::Const::REQUEST_METHOD => 'GET', Mongrel::Const::REQUEST_PATH => '/foo'}) + @mock_response.expects(:start).with(404) + @handler.process(@mock_request, @mock_response) + end - it "should pass HTTP request parameters to model find" do - setup_find_request('QUERY_STRING' => 'foo=baz&bar=xyzzy') - @mock_model_class.expects(:find).with do |key, args| - key == 'key' and args['foo'] == 'baz' and args['bar'] == 'xyzzy' + it "should pass HTTP request parameters to model find" do + setup_find_request('QUERY_STRING' => 'foo=baz&bar=xyzzy') + @mock_model_class.expects(:find).with do |key, args| + key == 'key' and args['foo'] == 'baz' and args['bar'] == 'xyzzy' + end + @handler.process(@mock_request, @mock_response) end - @handler.process(@mock_request, @mock_response) - end - - it "should pass HTTP request parameters to model search" do - setup_search_request('QUERY_STRING' => 'foo=baz&bar=xyzzy') - @mock_model_class.expects(:search).with do |args| - args['foo'] == 'baz' and args['bar'] == 'xyzzy' - end.returns([]) - @handler.process(@mock_request, @mock_response) - end - - it "should pass HTTP request parameters to model delete" do - setup_destroy_request('QUERY_STRING' => 'foo=baz&bar=xyzzy') - @mock_model_class.expects(:destroy).with do |key, args| - key == 'key' and args['foo'] == 'baz' and args['bar'] == 'xyzzy' + + it "should generate a 200 response when a model find call succeeds" do + setup_find_request + @mock_response.expects(:start).with(200) + @handler.process(@mock_request, @mock_response) end - @handler.process(@mock_request, @mock_response) - end - - it "should pass HTTP request parameters to model save" do - setup_save_request('QUERY_STRING' => 'foo=baz&bar=xyzzy') - @mock_model_instance.expects(:save).with do |args| - args[:data] == 'this is a fake request body' and args['foo'] == 'baz' and args['bar'] == 'xyzzy' + + it "should return a serialized object when a model find call succeeds" do + setup_find_request + @mock_model_instance = stub('model instance') + @mock_model_instance.expects(:to_yaml) + @mock_model_class.stubs(:find).returns(@mock_model_instance) + @handler.process(@mock_request, @mock_response) + end + + it "should serialize a controller exception when an exception is thrown by find" do + setup_find_request + @mock_model_class.expects(:find).raises(ArgumentError) + @mock_response.expects(:start).with(404) + @handler.process(@mock_request, @mock_response) end - @handler.process(@mock_request, @mock_response) end - it "should generate a 200 response when a model find call succeeds" do - setup_find_request - @mock_response.expects(:start).with(200) - @handler.process(@mock_request, @mock_response) + describe "when destroying a model instance" do |variable| + it "should fail to destroy model if key is not specified" do + @mock_request.stubs(:params).returns({ Mongrel::Const::REQUEST_METHOD => 'DELETE', Mongrel::Const::REQUEST_PATH => '/foo'}) + @mock_response.expects(:start).with(404) + @handler.process(@mock_request, @mock_response) + end + + it "should pass HTTP request parameters to model destroy" do + setup_destroy_request('QUERY_STRING' => 'foo=baz&bar=xyzzy') + @mock_model_class.expects(:destroy).with do |key, args| + key == 'key' and args['foo'] == 'baz' and args['bar'] == 'xyzzy' + end + @handler.process(@mock_request, @mock_response) + end + + it "should generate a 200 response when a model destroy call succeeds" do + setup_destroy_request + @mock_response.expects(:start).with(200) + @handler.process(@mock_request, @mock_response) + end + + it "should return a serialized success result when a model destroy call succeeds" do + setup_destroy_request + @mock_model_class.stubs(:destroy).returns(true) + @mock_body.expects(:write).with("--- true\n") + @handler.process(@mock_request, @mock_response) + end + + it "should serialize a controller exception when an exception is thrown by destroy" do + setup_destroy_request + @mock_model_class.expects(:destroy).raises(ArgumentError) + @mock_response.expects(:start).with(404) + @handler.process(@mock_request, @mock_response) + end end - it "should generate a 200 response when a model search call succeeds" do - setup_search_request - @mock_response.expects(:start).with(200) - @handler.process(@mock_request, @mock_response) + describe "when saving a model instance" do |variable| + it "should fail to save model if data is not specified" do + @mock_request.stubs(:params).returns({ Mongrel::Const::REQUEST_METHOD => 'PUT', Mongrel::Const::REQUEST_PATH => '/foo'}) + @mock_request.stubs(:body).returns('') + @mock_response.expects(:start).with(404) + @handler.process(@mock_request, @mock_response) + end + + it "should generate a 200 response when a model save call succeeds" do + setup_save_request + @mock_response.expects(:start).with(200) + @handler.process(@mock_request, @mock_response) + end + + it "should return a serialized object when a model save call succeeds" do + setup_save_request + @mock_model_instance.stubs(:save).returns(@mock_model_instance) + @mock_model_instance.expects(:to_yaml).returns('foo') + @handler.process(@mock_request, @mock_response) + end + + it "should serialize a controller exception when an exception is thrown by save" do + setup_save_request + @mock_model_instance.expects(:save).raises(ArgumentError) + @mock_response.expects(:start).with(404) + @handler.process(@mock_request, @mock_response) + end end - it "should generate a 200 response when a model destroy call succeeds" do - setup_destroy_request - @mock_response.expects(:start).with(200) - @handler.process(@mock_request, @mock_response) - end + describe "when searching for model instances" do |variable| + it "should pass HTTP request parameters to model search" do + setup_search_request('QUERY_STRING' => 'foo=baz&bar=xyzzy') + @mock_model_class.expects(:search).with do |args| + args['foo'] == 'baz' and args['bar'] == 'xyzzy' + end.returns([]) + @handler.process(@mock_request, @mock_response) + end - it "should generate a 200 response when a model save call succeeds" do - setup_save_request - @mock_response.expects(:start).with(200) - @handler.process(@mock_request, @mock_response) - end - - it "should return a serialized object when a model find call succeeds" do - setup_find_request - @mock_model_instance = stub('model instance') - @mock_model_instance.expects(:to_yaml) - @mock_model_class.stubs(:find).returns(@mock_model_instance) - @handler.process(@mock_request, @mock_response) - end - - it "should return a list of serialized objects when a model search call succeeds" do - setup_search_request - mock_matches = [1..5].collect {|i| mock("model instance #{i}", :to_yaml => "model instance #{i}") } - @mock_model_class.stubs(:search).returns(mock_matches) - @handler.process(@mock_request, @mock_response) - end - - it "should return a serialized success result when a model destroy call succeeds" do - setup_destroy_request - @mock_model_class.stubs(:destroy).returns(true) - @mock_body.expects(:write).with("--- true\n") - @handler.process(@mock_request, @mock_response) - end - - it "should return a serialized object when a model save call succeeds" do - setup_save_request - @mock_model_instance.stubs(:save).returns(@mock_model_instance) - @mock_model_instance.expects(:to_yaml).returns('foo') - @handler.process(@mock_request, @mock_response) - end - - it "should serialize a controller exception when an exception is thrown by find" do - setup_find_request - @mock_model_class.expects(:find).raises(ArgumentError) - @mock_response.expects(:start).with(404) - @handler.process(@mock_request, @mock_response) - end + it "should generate a 200 response when a model search call succeeds" do + setup_search_request + @mock_response.expects(:start).with(200) + @handler.process(@mock_request, @mock_response) + end + + it "should return a list of serialized objects when a model search call succeeds" do + setup_search_request + mock_matches = [1..5].collect {|i| mock("model instance #{i}", :to_yaml => "model instance #{i}") } + @mock_model_class.stubs(:search).returns(mock_matches) + @handler.process(@mock_request, @mock_response) + end - it "should serialize a controller exception when an exception is thrown by search" do - setup_search_request - @mock_model_class.expects(:search).raises(ArgumentError) - @mock_response.expects(:start).with(404) - @handler.process(@mock_request, @mock_response) - end - - it "should serialize a controller exception when an exception is thrown by destroy" do - setup_destroy_request - @mock_model_class.expects(:destroy).raises(ArgumentError) - @mock_response.expects(:start).with(404) - @handler.process(@mock_request, @mock_response) - end - - it "should serialize a controller exception when an exception is thrown by save" do - setup_save_request - @mock_model_instance.expects(:save).raises(ArgumentError) - @mock_response.expects(:start).with(404) - @handler.process(@mock_request, @mock_response) - end + it "should serialize a controller exception when an exception is thrown by search" do + setup_search_request + @mock_model_class.expects(:search).raises(ArgumentError) + @mock_response.expects(:start).with(404) + @handler.process(@mock_request, @mock_response) + end + end it "should serialize a controller exception if the request fails" do setup_bad_request diff --git a/spec/unit/network/http/webrick.rb b/spec/unit/network/http/webrick.rb index 0689b1b6b..05ed2f0e2 100644 --- a/spec/unit/network/http/webrick.rb +++ b/spec/unit/network/http/webrick.rb @@ -14,11 +14,11 @@ end describe Puppet::Network::HTTP::WEBrick, "when turning on listening" do before do - @mock_webrick = mock('webrick') + @mock_webrick = stub('webrick', :[] => {}) [:mount, :start, :shutdown].each {|meth| @mock_webrick.stubs(meth)} WEBrick::HTTPServer.stubs(:new).returns(@mock_webrick) @server = Puppet::Network::HTTP::WEBrick.new - @listen_params = { :address => "127.0.0.1", :port => 31337, :handlers => [ :node, :catalog ], :protocols => [ :rest, :xmlrpc ] } + @listen_params = { :address => "127.0.0.1", :port => 31337, :handlers => [ :node, :catalog ], :protocols => [ :rest ] } end it "should fail if already listening" do @@ -64,37 +64,48 @@ describe Puppet::Network::HTTP::WEBrick, "when turning on listening" do mock_handler = mock("handler instance for [#{protocol}]") mock_handler_class = mock("handler class for [#{protocol}]") @listen_params[:handlers].each do |handler| - mock_handler_class.expects(:new).with {|args| - args[:server] == @mock_webrick and args[:handler] == handler - }.returns(mock_handler) + @mock_webrick.expects(:mount) end - @server.expects(:class_for_protocol).with(protocol).at_least_once.returns(mock_handler_class) end @server.listen(@listen_params) end it "should use a WEBrick + REST class to configure WEBrick when REST services are requested" do - Puppet::Network::HTTP::WEBrickREST.expects(:new).at_least_once + Puppet::Network::HTTP::WEBrick.expects(:class_for_protocol).with(:rest).at_least_once @server.listen(@listen_params.merge(:protocols => [:rest])) end - it "should use a WEBrick + XMLRPC class to configure WEBrick when XMLRPC services are requested" do - Puppet::Network::HTTP::WEBrickXMLRPC.expects(:new).at_least_once - @server.listen(@listen_params.merge(:protocols => [:xmlrpc])) - end - it "should fail if services from an unknown protocol are requested" do - Proc.new { @server.listen(@listen_params.merge(:protocols => [ :foo ]))}.should raise_error(ArgumentError) + Proc.new { @server.listen(@listen_params.merge(:protocols => [ :foo ]))}.should raise_error end end + +describe Puppet::Network::HTTP::WEBrick, "when looking up the class to handle a protocol" do + it "should require a protocol" do + lambda { Puppet::Network::HTTP::WEBrick.class_for_protocol }.should raise_error(ArgumentError) + end + + it "should accept a protocol" do + lambda { Puppet::Network::HTTP::WEBrick.class_for_protocol("bob") }.should_not raise_error(ArgumentError) + end + + it "should use a WEBrick + REST class when a REST protocol is specified" do + Puppet::Network::HTTP::WEBrick.class_for_protocol("rest").should == Puppet::Network::HTTP::WEBrickREST + end + + it "should fail when an unknown protocol is specified" do + lambda { Puppet::Network::HTTP::WEBrick.class_for_protocol("abcdefg") }.should raise_error + end +end + describe Puppet::Network::HTTP::WEBrick, "when turning off listening" do before do - @mock_webrick = mock('webrick') + @mock_webrick = stub('webrick', :[] => {}) [:mount, :start, :shutdown].each {|meth| @mock_webrick.stubs(meth)} WEBrick::HTTPServer.stubs(:new).returns(@mock_webrick) @server = Puppet::Network::HTTP::WEBrick.new - @listen_params = { :address => "127.0.0.1", :port => 31337, :handlers => [ :node, :catalog ], :protocols => [ :rest, :xmlrpc ] } + @listen_params = { :address => "127.0.0.1", :port => 31337, :handlers => [ :node, :catalog ], :protocols => [ :rest ] } end it "should fail unless listening" do diff --git a/spec/unit/network/http/webrick/rest.rb b/spec/unit/network/http/webrick/rest.rb index aa7a3d53a..b7bd33880 100644 --- a/spec/unit/network/http/webrick/rest.rb +++ b/spec/unit/network/http/webrick/rest.rb @@ -1,49 +1,33 @@ -#!/usr/bin/env ruby -# -# Created by Rick Bradley on 2007-10-16. -# Copyright (c) 2007. All rights reserved. - require File.dirname(__FILE__) + '/../../../../spec_helper' require 'puppet/network/http' describe Puppet::Network::HTTP::WEBrickREST, "when initializing" do before do - @mock_webrick = stub('WEBrick server', :mount => true) + @mock_webrick = stub('WEBrick server', :mount => true, :[] => {}) @mock_model = mock('indirected model') Puppet::Indirector::Indirection.stubs(:model).returns(@mock_model) - @params = { :server => @mock_webrick, :handler => :foo } + @params = [ @mock_webrick, :foo ] end it "should require access to a WEBrick server" do - Proc.new { Puppet::Network::HTTP::WEBrickREST.new(@params.delete_if {|k,v| :server == k })}.should raise_error(ArgumentError) + Proc.new { + @params[0] = nil + Puppet::Network::HTTP::WEBrickREST.new(*@params) + }.should raise_error(ArgumentError) end it "should require an indirection name" do - Proc.new { Puppet::Network::HTTP::WEBrickREST.new(@params.delete_if {|k,v| :handler == k })}.should raise_error(ArgumentError) + Proc.new { Puppet::Network::HTTP::WEBrickREST.new(@params.first) }.should raise_error(ArgumentError) end it "should look up the indirection model from the indirection name" do Puppet::Indirector::Indirection.expects(:model).returns(@mock_model) - Puppet::Network::HTTP::WEBrickREST.new(@params) + Puppet::Network::HTTP::WEBrickREST.new(*@params) end it "should fail if the indirection is not known" do Puppet::Indirector::Indirection.expects(:model).returns(nil) - Proc.new { Puppet::Network::HTTP::WEBrickREST.new(@params) }.should raise_error(ArgumentError) - end - - it "should register itself with the WEBrick server for the singular HTTP methods" do - @mock_webrick.expects(:mount).with do |*args| - args.first == '/foo' and args.last.is_a?(Puppet::Network::HTTP::WEBrickREST) - end - Puppet::Network::HTTP::WEBrickREST.new(@params) - end - - it "should register itself with the WEBrick server for the plural GET method" do - @mock_webrick.expects(:mount).with do |*args| - args.first == '/foos' and args.last.is_a?(Puppet::Network::HTTP::WEBrickREST) - end - Puppet::Network::HTTP::WEBrickREST.new(@params) + Proc.new { Puppet::Network::HTTP::WEBrickREST.new(*@params) }.should raise_error(ArgumentError) end end @@ -52,9 +36,9 @@ describe Puppet::Network::HTTP::WEBrickREST, "when receiving a request" do @mock_request = stub('webrick http request', :query => {}) @mock_response = stub('webrick http response', :status= => true, :body= => true) @mock_model_class = stub('indirected model class') - @mock_webrick = stub('mongrel http server', :mount => true) + @mock_webrick = stub('webrick http server', :mount => true, :[] => {}) Puppet::Indirector::Indirection.stubs(:model).with(:foo).returns(@mock_model_class) - @handler = Puppet::Network::HTTP::WEBrickREST.new(:server => @mock_webrick, :handler => :foo) + @handler = Puppet::Network::HTTP::WEBrickREST.new(@mock_webrick, :foo) end def setup_find_request @@ -80,7 +64,7 @@ describe Puppet::Network::HTTP::WEBrickREST, "when receiving a request" do @mock_request.stubs(:path).returns('/foo') @mock_request.stubs(:body).returns('This is a fake request body') @mock_model_instance = stub('indirected model instance', :save => true) - @mock_model_class.stubs(:new).returns(@mock_model_instance) + @mock_model_class.stubs(:from_yaml).returns(@mock_model_instance) end def setup_bad_request @@ -88,6 +72,7 @@ describe Puppet::Network::HTTP::WEBrickREST, "when receiving a request" do @mock_request.stubs(:path).returns('/foos') end + it "should call the model find method if the request represents a singular HTTP GET" do setup_find_request @mock_model_class.expects(:find).with('key', {}) @@ -108,8 +93,7 @@ describe Puppet::Network::HTTP::WEBrickREST, "when receiving a request" do it "should call the model save method if the request represents an HTTP PUT" do setup_save_request - @mock_model_instance.expects(:save).with(:data => 'This is a fake request body') - @mock_model_class.expects(:new).returns(@mock_model_instance) + @mock_model_instance.expects(:save) @handler.service(@mock_request, @mock_response) end @@ -120,12 +104,14 @@ describe Puppet::Network::HTTP::WEBrickREST, "when receiving a request" do @handler.process(@mock_request, @mock_response) end - it "should fail if the request's pluralization is wrong" do + it "should fail if delete request's pluralization is wrong" do @mock_request.stubs(:request_method).returns('DELETE') @mock_request.stubs(:path).returns('/foos/key') @mock_response.expects(:status=).with(404) @handler.process(@mock_request, @mock_response) - + end + + it "should fail if put request's pluralization is wrong" do @mock_request.stubs(:request_method).returns('PUT') @mock_request.stubs(:path).returns('/foos/key') @mock_response.expects(:status=).with(404) @@ -139,145 +125,144 @@ describe Puppet::Network::HTTP::WEBrickREST, "when receiving a request" do @handler.process(@mock_request, @mock_response) end - it "should fail to find model if key is not specified" do - @mock_request.stubs(:request_method).returns('GET') - @mock_request.stubs(:path).returns('/foo') - @mock_response.expects(:status=).with(404) - @handler.process(@mock_request, @mock_response) - end - - it "should fail to destroy model if key is not specified" do - @mock_request.stubs(:request_method).returns('DELETE') - @mock_request.stubs(:path).returns('/foo') - @mock_response.expects(:status=).with(404) - @handler.process(@mock_request, @mock_response) - end - - it "should fail to save model if data is not specified" do - @mock_request.stubs(:request_method).returns('PUT') - @mock_request.stubs(:path).returns('/foo') - @mock_request.stubs(:body).returns('') - @mock_response.expects(:status=).with(404) - @handler.process(@mock_request, @mock_response) - end - - it "should pass HTTP request parameters to model find" do - setup_find_request - @mock_request.stubs(:query).returns(:foo => :baz, :bar => :xyzzy) - @mock_model_class.expects(:find).with do |key, args| - key == 'key' and args[:foo] == :baz and args[:bar] == :xyzzy + describe "when finding a model instance" do |variable| + it "should fail to find model if key is not specified" do + @mock_request.stubs(:request_method).returns('GET') + @mock_request.stubs(:path).returns('/foo') + @mock_response.expects(:status=).with(404) + @handler.process(@mock_request, @mock_response) + end + + it "should pass HTTP request parameters to model find" do + setup_find_request + @mock_request.stubs(:query).returns(:foo => :baz, :bar => :xyzzy) + @mock_model_class.expects(:find).with do |key, args| + key == 'key' and args[:foo] == :baz and args[:bar] == :xyzzy + end + @handler.service(@mock_request, @mock_response) + end + + it "should generate a 200 response when a model find call succeeds" do + setup_find_request + @mock_response.expects(:status=).with(200) + @handler.process(@mock_request, @mock_response) + end + + it "should return a serialized object when a model find call succeeds" do + setup_find_request + @mock_model_instance = stub('model instance') + @mock_model_instance.expects(:to_yaml) + @mock_model_class.stubs(:find).returns(@mock_model_instance) + @handler.process(@mock_request, @mock_response) + end + + it "should serialize a controller exception when an exception is thrown by find" do + setup_find_request + @mock_model_class.expects(:find).raises(ArgumentError) + @mock_response.expects(:status=).with(404) + @handler.process(@mock_request, @mock_response) end - @handler.service(@mock_request, @mock_response) - end - - it "should pass HTTP request parameters to model search" do - setup_search_request - @mock_request.stubs(:query).returns(:foo => :baz, :bar => :xyzzy) - @mock_model_class.expects(:search).with do |args| - args[:foo] == :baz and args[:bar] == :xyzzy - end.returns([]) - @handler.service(@mock_request, @mock_response) end - it "should pass HTTP request parameters to model destroy" do - setup_destroy_request - @mock_request.stubs(:query).returns(:foo => :baz, :bar => :xyzzy) - @mock_model_class.expects(:destroy).with do |key, args| - key == 'key' and args[:foo] == :baz and args[:bar] == :xyzzy + describe "when destroying a model instance" do |variable| + it "should fail to destroy model if key is not specified" do + @mock_request.stubs(:request_method).returns('DELETE') + @mock_request.stubs(:path).returns('/foo') + @mock_response.expects(:status=).with(404) + @handler.process(@mock_request, @mock_response) + end + + it "should pass HTTP request parameters to model destroy" do + setup_destroy_request + @mock_request.stubs(:query).returns(:foo => :baz, :bar => :xyzzy) + @mock_model_class.expects(:destroy).with do |key, args| + key == 'key' and args[:foo] == :baz and args[:bar] == :xyzzy + end + @handler.service(@mock_request, @mock_response) + end + + it "should generate a 200 response when a model destroy call succeeds" do + setup_destroy_request + @mock_response.expects(:status=).with(200) + @handler.process(@mock_request, @mock_response) + end + + it "should return a serialized success result when a model destroy call succeeds" do + setup_destroy_request + @mock_model_class.stubs(:destroy).returns(true) + @mock_response.expects(:body=).with("--- true\n") + @handler.process(@mock_request, @mock_response) + end + + it "should serialize a controller exception when an exception is thrown by search" do + setup_search_request + @mock_model_class.expects(:search).raises(ArgumentError) + @mock_response.expects(:status=).with(404) + @handler.process(@mock_request, @mock_response) end - @handler.service(@mock_request, @mock_response) end - it "should pass HTTP request parameters to model save" do - setup_save_request - @mock_request.stubs(:query).returns(:foo => :baz, :bar => :xyzzy) - @mock_model_instance.expects(:save).with do |args| - args[:data] == 'This is a fake request body' and args[:foo] == :baz and args[:bar] == :xyzzy + describe "when saving a model instance" do + it "should fail to save model if data is not specified" do + @mock_request.stubs(:request_method).returns('PUT') + @mock_request.stubs(:path).returns('/foo') + @mock_request.stubs(:body).returns('') + @mock_response.expects(:status=).with(404) + @handler.process(@mock_request, @mock_response) + end + + it "should generate a 200 response when a model save call succeeds" do + setup_save_request + @mock_response.expects(:status=).with(200) + @handler.process(@mock_request, @mock_response) + end + + it "should return a serialized object when a model save call succeeds" do + setup_save_request + @mock_model_instance.stubs(:save).returns(@mock_model_instance) + @mock_model_instance.expects(:to_yaml).returns('foo') + @handler.process(@mock_request, @mock_response) + end + + it "should serialize a controller exception when an exception is thrown by save" do + setup_save_request + @mock_model_instance.expects(:save).raises(ArgumentError) + @mock_response.expects(:status=).with(404) + @handler.process(@mock_request, @mock_response) end - @handler.service(@mock_request, @mock_response) - end - - it "should generate a 200 response when a model find call succeeds" do - setup_find_request - @mock_response.expects(:status=).with(200) - @handler.process(@mock_request, @mock_response) - end - - it "should generate a 200 response when a model search call succeeds" do - setup_search_request - @mock_response.expects(:status=).with(200) - @handler.process(@mock_request, @mock_response) - end - - it "should generate a 200 response when a model destroy call succeeds" do - setup_destroy_request - @mock_response.expects(:status=).with(200) - @handler.process(@mock_request, @mock_response) end - it "should generate a 200 response when a model save call succeeds" do - setup_save_request - @mock_response.expects(:status=).with(200) - @handler.process(@mock_request, @mock_response) - end + describe "when searching for model instances" do + it "should pass HTTP request parameters to model search" do + setup_search_request + @mock_request.stubs(:query).returns(:foo => :baz, :bar => :xyzzy) + @mock_model_class.expects(:search).with do |args| + args[:foo] == :baz and args[:bar] == :xyzzy + end.returns([]) + @handler.service(@mock_request, @mock_response) + end - it "should return a serialized object when a model find call succeeds" do - setup_find_request - @mock_model_instance = stub('model instance') - @mock_model_instance.expects(:to_yaml) - @mock_model_class.stubs(:find).returns(@mock_model_instance) - @handler.process(@mock_request, @mock_response) - end - - it "should return a list of serialized objects when a model search call succeeds" do - setup_search_request - mock_matches = [1..5].collect {|i| mock("model instance #{i}", :to_yaml => "model instance #{i}") } - @mock_model_class.stubs(:search).returns(mock_matches) - @handler.process(@mock_request, @mock_response) - end - - it "should return a serialized success result when a model destroy call succeeds" do - setup_destroy_request - @mock_model_class.stubs(:destroy).returns(true) - @mock_response.expects(:body=).with("--- true\n") - @handler.process(@mock_request, @mock_response) - end - - it "should return a serialized object when a model save call succeeds" do - setup_save_request - @mock_model_instance.stubs(:save).returns(@mock_model_instance) - @mock_model_instance.expects(:to_yaml).returns('foo') - @handler.process(@mock_request, @mock_response) - end - - it "should serialize a controller exception when an exception is thrown by find" do - setup_find_request - @mock_model_class.expects(:find).raises(ArgumentError) - @mock_response.expects(:status=).with(404) - @handler.process(@mock_request, @mock_response) + it "should generate a 200 response when a model search call succeeds" do + setup_search_request + @mock_response.expects(:status=).with(200) + @handler.process(@mock_request, @mock_response) + end + + it "should return a list of serialized objects when a model search call succeeds" do + setup_search_request + mock_matches = [1..5].collect {|i| mock("model instance #{i}", :to_yaml => "model instance #{i}") } + @mock_model_class.stubs(:search).returns(mock_matches) + @handler.process(@mock_request, @mock_response) + end + + it "should serialize a controller exception when an exception is thrown by destroy" do + setup_destroy_request + @mock_model_class.expects(:destroy).raises(ArgumentError) + @mock_response.expects(:status=).with(404) + @handler.process(@mock_request, @mock_response) + end end - it "should serialize a controller exception when an exception is thrown by search" do - setup_search_request - @mock_model_class.expects(:search).raises(ArgumentError) - @mock_response.expects(:status=).with(404) - @handler.process(@mock_request, @mock_response) - end - - it "should serialize a controller exception when an exception is thrown by destroy" do - setup_destroy_request - @mock_model_class.expects(:destroy).raises(ArgumentError) - @mock_response.expects(:status=).with(404) - @handler.process(@mock_request, @mock_response) - end - - it "should serialize a controller exception when an exception is thrown by save" do - setup_save_request - @mock_model_instance.expects(:save).raises(ArgumentError) - @mock_response.expects(:status=).with(404) - @handler.process(@mock_request, @mock_response) - end - it "should serialize a controller exception if the request fails" do setup_bad_request @mock_response.expects(:status=).with(404) diff --git a/spec/unit/network/server.rb b/spec/unit/network/server.rb index 846b5471d..ef9e85a73 100644 --- a/spec/unit/network/server.rb +++ b/spec/unit/network/server.rb @@ -161,8 +161,12 @@ describe Puppet::Network::Server, "in general" do Proc.new { @server2.unregister(:bar) }.should raise_error(ArgumentError) end - it "should provide a means of determining which style of service is being offered to clients" do - @server.protocols.should == [] + it "should provide a means of determining which protocols are in use" do + @server.should respond_to(:protocols) + end + + it "should only support the REST protocol at this time" do + @server.protocols.should == [ :rest ] end it "should provide a means of determining the listening address" do @@ -230,23 +234,54 @@ describe Puppet::Network::Server, "when listening is being turned on" do @mock_http_server_class = mock('http server class') Puppet::Network::HTTP.stubs(:server_class_by_type).returns(@mock_http_server_class) Puppet.stubs(:[]).with(:servertype).returns(:suparserver) - @server = Puppet::Network::Server.new(:address => "127.0.0.1", :port => 31337) + @server = Puppet::Network::Server.new(:address => "127.0.0.1", :port => 31337, :handlers => [:node]) @mock_http_server = mock('http server') @mock_http_server.stubs(:listen) end it "should fetch an instance of an HTTP server" do - mock_http_server_class = mock('http server class') - mock_http_server_class.expects(:new).returns(@mock_http_server) - @server.expects(:http_server_class).returns(mock_http_server_class) + @server.stubs(:http_server_class).returns(@mock_http_server_class) + @mock_http_server_class.expects(:new).returns(@mock_http_server) @server.listen end it "should cause the HTTP server to listen" do + @server.stubs(:http_server).returns(@mock_http_server) @mock_http_server.expects(:listen) - @server.expects(:http_server).returns(@mock_http_server) @server.listen end + + it "should pass the listening address to the HTTP server" do + @server.stubs(:http_server).returns(@mock_http_server) + @mock_http_server.expects(:listen).with do |args| + args[:address] == '127.0.0.1' + end + @server.listen + end + + it "should pass the listening port to the HTTP server" do + @server.stubs(:http_server).returns(@mock_http_server) + @mock_http_server.expects(:listen).with do |args| + args[:port] == 31337 + end + @server.listen + end + + it "should pass a list of handlers to the HTTP server" do + @server.stubs(:http_server).returns(@mock_http_server) + @mock_http_server.expects(:listen).with do |args| + args[:handlers] == [ :node ] + end + @server.listen + end + + it "should pass a list of protocols to the HTTP server" do + @server.stubs(:http_server).returns(@mock_http_server) + @mock_http_server.expects(:listen).with do |args| + args[:protocols] == [ :rest ] + end + @server.listen + end end describe Puppet::Network::Server, "when listening is being turned off" do @@ -274,16 +309,8 @@ end describe Class.new, "put these somewhere" do - it "should allow indirections to deny access to services based upon which client is connecting, or whether the client is authorized" - it "should deny access to clients based upon rules" it "should have the ability to use a class-level from_ hook (from_yaml, from_text, etc.) that can be called, based on content-type header, to allow for different deserializations of an object" it "should allow from_* on the inbound :data packet (look at its content_type) when doing a PUT/.new.save" it "should prepend a rest version number on the path (w00t)" it "should ... on server side, .save should from_yaml, then foo.save(args) instead of just Foo.new.save(args)" - it "should have a from_yaml class_method in the indirector (... default: yaml.load(data) => instance, but can be overridden)" -end - -describe Puppet::Indirector, "stuff required by HTTP servers" do - it "should provide the model with the ability to serialize to XML" - it "should provide the model with the ability to deserialize from XML" end |