diff --git a/test/towerops/job_monitoring_test.exs b/test/towerops/job_monitoring_test.exs index c2ad6169..7bc72479 100644 --- a/test/towerops/job_monitoring_test.exs +++ b/test/towerops/job_monitoring_test.exs @@ -119,5 +119,87 @@ defmodule Towerops.JobMonitoringTest do assert length(stuck) == 1 assert hd(stuck).id == stuck_job.id end + + test "returns stuck jobs from both worker types ordered by attempted_at" do + device = device_fixture() + + # Stuck polling job (3 minutes ago) + stuck_poller = oban_job_fixture(%{ + worker: "Towerops.Workers.DevicePollerWorker", + state: "executing", + args: %{"device_id" => device.id}, + attempted_at: DateTime.add(DateTime.utc_now(), -180, :second) + }) + + # Stuck discovery job (6 minutes ago - older, should be first) + stuck_discovery = oban_job_fixture(%{ + worker: "Towerops.Workers.DiscoveryWorker", + state: "executing", + args: %{"device_id" => device.id}, + attempted_at: DateTime.add(DateTime.utc_now(), -360, :second) + }) + + # Recent jobs that should not be included + oban_job_fixture(%{ + worker: "Towerops.Workers.DevicePollerWorker", + state: "executing", + args: %{"device_id" => device.id}, + attempted_at: DateTime.add(DateTime.utc_now(), -30, :second) + }) + + stuck = JobMonitoring.list_stuck_jobs() + + assert length(stuck) == 2 + # Verify oldest first (discovery job at -360s) + assert Enum.at(stuck, 0).id == stuck_discovery.id + assert Enum.at(stuck, 1).id == stuck_poller.id + end + + test "excludes jobs just under the threshold boundary" do + device = device_fixture() + + # Polling job at 119 seconds - should NOT be stuck (under 120s threshold) + oban_job_fixture(%{ + worker: "Towerops.Workers.DevicePollerWorker", + state: "executing", + args: %{"device_id" => device.id}, + attempted_at: DateTime.add(DateTime.utc_now(), -119, :second) + }) + + # Discovery job at 299 seconds - should NOT be stuck (under 300s threshold) + oban_job_fixture(%{ + worker: "Towerops.Workers.DiscoveryWorker", + state: "executing", + args: %{"device_id" => device.id}, + attempted_at: DateTime.add(DateTime.utc_now(), -299, :second) + }) + + assert JobMonitoring.list_stuck_jobs() == [] + end + + test "includes jobs just over the threshold" do + device = device_fixture() + + # Polling job at 125 seconds - should be stuck (over 120s threshold) + stuck_poller = oban_job_fixture(%{ + worker: "Towerops.Workers.DevicePollerWorker", + state: "executing", + args: %{"device_id" => device.id}, + attempted_at: DateTime.add(DateTime.utc_now(), -125, :second) + }) + + # Discovery job at 305 seconds - should be stuck (over 300s threshold) + stuck_discovery = oban_job_fixture(%{ + worker: "Towerops.Workers.DiscoveryWorker", + state: "executing", + args: %{"device_id" => device.id}, + attempted_at: DateTime.add(DateTime.utc_now(), -305, :second) + }) + + stuck = JobMonitoring.list_stuck_jobs() + + assert length(stuck) == 2 + assert stuck |> Enum.map(& &1.id) |> Enum.sort() == [stuck_discovery.id, stuck_poller.id] |> Enum.sort() + end end end