mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-06-17 18:25:26 -04:00
refactor(tests): centralize fake endpoint resolver cleanup
Test-only refactor continuing #2523. Centralizes the final repeated fake src.endpoint_resolver cleanup pattern into a focused import-state helper.
This commit is contained in:
committed by
GitHub
parent
301d1109b5
commit
452a94fb1b
@@ -12,6 +12,11 @@ Use ``clear_fake_database_modules`` to evict a *stubbed* ``core.database`` (and
|
|||||||
its companion ``src.database``) that another test left in import state, without
|
its companion ``src.database``) that another test left in import state, without
|
||||||
touching a real ``core.database`` loaded from disk.
|
touching a real ``core.database`` loaded from disk.
|
||||||
|
|
||||||
|
Use ``clear_fake_endpoint_resolver_modules`` to evict a *stubbed*
|
||||||
|
``src.endpoint_resolver`` (and the route modules that imported it) that another
|
||||||
|
test left in import state, without touching a real ``src.endpoint_resolver``
|
||||||
|
loaded from disk.
|
||||||
|
|
||||||
Background: importing ``routes.session_routes`` also sets ``session_routes`` on
|
Background: importing ``routes.session_routes`` also sets ``session_routes`` on
|
||||||
the parent ``routes`` package object. A ``from routes import session_routes``
|
the parent ``routes`` package object. A ``from routes import session_routes``
|
||||||
or ``import routes.session_routes as X`` statement resolves through that parent
|
or ``import routes.session_routes as X`` statement resolves through that parent
|
||||||
@@ -93,6 +98,50 @@ def clear_fake_database_modules():
|
|||||||
delattr(parent, "database")
|
delattr(parent, "database")
|
||||||
|
|
||||||
|
|
||||||
|
def clear_fake_endpoint_resolver_modules(*extra_modules):
|
||||||
|
"""Evict a *stubbed* ``src.endpoint_resolver`` (and dependent route modules).
|
||||||
|
|
||||||
|
Test-only. Several route tests need the *real* ``src.endpoint_resolver`` URL
|
||||||
|
helpers, but another test may have installed a fake — a stub module with no
|
||||||
|
on-disk ``__file__`` — into ``sys.modules`` and onto the ``src`` package
|
||||||
|
during collection. The route modules (``routes.model_routes`` and any extras
|
||||||
|
passed in, e.g. ``routes.chat_routes``) get cached against that fake on first
|
||||||
|
import, so they must be evicted too.
|
||||||
|
|
||||||
|
Conservative, mirroring ``clear_fake_database_modules`` and the per-file
|
||||||
|
guards it replaces:
|
||||||
|
|
||||||
|
* It acts only when ``src.endpoint_resolver`` is a fake/stub, detected by a
|
||||||
|
falsy ``__file__`` (missing, ``None``, or empty string) — exactly the
|
||||||
|
truthiness check the old inline guards used. A real resolver loaded from
|
||||||
|
disk carries a truthy ``__file__`` and is left untouched, as is the case
|
||||||
|
where nothing is cached. When the resolver is real, the dependent route
|
||||||
|
modules are left untouched too.
|
||||||
|
* When it does act, it drops ``routes.model_routes`` plus every name in
|
||||||
|
``extra_modules``.
|
||||||
|
* It removes the ``src.endpoint_resolver`` parent-package attribute only when
|
||||||
|
that attribute is the same fake object being evicted.
|
||||||
|
|
||||||
|
Behavior delta vs. the old bare ``sys.modules.pop(...)`` guards: dependent
|
||||||
|
modules are dropped via :func:`clear_module`, which also clears the parent
|
||||||
|
``routes`` package attribute (e.g. ``routes.model_routes``), not just the
|
||||||
|
``sys.modules`` entry. This prevents a stale parent attribute from shadowing
|
||||||
|
the fresh import — the same parent-attr handling the rest of this helper
|
||||||
|
family already applies.
|
||||||
|
"""
|
||||||
|
parent = sys.modules.get("src")
|
||||||
|
attr = getattr(parent, "endpoint_resolver", None) if parent is not None else None
|
||||||
|
mod = sys.modules.get("src.endpoint_resolver") or attr
|
||||||
|
if mod is None or getattr(mod, "__file__", None):
|
||||||
|
return
|
||||||
|
sys.modules.pop("src.endpoint_resolver", None)
|
||||||
|
if parent is not None and attr is mod:
|
||||||
|
delattr(parent, "endpoint_resolver")
|
||||||
|
clear_module("routes.model_routes")
|
||||||
|
for name in extra_modules:
|
||||||
|
clear_module(name)
|
||||||
|
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def preserve_import_state(*module_names):
|
def preserve_import_state(*module_names):
|
||||||
"""Save and restore sys.modules entries and parent-package attributes.
|
"""Save and restore sys.modules entries and parent-package attributes.
|
||||||
|
|||||||
@@ -1,12 +1,9 @@
|
|||||||
import json
|
import json
|
||||||
import sys
|
|
||||||
from types import SimpleNamespace
|
from types import SimpleNamespace
|
||||||
|
|
||||||
_endpoint_resolver = sys.modules.get("src.endpoint_resolver")
|
from tests.helpers.import_state import clear_fake_endpoint_resolver_modules
|
||||||
if _endpoint_resolver is not None and not getattr(_endpoint_resolver, "__file__", None):
|
|
||||||
sys.modules.pop("src.endpoint_resolver", None)
|
clear_fake_endpoint_resolver_modules("routes.chat_routes")
|
||||||
sys.modules.pop("routes.model_routes", None)
|
|
||||||
sys.modules.pop("routes.chat_routes", None)
|
|
||||||
|
|
||||||
from routes import chat_routes
|
from routes import chat_routes
|
||||||
|
|
||||||
|
|||||||
@@ -25,12 +25,11 @@ from unittest.mock import MagicMock
|
|||||||
import httpx
|
import httpx
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
from tests.helpers.import_state import clear_fake_endpoint_resolver_modules
|
||||||
|
|
||||||
# Match test_model_routes.py: if another test stubbed src.endpoint_resolver
|
# Match test_model_routes.py: if another test stubbed src.endpoint_resolver
|
||||||
# during collection, drop the stub so the real URL helpers load here.
|
# during collection, drop the stub so the real URL helpers load here.
|
||||||
_endpoint_resolver = sys.modules.get("src.endpoint_resolver")
|
clear_fake_endpoint_resolver_modules()
|
||||||
if _endpoint_resolver is not None and not getattr(_endpoint_resolver, "__file__", None):
|
|
||||||
sys.modules.pop("src.endpoint_resolver", None)
|
|
||||||
sys.modules.pop("routes.model_routes", None)
|
|
||||||
|
|
||||||
if "core.database" not in sys.modules:
|
if "core.database" not in sys.modules:
|
||||||
_core_db = types.ModuleType("core.database")
|
_core_db = types.ModuleType("core.database")
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import pytest
|
|||||||
|
|
||||||
from tests.helpers.import_state import (
|
from tests.helpers.import_state import (
|
||||||
clear_fake_database_modules,
|
clear_fake_database_modules,
|
||||||
|
clear_fake_endpoint_resolver_modules,
|
||||||
clear_module,
|
clear_module,
|
||||||
preserve_import_state,
|
preserve_import_state,
|
||||||
)
|
)
|
||||||
@@ -16,6 +17,16 @@ _SENTINEL = "tests._import_state_test_sentinel"
|
|||||||
# tests never leak into the real core/src packages.
|
# tests never leak into the real core/src packages.
|
||||||
_DB_NAMES = ("core", "core.database", "src", "src.database")
|
_DB_NAMES = ("core", "core.database", "src", "src.database")
|
||||||
|
|
||||||
|
# Names touched by clear_fake_endpoint_resolver_modules — snapshot/restore these
|
||||||
|
# so the tests never leak into the real src/routes packages.
|
||||||
|
_RESOLVER_NAMES = (
|
||||||
|
"src",
|
||||||
|
"src.endpoint_resolver",
|
||||||
|
"routes",
|
||||||
|
"routes.model_routes",
|
||||||
|
"routes.chat_routes",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_absent_module_is_removed_after_block():
|
def test_absent_module_is_removed_after_block():
|
||||||
assert _SENTINEL not in sys.modules
|
assert _SENTINEL not in sys.modules
|
||||||
@@ -250,3 +261,166 @@ def test_clear_fake_database_noop_when_nothing_cached():
|
|||||||
clear_fake_database_modules() # must not raise
|
clear_fake_database_modules() # must not raise
|
||||||
|
|
||||||
assert "core.database" not in sys.modules
|
assert "core.database" not in sys.modules
|
||||||
|
|
||||||
|
|
||||||
|
def test_clear_fake_resolver_removes_stub_endpoint_resolver():
|
||||||
|
with preserve_import_state(*_RESOLVER_NAMES):
|
||||||
|
fake_src = types.ModuleType("src")
|
||||||
|
fake_resolver = types.ModuleType("src.endpoint_resolver") # no __file__ => stub
|
||||||
|
fake_src.endpoint_resolver = fake_resolver
|
||||||
|
sys.modules["src"] = fake_src
|
||||||
|
sys.modules["src.endpoint_resolver"] = fake_resolver
|
||||||
|
|
||||||
|
clear_fake_endpoint_resolver_modules()
|
||||||
|
|
||||||
|
assert "src.endpoint_resolver" not in sys.modules
|
||||||
|
assert not hasattr(fake_src, "endpoint_resolver")
|
||||||
|
|
||||||
|
|
||||||
|
def test_clear_fake_resolver_preserves_real_endpoint_resolver():
|
||||||
|
with preserve_import_state(*_RESOLVER_NAMES):
|
||||||
|
fake_src = types.ModuleType("src")
|
||||||
|
real_resolver = types.ModuleType("src.endpoint_resolver")
|
||||||
|
real_resolver.__file__ = "/somewhere/src/endpoint_resolver.py" # looks on-disk
|
||||||
|
fake_src.endpoint_resolver = real_resolver
|
||||||
|
sys.modules["src"] = fake_src
|
||||||
|
sys.modules["src.endpoint_resolver"] = real_resolver
|
||||||
|
|
||||||
|
clear_fake_endpoint_resolver_modules()
|
||||||
|
|
||||||
|
assert sys.modules["src.endpoint_resolver"] is real_resolver
|
||||||
|
assert fake_src.endpoint_resolver is real_resolver
|
||||||
|
|
||||||
|
|
||||||
|
def test_clear_fake_resolver_evicts_empty_file_resolver():
|
||||||
|
"""A resolver with __file__ = "" is a stub under the old truthiness guard, so
|
||||||
|
it (and its dependents) must be evicted, not preserved."""
|
||||||
|
with preserve_import_state(*_RESOLVER_NAMES):
|
||||||
|
fake_src = types.ModuleType("src")
|
||||||
|
empty_resolver = types.ModuleType("src.endpoint_resolver")
|
||||||
|
empty_resolver.__file__ = "" # falsy => stub
|
||||||
|
fake_src.endpoint_resolver = empty_resolver
|
||||||
|
sys.modules["src"] = fake_src
|
||||||
|
sys.modules["src.endpoint_resolver"] = empty_resolver
|
||||||
|
model_routes = types.ModuleType("routes.model_routes")
|
||||||
|
sys.modules["routes.model_routes"] = model_routes
|
||||||
|
|
||||||
|
clear_fake_endpoint_resolver_modules()
|
||||||
|
|
||||||
|
assert "src.endpoint_resolver" not in sys.modules
|
||||||
|
assert not hasattr(fake_src, "endpoint_resolver")
|
||||||
|
assert "routes.model_routes" not in sys.modules
|
||||||
|
|
||||||
|
|
||||||
|
def test_clear_fake_resolver_removes_model_routes_when_resolver_fake():
|
||||||
|
"""model_routes is dropped, and its parent `routes` attr is cleared too —
|
||||||
|
the behavior delta over the old bare sys.modules.pop() guards."""
|
||||||
|
with preserve_import_state(*_RESOLVER_NAMES):
|
||||||
|
fake_src = types.ModuleType("src")
|
||||||
|
fake_resolver = types.ModuleType("src.endpoint_resolver")
|
||||||
|
fake_src.endpoint_resolver = fake_resolver
|
||||||
|
sys.modules["src"] = fake_src
|
||||||
|
sys.modules["src.endpoint_resolver"] = fake_resolver
|
||||||
|
|
||||||
|
fake_routes = types.ModuleType("routes")
|
||||||
|
model_routes = types.ModuleType("routes.model_routes")
|
||||||
|
fake_routes.model_routes = model_routes
|
||||||
|
sys.modules["routes"] = fake_routes
|
||||||
|
sys.modules["routes.model_routes"] = model_routes
|
||||||
|
|
||||||
|
clear_fake_endpoint_resolver_modules()
|
||||||
|
|
||||||
|
assert "routes.model_routes" not in sys.modules
|
||||||
|
assert not hasattr(fake_routes, "model_routes")
|
||||||
|
|
||||||
|
|
||||||
|
def test_clear_fake_resolver_removes_extra_modules_when_resolver_fake():
|
||||||
|
with preserve_import_state(*_RESOLVER_NAMES):
|
||||||
|
fake_src = types.ModuleType("src")
|
||||||
|
fake_resolver = types.ModuleType("src.endpoint_resolver")
|
||||||
|
fake_src.endpoint_resolver = fake_resolver
|
||||||
|
sys.modules["src"] = fake_src
|
||||||
|
sys.modules["src.endpoint_resolver"] = fake_resolver
|
||||||
|
|
||||||
|
fake_routes = types.ModuleType("routes")
|
||||||
|
chat_routes = types.ModuleType("routes.chat_routes")
|
||||||
|
fake_routes.chat_routes = chat_routes
|
||||||
|
sys.modules["routes"] = fake_routes
|
||||||
|
sys.modules["routes.chat_routes"] = chat_routes
|
||||||
|
|
||||||
|
clear_fake_endpoint_resolver_modules("routes.chat_routes")
|
||||||
|
|
||||||
|
assert "routes.chat_routes" not in sys.modules
|
||||||
|
assert not hasattr(fake_routes, "chat_routes")
|
||||||
|
|
||||||
|
|
||||||
|
def test_clear_fake_resolver_keeps_dependents_when_resolver_real():
|
||||||
|
with preserve_import_state(*_RESOLVER_NAMES):
|
||||||
|
fake_src = types.ModuleType("src")
|
||||||
|
real_resolver = types.ModuleType("src.endpoint_resolver")
|
||||||
|
real_resolver.__file__ = "/somewhere/src/endpoint_resolver.py"
|
||||||
|
fake_src.endpoint_resolver = real_resolver
|
||||||
|
sys.modules["src"] = fake_src
|
||||||
|
sys.modules["src.endpoint_resolver"] = real_resolver
|
||||||
|
|
||||||
|
model_routes = types.ModuleType("routes.model_routes")
|
||||||
|
chat_routes = types.ModuleType("routes.chat_routes")
|
||||||
|
sys.modules["routes.model_routes"] = model_routes
|
||||||
|
sys.modules["routes.chat_routes"] = chat_routes
|
||||||
|
|
||||||
|
clear_fake_endpoint_resolver_modules("routes.chat_routes")
|
||||||
|
|
||||||
|
assert sys.modules["routes.model_routes"] is model_routes
|
||||||
|
assert sys.modules["routes.chat_routes"] is chat_routes
|
||||||
|
|
||||||
|
|
||||||
|
def test_clear_fake_resolver_noop_when_nothing_cached():
|
||||||
|
with preserve_import_state(*_RESOLVER_NAMES):
|
||||||
|
sys.modules.pop("src.endpoint_resolver", None)
|
||||||
|
fake_src = types.ModuleType("src") # no endpoint_resolver attr
|
||||||
|
sys.modules["src"] = fake_src
|
||||||
|
model_routes = types.ModuleType("routes.model_routes")
|
||||||
|
sys.modules["routes.model_routes"] = model_routes
|
||||||
|
|
||||||
|
clear_fake_endpoint_resolver_modules() # must not raise
|
||||||
|
|
||||||
|
assert "src.endpoint_resolver" not in sys.modules
|
||||||
|
# dependents are left alone when the resolver was never cached
|
||||||
|
assert sys.modules["routes.model_routes"] is model_routes
|
||||||
|
|
||||||
|
|
||||||
|
def test_clear_fake_resolver_keeps_parent_attr_pointing_elsewhere():
|
||||||
|
"""When the cached src.endpoint_resolver is a stub but the `endpoint_resolver`
|
||||||
|
attr on the src package points at a *different* object, the attr is left
|
||||||
|
intact — only the same fake object is unlinked."""
|
||||||
|
with preserve_import_state(*_RESOLVER_NAMES):
|
||||||
|
fake_src = types.ModuleType("src")
|
||||||
|
cached_fake = types.ModuleType("src.endpoint_resolver") # the stub in sys.modules
|
||||||
|
other = types.ModuleType("src.endpoint_resolver") # parent attr points here
|
||||||
|
fake_src.endpoint_resolver = other
|
||||||
|
sys.modules["src"] = fake_src
|
||||||
|
sys.modules["src.endpoint_resolver"] = cached_fake
|
||||||
|
|
||||||
|
clear_fake_endpoint_resolver_modules()
|
||||||
|
|
||||||
|
assert "src.endpoint_resolver" not in sys.modules
|
||||||
|
assert fake_src.endpoint_resolver is other
|
||||||
|
|
||||||
|
|
||||||
|
def test_clear_fake_resolver_uses_parent_attr_when_not_in_sys_modules():
|
||||||
|
"""A stub reachable only via the src package's `endpoint_resolver` attribute
|
||||||
|
(not in sys.modules) is still detected, unlinked, and triggers dependent
|
||||||
|
eviction."""
|
||||||
|
with preserve_import_state(*_RESOLVER_NAMES):
|
||||||
|
sys.modules.pop("src.endpoint_resolver", None)
|
||||||
|
fake_src = types.ModuleType("src")
|
||||||
|
fake_resolver = types.ModuleType("src.endpoint_resolver")
|
||||||
|
fake_src.endpoint_resolver = fake_resolver
|
||||||
|
sys.modules["src"] = fake_src
|
||||||
|
model_routes = types.ModuleType("routes.model_routes")
|
||||||
|
sys.modules["routes.model_routes"] = model_routes
|
||||||
|
|
||||||
|
clear_fake_endpoint_resolver_modules()
|
||||||
|
|
||||||
|
assert not hasattr(fake_src, "endpoint_resolver")
|
||||||
|
assert "routes.model_routes" not in sys.modules
|
||||||
|
|||||||
@@ -11,12 +11,11 @@ from types import SimpleNamespace
|
|||||||
import httpx
|
import httpx
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
_endpoint_resolver = sys.modules.get("src.endpoint_resolver")
|
from tests.helpers.import_state import clear_fake_endpoint_resolver_modules
|
||||||
if _endpoint_resolver is not None and not getattr(_endpoint_resolver, "__file__", None):
|
|
||||||
# Other tests stub this module during collection. These helper tests need
|
# Other tests stub this module during collection. These helper tests need
|
||||||
# the real URL normalization helpers so Anthropic /v1 handling is covered.
|
# the real URL normalization helpers so Anthropic /v1 handling is covered.
|
||||||
sys.modules.pop("src.endpoint_resolver", None)
|
clear_fake_endpoint_resolver_modules()
|
||||||
sys.modules.pop("routes.model_routes", None)
|
|
||||||
|
|
||||||
if "core.database" not in sys.modules:
|
if "core.database" not in sys.modules:
|
||||||
_core_db = types.ModuleType("core.database")
|
_core_db = types.ModuleType("core.database")
|
||||||
|
|||||||
Reference in New Issue
Block a user