require 'spec/spec_helper'

class ErrorPlugin < Scout::Plugin
  def build_report
    raise 'FOO'
  end
end

class FooPlugin < Scout::Plugin
  OPTIONS=<<-EOS
  username:
    name: Username
    notes: The MySQL username to use
    default: other
  EOS
end

class FooPlugin::NestedPlugin < Scout::Plugin
end

class OptionsPlugin < Scout::Plugin
  OPTIONS=<<-EOS
  host:
    name: Host
    notes: The slave host to monitor
    default: 127.0.0.1
  username:
    name: Username
    notes: The MySQL username to use
    default: root
  EOS
end

describe Deputy do
  before do
    srand(Time.now.to_f * 100000) # fixes rand in klass() not being random
    Socket.stub!(:gethostname).and_return 'my_host'
  end

  it "has a VERSION" do
    Deputy::VERSION.should =~ /^\d+\.\d+\.\d+$/
  end

  def klass(name, options={})
    <<-CODE
      module TEMP_#{options[:rand]||rand(11111)}
        def self.interval
          #{options[:interval] || 60}
        end

        class #{name} < Scout::Plugin
          #{options[:class_eval]}
          def build_report
            #{options[:code]}
          end
        end
      end
    CODE
  end

  describe Scout do
    describe :plugins do
      it "finds plugins" do
        Scout.plugins(klass('A', :rand => 1111)).inspect.should == '[[60, TEMP_1111::A]]'
      end

      it "reports non-plugins" do
        Deputy.should_receive(:send_report)
        Scout.plugins("module TEMP_XXX;def self.interval;60;end;class Foo;def self.interval;11;end;end;end").inspect.should == '[]'
      end
    end

    describe :option do
      it "get default" do
        OptionsPlugin.new.send(:option, :username).should == 'root'
      end

      it "get nil when no default is there" do
        OptionsPlugin.new.send(:option, :foo).should == nil
      end

      it "get nil when no OPTIONS are there" do
        ErrorPlugin.new.send(:option, :foo).should == nil
      end

      it "does not overwrite others" do
        FooPlugin.new.send(:option, :username).should == 'other'
      end
    end

    describe :report do
      it "sends a report" do
        Deputy.should_receive(:send_report).with("NestedPlugin.x", 1)
        FooPlugin::NestedPlugin.new.send(:report, :x => 1)
      end

      it "sends multiple reports" do
        Deputy.should_receive(:send_report).with("NestedPlugin.x", 1)
        Deputy.should_receive(:send_report).with("NestedPlugin.y", 2)
        FooPlugin::NestedPlugin.new.send(:report, :x => 1, 'y' => 2)
      end

      it "sends alerts" do
        Deputy.should_receive(:send_report).with("NestedPlugin.alert", '1, "2"')
        FooPlugin::NestedPlugin.new.send(:alert, 1, "2")
      end

      it "sends errors" do
        Deputy.should_receive(:send_report).with("NestedPlugin.error", '1, "2"')
        FooPlugin::NestedPlugin.new.send(:error, 1, "2")
      end
    end

    describe :memory do
      before do
        `rm -f #{FooPlugin::NestedPlugin.new.send(:memory_file)}`
        `rm -f #{FooPlugin.new.send(:memory_file)}`
      end

      it "remembers" do
        FooPlugin::NestedPlugin.new.send(:remember, 'foo' => 1)
        FooPlugin::NestedPlugin.new.send(:memory, 'foo').should == 1
      end

      it "remembers different stuff" do
        FooPlugin::NestedPlugin.new.send(:remember, :foo => 1)
        FooPlugin::NestedPlugin.new.send(:remember, :bar => 1)
        FooPlugin::NestedPlugin.new.send(:memory, :foo).should == 1
        FooPlugin::NestedPlugin.new.send(:memory, :bar).should == 1
      end

      it "is nil for unremembered" do
        FooPlugin::NestedPlugin.new.send(:memory, :asdsa).should == nil
        FooPlugin.new.send(:memory, :asdsa).should == nil
      end

      it "remembers key and value" do
        FooPlugin::NestedPlugin.new.send(:remember, :foo, 1)
        FooPlugin::NestedPlugin.new.send(:memory, :foo).should == 1
      end
    end

    describe :needs do
      it "can create plugins with needs" do
        Scout.plugins(klass('D', :class_eval => "needs 'fastercsv'", :rand => 1112)).inspect.should == '[[60, TEMP_1112::D]]'
        defined?(FasterCSV).should == "constant"
      end
    end

    describe :clean_class_name do
      it "is simple name for simple e.g. from irb" do
        FooPlugin.clean_class_name.should == 'FooPlugin'
      end

      it "is last parts for namespaced" do
        FooPlugin::NestedPlugin.clean_class_name.should == 'NestedPlugin'
      end
    end
  end

  describe :run_plugins do
    before do
      Deputy.stub!(:config).and_return "sheriff_url" => 'http://sheri.ff'
    end

    it "executes all plugins" do
      $notify = 0
      FakeWeb.register_uri(:get, "http://sheri.ff/plugins.rb?hostname=my_host", :body => klass('C', :code => '$notify=1'))
      FakeWeb.register_uri(:get, "http://sheri.ff/notify?group=Deputies.finished&value=OK&hostname=my_host", :body => 'OK')
      Deputy.run_plugins
      $notify.should == 1
    end

    it "does not execute not-running plugins" do
      $notify = 0
      FakeWeb.register_uri(:get, "http://sheri.ff/plugins.rb?hostname=my_host", :body => klass('C', :code => '$notify=1', :interval => 9999999))
      FakeWeb.register_uri(:get, "http://sheri.ff/notify?group=Deputies.finished&value=OK&hostname=my_host", :body => 'OK')
      Deputy.run_plugins
      $notify.should == 0
    end

    it "sleeps a random interval if given in config" do
      Socket.stub!(:gethostname).and_return 'foo'
      Deputy.stub!(:config).and_return('max_random_start_delay' => 30)
      Deputy.stub!(:send_report)
      Deputy.should_receive(:sleep).with(25).and_raise("OK")
      lambda{
        Deputy.run_plugins
      }.should raise_error("OK")
    end

    it "does not sleeps no_wait given" do
      FakeWeb.register_uri(:get, "http://sheri.ff/plugins.rb?hostname=my_host", :body => '')
      FakeWeb.register_uri(:get, "http://sheri.ff/notify?group=Deputies.finished&value=OK&hostname=my_host", :body => 'OK')
      Deputy.stub!(:send_report)
      Deputy.should_not_receive(:sleep)
      Deputy.run_plugins
    end

    it "fails with nice backtrace" do
      FakeWeb.register_uri(:get, "http://sheri.ff/plugins.rb?hostname=my_host", :body => klass('FooBar', :code => 'raise'))
      FakeWeb.register_uri(:get, "http://sheri.ff/notify?group=Deputies.finished&value=Error&hostname=my_host", :body => 'OK')
      FakeWeb.register_uri(:get, "http://sheri.ff/notify?group=Deputies.Error&value=FooBar&hostname=my_host", :body => 'OK')
      lambda{
        Deputy.run_plugins
      }.should raise_error('FooBar')
    end

    it "continues to run plugins when one fails" do
      $notify = 0
      FakeWeb.register_uri(:get, "http://sheri.ff/plugins.rb?hostname=my_host", :body => klass('FooBar', :code => 'raise') + klass('C', :code => '$notify = 1'))
      FakeWeb.register_uri(:get, "http://sheri.ff/notify?group=Deputies.finished&value=Error&hostname=my_host", :body => 'OK')
      FakeWeb.register_uri(:get, "http://sheri.ff/notify?group=Deputies.Error&value=&hostname=my_host", :body => 'OK')
      lambda{
        Deputy.run_plugins
      }.should raise_error
      $notify.should == 1
    end
  end

  describe :send_report do
    before do
      Deputy.stub!(:config).and_return "sheriff_url" => 'http://sheri.ff'
    end

    it "sends a report" do
      FakeWeb.register_uri(:get, "http://sheri.ff/notify?group=Xxx.yyy&value=123&hostname=my_host", :body => 'OK')
      Deputy.send_report('Xxx.yyy', '123').should == 'OK'
    end

    it "escapes metric names" do
      FakeWeb.register_uri(:get, "http://sheri.ff/notify?group=Xxx.yy%3Fy&value=123&hostname=my_host", :body => 'OK')
      Deputy.send_report('Xxx.yy?y', '123').should == 'OK'
    end

    it "overrides host, if specified in options" do
      FakeWeb.register_uri(:get, "http://sheri.ff/notify?group=Xxx.yy%3Fy&value=123&hostname=general&forced_host=true", :body => 'OK')
      Deputy.send_report('Xxx.yy?y', '123', {:host => 'general'}).should == 'OK'
    end
  end

  describe :extract_auth_from_url! do
    it "finds auth" do
      Deputy.extract_auth_from_url!('http://x:y@foo').should == ['x','y']
    end

    it "removes auth from url" do
      url = 'http://x:y@foo'
      Deputy.extract_auth_from_url!(url)
      url.should == 'http://foo'
    end

    it "returns nil when auth was not found" do
      Deputy.extract_auth_from_url!('http://foo').should == nil
    end

    it "does not alter url when auth was not found" do
      url = 'http://foo'
      Deputy.extract_auth_from_url!(url)
      url.should == 'http://foo'
    end
  end
end