more tests
This commit is contained in:
parent
094a50d183
commit
6b624e1365
8 changed files with 77 additions and 61 deletions
|
|
@ -43,6 +43,9 @@ config :aprsme, AprsmeWeb.Telemetry, enabled: false
|
||||||
# Disable cleanup scheduler in test environment
|
# Disable cleanup scheduler in test environment
|
||||||
config :aprsme, :cleanup_scheduler, enabled: false
|
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
|
# Disable initialize replay delay in test environment
|
||||||
config :aprsme, :initialize_replay_delay, 0
|
config :aprsme, :initialize_replay_delay, 0
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,14 @@ defmodule Aprsme.Application do
|
||||||
Aprsme.PacketPipelineSupervisor
|
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()
|
children = children ++ redis_children()
|
||||||
|
|
||||||
# Add shutdown handlers at the end, after everything else is started
|
# Add shutdown handlers at the end, after everything else is started
|
||||||
|
|
|
||||||
|
|
@ -20,14 +20,15 @@ defmodule Aprsme.Cluster.ConnectionManager do
|
||||||
Phoenix.PubSub.subscribe(Aprsme.PubSub, "cluster:leadership")
|
Phoenix.PubSub.subscribe(Aprsme.PubSub, "cluster:leadership")
|
||||||
|
|
||||||
# Check initial leadership state
|
# 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}}
|
{:ok, %{connection_started: false}}
|
||||||
end
|
end
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def handle_info(:check_initial_state, state) do
|
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")
|
Logger.info("This node is the leader, starting APRS-IS connection")
|
||||||
start_aprs_connection()
|
start_aprs_connection()
|
||||||
{:noreply, %{state | connection_started: true}}
|
{:noreply, %{state | connection_started: true}}
|
||||||
|
|
@ -55,6 +56,12 @@ defmodule Aprsme.Cluster.ConnectionManager do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp leader_check do
|
||||||
|
LeaderElection.leader?()
|
||||||
|
catch
|
||||||
|
:exit, _ -> false
|
||||||
|
end
|
||||||
|
|
||||||
defp start_aprs_connection do
|
defp start_aprs_connection do
|
||||||
# Start the APRS-IS connection supervisor if not already started
|
# Start the APRS-IS connection supervisor if not already started
|
||||||
case DynamicSupervisor.start_child(
|
case DynamicSupervisor.start_child(
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,16 @@ defmodule Aprsme.LogSanitizer do
|
||||||
@doc """
|
@doc """
|
||||||
Sanitize any data structure recursively
|
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
|
def sanitize(data) when is_map(data) do
|
||||||
data
|
data
|
||||||
|> sanitize_map()
|
|> sanitize_map()
|
||||||
|
|
|
||||||
|
|
@ -21,14 +21,27 @@ defmodule Aprsme.PacketPipelineSetup do
|
||||||
config = Application.get_env(:aprsme, :packet_pipeline, [])
|
config = Application.get_env(:aprsme, :packet_pipeline, [])
|
||||||
max_demand = config[:max_demand] || 50
|
max_demand = config[:max_demand] || 50
|
||||||
|
|
||||||
case GenStage.sync_subscribe(Aprsme.PacketConsumer, to: Aprsme.PacketProducer, max_demand: max_demand) do
|
try do
|
||||||
{:ok, _subscription} ->
|
case GenStage.sync_subscribe(Aprsme.PacketConsumer,
|
||||||
require Logger
|
to: Aprsme.PacketProducer,
|
||||||
|
max_demand: max_demand
|
||||||
|
) do
|
||||||
|
{:ok, _subscription} ->
|
||||||
|
require Logger
|
||||||
|
|
||||||
Logger.info("GenStage packet pipeline subscription established")
|
Logger.info("GenStage packet pipeline subscription established")
|
||||||
{:noreply, state}
|
{: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
|
require Logger
|
||||||
|
|
||||||
Logger.error("Failed to establish GenStage subscription: #{inspect(reason)}")
|
Logger.error("Failed to establish GenStage subscription: #{inspect(reason)}")
|
||||||
|
|
|
||||||
|
|
@ -79,7 +79,7 @@ defmodule Aprsme.Cluster.ConnectionManagerTest do
|
||||||
# Start a minimal LeaderElection to prevent crashes
|
# Start a minimal LeaderElection to prevent crashes
|
||||||
Application.put_env(:aprsme, :cluster_enabled, false)
|
Application.put_env(:aprsme, :cluster_enabled, false)
|
||||||
{:ok, _} = LeaderElection.start_link([])
|
{:ok, _} = LeaderElection.start_link([])
|
||||||
Process.sleep(200)
|
Process.sleep(150)
|
||||||
:ok
|
:ok
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -94,7 +94,7 @@ defmodule Aprsme.Cluster.ConnectionManagerTest do
|
||||||
)
|
)
|
||||||
|
|
||||||
# Give time for message processing
|
# Give time for message processing
|
||||||
Process.sleep(100)
|
Process.sleep(50)
|
||||||
|
|
||||||
# Process should still be alive after receiving message
|
# Process should still be alive after receiving message
|
||||||
assert Process.whereis(ConnectionManager)
|
assert Process.whereis(ConnectionManager)
|
||||||
|
|
@ -103,8 +103,8 @@ defmodule Aprsme.Cluster.ConnectionManagerTest do
|
||||||
test "schedules initial state check" do
|
test "schedules initial state check" do
|
||||||
{:ok, pid} = ConnectionManager.start_link([])
|
{:ok, pid} = ConnectionManager.start_link([])
|
||||||
|
|
||||||
# Should receive :check_initial_state message after init
|
# With delay set to 0 in test config, the check fires immediately
|
||||||
Process.sleep(1100)
|
Process.sleep(50)
|
||||||
|
|
||||||
# Process should still be alive
|
# Process should still be alive
|
||||||
assert Process.alive?(pid)
|
assert Process.alive?(pid)
|
||||||
|
|
@ -116,8 +116,8 @@ defmodule Aprsme.Cluster.ConnectionManagerTest do
|
||||||
# Start LeaderElection in non-clustered mode so it becomes leader
|
# Start LeaderElection in non-clustered mode so it becomes leader
|
||||||
Application.put_env(:aprsme, :cluster_enabled, false)
|
Application.put_env(:aprsme, :cluster_enabled, false)
|
||||||
{:ok, _} = LeaderElection.start_link([])
|
{:ok, _} = LeaderElection.start_link([])
|
||||||
# Wait for election
|
# Wait for election (100ms timer + processing)
|
||||||
Process.sleep(200)
|
Process.sleep(150)
|
||||||
|
|
||||||
# Start ConnectionManager
|
# Start ConnectionManager
|
||||||
{:ok, pid} = ConnectionManager.start_link([])
|
{:ok, pid} = ConnectionManager.start_link([])
|
||||||
|
|
@ -127,20 +127,16 @@ defmodule Aprsme.Cluster.ConnectionManagerTest do
|
||||||
test "handles initial state check when leader", %{pid: pid} do
|
test "handles initial state check when leader", %{pid: pid} do
|
||||||
# Send initial state check
|
# Send initial state check
|
||||||
send(pid, :check_initial_state)
|
send(pid, :check_initial_state)
|
||||||
Process.sleep(100)
|
Process.sleep(50)
|
||||||
|
|
||||||
# Should handle without crashing
|
# Should handle without crashing
|
||||||
assert Process.alive?(pid)
|
assert Process.alive?(pid)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "handles initial state check when not leader", %{pid: pid} do
|
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 initial state check
|
||||||
send(pid, :check_initial_state)
|
send(pid, :check_initial_state)
|
||||||
Process.sleep(100)
|
Process.sleep(50)
|
||||||
|
|
||||||
# Should handle without crashing
|
# Should handle without crashing
|
||||||
assert Process.alive?(pid)
|
assert Process.alive?(pid)
|
||||||
|
|
@ -152,18 +148,18 @@ defmodule Aprsme.Cluster.ConnectionManagerTest do
|
||||||
# Ensure LeaderElection is started
|
# Ensure LeaderElection is started
|
||||||
Application.put_env(:aprsme, :cluster_enabled, false)
|
Application.put_env(:aprsme, :cluster_enabled, false)
|
||||||
{:ok, _} = LeaderElection.start_link([])
|
{:ok, _} = LeaderElection.start_link([])
|
||||||
Process.sleep(200)
|
Process.sleep(150)
|
||||||
|
|
||||||
{:ok, pid} = ConnectionManager.start_link([])
|
{:ok, pid} = ConnectionManager.start_link([])
|
||||||
# Wait for initial check
|
# With delay=0, initial check fires immediately; just wait for processing
|
||||||
Process.sleep(1100)
|
Process.sleep(50)
|
||||||
{:ok, pid: pid}
|
{:ok, pid: pid}
|
||||||
end
|
end
|
||||||
|
|
||||||
test "starts connection when becoming leader", %{pid: pid} do
|
test "starts connection when becoming leader", %{pid: pid} do
|
||||||
# Send leadership change - this node became leader
|
# Send leadership change - this node became leader
|
||||||
send(pid, {:leadership_change, node(), true})
|
send(pid, {:leadership_change, node(), true})
|
||||||
Process.sleep(100)
|
Process.sleep(50)
|
||||||
|
|
||||||
# Should handle the message without crashing
|
# Should handle the message without crashing
|
||||||
assert Process.alive?(pid)
|
assert Process.alive?(pid)
|
||||||
|
|
@ -172,11 +168,11 @@ defmodule Aprsme.Cluster.ConnectionManagerTest do
|
||||||
test "stops connection when losing leadership", %{pid: pid} do
|
test "stops connection when losing leadership", %{pid: pid} do
|
||||||
# First become leader and start connection
|
# First become leader and start connection
|
||||||
send(pid, {:leadership_change, node(), true})
|
send(pid, {:leadership_change, node(), true})
|
||||||
Process.sleep(100)
|
Process.sleep(50)
|
||||||
|
|
||||||
# Then lose leadership
|
# Then lose leadership
|
||||||
send(pid, {:leadership_change, node(), false})
|
send(pid, {:leadership_change, node(), false})
|
||||||
Process.sleep(100)
|
Process.sleep(50)
|
||||||
|
|
||||||
# Should handle the message without crashing
|
# Should handle the message without crashing
|
||||||
assert Process.alive?(pid)
|
assert Process.alive?(pid)
|
||||||
|
|
@ -187,7 +183,7 @@ defmodule Aprsme.Cluster.ConnectionManagerTest do
|
||||||
|
|
||||||
# Send leadership change for another node
|
# Send leadership change for another node
|
||||||
send(pid, {:leadership_change, other_node, true})
|
send(pid, {:leadership_change, other_node, true})
|
||||||
Process.sleep(100)
|
Process.sleep(50)
|
||||||
|
|
||||||
# Should not affect this node
|
# Should not affect this node
|
||||||
assert Process.alive?(pid)
|
assert Process.alive?(pid)
|
||||||
|
|
@ -196,10 +192,10 @@ defmodule Aprsme.Cluster.ConnectionManagerTest do
|
||||||
test "does not start connection twice", %{pid: pid} do
|
test "does not start connection twice", %{pid: pid} do
|
||||||
# Become leader twice
|
# Become leader twice
|
||||||
send(pid, {:leadership_change, node(), true})
|
send(pid, {:leadership_change, node(), true})
|
||||||
Process.sleep(100)
|
Process.sleep(50)
|
||||||
|
|
||||||
send(pid, {:leadership_change, node(), true})
|
send(pid, {:leadership_change, node(), true})
|
||||||
Process.sleep(100)
|
Process.sleep(50)
|
||||||
|
|
||||||
# Should handle duplicate leadership without issues
|
# Should handle duplicate leadership without issues
|
||||||
assert Process.alive?(pid)
|
assert Process.alive?(pid)
|
||||||
|
|
@ -211,20 +207,18 @@ defmodule Aprsme.Cluster.ConnectionManagerTest do
|
||||||
# Ensure LeaderElection is started
|
# Ensure LeaderElection is started
|
||||||
Application.put_env(:aprsme, :cluster_enabled, false)
|
Application.put_env(:aprsme, :cluster_enabled, false)
|
||||||
{:ok, _} = LeaderElection.start_link([])
|
{:ok, _} = LeaderElection.start_link([])
|
||||||
Process.sleep(200)
|
Process.sleep(150)
|
||||||
|
|
||||||
{:ok, pid} = ConnectionManager.start_link([])
|
{: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}
|
{:ok, pid: pid}
|
||||||
end
|
end
|
||||||
|
|
||||||
test "handles IsSupervisor already started error", %{pid: pid} do
|
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
|
# Try to become leader
|
||||||
send(pid, {:leadership_change, node(), true})
|
send(pid, {:leadership_change, node(), true})
|
||||||
Process.sleep(100)
|
Process.sleep(50)
|
||||||
|
|
||||||
# Should handle gracefully even if IsSupervisor fails to start
|
# Should handle gracefully even if IsSupervisor fails to start
|
||||||
assert Process.alive?(pid)
|
assert Process.alive?(pid)
|
||||||
|
|
@ -239,12 +233,9 @@ defmodule Aprsme.Cluster.ConnectionManagerTest do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Temporarily replace the module reference
|
|
||||||
# This test is more conceptual as we can't easily mock module references
|
|
||||||
|
|
||||||
# Send leadership change
|
# Send leadership change
|
||||||
send(pid, {:leadership_change, node(), true})
|
send(pid, {:leadership_change, node(), true})
|
||||||
Process.sleep(100)
|
Process.sleep(50)
|
||||||
|
|
||||||
# Should handle failure gracefully
|
# Should handle failure gracefully
|
||||||
assert Process.alive?(pid)
|
assert Process.alive?(pid)
|
||||||
|
|
@ -256,26 +247,26 @@ defmodule Aprsme.Cluster.ConnectionManagerTest do
|
||||||
# Ensure LeaderElection is started
|
# Ensure LeaderElection is started
|
||||||
Application.put_env(:aprsme, :cluster_enabled, false)
|
Application.put_env(:aprsme, :cluster_enabled, false)
|
||||||
{:ok, _} = LeaderElection.start_link([])
|
{:ok, _} = LeaderElection.start_link([])
|
||||||
Process.sleep(200)
|
Process.sleep(150)
|
||||||
|
|
||||||
{:ok, pid} = ConnectionManager.start_link([])
|
{:ok, pid} = ConnectionManager.start_link([])
|
||||||
{:ok, pid: pid}
|
{:ok, pid: pid}
|
||||||
end
|
end
|
||||||
|
|
||||||
test "full lifecycle - become leader, lose leadership", %{pid: pid} do
|
test "full lifecycle - become leader, lose leadership", %{pid: pid} do
|
||||||
# Wait for initial check
|
# With delay=0, initial check fires immediately; just wait for processing
|
||||||
Process.sleep(1100)
|
Process.sleep(50)
|
||||||
|
|
||||||
# Become leader
|
# Become leader
|
||||||
send(pid, {:leadership_change, node(), true})
|
send(pid, {:leadership_change, node(), true})
|
||||||
Process.sleep(100)
|
Process.sleep(50)
|
||||||
|
|
||||||
# Verify connection started (check state indirectly)
|
# Verify connection started (check state indirectly)
|
||||||
assert Process.alive?(pid)
|
assert Process.alive?(pid)
|
||||||
|
|
||||||
# Lose leadership
|
# Lose leadership
|
||||||
send(pid, {:leadership_change, node(), false})
|
send(pid, {:leadership_change, node(), false})
|
||||||
Process.sleep(100)
|
Process.sleep(50)
|
||||||
|
|
||||||
# Verify still alive after stopping connection
|
# Verify still alive after stopping connection
|
||||||
assert Process.alive?(pid)
|
assert Process.alive?(pid)
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,7 @@ defmodule AprsmeWeb.MapLive.MovementTest do
|
||||||
|
|
||||||
# The view should not push a new_packet event for GPS drift
|
# The view should not push a new_packet event for GPS drift
|
||||||
# Using a longer timeout to avoid flaky tests on slower systems
|
# 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
|
end
|
||||||
|
|
||||||
test "updates marker for significant movement", %{conn: conn} do
|
test "updates marker for significant movement", %{conn: conn} do
|
||||||
|
|
|
||||||
|
|
@ -5,18 +5,6 @@ defmodule Aprsme.MockHelpers do
|
||||||
|
|
||||||
def stub_packets_mock do
|
def stub_packets_mock do
|
||||||
# Stub the packets module to prevent external calls
|
# 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 ->
|
Mox.stub(Aprsme.PacketsMock, :get_recent_packets, fn _opts ->
|
||||||
[]
|
[]
|
||||||
end)
|
end)
|
||||||
|
|
@ -25,8 +13,4 @@ defmodule Aprsme.MockHelpers do
|
||||||
[]
|
[]
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
def stub_badpackets_mock do
|
|
||||||
Mox.stub_with(BadPacketsMock, BadPacketsStub)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue