From 5ba458d505978c57b1f8cacb9d844fb2ef91a123 Mon Sep 17 00:00:00 2001 From: Graham McIntire Date: Fri, 8 May 2026 16:50:44 -0500 Subject: [PATCH] Add coverage tests for clear_and_reload_markers, cleanup_old_packets, packet_batch low-zoom paths, and historical_loading_timeout --- .../live/map_live/integration_test.exs | 207 ++++++++++++++++++ 1 file changed, 207 insertions(+) diff --git a/test/aprsme_web/live/map_live/integration_test.exs b/test/aprsme_web/live/map_live/integration_test.exs index b5639cc..28b0191 100644 --- a/test/aprsme_web/live/map_live/integration_test.exs +++ b/test/aprsme_web/live/map_live/integration_test.exs @@ -688,6 +688,213 @@ defmodule AprsmeWeb.MapLive.IntegrationTest do assert render(view) =~ "aprs-map" end + test "clear_and_reload_markers with tracked callsign + visible packets exercises filter_with_tracked", %{conn: conn} do + packet_fixture(%{ + sender: "CRMV", + base_callsign: "CRMV", + ssid: "0", + received_at: DateTime.utc_now(), + lat: Decimal.new("33.0"), + lon: Decimal.new("-96.0"), + has_position: true, + path: "WIDE1-1" + }) + + {:ok, view, _html} = live(conn, "/?call=CRMV&lat=33.0&lng=-96.0&z=12", on_error: :warn) + render_hook(view, "map_ready", %{}) + + # Pre-populate visible_packets and tracked_callsign_latest_packet so the + # clear_and_reload handler exercises the filter_with_tracked path. + tracked = %{ + sender: "CRMV", + base_callsign: "CRMV", + ssid: "0", + lat: 33.0, + lon: -96.0, + has_position: true, + received_at: DateTime.utc_now() + } + + visible = %{ + "CRMV-0" => tracked, + "OTHER-0" => %{ + sender: "OTHER", + base_callsign: "OTHER", + ssid: "0", + lat: 33.5, + lon: -96.5, + has_position: true, + received_at: DateTime.utc_now() + } + } + + :sys.replace_state(view.pid, fn channel_state -> + inner_socket = channel_state.socket + + new_socket = %{ + inner_socket + | assigns: + inner_socket.assigns + |> Map.put(:visible_packets, visible) + |> Map.put(:tracked_callsign, "CRMV") + |> Map.put(:tracked_callsign_latest_packet, tracked) + } + + %{channel_state | socket: new_socket} + end) + + assert render_hook(view, "clear_and_reload_markers", %{}) + Process.sleep(50) + + # Also at low zoom — exercises the heat map branch. + :sys.replace_state(view.pid, fn channel_state -> + inner_socket = channel_state.socket + new_socket = %{inner_socket | assigns: Map.put(inner_socket.assigns, :map_zoom, 6)} + %{channel_state | socket: new_socket} + end) + + assert render_hook(view, "clear_and_reload_markers", %{}) + end + + test "{:packet_batch, _} at low zoom (untracked) triggers heat map branch", %{conn: conn} do + {:ok, view, _html} = live(conn, "/?lat=33.0&lng=-96.0&z=6", on_error: :warn) + render_hook(view, "map_ready", %{}) + + render_hook(view, "bounds_changed", %{ + "bounds" => %{"north" => 40.0, "south" => 30.0, "east" => -90.0, "west" => -100.0} + }) + + packets = [ + %{ + id: "lz-1", + sender: "LZ-1", + base_callsign: "LZ", + ssid: "1", + lat: 33.0, + lon: -96.0, + has_position: true, + symbol_table_id: "/", + symbol_code: ">", + received_at: DateTime.utc_now(), + path: "" + } + ] + + send(view.pid, {:packet_batch, packets}) + Process.sleep(50) + assert render(view) =~ "aprs-map" + end + + test "{:packet_batch, _} at low zoom with tracked callsign triggers trail-line branch", %{conn: conn} do + packet_fixture(%{ + sender: "LZTR", + base_callsign: "LZTR", + ssid: "0", + received_at: DateTime.utc_now(), + lat: Decimal.new("33.0"), + lon: Decimal.new("-96.0"), + has_position: true, + path: "WIDE1-1" + }) + + {:ok, view, _html} = live(conn, "/?call=LZTR&lat=33.0&lng=-96.0&z=6", on_error: :warn) + render_hook(view, "map_ready", %{}) + + packets = [ + %{ + id: "lztr-1", + sender: "LZTR", + base_callsign: "LZTR", + ssid: "0", + lat: 33.0, + lon: -96.0, + has_position: true, + symbol_table_id: "/", + symbol_code: ">", + received_at: DateTime.utc_now(), + path: "" + } + ] + + send(view.pid, {:packet_batch, packets}) + Process.sleep(50) + assert render(view) =~ "aprs-map" + end + + test "{:historical_loading_timeout, generation} matching with historical_loading=true forces completion", %{ + conn: conn + } do + {:ok, view, _html} = live(conn, "/?lat=33.0&lng=-96.0&z=10", on_error: :warn) + render_hook(view, "map_ready", %{}) + + :sys.replace_state(view.pid, fn channel_state -> + inner_socket = channel_state.socket + + new_socket = %{ + inner_socket + | assigns: + inner_socket.assigns + |> Map.put(:historical_loading, true) + |> Map.put(:loading_generation, 42) + |> Map.put(:total_batches, 5) + } + + %{channel_state | socket: new_socket} + end) + + send(view.pid, {:historical_loading_timeout, 42}) + Process.sleep(50) + assert render(view) =~ "aprs-map" + end + + test "cleanup_old_packets handler with expired packets in visible_packets prunes them", %{conn: conn} do + {:ok, view, _html} = live(conn, "/?lat=33.0&lng=-96.0&z=10", on_error: :warn) + render_hook(view, "map_ready", %{}) + + # Seed visible_packets with one fresh and one stale packet. + now = DateTime.utc_now() + old = DateTime.add(now, -3600, :second) + + visible = %{ + "FRESH-0" => %{ + sender: "FRESH", + base_callsign: "FRESH", + ssid: "0", + lat: 33.0, + lon: -96.0, + has_position: true, + received_at: now + }, + "STALE-0" => %{ + sender: "STALE", + base_callsign: "STALE", + ssid: "0", + lat: 33.0, + lon: -96.0, + has_position: true, + received_at: old + } + } + + :sys.replace_state(view.pid, fn channel_state -> + inner_socket = channel_state.socket + + new_socket = %{ + inner_socket + | assigns: + inner_socket.assigns + |> Map.put(:visible_packets, visible) + |> Map.put(:packet_age_threshold, DateTime.add(now, -1800, :second)) + } + + %{channel_state | socket: new_socket} + end) + + send(view.pid, :cleanup_old_packets) + Process.sleep(50) + assert render(view) =~ "aprs-map" + end + test "tracked-callsign with positionless incoming exercises preferred_tracked_packet branches", %{conn: conn} do # Set up a tracked callsign with a current packet that has position. # Send incoming packets without position to walk through the cond