describe Appsignal::Hooks::RedisClientHook do
  let(:options) { {} }
  before do
    start_agent(:options => options)
  end

  if DependencyHelper.redis_client_present?
    context "with redis-client" do
      context "with instrumentation enabled" do
        describe "#dependencies_present?" do
          subject { described_class.new.dependencies_present? }

          context "with gem version new than 0.14.0" do
            before { stub_const("RedisClient::VERSION", "1.2.3") }

            it { is_expected.to be_truthy }
          end

          context "with gem version 0.14.0" do
            before { stub_const("RedisClient::VERSION", "0.14.0") }

            it { is_expected.to be_truthy }
          end

          context "with gem version older than 0.14.0" do
            before { stub_const("RedisClient::VERSION", "0.13.9") }

            it { is_expected.to be_falsy }
          end
        end

        context "with rest-client gem" do
          describe "integration" do
            let(:options) { { :instrument_redis => true } }

            context "install" do
              before do
                Appsignal::Hooks.load_hooks
              end

              it "includes the integration for the ruby connection" do
                # Test if the last included module (prepended module) was our
                # integration. That's not certain with the assertions below
                # because we have to overwrite the `process` method for the test.
                expect(RedisClient::RubyConnection.included_modules.first)
                  .to eql(Appsignal::Integrations::RedisClientIntegration)
              end
            end

            context "requirements" do
              it "driver should have the write method" do
                # Since we stub the driver class below, to make sure that we don't
                # create a real connection, the test won't fail if the method definition
                # is changed.
                method = RedisClient::RubyConnection.instance_method(:write)
                expect(method.arity).to eql(1)
              end
            end

            context "instrumentation" do
              before do
                start_agent
                # Stub RedisClient::RubyConnection class so that it doesn't perform an actual
                # Redis query. This class will be included (prepended) with the
                # AppSignal Redis integration.
                stub_const("RedisClient::RubyConnection", Class.new do
                  def initialize(config)
                    @config = config
                  end

                  def write(_commands)
                    "stub_write"
                  end
                end)
                # Load the integration again for the stubbed RedisClient::RubyConnection class.
                # Call it directly because {Appsignal::Hooks.load_hooks} keeps
                # track if it was installed already or not.
                Appsignal::Hooks::RedisClientHook.new.install
              end
              let(:transaction) { http_request_transaction }
              let!(:client_config) { RedisClient::Config.new(:id => "stub_id") }
              before { set_current_transaction(transaction) }
              around { |example| keep_transactions { example.run } }

              it "instrument a redis call" do
                connection = RedisClient::RubyConnection.new client_config
                expect(connection.write([:get, "key"])).to eql("stub_write")

                expect(transaction).to include_event(
                  "name" => "query.redis",
                  "body" => "get ?",
                  "title" => "stub_id"
                )
              end

              it "instrument a redis script call" do
                connection = ::RedisClient::RubyConnection.new client_config
                script = "return redis.call('set',KEYS[1],ARGV[1])"
                keys = ["foo"]
                argv = ["bar"]
                expect(connection.write([:eval, script, keys.size, keys, argv]))
                  .to eql("stub_write")

                expect(transaction).to include_event(
                  "name" => "query.redis",
                  "body" => "#{script} ? ?",
                  "title" => "stub_id"
                )
              end
            end
          end
        end

        if DependencyHelper.hiredis_client_present?
          context "with hiredis driver" do
            describe "integration" do
              let(:options) { { :instrument_redis => true } }

              context "install" do
                before do
                  Appsignal::Hooks.load_hooks
                end

                it "includes the integration in the HiredisConnection class" do
                  # Test if the last included module (prepended module) was our
                  # integration. That's not certain with the assertions below
                  # because we have to overwrite the `process` method for the test.
                  expect(RedisClient::HiredisConnection.included_modules.first)
                    .to eql(Appsignal::Integrations::RedisClientIntegration)
                end
              end

              context "requirements" do
                it "driver should have the write method" do
                  # Since we stub the driver class below, to make sure that we don't
                  # create a real connection, the test won't fail if the method definition
                  # is changed.
                  method = RedisClient::HiredisConnection.instance_method(:write)
                  expect(method.arity).to eql(1)
                end
              end

              context "instrumentation" do
                before do
                  start_agent
                  # Stub RedisClient::HiredisConnection class so that it doesn't perform an actual
                  # Redis query. This class will be included (prepended) with the
                  # AppSignal Redis integration.
                  stub_const("RedisClient::HiredisConnection", Class.new do
                    def initialize(config)
                      @config = config
                    end

                    def write(_commands)
                      "stub_write"
                    end
                  end)
                  # Load the integration again for the stubbed RedisClient::HiredisConnection class.
                  # Call it directly because {Appsignal::Hooks.load_hooks} keeps
                  # track if it was installed already or not.
                  Appsignal::Hooks::RedisClientHook.new.install
                end
                let(:transaction) { http_request_transaction }
                let!(:client_config) { RedisClient::Config.new(:id => "stub_id") }
                before { set_current_transaction(transaction) }
                around { |example| keep_transactions { example.run } }

                it "instrument a redis call" do
                  connection = RedisClient::HiredisConnection.new client_config
                  expect(connection.write([:get, "key"])).to eql("stub_write")

                  expect(transaction).to include_event(
                    "name" => "query.redis",
                    "body" => "get ?",
                    "title" => "stub_id"
                  )
                end

                it "instrument a redis script call" do
                  connection = ::RedisClient::HiredisConnection.new client_config
                  script = "return redis.call('set',KEYS[1],ARGV[1])"
                  keys = ["foo"]
                  argv = ["bar"]
                  expect(connection.write([:eval, script, keys.size, keys,
                                           argv])).to eql("stub_write")

                  expect(transaction).to include_event(
                    "name" => "query.redis",
                    "body" => "#{script} ? ?",
                    "title" => "stub_id"
                  )
                end
              end
            end
          end
        end
      end

      context "with instrumentation disabled" do
        let(:options) { { :instrument_redis => false } }

        describe "#dependencies_present?" do
          subject { described_class.new.dependencies_present? }

          it { is_expected.to be_falsy }
        end
      end
    end
  else
    context "without redis-client" do
      describe "#dependencies_present?" do
        subject { described_class.new.dependencies_present? }

        it { is_expected.to be_falsy }
      end
    end
  end
end
