@@ -295,43 +295,57 @@ defmodule DatagroutConduit.Transport.WsTest do
295295 assert state . pings_sent == 0
296296 end
297297
298- test ":ping_tick degrades gracefully when conn_pid is a non-WebSockex process" do
299- # WebSockex.send_frame `exit`s when the target is not a WebSockex
300- # process; safe_send_ping/1 must catch that so the Ws GenServer
301- # doesn't crash on every disconnect.
302- { :ok , dummy } = Agent . start_link ( fn -> nil end )
303- state = % Ws { conn_pid: dummy , ping_interval_ms: 50_000 }
298+ # Helper: returns a freshly-dead pid. `WebSockex.send_frame/2` exits
299+ # with `:noproc` immediately when called against a dead pid — exactly
300+ # the production crash scenario `safe_send_ping/1` is designed to
301+ # catch — and it avoids both the 5-second `:gen.call` timeout and the
302+ # noisy `[error] Agent.Server received unexpected message …` lines
303+ # that an `Agent.start_link/1` dummy would emit when poked with the
304+ # `:"$websockex_send"` protocol.
305+ defp dead_pid do
306+ pid = spawn ( fn -> :ok end )
307+ ref = Process . monitor ( pid )
308+
309+ receive do
310+ { :DOWN , ^ ref , :process , ^ pid , _ } -> :ok
311+ after
312+ 500 -> flunk ( "spawned process did not exit in time" )
313+ end
314+
315+ pid
316+ end
317+
318+ test ":ping_tick degrades gracefully when the Conn process has died" do
319+ # WebSockex.send_frame `exit`s when the target Conn is gone;
320+ # safe_send_ping/1 must catch that so the Ws GenServer doesn't
321+ # crash alongside it.
322+ state = % Ws { conn_pid: dead_pid ( ) , ping_interval_ms: 50_000 }
304323
305324 assert { :noreply , new_state } = Ws . handle_info ( :ping_tick , state )
306325
307326 # Send failed, so pings_sent stays at 0 — but the GenServer survived.
308327 assert new_state . pings_sent == 0
309- Agent . stop ( dummy )
310328 end
311329
312330 test ":ping_tick reschedules itself at ping_interval_ms" do
313- # Use a non-WebSockex conn so the send fails gracefully; we only care
314- # about the reschedule signal here.
315- { :ok , dummy } = Agent . start_link ( fn -> nil end )
316- state = % Ws { conn_pid: dummy , ping_interval_ms: 30 }
331+ # Dead conn so the send fails fast; we only care about the
332+ # reschedule signal here.
333+ state = % Ws { conn_pid: dead_pid ( ) , ping_interval_ms: 30 }
317334
318335 # First tick fires immediately via direct handle_info call.
319336 { :noreply , _state } = Ws . handle_info ( :ping_tick , state )
320337
321338 # Production handler scheduled the NEXT tick — verify it lands in
322339 # this process's mailbox within ~100ms.
323340 assert_receive :ping_tick , 200
324- Agent . stop ( dummy )
325341 end
326342
327343 test "ping_interval_ms = 0 disables rescheduling (no further tick)" do
328- { :ok , dummy } = Agent . start_link ( fn -> nil end )
329- state = % Ws { conn_pid: dummy , ping_interval_ms: 0 }
344+ state = % Ws { conn_pid: dead_pid ( ) , ping_interval_ms: 0 }
330345
331346 { :noreply , _state } = Ws . handle_info ( :ping_tick , state )
332347
333348 refute_receive :ping_tick , 100
334- Agent . stop ( dummy )
335349 end
336350
337351 test "pings_sent/1 returns the live counter through the GenServer API" do
0 commit comments