# Testing the Calendar Skill End-to-end tests for contacts, send, reply, todo, calendar sync, and local calendar. All commands use `--dry-run` first, then live. **Important**: Tests 1-3 (contacts) must run first — `send` requires recipients to be in the contacts list. ```bash SKILL_DIR=~/.openclaw/workspace/skills/calendar CONTACTS_DIR=~/.openclaw/workspace/skills/contacts # Use a date 3 days from now for test events TEST_DATE=$(date -d "+3 days" +%Y-%m-%d) ``` --- ## 1. Contact Add and List Set up test contacts needed for send tests. ```bash # Add a contact with a single email $CONTACTS_DIR/scripts/contacts.sh add --name "测试用户" --email "mail@luyx.org" # List contacts $CONTACTS_DIR/scripts/contacts.sh list # Add a contact with typed email and nickname $CONTACTS_DIR/scripts/contacts.sh add --name "测试多邮箱" --email "work@example.com" --type work --nickname "多邮箱" # Add a second email to the same contact $CONTACTS_DIR/scripts/contacts.sh add --name "测试多邮箱" --email "home@example.com" --type home # List again — should show both emails $CONTACTS_DIR/scripts/contacts.sh list ``` **Verify:** - [ ] `contact add` prints "Added contact: ..." - [ ] Second `contact add` prints "Updated contact: ... — added ..." - [ ] `contact list` shows all contacts with email types - [ ] `.vcf` files created in `~/.openclaw/workspace/contacts/family/` ## 2. Recipient Resolution (Send Validation) Test that `send --to` correctly resolves contacts and rejects unknown addresses. ```bash # Name resolves (single email contact) — should work $SKILL_DIR/scripts/calendar.sh send \ --to "测试用户" \ --subject "Resolve Test" --summary "Resolve Test" \ --start "${TEST_DATE}T15:00:00" --end "${TEST_DATE}T16:00:00" \ --dry-run # Name:type resolves — should work $SKILL_DIR/scripts/calendar.sh send \ --to "测试多邮箱:work" \ --subject "Resolve Test" --summary "Resolve Test" \ --start "${TEST_DATE}T15:00:00" --end "${TEST_DATE}T16:00:00" \ --dry-run # Nickname resolves — should work $SKILL_DIR/scripts/calendar.sh send \ --to "多邮箱:home" \ --subject "Resolve Test" --summary "Resolve Test" \ --start "${TEST_DATE}T15:00:00" --end "${TEST_DATE}T16:00:00" \ --dry-run # Known raw email resolves — should work $SKILL_DIR/scripts/calendar.sh send \ --to "mail@luyx.org" \ --subject "Resolve Test" --summary "Resolve Test" \ --start "${TEST_DATE}T15:00:00" --end "${TEST_DATE}T16:00:00" \ --dry-run # Unknown email REJECTED — should FAIL $SKILL_DIR/scripts/calendar.sh send \ --to "xiaojuzi@meta.com" \ --subject "Resolve Test" --summary "Resolve Test" \ --start "${TEST_DATE}T15:00:00" --end "${TEST_DATE}T16:00:00" \ --dry-run # Multi-email without type REJECTED — should FAIL (ambiguous) $SKILL_DIR/scripts/calendar.sh send \ --to "测试多邮箱" \ --subject "Resolve Test" --summary "Resolve Test" \ --start "${TEST_DATE}T15:00:00" --end "${TEST_DATE}T16:00:00" \ --dry-run # Unknown name REJECTED — should FAIL $SKILL_DIR/scripts/calendar.sh send \ --to "不存在的人" \ --subject "Resolve Test" --summary "Resolve Test" \ --start "${TEST_DATE}T15:00:00" --end "${TEST_DATE}T16:00:00" \ --dry-run ``` **Verify:** - [ ] First 4 commands succeed (show ICS output) - [ ] Unknown email fails with "not found in contacts" + available contacts list - [ ] Multi-email without type fails with "has multiple emails. Specify type" - [ ] Unknown name fails with "not found" + available contacts list ## 3. Contact Delete ```bash # Delete the multi-email test contact $CONTACTS_DIR/scripts/contacts.sh delete --name "测试多邮箱" # Verify it's gone $CONTACTS_DIR/scripts/contacts.sh list # Delete by nickname — should fail (contact already deleted) $CONTACTS_DIR/scripts/contacts.sh delete --name "多邮箱" ``` **Verify:** - [ ] Delete prints "Deleted contact: 测试多邮箱" - [ ] `contact list` no longer shows that contact - [ ] Second delete fails with "No contact matching" - [ ] `.vcf` file removed from contacts dir --- ## 4. Dry Run: Send Invite **Prerequisite**: "测试用户" contact from test 1 must exist. 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 - ICS has `BEGIN:VALARM` with correct `TRIGGER` duration ```bash # Default alarm (1 day before) $SKILL_DIR/scripts/calendar.sh send \ --to "测试用户" \ --subject "Test Invite" \ --summary "Test Event" \ --start "${TEST_DATE}T15:00:00" \ --end "${TEST_DATE}T16:00:00" \ --dry-run # Custom alarm (1 hour before) $SKILL_DIR/scripts/calendar.sh send \ --to "测试用户" \ --subject "Test Invite (1h alarm)" \ --summary "Test Event (1h alarm)" \ --start "${TEST_DATE}T15:00:00" \ --end "${TEST_DATE}T16:00:00" \ --alarm 1h \ --dry-run ``` ## 5. Live Send: Self-Invite Send a real invite to `mail@luyx.org` only (no confirmation needed per email rules). ```bash $SKILL_DIR/scripts/calendar.sh send \ --to "测试用户" \ --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/` ## 6. Verify Calendar Sync and Local Calendar After sending in step 2, check that the event synced and appears locally. ```bash # 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 ## 7. 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. ```bash # Find the test invite in inbox himalaya envelope list # Confirm it's the calendar invite himalaya message read # Accept it $SKILL_DIR/scripts/calendar.sh reply \ --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 ## 8. Reply: Decline an Invite Send another self-invite, then decline it. This verifies decline removes the event from local calendar. ```bash # Send a second test invite $SKILL_DIR/scripts/calendar.sh send \ --to "测试用户" \ --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 \ --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" ## 9. Verify Final Calendar State After all tests, confirm the calendar is in a clean state. ```bash # Sync one more time vdirsyncer sync # Only the accepted event should remain khal list "$TEST_DATE" # List all upcoming events khal list today 7d ``` --- ## 10. 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` ```bash $SKILL_DIR/scripts/calendar.sh todo add \ --summary "Test Todo" \ --due "$TEST_DATE" \ --priority high \ --dry-run ``` ## 11. Live Add: Create a Todo ```bash $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 ## 12. List Todos ```bash # 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) ## 13. Edit a Todo Change the due date and priority of the test todo from step 8. ```bash # 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 ```bash # 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 ```bash # 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 ```bash # 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 ``` ## 14. Complete a Todo ```bash $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 ## 15. Delete a Todo Create a second test todo, then delete it. ```bash # 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 ## 16. Todo Check (Cron Output) ```bash # 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) ```bash $SKILL_DIR/scripts/calendar.sh todo complete --match "Check Test" $SKILL_DIR/scripts/calendar.sh todo check # Should produce no output ``` ## 17. Dry Run: Recurring Event (--rrule) Test recurring event generation. Use a date that falls on a Tuesday. ```bash # 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 "测试用户" \ --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 ## 18. Validation: DTSTART/BYDAY Mismatch Verify the tool rejects mismatched DTSTART and BYDAY. ```bash # This should FAIL — start is on a Tuesday but BYDAY=TH $SKILL_DIR/scripts/calendar.sh send \ --to "测试用户" \ --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 ## 19. Event List ```bash # 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 ## 20. Event Delete ```bash # Send a throwaway event first $SKILL_DIR/scripts/calendar.sh send \ --to "测试用户" \ --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 ## 21. Event Delete: Cancel Single Occurrence (EXDATE) Test that `--date` cancels one occurrence of a recurring event without deleting the series. ```bash # 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 "测试用户" \ --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 ## 22. Event Delete: Recurring Without --date or --all (Safety Guard) ```bash # 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` ## 23. Event Delete: Recurring With --all ```bash # 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 ## 24. Regression: Send Rejects Unknown Addresses Verify that `send` no longer accepts arbitrary email addresses. ```bash # This MUST fail — raw unknown email should be rejected $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:** - [ ] Command exits with error - [ ] Error shows "not found in contacts" with available contacts list - [ ] No ICS generated --- ## Quick Health Checks Run these first if any step fails. ```bash # 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) | | `send` rejects email address | Address not in contacts. Add with `contacts.sh add` first (separate from send) | | `send` says "has multiple emails" | Contact has work+home emails. Use `name:type` syntax (e.g. `小橘子:work`) | | Contacts dir empty after sync | Check vdirsyncer CardDAV pair is configured for `contacts/default/` |