from __future__ import annotations from dataclasses import replace from types import SimpleNamespace import pytest import app.mcp.tools as mcp_tools_module from app.config import get_settings from app.security.auth import AuthBackend class _DummyCoreService: def check_availability( self, start: str, end: str, calendar_ids: list[str] | None, ) -> SimpleNamespace: return SimpleNamespace( start=start, end=end, available=True, busy_slots=[], checked_calendars=calendar_ids or ["primary"], ) def scan_mailbox(self, max_results: int) -> SimpleNamespace: return SimpleNamespace( scanned=max_results, linkedin=0, advertising=0, veille_techno=0, skipped=0, failed=0, ) class _DummyCtx: def __init__(self, headers: dict[str, str]) -> None: self.request_context = SimpleNamespace( request=SimpleNamespace(headers=headers) ) def test_mcp_check_availability_requires_auth(monkeypatch) -> None: auth_settings = replace( get_settings(), auth_mode="api_key", agent_api_key="mcp-key", auth_jwt_secret="", ) monkeypatch.setattr(mcp_tools_module, "auth_backend", AuthBackend(auth_settings)) monkeypatch.setattr(mcp_tools_module, "core_service", _DummyCoreService()) with pytest.raises(PermissionError): mcp_tools_module.check_availability( start="2026-03-10T09:00:00+01:00", end="2026-03-10T10:00:00+01:00", calendar_ids=["primary"], ctx=_DummyCtx(headers={}), ) def test_mcp_check_availability_with_api_key(monkeypatch) -> None: auth_settings = replace( get_settings(), auth_mode="api_key", agent_api_key="mcp-key", auth_jwt_secret="", ) monkeypatch.setattr(mcp_tools_module, "auth_backend", AuthBackend(auth_settings)) monkeypatch.setattr(mcp_tools_module, "core_service", _DummyCoreService()) payload = mcp_tools_module.check_availability( start="2026-03-10T09:00:00+01:00", end="2026-03-10T10:00:00+01:00", calendar_ids=["primary"], ctx=_DummyCtx(headers={"x-api-key": "mcp-key"}), ) assert payload["available"] is True assert payload["checked_calendars"] == ["primary"] def test_mcp_scan_mailbox_requires_mail_scan_scope(monkeypatch) -> None: auth_settings = replace( get_settings(), auth_mode="api_key", agent_api_key="mcp-key", auth_jwt_secret="", ) monkeypatch.setattr(mcp_tools_module, "auth_backend", AuthBackend(auth_settings)) monkeypatch.setattr(mcp_tools_module, "core_service", _DummyCoreService()) payload = mcp_tools_module.scan_mailbox( max_results=10, ctx=_DummyCtx(headers={"x-api-key": "mcp-key"}), ) assert payload["scanned"] == 10 def test_mcp_auth_mode_oauth_uses_bearer_token(monkeypatch) -> None: mcp_settings = replace( get_settings(), auth_mode="api_key", agent_api_key="api-key-not-used-for-mcp", auth_jwt_secret="", mcp_auth_mode="oauth", mcp_oauth_introspection_url="https://issuer.example/introspect", mcp_oauth_issuer="https://issuer.example", mcp_oauth_audience="personal-agent-mcp", ) backend = mcp_tools_module._build_mcp_auth_backend(mcp_settings) monkeypatch.setattr( backend, "_introspect_oauth_token", lambda _: { "active": True, "sub": "oauth-agent", "iss": "https://issuer.example", "aud": "personal-agent-mcp", "scope": "availability:read", }, ) monkeypatch.setattr(mcp_tools_module, "auth_backend", backend) monkeypatch.setattr(mcp_tools_module, "core_service", _DummyCoreService()) payload = mcp_tools_module.check_availability( start="2026-03-10T09:00:00+01:00", end="2026-03-10T10:00:00+01:00", calendar_ids=["primary"], ctx=_DummyCtx(headers={"authorization": "Bearer oauth-token"}), ) assert payload["available"] is True assert payload["checked_calendars"] == ["primary"]