76630f24d5cb6bf253b333ea4fb6db482db0ffdc
[packages/precise/mcollective.git] / spec / unit / rpc / agent_spec.rb
1 #!/usr/bin/env rspec
2
3 require 'spec_helper'
4
5 module MCollective
6   module RPC
7     describe Agent do
8       before do
9         ddl = stub
10         ddl.stubs(:meta).returns({})
11         ddl.stubs(:action).returns([])
12         ddl.stubs(:validate_rpc_request).returns(true)
13         DDL.stubs(:new).returns(ddl)
14
15         @agent = Agent.new
16         @agent.reply = {}
17         @agent.request = {}
18       end
19
20       describe "#handlemsg" do
21         before do
22           Reply.any_instance.stubs(:initialize_data)
23
24           @agent.stubs(:respond_to?).with("rspec_action_action").returns(true)
25           @agent.stubs(:respond_to?).with("authorization_hook").returns(false)
26           @agent.stubs(:rspec_action_action).returns(nil)
27
28           @msg = {:msgtime => 1356006671,
29                   :senderid => "example.com",
30                   :requestid => "55f8abe1442328321667877a08bdc586",
31                   :body => {:agent => "rspec_agent",
32                             :action => "rspec_action",
33                             :data => {}},
34                   :caller => "cert=rspec"}
35         end
36
37         it "should or validate the incoming request" do
38           exception = DDLValidationError.new(:RSPEC, "Failed to validate", :error)
39           Request.any_instance.expects(:validate!).raises(exception)
40
41           reply = @agent.handlemsg(@msg, DDL.new)
42
43           reply[:statuscode].should == 4
44           reply[:statusmsg].should == "Failed to validate"
45         end
46
47         it "should call the authorization hook if set" do
48           @agent.expects(:respond_to?).with("authorization_hook").returns(true)
49           @agent.expects(:authorization_hook).raises("authorization denied")
50           Log.stubs(:error)
51
52           reply = @agent.handlemsg(@msg, DDL.new)
53
54           reply[:statuscode].should == 5
55           reply[:statusmsg].should == "authorization denied"
56         end
57
58         it "should audit the request" do
59           @agent.expects(:audit_request)
60
61           reply = @agent.handlemsg(@msg, DDL.new)
62           reply[:statuscode].should == 0
63         end
64
65         it "should call the before_processing_hook" do
66           @agent.expects(:before_processing_hook)
67
68           reply = @agent.handlemsg(@msg, DDL.new)
69           reply[:statuscode].should == 0
70         end
71
72         it "should fail if the action does not exist" do
73           @agent.expects(:respond_to?).with("rspec_action_action").returns(false)
74           reply = @agent.handlemsg(@msg, DDL.new)
75           reply[:statuscode].should == 2
76         end
77
78         it "should call the action correctly" do
79           @agent.expects(:rspec_action_action)
80           reply = @agent.handlemsg(@msg, DDL.new)
81           reply[:statuscode].should == 0
82         end
83
84         it "should handle RPC Aborted errors" do
85           @agent.expects(:rspec_action_action).raises(RPCAborted, "rspec test")
86           reply = @agent.handlemsg(@msg, DDL.new)
87           reply[:statuscode].should == 1
88           reply[:statusmsg].should == "rspec test"
89         end
90
91         it "should handle Unknown Action errors" do
92           @agent.stubs(:respond_to?).with("rspec_action_action").returns(false)
93           reply = @agent.handlemsg(@msg, DDL.new)
94           reply[:statuscode].should == 2
95           reply[:statusmsg].should == "Unknown action 'rspec_action' for agent 'rspec_agent'"
96         end
97
98         it "should handle Missing Data errors" do
99           @agent.expects(:rspec_action_action).raises(MissingRPCData, "rspec test")
100           reply = @agent.handlemsg(@msg, DDL.new)
101           reply[:statuscode].should == 3
102           reply[:statusmsg].should == "rspec test"
103         end
104
105         it "should handle Invalid Data errors" do
106           @agent.expects(:rspec_action_action).raises(InvalidRPCData, "rspec test")
107           reply = @agent.handlemsg(@msg, DDL.new)
108           reply[:statuscode].should == 4
109           reply[:statusmsg].should == "rspec test"
110         end
111
112         it "should handle unknown errors" do
113           @agent.expects(:rspec_action_action).raises(UnknownRPCError, "rspec test")
114           Log.expects(:error).twice
115
116           reply = @agent.handlemsg(@msg, DDL.new)
117           reply[:statuscode].should == 5
118           reply[:statusmsg].should == "rspec test"
119         end
120
121         it "should handle arbitrary exceptions" do
122           @agent.expects(:rspec_action_action).raises(Exception, "rspec test")
123           Log.expects(:error).twice
124
125           reply = @agent.handlemsg(@msg, DDL.new)
126           reply[:statuscode].should == 5
127           reply[:statusmsg].should == "rspec test"
128         end
129
130         it "should call the after_processing_hook" do
131           @agent.expects(:after_processing_hook)
132           reply = @agent.handlemsg(@msg, DDL.new)
133         end
134
135         it "should respond if required" do
136           Request.any_instance.expects(:should_respond?).returns(true)
137           Reply.any_instance.expects(:to_hash).returns({})
138           @agent.handlemsg(@msg, DDL.new).should == {}
139         end
140
141         it "should not respond when not required" do
142           Request.any_instance.expects(:should_respond?).returns(false)
143           Reply.any_instance.expects(:to_hash).never
144           @agent.handlemsg(@msg, DDL.new).should == nil
145         end
146       end
147
148       describe "#meta" do
149         it "should be deprecated" do
150           Agent.expects(:log_code).with(:PLMC34, is_a(String), :warn, has_value(regexp_matches(/agent_spec.rb/)))
151           Agent.metadata("foo")
152         end
153       end
154
155       describe "#load_ddl" do
156         it "should load the correct DDL" do
157           ddl = stub
158           ddl.stubs(:meta).returns({:timeout => 5})
159
160           DDL.expects(:new).with("agent", :agent).returns(ddl)
161
162           Agent.new.timeout.should == 5
163         end
164
165         it "should fail if the DDL isn't loaded" do
166           DDL.expects(:new).raises("failed to load")
167           expect { Agent.new }.to raise_code(:PLMC24)
168         end
169
170         it "should default to 10 second timeout" do
171           ddl = stub
172           ddl.stubs(:meta).returns({})
173
174           DDL.expects(:new).with("agent", :agent).returns(ddl)
175
176           Agent.new.timeout.should == 10
177         end
178       end
179
180       describe "#run" do
181         before do
182           @status = mock
183           @status.stubs(:exitstatus).returns(0)
184           @shell = mock
185           @shell.stubs(:runcommand)
186           @shell.stubs(:status).returns(@status)
187         end
188
189         it "should accept stderr and stdout and force them to be strings" do
190           Shell.expects(:new).with("rspec", {:stderr => "", :stdout => ""}).returns(@shell)
191           @agent.send(:run, "rspec", {:stderr => :err, :stdout => :out})
192           @agent.reply[:err].should == ""
193           @agent.reply[:out].should == ""
194         end
195
196         it "should accept existing variables for stdout and stderr and fail if they dont support <<" do
197           @agent.reply[:err] = "err"
198           @agent.reply[:out] = "out"
199
200           Shell.expects(:new).with("rspec", {:stderr => "err", :stdout => "out"}).returns(@shell)
201           @agent.send(:run, "rspec", {:stderr => @agent.reply[:err], :stdout => @agent.reply[:out]})
202           @agent.reply[:err].should == "err"
203           @agent.reply[:out].should == "out"
204
205           @agent.reply.expects("fail!").with("stderr should support << while calling run(rspec)").raises("stderr fail")
206           expect { @agent.send(:run, "rspec", {:stderr => nil, :stdout => ""}) }.to raise_error("stderr fail")
207
208           @agent.reply.expects("fail!").with("stdout should support << while calling run(rspec)").raises("stdout fail")
209           expect { @agent.send(:run, "rspec", {:stderr => "", :stdout => nil}) }.to raise_error("stdout fail")
210         end
211
212         it "should set stdin, cwd and environment if supplied" do
213           Shell.expects(:new).with("rspec", {:stdin => "stdin", :cwd => "cwd", :environment => "env"}).returns(@shell)
214           @agent.send(:run, "rspec", {:stdin => "stdin", :cwd => "cwd", :environment => "env"})
215         end
216
217         it "should ignore unknown options" do
218           Shell.expects(:new).with("rspec", {}).returns(@shell)
219           @agent.send(:run, "rspec", {:rspec => "rspec"})
220         end
221
222         it "should chomp strings if configured to do so" do
223           Shell.expects(:new).with("rspec", {:stderr => 'err', :stdout => 'out'}).returns(@shell)
224
225           @agent.reply[:err] = "err"
226           @agent.reply[:out] = "out"
227
228           @agent.reply[:err].expects("chomp!")
229           @agent.reply[:out].expects("chomp!")
230
231           @agent.send(:run, "rspec", {:chomp => true, :stdout => @agent.reply[:out], :stderr => @agent.reply[:err]})
232         end
233
234         it "should return the exitstatus" do
235           Shell.expects(:new).with("rspec", {}).returns(@shell)
236           @agent.send(:run, "rspec", {}).should == 0
237         end
238
239         it "should handle nil from the shell handler" do
240           @shell.expects(:status).returns(nil)
241           Shell.expects(:new).with("rspec", {}).returns(@shell)
242           @agent.send(:run, "rspec", {}).should == -1
243         end
244       end
245
246       describe "#validate" do
247         it "should detect missing data" do
248           @agent.request = {}
249           expect { @agent.send(:validate, :foo, String) }.to raise_error(MissingRPCData, "please supply a foo argument")
250         end
251
252         it "should catch validation errors and turn them into use case specific ones" do
253           @agent.request = {:input_key => "should be a number"}
254           Validator.expects(:validate).raises(ValidatorError, "input_key should be a number")
255           expect { @agent.send(:validate, :input_key, :number) }.to raise_error("Input input_key did not pass validation: input_key should be a number")
256         end
257       end
258     end
259   end
260 end