towerops/test/towerops_web/plugs/graphql_auth_test.exs
graham c5481c2cd3 add mobile token auth to GraphQL endpoint (#48)
Extend the GraphQL API and socket to accept mobile session tokens
alongside existing API tokens. Add OrganizationScope middleware
that extracts organization_id from query args for mobile users.
Add my_organizations query for mobile app org listing.

Reviewed-on: graham/towerops-web#48
2026-03-16 15:23:07 -05:00

137 lines
3.6 KiB
Elixir

defmodule ToweropsWeb.Plugs.GraphQLAuthTest do
use ToweropsWeb.ConnCase, async: true
import Towerops.AccountsFixtures
import Towerops.OrganizationsFixtures
alias Towerops.ApiTokens
alias Towerops.MobileSessions
alias ToweropsWeb.Plugs.GraphQLAuth
describe "init/1" do
test "returns options unchanged" do
assert GraphQLAuth.init([]) == []
end
end
describe "call/2 with API token (towerops_ prefix)" do
setup do
user = user_fixture()
organization = organization_fixture(user.id)
{:ok, {_token, raw_token}} =
ApiTokens.create_api_token(%{
organization_id: organization.id,
user_id: user.id,
name: "GraphQL Auth Test Token"
})
%{user: user, organization: organization, raw_token: raw_token}
end
test "authenticates and sets org_id + user", %{
conn: conn,
raw_token: raw_token,
organization: organization,
user: user
} do
conn =
conn
|> put_req_header("authorization", "Bearer #{raw_token}")
|> GraphQLAuth.call([])
refute conn.halted
assert conn.assigns.current_organization_id == organization.id
assert conn.assigns.current_user.id == user.id
end
test "returns 401 for expired API token", %{conn: conn, organization: organization, user: user} do
expires_at = DateTime.add(DateTime.utc_now(), -1, :day)
{:ok, {_token, raw_token}} =
ApiTokens.create_api_token(%{
organization_id: organization.id,
user_id: user.id,
name: "Expired Token",
expires_at: expires_at
})
conn =
conn
|> put_req_header("authorization", "Bearer #{raw_token}")
|> GraphQLAuth.call([])
assert conn.halted
assert conn.status == 401
end
end
describe "call/2 with mobile session token" do
setup do
user = user_fixture()
{:ok, session} =
MobileSessions.create_mobile_session(%{
user_id: user.id,
device_name: "Test iPhone"
})
%{user: user, session: session}
end
test "authenticates and sets user but not org_id", %{
conn: conn,
user: user,
session: session
} do
conn =
conn
|> put_req_header("authorization", "Bearer #{session.raw_token}")
|> GraphQLAuth.call([])
refute conn.halted
assert conn.assigns.current_user.id == user.id
refute Map.has_key?(conn.assigns, :current_organization_id)
end
test "returns 401 for expired mobile session", %{conn: conn} do
user = user_fixture()
expires_at = DateTime.add(DateTime.utc_now(), -1, :day)
{:ok, session} =
MobileSessions.create_mobile_session(%{
user_id: user.id,
device_name: "Expired iPhone",
expires_at: expires_at
})
conn =
conn
|> put_req_header("authorization", "Bearer #{session.raw_token}")
|> GraphQLAuth.call([])
assert conn.halted
assert conn.status == 401
end
end
describe "call/2 with missing or invalid auth" do
test "returns 401 when Authorization header is missing", %{conn: conn} do
conn = GraphQLAuth.call(conn, [])
assert conn.halted
assert conn.status == 401
assert json_response(conn, 401) == %{"error" => "Missing or invalid Authorization header"}
end
test "returns 401 when token is completely invalid", %{conn: conn} do
conn =
conn
|> put_req_header("authorization", "Bearer totally_invalid_token_xyz")
|> GraphQLAuth.call([])
assert conn.halted
assert conn.status == 401
end
end
end