-
Notifications
You must be signed in to change notification settings - Fork 200
test(e2e): harden test_delete_calendar_event scaffold + skip pending #1416 #1415
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 3 commits
74e3e35
f0cc831
20f3530
9a840b2
7386eee
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,6 +12,7 @@ | |
| """ | ||
|
|
||
| import logging | ||
| import uuid | ||
| from datetime import datetime, timedelta | ||
|
|
||
| import pytest | ||
|
|
@@ -21,6 +22,7 @@ | |
| parse_mcp_result, | ||
| safe_call_tool, | ||
| ) | ||
| from ...utilities.wait_helpers import wait_for_tool_result | ||
|
|
||
| logger = logging.getLogger(__name__) | ||
|
|
||
|
|
@@ -59,9 +61,7 @@ async def test_get_calendar_events_default_range(self, mcp_client): | |
| if not calendar_entity: | ||
| pytest.skip("No calendar entities available for testing") | ||
|
|
||
| logger.info( | ||
| f"Testing ha_config_get_calendar_events with {calendar_entity}..." | ||
| ) | ||
| logger.info(f"Testing ha_config_get_calendar_events with {calendar_entity}...") | ||
|
|
||
| result = await mcp_client.call_tool( | ||
| "ha_config_get_calendar_events", {"entity_id": calendar_entity} | ||
|
|
@@ -165,9 +165,9 @@ async def test_get_calendar_events_invalid_entity_format(self, mcp_client): | |
|
|
||
| # Should fail with validation error | ||
| assert data.get("success") is False, "Should fail for invalid format" | ||
| assert "calendar." in str( | ||
| data.get("error", "") | ||
| ), "Error should mention correct format" | ||
| assert "calendar." in str(data.get("error", "")), ( | ||
| "Error should mention correct format" | ||
| ) | ||
|
|
||
| logger.info(f"Validation error (expected): {data.get('error', 'Unknown')}") | ||
| logger.info("Invalid format test completed") | ||
|
|
@@ -204,6 +204,76 @@ async def _find_writable_calendar(self, mcp_client) -> str | None: | |
| # Fall back to first calendar | ||
| return results[0].get("entity_id") | ||
|
|
||
| @pytest.fixture | ||
| async def deletable_event_uid(self, mcp_client): | ||
| """Create a temporary event, yield (entity_id, uid), then best-effort delete. | ||
|
|
||
| Round-trips through ha_config_get_calendar_events to obtain the UID that | ||
| HA assigned — ha_config_set_calendar_event does not return it in the | ||
| response. Teardown swallows exceptions so it stays idempotent regardless | ||
| of whether the test body already deleted the event. | ||
| """ | ||
| calendar_entity = await self._find_writable_calendar(mcp_client) | ||
| if not calendar_entity: | ||
| pytest.skip("No writable calendar found for testing") | ||
|
|
||
| unique_id = uuid.uuid4().hex[:8] | ||
| summary = f"E2E Deletable Test Event {unique_id}" | ||
| now = datetime.now() | ||
| start = (now + timedelta(days=1)).replace( | ||
| hour=14, minute=0, second=0, microsecond=0 | ||
| ) | ||
| end = start + timedelta(hours=1) | ||
|
|
||
| create_data = await safe_call_tool( | ||
| mcp_client, | ||
| "ha_config_set_calendar_event", | ||
| { | ||
| "entity_id": calendar_entity, | ||
| "summary": summary, | ||
| "start": start.isoformat(), | ||
| "end": end.isoformat(), | ||
| }, | ||
| ) | ||
| if not create_data.get("success"): | ||
| error_msg = str(create_data.get("error", "Unknown")) | ||
|
Patch76 marked this conversation as resolved.
|
||
| pytest.skip( | ||
| f"Calendar {calendar_entity} does not support event creation: {error_msg}" | ||
| ) | ||
|
|
||
| events_data = await safe_call_tool( | ||
| mcp_client, | ||
| "ha_config_get_calendar_events", | ||
| { | ||
| "entity_id": calendar_entity, | ||
| "start": start.isoformat(), | ||
| "end": (end + timedelta(hours=1)).isoformat(), | ||
| }, | ||
| ) | ||
| events = events_data.get("events", []) | ||
| event_uid = next( | ||
| (e.get("uid") for e in events if e.get("summary") == summary), None | ||
| ) | ||
| if not event_uid: | ||
| pytest.skip( | ||
| f"Could not retrieve UID for created event '{summary}' from {calendar_entity}" | ||
| ) | ||
|
Patch76 marked this conversation as resolved.
Outdated
|
||
|
|
||
| try: | ||
| yield calendar_entity, event_uid | ||
| finally: | ||
| # Best-effort cleanup; test body may have already deleted the event. | ||
| try: | ||
| await mcp_client.call_tool( | ||
| "ha_config_remove_calendar_event", | ||
| {"entity_id": calendar_entity, "uid": event_uid}, | ||
| ) | ||
| except Exception as cleanup_error: | ||
| logger.debug( | ||
| f"Cleanup of test event {event_uid} on {calendar_entity}: " | ||
| f"{cleanup_error}" | ||
| ) | ||
|
Comment on lines
+284
to
+293
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Avoid using broad References
|
||
|
|
||
| async def test_create_calendar_event(self, mcp_client, cleanup_tracker): | ||
| """ | ||
| Test: Create a calendar event | ||
|
|
@@ -307,43 +377,65 @@ async def test_create_calendar_event_invalid_entity(self, mcp_client): | |
| ) | ||
|
|
||
| assert data.get("success") is False, "Should fail for invalid entity" | ||
| assert "calendar." in str( | ||
| data.get("error", "") | ||
| ), "Error should mention correct format" | ||
| assert "calendar." in str(data.get("error", "")), ( | ||
| "Error should mention correct format" | ||
| ) | ||
|
|
||
| logger.info(f"Validation error (expected): {data.get('error', 'Unknown')}") | ||
| logger.info("Invalid entity create test completed") | ||
|
|
||
| async def test_delete_calendar_event(self, mcp_client): | ||
| async def test_delete_calendar_event(self, mcp_client, deletable_event_uid): | ||
| """ | ||
| Test: Delete a calendar event | ||
| Test: Delete a calendar event (positive + negative paths) | ||
|
|
||
| Tests the delete event functionality (may fail if no deletable events exist). | ||
| """ | ||
| calendar_entity = await self._find_writable_calendar(mcp_client) | ||
| if not calendar_entity: | ||
| pytest.skip("No calendar entities available for testing") | ||
| Creates a fresh event, deletes it (positive: hard-assert success), then | ||
| re-attempts deletion of the just-released UID (negative: hard-assert | ||
| failure with suggestions). UID-collision risk is eliminated because the | ||
| UID was just held and released by this test. | ||
|
|
||
| Assumes ha_config_remove_calendar_event raises on missing UID — current | ||
| behaviour for the _remove_* tool family. If the project later commits to | ||
| idempotent-success on missing (see #1412), the negative-path assertion | ||
| will need to flip. | ||
| """ | ||
| calendar_entity, event_uid = deletable_event_uid | ||
| logger.info( | ||
| f"Testing ha_config_remove_calendar_event for {calendar_entity}..." | ||
| f"Testing ha_config_remove_calendar_event for {calendar_entity} " | ||
| f"with uid={event_uid}..." | ||
| ) | ||
|
|
||
| # Try to delete with a fake UID (will likely fail, but tests the API) | ||
| # Use safe_call_tool since we expect this to fail | ||
| data = await safe_call_tool( | ||
| # Positive path. Poll the delete to absorb the registration window | ||
| # after event creation on Local Calendar — the REST endpoint behind | ||
| # ha_config_get_calendar_events returns the UID before the | ||
| # calendar.delete_event service accepts it, empirically observed as | ||
| # a 400 on immediate delete-after-create. wait_for_tool_result | ||
| # retries on success=False until the predicate holds or the | ||
| # timeout fires. | ||
| first_delete = await wait_for_tool_result( | ||
| mcp_client, | ||
| "ha_config_remove_calendar_event", | ||
| {"entity_id": calendar_entity, "uid": "nonexistent-event-uid-xyz"}, | ||
| {"entity_id": calendar_entity, "uid": event_uid}, | ||
| predicate=lambda d: d.get("success") is True, | ||
| timeout=15, | ||
| description=f"first deletion of just-created event {event_uid}", | ||
| ) | ||
| logger.info(f"Deleted event {event_uid} (positive path)") | ||
|
|
||
| # This will likely fail since the event doesn't exist | ||
| # We're mainly testing that the tool handles errors gracefully | ||
| if data.get("success"): | ||
| logger.info("Unexpectedly succeeded (event may have existed)") | ||
| else: | ||
| logger.info(f"Delete failed as expected: {data.get('error', 'Unknown')}") | ||
| assert data.get("error", {}).get("suggestions"), "Should provide helpful suggestions" | ||
|
|
||
| # Negative path: re-delete the released UID | ||
| second_delete = await safe_call_tool( | ||
| mcp_client, | ||
| "ha_config_remove_calendar_event", | ||
| {"entity_id": calendar_entity, "uid": event_uid}, | ||
| ) | ||
| assert second_delete.get("success") is False, ( | ||
| f"Second deletion of released UID should fail: got {second_delete}" | ||
| ) | ||
| assert second_delete.get("error", {}).get("suggestions"), ( | ||
| "Delete failure should provide helpful suggestions" | ||
| ) | ||
|
Patch76 marked this conversation as resolved.
|
||
| logger.info( | ||
| f"Second delete failed as expected: {second_delete.get('error', 'Unknown')}" | ||
| ) | ||
| logger.info("ha_config_remove_calendar_event test completed") | ||
|
|
||
| async def test_delete_calendar_event_invalid_entity(self, mcp_client): | ||
|
|
@@ -362,9 +454,9 @@ async def test_delete_calendar_event_invalid_entity(self, mcp_client): | |
| ) | ||
|
|
||
| assert data.get("success") is False, "Should fail for invalid entity" | ||
| assert "calendar." in str( | ||
| data.get("error", "") | ||
| ), "Error should mention correct format" | ||
| assert "calendar." in str(data.get("error", "")), ( | ||
| "Error should mention correct format" | ||
| ) | ||
|
|
||
| logger.info(f"Validation error (expected): {data.get('error', 'Unknown')}") | ||
| logger.info("Invalid entity delete test completed") | ||
|
|
@@ -384,9 +476,9 @@ async def test_calendar_tools_overview(mcp_client): | |
| get_data = await safe_call_tool( | ||
| mcp_client, "ha_config_get_calendar_events", {"entity_id": "calendar.test"} | ||
| ) | ||
| assert ( | ||
| "events" in get_data or "error" in get_data | ||
| ), "ha_config_get_calendar_events should return events or error" | ||
| assert "events" in get_data or "error" in get_data, ( | ||
| "ha_config_get_calendar_events should return events or error" | ||
| ) | ||
| logger.info("ha_config_get_calendar_events tool is registered and functional") | ||
|
|
||
| # Test create event tool registration | ||
|
|
@@ -401,9 +493,9 @@ async def test_calendar_tools_overview(mcp_client): | |
| "end": (now + timedelta(hours=1)).isoformat(), | ||
| }, | ||
| ) | ||
| assert ( | ||
| "event" in create_data or "error" in create_data | ||
| ), "ha_config_set_calendar_event should return event or error" | ||
| assert "event" in create_data or "error" in create_data, ( | ||
| "ha_config_set_calendar_event should return event or error" | ||
| ) | ||
| logger.info("ha_config_set_calendar_event tool is registered and functional") | ||
|
|
||
| # Test delete event tool registration | ||
|
|
@@ -412,9 +504,9 @@ async def test_calendar_tools_overview(mcp_client): | |
| "ha_config_remove_calendar_event", | ||
| {"entity_id": "calendar.test", "uid": "test-uid"}, | ||
| ) | ||
| assert ( | ||
| "uid" in delete_data or "error" in delete_data | ||
| ), "ha_config_remove_calendar_event should return uid or error" | ||
| assert "uid" in delete_data or "error" in delete_data, ( | ||
| "ha_config_remove_calendar_event should return uid or error" | ||
| ) | ||
| logger.info("ha_config_remove_calendar_event tool is registered and functional") | ||
|
|
||
| logger.info("All calendar tools are properly registered") | ||
Uh oh!
There was an error while loading. Please reload this page.