towerops/test/towerops/alerts/alert_query_test.exs
Graham McIntire 2269f38fc5 test: lift coverage 78.42% → 79.59% with focused unit + integration tests
Adds tests across previously-uncovered or under-covered modules:

- Mix tasks: backfill_checks, copy_mibs, import_mibs (new test files)
- Status pages: StatusIncident changeset (new file)
- Webhook signature verification on AgentReleaseWebhookController
- CloudflareBanWorker: HTTP-stubbed success / 5xx / transport-error paths
- FirmwareVersionFetcherWorker: stubbed perform/0 success + non-200 + tx error
- Geoip.Import: production --production code path (HTTP-stubbed)
- AgentLive.Show: superuser restart_agent + update_agent + non-superuser paths
- NetworkMapLive: node_clicked, deep-link node, topology_updated PubSub
- PreseemInsightsLive: toggle_select, deselect_all, filter, dismiss-twice
- AlertQuery: 1-arity defaults variants
- ProfileWatcher: yaml + reload-trigger event passthrough
- MobileAuthController: verify_qr_token success + get/revoke session
- HealthController: redis-configured branch
- RedisHealthCheck: un-tag :integration tests now that Redis is local
- FourOhFourTracker: un-skip module (Redis available)
- PingExecutor: un-tag local-only tests + KeyError surface + clamp branch
- CheckExecutorWorker: dispatch tcp/dns/ssl/ping branches
- UserResetPasswordController: drop dead edit/update + their template

Also removes dead code: edit/update actions on UserResetPasswordController
and the unused edit.html.heex template — both routed via LiveView.
2026-05-07 18:40:43 -05:00

182 lines
6.4 KiB
Elixir

