neeto-monitor-ruby 1.0.42
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +288 -0
- data/Rakefile +14 -0
- data/lib/neeto-monitor-ruby.rb +7 -0
- data/lib/neeto_monitor_ruby/configuration.rb +92 -0
- data/lib/neeto_monitor_ruby/heartbeat_runner_job.rb +22 -0
- data/lib/neeto_monitor_ruby/init/common.rb +13 -0
- data/lib/neeto_monitor_ruby/init/rails.rb +27 -0
- data/lib/neeto_monitor_ruby/init/ruby.rb +10 -0
- data/lib/neeto_monitor_ruby/monitor.rb +158 -0
- data/lib/neeto_monitor_ruby/monitor_utils.rb +47 -0
- data/lib/neeto_monitor_ruby/neeto_monitor.rb +28 -0
- data/lib/neeto_monitor_ruby/plugin.rb +86 -0
- data/lib/neeto_monitor_ruby/plugins/sidekiq.rb +90 -0
- data/lib/neeto_monitor_ruby/version.rb +5 -0
- data/test/dummy/failure_worker.rb +14 -0
- data/test/dummy/monitor_disabled_worker.rb +14 -0
- data/test/dummy/success_worker.rb +13 -0
- data/test/neeto_monitor/configuration_test.rb +97 -0
- data/test/neeto_monitor/heartbeat_runner_job_test.rb +36 -0
- data/test/neeto_monitor/monitor_test.rb +164 -0
- data/test/neeto_monitor/neeto_monitor_test.rb +10 -0
- data/test/neeto_monitor/plugin_test.rb +78 -0
- data/test/neeto_monitor/plugins/sidekiq_test.rb +75 -0
- data/test/test_helper.rb +15 -0
- metadata +155 -0
| @@ -0,0 +1,47 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module NeetoMonitorRuby
         | 
| 4 | 
            +
              module MonitorUtils
         | 
| 5 | 
            +
                MONITOR_TYPES = {
         | 
| 6 | 
            +
                  check: "check",
         | 
| 7 | 
            +
                  job: "job",
         | 
| 8 | 
            +
                  heartbeat: "heartbeat"
         | 
| 9 | 
            +
                }.freeze
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                JOB_STATES = {
         | 
| 12 | 
            +
                  run: "run",
         | 
| 13 | 
            +
                  complete: "complete",
         | 
| 14 | 
            +
                  fail: "fail"
         | 
| 15 | 
            +
                }.freeze
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                def self.included(klass)
         | 
| 18 | 
            +
                  klass.extend(ClassMethods)
         | 
| 19 | 
            +
                end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                module ClassMethods
         | 
| 22 | 
            +
                  def job(monitor_key, &block)
         | 
| 23 | 
            +
                    monitor = Monitor.new(monitor_key)
         | 
| 24 | 
            +
                    series = monitor.generate_stamp
         | 
| 25 | 
            +
                    monitor.job_ping(state: JOB_STATES[:run], series:)
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                    block.call
         | 
| 28 | 
            +
                    monitor.job_ping(state: JOB_STATES[:complete], series:)
         | 
| 29 | 
            +
                  rescue StandardError => exception
         | 
| 30 | 
            +
                    monitor.job_ping(state: JOB_STATES[:fail], message: exception.message, series:)
         | 
| 31 | 
            +
                    raise exception
         | 
| 32 | 
            +
                  end
         | 
| 33 | 
            +
                end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                def generate_stamp
         | 
| 36 | 
            +
                  Time.now.utc.to_f
         | 
| 37 | 
            +
                end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                def generate_series
         | 
| 40 | 
            +
                  "#{generate_stamp}-#{random_string}"
         | 
| 41 | 
            +
                end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                def random_string
         | 
| 44 | 
            +
                  rand(2**256).to_s(36)[0..7]
         | 
| 45 | 
            +
                end
         | 
| 46 | 
            +
              end
         | 
| 47 | 
            +
            end
         | 
| @@ -0,0 +1,28 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require "forwardable"
         | 
| 4 | 
            +
            require_relative "configuration"
         | 
| 5 | 
            +
            require_relative "plugin"
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            module NeetoMonitorRuby
         | 
| 8 | 
            +
              extend Forwardable
         | 
