Packages

Bypass provides a quick way to create a custom plug that can be put in place instead of an actual HTTP server to return prebaked responses to client requests. This is helpful when you want to create a mock HTTP server and test how your HTTP client handles different types of server responses.

Current section

Files

Jump to
bypass lib bypass.ex
Raw

lib/bypass.ex

defmodule Bypass do
defstruct pid: nil, port: nil, ref: nil
def start_link(ref) do
import Supervisor.Spec, warn: false
children = [
worker(Agent, [fn -> %{ref: ref, fun: nil, result: nil, socket: nil} end, [name: Bypass.State.name(ref)]])
]
Supervisor.start_link(children, strategy: :rest_for_one, max_restarts: 0)
end
def open do
import Supervisor.Spec, warn: false
ref = make_ref()
# just get a free port
{:ok, socket} = :ranch_tcp.listen(port: 0)
{:ok, port} = :inet.port(socket)
:erlang.port_close(socket)
child = supervisor(Bypass, [ref], id: ref, max_restarts: 0)
{:ok, pid} = Supervisor.start_child(Bypass.Supervisor, child)
do_up(ref, port)
bypass = %Bypass{pid: pid, port: port, ref: ref}
ExUnit.Callbacks.on_exit({Bypass, :open, ref}, fn -> Bypass.close(bypass) end)
bypass
end
def close(%Bypass{ref: ref} = _bypass) do
socket = Bypass.State.get_socket(ref)
case Supervisor.terminate_child(Bypass.Supervisor, ref) do
:ok ->
:ok = Supervisor.delete_child(Bypass.Supervisor, ref)
:ranch_server.cleanup_listener_opts(ref)
close_socket(socket)
{:error, :not_found} = error ->
error
end
end
def up(%Bypass{ref: ref, port: port}) do
do_up(ref, port)
end
def down(%Bypass{ref: ref} = _bypass) do
:ok = Supervisor.terminate_child(sup(ref), {:ranch_listener_sup, ref})
:ok = Supervisor.delete_child(sup(ref), {:ranch_listener_sup, ref})
:ranch_server.cleanup_listener_opts(ref)
close_socket(Bypass.State.get_socket(ref))
end
def expect(%Bypass{ref: ref} = _bypass, fun) do
Bypass.State.put_fun(ref, fun)
ExUnit.Callbacks.on_exit {Bypass, :expect, ref}, fn ->
case Bypass.State.get_result(ref) do
:ok -> :ok
nil -> raise ExUnit.AssertionError, "No HTTP request arrived at Bypass"
{class, reason, stacktrace} -> :erlang.raise(class, reason, stacktrace)
end
end
end
def cancel_expect(%Bypass{ref: ref} = _bypass) do
Bypass.State.put_fun(ref, nil)
ExUnit.Callbacks.on_exit {Bypass, :expect, ref}, fn -> :ok end
end
defp sup(ref) do
{^ref, supervisor, _, _} = Supervisor.which_children(Bypass.Supervisor) |> List.keyfind(ref, 0)
supervisor
end
defp do_up(ref, port) do
socket = Bypass.State.make_socket(ref, port)
ExUnit.Callbacks.on_exit({Bypass, :close_socket, ref}, fn -> close_socket(socket) end)
{:ok, _} = Supervisor.start_child(sup(ref), Bypass.Plug.child_spec(ref, port, socket))
end
# Close socket if it's open.
defp close_socket(socket) do
case :erlang.port_info(socket, :name) do
:undefined -> :ok
_ -> :erlang.port_close(socket)
end
end
end