mirror of
https://github.com/BigBodyCobain/Shadowbroker.git
synced 2026-08-10 04:30:36 +02:00
release: prepare v0.9.7
This commit is contained in:
@@ -0,0 +1,322 @@
|
||||
"""Sprint 3B: Backend Split Verification — regression tests.
|
||||
|
||||
Covers invariants established by the Phase 1 foundation extraction:
|
||||
1. auth.py / limiter.py / node_state.py are importable without importing main.
|
||||
2. _NODE_SYNC_STOP is a threading.Event.
|
||||
3. set_sync_state / get_sync_state round-trip is correct.
|
||||
4. globals()["_NODE_SYNC_STATE"] pattern is absent from main.py sync paths.
|
||||
5. auth/node_state import topology has no circular dependency on main.
|
||||
6. Peer-push routes remain tied to _verify_peer_push_hmac.
|
||||
7. Lifespan node-state wiring invariants remain correct after extraction.
|
||||
"""
|
||||
|
||||
import inspect
|
||||
import os
|
||||
import threading
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Helper
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _read_backend_source(filename: str) -> str:
|
||||
path = os.path.join(os.path.dirname(__file__), "..", filename)
|
||||
with open(os.path.normpath(path), encoding="utf-8") as fh:
|
||||
return fh.read()
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 1. Foundation modules must not drag in main
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestFoundationModuleImportIsolation:
|
||||
"""auth, limiter, and node_state must not import main at the module level.
|
||||
|
||||
This preserves the ability for future router files to import from these
|
||||
modules without triggering a full main.py import cycle.
|
||||
"""
|
||||
|
||||
def test_auth_does_not_import_main(self):
|
||||
source = _read_backend_source("auth.py")
|
||||
assert "import main" not in source, "auth.py must not import main"
|
||||
assert "from main " not in source, "auth.py must not import from main"
|
||||
|
||||
def test_limiter_does_not_import_main(self):
|
||||
source = _read_backend_source("limiter.py")
|
||||
assert "import main" not in source, "limiter.py must not import main"
|
||||
assert "from main " not in source, "limiter.py must not import from main"
|
||||
|
||||
def test_node_state_does_not_import_main(self):
|
||||
source = _read_backend_source("node_state.py")
|
||||
assert "import main" not in source, "node_state.py must not import main"
|
||||
assert "from main " not in source, "node_state.py must not import from main"
|
||||
|
||||
def test_require_admin_importable_from_auth(self):
|
||||
"""require_admin must be a callable exported by auth."""
|
||||
from auth import require_admin
|
||||
assert callable(require_admin)
|
||||
|
||||
def test_limiter_importable_from_limiter_module(self):
|
||||
"""limiter must be a Limiter instance exported by limiter.py."""
|
||||
from limiter import limiter as rate_limiter
|
||||
from slowapi import Limiter
|
||||
assert isinstance(rate_limiter, Limiter)
|
||||
|
||||
def test_node_state_exports_importable(self):
|
||||
"""_NODE_SYNC_STOP, get_sync_state, set_sync_state must all be importable."""
|
||||
from node_state import _NODE_SYNC_STOP, get_sync_state, set_sync_state
|
||||
assert callable(get_sync_state)
|
||||
assert callable(set_sync_state)
|
||||
assert _NODE_SYNC_STOP is not None
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 2. _NODE_SYNC_STOP type
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestNodeSyncStopType:
|
||||
def test_node_sync_stop_is_threading_event(self):
|
||||
from node_state import _NODE_SYNC_STOP
|
||||
assert isinstance(_NODE_SYNC_STOP, threading.Event), (
|
||||
"_NODE_SYNC_STOP must be a threading.Event"
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 3. set_sync_state / get_sync_state round-trip
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestSyncStateRoundTrip:
|
||||
def test_set_then_get_returns_new_state(self):
|
||||
from node_state import get_sync_state, set_sync_state
|
||||
from services.mesh.mesh_infonet_sync_support import SyncWorkerState
|
||||
|
||||
original = get_sync_state()
|
||||
new_state = SyncWorkerState()
|
||||
try:
|
||||
set_sync_state(new_state)
|
||||
assert get_sync_state() is new_state, (
|
||||
"get_sync_state must return the exact object passed to set_sync_state"
|
||||
)
|
||||
finally:
|
||||
set_sync_state(original)
|
||||
|
||||
def test_get_is_stable_without_set(self):
|
||||
from node_state import get_sync_state
|
||||
assert get_sync_state() is get_sync_state(), (
|
||||
"get_sync_state must return the same object on repeated calls "
|
||||
"when set_sync_state has not been called between them"
|
||||
)
|
||||
|
||||
def test_set_sync_state_is_module_scoped(self):
|
||||
"""set_sync_state must modify the node_state module's own namespace
|
||||
(not just a local variable), so subsequent get_sync_state() calls from
|
||||
any importing module see the updated value."""
|
||||
import node_state
|
||||
from services.mesh.mesh_infonet_sync_support import SyncWorkerState
|
||||
|
||||
original = node_state.get_sync_state()
|
||||
sentinel = SyncWorkerState()
|
||||
try:
|
||||
node_state.set_sync_state(sentinel)
|
||||
assert node_state._NODE_SYNC_STATE is sentinel, (
|
||||
"set_sync_state must update node_state._NODE_SYNC_STATE in-module"
|
||||
)
|
||||
assert node_state.get_sync_state() is sentinel
|
||||
finally:
|
||||
node_state.set_sync_state(original)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 4. globals()["_NODE_SYNC_STATE"] pattern absent from main.py sync paths
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestGlobalsPatternAbsent:
|
||||
"""No direct globals()["_NODE_SYNC_STATE"] assignment must remain in the
|
||||
sync-relevant paths of main.py after the 3B extraction."""
|
||||
|
||||
def test_globals_pattern_absent_from_run_public_sync_cycle(self):
|
||||
import main
|
||||
source = inspect.getsource(main._run_public_sync_cycle)
|
||||
assert 'globals()["_NODE_SYNC_STATE"]' not in source, (
|
||||
'_run_public_sync_cycle must use set_sync_state(), '
|
||||
'not globals()["_NODE_SYNC_STATE"]'
|
||||
)
|
||||
|
||||
def test_globals_pattern_absent_from_public_infonet_sync_loop(self):
|
||||
import main
|
||||
source = inspect.getsource(main._public_infonet_sync_loop)
|
||||
assert 'globals()["_NODE_SYNC_STATE"]' not in source, (
|
||||
'_public_infonet_sync_loop must use set_sync_state(), '
|
||||
'not globals()["_NODE_SYNC_STATE"]'
|
||||
)
|
||||
|
||||
def test_globals_pattern_absent_from_lifespan(self):
|
||||
import main
|
||||
source = inspect.getsource(main.lifespan)
|
||||
assert 'globals()["_NODE_SYNC_STATE"]' not in source, (
|
||||
'lifespan must use set_sync_state(), not globals()["_NODE_SYNC_STATE"]'
|
||||
)
|
||||
|
||||
def test_node_sync_state_direct_ref_absent_from_main(self):
|
||||
"""_NODE_SYNC_STATE must not be referenced directly in main.py at all —
|
||||
all access must go through get_sync_state() / set_sync_state()."""
|
||||
source = _read_backend_source("main.py")
|
||||
assert "_NODE_SYNC_STATE" not in source, (
|
||||
"main.py must not reference _NODE_SYNC_STATE directly; "
|
||||
"all access must use get_sync_state() / set_sync_state()"
|
||||
)
|
||||
|
||||
def test_set_sync_state_called_in_sync_cycle(self):
|
||||
import main
|
||||
source = inspect.getsource(main._run_public_sync_cycle)
|
||||
assert "set_sync_state(" in source, (
|
||||
"_run_public_sync_cycle must call set_sync_state() to update node sync state"
|
||||
)
|
||||
|
||||
def test_set_sync_state_called_in_sync_loop(self):
|
||||
import main
|
||||
source = inspect.getsource(main._public_infonet_sync_loop)
|
||||
assert "set_sync_state(" in source, (
|
||||
"_public_infonet_sync_loop must call set_sync_state() to update node sync state"
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 5. Import topology — no circular dependency on main
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestImportTopology:
|
||||
"""auth.py, limiter.py, and node_state.py must never import from main.py.
|
||||
|
||||
A circular import would break the isolation goal of the 3B extraction and
|
||||
cause import-time failures when router files later import from these modules.
|
||||
"""
|
||||
|
||||
def test_auth_no_main_import(self):
|
||||
source = _read_backend_source("auth.py")
|
||||
assert "import main" not in source
|
||||
assert "from main " not in source
|
||||
|
||||
def test_node_state_no_main_import(self):
|
||||
source = _read_backend_source("node_state.py")
|
||||
assert "import main" not in source
|
||||
assert "from main " not in source
|
||||
|
||||
def test_node_state_no_auth_import(self):
|
||||
"""node_state must not import auth — the state layer must stay
|
||||
dependency-free so it can be imported first during startup."""
|
||||
source = _read_backend_source("node_state.py")
|
||||
assert "import auth" not in source
|
||||
assert "from auth " not in source
|
||||
|
||||
def test_limiter_no_main_import(self):
|
||||
source = _read_backend_source("limiter.py")
|
||||
assert "import main" not in source
|
||||
assert "from main " not in source
|
||||
|
||||
def test_main_imports_from_node_state(self):
|
||||
"""main.py must declare its node_state imports via 'from node_state import'."""
|
||||
source = _read_backend_source("main.py")
|
||||
assert "from node_state import" in source, (
|
||||
"main.py must import node-state helpers from node_state"
|
||||
)
|
||||
|
||||
def test_main_imports_from_auth(self):
|
||||
"""main.py must declare its auth imports via 'from auth import'."""
|
||||
source = _read_backend_source("main.py")
|
||||
assert "from auth import" in source, (
|
||||
"main.py must import auth helpers from auth"
|
||||
)
|
||||
|
||||
def test_main_imports_from_limiter(self):
|
||||
"""main.py must import the shared limiter instance from limiter.py."""
|
||||
source = _read_backend_source("main.py")
|
||||
assert "from limiter import" in source, (
|
||||
"main.py must import the limiter instance from limiter"
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 6. Peer-push routes protected by _verify_peer_push_hmac
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestPeerPushHmacProtection:
|
||||
"""The peer-push ingest routes must call _verify_peer_push_hmac before
|
||||
accepting any payload, and that function must originate in auth.py."""
|
||||
|
||||
def test_verify_peer_push_hmac_defined_in_auth(self):
|
||||
source = _read_backend_source("auth.py")
|
||||
assert "def _verify_peer_push_hmac" in source, (
|
||||
"_verify_peer_push_hmac must be defined in auth.py"
|
||||
)
|
||||
|
||||
def test_verify_peer_push_hmac_imported_into_main(self):
|
||||
source = _read_backend_source("main.py")
|
||||
assert "_verify_peer_push_hmac" in source, (
|
||||
"_verify_peer_push_hmac must appear in main.py (imported from auth)"
|
||||
)
|
||||
|
||||
def test_infonet_peer_push_calls_verify_hmac(self):
|
||||
import main
|
||||
source = inspect.getsource(main.infonet_peer_push)
|
||||
assert "_verify_peer_push_hmac" in source, (
|
||||
"infonet_peer_push must call _verify_peer_push_hmac before accepting payload"
|
||||
)
|
||||
|
||||
def test_gate_peer_push_calls_verify_hmac(self):
|
||||
import main
|
||||
source = inspect.getsource(main.gate_peer_push)
|
||||
assert "_verify_peer_push_hmac" in source, (
|
||||
"gate_peer_push must call _verify_peer_push_hmac before accepting payload"
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 7. Lifespan node-state wiring invariants
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestLifespanNodeStateWiring:
|
||||
"""The lifespan startup block must wire node-state correctly after 3B
|
||||
extraction: set_sync_state replaces the old globals() assignment, and
|
||||
_NODE_SYNC_STOP is cleared before the sync thread is started."""
|
||||
|
||||
def _lifespan_source(self) -> str:
|
||||
import main
|
||||
return inspect.getsource(main.lifespan)
|
||||
|
||||
def test_lifespan_calls_set_sync_state(self):
|
||||
source = self._lifespan_source()
|
||||
assert "set_sync_state(" in source, (
|
||||
"lifespan must call set_sync_state() to initialize node sync state "
|
||||
"for the disabled-at-startup path"
|
||||
)
|
||||
|
||||
def test_lifespan_clears_node_sync_stop(self):
|
||||
source = self._lifespan_source()
|
||||
assert "_NODE_SYNC_STOP.clear()" in source, (
|
||||
"lifespan must call _NODE_SYNC_STOP.clear() before starting the sync loop"
|
||||
)
|
||||
|
||||
def test_lifespan_does_not_reference_node_sync_state_directly(self):
|
||||
source = self._lifespan_source()
|
||||
assert "_NODE_SYNC_STATE" not in source, (
|
||||
"lifespan must not reference _NODE_SYNC_STATE directly; "
|
||||
"use set_sync_state() / get_sync_state()"
|
||||
)
|
||||
|
||||
def test_main_imports_set_sync_state_from_node_state(self):
|
||||
source = _read_backend_source("main.py")
|
||||
# Confirm set_sync_state is present and comes from node_state
|
||||
assert "set_sync_state" in source
|
||||
node_state_block = next(
|
||||
(line for line in source.splitlines() if "from node_state import" in line),
|
||||
"",
|
||||
)
|
||||
assert node_state_block, "main.py must have a 'from node_state import' line"
|
||||
# set_sync_state may span a multi-line import; verify it appears somewhere
|
||||
# near the node_state import (within the module-level import block).
|
||||
import_section = source[:source.find("\n\n\n")]
|
||||
assert "set_sync_state" in import_section, (
|
||||
"set_sync_state must be imported at module level from node_state"
|
||||
)
|
||||
Reference in New Issue
Block a user