aprs.me/test/aprsme_web/live/map_live/data_builder_test.exs
Graham McIntire 544d8648a0
Fix object/item packets showing sender callsign instead of object name
Objects and items now display with their own names on the map instead of
the originating station's callsign. This prevents confusion where stations
creating objects (like repeaters) appear at multiple locations.

For example, CALGRY creates repeater objects (147.06-WC, 146.85-NH) which
were appearing on the map labeled as "CALGRY" at the repeater locations
instead of showing the actual object names.

Changes:
- Updated map_label() to return object_name for object packets
- Updated map_label() to return item_name for item packets
- Position packets continue to show the sender callsign
- Updated tests to expect object/item names instead of sender

This matches APRS.fi behavior where objects are displayed separately
from their originating station.
2026-03-22 13:02:24 -05:00

376 lines
11 KiB
Elixir

defmodule AprsmeWeb.MapLive.DataBuilderTest do
use Aprsme.DataCase, async: true
alias AprsmeWeb.MapLive.DataBuilder
describe "display name for APRS objects and items" do
test "uses sender for map label but object_name for grouping when packet is an object" do
packet = %{
id: Ecto.UUID.generate(),
sender: "DB0SDA",
base_callsign: "DB0SDA",
ssid: "",
object_name: "P-K5SGD",
is_object: true,
is_item: false,
lat: 33.169333,
lon: -96.492167,
data_type: "object",
received_at: DateTime.utc_now(),
symbol_table_id: "P",
symbol_code: "#",
comment: "DAPNET POCSAG Transmitter",
has_position: true,
path: "TCPIP*,qAC,FIFTH"
}
result = DataBuilder.build_packet_data(packet, true)
assert result
# Map label shows object name for objects
assert result["callsign"] == "P-K5SGD"
# Grouping also uses object name
assert result["callsign_group"] == "P-K5SGD"
end
test "uses sender for map label but item_name for grouping when packet is an item" do
packet = %{
id: Ecto.UUID.generate(),
sender: "N0CALL",
base_callsign: "N0CALL",
ssid: "",
item_name: "REPEATER1",
is_item: true,
is_object: false,
lat: 40.0,
lon: -105.0,
data_type: "item",
received_at: DateTime.utc_now(),
symbol_table_id: "/",
symbol_code: "r",
comment: "Some repeater",
has_position: true,
path: "WIDE1-1"
}
result = DataBuilder.build_packet_data(packet, true)
assert result
# Map label shows item name for items
assert result["callsign"] == "REPEATER1"
# Grouping also uses item name
assert result["callsign_group"] == "REPEATER1"
end
test "falls back to sender when no object_name or item_name" do
packet = %{
id: Ecto.UUID.generate(),
sender: "K5SGD-Y",
base_callsign: "K5SGD",
ssid: "Y",
object_name: nil,
item_name: nil,
is_object: false,
is_item: false,
lat: 33.169333,
lon: -96.492167,
data_type: "position",
received_at: DateTime.utc_now(),
symbol_table_id: "/",
symbol_code: ">",
comment: "Test station",
has_position: true,
path: "WIDE1-1"
}
result = DataBuilder.build_packet_data(packet, true)
assert result
assert result["callsign"] == "K5SGD-Y"
end
test "uses sender in popup for object packets" do
packet = %{
id: Ecto.UUID.generate(),
sender: "DB0SDA",
base_callsign: "DB0SDA",
ssid: "",
object_name: "P-K5SGD",
is_object: true,
is_item: false,
lat: 33.169333,
lon: -96.492167,
data_type: "object",
received_at: DateTime.utc_now(),
symbol_table_id: "P",
symbol_code: "#",
comment: "DAPNET POCSAG Transmitter",
has_position: true,
path: "TCPIP*,qAC,FIFTH"
}
result = DataBuilder.build_packet_data(packet, true)
# Popup now shows object name for objects
assert result["popup"] =~ "P-K5SGD"
refute result["popup"] =~ "DB0SDA"
end
test "build_minimal_packet_data uses sender for map label but object_name for grouping" do
packet = %{
id: Ecto.UUID.generate(),
sender: "DB0SDA",
base_callsign: "DB0SDA",
ssid: "",
object_name: "P-K5SGD",
is_object: true,
is_item: false,
lat: 33.169333,
lon: -96.492167,
data_type: "object",
received_at: DateTime.utc_now(),
symbol_table_id: "P",
symbol_code: "#",
comment: "DAPNET POCSAG Transmitter",
has_position: true,
path: "TCPIP*,qAC,FIFTH"
}
result = DataBuilder.build_minimal_packet_data(packet, true, false)
assert result
# Map label shows object name for objects
assert result["callsign"] == "P-K5SGD"
# Grouping also uses object name
assert result["callsign_group"] == "P-K5SGD"
# Symbol HTML should show object name
assert result["symbol_html"] =~ "P-K5SGD"
refute result["symbol_html"] =~ "DB0SDA"
end
test "build_minimal_packet_data uses red dot HTML for historical (non-most-recent) packets" do
packet = %{
id: Ecto.UUID.generate(),
sender: "K5GVL-10",
base_callsign: "K5GVL",
ssid: "10",
lat: 33.1,
lon: -96.5,
received_at: DateTime.utc_now(),
symbol_table_id: "/",
symbol_code: "-",
comment: "Test",
path: "WIDE1-1"
}
result = DataBuilder.build_minimal_packet_data(packet, false, false)
assert result
assert result["historical"] == true
# Historical dot should use inline red dot HTML, not a full symbol
assert result["symbol_html"] =~ "background-color"
assert result["symbol_html"] =~ "#FF6B6B"
end
test "build_minimal_packet_data uses full symbol for most-recent packets" do
packet = %{
id: Ecto.UUID.generate(),
sender: "K5GVL-10",
base_callsign: "K5GVL",
ssid: "10",
lat: 33.1,
lon: -96.5,
received_at: DateTime.utc_now(),
symbol_table_id: "/",
symbol_code: "-",
comment: "Test",
path: "WIDE1-1"
}
result = DataBuilder.build_minimal_packet_data(packet, true, false)
assert result
assert result["is_most_recent_for_callsign"] == true
# Most recent should NOT use the red dot
refute result["symbol_html"] =~ "#FF6B6B"
end
test "build_minimal_packet_data includes callsign_group field" do
packet = %{
id: Ecto.UUID.generate(),
sender: "K5GVL-10",
base_callsign: "K5GVL",
ssid: "10",
lat: 33.1,
lon: -96.5,
received_at: DateTime.utc_now(),
symbol_table_id: "/",
symbol_code: "-",
comment: "Test",
path: "WIDE1-1"
}
result = DataBuilder.build_minimal_packet_data(packet, true, false)
assert result["callsign_group"] == "K5GVL-10"
end
test "build_packet_data includes callsign_group field" do
packet = %{
id: Ecto.UUID.generate(),
sender: "K5GVL-10",
base_callsign: "K5GVL",
ssid: "10",
lat: 33.1,
lon: -96.5,
received_at: DateTime.utc_now(),
symbol_table_id: "/",
symbol_code: "-",
comment: "Test",
data_type: "position",
path: "WIDE1-1"
}
result = DataBuilder.build_packet_data(packet, true)
assert result["callsign_group"] == "K5GVL-10"
end
test "build_packet_data_list output is sorted by callsign group then chronologically" do
now = DateTime.utc_now()
# Two callsigns, each with 2 packets at different times
packets = [
%{
id: Ecto.UUID.generate(),
sender: "ZZZ-1",
base_callsign: "ZZZ",
ssid: "1",
lat: 33.1,
lon: -96.5,
received_at: DateTime.add(now, -300, :second),
symbol_table_id: "/",
symbol_code: "-",
comment: "",
path: ""
},
%{
id: Ecto.UUID.generate(),
sender: "AAA-1",
base_callsign: "AAA",
ssid: "1",
lat: 34.0,
lon: -97.0,
received_at: now,
symbol_table_id: "/",
symbol_code: ">",
comment: "",
path: ""
},
%{
id: Ecto.UUID.generate(),
sender: "ZZZ-1",
base_callsign: "ZZZ",
ssid: "1",
lat: 33.2,
lon: -96.4,
received_at: now,
symbol_table_id: "/",
symbol_code: "-",
comment: "",
path: ""
}
]
results = DataBuilder.build_packet_data_list(packets)
# All results should have callsign_group
assert Enum.all?(results, &Map.has_key?(&1, "callsign_group"))
# Packets should be grouped by callsign (all AAA together, all ZZZ together)
callsigns = Enum.map(results, & &1["callsign"])
groups = Enum.chunk_by(callsigns, & &1)
# Each group should contain only one unique callsign
assert Enum.all?(groups, fn group -> length(Enum.uniq(group)) == 1 end)
end
test "build_packet_data_list groups by object_name for objects" do
now = DateTime.utc_now()
earlier = DateTime.add(now, -600, :second)
object_packet_1 = %{
id: Ecto.UUID.generate(),
sender: "DB0SDA",
base_callsign: "DB0SDA",
ssid: "",
object_name: "P-K5SGD",
is_object: true,
is_item: false,
lat: 33.169333,
lon: -96.492167,
data_type: "object",
received_at: now,
symbol_table_id: "P",
symbol_code: "#",
comment: "DAPNET POCSAG Transmitter",
has_position: true,
path: "TCPIP*,qAC,FIFTH"
}
object_packet_2 = %{
id: Ecto.UUID.generate(),
sender: "DB0SDA",
base_callsign: "DB0SDA",
ssid: "",
object_name: "P-K5SGD",
is_object: true,
is_item: false,
lat: 33.170,
lon: -96.493,
data_type: "object",
received_at: earlier,
symbol_table_id: "P",
symbol_code: "#",
comment: "DAPNET POCSAG Transmitter",
has_position: true,
path: "TCPIP*,qAC,FIFTH"
}
results = DataBuilder.build_packet_data_list([object_packet_1, object_packet_2])
# Map labels should show object name
callsigns = Enum.map(results, & &1["callsign"])
assert Enum.all?(callsigns, &(&1 == "P-K5SGD"))
# But grouping should be by object name
groups = Enum.map(results, & &1["callsign_group"])
assert Enum.all?(groups, &(&1 == "P-K5SGD"))
end
test "build_simple_popup shows sender for object packets" do
packet = %{
id: Ecto.UUID.generate(),
sender: "WA0YMH-2",
base_callsign: "WA0YMH",
ssid: "2",
object_name: "X3234025",
is_object: true,
is_item: false,
lat: 33.251833,
lon: -95.719833,
data_type: "object",
received_at: DateTime.utc_now(),
symbol_table_id: "/",
symbol_code: "O",
comment: "Clb=5.9m/s t=-58.5C 404.800MHz Type=RS41-NG rdzTTGOsonde",
has_position: true,
path: "TCPIP*,qAR,WA0YMH-2"
}
popup = DataBuilder.build_simple_popup(packet, false)
assert popup =~ "X3234025"
refute popup =~ "WA0YMH-2"
end
end
end