From fb5fb1b181653a961b0bba17de4308f2c08dfc2e Mon Sep 17 00:00:00 2001 From: Mike Cifelli Date: Sat, 13 Apr 2024 11:42:15 -0400 Subject: [PATCH] Add tests --- config/test.exs | 3 +- lib/chronoscope/nts.ex | 16 +++--- .../nts/key_establishment_client.ex | 16 +++--- .../nts/key_establishment_client_test.exs | 52 +++++++++++++++++++ .../nts/key_establishment_request_test.exs | 2 +- .../nts/key_establishment_response_test.exs | 2 +- test/support/mocks.ex | 8 +++ 7 files changed, 82 insertions(+), 17 deletions(-) create mode 100644 test/chronoscope/nts/key_establishment_client_test.exs diff --git a/config/test.exs b/config/test.exs index 400e1fa..fc6404a 100644 --- a/config/test.exs +++ b/config/test.exs @@ -21,4 +21,5 @@ config :phoenix, :plug_init_mode, :runtime config :chronoscope, Chronoscope.NTS, behaviour: Chronoscope.NTS.BehaviourMock, - datetime: Chronoscope.NTS.DateTimeMock + datetime: Chronoscope.NTS.DateTimeMock, + ssl: Chronoscope.NTS.SSLMock diff --git a/lib/chronoscope/nts.ex b/lib/chronoscope/nts.ex index b87f1af..bed5f24 100644 --- a/lib/chronoscope/nts.ex +++ b/lib/chronoscope/nts.ex @@ -21,20 +21,20 @@ defmodule Chronoscope.NTS do @impl true def key_establishment(host, port) do + GenServer.call(client_pid(host, port), :key_establishment) + end + + defp client_pid(host, port) do name = "#{host}:#{port}" case Registry.lookup(NTS.Registry, name) do [{pid, _}] -> - GenServer.call(pid, :key_establishment) + pid [] -> - {:ok, pid} = - DynamicSupervisor.start_child( - NTS.DynamicSupervisor, - {NTS.Client, host: host, port: port, name: {:via, Registry, {NTS.Registry, name}}} - ) - - GenServer.call(pid, :key_establishment) + NTS.DynamicSupervisor + |> DynamicSupervisor.start_child({NTS.Client, host: host, port: port, name: {:via, Registry, {NTS.Registry, name}}}) + |> then(fn {:ok, pid} -> pid end) end end end diff --git a/lib/chronoscope/nts/key_establishment_client.ex b/lib/chronoscope/nts/key_establishment_client.ex index f7f5107..4612b78 100644 --- a/lib/chronoscope/nts/key_establishment_client.ex +++ b/lib/chronoscope/nts/key_establishment_client.ex @@ -21,7 +21,7 @@ defmodule Chronoscope.NTS.KeyEstablishmentClient do defp ssl_connect(host, port) do host |> String.to_charlist() - |> :ssl.connect(port, tls_options(host), @timeout_in_milliseconds) + |> ssl().connect(port, tls_options(host), @timeout_in_milliseconds) end defp tls_options(host) do @@ -31,21 +31,21 @@ defmodule Chronoscope.NTS.KeyEstablishmentClient do end defp perform_key_establishment(socket) do - :ok = :ssl.send(socket, KeyEstablishmentRequest.create()) - {:ok, peercert} = :ssl.peercert(socket) + :ok = ssl().send(socket, KeyEstablishmentRequest.create()) + {:ok, peercert} = ssl().peercert(socket) receive do {:ssl, _socket, response} -> - :ssl.close(socket) + ssl().close(socket) parse_response(response, peercert) msg -> - :ssl.close(socket) + ssl().close(socket) Logger.error("received unexpected message: #{inspect(msg)}") {:error, :no_response} after @timeout_in_milliseconds -> - :ssl.close(socket) + ssl().close(socket) Logger.error("timed out waiting for response") {:error, :timeout} end @@ -67,4 +67,8 @@ defmodule Chronoscope.NTS.KeyEstablishmentClient do {:error, String.trim(error)} end end + + defp ssl() do + Application.get_env(:chronoscope, Chronoscope.NTS)[:ssl] || :ssl + end end diff --git a/test/chronoscope/nts/key_establishment_client_test.exs b/test/chronoscope/nts/key_establishment_client_test.exs new file mode 100644 index 0000000..8691127 --- /dev/null +++ b/test/chronoscope/nts/key_establishment_client_test.exs @@ -0,0 +1,52 @@ +defmodule Chronoscope.NTS.KeyEstablishmentClientTest do + use Chronoscope.Case + + alias Chronoscope.NTS.SSLMock + alias Chronoscope.NTS.KeyEstablishmentRequest + + import Chronoscope.NTS.KeyEstablishmentClient + import Mox + + setup :verify_on_exit! + + @timeout 3000 + + defp peercert() do + :secp256r1 + |> X509.PrivateKey.new_ec() + |> X509.Certificate.self_signed("/C=US/ST=CA/L=San Francisco/O=Acme/CN=Test") + |> X509.Certificate.to_der() + end + + defp send_ssl_response(response) do + send(self(), {:ssl, nil, response}) + :ok + end + + describe "Chronoscope.NTS.KeyEstablishmentClient.key_establishment()" do + test "handles an empty response" do + request = KeyEstablishmentRequest.create() + + SSLMock + |> expect(:connect, fn ~c"localhost", 2222, _tls_options, @timeout -> {:ok, :socket} end) + |> expect(:send, fn :socket, ^request -> send_ssl_response([]) end) + |> expect(:peercert, fn :socket -> {:ok, peercert()} end) + |> expect(:close, fn :socket -> :ok end) + + assert {:ok, %{cert_expiration: _expiration}} = key_establishment(%{host: "localhost", port: 2222}) + end + + test "sends the correct TLS options" do + SSLMock + |> expect(:connect, fn ~c"localhost", 2222, tls_options, @timeout -> + assert tls_options[:alpn_advertised_protocols] == ["ntske/1"] + {:ok, :socket} + end) + |> expect(:send, fn :socket, _request -> send_ssl_response([]) end) + |> expect(:peercert, fn :socket -> {:ok, peercert()} end) + |> expect(:close, fn :socket -> :ok end) + + key_establishment(%{host: "localhost", port: 2222}) + end + end +end diff --git a/test/chronoscope/nts/key_establishment_request_test.exs b/test/chronoscope/nts/key_establishment_request_test.exs index b75769d..671eb86 100644 --- a/test/chronoscope/nts/key_establishment_request_test.exs +++ b/test/chronoscope/nts/key_establishment_request_test.exs @@ -1,5 +1,5 @@ defmodule Chronoscope.NTS.KeyEstablishmentRequestTest do - use ExUnit.Case + use Chronoscope.Case import Chronoscope.NTS.KeyEstablishmentRequest diff --git a/test/chronoscope/nts/key_establishment_response_test.exs b/test/chronoscope/nts/key_establishment_response_test.exs index 4d88e9f..85e8673 100644 --- a/test/chronoscope/nts/key_establishment_response_test.exs +++ b/test/chronoscope/nts/key_establishment_response_test.exs @@ -1,5 +1,5 @@ defmodule Chronoscope.NTS.KeyEstablishmentResponseTest do - use ExUnit.Case + use Chronoscope.Case import Chronoscope.NTS.KeyEstablishmentResponse diff --git a/test/support/mocks.ex b/test/support/mocks.ex index ee25f62..37ee853 100644 --- a/test/support/mocks.ex +++ b/test/support/mocks.ex @@ -7,5 +7,13 @@ defmodule Chronoscope.DateTime.Stub do 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.DateTimeMock, for: Chronoscope.DateTime.Behaviour) +Mox.defmock(Chronoscope.NTS.SSLMock, for: Chronoscope.SSL.Behaviour)