Skip to main content

lib/container/redis_container.ex

# SPDX-License-Identifier: MIT
defmodule TestcontainerEx.RedisContainer do
  @moduledoc """
  Provides functionality for creating and managing Redis container configurations.
  """

  alias TestcontainerEx.CommandWaitStrategy
  alias TestcontainerEx.Container.Builder
  alias TestcontainerEx.Container.Config
  alias TestcontainerEx.RedisContainer

  use TestcontainerEx.ContainerConfig

  @default_image "redis"
  @default_tag "7.2-alpine"
  @default_image_with_tag "#{@default_image}:#{@default_tag}"
  @default_port 6379
  @default_wait_timeout 60_000

  @type t :: %__MODULE__{}

  @enforce_keys [:image, :port, :wait_timeout]
  defstruct [
    :image,
    :port,
    :wait_timeout,
    :name,
    check_image: @default_image,
    reuse: false,
    password: nil
  ]

  def new,
    do: %__MODULE__{
      image: @default_image_with_tag,
      wait_timeout: @default_wait_timeout,
      port: @default_port,
      password: nil
    }

  def with_image(%__MODULE__{} = config, image) when is_binary(image) do
    %{config | image: image}
  end

  def with_port(%__MODULE__{} = config, port) when is_integer(port) do
    %{config | port: port}
  end

  def with_password(%__MODULE__{} = config, password) when is_binary(password) do
    %{config | password: password}
  end

  def with_wait_timeout(%__MODULE__{} = config, wait_timeout) when is_integer(wait_timeout) do
    %{config | wait_timeout: wait_timeout}
  end

  @doc """
  Sets the container name.
  """
  @spec with_name(t(), String.t()) :: t()
  def with_name(%__MODULE__{} = config, name) when is_binary(name) do
    %__MODULE__{config | name: name}
  end

  def default_image, do: @default_image

  def port(%Config{} = container), do: TestcontainerEx.get_port(container, @default_port)

  def connection_url(%Config{} = container) do
    password = container.environment[:REDIS_PASSWORD]
    auth_part = if password, do: ":#{password}@", else: ""
    "redis://#{auth_part}#{TestcontainerEx.get_host(container)}:#{port(container)}/"
  end

  defimpl Builder do
    @spec build(RedisContainer.t()) :: Config.t()
    @impl true
    def build(%RedisContainer{} = config) do
      container =
        Config.new(config.image)
        |> Config.with_exposed_port(config.port)
        |> Config.with_check_image(config.check_image)
        |> Config.with_reuse(config.reuse)
        |> then(fn cfg ->
          if config.name, do: Config.with_name(cfg, config.name), else: cfg
        end)

      container =
        if config.password do
          container
          |> Config.with_cmd(["redis-server", "--requirepass", config.password])
          |> Config.with_waiting_strategy(
            CommandWaitStrategy.new(
              ["redis-cli", "-a", config.password, "PING"],
              config.wait_timeout
            )
          )
          |> Config.with_environment("REDIS_PASSWORD", config.password)
        else
          container
          |> Config.with_waiting_strategy(
            CommandWaitStrategy.new(["redis-cli", "PING"], config.wait_timeout)
          )
        end

      Config.valid_image!(container)
    end

    @impl true
    def after_start(_config, _container, _conn), do: :ok
  end
end