- Replace apply/2 with direct fully-qualified calls in movement_test - Fix assert_receive timeouts < 1000ms across 8 test files - Move nested import statements to module-level scope - Fix tests with no assertions and add missing doctest - Replace weak type assertions with specific value checks - Fix conditional assertions and length/1 expensive patterns - Disable inappropriate Jump.CredoChecks.AvoidSocketAssignsInTest - Fix tests not calling application code with credo:disable - Add various credo:disable comments for legitimate patterns
168 lines
5.4 KiB
Elixir
168 lines
5.4 KiB
Elixir
defmodule Aprsme.ConnectionMonitorTest do
|
|
use ExUnit.Case, async: false
|
|
|
|
alias Aprsme.ConnectionMonitor
|
|
|
|
describe "cluster disabled (default test config)" do
|
|
test "init/1 returns :ignore when cluster disabled" do
|
|
assert ConnectionMonitor.init([]) == :ignore
|
|
end
|
|
|
|
test "accepting_connections?/0 returns true when cluster disabled" do
|
|
assert ConnectionMonitor.accepting_connections?() == true
|
|
end
|
|
|
|
test "get_stats/0 returns default stats when cluster disabled" do
|
|
assert ConnectionMonitor.get_stats() == %{connections: 0, cpu: 0.0, memory: 0.0}
|
|
end
|
|
end
|
|
|
|
describe "cluster enabled without a running monitor" do
|
|
test "get_stats/0 returns default stats instead of crashing" do
|
|
Application.put_env(:aprsme, :cluster_enabled, true)
|
|
|
|
on_exit(fn ->
|
|
Application.put_env(:aprsme, :cluster_enabled, false)
|
|
end)
|
|
|
|
assert ConnectionMonitor.get_stats() == %{connections: 0, cpu: 0.0, memory: 0.0}
|
|
end
|
|
end
|
|
|
|
describe "cluster enabled" do
|
|
setup do
|
|
Application.put_env(:aprsme, :cluster_enabled, true)
|
|
{:ok, pid} = ConnectionMonitor.start_link([])
|
|
|
|
on_exit(fn ->
|
|
if Process.alive?(pid) do
|
|
try do
|
|
GenServer.stop(pid)
|
|
catch
|
|
:exit, _ -> :ok
|
|
end
|
|
end
|
|
|
|
Application.put_env(:aprsme, :cluster_enabled, false)
|
|
end)
|
|
|
|
%{pid: pid}
|
|
end
|
|
|
|
test "register_connection/0 increments connections" do
|
|
ConnectionMonitor.register_connection()
|
|
# Allow the cast to be processed
|
|
stats = ConnectionMonitor.get_stats()
|
|
assert stats.connections == 1
|
|
end
|
|
|
|
test "unregister_connection/0 decrements connections" do
|
|
ConnectionMonitor.register_connection()
|
|
ConnectionMonitor.register_connection()
|
|
ConnectionMonitor.unregister_connection()
|
|
stats = ConnectionMonitor.get_stats()
|
|
assert stats.connections == 1
|
|
end
|
|
|
|
test "unregister_connection/0 does not go below 0" do
|
|
ConnectionMonitor.unregister_connection()
|
|
stats = ConnectionMonitor.get_stats()
|
|
assert stats.connections == 0
|
|
end
|
|
|
|
test "accepting_connections?/0 returns true when not draining" do
|
|
assert ConnectionMonitor.accepting_connections?() == true
|
|
end
|
|
|
|
test "accepting_connections?/0 returns false when draining", %{pid: pid} do
|
|
:sys.replace_state(pid, fn state -> %{state | draining: true} end)
|
|
assert ConnectionMonitor.accepting_connections?() == false
|
|
end
|
|
|
|
test ":check_load handler keeps process alive", %{pid: pid} do
|
|
send(pid, :check_load)
|
|
# Give it time to process
|
|
_ = ConnectionMonitor.get_stats()
|
|
assert Process.alive?(pid)
|
|
end
|
|
|
|
test "get_stats/0 returns cpu and memory fields", %{pid: _pid} do
|
|
stats = ConnectionMonitor.get_stats()
|
|
|
|
assert Map.has_key?(stats, :cpu)
|
|
assert Map.has_key?(stats, :memory)
|
|
assert is_number(stats.cpu)
|
|
assert is_number(stats.memory)
|
|
end
|
|
|
|
test "draining triggers when connections exceed imbalance ratio", %{pid: pid} do
|
|
# Simulate having many connections to trigger draining analysis
|
|
for _ <- 1..20 do
|
|
ConnectionMonitor.register_connection()
|
|
end
|
|
|
|
# Force a load check which will gather stats and analyze
|
|
send(pid, :check_load)
|
|
Process.sleep(100)
|
|
|
|
assert Process.alive?(pid)
|
|
stats = ConnectionMonitor.get_stats()
|
|
assert stats.connections == 20
|
|
end
|
|
|
|
test "register_connection/0 is a no-op when cluster disabled" do
|
|
Application.put_env(:aprsme, :cluster_enabled, false)
|
|
|
|
# Should not crash even though the GenServer is running
|
|
assert ConnectionMonitor.register_connection() == nil
|
|
|
|
Application.put_env(:aprsme, :cluster_enabled, true)
|
|
end
|
|
|
|
test "unregister_connection/0 is a no-op when cluster disabled" do
|
|
Application.put_env(:aprsme, :cluster_enabled, false)
|
|
|
|
assert ConnectionMonitor.unregister_connection() == nil
|
|
|
|
Application.put_env(:aprsme, :cluster_enabled, true)
|
|
end
|
|
|
|
test "draining is set to true when state has high connections", %{pid: pid} do
|
|
# Set up state with draining true and connections > 0 to trigger drain broadcast
|
|
Phoenix.PubSub.subscribe(Aprsme.PubSub, "connection:drain:#{Node.self()}")
|
|
|
|
:sys.replace_state(pid, fn state ->
|
|
%{state | draining: true, local_connections: 50}
|
|
end)
|
|
|
|
# Trigger check_load to run the full analysis pipeline
|
|
send(pid, :check_load)
|
|
Process.sleep(200)
|
|
|
|
assert Process.alive?(pid)
|
|
assert %{connections: 50} = ConnectionMonitor.get_stats()
|
|
end
|
|
|
|
test "get_stats returns draining field", %{pid: pid} do
|
|
:sys.replace_state(pid, fn state -> %{state | draining: true} end)
|
|
stats = ConnectionMonitor.get_stats()
|
|
assert stats.draining == true
|
|
end
|
|
|
|
test "gather_cluster_stats includes local node stats without deadlock", %{pid: pid} do
|
|
ConnectionMonitor.register_connection()
|
|
ConnectionMonitor.register_connection()
|
|
|
|
# Trigger check_load which calls gather_cluster_stats internally.
|
|
# Before the fix, this would deadlock for 5s because gather_cluster_stats
|
|
# did an RPC call to Node.self() which GenServer.call'd back into itself.
|
|
send(pid, :check_load)
|
|
Process.sleep(100)
|
|
|
|
state = :sys.get_state(pid)
|
|
# Local node stats must always be present
|
|
assert Map.has_key?(state.node_stats, Node.self())
|
|
assert state.node_stats[Node.self()].connections == 2
|
|
end
|
|
end
|
|
end
|