more tests

This commit is contained in:
Graham McIntire 2026-02-09 17:21:16 -06:00
parent 094a50d183
commit 6b624e1365
No known key found for this signature in database
8 changed files with 77 additions and 61 deletions

View file

@ -43,6 +43,9 @@ config :aprsme, AprsmeWeb.Telemetry, enabled: false
# Disable cleanup scheduler in test environment
config :aprsme, :cleanup_scheduler, enabled: false
# Speed up ConnectionManager init in test environment
config :aprsme, :connection_manager_init_delay, 0
# Disable initialize replay delay in test environment
config :aprsme, :initialize_replay_delay, 0

View file

@ -53,6 +53,14 @@ defmodule Aprsme.Application do
Aprsme.PacketPipelineSupervisor
]
# Skip packet pipeline in test to avoid Sandbox ownership errors
children =
if Application.get_env(:aprsme, :env) == :test do
List.delete(children, Aprsme.PacketPipelineSupervisor)
else
children
end
children = children ++ redis_children()
# Add shutdown handlers at the end, after everything else is started

View file

@ -20,14 +20,15 @@ defmodule Aprsme.Cluster.ConnectionManager do
Phoenix.PubSub.subscribe(Aprsme.PubSub, "cluster:leadership")
# Check initial leadership state
Process.send_after(self(), :check_initial_state, 1000)
delay = Application.get_env(:aprsme, :connection_manager_init_delay, 1000)
Process.send_after(self(), :check_initial_state, delay)
{:ok, %{connection_started: false}}
end
@impl true
def handle_info(:check_initial_state, state) do
if LeaderElection.leader?() do
if leader_check() do
Logger.info("This node is the leader, starting APRS-IS connection")
start_aprs_connection()
{:noreply, %{state | connection_started: true}}
@ -55,6 +56,12 @@ defmodule Aprsme.Cluster.ConnectionManager do
end
end
defp leader_check do
LeaderElection.leader?()
catch
:exit, _ -> false
end
defp start_aprs_connection do
# Start the APRS-IS connection supervisor if not already started
case DynamicSupervisor.start_child(

View file

@ -65,6 +65,16 @@ defmodule Aprsme.LogSanitizer do
@doc """
Sanitize any data structure recursively
"""
def sanitize(%{__struct__: _} = data) when is_exception(data) do
sanitize_string(Exception.message(data))
end
def sanitize(%{__struct__: _} = data) do
data
|> Map.from_struct()
|> sanitize()
end
def sanitize(data) when is_map(data) do
data
|> sanitize_map()

View file

@ -21,14 +21,27 @@ defmodule Aprsme.PacketPipelineSetup do
config = Application.get_env(:aprsme, :packet_pipeline, [])
max_demand = config[:max_demand] || 50
case GenStage.sync_subscribe(Aprsme.PacketConsumer, to: Aprsme.PacketProducer, max_demand: max_demand) do
{:ok, _subscription} ->
require Logger
try do
case GenStage.sync_subscribe(Aprsme.PacketConsumer,
to: Aprsme.PacketProducer,
max_demand: max_demand
) do
{:ok, _subscription} ->
require Logger
Logger.info("GenStage packet pipeline subscription established")
{:noreply, state}
Logger.info("GenStage packet pipeline subscription established")
{:noreply, state}
{:error, reason} ->
{:error, reason} ->
require Logger
Logger.error("Failed to establish GenStage subscription: #{inspect(reason)}")
# Retry after a delay
Process.send_after(self(), :setup_subscription, 1000)
{:noreply, state}
end
catch
:exit, reason ->
require Logger
Logger.error("Failed to establish GenStage subscription: #{inspect(reason)}")

View file

@ -79,7 +79,7 @@ defmodule Aprsme.Cluster.ConnectionManagerTest do
# Start a minimal LeaderElection to prevent crashes
Application.put_env(:aprsme, :cluster_enabled, false)
{:ok, _} = LeaderElection.start_link([])
Process.sleep(200)
Process.sleep(150)
:ok
end
@ -94,7 +94,7 @@ defmodule Aprsme.Cluster.ConnectionManagerTest do
)
# Give time for message processing
Process.sleep(100)
Process.sleep(50)
# Process should still be alive after receiving message
assert Process.whereis(ConnectionManager)
@ -103,8 +103,8 @@ defmodule Aprsme.Cluster.ConnectionManagerTest do
test "schedules initial state check" do
{:ok, pid} = ConnectionManager.start_link([])
# Should receive :check_initial_state message after init
Process.sleep(1100)
# With delay set to 0 in test config, the check fires immediately
Process.sleep(50)
# Process should still be alive
assert Process.alive?(pid)
@ -116,8 +116,8 @@ defmodule Aprsme.Cluster.ConnectionManagerTest do
# Start LeaderElection in non-clustered mode so it becomes leader
Application.put_env(:aprsme, :cluster_enabled, false)
{:ok, _} = LeaderElection.start_link([])
# Wait for election
Process.sleep(200)
# Wait for election (100ms timer + processing)
Process.sleep(150)
# Start ConnectionManager
{:ok, pid} = ConnectionManager.start_link([])
@ -127,20 +127,16 @@ defmodule Aprsme.Cluster.ConnectionManagerTest do
test "handles initial state check when leader", %{pid: pid} do
# Send initial state check
send(pid, :check_initial_state)
Process.sleep(100)
Process.sleep(50)
# Should handle without crashing
assert Process.alive?(pid)
end
test "handles initial state check when not leader", %{pid: pid} do
# ConnectionManager will check with LeaderElection
# In test environment, it will get that it's the leader
# This is OK - we're just testing that it handles the check without crashing
# Send initial state check
send(pid, :check_initial_state)
Process.sleep(100)
Process.sleep(50)
# Should handle without crashing
assert Process.alive?(pid)
@ -152,18 +148,18 @@ defmodule Aprsme.Cluster.ConnectionManagerTest do
# Ensure LeaderElection is started
Application.put_env(:aprsme, :cluster_enabled, false)
{:ok, _} = LeaderElection.start_link([])
Process.sleep(200)
Process.sleep(150)
{:ok, pid} = ConnectionManager.start_link([])
# Wait for initial check
Process.sleep(1100)
# With delay=0, initial check fires immediately; just wait for processing
Process.sleep(50)
{:ok, pid: pid}
end
test "starts connection when becoming leader", %{pid: pid} do
# Send leadership change - this node became leader
send(pid, {:leadership_change, node(), true})
Process.sleep(100)
Process.sleep(50)
# Should handle the message without crashing
assert Process.alive?(pid)
@ -172,11 +168,11 @@ defmodule Aprsme.Cluster.ConnectionManagerTest do
test "stops connection when losing leadership", %{pid: pid} do
# First become leader and start connection
send(pid, {:leadership_change, node(), true})
Process.sleep(100)
Process.sleep(50)
# Then lose leadership
send(pid, {:leadership_change, node(), false})
Process.sleep(100)
Process.sleep(50)
# Should handle the message without crashing
assert Process.alive?(pid)
@ -187,7 +183,7 @@ defmodule Aprsme.Cluster.ConnectionManagerTest do
# Send leadership change for another node
send(pid, {:leadership_change, other_node, true})
Process.sleep(100)
Process.sleep(50)
# Should not affect this node
assert Process.alive?(pid)
@ -196,10 +192,10 @@ defmodule Aprsme.Cluster.ConnectionManagerTest do
test "does not start connection twice", %{pid: pid} do
# Become leader twice
send(pid, {:leadership_change, node(), true})
Process.sleep(100)
Process.sleep(50)
send(pid, {:leadership_change, node(), true})
Process.sleep(100)
Process.sleep(50)
# Should handle duplicate leadership without issues
assert Process.alive?(pid)
@ -211,20 +207,18 @@ defmodule Aprsme.Cluster.ConnectionManagerTest do
# Ensure LeaderElection is started
Application.put_env(:aprsme, :cluster_enabled, false)
{:ok, _} = LeaderElection.start_link([])
Process.sleep(200)
Process.sleep(150)
{:ok, pid} = ConnectionManager.start_link([])
Process.sleep(1100)
# With delay=0, initial check fires immediately; just wait for processing
Process.sleep(50)
{:ok, pid: pid}
end
test "handles IsSupervisor already started error", %{pid: pid} do
# The APRS-IS connection is disabled in test environment,
# but we can still test that the ConnectionManager handles errors gracefully
# Try to become leader
send(pid, {:leadership_change, node(), true})
Process.sleep(100)
Process.sleep(50)
# Should handle gracefully even if IsSupervisor fails to start
assert Process.alive?(pid)
@ -239,12 +233,9 @@ defmodule Aprsme.Cluster.ConnectionManagerTest do
end
end
# Temporarily replace the module reference
# This test is more conceptual as we can't easily mock module references
# Send leadership change
send(pid, {:leadership_change, node(), true})
Process.sleep(100)
Process.sleep(50)
# Should handle failure gracefully
assert Process.alive?(pid)
@ -256,26 +247,26 @@ defmodule Aprsme.Cluster.ConnectionManagerTest do
# Ensure LeaderElection is started
Application.put_env(:aprsme, :cluster_enabled, false)
{:ok, _} = LeaderElection.start_link([])
Process.sleep(200)
Process.sleep(150)
{:ok, pid} = ConnectionManager.start_link([])
{:ok, pid: pid}
end
test "full lifecycle - become leader, lose leadership", %{pid: pid} do
# Wait for initial check
Process.sleep(1100)
# With delay=0, initial check fires immediately; just wait for processing
Process.sleep(50)
# Become leader
send(pid, {:leadership_change, node(), true})
Process.sleep(100)
Process.sleep(50)
# Verify connection started (check state indirectly)
assert Process.alive?(pid)
# Lose leadership
send(pid, {:leadership_change, node(), false})
Process.sleep(100)
Process.sleep(50)
# Verify still alive after stopping connection
assert Process.alive?(pid)

