<|start_filename|>test/assertions_test.exs<|end_filename|>
defmodule DoubleBypass.AssertionsTest do
  use ExUnit.Case
  use Plug.Test
  import Plug.Conn

  @path "/path"
  @query "query=query"
  @method "POST"
  @headers [{"content-type", "application/json"}, {"keep-alive", "timeout=5"}]
  @json_resp_body %{"body" => "body"}
  @string_resp_body "body"
  @status_code 204
  @resp_headers [{"location", "http://localhost"}]

  setup_all do
    body = @json_resp_body |> Jason.encode!()

    conn =
      conn(:post, @path <> "?query=query", body)
      |> put_req_header("content-type", "application/json")
      |> put_req_header("keep-alive", "timeout=5")

    %{conn: conn}
  end

  test "test path", %{conn: conn} do
    DoubleBypass.Assertions.run(conn, %{path: @path})
  end

  test "test query", %{conn: conn} do
    DoubleBypass.Assertions.run(conn, %{query: @query})
  end

  test "test method", %{conn: conn} do
    DoubleBypass.Assertions.run(conn, %{method: @method})
  end

  test "test headers", %{conn: conn} do
    DoubleBypass.Assertions.run(conn, %{headers: @headers})
  end

  test "test json body", %{conn: conn} do
    DoubleBypass.Assertions.run(conn, %{body: @json_resp_body})
  end

  test "test string body" do
    conn = conn(:post, "/", @string_resp_body)
    DoubleBypass.Assertions.run(conn, %{body: @string_resp_body})
  end

  test "test multiple params", %{conn: conn} do
    DoubleBypass.Assertions.run(conn, %{
      path: @path,
      query: @query,
      method: @method,
      headers: @headers,
      body: @json_resp_body
    })
  end

  test "test json response", %{conn: conn} do
    resp = DoubleBypass.Assertions.run(conn, %{response: @json_resp_body})
    assert resp.resp_body |> Jason.decode!() == @json_resp_body
  end

  test "test string response", %{conn: conn} do
    resp = DoubleBypass.Assertions.run(conn, %{response: @string_resp_body})
    assert resp.resp_body == @string_resp_body
  end

  test "test status code", %{conn: conn} do
    resp = DoubleBypass.Assertions.run(conn, %{status_code: @status_code})
    assert resp.status == @status_code
  end

  test "test response headers code", %{conn: conn} do
    [header] = @resp_headers
    resp = DoubleBypass.Assertions.run(conn, %{resp_headers: @resp_headers})
    assert Enum.member?(resp.resp_headers, header)
  end

  test "test status code and json response", %{conn: conn} do
    resp =
      DoubleBypass.Assertions.run(conn, %{
        response: @json_resp_body,
        status_code: @status_code
      })

    assert resp.status == @status_code
    assert resp.resp_body |> Jason.decode!() == @json_resp_body
  end
end


<|start_filename|>lib/double_bypass.ex<|end_filename|>
defmodule DoubleBypass do
  @moduledoc """
  Responsible for initializing and configuring the Bypass servers.
  """
  use ExUnit.CaseTemplate

  def setup_bypass?(tags, bypass_tags) do
    Enum.any?(bypass_tags, fn {bypass_tag, _host} ->
      bypass_tag in Map.keys(tags)
    end)
  end

  def setup_bypass(tags, bypass_tags, options \\ %{}) do
    {:ok, _} = Application.ensure_all_started(:bypass)
    init(%{}, tags, bypass_tags, options)
  end

  defp init(acc, _tags, [], _options), do: acc

  defp init(acc, tags, _bypass_tags, _options) when tags == %{}, do: acc

  defp init(acc, tags, [{bypass_tag, environment_variable} | t], options)
       when is_bitstring(environment_variable) do
    case tags[bypass_tag] do
      nil ->
        init(acc, tags, t, options)

      map ->
        acc
        |> Map.put(
          bypass_tag,
          init_server(map, %{
            getter: fn -> System.get_env(environment_variable) end,
            setter: &System.put_env(environment_variable, &1)
          })
        )
        |> init(tags, t, options)
    end
  end

  defp init(acc, tags, [{bypass_tag, host_opts} | t], options) do
    case tags[bypass_tag] do
      nil ->
        init(acc, tags, t, options)

      map ->
        getter = getter(host_opts, options)
        setter = setter(host_opts, options)

        opts =
          host_opts
          |> Map.put(:getter, getter)
          |> Map.put(:setter, setter)

        acc
        |> Map.put(bypass_tag, init_server(map, opts))
        |> init(tags, t, options)
    end
  end

  defp init_server(opts, %{getter: getter, setter: setter}) do
    bypass = Bypass.open()
    original_config = getter.()
    setter.("http://localhost:#{bypass.port}")

    if map_size(opts) > 0 do
      Bypass.expect(bypass, &DoubleBypass.Assertions.run(&1, opts))
    end

    onexit(setter, original_config)
    bypass
  end

  defp onexit(setter, original_config) do
    on_exit(fn ->
      if original_config, do: setter.(original_config)
      :ok
    end)
  end

  defp getter(%{getter: getter}, _options), do: getter

  defp getter(%{key: key}, %{getter: getter}), do: fn -> getter.(key) end

  defp setter(%{setter: setter}, _options), do: setter

  defp setter(%{key: key}, %{setter: setter}), do: fn bypass_url -> setter.(key, bypass_url) end
end


<|start_filename|>test/double_bypass_test.exs<|end_filename|>
defmodule DoubleBypass.Config do
  @moduledoc false

  @config %{
    service_two_host: "www.test-url.com",
    service_three_host: "www.test-url.com"
  }

  use Agent

  def start_link do
    Agent.start_link(fn -> @config end, name: __MODULE__)
  end

  def put(config, value) when is_atom(config) do
    Agent.update(__MODULE__, &Map.put(&1, config, value))
  end

  def get(key) when is_atom(key) do
    get([key])
  end

  def get(keys) when is_list(keys) do
    get_in(Agent.get(__MODULE__, & &1), keys)
  end

  def stop do
    Agent.stop(__MODULE__)
  end
end

defmodule DoubleBypassTest do
  use ExUnit.Case

  @tags %{
    service_one_bypass: %{test: "params"},
    service_two_bypass: %{test: "params"},
    service_three_bypass: %{test: "params"}
  }

  defp bypass_tags do
    [
      service_one_bypass: "SERVICE_HOST",
      service_two_bypass: %{
        setter: &DoubleBypass.Config.put(:service_two_host, &1),
        getter: fn -> DoubleBypass.Config.get(:service_two_host) end
      },
      service_three_bypass: %{key: :service_three_host}
    ]
  end

  setup_all do
    {:ok, _pid} = DoubleBypass.Config.start_link()
    :ok
  end

  test "setup_bypass?" do
    assert DoubleBypass.setup_bypass?(@tags, bypass_tags())
    refute DoubleBypass.setup_bypass?(@tags, service_four_bypass: "SERVICE_FOUR_HOST")
  end

  test "setup_bypass" do
    DoubleBypass.setup_bypass(@tags, bypass_tags(), %{
      setter: &DoubleBypass.Config.put/2,
      getter: &DoubleBypass.Config.get/1
    })

    HTTPoison.start()
    HTTPoison.get!(System.get_env("SERVICE_HOST"))
    HTTPoison.get!(DoubleBypass.Config.get(:service_two_host))
    HTTPoison.get!(DoubleBypass.Config.get(:service_three_host))
  end
end
