# frozen_string_literal: true

require_relative "helper"
require "sidekiq/redis_connection"
require "sidekiq/capsule"

describe Sidekiq::RedisConnection do
  describe "create" do
    before do
      @config = reset!
      @config.default_capsule.concurrency = 12
    end

    def client_for(redis)
      redis._client
    end

    def config_for(redis)
      redis._config
    end

    def client_class
      Sidekiq::RedisClientAdapter::CompatClient
    end

    it "creates a pooled redis connection" do
      pool = Sidekiq::RedisConnection.create
      assert_equal 5, pool.size
      assert_equal client_class, pool.checkout.class
    end

    # Readers for these ivars should be available in the next release of
    # `connection_pool`, until then we need to reach into the internal state to
    # verify the setting.
    describe "size" do
      def client_connection(args = {})
        Sidekiq.stub(:server?, nil) do
          @config.redis = args
          @config.redis_pool
        end
      end

      def server_connection(args = {})
        Sidekiq.stub(:server?, "constant") do
          @config.redis = args
          @config.redis_pool
        end
      end

      it "sizes default pool" do
        pool = server_connection
        assert_equal 5, pool.size
      end

      it "defaults client pool sizes to 5" do
        pool = client_connection
        assert_equal 5, pool.size
      end

      it "sizes capsule pools based on concurrency" do
        assert_equal 12, @config.default_capsule.redis_pool.size
      end

      it "does not change client pool sizes with ENV" do
        # Only Sidekiq::CLI looks at ENV
        ENV["RAILS_MAX_THREADS"] = "9"
        pool = client_connection
        assert_equal 5, pool.size
      ensure
        ENV.delete("RAILS_MAX_THREADS")
      end
    end

    it "disables client setname with nil id" do
      pool = Sidekiq::RedisConnection.create(id: nil)
      assert_equal client_class, pool.checkout.class
      client = client_for(pool.checkout)
      assert_nil client.id
    end

    describe "network_timeout" do
      it "sets a custom network_timeout if specified" do
        pool = Sidekiq::RedisConnection.create(network_timeout: 8)
        redis = pool.checkout

        assert_equal 8, client_for(redis).read_timeout
      end

      it "uses the default network_timeout if none specified" do
        pool = Sidekiq::RedisConnection.create
        redis = pool.checkout
        assert_equal 1.0, client_for(redis).read_timeout
      end
    end

    describe "namespace" do
      it "isn't supported" do
        error = assert_raises ArgumentError do
          Sidekiq::RedisConnection.create(namespace: "xxx")
        end
        assert_includes error.message, "Your Redis configuration uses the namespace 'xxx' but this feature isn't supported by redis-client"
      end
    end

    describe "socket path" do
      it "uses a given :path" do
        pool = Sidekiq::RedisConnection.create(path: "/tmp/redis.sock")
        config = config_for(pool.checkout)
        assert_equal "/tmp/redis.sock", config.path
      end

      it "uses a given :path and :db" do
        pool = Sidekiq::RedisConnection.create(path: "/tmp/redis.sock", db: 8)
        config = config_for(pool.checkout)
        assert_equal "/tmp/redis.sock", config.path
        assert_equal 8, config.db
      end
    end

    describe "pool_timeout" do
      it "uses a given :timeout over the default of 1" do
        pool = Sidekiq::RedisConnection.create(pool_timeout: 5)

        assert_equal 5, pool.instance_eval { @timeout }
      end

      it "uses the default timeout of 1 if no override" do
        pool = Sidekiq::RedisConnection.create

        assert_equal 1, pool.instance_eval { @timeout }
      end
    end

    describe "driver" do
      it "uses ruby driver by default" do
        pool = Sidekiq::RedisConnection.create
        config = config_for(pool.checkout)

        assert_equal RedisClient::RubyConnection, config.driver
      end
    end

    describe "logging redis options" do
      it "redacts credentials" do
        options = {
          role: "master",
          master_name: "mymaster",
          sentinels: [
            {host: "host1", port: 26379, password: "secret"},
            {host: "host2", port: 26379, password: "secret"},
            {host: "host3", port: 26379, password: "secret"}
          ],
          password: "secret"
        }

        output = capture_logging(@config) do |logger|
          Sidekiq::RedisConnection.create(options.merge(logger: logger))
        end

        refute_includes(options.inspect, "REDACTED")
        assert_includes(output, ':host=>"host1", :port=>26379, :password=>"REDACTED"')
        assert_includes(output, ':host=>"host2", :port=>26379, :password=>"REDACTED"')
        assert_includes(output, ':host=>"host3", :port=>26379, :password=>"REDACTED"')
        assert_includes(output, ':password=>"REDACTED"')
      end

      it "prunes SSL parameters from the logging" do
        output = capture_logging(@config) do |logger|
          options = {
            ssl_params: {
              cert_store: OpenSSL::X509::Store.new
            },
            logger: logger
          }

          Sidekiq::RedisConnection.create(options)
          assert_includes(options.inspect, "ssl_params")
        end
        refute_includes(output, "ssl_params")
      end
    end
  end

  describe ".determine_redis_provider" do
    before do
      @old_env = ENV.to_hash
    end

    after do
      ENV.update(@old_env)
    end

    def with_env_var(var, uri, skip_provider = false)
      vars = ["REDISTOGO_URL", "REDIS_PROVIDER", "REDIS_URL"] - [var]
      vars.each do |v|
        next if skip_provider
        ENV[v] = nil
      end
      ENV[var] = uri
      assert_equal uri, Sidekiq::RedisConnection.__send__(:determine_redis_provider)
      ENV[var] = nil
    end

    describe "with REDISTOGO_URL and a parallel REDIS_PROVIDER set" do
      it "sets connection URI to the provider" do
        uri = "redis://sidekiq-redis-provider:6379/0"
        provider = "SIDEKIQ_REDIS_PROVIDER"

        ENV["REDIS_PROVIDER"] = provider
        ENV[provider] = uri
        ENV["REDISTOGO_URL"] = "redis://redis-to-go:6379/0"
        with_env_var provider, uri, true

        ENV[provider] = nil
      end
    end

    describe "with REDIS_PROVIDER set" do
      it "rejects URLs in REDIS_PROVIDER" do
        uri = "redis://sidekiq-redis-provider:6379/0"

        ENV["REDIS_PROVIDER"] = uri

        assert_raises RuntimeError do
          Sidekiq::RedisConnection.__send__(:determine_redis_provider)
        end

        ENV["REDIS_PROVIDER"] = nil
      end

      it "sets connection URI to the provider" do
        uri = "redis://sidekiq-redis-provider:6379/0"
        provider = "SIDEKIQ_REDIS_PROVIDER"

        ENV["REDIS_PROVIDER"] = provider
        ENV[provider] = uri

        with_env_var provider, uri, true

        ENV[provider] = nil
      end
    end

    describe "with REDIS_URL set" do
      it "sets connection URI to custom uri" do
        with_env_var "REDIS_URL", "redis://redis-uri:6379/0"
      end
    end
  end
end
