Refactor mocks

This commit is contained in:
Mike Cifelli 2024-04-19 11:34:37 -04:00
parent a17857576d
commit 9c3523d9d1
Signed by: mike
GPG Key ID: 6B08C6BE47D08E4C
10 changed files with 86 additions and 52 deletions

View File

@ -21,5 +21,8 @@ config :phoenix, :plug_init_mode, :runtime
config :chronoscope, Chronoscope.NTS, config :chronoscope, Chronoscope.NTS,
behaviour: Chronoscope.NTS.BehaviourMock, behaviour: Chronoscope.NTS.BehaviourMock,
datetime: Chronoscope.NTS.DateTimeMock, date_time: Chronoscope.NTS.DateTimeMock,
ssl: Chronoscope.NTS.SSLMock ssl: Chronoscope.NTS.SSLMock,
registry: Chronoscope.NTS.RegistryMock,
dynamic_supervisor: Chronoscope.NTS.DynamicSupervisorMock,
gen_server: Chronoscope.NTS.GenServerMock

View File

@ -9,22 +9,26 @@ defmodule Chronoscope.NTS do
alias Chronoscope.NTS alias Chronoscope.NTS
@registry Application.compile_env(:chronoscope, Chronoscope.NTS)[:registry] || Registry
@genserver Application.compile_env(:chronoscope, Chronoscope.NTS)[:gen_server] || GenServer
@dynamic_supervisor Application.compile_env(:chronoscope, Chronoscope.NTS)[:dynamic_supervisor] || DynamicSupervisor
def healthy?() do def healthy?() do
true true
end end
def list() do def list() do
NTS.DynamicSupervisor NTS.DynamicSupervisor
|> DynamicSupervisor.which_children() |> @dynamic_supervisor.which_children()
|> Enum.map(fn {_, pid, _, _} -> GenServer.call(pid, :list) end) |> Enum.map(fn {_, pid, _, _} -> @genserver.call(pid, :list) end)
end end
def remove(host, port) do def remove(host, port) do
name = client_name(%{host: host, port: port}) name = client_name(%{host: host, port: port})
case Registry.lookup(NTS.Registry, name) do case @registry.lookup(NTS.Registry, name) do
[{pid, _}] -> GenServer.call(pid, :terminate) [{pid, _}] -> {:ok, @genserver.call(pid, :terminate)}
[] -> {:error, :notfound} [] -> {:error, :not_found}
end end
end end
@ -32,13 +36,13 @@ defmodule Chronoscope.NTS do
def key_establishment(host, port) do def key_establishment(host, port) do
%{host: host, port: port} %{host: host, port: port}
|> client_pid() |> client_pid()
|> GenServer.call(:key_establishment) |> @genserver.call(:key_establishment)
end end
defp client_pid(server) do defp client_pid(server) do
name = client_name(server) name = client_name(server)
case Registry.lookup(NTS.Registry, name) do case @registry.lookup(NTS.Registry, name) do
[{pid, _}] -> pid [{pid, _}] -> pid
[] -> start_client(server, name) [] -> start_client(server, name)
end end
@ -50,7 +54,7 @@ defmodule Chronoscope.NTS do
defp start_client(server, name) do defp start_client(server, name) do
NTS.DynamicSupervisor NTS.DynamicSupervisor
|> DynamicSupervisor.start_child({NTS.Client, server: server, name: {:via, Registry, {NTS.Registry, name}}}) |> @dynamic_supervisor.start_child({NTS.Client, server: server, name: {:via, @registry, {NTS.Registry, name}}})
|> then(fn {:ok, pid} -> pid end) |> then(fn {:ok, pid} -> pid end)
end end
end end

View File

@ -1,4 +1,6 @@
defmodule Chronoscope.NTS.Certificate do defmodule Chronoscope.NTS.Certificate do
@date_time Application.compile_env(:chronoscope, Chronoscope.NTS)[:date_time] || DateTime
def expiration_date(certificate) do def expiration_date(certificate) do
{:Validity, _, {:utcTime, expiration}} = {:Validity, _, {:utcTime, expiration}} =
certificate certificate
@ -20,7 +22,7 @@ defmodule Chronoscope.NTS.Certificate do
defp short_year_to_full_year(short_year) do defp short_year_to_full_year(short_year) do
{century, current_year} = {century, current_year} =
datetime().utc_now().year @date_time.utc_now().year
|> to_string() |> to_string()
|> String.split_at(-2) |> String.split_at(-2)
@ -32,8 +34,4 @@ defmodule Chronoscope.NTS.Certificate do
|> then(&"#{&1 + 1}#{short_year}") |> then(&"#{&1 + 1}#{short_year}")
end end
end end
defp datetime() do
Application.get_env(:chronoscope, Chronoscope.NTS)[:datetime] || DateTime
end
end end

