mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-06-16 01:35:36 -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.
|
under test reads, and must keep the returned objects alive.
|
||||||
- Do not use it as a general DB fixture framework.
|
- 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
|
## What not to abstract yet
|
||||||
|
|
||||||
Some remaining patterns should stay as-is for now rather than being forced into
|
Some remaining patterns should stay as-is for now rather than being forced into
|
||||||
helpers:
|
helpers:
|
||||||
|
|
||||||
- Large mixed files such as security/review regression files.
|
- 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.
|
- One-off custom module patching.
|
||||||
- DB/session/route setup, until it has been audited separately.
|
- Custom DB session, route, and app setup.
|
||||||
|
|
||||||
## Validation expectations
|
## Validation expectations
|
||||||
|
|
||||||
@@ -178,7 +189,7 @@ Run validation locally before opening or approving a PR. Practical checks:
|
|||||||
|
|
||||||
1. Import-state cleanup - complete.
|
1. Import-state cleanup - complete.
|
||||||
2. Document helper conventions (this file).
|
2. Document helper conventions (this file).
|
||||||
3. Audit fake DB / `SessionLocal` / route setup duplication.
|
3. Pilot the repeated import-time `core.database` stub helper.
|
||||||
4. Add tiny helpers only when the repeated semantics are clear.
|
4. Add further tiny helpers only when the repeated semantics are clear.
|
||||||
5. Start low-risk file moves only after helper conventions are documented.
|
5. Start low-risk file moves only after helper conventions are documented.
|
||||||
6. Avoid moving high-risk security/route regression files first.
|
6. Avoid moving high-risk security/route regression files first.
|
||||||
|
|||||||
@@ -4,17 +4,30 @@ import types
|
|||||||
from unittest.mock import MagicMock
|
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.
|
"""Create a core.database stub and inject it via monkeypatch.
|
||||||
|
|
||||||
Always sets SessionLocal. Pass model class names via `models` to set
|
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.
|
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 = types.ModuleType("core.database")
|
||||||
db.SessionLocal = MagicMock()
|
db.SessionLocal = MagicMock()
|
||||||
for name in models:
|
for name in models:
|
||||||
setattr(db, name, MagicMock())
|
setattr(db, name, MagicMock())
|
||||||
|
for name, value in (attributes or {}).items():
|
||||||
|
setattr(db, name, value)
|
||||||
monkeypatch.setitem(sys.modules, "core.database", db)
|
monkeypatch.setitem(sys.modules, "core.database", db)
|
||||||
return 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
|
import pytest
|
||||||
|
|
||||||
from tests.helpers.cli_loader import load_script
|
from tests.helpers.cli_loader import load_script
|
||||||
|
from tests.helpers.db_stubs import make_core_db_stub
|
||||||
|
|
||||||
|
|
||||||
class _Conn:
|
class _Conn:
|
||||||
@@ -37,14 +38,13 @@ def _load_mail_cli(monkeypatch):
|
|||||||
pollers = ModuleType("routes.email_pollers")
|
pollers = ModuleType("routes.email_pollers")
|
||||||
pollers._scheduled_poll_once = lambda: {}
|
pollers._scheduled_poll_once = lambda: {}
|
||||||
pollers._run_auto_summarize_once = lambda **kwargs: ""
|
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_helpers", helpers)
|
||||||
monkeypatch.setitem(sys.modules, "routes.email_pollers", pollers)
|
monkeypatch.setitem(sys.modules, "routes.email_pollers", pollers)
|
||||||
monkeypatch.setitem(sys.modules, "core", core_mod)
|
make_core_db_stub(
|
||||||
monkeypatch.setitem(sys.modules, "core.database", database_mod)
|
monkeypatch,
|
||||||
|
attributes={"SessionLocal": object, "EmailAccount": object},
|
||||||
|
install_core_package=True,
|
||||||
|
)
|
||||||
return load_script("odysseus-mail")
|
return load_script("odysseus-mail")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import sys
|
|||||||
from types import ModuleType
|
from types import ModuleType
|
||||||
|
|
||||||
from tests.helpers.cli_loader import load_script
|
from tests.helpers.cli_loader import load_script
|
||||||
|
from tests.helpers.db_stubs import make_core_db_stub
|
||||||
|
|
||||||
|
|
||||||
def _load_mail_cli(monkeypatch):
|
def _load_mail_cli(monkeypatch):
|
||||||
@@ -17,15 +18,13 @@ def _load_mail_cli(monkeypatch):
|
|||||||
pollers._scheduled_poll_once = lambda: {}
|
pollers._scheduled_poll_once = lambda: {}
|
||||||
pollers._run_auto_summarize_once = lambda **kwargs: ""
|
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_helpers", helpers)
|
||||||
monkeypatch.setitem(sys.modules, "routes.email_pollers", pollers)
|
monkeypatch.setitem(sys.modules, "routes.email_pollers", pollers)
|
||||||
monkeypatch.setitem(sys.modules, "core", core_mod)
|
make_core_db_stub(
|
||||||
monkeypatch.setitem(sys.modules, "core.database", database_mod)
|
monkeypatch,
|
||||||
|
attributes={"SessionLocal": object, "EmailAccount": object},
|
||||||
|
install_core_package=True,
|
||||||
|
)
|
||||||
|
|
||||||
return load_script("odysseus-mail")
|
return load_script("odysseus-mail")
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,15 @@
|
|||||||
import sys
|
|
||||||
from types import ModuleType
|
|
||||||
from types import SimpleNamespace
|
from types import SimpleNamespace
|
||||||
|
|
||||||
from tests.helpers.cli_loader import load_script
|
from tests.helpers.cli_loader import load_script
|
||||||
|
from tests.helpers.db_stubs import make_core_db_stub
|
||||||
|
|
||||||
|
|
||||||
def _load_sessions_cli(monkeypatch):
|
def _load_sessions_cli(monkeypatch):
|
||||||
core_mod = ModuleType("core")
|
make_core_db_stub(
|
||||||
database_mod = ModuleType("core.database")
|
monkeypatch,
|
||||||
database_mod.SessionLocal = object
|
attributes={"SessionLocal": object, "Session": object},
|
||||||
database_mod.Session = object
|
install_core_package=True,
|
||||||
monkeypatch.setitem(sys.modules, "core", core_mod)
|
)
|
||||||
monkeypatch.setitem(sys.modules, "core.database", database_mod)
|
|
||||||
return load_script("odysseus-sessions")
|
return load_script("odysseus-sessions")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user