defmodule Mojito.Pool.Poolboy.SingleTest do
  use ExSpec, async: true
  doctest Mojito.Pool.Poolboy.Single
  doctest Mojito.ConnServer

  context "Mojito.Pool.Single" do
    @http_port Application.get_env(:mojito, :test_server_http_port)
    @https_port Application.get_env(:mojito, :test_server_https_port)

    defp with_pool(fun) do
      rand = round(:rand.uniform() * 1_000_000_000)
      pool_name = "TestPool#{rand}" |> String.to_atom()
      {:ok, pid} = start_pool(pool_name, size: 2, max_overflow: 1)
      fun.(pool_name)
      GenServer.stop(pid)
    end

    defp start_pool(name, opts) do
      children = [Mojito.Pool.Poolboy.Single.child_spec([{:name, name} | opts])]
      Supervisor.start_link(children, strategy: :one_for_one)
    end

    defp get(pool, path, opts \\ []) do
      Mojito.Pool.Poolboy.Single.request(
        pool,
        :get,
        "http://localhost:#{@http_port}#{path}",
        [],
        "",
        opts
      )
    end

    defp get_ssl(pool, path, opts \\ []) do
      Mojito.Pool.Poolboy.Single.request(
        pool,
        :get,
        "https://localhost:#{@https_port}#{path}",
        [],
        "",
        [transport_opts: [verify: :verify_none]] ++ opts
      )
    end

    it "can make HTTP requests" do
      with_pool(fn pool_name ->
        assert({:ok, response} = get(pool_name, "/"))
        assert(200 == response.status_code)
      end)
    end

    it "can make HTTPS requests" do
      with_pool(fn pool_name ->
        assert({:ok, response} = get_ssl(pool_name, "/"))
        assert(200 == response.status_code)
      end)
    end

    it "can get a max body size error" do
      with_pool(fn pool_name ->
        assert(
          {:error, %Mojito.Error{message: nil, reason: :max_body_size_exceeded}} ==
            get(pool_name, "/infinite", max_body_size: 10)
        )
      end)
    end

    it "can saturate pool" do
      with_pool(fn pool_name ->
        spawn(fn -> get(pool_name, "/wait1") end)
        spawn(fn -> get(pool_name, "/wait1") end)
        spawn(fn -> get(pool_name, "/wait1") end)
        spawn(fn -> get(pool_name, "/wait1") end)
        :timer.sleep(100)

        assert(
          {:error, %{reason: :checkout_timeout}} =
            get(pool_name, "/wait1", timeout: 50)
        )

        ## 0 ready, 1 waiting, 3 in-progress
        assert({:full, 0, 1, 3} = :poolboy.status(pool_name))

        :timer.sleep(1000)

        ## 1 ready, 0 waiting, 1 in-progress (the one previously waiting)
        assert({:ready, 1, 0, 1} = :poolboy.status(pool_name))

        :timer.sleep(1000)

        ## 2 ready, 0 waiting, 0 in progress (all done)
        assert({:ready, 2, 0, 0} = :poolboy.status(pool_name))
      end)
    end

    it "retries request when connection was closed" do
      with_pool(fn pool_name ->
        1..100
        |> Enum.each(fn _ ->
          assert({:ok, _resp} = get(pool_name, "/"))
        end)
      end)
    end
  end
end