View File

@ -5,6 +5,7 @@ defmodule Chronoscope.NTS.Client do
alias Chronoscope.NTS.KeyEstablishmentClient alias Chronoscope.NTS.KeyEstablishmentClient
@interval_in_seconds 30 @interval_in_seconds 30
@date_time Application.compile_env(:chronoscope, Chronoscope.NTS)[:date_time] || DateTime
def start_link(server: server, name: name) do def start_link(server: server, name: name) do
GenServer.start_link(__MODULE__, server, name: name) GenServer.start_link(__MODULE__, server, name: name)
@ -70,10 +71,6 @@ defmodule Chronoscope.NTS.Client do
end end
defp utc_now() do defp utc_now() do
datetime().utc_now() @date_time.utc_now()
end
defp datetime() do
Application.get_env(:chronoscope, Chronoscope.NTS)[:datetime] || DateTime
end end
end end

View File

@ -6,6 +6,7 @@ defmodule Chronoscope.NTS.KeyEstablishmentClient do
alias Chronoscope.NTS.KeyEstablishmentResponse alias Chronoscope.NTS.KeyEstablishmentResponse
@timeout_in_milliseconds 3000 @timeout_in_milliseconds 3000
@ssl Application.compile_env(:chronoscope, Chronoscope.NTS)[:ssl] || :ssl
def key_establishment(%{host: host, port: port}) do def key_establishment(%{host: host, port: port}) do
case ssl_connect(host, port) do case ssl_connect(host, port) do
@ -21,7 +22,7 @@ defmodule Chronoscope.NTS.KeyEstablishmentClient do
defp ssl_connect(host, port) do defp ssl_connect(host, port) do
host host
|> String.to_charlist() |> String.to_charlist()
|> ssl().connect(port, tls_options(host), @timeout_in_milliseconds) |> @ssl.connect(port, tls_options(host), @timeout_in_milliseconds)
end end
defp tls_options(host) do defp tls_options(host) do
@ -31,21 +32,21 @@ defmodule Chronoscope.NTS.KeyEstablishmentClient do
end end
defp perform_key_establishment(socket) do defp perform_key_establishment(socket) do
:ok = ssl().send(socket, KeyEstablishmentRequest.create()) :ok = @ssl.send(socket, KeyEstablishmentRequest.create())
{:ok, peercert} = ssl().peercert(socket) {:ok, peercert} = @ssl.peercert(socket)
receive do receive do
{:ssl, _socket, response} -> {:ssl, _socket, response} ->
ssl().close(socket) @ssl.close(socket)
parse_response(response, peercert) parse_response(response, peercert)
msg -> msg ->
ssl().close(socket) @ssl.close(socket)
Logger.error("received unexpected message: #{inspect(msg)}") Logger.error("received unexpected message: #{inspect(msg)}")
{:error, :no_response} {:error, :no_response}
after after
@timeout_in_milliseconds -> @timeout_in_milliseconds ->
ssl().close(socket) @ssl.close(socket)
Logger.error("timed out waiting for response") Logger.error("timed out waiting for response")
{:error, :timeout} {:error, :timeout}
end end
@ -67,8 +68,4 @@ defmodule Chronoscope.NTS.KeyEstablishmentClient do
{:error, String.trim(error)} {:error, String.trim(error)}
end end
end end
defp ssl() do
Application.get_env(:chronoscope, Chronoscope.NTS)[:ssl] || :ssl
end
end end

View File

@ -7,6 +7,7 @@ defmodule ChronoscopeWeb.API.V1.NTS.KeyEstablishmentController do
@default_port 4460 @default_port 4460
@max_host_length 255 @max_host_length 255
@nts Application.compile_env(:chronoscope, NTS)[:behaviour] || NTS
def get(conn, %{"host" => host, "port" => port}) do def get(conn, %{"host" => host, "port" => port}) do
try do try do
@ -41,7 +42,7 @@ defmodule ChronoscopeWeb.API.V1.NTS.KeyEstablishmentController do
defp key_establishment_response(host, port) do defp key_establishment_response(host, port) do
host host
|> String.slice(0, @max_host_length) |> String.slice(0, @max_host_length)
|> nts_behaviour().key_establishment(port) |> @nts.key_establishment(port)
end end
defp format_response(response) do defp format_response(response) do
@ -55,8 +56,4 @@ defmodule ChronoscopeWeb.API.V1.NTS.KeyEstablishmentController do
|> put_status(:bad_request) |> put_status(:bad_request)
|> json(%{error: message}) |> json(%{error: message})
end end
defp nts_behaviour() do
Application.get_env(:chronoscope, NTS)[:behaviour] || NTS
end
end end

