test: broaden coverage across Packets/Weather/Info/Map/Status modules

This commit is contained in:
Graham McIntire 2026-04-23 18:04:28 -05:00
parent 5ea97a9264
commit fac52cf288
No known key found for this signature in database
GPG key ID: F4ABF488E6029E59
4 changed files with 290 additions and 0 deletions

View file

@ -472,4 +472,99 @@ defmodule Aprsme.Packets.PreparedQueriesTest do
assert length(result) == 2
end
end
describe "get_recent_packets_in_bounds/2" do
test "returns packets within the bounding box" do
{:ok, _} =
create_positioned_packet(%{
sender: "INBOUNDS-1",
base_callsign: "INBOUNDS",
lat: Decimal.new("33.5"),
lon: Decimal.new("-96.5")
})
{:ok, _} =
create_positioned_packet(%{
sender: "OUTOFBOUNDS-1",
base_callsign: "OUTOFBOUNDS",
lat: Decimal.new("50.0"),
lon: Decimal.new("-120.0")
})
# bounds = [north, south, east, west]
result = PreparedQueries.get_recent_packets_in_bounds([34.0, 33.0, -96.0, -97.0], 24)
senders = Enum.map(result, & &1.sender)
assert "INBOUNDS-1" in senders
refute "OUTOFBOUNDS-1" in senders
end
end
describe "get_packet_count_in_area/2" do
test "counts only packets inside the bounding box" do
{:ok, _} =
create_positioned_packet(%{
sender: "COUNT-IN-1",
base_callsign: "COUNT",
lat: Decimal.new("33.25"),
lon: Decimal.new("-96.25")
})
{:ok, _} =
create_positioned_packet(%{
sender: "COUNT-OUT",
base_callsign: "COUNT",
lat: Decimal.new("55.0"),
lon: Decimal.new("-30.0")
})
# bounds = [north, south, east, west]
count = PreparedQueries.get_packet_count_in_area([34.0, 33.0, -96.0, -97.0], 24)
assert is_integer(count)
assert count >= 1
end
test "returns 0 when area has no packets" do
assert PreparedQueries.get_packet_count_in_area([0.0, -1.0, 180.0, 179.0], 1) == 0
end
end
describe "has_weather_packets?/1" do
test "returns true when callsign has weather packets" do
{:ok, _} =
create_positioned_packet(%{
sender: "HASWX-1",
base_callsign: "HASWX",
temperature: 72.0,
humidity: 50.0
})
assert PreparedQueries.has_weather_packets?("HASWX-1")
end
test "returns false when callsign has no weather packets" do
refute PreparedQueries.has_weather_packets?("NOWX-NEVER")
end
end
describe "weather_callsigns/1" do
test "returns a MapSet containing callsigns with weather data" do
{:ok, _} =
create_positioned_packet(%{
sender: "WXSET-1",
base_callsign: "WXSET",
has_weather: true,
temperature: 65.0
})
result = PreparedQueries.weather_callsigns(["WXSET-1", "NOWX-1"])
assert %MapSet{} = result
assert MapSet.member?(result, "WXSET-1")
refute MapSet.member?(result, "NOWX-1")
end
test "returns an empty MapSet for empty input" do
assert PreparedQueries.weather_callsigns([]) == MapSet.new()
end
end
end

View file

@ -165,4 +165,33 @@ defmodule AprsmeWeb.InfoLive.ShowHelpersTest do
assert html =~ "<div"
end
end
describe "position_changed_for_test/2" do
test "returns true when current packet is nil" do
assert Show.position_changed_for_test(nil, %{lat: 1.0, lon: 1.0})
end
test "returns false when new packet is nil" do
refute Show.position_changed_for_test(%{lat: 1.0, lon: 1.0}, nil)
end
test "returns false when positions are identical" do
current = %{lat: 33.0, lon: -96.0}
new = %{lat: 33.0, lon: -96.0}
refute Show.position_changed_for_test(current, new)
end
test "returns true when position changes by more than threshold" do
current = %{lat: 33.0, lon: -96.0}
new = %{lat: 33.01, lon: -96.0}
assert Show.position_changed_for_test(current, new)
end
test "returns true when either coordinate is nil" do
assert Show.position_changed_for_test(%{lat: nil, lon: -96.0}, %{lat: 33.0, lon: -96.0})
assert Show.position_changed_for_test(%{lat: 33.0, lon: nil}, %{lat: 33.0, lon: -96.0})
assert Show.position_changed_for_test(%{lat: 33.0, lon: -96.0}, %{lat: nil, lon: -96.0})
assert Show.position_changed_for_test(%{lat: 33.0, lon: -96.0}, %{lat: 33.0, lon: nil})
end
end
end

View file

