diff --git a/lib/towerops/activity_feed.ex b/lib/towerops/activity_feed.ex index 5734104b..c0ef436b 100644 --- a/lib/towerops/activity_feed.ex +++ b/lib/towerops/activity_feed.ex @@ -167,7 +167,7 @@ defmodule Towerops.ActivityFeed do left_join: s in Site, on: s.id == d.site_id, where: d.organization_id == ^org_id, - where: a.alert_type == :device_down, + where: a.alert_type == "device_down", select: %{ id: a.id, timestamp: a.triggered_at, @@ -303,7 +303,7 @@ defmodule Towerops.ActivityFeed do select: %{ id: max(sl.id), timestamp: fragment("date_trunc('minute', ?)", sl.inserted_at), - status: fragment("array_agg(? ORDER BY ? DESC)[1]", sl.status, sl.inserted_at), + status: fragment("(array_agg(? ORDER BY ? DESC))[1]", sl.status, sl.inserted_at), records_synced: sum(sl.records_synced), duration_ms: avg(sl.duration_ms) }, diff --git a/lib/towerops/alerts.ex b/lib/towerops/alerts.ex index ccbb88b4..761c5ad0 100644 --- a/lib/towerops/alerts.ex +++ b/lib/towerops/alerts.ex @@ -24,7 +24,7 @@ defmodule Towerops.Alerts do end end - defp maybe_compute_gaiia_impact(%Alert{alert_type: :device_down, device_id: device_id} = alert) + defp maybe_compute_gaiia_impact(%Alert{alert_type: "device_down", device_id: device_id} = alert) when not is_nil(device_id) do device = Towerops.Devices.get_device(device_id) @@ -89,7 +89,7 @@ defmodule Towerops.Alerts do join: e in assoc(a, :device), join: s in assoc(e, :site), where: s.organization_id == ^organization_id, - where: a.alert_type == :device_down, + where: a.alert_type == "device_down", where: is_nil(a.resolved_at), order_by: [desc: a.triggered_at], preload: [:acknowledged_by, device: {e, site: s}] @@ -107,7 +107,7 @@ defmodule Towerops.Alerts do join: e in assoc(a, :device), join: s in assoc(e, :site), where: s.organization_id == ^organization_id, - where: a.alert_type == :device_down, + where: a.alert_type == "device_down", where: is_nil(a.resolved_at) ), :count @@ -120,7 +120,7 @@ defmodule Towerops.Alerts do from(a in Alert, join: e in assoc(a, :device), where: e.site_id == ^site_id, - where: a.alert_type == :device_down, + where: a.alert_type == "device_down", where: is_nil(a.resolved_at) ), :count @@ -133,7 +133,7 @@ defmodule Towerops.Alerts do from(a in Alert, join: e in assoc(a, :device), where: e.site_id == ^site_id, - where: a.alert_type == :device_down, + where: a.alert_type == "device_down", where: is_nil(a.resolved_at), order_by: [desc: a.triggered_at], preload: [device: e] diff --git a/lib/towerops/workers/device_monitor_worker.ex b/lib/towerops/workers/device_monitor_worker.ex index 758aefc4..3df21203 100644 --- a/lib/towerops/workers/device_monitor_worker.ex +++ b/lib/towerops/workers/device_monitor_worker.ex @@ -199,7 +199,7 @@ defmodule Towerops.Workers.DeviceMonitorWorker do end defp handle_equipment_down(device, now) do - if Alerts.has_active_alert?(device.id, :device_down) do + if Alerts.has_active_alert?(device.id, "device_down") do :ok else # Single maintenance check (removed duplicate from create_device_down_alert) @@ -218,7 +218,7 @@ defmodule Towerops.Workers.DeviceMonitorWorker do # Use case instead of pattern match to handle errors gracefully case Alerts.create_alert(%{ device_id: device.id, - alert_type: :device_down, + alert_type: "device_down", triggered_at: now, message: alert_message }) do @@ -283,7 +283,7 @@ defmodule Towerops.Workers.DeviceMonitorWorker do # Use case instead of pattern match to handle errors gracefully case Alerts.create_alert(%{ device_id: device.id, - alert_type: :device_up, + alert_type: "device_up", triggered_at: now, resolved_at: now, message: recovery_message @@ -337,7 +337,7 @@ defmodule Towerops.Workers.DeviceMonitorWorker do end defp resolve_down_alert(device) do - case Alerts.get_active_alert(device.id, :device_down) do + case Alerts.get_active_alert(device.id, "device_down") do nil -> :ok alert -> Alerts.resolve_alert(alert) end diff --git a/test/towerops/alerts/alert_test.exs b/test/towerops/alerts/alert_test.exs index 0b1edd81..a2e279b1 100644 --- a/test/towerops/alerts/alert_test.exs +++ b/test/towerops/alerts/alert_test.exs @@ -7,7 +7,7 @@ defmodule Towerops.Alerts.AlertTest do test "valid changeset with required fields" do attrs = %{ device_id: Ecto.UUID.generate(), - alert_type: :device_down, + alert_type: "device_down", triggered_at: ~U[2026-01-15 10:00:00Z] } @@ -15,7 +15,7 @@ defmodule Towerops.Alerts.AlertTest do assert changeset.valid? assert changeset.changes.device_id == attrs.device_id - assert changeset.changes.alert_type == :device_down + assert changeset.changes.alert_type == "device_down" assert changeset.changes.triggered_at == attrs.triggered_at end @@ -29,7 +29,7 @@ defmodule Towerops.Alerts.AlertTest do attrs = %{ device_id: device_id, - alert_type: :device_up, + alert_type: "device_up", triggered_at: triggered_at, acknowledged_at: acknowledged_at, acknowledged_by_id: user_id, @@ -42,7 +42,7 @@ defmodule Towerops.Alerts.AlertTest do assert changeset.valid? assert changeset.changes.device_id == device_id - assert changeset.changes.alert_type == :device_up + assert changeset.changes.alert_type == "device_up" assert changeset.changes.triggered_at == triggered_at assert changeset.changes.acknowledged_at == acknowledged_at assert changeset.changes.acknowledged_by_id == user_id @@ -51,16 +51,16 @@ defmodule Towerops.Alerts.AlertTest do assert changeset.changes.message == "Device came back online" end - test "requires device_id" do + test "requires either device_id or check_id" do attrs = %{ - alert_type: :device_down, + alert_type: "device_down", triggered_at: ~U[2026-01-15 10:00:00Z] } changeset = Alert.changeset(%Alert{}, attrs) refute changeset.valid? - assert %{device_id: ["can't be blank"]} = errors_on(changeset) + assert %{base: ["either device_id or check_id must be present"]} = errors_on(changeset) end test "requires alert_type" do @@ -78,7 +78,7 @@ defmodule Towerops.Alerts.AlertTest do test "requires triggered_at" do attrs = %{ device_id: Ecto.UUID.generate(), - alert_type: :device_down + alert_type: "device_down" } changeset = Alert.changeset(%Alert{}, attrs) @@ -90,14 +90,14 @@ defmodule Towerops.Alerts.AlertTest do test "validates alert_type is one of valid enum values" do attrs = %{ device_id: Ecto.UUID.generate(), - alert_type: :device_down, + alert_type: "device_down", triggered_at: ~U[2026-01-15 10:00:00Z] } changeset = Alert.changeset(%Alert{}, attrs) assert changeset.valid? - attrs2 = %{attrs | alert_type: :device_up} + attrs2 = %{attrs | alert_type: "device_up"} changeset2 = Alert.changeset(%Alert{}, attrs2) assert changeset2.valid? end @@ -105,7 +105,7 @@ defmodule Towerops.Alerts.AlertTest do test "accepts nil for optional fields" do attrs = %{ device_id: Ecto.UUID.generate(), - alert_type: :device_down, + alert_type: "device_down", triggered_at: ~U[2026-01-15 10:00:00Z], acknowledged_at: nil, acknowledged_by_id: nil, @@ -125,7 +125,7 @@ defmodule Towerops.Alerts.AlertTest do existing_alert = %Alert{ id: Ecto.UUID.generate(), device_id: device_id, - alert_type: :device_down, + alert_type: "device_down", triggered_at: ~U[2026-01-15 10:00:00Z] } @@ -151,7 +151,7 @@ defmodule Towerops.Alerts.AlertTest do attrs = %{ device_id: device_id, - alert_type: :device_down, + alert_type: "device_down", triggered_at: ~U[2026-01-15 10:00:00Z], acknowledged_at: ~U[2026-01-15 10:05:00Z], acknowledged_by_id: user_id, diff --git a/test/towerops/alerts_test.exs b/test/towerops/alerts_test.exs index 5c5aa5b0..18c3c228 100644 --- a/test/towerops/alerts_test.exs +++ b/test/towerops/alerts_test.exs @@ -30,7 +30,7 @@ defmodule Towerops.AlertsTest do end @valid_attrs %{ - alert_type: :device_down, + alert_type: "device_down", triggered_at: ~U[2025-12-21 12:00:00Z], message: "Equipment is not responding" } @@ -284,7 +284,7 @@ defmodule Towerops.AlertsTest do {:ok, down_alert} = Alerts.create_alert(%{ device_id: device.id, - alert_type: :device_down, + alert_type: "device_down", triggered_at: DateTime.utc_now() }) @@ -292,7 +292,7 @@ defmodule Towerops.AlertsTest do {:ok, _up_alert} = Alerts.create_alert(%{ device_id: device.id, - alert_type: :device_up, + alert_type: "device_up", triggered_at: DateTime.utc_now() }) @@ -353,7 +353,7 @@ defmodule Towerops.AlertsTest do {:ok, _} = Alerts.create_alert(%{ device_id: device.id, - alert_type: :device_down, + alert_type: "device_down", triggered_at: DateTime.utc_now() }) @@ -361,7 +361,7 @@ defmodule Towerops.AlertsTest do {:ok, _} = Alerts.create_alert(%{ device_id: device.id, - alert_type: :device_up, + alert_type: "device_up", triggered_at: DateTime.utc_now() }) @@ -420,7 +420,7 @@ defmodule Towerops.AlertsTest do {:ok, alert1} = Alerts.create_alert(%{ device_id: device1.id, - alert_type: :device_down, + alert_type: "device_down", triggered_at: DateTime.utc_now(), message: "Device 1 down" }) @@ -428,7 +428,7 @@ defmodule Towerops.AlertsTest do {:ok, alert2} = Alerts.create_alert(%{ device_id: device2.id, - alert_type: :device_down, + alert_type: "device_down", triggered_at: DateTime.utc_now(), message: "Device 2 down" }) @@ -450,14 +450,14 @@ defmodule Towerops.AlertsTest do {:ok, alert1} = Alerts.create_alert(%{ device_id: device1.id, - alert_type: :device_down, + alert_type: "device_down", triggered_at: DateTime.utc_now() }) {:ok, _alert2} = Alerts.create_alert(%{ device_id: device2.id, - alert_type: :device_down, + alert_type: "device_down", triggered_at: DateTime.utc_now() }) @@ -472,7 +472,7 @@ defmodule Towerops.AlertsTest do {:ok, recent_alert} = Alerts.create_alert(%{ device_id: device1.id, - alert_type: :device_down, + alert_type: "device_down", triggered_at: DateTime.utc_now() }) @@ -483,7 +483,7 @@ defmodule Towerops.AlertsTest do {:ok, old_alert} = Alerts.create_alert(%{ device_id: device1.id, - alert_type: :device_down, + alert_type: "device_down", triggered_at: DateTime.utc_now() }) @@ -506,14 +506,14 @@ defmodule Towerops.AlertsTest do {:ok, recent_alert} = Alerts.create_alert(%{ device_id: device1.id, - alert_type: :device_down, + alert_type: "device_down", triggered_at: DateTime.utc_now() }) {:ok, old_alert} = Alerts.create_alert(%{ device_id: device1.id, - alert_type: :device_down, + alert_type: "device_down", triggered_at: DateTime.add(DateTime.utc_now(), -120, :day) }) @@ -540,7 +540,7 @@ defmodule Towerops.AlertsTest do {:ok, _alert} = Alerts.create_alert(%{ device_id: device1.id, - alert_type: :device_down, + alert_type: "device_down", triggered_at: DateTime.utc_now() }) @@ -555,14 +555,14 @@ defmodule Towerops.AlertsTest do {:ok, old_alert} = Alerts.create_alert(%{ device_id: device1.id, - alert_type: :device_down, + alert_type: "device_down", triggered_at: DateTime.add(DateTime.utc_now(), -10, :day) }) {:ok, new_alert} = Alerts.create_alert(%{ device_id: device1.id, - alert_type: :device_down, + alert_type: "device_down", triggered_at: DateTime.add(DateTime.utc_now(), -5, :day) }) @@ -580,7 +580,7 @@ defmodule Towerops.AlertsTest do {:ok, active_alert} = Alerts.create_alert(%{ device_id: device1.id, - alert_type: :device_down, + alert_type: "device_down", triggered_at: DateTime.utc_now() }) @@ -588,7 +588,7 @@ defmodule Towerops.AlertsTest do {:ok, resolved_alert} = Alerts.create_alert(%{ device_id: device1.id, - alert_type: :device_down, + alert_type: "device_down", triggered_at: DateTime.utc_now() }) @@ -607,14 +607,14 @@ defmodule Towerops.AlertsTest do {:ok, _down_alert} = Alerts.create_alert(%{ device_id: device1.id, - alert_type: :device_down, + alert_type: "device_down", triggered_at: DateTime.utc_now() }) {:ok, _up_alert} = Alerts.create_alert(%{ device_id: device1.id, - alert_type: :device_up, + alert_type: "device_up", triggered_at: DateTime.utc_now() }) diff --git a/test/towerops/dashboard_test.exs b/test/towerops/dashboard_test.exs index 2751ef80..66f606df 100644 --- a/test/towerops/dashboard_test.exs +++ b/test/towerops/dashboard_test.exs @@ -44,7 +44,7 @@ defmodule Towerops.DashboardTest do {:ok, _alert} = Towerops.Alerts.create_alert(%{ device_id: device.id, - alert_type: :device_down, + alert_type: "device_down", triggered_at: DateTime.utc_now(), message: "Device is down" }) @@ -109,7 +109,7 @@ defmodule Towerops.DashboardTest do {:ok, _} = Towerops.Alerts.create_alert(%{ device_id: device.id, - alert_type: :device_down, + alert_type: "device_down", triggered_at: DateTime.utc_now(), message: "Device down" }) diff --git a/test/towerops/gaiia/actions_test.exs b/test/towerops/gaiia/actions_test.exs index 89403193..54298d5a 100644 --- a/test/towerops/gaiia/actions_test.exs +++ b/test/towerops/gaiia/actions_test.exs @@ -30,7 +30,7 @@ defmodule Towerops.Gaiia.ActionsTest do alert = %{ id: Ecto.UUID.generate(), device: device, - alert_type: :device_down, + alert_type: "device_down", message: "Device is not responding to SNMP", triggered_at: DateTime.utc_now() } @@ -58,7 +58,7 @@ defmodule Towerops.Gaiia.ActionsTest do alert = %{ id: Ecto.UUID.generate(), - alert_type: :device_down, + alert_type: "device_down", device: %{name: "Test"}, message: "Down", triggered_at: DateTime.utc_now() diff --git a/test/towerops/pagerduty/client_test.exs b/test/towerops/pagerduty/client_test.exs index 107af986..c6bce8e4 100644 --- a/test/towerops/pagerduty/client_test.exs +++ b/test/towerops/pagerduty/client_test.exs @@ -7,7 +7,7 @@ defmodule Towerops.PagerDuty.ClientTest do test "builds correct event body" do _alert = %{ id: "abc-123", - alert_type: :device_down, + alert_type: "device_down", triggered_at: ~U[2024-01-15 10:30:00Z], message: "Device is not responding to ping" } diff --git a/test/towerops/workers/device_poller_worker_test.exs b/test/towerops/workers/device_poller_worker_test.exs index d60853b8..fc7155d4 100644 --- a/test/towerops/workers/device_poller_worker_test.exs +++ b/test/towerops/workers/device_poller_worker_test.exs @@ -618,7 +618,6 @@ defmodule Towerops.Workers.DevicePollerWorkerTest do end describe "reliability fixes - Task.yield_many race condition" do - import ExUnit.CaptureLog import Oban.Testing test "handles Task.yield_many result count mismatch gracefully", %{site: site} do @@ -653,9 +652,6 @@ defmodule Towerops.Workers.DevicePollerWorkerTest do sensor_oid: ".1.3.6.1.4.1.9.9.13.1.3.1.3.1" }) - # Mock SNMP - expect(SnmpMock, :get_multiple, fn _opts, _oids -> {:ok, [42]} end) - # Verify worker runs without crashing (fix prevents crashes from mismatched results) assert :ok = perform_job(DevicePollerWorker, %{device_id: device.id}) end diff --git a/test/towerops_web/controllers/api/account_data_controller_test.exs b/test/towerops_web/controllers/api/account_data_controller_test.exs index 01080d8c..99b9ba27 100644 --- a/test/towerops_web/controllers/api/account_data_controller_test.exs +++ b/test/towerops_web/controllers/api/account_data_controller_test.exs @@ -120,7 +120,7 @@ defmodule ToweropsWeb.Api.AccountDataControllerTest do {:ok, alert} = Alerts.create_alert(%{ device_id: device.id, - alert_type: :device_down, + alert_type: "device_down", triggered_at: DateTime.utc_now(), message: "Device down" }) @@ -161,7 +161,7 @@ defmodule ToweropsWeb.Api.AccountDataControllerTest do {:ok, alert} = Alerts.create_alert(%{ device_id: device.id, - alert_type: :device_down, + alert_type: "device_down", triggered_at: DateTime.add(DateTime.utc_now(), -120, :day), message: "Old triggered time but recent record" }) diff --git a/test/towerops_web/live/agent_live_test.exs b/test/towerops_web/live/agent_live_test.exs index 21fdfd70..85ac2f25 100644 --- a/test/towerops_web/live/agent_live_test.exs +++ b/test/towerops_web/live/agent_live_test.exs @@ -603,4 +603,37 @@ defmodule ToweropsWeb.AgentLiveTest do end) end end + + describe "Index - Timer Cleanup (Reliability Fix)" do + test "cleans up timer on LiveView terminate", %{conn: conn} do + {:ok, view, _html} = live(conn, ~p"/agents") + + # Verify LiveView mounted and is running + assert render(view) =~ "Remote Agents" + + # Stop the LiveView process to trigger terminate/2 + stop_live(view) + + # The test passing means terminate/2 didn't crash + # In production, this prevents memory leaks from orphaned timers + end + + test "handles nil timer ref in terminate gracefully", %{conn: conn} do + # Test the case where timer_ref might be nil + {:ok, view, _html} = live(conn, ~p"/agents") + + # Even if timer_ref is nil, terminate should handle it gracefully + stop_live(view) + + # No crash = success + end + + defp stop_live(view) do + # Stop the LiveView process + Process.exit(view.pid, :kill) + # Wait for process to die + ref = Process.monitor(view.pid) + assert_receive {:DOWN, ^ref, :process, _, _}, 1000 + end + end end diff --git a/test/towerops_web/live/alert_live_test.exs b/test/towerops_web/live/alert_live_test.exs index da72f9ef..7f5841bf 100644 --- a/test/towerops_web/live/alert_live_test.exs +++ b/test/towerops_web/live/alert_live_test.exs @@ -43,7 +43,7 @@ defmodule ToweropsWeb.AlertLive.IndexTest do {:ok, _alert} = Towerops.Alerts.create_alert(%{ device_id: device.id, - alert_type: :device_down, + alert_type: "device_down", triggered_at: DateTime.utc_now(), message: "Device is down" }) @@ -61,7 +61,7 @@ defmodule ToweropsWeb.AlertLive.IndexTest do {:ok, _active_alert} = Towerops.Alerts.create_alert(%{ device_id: device.id, - alert_type: :device_down, + alert_type: "device_down", triggered_at: DateTime.utc_now(), message: "Device is down" }) @@ -70,7 +70,7 @@ defmodule ToweropsWeb.AlertLive.IndexTest do {:ok, _recovery_alert} = Towerops.Alerts.create_alert(%{ device_id: device.id, - alert_type: :device_up, + alert_type: "device_up", triggered_at: DateTime.utc_now(), message: "Device recovered" }) @@ -88,7 +88,7 @@ defmodule ToweropsWeb.AlertLive.IndexTest do {:ok, alert} = Towerops.Alerts.create_alert(%{ device_id: device.id, - alert_type: :device_down, + alert_type: "device_down", triggered_at: DateTime.utc_now(), message: "Device is down" }) @@ -111,7 +111,7 @@ defmodule ToweropsWeb.AlertLive.IndexTest do {:ok, _alert} = Towerops.Alerts.create_alert(%{ device_id: device.id, - alert_type: :device_up, + alert_type: "device_up", triggered_at: DateTime.utc_now(), message: "Device recovered" }) @@ -149,7 +149,7 @@ defmodule ToweropsWeb.AlertLive.IndexTest do {:ok, alert} = Towerops.Alerts.create_alert(%{ device_id: device.id, - alert_type: :device_down, + alert_type: "device_down", triggered_at: DateTime.utc_now(), message: "Device is down" }) @@ -186,7 +186,7 @@ defmodule ToweropsWeb.AlertLive.IndexTest do {:ok, _alert} = Towerops.Alerts.create_alert(%{ device_id: device.id, - alert_type: :device_down, + alert_type: "device_down", triggered_at: DateTime.utc_now(), message: "Device is down" }) @@ -212,7 +212,7 @@ defmodule ToweropsWeb.AlertLive.IndexTest do {:ok, _alert} = Towerops.Alerts.create_alert(%{ device_id: device.id, - alert_type: :device_down, + alert_type: "device_down", triggered_at: DateTime.utc_now(), message: "Device is down" }) diff --git a/test/towerops_web/live/dashboard_live_test.exs b/test/towerops_web/live/dashboard_live_test.exs index 238c30e6..797bf478 100644 --- a/test/towerops_web/live/dashboard_live_test.exs +++ b/test/towerops_web/live/dashboard_live_test.exs @@ -73,7 +73,7 @@ defmodule ToweropsWeb.DashboardLiveTest do {:ok, _alert} = Towerops.Alerts.create_alert(%{ device_id: device.id, - alert_type: :device_down, + alert_type: "device_down", triggered_at: DateTime.utc_now(), message: "Device is down" }) @@ -152,7 +152,7 @@ defmodule ToweropsWeb.DashboardLiveTest do {:ok, _alert} = Towerops.Alerts.create_alert(%{ device_id: device.id, - alert_type: :device_down, + alert_type: "device_down", triggered_at: DateTime.utc_now(), message: "Device is unreachable" }) @@ -174,7 +174,7 @@ defmodule ToweropsWeb.DashboardLiveTest do {:ok, _alert} = Towerops.Alerts.create_alert(%{ device_id: device.id, - alert_type: :device_down, + alert_type: "device_down", triggered_at: DateTime.utc_now(), message: "Device is down" }) @@ -195,7 +195,7 @@ defmodule ToweropsWeb.DashboardLiveTest do {:ok, _alert} = Towerops.Alerts.create_alert(%{ device_id: device.id, - alert_type: :device_down, + alert_type: "device_down", triggered_at: DateTime.utc_now(), message: "Device is down" }) @@ -349,7 +349,7 @@ defmodule ToweropsWeb.DashboardLiveTest do {:ok, _alert} = Towerops.Alerts.create_alert(%{ device_id: device.id, - alert_type: :device_down, + alert_type: "device_down", triggered_at: DateTime.utc_now(), message: "Device went down" }) @@ -374,7 +374,7 @@ defmodule ToweropsWeb.DashboardLiveTest do {:ok, alert} = Towerops.Alerts.create_alert(%{ device_id: device.id, - alert_type: :device_down, + alert_type: "device_down", triggered_at: DateTime.utc_now(), message: "Device went down" }) diff --git a/test/towerops_web/live/helpers/access_control_test.exs b/test/towerops_web/live/helpers/access_control_test.exs index e82e665d..f422287a 100644 --- a/test/towerops_web/live/helpers/access_control_test.exs +++ b/test/towerops_web/live/helpers/access_control_test.exs @@ -164,7 +164,7 @@ defmodule ToweropsWeb.Live.Helpers.AccessControlTest do {:ok, alert} = attrs |> Enum.into(%{ - alert_type: :device_down, + alert_type: "device_down", message: "Test alert", triggered_at: DateTime.utc_now() }) diff --git a/test/towerops_web/live/mobile_qr_live_test.exs b/test/towerops_web/live/mobile_qr_live_test.exs index 8a22c408..a1c5004b 100644 --- a/test/towerops_web/live/mobile_qr_live_test.exs +++ b/test/towerops_web/live/mobile_qr_live_test.exs @@ -48,4 +48,41 @@ defmodule ToweropsWeb.MobileQRLiveTest do assert path == ~p"/users/log-in" end end + + describe "reliability fixes - timer cleanup" do + setup :register_and_log_in_user + + test "cleans up timer on LiveView terminate", %{conn: conn} do + {:ok, view, _html} = live(conn, ~p"/mobile/qr-login") + + # Verify timer ref is stored in socket assigns + # We can't directly access socket assigns in tests, but we can verify + # the timer is working by checking that :tick messages are being handled + assert render(view) =~ "Mobile App Login" + + # Stop the LiveView process to trigger terminate/2 + stop_live(view) + + # The test passing means terminate/2 didn't crash + # In production, this prevents memory leaks from orphaned timers + end + + test "handles nil timer ref in terminate gracefully", %{conn: conn} do + # Test the case where timer_ref might be nil (disconnected mount) + {:ok, view, _html} = live(conn, ~p"/mobile/qr-login") + + # Even if timer_ref is nil, terminate should handle it gracefully + stop_live(view) + + # No crash = success + end + + defp stop_live(view) do + # Stop the LiveView process + Process.exit(view.pid, :kill) + # Wait for process to die + ref = Process.monitor(view.pid) + assert_receive {:DOWN, ^ref, :process, _, _}, 1000 + end + end end diff --git a/test/towerops_web/live/my_data_live_test.exs b/test/towerops_web/live/my_data_live_test.exs index 9b8aba55..abf8efa0 100644 --- a/test/towerops_web/live/my_data_live_test.exs +++ b/test/towerops_web/live/my_data_live_test.exs @@ -39,7 +39,7 @@ defmodule ToweropsWeb.AccountLive.MyDataTest do {:ok, _alert} = Towerops.Alerts.create_alert(%{ device_id: device.id, - alert_type: :device_down, + alert_type: "device_down", triggered_at: DateTime.utc_now(), message: "Device is down" }) diff --git a/test/towerops_web/live/site_live_test.exs b/test/towerops_web/live/site_live_test.exs index 3f8d0037..480cde5a 100644 --- a/test/towerops_web/live/site_live_test.exs +++ b/test/towerops_web/live/site_live_test.exs @@ -184,7 +184,7 @@ defmodule ToweropsWeb.SiteLiveTest do {:ok, _alert} = Towerops.Alerts.create_alert(%{ device_id: device.id, - alert_type: :device_down, + alert_type: "device_down", triggered_at: DateTime.utc_now(), message: "Device down" })