From 8dad09e27d019e3188995e157ce8e3adadba0272 Mon Sep 17 00:00:00 2001 From: Graham McIntire Date: Fri, 24 Apr 2026 08:17:22 -0500 Subject: [PATCH] fix(packet_replay): order sanitize_value clauses; update state on stop; test send_packet paths --- lib/aprsme/packet_replay.ex | 20 +++++---- test/aprsme/packet_replay_test.exs | 67 ++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+), 9 deletions(-) diff --git a/lib/aprsme/packet_replay.ex b/lib/aprsme/packet_replay.ex index fb6fcaa..aedb91b 100644 --- a/lib/aprsme/packet_replay.ex +++ b/lib/aprsme/packet_replay.ex @@ -273,7 +273,7 @@ defmodule Aprsme.PacketReplay do message: "Replay complete" }) - {:stop, :normal, state} + {:stop, :normal, %{state | packets_sent: new_packets_sent}} end end @@ -425,14 +425,6 @@ defmodule Aprsme.PacketReplay do end) end - defp sanitize_value(value) when is_map(value) do - sanitize_map_values(value) - end - - defp sanitize_value(value) when is_list(value) do - Enum.map(value, &sanitize_value/1) - end - defp sanitize_value(%DateTime{} = dt) do DateTime.to_iso8601(dt) end @@ -445,6 +437,16 @@ defmodule Aprsme.PacketReplay do Decimal.to_float(d) end + defp sanitize_value(%_{} = _struct), do: nil + + defp sanitize_value(value) when is_map(value) do + sanitize_map_values(value) + end + + defp sanitize_value(value) when is_list(value) do + Enum.map(value, &sanitize_value/1) + end + defp sanitize_value(value) do value end diff --git a/test/aprsme/packet_replay_test.exs b/test/aprsme/packet_replay_test.exs index 610f550..b0794fe 100644 --- a/test/aprsme/packet_replay_test.exs +++ b/test/aprsme/packet_replay_test.exs @@ -332,6 +332,73 @@ defmodule Aprsme.PacketReplayTest do end end + describe "handle_info({:send_packet, packet, stream}) active path" do + test "broadcasts a packet, increments counter, and stops when stream is empty" do + {:ok, state} = PacketReplay.init(user_id: "send_empty", bounds: [0, 0, 1, 1]) + + receive do + :start_replay -> :ok + after + 0 -> :ok + end + + packet = %Aprsme.Packet{ + sender: "SENDPKT1", + base_callsign: "SENDPKT1", + ssid: "0", + received_at: DateTime.utc_now(), + has_position: true + } + + empty_stream = Stream.unfold(nil, fn _ -> nil end) + + assert {:stop, :normal, new_state} = + PacketReplay.handle_info({:send_packet, packet, empty_stream}, state) + + assert new_state.packets_sent == 1 + end + + test "broadcasts and schedules next when stream has more packets" do + {:ok, state} = PacketReplay.init(user_id: "send_more", bounds: [0, 0, 1, 1]) + + receive do + :start_replay -> :ok + after + 0 -> :ok + end + + packet = %Aprsme.Packet{ + sender: "SENDPKT2", + base_callsign: "SENDPKT2", + ssid: "0", + received_at: DateTime.utc_now(), + has_position: true + } + + next_packet = %Aprsme.Packet{ + sender: "NEXT", + base_callsign: "NEXT", + ssid: "0", + received_at: DateTime.add(DateTime.utc_now(), 1, :second), + has_position: true + } + + # Simple stream: one element, then done. + stream = + Stream.unfold([{0.0, next_packet}], fn + [h | t] -> {h, t} + [] -> nil + end) + + assert {:noreply, new_state} = + PacketReplay.handle_info({:send_packet, packet, stream}, state) + + assert new_state.packets_sent == 1 + assert is_reference(new_state.replay_timer) + Process.cancel_timer(new_state.replay_timer) + end + end + describe "handle_info(:start_replay, ...)" do test "broadcasts replay_started then stops when no matching packets" do {:ok, state} = PacketReplay.init(user_id: "empty_stream", bounds: [0, 0, 1, 1])