| 9 | 
            +
              extend self
         | 
| 10 | 
            +
             | 
| 11 | 
            +
              attr_accessor :config, :logger
         | 
| 12 | 
            +
             | 
| 13 | 
            +
              def_delegator "NeetoMonitorRuby::Monitor", :load_monitors!
         | 
| 14 | 
            +
             | 
| 15 | 
            +
              def init!(options = {})
         | 
| 16 | 
            +
                self.config = Configuration.configure!(options, true)
         | 
| 17 | 
            +
                self.logger = self.config.logger
         | 
| 18 | 
            +
              end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
              def load_plugins!
         | 
| 21 | 
            +
                Dir[File.expand_path("../plugins/*.rb", __FILE__)].each do |plugin|
         | 
| 22 | 
            +
                  require plugin
         | 
| 23 | 
            +
                end
         | 
| 24 | 
            +
                Plugin.load!(self.config)
         | 
| 25 | 
            +
              end
         | 
| 26 | 
            +
            end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
            NeetoMonitor = NeetoMonitorRuby
         | 
| @@ -0,0 +1,86 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module NeetoMonitorRuby
         | 
| 4 | 
            +
              class Plugin
         | 
| 5 | 
            +
                class << self
         | 
| 6 | 
            +
                  @@instances = {}
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                  def instances
         | 
| 9 | 
            +
                    @@instances
         | 
| 10 | 
            +
                  end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                  def register(name = nil, &block)
         | 
| 13 | 
            +
                    raise(ArgumentError, "Plugin name is required, but was nil.") unless name
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                    key = name.to_sym
         | 
| 16 | 
            +
                    raise("Already registered: #{name}") if instances[key]
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                    instances[key] = new(name).tap { |d| d.instance_eval(&block) }
         | 
| 19 | 
            +
                  end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                  def load!(config)
         | 
| 22 | 
            +
                    instances.each_pair do |name, plugin|
         | 
| 23 | 
            +
                      if config.public_send("#{name}_enabled")
         | 
| 24 | 
            +
                        plugin.load!
         | 
| 25 | 
            +
                      else
         | 
| 26 | 
            +
                        NeetoMonitorRuby.logger.debug("skipped plugin #{name} load because it's disabled")
         | 
| 27 | 
            +
                      end
         | 
| 28 | 
            +
                    end
         | 
| 29 | 
            +
                  end
         | 
| 30 | 
            +
                end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                attr_reader :name, :requirements, :executions, :config
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                def initialize(name)
         | 
| 35 | 
            +
                  @name = name
         | 
| 36 | 
            +
                  @loaded = false
         | 
| 37 | 
            +
                  @requirements = []
         | 
| 38 | 
            +
                  @executions = []
         | 
| 39 | 
            +
                end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                def requirement(&block)
         | 
| 42 | 
            +
                  @requirements << block
         | 
| 43 | 
            +
                end
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                def execution(&block)
         | 
| 46 | 
            +
                  @executions << block
         | 
| 47 | 
            +
                end
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                def load!
         | 
| 50 | 
            +
                  if loaded?
         | 
| 51 | 
            +
                    NeetoMonitorRuby.logger.debug("skip plugin #{name} as already loaded")
         | 
| 52 | 
            +
                    return false
         | 
| 53 | 
            +
                  elsif fulfilled?
         | 
| 54 | 
            +
                    NeetoMonitorRuby.logger.debug("load plugin #{name}")
         | 
| 55 | 
            +
                    @executions.each { |block| instance_eval(&block) }
         | 
| 56 | 
            +
                    @loaded = true
         | 
| 57 | 
            +
                  else
         | 
| 58 | 
            +
                    NeetoMonitorRuby.logger.debug("skip plugin load #{name} requirement not fulfilled")
         | 
| 59 | 
            +
                  end
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                  @loaded
         | 
| 62 | 
            +
                rescue => exception
         | 
| 63 | 
            +
                  log_plugin_exception(exception)
         | 
| 64 | 
            +
                  @loaded = true
         | 
| 65 | 
            +
                  false
         | 
| 66 | 
            +
                end
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                def fulfilled?
         | 
| 69 | 
            +
                  @requirements.all? { |block| instance_eval(&block) }
         | 
