Add gemini client

This commit is contained in:
Mike Cifelli 2024-04-20 13:41:19 -04:00
parent 3e017e6c78
commit 6622913d52
Signed by: mike
GPG Key ID: 6B08C6BE47D08E4C
21 changed files with 295 additions and 85 deletions

View File

@ -19,10 +19,11 @@ config :logger, level: :warning
# Initialize plugs at runtime for faster test compilation
config :phoenix, :plug_init_mode, :runtime
config :chronoscope, Chronoscope.NTS,
behaviour: Chronoscope.NTS.BehaviourMock,
date_time: Chronoscope.NTS.DateTimeMock,
ssl: Chronoscope.NTS.SSLMock,
registry: Chronoscope.NTS.RegistryMock,
dynamic_supervisor: Chronoscope.NTS.DynamicSupervisorMock,
gen_server: Chronoscope.NTS.GenServerMock
config :chronoscope,
date_time: Chronoscope.DateTimeMock,
ssl: Chronoscope.SSLMock,
dynamic_supervisor: Chronoscope.DynamicSupervisorMock,
registry: Chronoscope.RegistryMock,
gen_server: Chronoscope.GenServerMock,
nts: Chronoscope.NTSMock,
gemini: Chronoscope.GeminiMock

View File

@ -18,6 +18,9 @@ defmodule Chronoscope.Application do
{DynamicSupervisor, name: Chronoscope.NTS.DynamicSupervisor, strategy: :one_for_one},
{Task.Supervisor, name: Chronoscope.NTS.TaskSupervisor},
{Registry, [keys: :unique, name: Chronoscope.NTS.Registry]},
{DynamicSupervisor, name: Chronoscope.Gemini.DynamicSupervisor, strategy: :one_for_one},
{Task.Supervisor, name: Chronoscope.Gemini.TaskSupervisor},
{Registry, [keys: :unique, name: Chronoscope.Gemini.Registry]},
# Start to serve requests, typically the last entry
ChronoscopeWeb.Endpoint
]

View File

@ -1,13 +1,18 @@
defmodule Chronoscope.NTS.Certificate do
@date_time Application.compile_env(:chronoscope, Chronoscope.NTS)[:date_time] || DateTime
defmodule Chronoscope.Certificate do
@date_time Application.compile_env(:chronoscope, :date_time, DateTime)
def expiration_date(certificate) do
{:Validity, _, {:utcTime, expiration}} =
case(
certificate
|> X509.Certificate.from_der!()
|> X509.Certificate.validity()
) do
{:Validity, _, {:utcTime, expiration}} ->
cert_time_to_iso8601(expiration)
{:Validity, {:utcTime, expiration}, _} ->
cert_time_to_iso8601(expiration)
end
end
def cert_time_to_iso8601(cert_time) do

View File

@ -1,2 +1,60 @@
defmodule Chronoscope.Gemini do
defmodule Chronoscope.Gemini.Behaviour do
@callback(connect(host :: String.t(), port :: integer(), path :: String.t()) :: {:ok, Map.t()}, {:error, any()})
end
defmodule Chronoscope.Gemini do
@behaviour Chronoscope.Gemini.Behaviour
require Logger
alias Chronoscope.Gemini
@registry Application.compile_env(:chronoscope, :registry, Registry)
@genserver Application.compile_env(:chronoscope, :gen_server, GenServer)
@dynamic_supervisor Application.compile_env(:chronoscope, :dynamic_supervisor, DynamicSupervisor)
def healthy?() do
true
end
def list() do
Gemini.DynamicSupervisor
|> @dynamic_supervisor.which_children()
|> Enum.map(fn {_, pid, _, _} -> @genserver.call(pid, :list) end)
end
def remove(host, port) do
name = client_name(%{host: host, port: port})
case @registry.lookup(Gemini.Registry, name) do
[{pid, _}] -> {:ok, @genserver.call(pid, :terminate)}
[] -> {:error, :not_found}
end
end
@impl true
def connect(host, port, path) do
%{host: host, port: port, path: path}
|> client_pid()
|> @genserver.call(:connect)
end
defp client_pid(resource) do
name = client_name(resource)
case @registry.lookup(Gemini.Registry, name) do
[{pid, _}] -> pid
[] -> start_client(resource, name)
end
end
defp client_name(%{host: host, port: port, path: path}) do
"#{host}:#{port}#{path}"
end
defp start_client(resource, name) do
Gemini.DynamicSupervisor
|> @dynamic_supervisor.start_child({Gemini.Client, resource: resource, name: {:via, @registry, {Gemini.Registry, name}}})
|> then(fn {:ok, pid} -> pid end)
end
end

