mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-06-15 17:25:26 -04:00
test: pilot core database stub helper (#3685)
This commit is contained in:
committed by
GitHub
parent
b1af29c7bc
commit
a22c0fa85e
+15
-4
@@ -150,15 +150,26 @@ Use for the repeated file-backed temp sqlite setup in tests.
|
||||
under test reads, and must keep the returned objects alive.
|
||||
- Do not use it as a general DB fixture framework.
|
||||
|
||||
### `tests.helpers.db_stubs.make_core_db_stub`
|
||||
|
||||
Use for small import-time `core.database` stubs with a placeholder
|
||||
`SessionLocal`.
|
||||
|
||||
- Pass model names via `models` when MagicMock attributes are sufficient.
|
||||
- Pass `attributes` when an import needs exact placeholder values.
|
||||
- Set `install_core_package=True` only when the test also needs a fake parent
|
||||
`core` module stub.
|
||||
- Keep custom fake sessions and route-specific database behavior local.
|
||||
|
||||
## What not to abstract yet
|
||||
|
||||
Some remaining patterns should stay as-is for now rather than being forced into
|
||||
helpers:
|
||||
|
||||
- Large mixed files such as security/review regression files.
|
||||
- Setup-oriented `sys.modules` stub installers.
|
||||
- Broad setup-oriented `sys.modules` stub installers.
|
||||
- One-off custom module patching.
|
||||
- DB/session/route setup, until it has been audited separately.
|
||||
- Custom DB session, route, and app setup.
|
||||
|
||||
## Validation expectations
|
||||
|
||||
@@ -178,7 +189,7 @@ Run validation locally before opening or approving a PR. Practical checks:
|
||||
|
||||
1. Import-state cleanup - complete.
|
||||
2. Document helper conventions (this file).
|
||||
3. Audit fake DB / `SessionLocal` / route setup duplication.
|
||||
4. Add tiny helpers only when the repeated semantics are clear.
|
||||
3. Pilot the repeated import-time `core.database` stub helper.
|
||||
4. Add further tiny helpers only when the repeated semantics are clear.
|
||||
5. Start low-risk file moves only after helper conventions are documented.
|
||||
6. Avoid moving high-risk security/route regression files first.
|
||||
|
||||
@@ -4,17 +4,30 @@ import types
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
|
||||
def make_core_db_stub(monkeypatch, models=()):
|
||||
def make_core_db_stub(
|
||||
monkeypatch,
|
||||
models=(),
|
||||
*,
|
||||
attributes=None,
|
||||
install_core_package=False,
|
||||
):
|
||||
"""Create a core.database stub and inject it via monkeypatch.
|
||||
|
||||
Always sets SessionLocal. Pass model class names via `models` to set
|
||||
each as a MagicMock attribute on the stub.
|
||||
each as a MagicMock attribute on the stub. Pass `attributes` to override
|
||||
specific values, and `install_core_package` when the import also needs a
|
||||
stub parent package.
|
||||
|
||||
Returns the stub module for optional further configuration.
|
||||
"""
|
||||
if install_core_package:
|
||||
monkeypatch.setitem(sys.modules, "core", types.ModuleType("core"))
|
||||
|
||||
db = types.ModuleType("core.database")
|
||||
db.SessionLocal = MagicMock()
|
||||
for name in models:
|
||||
setattr(db, name, MagicMock())
|
||||
for name, value in (attributes or {}).items():
|
||||
setattr(db, name, value)
|
||||
monkeypatch.setitem(sys.modules, "core.database", db)
|
||||
return db
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
import sys
|
||||
from contextlib import contextmanager
|
||||
from types import ModuleType
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from pytest import MonkeyPatch
|
||||
|
||||
from tests.helpers.db_stubs import make_core_db_stub
|
||||
|
||||
|
||||
_MISSING = object()
|
||||
_MODULE_NAMES = ("core", "core.database")
|
||||
|
||||
|
||||
@contextmanager
|
||||
def _preserve_core_modules():
|
||||
original_modules = {
|
||||
name: sys.modules.get(name, _MISSING) for name in _MODULE_NAMES
|
||||
}
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
for name in _MODULE_NAMES:
|
||||
sys.modules.pop(name, None)
|
||||
for name, module in original_modules.items():
|
||||
if module is not _MISSING:
|
||||
sys.modules[name] = module
|
||||
|
||||
|
||||
def test_models_create_mock_attributes(monkeypatch):
|
||||
db = make_core_db_stub(monkeypatch, models=("User", "Session"))
|
||||
|
||||
assert sys.modules["core.database"] is db
|
||||
assert isinstance(db.SessionLocal, MagicMock)
|
||||
assert isinstance(db.User, MagicMock)
|
||||
assert isinstance(db.Session, MagicMock)
|
||||
|
||||
|
||||
def test_attributes_override_defaults_and_model_mocks(monkeypatch):
|
||||
session_local = object()
|
||||
email_account = object()
|
||||
|
||||
db = make_core_db_stub(
|
||||
monkeypatch,
|
||||
models=("EmailAccount",),
|
||||
attributes={
|
||||
"SessionLocal": session_local,
|
||||
"EmailAccount": email_account,
|
||||
},
|
||||
)
|
||||
|
||||
assert db.SessionLocal is session_local
|
||||
assert db.EmailAccount is email_account
|
||||
|
||||
|
||||
def test_core_module_installation_is_opt_in():
|
||||
with _preserve_core_modules():
|
||||
sys.modules.pop("core", None)
|
||||
sys.modules.pop("core.database", None)
|
||||
monkeypatch = MonkeyPatch()
|
||||
try:
|
||||
db = make_core_db_stub(monkeypatch)
|
||||
|
||||
assert "core" not in sys.modules
|
||||
assert sys.modules["core.database"] is db
|
||||
finally:
|
||||
monkeypatch.undo()
|
||||
|
||||
|
||||
def test_existing_core_is_preserved_when_installation_is_disabled():
|
||||
with _preserve_core_modules():
|
||||
original_core = ModuleType("core")
|
||||
sys.modules["core"] = original_core
|
||||
sys.modules.pop("core.database", None)
|
||||
monkeypatch = MonkeyPatch()
|
||||
try:
|
||||
db = make_core_db_stub(monkeypatch, install_core_package=False)
|
||||
|
||||
assert sys.modules["core"] is original_core
|
||||
assert sys.modules["core.database"] is db
|
||||
finally:
|
||||
monkeypatch.undo()
|
||||
|
||||
assert sys.modules["core"] is original_core
|
||||
assert "core.database" not in sys.modules
|
||||
|
||||
|
||||
def test_undo_removes_modules_that_were_absent():
|
||||
with _preserve_core_modules():
|
||||
sys.modules.pop("core", None)
|
||||
sys.modules.pop("core.database", None)
|
||||
monkeypatch = MonkeyPatch()
|
||||
try:
|
||||
make_core_db_stub(monkeypatch, install_core_package=True)
|
||||
|
||||
assert "core" in sys.modules
|
||||
assert "core.database" in sys.modules
|
||||
finally:
|
||||
monkeypatch.undo()
|
||||
|
||||
assert "core" not in sys.modules
|
||||
assert "core.database" not in sys.modules
|
||||
|
||||
|
||||
def test_undo_restores_existing_modules():
|
||||
with _preserve_core_modules():
|
||||
original_core = ModuleType("core")
|
||||
original_database = ModuleType("core.database")
|
||||
sys.modules["core"] = original_core
|
||||
sys.modules["core.database"] = original_database
|
||||
monkeypatch = MonkeyPatch()
|
||||
try:
|
||||
make_core_db_stub(monkeypatch, install_core_package=True)
|
||||
|
||||
assert sys.modules["core"] is not original_core
|
||||
assert sys.modules["core.database"] is not original_database
|
||||
finally:
|
||||
monkeypatch.undo()
|
||||
|
||||
assert sys.modules["core"] is original_core
|
||||
assert sys.modules["core.database"] is original_database
|
||||
@@ -4,6 +4,7 @@ from types import ModuleType, SimpleNamespace
|
||||
import pytest
|
||||
|
||||
from tests.helpers.cli_loader import load_script
|
||||
from tests.helpers.db_stubs import make_core_db_stub
|
||||
|
||||
|
||||
class _Conn:
|
||||
@@ -37,14 +38,13 @@ def _load_mail_cli(monkeypatch):
|
||||
pollers = ModuleType("routes.email_pollers")
|
||||
pollers._scheduled_poll_once = lambda: {}
|
||||
pollers._run_auto_summarize_once = lambda **kwargs: ""
|
||||
core_mod = ModuleType("core")
|
||||
database_mod = ModuleType("core.database")
|
||||
database_mod.SessionLocal = object
|
||||
database_mod.EmailAccount = object
|
||||
monkeypatch.setitem(sys.modules, "routes.email_helpers", helpers)
|
||||
monkeypatch.setitem(sys.modules, "routes.email_pollers", pollers)
|
||||
monkeypatch.setitem(sys.modules, "core", core_mod)
|
||||
monkeypatch.setitem(sys.modules, "core.database", database_mod)
|
||||
make_core_db_stub(
|
||||
monkeypatch,
|
||||
attributes={"SessionLocal": object, "EmailAccount": object},
|
||||
install_core_package=True,
|
||||
)
|
||||
return load_script("odysseus-mail")
|
||||
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import sys
|
||||
from types import ModuleType
|
||||
|
||||
from tests.helpers.cli_loader import load_script
|
||||
from tests.helpers.db_stubs import make_core_db_stub
|
||||
|
||||
|
||||
def _load_mail_cli(monkeypatch):
|
||||
@@ -17,15 +18,13 @@ def _load_mail_cli(monkeypatch):
|
||||
pollers._scheduled_poll_once = lambda: {}
|
||||
pollers._run_auto_summarize_once = lambda **kwargs: ""
|
||||
|
||||
core_mod = ModuleType("core")
|
||||
database_mod = ModuleType("core.database")
|
||||
database_mod.SessionLocal = object
|
||||
database_mod.EmailAccount = object
|
||||
|
||||
monkeypatch.setitem(sys.modules, "routes.email_helpers", helpers)
|
||||
monkeypatch.setitem(sys.modules, "routes.email_pollers", pollers)
|
||||
monkeypatch.setitem(sys.modules, "core", core_mod)
|
||||
monkeypatch.setitem(sys.modules, "core.database", database_mod)
|
||||
make_core_db_stub(
|
||||
monkeypatch,
|
||||
attributes={"SessionLocal": object, "EmailAccount": object},
|
||||
install_core_package=True,
|
||||
)
|
||||
|
||||
return load_script("odysseus-mail")
|
||||
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
import sys
|
||||
from types import ModuleType
|
||||
from types import SimpleNamespace
|
||||
|
||||
from tests.helpers.cli_loader import load_script
|
||||
from tests.helpers.db_stubs import make_core_db_stub
|
||||
|
||||
|
||||
def _load_sessions_cli(monkeypatch):
|
||||
core_mod = ModuleType("core")
|
||||
database_mod = ModuleType("core.database")
|
||||
database_mod.SessionLocal = object
|
||||
database_mod.Session = object
|
||||
monkeypatch.setitem(sys.modules, "core", core_mod)
|
||||
monkeypatch.setitem(sys.modules, "core.database", database_mod)
|
||||
make_core_db_stub(
|
||||
monkeypatch,
|
||||
attributes={"SessionLocal": object, "Session": object},
|
||||
install_core_package=True,
|
||||
)
|
||||
return load_script("odysseus-sessions")
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user