"""Bearer MCP-токен на REST /api/*: аутентификация расширения и агентов.
Сессии SSO из браузера не видны запросам из extension-контекста (SameSite=Lax
режет cross-site куки), поэтому расширение ходит с Bearer MCP-токеном
(Настройки → «Токены MCP») — тем же, что у ИИ-агентов на /mcp.
"""
from fastapi.testclient import TestClient
from app.dependencies import require_user
def _create_token(client: TestClient, label: str = "расширение") -> str:
res = client.post("/api/mcp-tokens", json={"label": label})
assert res.status_code == 200, res.text
return res.json()["token"]
def test_bearer_grants_api_access(client: TestClient):
token = _create_token(client)
# Оверрайд require_user снят: настоящая зависимость читает Bearer.
saved = client.app.dependency_overrides.pop(require_user, None) # type: ignore[attr-defined]
try:
res = client.get("/api/tasks", headers={"Authorization": f"Bearer {token}"})
assert res.status_code == 200, res.text
res = client.get("/api/projects", headers={"Authorization": f"Bearer {token}"})
assert res.status_code == 200
finally:
if saved is not None:
client.app.dependency_overrides[require_user] = saved
def test_bearer_wrong_token_401(client: TestClient) -> None:
saved = client.app.dependency_overrides.pop(require_user, None) # type: ignore[attr-defined]
try:
res = client.get("/api/tasks", headers={"Authorization": "Bearer gnt_nope"})
assert res.status_code == 401
finally:
if saved is not None:
client.app.dependency_overrides[require_user] = saved
def test_bearer_revoked_token_401(client: TestClient) -> None:
res = client.post("/api/mcp-tokens", json={"label": "отзываем"})
token, token_id = res.json()["token"], res.json()["id"]
assert client.delete(f"/api/mcp-tokens/{token_id}").status_code == 200
saved = client.app.dependency_overrides.pop(require_user, None) # type: ignore[attr-defined]
try:
res = client.get("/api/tasks", headers={"Authorization": f"Bearer {token}"})
assert res.status_code == 401
finally:
if saved is not None:
client.app.dependency_overrides[require_user] = saved
def test_bearer_auth_me_returns_profile(client: TestClient) -> None:
token = _create_token(client)
saved = client.app.dependency_overrides.pop(require_user, None) # type: ignore[attr-defined]
try:
res = client.get("/auth/me", headers={"Authorization": f"Bearer {token}"})
assert res.status_code == 200, res.text
assert res.json()["user"]["user_id"] == "1"
finally:
if saved is not None:
client.app.dependency_overrides[require_user] = saved
def test_bearer_scoped_to_owner(client: TestClient) -> None:
"""Задача сессии видна через bearer того же пользователя (скоуп по user_id)."""
token = _create_token(client)
created = client.post("/api/tasks", json={"title": "скоуп"}).json()
saved = client.app.dependency_overrides.pop(require_user, None) # type: ignore[attr-defined]
try:
res = client.get("/api/tasks", headers={"Authorization": f"Bearer {token}"})
assert [t["id"] for t in res.json()] == [created["id"]]
finally:
if saved is not None:
client.app.dependency_overrides[require_user] = saved