| 70 | 
            +
                rescue => exception
         | 
| 71 | 
            +
                  log_plugin_exception(exception)
         | 
| 72 | 
            +
                  false
         | 
| 73 | 
            +
                end
         | 
| 74 | 
            +
             | 
| 75 | 
            +
                def loaded?
         | 
| 76 | 
            +
                  @loaded
         | 
| 77 | 
            +
                end
         | 
| 78 | 
            +
             | 
| 79 | 
            +
                private
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                  def log_plugin_exception(exception)
         | 
| 82 | 
            +
                    NeetoMonitorRuby.logger.error("plugin error name=#{name} class=#{exception.class} #{exception.message}")
         | 
| 83 | 
            +
                    NeetoMonitorRuby.logger.error e.backtrace
         | 
| 84 | 
            +
                  end
         | 
| 85 | 
            +
              end
         | 
| 86 | 
            +
            end
         | 
| @@ -0,0 +1,90 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require_relative "../monitor_utils"
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module NeetoMonitorRuby
         | 
| 6 | 
            +
              module Plugins
         | 
| 7 | 
            +
                module Sidekiq
         | 
| 8 | 
            +
                  class ServerMiddleware
         | 
| 9 | 
            +
                    SKIPPED_WORKERS = %w(ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper).freeze
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                    attr_reader :sidekiq_worker
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                    def call(worker, _message, _queue)
         | 
| 14 | 
            +
                      @sidekiq_worker = worker
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                      job_ping(state: MonitorUtils::JOB_STATES[:run])
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                      worker_result = yield
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                      job_ping(state: MonitorUtils::JOB_STATES[:complete])
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                      worker_result
         | 
| 23 | 
            +
                    rescue => exception
         | 
| 24 | 
            +
                      job_ping(state: MonitorUtils::JOB_STATES[:fail], message: exception.message)
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                      raise exception
         | 
| 27 | 
            +
                    end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                    private
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                      def job_ping(state:, message: nil)
         | 
| 32 | 
            +
                        return unless can_monitor?
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                        ::Sidekiq.logger.debug("neetoMonitor pinging key: #{monitor_key} state: #{state} message: #{message}")
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                        neeto_monitor.job_ping(state:, message:, series:)
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                      rescue => exception
         | 
| 39 | 
            +
                        ::Sidekiq.logger.error("Error: neetoMonitor ping #{monitor_key} - #{exception.message}")
         | 
| 40 | 
            +
                        ::Sidekiq.logger.error(exception.backtrace)
         | 
| 41 | 
            +
                      end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                      def series
         | 
| 44 | 
            +
                        @_series ||= neeto_monitor.generate_series
         | 
| 45 | 
            +
                      end
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                      def neeto_monitor
         | 
| 48 | 
            +
                        @_neeto_monitor ||= NeetoMonitorRuby::Monitor.new(monitor_key)
         | 
| 49 | 
            +
                      end
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                      def monitor_key
         | 
| 52 | 
            +
                        return @_monitor_key if @_monitor_key
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                        @_monitor_key = fetch_sidekiq_option("neeto_monitor_key", sidekiq_worker.class.name)
         | 
| 55 | 
            +
                        return @_monitor_key unless NeetoMonitorRuby.config.key_prefix_enabled && NeetoMonitorRuby.config.key_prefix
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                        @_monitor_key = [NeetoMonitorRuby.config.key_prefix, @_monitor_key].join("::")
         | 
| 58 | 
            +
                      end
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                      def neeto_monitor_disabled?
         | 
| 61 | 
            +
                        fetch_sidekiq_option("neeto_monitor_disabled", false)
         | 
| 62 | 
            +
                      end
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                      def fetch_sidekiq_option(key, default = nil)
         | 
| 65 | 
            +
                        sidekiq_worker.class.sidekiq_options.fetch(key, default)
         | 
| 66 | 
            +
                      end
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                      def can_monitor?
         | 
| 69 | 
            +
                        @_can_monitor ||= !neeto_monitor.api_key.nil? && !neeto_monitor_disabled? && !skip?
         | 
| 70 | 
            +
                      end
         | 
| 71 | 
            +
             | 
| 72 | 
            +
                      def skip?
         | 