@ -528,4 +528,107 @@ defmodule AprsmeWeb.MapLive.IndexTest do
end
end
end
describe "MapLive.Index error handling and edge cases" do
test "error_boundary_triggered event is handled without crashing", %{conn: conn} do
{:ok, view, _html} = live(conn, "/", on_error: :warn)
assert render_hook(view, "error_boundary_triggered", %{
"message" => "Test error",
"stack" => "at test:1",
"component_id" => "test-component"
})
end
test "update_map_state with bounds in params", %{conn: conn} do
{:ok, view, _html} = live(conn, "/", on_error: :warn)
assert render_hook(view, "update_map_state", %{
"center" => %{"lat" => 33.0, "lng" => -96.0},
"zoom" => 10,
"bounds" => %{
"north" => 34.0,
"south" => 32.0,
"east" => -95.0,
"west" => -97.0
}
})
end
test "update_map_state with invalid/non-map center uses socket default", %{conn: conn} do
{:ok, view, _html} = live(conn, "/?lat=33.0&lng=-96.0", on_error: :warn)
assert render_hook(view, "update_map_state", %{
"center" => "not-a-map",
"zoom" => 8
})
end
test "marker_hover_start with invalid path does not crash", %{conn: conn} do
{:ok, view, _html} = live(conn, "/", on_error: :warn)
assert render_hook(view, "marker_hover_start", %{
"id" => "m1",
"path" => "",
"lat" => 33.0,
"lng" => -96.0
})
end
test "set_location event updates center", %{conn: conn} do
{:ok, view, _html} = live(conn, "/", on_error: :warn)
assert render_hook(view, "set_location", %{"lat" => 40.7, "lng" => -74.0})
end
test "update_trail_duration with extreme value is clamped", %{conn: conn} do
{:ok, view, _html} = live(conn, "/", on_error: :warn)
# Very large value should be parsed and clamped
assert render_hook(view, "update_trail_duration", %{"trail_duration" => "9999"})
assert render_hook(view, "update_trail_duration", %{"trail_duration" => "0"})
assert render_hook(view, "update_trail_duration", %{"trail_duration" => "invalid"})
end
test "update_historical_hours with invalid input", %{conn: conn} do
{:ok, view, _html} = live(conn, "/", on_error: :warn)
assert render_hook(view, "update_historical_hours", %{"historical_hours" => "invalid"})
assert render_hook(view, "update_historical_hours", %{"historical_hours" => "0"})
end
test "handles process_bounds_update via direct send", %{conn: conn} do
{:ok, view, _html} = live(conn, "/", on_error: :warn)
bounds = %{north: 34.0, south: 32.0, east: -95.0, west: -97.0}
send(view.pid, {:process_bounds_update, bounds})
Process.sleep(50)
assert render(view) =~ "aprs-map"
end
test "handles postgres_packet broadcast via batcher", %{conn: conn} do
{:ok, view, _html} = live(conn, "/?lat=33.0&lng=-96.0&z=12", on_error: :warn)
render_hook(view, "map_ready", %{})
Process.sleep(50)
packet = %{
id: "pg-1",
sender: "PG-1",
base_callsign: "PG",
ssid: "1",
lat: 33.0,
lon: -96.0,
has_position: true,
symbol_table_id: "/",
symbol_code: ">",
received_at: DateTime.utc_now(),
path: ""
}
send(view.pid, {:postgres_packet, packet})
Process.sleep(50)
assert render(view) =~ "aprs-map"
end
end
end

View file

@ -77,5 +77,68 @@ defmodule AprsmeWeb.WeatherLive.CallsignViewTest do
# returned as-is.
assert CallsignView.format_weather_value(%{humidity: "65"}, :humidity, "en") == "65"
end
test "returns nil for unparseable string values on tracked keys" do
assert CallsignView.format_weather_value(%{temperature: "not-a-number"}, :temperature, "en") ==
nil
end
test "returns stringified value for untracked numeric keys" do
# humidity is numeric but not in the formatter map — passes through as string.
assert CallsignView.format_weather_value(%{humidity: 65}, :humidity, "en") == "65"
end
test "formats rain fields with units" do
result = CallsignView.format_weather_value(%{rain_1h: 0.25}, :rain_1h, "en")
assert is_binary(result)
assert String.contains?(result, " ")
end
end
describe "live packet broadcast handling" do
test "ignores non-matching postgres_packet broadcasts", %{conn: conn} do
{:ok, lv, _html} = live(conn, ~p"/weather/OWNCALL", on_error: :warn)
# Broadcast an unrelated packet with no weather data
unrelated = %{
sender: "DIFFERENT",
temperature: nil,
humidity: nil,
pressure: nil,
wind_speed: nil,
wind_direction: nil,
rain_1h: nil
}
send(lv.pid, {:postgres_packet, unrelated})
Process.sleep(20)
assert Process.alive?(lv.pid)
end
test "ignores postgres_packet with wrong callsign", %{conn: conn} do
{:ok, lv, _html} = live(conn, ~p"/weather/MYCALL", on_error: :warn)
wrong_call = %{
sender: "OTHER",
temperature: 72.5,
humidity: 50,
pressure: nil,
wind_speed: nil,
wind_direction: nil,
rain_1h: nil
}
send(lv.pid, {:postgres_packet, wrong_call})
Process.sleep(20)
assert Process.alive?(lv.pid)
end
test "ignores unknown messages without crashing", %{conn: conn} do
{:ok, lv, _html} = live(conn, ~p"/weather/UNKCALL", on_error: :warn)
send(lv.pid, :some_unknown_message)
Process.sleep(20)
assert Process.alive?(lv.pid)
end
end
end