towerops/lib/towerops_web/live/maintenance_live/form.ex
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

120 lines
3.6 KiB
Elixir

defmodule ToweropsWeb.MaintenanceLive.Form do
@moduledoc false
use ToweropsWeb, :live_view
alias Towerops.Maintenance
alias Towerops.Maintenance.MaintenanceWindow
@impl true
def mount(_params, _session, socket) do
org_id = socket.assigns.current_scope.organization.id
sites = Towerops.Sites.list_organization_sites(org_id)
devices = Towerops.Devices.list_organization_devices(org_id)
{:ok,
socket
|> assign(:sites, sites)
|> assign(:devices, devices)
|> assign(:timezone, socket.assigns.current_scope.timezone)}
end
@impl true
def handle_params(params, _url, socket) do
case socket.assigns.live_action do
:new ->
window = %MaintenanceWindow{suppress_alerts: true, recurring: false}
changeset = MaintenanceWindow.changeset(window, %{})
{:noreply,
socket
|> assign(:page_title, t("New Maintenance Window"))
|> assign(:window, window)
|> assign(:form, to_form(changeset))
|> assign(:scope_type, "org")}
:edit ->
window = Maintenance.get_window!(params["id"])
scope_type =
cond do
window.device_id -> "device"
window.site_id -> "site"
true -> "org"
end
changeset = MaintenanceWindow.changeset(window, %{})
{:noreply,
socket
|> assign(:page_title, t("Edit Maintenance Window"))
|> assign(:window, window)
|> assign(:form, to_form(changeset))
|> assign(:scope_type, scope_type)}
end
end
@impl true
def handle_event("validate", %{"maintenance_window" => params}, socket) do
changeset =
socket.assigns.window
|> MaintenanceWindow.changeset(params)
|> Map.put(:action, :validate)
{:noreply, assign(socket, :form, to_form(changeset))}
end
def handle_event("change_scope", %{"scope_type" => scope_type}, socket) do
{:noreply, assign(socket, :scope_type, scope_type)}
end
def handle_event("save", %{"maintenance_window" => params}, socket) do
scope = socket.assigns.current_scope
params =
params
|> Map.put("organization_id", scope.organization.id)
|> Map.put("created_by_id", scope.user.id)
|> maybe_clear_scope(socket.assigns.scope_type)
case socket.assigns.live_action do
:new -> save_window(socket, :create, params)
:edit -> save_window(socket, :update, params)
end
end
@doc false
def maybe_clear_scope(params, "org"), do: Map.merge(params, %{"site_id" => nil, "device_id" => nil})
def maybe_clear_scope(params, "site"), do: Map.put(params, "device_id", nil)
def maybe_clear_scope(params, _), do: params
@doc false
def format_datetime_local(nil), do: ""
def format_datetime_local(%DateTime{} = dt), do: Calendar.strftime(dt, "%Y-%m-%dT%H:%M")
def format_datetime_local(_), do: ""
defp save_window(socket, :create, params) do
case Maintenance.create_window(params) do
{:ok, window} ->
{:noreply,
socket
|> put_flash(:info, t("Maintenance window created"))
|> push_navigate(to: ~p"/maintenance/#{window.id}")}
{:error, changeset} ->
{:noreply, assign(socket, :form, to_form(changeset))}
end
end
defp save_window(socket, :update, params) do
case Maintenance.update_window(socket.assigns.window, params) do
{:ok, window} ->
{:noreply,
socket
|> put_flash(:info, t("Maintenance window updated"))
|> push_navigate(to: ~p"/maintenance/#{window.id}")}
{:error, changeset} ->
{:noreply, assign(socket, :form, to_form(changeset))}
end
end
end