| 73 | 
            +
                        SKIPPED_WORKERS.include?(monitor_key) || SKIPPED_WORKERS.include?(monitor_key.split("::", 2).last)
         | 
| 74 | 
            +
                      end
         | 
| 75 | 
            +
                  end
         | 
| 76 | 
            +
             | 
| 77 | 
            +
                  Plugin.register "sidekiq" do
         | 
| 78 | 
            +
                    requirement { defined?(::Sidekiq) }
         | 
| 79 | 
            +
             | 
| 80 | 
            +
                    execution do
         | 
| 81 | 
            +
                      ::Sidekiq.configure_server do |sidekiq|
         | 
| 82 | 
            +
                        sidekiq.server_middleware do |chain|
         | 
| 83 | 
            +
                          chain.prepend ServerMiddleware
         | 
| 84 | 
            +
                        end
         | 
| 85 | 
            +
                      end
         | 
| 86 | 
            +
                    end
         | 
| 87 | 
            +
                  end
         | 
| 88 | 
            +
                end
         | 
| 89 | 
            +
              end
         | 
| 90 | 
            +
            end
         | 
| @@ -0,0 +1,97 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require_relative "../test_helper"
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module NeetoMonitor
         | 
| 6 | 
            +
              class ConfigurationTest < Minitest::Test
         | 
| 7 | 
            +
                attr_reader :yaml_file_path
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                def setup
         | 
| 10 | 
            +
                  @yaml_file_path = "./test/config.yml"
         | 
| 11 | 
            +
                  NeetoMonitor::Configuration.reset!
         | 
| 12 | 
            +
                end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                def test_configure_with_options
         | 
| 15 | 
            +
                  config = NeetoMonitor::Configuration.configure!({ api_key: "api_key", environment: "test" }, true)
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                  assert_equal "api_key", config.api_key
         | 
| 18 | 
            +
                  assert_equal "test", config.environment
         | 
| 19 | 
            +
                end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                def test_configure_with_default_values
         | 
| 22 | 
            +
                  config = NeetoMonitor::Configuration.configure!
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                  assert_equal NeetoMonitor::Configuration::BASE_URL, config.base_url
         | 
| 25 | 
            +
                  assert_equal NeetoMonitor::Configuration::DEFAULT_PING_TIMEOUT, config.ping_timeout
         | 
| 26 | 
            +
                  assert_equal true, config.sidekiq_enabled
         | 
| 27 | 
            +
                  assert_nil config.config_path
         | 
| 28 | 
            +
                end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                def test_load_config_with_valid_file
         | 
| 31 | 
            +
                  config_file = File.new(yaml_file_path, "w")
         | 
| 32 | 
            +
                  config_file.write(YAML.dump(api_key: "dgs56sds7d", environment: "test"))
         | 
| 33 | 
            +
                  config_file.close
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                  config = NeetoMonitor::Configuration.configure!({ config_path: config_file.path }, true)
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                  assert_equal "dgs56sds7d", config.api_key
         | 
| 38 | 
            +
                  assert_equal "test", config.environment
         | 
| 39 | 
            +
                  assert_equal "https://neetomonitor.com", config.base_url
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                  File.delete(yaml_file_path)
         | 
| 42 | 
            +
                end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                def test_load_config_with_invalid_file
         | 
| 45 | 
            +
                  config_file = File.new(yaml_file_path, "w")
         | 
| 46 | 
            +
                  config_file.write("api_key kbkj'api_key'\" \n}environment: 'test'")
         | 
| 47 | 
            +
                  config_file.close
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                  assert_raises NeetoMonitor::ConfigurationError do
         | 
| 50 | 
            +
                    NeetoMonitor::Configuration.load_config(config_file.path)
         | 
| 51 | 
            +
                  end
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                  File.delete(yaml_file_path)
         | 
| 54 | 
            +
                end
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                def test_reset
         | 
| 57 | 
            +
                  config = NeetoMonitor::Configuration.configure!({ api_key: "api_key", config_path: "no-config.yml" }, true)
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                  assert_equal "https://neetomonitor.com", config.base_url
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                  NeetoMonitor::Configuration.reset!
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                  assert_equal "3d6d07b40ac6", NeetoMonitor::Configuration.config.api_key
         | 