View File

@ -0,0 +1,73 @@
defmodule Chronoscope.Gemini.Client do
use GenServer, restart: :transient
alias Chronoscope.Gemini
alias Chronoscope.Gemini.ConnectionClient
@interval_in_seconds 30
@date_time Application.compile_env(:chronoscope, :date_time, DateTime)
def start_link(resource: resource, name: name) do
GenServer.start_link(__MODULE__, resource, name: name)
end
@impl true
def init(resource) do
now = utc_now()
{:ok,
%{
resource: resource,
reponse: {:error, "initializing"},
last_request: DateTime.add(now, -@interval_in_seconds, :second)
}}
end
@impl true
def handle_call(:terminate, _from, state) do
{:stop, :normal, self(), state}
end
@impl true
def handle_call(:list, _from, state) do
{:reply, state, state}
end
@impl true
def handle_call(:connect, _from, state) do
new_state = update_state(state)
{:reply, new_state.response, new_state}
end
defp update_state(state) do
now = utc_now()
if interval_surpassed?(now, state.last_request) do
Map.merge(state, current_data(state, now))
else
state
end
end
defp current_data(state, now) do
%{
response: server_response(state),
last_request: now
}
end
defp server_response(%{resource: resource}) do
Gemini.TaskSupervisor
|> Task.Supervisor.async(fn -> ConnectionClient.connect(resource) end)
|> Task.await()
end
defp interval_surpassed?(now, last_request) do
DateTime.diff(now, last_request, :second) >= @interval_in_seconds
end
defp utc_now() do
@date_time.utc_now()
end
end

View File

@ -0,0 +1,60 @@
defmodule Chronoscope.Gemini.ConnectionClient do
require Logger
alias Chronoscope.Certificate
alias Chronoscope.Gemini.Request
alias Chronoscope.Gemini.Response
@timeout_in_milliseconds 3000
@ssl Application.compile_env(:chronoscope, :ssl, :ssl)
def connect(%{host: host, port: port, path: _} = resource) do
case ssl_connect(host, port) do
{:ok, socket} -> make_request(socket, resource)
{:error, {:tls_alert, {:handshake_failure, error}}} -> {:error, String.trim("#{error}")}
{:error, {:tls_alert, {:no_application_protocol, error}}} -> {:error, String.trim("#{error}")}
{:error, :timeout} -> {:error, :timeout}
{:error, error} -> {:error, inspect(error)}
error -> {:error, inspect(error)}
end
end
defp ssl_connect(host, port) do
host
|> String.to_charlist()
|> @ssl.connect(port, tls_options(host), @timeout_in_milliseconds)
end
defp tls_options(_host) do
# TODO
[]
end
defp make_request(socket, url) do
:ok = @ssl.send(socket, Request.create(url))
{:ok, peercert} = @ssl.peercert(socket)
receive do
{:ssl, _socket, response} ->
@ssl.close(socket)
parse_response(response, peercert)
msg ->
@ssl.close(socket)
Logger.error("received unexpected message: #{inspect(msg)}")
{:error, :no_response}
after
@timeout_in_milliseconds ->
@ssl.close(socket)
Logger.error("timed out waiting for response")
{:error, :timeout}
end
end
defp parse_response(response, peercert) do
response
|> Response.parse()
|> Map.put(:cert_expiration, Certificate.expiration_date(peercert))
|> then(&{:ok, &1})
end
end

View File

@ -0,0 +1,5 @@
defmodule Chronoscope.Gemini.Request do
def create(resource) do
"gemini://#{resource.host}:#{resource.port}#{resource.path}\r\n"
end
end

View File

@ -0,0 +1,6 @@
defmodule Chronoscope.Gemini.Response do
def parse(response) do
# TODO
%{status: 20, type: "text/gemini", body: response}
end
end

View File

@ -9,9 +9,9 @@ defmodule Chronoscope.NTS do
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
@registry Application.compile_env(:chronoscope, :registry, Registry)
@genserver Application.compile_env(:chronoscope, :gen_server, GenServer)
@dynamic_supervisor Application.compile_env(:chronoscope, :dynamic_supervisor, DynamicSupervisor)
def healthy?() do
true

View File

