Files
odysseus/tests/test_model_helper_owner_scope.py
T
Tal.Yuan 88e0ce3037 refactor(routes): move note domain into routes/note/ subpackage (#5236)
Slice 2f of the route-domain reorganization (#4082/#4071, per
specs/architecture-runtime-inventory.md §6.3). Moves note_routes.py into
routes/note/, leaving a backward-compat sys.modules shim at the old path.
Pure file reorganization, no behavior change.

The shim uses sys.modules replacement (same pattern as the merged gallery
#4903, research #4975, memory #5007, history #5090, and contacts #5227
slices) so that `import routes.note_routes`, `from routes.note_routes import
X`, `importlib.import_module(...)`, and the `import ... as note_routes` +
`monkeypatch.setattr(note_routes, "SessionLocal", ...)` pattern used by
test_note_reminder_fire_scope.py / test_notes_fail_closed_auth.py all
operate on the same module object the application uses.

The canonical module does NOT depend on the shim — routes/note/note_routes.py
imports only from core/, src/, and stdlib. The outbound email cross-domain
imports (routes.email_routes._get_email_config, routes.email_helpers.
_send_smtp_message) are function-local lazy imports that keep resolving
through the email module's own path (email is not yet migrated).

One source-introspection test site repointed to the new canonical path:
- test_model_helper_owner_scope.py (shared with history; history entry
  already repointed in #5090, note entry repointed here)

Adds tests/test_note_routes_shim.py to pin the sys.modules shim contract
(legacy and canonical paths resolve to the same module object; monkeypatch
via legacy alias reaches the canonical module).

Verified: compileall clean; full suite 4487 passed, 3 skipped.
2026-07-20 13:52:30 +02:00

46 lines
1.9 KiB
Python

"""Model-assisted route helpers must resolve endpoints with owner scope."""
import ast
from pathlib import Path
def _function_source(path: str, name: str) -> str:
source = Path(path).read_text(encoding="utf-8")
tree = ast.parse(source)
for node in ast.walk(tree):
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)) and node.name == name:
return ast.get_source_segment(source, node) or ""
raise AssertionError(f"{name} not found in {path}")
def test_document_ai_tidy_resolves_with_owner_scope():
body = _function_source("routes/document_routes.py", "ai_tidy_documents")
assert "resolve_task_endpoint(owner=user or None)" in body
assert 'resolve_endpoint("default", owner=user or None)' in body
def test_calendar_quick_parse_resolves_with_owner_scope():
body = _function_source("routes/calendar_routes.py", "quick_parse")
assert "owner = _require_user(request)" in body
assert 'resolve_endpoint("utility", owner=owner or None)' in body
assert 'resolve_endpoint("default", owner=owner or None)' in body
def test_task_parse_resolves_with_owner_scope():
body = _function_source("routes/task_routes.py", "parse_task")
assert "user = _owner(request)" in body
assert 'resolve_endpoint("utility", owner=user or None)' in body
assert 'resolve_endpoint("default", owner=user or None)' in body
def test_history_compact_resolves_with_owner_scope():
body = _function_source("routes/history/history_routes.py", "compact_session")
assert "owner = effective_user(request)" in body
assert 'resolve_endpoint("utility", owner=owner or None)' in body
def test_note_reminder_synthesis_resolves_with_owner_scope():
body = _function_source("routes/note/note_routes.py", "dispatch_reminder")
assert 'resolve_endpoint("utility", owner=owner or None)' in body
assert 'resolve_endpoint("default", owner=owner or None)' in body