defmodule ToweropsWeb.Api.V1.AgentsControllerTest do use ToweropsWeb.ConnCase, async: true import Towerops.AccountsFixtures import Towerops.AgentsFixtures import Towerops.OrganizationsFixtures alias Towerops.ApiTokens 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 describe "index/2" do test "lists agents for the authenticated organization", %{ conn: conn, organization: organization } do {:ok, agent1, _raw1} = agent_token_fixture(organization.id, "Agent Alpha") {:ok, agent2, _raw2} = agent_token_fixture(organization.id, "Agent Beta") conn = get(conn, ~p"/api/v1/agents") assert %{"data" => agents} = json_response(conn, 200) assert length(agents) == 2 agent_ids = Enum.map(agents, & &1["id"]) assert agent1.id in agent_ids assert agent2.id in agent_ids agent_json = List.first(agents) assert Map.has_key?(agent_json, "id") assert Map.has_key?(agent_json, "name") assert Map.has_key?(agent_json, "enabled") assert Map.has_key?(agent_json, "last_seen_at") assert Map.has_key?(agent_json, "last_ip") assert Map.has_key?(agent_json, "metadata") assert Map.has_key?(agent_json, "inserted_at") end test "returns empty list when organization has no agents", %{conn: conn} do conn = get(conn, ~p"/api/v1/agents") assert %{"data" => []} = json_response(conn, 200) end test "does not return agents from other organizations", %{conn: conn} do other_user = user_fixture() other_org = organization_fixture(other_user.id) {:ok, _other_agent, _raw} = agent_token_fixture(other_org.id) conn = get(conn, ~p"/api/v1/agents") assert %{"data" => agents} = json_response(conn, 200) assert Enum.empty?(agents) end end describe "create/2" do test "creates agent with valid name and returns token", %{ conn: conn, organization: organization } do conn = post(conn, ~p"/api/v1/agents", %{"name" => "New Agent"}) assert %{"data" => agent} = json_response(conn, 201) assert agent["name"] == "New Agent" assert is_binary(agent["id"]) assert is_binary(agent["token"]) assert agent["enabled"] == true # Verify the agent was created in the correct organization created = Towerops.Agents.get_agent_token!(agent["id"]) assert created.organization_id == organization.id end test "returns 400 without name parameter", %{conn: conn} do conn = post(conn, ~p"/api/v1/agents", %{}) assert %{"error" => "Missing 'name' parameter"} = json_response(conn, 400) end end describe "show/2" do test "returns agent with device_count", %{conn: conn, organization: organization} do {:ok, agent, _raw} = agent_token_fixture(organization.id, "Show Agent") conn = get(conn, ~p"/api/v1/agents/#{agent.id}") assert %{"data" => data} = json_response(conn, 200) assert data["id"] == agent.id assert data["name"] == "Show Agent" assert Map.has_key?(data, "device_count") assert data["device_count"] == 0 end test "returns 404 for non-existent agent", %{conn: conn} do conn = get(conn, ~p"/api/v1/agents/#{Ecto.UUID.generate()}") assert %{"error" => "Agent not found"} = json_response(conn, 404) end test "returns 404 for agent in another organization", %{conn: conn} do other_user = user_fixture() other_org = organization_fixture(other_user.id) {:ok, other_agent, _raw} = agent_token_fixture(other_org.id) conn = get(conn, ~p"/api/v1/agents/#{other_agent.id}") assert %{"error" => "Agent not found"} = json_response(conn, 404) end end describe "delete/2" do test "removes agent and returns 204", %{conn: conn, organization: organization} do {:ok, agent, _raw} = agent_token_fixture(organization.id) conn = delete(conn, ~p"/api/v1/agents/#{agent.id}") assert response(conn, 204) # Verify the agent was deleted assert_raise Ecto.NoResultsError, fn -> Towerops.Agents.get_agent_token!(agent.id) end end test "returns 404 for non-existent agent", %{conn: conn} do conn = delete(conn, ~p"/api/v1/agents/#{Ecto.UUID.generate()}") assert %{"error" => "Agent not found"} = json_response(conn, 404) end test "returns 404 for agent in another organization", %{conn: conn} do other_user = user_fixture() other_org = organization_fixture(other_user.id) {:ok, other_agent, _raw} = agent_token_fixture(other_org.id) conn = delete(conn, ~p"/api/v1/agents/#{other_agent.id}") assert %{"error" => "Agent 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/agents") 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/agents") assert %{"error" => _message} = json_response(conn, 401) end end end