| 64 | 
            +
                  assert_nil NeetoMonitor::Configuration.config.config_path
         | 
| 65 | 
            +
                end
         | 
| 66 | 
            +
             | 
| 67 | 
            +
                def test_load_config_with_file_not_found
         | 
| 68 | 
            +
                  assert_raises NeetoMonitor::ConfigurationError do
         | 
| 69 | 
            +
                    NeetoMonitor::Configuration.load_config("./test/not_found.yml")
         | 
| 70 | 
            +
                  end
         | 
| 71 | 
            +
                end
         | 
| 72 | 
            +
             | 
| 73 | 
            +
                def test_fetch_or_set_value_with_environment_variable
         | 
| 74 | 
            +
                  original_value = ENV["NEETO_MONITOR_API_KEY"]
         | 
| 75 | 
            +
                  ENV["NEETO_MONITOR_API_KEY"] = "env_api_key"
         | 
| 76 | 
            +
             | 
| 77 | 
            +
                  config = NeetoMonitor::Configuration.new
         | 
| 78 | 
            +
             | 
| 79 | 
            +
                  assert_equal "env_api_key", config.api_key
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                  ENV["NEETO_MONITOR_API_KEY"] = original_value
         | 
| 82 | 
            +
                end
         | 
| 83 | 
            +
             | 
| 84 | 
            +
                def test_fetch_or_set_value_with_options
         | 
| 85 | 
            +
                  config = NeetoMonitor::Configuration.new(api_key: "options_api_key")
         | 
| 86 | 
            +
             | 
| 87 | 
            +
                  assert_equal "options_api_key", config.api_key
         | 
| 88 | 
            +
                end
         | 
| 89 | 
            +
             | 
| 90 | 
            +
                def test_fetch_or_set_value_with_default_value
         | 
| 91 | 
            +
                  config = NeetoMonitor::Configuration.new
         | 
| 92 | 
            +
             | 
| 93 | 
            +
                  assert_equal 8, config.ping_timeout
         | 
| 94 | 
            +
                  assert_equal true, config.sidekiq_enabled
         | 
| 95 | 
            +
                end
         | 
| 96 | 
            +
              end
         | 
| 97 | 
            +
            end
         | 
| @@ -0,0 +1,36 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require "test_helper"
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            class NeetoMonitor::HeartbeatRunnerJobTest < ActiveJob::TestCase
         | 
| 6 | 
            +
              def test_job_pings_heartbeat_and_enqueues_another_job
         | 
| 7 | 
            +
                heartbeat_name = "neeto-heartbeat"
         | 
| 8 | 
            +
                schedule = "* * * * *"
         | 
| 9 | 
            +
                stub_ping_request
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                NeetoMonitorRuby::HeartbeatRunnerJob.perform_now(heartbeat_name, schedule)
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                assert_enqueued_with(
         | 
| 14 | 
            +
                  job: NeetoMonitorRuby::HeartbeatRunnerJob,
         | 
| 15 | 
            +
                  args: [heartbeat_name, schedule],
         | 
| 16 | 
            +
                  at: ->(t) { t > Time.now }
         | 
| 17 | 
            +
                ) do
         | 
| 18 | 
            +
                  NeetoMonitorRuby::HeartbeatRunnerJob.perform_now(heartbeat_name, schedule)
         | 
| 19 | 
            +
                end
         | 
| 20 | 
            +
              end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
              private
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                def stub_ping_request(monitor_key = "neeto-heartbeat", params = {}, status_code = 200)
         | 
| 25 | 
            +
                  stub_request(:get, "https://neetomonitor.com/tm/3d6d07b40ac6/#{monitor_key}")
         | 
| 26 | 
            +
                    .with(
         | 
| 27 | 
            +
                      query: hash_including({ "env" => "test" }.merge(params)),
         | 
| 28 | 
            +
                      headers: {
         | 
| 29 | 
            +
                        "Accept" => "application/json",
         | 
| 30 | 
            +
                        "Content-Type" => "application/json",
         | 
| 31 | 
            +
                        "User-Agent" => "neeto-monitor-ruby"
         | 
| 32 | 
            +
                      }
         | 
| 33 | 
            +
                    )
         | 
| 34 | 
            +
                    .to_return(status: status_code, body: "", headers: {})
         | 
