"""Regression: an implicit demo-mode fallback must not be permanent. explorer.rpc.RpcClient used to set ``self.demo = True`` forever on the first RPCConnectionError. web_app caches one client per process and gunicorn workers live for hours, so one timeout while the node was starting left the site serving demo data / 502s until a restart. The fallback is now a short cooldown after which the node is retried. """ import os import sys from pathlib import Path import pytest EXPLORER = Path(__file__).resolve().parent.parent / "explorer" if str(EXPLORER) not in sys.path: sys.path.insert(0, str(EXPLORER)) @pytest.fixture def client(monkeypatch): monkeypatch.delenv("DEMO_MODE", raising=False) import importlib import config as cfg importlib.reload(cfg) import rpc as rpcmod importlib.reload(rpcmod) c = rpcmod.RpcClient() assert c.demo is False return c, rpcmod def test_fallback_is_a_cooldown_not_a_latch(client, monkeypatch): c, rpcmod = client calls = {"n": 0} def flaky(method, params): calls["n"] += 1 if calls["n"] == 1: raise rpcmod.RPCConnectionError("boom") return 12566 monkeypatch.setattr(c, "_http_call", flaky) now = [1000.0] monkeypatch.setattr(rpcmod.time, "monotonic", lambda: now[0]) # First call: node down -> demo data, demo flag on for the cooldown. assert c.call("getblockcount") != 12566 assert c.demo is True and "unreachable" in c.demo_reason # Inside the cooldown: still demo, node NOT hammered. now[0] += 1.0 c.call("getblockcount") assert calls["n"] == 1 # After the cooldown: node retried, real answer, flag cleared. now[0] += c.IMPLICIT_DEMO_COOLDOWN assert c.call("getblockcount") == 12566 assert c.demo is False and c.demo_reason is None def test_explicit_demo_mode_stays_sticky(monkeypatch): monkeypatch.setenv("DEMO_MODE", "1") import importlib import config as cfg importlib.reload(cfg) import rpc as rpcmod importlib.reload(rpcmod) c = rpcmod.RpcClient() assert c.demo is True monkeypatch.setattr(rpcmod.time, "monotonic", lambda: 1e9) assert c.demo is True # no cooldown can expire an explicit DEMO_MODE def test_demo_mode_zero_raises_instead_of_faking(monkeypatch): monkeypatch.setenv("DEMO_MODE", "0") import importlib import config as cfg importlib.reload(cfg) import rpc as rpcmod importlib.reload(rpcmod) c = rpcmod.RpcClient() monkeypatch.setattr(c, "_http_call", lambda m, p: (_ for _ in ()).throw(rpcmod.RPCConnectionError("down"))) with pytest.raises(rpcmod.RPCConnectionError): c.call("getblockcount") assert c.demo is False