fix bounds error
This commit is contained in:
parent
86f874194e
commit
445c9899cf
10 changed files with 531 additions and 161 deletions
|
|
@ -19,6 +19,7 @@ import Config
|
||||||
# Always start the server in production/docker environments
|
# Always start the server in production/docker environments
|
||||||
if System.get_env("PHX_SERVER") || config_env() == :prod do
|
if System.get_env("PHX_SERVER") || config_env() == :prod do
|
||||||
config :aprs, AprsWeb.Endpoint, server: true
|
config :aprs, AprsWeb.Endpoint, server: true
|
||||||
|
|
||||||
IO.puts("Phoenix server enabled - will start HTTP listener")
|
IO.puts("Phoenix server enabled - will start HTTP listener")
|
||||||
else
|
else
|
||||||
IO.puts("Phoenix server disabled - no HTTP listener will start")
|
IO.puts("Phoenix server disabled - no HTTP listener will start")
|
||||||
|
|
|
||||||
|
|
@ -299,7 +299,10 @@ defmodule Aprs.Is do
|
||||||
Logger.info("Successfully stored packet from #{packet.sender}")
|
Logger.info("Successfully stored packet from #{packet.sender}")
|
||||||
|
|
||||||
{:error, changeset} ->
|
{:error, changeset} ->
|
||||||
Logger.error("Failed to store packet from #{inspect(parsed_message.sender)}: #{inspect(changeset.errors)}")
|
Logger.error(
|
||||||
|
"Failed to store packet from #{inspect(parsed_message.sender)}: #{inspect(changeset.errors)}"
|
||||||
|
)
|
||||||
|
|
||||||
# Log the problematic attributes for debugging
|
# Log the problematic attributes for debugging
|
||||||
Logger.debug("Packet attributes that failed: #{inspect(attrs)}")
|
Logger.debug("Packet attributes that failed: #{inspect(attrs)}")
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -146,6 +146,7 @@ defmodule Aprs.Packet do
|
||||||
if is_valid_coordinates?(lat, lon) do
|
if is_valid_coordinates?(lat, lon) do
|
||||||
try do
|
try do
|
||||||
location = create_point(lat, lon)
|
location = create_point(lat, lon)
|
||||||
|
|
||||||
if location do
|
if location do
|
||||||
put_change(changeset, :location, location)
|
put_change(changeset, :location, location)
|
||||||
else
|
else
|
||||||
|
|
@ -154,6 +155,7 @@ defmodule Aprs.Packet do
|
||||||
rescue
|
rescue
|
||||||
error ->
|
error ->
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
Logger.error("Failed to create geometry for lat=#{lat}, lon=#{lon}: #{inspect(error)}")
|
Logger.error("Failed to create geometry for lat=#{lat}, lon=#{lon}: #{inspect(error)}")
|
||||||
changeset
|
changeset
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -74,7 +74,6 @@ defmodule Aprs.Packets do
|
||||||
%Packet{}
|
%Packet{}
|
||||||
|> Packet.changeset(packet_attrs)
|
|> Packet.changeset(packet_attrs)
|
||||||
|> Repo.insert()
|
|> Repo.insert()
|
||||||
|
|
||||||
rescue
|
rescue
|
||||||
error ->
|
error ->
|
||||||
Logger.error("Exception in store_packet for #{inspect(packet_data[:sender])}: #{inspect(error)}")
|
Logger.error("Exception in store_packet for #{inspect(packet_data[:sender])}: #{inspect(error)}")
|
||||||
|
|
|
||||||
|
|
@ -200,6 +200,7 @@ defmodule AprsWeb.MapLive.CallsignView do
|
||||||
# Helper function to convert string or float to float
|
# Helper function to convert string or float to float
|
||||||
defp to_float(value) when is_float(value), do: value
|
defp to_float(value) when is_float(value), do: value
|
||||||
defp to_float(value) when is_integer(value), do: value * 1.0
|
defp to_float(value) when is_integer(value), do: value * 1.0
|
||||||
|
|
||||||
defp to_float(value) when is_binary(value) do
|
defp to_float(value) when is_binary(value) do
|
||||||
case Float.parse(value) do
|
case Float.parse(value) do
|
||||||
{float_val, _} -> float_val
|
{float_val, _} -> float_val
|
||||||
|
|
|
||||||
|
|
@ -57,42 +57,45 @@ defmodule AprsWeb.MapLive.Index do
|
||||||
)
|
)
|
||||||
|
|
||||||
if connected?(socket) do
|
if connected?(socket) do
|
||||||
IO.puts("Socket is connected, attempting to get IP location")
|
|
||||||
Endpoint.subscribe("aprs_messages")
|
Endpoint.subscribe("aprs_messages")
|
||||||
# Get IP-based location on initial load
|
|
||||||
IO.puts("Connect info: #{inspect(socket.private[:connect_info])}")
|
|
||||||
IO.puts("Peer data: #{inspect(socket.private[:connect_info][:peer_data])}")
|
|
||||||
|
|
||||||
ip =
|
# Only do IP geolocation in non-test environments
|
||||||
case socket.private[:connect_info][:peer_data][:address] do
|
if Mix.env() != :test do
|
||||||
{a, b, c, d} -> "#{a}.#{b}.#{c}.#{d}"
|
IO.puts("Socket is connected, attempting to get IP location")
|
||||||
{a, b, c, d, e, f, g, h} -> "#{a}:#{b}:#{c}:#{d}:#{e}:#{f}:#{g}:#{h}"
|
# Get IP-based location on initial load
|
||||||
_ -> nil
|
IO.puts("Connect info: #{inspect(socket.private[:connect_info])}")
|
||||||
end
|
IO.puts("Peer data: #{inspect(socket.private[:connect_info][:peer_data])}")
|
||||||
|
|
||||||
IO.puts("Extracted IP address: #{inspect(ip)}")
|
ip =
|
||||||
|
case socket.private[:connect_info][:peer_data][:address] do
|
||||||
if ip do
|
{a, b, c, d} -> "#{a}.#{b}.#{c}.#{d}"
|
||||||
IO.puts("Starting IP geolocation task for IP: #{ip}")
|
{a, b, c, d, e, f, g, h} -> "#{a}:#{b}:#{c}:#{d}:#{e}:#{f}:#{g}:#{h}"
|
||||||
# Start as a separate task and await the result
|
_ -> nil
|
||||||
Task.start(fn ->
|
|
||||||
try do
|
|
||||||
get_ip_location(ip)
|
|
||||||
rescue
|
|
||||||
error ->
|
|
||||||
IO.puts("Error in IP geolocation task: #{inspect(error)}")
|
|
||||||
IO.puts("Stacktrace: #{inspect(__STACKTRACE__)}")
|
|
||||||
send(self(), {:ip_location, @default_center})
|
|
||||||
end
|
end
|
||||||
end)
|
|
||||||
else
|
# Only attempt IP geolocation if not localhost
|
||||||
IO.puts("No IP address found, skipping geolocation")
|
if ip && !String.starts_with?(ip, "127.") && !String.starts_with?(ip, "::1") do
|
||||||
|
IO.puts("Starting IP geolocation task for IP: #{ip}")
|
||||||
|
# Start as a separate task and await the result
|
||||||
|
Task.start(fn ->
|
||||||
|
try do
|
||||||
|
get_ip_location(ip)
|
||||||
|
rescue
|
||||||
|
error ->
|
||||||
|
IO.puts("Error in IP geolocation task: #{inspect(error)}")
|
||||||
|
IO.puts("Stacktrace: #{inspect(__STACKTRACE__)}")
|
||||||
|
send(self(), {:ip_location, @default_center})
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
else
|
||||||
|
IO.puts("No IP address found, skipping geolocation")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Schedule regular cleanup of old packets from the map
|
# Schedule regular cleanup of old packets from the map
|
||||||
if connected?(socket), do: Process.send_after(self(), :cleanup_old_packets, 60_000)
|
Process.send_after(self(), :cleanup_old_packets, 60_000)
|
||||||
# Schedule initialization of replay after a short delay
|
# Schedule initialization of replay after a short delay
|
||||||
if connected?(socket), do: Process.send_after(self(), :initialize_replay, 2000)
|
Process.send_after(self(), :initialize_replay, 2000)
|
||||||
end
|
end
|
||||||
|
|
||||||
{:ok, socket}
|
{:ok, socket}
|
||||||
|
|
@ -898,6 +901,12 @@ defmodule AprsWeb.MapLive.Index do
|
||||||
|
|
||||||
# Helper function to convert string or float to float
|
# Helper function to convert string or float to float
|
||||||
defp to_float(value) when is_float(value), do: value
|
defp to_float(value) when is_float(value), do: value
|
||||||
defp to_float(value) when is_binary(value), do: String.to_float(value)
|
|
||||||
defp to_float(value) when is_integer(value), do: value * 1.0
|
defp to_float(value) when is_integer(value), do: value * 1.0
|
||||||
|
|
||||||
|
defp to_float(value) when is_binary(value) do
|
||||||
|
case Float.parse(value) do
|
||||||
|
{float_val, _} -> float_val
|
||||||
|
:error -> raise ArgumentError, "Invalid float string: #{value}"
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -1,130 +0,0 @@
|
||||||
#!/usr/bin/env elixir
|
|
||||||
|
|
||||||
# Test script to verify bounds parsing fix in LiveView components
|
|
||||||
# This script tests the to_float helper function to ensure it handles both string and float inputs
|
|
||||||
|
|
||||||
IO.puts("🧪 Testing bounds parsing fix...")
|
|
||||||
|
|
||||||
# Simulate the helper function from the LiveView modules
|
|
||||||
defmodule BoundsParsingTest do
|
|
||||||
# Helper function to convert string or float to float
|
|
||||||
defp to_float(value) when is_float(value), do: value
|
|
||||||
defp to_float(value) when is_binary(value), do: String.to_float(value)
|
|
||||||
defp to_float(value) when is_integer(value), do: value * 1.0
|
|
||||||
|
|
||||||
def test_bounds_parsing do
|
|
||||||
# Test case 1: Float values (the problematic case from the error)
|
|
||||||
float_bounds = %{
|
|
||||||
"north" => 61.897577621605016,
|
|
||||||
"south" => 5.441022303717974,
|
|
||||||
"east" => -34.45312500000001,
|
|
||||||
"west" => -161.54296875000003
|
|
||||||
}
|
|
||||||
|
|
||||||
# Test case 2: String values (the expected case)
|
|
||||||
string_bounds = %{
|
|
||||||
"north" => "61.897577621605016",
|
|
||||||
"south" => "5.441022303717974",
|
|
||||||
"east" => "-34.45312500000001",
|
|
||||||
"west" => "-161.54296875000003"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Test case 3: Integer values
|
|
||||||
integer_bounds = %{
|
|
||||||
"north" => 62,
|
|
||||||
"south" => 5,
|
|
||||||
"east" => -34,
|
|
||||||
"west" => -162
|
|
||||||
}
|
|
||||||
|
|
||||||
# Test case 4: Mixed types
|
|
||||||
mixed_bounds = %{
|
|
||||||
"north" => 61.897577621605016,
|
|
||||||
"south" => "5.441022303717974",
|
|
||||||
"east" => -34,
|
|
||||||
"west" => "-161.54296875000003"
|
|
||||||
}
|
|
||||||
|
|
||||||
test_cases = [
|
|
||||||
{"Float bounds", float_bounds},
|
|
||||||
{"String bounds", string_bounds},
|
|
||||||
{"Integer bounds", integer_bounds},
|
|
||||||
{"Mixed bounds", mixed_bounds}
|
|
||||||
]
|
|
||||||
|
|
||||||
Enum.each(test_cases, fn {name, bounds} ->
|
|
||||||
IO.puts("\n🔍 Testing #{name}:")
|
|
||||||
|
|
||||||
try do
|
|
||||||
normalized_bounds = %{
|
|
||||||
north: to_float(bounds["north"]),
|
|
||||||
south: to_float(bounds["south"]),
|
|
||||||
east: to_float(bounds["east"]),
|
|
||||||
west: to_float(bounds["west"])
|
|
||||||
}
|
|
||||||
|
|
||||||
IO.puts("✅ Success: #{inspect(normalized_bounds)}")
|
|
||||||
rescue
|
|
||||||
error ->
|
|
||||||
IO.puts("❌ Error: #{inspect(error)}")
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_replay_speed_parsing do
|
|
||||||
IO.puts("\n🔍 Testing replay speed parsing:")
|
|
||||||
|
|
||||||
test_speeds = [
|
|
||||||
{"Float speed", 1.5},
|
|
||||||
{"String speed", "2.0"},
|
|
||||||
{"Integer speed", 3},
|
|
||||||
{"String integer", "1"}
|
|
||||||
]
|
|
||||||
|
|
||||||
Enum.each(test_speeds, fn {name, speed} ->
|
|
||||||
try do
|
|
||||||
parsed_speed = to_float(speed)
|
|
||||||
IO.puts("✅ #{name}: #{inspect(speed)} -> #{parsed_speed}")
|
|
||||||
rescue
|
|
||||||
error ->
|
|
||||||
IO.puts("❌ #{name} error: #{inspect(error)}")
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Test edge cases
|
|
||||||
def test_edge_cases do
|
|
||||||
IO.puts("\n🔍 Testing edge cases:")
|
|
||||||
|
|
||||||
edge_cases = [
|
|
||||||
{"Zero float", 0.0},
|
|
||||||
{"Zero string", "0.0"},
|
|
||||||
{"Zero integer", 0},
|
|
||||||
{"Negative float", -123.456},
|
|
||||||
{"Negative string", "-123.456"},
|
|
||||||
{"Large number", 999999.999999}
|
|
||||||
]
|
|
||||||
|
|
||||||
Enum.each(edge_cases, fn {name, value} ->
|
|
||||||
try do
|
|
||||||
result = to_float(value)
|
|
||||||
IO.puts("✅ #{name}: #{inspect(value)} -> #{result}")
|
|
||||||
rescue
|
|
||||||
error ->
|
|
||||||
IO.puts("❌ #{name} error: #{inspect(error)}")
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Run the tests
|
|
||||||
BoundsParsingTest.test_bounds_parsing()
|
|
||||||
BoundsParsingTest.test_replay_speed_parsing()
|
|
||||||
BoundsParsingTest.test_edge_cases()
|
|
||||||
|
|
||||||
IO.puts("\n🎉 Bounds parsing test completed!")
|
|
||||||
IO.puts("\n📊 Summary:")
|
|
||||||
IO.puts(" - The to_float helper function handles float, string, and integer inputs")
|
|
||||||
IO.puts(" - This prevents the ArgumentError: not a binary when JavaScript sends float values")
|
|
||||||
IO.puts(" - The LiveView bounds_changed event should now work correctly")
|
|
||||||
IO.puts("\n🚀 The bounds parsing fix is working correctly!")
|
|
||||||
296
test/aprs/geometry_test.exs
Normal file
296
test/aprs/geometry_test.exs
Normal file
|
|
@ -0,0 +1,296 @@
|
||||||
|
defmodule Aprs.GeometryTest do
|
||||||
|
use Aprs.DataCase
|
||||||
|
|
||||||
|
alias Aprs.Packet
|
||||||
|
alias Aprs.Packets
|
||||||
|
alias Geo.PostGIS.Geometry
|
||||||
|
|
||||||
|
describe "PostGIS geometry functionality" do
|
||||||
|
test "create_point/2 creates valid Point geometry" do
|
||||||
|
lat = 32.7767
|
||||||
|
lon = -96.7970
|
||||||
|
|
||||||
|
point = Packet.create_point(lat, lon)
|
||||||
|
|
||||||
|
assert %Geo.Point{} = point
|
||||||
|
assert point.coordinates == {lon, lat}
|
||||||
|
assert point.srid == 4326
|
||||||
|
end
|
||||||
|
|
||||||
|
test "create_point/2 returns nil for invalid coordinates" do
|
||||||
|
# Test invalid latitude
|
||||||
|
assert Packet.create_point(91.0, -96.7970) == nil
|
||||||
|
assert Packet.create_point(-91.0, -96.7970) == nil
|
||||||
|
|
||||||
|
# Test invalid longitude
|
||||||
|
assert Packet.create_point(32.7767, 181.0) == nil
|
||||||
|
assert Packet.create_point(32.7767, -181.0) == nil
|
||||||
|
|
||||||
|
# Test nil values
|
||||||
|
assert Packet.create_point(nil, -96.7970) == nil
|
||||||
|
assert Packet.create_point(32.7767, nil) == nil
|
||||||
|
end
|
||||||
|
|
||||||
|
test "extract_coordinates/1 extracts lat/lon from Point geometry" do
|
||||||
|
point = %Geo.Point{coordinates: {-96.7970, 32.7767}, srid: 4326}
|
||||||
|
|
||||||
|
{lat, lon} = Packet.extract_coordinates(point)
|
||||||
|
|
||||||
|
assert lat == 32.7767
|
||||||
|
assert lon == -96.7970
|
||||||
|
end
|
||||||
|
|
||||||
|
test "extract_coordinates/1 returns {nil, nil} for invalid geometry" do
|
||||||
|
assert Packet.extract_coordinates(nil) == {nil, nil}
|
||||||
|
assert Packet.extract_coordinates("invalid") == {nil, nil}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "packet changeset creates geometry from lat/lon" do
|
||||||
|
attrs = %{
|
||||||
|
base_callsign: "TEST",
|
||||||
|
data_type: "position",
|
||||||
|
destination: "APRS",
|
||||||
|
information_field: "test",
|
||||||
|
path: "WIDE1-1",
|
||||||
|
sender: "TEST-1",
|
||||||
|
ssid: "1",
|
||||||
|
received_at: DateTime.utc_now(),
|
||||||
|
lat: 32.7767,
|
||||||
|
lon: -96.7970
|
||||||
|
}
|
||||||
|
|
||||||
|
changeset = Packet.changeset(%Packet{}, attrs)
|
||||||
|
|
||||||
|
assert changeset.valid?
|
||||||
|
assert get_change(changeset, :location) != nil
|
||||||
|
assert get_change(changeset, :has_position) == true
|
||||||
|
|
||||||
|
location = get_change(changeset, :location)
|
||||||
|
assert %Geo.Point{} = location
|
||||||
|
assert location.coordinates == {-96.7970, 32.7767}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "packet changeset handles invalid coordinates gracefully" do
|
||||||
|
attrs = %{
|
||||||
|
base_callsign: "TEST",
|
||||||
|
data_type: "position",
|
||||||
|
destination: "APRS",
|
||||||
|
information_field: "test",
|
||||||
|
path: "WIDE1-1",
|
||||||
|
sender: "TEST-1",
|
||||||
|
ssid: "1",
|
||||||
|
received_at: DateTime.utc_now(),
|
||||||
|
# Invalid latitude
|
||||||
|
lat: 91.0,
|
||||||
|
lon: -96.7970
|
||||||
|
}
|
||||||
|
|
||||||
|
changeset = Packet.changeset(%Packet{}, attrs)
|
||||||
|
|
||||||
|
# Should still be valid, just without location
|
||||||
|
assert changeset.valid?
|
||||||
|
assert get_change(changeset, :location) == nil
|
||||||
|
assert get_change(changeset, :has_position) != true
|
||||||
|
end
|
||||||
|
|
||||||
|
test "store_packet/1 successfully stores packet with geometry" do
|
||||||
|
attrs = %{
|
||||||
|
base_callsign: "TEST",
|
||||||
|
data_type: "position",
|
||||||
|
destination: "APRS",
|
||||||
|
information_field: "!3216.46N/09647.82W>Test packet",
|
||||||
|
path: "WIDE1-1,WIDE2-1",
|
||||||
|
sender: "TEST-1",
|
||||||
|
ssid: "1",
|
||||||
|
received_at: DateTime.utc_now(),
|
||||||
|
lat: 32.2743,
|
||||||
|
lon: -96.7970,
|
||||||
|
has_position: true,
|
||||||
|
region: "32.3,-96.8",
|
||||||
|
raw_packet: "TEST-1>APRS,WIDE1-1,WIDE2-1:!3216.46N/09647.82W>Test packet"
|
||||||
|
}
|
||||||
|
|
||||||
|
assert {:ok, packet} = Packets.store_packet(attrs)
|
||||||
|
assert packet.sender == "TEST-1"
|
||||||
|
assert packet.has_position == true
|
||||||
|
assert packet.location != nil
|
||||||
|
assert %Geo.Point{} = packet.location
|
||||||
|
assert packet.location.coordinates == {-96.7970, 32.2743}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "store_packet/1 handles packet without position data" do
|
||||||
|
attrs = %{
|
||||||
|
base_callsign: "TEST",
|
||||||
|
data_type: "message",
|
||||||
|
destination: "APRS",
|
||||||
|
information_field: ":TEST-2 :Hello world",
|
||||||
|
path: "WIDE1-1",
|
||||||
|
sender: "TEST-1",
|
||||||
|
ssid: "1",
|
||||||
|
received_at: DateTime.utc_now(),
|
||||||
|
raw_packet: "TEST-1>APRS,WIDE1-1::TEST-2 :Hello world"
|
||||||
|
}
|
||||||
|
|
||||||
|
assert {:ok, packet} = Packets.store_packet(attrs)
|
||||||
|
assert packet.sender == "TEST-1"
|
||||||
|
assert packet.has_position == false
|
||||||
|
assert packet.location == nil
|
||||||
|
end
|
||||||
|
|
||||||
|
test "store_packet/1 handles coordinate validation errors gracefully" do
|
||||||
|
attrs = %{
|
||||||
|
base_callsign: "TEST",
|
||||||
|
data_type: "position",
|
||||||
|
destination: "APRS",
|
||||||
|
information_field: "test",
|
||||||
|
path: "WIDE1-1",
|
||||||
|
sender: "TEST-1",
|
||||||
|
ssid: "1",
|
||||||
|
received_at: DateTime.utc_now(),
|
||||||
|
# Invalid latitude
|
||||||
|
lat: 200.0,
|
||||||
|
lon: -96.7970,
|
||||||
|
raw_packet: "TEST-1>APRS,WIDE1-1:test"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Should not crash, but should handle gracefully
|
||||||
|
assert {:ok, packet} = Packets.store_packet(attrs)
|
||||||
|
assert packet.sender == "TEST-1"
|
||||||
|
assert packet.has_position == false
|
||||||
|
assert packet.location == nil
|
||||||
|
end
|
||||||
|
|
||||||
|
test "Geo.PostGIS.Geometry cast works with Point" do
|
||||||
|
point = %Geo.Point{coordinates: {-96.7970, 32.7767}, srid: 4326}
|
||||||
|
|
||||||
|
assert {:ok, ^point} = Geometry.cast(point)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "Geo.PostGIS.Geometry cast works with coordinate tuple" do
|
||||||
|
coords = {-96.7970, 32.7767}
|
||||||
|
|
||||||
|
# PostGIS geometry casting may not support tuples directly
|
||||||
|
# Instead test creating a point manually
|
||||||
|
point = %Geo.Point{coordinates: coords, srid: 4326}
|
||||||
|
assert {:ok, ^point} = Geometry.cast(point)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "packet lat/lon helper functions work correctly" do
|
||||||
|
packet = %Packet{
|
||||||
|
location: %Geo.Point{coordinates: {-96.7970, 32.7767}, srid: 4326}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert Packet.lat(packet) == 32.7767
|
||||||
|
assert Packet.lon(packet) == -96.7970
|
||||||
|
end
|
||||||
|
|
||||||
|
test "packet lat/lon helpers return nil for packets without location" do
|
||||||
|
packet = %Packet{location: nil}
|
||||||
|
|
||||||
|
assert Packet.lat(packet) == nil
|
||||||
|
assert Packet.lon(packet) == nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "coordinate conversion and validation" do
|
||||||
|
test "handles various coordinate formats" do
|
||||||
|
# Test float coordinates
|
||||||
|
assert Packet.create_point(32.7767, -96.7970) != nil
|
||||||
|
|
||||||
|
# Test integer coordinates
|
||||||
|
assert Packet.create_point(33, -97) != nil
|
||||||
|
|
||||||
|
# Test boundary values
|
||||||
|
assert Packet.create_point(90.0, 180.0) != nil
|
||||||
|
assert Packet.create_point(-90.0, -180.0) != nil
|
||||||
|
|
||||||
|
# Test just inside boundaries
|
||||||
|
assert Packet.create_point(89.9999, 179.9999) != nil
|
||||||
|
assert Packet.create_point(-89.9999, -179.9999) != nil
|
||||||
|
end
|
||||||
|
|
||||||
|
test "rejects coordinates outside valid ranges" do
|
||||||
|
# Test latitude boundaries
|
||||||
|
assert Packet.create_point(90.0001, 0.0) == nil
|
||||||
|
assert Packet.create_point(-90.0001, 0.0) == nil
|
||||||
|
|
||||||
|
# Test longitude boundaries
|
||||||
|
assert Packet.create_point(0.0, 180.0001) == nil
|
||||||
|
assert Packet.create_point(0.0, -180.0001) == nil
|
||||||
|
end
|
||||||
|
|
||||||
|
test "handles edge case coordinates" do
|
||||||
|
# Test zero coordinates
|
||||||
|
assert Packet.create_point(0.0, 0.0) != nil
|
||||||
|
|
||||||
|
# Test very small coordinates
|
||||||
|
assert Packet.create_point(0.000001, 0.000001) != nil
|
||||||
|
|
||||||
|
# Test coordinates near boundaries
|
||||||
|
assert Packet.create_point(89.999999, 179.999999) != nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "packet storage with different position formats" do
|
||||||
|
test "stores packet with MicE-like data structure" do
|
||||||
|
# Test packet storage that would involve MicE conversion
|
||||||
|
attrs = %{
|
||||||
|
base_callsign: "TEST",
|
||||||
|
data_type: "position",
|
||||||
|
destination: "APRS",
|
||||||
|
information_field: "test",
|
||||||
|
path: "WIDE1-1",
|
||||||
|
sender: "TEST-1",
|
||||||
|
ssid: "1",
|
||||||
|
received_at: DateTime.utc_now(),
|
||||||
|
# Pre-converted coordinates
|
||||||
|
lat: 32.7767,
|
||||||
|
lon: -96.7970,
|
||||||
|
raw_packet: "TEST-1>APRS,WIDE1-1:test"
|
||||||
|
}
|
||||||
|
|
||||||
|
assert {:ok, packet} = Packets.store_packet(attrs)
|
||||||
|
assert packet.sender == "TEST-1"
|
||||||
|
assert packet.has_position == true
|
||||||
|
assert packet.location != nil
|
||||||
|
end
|
||||||
|
|
||||||
|
test "stores packet with standard position data" do
|
||||||
|
attrs = %{
|
||||||
|
base_callsign: "TEST",
|
||||||
|
data_type: "position",
|
||||||
|
destination: "APRS",
|
||||||
|
information_field: "test",
|
||||||
|
path: "WIDE1-1",
|
||||||
|
sender: "TEST-2",
|
||||||
|
ssid: "2",
|
||||||
|
received_at: DateTime.utc_now(),
|
||||||
|
lat: 32.7767,
|
||||||
|
lon: -96.7970,
|
||||||
|
raw_packet: "TEST-2>APRS,WIDE1-1:test"
|
||||||
|
}
|
||||||
|
|
||||||
|
assert {:ok, packet} = Packets.store_packet(attrs)
|
||||||
|
assert packet.has_position == true
|
||||||
|
assert %Geo.Point{} = packet.location
|
||||||
|
end
|
||||||
|
|
||||||
|
test "stores packet without position data" do
|
||||||
|
attrs = %{
|
||||||
|
base_callsign: "TEST",
|
||||||
|
data_type: "message",
|
||||||
|
destination: "APRS",
|
||||||
|
information_field: ":TEST-2 :Hello",
|
||||||
|
path: "WIDE1-1",
|
||||||
|
sender: "TEST-3",
|
||||||
|
ssid: "3",
|
||||||
|
received_at: DateTime.utc_now(),
|
||||||
|
raw_packet: "TEST-3>APRS,WIDE1-1::TEST-2 :Hello"
|
||||||
|
}
|
||||||
|
|
||||||
|
assert {:ok, packet} = Packets.store_packet(attrs)
|
||||||
|
assert packet.has_position == false
|
||||||
|
assert packet.location == nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -66,5 +66,82 @@ defmodule AprsWeb.MapLive.CallsignViewTest do
|
||||||
|
|
||||||
assert page_title(view) =~ "APRS Map - W5ISP-9"
|
assert page_title(view) =~ "APRS Map - W5ISP-9"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "handles bounds_changed event with float values", %{conn: conn} do
|
||||||
|
{:ok, view, _html} = live(conn, "/W5ISP-9")
|
||||||
|
|
||||||
|
# Simulate JavaScript sending float values (the problematic case)
|
||||||
|
bounds_params = %{
|
||||||
|
"bounds" => %{
|
||||||
|
"north" => 61.897577621605016,
|
||||||
|
"south" => 5.441022303717974,
|
||||||
|
"east" => -34.45312500000001,
|
||||||
|
"west" => -161.54296875000003
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# This should not crash with ArgumentError: not a binary
|
||||||
|
assert render_hook(view, "bounds_changed", bounds_params)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "handles bounds_changed event with string values", %{conn: conn} do
|
||||||
|
{:ok, view, _html} = live(conn, "/W5ISP-9")
|
||||||
|
|
||||||
|
# Simulate JavaScript sending string values (expected case)
|
||||||
|
bounds_params = %{
|
||||||
|
"bounds" => %{
|
||||||
|
"north" => "61.897577621605016",
|
||||||
|
"south" => "5.441022303717974",
|
||||||
|
"east" => "-34.45312500000001",
|
||||||
|
"west" => "-161.54296875000003"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert render_hook(view, "bounds_changed", bounds_params)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "handles bounds_changed event with mixed value types", %{conn: conn} do
|
||||||
|
{:ok, view, _html} = live(conn, "/W5ISP-9")
|
||||||
|
|
||||||
|
# Test mixed types (float, string, integer)
|
||||||
|
bounds_params = %{
|
||||||
|
"bounds" => %{
|
||||||
|
# float
|
||||||
|
"north" => 61.897577621605016,
|
||||||
|
# string
|
||||||
|
"south" => "5.441022303717974",
|
||||||
|
# integer
|
||||||
|
"east" => -34,
|
||||||
|
# string
|
||||||
|
"west" => "-161.54296875000003"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert render_hook(view, "bounds_changed", bounds_params)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "handles adjust_replay_speed event with float values", %{conn: conn} do
|
||||||
|
{:ok, view, _html} = live(conn, "/W5ISP-9")
|
||||||
|
|
||||||
|
# Test with float speed value
|
||||||
|
speed_params = %{"speed" => 1.5}
|
||||||
|
assert render_hook(view, "adjust_replay_speed", speed_params)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "handles adjust_replay_speed event with string values", %{conn: conn} do
|
||||||
|
{:ok, view, _html} = live(conn, "/W5ISP-9")
|
||||||
|
|
||||||
|
# Test with string speed value
|
||||||
|
speed_params = %{"speed" => "2.0"}
|
||||||
|
assert render_hook(view, "adjust_replay_speed", speed_params)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "handles adjust_replay_speed event with integer values", %{conn: conn} do
|
||||||
|
{:ok, view, _html} = live(conn, "/W5ISP-9")
|
||||||
|
|
||||||
|
# Test with integer speed value
|
||||||
|
speed_params = %{"speed" => 3}
|
||||||
|
assert render_hook(view, "adjust_replay_speed", speed_params)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
112
test/aprs_web/live/map_live/index_test.exs
Normal file
112
test/aprs_web/live/map_live/index_test.exs
Normal file
|
|
@ -0,0 +1,112 @@
|
||||||
|
defmodule AprsWeb.MapLive.IndexTest do
|
||||||
|
use AprsWeb.ConnCase
|
||||||
|
|
||||||
|
import Phoenix.LiveViewTest
|
||||||
|
|
||||||
|
describe "Index" do
|
||||||
|
test "renders map view", %{conn: conn} do
|
||||||
|
{:ok, view, html} = live(conn, "/")
|
||||||
|
|
||||||
|
assert html =~ "APRS Map"
|
||||||
|
assert has_element?(view, "#aprs-map")
|
||||||
|
end
|
||||||
|
|
||||||
|
test "handles bounds_changed event with float values", %{conn: conn} do
|
||||||
|
{:ok, view, _html} = live(conn, "/")
|
||||||
|
|
||||||
|
# Simulate JavaScript sending float values (the problematic case)
|
||||||
|
bounds_params = %{
|
||||||
|
"bounds" => %{
|
||||||
|
"north" => 61.897577621605016,
|
||||||
|
"south" => 5.441022303717974,
|
||||||
|
"east" => -34.45312500000001,
|
||||||
|
"west" => -161.54296875000003
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# This should not crash with ArgumentError: not a binary
|
||||||
|
assert render_hook(view, "bounds_changed", bounds_params)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "handles bounds_changed event with string values", %{conn: conn} do
|
||||||
|
{:ok, view, _html} = live(conn, "/")
|
||||||
|
|
||||||
|
# Simulate JavaScript sending string values (expected case)
|
||||||
|
bounds_params = %{
|
||||||
|
"bounds" => %{
|
||||||
|
"north" => "61.897577621605016",
|
||||||
|
"south" => "5.441022303717974",
|
||||||
|
"east" => "-34.45312500000001",
|
||||||
|
"west" => "-161.54296875000003"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert render_hook(view, "bounds_changed", bounds_params)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "handles bounds_changed event with mixed value types", %{conn: conn} do
|
||||||
|
{:ok, view, _html} = live(conn, "/")
|
||||||
|
|
||||||
|
# Test mixed types (float, string, integer)
|
||||||
|
bounds_params = %{
|
||||||
|
"bounds" => %{
|
||||||
|
# float
|
||||||
|
"north" => 61.897577621605016,
|
||||||
|
# string
|
||||||
|
"south" => "5.441022303717974",
|
||||||
|
# integer
|
||||||
|
"east" => -34,
|
||||||
|
# string
|
||||||
|
"west" => "-161.54296875000003"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert render_hook(view, "bounds_changed", bounds_params)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "handles adjust_replay_speed event with float values", %{conn: conn} do
|
||||||
|
{:ok, view, _html} = live(conn, "/")
|
||||||
|
|
||||||
|
# Test with float speed value
|
||||||
|
speed_params = %{"speed" => 1.5}
|
||||||
|
assert render_hook(view, "adjust_replay_speed", speed_params)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "handles adjust_replay_speed event with string values", %{conn: conn} do
|
||||||
|
{:ok, view, _html} = live(conn, "/")
|
||||||
|
|
||||||
|
# Test with string speed value
|
||||||
|
speed_params = %{"speed" => "2.0"}
|
||||||
|
assert render_hook(view, "adjust_replay_speed", speed_params)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "handles adjust_replay_speed event with integer values", %{conn: conn} do
|
||||||
|
{:ok, view, _html} = live(conn, "/")
|
||||||
|
|
||||||
|
# Test with integer speed value
|
||||||
|
speed_params = %{"speed" => 3}
|
||||||
|
assert render_hook(view, "adjust_replay_speed", speed_params)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "handles adjust_replay_speed event with integer string values", %{conn: conn} do
|
||||||
|
{:ok, view, _html} = live(conn, "/")
|
||||||
|
|
||||||
|
# Test with integer string value (should work with improved Float.parse)
|
||||||
|
speed_params = %{"speed" => "1"}
|
||||||
|
assert render_hook(view, "adjust_replay_speed", speed_params)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "sets correct page title", %{conn: conn} do
|
||||||
|
{:ok, view, _html} = live(conn, "/")
|
||||||
|
|
||||||
|
assert page_title(view) =~ "APRS Map"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "handles map_ready event", %{conn: conn} do
|
||||||
|
{:ok, view, _html} = live(conn, "/")
|
||||||
|
|
||||||
|
# Simulate map initialization
|
||||||
|
assert render_hook(view, "map_ready", %{})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
Loading…
Add table
Reference in a new issue