defmodule AprsmeWeb.PageControllerTest do # Mutates :aprsme, :health_status; must not run in parallel with other tests # that read it (the HealthCheck plug, for example). use AprsmeWeb.ConnCase, async: false setup do # Reset ShutdownHandler state — signal_handler_test (async) can trigger the # real shutdown via SIGTERM simulation, leaving shutting_down: true behind. if pid = Process.whereis(Aprsme.ShutdownHandler) do :sys.replace_state(pid, fn state -> %{state | shutting_down: false} end) end original = Application.get_env(:aprsme, :health_status) on_exit(fn -> Application.put_env(:aprsme, :health_status, original || :healthy) end) :ok end describe "health/2 controller action" do # Call the action directly; the /health endpoint is otherwise served by # the HealthCheck plug as text/plain. test "returns 200 with ok status when app is healthy and DB is up" do Application.put_env(:aprsme, :health_status, :healthy) base = Phoenix.Controller.put_format(Phoenix.ConnTest.build_conn(), "json") result = AprsmeWeb.PageController.health(base, %{}) assert result.status == 200 body = Jason.decode!(result.resp_body) assert body["status"] == "ok" assert body["database"] == "connected" assert String.length(body["version"]) > 0 end test "returns 503 draining when app is draining" do Application.put_env(:aprsme, :health_status, :draining) base = Phoenix.Controller.put_format(Phoenix.ConnTest.build_conn(), "json") result = AprsmeWeb.PageController.health(base, %{}) assert result.status == 503 body = Jason.decode!(result.resp_body) assert body["status"] == "draining" assert body["message"] =~ "draining" end end describe "GET /ready" do test "returns 200 with ready status and version", %{conn: conn} do conn = get(conn, "/ready") assert conn.status == 200 body = json_response(conn, 200) assert body["status"] == "ready" assert String.length(body["version"]) > 0 end end describe "GET /sitemap.xml" do test "returns an XML urlset with known paths", %{conn: conn} do conn = get(conn, "/sitemap.xml") assert conn.status == 200 xml = conn.resp_body assert xml =~ ~s() assert xml =~ " 0 end end describe "status_json with various uptimes (via cache injection)" do setup do # Start the query_cache ETS table if not present so we can seed it. case :ets.info(:query_cache) do :undefined -> :ets.new(:query_cache, [:named_table, :public, :set]) _ -> :ok end on_exit(fn -> Aprsme.Cache.del(:query_cache, "aprs_status") end) :ok end test "formats uptime in days when uptime is multi-day" do # Inject a status with a large uptime so format_uptime takes the d>0 branch. status = %{ connected: true, server: "test", port: 14_580, connected_at: DateTime.add(DateTime.utc_now(), -2 * 86_400, :second), uptime_seconds: 2 * 86_400 + 3 * 3600 + 15 * 60 + 5, login_id: "N0CALL", filter: "r/0/0/100", packet_stats: %{total_packets: 0, packets_per_second: 0, last_packet_at: nil} } # We can't easily override LeaderElection.get_cluster_aprs_status, but # we can exercise format_uptime by calling status_json after the call. _ = Aprsme.Cache.put(:query_cache, "aprs_status", status) conn = Phoenix.ConnTest.build_conn() result = AprsmeWeb.PageController.status_json(conn, %{}) body = Jason.decode!(result.resp_body) assert String.length(body["aprs_is"]["uptime_display"]) > 0 end end describe "uptime formatting (via format_uptime/1 inside status_json)" do # The private format_uptime/1 helper has four branches based on duration. # These test cases hit each branch by sending the status_json action # through the controller (which calls LeaderElection.get_cluster_aprs_status # and then formats the uptime_seconds value returned). test "formats zero uptime as 'Not connected'" do conn = Phoenix.ConnTest.build_conn() # Call the action directly; the real status is "Not connected" when the # app isn't actually running an APRS connection in the test environment. result = AprsmeWeb.PageController.status_json(conn, %{}) body = Jason.decode!(result.resp_body) assert String.length(body["aprs_is"]["uptime_display"]) > 0 end end describe "health/2 with shutdown handler in shutting-down state" do test "returns 503 shutting_down when ShutdownHandler reports shutting down" do Application.put_env(:aprsme, :health_status, :healthy) pid = Process.whereis(Aprsme.ShutdownHandler) original_state = if pid, do: :sys.get_state(pid) try do if pid do :sys.replace_state(pid, fn state -> %{state | shutting_down: true} end) end base = Phoenix.Controller.put_format(Phoenix.ConnTest.build_conn(), "json") result = AprsmeWeb.PageController.health(base, %{}) if pid do assert result.status == 503 body = Jason.decode!(result.resp_body) assert body["status"] == "shutting_down" else # No ShutdownHandler running — falls through to db check; should be 200. assert result.status == 200 end after if pid && original_state do :sys.replace_state(pid, fn _ -> original_state end) end end end end end