diff --git a/test/towerops_web/plugs/agent_auth_test.exs b/test/towerops_web/plugs/agent_auth_test.exs new file mode 100644 index 00000000..0a3e4f56 --- /dev/null +++ b/test/towerops_web/plugs/agent_auth_test.exs @@ -0,0 +1,122 @@ +defmodule ToweropsWeb.Plugs.AgentAuthTest do + use ToweropsWeb.ConnCase, async: false + + import Towerops.AccountsFixtures + + alias Towerops.Agents + alias ToweropsWeb.Plugs.AgentAuth + + setup %{conn: conn} do + user = user_fixture() + {:ok, organization} = Towerops.Organizations.create_organization(%{name: "Test Org"}, user.id) + + {:ok, agent_token, token_string} = Agents.create_agent_token(organization.id, "Test Agent") + + # Set format to json for API endpoint + conn = + conn + |> Plug.Conn.fetch_query_params() + |> Phoenix.Controller.put_format("json") + + %{conn: conn, organization: organization, agent_token: agent_token, token_string: token_string} + end + + describe "init/1" do + test "returns options unchanged" do + opts = [some: :option] + assert AgentAuth.init(opts) == opts + end + end + + describe "call/2" do + test "authenticates valid Bearer token", %{conn: conn, agent_token: agent_token, token_string: token_string} do + conn = + conn + |> put_req_header("authorization", "Bearer #{token_string}") + |> AgentAuth.call([]) + + assert conn.assigns.current_agent_token.id == agent_token.id + refute conn.halted + end + + test "rejects missing authorization header", %{conn: conn} do + conn = AgentAuth.call(conn, []) + + assert conn.status == 401 + assert conn.halted + end + + test "rejects invalid Bearer token format", %{conn: conn} do + conn = + conn + |> put_req_header("authorization", "InvalidFormat token123") + |> AgentAuth.call([]) + + assert conn.status == 401 + assert conn.halted + end + + test "rejects malformed Bearer token", %{conn: conn} do + conn = + conn + |> put_req_header("authorization", "Bearer") + |> AgentAuth.call([]) + + assert conn.status == 401 + assert conn.halted + end + + test "rejects invalid token value", %{conn: conn} do + conn = + conn + |> put_req_header("authorization", "Bearer invalid_token_value") + |> AgentAuth.call([]) + + assert conn.status == 401 + assert conn.halted + end + + test "updates agent token heartbeat asynchronously", %{ + conn: conn, + agent_token: agent_token, + token_string: token_string + } do + # Get initial last_seen_at + initial_agent_token = Agents.get_agent_token!(agent_token.id) + initial_last_seen = initial_agent_token.last_seen_at + + conn = + conn + |> put_req_header("authorization", "Bearer #{token_string}") + |> AgentAuth.call([]) + + # Wait for async task to complete + Process.sleep(100) + + # Verify heartbeat was updated + updated_agent_token = Agents.get_agent_token!(agent_token.id) + + if initial_last_seen do + assert DateTime.compare(updated_agent_token.last_seen_at, initial_last_seen) in [:gt, :eq] + else + assert updated_agent_token.last_seen_at + end + + assert updated_agent_token.last_ip == "127.0.0.1" + refute conn.halted + end + + test "handles token verification error gracefully", %{conn: conn} do + # Use a token that looks valid but doesn't exist + fake_token = String.duplicate("a", 32) + + conn = + conn + |> put_req_header("authorization", "Bearer #{fake_token}") + |> AgentAuth.call([]) + + assert conn.status == 401 + assert conn.halted + end + end +end