diff --git a/lib/chronoscope_web/controllers/api/v1/gemini/connection_controller.ex b/lib/chronoscope_web/controllers/api/v1/gemini/connection_controller.ex
index 2448ce4..39d8861 100644
--- a/lib/chronoscope_web/controllers/api/v1/gemini/connection_controller.ex
+++ b/lib/chronoscope_web/controllers/api/v1/gemini/connection_controller.ex
@@ -7,7 +7,7 @@ defmodule ChronoscopeWeb.API.V1.Gemini.ConnectionController do
@default_port 1965
@default_path "/"
- @max_host_length 255
+ @max_parameter_length 255
@gemini Application.compile_env(:chronoscope, :gemini, Gemini)
@@ -54,10 +54,9 @@ defmodule ChronoscopeWeb.API.V1.Gemini.ConnectionController do
end
defp connect(host, port, path) do
- # TODO - max path length
host
- |> String.slice(0, @max_host_length)
- |> @gemini.connect(port, path)
+ |> String.slice(0, @max_parameter_length)
+ |> @gemini.connect(port, String.slice(path, 0, @max_parameter_length))
end
defp format_response(response) do
diff --git a/test/chronoscope/gemini/request_test.exs b/test/chronoscope/gemini/request_test.exs
new file mode 100644
index 0000000..114fc9e
--- /dev/null
+++ b/test/chronoscope/gemini/request_test.exs
@@ -0,0 +1,11 @@
+defmodule Chronoscope.Gemini.RequestTest do
+ use Chronoscope.Case, async: true
+
+ import Chronoscope.Gemini.Request
+
+ describe "Chronoscope.Gemini.Request.create()" do
+ test "builds a request" do
+ assert create(%{host: "example.org", port: 9999, path: "/radish"}) == "gemini://example.org:9999/radish\r\n"
+ end
+ end
+end
diff --git a/test/chronoscope/gemini/response_test.exs b/test/chronoscope/gemini/response_test.exs
new file mode 100644
index 0000000..9800374
--- /dev/null
+++ b/test/chronoscope/gemini/response_test.exs
@@ -0,0 +1,32 @@
+defmodule Chronoscope.Gemini.ResponseTest do
+ use Chronoscope.Case, async: true
+
+ import Chronoscope.Gemini.Response
+
+ describe "Chronoscope.Gemini.Response.parse()" do
+ test "handles an empty response" do
+ assert parse([]) == {:error, "bad response: "}
+ end
+
+ test "handles an bad http response" do
+ response =
+ ("HTTP/1.1 400 Bad Request\r\nServer: nginx\r\nDate: Tue, 07 May 2024 14:03:56 GMT\r\nContent-Type: text/html\r\nContent-Length: 150\r\nConnection: close\r\n" <>
+ "\r\n\r\n
400 Bad Request\r\n\r\n400 Bad Request
\r\n
nginx\r\n\r\n\r\n")
+ |> String.to_charlist()
+
+ assert parse(response) == {:error, "bad response: " <> to_string(response)}
+ end
+
+ test "handles a good response" do
+ response = "20 text/gemini\r\nHello, world!\r\n" |> String.to_charlist()
+
+ assert parse(response) == {:ok, %{body: "Hello, world!\r\n", meta: "text/gemini", status_code: 20}}
+ end
+
+ test "handles a redirect" do
+ response = "31 gemini://localhost/grapes\r\n" |> String.to_charlist()
+
+ assert parse(response) == {:ok, %{body: "", meta: "gemini://localhost/grapes", status_code: 31}}
+ end
+ end
+end
diff --git a/test/chronoscope/nts/key_establishment_request_test.exs b/test/chronoscope/nts/key_establishment_request_test.exs
index bc1beb0..4cec7d4 100644
--- a/test/chronoscope/nts/key_establishment_request_test.exs
+++ b/test/chronoscope/nts/key_establishment_request_test.exs
@@ -4,7 +4,7 @@ defmodule Chronoscope.NTS.KeyEstablishmentRequestTest do
import Chronoscope.NTS.KeyEstablishmentRequest
describe "Chronoscope.NTS.KeyEstablishmentRequest.create()" do
- test "builds request" do
+ test "builds a request" do
assert create() ==
<<0x80, 0x01, 0x00, 0x02, 0x00, 0x00, 0x80, 0x04, 0x00, 0x04, 0x00, 0x1E, 0x00, 0x0F, 0x80, 0x00, 0x00, 0x00>>
end
diff --git a/test/chronoscope_web/controllers/api/v1/gemini/connection_controller_test.exs b/test/chronoscope_web/controllers/api/v1/gemini/connection_controller_test.exs
new file mode 100644
index 0000000..e932087
--- /dev/null
+++ b/test/chronoscope_web/controllers/api/v1/gemini/connection_controller_test.exs
@@ -0,0 +1,163 @@
+defmodule ChronoscopeWeb.API.V1.Gemini.ConnectionControllerTest do
+ use ChronoscopeWeb.ConnCase, async: true
+
+ alias Chronoscope.GeminiMock
+
+ import Mox
+
+ setup :verify_on_exit!
+
+ describe "GET /api/v1/gemini/connect" do
+ test "requires a host name", %{conn: conn} do
+ response =
+ conn
+ |> get(~p"/api/v1/gemini/connect")
+ |> json_response(400)
+
+ assert %{"error" => "missing host"} == response
+ end
+
+ test "uses the given host name", %{conn: conn} do
+ GeminiMock
+ |> expect(:connect, fn "localhost", 1965, "/" -> {:ok, %{status: :ok}} end)
+
+ response =
+ conn
+ |> get(~p"/api/v1/gemini/connect?host=localhost")
+ |> json_response(200)
+
+ assert %{"status" => "ok", "response" => %{}} == response
+ end
+
+ test "truncates the host name", %{conn: conn} do
+ GeminiMock
+ |> expect(
+ :connect,
+ 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",
+ 1965,
+ "/" ->
+ {:ok, %{status: :ok}}
+ end
+ )
+
+ response =
+ conn
+ |> get(
+ ~p"/api/v1/gemini/connect?host=test.example.com.0123456789.0123456789.0123456789.0123456789.0123456789.0123456789.0123456789.0123456789.0123456789.0123456789.0123456789.0123456789.0123456789.0123456789.0123456789.0123456789.0123456789.0123456789.0123456789.0123456789.0123456789.0123456789.0123456789.0123456789.0123456789.0123456789.0123456789"
+ )
+ |> json_response(200)
+
+ assert %{"status" => "ok", "response" => %{}} == response
+ end
+
+ test "uses the given path", %{conn: conn} do
+ GeminiMock
+ |> expect(:connect, fn "localhost", 1965, "/test" -> {:ok, %{status: :ok}} end)
+
+ response =
+ conn
+ |> get(~p"/api/v1/gemini/connect?host=localhost&path=/test")
+ |> json_response(200)
+
+ assert %{"status" => "ok", "response" => %{}} == response
+ end
+
+ test "truncates the path", %{conn: conn} do
+ GeminiMock
+ |> expect(
+ :connect,
+ fn "localhost",
+ 1965,
+ "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" ->
+ {:ok, %{status: :ok}}
+ end
+ )
+
+ response =
+ conn
+ |> get(
+ ~p"/api/v1/gemini/connect?host=localhost&path=test.example.com.0123456789.0123456789.0123456789.0123456789.0123456789.0123456789.0123456789.0123456789.0123456789.0123456789.0123456789.0123456789.0123456789.0123456789.0123456789.0123456789.0123456789.0123456789.0123456789.0123456789.0123456789.0123456789.0123456789.0123456789.0123456789.0123456789.0123456789"
+ )
+ |> json_response(200)
+
+ assert %{"status" => "ok", "response" => %{}} == response
+ end
+
+ test "uses the given port number", %{conn: conn} do
+ GeminiMock
+ |> expect(:connect, fn "localhost", 1966, "/" -> {:ok, %{status: :ok}} end)
+
+ response =
+ conn
+ |> get(~p"/api/v1/gemini/connect?host=localhost&port=1966")
+ |> json_response(200)
+
+ assert %{"status" => "ok", "response" => %{}} == response
+ end
+
+ test "handles an out of range port number", %{conn: conn} do
+ response =
+ conn
+ |> get(~p"/api/v1/gemini/connect?host=localhost&port=65536")
+ |> json_response(400)
+
+ assert %{"error" => "port out of range"} == response
+ end
+
+ test "handles a negative port number", %{conn: conn} do
+ response =
+ conn
+ |> get(~p"/api/v1/gemini/connect?host=localhost&port=-1965")
+ |> json_response(400)
+
+ assert %{"error" => "port out of range"} == response
+ end
+
+ test "handles a bad port number", %{conn: conn} do
+ response =
+ conn
+ |> get(~p"/api/v1/gemini/connect?host=localhost&port=AA60")
+ |> json_response(400)
+
+ assert %{"error" => "invalid port"} == response
+ end
+
+ test "uses both the port and path", %{conn: conn} do
+ GeminiMock
+ |> expect(:connect, fn "localhost", 1966, "/radish" -> {:ok, %{status: :ok}} end)
+
+ response =
+ conn
+ |> get(~p"/api/v1/gemini/connect?host=localhost&port=1966&path=/radish")
+ |> json_response(200)
+
+ assert %{"status" => "ok", "response" => %{}} == response
+ end
+
+ test "returns an empty successful response", %{conn: conn} do
+ GeminiMock
+ |> expect(:connect, fn "localhost", 1965, "/" -> {:ok, %{status: :ok}} end)
+
+ response =
+ conn
+ |> get(~p"/api/v1/gemini/connect?host=localhost")
+ |> json_response(200)
+
+ assert %{"status" => "ok", "response" => %{}} == response
+ end
+
+ test "returns a full successful response", %{conn: conn} do
+ GeminiMock
+ |> expect(:connect, fn "localhost", 1965, "/" ->
+ {:ok, %{}}
+ end)
+
+ response =
+ conn
+ |> get(~p"/api/v1/gemini/connect?host=localhost")
+ |> json_response(200)
+
+ assert %{"status" => "ok", "response" => %{}} == response
+ end
+ end
+end
diff --git a/test/chronoscope_web/controllers/api/v1/nts/key_establishment_controller_test.exs b/test/chronoscope_web/controllers/api/v1/nts/key_establishment_controller_test.exs
index 726509d..0c88fc6 100644
--- a/test/chronoscope_web/controllers/api/v1/nts/key_establishment_controller_test.exs
+++ b/test/chronoscope_web/controllers/api/v1/nts/key_establishment_controller_test.exs
@@ -17,6 +17,18 @@ defmodule ChronoscopeWeb.API.V1.NTS.KeyEstablishmentControllerTest do
assert %{"error" => "missing host"} == response
end
+ test "uses the given host name", %{conn: conn} do
+ NTSMock
+ |> expect(:key_establishment, fn "localhost", 4460 -> {:ok, %{status: :ok}} end)
+
+ response =
+ conn
+ |> get(~p"/api/v1/nts/key-establishment?host=localhost")
+ |> json_response(200)
+
+ assert %{"status" => "ok", "response" => %{"cookies" => 0}} == response
+ end
+
test "truncates the host name", %{conn: conn} do
NTSMock
|> expect(