Adds tests covering: - Aprsme.Packet edge cases (already committed) - Encoding valid_codepoint? branches via slow grapheme path and encoding_info shapes - ErrorHandler database/network error categorization (CastError, ConstraintError, QueryError, Postgrex.Error, RuntimeError circuit_open/timeout, custom map :reason) - HealthCheck plug ask_shutting_down? nil and unresponsive-pid paths - SpatialPubSub ensure_float branches, dateline broadcast wrap-around, monitored client DOWN handler, no-coordinate broadcast skip - DataBuilder weather popup, weather-only fallback, tuple value conversion, string weather convert_unit branches - DeviceIdentification fetch_devices_from_url default-arg head - PageController format_uptime branches via fake Aprsme.Is GenServer - WeatherLive.CallsignView compare_timestamps branches via :weather_packet messages - PacketConsumer field-conversion private helpers via crafted handle_events events
214 lines
7.6 KiB
Elixir
214 lines
7.6 KiB
Elixir
defmodule AprsmeWeb.Plugs.HealthCheckTest do
|
|
# :health_status is an app-wide env var the plug reads, so these tests can't
|
|
# safely run in parallel with anything else that might touch it.
|
|
use AprsmeWeb.ConnCase, async: false
|
|
|
|
alias AprsmeWeb.Plugs.HealthCheck
|
|
|
|
setup do
|
|
# Ensure ShutdownHandler isn't lingering in shutting_down: true from prior tests.
|
|
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, :healthy)
|
|
Application.put_env(:aprsme, :health_status, :healthy)
|
|
|
|
on_exit(fn -> Application.put_env(:aprsme, :health_status, original) end)
|
|
:ok
|
|
end
|
|
|
|
describe "call/2 pass-through" do
|
|
test "returns the conn unchanged for non-/health paths", %{conn: conn} do
|
|
conn = %{conn | request_path: "/something-else"}
|
|
result = HealthCheck.call(conn, [])
|
|
assert result == conn
|
|
refute result.halted
|
|
end
|
|
end
|
|
|
|
describe "liveness probe" do
|
|
test "returns 200 OK when healthy", %{conn: conn} do
|
|
conn = %{conn | request_path: "/health"}
|
|
result = HealthCheck.call(conn, probe_type: :liveness)
|
|
assert result.status == 200
|
|
assert result.resp_body == "OK"
|
|
assert result.halted
|
|
end
|
|
|
|
test "returns 200 even when readiness would fail (draining)", %{conn: conn} do
|
|
original = Application.get_env(:aprsme, :health_status, :healthy)
|
|
|
|
try do
|
|
Application.put_env(:aprsme, :health_status, :draining)
|
|
conn = %{conn | request_path: "/health"}
|
|
result = HealthCheck.call(conn, probe_type: :liveness)
|
|
assert result.status == 200
|
|
after
|
|
Application.put_env(:aprsme, :health_status, original)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "readiness probe" do
|
|
test "returns 200 OK under normal conditions", %{conn: conn} do
|
|
conn = %{conn | request_path: "/health"}
|
|
result = HealthCheck.call(conn, probe_type: :readiness)
|
|
# Readiness also hits the DB and PubSub, which should work in test env.
|
|
assert result.status in [200, 503]
|
|
assert result.halted
|
|
end
|
|
|
|
test "returns 503 when health_status is :draining", %{conn: conn} do
|
|
original = Application.get_env(:aprsme, :health_status, :healthy)
|
|
|
|
try do
|
|
Application.put_env(:aprsme, :health_status, :draining)
|
|
conn = %{conn | request_path: "/health"}
|
|
result = HealthCheck.call(conn, probe_type: :readiness)
|
|
assert result.status == 503
|
|
assert result.resp_body =~ "draining"
|
|
after
|
|
Application.put_env(:aprsme, :health_status, original)
|
|
end
|
|
end
|
|
|
|
test "defaults to readiness when no probe_type opt given", %{conn: conn} do
|
|
conn = %{conn | request_path: "/health"}
|
|
result = HealthCheck.call(conn, [])
|
|
assert result.status in [200, 503]
|
|
assert result.halted
|
|
end
|
|
end
|
|
|
|
describe "init/1" do
|
|
test "returns opts unchanged" do
|
|
assert HealthCheck.init(probe_type: :liveness) == [probe_type: :liveness]
|
|
assert HealthCheck.init([]) == []
|
|
end
|
|
end
|
|
|
|
describe "readiness probe when ShutdownHandler is marked shutting down" do
|
|
test "returns 503 when ShutdownHandler reports shutting_down", %{conn: conn} do
|
|
# Start a fake ShutdownHandler that always says "yes, shutting down".
|
|
# Only start if not already running; if running, :sys.replace_state.
|
|
original_shutdown_pid = Process.whereis(Aprsme.ShutdownHandler)
|
|
|
|
# We can't easily replace the real ShutdownHandler without disrupting
|
|
# the app. Instead, exercise the :draining short-circuit via health_status.
|
|
original = Application.get_env(:aprsme, :health_status, :healthy)
|
|
assert is_pid(original_shutdown_pid) or is_nil(original_shutdown_pid)
|
|
|
|
try do
|
|
Application.put_env(:aprsme, :health_status, :draining)
|
|
conn = %{conn | request_path: "/health"}
|
|
result = HealthCheck.call(conn, probe_type: :readiness)
|
|
assert result.status == 503
|
|
after
|
|
Application.put_env(:aprsme, :health_status, original)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "liveness probe edge cases" do
|
|
test "returns 200 when basic_health_checks is :ok", %{conn: conn} do
|
|
conn = %{conn | request_path: "/health"}
|
|
result = HealthCheck.call(conn, probe_type: :liveness)
|
|
assert result.status == 200
|
|
assert result.resp_body =~ "OK"
|
|
end
|
|
end
|
|
|
|
describe "call/2 halted conn short-circuit" do
|
|
test "always halts the conn for /health paths", %{conn: conn} do
|
|
conn = %{conn | request_path: "/health"}
|
|
result = HealthCheck.call(conn, probe_type: :liveness)
|
|
assert result.halted
|
|
end
|
|
end
|
|
|
|
describe "shutting_down? helper paths" do
|
|
test "returns 200 OK when no ShutdownHandler process is registered", %{conn: conn} do
|
|
Application.put_env(:aprsme, :health_status, :healthy)
|
|
|
|
original_pid = Process.whereis(Aprsme.ShutdownHandler)
|
|
|
|
if original_pid do
|
|
:erlang.unregister(Aprsme.ShutdownHandler)
|
|
end
|
|
|
|
try do
|
|
conn = %{conn | request_path: "/health"}
|
|
result = HealthCheck.call(conn, probe_type: :readiness)
|
|
# ask_shutting_down?(nil) → false → readiness_status(_, false) → full_health_checks
|
|
assert result.status == 200
|
|
after
|
|
if original_pid && !Process.whereis(Aprsme.ShutdownHandler) do
|
|
Process.register(original_pid, Aprsme.ShutdownHandler)
|
|
end
|
|
end
|
|
end
|
|
|
|
test "returns 200 OK even when registered ShutdownHandler-named pid is unresponsive", %{conn: conn} do
|
|
Application.put_env(:aprsme, :health_status, :healthy)
|
|
|
|
original_pid = Process.whereis(Aprsme.ShutdownHandler)
|
|
|
|
# Register a dummy task pid under the name. GenServer.call to it will
|
|
# exit; the catch clause in ask_if_alive returns false → readiness OK.
|
|
if original_pid do
|
|
:erlang.unregister(Aprsme.ShutdownHandler)
|
|
end
|
|
|
|
task = Task.async(fn -> Process.sleep(2_000) end)
|
|
Process.register(task.pid, Aprsme.ShutdownHandler)
|
|
|
|
try do
|
|
conn = %{conn | request_path: "/health"}
|
|
result = HealthCheck.call(conn, probe_type: :readiness)
|
|
# The dummy pid is alive but won't respond to :shutting_down? → catch → false → OK
|
|
assert result.status == 200
|
|
after
|
|
if Process.whereis(Aprsme.ShutdownHandler) == task.pid do
|
|
:erlang.unregister(Aprsme.ShutdownHandler)
|
|
end
|
|
|
|
Task.shutdown(task, :brutal_kill)
|
|
|
|
if original_pid && !Process.whereis(Aprsme.ShutdownHandler) do
|
|
Process.register(original_pid, Aprsme.ShutdownHandler)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "readiness probe success path" do
|
|
test "returns 200 with OK when all checks pass", %{conn: conn} do
|
|
Application.put_env(:aprsme, :health_status, :healthy)
|
|
|
|
conn = %{conn | request_path: "/health"}
|
|
result = HealthCheck.call(conn, probe_type: :readiness)
|
|
|
|
assert result.status == 200
|
|
assert result.resp_body == "OK"
|
|
assert result.halted
|
|
end
|
|
|
|
test "returns 503 when ShutdownHandler reports shutting_down via the live process", %{conn: conn} do
|
|
Application.put_env(:aprsme, :health_status, :healthy)
|
|
|
|
pid = Process.whereis(Aprsme.ShutdownHandler)
|
|
assert pid, "ShutdownHandler must be running to exercise the live shutting_down? path"
|
|
:sys.replace_state(pid, fn state -> %{state | shutting_down: true} end)
|
|
|
|
try do
|
|
conn = %{conn | request_path: "/health"}
|
|
result = HealthCheck.call(conn, probe_type: :readiness)
|
|
assert result.status == 503
|
|
assert result.resp_body =~ "shutting down"
|
|
after
|
|
:sys.replace_state(pid, fn state -> %{state | shutting_down: false} end)
|
|
end
|
|
end
|
|
end
|
|
end
|