199 lines
6 KiB
Elixir
199 lines
6 KiB
Elixir
defmodule ToweropsWeb.Admin.MonitoringLiveTest do
|
|
use ToweropsWeb.ConnCase
|
|
|
|
import Phoenix.LiveViewTest
|
|
import Towerops.DevicesFixtures
|
|
import Towerops.JobsFixtures
|
|
|
|
describe "MonitoringLive" do
|
|
setup [:register_and_log_in_superuser]
|
|
|
|
test "renders monitoring dashboard", %{conn: conn} do
|
|
{:ok, _view, html} = live(conn, ~p"/admin/monitoring")
|
|
|
|
assert html =~ "Job Monitoring Dashboard"
|
|
assert html =~ "Active Operations"
|
|
assert html =~ "Problems"
|
|
assert html =~ "Health Metrics"
|
|
end
|
|
|
|
test "subscribes to job lifecycle events", %{conn: conn} do
|
|
{:ok, view, _html} = live(conn, ~p"/admin/monitoring")
|
|
|
|
# Verify PubSub subscription by checking it's alive
|
|
assert Process.alive?(view.pid)
|
|
end
|
|
|
|
test "displays active operations section", %{conn: conn} do
|
|
{:ok, _view, html} = live(conn, ~p"/admin/monitoring")
|
|
|
|
assert html =~ "Active Operations"
|
|
assert html =~ "No jobs currently executing"
|
|
end
|
|
end
|
|
|
|
describe "real-time updates" do
|
|
setup [:register_and_log_in_superuser]
|
|
|
|
test "displays executing jobs from database", %{conn: conn} do
|
|
device = device_fixture()
|
|
|
|
_job =
|
|
oban_job_fixture(%{
|
|
worker: "Towerops.Workers.DevicePollerWorker",
|
|
state: "executing",
|
|
args: %{"device_id" => device.id},
|
|
attempted_at: DateTime.utc_now()
|
|
})
|
|
|
|
{:ok, _view, html} = live(conn, ~p"/admin/monitoring")
|
|
|
|
# Should show the device name in executing jobs
|
|
assert html =~ device.name
|
|
assert html =~ "Active Operations (1)"
|
|
end
|
|
end
|
|
|
|
describe "metrics display" do
|
|
setup [:register_and_log_in_superuser]
|
|
|
|
test "shows current activity metrics", %{conn: conn} do
|
|
device = device_fixture()
|
|
|
|
oban_job_fixture(%{
|
|
worker: "Towerops.Workers.DevicePollerWorker",
|
|
state: "executing",
|
|
args: %{"device_id" => device.id}
|
|
})
|
|
|
|
{:ok, _view, html} = live(conn, ~p"/admin/monitoring")
|
|
|
|
assert html =~ "Executing"
|
|
assert html =~ "1"
|
|
end
|
|
end
|
|
|
|
describe "handle_info events" do
|
|
setup [:register_and_log_in_superuser]
|
|
|
|
test "handles job started event", %{conn: conn} do
|
|
{:ok, view, _html} = live(conn, ~p"/admin/monitoring")
|
|
|
|
send(view.pid, %{event: :started, job_id: 1, worker: "TestWorker"})
|
|
|
|
assert render(view)
|
|
end
|
|
|
|
test "handles job completed event", %{conn: conn} do
|
|
{:ok, view, _html} = live(conn, ~p"/admin/monitoring")
|
|
|
|
send(view.pid, %{event: :completed, job_id: 1, worker: "TestWorker"})
|
|
|
|
assert render(view)
|
|
end
|
|
|
|
test "handles job failed event", %{conn: conn} do
|
|
{:ok, view, _html} = live(conn, ~p"/admin/monitoring")
|
|
|
|
send(view.pid, %{event: :failed, job_id: 1, worker: "TestWorker"})
|
|
|
|
assert render(view)
|
|
end
|
|
|
|
test "handles unknown events gracefully", %{conn: conn} do
|
|
{:ok, view, _html} = live(conn, ~p"/admin/monitoring")
|
|
|
|
send(view.pid, :some_unknown_event)
|
|
|
|
assert render(view)
|
|
end
|
|
end
|
|
|
|
describe "renders helpers via template" do
|
|
setup [:register_and_log_in_superuser]
|
|
|
|
test "renders worker_name + duration_in_words for a stuck poller", %{conn: conn} do
|
|
device = device_fixture()
|
|
|
|
# Poller stuck > 2 minutes triggers the stuck-jobs render path
|
|
five_minutes_ago = DateTime.add(DateTime.utc_now(), -5 * 60, :second)
|
|
|
|
oban_job_fixture(%{
|
|
worker: "Towerops.Workers.DevicePollerWorker",
|
|
state: "executing",
|
|
attempted_at: five_minutes_ago,
|
|
args: %{"device_id" => device.id}
|
|
})
|
|
|
|
{:ok, _view, html} = live(conn, ~p"/admin/monitoring")
|
|
assert html =~ "Stuck Jobs"
|
|
# worker_name/1 maps the FQ worker to "Device Poll"
|
|
assert html =~ "Device Poll"
|
|
# duration_in_words/1 emits a "5m" or "Xm" string
|
|
assert html =~ ~r/Running for \d+m/
|
|
end
|
|
|
|
test "renders event_outcome / event_border_color for completed jobs in recent activity", %{
|
|
conn: conn
|
|
} do
|
|
device = device_fixture()
|
|
|
|
oban_job_fixture(%{
|
|
worker: "Towerops.Workers.DevicePollerWorker",
|
|
state: "completed",
|
|
attempted_at: DateTime.add(DateTime.utc_now(), -120, :second),
|
|
completed_at: DateTime.utc_now(),
|
|
args: %{"device_id" => device.id}
|
|
})
|
|
|
|
oban_job_fixture(%{
|
|
worker: "Towerops.Workers.DiscoveryWorker",
|
|
state: "cancelled",
|
|
attempted_at: DateTime.add(DateTime.utc_now(), -120, :second),
|
|
completed_at: DateTime.utc_now(),
|
|
args: %{"device_id" => device.id}
|
|
})
|
|
|
|
oban_job_fixture(%{
|
|
worker: "Towerops.Workers.DevicePollerWorker",
|
|
state: "discarded",
|
|
attempted_at: DateTime.add(DateTime.utc_now(), -120, :second),
|
|
completed_at: DateTime.utc_now(),
|
|
args: %{"device_id" => device.id}
|
|
})
|
|
|
|
{:ok, _view, html} = live(conn, ~p"/admin/monitoring")
|
|
# event_outcome/1 strings
|
|
assert html =~ "Completed"
|
|
assert html =~ "Failed"
|
|
assert html =~ "Discarded"
|
|
# event_border_color/1 classes
|
|
assert html =~ "border-green-500"
|
|
assert html =~ "border-red-500"
|
|
assert html =~ "border-gray-400"
|
|
# SNMP Discovery from worker_name fallback
|
|
assert html =~ "SNMP Discovery"
|
|
end
|
|
|
|
test "duration_in_words renders 'h m' for jobs stuck for hours", %{conn: conn} do
|
|
device = device_fixture()
|
|
two_hours_ago = DateTime.add(DateTime.utc_now(), -3 * 3600, :second)
|
|
|
|
oban_job_fixture(%{
|
|
worker: "Towerops.Workers.DiscoveryWorker",
|
|
state: "executing",
|
|
attempted_at: two_hours_ago,
|
|
args: %{"device_id" => device.id}
|
|
})
|
|
|
|
{:ok, _view, html} = live(conn, ~p"/admin/monitoring")
|
|
assert html =~ ~r/Running for \dh \d+m/
|
|
end
|
|
end
|
|
|
|
defp register_and_log_in_superuser(%{conn: conn}) do
|
|
user = Towerops.AccountsFixtures.user_fixture(enable_totp: true)
|
|
user = Towerops.Repo.update!(Ecto.Changeset.change(user, is_superuser: true))
|
|
%{conn: log_in_user(conn, user), user: user}
|
|
end
|
|
end
|