View File

@ -1,7 +1,14 @@
defmodule Chronoscope.NTSTest do defmodule Chronoscope.NTSTest do
use Chronoscope.Case use Chronoscope.Case
alias Chronoscope.NTS.DynamicSupervisorMock
alias Chronoscope.NTS.GenServerMock
alias Chronoscope.NTS.RegistryMock
import Chronoscope.NTS import Chronoscope.NTS
import Mox
setup :verify_on_exit!
describe "Chronoscope.NTS.healthy?()" do describe "Chronoscope.NTS.healthy?()" do
test "is healthy" do test "is healthy" do
@ -11,13 +18,30 @@ defmodule Chronoscope.NTSTest do
describe "Chronoscope.NTS.list()" do describe "Chronoscope.NTS.list()" do
test "shows empty client list" do test "shows empty client list" do
DynamicSupervisorMock
|> expect(:which_children, fn _ -> [] end)
assert list() == [] assert list() == []
end end
test "lists all children" do
DynamicSupervisorMock
|> expect(:which_children, fn _ -> [{1, 2, 3, 4}, {5, 6, 7, 8}] end)
GenServerMock
|> expect(:call, fn 2, :list -> :one end)
|> expect(:call, fn 6, :list -> :two end)
assert list() == [:one, :two]
end
end end
describe "Chronoscope.NTS.remove()" do describe "Chronoscope.NTS.remove()" do
test "does nothing if the client doesn't exist" do test "does nothing if the client doesn't exist" do
assert remove("localhost", 1111) == {:error, :notfound} RegistryMock
|> expect(:lookup, fn _, _ -> [] end)
assert remove("localhost", 1111) == {:error, :not_found}
end end
end end
end end

View File

@ -0,0 +1,23 @@
defmodule Chronoscope.DateTime.Behaviour do
@callback utc_now :: DateTime.t()
end
defmodule Chronoscope.SSL.Behaviour do
@callback connect(any(), any(), any(), any()) :: {:ok, any()} | {:error, any()}
@callback send(any(), any()) :: :ok | {:error, any()}
@callback peercert(any()) :: {:ok, any()} | {:error, any()}
@callback close(any()) :: :ok | {:error, any()}
end
defmodule Chronoscope.Registry.Behaviour do
@callback lookup(atom(), any()) :: [{pid(), any()}]
end
defmodule Chronoscope.DynamicSupervisor.Behaviour do
@callback start_child(Supervisor.supervisor(), any()) :: any()
@callback which_children(Supervisor.supervisor()) :: [any()]
end
defmodule Chronoscope.GenServer.Behaviour do
@callback call(pid(), any()) :: any()
end

View File

@ -1,19 +1,6 @@
defmodule Chronoscope.DateTime.Behaviour do
@callback utc_now :: DateTime.t()
end
defmodule Chronoscope.DateTime.Stub do
@behaviour Chronoscope.DateTime.Behaviour
def utc_now(), do: DateTime.utc_now()
end
defmodule Chronoscope.SSL.Behaviour do
@callback connect(any(), any(), any(), any()) :: {:ok, any()} | {:error, any()}
@callback send(any(), any()) :: :ok | {:error, any()}
@callback peercert(any()) :: {:ok, any()} | {:error, any()}
@callback close(any()) :: :ok | {:error, any()}
end
Mox.defmock(Chronoscope.NTS.BehaviourMock, for: Chronoscope.NTS.Behaviour) Mox.defmock(Chronoscope.NTS.BehaviourMock, for: Chronoscope.NTS.Behaviour)
Mox.defmock(Chronoscope.NTS.DateTimeMock, for: Chronoscope.DateTime.Behaviour) Mox.defmock(Chronoscope.NTS.DateTimeMock, for: Chronoscope.DateTime.Behaviour)
Mox.defmock(Chronoscope.NTS.SSLMock, for: Chronoscope.SSL.Behaviour) Mox.defmock(Chronoscope.NTS.SSLMock, for: Chronoscope.SSL.Behaviour)
Mox.defmock(Chronoscope.NTS.RegistryMock, for: Chronoscope.Registry.Behaviour)
Mox.defmock(Chronoscope.NTS.DynamicSupervisorMock, for: Chronoscope.DynamicSupervisor.Behaviour)
Mox.defmock(Chronoscope.NTS.GenServerMock, for: Chronoscope.GenServer.Behaviour)

4
test/support/stubs.ex Normal file
View File

@ -0,0 +1,4 @@
defmodule Chronoscope.DateTime.Stub do
@behaviour Chronoscope.DateTime.Behaviour
def utc_now(), do: DateTime.utc_now()
end