- Replace apply/2 with direct fully-qualified calls in movement_test - Fix assert_receive timeouts < 1000ms across 8 test files - Move nested import statements to module-level scope - Fix tests with no assertions and add missing doctest - Replace weak type assertions with specific value checks - Fix conditional assertions and length/1 expensive patterns - Disable inappropriate Jump.CredoChecks.AvoidSocketAssignsInTest - Fix tests not calling application code with credo:disable - Add various credo:disable comments for legitimate patterns
711 lines
22 KiB
Elixir
711 lines
22 KiB
Elixir
defmodule Aprsme.Packets.PreparedQueriesTest do
|
|
use Aprsme.DataCase, async: false
|
|
|
|
alias Aprsme.Packet
|
|
alias Aprsme.Packets.PreparedQueries
|
|
alias Aprsme.Repo
|
|
|
|
defp create_positioned_packet(attrs) do
|
|
defaults = %{
|
|
sender: "TEST-1",
|
|
base_callsign: "TEST",
|
|
ssid: "1",
|
|
destination: "APRS",
|
|
data_type: "position",
|
|
lat: Decimal.new("33.0000"),
|
|
lon: Decimal.new("-96.0000"),
|
|
received_at: DateTime.truncate(DateTime.utc_now(), :second)
|
|
}
|
|
|
|
merged = Map.merge(defaults, attrs)
|
|
|
|
%Packet{}
|
|
|> Packet.changeset(Map.from_struct(Map.merge(%Packet{}, merged)))
|
|
|> Repo.insert()
|
|
end
|
|
|
|
describe "get_latest_packets_for_callsigns/1" do
|
|
test "returns full packet structs for multiple callsigns" do
|
|
{:ok, _} =
|
|
create_positioned_packet(%{
|
|
sender: "K5GVL-10",
|
|
base_callsign: "K5GVL",
|
|
ssid: "10",
|
|
lat: Decimal.new("33.1000"),
|
|
lon: Decimal.new("-96.6000"),
|
|
comment: "Digi TX"
|
|
})
|
|
|
|
{:ok, _} =
|
|
create_positioned_packet(%{
|
|
sender: "N5TXZ-10",
|
|
base_callsign: "N5TXZ",
|
|
ssid: "10",
|
|
lat: Decimal.new("33.2000"),
|
|
lon: Decimal.new("-96.5000"),
|
|
comment: "iGate"
|
|
})
|
|
|
|
result = PreparedQueries.get_latest_packets_for_callsigns(["K5GVL-10", "N5TXZ-10"])
|
|
|
|
assert length(result) == 2
|
|
assert Enum.all?(result, &is_struct(&1, Packet))
|
|
|
|
senders = Enum.map(result, & &1.sender)
|
|
assert "K5GVL-10" in senders
|
|
assert "N5TXZ-10" in senders
|
|
|
|
k5gvl = Enum.find(result, &(&1.sender == "K5GVL-10"))
|
|
assert_in_delta k5gvl.lat, 33.1, 0.01
|
|
assert_in_delta k5gvl.lon, -96.6, 0.01
|
|
end
|
|
|
|
test "returns empty list for empty input" do
|
|
assert PreparedQueries.get_latest_packets_for_callsigns([]) == []
|
|
end
|
|
|
|
test "returns empty list for nonexistent callsigns" do
|
|
result = PreparedQueries.get_latest_packets_for_callsigns(["NONEXIST-1", "FAKE-2"])
|
|
assert result == []
|
|
end
|
|
|
|
test "returns latest packet when multiple exist for a callsign" do
|
|
{:ok, _} =
|
|
create_positioned_packet(%{
|
|
sender: "K5GVL-10",
|
|
base_callsign: "K5GVL",
|
|
ssid: "10",
|
|
lat: Decimal.new("33.1000"),
|
|
lon: Decimal.new("-96.6000"),
|
|
received_at: DateTime.add(DateTime.utc_now(), -3600, :second)
|
|
})
|
|
|
|
{:ok, _} =
|
|
create_positioned_packet(%{
|
|
sender: "K5GVL-10",
|
|
base_callsign: "K5GVL",
|
|
ssid: "10",
|
|
lat: Decimal.new("34.0000"),
|
|
lon: Decimal.new("-97.0000"),
|
|
received_at: DateTime.truncate(DateTime.utc_now(), :second)
|
|
})
|
|
|
|
result = PreparedQueries.get_latest_packets_for_callsigns(["K5GVL-10"])
|
|
|
|
assert length(result) == 1
|
|
packet = hd(result)
|
|
assert is_struct(packet, Packet)
|
|
assert_in_delta packet.lat, 34.0, 0.01
|
|
assert_in_delta packet.lon, -97.0, 0.01
|
|
end
|
|
end
|
|
|
|
describe "get_latest_packet_for_callsign/1" do
|
|
test "prefers the latest positioned packet over a newer non-position packet" do
|
|
positioned_received_at = DateTime.utc_now() |> DateTime.add(-60, :second) |> DateTime.truncate(:second)
|
|
status_received_at = DateTime.truncate(DateTime.utc_now(), :second)
|
|
|
|
{:ok, _} =
|
|
create_positioned_packet(%{
|
|
sender: "K5GVL-10",
|
|
base_callsign: "K5GVL",
|
|
ssid: "10",
|
|
lat: Decimal.new("33.1000"),
|
|
lon: Decimal.new("-96.6000"),
|
|
received_at: positioned_received_at
|
|
})
|
|
|
|
{:ok, _} =
|
|
%Packet{}
|
|
|> Packet.changeset(%{
|
|
sender: "K5GVL-10",
|
|
base_callsign: "K5GVL",
|
|
ssid: "10",
|
|
destination: "APRS",
|
|
data_type: "status",
|
|
received_at: status_received_at,
|
|
has_position: false,
|
|
raw_packet: "K5GVL-10>APRS:>status update"
|
|
})
|
|
|> Repo.insert()
|
|
|
|
packet = PreparedQueries.get_latest_packet_for_callsign("K5GVL-10")
|
|
|
|
assert packet.has_position == true
|
|
assert_in_delta packet.lat, 33.1, 0.01
|
|
assert_in_delta packet.lon, -96.6, 0.01
|
|
end
|
|
|
|
test "falls back to the latest packet when no positioned packet exists" do
|
|
received_at = DateTime.truncate(DateTime.utc_now(), :second)
|
|
|
|
{:ok, _} =
|
|
%Packet{}
|
|
|> Packet.changeset(%{
|
|
sender: "STATUS-ONLY",
|
|
base_callsign: "STATUS",
|
|
ssid: "0",
|
|
destination: "APRS",
|
|
data_type: "status",
|
|
received_at: received_at,
|
|
has_position: false,
|
|
raw_packet: "STATUS-ONLY>APRS:>status update"
|
|
})
|
|
|> Repo.insert()
|
|
|
|
packet = PreparedQueries.get_latest_packet_for_callsign("STATUS-ONLY")
|
|
|
|
assert packet.sender == "STATUS-ONLY"
|
|
assert packet.has_position == false
|
|
end
|
|
|
|
test "ignores packets older than the retention window" do
|
|
# Insert a packet whose received_at is way beyond any reasonable retention
|
|
# window — older than the configured retention_days plus a margin. The
|
|
# query should not return it, so partition pruning can skip those
|
|
# partitions on the live table.
|
|
retention_days = Application.get_env(:aprsme, :packet_retention_days, 7)
|
|
ancient = DateTime.add(DateTime.utc_now(), -(retention_days + 30) * 86_400, :second)
|
|
|
|
{:ok, _} =
|
|
create_positioned_packet(%{
|
|
sender: "ANCIENT-1",
|
|
base_callsign: "ANCIENT",
|
|
ssid: "1",
|
|
received_at: DateTime.truncate(ancient, :second)
|
|
})
|
|
|
|
assert is_nil(PreparedQueries.get_latest_packet_for_callsign("ANCIENT-1"))
|
|
end
|
|
end
|
|
|
|
describe "get_nearby_stations_knn/4" do
|
|
test "returns stations sorted by distance from the point" do
|
|
# Close to center
|
|
{:ok, _} =
|
|
create_positioned_packet(%{
|
|
sender: "NEAR-1",
|
|
base_callsign: "NEAR",
|
|
ssid: "1",
|
|
lat: Decimal.new("33.0100"),
|
|
lon: Decimal.new("-96.0100")
|
|
})
|
|
|
|
# Further
|
|
{:ok, _} =
|
|
create_positioned_packet(%{
|
|
sender: "FAR-1",
|
|
base_callsign: "FAR",
|
|
ssid: "1",
|
|
lat: Decimal.new("33.5000"),
|
|
lon: Decimal.new("-96.5000")
|
|
})
|
|
|
|
result = PreparedQueries.get_nearby_stations_knn(33.0, -96.0, nil, %{limit: 5})
|
|
|
|
callsigns = Enum.map(result, & &1.callsign)
|
|
assert "NEAR-1" in callsigns
|
|
assert "FAR-1" in callsigns
|
|
# NEAR-1 must come before FAR-1 (sorted by distance)
|
|
assert Enum.find_index(result, &(&1.callsign == "NEAR-1")) <
|
|
Enum.find_index(result, &(&1.callsign == "FAR-1"))
|
|
end
|
|
|
|
test "excludes the specified callsign" do
|
|
{:ok, _} =
|
|
create_positioned_packet(%{
|
|
sender: "ME-1",
|
|
base_callsign: "ME",
|
|
ssid: "1",
|
|
lat: Decimal.new("33.0100"),
|
|
lon: Decimal.new("-96.0100")
|
|
})
|
|
|
|
{:ok, _} =
|
|
create_positioned_packet(%{
|
|
sender: "OTHER-1",
|
|
base_callsign: "OTHER",
|
|
ssid: "1",
|
|
lat: Decimal.new("33.0200"),
|
|
lon: Decimal.new("-96.0200")
|
|
})
|
|
|
|
result = PreparedQueries.get_nearby_stations_knn(33.0, -96.0, "ME-1", %{limit: 5})
|
|
|
|
callsigns = Enum.map(result, & &1.callsign)
|
|
refute "ME-1" in callsigns
|
|
assert "OTHER-1" in callsigns
|
|
end
|
|
|
|
test "honors :max_radius_km option" do
|
|
# Within 10km of (33.0, -96.0): ~1km offset
|
|
{:ok, _} =
|
|
create_positioned_packet(%{
|
|
sender: "INSIDE-1",
|
|
base_callsign: "INSIDE",
|
|
ssid: "1",
|
|
lat: Decimal.new("33.0100"),
|
|
lon: Decimal.new("-96.0100")
|
|
})
|
|
|
|
# Outside 10km: ~50km away
|
|
{:ok, _} =
|
|
create_positioned_packet(%{
|
|
sender: "OUTSIDE-1",
|
|
base_callsign: "OUTSIDE",
|
|
ssid: "1",
|
|
lat: Decimal.new("33.5000"),
|
|
lon: Decimal.new("-96.5000")
|
|
})
|
|
|
|
result = PreparedQueries.get_nearby_stations_knn(33.0, -96.0, nil, %{limit: 10, max_radius_km: 10})
|
|
|
|
callsigns = Enum.map(result, & &1.callsign)
|
|
assert "INSIDE-1" in callsigns
|
|
refute "OUTSIDE-1" in callsigns
|
|
end
|
|
end
|
|
|
|
describe "get_latest_positions_for_callsigns/1" do
|
|
test "returns positions for multiple callsigns" do
|
|
{:ok, _} =
|
|
create_positioned_packet(%{
|
|
sender: "K5GVL-10",
|
|
base_callsign: "K5GVL",
|
|
ssid: "10",
|
|
lat: Decimal.new("33.1000"),
|
|
lon: Decimal.new("-96.6000")
|
|
})
|
|
|
|
{:ok, _} =
|
|
create_positioned_packet(%{
|
|
sender: "N5TXZ-10",
|
|
base_callsign: "N5TXZ",
|
|
ssid: "10",
|
|
lat: Decimal.new("33.2000"),
|
|
lon: Decimal.new("-96.5000")
|
|
})
|
|
|
|
result = PreparedQueries.get_latest_positions_for_callsigns(["K5GVL-10", "N5TXZ-10"])
|
|
|
|
assert length(result) == 2
|
|
callsigns = Enum.map(result, & &1.callsign)
|
|
assert "K5GVL-10" in callsigns
|
|
assert "N5TXZ-10" in callsigns
|
|
|
|
k5gvl = Enum.find(result, &(&1.callsign == "K5GVL-10"))
|
|
assert_in_delta k5gvl.lat, 33.1, 0.01
|
|
assert_in_delta k5gvl.lng, -96.6, 0.01
|
|
end
|
|
|
|
test "returns empty list for empty input" do
|
|
assert PreparedQueries.get_latest_positions_for_callsigns([]) == []
|
|
end
|
|
|
|
test "returns empty list for nonexistent callsigns" do
|
|
result = PreparedQueries.get_latest_positions_for_callsigns(["NONEXIST-1", "FAKE-2"])
|
|
assert result == []
|
|
end
|
|
|
|
test "is case-insensitive" do
|
|
{:ok, _} =
|
|
create_positioned_packet(%{
|
|
sender: "K5GVL-10",
|
|
base_callsign: "K5GVL",
|
|
ssid: "10",
|
|
lat: Decimal.new("33.1000"),
|
|
lon: Decimal.new("-96.6000")
|
|
})
|
|
|
|
result = PreparedQueries.get_latest_positions_for_callsigns(["k5gvl-10"])
|
|
|
|
assert length(result) == 1
|
|
assert hd(result).callsign == "K5GVL-10"
|
|
end
|
|
|
|
test "returns latest position when multiple packets exist for a callsign" do
|
|
{:ok, _} =
|
|
create_positioned_packet(%{
|
|
sender: "K5GVL-10",
|
|
base_callsign: "K5GVL",
|
|
ssid: "10",
|
|
lat: Decimal.new("33.1000"),
|
|
lon: Decimal.new("-96.6000"),
|
|
received_at: DateTime.add(DateTime.utc_now(), -3600, :second)
|
|
})
|
|
|
|
{:ok, _} =
|
|
create_positioned_packet(%{
|
|
sender: "K5GVL-10",
|
|
base_callsign: "K5GVL",
|
|
ssid: "10",
|
|
lat: Decimal.new("34.0000"),
|
|
lon: Decimal.new("-97.0000"),
|
|
received_at: DateTime.truncate(DateTime.utc_now(), :second)
|
|
})
|
|
|
|
result = PreparedQueries.get_latest_positions_for_callsigns(["K5GVL-10"])
|
|
|
|
assert length(result) == 1
|
|
position = hd(result)
|
|
assert_in_delta position.lat, 34.0, 0.01
|
|
assert_in_delta position.lng, -97.0, 0.01
|
|
end
|
|
end
|
|
|
|
describe "get_nearby_weather_stations/4" do
|
|
setup do
|
|
# San Francisco center: 37.7749, -122.4194
|
|
center_lat = 37.7749
|
|
center_lon = -122.4194
|
|
|
|
# Station A: ~0.5 miles north, weather 1h ago (should be included)
|
|
{:ok, station_a} =
|
|
create_positioned_packet(%{
|
|
sender: "WX-A",
|
|
base_callsign: "WX-A",
|
|
ssid: "0",
|
|
lat: Decimal.new("37.7822"),
|
|
lon: Decimal.new("-122.4194"),
|
|
temperature: 72.5,
|
|
humidity: 65.0,
|
|
pressure: 1013.25,
|
|
wind_speed: 5.0,
|
|
wind_direction: 180,
|
|
symbol_table_id: "/",
|
|
symbol_code: "_",
|
|
comment: "Weather Station A",
|
|
received_at: DateTime.add(DateTime.utc_now(), -3600, :second)
|
|
})
|
|
|
|
# Station B: ~10 miles east, weather 2h ago (should be included)
|
|
{:ok, station_b} =
|
|
create_positioned_packet(%{
|
|
sender: "WX-B-1",
|
|
base_callsign: "WX-B",
|
|
ssid: "1",
|
|
lat: Decimal.new("37.7749"),
|
|
lon: Decimal.new("-122.2700"),
|
|
temperature: 68.0,
|
|
humidity: 70.0,
|
|
wind_gust: 12.0,
|
|
symbol_table_id: "/",
|
|
symbol_code: "_",
|
|
comment: "Weather Station B",
|
|
received_at: DateTime.add(DateTime.utc_now(), -7200, :second)
|
|
})
|
|
|
|
# Station C: ~50 miles south, weather 1h ago (should be excluded - outside radius)
|
|
{:ok, _station_c} =
|
|
create_positioned_packet(%{
|
|
sender: "WX-C",
|
|
base_callsign: "WX-C",
|
|
ssid: "0",
|
|
lat: Decimal.new("37.0500"),
|
|
lon: Decimal.new("-122.4194"),
|
|
temperature: 75.0,
|
|
humidity: 60.0,
|
|
symbol_table_id: "/",
|
|
symbol_code: "_",
|
|
comment: "Weather Station C",
|
|
received_at: DateTime.add(DateTime.utc_now(), -3600, :second)
|
|
})
|
|
|
|
# Station D: ~5 miles west, weather 8h ago (should be excluded - outside time window)
|
|
{:ok, _station_d} =
|
|
create_positioned_packet(%{
|
|
sender: "WX-D",
|
|
base_callsign: "WX-D",
|
|
ssid: "0",
|
|
lat: Decimal.new("37.7749"),
|
|
lon: Decimal.new("-122.4900"),
|
|
temperature: 70.0,
|
|
humidity: 68.0,
|
|
symbol_table_id: "/",
|
|
symbol_code: "_",
|
|
comment: "Weather Station D",
|
|
received_at: DateTime.add(DateTime.utc_now(), -28_800, :second)
|
|
})
|
|
|
|
# Station E: ~5 miles southeast, no weather data (should be excluded)
|
|
{:ok, _station_e} =
|
|
create_positioned_packet(%{
|
|
sender: "WX-E",
|
|
base_callsign: "WX-E",
|
|
ssid: "0",
|
|
lat: Decimal.new("37.7400"),
|
|
lon: Decimal.new("-122.3800"),
|
|
symbol_table_id: "/",
|
|
symbol_code: "-",
|
|
comment: "Regular Station",
|
|
received_at: DateTime.add(DateTime.utc_now(), -3600, :second)
|
|
})
|
|
|
|
# Duplicate SSID for Station B (should be deduplicated)
|
|
{:ok, _station_b2} =
|
|
create_positioned_packet(%{
|
|
sender: "WX-B-2",
|
|
base_callsign: "WX-B",
|
|
ssid: "2",
|
|
lat: Decimal.new("37.7749"),
|
|
lon: Decimal.new("-122.2700"),
|
|
temperature: 69.0,
|
|
humidity: 72.0,
|
|
symbol_table_id: "/",
|
|
symbol_code: "_",
|
|
comment: "Weather Station B SSID 2",
|
|
received_at: DateTime.add(DateTime.utc_now(), -1800, :second)
|
|
})
|
|
|
|
%{
|
|
center_lat: center_lat,
|
|
center_lon: center_lon,
|
|
station_a: station_a,
|
|
station_b: station_b
|
|
}
|
|
end
|
|
|
|
test "returns nearby weather stations within radius and time window", %{
|
|
center_lat: lat,
|
|
center_lon: lon
|
|
} do
|
|
result = PreparedQueries.get_nearby_weather_stations(lat, lon, 15.0)
|
|
|
|
assert length(result) == 2
|
|
callsigns = Enum.map(result, & &1.callsign)
|
|
assert "WX-A" in callsigns
|
|
assert "WX-B-2" in callsigns
|
|
end
|
|
|
|
test "returns results ordered by distance (closest first)", %{center_lat: lat, center_lon: lon} do
|
|
result = PreparedQueries.get_nearby_weather_stations(lat, lon, 15.0)
|
|
|
|
assert length(result) == 2
|
|
# Station A (~0.5 miles) should be first
|
|
assert hd(result).callsign == "WX-A"
|
|
assert hd(result).distance_miles < 1.0
|
|
|
|
# Station B (~8-10 miles) should be second
|
|
second = Enum.at(result, 1)
|
|
assert second.callsign == "WX-B-2"
|
|
assert second.distance_miles > 7.0
|
|
assert second.distance_miles < 10.0
|
|
end
|
|
|
|
test "excludes stations outside radius", %{center_lat: lat, center_lon: lon} do
|
|
# Use 5 mile radius - should only get Station A
|
|
result = PreparedQueries.get_nearby_weather_stations(lat, lon, 5.0)
|
|
|
|
assert length(result) == 1
|
|
assert hd(result).callsign == "WX-A"
|
|
end
|
|
|
|
test "excludes stations outside time window", %{center_lat: lat, center_lon: lon} do
|
|
# Use 1 hour window - should get both A (1h ago) and B-2 (30min ago)
|
|
result = PreparedQueries.get_nearby_weather_stations(lat, lon, 15.0, hours: 1)
|
|
|
|
assert length(result) == 2
|
|
callsigns = Enum.map(result, & &1.callsign)
|
|
assert "WX-A" in callsigns
|
|
assert "WX-B-2" in callsigns
|
|
end
|
|
|
|
test "excludes stations without weather data", %{center_lat: lat, center_lon: lon} do
|
|
result = PreparedQueries.get_nearby_weather_stations(lat, lon, 15.0)
|
|
|
|
callsigns = Enum.map(result, & &1.callsign)
|
|
refute "WX-E" in callsigns
|
|
end
|
|
|
|
test "deduplicates by base_callsign and returns most recent", %{
|
|
center_lat: lat,
|
|
center_lon: lon
|
|
} do
|
|
result = PreparedQueries.get_nearby_weather_stations(lat, lon, 15.0)
|
|
|
|
base_callsigns = Enum.map(result, & &1.base_callsign)
|
|
assert length(base_callsigns) == length(Enum.uniq(base_callsigns))
|
|
|
|
# Should get WX-B-2 (most recent) not WX-B-1
|
|
wx_b = Enum.find(result, &(&1.base_callsign == "WX-B"))
|
|
assert wx_b.callsign == "WX-B-2"
|
|
end
|
|
|
|
test "respects limit option", %{center_lat: lat, center_lon: lon} do
|
|
result = PreparedQueries.get_nearby_weather_stations(lat, lon, 15.0, limit: 1)
|
|
|
|
assert length(result) == 1
|
|
assert hd(result).callsign == "WX-A"
|
|
end
|
|
|
|
test "returns all required fields", %{center_lat: lat, center_lon: lon} do
|
|
result = PreparedQueries.get_nearby_weather_stations(lat, lon, 15.0)
|
|
|
|
station = hd(result)
|
|
assert byte_size(station.callsign) > 0
|
|
assert byte_size(station.base_callsign) > 0
|
|
assert is_float(station.lat)
|
|
assert is_float(station.lon)
|
|
assert is_float(station.distance_miles)
|
|
assert byte_size(station.symbol_table_id) > 0
|
|
assert byte_size(station.symbol_code) > 0
|
|
assert byte_size(station.comment) > 0
|
|
assert %DateTime{} = station.received_at
|
|
|
|
# Weather fields (may be nil)
|
|
case station.temperature do
|
|
nil -> :ok
|
|
value when is_float(value) -> :ok
|
|
end
|
|
|
|
case station.humidity do
|
|
nil -> :ok
|
|
value when is_float(value) -> :ok
|
|
end
|
|
|
|
case station.pressure do
|
|
nil -> :ok
|
|
value when is_float(value) -> :ok
|
|
end
|
|
|
|
case station.wind_speed do
|
|
nil -> :ok
|
|
value when is_float(value) -> :ok
|
|
end
|
|
|
|
case station.wind_direction do
|
|
nil -> :ok
|
|
value when is_integer(value) -> :ok
|
|
end
|
|
|
|
case station.wind_gust do
|
|
nil -> :ok
|
|
value when is_float(value) -> :ok
|
|
end
|
|
|
|
case station.rain_1h do
|
|
nil -> :ok
|
|
value when is_float(value) -> :ok
|
|
end
|
|
|
|
case station.rain_24h do
|
|
nil -> :ok
|
|
value when is_float(value) -> :ok
|
|
end
|
|
|
|
case station.rain_since_midnight do
|
|
nil -> :ok
|
|
value when is_float(value) -> :ok
|
|
end
|
|
end
|
|
|
|
test "returns empty list when no stations in radius", %{center_lat: _lat, center_lon: _lon} do
|
|
# Use coordinates far from any station
|
|
result = PreparedQueries.get_nearby_weather_stations(40.0, -100.0, 5.0)
|
|
|
|
assert result == []
|
|
end
|
|
|
|
test "handles custom hours option", %{center_lat: lat, center_lon: lon} do
|
|
# Use 3 hour window - should get both A and B
|
|
result = PreparedQueries.get_nearby_weather_stations(lat, lon, 15.0, hours: 3)
|
|
|
|
assert length(result) == 2
|
|
end
|
|
end
|
|
|
|
describe "get_recent_packets_in_bounds/2" do
|
|
test "returns packets within the bounding box" do
|
|
{:ok, _} =
|
|
create_positioned_packet(%{
|
|
sender: "INBOUNDS-1",
|
|
base_callsign: "INBOUNDS",
|
|
lat: Decimal.new("33.5"),
|
|
lon: Decimal.new("-96.5")
|
|
})
|
|
|
|
{:ok, _} =
|
|
create_positioned_packet(%{
|
|
sender: "OUTOFBOUNDS-1",
|
|
base_callsign: "OUTOFBOUNDS",
|
|
lat: Decimal.new("50.0"),
|
|
lon: Decimal.new("-120.0")
|
|
})
|
|
|
|
# bounds = [north, south, east, west]
|
|
result = PreparedQueries.get_recent_packets_in_bounds([34.0, 33.0, -96.0, -97.0], 24)
|
|
|
|
senders = Enum.map(result, & &1.sender)
|
|
assert "INBOUNDS-1" in senders
|
|
refute "OUTOFBOUNDS-1" in senders
|
|
end
|
|
end
|
|
|
|
describe "get_packet_count_in_area/2" do
|
|
test "counts only packets inside the bounding box" do
|
|
{:ok, _} =
|
|
create_positioned_packet(%{
|
|
sender: "COUNT-IN-1",
|
|
base_callsign: "COUNT",
|
|
lat: Decimal.new("33.25"),
|
|
lon: Decimal.new("-96.25")
|
|
})
|
|
|
|
{:ok, _} =
|
|
create_positioned_packet(%{
|
|
sender: "COUNT-OUT",
|
|
base_callsign: "COUNT",
|
|
lat: Decimal.new("55.0"),
|
|
lon: Decimal.new("-30.0")
|
|
})
|
|
|
|
# bounds = [north, south, east, west]
|
|
count = PreparedQueries.get_packet_count_in_area([34.0, 33.0, -96.0, -97.0], 24)
|
|
assert is_integer(count)
|
|
assert count >= 1
|
|
end
|
|
|
|
test "returns 0 when area has no packets" do
|
|
assert PreparedQueries.get_packet_count_in_area([0.0, -1.0, 180.0, 179.0], 1) == 0
|
|
end
|
|
end
|
|
|
|
describe "has_weather_packets?/1" do
|
|
test "returns true when callsign has weather packets" do
|
|
{:ok, _} =
|
|
create_positioned_packet(%{
|
|
sender: "HASWX-1",
|
|
base_callsign: "HASWX",
|
|
temperature: 72.0,
|
|
humidity: 50.0
|
|
})
|
|
|
|
assert PreparedQueries.has_weather_packets?("HASWX-1")
|
|
end
|
|
|
|
test "returns false when callsign has no weather packets" do
|
|
refute PreparedQueries.has_weather_packets?("NOWX-NEVER")
|
|
end
|
|
end
|
|
|
|
describe "weather_callsigns/1" do
|
|
test "returns a MapSet containing callsigns with weather data" do
|
|
{:ok, _} =
|
|
create_positioned_packet(%{
|
|
sender: "WXSET-1",
|
|
base_callsign: "WXSET",
|
|
has_weather: true,
|
|
temperature: 65.0
|
|
})
|
|
|
|
result = PreparedQueries.weather_callsigns(["WXSET-1", "NOWX-1"])
|
|
assert %MapSet{} = result
|
|
assert MapSet.member?(result, "WXSET-1")
|
|
refute MapSet.member?(result, "NOWX-1")
|
|
end
|
|
|
|
test "returns an empty MapSet for empty input" do
|
|
assert PreparedQueries.weather_callsigns([]) == MapSet.new()
|
|
end
|
|
end
|
|
end
|