Updated mcollective.init according to OSCI-658
[packages/precise/mcollective.git] / spec / unit / rpc / agent_spec.rb
diff --git a/spec/unit/rpc/agent_spec.rb b/spec/unit/rpc/agent_spec.rb
new file mode 100755 (executable)
index 0000000..76630f2
--- /dev/null
@@ -0,0 +1,260 @@
+#!/usr/bin/env rspec
+
+require 'spec_helper'
+
+module MCollective
+  module RPC
+    describe Agent do
+      before do
+        ddl = stub
+        ddl.stubs(:meta).returns({})
+        ddl.stubs(:action).returns([])
+        ddl.stubs(:validate_rpc_request).returns(true)
+        DDL.stubs(:new).returns(ddl)
+
+        @agent = Agent.new
+        @agent.reply = {}
+        @agent.request = {}
+      end
+
+      describe "#handlemsg" do
+        before do
+          Reply.any_instance.stubs(:initialize_data)
+
+          @agent.stubs(:respond_to?).with("rspec_action_action").returns(true)
+          @agent.stubs(:respond_to?).with("authorization_hook").returns(false)
+          @agent.stubs(:rspec_action_action).returns(nil)
+
+          @msg = {:msgtime => 1356006671,
+                  :senderid => "example.com",
+                  :requestid => "55f8abe1442328321667877a08bdc586",
+                  :body => {:agent => "rspec_agent",
+                            :action => "rspec_action",
+                            :data => {}},
+                  :caller => "cert=rspec"}
+        end
+
+        it "should or validate the incoming request" do
+          exception = DDLValidationError.new(:RSPEC, "Failed to validate", :error)
+          Request.any_instance.expects(:validate!).raises(exception)
+
+          reply = @agent.handlemsg(@msg, DDL.new)
+
+          reply[:statuscode].should == 4
+          reply[:statusmsg].should == "Failed to validate"
+        end
+
+        it "should call the authorization hook if set" do
+          @agent.expects(:respond_to?).with("authorization_hook").returns(true)
+          @agent.expects(:authorization_hook).raises("authorization denied")
+          Log.stubs(:error)
+
+          reply = @agent.handlemsg(@msg, DDL.new)
+
+          reply[:statuscode].should == 5
+          reply[:statusmsg].should == "authorization denied"
+        end
+
+        it "should audit the request" do
+          @agent.expects(:audit_request)
+
+          reply = @agent.handlemsg(@msg, DDL.new)
+          reply[:statuscode].should == 0
+        end
+
+        it "should call the before_processing_hook" do
+          @agent.expects(:before_processing_hook)
+
+          reply = @agent.handlemsg(@msg, DDL.new)
+          reply[:statuscode].should == 0
+        end
+
+        it "should fail if the action does not exist" do
+          @agent.expects(:respond_to?).with("rspec_action_action").returns(false)
+          reply = @agent.handlemsg(@msg, DDL.new)
+          reply[:statuscode].should == 2
+        end
+
+        it "should call the action correctly" do
+          @agent.expects(:rspec_action_action)
+          reply = @agent.handlemsg(@msg, DDL.new)
+          reply[:statuscode].should == 0
+        end
+
+        it "should handle RPC Aborted errors" do
+          @agent.expects(:rspec_action_action).raises(RPCAborted, "rspec test")
+          reply = @agent.handlemsg(@msg, DDL.new)
+          reply[:statuscode].should == 1
+          reply[:statusmsg].should == "rspec test"
+        end
+
+        it "should handle Unknown Action errors" do
+          @agent.stubs(:respond_to?).with("rspec_action_action").returns(false)
+          reply = @agent.handlemsg(@msg, DDL.new)
+          reply[:statuscode].should == 2
+          reply[:statusmsg].should == "Unknown action 'rspec_action' for agent 'rspec_agent'"
+        end
+
+        it "should handle Missing Data errors" do
+          @agent.expects(:rspec_action_action).raises(MissingRPCData, "rspec test")
+          reply = @agent.handlemsg(@msg, DDL.new)
+          reply[:statuscode].should == 3
+          reply[:statusmsg].should == "rspec test"
+        end
+
+        it "should handle Invalid Data errors" do
+          @agent.expects(:rspec_action_action).raises(InvalidRPCData, "rspec test")
+          reply = @agent.handlemsg(@msg, DDL.new)
+          reply[:statuscode].should == 4
+          reply[:statusmsg].should == "rspec test"
+        end
+
+        it "should handle unknown errors" do
+          @agent.expects(:rspec_action_action).raises(UnknownRPCError, "rspec test")
+          Log.expects(:error).twice
+
+          reply = @agent.handlemsg(@msg, DDL.new)
+          reply[:statuscode].should == 5
+          reply[:statusmsg].should == "rspec test"
+        end
+
+        it "should handle arbitrary exceptions" do
+          @agent.expects(:rspec_action_action).raises(Exception, "rspec test")
+          Log.expects(:error).twice
+
+          reply = @agent.handlemsg(@msg, DDL.new)
+          reply[:statuscode].should == 5
+          reply[:statusmsg].should == "rspec test"
+        end
+
+        it "should call the after_processing_hook" do
+          @agent.expects(:after_processing_hook)
+          reply = @agent.handlemsg(@msg, DDL.new)
+        end
+
+        it "should respond if required" do
+          Request.any_instance.expects(:should_respond?).returns(true)
+          Reply.any_instance.expects(:to_hash).returns({})
+          @agent.handlemsg(@msg, DDL.new).should == {}
+        end
+
+        it "should not respond when not required" do
+          Request.any_instance.expects(:should_respond?).returns(false)
+          Reply.any_instance.expects(:to_hash).never
+          @agent.handlemsg(@msg, DDL.new).should == nil
+        end
+      end
+
+      describe "#meta" do
+        it "should be deprecated" do
+          Agent.expects(:log_code).with(:PLMC34, is_a(String), :warn, has_value(regexp_matches(/agent_spec.rb/)))
+          Agent.metadata("foo")
+        end
+      end
+
+      describe "#load_ddl" do
+        it "should load the correct DDL" do
+          ddl = stub
+          ddl.stubs(:meta).returns({:timeout => 5})
+
+          DDL.expects(:new).with("agent", :agent).returns(ddl)
+
+          Agent.new.timeout.should == 5
+        end
+
+        it "should fail if the DDL isn't loaded" do
+          DDL.expects(:new).raises("failed to load")
+          expect { Agent.new }.to raise_code(:PLMC24)
+        end
+
+        it "should default to 10 second timeout" do
+          ddl = stub
+          ddl.stubs(:meta).returns({})
+
+          DDL.expects(:new).with("agent", :agent).returns(ddl)
+
+          Agent.new.timeout.should == 10
+        end
+      end
+
+      describe "#run" do
+        before do
+          @status = mock
+          @status.stubs(:exitstatus).returns(0)
+          @shell = mock
+          @shell.stubs(:runcommand)
+          @shell.stubs(:status).returns(@status)
+        end
+
+        it "should accept stderr and stdout and force them to be strings" do
+          Shell.expects(:new).with("rspec", {:stderr => "", :stdout => ""}).returns(@shell)
+          @agent.send(:run, "rspec", {:stderr => :err, :stdout => :out})
+          @agent.reply[:err].should == ""
+          @agent.reply[:out].should == ""
+        end
+
+        it "should accept existing variables for stdout and stderr and fail if they dont support <<" do
+          @agent.reply[:err] = "err"
+          @agent.reply[:out] = "out"
+
+          Shell.expects(:new).with("rspec", {:stderr => "err", :stdout => "out"}).returns(@shell)
+          @agent.send(:run, "rspec", {:stderr => @agent.reply[:err], :stdout => @agent.reply[:out]})
+          @agent.reply[:err].should == "err"
+          @agent.reply[:out].should == "out"
+
+          @agent.reply.expects("fail!").with("stderr should support << while calling run(rspec)").raises("stderr fail")
+          expect { @agent.send(:run, "rspec", {:stderr => nil, :stdout => ""}) }.to raise_error("stderr fail")
+
+          @agent.reply.expects("fail!").with("stdout should support << while calling run(rspec)").raises("stdout fail")
+          expect { @agent.send(:run, "rspec", {:stderr => "", :stdout => nil}) }.to raise_error("stdout fail")
+        end
+
+        it "should set stdin, cwd and environment if supplied" do
+          Shell.expects(:new).with("rspec", {:stdin => "stdin", :cwd => "cwd", :environment => "env"}).returns(@shell)
+          @agent.send(:run, "rspec", {:stdin => "stdin", :cwd => "cwd", :environment => "env"})
+        end
+
+        it "should ignore unknown options" do
+          Shell.expects(:new).with("rspec", {}).returns(@shell)
+          @agent.send(:run, "rspec", {:rspec => "rspec"})
+        end
+
+        it "should chomp strings if configured to do so" do
+          Shell.expects(:new).with("rspec", {:stderr => 'err', :stdout => 'out'}).returns(@shell)
+
+          @agent.reply[:err] = "err"
+          @agent.reply[:out] = "out"
+
+          @agent.reply[:err].expects("chomp!")
+          @agent.reply[:out].expects("chomp!")
+
+          @agent.send(:run, "rspec", {:chomp => true, :stdout => @agent.reply[:out], :stderr => @agent.reply[:err]})
+        end
+
+        it "should return the exitstatus" do
+          Shell.expects(:new).with("rspec", {}).returns(@shell)
+          @agent.send(:run, "rspec", {}).should == 0
+        end
+
+        it "should handle nil from the shell handler" do
+          @shell.expects(:status).returns(nil)
+          Shell.expects(:new).with("rspec", {}).returns(@shell)
+          @agent.send(:run, "rspec", {}).should == -1
+        end
+      end
+
+      describe "#validate" do
+        it "should detect missing data" do
+          @agent.request = {}
+          expect { @agent.send(:validate, :foo, String) }.to raise_error(MissingRPCData, "please supply a foo argument")
+        end
+
+        it "should catch validation errors and turn them into use case specific ones" do
+          @agent.request = {:input_key => "should be a number"}
+          Validator.expects(:validate).raises(ValidatorError, "input_key should be a number")
+          expect { @agent.send(:validate, :input_key, :number) }.to raise_error("Input input_key did not pass validation: input_key should be a number")
+        end
+      end
+    end
+  end
+end