test(backend): add E2E harness foundation (entrypoints, middleware, LLM/embedding fakes)

This commit is contained in:
Anish Sarkar 2026-05-06 17:17:42 +05:30
parent c720866a67
commit 58ba95fad2
9 changed files with 550 additions and 0 deletions

View file

@ -0,0 +1,54 @@
"""X-E2E-Scenario middleware.
Reads the X-E2E-Scenario request header and pipes the value into a
ContextVar that the strict fakes consult to switch between happy-path
and error scenarios on a per-request basis.
Mounted by tests/e2e/run_backend.py only. Production never adds this
middleware, so production never reads the header.
Supported scenarios:
- "happy" (default): everything succeeds with deterministic fixtures.
- "denied": Composio.connected_accounts.initiate returns a redirect URL
pointing at our callback with ?error=access_denied.
- "auth_expired": GOOGLEDRIVE_LIST_FILES returns an authentication
failure that the route translates to connector.config.auth_expired.
- "duplicate": no special fake behavior; the duplicate path is exercised
by running the OAuth flow twice with the same toolkit.
"""
from __future__ import annotations
from contextvars import ContextVar
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.requests import Request
from starlette.responses import Response
from starlette.types import ASGIApp
_scenario: ContextVar[str] = ContextVar("e2e_scenario", default="happy")
def current_scenario() -> str:
"""Return the active E2E scenario for the current request context."""
return _scenario.get()
class ScenarioMiddleware(BaseHTTPMiddleware):
"""Reads X-E2E-Scenario and exposes it via a ContextVar.
The header is also forwarded as state on the request so route
handlers can branch if they ever need to (Composio routes do not).
"""
def __init__(self, app: ASGIApp) -> None:
super().__init__(app)
async def dispatch(self, request: Request, call_next) -> Response:
value = request.headers.get("X-E2E-Scenario", "happy")
token = _scenario.set(value)
try:
request.state.e2e_scenario = value
return await call_next(request)
finally:
_scenario.reset(token)