aprs.me/test/aprsme_web/live/shared/packet_utils_test.exs
Graham McInitre 1006fdaefc refactor: remove dead code, duplication, and over-abstractions
- Delete dead config keys and test file (typo'd aprsme_is_*, vacuous disable test)
- Remove duplicate function clauses and identical function pairs
- Rename misleading one_hour_ago variable to one_day_ago
- Strip stale Oban comment
- Remove TestHelpers time function duplicates
- Inline Aprsme.Schema module (only 1 caller)
- Deduplicate prod esbuild config
- Thin SymbolRenderer delegations, inline TimeUtils wrappers
- Remove redundant DeploymentNotifier GenServer polling
- Replace random fallback in get_callsign_key with sentinel
2026-07-21 10:36:49 -05:00

332 lines
11 KiB
Elixir

defmodule AprsmeWeb.Live.Shared.PacketUtilsTest do
use ExUnit.Case, async: true
alias AprsmeWeb.Live.Shared.PacketUtils
describe "get_callsign_key/1" do
test "returns sender callsign when no object/item name" do
assert PacketUtils.get_callsign_key(%{sender: "K5ABC"}) == "K5ABC"
end
test "prefers object_name when present" do
packet = %{sender: "K5ABC", object_name: "REPEATER1", data_type: "object"}
assert PacketUtils.get_callsign_key(packet) == "REPEATER1"
end
test "prefers item_name when present" do
packet = %{sender: "K5ABC", item_name: "EVENT", data_type: "item"}
assert PacketUtils.get_callsign_key(packet) == "EVENT"
end
test "object_name wins over item_name" do
packet = %{object_name: "OBJ", item_name: "ITEM", sender: "K5ABC"}
assert PacketUtils.get_callsign_key(packet) == "OBJ"
end
test "returns __unknown__ for non-maps" do
assert PacketUtils.get_callsign_key(nil) == "__unknown__"
end
test "returns __unknown__ when every field is empty" do
packet = %{sender: "", object_name: "", item_name: ""}
assert PacketUtils.get_callsign_key(packet) == "__unknown__"
end
test "strips whitespace from object/item names" do
assert PacketUtils.get_callsign_key(%{object_name: " REPEATER "}) == "REPEATER"
end
end
describe "prune_oldest_packets/2" do
test "returns the same map when size is within limit" do
packets = %{"a" => %{}, "b" => %{}}
assert PacketUtils.prune_oldest_packets(packets, 10) == packets
end
test "keeps the newest packets by received_at DateTime" do
now = DateTime.utc_now()
older = DateTime.add(now, -3600, :second)
oldest = DateTime.add(now, -7200, :second)
packets = %{
"a" => %{received_at: now},
"b" => %{received_at: older},
"c" => %{received_at: oldest}
}
result = PacketUtils.prune_oldest_packets(packets, 2)
assert map_size(result) == 2
assert Map.has_key?(result, "a")
assert Map.has_key?(result, "b")
refute Map.has_key?(result, "c")
end
test "supports integer timestamps via string-keyed received_at" do
packets = %{
"a" => %{"received_at" => 1000},
"b" => %{"received_at" => 500}
}
result = PacketUtils.prune_oldest_packets(packets, 1)
assert Map.keys(result) == ["a"]
end
test "treats packets without received_at as oldest (timestamp 0)" do
now = DateTime.utc_now()
packets = %{"a" => %{received_at: now}, "b" => %{no_date: true}}
result = PacketUtils.prune_oldest_packets(packets, 1)
assert Map.has_key?(result, "a")
end
test "prunes to zero when max_count is 0 and size exceeds it" do
packets = %{"a" => %{received_at: DateTime.utc_now()}}
assert PacketUtils.prune_oldest_packets(packets, 0) == %{}
end
end
describe "parse_trail_duration/1" do
test "accepts known values" do
for hours <- [1, 6, 12, 24, 48, 168] do
assert PacketUtils.parse_trail_duration(to_string(hours)) == hours
end
end
test "rejects arbitrary numbers not in allowlist" do
assert PacketUtils.parse_trail_duration("3") == 1
assert PacketUtils.parse_trail_duration("100") == 1
assert PacketUtils.parse_trail_duration("-1") == 1
end
test "returns default for garbage and non-string input" do
assert PacketUtils.parse_trail_duration("abc") == 1
assert PacketUtils.parse_trail_duration("24abc") == 1
assert PacketUtils.parse_trail_duration(nil) == 1
assert PacketUtils.parse_trail_duration(24) == 1
end
end
describe "parse_historical_hours/1" do
test "accepts known values" do
for hours <- [1, 3, 6, 12, 24] do
assert PacketUtils.parse_historical_hours(to_string(hours)) == hours
end
end
test "rejects values not in allowlist" do
assert PacketUtils.parse_historical_hours("48") == 1
assert PacketUtils.parse_historical_hours("0") == 1
end
test "returns default for garbage" do
assert PacketUtils.parse_historical_hours(nil) == 1
assert PacketUtils.parse_historical_hours("abc") == 1
end
end
describe "get_packet_field/3" do
test "retrieves atom key" do
assert PacketUtils.get_packet_field(%{sender: "K5ABC"}, :sender) == "K5ABC"
end
test "falls back to string key" do
assert PacketUtils.get_packet_field(%{"sender" => "K5ABC"}, :sender) == "K5ABC"
end
test "falls back to data_extended atom key" do
assert PacketUtils.get_packet_field(%{data_extended: %{temperature: 72}}, :temperature) == 72
end
test "falls back to data_extended string key" do
assert PacketUtils.get_packet_field(%{"data_extended" => %{"humidity" => 50}}, :humidity) == 50
end
test "returns default when no match" do
assert PacketUtils.get_packet_field(%{}, :whatever, :fallback) == :fallback
end
test "defaults to nil when no default given" do
assert PacketUtils.get_packet_field(%{}, :whatever) == nil
end
test "handles nil data_extended gracefully" do
assert PacketUtils.get_packet_field(%{data_extended: nil}, :temperature, 0) == 0
end
end
describe "get_weather_field/2" do
test "reads from top-level atom key" do
assert PacketUtils.get_weather_field(%{temperature: 72}, :temperature) == 72
end
test "reads from data_extended" do
packet = %{data_extended: %{pressure: 1013}}
assert PacketUtils.get_weather_field(packet, :pressure) == 1013
end
test "returns nil when absent" do
assert PacketUtils.get_weather_field(%{}, :temperature) == nil
end
end
describe "has_weather_data?/1" do
test "detects weather data in top-level fields" do
assert PacketUtils.has_weather_data?(%{temperature: 72})
assert PacketUtils.has_weather_data?(%{humidity: 50})
end
test "detects weather data in data_extended" do
assert PacketUtils.has_weather_data?(%{data_extended: %{temperature: 72}})
end
test "returns false when no weather fields are present" do
refute PacketUtils.has_weather_data?(%{sender: "K5ABC"})
refute PacketUtils.has_weather_data?(%{})
end
end
describe "packet_within_time_threshold?/2" do
test "returns true when packet is after threshold" do
now = DateTime.utc_now()
older = DateTime.add(now, -3600, :second)
assert PacketUtils.packet_within_time_threshold?(%{received_at: now}, older)
end
test "returns true when packet equals threshold" do
now = DateTime.utc_now()
assert PacketUtils.packet_within_time_threshold?(%{received_at: now}, now)
end
test "returns false when packet is older than threshold" do
now = DateTime.utc_now()
older = DateTime.add(now, -3600, :second)
refute PacketUtils.packet_within_time_threshold?(%{received_at: older}, now)
end
test "treats packet with no received_at as within threshold" do
assert PacketUtils.packet_within_time_threshold?(%{}, DateTime.utc_now())
end
test "accepts integer epoch threshold" do
now = DateTime.utc_now()
older_epoch = DateTime.to_unix(now) - 3600
assert PacketUtils.packet_within_time_threshold?(%{received_at: now}, older_epoch)
end
test "accepts ISO8601 string threshold" do
now = DateTime.utc_now()
older = now |> DateTime.add(-3600, :second) |> DateTime.to_iso8601()
assert PacketUtils.packet_within_time_threshold?(%{received_at: now}, older)
end
end
describe "get_timestamp/1" do
test "returns ISO8601 for DateTime" do
dt = ~U[2024-01-15 12:00:00Z]
assert PacketUtils.get_timestamp(%{received_at: dt}) == "2024-01-15T12:00:00Z"
end
test "returns ISO8601 for NaiveDateTime" do
ndt = ~N[2024-01-15 12:00:00]
result = PacketUtils.get_timestamp(%{received_at: ndt})
assert result =~ "2024-01-15T12:00:00"
end
test "reformats ISO8601 strings" do
# Any valid ISO8601 string parses and re-emits.
assert PacketUtils.get_timestamp(%{received_at: "2024-01-15T12:00:00Z"}) ==
"2024-01-15T12:00:00Z"
end
test "passes through unparseable strings" do
assert PacketUtils.get_timestamp(%{received_at: "whenever"}) == "whenever"
end
test "returns empty string for nil or missing" do
assert PacketUtils.get_timestamp(%{received_at: nil}) == ""
assert PacketUtils.get_timestamp(%{}) == ""
end
end
describe "generate_callsign/1" do
test "joins base_callsign and ssid with a dash" do
assert PacketUtils.generate_callsign(%{base_callsign: "K5ABC", ssid: "9"}) == "K5ABC-9"
end
test "returns base_callsign only when ssid is nil" do
assert PacketUtils.generate_callsign(%{base_callsign: "K5ABC", ssid: nil}) == "K5ABC"
end
test "returns base_callsign only when ssid is empty" do
assert PacketUtils.generate_callsign(%{base_callsign: "K5ABC", ssid: ""}) == "K5ABC"
end
test "returns empty when base_callsign is missing" do
assert PacketUtils.generate_callsign(%{}) == ""
end
end
describe "display_name/1" do
test "prefers object_name over everything" do
packet = %{sender: "K5ABC", object_name: "REP", item_name: "ITEM"}
assert PacketUtils.display_name(packet) == "REP"
end
test "falls back to item_name when no object_name" do
packet = %{sender: "K5ABC", item_name: "ITEM"}
assert PacketUtils.display_name(packet) == "ITEM"
end
test "falls back to sender when neither is present" do
assert PacketUtils.display_name(%{sender: "K5ABC"}) == "K5ABC"
end
test "strips whitespace from names" do
assert PacketUtils.display_name(%{object_name: " REP "}) == "REP"
end
test "ignores blank object_name/item_name (treated as absent)" do
packet = %{sender: "K5ABC", object_name: " ", item_name: ""}
assert PacketUtils.display_name(packet) == "K5ABC"
end
end
describe "map_label/1" do
test "returns object_name for data_type=object" do
packet = %{data_type: "object", object_name: "REP", sender: "K5ABC"}
assert PacketUtils.map_label(packet) == "REP"
end
test "returns item_name for data_type=item" do
packet = %{data_type: "item", item_name: "EVENT", sender: "K5ABC"}
assert PacketUtils.map_label(packet) == "EVENT"
end
test "falls back to sender when object_name empty on object type" do
packet = %{data_type: "object", object_name: "", sender: "K5ABC"}
assert PacketUtils.map_label(packet) == "K5ABC"
end
test "returns sender for position packets" do
packet = %{data_type: "position", sender: "K5ABC"}
assert PacketUtils.map_label(packet) == "K5ABC"
end
end
describe "filter_packets_by_time_and_bounds/3" do
test "filters by both predicates" do
now = DateTime.utc_now()
older = DateTime.add(now, -3600, :second)
threshold = DateTime.add(now, -1800, :second)
bounds = %{north: 50.0, south: 30.0, east: -90.0, west: -110.0}
packets = %{
"in_and_recent" => %{lat: 40.0, lon: -100.0, received_at: now},
"in_but_old" => %{lat: 40.0, lon: -100.0, received_at: older},
"out_and_recent" => %{lat: 0.0, lon: 0.0, received_at: now}
}
result = PacketUtils.filter_packets_by_time_and_bounds(packets, bounds, threshold)
assert Map.keys(result) == ["in_and_recent"]
end
end
end