Updated mcollective.init according to OSCI-658
[packages/precise/mcollective.git] / spec / unit / message_spec.rb
diff --git a/spec/unit/message_spec.rb b/spec/unit/message_spec.rb
new file mode 100755 (executable)
index 0000000..46ad7da
--- /dev/null
@@ -0,0 +1,423 @@
+#!/usr/bin/env rspec
+
+require 'spec_helper'
+
+module MCollective
+  describe Message do
+    before do
+      Config.instance.set_config_defaults("")
+    end
+
+    describe "#initialize" do
+      it "should set defaults" do
+        m = Message.new("payload", "message")
+        m.payload.should == "payload"
+        m.message.should == "message"
+        m.request.should == nil
+        m.headers.should == {}
+        m.agent.should == nil
+        m.collective.should == nil
+        m.type.should == :message
+        m.filter.should == Util.empty_filter
+        m.requestid.should == nil
+        m.base64?.should == false
+        m.options.should == {}
+        m.discovered_hosts.should == nil
+        m.ttl.should == 60
+        m.validated.should == false
+        m.msgtime.should == 0
+        m.expected_msgid == nil
+      end
+
+      it "should set all supplied options" do
+        Message.any_instance.expects(:base64_decode!)
+
+        m = Message.new("payload", "message", :base64 => true,
+                        :agent => "rspecagent",
+                        :headers => {:rspec => "test"},
+                        :type => :rspec,
+                        :filter => "filter",
+                        :options => {:ttl => 30},
+                        :collective => "collective")
+        m.payload.should == "payload"
+        m.message.should == "message"
+        m.request.should == nil
+        m.headers.should == {:rspec => "test"}
+        m.agent.should == "rspecagent"
+        m.collective.should == "collective"
+        m.type.should == :rspec
+        m.filter.should == "filter"
+        m.base64?.should == true
+        m.options.should == {:ttl => 30}
+        m.ttl.should == 30
+      end
+
+      it "if given a request it should set options based on the request" do
+        request = mock
+        request.expects(:agent).returns("request")
+        request.expects(:collective).returns("collective")
+
+        m = Message.new("payload", "message", :request => request)
+        m.agent.should == "request"
+        m.collective.should == "collective"
+        m.type.should == :reply
+        m.request.should == request
+      end
+    end
+
+    describe "#reply_to=" do
+      it "should only set the reply-to header for requests" do
+        Config.instance.expects(:direct_addressing).returns(true)
+        m = Message.new("payload", "message", :type => :reply)
+        m.discovered_hosts = ["foo"]
+        expect { m.reply_to = "foo" }.to raise_error(/reply targets/)
+
+        [:request, :direct_request].each do |t|
+          m.type = t
+          m.reply_to = "foo"
+          m.reply_to.should == "foo"
+        end
+      end
+    end
+
+    describe "#expected_msgid=" do
+      it "should correctly set the property" do
+        m = Message.new("payload", "message", :type => :reply)
+        m.expected_msgid = "rspec test"
+        m.expected_msgid.should == "rspec test"
+      end
+
+      it "should only be set for reply messages" do
+        m = Message.new("payload", "message", :type => :request)
+
+        expect {
+          m.expected_msgid = "rspec test"
+        }.to raise_error("Can only store the expected msgid for reply messages")
+      end
+    end
+
+    describe "#base64_decode!" do
+      it "should not decode if not encoded" do
+        SSL.expects(:base64_decode).never
+        m = Message.new("payload", "message")
+      end
+
+      it "should decode encoded messages" do
+        SSL.expects(:base64_decode)
+        m = Message.new("payload", "message", :base64 => true)
+      end
+
+      it "should set base64 to false after decoding" do
+        SSL.expects(:base64_decode).with("payload")
+        m = Message.new("payload", "message", :base64 => true)
+        m.base64?.should == false
+      end
+    end
+
+    describe "#base64_encode" do
+      it "should not encode already encoded messages" do
+        SSL.expects(:base64_encode).never
+        Message.any_instance.stubs(:base64_decode!)
+        m = Message.new("payload", "message", :base64 => true)
+        m.base64_encode!
+      end
+
+      it "should encode plain messages" do
+        SSL.expects(:base64_encode).with("payload")
+        m = Message.new("payload", "message")
+        m.base64_encode!
+      end
+
+      it "should set base64 to false after encoding" do
+        SSL.expects(:base64_encode)
+        m = Message.new("payload", "message")
+        m.base64_encode!
+        m.base64?.should == true
+      end
+    end
+
+    describe "#base64?" do
+      it "should correctly report base64 state" do
+        m = Message.new("payload", "message")
+        m.base64?.should == m.instance_variable_get("@base64")
+      end
+    end
+
+    describe "#type=" do
+      it "should only allow types to be set when discovered hosts were given" do
+        m = Message.new("payload", "message")
+        Config.instance.stubs(:direct_addressing).returns(true)
+
+        expect {
+          m.type = :direct_request
+        }.to raise_error("Can only set type to :direct_request if discovered_hosts have been set")
+      end
+
+      it "should not allow direct_request to be set if direct addressing isnt enabled" do
+        m = Message.new("payload", "message")
+        Config.instance.stubs(:direct_addressing).returns(false)
+
+        expect {
+          m.type = :direct_request
+        }.to raise_error("Direct requests is not enabled using the direct_addressing config option")
+      end
+
+      it "should only accept valid types" do
+        m = Message.new("payload", "message")
+        Config.instance.stubs(:direct_addressing).returns(true)
+
+        expect {
+          m.type = :foo
+        }.to raise_error("Unknown message type foo")
+      end
+
+      it "should clear the filter in direct_request mode and add just an agent filter" do
+        m = Message.new("payload", "message")
+        m.discovered_hosts = ["rspec"]
+        Config.instance.stubs(:direct_addressing).returns(true)
+
+        m.filter = Util.empty_filter.merge({"cf_class" => ["test"]})
+        m.agent = "rspec"
+        m.type = :direct_request
+        m.filter.should == Util.empty_filter.merge({"agent" => ["rspec"]})
+      end
+
+      it "should set the type" do
+        m = Message.new("payload", "message")
+        m.type = :request
+        m.type.should == :request
+      end
+    end
+
+    describe "#encode!" do
+      it "should encode replies using the security plugin #encodereply" do
+        request = mock
+        request.stubs(:agent).returns("rspec_agent")
+        request.stubs(:collective).returns("collective")
+        request.stubs(:payload).returns({:requestid => "123", :callerid => "id=callerid"})
+
+        security = mock
+        security.expects(:encodereply).with('rspec_agent', 'payload', '123', 'id=callerid')
+        security.expects(:valid_callerid?).with("id=callerid").returns(true)
+
+        PluginManager.expects("[]").with("security_plugin").returns(security).twice
+
+        m = Message.new("payload", "message", :request => request, :type => :reply)
+
+        m.encode!
+      end
+
+      it "should encode requests using the security plugin #encoderequest" do
+        security = mock
+        security.expects(:encoderequest).with("identity", 'payload', '123', Util.empty_filter, 'rspec_agent', 'mcollective', 60).twice
+        PluginManager.expects("[]").with("security_plugin").returns(security).twice
+
+        Config.instance.expects(:identity).returns("identity").twice
+
+        Message.any_instance.expects(:requestid).returns("123").twice
+
+        m = Message.new("payload", "message", :type => :request, :agent => "rspec_agent", :collective => "mcollective")
+        m.encode!
+
+        m = Message.new("payload", "message", :type => :direct_request, :agent => "rspec_agent", :collective => "mcollective")
+        m.encode!
+      end
+
+      it "should retain the requestid if it was specifically set" do
+        security = mock
+        security.expects(:encoderequest).with("identity", 'payload', '123', Util.empty_filter, 'rspec_agent', 'mcollective', 60)
+        PluginManager.expects("[]").with("security_plugin").returns(security)
+
+        Config.instance.expects(:identity).returns("identity")
+
+        m = Message.new("payload", "message", :type => :request, :agent => "rspec_agent", :collective => "mcollective")
+        m.expects(:create_reqid).never
+        m.requestid = "123"
+        m.encode!
+        m.requestid.should == "123"
+      end
+
+      it "should not allow bad callerids when replying" do
+        request = mock
+        request.stubs(:agent).returns("rspec_agent")
+        request.stubs(:collective).returns("collective")
+        request.stubs(:payload).returns({:requestid => "123", :callerid => "caller/id"})
+
+        security = mock
+        security.expects(:valid_callerid?).with("caller/id").returns(false)
+        PluginManager.expects("[]").with("security_plugin").returns(security)
+
+        m = Message.new("payload", "message", :request => request, :type => :reply)
+
+        expect {
+          m.encode!
+        }.to raise_error('callerid in original request is not valid, surpressing reply to potentially forged request')
+      end
+    end
+
+    describe "#decode!" do
+      it "should check for valid types" do
+        expect {
+          m = Message.new("payload", "message", :type => :foo)
+          m.decode!
+        }.to raise_error("Cannot decode message type foo")
+      end
+
+      it "should set state based on decoded message" do
+        msg = mock
+        msg.stubs(:include?).returns(true)
+        msg.stubs("[]").with(:collective).returns("collective")
+        msg.stubs("[]").with(:agent).returns("rspecagent")
+        msg.stubs("[]").with(:filter).returns("filter")
+        msg.stubs("[]").with(:requestid).returns("1234")
+        msg.stubs("[]").with(:ttl).returns(30)
+        msg.stubs("[]").with(:msgtime).returns(1314628987)
+
+        security = mock
+        security.expects(:decodemsg).returns(msg)
+        PluginManager.expects("[]").with("security_plugin").returns(security)
+
+        m = Message.new(msg, "message", :type => :reply)
+        m.decode!
+
+        m.collective.should == "collective"
+        m.agent.should == "rspecagent"
+        m.filter.should == "filter"
+        m.requestid.should == "1234"
+        m.ttl.should == 30
+      end
+
+      it "should not allow bad callerids from the security plugin on requests" do
+        security = mock
+        security.expects(:decodemsg).returns({:callerid => "foo/bar"})
+        security.expects(:valid_callerid?).with("foo/bar").returns(false)
+
+        PluginManager.expects("[]").with("security_plugin").returns(security).twice
+
+        m = Message.new("payload", "message", :type => :request)
+
+        expect {
+          m.decode!
+        }.to raise_error('callerid in request is not valid, surpressing reply to potentially forged request')
+      end
+    end
+
+    describe "#validate" do
+      it "should only validate requests" do
+        m = Message.new("msg", "message", :type => :reply)
+        expect {
+          m.validate
+        }.to raise_error("Can only validate request messages")
+      end
+
+      it "should raise an exception for incorrect messages" do
+        sec = mock
+        sec.expects("validate_filter?").returns(false)
+        PluginManager.expects("[]").with("security_plugin").returns(sec)
+
+        payload = mock
+        payload.expects("[]").with(:filter).returns({})
+
+        m = Message.new(payload, "message", :type => :request)
+        m.instance_variable_set("@msgtime", Time.now.to_i)
+
+        expect {
+          m.validate
+        }.to raise_error(NotTargettedAtUs)
+      end
+
+      it "should pass for good messages" do
+        sec = mock
+        sec.expects(:validate_filter?).returns(true)
+        PluginManager.expects("[]").returns(sec)
+
+        payload = mock
+        payload.expects("[]").with(:filter).returns({})
+        m = Message.new(payload, "message", :type => :request)
+        m.instance_variable_set("@msgtime", Time.now.to_i)
+        m.validate
+      end
+
+      it "should set the @validated property" do
+        sec = mock
+        sec.expects(:validate_filter?).returns(true)
+        PluginManager.expects("[]").returns(sec)
+
+        payload = mock
+        payload.expects("[]").with(:filter).returns({})
+        m = Message.new(payload, "message", :type => :request)
+        m.instance_variable_set("@msgtime", Time.now.to_i)
+
+        m.validated.should == false
+        m.validate
+        m.validated.should == true
+      end
+
+      it "should not validate for messages older than TTL" do
+        stats = mock
+        stats.expects(:ttlexpired).once
+
+        MCollective::PluginManager << {:type => "global_stats", :class => stats}
+
+        m = Message.new({:callerid => "caller", :senderid => "sender"}, "message", :type => :request)
+        m.instance_variable_set("@msgtime", (Time.now.to_i - 120))
+
+        expect {
+          m.validate
+        }.to raise_error(MsgTTLExpired)
+      end
+    end
+
+    describe "#publish" do
+      it "should publish itself to the connector" do
+        m = Message.new("msg", "message", :type => :request)
+
+        connector = mock
+        connector.expects(:publish).with(m)
+        PluginManager.expects("[]").returns(connector)
+
+        m.publish
+      end
+
+      it "should support direct addressing" do
+        m = Message.new("msg", "message", :type => :request)
+        m.discovered_hosts = ["one", "two", "three"]
+
+        Config.instance.stubs(:direct_addressing).returns(true)
+        Config.instance.stubs(:direct_addressing_threshold).returns(10)
+
+        connector = mock
+        connector.expects(:publish).with(m)
+        PluginManager.expects("[]").returns(connector)
+
+        m.publish
+        m.type.should == :direct_request
+      end
+
+      it "should only direct publish below the configured threshold" do
+        m = Message.new("msg", "message", :type => :request)
+        m.discovered_hosts = ["one", "two", "three"]
+
+        Config.instance.expects(:direct_addressing).returns(true)
+        Config.instance.expects(:direct_addressing_threshold).returns(1)
+
+        connector = mock
+        connector.expects(:publish).with(m)
+        PluginManager.expects("[]").returns(connector)
+
+        m.publish
+        m.type.should == :request
+      end
+    end
+
+    describe "#create_reqid" do
+      it "should create a valid request id" do
+        m = Message.new("msg", "message", :agent => "rspec", :collective => "mc")
+
+        SSL.expects(:uuid).returns("reqid")
+
+        m.create_reqid.should == "reqid"
+      end
+    end
+  end
+end