defmodule ToweropsWeb.Api.V1.MembersControllerTest 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 add_member(organization, role \\ :technician) do other_user = user_fixture() {:ok, _membership} = Organizations.create_membership(%{ organization_id: organization.id, user_id: other_user.id, role: role }) other_user end describe "index/2" do test "lists members including owner", %{conn: conn, user: user} do conn = get(conn, ~p"/api/v1/members") assert %{"data" => members} = json_response(conn, 200) assert [_ | _] = members owner = Enum.find(members, &(&1["id"] == user.id)) assert owner["email"] == user.email assert owner["role"] == "owner" assert Map.has_key?(owner, "inserted_at") end test "lists multiple members", %{conn: conn, organization: organization} do other_user = add_member(organization, :technician) conn = get(conn, ~p"/api/v1/members") assert %{"data" => members} = json_response(conn, 200) assert length(members) == 2 member_ids = Enum.map(members, & &1["id"]) assert other_user.id in member_ids end end describe "update/2" do test "changes member role", %{conn: conn, organization: organization} do other_user = add_member(organization, :technician) conn = patch(conn, ~p"/api/v1/members/#{other_user.id}", %{ "role" => "admin" }) assert %{"data" => data} = json_response(conn, 200) assert data["id"] == other_user.id assert data["role"] == "admin" end test "returns 403 when changing owner role", %{conn: conn, user: user} do conn = patch(conn, ~p"/api/v1/members/#{user.id}", %{ "role" => "technician" }) assert %{"error" => "Cannot change owner role"} = json_response(conn, 403) end test "returns 404 for non-member", %{conn: conn} do fake_id = Ecto.UUID.generate() conn = patch(conn, ~p"/api/v1/members/#{fake_id}", %{ "role" => "admin" }) assert %{"error" => "Member not found"} = json_response(conn, 404) end test "returns 400 without role param", %{conn: conn, organization: organization} do other_user = add_member(organization) conn = patch(conn, ~p"/api/v1/members/#{other_user.id}", %{}) assert %{"error" => "Missing 'role' parameter"} = json_response(conn, 400) end end describe "delete/2" do test "removes member", %{conn: conn, organization: organization} do other_user = add_member(organization) conn = delete(conn, ~p"/api/v1/members/#{other_user.id}") assert response(conn, 204) end test "returns 403 when removing owner", %{conn: conn, user: user} do conn = delete(conn, ~p"/api/v1/members/#{user.id}") assert %{"error" => "Cannot remove owner"} = json_response(conn, 403) end test "returns 404 for non-member", %{conn: conn} do fake_id = Ecto.UUID.generate() conn = delete(conn, ~p"/api/v1/members/#{fake_id}") assert %{"error" => "Member 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/members") 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/members") assert %{"error" => _message} = json_response(conn, 401) end end end