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.
275 lines
8.5 KiB
Elixir
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
|