diff --git a/test/aprsme/is_test.exs b/test/aprsme/is_test.exs index b6c7457..f058bd0 100644 --- a/test/aprsme/is_test.exs +++ b/test/aprsme/is_test.exs @@ -489,6 +489,32 @@ defmodule Aprsme.IsTest do assert Process.cancel_timer(timer) == false assert Process.cancel_timer(keepalive) == false end + + test "closes an active socket on terminate" do + {client, listen, cleanup} = loopback_socket() + state = build_state(%{socket: client, buffer: ""}) + + capture_log(fn -> + assert Aprsme.Is.terminate(:normal, state) == :normal + end) + + # Socket should be closed after terminate + assert :gen_tcp.send(client, "X") == {:error, :closed} + + :gen_tcp.close(listen) + cleanup.() + end + + test "cancels safety_valve_timer on terminate" do + safety_timer = Process.send_after(self(), :safety, to_timeout(minute: 5)) + state = build_state(%{socket: nil, safety_valve_timer: safety_timer}) + + capture_log(fn -> + assert Aprsme.Is.terminate(:normal, state) == :normal + end) + + assert Process.cancel_timer(safety_timer) == false + end end describe "code_change/3" do @@ -787,6 +813,27 @@ defmodule Aprsme.IsTest do Process.cancel_timer(new_state.timer) end) end + + test "activate is a no-op when backpressure is already active", %{socket: socket} do + state = build_state(%{socket: socket, backpressure_active: true}) + + assert {:noreply, ^state} = + Aprsme.Is.handle_info({:backpressure, :activate}, state) + end + + test "activate is a no-op when there is no socket" do + state = build_state(%{socket: nil, backpressure_active: false}) + + assert {:noreply, ^state} = + Aprsme.Is.handle_info({:backpressure, :activate}, state) + end + + test "deactivate is a no-op when backpressure is already inactive" do + state = build_state(%{socket: nil, backpressure_active: false}) + + assert {:noreply, ^state} = + Aprsme.Is.handle_info({:backpressure, :deactivate}, state) + end end describe "send_message/3 and /1 helpers" do @@ -1224,4 +1271,49 @@ defmodule Aprsme.IsTest do end) end end + + describe "get_status/0 with a running process under the module name" do + test "falls back to the disconnected status when the GenServer doesn't respond" do + # Register a minimal process under the Aprsme.Is name that doesn't know + # how to handle :get_status. GenServer.call/3 to a non-GenServer raises + # :exit — status_from/1 catches it and returns disconnected_status with + # the rotate.aprs2.net fallback. + parent = self() + + pid = + spawn(fn -> + receive do + :stop -> :ok + after + 5_000 -> :ok + end + + send(parent, :done) + end) + + # Unregister any prior registration first. + if Process.whereis(Aprsme.Is) do + :erlang.unregister(Aprsme.Is) + end + + true = Process.register(pid, Aprsme.Is) + + try do + # Trap exits so the :exit from GenServer.call doesn't kill us. + Process.flag(:trap_exit, true) + + # capture_log because disconnected_status logs nothing, but be defensive. + status = Aprsme.Is.get_status() + + assert status.connected == false + assert is_map(status) + after + if Process.whereis(Aprsme.Is) == pid do + :erlang.unregister(Aprsme.Is) + end + + send(pid, :stop) + end + end + end end