@ -5,20 +5,19 @@ defmodule Chronoscope.NTS.Client do
alias Chronoscope.NTS.KeyEstablishmentClient
@interval_in_seconds 30
@date_time Application.compile_env(:chronoscope, Chronoscope.NTS)[:date_time] || DateTime
@date_time Application.compile_env(:chronoscope, :date_time, DateTime)
def start_link(server: server, name: name) do
GenServer.start_link(__MODULE__, server, name: name)
end
@impl true
def init(%{host: host, port: port}) do
def init(server) do
now = utc_now()
{:ok,
%{
host: host,
port: port,
server: server,
key_establishment_response: {:error, "initializing"},
last_key_establishment: DateTime.add(now, -@interval_in_seconds, :second)
}}
@ -58,9 +57,7 @@ defmodule Chronoscope.NTS.Client do
}
end
defp server_response(state) do
server = Map.take(state, [:host, :port])
defp server_response(%{server: server}) do
NTS.TaskSupervisor
|> Task.Supervisor.async(fn -> KeyEstablishmentClient.key_establishment(server) end)
|> Task.await()

View File

@ -1,12 +1,12 @@
defmodule Chronoscope.NTS.KeyEstablishmentClient do
require Logger
alias Chronoscope.NTS.Certificate
alias Chronoscope.Certificate
alias Chronoscope.NTS.KeyEstablishmentRequest
alias Chronoscope.NTS.KeyEstablishmentResponse
@timeout_in_milliseconds 3000
@ssl Application.compile_env(:chronoscope, Chronoscope.NTS)[:ssl] || :ssl
@ssl Application.compile_env(:chronoscope, :ssl, :ssl)
def key_establishment(%{host: host, port: port}) do
case ssl_connect(host, port) do

View File

@ -7,7 +7,7 @@ defmodule ChronoscopeWeb.API.V1.NTS.KeyEstablishmentController do
@default_port 4460
@max_host_length 255
@nts Application.compile_env(:chronoscope, NTS)[:behaviour] || NTS
@nts Application.compile_env(:chronoscope, :nts, NTS)
def get(conn, %{"host" => host, "port" => port}) do
try do

View File

@ -1,21 +1,21 @@
defmodule Chronoscope.NTS.CertificateTest do
defmodule Chronoscope.CertificateTest do
use Chronoscope.Case, async: true
alias Chronoscope.NTS.DateTimeMock
alias Chronoscope.Certificate
alias Chronoscope.DateTimeMock
import Chronoscope.NTS.Certificate
import Mox
setup :verify_on_exit!
describe "Chronoscope.NTS.Certificate.expiration_date()" do
describe "Chronoscope.Certificate.expiration_date()" do
test "parses the expiration date of a certificate" do
{:ok, expiration, _} =
:secp256r1
|> X509.PrivateKey.new_ec()
|> X509.Certificate.self_signed("/C=US/ST=CA/L=San Francisco/O=Acme/CN=Test", validity: 12)
|> X509.Certificate.to_der()
|> expiration_date()
|> Certificate.expiration_date()
|> DateTime.from_iso8601()
expiration_time_in_minutes = DateTime.diff(expiration, DateTime.utc_now(), :minute)
@ -25,26 +25,26 @@ defmodule Chronoscope.NTS.CertificateTest do
end
end
describe "Chronoscope.NTS.Certificate.cert_time_to_iso8601()" do
describe "Chronoscope.Certificate.cert_time_to_iso8601()" do
test "converts certificate datetime to iso8601" do
DateTimeMock
|> expect(:utc_now, fn -> ~U[2024-03-31 01:23:45Z] end)
assert cert_time_to_iso8601("240326110000Z") == "2024-03-26T11:00:00Z"
assert Certificate.cert_time_to_iso8601("240326110000Z") == "2024-03-26T11:00:00Z"
end
test "handles century rollover" do
DateTimeMock
|> expect(:utc_now, fn -> ~U[2024-03-31 01:23:45Z] end)
assert cert_time_to_iso8601("010326110000Z") == "2101-03-26T11:00:00Z"
assert Certificate.cert_time_to_iso8601("010326110000Z") == "2101-03-26T11:00:00Z"
end
test "handles millenium rollover" do
DateTimeMock
|> expect(:utc_now, fn -> ~U[2999-03-31 01:23:45Z] end)
assert cert_time_to_iso8601("010326110000Z") == "3001-03-26T11:00:00Z"
assert Certificate.cert_time_to_iso8601("010326110000Z") == "3001-03-26T11:00:00Z"
end
end
end

