From 791733af4556649dbbb34b18c2a75f6c10e71bcd Mon Sep 17 00:00:00 2001 From: Graham McIntire Date: Fri, 24 Apr 2026 08:23:03 -0500 Subject: [PATCH] test: SpatialPubSub broadcast paths and client lifecycle --- test/aprsme/spatial_pubsub_test.exs | 67 +++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/test/aprsme/spatial_pubsub_test.exs b/test/aprsme/spatial_pubsub_test.exs index c4810bb..54eef6e 100644 --- a/test/aprsme/spatial_pubsub_test.exs +++ b/test/aprsme/spatial_pubsub_test.exs @@ -98,6 +98,73 @@ defmodule Aprsme.SpatialPubSubTest do end end + describe "broadcast_packet" do + test "broadcasts to clients whose viewport contains the packet location" do + client_id = "test_bc_#{:rand.uniform(100_000)}" + bounds = %{north: 41.0, south: 40.0, east: -73.0, west: -74.0} + {:ok, topic} = SpatialPubSub.register_viewport(client_id, bounds) + + Phoenix.PubSub.subscribe(Aprsme.PubSub, topic) + + # Broadcast a packet inside the viewport. + assert :ok = SpatialPubSub.broadcast_packet(%{lat: 40.5, lon: -73.5, sender: "IN1"}) + + assert_receive {:spatial_packet, %{sender: "IN1"}}, 1_000 + + SpatialPubSub.unregister_client(client_id) + end + + test "does not broadcast to clients outside the viewport" do + client_id = "test_outside_#{:rand.uniform(100_000)}" + bounds = %{north: 41.0, south: 40.0, east: -73.0, west: -74.0} + {:ok, topic} = SpatialPubSub.register_viewport(client_id, bounds) + + Phoenix.PubSub.subscribe(Aprsme.PubSub, topic) + + # Broadcast far outside bounds. + assert :ok = SpatialPubSub.broadcast_packet(%{lat: 0.0, lon: 0.0, sender: "OUT1"}) + refute_receive {:spatial_packet, _}, 100 + + SpatialPubSub.unregister_client(client_id) + end + + test "ignores packets with missing or invalid location" do + # Packet with nil coords should not crash. + assert :ok = SpatialPubSub.broadcast_packet(%{lat: nil, lon: nil}) + assert :ok = SpatialPubSub.broadcast_packet(%{lat: "bad", lon: "bad"}) + assert :ok = SpatialPubSub.broadcast_packet(%{}) + end + + test "binary-string coordinates in bounds are parsed" do + client_id = "test_strbounds_#{:rand.uniform(100_000)}" + bounds = %{north: "41.0", south: "40.0", east: "-73.0", west: "-74.0"} + assert {:ok, _topic} = SpatialPubSub.register_viewport(client_id, bounds) + SpatialPubSub.unregister_client(client_id) + end + end + + describe "client lifecycle" do + test "unregister removes client from spatial index" do + client_id = "test_unreg_#{:rand.uniform(100_000)}" + bounds = %{north: 41.0, south: 40.0, east: -73.0, west: -74.0} + {:ok, _topic} = SpatialPubSub.register_viewport(client_id, bounds) + + before_stats = SpatialPubSub.get_stats() + SpatialPubSub.unregister_client(client_id) + # Give cast time to process + _ = SpatialPubSub.get_stats() + after_stats = SpatialPubSub.get_stats() + + assert after_stats.clients_count <= before_stats.clients_count + end + + test "update_viewport returns error for unregistered client" do + bounds = %{north: 41.0, south: 40.0, east: -73.0, west: -74.0} + result = SpatialPubSub.update_viewport("never_registered_#{:rand.uniform(100_000)}", bounds) + assert result == {:error, :not_registered} + end + end + describe "when the server is not running" do setup do pid = Process.whereis(SpatialPubSub)