1212Test credentials: user1 / x
1313"""
1414
15+ import uuid
16+ from datetime import datetime , timedelta , timezone
17+
1518import pytest
19+ import pytest_asyncio
1620
1721try :
1822 from niquests .auth import HTTPBasicAuth
1923except ImportError :
2024 from requests .auth import HTTPBasicAuth # type: ignore[no-redef]
2125
22- from caldav .jmap import JMAPClient
26+ from caldav .jmap import AsyncJMAPClient , JMAPClient
2327from caldav .jmap .constants import CALENDAR_CAPABILITY
28+ from caldav .jmap .error import JMAPMethodError
2429from caldav .jmap .session import fetch_session
2530
2631CYRUS_HOST = "localhost"
@@ -47,6 +52,25 @@ def _cyrus_reachable() -> bool:
4752)
4853
4954
55+ def _minimal_ical (title : str = "Test Event" , start : datetime | None = None ) -> str :
56+ if start is None :
57+ start = datetime (2026 , 6 , 1 , 10 , 0 , 0 , tzinfo = timezone .utc )
58+ end = start + timedelta (hours = 1 )
59+ uid = str (uuid .uuid4 ())
60+ return (
61+ "BEGIN:VCALENDAR\r \n "
62+ "VERSION:2.0\r \n "
63+ "PRODID:-//test//test//EN\r \n "
64+ "BEGIN:VEVENT\r \n "
65+ f"UID:{ uid } \r \n "
66+ f"SUMMARY:{ title } \r \n "
67+ f"DTSTART:{ start .strftime ('%Y%m%dT%H%M%SZ' )} \r \n "
68+ f"DTEND:{ end .strftime ('%Y%m%dT%H%M%SZ' )} \r \n "
69+ "END:VEVENT\r \n "
70+ "END:VCALENDAR\r \n "
71+ )
72+
73+
5074@pytest .fixture (scope = "module" )
5175def client ():
5276 return JMAPClient (url = JMAP_URL , username = CYRUS_USERNAME , password = CYRUS_PASSWORD )
@@ -57,6 +81,47 @@ def session():
5781 return fetch_session (JMAP_URL , auth = HTTPBasicAuth (CYRUS_USERNAME , CYRUS_PASSWORD ))
5882
5983
84+ @pytest .fixture (scope = "module" )
85+ def calendar_id (client ):
86+ calendars = client .get_calendars ()
87+ assert calendars , "Cyrus did not provision any calendars for user1"
88+ return calendars [0 ].id
89+
90+
91+ @pytest .fixture
92+ def created_event_id (client , calendar_id ):
93+ event_id = client .create_event (calendar_id , _minimal_ical ("Integration Test Event" ))
94+ yield event_id
95+ try :
96+ client .delete_event (event_id )
97+ except Exception :
98+ pass
99+
100+
101+ @pytest_asyncio .fixture
102+ async def async_client ():
103+ return AsyncJMAPClient (url = JMAP_URL , username = CYRUS_USERNAME , password = CYRUS_PASSWORD )
104+
105+
106+ @pytest_asyncio .fixture
107+ async def async_calendar_id (async_client ):
108+ calendars = await async_client .get_calendars ()
109+ assert calendars , "Cyrus did not provision any calendars for user1"
110+ return calendars [0 ].id
111+
112+
113+ @pytest_asyncio .fixture
114+ async def async_created_event_id (async_client , async_calendar_id ):
115+ event_id = await async_client .create_event (
116+ async_calendar_id , _minimal_ical ("Async Integration Test Event" )
117+ )
118+ yield event_id
119+ try :
120+ await async_client .delete_event (event_id )
121+ except Exception :
122+ pass
123+
124+
60125class TestJMAPSessionIntegration :
61126 def test_session_fetch_returns_api_url (self , session ):
62127 assert session .api_url
@@ -80,3 +145,111 @@ def test_calendars_have_id_and_name(self, client):
80145 for cal in calendars :
81146 assert cal .id , f"Calendar missing id: { cal } "
82147 assert cal .name , f"Calendar has empty name: { cal } "
148+
149+
150+ class TestJMAPEventIntegration :
151+ def test_event_create_get (self , client , created_event_id ):
152+ ical = client .get_event (created_event_id )
153+ assert "BEGIN:VCALENDAR" in ical
154+ assert "Integration Test Event" in ical
155+
156+ def test_event_update (self , client , created_event_id ):
157+ client .update_event (created_event_id , _minimal_ical ("Updated Title" ))
158+ fetched = client .get_event (created_event_id )
159+ assert "Updated Title" in fetched
160+
161+ def test_event_delete (self , client , calendar_id ):
162+ event_id = client .create_event (calendar_id , _minimal_ical ("To Be Deleted" ))
163+ client .delete_event (event_id )
164+ with pytest .raises (JMAPMethodError ):
165+ client .get_event (event_id )
166+
167+ def test_event_query_time_range (self , client , calendar_id , created_event_id ):
168+ results = client .search_events (
169+ calendar_id = calendar_id ,
170+ start = "2026-06-01T00:00:00" ,
171+ end = "2026-06-02T00:00:00" ,
172+ )
173+ assert len (results ) >= 1
174+ assert any ("Integration Test Event" in r for r in results )
175+
176+ def test_event_sync (self , client , calendar_id ):
177+ token_before = client .get_sync_token ()
178+ event_id = client .create_event (calendar_id , _minimal_ical ("Sync Test Event" ))
179+ try :
180+ added , _modified , _deleted = client .get_objects_by_sync_token (token_before )
181+ assert any ("Sync Test Event" in a for a in added )
182+ finally :
183+ client .delete_event (event_id )
184+
185+ def test_ical_roundtrip (self , client , calendar_id ):
186+ start = datetime (2026 , 7 , 15 , 9 , 0 , 0 , tzinfo = timezone .utc )
187+ event_id = client .create_event (calendar_id , _minimal_ical ("Roundtrip Event" , start = start ))
188+ try :
189+ fetched = client .get_event (event_id )
190+ assert "Roundtrip Event" in fetched
191+ assert "20260715" in fetched
192+ finally :
193+ client .delete_event (event_id )
194+
195+
196+ class TestAsyncJMAPEventIntegration :
197+ @pytest .mark .asyncio
198+ async def test_event_create_get (self , async_client , async_created_event_id ):
199+ ical = await async_client .get_event (async_created_event_id )
200+ assert "BEGIN:VCALENDAR" in ical
201+ assert "Async Integration Test Event" in ical
202+
203+ @pytest .mark .asyncio
204+ async def test_event_update (self , async_client , async_created_event_id ):
205+ await async_client .update_event (
206+ async_created_event_id , _minimal_ical ("Async Updated Title" )
207+ )
208+ fetched = await async_client .get_event (async_created_event_id )
209+ assert "Async Updated Title" in fetched
210+
211+ @pytest .mark .asyncio
212+ async def test_event_delete (self , async_client , async_calendar_id ):
213+ event_id = await async_client .create_event (
214+ async_calendar_id , _minimal_ical ("Async To Be Deleted" )
215+ )
216+ await async_client .delete_event (event_id )
217+ with pytest .raises (JMAPMethodError ):
218+ await async_client .get_event (event_id )
219+
220+ @pytest .mark .asyncio
221+ async def test_event_query_time_range (
222+ self , async_client , async_calendar_id , async_created_event_id
223+ ):
224+ results = await async_client .search_events (
225+ calendar_id = async_calendar_id ,
226+ start = "2026-06-01T00:00:00" ,
227+ end = "2026-06-02T00:00:00" ,
228+ )
229+ assert len (results ) >= 1
230+ assert any ("Async Integration Test Event" in r for r in results )
231+
232+ @pytest .mark .asyncio
233+ async def test_event_sync (self , async_client , async_calendar_id ):
234+ token_before = await async_client .get_sync_token ()
235+ event_id = await async_client .create_event (
236+ async_calendar_id , _minimal_ical ("Async Sync Test Event" )
237+ )
238+ try :
239+ added , _modified , _deleted = await async_client .get_objects_by_sync_token (token_before )
240+ assert any ("Async Sync Test Event" in a for a in added )
241+ finally :
242+ await async_client .delete_event (event_id )
243+
244+ @pytest .mark .asyncio
245+ async def test_ical_roundtrip (self , async_client , async_calendar_id ):
246+ start = datetime (2026 , 7 , 15 , 9 , 0 , 0 , tzinfo = timezone .utc )
247+ event_id = await async_client .create_event (
248+ async_calendar_id , _minimal_ical ("Async Roundtrip Event" , start = start )
249+ )
250+ try :
251+ fetched = await async_client .get_event (event_id )
252+ assert "Async Roundtrip Event" in fetched
253+ assert "20260715" in fetched
254+ finally :
255+ await async_client .delete_event (event_id )
0 commit comments