View file

@ -72,7 +72,7 @@ defmodule AprsmeWeb.MapLive.MovementTest do
# The view should not push a new_packet event for GPS drift
# Using a longer timeout to avoid flaky tests on slower systems
refute_push_event(view, "new_packet", %{id: "TEST-1"}, 2000)
refute_push_event(view, "new_packet", %{id: "TEST-1"}, 200)
end
test "updates marker for significant movement", %{conn: conn} do

View file

@ -5,18 +5,6 @@ defmodule Aprsme.MockHelpers do
def stub_packets_mock do
# Stub the packets module to prevent external calls
Mox.stub(Aprsme.PacketsMock, :get_packets_for_callsign, fn _callsign ->
{:ok, []}
end)
Mox.stub(Aprsme.PacketsMock, :get_packets_for_callsign_with_limit, fn _callsign, _limit ->
{:ok, []}
end)
Mox.stub(Aprsme.PacketsMock, :get_packets_for_callsign_with_date_range, fn _callsign, _start_date, _end_date ->
{:ok, []}
end)
Mox.stub(Aprsme.PacketsMock, :get_recent_packets, fn _opts ->
[]
end)
@ -25,8 +13,4 @@ defmodule Aprsme.MockHelpers do
[]
end)
end
def stub_badpackets_mock do
Mox.stub_with(BadPacketsMock, BadPacketsStub)
end
end