Files
youlu-openclaw-workspace/skills/calendar/TESTING.md
Yanxin Lu e1f1c0f334 calendar: add EXDATE support for cancelling single occurrences of recurring events
event delete on a recurring event previously deleted the entire .ics file,
killing the whole series. Now requires --date (adds EXDATE) or --all (deletes
series) for recurring events, refusing to act without either flag.
2026-03-26 09:19:02 -07:00

13 KiB

Testing the Calendar Skill

End-to-end tests for send, reply, todo, calendar sync, and local calendar. All commands use --dry-run first, then live.

SKILL_DIR=~/.openclaw/workspace/skills/calendar

# Use a date 3 days from now for test events
TEST_DATE=$(date -d "+3 days" +%Y-%m-%d)

1. Dry Run: Send Invite

Generates the ICS and MIME email without sending. Check that:

  • ICS has METHOD:REQUEST
  • MIME has Content-Type: text/calendar; method=REQUEST
  • Only --to recipients appear as attendees
  • Times and timezone look correct
$SKILL_DIR/scripts/calendar.sh send \
  --to "mail@luyx.org" \
  --subject "Test Invite" \
  --summary "Test Event" \
  --start "${TEST_DATE}T15:00:00" \
  --end "${TEST_DATE}T16:00:00" \
  --dry-run

2. Live Send: Self-Invite

Send a real invite to mail@luyx.org only (no confirmation needed per email rules).

$SKILL_DIR/scripts/calendar.sh send \
  --to "mail@luyx.org" \
  --subject "Calendar Skill Test" \
  --summary "Calendar Skill Test" \
  --start "${TEST_DATE}T15:00:00" \
  --end "${TEST_DATE}T16:00:00" \
  --location "Test Location"

Verify:

  • Script exits without error
  • Email arrives at mail@luyx.org
  • Email shows Accept/Decline/Tentative buttons (not just an attachment)
  • .ics file saved to ~/.openclaw/workspace/calendars/home/

3. Verify Calendar Sync and Local Calendar

After sending in step 2, check that the event synced and appears locally.

# Check vdirsyncer sync ran (should have printed "Synced to CalDAV server" in step 2)
# If not, run manually:
vdirsyncer sync

# List .ics files in local calendar
ls ~/.openclaw/workspace/calendars/home/

# Check the event shows up in khal
khal list "$TEST_DATE"

Verify:

  • vdirsyncer sync completes without errors
  • .ics file exists in ~/.openclaw/workspace/calendars/home/
  • khal list shows "Calendar Skill Test" on the test date

4. Reply: Accept the Self-Invite

The invite sent in step 2 should be in the inbox. Find it, then accept it. This tests the full reply flow without needing an external sender.

# Find the test invite in inbox
himalaya envelope list

# Confirm it's the calendar invite
himalaya message read <envelope-id>

# Accept it
$SKILL_DIR/scripts/calendar.sh reply \
  --envelope-id <envelope-id> \
  --action accept

Verify:

  • Reply sent to organizer (youlu@luyanxin.com, i.e. ourselves)
  • Event saved to ~/.openclaw/workspace/calendars/home/
  • vdirsyncer sync ran
  • khal list "$TEST_DATE" still shows the event

5. Reply: Decline an Invite

Send another self-invite, then decline it. This verifies decline removes the event from local calendar.

# Send a second test invite
$SKILL_DIR/scripts/calendar.sh send \
  --to "mail@luyx.org" \
  --subject "Decline Test" \
  --summary "Decline Test Event" \
  --start "${TEST_DATE}T17:00:00" \
  --end "${TEST_DATE}T18:00:00"

# Find it in inbox
himalaya envelope list

# Decline it
$SKILL_DIR/scripts/calendar.sh reply \
  --envelope-id <envelope-id> \
  --action decline \
  --comment "Testing decline flow."

Verify:

  • Reply sent to organizer with comment
  • Event removed from local calendar
  • khal list "$TEST_DATE" does NOT show "Decline Test Event"

6. Verify Final Calendar State

After all tests, confirm the calendar is in a clean state.

# Sync one more time
vdirsyncer sync

# Only the accepted event should remain
khal list "$TEST_DATE"

# List all upcoming events
khal list today 7d

7. Dry Run: Add Todo

Generates the VTODO ICS without saving. Check that:

  • ICS has BEGIN:VTODO
  • ICS has correct PRIORITY value (1 for high)
  • ICS has STATUS:NEEDS-ACTION
  • ICS has BEGIN:VALARM
$SKILL_DIR/scripts/calendar.sh todo add \
  --summary "Test Todo" \
  --due "$TEST_DATE" \
  --priority high \
  --dry-run

8. Live Add: Create a Todo

$SKILL_DIR/scripts/calendar.sh todo add \
  --summary "Test Todo" \
  --due "$TEST_DATE" \
  --priority medium \
  --description "Test description"

Verify:

  • Script exits without error
  • .ics file created in ~/.openclaw/workspace/calendars/tasks/
  • todo list (todoman directly) shows "Test Todo"
  • vdirsyncer sync ran

