diff --git a/lib/chronoscope/nts/client.ex b/lib/chronoscope/nts/client.ex index 00ac080..cf52078 100644 --- a/lib/chronoscope/nts/client.ex +++ b/lib/chronoscope/nts/client.ex @@ -12,7 +12,7 @@ defmodule Chronoscope.NTS.Client do @impl true def init(%{host: host, port: port}) do - now = DateTime.utc_now() + now = utc_now() {:ok, %{ @@ -36,7 +36,7 @@ defmodule Chronoscope.NTS.Client do end defp update_state(state) do - now = DateTime.utc_now() + now = utc_now() if interval_surpassed?(now, state.last_key_establishment) do Map.merge(state, current_data(state, now)) @@ -63,4 +63,12 @@ defmodule Chronoscope.NTS.Client do defp interval_surpassed?(now, last_key_establishment) do DateTime.diff(now, last_key_establishment, :second) >= @interval_in_seconds end + + defp utc_now() do + datetime().utc_now() + end + + defp datetime() do + Application.get_env(:chronoscope, Chronoscope.NTS)[:datetime] || DateTime + end end diff --git a/test/chronoscope/nts/client_test.exs b/test/chronoscope/nts/client_test.exs new file mode 100644 index 0000000..2ab83f3 --- /dev/null +++ b/test/chronoscope/nts/client_test.exs @@ -0,0 +1,91 @@ +defmodule Chronoscope.NTS.ClientTest do + use Chronoscope.Case + + alias Chronoscope.NTS.Certificate + alias Chronoscope.NTS.DateTimeMock + alias Chronoscope.NTS.SSLMock + + import Chronoscope.NTS.Client + import Mox + + setup :verify_on_exit! + + 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 + + setup _tags do + DateTimeMock + |> stub(:utc_now, fn -> ~U[2024-03-31 01:23:45Z] end) + + :ok + end + + describe "Chronoscope.NTS.Client.init()" do + test "initializes successfully" do + assert init(%{host: "localhost", port: 3333}) == + {:ok, + %{ + host: "localhost", + key_establishment_response: {:error, "initializing"}, + last_key_establishment: ~U[2024-03-31 01:23:15Z], + port: 3333 + }} + end + end + + describe "Chronoscope.NTS.Client.handle_call()" do + test ":list" do + assert handle_call(:list, nil, %{state: true}) == {:reply, %{state: true}, %{state: true}} + end + + test ":key_establishment - cached" do + assert handle_call(:key_establishment, nil, %{ + host: "localhost", + key_establishment_response: {:error, "initializing"}, + last_key_establishment: ~U[2024-03-31 01:23:45Z], + port: 3333 + }) == + {:reply, {:error, "initializing"}, + %{ + host: "localhost", + key_establishment_response: {:error, "initializing"}, + last_key_establishment: ~U[2024-03-31 01:23:45Z], + port: 3333 + }} + end + + test ":key_establishment - not cached" do + peercert = peercert() + peercert_expiration = Certificate.expiration_date(peercert) + + SSLMock + |> expect(:connect, fn ~c"localhost", 3333, _, _ -> {:ok, :socket} end) + |> expect(:send, fn :socket, _ -> send_ssl_response([]) end) + |> expect(:peercert, fn :socket -> {:ok, peercert} end) + |> expect(:close, fn :socket -> :ok end) + + assert {:reply, {:ok, %{cert_expiration: ^peercert_expiration}}, + %{ + host: "localhost", + key_establishment_response: {:ok, %{cert_expiration: ^peercert_expiration}}, + last_key_establishment: ~U[2024-03-31 01:23:45Z], + port: 3333 + }} = + handle_call(:key_establishment, nil, %{ + host: "localhost", + key_establishment_response: {:error, "initializing"}, + last_key_establishment: ~U[2024-03-31 01:23:00Z], + port: 3333 + }) + end + end +end