+#!/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