View File

@ -1,9 +1,9 @@
defmodule Chronoscope.NTS.ClientTest do
use Chronoscope.Case, async: true
alias Chronoscope.NTS.Certificate
alias Chronoscope.NTS.DateTimeMock
alias Chronoscope.NTS.SSLMock
alias Chronoscope.Certificate
alias Chronoscope.DateTimeMock
alias Chronoscope.SSLMock
import Chronoscope.NTS.Client
import Mox
@ -22,10 +22,9 @@ defmodule Chronoscope.NTS.ClientTest do
assert init(%{host: "localhost", port: 3333}) ==
{:ok,
%{
host: "localhost",
server: %{host: "localhost", port: 3333},
key_establishment_response: {:error, "initializing"},
last_key_establishment: ~U[2024-03-31 01:23:15Z],
port: 3333
last_key_establishment: ~U[2024-03-31 01:23:15Z]
}}
end
end
@ -67,16 +66,14 @@ defmodule Chronoscope.NTS.ClientTest do
assert {:reply, {:ok, %{cert_expiration: ^peercert_expiration}},
%{
host: "localhost",
server: %{host: "localhost", port: 3333},
key_establishment_response: {:ok, %{cert_expiration: ^peercert_expiration}},
last_key_establishment: ~U[2024-03-31 01:23:45Z],
port: 3333
last_key_establishment: ~U[2024-03-31 01:23:45Z]
}} =
handle_call(:key_establishment, nil, %{
host: "localhost",
server: %{host: "localhost", port: 3333},
key_establishment_response: {:error, "initializing"},
last_key_establishment: ~U[2024-03-31 01:23:00Z],
port: 3333
last_key_establishment: ~U[2024-03-31 01:23:00Z]
})
end
end

View File

@ -1,11 +1,11 @@
defmodule Chronoscope.NTS.KeyEstablishmentClientTest do
use Chronoscope.Case, async: true
alias Chronoscope.NTS.Certificate
alias Chronoscope.Certificate
alias Chronoscope.NTS.KeyEstablishmentClient
alias Chronoscope.NTS.KeyEstablishmentRequest
alias Chronoscope.NTS.SSLMock
alias Chronoscope.SSLMock
import Chronoscope.NTS.KeyEstablishmentClient
import Mox
setup :verify_on_exit!
@ -23,7 +23,7 @@ defmodule Chronoscope.NTS.KeyEstablishmentClientTest do
|> expect(:peercert, fn :socket -> {:ok, peercert()} end)
|> expect(:close, fn :socket -> :ok end)
key_establishment(%{host: "localhost", port: 2222})
KeyEstablishmentClient.key_establishment(%{host: "localhost", port: 2222})
end
test "handles an empty response" do
@ -37,7 +37,8 @@ defmodule Chronoscope.NTS.KeyEstablishmentClientTest do
|> expect(:peercert, fn :socket -> {:ok, peercert} end)
|> expect(:close, fn :socket -> :ok end)
assert {:ok, %{cert_expiration: ^peercert_expiration}} = key_establishment(%{host: "localhost", port: 2222})
assert {:ok, %{cert_expiration: ^peercert_expiration}} =
KeyEstablishmentClient.key_establishment(%{host: "localhost", port: 2222})
end
test "handles a full response" do
@ -69,7 +70,7 @@ defmodule Chronoscope.NTS.KeyEstablishmentClientTest do
next_protocols: ["NTPv4"],
port: 1230,
server: "127.0.0.1"
}} = key_establishment(%{host: "localhost", port: 2222})
}} = KeyEstablishmentClient.key_establishment(%{host: "localhost", port: 2222})
end
test "handles a bad certificate hostname failure" do
@ -78,7 +79,7 @@ defmodule Chronoscope.NTS.KeyEstablishmentClientTest do
{:error, {:tls_alert, {:handshake_failure, "connection failed {bad_cert,hostname_check_failed}"}}}
end)
assert key_establishment(%{host: "localhost", port: 2222}) ==
assert KeyEstablishmentClient.key_establishment(%{host: "localhost", port: 2222}) ==
{:error, "The certificate is NOT trusted. The name in the certificate does not match the expected."}
end
@ -88,7 +89,7 @@ defmodule Chronoscope.NTS.KeyEstablishmentClientTest do
{:error, {:tls_alert, {:handshake_failure, "unsatisfactory handshake"}}}
end)
assert key_establishment(%{host: "localhost", port: 2222}) == {:error, "unsatisfactory handshake"}
assert KeyEstablishmentClient.key_establishment(%{host: "localhost", port: 2222}) == {:error, "unsatisfactory handshake"}
end
test "handles a no application protocol failure" do
@ -97,28 +98,30 @@ defmodule Chronoscope.NTS.KeyEstablishmentClientTest do
{:error, {:tls_alert, {:no_application_protocol, "unsatisfactory protocol"}}}
end)
assert key_establishment(%{host: "localhost", port: 2222}) == {:error, "unsatisfactory protocol"}
assert KeyEstablishmentClient.key_establishment(%{host: "localhost", port: 2222}) == {:error, "unsatisfactory protocol"}
end
test "handles a timeout failure" do
SSLMock
|> expect(:connect, fn ~c"localhost", 2222, _tls_options, @timeout -> {:error, :timeout} end)
assert key_establishment(%{host: "localhost", port: 2222}) == {:error, :timeout}
assert KeyEstablishmentClient.key_establishment(%{host: "localhost", port: 2222}) == {:error, :timeout}
end
test "handles an unknown error" do
SSLMock
|> expect(:connect, fn ~c"localhost", 2222, _tls_options, @timeout -> {:error, {:unsatisfactory, "client"}} end)
assert key_establishment(%{host: "localhost", port: 2222}) == {:error, "{:unsatisfactory, \"client\"}"}
assert KeyEstablishmentClient.key_establishment(%{host: "localhost", port: 2222}) ==
{:error, "{:unsatisfactory, \"client\"}"}
end
test "handles an unexpected error" do
SSLMock
|> expect(:connect, fn ~c"localhost", 2222, _tls_options, @timeout -> {:unsatisfactory, "client"} end)
assert key_establishment(%{host: "localhost", port: 2222}) == {:error, "{:unsatisfactory, \"client\"}"}
assert KeyEstablishmentClient.key_establishment(%{host: "localhost", port: 2222}) ==
{:error, "{:unsatisfactory, \"client\"}"}
end
end
end