| 35 | 
            +
                end
         | 
| 36 | 
            +
            end
         | 
| @@ -0,0 +1,164 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require_relative "../test_helper"
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            class NeetoMonitor::MonitorTest < Minitest::Test
         | 
| 6 | 
            +
              include ActiveJob::TestHelper
         | 
| 7 | 
            +
             | 
| 8 | 
            +
              def test_monitor_sends_telemetry_event
         | 
| 9 | 
            +
                stub_ping_request
         | 
| 10 | 
            +
                monitor = NeetoMonitor::Monitor.new("website-check")
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                assert monitor.ping
         | 
| 13 | 
            +
              end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
              def test_monitor_sends_telemetry_event_with_params
         | 
| 16 | 
            +
                params = { state: "run", series: "job101" }
         | 
| 17 | 
            +
                job_monitor_key = "night-job-check"
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                stub_ping_request(job_monitor_key, params)
         | 
| 20 | 
            +
                monitor = NeetoMonitor::Monitor.new(job_monitor_key)
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                assert monitor.ping(params)
         | 
| 23 | 
            +
              end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
              def test_monitor_job_sends_complete_event
         | 
| 26 | 
            +
                job_monitor_key = "night-job-check"
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                stub_ping_request(job_monitor_key, { state: "run", kind: "job" })
         | 
| 29 | 
            +
                stub_ping_request(job_monitor_key, { state: "complete", kind: "job" })
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                response = NeetoMonitor::Monitor.job job_monitor_key do
         | 
| 32 | 
            +
                  perform_addition_work(100, 200)
         | 
| 33 | 
            +
                end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                assert response
         | 
| 36 | 
            +
              end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
              def test_monitor_job_sends_fail_event
         | 
| 39 | 
            +
                job_monitor_key = "night-job-check"
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                stub_ping_request(job_monitor_key, { state: "run", kind: "job" })
         | 
| 42 | 
            +
                stub_ping_request(job_monitor_key, { state: "fail", kind: "job" })
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                assert_raises(StandardError) do
         | 
| 45 | 
            +
                  NeetoMonitor::Monitor.job job_monitor_key { raise StandardError.new }
         | 
| 46 | 
            +
                end
         | 
| 47 | 
            +
              end
         | 
| 48 | 
            +
             | 
| 49 | 
            +
              def test_load_monitors!
         | 
| 50 | 
            +
                yaml_file_path = "./test/config.yml"
         | 
| 51 | 
            +
                reset = true
         | 
| 52 | 
            +
                stub_bulk_create_request
         | 
| 53 | 
            +
                stub_ping_request("neeto-heartbeat")
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                assert_no_enqueued_jobs
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                NeetoMonitorRuby::HeartbeatRunnerJob.perform_later("neeto-heartbeat", "* * * * *")
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                assert_enqueued_jobs 1
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                config_file = File.new(yaml_file_path, "w")
         | 
| 62 | 
            +
                config_file.write(YAML.dump(config_hash))
         | 
| 63 | 
            +
                config_file.close
         | 
| 64 | 
            +
             | 
| 65 | 
            +
                NeetoMonitor.init!({ config_path: config_file.path })
         | 
| 66 | 
            +
                NeetoMonitor::Monitor.load_monitors!
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                assert_enqueued_jobs 1
         | 
| 69 | 
            +
             | 
| 70 | 
            +
                NeetoMonitor.init!
         | 
| 71 | 
            +
                File.delete(yaml_file_path)
         | 
| 72 | 
            +
              end
         | 
| 73 | 
            +
             | 
| 74 | 
            +
              private
         | 
| 75 | 
            +
             | 
| 76 | 
            +
                def stub_ping_request(monitor_key = "website-check", params = {}, status_code = 200)
         | 
| 77 | 
            +
                  stub_request(:get, "https://neetomonitor.com/tm/3d6d07b40ac6/#{monitor_key}")
         | 
| 78 | 
            +
                    .with(
         | 
| 79 | 
            +
                      query: hash_including({ "env" => "test" }.merge(params)),
         | 
| 80 | 
            +
                      headers: {
         | 
| 81 | 
            +
                        "Accept" => "application/json",
         | 
| 82 | 
            +
                        "Content-Type" => "application/json",
         | 
| 83 | 
            +
                        "User-Agent" => "neeto-monitor-ruby"
         | 
| 84 | 
            +
                      }
         | 
| 85 | 
            +
                    )
         | 
