199 lines
7 KiB
Elixir
199 lines
7 KiB
Elixir
defmodule Towerops.Workers.AlertNotificationWorkerTest do
|
|
use Towerops.DataCase, async: false
|
|
|
|
alias Towerops.OnCall.Escalation
|
|
alias Towerops.Organizations
|
|
alias Towerops.Workers.AlertNotificationWorker
|
|
|
|
setup do
|
|
user = Towerops.AccountsFixtures.user_fixture()
|
|
|
|
{:ok, organization} =
|
|
Organizations.create_organization(%{name: "Test Org"}, user.id)
|
|
|
|
%{organization: organization, user: user}
|
|
end
|
|
|
|
describe "alert_routing organization setting" do
|
|
test "defaults to builtin routing", %{organization: org} do
|
|
assert org.alert_routing == "builtin"
|
|
end
|
|
|
|
test "can be set to pagerduty", %{organization: org} do
|
|
{:ok, updated} = Organizations.update_organization(org, %{alert_routing: "pagerduty"})
|
|
assert updated.alert_routing == "pagerduty"
|
|
end
|
|
|
|
test "can be set to both", %{organization: org} do
|
|
{:ok, updated} = Organizations.update_organization(org, %{alert_routing: "both"})
|
|
assert updated.alert_routing == "both"
|
|
end
|
|
|
|
test "can be set back to builtin", %{organization: org} do
|
|
{:ok, pd} = Organizations.update_organization(org, %{alert_routing: "pagerduty"})
|
|
{:ok, builtin} = Organizations.update_organization(pd, %{alert_routing: "builtin"})
|
|
assert builtin.alert_routing == "builtin"
|
|
end
|
|
|
|
test "rejects invalid routing values", %{organization: org} do
|
|
{:error, changeset} = Organizations.update_organization(org, %{alert_routing: "invalid"})
|
|
assert {"must be builtin, pagerduty, or both", _} = changeset.errors[:alert_routing]
|
|
end
|
|
|
|
test "persists through reload", %{organization: org} do
|
|
{:ok, _} = Organizations.update_organization(org, %{alert_routing: "pagerduty"})
|
|
reloaded = Towerops.Repo.get!(Towerops.Organizations.Organization, org.id)
|
|
assert reloaded.alert_routing == "pagerduty"
|
|
end
|
|
end
|
|
|
|
describe "enqueue helpers" do
|
|
defp args_get(args, key) do
|
|
Map.get(args, key) || Map.get(args, String.to_atom(key))
|
|
end
|
|
|
|
test "enqueue_trigger inserts an oban job with trigger action" do
|
|
assert {:ok, job} = AlertNotificationWorker.enqueue_trigger("alert-1")
|
|
assert %Oban.Job{args: args, worker: "Towerops.Workers.AlertNotificationWorker"} = job
|
|
assert "trigger" == args_get(args, "action")
|
|
assert "alert-1" == args_get(args, "alert_id")
|
|
end
|
|
|
|
test "enqueue_acknowledge inserts an oban job with acknowledge action" do
|
|
assert {:ok, job} = AlertNotificationWorker.enqueue_acknowledge("alert-2")
|
|
assert "acknowledge" == args_get(job.args, "action")
|
|
assert "alert-2" == args_get(job.args, "alert_id")
|
|
end
|
|
|
|
test "enqueue_resolve inserts an oban job with resolve action" do
|
|
assert {:ok, job} = AlertNotificationWorker.enqueue_resolve("alert-3")
|
|
assert "resolve" == args_get(job.args, "action")
|
|
assert "alert-3" == args_get(job.args, "alert_id")
|
|
end
|
|
end
|
|
|
|
describe "perform/1 — alert not found" do
|
|
test "trigger returns :ok when alert is missing" do
|
|
job = %Oban.Job{args: %{"action" => "trigger", "alert_id" => Ecto.UUID.generate()}}
|
|
assert :ok == AlertNotificationWorker.perform(job)
|
|
end
|
|
|
|
test "acknowledge returns :ok when alert is missing" do
|
|
job = %Oban.Job{args: %{"action" => "acknowledge", "alert_id" => Ecto.UUID.generate()}}
|
|
assert :ok == AlertNotificationWorker.perform(job)
|
|
end
|
|
|
|
test "resolve returns :ok when alert is missing" do
|
|
job = %Oban.Job{args: %{"action" => "resolve", "alert_id" => Ecto.UUID.generate()}}
|
|
assert :ok == AlertNotificationWorker.perform(job)
|
|
end
|
|
end
|
|
|
|
describe "perform/1 — device missing" do
|
|
test "trigger returns :ok when device of alert is missing", %{organization: org} do
|
|
{:ok, site} =
|
|
Towerops.Sites.create_site(%{
|
|
name: "Site-#{System.unique_integer([:positive])}",
|
|
organization_id: org.id
|
|
})
|
|
|
|
device =
|
|
Towerops.DevicesFixtures.device_fixture(%{
|
|
organization_id: org.id,
|
|
site_id: site.id,
|
|
name: "D1"
|
|
})
|
|
|
|
{:ok, alert} =
|
|
Towerops.Alerts.create_alert(%{
|
|
device_id: device.id,
|
|
alert_type: "device_down",
|
|
triggered_at: DateTime.utc_now(),
|
|
message: "down"
|
|
})
|
|
|
|
# Delete the device, but keep the alert
|
|
Towerops.Repo.delete!(device)
|
|
|
|
job = %Oban.Job{args: %{"action" => "trigger", "alert_id" => alert.id}}
|
|
assert :ok == AlertNotificationWorker.perform(job)
|
|
end
|
|
end
|
|
|
|
describe "perform/1 — incident lifecycle" do
|
|
setup %{organization: org} do
|
|
site = Towerops.OrganizationsFixtures.site_fixture(org.id)
|
|
device = Towerops.DevicesFixtures.device_fixture(%{organization_id: org.id, site_id: site.id})
|
|
user = Towerops.AccountsFixtures.user_fixture()
|
|
|
|
policy = Towerops.OnCallFixtures.escalation_policy_fixture(org.id)
|
|
|
|
rule =
|
|
Towerops.OnCallFixtures.escalation_rule_fixture(policy.id, %{
|
|
position: 0,
|
|
timeout_minutes: 5
|
|
})
|
|
|
|
_target =
|
|
Towerops.OnCallFixtures.escalation_target_fixture(rule.id, %{
|
|
target_type: "user",
|
|
user_id: user.id
|
|
})
|
|
|
|
# Make the device's organization route to "builtin" (default), with the
|
|
# policy as the org's default — so trigger starts an incident.
|
|
{:ok, org_with_policy} =
|
|
Organizations.update_organization(org, %{
|
|
default_escalation_policy_id: policy.id,
|
|
alert_routing: "builtin"
|
|
})
|
|
|
|
{:ok, alert} =
|
|
Towerops.Alerts.create_alert(%{
|
|
device_id: device.id,
|
|
alert_type: "device_down",
|
|
triggered_at: DateTime.utc_now(),
|
|
message: "incident-down"
|
|
})
|
|
|
|
%{
|
|
device: device,
|
|
alert: alert,
|
|
user: user,
|
|
policy: policy,
|
|
organization: org_with_policy
|
|
}
|
|
end
|
|
|
|
test "acknowledge looks up open incident by alert and acknowledges it", %{alert: alert} do
|
|
# First trigger to create the incident
|
|
assert :ok =
|
|
AlertNotificationWorker.perform(%Oban.Job{
|
|
args: %{"action" => "trigger", "alert_id" => alert.id}
|
|
})
|
|
|
|
assert :ok =
|
|
AlertNotificationWorker.perform(%Oban.Job{
|
|
args: %{"action" => "acknowledge", "alert_id" => alert.id}
|
|
})
|
|
|
|
# The acknowledged incident should now be in :acknowledged state
|
|
incident = Escalation.find_incident_for_alert(alert.id)
|
|
# Either it was acknowledged (returned by find_incident_for_alert if
|
|
# that helper still returns acknowledged) OR was already closed.
|
|
assert Enum.any?([is_nil(incident), incident.status in ["acknowledged", "triggered"]])
|
|
end
|
|
|
|
test "resolve looks up open incident by alert and resolves it", %{alert: alert} do
|
|
assert :ok =
|
|
AlertNotificationWorker.perform(%Oban.Job{
|
|
args: %{"action" => "trigger", "alert_id" => alert.id}
|
|
})
|
|
|
|
assert :ok =
|
|
AlertNotificationWorker.perform(%Oban.Job{
|
|
args: %{"action" => "resolve", "alert_id" => alert.id}
|
|
})
|
|
end
|
|
end
|
|
end
|