defmodule Towerops.Alerts.AlertQueryTest do
use Towerops.DataCase, async: true
use ExUnitProperties
import Towerops.DevicesFixtures
alias Towerops.Alerts.Alert
alias Towerops.Alerts.AlertQuery
alias Towerops.Repo
setup do
device = device_fixture()
%{device: device, organization_id: device.organization_id}
end
describe "base/0" do
test "returns the Alert schema as a queryable" do
assert AlertQuery.base() == Alert
end
end
describe "for_organization/2" do
test "filters to a single organization", %{device: device, organization_id: org_id} do
keep = insert_alert!(device.id, org_id, "device_down")
other_device = device_fixture()
_other_org = insert_alert!(other_device.id, other_device.organization_id, "device_down")
[result] = AlertQuery.base() |> AlertQuery.for_organization(org_id) |> Repo.all()
assert result.id == keep.id
end
test "is composable with other filters", %{device: device, organization_id: org_id} do
a = insert_alert!(device.id, org_id, "device_down")
_b = insert_alert!(device.id, org_id, "device_up")
[result] =
AlertQuery.base()
|> AlertQuery.for_organization(org_id)
|> AlertQuery.of_type("device_down")
|> Repo.all()
assert result.id == a.id
end
end
describe "for_device/2" do
test "filters to a single device", %{device: device, organization_id: org_id} do
other_device = device_fixture()
keep = insert_alert!(device.id, org_id, "device_down")
_drop = insert_alert!(other_device.id, other_device.organization_id, "device_down")
[result] = AlertQuery.base() |> AlertQuery.for_device(device.id) |> Repo.all()
assert result.id == keep.id
end
end
describe "of_type/2" do
test "filters by alert_type", %{device: device, organization_id: org_id} do
down = insert_alert!(device.id, org_id, "device_down")
_up = insert_alert!(device.id, org_id, "device_up")
[result] = AlertQuery.base() |> AlertQuery.of_type("device_down") |> Repo.all()
assert result.id == down.id
end
end
describe "unresolved/1 and resolved/1" do
test "partition alerts by resolved_at presence", %{device: device, organization_id: org_id} do
open = insert_alert!(device.id, org_id, "device_down")
closed =
insert_alert!(device.id, org_id, "device_down", resolved_at: DateTime.truncate(DateTime.utc_now(), :second))
assert [%{id: id1}] = AlertQuery.base() |> AlertQuery.unresolved() |> Repo.all()
assert id1 == open.id
assert [%{id: id2}] = AlertQuery.base() |> AlertQuery.resolved() |> Repo.all()
assert id2 == closed.id
end
end
describe "with_severity/2" do
test "filters by severity", %{device: device, organization_id: org_id} do
crit = insert_alert!(device.id, org_id, "device_down", severity: 1)
_warn = insert_alert!(device.id, org_id, "device_down", severity: 2)
[result] = AlertQuery.base() |> AlertQuery.with_severity(1) |> Repo.all()
assert result.id == crit.id
end
end
describe "default-arg one-arity helpers" do
test "for_organization/1 starts from base()", %{device: device, organization_id: org_id} do
keep = insert_alert!(device.id, org_id, "device_down")
[result] = org_id |> AlertQuery.for_organization() |> Repo.all()
assert result.id == keep.id
end
test "for_device/1 starts from base()", %{device: device, organization_id: org_id} do
keep = insert_alert!(device.id, org_id, "device_down")
[result] = device.id |> AlertQuery.for_device() |> Repo.all()
assert result.id == keep.id
end
test "of_type/1 starts from base()", %{device: device, organization_id: org_id} do
down = insert_alert!(device.id, org_id, "device_down")
_up = insert_alert!(device.id, org_id, "device_up")
[result] = "device_down" |> AlertQuery.of_type() |> Repo.all()
assert result.id == down.id
end
test "unresolved/0 starts from base()", %{device: device, organization_id: org_id} do
open = insert_alert!(device.id, org_id, "device_down")
_closed =
insert_alert!(device.id, org_id, "device_down", resolved_at: DateTime.truncate(DateTime.utc_now(), :second))
[result] = Repo.all(AlertQuery.unresolved())
assert result.id == open.id
end
test "resolved/0 starts from base()", %{device: device, organization_id: org_id} do
_open = insert_alert!(device.id, org_id, "device_down")
closed =
insert_alert!(device.id, org_id, "device_down", resolved_at: DateTime.truncate(DateTime.utc_now(), :second))
[result] = Repo.all(AlertQuery.resolved())
assert result.id == closed.id
end
test "with_severity/1 starts from base()", %{device: device, organization_id: org_id} do
crit = insert_alert!(device.id, org_id, "device_down", severity: 1)
_warn = insert_alert!(device.id, org_id, "device_down", severity: 2)
[result] = 1 |> AlertQuery.with_severity() |> Repo.all()
assert result.id == crit.id
end
end
describe "property: composed filters always return a subset" do
property "organization + type + unresolved is always a subset of for_organization", %{
device: device,
organization_id: org_id
} do
_ = insert_alert!(device.id, org_id, "device_down")
_ = insert_alert!(device.id, org_id, "device_up")
_ = insert_alert!(device.id, org_id, "device_down", resolved_at: DateTime.truncate(DateTime.utc_now(), :second))
check all(alert_type <- StreamData.member_of(["device_down", "device_up", "check_ping"]), max_runs: 25) do
all_for_org_ids =
AlertQuery.base()
|> AlertQuery.for_organization(org_id)
|> Repo.all()
|> MapSet.new(& &1.id)
narrowed_ids =
AlertQuery.base()
|> AlertQuery.for_organization(org_id)
|> AlertQuery.of_type(alert_type)
|> AlertQuery.unresolved()
|> Repo.all()
|> MapSet.new(& &1.id)
assert MapSet.subset?(narrowed_ids, all_for_org_ids)
end
end
end
defp insert_alert!(device_id, organization_id, alert_type, extra \\ []) do
attrs =
Enum.into(extra, %{
device_id: device_id,
organization_id: organization_id,
alert_type: alert_type,
triggered_at: DateTime.truncate(DateTime.utc_now(), :second)
})
%Alert{}
|> Alert.changeset(attrs)
|> Repo.insert!()
end
end