331 lines
13 KiB
Bash
Executable File
331 lines
13 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
BASE_URL="${BASE_URL:-http://127.0.0.1:8080}"
|
|
|
|
echo "[smoke] checking api list"
|
|
curl -fsS -H 'X-WP-User: admin' "$BASE_URL/wp-json/calendar/v1/events" >/dev/null
|
|
|
|
echo "[smoke] checking ics"
|
|
curl -fsS "$BASE_URL/calendar.ics" | grep -q 'BEGIN:VCALENDAR'
|
|
|
|
echo "[smoke] checking caldav read"
|
|
curl -fsS -u rw_user@example.test:rwpass123456 "$BASE_URL/caldav/calendars/public/1.ics" >/dev/null
|
|
|
|
echo "[smoke] checking caldav multiget href filtering"
|
|
TMP_XML="$(mktemp)"
|
|
cleanup_tmp_xml() { rm -f "$TMP_XML"; }
|
|
trap cleanup_tmp_xml EXIT
|
|
cat > "$TMP_XML" <<'XML'
|
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
<C:calendar-multiget xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav">
|
|
<D:prop><D:getetag/><C:calendar-data/></D:prop>
|
|
<D:href>/caldav/calendars/public/4.ics</D:href>
|
|
</C:calendar-multiget>
|
|
XML
|
|
REPORT_XML="$(curl -fsS -u rw_user@example.test:rwpass123456 -X REPORT \
|
|
"$BASE_URL/caldav/calendars/public/" \
|
|
-H 'Content-Type: text/xml; charset=utf-8' \
|
|
-H 'Depth: 1' \
|
|
--data-binary @"$TMP_XML")"
|
|
echo "$REPORT_XML" | grep -q '/caldav/calendars/public/4.ics'
|
|
if echo "$REPORT_XML" | grep -q '/caldav/calendars/public/1.ics'; then
|
|
echo "[smoke] multiget returned unrelated resources"
|
|
exit 1
|
|
fi
|
|
trap - EXIT
|
|
cleanup_tmp_xml
|
|
|
|
echo "[smoke] checking recurrence exception delete"
|
|
curl -fsS -X DELETE -H 'X-WP-User: admin' \
|
|
"$BASE_URL/wp-json/calendar/v1/events/10/occurrences/2026-04-17T14:00:00+01:00" \
|
|
-o /dev/null -w '%{http_code}' | grep -q '^204$'
|
|
|
|
echo "[smoke] checking event occurrences endpoint + idempotent delete"
|
|
CREATE_JSON='{"title":"Smoke Occurrences API","description":"occ-endpoint","location":"","category":"","all_day_event":false,"start_datetime":"2026-04-01T10:00:00+01:00","end_datetime":"2026-04-01T11:00:00+01:00","repeat_type":"daily","repeat_interval":1,"repeat_range_mode":"count","repeat_count":3}'
|
|
CREATE_RESP="$(curl -fsS -H 'Content-Type: application/json' -H 'X-WP-User: admin' \
|
|
-d "$CREATE_JSON" "$BASE_URL/wp-json/calendar/v1/events")"
|
|
EVENT_ID="$(python3 - <<'PY' "$CREATE_RESP"
|
|
import json,sys
|
|
print(json.loads(sys.argv[1])["data"]["id"])
|
|
PY
|
|
)"
|
|
OCC_RESP="$(curl -fsS -H 'X-WP-User: admin' \
|
|
"$BASE_URL/wp-json/calendar/v1/events/${EVENT_ID}/occurrences?from=2026-04-01&months=1")"
|
|
python3 - <<'PY' "$OCC_RESP"
|
|
import json,sys
|
|
data=json.loads(sys.argv[1])
|
|
days=[x["occurrence_start"][:10] for x in data["data"]]
|
|
assert len(data["data"]) >= 3
|
|
assert "2026-04-02" in days
|
|
PY
|
|
curl -fsS -X DELETE -H 'X-WP-User: admin' \
|
|
"$BASE_URL/wp-json/calendar/v1/events/${EVENT_ID}/occurrences/2026-04-02T10:00:00+01:00" \
|
|
-o /dev/null -w '%{http_code}' | grep -q '^204$'
|
|
curl -fsS -X DELETE -H 'X-WP-User: admin' \
|
|
"$BASE_URL/wp-json/calendar/v1/events/${EVENT_ID}/occurrences/2026-04-02T10:00:00+01:00" \
|
|
-o /dev/null -w '%{http_code}' | grep -q '^204$'
|
|
OCC_RESP="$(curl -fsS -H 'X-WP-User: admin' \
|
|
"$BASE_URL/wp-json/calendar/v1/events/${EVENT_ID}/occurrences?from=2026-04-01&months=1")"
|
|
python3 - <<'PY' "$OCC_RESP"
|
|
import json,sys
|
|
days=[x["occurrence_start"][:10] for x in json.loads(sys.argv[1])["data"]]
|
|
assert "2026-04-02" not in days
|
|
PY
|
|
|
|
echo "[smoke] checking caldav vtimezone parsing regression"
|
|
TMP_ICS="$(mktemp)"
|
|
cleanup_tmp_ics() { rm -f "$TMP_ICS"; }
|
|
trap cleanup_tmp_ics EXIT
|
|
cat > "$TMP_ICS" <<'ICS'
|
|
BEGIN:VCALENDAR
|
|
PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN
|
|
VERSION:2.0
|
|
BEGIN:VTIMEZONE
|
|
TZID:Europe/London
|
|
BEGIN:STANDARD
|
|
DTSTART:18471201T000000
|
|
RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=9
|
|
TZOFFSETFROM:+0115
|
|
TZOFFSETTO:+0000
|
|
TZNAME:GMT
|
|
END:STANDARD
|
|
END:VTIMEZONE
|
|
BEGIN:VEVENT
|
|
UID:smoke-vtimezone-parser-001
|
|
SUMMARY:Smoke VTIMEZONE Parse
|
|
DTSTART;TZID=Europe/London:20260423T150000
|
|
DTEND;TZID=Europe/London:20260423T160000
|
|
END:VEVENT
|
|
END:VCALENDAR
|
|
ICS
|
|
curl -fsS -u rw_user@example.test:rwpass123456 -X PUT \
|
|
-H 'Content-Type: text/calendar; charset=utf-8' \
|
|
--data-binary @"$TMP_ICS" \
|
|
"$BASE_URL/caldav/calendars/public/smoke-vtimezone.ics" >/dev/null
|
|
CALDAV_ICS="$(curl -fsS -u rw_user@example.test:rwpass123456 \
|
|
"$BASE_URL/caldav/calendars/public/smoke-vtimezone.ics")"
|
|
echo "$CALDAV_ICS" | grep -q 'DTSTART[^[:space:]]*:20260423T150000'
|
|
if echo "$CALDAV_ICS" | grep -q 'RRULE'; then
|
|
echo "[smoke] unexpected RRULE found in VEVENT-only upload"
|
|
exit 1
|
|
fi
|
|
trap - EXIT
|
|
cleanup_tmp_ics
|
|
|
|
echo "[smoke] checking caldav monthly nth-weekday roundtrip"
|
|
TMP_ICS="$(mktemp)"
|
|
cleanup_tmp_ics() { rm -f "$TMP_ICS"; }
|
|
trap cleanup_tmp_ics EXIT
|
|
cat > "$TMP_ICS" <<'ICS'
|
|
BEGIN:VCALENDAR
|
|
PRODID:-//Smoke//EN
|
|
VERSION:2.0
|
|
BEGIN:VEVENT
|
|
UID:smoke-monthly-nth-001
|
|
SUMMARY:Smoke Monthly Nth
|
|
DTSTART;TZID=Europe/London:20260402T150000
|
|
DTEND;TZID=Europe/London:20260402T160000
|
|
RRULE:FREQ=MONTHLY;BYDAY=SU;BYSETPOS=4
|
|
END:VEVENT
|
|
END:VCALENDAR
|
|
ICS
|
|
curl -fsS -u rw_user@example.test:rwpass123456 -X PUT \
|
|
-H 'Content-Type: text/calendar; charset=utf-8' \
|
|
--data-binary @"$TMP_ICS" \
|
|
"$BASE_URL/caldav/calendars/public/smoke-monthly-nth.ics" >/dev/null
|
|
CALDAV_ICS="$(curl -fsS -u rw_user@example.test:rwpass123456 \
|
|
"$BASE_URL/caldav/calendars/public/smoke-monthly-nth.ics")"
|
|
echo "$CALDAV_ICS" | grep -q 'RRULE:FREQ=MONTHLY;BYDAY=SU;BYSETPOS=4'
|
|
trap - EXIT
|
|
cleanup_tmp_ics
|
|
|
|
echo "[smoke] checking caldav monthly BYDAY ordinal import"
|
|
TMP_ICS="$(mktemp)"
|
|
cleanup_tmp_ics() { rm -f "$TMP_ICS"; }
|
|
trap cleanup_tmp_ics EXIT
|
|
cat > "$TMP_ICS" <<'ICS'
|
|
BEGIN:VCALENDAR
|
|
PRODID:-//Smoke//EN
|
|
VERSION:2.0
|
|
BEGIN:VEVENT
|
|
UID:smoke-monthly-ordinal-001
|
|
SUMMARY:Smoke Monthly Ordinal
|
|
DTSTART;TZID=Europe/London:20260411T150000
|
|
DTEND;TZID=Europe/London:20260411T160000
|
|
RRULE:FREQ=MONTHLY;BYDAY=2SA
|
|
END:VEVENT
|
|
END:VCALENDAR
|
|
ICS
|
|
curl -fsS -u rw_user@example.test:rwpass123456 -X PUT \
|
|
-H 'Content-Type: text/calendar; charset=utf-8' \
|
|
--data-binary @"$TMP_ICS" \
|
|
"$BASE_URL/caldav/calendars/public/smoke-monthly-ordinal.ics" >/dev/null
|
|
CALDAV_ICS="$(curl -fsS -u rw_user@example.test:rwpass123456 \
|
|
"$BASE_URL/caldav/calendars/public/smoke-monthly-ordinal.ics")"
|
|
echo "$CALDAV_ICS" | grep -q 'RRULE:FREQ=MONTHLY;BYDAY=SA;BYSETPOS=2'
|
|
trap - EXIT
|
|
cleanup_tmp_ics
|
|
|
|
echo "[smoke] checking caldav monthly last-weekday roundtrip"
|
|
TMP_ICS="$(mktemp)"
|
|
cleanup_tmp_ics() { rm -f "$TMP_ICS"; }
|
|
trap cleanup_tmp_ics EXIT
|
|
cat > "$TMP_ICS" <<'ICS'
|
|
BEGIN:VCALENDAR
|
|
PRODID:-//Smoke//EN
|
|
VERSION:2.0
|
|
BEGIN:VEVENT
|
|
UID:smoke-monthly-last-001
|
|
SUMMARY:Smoke Monthly Last
|
|
DTSTART;TZID=Europe/London:20260425T150000
|
|
DTEND;TZID=Europe/London:20260425T160000
|
|
RRULE:FREQ=MONTHLY;BYDAY=SA;BYSETPOS=-1
|
|
END:VEVENT
|
|
END:VCALENDAR
|
|
ICS
|
|
curl -fsS -u rw_user@example.test:rwpass123456 -X PUT \
|
|
-H 'Content-Type: text/calendar; charset=utf-8' \
|
|
--data-binary @"$TMP_ICS" \
|
|
"$BASE_URL/caldav/calendars/public/smoke-monthly-last.ics" >/dev/null
|
|
CALDAV_ICS="$(curl -fsS -u rw_user@example.test:rwpass123456 \
|
|
"$BASE_URL/caldav/calendars/public/smoke-monthly-last.ics")"
|
|
echo "$CALDAV_ICS" | grep -q 'RRULE:FREQ=MONTHLY;BYDAY=SA;BYSETPOS=-1'
|
|
trap - EXIT
|
|
cleanup_tmp_ics
|
|
|
|
echo "[smoke] checking monthly nth-weekday delete-exception preserves rule"
|
|
CREATE_JSON='{"title":"Smoke 4th Sunday Anchor","description":"anchor-normalization","location":"","category":"","all_day_event":false,"start_datetime":"2026-05-19T15:00:00+01:00","end_datetime":"2026-05-19T16:00:00+01:00","repeat_type":"monthly","repeat_interval":1,"repeat_nth_mode":"weekday_of_month","repeat_nth_pos":4,"repeat_nth_weekday":0,"repeat_range_mode":"no_end"}'
|
|
CREATE_RESP="$(curl -fsS -H 'Content-Type: application/json' -H 'X-WP-User: admin' \
|
|
-d "$CREATE_JSON" "$BASE_URL/wp-json/calendar/v1/events")"
|
|
python3 - <<'PY' "$CREATE_RESP"
|
|
import json,sys
|
|
data=json.loads(sys.argv[1])["data"]
|
|
assert data["start_datetime"] == "2026-05-24T15:00:00+01:00"
|
|
PY
|
|
EVENT_ID="$(python3 - <<'PY' "$CREATE_RESP"
|
|
import json,sys
|
|
print(json.loads(sys.argv[1])["data"]["id"])
|
|
PY
|
|
)"
|
|
curl -fsS -X DELETE -H 'X-WP-User: admin' \
|
|
"$BASE_URL/wp-json/calendar/v1/events/${EVENT_ID}/occurrences/2026-05-24T15:00:00+01:00" \
|
|
-o /dev/null -w '%{http_code}' | grep -q '^204$'
|
|
CALDAV_ICS="$(curl -fsS -u rw_user@example.test:rwpass123456 \
|
|
"$BASE_URL/caldav/calendars/public/${EVENT_ID}.ics")"
|
|
echo "$CALDAV_ICS" | grep -q 'RRULE:FREQ=MONTHLY;BYDAY=SU;BYSETPOS=4'
|
|
echo "$CALDAV_ICS" | grep -q 'EXDATE;TZID=Europe/London:20260524T150000'
|
|
|
|
echo "[smoke] checking delete-exception canonical key matching"
|
|
CREATE_JSON='{"title":"Smoke Daily Exception TZ","description":"tz-key","location":"","category":"","all_day_event":false,"start_datetime":"2026-03-02T10:00:00+00:00","end_datetime":"2026-03-02T11:00:00+00:00","repeat_type":"daily","repeat_interval":1,"repeat_range_mode":"until","repeat_until":"2026-03-19"}'
|
|
CREATE_RESP="$(curl -fsS -H 'Content-Type: application/json' -H 'X-WP-User: admin' \
|
|
-d "$CREATE_JSON" "$BASE_URL/wp-json/calendar/v1/events")"
|
|
EVENT_ID="$(python3 - <<'PY' "$CREATE_RESP"
|
|
import json,sys
|
|
print(json.loads(sys.argv[1])["data"]["id"])
|
|
PY
|
|
)"
|
|
curl -fsS -X DELETE -H 'X-WP-User: admin' \
|
|
"$BASE_URL/wp-json/calendar/v1/events/${EVENT_ID}/occurrences/2026-03-11T11:00:00+01:00" \
|
|
-o /dev/null -w '%{http_code}' | grep -q '^204$'
|
|
OCC_RESP="$(curl -fsS -H 'X-WP-User: admin' \
|
|
"$BASE_URL/wp-json/calendar/v1/events/${EVENT_ID}/occurrences?from=2026-03-01&months=1")"
|
|
python3 - <<'PY' "$OCC_RESP"
|
|
import json,sys
|
|
days=[x["occurrence_start"][:10] for x in json.loads(sys.argv[1])["data"]]
|
|
assert "2026-03-11" not in days
|
|
PY
|
|
curl -fsS -X DELETE -H 'X-WP-User: admin' \
|
|
"$BASE_URL/wp-json/calendar/v1/events/${EVENT_ID}/occurrences/2026-03-10" \
|
|
-o /dev/null -w '%{http_code}' | grep -q '^204$'
|
|
OCC_RESP="$(curl -fsS -H 'X-WP-User: admin' \
|
|
"$BASE_URL/wp-json/calendar/v1/events/${EVENT_ID}/occurrences?from=2026-03-01&months=1")"
|
|
python3 - <<'PY' "$OCC_RESP"
|
|
import json,sys
|
|
days=[x["occurrence_start"][:10] for x in json.loads(sys.argv[1])["data"]]
|
|
assert "2026-03-10" not in days
|
|
PY
|
|
|
|
echo "[smoke] checking caldav EXDATE import to recurrence exceptions"
|
|
TMP_ICS="$(mktemp)"
|
|
cleanup_tmp_ics() { rm -f "$TMP_ICS"; }
|
|
trap cleanup_tmp_ics EXIT
|
|
cat > "$TMP_ICS" <<'ICS'
|
|
BEGIN:VCALENDAR
|
|
PRODID:-//Smoke//EN
|
|
VERSION:2.0
|
|
BEGIN:VEVENT
|
|
UID:smoke-exdate-import-001
|
|
SUMMARY:Smoke EXDATE Import
|
|
DTSTART;TZID=Europe/London:20260302T100000
|
|
DTEND;TZID=Europe/London:20260302T110000
|
|
RRULE:FREQ=DAILY;UNTIL=20260319T235959
|
|
EXDATE;TZID=Europe/London:20260310T100000,20260311T100000
|
|
END:VEVENT
|
|
END:VCALENDAR
|
|
ICS
|
|
curl -fsS -u rw_user@example.test:rwpass123456 -X PUT \
|
|
-H 'Content-Type: text/calendar; charset=utf-8' \
|
|
--data-binary @"$TMP_ICS" \
|
|
"$BASE_URL/caldav/calendars/public/smoke-exdate-import.ics" >/dev/null
|
|
EVENT_ID="$(curl -fsS -H 'X-WP-User: admin' "$BASE_URL/wp-json/calendar/v1/events" | python3 -c "import json,sys; data=json.load(sys.stdin)['data']; print([e for e in data if e.get('uid')=='smoke-exdate-import-001'][0]['id'])")"
|
|
OCC_RESP="$(curl -fsS -H 'X-WP-User: admin' \
|
|
"$BASE_URL/wp-json/calendar/v1/events/${EVENT_ID}/occurrences?from=2026-03-01&months=1")"
|
|
python3 - <<'PY' "$OCC_RESP"
|
|
import json,sys
|
|
days=[x["occurrence_start"][:10] for x in json.loads(sys.argv[1])["data"]]
|
|
assert "2026-03-10" not in days
|
|
assert "2026-03-11" not in days
|
|
PY
|
|
trap - EXIT
|
|
cleanup_tmp_ics
|
|
|
|
echo "[smoke] checking caldav cancelled-occurrence component handling"
|
|
TMP_ICS="$(mktemp)"
|
|
cleanup_tmp_ics() { rm -f "$TMP_ICS"; }
|
|
trap cleanup_tmp_ics EXIT
|
|
cat > "$TMP_ICS" <<'ICS'
|
|
BEGIN:VCALENDAR
|
|
PRODID:-//Smoke//EN
|
|
VERSION:2.0
|
|
BEGIN:VEVENT
|
|
UID:smoke-cancelled-occurrence-001
|
|
SUMMARY:Smoke Cancelled Occurrence
|
|
DTSTART;TZID=Europe/London:20260408T123000
|
|
DTEND;TZID=Europe/London:20260408T133000
|
|
RRULE:FREQ=WEEKLY
|
|
END:VEVENT
|
|
BEGIN:VEVENT
|
|
UID:smoke-cancelled-occurrence-001
|
|
RECURRENCE-ID;TZID=Europe/London:20260506T123000
|
|
DTSTART;TZID=Europe/London:20260506T123000
|
|
DTEND;TZID=Europe/London:20260506T133000
|
|
STATUS:CANCELLED
|
|
END:VEVENT
|
|
END:VCALENDAR
|
|
ICS
|
|
curl -fsS -u rw_user@example.test:rwpass123456 -X PUT \
|
|
-H 'Content-Type: text/calendar; charset=utf-8' \
|
|
--data-binary @"$TMP_ICS" \
|
|
"$BASE_URL/caldav/calendars/public/smoke-cancelled-occurrence.ics" >/dev/null
|
|
CALDAV_ICS="$(curl -fsS -u rw_user@example.test:rwpass123456 \
|
|
"$BASE_URL/caldav/calendars/public/smoke-cancelled-occurrence.ics")"
|
|
echo "$CALDAV_ICS" | grep -q 'RRULE:FREQ=WEEKLY'
|
|
echo "$CALDAV_ICS" | grep -q 'EXDATE;TZID=Europe/London:20260506T123000'
|
|
EVENT_ID="$(curl -fsS -H 'X-WP-User: admin' "$BASE_URL/wp-json/calendar/v1/events" | python3 -c "import json,sys; data=json.load(sys.stdin)['data']; print([e for e in data if e.get('uid')=='smoke-cancelled-occurrence-001'][0]['id'])")"
|
|
OCC_RESP="$(curl -fsS -H 'X-WP-User: admin' \
|
|
"$BASE_URL/wp-json/calendar/v1/events/${EVENT_ID}/occurrences?from=2026-05-01&months=1")"
|
|
python3 - <<'PY' "$OCC_RESP"
|
|
import json,sys
|
|
days=[x["occurrence_start"][:10] for x in json.loads(sys.argv[1])["data"]]
|
|
assert "2026-05-06" not in days
|
|
assert "2026-05-13" in days
|
|
PY
|
|
trap - EXIT
|
|
cleanup_tmp_ics
|
|
|
|
echo "[smoke] checking future-only default"
|
|
curl -fsS "$BASE_URL/calendar" | grep -q 'id="futureOnly" type="checkbox" checked'
|
|
|
|
echo "[smoke] all checks passed"
|