Files
siftlode/backend/tests/test_entry_points.py
T
peter d73cdcee97 fix(capture): every process registers every table before its first query
The models now live in their units' packages, and a core model names a unit's by
string (User.subscriptions -> Subscription). The download child imports neither
catalog nor app.tables before db.get, so mapper configuration failed and every
download, clip, edit and upload job died with child_exit. Each entry point now
imports app.tables, and a test imports each one in a clean interpreter.
2026-10-20 00:45:20 +02:00

64 lines
2.2 KiB
Python

"""Every process the app runs as sees every table before its first query.
The units declare their tables in their own `models` modules, and a core model names a unit's by
string (`User.subscriptions` → `Subscription`). A fresh interpreter that never imported the unit's
module fails to configure its mappers on its first query: the download child once did exactly that,
and every job failed, while this suite stayed green — it imports `app.tables` in-process, which hides
the gap. So each entry point is imported here in a clean interpreter, and must register the whole
schema on its own.
"""
import ast
import subprocess
import sys
from pathlib import Path
import app.tables # noqa: F401
from app.db import Base
BACKEND = Path(__file__).parent.parent
APP = BACKEND / "app"
# Each `python -m` module the deployment starts, and the API's app module.
ENTRY_POINTS = ("app.main", "app.worker", "app.downloads.runner")
_PROBE = (
"import importlib, sys\n"
"importlib.import_module(sys.argv[1])\n"
"from sqlalchemy.orm import configure_mappers\n"
"from app.db import Base\n"
"configure_mappers()\n"
"print('\\n'.join(sorted(Base.metadata.tables)))\n"
)
def test_each_entry_point_registers_the_whole_schema_on_its_own():
whole = sorted(Base.metadata.tables)
for module in ENTRY_POINTS:
r = subprocess.run(
[sys.executable, "-c", _PROBE, module],
cwd=BACKEND,
capture_output=True,
text=True,
timeout=120,
check=False,
)
assert r.returncode == 0, (module, r.stderr[-2000:])
assert r.stdout.split() == whole, module
def test_every_module_that_runs_as_a_process_is_listed():
mains = set()
for path in APP.rglob("*.py"):
tree = ast.parse(path.read_text(encoding="utf-8"))
for node in tree.body:
if (
isinstance(node, ast.If)
and isinstance(node.test, ast.Compare)
and isinstance(node.test.left, ast.Name)
and node.test.left.id == "__name__"
):
rel = path.relative_to(BACKEND).with_suffix("").as_posix()
mains.add(rel.replace("/", "."))
assert mains <= set(ENTRY_POINTS), mains - set(ENTRY_POINTS)