View File

@ -1,19 +1,18 @@
defmodule Chronoscope.NTSTest do
use Chronoscope.Case, async: true
alias Chronoscope.DynamicSupervisorMock
alias Chronoscope.GenServerMock
alias Chronoscope.NTS
alias Chronoscope.NTS.DynamicSupervisorMock
alias Chronoscope.NTS.GenServerMock
alias Chronoscope.NTS.RegistryMock
alias Chronoscope.RegistryMock
import Chronoscope.NTS
import Mox
setup :verify_on_exit!
describe "Chronoscope.NTS.healthy?()" do
test "is healthy" do
assert healthy?() == true
assert NTS.healthy?() == true
end
end
@ -22,7 +21,7 @@ defmodule Chronoscope.NTSTest do
DynamicSupervisorMock
|> expect(:which_children, fn _ -> [] end)
assert list() == []
assert NTS.list() == []
end
test "lists all children" do
@ -33,7 +32,7 @@ defmodule Chronoscope.NTSTest do
|> expect(:call, fn 2, :list -> :one end)
|> expect(:call, fn 6, :list -> :two end)
assert list() == [:one, :two]
assert NTS.list() == [:one, :two]
end
end
@ -42,7 +41,7 @@ defmodule Chronoscope.NTSTest do
RegistryMock
|> expect(:lookup, fn _, _ -> [] end)
assert remove("localhost", 1111) == {:error, :not_found}
assert NTS.remove("localhost", 1111) == {:error, :not_found}
end
test "removes a client" do
@ -52,7 +51,7 @@ defmodule Chronoscope.NTSTest do
GenServerMock
|> expect(:call, fn 1, :terminate -> :terminating end)
assert remove("localhost", 1111) == {:ok, :terminating}
assert NTS.remove("localhost", 1111) == {:ok, :terminating}
end
end
@ -62,11 +61,11 @@ defmodule Chronoscope.NTSTest do
|> expect(:lookup, fn _, _ -> [] end)
DynamicSupervisorMock
|> expect(:start_child, fn NTS.DynamicSupervisor,
{NTS.Client,
|> expect(:start_child, fn Chronoscope.NTS.DynamicSupervisor,
{Chronoscope.NTS.Client,
[
server: %{host: "localhost", port: 1111},
name: {:via, RegistryMock, {NTS.Registry, "localhost:1111"}}
name: {:via, RegistryMock, {Chronoscope.NTS.Registry, "localhost:1111"}}
]} ->
{:ok, 1}
end)
@ -74,7 +73,7 @@ defmodule Chronoscope.NTSTest do
GenServerMock
|> expect(:call, fn 1, :key_establishment -> :result end)
assert key_establishment("localhost", 1111) == :result
assert NTS.key_establishment("localhost", 1111) == :result
end
test "reuses an existing client" do
@ -84,7 +83,7 @@ defmodule Chronoscope.NTSTest do
GenServerMock
|> expect(:call, fn 1, :key_establishment -> :result end)
assert key_establishment("localhost", 1111) == :result
assert NTS.key_establishment("localhost", 1111) == :result
end
end
end

View File

@ -1,6 +1,8 @@
defmodule ChronoscopeWeb.API.V1.NTS.KeyEstablishmentControllerTest do
use ChronoscopeWeb.ConnCase, async: true
alias Chronoscope.NTSMock
import Mox
setup :verify_on_exit!
@ -16,7 +18,7 @@ defmodule ChronoscopeWeb.API.V1.NTS.KeyEstablishmentControllerTest do
end
test "truncates the host name", %{conn: conn} do
Chronoscope.NTS.BehaviourMock
NTSMock
|> expect(
:key_establishment,
fn "test.example.com.0123456789.0123456789.0123456789.0123456789.0123456789.0123456789.0123456789.0123456789.0123456789.0123456789.0123456789.0123456789.0123456789.0123456789.0123456789.0123456789.0123456789.0123456789.0123456789.0123456789.0123456789.0123456",
@ -36,7 +38,7 @@ defmodule ChronoscopeWeb.API.V1.NTS.KeyEstablishmentControllerTest do
end
test "uses the given port number", %{conn: conn} do
Chronoscope.NTS.BehaviourMock
NTSMock
|> expect(:key_establishment, fn "localhost", 4461 -> {:ok, %{status: :ok}} end)
response =
@ -75,7 +77,7 @@ defmodule ChronoscopeWeb.API.V1.NTS.KeyEstablishmentControllerTest do
end
test "returns an empty successful response", %{conn: conn} do
Chronoscope.NTS.BehaviourMock
NTSMock
|> expect(:key_establishment, fn "localhost", 4460 -> {:ok, %{status: :ok}} end)
response =
@ -87,7 +89,7 @@ defmodule ChronoscopeWeb.API.V1.NTS.KeyEstablishmentControllerTest do
end
test "returns a full successful response", %{conn: conn} do
Chronoscope.NTS.BehaviourMock
NTSMock
|> expect(:key_establishment, fn "localhost", 4460 ->
{:ok, %{cookies: [[], [], []], cookie_length: 300}}
end)

View File

@ -8,7 +8,7 @@ defmodule Chronoscope.Case do
end
setup _tags do
Mox.stub_with(Chronoscope.NTS.DateTimeMock, Chronoscope.DateTime.Stub)
Mox.stub_with(Chronoscope.DateTimeMock, Chronoscope.DateTimeStub)
:ok
end

View File

@ -32,7 +32,7 @@ defmodule ChronoscopeWeb.ConnCase do
end
setup _tags do
Mox.stub_with(Chronoscope.NTS.DateTimeMock, Chronoscope.DateTime.Stub)
Mox.stub_with(Chronoscope.DateTimeMock, Chronoscope.DateTimeStub)
{:ok, conn: Phoenix.ConnTest.build_conn()}
end
end

View File

@ -1,6 +1,7 @@
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)
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)
Mox.defmock(Chronoscope.DateTimeMock, for: Chronoscope.DateTime.Behaviour)
Mox.defmock(Chronoscope.SSLMock, for: Chronoscope.SSL.Behaviour)
Mox.defmock(Chronoscope.RegistryMock, for: Chronoscope.Registry.Behaviour)
Mox.defmock(Chronoscope.DynamicSupervisorMock, for: Chronoscope.DynamicSupervisor.Behaviour)
Mox.defmock(Chronoscope.GenServerMock, for: Chronoscope.GenServer.Behaviour)
Mox.defmock(Chronoscope.NTSMock, for: Chronoscope.NTS.Behaviour)
Mox.defmock(Chronoscope.GeminiMock, for: Chronoscope.Gemini.Behaviour)

View File

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