46ad7da6e2ee3ac2a244c90ca95e2ca05cdd0f35
[packages/precise/mcollective.git] / spec / unit / message_spec.rb
1 #!/usr/bin/env rspec
2
3 require 'spec_helper'
4
5 module MCollective
6   describe Message do
7     before do
8       Config.instance.set_config_defaults("")
9     end
10
11     describe "#initialize" do
12       it "should set defaults" do
13         m = Message.new("payload", "message")
14         m.payload.should == "payload"
15         m.message.should == "message"
16         m.request.should == nil
17         m.headers.should == {}
18         m.agent.should == nil
19         m.collective.should == nil
20         m.type.should == :message
21         m.filter.should == Util.empty_filter
22         m.requestid.should == nil
23         m.base64?.should == false
24         m.options.should == {}
25         m.discovered_hosts.should == nil
26         m.ttl.should == 60
27         m.validated.should == false
28         m.msgtime.should == 0
29         m.expected_msgid == nil
30       end
31
32       it "should set all supplied options" do
33         Message.any_instance.expects(:base64_decode!)
34
35         m = Message.new("payload", "message", :base64 => true,
36                         :agent => "rspecagent",
37                         :headers => {:rspec => "test"},
38                         :type => :rspec,
39                         :filter => "filter",
40                         :options => {:ttl => 30},
41                         :collective => "collective")
42         m.payload.should == "payload"
43         m.message.should == "message"
44         m.request.should == nil
45         m.headers.should == {:rspec => "test"}
46         m.agent.should == "rspecagent"
47         m.collective.should == "collective"
48         m.type.should == :rspec
49         m.filter.should == "filter"
50         m.base64?.should == true
51         m.options.should == {:ttl => 30}
52         m.ttl.should == 30
53       end
54
55       it "if given a request it should set options based on the request" do
56         request = mock
57         request.expects(:agent).returns("request")
58         request.expects(:collective).returns("collective")
59
60         m = Message.new("payload", "message", :request => request)
61         m.agent.should == "request"
62         m.collective.should == "collective"
63         m.type.should == :reply
64         m.request.should == request
65       end
66     end
67
68     describe "#reply_to=" do
69       it "should only set the reply-to header for requests" do
70         Config.instance.expects(:direct_addressing).returns(true)
71         m = Message.new("payload", "message", :type => :reply)
72         m.discovered_hosts = ["foo"]
73         expect { m.reply_to = "foo" }.to raise_error(/reply targets/)
74
75         [:request, :direct_request].each do |t|
76           m.type = t
77           m.reply_to = "foo"
78           m.reply_to.should == "foo"
79         end
80       end
81     end
82
83     describe "#expected_msgid=" do
84       it "should correctly set the property" do
85         m = Message.new("payload", "message", :type => :reply)
86         m.expected_msgid = "rspec test"
87         m.expected_msgid.should == "rspec test"
88       end
89
90       it "should only be set for reply messages" do
91         m = Message.new("payload", "message", :type => :request)
92
93         expect {
94           m.expected_msgid = "rspec test"
95         }.to raise_error("Can only store the expected msgid for reply messages")
96       end
97     end
98
99     describe "#base64_decode!" do
100       it "should not decode if not encoded" do
101         SSL.expects(:base64_decode).never
102         m = Message.new("payload", "message")
103       end
104
105       it "should decode encoded messages" do
106         SSL.expects(:base64_decode)
107         m = Message.new("payload", "message", :base64 => true)
108       end
109
110       it "should set base64 to false after decoding" do
111         SSL.expects(:base64_decode).with("payload")
112         m = Message.new("payload", "message", :base64 => true)
113         m.base64?.should == false
114       end
115     end
116
117     describe "#base64_encode" do
118       it "should not encode already encoded messages" do
119         SSL.expects(:base64_encode).never
120         Message.any_instance.stubs(:base64_decode!)
121         m = Message.new("payload", "message", :base64 => true)
122         m.base64_encode!
123       end
124
125       it "should encode plain messages" do
126         SSL.expects(:base64_encode).with("payload")
127         m = Message.new("payload", "message")
128         m.base64_encode!
129       end
130
131       it "should set base64 to false after encoding" do
132         SSL.expects(:base64_encode)
133         m = Message.new("payload", "message")
134         m.base64_encode!
135         m.base64?.should == true
136       end
137     end
138
139     describe "#base64?" do
140       it "should correctly report base64 state" do
141         m = Message.new("payload", "message")
142         m.base64?.should == m.instance_variable_get("@base64")
143       end
144     end
145
146     describe "#type=" do
147       it "should only allow types to be set when discovered hosts were given" do
148         m = Message.new("payload", "message")
149         Config.instance.stubs(:direct_addressing).returns(true)
150
151         expect {
152           m.type = :direct_request
153         }.to raise_error("Can only set type to :direct_request if discovered_hosts have been set")
154       end
155
156       it "should not allow direct_request to be set if direct addressing isnt enabled" do
157         m = Message.new("payload", "message")
158         Config.instance.stubs(:direct_addressing).returns(false)
159
160         expect {
161           m.type = :direct_request
162         }.to raise_error("Direct requests is not enabled using the direct_addressing config option")
163       end
164
165       it "should only accept valid types" do
166         m = Message.new("payload", "message")
167         Config.instance.stubs(:direct_addressing).returns(true)
168
169         expect {
170           m.type = :foo
171         }.to raise_error("Unknown message type foo")
172       end
173
174       it "should clear the filter in direct_request mode and add just an agent filter" do
175         m = Message.new("payload", "message")
176         m.discovered_hosts = ["rspec"]
177         Config.instance.stubs(:direct_addressing).returns(true)
178
179         m.filter = Util.empty_filter.merge({"cf_class" => ["test"]})
180         m.agent = "rspec"
181         m.type = :direct_request
182         m.filter.should == Util.empty_filter.merge({"agent" => ["rspec"]})
183       end
184
185       it "should set the type" do
186         m = Message.new("payload", "message")
187         m.type = :request
188         m.type.should == :request
189       end
190     end
191
192     describe "#encode!" do
193       it "should encode replies using the security plugin #encodereply" do
194         request = mock
195         request.stubs(:agent).returns("rspec_agent")
196         request.stubs(:collective).returns("collective")
197         request.stubs(:payload).returns({:requestid => "123", :callerid => "id=callerid"})
198
199         security = mock
200         security.expects(:encodereply).with('rspec_agent', 'payload', '123', 'id=callerid')
201         security.expects(:valid_callerid?).with("id=callerid").returns(true)
202
203         PluginManager.expects("[]").with("security_plugin").returns(security).twice
204
205         m = Message.new("payload", "message", :request => request, :type => :reply)
206
207         m.encode!
208       end
209
210       it "should encode requests using the security plugin #encoderequest" do
211         security = mock
212         security.expects(:encoderequest).with("identity", 'payload', '123', Util.empty_filter, 'rspec_agent', 'mcollective', 60).twice
213         PluginManager.expects("[]").with("security_plugin").returns(security).twice
214
215         Config.instance.expects(:identity).returns("identity").twice
216
217         Message.any_instance.expects(:requestid).returns("123").twice
218
219         m = Message.new("payload", "message", :type => :request, :agent => "rspec_agent", :collective => "mcollective")
220         m.encode!
221
222         m = Message.new("payload", "message", :type => :direct_request, :agent => "rspec_agent", :collective => "mcollective")
223         m.encode!
224       end
225
226       it "should retain the requestid if it was specifically set" do
227         security = mock
228         security.expects(:encoderequest).with("identity", 'payload', '123', Util.empty_filter, 'rspec_agent', 'mcollective', 60)
229         PluginManager.expects("[]").with("security_plugin").returns(security)
230
231         Config.instance.expects(:identity).returns("identity")
232
233         m = Message.new("payload", "message", :type => :request, :agent => "rspec_agent", :collective => "mcollective")
234         m.expects(:create_reqid).never
235         m.requestid = "123"
236         m.encode!
237         m.requestid.should == "123"
238       end
239
240       it "should not allow bad callerids when replying" do
241         request = mock
242         request.stubs(:agent).returns("rspec_agent")
243         request.stubs(:collective).returns("collective")
244         request.stubs(:payload).returns({:requestid => "123", :callerid => "caller/id"})
245
246         security = mock
247         security.expects(:valid_callerid?).with("caller/id").returns(false)
248         PluginManager.expects("[]").with("security_plugin").returns(security)
249
250         m = Message.new("payload", "message", :request => request, :type => :reply)
251
252         expect {
253           m.encode!
254         }.to raise_error('callerid in original request is not valid, surpressing reply to potentially forged request')
255       end
256     end
257
258     describe "#decode!" do
259       it "should check for valid types" do
260         expect {
261           m = Message.new("payload", "message", :type => :foo)
262           m.decode!
263         }.to raise_error("Cannot decode message type foo")
264       end
265
266       it "should set state based on decoded message" do
267         msg = mock
268         msg.stubs(:include?).returns(true)
269         msg.stubs("[]").with(:collective).returns("collective")
270         msg.stubs("[]").with(:agent).returns("rspecagent")
271         msg.stubs("[]").with(:filter).returns("filter")
272         msg.stubs("[]").with(:requestid).returns("1234")
273         msg.stubs("[]").with(:ttl).returns(30)
274         msg.stubs("[]").with(:msgtime).returns(1314628987)
275
276         security = mock
277         security.expects(:decodemsg).returns(msg)
278         PluginManager.expects("[]").with("security_plugin").returns(security)
279
280         m = Message.new(msg, "message", :type => :reply)
281         m.decode!
282
283         m.collective.should == "collective"
284         m.agent.should == "rspecagent"
285         m.filter.should == "filter"
286         m.requestid.should == "1234"
287         m.ttl.should == 30
288       end
289
290       it "should not allow bad callerids from the security plugin on requests" do
291         security = mock
292         security.expects(:decodemsg).returns({:callerid => "foo/bar"})
293         security.expects(:valid_callerid?).with("foo/bar").returns(false)
294
295         PluginManager.expects("[]").with("security_plugin").returns(security).twice
296
297         m = Message.new("payload", "message", :type => :request)
298
299         expect {
300           m.decode!
301         }.to raise_error('callerid in request is not valid, surpressing reply to potentially forged request')
302       end
303     end
304
305     describe "#validate" do
306       it "should only validate requests" do
307         m = Message.new("msg", "message", :type => :reply)
308         expect {
309           m.validate
310         }.to raise_error("Can only validate request messages")
311       end
312
313       it "should raise an exception for incorrect messages" do
314         sec = mock
315         sec.expects("validate_filter?").returns(false)
316         PluginManager.expects("[]").with("security_plugin").returns(sec)
317
318         payload = mock
319         payload.expects("[]").with(:filter).returns({})
320
321         m = Message.new(payload, "message", :type => :request)
322         m.instance_variable_set("@msgtime", Time.now.to_i)
323
324         expect {
325           m.validate
326         }.to raise_error(NotTargettedAtUs)
327       end
328
329       it "should pass for good messages" do
330         sec = mock
331         sec.expects(:validate_filter?).returns(true)
332         PluginManager.expects("[]").returns(sec)
333
334         payload = mock
335         payload.expects("[]").with(:filter).returns({})
336         m = Message.new(payload, "message", :type => :request)
337         m.instance_variable_set("@msgtime", Time.now.to_i)
338         m.validate
339       end
340
341       it "should set the @validated property" do
342         sec = mock
343         sec.expects(:validate_filter?).returns(true)
344         PluginManager.expects("[]").returns(sec)
345
346         payload = mock
347         payload.expects("[]").with(:filter).returns({})
348         m = Message.new(payload, "message", :type => :request)
349         m.instance_variable_set("@msgtime", Time.now.to_i)
350
351         m.validated.should == false
352         m.validate
353         m.validated.should == true
354       end
355
356       it "should not validate for messages older than TTL" do
357         stats = mock
358         stats.expects(:ttlexpired).once
359
360         MCollective::PluginManager << {:type => "global_stats", :class => stats}
361
362         m = Message.new({:callerid => "caller", :senderid => "sender"}, "message", :type => :request)
363         m.instance_variable_set("@msgtime", (Time.now.to_i - 120))
364
365         expect {
366           m.validate
367         }.to raise_error(MsgTTLExpired)
368       end
369     end
370
371     describe "#publish" do
372       it "should publish itself to the connector" do
373         m = Message.new("msg", "message", :type => :request)
374
375         connector = mock
376         connector.expects(:publish).with(m)
377         PluginManager.expects("[]").returns(connector)
378
379         m.publish
380       end
381
382       it "should support direct addressing" do
383         m = Message.new("msg", "message", :type => :request)
384         m.discovered_hosts = ["one", "two", "three"]
385
386         Config.instance.stubs(:direct_addressing).returns(true)
387         Config.instance.stubs(:direct_addressing_threshold).returns(10)
388
389         connector = mock
390         connector.expects(:publish).with(m)
391         PluginManager.expects("[]").returns(connector)
392
393         m.publish
394         m.type.should == :direct_request
395       end
396
397       it "should only direct publish below the configured threshold" do
398         m = Message.new("msg", "message", :type => :request)
399         m.discovered_hosts = ["one", "two", "three"]
400
401         Config.instance.expects(:direct_addressing).returns(true)
402         Config.instance.expects(:direct_addressing_threshold).returns(1)
403
404         connector = mock
405         connector.expects(:publish).with(m)
406         PluginManager.expects("[]").returns(connector)
407
408         m.publish
409         m.type.should == :request
410       end
411     end
412
413     describe "#create_reqid" do
414       it "should create a valid request id" do
415         m = Message.new("msg", "message", :agent => "rspec", :collective => "mc")
416
417         SSL.expects(:uuid).returns("reqid")
418
419         m.create_reqid.should == "reqid"
420       end
421     end
422   end
423 end