defmodule ToweropsWeb.Api.V1.AlertsControllerTest do use ToweropsWeb.ConnCase, async: true import Towerops.AccountsFixtures import Towerops.DevicesFixtures import Towerops.OrganizationsFixtures alias Towerops.Alerts 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 defp create_alert(device, attrs \\ %{}) do default_attrs = %{ device_id: device.id, alert_type: "ping_down", message: "Device unreachable", triggered_at: Towerops.Time.now() } {:ok, alert} = Alerts.create_alert(Map.merge(default_attrs, attrs)) alert end describe "index/2" do test "lists alerts for the authenticated organization", %{ conn: conn, organization: organization } do device = device_fixture(%{organization_id: organization.id}) alert1 = create_alert(device) alert2 = create_alert(device, %{alert_type: "device_down", message: "SNMP timeout"}) conn = get(conn, ~p"/api/v1/alerts") assert %{"data" => alerts} = json_response(conn, 200) assert length(alerts) == 2 alert_ids = Enum.map(alerts, & &1["id"]) assert alert1.id in alert_ids assert alert2.id in alert_ids alert_json = List.first(alerts) assert Map.has_key?(alert_json, "id") assert Map.has_key?(alert_json, "alert_type") assert Map.has_key?(alert_json, "message") assert Map.has_key?(alert_json, "triggered_at") assert Map.has_key?(alert_json, "acknowledged_at") assert Map.has_key?(alert_json, "resolved_at") assert Map.has_key?(alert_json, "device_id") assert Map.has_key?(alert_json, "device_name") assert Map.has_key?(alert_json, "acknowledged_by_email") assert Map.has_key?(alert_json, "gaiia_impact") assert Map.has_key?(alert_json, "inserted_at") end test "returns empty list when organization has no alerts", %{conn: conn} do conn = get(conn, ~p"/api/v1/alerts") assert %{"data" => []} = json_response(conn, 200) end test "filters by status param for open alerts", %{ conn: conn, organization: organization } do device = device_fixture(%{organization_id: organization.id}) _open_alert = create_alert(device) resolved_alert = create_alert(device, %{alert_type: "device_down", message: "Resolved one"}) Alerts.resolve_alert(resolved_alert) conn = get(conn, ~p"/api/v1/alerts?status=active") assert %{"data" => alerts} = json_response(conn, 200) assert length(alerts) == 1 [returned] = alerts assert is_nil(returned["resolved_at"]) end test "filters by device_id param", %{ conn: conn, organization: organization } do device1 = device_fixture(%{organization_id: organization.id}) device2 = device_fixture(%{organization_id: organization.id}) _alert1 = create_alert(device1) _alert2 = create_alert(device2) conn = get(conn, ~p"/api/v1/alerts?device_id=#{device1.id}") assert %{"data" => alerts} = json_response(conn, 200) assert length(alerts) == 1 assert List.first(alerts)["device_id"] == device1.id end test "does not return alerts from other organizations", %{conn: conn} do other_user = user_fixture() other_org = organization_fixture(other_user.id) other_device = device_fixture(%{organization_id: other_org.id}) _other_alert = create_alert(other_device) conn = get(conn, ~p"/api/v1/alerts") assert %{"data" => []} = json_response(conn, 200) end end describe "show/2" do test "returns alert details when it belongs to the organization", %{ conn: conn, organization: organization } do device = device_fixture(%{organization_id: organization.id}) alert = create_alert(device) conn = get(conn, ~p"/api/v1/alerts/#{alert.id}") assert %{"data" => alert_json} = json_response(conn, 200) assert alert_json["id"] == alert.id assert alert_json["alert_type"] == "ping_down" assert alert_json["message"] == "Device unreachable" assert alert_json["device_id"] == device.id assert alert_json["device_name"] == device.name end test "returns 404 when alert does not exist", %{conn: conn} do conn = get(conn, ~p"/api/v1/alerts/#{Ecto.UUID.generate()}") assert %{"error" => "Alert not found"} = json_response(conn, 404) end test "returns 404 when alert belongs to a different organization", %{conn: conn} do other_user = user_fixture() other_org = organization_fixture(other_user.id) other_device = device_fixture(%{organization_id: other_org.id}) other_alert = create_alert(other_device) conn = get(conn, ~p"/api/v1/alerts/#{other_alert.id}") assert %{"error" => "Alert not found"} = json_response(conn, 404) end end describe "acknowledge/2" do test "sets acknowledged_at on the alert", %{ conn: conn, organization: organization } do device = device_fixture(%{organization_id: organization.id}) alert = create_alert(device) conn = post(conn, ~p"/api/v1/alerts/#{alert.id}/acknowledge") assert %{"data" => alert_json} = json_response(conn, 200) assert alert_json["id"] == alert.id assert alert_json["acknowledged_at"] end test "returns 404 when alert belongs to a different organization", %{conn: conn} do other_user = user_fixture() other_org = organization_fixture(other_user.id) other_device = device_fixture(%{organization_id: other_org.id}) other_alert = create_alert(other_device) conn = post(conn, ~p"/api/v1/alerts/#{other_alert.id}/acknowledge") assert %{"error" => "Alert not found"} = json_response(conn, 404) end test "returns 404 when alert does not exist", %{conn: conn} do conn = post(conn, ~p"/api/v1/alerts/#{Ecto.UUID.generate()}/acknowledge") assert %{"error" => "Alert not found"} = json_response(conn, 404) end end describe "resolve/2" do test "sets resolved_at on the alert", %{ conn: conn, organization: organization } do device = device_fixture(%{organization_id: organization.id}) alert = create_alert(device) conn = post(conn, ~p"/api/v1/alerts/#{alert.id}/resolve") assert %{"data" => alert_json} = json_response(conn, 200) assert alert_json["id"] == alert.id assert alert_json["resolved_at"] end test "returns 404 when alert belongs to a different organization", %{conn: conn} do other_user = user_fixture() other_org = organization_fixture(other_user.id) other_device = device_fixture(%{organization_id: other_org.id}) other_alert = create_alert(other_device) conn = post(conn, ~p"/api/v1/alerts/#{other_alert.id}/resolve") assert %{"error" => "Alert not found"} = json_response(conn, 404) end test "returns 404 when alert does not exist", %{conn: conn} do conn = post(conn, ~p"/api/v1/alerts/#{Ecto.UUID.generate()}/resolve") assert %{"error" => "Alert 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/alerts") 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/alerts") assert %{"error" => _message} = json_response(conn, 401) end end end