| 86 | 
            +
                    .to_return(status: status_code, body: "", headers: {})
         | 
| 87 | 
            +
                end
         | 
| 88 | 
            +
             | 
| 89 | 
            +
                def perform_addition_work(param1, param2)
         | 
| 90 | 
            +
                  param1 + param2
         | 
| 91 | 
            +
                end
         | 
| 92 | 
            +
             | 
| 93 | 
            +
                def stub_bulk_create_request
         | 
| 94 | 
            +
                  stub_request(:post, "https://neetomonitor.com/api/v1/clients/bulk_checks")
         | 
| 95 | 
            +
                    .with(
         | 
| 96 | 
            +
                      body: bulk_create_params.to_json,
         | 
| 97 | 
            +
                      headers: {
         | 
| 98 | 
            +
                        "Accept" => "application/json",
         | 
| 99 | 
            +
                        "Content-Type" => "application/json",
         | 
| 100 | 
            +
                        "User-Agent" => "neeto-monitor-ruby"
         | 
| 101 | 
            +
                      }
         | 
| 102 | 
            +
                    )
         | 
| 103 | 
            +
                    .to_return(status: 200, body: "", headers: {})
         | 
| 104 | 
            +
                end
         | 
| 105 | 
            +
             | 
| 106 | 
            +
                def config_hash
         | 
| 107 | 
            +
                  {
         | 
| 108 | 
            +
                    api_key: "3d6d07b40ac6",
         | 
| 109 | 
            +
                    environment: "test",
         | 
| 110 | 
            +
                    monitors: {
         | 
| 111 | 
            +
                      jobs: [
         | 
| 112 | 
            +
                        {
         | 
| 113 | 
            +
                          name: "neeto-job",
         | 
| 114 | 
            +
                          schedule: "0 0 * * *"
         | 
| 115 | 
            +
                        }
         | 
| 116 | 
            +
                      ],
         | 
| 117 | 
            +
                      checks: [
         | 
| 118 | 
            +
                        {
         | 
| 119 | 
            +
                          name: "neeto-monitor",
         | 
| 120 | 
            +
                          request_attributes: {
         | 
| 121 | 
            +
                            endpoint: "http://app.neetomonitor.test/health_check/",
         | 
| 122 | 
            +
                            kind: "http",
         | 
| 123 | 
            +
                            verb: "get",
         | 
| 124 | 
            +
                            interval: 10,
         | 
| 125 | 
            +
                            timeout: 20
         | 
| 126 | 
            +
                          }
         | 
| 127 | 
            +
                        }
         | 
| 128 | 
            +
                      ],
         | 
| 129 | 
            +
                      heartbeats: [
         | 
| 130 | 
            +
                        {
         | 
| 131 | 
            +
                          name: "neeto-heartbeat",
         | 
| 132 | 
            +
                          schedule: "* * * * *"
         | 
| 133 | 
            +
                        }
         | 
| 134 | 
            +
                      ]
         | 
| 135 | 
            +
                    }
         | 
| 136 | 
            +
                  }
         | 
| 137 | 
            +
                end
         | 
| 138 | 
            +
             | 
| 139 | 
            +
                def bulk_create_params
         | 
| 140 | 
            +
                  {
         | 
| 141 | 
            +
                    checks: [
         | 
| 142 | 
            +
                      {
         | 
| 143 | 
            +
                        name: "neeto-job",
         | 
| 144 | 
            +
                        schedule: "0 0 * * *",
         | 
| 145 | 
            +
                        kind: "job",
         | 
| 146 | 
            +
                        environment: "test"
         | 
| 147 | 
            +
                      },
         | 
| 148 | 
            +
                      {
         | 
| 149 | 
            +
                        name: "neeto-monitor",
         | 
| 150 | 
            +
                        request_attributes: {
         | 
| 151 | 
            +
                          endpoint: "http://app.neetomonitor.test/health_check/",
         | 
| 152 | 
            +
                          kind: "http",
         | 
| 153 | 
            +
                          verb: "get",
         | 
| 154 | 
            +
                          interval: 10,
         | 
| 155 | 
            +
                          timeout: 20
         | 
| 156 | 
            +
                        },
         | 
| 157 | 
            +
                        kind: "check",
         | 
| 158 | 
            +
                        environment: "test"
         | 
| 159 | 
            +
                      }
         | 
| 160 | 
            +
                    ],
         | 
| 161 | 
            +
                    api_key: "3d6d07b40ac6"
         | 
| 162 | 
            +
                  }
         | 
