defmodule ToweropsWeb.Api.V1.InvitationsControllerTest do use ToweropsWeb.ConnCase, async: true import Towerops.AccountsFixtures import Towerops.OrganizationsFixtures alias Towerops.ApiTokens alias Towerops.Organizations 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" }) conn = build_conn() |> put_req_header("authorization", "Bearer #{raw_token}") |> put_req_header("accept", "application/json") %{conn: conn, user: user, organization: organization} end defp create_test_invitation(organization, user, email \\ "invited@example.com") do {:ok, invitation} = Organizations.create_invitation(%{ organization_id: organization.id, email: email, role: :technician, invited_by_id: user.id, token: Ecto.UUID.generate(), expires_at: DateTime.utc_now() |> DateTime.add(7 * 86_400, :second) |> DateTime.truncate(:second) }) invitation end describe "index/2" do test "lists pending invitations", %{ conn: conn, organization: organization, user: user } do invitation = create_test_invitation(organization, user) conn = get(conn, ~p"/api/v1/invitations") assert %{"data" => invitations} = json_response(conn, 200) assert length(invitations) == 1 inv = List.first(invitations) assert inv["id"] == invitation.id assert inv["email"] == "invited@example.com" assert inv["role"] == "technician" assert Map.has_key?(inv, "expires_at") assert Map.has_key?(inv, "invited_by_email") assert Map.has_key?(inv, "inserted_at") end test "returns empty list when no invitations exist", %{conn: conn} do conn = get(conn, ~p"/api/v1/invitations") assert %{"data" => []} = json_response(conn, 200) end end describe "create/2" do test "creates invitation", %{conn: conn} do params = %{ "email" => "newuser@example.com", "role" => "technician" } conn = post(conn, ~p"/api/v1/invitations", params) assert %{"data" => data} = json_response(conn, 201) assert data["email"] == "newuser@example.com" assert data["role"] == "technician" assert Map.has_key?(data, "id") assert Map.has_key?(data, "expires_at") assert Map.has_key?(data, "inserted_at") end test "returns 400 without required params", %{conn: conn} do conn = post(conn, ~p"/api/v1/invitations", %{}) assert %{"error" => "Missing 'email' and 'role' parameters"} = json_response(conn, 400) end end describe "delete/2" do test "removes invitation", %{conn: conn, organization: organization, user: user} do invitation = create_test_invitation(organization, user) conn = delete(conn, ~p"/api/v1/invitations/#{invitation.id}") assert response(conn, 204) end test "returns 404 for invitation in different organization", %{conn: conn} do other_user = user_fixture() other_org = organization_fixture(other_user.id) invitation = create_test_invitation(other_org, other_user, "other@example.com") conn = delete(conn, ~p"/api/v1/invitations/#{invitation.id}") assert %{"error" => "Invitation not found"} = json_response(conn, 404) end test "returns 404 for non-existent invitation", %{conn: conn} do fake_id = Ecto.UUID.generate() conn = delete(conn, ~p"/api/v1/invitations/#{fake_id}") assert %{"error" => "Invitation not found"} = json_response(conn, 404) end end describe "authentication" do test "returns 401 without authorization header" do conn = build_conn() |> put_req_header("accept", "application/json") |> get(~p"/api/v1/invitations") assert %{"error" => _message} = json_response(conn, 401) end test "returns 401 with invalid token" do conn = build_conn() |> put_req_header("authorization", "Bearer towerops_invalid_token") |> put_req_header("accept", "application/json") |> get(~p"/api/v1/invitations") assert %{"error" => _message} = json_response(conn, 401) end end end