defmodule Aprsme.Telemetry.DatabaseMetricsTest do # Uses DataCase for a sandbox DB checkout — some metrics run live queries # against pg_stat_* views. async: false because we mutate :aprsme :env # in one test. use Aprsme.DataCase, async: false alias Aprsme.Telemetry.DatabaseMetrics defp attach_collector(event, tag \\ to_string(System.unique_integer([:positive]))) do test_pid = self() handler_id = "test-#{tag}-#{Enum.join(event, ",")}" :telemetry.attach( handler_id, event, fn ^event, measurements, metadata, _ -> send(test_pid, {:telemetry, event, measurements, metadata}) end, nil ) on_exit(fn -> :telemetry.detach(handler_id) end) handler_id end describe "collect_postgres_metrics/0 in :test env" do test "is a no-op returning :ok" do original = Application.get_env(:aprsme, :env) Application.put_env(:aprsme, :env, :test) try do assert DatabaseMetrics.collect_postgres_metrics() == :ok after Application.put_env(:aprsme, :env, original) end end end describe "collect_db_pool_metrics/0" do test "emits [:aprsme, :repo, :pool] telemetry with pool_size from config" do attach_collector([:aprsme, :repo, :pool]) DatabaseMetrics.collect_db_pool_metrics() assert_receive {:telemetry, [:aprsme, :repo, :pool], measurements, _}, 1_000 assert Map.has_key?(measurements, :size) assert Map.has_key?(measurements, :idle) assert Map.has_key?(measurements, :busy) assert Map.has_key?(measurements, :available) assert Map.has_key?(measurements, :queue_length) assert Map.has_key?(measurements, :total) end end describe "collect_pgbouncer_metrics/0" do test "is a no-op returning :ok" do assert DatabaseMetrics.collect_pgbouncer_metrics() == :ok end end describe "collect_db_pool_metrics/0 when Repo is not running" do test "emits unavailable metrics when no Repo is registered" do # Stop the Repo for this test — we need to restart it afterward to avoid # breaking downstream tests. repo_pid = Process.whereis(Aprsme.Repo) if repo_pid do # Temporarily unregister without stopping to avoid disrupting other tests. :erlang.unregister(Aprsme.Repo) on_exit(fn -> if !Process.whereis(Aprsme.Repo) do true = Process.register(repo_pid, Aprsme.Repo) end end) end attach_collector([:aprsme, :repo, :pool]) DatabaseMetrics.collect_db_pool_metrics() assert_receive {:telemetry, [:aprsme, :repo, :pool], measurements, _}, 1_000 # The "unavailable" helper reports mostly zeros. assert measurements.size == (Application.get_env(:aprsme, Aprsme.Repo)[:pool_size] || 10) assert measurements.idle == 0 assert measurements.busy == 0 end end describe "to_number/1 and to_number/2 helpers (private — exercised through emit)" do # to_number/1 has 4 heads (nil, %Decimal{}, number, fallback). to_number/2 has # the same shape for the :integer overload. We exercise these via the public # functions that build telemetry payloads from query results. test "emits packets_table telemetry with Decimal values converted via to_number/1" do attach_collector([:aprsme, :postgres, :packets_table]) original = Application.get_env(:aprsme, :env) Application.put_env(:aprsme, :env, :prod) try do DatabaseMetrics.collect_postgres_metrics() after Application.put_env(:aprsme, :env, original) end # Values should always be plain numbers, not Decimals. assert_receive {:telemetry, [:aprsme, :postgres, :packets_table], measurements, _}, 2_000 Enum.each(measurements, fn {_k, v} -> assert is_number(v) end) end end describe "collect_postgres_metrics/0 outside :test env" do test "emits connections and packets_table telemetry events" do original = Application.get_env(:aprsme, :env) Application.put_env(:aprsme, :env, :prod) attach_collector([:aprsme, :postgres, :connections]) attach_collector([:aprsme, :postgres, :packets_table]) try do DatabaseMetrics.collect_postgres_metrics() after Application.put_env(:aprsme, :env, original) end assert_receive {:telemetry, [:aprsme, :postgres, :connections], conn_metrics, _}, 2_000 assert Map.has_key?(conn_metrics, :total) assert Map.has_key?(conn_metrics, :active) assert Map.has_key?(conn_metrics, :idle) end end end