towerops/test/towerops/maintenance_context_test.exs
Graham McIntire 3ca0834ef0 tests: raise coverage to 70% via helper promotion + new unit/property tests
Promoted pure presentation and utility helpers from `defp` to `def @doc false`
across ~20 LiveViews, Oban workers, and sync modules so they're reachable from
unit tests. Refactored several `cond` blocks into idiomatic function heads with
guards. Added ~250 new test cases in new files under test/towerops and
test/towerops_web, including DB-backed tests for CnMaestro.Sync and
AlertNotificationWorker, and removed dead LiveView tab components and
CapacityLive (no callers anywhere in lib/test).

Configured mix.exs test_coverage.ignore_modules to exclude vendored third-party
code (SnmpKit, protobuf-generated Towerops.Agent.*, Absinthe GraphQL types,
Phoenix HTML modules, Inspect protocol impls) from coverage calculations —
these are not our project code.

Coverage: 66.93% → 70.09%. Full suite: 10,127 tests, 0 failures.
2026-04-24 09:49:06 -05:00

275 lines
8.5 KiB
Elixir

defmodule Towerops.MaintenanceContextTest do
use Towerops.DataCase, async: true
import Towerops.AccountsFixtures
alias Towerops.Maintenance
setup do
user = user_fixture()
org = Towerops.OrganizationsFixtures.organization_fixture(user.id)
{:ok, site} =
Towerops.Sites.create_site(%{
name: "Maint Site #{System.unique_integer([:positive])}",
organization_id: org.id
})
device =
Towerops.DevicesFixtures.device_fixture(%{
organization_id: org.id,
site_id: site.id,
name: "Maint Device",
ip_address: "10.99.0.1"
})
%{org: org, site: site, device: device, user: user}
end
defp create_window!(attrs) do
user_id = Map.get(attrs, :created_by_id) || (attrs[:user] && attrs.user.id)
attrs = Map.delete(attrs, :user)
attrs =
if user_id do
Map.put(attrs, :created_by_id, user_id)
else
Map.put_new(attrs, :created_by_id, user_fixture().id)
end
{:ok, w} =
Maintenance.create_window(
Map.merge(
%{
name: "Routine",
starts_at: DateTime.add(DateTime.utc_now(), -3600, :second),
ends_at: DateTime.add(DateTime.utc_now(), 3600, :second),
suppress_alerts: true
},
attrs
)
)
w
end
describe "CRUD" do
test "create/update/delete/get cycle", %{org: org} do
w =
create_window!(%{
organization_id: org.id,
name: "Planned"
})
got = Maintenance.get_window!(w.id)
assert got.name == "Planned"
{:ok, updated} = Maintenance.update_window(got, %{name: "Renamed"})
assert updated.name == "Renamed"
assert {:ok, _} = Maintenance.delete_window(updated)
end
end
describe "list_windows/2 filtering" do
setup %{org: org} do
now = DateTime.utc_now()
active =
create_window!(%{
organization_id: org.id,
name: "Active",
starts_at: DateTime.add(now, -3600, :second),
ends_at: DateTime.add(now, 3600, :second)
})
upcoming =
create_window!(%{
organization_id: org.id,
name: "Upcoming",
starts_at: DateTime.add(now, 3600, :second),
ends_at: DateTime.add(now, 7200, :second)
})
past =
create_window!(%{
organization_id: org.id,
name: "Past",
starts_at: DateTime.add(now, -7200, :second),
ends_at: DateTime.add(now, -3600, :second)
})
%{active: active, upcoming: upcoming, past: past}
end
test "no filter returns all", %{org: org} do
results = Maintenance.list_windows(org.id)
assert length(results) == 3
end
test "filter: :active", %{org: org, active: active} do
[got] = Maintenance.list_windows(org.id, filter: :active)
assert got.id == active.id
end
test "filter: :upcoming", %{org: org, upcoming: upcoming} do
[got] = Maintenance.list_windows(org.id, filter: :upcoming)
assert got.id == upcoming.id
end
test "filter: :past", %{org: org, past: past} do
[got] = Maintenance.list_windows(org.id, filter: :past)
assert got.id == past.id
end
end
describe "device_in_maintenance?/1" do
test "direct device window", %{org: org, device: device} do
create_window!(%{organization_id: org.id, device_id: device.id})
assert Maintenance.device_in_maintenance?(device.id)
end
test "site window applies", %{org: org, site: site, device: device} do
create_window!(%{organization_id: org.id, site_id: site.id})
assert Maintenance.device_in_maintenance?(device.id)
end
test "org-wide window applies", %{org: org, device: device} do
create_window!(%{organization_id: org.id})
assert Maintenance.device_in_maintenance?(device.id)
end
test "false with no active windows", %{device: device} do
refute Maintenance.device_in_maintenance?(device.id)
end
test "past windows don't count", %{org: org, device: device} do
now = DateTime.utc_now()
create_window!(%{
organization_id: org.id,
device_id: device.id,
starts_at: DateTime.add(now, -7200, :second),
ends_at: DateTime.add(now, -3600, :second)
})
refute Maintenance.device_in_maintenance?(device.id)
end
test "suppress_alerts: false doesn't count", %{org: org, device: device} do
create_window!(%{organization_id: org.id, device_id: device.id, suppress_alerts: false})
refute Maintenance.device_in_maintenance?(device.id)
end
end
describe "site_in_maintenance?/1" do
test "site-scoped window only", %{org: org, site: site} do
create_window!(%{organization_id: org.id, site_id: site.id})
assert Maintenance.site_in_maintenance?(site.id)
end
test "device-scoped window doesn't make site in maintenance", %{org: org, site: site, device: device} do
create_window!(%{organization_id: org.id, site_id: site.id, device_id: device.id})
refute Maintenance.site_in_maintenance?(site.id)
end
test "returns false with no matching windows", %{site: site} do
refute Maintenance.site_in_maintenance?(site.id)
end
end
describe "active_windows_for_device/1" do
test "de-dupes across direct/site/org matches", %{org: org, site: site, device: device} do
create_window!(%{organization_id: org.id, device_id: device.id, name: "d"})
create_window!(%{organization_id: org.id, site_id: site.id, name: "s"})
create_window!(%{organization_id: org.id, name: "o"})
results = Maintenance.active_windows_for_device(device.id)
assert length(results) == 3
assert length(Enum.uniq_by(results, & &1.id)) == length(results)
end
test "returns empty list for device with no windows", %{device: device} do
assert [] == Maintenance.active_windows_for_device(device.id)
end
end
describe "active_windows_for_org/1" do
test "returns active windows with preloads", %{org: org} do
create_window!(%{organization_id: org.id, name: "Active"})
past = DateTime.add(DateTime.utc_now(), -7200, :second)
create_window!(%{
organization_id: org.id,
name: "Past",
starts_at: past,
ends_at: DateTime.add(past, 3600, :second)
})
results = Maintenance.active_windows_for_org(org.id)
assert length(results) == 1
assert Enum.all?(results, fn w -> w.name == "Active" end)
end
end
describe "maintenance_badge/1" do
test "returns name for device in maintenance", %{org: org, device: device} do
create_window!(%{organization_id: org.id, device_id: device.id, name: "Badge"})
assert "Badge" == Maintenance.maintenance_badge(device)
end
test "returns nil for device not in maintenance", %{device: device} do
assert nil == Maintenance.maintenance_badge(device)
end
test "returns name for site in maintenance", %{org: org, site: site} do
create_window!(%{organization_id: org.id, site_id: site.id, name: "SiteBadge"})
assert "SiteBadge" == Maintenance.maintenance_badge(site)
end
test "returns nil for site without maintenance", %{site: site} do
assert nil == Maintenance.maintenance_badge(site)
end
test "returns nil for other types" do
assert nil == Maintenance.maintenance_badge(nil)
assert nil == Maintenance.maintenance_badge(%{})
assert nil == Maintenance.maintenance_badge("foo")
end
end
describe "devices_in_maintenance/1 batch" do
test "empty list returns empty MapSet" do
assert MapSet.new() == Maintenance.devices_in_maintenance([])
end
test "identifies direct + site + org scopes in single batch", %{org: org, site: site, device: device} do
# Create another device that's only org-covered
other_device =
Towerops.DevicesFixtures.device_fixture(%{
organization_id: org.id,
site_id: site.id,
name: "OtherDev",
ip_address: "10.99.0.2"
})
create_window!(%{organization_id: org.id, name: "org wide"})
result = Maintenance.devices_in_maintenance([device.id, other_device.id])
assert MapSet.member?(result, device.id)
assert MapSet.member?(result, other_device.id)
end
test "omits devices not in maintenance", %{org: org, site: site} do
device =
Towerops.DevicesFixtures.device_fixture(%{
organization_id: org.id,
site_id: site.id,
ip_address: "10.88.0.1"
})
refute MapSet.member?(Maintenance.devices_in_maintenance([device.id]), device.id)
end
end
end