- Fix leader election guard test for :undefined pid - Fix cpu_sup.util() pattern matching to handle numeric return - Fix encoding_utils to always return explicit nil when needed - Remove unused finite_float? function - Fix signal handler to match :ok return from :os.set_signal/2 - Remove redundant catch-all pattern in database metrics - Fix Gridsquare pattern matching in info_live template - Remove unnecessary nil check for calculate_course result - Fix get_packet_received_at to not check for nil (always returns DateTime) - Remove redundant catch-all pattern in weather format_weather_value - Fix query builder to use from(p in Packet) instead of bare Packet atom Reduced dialyzer errors from 49 to 6. Remaining warnings are mostly false positives from template compilation and overloaded function specs. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
407 lines
11 KiB
Elixir
407 lines
11 KiB
Elixir
defmodule AprsmeWeb.MapLive.PacketUtilsTest do
|
|
use ExUnit.Case, async: true
|
|
|
|
alias AprsmeWeb.MapLive.PacketUtils
|
|
|
|
describe "weather popup generation" do
|
|
test "generates weather popup with actual weather data" do
|
|
packet = %{
|
|
id: 1,
|
|
base_callsign: "TEST",
|
|
sender: "TEST-1",
|
|
ssid: "1",
|
|
data_type: "position",
|
|
symbol_table_id: "/",
|
|
symbol_code: "_",
|
|
lat: 32.7767,
|
|
lon: -96.7970,
|
|
received_at: DateTime.utc_now(),
|
|
temperature: 72.0,
|
|
humidity: 45.0,
|
|
wind_direction: 180,
|
|
wind_speed: 10.0,
|
|
wind_gust: 15.0,
|
|
pressure: 1013.2,
|
|
rain_1h: 0.0,
|
|
rain_24h: 0.1,
|
|
rain_since_midnight: 0.2
|
|
}
|
|
|
|
result = PacketUtils.build_packet_data(packet, true)
|
|
|
|
assert result
|
|
assert result["popup"]
|
|
|
|
popup = result["popup"]
|
|
|
|
# Check that it's a weather popup
|
|
assert popup =~ "Weather Report"
|
|
assert popup =~ "data-timestamp="
|
|
|
|
# Check weather data is present
|
|
assert popup =~ "Temperature: 72.0°F"
|
|
assert popup =~ "Humidity: 45.0%"
|
|
assert popup =~ "Wind: 180° at 10.0 mph, gusts to 15.0 mph"
|
|
assert popup =~ "Pressure: 1013.2 hPa"
|
|
assert popup =~ "Rain (1h): 0.0 in"
|
|
assert popup =~ "Rain (24h): 0.1 in"
|
|
assert popup =~ "Rain (since midnight): 0.2 in"
|
|
|
|
# Check callsign and links
|
|
assert popup =~ "TEST-1"
|
|
assert popup =~ "/TEST-1"
|
|
assert popup =~ "/info/TEST-1"
|
|
assert popup =~ "/weather/TEST-1"
|
|
assert popup =~ "weather charts"
|
|
end
|
|
|
|
test "generates weather popup with N/A for missing weather data" do
|
|
packet = %{
|
|
id: 1,
|
|
base_callsign: "TEST",
|
|
sender: "TEST-1",
|
|
ssid: "1",
|
|
data_type: "position",
|
|
symbol_table_id: "/",
|
|
symbol_code: "_",
|
|
lat: 32.7767,
|
|
lon: -96.7970,
|
|
received_at: DateTime.utc_now(),
|
|
# Add at least one non-nil weather field to make it a weather packet
|
|
temperature: 75,
|
|
humidity: nil,
|
|
pressure: nil,
|
|
wind_speed: nil,
|
|
wind_direction: nil,
|
|
wind_gust: nil,
|
|
rain_1h: nil,
|
|
rain_24h: nil,
|
|
rain_since_midnight: nil
|
|
}
|
|
|
|
result = PacketUtils.build_packet_data(packet, true)
|
|
|
|
assert result
|
|
assert result["popup"]
|
|
|
|
popup = result["popup"]
|
|
|
|
# Check that it's a weather popup
|
|
assert popup =~ "Weather Report"
|
|
assert popup =~ "data-timestamp="
|
|
|
|
# Check temperature is shown, others show N/A
|
|
assert popup =~ "Temperature: 75°F"
|
|
assert popup =~ "Humidity: N/A%"
|
|
assert popup =~ "Wind: N/A° at N/A mph, gusts to N/A mph"
|
|
assert popup =~ "Pressure: N/A hPa"
|
|
assert popup =~ "Rain (1h): N/A in"
|
|
assert popup =~ "Rain (24h): N/A in"
|
|
assert popup =~ "Rain (since midnight): N/A in"
|
|
end
|
|
|
|
test "generates weather popup with partial weather data" do
|
|
packet = %{
|
|
id: 1,
|
|
base_callsign: "TEST",
|
|
sender: "TEST-1",
|
|
ssid: "1",
|
|
data_type: "position",
|
|
symbol_table_id: "/",
|
|
symbol_code: "_",
|
|
lat: 32.7767,
|
|
lon: -96.7970,
|
|
received_at: DateTime.utc_now(),
|
|
temperature: 85.0,
|
|
humidity: 60.0
|
|
# Missing wind, pressure, rain data
|
|
}
|
|
|
|
result = PacketUtils.build_packet_data(packet, true)
|
|
|
|
assert result
|
|
assert result["popup"]
|
|
|
|
popup = result["popup"]
|
|
|
|
# Check that it's a weather popup
|
|
assert popup =~ "Weather Report"
|
|
assert popup =~ "data-timestamp="
|
|
|
|
# Check available weather data
|
|
assert popup =~ "Temperature: 85.0°F"
|
|
assert popup =~ "Humidity: 60.0%"
|
|
|
|
# Check missing data shows N/A
|
|
assert popup =~ "Wind: N/A° at N/A mph, gusts to N/A mph"
|
|
assert popup =~ "Pressure: N/A hPa"
|
|
assert popup =~ "Rain (1h): N/A in"
|
|
assert popup =~ "Rain (24h): N/A in"
|
|
assert popup =~ "Rain (since midnight): N/A in"
|
|
end
|
|
|
|
test "generates standard popup for non-weather packets" do
|
|
packet = %{
|
|
id: 1,
|
|
base_callsign: "TEST",
|
|
sender: "TEST-1",
|
|
ssid: "1",
|
|
data_type: "position",
|
|
symbol_table_id: "/",
|
|
symbol_code: ">",
|
|
lat: 32.7767,
|
|
lon: -96.7970,
|
|
received_at: DateTime.utc_now(),
|
|
comment: "Test comment"
|
|
}
|
|
|
|
result = PacketUtils.build_packet_data(packet, true)
|
|
|
|
assert result
|
|
assert result["popup"]
|
|
|
|
popup = result["popup"]
|
|
|
|
# Check that it's NOT a weather popup
|
|
refute popup =~ "Weather Report"
|
|
# All popups now have data-timestamp for cache busting
|
|
assert popup =~ "data-timestamp="
|
|
|
|
# Check standard popup content
|
|
assert popup =~ "TEST-1"
|
|
assert popup =~ "/TEST-1"
|
|
assert popup =~ "/info/TEST-1"
|
|
assert popup =~ "Test comment"
|
|
end
|
|
|
|
test "generates weather popup with timestamp" do
|
|
received_at = DateTime.utc_now()
|
|
|
|
packet = %{
|
|
id: 1,
|
|
base_callsign: "TEST",
|
|
sender: "TEST-1",
|
|
ssid: "1",
|
|
data_type: "position",
|
|
symbol_table_id: "/",
|
|
symbol_code: "_",
|
|
lat: 32.7767,
|
|
lon: -96.7970,
|
|
received_at: received_at,
|
|
temperature: 72.0,
|
|
humidity: 45.0
|
|
}
|
|
|
|
result = PacketUtils.build_packet_data(packet, true)
|
|
|
|
assert result
|
|
assert result["popup"]
|
|
|
|
popup = result["popup"]
|
|
|
|
# Check timestamp is included
|
|
assert popup =~ "aprs-timestamp"
|
|
assert popup =~ Calendar.strftime(received_at, "%Y-%m-%d %H:%M:%S UTC")
|
|
end
|
|
|
|
test "handles weather data from data_extended field" do
|
|
packet = %{
|
|
id: 1,
|
|
base_callsign: "TEST",
|
|
sender: "TEST-1",
|
|
ssid: "1",
|
|
data_type: "position",
|
|
symbol_table_id: "/",
|
|
symbol_code: "_",
|
|
lat: 32.7767,
|
|
lon: -96.7970,
|
|
received_at: DateTime.utc_now(),
|
|
data_extended: %{
|
|
temperature: 75.0,
|
|
humidity: 50.0,
|
|
wind_direction: 270,
|
|
wind_speed: 12.0,
|
|
pressure: 1015.0
|
|
}
|
|
}
|
|
|
|
result = PacketUtils.build_packet_data(packet, true)
|
|
|
|
assert result
|
|
assert result["popup"]
|
|
|
|
popup = result["popup"]
|
|
|
|
# Check weather data from data_extended
|
|
assert popup =~ "Temperature: 75.0°F"
|
|
assert popup =~ "Humidity: 50.0%"
|
|
assert popup =~ "Wind: 270° at 12.0 mph"
|
|
assert popup =~ "Pressure: 1015.0 hPa"
|
|
end
|
|
|
|
test "handles weather data with string keys in data_extended" do
|
|
packet = %{
|
|
id: 1,
|
|
base_callsign: "TEST",
|
|
sender: "TEST-1",
|
|
ssid: "1",
|
|
data_type: "position",
|
|
symbol_table_id: "/",
|
|
symbol_code: "_",
|
|
lat: 32.7767,
|
|
lon: -96.7970,
|
|
received_at: DateTime.utc_now(),
|
|
data_extended: %{
|
|
"temperature" => 80.0,
|
|
"humidity" => 55.0,
|
|
"wind_direction" => 90,
|
|
"wind_speed" => 8.0
|
|
}
|
|
}
|
|
|
|
result = PacketUtils.build_packet_data(packet, true)
|
|
|
|
assert result
|
|
assert result["popup"]
|
|
|
|
popup = result["popup"]
|
|
|
|
# Check weather data from data_extended with string keys
|
|
assert popup =~ "Temperature: 80.0°F"
|
|
assert popup =~ "Humidity: 55.0%"
|
|
assert popup =~ "Wind: 90° at 8.0 mph"
|
|
end
|
|
end
|
|
|
|
describe "weather packet detection" do
|
|
test "identifies weather packets by weather data fields" do
|
|
weather_packet = %{
|
|
data_type: "position",
|
|
symbol_table_id: "/",
|
|
symbol_code: "_",
|
|
temperature: 75
|
|
}
|
|
|
|
assert PacketUtils.weather_packet?(weather_packet)
|
|
end
|
|
|
|
test "identifies weather packets with humidity" do
|
|
weather_packet = %{
|
|
data_type: "weather",
|
|
symbol_table_id: "/",
|
|
symbol_code: ">",
|
|
humidity: 80
|
|
}
|
|
|
|
assert PacketUtils.weather_packet?(weather_packet)
|
|
end
|
|
|
|
test "does not identify non-weather packets" do
|
|
regular_packet = %{
|
|
data_type: "position",
|
|
symbol_table_id: "/",
|
|
symbol_code: ">"
|
|
}
|
|
|
|
refute PacketUtils.weather_packet?(regular_packet)
|
|
end
|
|
|
|
test "has_weather_packets? returns false for non-existent callsign" do
|
|
# This should return false since the database call will fail in test context
|
|
refute PacketUtils.has_weather_packets?("NONEXISTENT")
|
|
end
|
|
|
|
test "has_weather_packets? handles invalid input gracefully" do
|
|
refute PacketUtils.has_weather_packets?(nil)
|
|
refute PacketUtils.has_weather_packets?("")
|
|
refute PacketUtils.has_weather_packets?(123)
|
|
end
|
|
end
|
|
|
|
describe "weather field extraction" do
|
|
test "extracts weather fields from packet" do
|
|
packet = %{
|
|
temperature: 72.0,
|
|
humidity: 45.0,
|
|
wind_direction: 180,
|
|
wind_speed: 10.0
|
|
}
|
|
|
|
assert PacketUtils.get_weather_field(packet, :temperature) == 72.0
|
|
assert PacketUtils.get_weather_field(packet, :humidity) == 45.0
|
|
assert PacketUtils.get_weather_field(packet, :wind_direction) == 180
|
|
assert PacketUtils.get_weather_field(packet, :wind_speed) == 10.0
|
|
end
|
|
|
|
test "extracts weather fields from data_extended" do
|
|
packet = %{
|
|
data_extended: %{
|
|
temperature: 75.0,
|
|
humidity: 50.0
|
|
}
|
|
}
|
|
|
|
assert PacketUtils.get_weather_field(packet, :temperature) == 75.0
|
|
assert PacketUtils.get_weather_field(packet, :humidity) == 50.0
|
|
end
|
|
|
|
test "returns N/A for missing weather fields" do
|
|
packet = %{}
|
|
|
|
assert PacketUtils.get_weather_field(packet, :temperature) == "N/A"
|
|
assert PacketUtils.get_weather_field(packet, :humidity) == "N/A"
|
|
assert PacketUtils.get_weather_field(packet, :wind_direction) == "N/A"
|
|
end
|
|
|
|
test "handles string keys in data_extended" do
|
|
packet = %{
|
|
data_extended: %{
|
|
"temperature" => 80.0,
|
|
"humidity" => 55.0
|
|
}
|
|
}
|
|
|
|
assert PacketUtils.get_weather_field(packet, :temperature) == 80.0
|
|
assert PacketUtils.get_weather_field(packet, :humidity) == 55.0
|
|
end
|
|
|
|
test "handles string keys in packet" do
|
|
packet = %{
|
|
"temperature" => 85.0,
|
|
"humidity" => 60.0
|
|
}
|
|
|
|
assert PacketUtils.get_weather_field(packet, :temperature) == 85.0
|
|
assert PacketUtils.get_weather_field(packet, :humidity) == 60.0
|
|
end
|
|
end
|
|
|
|
describe "callsign generation" do
|
|
test "generates callsign with SSID" do
|
|
packet = %{
|
|
base_callsign: "TEST",
|
|
ssid: "1"
|
|
}
|
|
|
|
assert PacketUtils.generate_callsign(packet) == "TEST-1"
|
|
end
|
|
|
|
test "generates callsign without SSID" do
|
|
packet = %{
|
|
base_callsign: "TEST",
|
|
ssid: nil
|
|
}
|
|
|
|
assert PacketUtils.generate_callsign(packet) == "TEST"
|
|
end
|
|
|
|
test "generates callsign with empty SSID" do
|
|
packet = %{
|
|
base_callsign: "TEST",
|
|
ssid: ""
|
|
}
|
|
|
|
assert PacketUtils.generate_callsign(packet) == "TEST"
|
|
end
|
|
end
|
|
end
|