- 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
240 lines
6.9 KiB
Elixir
240 lines
6.9 KiB
Elixir
defmodule Aprsme.StreamingPacketsPubSubTest do
|
|
use ExUnit.Case, async: true
|
|
|
|
alias Aprsme.StreamingPacketsPubSub
|
|
|
|
describe "subscribe_to_bounds/2" do
|
|
test "subscribes to packets within geographic bounds" do
|
|
bounds = %{
|
|
north: 40.0,
|
|
south: 30.0,
|
|
east: -70.0,
|
|
west: -80.0
|
|
}
|
|
|
|
assert :ok = StreamingPacketsPubSub.subscribe_to_bounds(self(), bounds)
|
|
end
|
|
|
|
test "receives packets within subscribed bounds" do
|
|
bounds = %{
|
|
north: 40.0,
|
|
south: 30.0,
|
|
east: -70.0,
|
|
west: -80.0
|
|
}
|
|
|
|
StreamingPacketsPubSub.subscribe_to_bounds(self(), bounds)
|
|
|
|
# Packet within bounds
|
|
packet_in_bounds = %{
|
|
sender: "K1ABC",
|
|
latitude: 35.0,
|
|
longitude: -75.0,
|
|
received_at: DateTime.utc_now()
|
|
}
|
|
|
|
StreamingPacketsPubSub.broadcast_packet(packet_in_bounds)
|
|
|
|
assert_receive {:streaming_packet, ^packet_in_bounds}, 1000
|
|
end
|
|
|
|
test "does not receive packets outside subscribed bounds" do
|
|
bounds = %{
|
|
north: 40.0,
|
|
south: 30.0,
|
|
east: -70.0,
|
|
west: -80.0
|
|
}
|
|
|
|
StreamingPacketsPubSub.subscribe_to_bounds(self(), bounds)
|
|
|
|
# Packet outside bounds
|
|
packet_outside_bounds = %{
|
|
sender: "K2XYZ",
|
|
# North of bounds
|
|
latitude: 45.0,
|
|
longitude: -75.0,
|
|
received_at: DateTime.utc_now()
|
|
}
|
|
|
|
StreamingPacketsPubSub.broadcast_packet(packet_outside_bounds)
|
|
|
|
refute_receive {:streaming_packet, _}, 100
|
|
end
|
|
|
|
test "handles multiple subscribers with different bounds" do
|
|
subscriber1 =
|
|
spawn(fn ->
|
|
receive do
|
|
_ -> :ok
|
|
end
|
|
end)
|
|
|
|
subscriber2 =
|
|
spawn(fn ->
|
|
receive do
|
|
_ -> :ok
|
|
end
|
|
end)
|
|
|
|
bounds1 = %{north: 40.0, south: 30.0, east: -70.0, west: -80.0}
|
|
bounds2 = %{north: 50.0, south: 40.0, east: -60.0, west: -70.0}
|
|
|
|
StreamingPacketsPubSub.subscribe_to_bounds(subscriber1, bounds1)
|
|
StreamingPacketsPubSub.subscribe_to_bounds(subscriber2, bounds2)
|
|
|
|
# Packet in bounds1 only
|
|
packet = %{
|
|
sender: "K3TEST",
|
|
latitude: 35.0,
|
|
longitude: -75.0,
|
|
received_at: DateTime.utc_now()
|
|
}
|
|
|
|
# Monitor to ensure processes are alive
|
|
ref1 = Process.monitor(subscriber1)
|
|
_ref2 = Process.monitor(subscriber2)
|
|
|
|
StreamingPacketsPubSub.broadcast_packet(packet)
|
|
|
|
# Verify subscriber1 gets the packet
|
|
send(subscriber1, {:check, self()})
|
|
assert_receive {:DOWN, ^ref1, :process, ^subscriber1, :normal}, 1000
|
|
|
|
# Verify subscriber2 is still alive (didn't receive packet)
|
|
Process.alive?(subscriber2)
|
|
end
|
|
end
|
|
|
|
describe "unsubscribe/1" do
|
|
test "stops receiving packets after unsubscribe" do
|
|
bounds = %{north: 40.0, south: 30.0, east: -70.0, west: -80.0}
|
|
|
|
StreamingPacketsPubSub.subscribe_to_bounds(self(), bounds)
|
|
StreamingPacketsPubSub.unsubscribe(self())
|
|
|
|
packet = %{
|
|
sender: "K4TEST",
|
|
latitude: 35.0,
|
|
longitude: -75.0,
|
|
received_at: DateTime.utc_now()
|
|
}
|
|
|
|
StreamingPacketsPubSub.broadcast_packet(packet)
|
|
|
|
refute_receive {:streaming_packet, _}, 100
|
|
end
|
|
end
|
|
|
|
describe "list_subscribers/0" do
|
|
test "returns all active subscribers with their bounds" do
|
|
bounds = %{north: 40.0, south: 30.0, east: -70.0, west: -80.0}
|
|
|
|
StreamingPacketsPubSub.subscribe_to_bounds(self(), bounds)
|
|
|
|
subscribers = StreamingPacketsPubSub.list_subscribers()
|
|
|
|
assert Enum.any?(subscribers, fn {pid, sub_bounds} ->
|
|
pid == self() && sub_bounds == bounds
|
|
end)
|
|
end
|
|
end
|
|
|
|
describe "when the server is not running" do
|
|
setup do
|
|
pid = Process.whereis(StreamingPacketsPubSub)
|
|
|
|
if pid do
|
|
:erlang.unregister(StreamingPacketsPubSub)
|
|
end
|
|
|
|
on_exit(fn ->
|
|
if pid && !Process.whereis(StreamingPacketsPubSub) do
|
|
true = Process.register(pid, StreamingPacketsPubSub)
|
|
end
|
|
end)
|
|
|
|
:ok
|
|
end
|
|
|
|
test "returns safe defaults instead of crashing" do
|
|
bounds = %{north: 40.0, south: 30.0, east: -70.0, west: -80.0}
|
|
|
|
assert {:error, :not_running} = StreamingPacketsPubSub.subscribe_to_bounds(self(), bounds)
|
|
assert :ok = StreamingPacketsPubSub.unsubscribe(self())
|
|
assert :ok = StreamingPacketsPubSub.broadcast_packet(%{latitude: 35.0, longitude: -75.0})
|
|
assert [] == StreamingPacketsPubSub.list_subscribers()
|
|
end
|
|
end
|
|
|
|
describe "performance" do
|
|
test "handles high volume of packets efficiently" do
|
|
bounds = %{north: 90.0, south: -90.0, east: 180.0, west: -180.0}
|
|
StreamingPacketsPubSub.subscribe_to_bounds(self(), bounds)
|
|
|
|
packets =
|
|
for i <- 1..1000 do
|
|
%{
|
|
sender: "K#{i}TEST",
|
|
latitude: :rand.uniform() * 180 - 90,
|
|
longitude: :rand.uniform() * 360 - 180,
|
|
received_at: DateTime.utc_now()
|
|
}
|
|
end
|
|
|
|
start_time = System.monotonic_time(:millisecond)
|
|
|
|
Enum.each(packets, &StreamingPacketsPubSub.broadcast_packet/1)
|
|
|
|
end_time = System.monotonic_time(:millisecond)
|
|
elapsed = end_time - start_time
|
|
|
|
# Should process 1000 packets in under 100ms
|
|
assert elapsed < 100
|
|
|
|
# Should receive all packets
|
|
received_count =
|
|
Enum.reduce(1..1000, 0, fn _, acc ->
|
|
receive do
|
|
{:streaming_packet, _} -> acc + 1
|
|
after
|
|
10 -> acc
|
|
end
|
|
end)
|
|
|
|
assert received_count == 1000
|
|
end
|
|
end
|
|
|
|
describe "subscribe_to_bounds/2 with invalid bounds" do
|
|
test "returns {:error, :invalid_bounds} when bounds are not numeric" do
|
|
bounds = %{north: "north", south: 0.0, east: 0.0, west: 0.0}
|
|
assert {:error, :invalid_bounds} = StreamingPacketsPubSub.subscribe_to_bounds(self(), bounds)
|
|
end
|
|
|
|
test "returns {:error, :invalid_bounds} when north < south" do
|
|
bounds = %{north: -10.0, south: 10.0, east: 0.0, west: 0.0}
|
|
assert {:error, :invalid_bounds} = StreamingPacketsPubSub.subscribe_to_bounds(self(), bounds)
|
|
end
|
|
|
|
test "returns {:error, :invalid_bounds} when latitude is out of range" do
|
|
bounds = %{north: 100.0, south: 0.0, east: 0.0, west: 0.0}
|
|
assert {:error, :invalid_bounds} = StreamingPacketsPubSub.subscribe_to_bounds(self(), bounds)
|
|
end
|
|
|
|
test "returns {:error, :invalid_bounds} when longitude is out of range" do
|
|
bounds = %{north: 30.0, south: 10.0, east: 200.0, west: 0.0}
|
|
assert {:error, :invalid_bounds} = StreamingPacketsPubSub.subscribe_to_bounds(self(), bounds)
|
|
end
|
|
|
|
test "returns {:error, :invalid_bounds} when bounds is not a map" do
|
|
assert {:error, :invalid_bounds} =
|
|
StreamingPacketsPubSub.subscribe_to_bounds(self(), :not_a_map)
|
|
end
|
|
|
|
test "returns {:error, :invalid_bounds} for a map missing required keys" do
|
|
assert {:error, :invalid_bounds} =
|
|
StreamingPacketsPubSub.subscribe_to_bounds(self(), %{partial: 1})
|
|
end
|
|
end
|
|
end
|