| 163 | 
            +
                end
         | 
| 164 | 
            +
            end
         | 
| @@ -0,0 +1,78 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require_relative "../test_helper"
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module NeetoMonitor
         | 
| 6 | 
            +
              class PluginTest < Minitest::Test
         | 
| 7 | 
            +
                attr_reader :config
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                def setup
         | 
| 10 | 
            +
                  NeetoMonitor::Plugin.instances.clear
         | 
| 11 | 
            +
                  @config = Configuration.new({ sidekiq_enabled: true })
         | 
| 12 | 
            +
                end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                def test_register
         | 
| 15 | 
            +
                  NeetoMonitor::Plugin.register("sidekiq", &requirement_block)
         | 
| 16 | 
            +
                  instances = NeetoMonitor::Plugin.instances
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                  assert_equal 1, instances.size
         | 
| 19 | 
            +
                  assert_equal "sidekiq", instances[:sidekiq].name
         | 
| 20 | 
            +
                  assert_instance_of NeetoMonitor::Plugin, instances[:sidekiq]
         | 
| 21 | 
            +
                end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                def test_register_without_name
         | 
| 24 | 
            +
                  assert_raises(ArgumentError) { NeetoMonitor::Plugin.register }
         | 
| 25 | 
            +
                end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                def test_register_with_duplicate_name
         | 
| 28 | 
            +
                  NeetoMonitor::Plugin.register("sidekiq", &requirement_block)
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                  assert_raises(RuntimeError) { NeetoMonitor::Plugin.register("sidekiq", &requirement_block) }
         | 
| 31 | 
            +
                end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                def test_load_enabled_plugins
         | 
| 34 | 
            +
                  @@executed = false
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                  NeetoMonitor::Plugin.register("sidekiq") do
         | 
| 37 | 
            +
                    requirement { true }
         | 
| 38 | 
            +
                    execution { @@executed = true }
         | 
| 39 | 
            +
                  end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                  NeetoMonitor::Plugin.load!(config)
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                  sidekiq_instance = NeetoMonitor::Plugin.instances[:sidekiq]
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                  assert sidekiq_instance.loaded?
         | 
| 46 | 
            +
                  assert @@executed
         | 
| 47 | 
            +
                end
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                def test_skip_disabled_plugins
         | 
| 50 | 
            +
                  NeetoMonitor::Plugin.register("sidekiq", &requirement_block)
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                  new_config = Configuration.new({ sidekiq_enabled: false })
         | 
| 53 | 
            +
                  NeetoMonitor::Plugin.load!(new_config)
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                  instances = NeetoMonitor::Plugin.instances
         | 
| 56 | 
            +
                  refute instances[:sidekiq].loaded?
         | 
| 57 | 
            +
                end
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                def test_skip_plugins_with_unfulfilled_requirements
         | 
| 60 | 
            +
                  NeetoMonitor::Plugin.register("sidekiq", &requirement_unfulfilled_block)
         | 
| 61 | 
            +
             | 
| 62 | 
            +
                  NeetoMonitor::Plugin.load!(config)
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                  instances = NeetoMonitor::Plugin.instances
         | 
| 65 | 
            +
                  refute instances[:sidekiq].loaded?
         | 
| 66 | 
            +
                end
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                private
         | 
| 69 | 
            +
             | 
| 70 | 
            +
                  def requirement_block
         | 
| 71 | 
            +
                    proc { requirement { true } }
         | 
| 72 | 
            +
                  end
         | 
| 73 | 
            +
             | 
| 74 | 
            +
                  def requirement_unfulfilled_block
         | 
| 75 | 
            +
                    proc { requirement { false } }
         | 
| 76 | 
            +
                  end
         | 
| 77 | 
            +
              end
         | 
| 78 | 
            +
            end
         |