9. List Todos

# Via our wrapper (formatted Chinese output)
$SKILL_DIR/scripts/calendar.sh todo list

# Via todoman directly (should show the same items)
todo list

# Include completed
$SKILL_DIR/scripts/calendar.sh todo list --all

Verify:

  • "Test Todo" appears in both outputs
  • Priority grouping is correct in wrapper output
  • --all flag works (same output when none are completed)

10. Edit a Todo

Change the due date and priority of the test todo from step 8.

# Edit due date
$SKILL_DIR/scripts/calendar.sh todo edit --match "Test Todo" --due "$(date -d '+5 days' +%Y-%m-%d)"

# Verify change
$SKILL_DIR/scripts/calendar.sh todo list

Verify:

  • Script exits without error
  • Output shows "Updated todo: Test Todo" with the change
  • todo list shows the new due date
  • vdirsyncer sync ran
# Edit priority
$SKILL_DIR/scripts/calendar.sh todo edit --match "Test Todo" --priority high

# Verify change
$SKILL_DIR/scripts/calendar.sh todo list

Verify:

  • Priority changed to high
  • Todo appears under the high priority group in formatted output
# Edit multiple fields at once
$SKILL_DIR/scripts/calendar.sh todo edit --match "Test Todo" --due "$TEST_DATE" --priority low

Verify:

  • Both due date and priority updated in one command
# Error: no matching todo
$SKILL_DIR/scripts/calendar.sh todo edit --match "nonexistent" --due "$TEST_DATE"
# Should print error and exit non-zero

# Error: no fields to edit
$SKILL_DIR/scripts/calendar.sh todo edit --match "Test Todo"
# Should print "Nothing to change" message

11. Complete a Todo

$SKILL_DIR/scripts/calendar.sh todo complete --match "Test Todo"

Verify:

  • todo list (todoman) — "Test Todo" no longer appears
  • $SKILL_DIR/scripts/calendar.sh todo list — also gone
  • $SKILL_DIR/scripts/calendar.sh todo list --all — appears as completed (with checkmark)
  • vdirsyncer sync ran

12. Delete a Todo

Create a second test todo, then delete it.

# Create
$SKILL_DIR/scripts/calendar.sh todo add \
  --summary "Delete Me Todo" \
  --due "$TEST_DATE" \
  --priority low

# Confirm it appears
todo list

# Delete
$SKILL_DIR/scripts/calendar.sh todo delete --match "Delete Me"

Verify:

  • .ics file removed from tasks dir
  • todo list (todoman) does not show "Delete Me Todo"
  • vdirsyncer sync ran

13. Todo Check (Cron Output)

# Create a test todo
$SKILL_DIR/scripts/calendar.sh todo add \
  --summary "Check Test Todo" \
  --due "$TEST_DATE"

# Run check
$SKILL_DIR/scripts/calendar.sh todo check

Verify:

  • Output matches daily digest format (priority groups, urgency labels)
  • Complete the todo, run todo check again — silent exit (no output)
$SKILL_DIR/scripts/calendar.sh todo complete --match "Check Test"
$SKILL_DIR/scripts/calendar.sh todo check
# Should produce no output

14. Dry Run: Recurring Event (--rrule)

Test recurring event generation. Use a date that falls on a Tuesday.

# Find next Tuesday
NEXT_TUE=$(python3 -c "from datetime import date,timedelta; d=date.today(); d+=timedelta((1-d.weekday())%7 or 7); print(d)")

$SKILL_DIR/scripts/calendar.sh send \
  --to "mail@luyx.org" \
  --subject "Recurring Test (Tue)" \
  --summary "Recurring Test (Tue)" \
  --start "${NEXT_TUE}T14:30:00" \
  --end "${NEXT_TUE}T15:00:00" \
  --rrule "FREQ=WEEKLY;COUNT=4;BYDAY=TU" \
  --dry-run

Verify:

  • ICS has RRULE:FREQ=WEEKLY;BYDAY=TU;COUNT=4
  • DTSTART falls on a Tuesday
  • No validation errors

15. Validation: DTSTART/BYDAY Mismatch

Verify the tool rejects mismatched DTSTART and BYDAY.

# This should FAIL — start is on a Tuesday but BYDAY=TH
$SKILL_DIR/scripts/calendar.sh send \
  --to "mail@luyx.org" \
  --subject "Mismatch Test" \
  --summary "Mismatch Test" \
  --start "${NEXT_TUE}T09:00:00" \
  --end "${NEXT_TUE}T09:30:00" \
  --rrule "FREQ=WEEKLY;COUNT=4;BYDAY=TH" \
  --dry-run

Verify:

  • Script exits with error
  • Error message says DTSTART falls on TU but RRULE says BYDAY=TH
  • Suggests changing --start to a date that falls on TH

16. Event List

# List upcoming events
$SKILL_DIR/scripts/calendar.sh event list

# Search by text
$SKILL_DIR/scripts/calendar.sh event list --search "Calendar Skill Test"

# List with UIDs
$SKILL_DIR/scripts/calendar.sh event list --format "{uid} {title}"

Verify:

  • Events from earlier tests appear
  • Search narrows results correctly
  • UIDs are displayed with --format

17. Event Delete

# Send a throwaway event first
$SKILL_DIR/scripts/calendar.sh send \
  --to "mail@luyx.org" \
  --subject "Delete Me Event" \
  --summary "Delete Me Event" \
  --start "${TEST_DATE}T20:00:00" \
  --end "${TEST_DATE}T21:00:00"

# Verify it exists
$SKILL_DIR/scripts/calendar.sh event list --search "Delete Me"

# Delete it
$SKILL_DIR/scripts/calendar.sh event delete --match "Delete Me Event"

# Verify it's gone
$SKILL_DIR/scripts/calendar.sh event list --search "Delete Me"

Verify:

  • Event is created and visible
  • Delete removes exactly one event
  • Other events are untouched
  • vdirsyncer sync ran after delete

18. Event Delete: Cancel Single Occurrence (EXDATE)

Test that --date cancels one occurrence of a recurring event without deleting the series.

# Create a recurring event (weekly on Saturday, 4 weeks)
NEXT_SAT=$(python3 -c "from datetime import date,timedelta; d=date.today(); d+=timedelta((5-d.weekday())%7 or 7); print(d)")

$SKILL_DIR/scripts/calendar.sh send \
  --to "mail@luyx.org" \
  --subject "EXDATE Test (Sat)" \
  --summary "EXDATE Test (Sat)" \
  --start "${NEXT_SAT}T10:00:00" \
  --end "${NEXT_SAT}T11:00:00" \
  --rrule "FREQ=WEEKLY;COUNT=4;BYDAY=SA"

# Verify it exists
$SKILL_DIR/scripts/calendar.sh event list --search "EXDATE Test"

# Cancel just the first occurrence
$SKILL_DIR/scripts/calendar.sh event delete --match "EXDATE Test" --date "$NEXT_SAT"

# Verify: .ics file still exists (not deleted)
ls ~/.openclaw/workspace/calendars/home/ | grep -i exdate

Verify:

  • event delete --match ... --date ... prints "Cancelled ... (added EXDATE, series continues)"
  • .ics file still exists in calendar dir
  • khal list no longer shows the cancelled date but shows subsequent Saturdays

19. Event Delete: Recurring Without --date or --all (Safety Guard)

# Try to delete the recurring event without --date or --all — should FAIL
$SKILL_DIR/scripts/calendar.sh event delete --match "EXDATE Test"

Verify:

  • Script exits with error
  • Error message explains the two options: --date or --all

20. Event Delete: Recurring With --all

# Delete the entire series
$SKILL_DIR/scripts/calendar.sh event delete --match "EXDATE Test" --all

Verify:

  • .ics file is removed
  • event list --search "EXDATE Test" shows nothing

21. Regression: Existing Invite Commands (was #18)

Verify new features didn't break VEVENT flow.

$SKILL_DIR/scripts/calendar.sh send \
  --to "test@example.com" \
  --subject "Regression Test" \
  --summary "Regression Test Event" \
  --start "${TEST_DATE}T10:00:00" \
  --end "${TEST_DATE}T11:00:00" \
  --dry-run

Verify:

  • ICS has BEGIN:VEVENT, METHOD:REQUEST
  • No RRULE present (single event)
  • No errors

Quick Health Checks

Run these first if any step fails.

# icalendar library is available
uv run --project $SKILL_DIR python -c "import icalendar; print('ok')"

# himalaya can list emails
himalaya envelope list --page-size 5

# vdirsyncer can sync
vdirsyncer sync

# khal can read local calendar
khal list today 7d

# todoman can list todos
todo list

Common Failures

Symptom Likely Cause
himalaya message send errors SMTP config issue, check ~/.config/himalaya/config.toml
No .ics attachment found Email doesn't have a calendar invite, or himalaya can't download attachments
vdirsyncer sync fails Check credentials in ~/.config/vdirsyncer/config, or server is unreachable
ModuleNotFoundError: icalendar Run uv sync --project $SKILL_DIR to install dependencies
Invite shows as attachment (no Accept/Decline) Check MIME Content-Type includes method=REQUEST
Event not in khal list after sync Check .ics file exists in ~/.openclaw/workspace/calendars/home/
todo command not found Install with uv tool install todoman
todo list errors Check ~/.config/todoman/config.py exists and path points to tasks dir
Todo not syncing Check ~/.openclaw/workspace/calendars/tasks/ exists, verify vdirsyncer cal/tasks pair
DTSTART/BYDAY mismatch error --start date doesn't fall on the BYDAY day. Change the start date to match
Recurring events on wrong day DTSTART was not aligned with BYDAY. Delete the event and resend with correct --start
SMTP rate limit / EOF error Too many sends too fast. Wait 10+ seconds between sends (Migadu limit)
Events disappeared after cleanup Never use rm *.ics on calendar dirs. Use event delete --match instead
Recurring series deleted when cancelling one date Use --date YYYY-MM-DD to add EXDATE, not bare event delete (which requires --all for recurring)