towerops/test/towerops_web/controllers/api/v1/alerts_controller_test.exs
Graham McIntire 7613a4dde5
add missing REST API controller tests
Tests for agents, alerts, integrations, organization, members, and
invitations controllers. Refactor alert filters to reduce complexity.
75 new tests, all passing.
2026-03-11 14:43:17 -05:00

254 lines
8.1 KiB
Elixir

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: DateTime.truncate(DateTime.utc_now(), :second)
}
{: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