Scope core.* module stubs to the test, not the module (#1513)

Three test files (test_auth_regressions, test_auth_event_loop,
test_null_owner_gates) install stubs for core.database / core.auth /
src.endpoint_resolver at module-import time, so they outlive the
file and are still present in sys.modules when later-collected test
files try to import the real modules. The stubs are minimal (a
handful of MagicMock attrs) so the import chain that follows fails
with ImportError on the very next real import.

test_companion_pairing also leaks, with a twist: its _DBStub
subclass returns a MagicMock for *any* attribute including dunders,
so the next test that does `from core.database import *` reads
`__all__` as a MagicMock and dies with 'Item in __all__ must be
str, not MagicMock'.

Move the stub installation into an autouse fixture per file and
register each stub with monkeypatch.setitem so sys.modules is
restored to its pre-test state on teardown. Tighten _DBStub to
refuse dunder names so __all__ stays undefined. _CAPTURED is
cleared per test so the mint-token assertions see a fresh dict.

Before: 3 test files fail at collection time (test_chat_image_routing,
test_context_compactor, test_webhook_ssrf_resilience). After: 0
collection errors. 1365/1370 pass, 1 skip, 4 unrelated pre-existing
failures (verified against origin/main baseline).

Out of scope: test_task_scheduler_session_delivery::
test_session_delivery_survives_empty_database also fails in the
full suite due to order-dependent state from a different test
file. That's a separate leak with a different root cause.
This commit is contained in:
Ernest Hysa
2026-06-03 06:23:40 +01:00
committed by GitHub
parent 0dd67143f1
commit a91321d1d8
5 changed files with 93 additions and 76 deletions
+35 -38
View File
@@ -24,32 +24,38 @@ from unittest.mock import MagicMock
# the conftest's `sqlalchemy.*` MagicMock stubs ("metaclass conflict").
# Stub also a handful of route modules each of these targeted modules
# happens to drag in at import-time.
for _stub in [
"core.database",
"core.auth",
"src.endpoint_resolver",
]:
if _stub not in sys.modules:
m = types.ModuleType(_stub)
# Provide the names the importers will look up.
if _stub == "core.database":
m.Base = MagicMock()
m.SessionLocal = MagicMock()
m.CalendarCal = MagicMock()
m.CalendarEvent = MagicMock()
m.Document = MagicMock()
m.DocumentVersion = MagicMock()
m.Session = MagicMock()
m.ChatMessage = MagicMock()
m.GalleryImage = MagicMock()
m.GalleryAlbum = MagicMock()
m.Note = MagicMock()
m.ScheduledTask = MagicMock()
m.TaskRun = MagicMock()
m.ModelEndpoint = MagicMock()
elif _stub == "core.auth":
m.AuthManager = MagicMock()
sys.modules[_stub] = m
@pytest.fixture(autouse=True)
def _null_owner_stubs(monkeypatch):
for _stub, _attrs in (
("core.database", (
"Base", "SessionLocal", "CalendarCal", "CalendarEvent",
"Document", "DocumentVersion", "Session", "ChatMessage",
"GalleryImage", "GalleryAlbum", "Note", "ScheduledTask",
"TaskRun", "ModelEndpoint", "Webhook",
)),
("core.auth", ("AuthManager",)),
("src.endpoint_resolver", ()),
):
if _stub not in sys.modules:
m = types.ModuleType(_stub)
for _name in _attrs:
setattr(m, _name, MagicMock())
sys.modules[_stub] = m
else:
m = sys.modules[_stub]
for _name in _attrs:
if not hasattr(m, _name):
setattr(m, _name, MagicMock())
monkeypatch.setitem(sys.modules, _stub, m)
# src.webhook_manager is only dragged in by _import_webhook_helper().
if "src.webhook_manager" not in sys.modules:
wm = types.ModuleType("src.webhook_manager")
wm.WebhookManager = MagicMock()
wm.validate_webhook_url = MagicMock()
wm.validate_events = MagicMock()
sys.modules["src.webhook_manager"] = wm
monkeypatch.setitem(sys.modules, "src.webhook_manager", wm)
from fastapi import HTTPException
@@ -179,18 +185,9 @@ def test_gallery_owner_filter_passes_user():
# calendar/notes/gallery gates above and _verify_session_owner.
def _import_webhook_helper():
"""Import routes.webhook_routes without dragging in the real webhook
manager / database. Stub src.webhook_manager (only referenced by an
import line) and ensure core.database exposes the names the import chain
(core/__init__ → session_manager) looks up."""
for _name in ("Webhook", "ChatMessage"):
setattr(sys.modules["core.database"], _name, MagicMock())
if "src.webhook_manager" not in sys.modules:
wm = types.ModuleType("src.webhook_manager")
wm.WebhookManager = MagicMock()
wm.validate_webhook_url = MagicMock()
wm.validate_events = MagicMock()
sys.modules["src.webhook_manager"] = wm
"""Import routes.webhook_routes. Stubs for core.database (ChatMessage,
Webhook) and src.webhook_manager are provided by the _null_owner_stubs
autouse fixture."""
return __import__(
"routes.webhook_routes", fromlist=["_caller_owns_session"]
)