Skip to content

Instantly share code, notes, and snippets.

@ytnobody
Created September 2, 2019 02:48
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ytnobody/fa8ffa7fc06f7f7e047f593e98dbd028 to your computer and use it in GitHub Desktop.
Save ytnobody/fa8ffa7fc06f7f7e047f593e98dbd028 to your computer and use it in GitHub Desktop.
failed etude of elixir mock server
iex(5)> sv = MyMock.run()
11:35:22.016 [info] Added handler for /user
11:35:22.016 [info] Added handler for /user/:id
%Mock.MockServer{
name: :my_mock,
pid: #PID<0.765.0>,
port: 64400,
route: %{
"/user" => %{"GET" => #Function<0.22167735/1 in MyMock.run/1>},
"/user/:id" => %{
"GET" => #Function<1.22167735/1 in MyMock.run/1>,
"POST" => #Function<2.22167735/1 in MyMock.run/1>
}
}
}
iex(6)> HTTPoison.get("http://127.0.0.1:64400/user/123")
{:ok,
%FakeServer.Response{
body: "{\"id\": 123}",
headers: %{"Content-Type" => "application/json"},
status: 200
}}
{:ok,
%HTTPoison.Response{
body: "",
headers: [{"content-length", "0"}],
request_url: "http://127.0.0.1:64400/user/123",
status_code: 500
}}
iex(7)>
11:35:43.317 [error] Ranch protocol #PID<0.783.0> of listener :my_mock (connection #PID<0.782.0>, stream id 1) terminated
** (exit) :function_clause
defmodule Mock.MockServer do
require Logger
alias FakeServer.Response
defstruct [
:pid,
:name,
route: %{},
port: 64400
]
def build(name, port \\ nil), do: %__MODULE__{name: name, port: port || 64400}
def run(sv) do
case FakeServer.start(sv.name, sv.port) do
{:error, message} -> {:error, message}
{:ok, pid} ->
sv |> Map.put(:pid, pid) |> load_handlers()
end
end
def stop(sv) do
FakeServer.stop(sv.name)
end
def route_add(sv, method, path, code) do
entry = case sv.route |> Map.has_key?(path) do
false -> %{method => code}
true -> sv.route |> Map.fetch!(path) |> Map.put(method, code)
end
route = sv.route |> Map.put(path, entry)
sv |> Map.put(:route, route)
end
def get(sv, path, code), do: route_add(sv, "GET", path, code)
def post(sv, path, code), do: route_add(sv, "POST", path, code)
def put(sv, path, code), do: route_add(sv, "PUT", path, code)
def delete(sv, path, code), do: route_add(sv, "DELETE", path, code)
def patch(sv, path, code), do: route_add(sv, "PATCH", path, code)
defp build_handler(sv, path) do
case sv.route |> Map.fetch(path) do
:error -> {:error, "Bad entry #{path}"}
{:ok, entry} ->
fn (req) ->
res = case entry |> Map.fetch(req.method) do
:error -> Response.not_found!()
{:ok, code} -> code.(req) |> build_response()
end
res |> IO.inspect()
build_response(res)
end
end
end
defp build_handlers(sv) do
handler_map_list = sv.route |> Map.keys() |> Enum.map(
fn path ->
case build_handler(sv, path) do
{:error, message} -> {:error, message}
handler -> %{path: path, handler: handler}
end
end
)
bad_items = handler_map_list |> Enum.filter(fn handler -> is_tuple(handler) end)
case bad_items do
[] -> handler_map_list
_ ->
message = bad_items
|> Enum.map(fn item -> Tuple.to_list(item) |> Enum.at(1) end)
|> Enum.join("\n")
{:error, message}
end
end
defp build_response([status_code, headers, body]), do: Response.new(status_code, body, headers || %{"Content-type" => "text/html"})
defp put_route(sv, handler_map) do
FakeServer.put_route(sv.name, handler_map.path, handler_map.handler)
end
defp load_handlers(sv) do
with pid when not is_nil(pid) <- sv.pid,
handler_map_list when not is_tuple(handler_map_list) <- build_handlers(sv)
do
handler_map_list |> Enum.each(
fn handler_map ->
case put_route(sv, handler_map) do
{:error, reason} -> Logger.error("Failed to add handler for #{handler_map.path}: #{reason}")
:ok -> Logger.info("Added handler for #{handler_map.path}")
end
end
)
sv
else
nil -> {:error, "Server not running"}
end
end
end
defmodule MyMock do
alias Mock.MockServer
def run(port \\ nil) do
MockServer.build(:my_mock, port)
|> MockServer.get( "/user", fn _req -> [200, %{"Content-Type" => "application/json"}, "{\"message\": \"Hello, world\"}"] end)
|> MockServer.get( "/user/:id", fn _req -> [200, %{"Content-Type" => "application/json"}, "{\"id\": 123}"] end)
|> MockServer.post("/user/:id", fn _req -> [200, %{"Content-Type" => "application/json"}, "{\"message\": \foo\"}"] end)
|> MockServer.run()
end
def stop(sv) do
MockServer.stop(sv)
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment