Skip to content

Make the Async Test Suite Symmetric with the Sync Test Suite #667

@tobixen

Description

@tobixen

This issue was AI-generated using Claude Code with Sonnet 4.6

Prompt: The sync test suite takes 164k while the async test suite takes 40k, most likely there are lots of code paths not excersised with the async client. Make a plan for making the async test suite more or less symmetric with the sync test suite. write the plan to /tmp/testplan.md as for now.
Followup-Prompt: Please create checkbox lists (* [ ] - asdfasdf)

The sync integration tests live in tests/test_caldav.py (4264 lines).
The async integration tests live in tests/test_async_integration.py (1034 lines).

The size disparity reflects a large coverage gap: the sync suite has ~56 functional
tests + 8 scheduling tests; the async suite has only 9 functional tests + 7
scheduling tests (one scheduling test is missing too).

Async test Rough sync equivalent
test_principal_calendars testPrincipal (partial)
test_principal_make_calendar testCreateDeleteCalendar (partial)
test_search_events testSearchEvent (partial)
test_search_events_by_date_range testSearchEvent (partial)
test_search_todos_pending testSearchTodos (partial)
test_search_todos_all testSearchTodos (partial)
test_events_method testCreateEvent (partial)
test_todos_method testCreateTaskListAndTodo (partial)
test_add_organizer_no_arg testAddOrganizer (partial – no-arg case only)
Async test Sync equivalent
test_invite_and_respond testInviteAndRespond
test_freebusy testFreeBusy
(missing) testAcceptInviteUsernameEmailFallback
test_schedule_tag_returned_on_save testScheduleTagReturnedOnSave
test_schedule_tag_stable_on_partstate_update testScheduleTagStableOnPartstateUpdate
test_schedule_tag_changes_on_organizer_update testScheduleTagChangesOnOrganizerUpdate
test_schedule_tag_mismatch_raises_error testScheduleTagMismatchRaisesError
test_schedule_tag_match_succeeds testScheduleTagMatchSucceeds

