Show a dynamically updated list of NTS servers #1
|
@ -37,3 +37,6 @@ npm-debug.log
|
|||
|
||||
# Ignore IDE files.
|
||||
.elixir_ls/
|
||||
|
||||
# Ignore server lists.
|
||||
/priv/nts.txt
|
||||
|
|
|
@ -30,6 +30,9 @@ config :chronoscope, ChronoscopeWeb.Endpoint,
|
|||
# at the `config/runtime.exs`.
|
||||
config :chronoscope, Chronoscope.Mailer, adapter: Swoosh.Adapters.Local
|
||||
|
||||
config :chronoscope, :nts_topic, "nts-servers"
|
||||
config :chronoscope, :nts_file, "priv/nts.txt"
|
||||
|
||||
# Configure esbuild (the version is required)
|
||||
config :esbuild,
|
||||
version: "0.17.11",
|
||||
|
|
|
@ -10,6 +10,8 @@ config :chronoscope, ChronoscopeWeb.Endpoint,
|
|||
# In test we don't send emails.
|
||||
config :chronoscope, Chronoscope.Mailer, adapter: Swoosh.Adapters.Test
|
||||
|
||||
config :chronoscope, :nts_file, "test/priv/nts.txt"
|
||||
|
||||
# Disable swoosh api client as it is only required for production adapters.
|
||||
config :swoosh, :api_client, false
|
||||
|
||||
|
|
|
@ -22,7 +22,8 @@ defmodule Chronoscope.Application do
|
|||
{Task.Supervisor, name: Chronoscope.Gemini.TaskSupervisor},
|
||||
{Registry, [keys: :unique, name: Chronoscope.Gemini.Registry]},
|
||||
# Start to serve requests, typically the last entry
|
||||
ChronoscopeWeb.Endpoint
|
||||
ChronoscopeWeb.Endpoint,
|
||||
Chronoscope.Monitor
|
||||
]
|
||||
|
||||
# See https://hexdocs.pm/elixir/Supervisor.html
|
||||
|
|
|
@ -9,6 +9,8 @@ defmodule Chronoscope.Gemini do
|
|||
|
||||
alias Chronoscope.Gemini
|
||||
|
||||
@timeout_in_milliseconds 10_000
|
||||
|
||||
@registry Application.compile_env(:chronoscope, :registry, Registry)
|
||||
@genserver Application.compile_env(:chronoscope, :gen_server, GenServer)
|
||||
@dynamic_supervisor Application.compile_env(:chronoscope, :dynamic_supervisor, DynamicSupervisor)
|
||||
|
@ -20,14 +22,14 @@ defmodule Chronoscope.Gemini do
|
|||
def list() do
|
||||
Gemini.DynamicSupervisor
|
||||
|> @dynamic_supervisor.which_children()
|
||||
|> Enum.map(fn {_, pid, _, _} -> @genserver.call(pid, :list) end)
|
||||
|> Enum.map(fn {_, pid, _, _} -> @genserver.call(pid, :list, @timeout_in_milliseconds) end)
|
||||
end
|
||||
|
||||
def remove(host, port, path) do
|
||||
name = client_name(%{host: host, port: port, path: path})
|
||||
|
||||
case @registry.lookup(Gemini.Registry, name) do
|
||||
[{pid, _}] -> {:ok, @genserver.call(pid, :terminate)}
|
||||
[{pid, _}] -> {:ok, @genserver.call(pid, :terminate, @timeout_in_milliseconds)}
|
||||
[] -> {:error, :not_found}
|
||||
end
|
||||
end
|
||||
|
@ -36,7 +38,7 @@ defmodule Chronoscope.Gemini do
|
|||
def connect(host, port, path) do
|
||||
%{host: host, port: port, path: path}
|
||||
|> client_pid()
|
||||
|> @genserver.call(:connect)
|
||||
|> @genserver.call(:connect, @timeout_in_milliseconds)
|
||||
end
|
||||
|
||||
defp client_pid(resource) do
|
||||
|
|
|
@ -5,6 +5,7 @@ defmodule Chronoscope.Gemini.Client do
|
|||
alias Chronoscope.Gemini.ConnectionClient
|
||||
|
||||
@interval_in_seconds 30
|
||||
@timeout_in_milliseconds 10_000
|
||||
|
||||
@date_time Application.compile_env(:chronoscope, :date_time, DateTime)
|
||||
|
||||
|
@ -61,7 +62,7 @@ defmodule Chronoscope.Gemini.Client do
|
|||
defp server_response(%{resource: resource}) do
|
||||
Gemini.TaskSupervisor
|
||||
|> Task.Supervisor.async(fn -> ConnectionClient.connect(resource) end)
|
||||
|> Task.await()
|
||||
|> Task.await(@timeout_in_milliseconds)
|
||||
end
|
||||
|
||||
defp interval_surpassed?(now, last_request) do
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
defmodule Chronoscope.Monitor do
|
||||
use GenServer
|
||||
|
||||
require Logger
|
||||
|
||||
alias Chronoscope.NTS
|
||||
alias Chronoscope.NTS.Parse
|
||||
|
||||
@nts_file Application.compile_env(:chronoscope, :nts_file)
|
||||
|
||||
def start_link(_) do
|
||||
GenServer.start_link(__MODULE__, :ok, name: __MODULE__)
|
||||
end
|
||||
|
||||
@impl true
|
||||
def init(_) do
|
||||
File.touch(@nts_file)
|
||||
activate_nts_clients()
|
||||
{:ok, %{nts_servers: activate_nts_clients()}}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_info(:activate_clients, _state) do
|
||||
{:noreply, %{nts_servers: activate_nts_clients()}}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_call(:get_nts_servers, _from, state) do
|
||||
{:reply, state.nts_servers, state}
|
||||
end
|
||||
|
||||
defp activate_nts_clients() do
|
||||
@nts_file
|
||||
|> File.stream!()
|
||||
|> Stream.map(&String.trim/1)
|
||||
|> Stream.map(&Parse.parse_nts_server/1)
|
||||
|> Enum.to_list()
|
||||
|> tap(fn server -> Enum.each(server, &NTS.auto_refresh/1) end)
|
||||
end
|
||||
end
|
|
@ -9,10 +9,11 @@ defmodule Chronoscope.NTS do
|
|||
|
||||
alias Chronoscope.NTS
|
||||
|
||||
@timeout_in_milliseconds 10_000
|
||||
|
||||
@registry Application.compile_env(:chronoscope, :registry, Registry)
|
||||
@genserver Application.compile_env(:chronoscope, :gen_server, GenServer)
|
||||
@dynamic_supervisor Application.compile_env(:chronoscope, :dynamic_supervisor, DynamicSupervisor)
|
||||
@topic "nts-servers"
|
||||
|
||||
def healthy?() do
|
||||
true
|
||||
|
@ -24,11 +25,29 @@ defmodule Chronoscope.NTS do
|
|||
|> Enum.map(fn {_, pid, _, _} -> @genserver.call(pid, :list) end)
|
||||
end
|
||||
|
||||
def list_clients(clients) do
|
||||
clients
|
||||
|> Enum.map(&client_pid/1)
|
||||
|> Enum.map(fn pid -> @genserver.call(pid, :list, @timeout_in_milliseconds) end)
|
||||
end
|
||||
|
||||
def auto_refresh(server) do
|
||||
server
|
||||
|> client_pid()
|
||||
|> @genserver.call(:auto_refresh, @timeout_in_milliseconds)
|
||||
end
|
||||
|
||||
def cancel_auto_refresh(server) do
|
||||
server
|
||||
|> client_pid()
|
||||
|> @genserver.call(:cancel_auto_refresh, @timeout_in_milliseconds)
|
||||
end
|
||||
|
||||
def remove(host, port) do
|
||||
name = client_name(%{host: host, port: port})
|
||||
|
||||
case @registry.lookup(NTS.Registry, name) do
|
||||
[{pid, _}] -> {:ok, @genserver.call(pid, :terminate)}
|
||||
[{pid, _}] -> {:ok, @genserver.call(pid, :terminate, @timeout_in_milliseconds)}
|
||||
[] -> {:error, :not_found}
|
||||
end
|
||||
end
|
||||
|
@ -37,8 +56,7 @@ defmodule Chronoscope.NTS do
|
|||
def key_establishment(host, port) do
|
||||
%{host: host, port: port}
|
||||
|> client_pid()
|
||||
|> @genserver.call(:key_establishment)
|
||||
|> tap(fn _ -> ChronoscopeWeb.Endpoint.broadcast(@topic, "", "") end)
|
||||
|> @genserver.call(:key_establishment, @timeout_in_milliseconds)
|
||||
end
|
||||
|
||||
defp client_pid(server) do
|
||||
|
|
|
@ -5,7 +5,10 @@ defmodule Chronoscope.NTS.Client do
|
|||
alias Chronoscope.NTS.KeyEstablishmentClient
|
||||
|
||||
@interval_in_seconds 30
|
||||
@timeout_in_milliseconds 10_000
|
||||
@refresh_interval_in_milliseconds :timer.minutes(1)
|
||||
|
||||
@topic Application.compile_env(:chronoscope, :nts_topic)
|
||||
@date_time Application.compile_env(:chronoscope, :date_time, DateTime)
|
||||
|
||||
def start_link(server: server, name: name) do
|
||||
|
@ -34,6 +37,28 @@ defmodule Chronoscope.NTS.Client do
|
|||
{:reply, state, state}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_call(:auto_refresh, _from, %{timer: _timer} = state) do
|
||||
{:reply, :already_started, state}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_call(:auto_refresh, _from, state) do
|
||||
{:ok, timer} = :timer.send_interval(@refresh_interval_in_milliseconds, :key_establishment)
|
||||
{:reply, :ok, Map.put(state, :timer, timer)}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_call(:cancel_auto_refresh, _from, %{timer: timer} = state) do
|
||||
:timer.cancel(timer)
|
||||
{:reply, :ok, Map.delete(state, :timer)}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_call(:cancel_auto_refresh, _from, state) do
|
||||
{:reply, :already_cancelled, state}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_call(:key_establishment, _from, state) do
|
||||
new_state = update_state(state)
|
||||
|
@ -41,11 +66,18 @@ defmodule Chronoscope.NTS.Client do
|
|||
{:reply, new_state.key_establishment_response, new_state}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_info(:key_establishment, state) do
|
||||
{:noreply, update_state(state)}
|
||||
end
|
||||
|
||||
defp update_state(state) do
|
||||
now = utc_now()
|
||||
|
||||
if interval_surpassed?(now, state.last_key_establishment) do
|
||||
Map.merge(state, current_data(state, now))
|
||||
state
|
||||
|> Map.merge(current_data(state, now))
|
||||
|> tap(fn _ -> ChronoscopeWeb.Endpoint.broadcast(@topic, "", "") end)
|
||||
else
|
||||
state
|
||||
end
|
||||
|
@ -61,7 +93,7 @@ defmodule Chronoscope.NTS.Client do
|
|||
defp server_response(%{server: server}) do
|
||||
NTS.TaskSupervisor
|
||||
|> Task.Supervisor.async(fn -> KeyEstablishmentClient.key_establishment(server) end)
|
||||
|> Task.await()
|
||||
|> Task.await(@timeout_in_milliseconds)
|
||||
end
|
||||
|
||||
defp interval_surpassed?(now, last_key_establishment) do
|
||||
|
|
|
@ -5,7 +5,7 @@ defmodule Chronoscope.NTS.KeyEstablishmentClient do
|
|||
alias Chronoscope.NTS.KeyEstablishmentRequest
|
||||
alias Chronoscope.NTS.KeyEstablishmentResponse
|
||||
|
||||
@timeout_in_milliseconds 3000
|
||||
@timeout_in_milliseconds 3500
|
||||
|
||||
@ssl Application.compile_env(:chronoscope, :ssl, :ssl)
|
||||
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
defmodule Chronoscope.NTS.Parse do
|
||||
@default_port 4460
|
||||
|
||||
def parse_nts_server(server) do
|
||||
case String.split(server, ":") do
|
||||
[host, port] -> %{host: host, port: String.to_integer(port)}
|
||||
[host] -> %{host: host, port: @default_port}
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,4 +1,4 @@
|
|||
<header class="px-4 sm:px-6 lg:px-8">
|
||||
<header class="px-4 sm:px-6 lg:px-8 sticky top-0 z-10 bg-white dark:bg-zinc-800">
|
||||
<div class="flex items-center justify-between border-b border-zinc-100 dark:border-zinc-700 py-3 text-sm">
|
||||
<div class="flex items-center gap-4 font-semibold leading-6 text-zinc-900 dark:text-zinc-300 pe-4">
|
||||
<a href="/" class="hover:text-zinc-700 dark:hover:text-zinc-400">
|
||||
|
|
|
@ -4,14 +4,20 @@ defmodule ChronoscopeWeb.IndexLive do
|
|||
alias Chronoscope.NTS
|
||||
alias Chronoscope.NTS.KeyEstablishmentResponse
|
||||
|
||||
@topic "nts-servers"
|
||||
@topic Application.compile_env(:chronoscope, :nts_topic)
|
||||
|
||||
def mount(_params, _session, socket) do
|
||||
ChronoscopeWeb.Endpoint.subscribe(@topic)
|
||||
{:ok, assign(socket, %{servers: NTS.list()})}
|
||||
{:ok, assign(socket, %{servers: server_list()})}
|
||||
end
|
||||
|
||||
def handle_info(%{topic: @topic}, socket) do
|
||||
{:noreply, assign(socket, %{servers: NTS.list()})}
|
||||
{:noreply, assign(socket, %{servers: server_list()})}
|
||||
end
|
||||
|
||||
defp server_list() do
|
||||
Chronoscope.Monitor
|
||||
|> GenServer.call(:get_nts_servers)
|
||||
|> NTS.list_clients()
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,31 +2,31 @@
|
|||
<table class="mx-auto border-collapse table-auto min-w-full divide-y divide-zinc-200 dark:divide-zinc-700 text-left">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" class="py-3 px-6 whitespace-nowrap">
|
||||
<th scope="col" class="py-2 px-6 whitespace-nowrap">
|
||||
NTS-KE Server
|
||||
</th>
|
||||
<th scope="col" class="py-3 px-6 whitespace-nowrap">
|
||||
<th scope="col" class="py-2 px-6 whitespace-nowrap">
|
||||
Status
|
||||
</th>
|
||||
<th scope="col" class="py-3 px-6 whitespace-nowrap">
|
||||
<th scope="col" class="py-2 px-6 whitespace-nowrap">
|
||||
Algorithm
|
||||
</th>
|
||||
<th scope="col" class="py-3 px-6 whitespace-nowrap">
|
||||
<th scope="col" class="py-2 px-6 whitespace-nowrap">
|
||||
Cookies
|
||||
</th>
|
||||
<th scope="col" class="py-3 px-6 whitespace-nowrap">
|
||||
<th scope="col" class="py-2 px-6 whitespace-nowrap">
|
||||
Cookie Length
|
||||
</th>
|
||||
<th scope="col" class="py-3 px-6 whitespace-nowrap">
|
||||
<th scope="col" class="py-2 px-6 whitespace-nowrap">
|
||||
NTP Host
|
||||
</th>
|
||||
<th scope="col" class="py-3 px-6 whitespace-nowrap">
|
||||
<th scope="col" class="py-2 px-6 whitespace-nowrap">
|
||||
NTP Port
|
||||
</th>
|
||||
<th scope="col" class="py-3 px-6 whitespace-nowrap">
|
||||
<th scope="col" class="py-2 px-6 whitespace-nowrap">
|
||||
Certificate Expiration
|
||||
</th>
|
||||
<th scope="col" class="py-3 px-6 whitespace-nowrap">
|
||||
<th scope="col" class="py-2 px-6 whitespace-nowrap">
|
||||
Last Check
|
||||
</th>
|
||||
</tr>
|
||||
|
@ -36,13 +36,13 @@
|
|||
<% {status, response} = server.key_establishment_response %>
|
||||
|
||||
<%= if (status == :ok) do %>
|
||||
<td class="py-4 px-6 whitespace-nowrap">
|
||||
<%= server.server.host %>:<%= server.server.port %>
|
||||
<td class="py-2 px-6 whitespace-nowrap">
|
||||
<%= server.server.host %><span :if={server.server.port != 4460}>:<%= server.server.port %></span>
|
||||
</td>
|
||||
<td class="py-4 px-6 whitespace-nowrap">
|
||||
<td class="py-2 px-6 whitespace-nowrap">
|
||||
<%= status %>
|
||||
</td>
|
||||
<td class="py-4 px-6 whitespace-nowrap">
|
||||
<td class="py-2 px-6 whitespace-nowrap">
|
||||
<% aead_algorithm = Enum.at(response.aead_algorithms, 0) %>
|
||||
|
||||
<span class="group relative">
|
||||
|
@ -52,30 +52,30 @@
|
|||
</span>
|
||||
</span>
|
||||
</td>
|
||||
<td class="py-4 px-6 whitespace-nowrap">
|
||||
<td class="py-2 px-6 whitespace-nowrap">
|
||||
<%= length(response.cookies) %>
|
||||
</td>
|
||||
<td class="py-4 px-6 whitespace-nowrap">
|
||||
<td class="py-2 px-6 whitespace-nowrap">
|
||||
<%= response.cookie_length %>
|
||||
</td>
|
||||
<td class="py-4 px-6 whitespace-nowrap">
|
||||
<td class="py-2 px-6 whitespace-nowrap">
|
||||
<%= Map.get(response, :server, "-") %>
|
||||
</td>
|
||||
<td class="py-4 px-6 whitespace-nowrap">
|
||||
<td class="py-2 px-6 whitespace-nowrap">
|
||||
<%= Map.get(response, :port, "-") %>
|
||||
</td>
|
||||
<td class="py-4 px-6 whitespace-nowrap">
|
||||
<td class="py-2 px-6 whitespace-nowrap">
|
||||
<%= response.cert_expiration |> DateTime.from_iso8601 |> then(fn {:ok, dt, _} -> Calendar.strftime(dt, "%Y-%m-%d %H:%M:%SZ") end)%>
|
||||
</td>
|
||||
<td class="py-4 px-6 whitespace-nowrap">
|
||||
<td class="py-2 px-6 whitespace-nowrap">
|
||||
<%= server.last_key_establishment |> Calendar.strftime("%Y-%m-%d %H:%M:%SZ") %>
|
||||
</td>
|
||||
|
||||
<% else %>
|
||||
<td class="py-4 px-6 whitespace-nowrap">
|
||||
<%= server.server.host %>:<%= server.server.port %>
|
||||
<td class="py-2 px-6 whitespace-nowrap">
|
||||
<%= server.server.host %><span :if={server.server.port != 4460}>:<%= server.server.port %></span>
|
||||
</td>
|
||||
<td class="py-4 px-6 whitespace-nowrap">
|
||||
<td class="py-2 px-6 whitespace-nowrap">
|
||||
<span class="group relative">
|
||||
<%= status %>
|
||||
<span class="pointer-events-none absolute -top-9 left-0 w-max p-1 rounded-lg bg-zinc-300 dark:bg-zinc-600 opacity-0 transition-opacity group-hover:opacity-100">
|
||||
|
@ -83,13 +83,13 @@
|
|||
</span>
|
||||
</span>
|
||||
</td>
|
||||
<td class="py-4 px-6 whitespace-nowrap"> - </td>
|
||||
<td class="py-4 px-6 whitespace-nowrap"> - </td>
|
||||
<td class="py-4 px-6 whitespace-nowrap"> - </td>
|
||||
<td class="py-4 px-6 whitespace-nowrap"> - </td>
|
||||
<td class="py-4 px-6 whitespace-nowrap"> - </td>
|
||||
<td class="py-4 px-6 whitespace-nowrap"> - </td>
|
||||
<td class="py-4 px-6 whitespace-nowrap">
|
||||
<td class="py-2 px-6 whitespace-nowrap"> - </td>
|
||||
<td class="py-2 px-6 whitespace-nowrap"> - </td>
|
||||
<td class="py-2 px-6 whitespace-nowrap"> - </td>
|
||||
<td class="py-2 px-6 whitespace-nowrap"> - </td>
|
||||
<td class="py-2 px-6 whitespace-nowrap"> - </td>
|
||||
<td class="py-2 px-6 whitespace-nowrap"> - </td>
|
||||
<td class="py-2 px-6 whitespace-nowrap">
|
||||
<%=
|
||||
server.last_key_establishment |> Calendar.strftime("%Y-%m-%d %H:%M:%SZ")
|
||||
%>
|
||||
|
|
2
mix.exs
2
mix.exs
|
@ -4,7 +4,7 @@ defmodule Chronoscope.MixProject do
|
|||
def project do
|
||||
[
|
||||
app: :chronoscope,
|
||||
version: "0.1.0",
|
||||
version: "1.0.0",
|
||||
elixir: "~> 1.16",
|
||||
elixirc_paths: elixirc_paths(Mix.env()),
|
||||
start_permanent: Mix.env() == :prod,
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
defmodule Chronoscope.NTS.ParseTest do
|
||||
use Chronoscope.Case, async: true
|
||||
|
||||
import Chronoscope.NTS.Parse
|
||||
|
||||
describe "Chronoscope.NTS.Parse.parse_nts_server()" do
|
||||
test "parses a server with a port" do
|
||||
assert parse_nts_server("test.example.com:1234") == %{host: "test.example.com", port: 1234}
|
||||
end
|
||||
|
||||
test "parses a server without a port" do
|
||||
assert parse_nts_server("test.example.com") == %{host: "test.example.com", port: 4460}
|
||||
end
|
||||
end
|
||||
end
|
|
@ -36,6 +36,37 @@ defmodule Chronoscope.NTSTest do
|
|||
end
|
||||
end
|
||||
|
||||
describe "Chronoscope.NTS.list_clients()" do
|
||||
test "shows empty client list" do
|
||||
assert NTS.list_clients([]) == []
|
||||
end
|
||||
|
||||
test "lists the given clients and starts them if they don't exist" do
|
||||
RegistryMock
|
||||
|> expect(:lookup, fn _, "test:1" -> [{1, nil}] end)
|
||||
|> expect(:lookup, fn _, "test2:2" -> [] end)
|
||||
|
||||
DynamicSupervisorMock
|
||||
|> expect(
|
||||
:start_child,
|
||||
fn Chronoscope.NTS.DynamicSupervisor,
|
||||
{Chronoscope.NTS.Client,
|
||||
[
|
||||
server: %{host: "test2", port: 2},
|
||||
name: {:via, RegistryMock, {Chronoscope.NTS.Registry, "test2:2"}}
|
||||
]} ->
|
||||
{:ok, 2}
|
||||
end
|
||||
)
|
||||
|
||||
GenServerMock
|
||||
|> expect(:call, fn 1, :list -> :one end)
|
||||
|> expect(:call, fn 2, :list -> :two end)
|
||||
|
||||
assert NTS.list_clients([%{host: "test", port: 1}, %{host: "test2", port: 2}]) == [:one, :two]
|
||||
end
|
||||
end
|
||||
|
||||
describe "Chronoscope.NTS.remove()" do
|
||||
test "does nothing if the client doesn't exist" do
|
||||
RegistryMock
|
||||
|
@ -74,7 +105,7 @@ defmodule Chronoscope.NTSTest do
|
|||
)
|
||||
|
||||
GenServerMock
|
||||
|> expect(:call, fn 1, :key_establishment -> :result end)
|
||||
|> expect(:call, fn 1, :key_establishment, 10_000 -> :result end)
|
||||
|
||||
assert NTS.key_establishment("localhost", 1111) == :result
|
||||
end
|
||||
|
@ -84,7 +115,7 @@ defmodule Chronoscope.NTSTest do
|
|||
|> expect(:lookup, fn _, _ -> [{1, 2}] end)
|
||||
|
||||
GenServerMock
|
||||
|> expect(:call, fn 1, :key_establishment -> :result end)
|
||||
|> expect(:call, fn 1, :key_establishment, 10_000 -> :result end)
|
||||
|
||||
assert NTS.key_establishment("localhost", 1111) == :result
|
||||
end
|
||||
|
|
|
@ -1,16 +1,11 @@
|
|||
defmodule ChronoscopeWeb.PageControllerTest do
|
||||
use ChronoscopeWeb.ConnCase, async: true
|
||||
|
||||
alias Chronoscope.DynamicSupervisorMock
|
||||
|
||||
import Mox
|
||||
|
||||
setup :verify_on_exit!
|
||||
|
||||
test "GET /", %{conn: conn} do
|
||||
DynamicSupervisorMock
|
||||
|> expect(:which_children, fn _ -> [] end)
|
||||
|
||||
conn = get(conn, ~p"/")
|
||||
assert html_response(conn, 200) =~ "Chronoscope"
|
||||
end
|
||||
|
|
|
@ -19,5 +19,6 @@ defmodule Chronoscope.DynamicSupervisor.Behaviour do
|
|||
end
|
||||
|
||||
defmodule Chronoscope.GenServer.Behaviour do
|
||||
@callback call(pid(), any(), any()) :: any()
|
||||
@callback call(pid(), any()) :: any()
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue