- SharedPacketHandler.handle_packet_update: dispatch on filter-result, and use maybe_enrich/2 dispatched on the enrich? boolean instead of an inner `if`. lookup_device_info/1 is now a trio of function heads. - Cluster.Topology.child_spec: build_spec/3 dispatches on (cluster_enabled?, topologies) tuples — no more nested `if`. - Cluster.PacketDistributor.distribute_packet: maybe_broadcast/2 dispatches on the leader-and-enabled check; the non-leader / disabled paths return :ok explicitly instead of relying on an `if` expression's implicit nil. Adds tests for SharedPacketHandler (filter/enrich/match paths, callsign_filter + callsign_and_weather_filter factories) and extends Cluster.Topology tests to cover the nil-topology and opts-merge cases. Coverage 66.41 → 66.81%.
148 lines
4.8 KiB
Elixir
148 lines
4.8 KiB
Elixir
defmodule AprsmeWeb.Live.SharedPacketHandlerTest do
|
|
use ExUnit.Case, async: true
|
|
|
|
alias AprsmeWeb.Live.SharedPacketHandler
|
|
|
|
defp socket, do: %Phoenix.LiveView.Socket{assigns: %{__changed__: %{}}}
|
|
|
|
defp allow_all, do: fn _packet, _socket -> true end
|
|
defp allow_none, do: fn _packet, _socket -> false end
|
|
|
|
defp record_process(ref),
|
|
do: fn packet, s ->
|
|
send(ref, {:processed, packet})
|
|
{:noreply, s}
|
|
end
|
|
|
|
describe "handle_packet_update/3" do
|
|
test "skips processing when filter returns false" do
|
|
socket = socket()
|
|
{result, ^socket} = {nil, socket}
|
|
_ = result
|
|
|
|
assert {:noreply, ^socket} =
|
|
SharedPacketHandler.handle_packet_update(
|
|
%{sender: "K5ABC"},
|
|
socket,
|
|
filter_fn: allow_none(),
|
|
process_fn: record_process(self())
|
|
)
|
|
|
|
refute_received {:processed, _}
|
|
end
|
|
|
|
test "processes and enriches packet when filter returns true" do
|
|
{:noreply, _} =
|
|
SharedPacketHandler.handle_packet_update(
|
|
%{sender: "K5ABC"},
|
|
socket(),
|
|
filter_fn: allow_all(),
|
|
process_fn: record_process(self())
|
|
)
|
|
|
|
assert_received {:processed, packet}
|
|
# Enrichment added nil device_* keys even for unknown identifier.
|
|
assert Map.has_key?(packet, :device_model)
|
|
end
|
|
|
|
test "skips enrichment when enrich_packet: false" do
|
|
{:noreply, _} =
|
|
SharedPacketHandler.handle_packet_update(
|
|
%{sender: "K5ABC"},
|
|
socket(),
|
|
filter_fn: allow_all(),
|
|
process_fn: record_process(self()),
|
|
enrich_packet: false
|
|
)
|
|
|
|
assert_received {:processed, packet}
|
|
refute Map.has_key?(packet, :device_model)
|
|
end
|
|
end
|
|
|
|
describe "packet_matches_callsign?/2" do
|
|
test "matches on atom :sender key" do
|
|
assert SharedPacketHandler.packet_matches_callsign?(%{sender: "K5ABC"}, "K5ABC")
|
|
end
|
|
|
|
test "matches on string 'sender' key" do
|
|
assert SharedPacketHandler.packet_matches_callsign?(%{"sender" => "K5ABC"}, "K5ABC")
|
|
end
|
|
|
|
test "string key wins over atom key when both present" do
|
|
# The implementation prefers the string key via Map.get fallback ordering.
|
|
assert SharedPacketHandler.packet_matches_callsign?(
|
|
%{"sender" => "K5ABC", sender: "OTHER"},
|
|
"K5ABC"
|
|
)
|
|
end
|
|
|
|
test "comparison is case-insensitive via Callsign.matches?" do
|
|
assert SharedPacketHandler.packet_matches_callsign?(%{sender: "k5abc"}, "K5ABC")
|
|
end
|
|
|
|
test "returns false when mismatched" do
|
|
refute SharedPacketHandler.packet_matches_callsign?(%{sender: "K5ABC"}, "W1XYZ")
|
|
end
|
|
end
|
|
|
|
describe "has_weather_data?/1" do
|
|
test "returns true when at least one weather field is present" do
|
|
assert SharedPacketHandler.has_weather_data?(%{temperature: 72.0})
|
|
assert SharedPacketHandler.has_weather_data?(%{"humidity" => 50})
|
|
end
|
|
|
|
test "returns false when no weather fields are present" do
|
|
refute SharedPacketHandler.has_weather_data?(%{sender: "K5ABC"})
|
|
refute SharedPacketHandler.has_weather_data?(%{})
|
|
end
|
|
end
|
|
|
|
describe "enrich_with_device_info/1" do
|
|
test "adds nil device_* keys when no device identifier" do
|
|
result = SharedPacketHandler.enrich_with_device_info(%{sender: "K5ABC"})
|
|
assert result.device_model == nil
|
|
assert result.device_vendor == nil
|
|
assert result.device_contact == nil
|
|
assert result.device_class == nil
|
|
end
|
|
|
|
test "still adds nil device_* when device_identifier is empty string" do
|
|
result = SharedPacketHandler.enrich_with_device_info(%{device_identifier: ""})
|
|
assert result.device_model == nil
|
|
end
|
|
end
|
|
|
|
describe "enrich_packets_with_device_info/1" do
|
|
test "enriches each packet in the list" do
|
|
packets = [
|
|
%{sender: "A", device_identifier: nil},
|
|
%{sender: "B", device_identifier: ""},
|
|
%{sender: "C", device_identifier: "APXYZ"}
|
|
]
|
|
|
|
enriched = SharedPacketHandler.enrich_packets_with_device_info(packets)
|
|
assert length(enriched) == 3
|
|
assert Enum.all?(enriched, &Map.has_key?(&1, :device_model))
|
|
end
|
|
|
|
test "handles empty list" do
|
|
assert SharedPacketHandler.enrich_packets_with_device_info([]) == []
|
|
end
|
|
end
|
|
|
|
describe "filter factories" do
|
|
test "callsign_filter matches packets by sender" do
|
|
f = SharedPacketHandler.callsign_filter("K5ABC")
|
|
assert f.(%{sender: "K5ABC"}, :any)
|
|
refute f.(%{sender: "W1XYZ"}, :any)
|
|
end
|
|
|
|
test "callsign_and_weather_filter requires both" do
|
|
f = SharedPacketHandler.callsign_and_weather_filter("K5ABC")
|
|
refute f.(%{sender: "K5ABC"}, :any)
|
|
refute f.(%{sender: "OTHER", temperature: 72.0}, :any)
|
|
assert f.(%{sender: "K5ABC", temperature: 72.0}, :any)
|
|
end
|
|
end
|
|
end
|