From b911bbd62614b4a257cbe2c4a39810c903f9b995 Mon Sep 17 00:00:00 2001 From: Graham McIntire Date: Fri, 8 May 2026 16:54:26 -0500 Subject: [PATCH] Add coverage tests for DeviceCache live process branch, PacketConsumer start_link with name, and integer/Decimal coordinate paths --- test/aprsme/device_cache_test.exs | 22 ++++++++++ test/aprsme/packet_consumer_test.exs | 64 ++++++++++++++++++++++++++++ test/aprsme/packets_test.exs | 47 ++++++++++++++++++++ 3 files changed, 133 insertions(+) diff --git a/test/aprsme/device_cache_test.exs b/test/aprsme/device_cache_test.exs index f5f4c66..c5adb30 100644 --- a/test/aprsme/device_cache_test.exs +++ b/test/aprsme/device_cache_test.exs @@ -100,6 +100,28 @@ defmodule Aprsme.DeviceCacheTest do assert {:noreply, ^state} = DeviceCache.handle_info(:refresh_cache, state) end + test "refresh_cache/0 calls into the running GenServer process" do + # Start a temporary DeviceCache to exercise the live-process branch + # (line 51: _pid -> GenServer.call(...)). + {:ok, pid} = GenServer.start_link(DeviceCache, []) + + original_pid = Process.whereis(DeviceCache) + if original_pid, do: :erlang.unregister(DeviceCache) + Process.register(pid, DeviceCache) + + try do + assert DeviceCache.refresh_cache() == :ok + after + if Process.whereis(DeviceCache) == pid, do: :erlang.unregister(DeviceCache) + + if original_pid && !Process.whereis(DeviceCache) do + Process.register(original_pid, DeviceCache) + end + + Process.exit(pid, :normal) + end + end + test "initial_load in non-test env loads real devices from the DB" do # The load_devices_into_cache non-test branch reads Repo.all(Devices). # Swap env, then trigger and restore. diff --git a/test/aprsme/packet_consumer_test.exs b/test/aprsme/packet_consumer_test.exs index 74a39a8..e85fa04 100644 --- a/test/aprsme/packet_consumer_test.exs +++ b/test/aprsme/packet_consumer_test.exs @@ -7,6 +7,70 @@ defmodule Aprsme.PacketConsumerTest do alias Aprsme.PacketConsumer alias Aprsme.StreamingPacketsPubSub + describe "start_link/1" do + test "starts an unnamed GenStage consumer when no :name option is given" do + assert {:ok, pid} = PacketConsumer.start_link(subscribe_to: []) + assert is_pid(pid) + GenStage.stop(pid) + end + + test "starts a named GenStage consumer when :name is provided" do + name = :"test_consumer_#{System.unique_integer([:positive])}" + assert {:ok, pid} = PacketConsumer.start_link(name: name, subscribe_to: []) + assert Process.whereis(name) == pid + GenStage.stop(pid) + end + end + + describe "normalize_numeric_types via handle_events with integer weather field" do + test "integer temperature is coerced to float by normalize_numeric_types" do + events = [ + %{ + sender: "INT-T", + base_callsign: "INT-T", + ssid: "0", + destination: "APRS", + path: "WIDE1-1", + data_type: "position", + lat: 33.0, + lon: -96.0, + # Integer weather value should be coerced to float (line 774). + temperature: 72, + humidity: 50, + wind_speed: 5, + information_field: "test", + raw_packet: "INT-T>APRS" + } + ] + + state = %{batch: [], batch_length: 0, batch_size: 1, batch_timeout: 1000, max_batch_size: 100, timer: nil} + {:noreply, [], _new_state} = PacketConsumer.handle_events(events, nil, state) + Process.sleep(50) + end + + test "ssid passed as integer gets normalized to string by normalize_ssid" do + events = [ + %{ + sender: "SSID-INT", + base_callsign: "SSID-INT", + # Integer ssid hits the to_string branch in normalize_ssid (line 737). + ssid: 9, + destination: "APRS", + path: "WIDE1-1", + data_type: "position", + lat: 33.0, + lon: -96.0, + information_field: "test", + raw_packet: "SSID-INT>APRS" + } + ] + + state = %{batch: [], batch_length: 0, batch_size: 1, batch_timeout: 1000, max_batch_size: 100, timer: nil} + {:noreply, [], _new_state} = PacketConsumer.handle_events(events, nil, state) + Process.sleep(50) + end + end + describe "init/1" do test "returns a :consumer tuple with default configuration" do # Start an isolated consumer with no subscription to avoid contaminating diff --git a/test/aprsme/packets_test.exs b/test/aprsme/packets_test.exs index f755f05..96ab79f 100644 --- a/test/aprsme/packets_test.exs +++ b/test/aprsme/packets_test.exs @@ -1812,6 +1812,53 @@ defmodule Aprsme.PacketsTest do end end + describe "round_coordinate via store_packet with various input types" do + test "store_packet with Decimal lat/lon hits round_coordinate(%Decimal{})" do + packet = %{ + sender: "DEC-COORD", + base_callsign: "DEC-COORD", + ssid: "0", + destination: "APRS", + raw_packet: "DEC-COORD>APRS", + data_type: "position", + lat: Decimal.new("33.123456789"), + lon: Decimal.new("-96.987654321") + } + + assert {:ok, _} = Packets.store_packet(packet) + end + + test "store_packet with integer lat/lon hits round_coordinate(integer)" do + packet = %{ + sender: "INT-COORD", + base_callsign: "INT-COORD", + ssid: "0", + destination: "APRS", + raw_packet: "INT-COORD>APRS", + data_type: "position", + lat: 33, + lon: -96 + } + + assert {:ok, _} = Packets.store_packet(packet) + end + + test "store_packet with valid binary lat/lon hits round_coordinate(binary) parse path" do + packet = %{ + sender: "BIN-COORD", + base_callsign: "BIN-COORD", + ssid: "0", + destination: "APRS", + raw_packet: "BIN-COORD>APRS", + data_type: "position", + lat: "33.5", + lon: "-96.5" + } + + assert {:ok, _} = Packets.store_packet(packet) + end + end + describe "store_bad_packet/2 with various error shapes" do test "store_bad_packet/2 with binary packet_data and a struct error uses Exception.message" do result = Packets.store_bad_packet("rawpacket-binary", %ArgumentError{message: "boom"})