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\n400 Bad Request\r\n\r\n

400 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(