These are all in RepeatedFunctionalTestsBaseClass unless noted.

  • - testCreateOverwriteDeleteEventtest_create_overwrite_delete_event
    Tests no_create/no_overwrite flags, overwrite-same-UID, delete, 404 after delete.

  • - testLookupEventtest_lookup_event
    Add event; look up by URL (event_by_url), by UID (get_event_by_uid), and
    directly via Event(url=…).load(); verify NotFoundError on bad UID.

  • - testObjectByUIDtest_object_by_uid
    Add a TODO with known UID; retrieve via get_object_by_uid; verify NotFoundError
    for non-existing / prefix-match UIDs.

  • - testLoadEventtest_load_event
    Add event to calendar 1; call load() on the returned object and on a freshly
    fetched one. Needs a second calendar fixture (see Infrastructure section).

  • - testCopyEventtest_copy_event
    event.copy() within same calendar (new uid); copy(new_parent=c2, keep_uid=True)
    cross-calendar; copy(keep_uid=True) same calendar (no-op). Needs two calendars.

  • - testMultiGettest_multi_get
    Add two events; retrieve both via calendar_multiget([url1, url2]); call
    event.load_by_multiget().

  • - testGetSupportedComponentstest_get_supported_components
    calendar.get_supported_components() must include "VEVENT".

  • - testObjectBySyncTokentest_object_by_sync_token
    Full sync-token cycle: add objects → objects() → verify initial count and
    token; modify object → get_objects_by_sync_token → get 1 back (loaded);
    add another → get 1 back; delete → get deleted (data=None). Use
    asyncio.sleep(1) for time-based tokens. Use is_supported("sync-token", dict)
    to detect fragile/time-based behaviour.

  • - testSynctest_sync
    Same lifecycle but via my_objects.sync() returning (updated, deleted) pairs.

  • - testSearchShouldYieldDatatest_search_should_yield_data
    Ref issue Skipped event with missing 'vevent' property (Home Assistant) #201: search(event=True) on a populated calendar must return objects
    with non-empty .data.

  • - testSearchEvent (comprehensive) → test_search_event
    Add ev1, ev3, evr (old-date events with search.time-range.event.old-dates
    gate); test search() with no args, comp_class=Event, todo=True (empty),
    date range, UID, text fields (summary, description, category – if supported).
    Note: the existing test_search_events uses near-future events and only checks
    counts; this new test covers old-date and text-search code paths.

  • - testSearchCompTypetest_search_comp_type
    Add event and todo to a mixed calendar; verify search(comp_class=Event) /
    search(comp_class=Todo) / search(event=True, todo=True) counts.

  • - testSearchWithoutCompTypetest_search_without_comp_type
    search() with no filter returns all items.

  • - testSearchSortTodotest_search_sort_todo
    Add todos with different priorities; verify sort order via
    search(todo=True, sort_keys=["priority"]).

  • - testDateSearchAndFreeBusytest_date_search_and_freebusy
    Add event, search by date range (no deprecated API, just search(event=True, start=…, end=…)); modify event, re-search to verify date moved; if
    freebusy-query supported, call calendar.freebusy_request(…) and verify
    isinstance(result, FreeBusy).

  • - testRecurringDateSearchtest_recurring_date_search
    Add evr (yearly RRULE); search with expand=True over two occurrences;
    verify expansion results.

  • - testRecurringDateWithExceptionSearchtest_recurring_date_with_exception_search
    Add evr2 (bi-weekly with exception); search with expand=True over date range
    containing two occurrences; verify RECURRENCE-IDs.

  • - testEditSingleRecurrencetest_edit_single_recurrence
    Add daily recurring event; expand to find one occurrence; edit summary; verify
    only that day changed; test save(all_recurrences=True).

  • - testAlarmtest_alarm
    Add event with VALARM; verify search(alarm_start=…, alarm_end=…) counts.

  • - testSetDuetest_set_due
    Create a todo; call todo.set_due(…) or todo.complete(); verify field values.

  • - testCreateTaskListAndTodo (comprehensive) → extend test_todos_method or new test
    Add todo with add_todo(uid=…, summary=…); verify get_todos(); verify
    get_object_by_uid.

  • - testTodostest_todos
    Pending / completed / include_completed filtering; complete() on a todo.

  • - testTodoCompletiontest_todo_completion
    Verify todo.complete() transitions STATUS to COMPLETED; verify pending-only
    search no longer returns it; verify include_completed search does return it.

  • - testTodoRecurringCompleteSafetest_todo_recurring_complete_safe
    Recurring todo with fixed COUNT; complete() with handle_rrule=True; verify
    next occurrence appears with NEEDS-ACTION.

  • - testTodoRecurringCompleteThisandfuturetest_todo_recurring_complete_thisandfuture
    complete(rrule_mode="thisandfuture") on a recurring todo.

  • - testTodoDatesearchtest_todo_datesearch
    Search todos by DUE date ranges; verify start/end filtering works.

  • - testCreateJournalListAndJournalEntrytest_create_journal_list_and_journal_entry
    Make a VJOURNAL calendar; add a journal entry; verify get_journals().

  • - testGetCalendarHomeSettest_get_calendar_home_set
    principal.get_properties([cdav.CalendarHomeSet()]) must contain the key.

  • - testGetDefaultCalendartest_get_default_calendar
    principal.get_calendars() must be non-empty (gated on get-current-user-principal.has-calendar).

  • - testSetCalendarPropertiestest_set_calendar_properties
    Get display name via get_properties; set a new name via set_properties; read
    back and verify. Gate on create-calendar.set-displayname.

  • - testCalendarByFullURLtest_calendar_by_full_url
    Look up calendar by full URL string and by URL object as cal_id.

  • - testFindCalendarOwnertest_find_calendar_owner
    calendar.get_property(dav.Owner()); if non-None, construct a Principal from
    it and verify get_vcal_address().

  • - testPrincipal → extend test_principal_calendars
    Check that all items returned by get_calendars() are AsyncCalendar instances.

  • - testPrincipalstest_principals
    caldav.principals() (list-all); optionally principals(name=…) search.

  • - testIssue397test_issue_397
    Recurring VEVENT with attendee + RECURRENCE-ID; store and retrieve; check
    RELATED-TO / RECURRENCE-ID structure.

  • - testIssue399ChangeAttendeeStatusUsernameEmailFallbacktest_issue_399_change_attendee_status
    Build invite with ATTENDEE matching client username; call
    change_attendee_status(partstat="ACCEPTED") without explicit attendee arg;
    verify PARTSTAT set.

  • - testAddOrganizer (full) → extend test_add_organizer_no_arg
    Add explicit string arg case and explicit vCalAddress arg case.

  • - testSupporttest_support
    check_dav_support(), check_cdav_support(), check_scheduling_support().

  • - testSchedulingInfotest_scheduling_info
    principal.calendar_user_address_set() and get_vcal_address().

  • - testSchedulingMailboxestest_scheduling_mailboxes
    principal.schedule_inbox() and schedule_outbox().

  • - testPropfindtest_propfind
    Raw XML propfind to principal URL; verify "resourcetype" in response.

  • - testChangeAttendeeStatusWithEmailGiventest_change_attendee_status_with_email_given
    Create event with ATTENDEE; call change_attendee_status(attendee="…", PARTSTAT="ACCEPTED");
    save; reload and verify.

  • - testWrongAuthTypetest_wrong_auth_type
    With digest or bearer auth_type, connecting must raise AuthorizationError.

  • - testWrongPasswordtest_wrong_password
    Bad password must raise AuthorizationError (gate on wrong-password-check).

  • - testCreateChildParenttest_create_child_parent
    Add parent + child + grandparent events with RELATED-TO; retrieve and verify
    RELTYPE values.

  • - testOffsetURLtest_offset_url
    Connect with url=principal.url and url=calendar.url; verify
    principal().get_calendars() still works.

  • - testUtf8Eventtest_utf8_event
    Calendar with non-ASCII name; event with non-ASCII summary; retrieve and verify.

  • - testCreateCalendarAndEventFromVobjecttest_create_calendar_and_event_from_vobject
    Add event from vobject.readOne(ev1); verify count. Note: vobject is optional
    dependency – gate with pytest.importorskip("vobject").

  • - testCreateEventFromiCaltest_create_event_from_ical
    Add event from an icalendar.Calendar object and from an icalendar.Event
    object. Gate with icalendar.Calendar.new existence (requires icalendar 7+).

  • - testAcceptInviteUsernameEmailFallbacktest_accept_invite_username_email_fallback
    Build invite with ATTENDEE matching attendee's username as email; call
    change_attendee_status(partstat="ACCEPTED") with no explicit attendee; verify
    PARTSTAT updated.

  • - testCheckCompatibility – runs caldav_server_tester externally; skip for async
    (the sync version already covers it per server).

  • - testObjects – constructs bare DAVObject; trivial unit-level, skip.

  • - Second calendar fixture (async_calendar2) – Several tests need two distinct
    calendars at once (testLoadEvent, testCopyEvent). Add async_calendar2 fixture in
    AsyncFunctionalTestsBaseClass following the same pattern as async_calendar but
    with a different calendar_name.

  • - Journal list fixture (async_journal_list) – Similar to async_task_list but
    with ["VJOURNAL"] component set. Add alongside.

  • - Test data constants – The async file uses inline make_event/make_todo helpers
    with near-future dates. For old-date searches (Group C) we need the verbatim sync
    test data: ev1, ev2, ev3, evr, evr2, todo, todo2todo8, journal.
    Preferred: import from test_caldav to avoid duplication:
    from .test_caldav import ev1, ev2, ev3, evr, evr2, todo, todo2, todo3, journal

  • - Server-params helper for auth teststestWrongAuthType and testWrongPassword
    need raw connection parameters. The TestServer object (self.server) already
    carries config; add _make_async_client_with_params(**overrides) on the base
    class that builds a fresh async client from the server config with some params
    overridden.

  • - asyncio.sleep instead of time.sleep – already done in existing async tests;
    maintain this discipline throughout.

  • - async_task_list fixture – already exists and handles supported_calendar_component_set;
    reuse for todo date-search and sort tests.

  • - Phase 1 – Infrastructure (do first, enables everything else)
    Add async_calendar2, async_journal_list, import shared test data constants,
    add _make_async_client_with_params helper.

  • - Phase 2 – Core CRUD (Group A, tests 1–7)
    Self-contained tests of the most basic library operations; most likely to surface
    async-specific bugs.

  • - Phase 3 – Sync tokens (Group B, tests 8–9)
    Verbose but straightforward; add after Group A is stable.

  • - Phase 4 – Search (Group C, tests 10–19)
    Tests using old-date events must be gated on
    search.time-range.event.old-dates / search.unlimited-time-range.

  • - Phase 5 – Todos (Group D, tests 20–27)
    Gate all on save-load.todo.

  • - Phase 6 – Properties and meta (Group E, tests 28–34)
    Low risk, mostly property reads.

  • - Phase 7 – Regressions, auth errors, misc (Groups F and G, tests 35–50)

  • Use is_supported() exclusively (not the legacy check_compatibility_flag API).
  • All server I/O must be await-ed.
  • time.sleepawait asyncio.sleep.
  • Use the existing self.skip_unless_support(feature) helper already on the base class.
  • Event/todo data: prefer icalendar_component accessors over vobject_instance for
    new tests.
  • Keep each test method focused on one behaviour; resist combining two sync tests into
    one large async test.
  • For tests gated on near-future dates (existing async tests), keep using
    _get_base_date(). For old-date searches, use the imported constants and gate on
    the appropriate feature flag.

All changes go into tests/test_async_integration.py.
No changes to tests/test_caldav.py are needed (its infrastructure is sync-only).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions