diff --git a/app/plugins/builtins/ai_proactive/context_tools.py b/app/plugins/builtins/ai_proactive/context_tools.py index 9cfac28..76cd565 100644 --- a/app/plugins/builtins/ai_proactive/context_tools.py +++ b/app/plugins/builtins/ai_proactive/context_tools.py @@ -165,8 +165,8 @@ async def get_open_tasks_handler(arguments: dict[str, Any], context: dict[str, A from app.plugins.builtins.calendar.contracts import get_contract as get_calendar_contract _cal = get_calendar_contract() - calendar_entry = _cal.calendar_entry - calendar_entry_link = _cal.calendar_entry_link + calendar_entry = _cal.CalendarEntry + calendar_entry_link = _cal.CalendarEntryLink db, tenant_id, _ = await _get_db_and_tenant(context) entity_type = arguments["entity_type"] diff --git a/app/plugins/builtins/ai_proactive/services.py b/app/plugins/builtins/ai_proactive/services.py index ea315f4..26b010c 100644 --- a/app/plugins/builtins/ai_proactive/services.py +++ b/app/plugins/builtins/ai_proactive/services.py @@ -274,8 +274,8 @@ async def gather_context( # Upcoming calendar events from app.plugins.builtins.calendar.contracts import get_contract as get_calendar_contract _cal = get_calendar_contract() - calendar_entry = _cal.calendar_entry - calendar_entry_link = _cal.calendar_entry_link + calendar_entry = _cal.CalendarEntry + calendar_entry_link = _cal.CalendarEntryLink now = datetime.now(UTC) event_result = await db.execute( @@ -389,8 +389,8 @@ async def gather_context( # Upcoming events from app.plugins.builtins.calendar.contracts import get_contract as get_calendar_contract _cal = get_calendar_contract() - calendar_entry = _cal.calendar_entry - calendar_entry_link = _cal.calendar_entry_link + calendar_entry = _cal.CalendarEntry + calendar_entry_link = _cal.CalendarEntryLink now = datetime.now(UTC) event_result = await db.execute( diff --git a/app/plugins/builtins/mail/routes.py b/app/plugins/builtins/mail/routes.py index e5bca52..aa6daba 100644 --- a/app/plugins/builtins/mail/routes.py +++ b/app/plugins/builtins/mail/routes.py @@ -1494,8 +1494,8 @@ async def create_event_from_mail( try: from app.plugins.builtins.calendar.contracts import get_contract as get_calendar_contract _cal = get_calendar_contract() - calendar = _cal.calendar - calendar_entry = _cal.calendar_entry + calendar = _cal.Calendar + calendar_entry = _cal.CalendarEntry except ImportError: return {"created": False, "error": "calendar plugin not available"} cal_id = _parse_uuid(data.calendar_id, "calendar_id") diff --git a/tests/conftest.py b/tests/conftest.py index a35a2e0..8f1d67c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -53,6 +53,7 @@ from app.models.tenant import Tenant from app.models.user import User, UserTenant from app.models.user_preference import UserPreference # noqa: F401 from app.models.workflow import Workflow, WorkflowInstance, WorkflowStepHistory # noqa: F401 +from app.plugins.builtins.calendar import CalendarPlugin # noqa: F401 from app.models.outbox import EventOutbox # noqa: F401 from app.models.consumer_inbox import ConsumerInbox # noqa: F401 from app.models.saved_filter import SavedFilter # noqa: F401 diff --git a/tests/test_ai_proactive.py b/tests/test_ai_proactive.py index e0d6aae..cd4f0a7 100644 --- a/tests/test_ai_proactive.py +++ b/tests/test_ai_proactive.py @@ -15,6 +15,7 @@ from unittest.mock import AsyncMock, MagicMock, patch import pytest import pytest_asyncio +from fastapi import HTTPException from httpx import ASGITransport, AsyncClient from sqlalchemy import text from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession, async_sessionmaker @@ -1104,14 +1105,11 @@ async def test_generate_suggestion_llm_failure(): @pytest.mark.asyncio @patch("app.plugins.builtins.ai_proactive.services.create_db_session") -async def test_handle_context_change_rate_limited(mock_create_session, redis_client): +async def test_handle_context_change_rate_limited(mock_create_session): """Rate-limit prevents suggestion generation.""" tenant_id = uuid.uuid4() user_id = uuid.uuid4() - # Pre-set rate limit key in Redis - await redis_client.setex(f"ai_proactive:rate:{user_id}", 10, "1") - mock_session = AsyncMock() mock_session.__aenter__ = AsyncMock(return_value=mock_session) mock_session.__aexit__ = AsyncMock(return_value=None) @@ -1127,8 +1125,14 @@ async def test_handle_context_change_rate_limited(mock_create_session, redis_cli rate_limit_seconds=10, model="ollama/deepseek-v4", ) + # Since bb36378 the rate limit goes through app.core.rate_limit.check_rate_limit; + # simulate an exceeded limit instead of patching the removed services.get_cache. with ( - patch("app.plugins.builtins.ai_proactive.services.get_cache", return_value=redis_client), + patch( + "app.core.rate_limit.check_rate_limit", + new_callable=AsyncMock, + side_effect=HTTPException(status_code=429), + ), patch( "app.plugins.builtins.ai_proactive.services.get_user_settings", new_callable=AsyncMock, @@ -1147,7 +1151,7 @@ async def test_handle_context_change_rate_limited(mock_create_session, redis_cli @pytest.mark.asyncio @patch("app.plugins.builtins.ai_proactive.services.create_db_session") -async def test_handle_context_change_disabled(mock_create_session, redis_client): +async def test_handle_context_change_disabled(mock_create_session): """Settings disabled → no suggestion.""" tenant_id = uuid.uuid4() user_id = uuid.uuid4() @@ -1167,13 +1171,12 @@ async def test_handle_context_change_disabled(mock_create_session, redis_client) rate_limit_seconds=10, model="ollama/deepseek-v4", ) - with ( - patch("app.plugins.builtins.ai_proactive.services.get_cache", return_value=redis_client), - patch( - "app.plugins.builtins.ai_proactive.services.get_user_settings", - new_callable=AsyncMock, - return_value=disabled_settings, - ), + # Disabled settings cause an early return before any Redis access; + # no cache/rate-limit patch needed since bb36378. + with patch( + "app.plugins.builtins.ai_proactive.services.get_user_settings", + new_callable=AsyncMock, + return_value=disabled_settings, ): await handle_context_change({ "user_id": str(user_id),