mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-05-22 16:06:50 +00:00
refactor(tests): reorganize tests into unittest/ and e2e/ directories
- Move all unit tests from tests/ to tests/unittest/ - Add tests/e2e/ directory for end-to-end tests - Update conftest.py for new test structure - Add new tests for auth dependencies, policies, route injection - Add new tests for run callbacks, create store, execution artifacts - Remove obsolete tests for deleted persistence layer Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
"""Helpers for router-level tests that need an authenticated request.
|
||||
|
||||
The production gateway stamps ``request.user`` / ``request.auth`` in the
|
||||
auth middleware, then route decorators read that authenticated context.
|
||||
Router-level unit tests build very small FastAPI apps that include only
|
||||
one router, so they need a lightweight stand-in for that middleware.
|
||||
|
||||
This module provides two surfaces:
|
||||
|
||||
1. :func:`make_authed_test_app` — wraps ``FastAPI()`` with a tiny
|
||||
``BaseHTTPMiddleware`` that stamps a fake user / AuthContext on every
|
||||
request, plus a permissive ``thread_store`` mock on
|
||||
``app.state``. Use from TestClient-based router tests.
|
||||
|
||||
2. :func:`call_unwrapped` — invokes the underlying function by walking
|
||||
``__wrapped__``. Use from direct-call tests that want to bypass the
|
||||
route decorators entirely.
|
||||
|
||||
Both helpers are deliberately permissive: they never deny a request.
|
||||
Tests that want to verify the *auth boundary itself* (e.g.
|
||||
``test_auth_middleware``, ``test_auth_type_system``) build their own
|
||||
apps with the real middleware — those should not use this module.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
from typing import ParamSpec, TypeVar
|
||||
from unittest.mock import AsyncMock, MagicMock
|
||||
from uuid import uuid4
|
||||
|
||||
from fastapi import FastAPI, Request, Response
|
||||
from starlette.middleware.base import BaseHTTPMiddleware
|
||||
from starlette.types import ASGIApp
|
||||
|
||||
from app.plugins.auth.domain.models import User
|
||||
from app.plugins.auth.authorization import AuthContext, Permissions
|
||||
|
||||
# Default permission set granted to the stub user. Mirrors `_ALL_PERMISSIONS`
|
||||
# in authz.py — kept inline so the tests don't import a private symbol.
|
||||
_STUB_PERMISSIONS: list[str] = [
|
||||
Permissions.THREADS_READ,
|
||||
Permissions.THREADS_WRITE,
|
||||
Permissions.THREADS_DELETE,
|
||||
Permissions.RUNS_CREATE,
|
||||
Permissions.RUNS_READ,
|
||||
Permissions.RUNS_CANCEL,
|
||||
]
|
||||
|
||||
|
||||
def _make_stub_user() -> User:
|
||||
"""A deterministic test user — same shape as production, fresh UUID."""
|
||||
return User(
|
||||
email="router-test@example.com",
|
||||
password_hash="x",
|
||||
system_role="user",
|
||||
id=uuid4(),
|
||||
)
|
||||
|
||||
|
||||
class _StubAuthMiddleware(BaseHTTPMiddleware):
|
||||
"""Stamp a fake user / AuthContext onto every request."""
|
||||
|
||||
def __init__(self, app: ASGIApp, user_factory: Callable[[], User]) -> None:
|
||||
super().__init__(app)
|
||||
self._user_factory = user_factory
|
||||
|
||||
async def dispatch(self, request: Request, call_next: Callable) -> Response:
|
||||
user = self._user_factory()
|
||||
auth_context = AuthContext(user=user, permissions=list(_STUB_PERMISSIONS))
|
||||
request.scope["user"] = user
|
||||
request.scope["auth"] = auth_context
|
||||
request.state.user = user
|
||||
request.state.auth = auth_context
|
||||
return await call_next(request)
|
||||
|
||||
|
||||
def make_authed_test_app(
|
||||
*,
|
||||
user_factory: Callable[[], User] | None = None,
|
||||
owner_check_passes: bool = True,
|
||||
) -> FastAPI:
|
||||
"""Build a FastAPI test app with stub auth + permissive thread_store.
|
||||
|
||||
Args:
|
||||
user_factory: Override the default test user. Must return a fully
|
||||
populated :class:`User`. Useful for cross-user isolation tests
|
||||
that need a stable id across requests.
|
||||
owner_check_passes: When True (default), ``thread_store.check_access``
|
||||
returns True for every call so owner-gated routes do not block
|
||||
the handler under test. Pass False to verify denial paths.
|
||||
|
||||
Returns:
|
||||
A ``FastAPI`` app with the stub middleware installed and
|
||||
``app.state.thread_store`` set to a permissive mock. The
|
||||
caller is still responsible for ``app.include_router(...)``.
|
||||
"""
|
||||
factory = user_factory or _make_stub_user
|
||||
app = FastAPI()
|
||||
app.add_middleware(_StubAuthMiddleware, user_factory=factory)
|
||||
|
||||
repo = MagicMock()
|
||||
repo.check_access = AsyncMock(return_value=owner_check_passes)
|
||||
app.state.thread_store = repo
|
||||
|
||||
return app
|
||||
|
||||
|
||||
_P = ParamSpec("_P")
|
||||
_R = TypeVar("_R")
|
||||
|
||||
|
||||
def call_unwrapped(decorated: Callable[_P, _R], /, *args: _P.args, **kwargs: _P.kwargs) -> _R:
|
||||
"""Invoke the underlying function of a ``@require_permission``-decorated route.
|
||||
|
||||
``functools.wraps`` sets ``__wrapped__`` on each layer; we walk all
|
||||
the way down to the original handler. Use from tests that call route
|
||||
functions directly and do not want to build a full request/middleware
|
||||
stack.
|
||||
"""
|
||||
fn: Callable = decorated
|
||||
while hasattr(fn, "__wrapped__"):
|
||||
fn = fn.__wrapped__ # type: ignore[attr-defined]
|
||||
return fn(*args, **kwargs)
|
||||
Reference in New Issue
Block a user