Implemented tests for critical security modules with significant coverage improvements: - ApiAuth plug: 0% → 100% (12 tests) - Permissions module: 0% → 85% (32 tests) - Devices API controller: 0% → 95% (22 tests) Enhanced test fixtures to support membership roles and device creation with proper organization scoping.
195 lines
5.5 KiB
Elixir
195 lines
5.5 KiB
Elixir
defmodule ToweropsWeb.Plugs.ApiAuthTest do
|
|
use ToweropsWeb.ConnCase, async: true
|
|
|
|
import Towerops.AccountsFixtures
|
|
import Towerops.OrganizationsFixtures
|
|
|
|
alias Towerops.ApiTokens
|
|
alias ToweropsWeb.Plugs.ApiAuth
|
|
|
|
describe "call/2 with valid authorization" 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: "Test Token"
|
|
})
|
|
|
|
%{
|
|
user: user,
|
|
organization: organization,
|
|
raw_token: raw_token
|
|
}
|
|
end
|
|
|
|
test "authenticates successfully with valid Bearer token", %{
|
|
conn: conn,
|
|
raw_token: raw_token,
|
|
organization: organization,
|
|
user: user
|
|
} do
|
|
conn =
|
|
conn
|
|
|> put_req_header("authorization", "Bearer #{raw_token}")
|
|
|> ApiAuth.call([])
|
|
|
|
refute conn.halted
|
|
assert conn.assigns.current_organization_id == organization.id
|
|
assert conn.assigns.current_user.id == user.id
|
|
assert conn.assigns.api_authenticated == true
|
|
end
|
|
|
|
test "authenticates successfully with token without user_id", %{
|
|
conn: conn,
|
|
organization: organization
|
|
} do
|
|
{:ok, {_token, raw_token}} =
|
|
ApiTokens.create_api_token(%{
|
|
organization_id: organization.id,
|
|
name: "Token Without User"
|
|
})
|
|
|
|
conn =
|
|
conn
|
|
|> put_req_header("authorization", "Bearer #{raw_token}")
|
|
|> ApiAuth.call([])
|
|
|
|
refute conn.halted
|
|
assert conn.assigns.current_organization_id == organization.id
|
|
assert is_nil(conn.assigns.current_user)
|
|
assert conn.assigns.api_authenticated == true
|
|
end
|
|
end
|
|
|
|
describe "call/2 with missing authorization" do
|
|
test "returns 401 when Authorization header is missing", %{conn: conn} do
|
|
conn = ApiAuth.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 Authorization header is empty", %{conn: conn} do
|
|
conn =
|
|
conn
|
|
|> put_req_header("authorization", "")
|
|
|> ApiAuth.call([])
|
|
|
|
assert conn.halted
|
|
assert conn.status == 401
|
|
assert json_response(conn, 401) == %{"error" => "Missing or invalid Authorization header"}
|
|
end
|
|
end
|
|
|
|
describe "call/2 with invalid authorization format" do
|
|
test "returns 401 when Authorization header doesn't start with 'Bearer '", %{conn: conn} do
|
|
conn =
|
|
conn
|
|
|> put_req_header("authorization", "Basic dGVzdDp0ZXN0")
|
|
|> ApiAuth.call([])
|
|
|
|
assert conn.halted
|
|
assert conn.status == 401
|
|
assert json_response(conn, 401) == %{"error" => "Missing or invalid Authorization header"}
|
|
end
|
|
|
|
test "returns 401 when Bearer token is malformed", %{conn: conn} do
|
|
conn =
|
|
conn
|
|
|> put_req_header("authorization", "Bearertoken123")
|
|
|> ApiAuth.call([])
|
|
|
|
assert conn.halted
|
|
assert conn.status == 401
|
|
assert json_response(conn, 401) == %{"error" => "Missing or invalid Authorization header"}
|
|
end
|
|
|
|
test "returns 401 when authorization has only 'Bearer' with no token", %{conn: conn} do
|
|
conn =
|
|
conn
|
|
|> put_req_header("authorization", "Bearer ")
|
|
|> ApiAuth.call([])
|
|
|
|
assert conn.halted
|
|
assert conn.status == 401
|
|
assert json_response(conn, 401) == %{"error" => "Invalid or expired API token"}
|
|
end
|
|
end
|
|
|
|
describe "call/2 with invalid token" do
|
|
test "returns 401 for non-existent token", %{conn: conn} do
|
|
conn =
|
|
conn
|
|
|> put_req_header("authorization", "Bearer towerops_invalid_token_12345")
|
|
|> ApiAuth.call([])
|
|
|
|
assert conn.halted
|
|
assert conn.status == 401
|
|
assert json_response(conn, 401) == %{"error" => "Invalid or expired API token"}
|
|
end
|
|
|
|
test "returns 401 for randomly generated token", %{conn: conn} do
|
|
random_token = "towerops_" <> Base.encode64(:crypto.strong_rand_bytes(32))
|
|
|
|
conn =
|
|
conn
|
|
|> put_req_header("authorization", "Bearer #{random_token}")
|
|
|> ApiAuth.call([])
|
|
|
|
assert conn.halted
|
|
assert conn.status == 401
|
|
assert json_response(conn, 401) == %{"error" => "Invalid or expired API token"}
|
|
end
|
|
end
|
|
|
|
describe "call/2 with expired token" do
|
|
setup do
|
|
user = user_fixture()
|
|
organization = organization_fixture(user.id)
|
|
|
|
# Create an expired token (1 day in the past)
|
|
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
|
|
})
|
|
|
|
%{
|
|
user: user,
|
|
organization: organization,
|
|
expired_token: raw_token
|
|
}
|
|
end
|
|
|
|
test "returns 401 for expired token", %{conn: conn, expired_token: expired_token} do
|
|
conn =
|
|
conn
|
|
|> put_req_header("authorization", "Bearer #{expired_token}")
|
|
|> ApiAuth.call([])
|
|
|
|
assert conn.halted
|
|
assert conn.status == 401
|
|
assert json_response(conn, 401) == %{"error" => "Invalid or expired API token"}
|
|
end
|
|
end
|
|
|
|
describe "init/1" do
|
|
test "returns options unchanged" do
|
|
opts = [some: :option]
|
|
assert ApiAuth.init(opts) == opts
|
|
end
|
|
|
|
test "returns empty list unchanged" do
|
|
assert ApiAuth.init([]) == []
|
|
end
|
|
end
|
|
end
|