test(auth): port AUTH test plan docs + lint/format pass

- Port backend/docs/AUTH_TEST_PLAN.md and AUTH_UPGRADE.md from PR #1728
- Rename metadata.user_id → metadata.owner_id in AUTH_TEST_PLAN.md
  (4 occurrences from the original PR doc)
- ruff auto-fix UP037 in sentinel type annotations: drop quotes around
  "str | None | _AutoSentinel" now that from __future__ import
  annotations makes them implicit string forms
- ruff format: 2 files (app/gateway/app.py, runtime/user_context.py)

Note on test coverage additions:
- conftest.py autouse fixture was already added in commit 4 (had to
  be co-located with the repository changes to keep pre-existing
  persistence tests passing)
- cross-user isolation E2E tests (test_owner_isolation.py) deferred
  — enforcement is already proven by the 98-test repository suite
  via the autouse fixture + explicit _AUTO sentinel exercises
- New test cases (TC-API-17..20, TC-ATK-13, TC-MIG-01..07) listed
  in AUTH_TEST_PLAN.md are deferred to a follow-up PR — they are
  manual-QA test cases rather than pytest code, and the spec-level
  coverage is already met by test_user_context.py + the 98-test
  repository suite.

Final test results:
- Auth suite (test_auth*, test_langgraph_auth, test_ensure_admin,
  test_user_context): 186 passed
- Persistence suite (test_run_event_store, test_run_repository,
  test_thread_meta_repo, test_feedback): 98 passed
- Lint: ruff check + ruff format both clean
This commit is contained in:
greatmengqi
2026-04-08 11:12:30 +08:00
parent e5ad92474c
commit 3aa3e37532
7 changed files with 1937 additions and 32 deletions
@@ -49,9 +49,7 @@ class CurrentUser(Protocol):
id: str
_current_user: Final[ContextVar["CurrentUser | None"]] = ContextVar(
"deerflow_current_user", default=None
)
_current_user: Final[ContextVar[CurrentUser | None]] = ContextVar("deerflow_current_user", default=None)
def set_current_user(user: CurrentUser) -> Token[CurrentUser | None]:
@@ -104,9 +102,9 @@ def require_current_user() -> CurrentUser:
class _AutoSentinel:
"""Singleton marker meaning 'resolve owner_id from contextvar'."""
_instance: "_AutoSentinel | None" = None
_instance: _AutoSentinel | None = None
def __new__(cls) -> "_AutoSentinel":
def __new__(cls) -> _AutoSentinel:
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
@@ -119,7 +117,7 @@ AUTO: Final[_AutoSentinel] = _AutoSentinel()
def resolve_owner_id(
value: "str | None | _AutoSentinel",
value: str | None | _AutoSentinel,
*,
method_name: str = "repository method",
) -> str | None:
@@ -139,10 +137,6 @@ def resolve_owner_id(
if isinstance(value, _AutoSentinel):
user = _current_user.get()
if user is None:
raise RuntimeError(
f"{method_name} called with owner_id=AUTO but no user context is set; "
"pass an explicit owner_id, set the contextvar via auth middleware, "
"or opt out with owner_id=None for migration/CLI paths."
)
raise RuntimeError(f"{method_name} called with owner_id=AUTO but no user context is set; pass an explicit owner_id, set the contextvar via auth middleware, or opt out with owner_id=None for migration/CLI paths.")
return user.id
return value