from __future__ import annotations import hashlib import base64 import time from unittest.mock import AsyncMock, MagicMock, patch from app.crypto import encrypt_token from app.config import get_settings def _pkce_pair() -> tuple[str, str]: verifier = "test-verifier-string-long-enough-1234567890" digest = hashlib.sha256(verifier.encode("ascii")).digest() challenge = base64.urlsafe_b64encode(digest).rstrip(b"=").decode("ascii") return verifier, challenge def test_health(client): resp = client.get("/health") assert resp.status_code == 200 assert resp.json() == {"ok": True} def test_registration_disabled_by_flag(client, monkeypatch): monkeypatch.setenv("DCR_ENABLED", "false") get_settings.cache_clear() resp = client.post("/register", json={"client_name": "x", "redirect_uris": ["http://localhost:1/cb"]}) assert resp.status_code == 400 assert resp.json()["error"] == "registration_not_supported" def test_authorize_rejects_unknown_client(client): _, challenge = _pkce_pair() resp = client.get( "/authorize", params={ "response_type": "code", "client_id": "unknown", "redirect_uri": "http://localhost:8787/callback", "code_challenge": challenge, "code_challenge_method": "S256", }, follow_redirects=False, ) assert resp.status_code == 400 def test_authorize_starts_for_allowed_client(client, database, monkeypatch): settings = get_settings() user = database.upsert_user("sub-1", "u@example.com") database.set_upstream_tokens(user.id, encrypt_token(settings.token_encryption_key, "gitea_tok"), None, time.time() + 3600) import app.main as main monkeypatch.setattr(main, "session_user_id", lambda request: user.id) _, challenge = _pkce_pair() resp = client.get( "/authorize", params={ "response_type": "code", "client_id": "cursor", "redirect_uri": "http://localhost:8787/callback", "code_challenge": challenge, "code_challenge_method": "S256", "state": "abc", }, follow_redirects=False, ) assert resp.status_code == 302 assert resp.headers["location"].startswith("http://localhost:8787/callback?") assert "code=" in resp.headers["location"] assert "state=abc" in resp.headers["location"] def test_mcp_without_token_returns_401(client): resp = client.post("/mcp", json={"jsonrpc": "2.0", "method": "ping", "id": 1}) assert resp.status_code == 401 def test_mcp_proxies_with_gitea_token(client, database): settings = get_settings() user = database.upsert_user("sub-2", "u2@example.com") database.set_upstream_tokens(user.id, encrypt_token(settings.token_encryption_key, "gitea_secret"), None, time.time() + 3600) token = database.create_access_token("cursor", user.id) mock_response = MagicMock() mock_response.status_code = 200 mock_response.content = b'{"ok":true}' mock_response.headers = {"content-type": "application/json"} mock_client = AsyncMock() mock_client.request = AsyncMock(return_value=mock_response) mock_client.__aenter__ = AsyncMock(return_value=mock_client) mock_client.__aexit__ = AsyncMock(return_value=None) with patch("app.main.httpx.AsyncClient", return_value=mock_client): resp = client.post( "/mcp", json={"jsonrpc": "2.0", "method": "tools/list", "id": 1}, headers={"Authorization": f"Bearer {token}"}, ) assert resp.status_code == 200 mock_client.request.assert_awaited_once() call_kwargs = mock_client.request.await_args.kwargs assert call_kwargs["headers"]["Authorization"] == "Bearer gitea_secret"