You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

237 lines
7.5 KiB
Python

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,
)
def available_meeting_intervals(
self,
start: str,
end: str,
calendar_ids: list[str] | None,
) -> SimpleNamespace:
return SimpleNamespace(
start=start,
end=end,
timezone="Europe/Paris",
meeting_intervals=[
SimpleNamespace(
start="2026-03-10T08:30:00+01:00",
end="2026-03-10T09:00:00+01:00",
)
],
checked_calendars=calendar_ids or ["primary"],
)
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_available_meeting_intervals_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.available_meeting_intervals(
start="2026-03-10T08:00:00+01:00",
end="2026-03-10T10:00:00+01:00",
calendar_ids=["primary"],
ctx=_DummyCtx(headers={}),
)
def test_mcp_available_meeting_intervals_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.available_meeting_intervals(
start="2026-03-10T08: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["timezone"] == "Europe/Paris"
assert payload["meeting_intervals"] == [
{
"start": "2026-03-10T08:30:00+01:00",
"end": "2026-03-10T09:00:00+01:00",
}
]
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"]
def test_mcp_available_meeting_intervals_oauth_requires_new_scope(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())
with pytest.raises(PermissionError, match="available_meeting_intervals:read"):
mcp_tools_module.available_meeting_intervals(
start="2026-03-10T08:00:00+01:00",
end="2026-03-10T10:00:00+01:00",
calendar_ids=["primary"],
ctx=_DummyCtx(headers={"authorization": "Bearer oauth-token"}),
)