dexcord/test/support/env_sandbox.ex
Luna f44d30954a fix(gateway): decode HELLO coalesced with the 101 upgrade; hermetic test env
Root cause of the order/seed-dependent integration failures: when the WS
upgrade's 101 response and the server's first frame (HELLO) arrive in one TCP
segment, Mint returns them as [status, headers, {:data, hello}, {:done}] - the
{:data} ordered BEFORE the {:done} that builds the websocket. process_responses
only decoded {:data} once the websocket existed, so the coalesced HELLO was
silently dropped and the client wedged in :hello_wait. Coalescing is far more
likely under full-suite scheduler load, which also starved the cadence-sensitive
tests - hence the flaky, seed-varying failures. Buffer pre-upgrade bytes and
decode them the instant the websocket is built.

Test hermeticity:
- Add Dexcord.EnvSandbox: snapshot all :dexcord app env in setup, restore
  wholesale on_exit. Replaces every file's hand-rolled per-key save/restore so no
  knob (timing bounds, budgets, ratelimit clock, base URLs) can leak between tests.
- gateway_fatal_test: await the directly-started supervisor's full termination
  (monitor + DOWN) instead of a fire-and-forget Process.exit, so its globally-named
  singletons and named ETS are gone before the next test's tree starts.
2026-07-04 01:25:48 -03:00

41 lines
1.6 KiB
Elixir

defmodule Dexcord.EnvSandbox do
@moduledoc false
# A single, bulletproof application-env fixture for the test suite.
#
# Many tests tune `:dexcord` application env (timing knobs, budgets, rate-limit
# clocks, base URLs, ...) to exercise real logic quickly. Any key a test forgets
# to restore leaks into whatever test runs next and produces order-dependent,
# seed-sensitive failures. Rather than have every file maintain its own hand-rolled
# save/restore list (and get it subtly wrong), tests call `sandbox_env/0` once in
# `setup`: it snapshots the ENTIRE `:dexcord` env up front and, via `on_exit`,
# restores it wholesale afterwards - deleting any key the test introduced and
# resetting every pre-existing key to its captured value. Tests then `put_env`
# freely with no per-key bookkeeping.
import ExUnit.Callbacks, only: [on_exit: 1]
@doc """
Snapshots all `:dexcord` application env and registers wholesale restoration on
test exit. Call once from a test's `setup`.
"""
@spec sandbox_env :: :ok
def sandbox_env do
before = Application.get_all_env(:dexcord)
before_keys = MapSet.new(before, fn {k, _v} -> k end)
on_exit(fn ->
# Drop any key the test introduced that wasn't there before.
for {key, _val} <- Application.get_all_env(:dexcord),
not MapSet.member?(before_keys, key) do
Application.delete_env(:dexcord, key)
end
# Restore every snapshotted key to exactly its captured value.
for {key, val} <- before do
Application.put_env(:dexcord, key, val)
end
end)
:ok
end
end