#!/usr/bin/env rspec

require 'spec_helper'

module MCollective
  describe Agents do
    before do
      tmpfile = Tempfile.new("mc_agents_spec")
      path = tmpfile.path
      tmpfile.close!

      @tmpdir = FileUtils.mkdir_p(path)
      @tmpdir = @tmpdir[0] if @tmpdir.is_a?(Array) # ruby 1.9.2

      @agentsdir = File.join([@tmpdir, "mcollective", "agent"])
      FileUtils.mkdir_p(@agentsdir)

      logger = mock
      logger.stubs(:log)
      logger.stubs(:start)
      Log.configure(logger)
    end

    after do
      FileUtils.rm_r(@tmpdir)
    end

    describe "#initialize" do
      it "should fail if configuration has not been loaded" do
        Config.instance.expects(:configured).returns(false)

        expect {
          Agents.new
        }.to raise_error("Configuration has not been loaded, can't load agents")
      end

      it "should load agents" do
        Config.instance.expects(:configured).returns(true)
        Agents.any_instance.expects(:loadagents).once

        Agents.new
      end
    end

    describe "#clear!" do
      it "should delete and unsubscribe all loaded agents" do
        Config.instance.expects(:configured).returns(true).at_least_once
        Config.instance.expects(:libdir).returns([@tmpdir])
        PluginManager.expects(:delete).with("foo_agent").once
        Util.expects(:make_subscriptions).with("foo", :broadcast).returns("foo_target")
        Util.expects(:unsubscribe).with("foo_target")

        a = Agents.new({"foo" => 1})
      end
    end

    describe "#loadagents" do
      before do
        Config.instance.stubs(:configured).returns(true)
        Config.instance.stubs(:libdir).returns([@tmpdir])
        Agents.any_instance.stubs("clear!").returns(true)
      end

      it "should delete all existing agents" do
        Agents.any_instance.expects("clear!").once
        a = Agents.new
      end

      it "should attempt to load agents from all libdirs" do
        Config.instance.expects(:libdir).returns(["/nonexisting", "/nonexisting"])
        File.expects("directory?").with("/nonexisting/mcollective/agent").twice

        a = Agents.new
      end

      it "should load found agents" do
        Agents.any_instance.expects("loadagent").with("test").once

        FileUtils.touch(File.join([@agentsdir, "test.rb"]))

        a = Agents.new
      end

      it "should load each agent unless already loaded" do
        Agents.any_instance.expects("loadagent").with("test").never

        FileUtils.touch(File.join([@agentsdir, "test.rb"]))

        PluginManager << {:type => "test_agent", :class => String.new}
        a = Agents.new
      end
    end

    describe "#loadagent" do
      before do
        FileUtils.touch(File.join([@agentsdir, "test.rb"]))
        Config.instance.stubs(:configured).returns(true)
        Config.instance.stubs(:libdir).returns([@tmpdir])
        Agents.any_instance.stubs("clear!").returns(true)
        PluginManager.stubs(:loadclass).returns(true)
        PluginManager.stubs("[]").with("test_agent").returns(true)
        Util.stubs(:make_subscriptions).with("test", :broadcast).returns([{:agent => "test", :type => :broadcast, :collective => "test"}])
        Util.stubs(:subscribe).with([{:agent => "test", :type => :broadcast, :collective => "test"}]).returns(true)
        Agents.stubs(:findagentfile).returns(File.join([@agentsdir, "test.rb"]))
        Agents.any_instance.stubs("activate_agent?").returns(true)

        @a = Agents.new
      end

      it "should return false if the agent file is missing" do
        Agents.any_instance.expects(:findagentfile).returns(false).once
        @a.loadagent("test").should == false
      end

      it "should delete the agent before loading again" do
        PluginManager.expects(:delete).with("test_agent").twice
        @a.loadagent("test")
      end

      it "should load the agent class from disk" do
        PluginManager.expects(:loadclass).with("MCollective::Agent::Test")
        @a.loadagent("test")
      end

      it "should check if the agent should be activated" do
        Agents.any_instance.expects(:findagentfile).with("test").returns(File.join([@agentsdir, "test.rb"]))
        Agents.any_instance.expects("activate_agent?").with("test").returns(true)
        @a.loadagent("test").should == true
      end

      it "should set discovery and registration to be single instance plugins" do
        PluginManager.expects("<<").with({:type => "registration_agent", :class => "MCollective::Agent::Registration", :single_instance => true}).once
        PluginManager.expects("<<").with({:type => "discovery_agent", :class => "MCollective::Agent::Discovery", :single_instance => true}).once
        Agents.any_instance.expects("activate_agent?").with("registration").returns(true)
        Agents.any_instance.expects("activate_agent?").with("discovery").returns(true)

        PluginManager.expects(:loadclass).with("MCollective::Agent::Registration").returns(true).once
        PluginManager.expects(:loadclass).with("MCollective::Agent::Discovery").returns(true).once

        FileUtils.touch(File.join([@agentsdir, "registration.rb"]))
        FileUtils.touch(File.join([@agentsdir, "discovery.rb"]))

        @a.loadagent("registration")
        @a.loadagent("discovery")
      end

      it "should add general plugins as multiple instance plugins" do
        PluginManager.expects("<<").with({:type => "test_agent", :class => "MCollective::Agent::Test", :single_instance => false}).once
        @a.loadagent("test")
      end

      it "should add the agent to the plugin manager and subscribe" do
        PluginManager.expects("<<").with({:type => "foo_agent", :class => "MCollective::Agent::Foo", :single_instance => false})
        Util.stubs(:make_subscriptions).with("foo", :broadcast).returns([{:agent => "foo", :type => :broadcast, :collective => "test"}])
        Util.expects("subscribe").with([{:type => :broadcast, :agent => 'foo', :collective => 'test'}]).returns(true)
        Agents.any_instance.expects(:findagentfile).with("foo").returns(File.join([@agentsdir, "foo.rb"]))
        FileUtils.touch(File.join([@agentsdir, "foo.rb"]))
        Agents.any_instance.expects("activate_agent?").with("foo").returns(true)
        PluginManager.stubs("[]").with("foo_agent").returns(true)

        @a.loadagent("foo")
      end

      it "should check if an agent is loadable and remove it from the list if not" do
        PluginManager.expects("<<").with({:type => "foo_agent", :class => "MCollective::Agent::Foo", :single_instance => false})
        Agents.any_instance.expects(:findagentfile).with("foo").returns(File.join([@agentsdir, "foo.rb"]))
        FileUtils.touch(File.join([@agentsdir, "foo.rb"]))
        Agents.any_instance.expects("activate_agent?").with("foo").returns(true)
        PluginManager.stubs("[]").with("foo_agent").raises("rspec")

        Log.expects(:error).once.with("Loading agent foo failed: rspec")

        @a.loadagent("foo").should == false
        Agents.agentlist.include?("foo").should == false
      end

      it "should add the agent to the agent list" do
        Agents.agentlist.should == ["test"]
      end

      it "should return true on success" do
        @a.loadagent("test").should == true
      end

      it "should handle load exceptions" do
        Agents.any_instance.expects(:findagentfile).with("foo").returns(File.join([@agentsdir, "foo.rb"]))
        Log.expects(:error).with(regexp_matches(/Loading agent foo failed/))
        @a.loadagent("foo").should == false
      end

      it "should delete plugins that failed to load" do
        Agents.any_instance.expects(:findagentfile).with("foo").returns(File.join([@agentsdir, "foo.rb"]))
        PluginManager.expects(:delete).with("foo_agent").twice

        @a.loadagent("foo").should == false
      end
    end

    describe "#class_for_agent" do
      it "should return the correct class" do
        Config.instance.stubs(:configured).returns(true)
        Agents.any_instance.stubs(:loadagents).returns(true)
        Agents.new.class_for_agent("foo").should == "MCollective::Agent::Foo"
      end
    end

    describe "#activate_agent?" do
      before do
        Config.instance.stubs(:configured).returns(true)
        Agents.any_instance.stubs(:loadagents).returns(true)
        @a = Agents.new

        module MCollective::Agent;end
        class MCollective::Agent::Test; end
      end

      it "should check if the correct class has an activation method" do
        Agent::Test.expects("respond_to?").with("activate?").once

        @a.activate_agent?("test")
      end

      it "should call the activation method" do
        Agent::Test.expects("activate?").returns(true).once
        @a.activate_agent?("test")
      end

      it "should log a debug message and return true if the class has no activation method" do
        Agent::Test.expects("respond_to?").with("activate?").returns(false).once
        Log.expects(:debug).with("MCollective::Agent::Test does not have an activate? method, activating as default")

        @a.activate_agent?("test").should == true
      end

      it "should handle exceptions in the activation as false" do
        Agent::Test.expects("activate?").raises(RuntimeError)
        @a.activate_agent?("test").should == false
      end
    end

    describe "#findagentfile" do
      before do
        Config.instance.stubs(:configured).returns(true)
        Config.instance.stubs(:libdir).returns([@tmpdir])
        Agents.any_instance.stubs(:loadagents).returns(true)
        @a = Agents.new
      end

      it "should support multiple libdirs" do
        Config.instance.expects(:libdir).returns([@tmpdir, @tmpdir]).once
        File.expects("exist?").returns(false).twice
        @a.findagentfile("test")
      end

      it "should look for the correct filename in the libdir" do
        File.expects("exist?").with(File.join([@tmpdir, "mcollective", "agent", "test.rb"])).returns(false).once
        @a.findagentfile("test")
      end

      it "should return the full path if the agent is found" do
        agentfile = File.join([@tmpdir, "mcollective", "agent", "test.rb"])
        File.expects("exist?").with(agentfile).returns(true).once
        @a.findagentfile("test").should == agentfile
      end

      it "should return false if no agent is found" do
        @a.findagentfile("foo").should == false
      end
    end

    describe "#include?" do
      it "should correctly report the plugin state" do
        Config.instance.stubs(:configured).returns(true)
        Config.instance.stubs(:libdir).returns([@tmpdir])
        Agents.any_instance.stubs(:loadagents).returns(true)
        PluginManager.expects("include?").with("test_agent").returns(true)

        @a = Agents.new

        @a.include?("test").should == true
      end
    end

    describe "#agentlist" do
      it "should return the correct agent list" do
        Config.instance.stubs(:configured).returns(true)
        Config.instance.stubs(:libdir).returns([@tmpdir])
        Agents.any_instance.stubs(:loadagents).returns(true)

        @a = Agents.new("test" => true)
        Agents.agentlist.should == ["test"]
      end
    end
  end
end
