From 8d805ca8dc2845106d4b0514a59c5c4295b8665a Mon Sep 17 00:00:00 2001 From: Adrian Stephens Date: Thu, 2 Apr 2026 09:44:38 +0100 Subject: [PATCH] moving from svn --- .codex | 0 .gitignore | 1 + code/calendar-plugin.php | 22 + code/src/Contracts/AuthAdapterInterface.php | 16 + .../Contracts/DatabaseAdapterInterface.php | 26 + code/src/Contracts/HttpAdapterInterface.php | 14 + .../src/Contracts/OptionsAdapterInterface.php | 14 + code/src/Domain/CalDavService.php | 166 + code/src/Domain/EventService.php | 709 +++ code/src/Domain/IcsService.php | 439 ++ code/src/Domain/RecurrenceExpander.php | 193 + code/src/Domain/SettingsService.php | 91 + code/src/Domain/UserService.php | 473 ++ code/src/Infrastructure/ServiceContainer.php | 25 + .../WordPress/MigrationManager.php | 222 + .../WordPress/WordPressAuthAdapter.php | 31 + .../WordPress/WordPressDatabaseAdapter.php | 60 + .../WordPress/WordPressHttpAdapter.php | 25 + .../WordPress/WordPressOptionsAdapter.php | 25 + code/src/Plugin.php | 2744 +++++++++++ code/src/bootstrap.php | 18 + code/uninstall.php | 51 + compatibility-layer/README.md | 26 + .../__pycache__/server.cpython-313.pyc | Bin 0 -> 587 bytes .../caldav_client_compat_smoke.sh | 3 + compatibility-layer/e2e_wp_emulation.php | 346 ++ compatibility-layer/init.sh | 3 + compatibility-layer/reset.sh | 3 + compatibility-layer/run.sh | 3 + compatibility-layer/security_smoke.sh | 3 + compatibility-layer/seed.sh | 3 + compatibility-layer/server.py | 9 + compatibility-layer/smoke.sh | 3 + compatibility-layer/ui_e2e.sh | 3 + compatibility-layer/ui_e2e_wp_emulation.php | 108 + .../wp-emu-root/wp-admin/includes/upgrade.php | 2 + compatibility-layer/wp_emulation.php | 465 ++ credentials/.env | 48 + credentials/id_rsa | 38 + docs/fixture.md | 118 + fixture-tests/README.md | 9 + .../fixture_caldav_client_compat_smoke.sh | 77 + fixture-tests/fixture_security_smoke.sh | 110 + fixture-tests/fixture_smoke.sh | 330 ++ fixture/__pycache__/server.cpython-313.pyc | Bin 0 -> 221673 bytes fixture/fixture.db | Bin 0 -> 114688 bytes fixture/http_trace.log | 1164 +++++ fixture/init.sh | 4 + fixture/reset.sh | 4 + fixture/run.sh | 4 + fixture/seed.sh | 4 + fixture/server.py | 4230 +++++++++++++++++ package/calendar-plugin-0.1.0.manifest.sha256 | 19 + package/calendar-plugin-0.1.0.zip | Bin 0 -> 45752 bytes package/calendar-plugin-0.1.1.manifest.sha256 | 19 + package/calendar-plugin-0.1.1.zip | Bin 0 -> 45763 bytes package/calendar-plugin-0.1.2.manifest.sha256 | 19 + package/calendar-plugin-0.1.2.zip | Bin 0 -> 45804 bytes package/calendar-plugin-0.1.3.manifest.sha256 | 20 + package/calendar-plugin-0.1.3.zip | Bin 0 -> 46717 bytes package/calendar-plugin-0.1.4.manifest.sha256 | 20 + package/calendar-plugin-0.1.4.zip | Bin 0 -> 47633 bytes package/calendar-plugin-0.1.5.manifest.sha256 | 20 + package/calendar-plugin-0.1.5.zip | Bin 0 -> 47489 bytes package/calendar-plugin-0.1.8.manifest.sha256 | 20 + package/calendar-plugin-0.1.8.zip | Bin 0 -> 48825 bytes package/calendar-plugin-0.1.9.manifest.sha256 | 20 + package/calendar-plugin-0.1.9.zip | Bin 0 -> 49577 bytes package/deployment-record-2026-03-30.md | 56 + .../calendar-plugin/calendar-plugin.php | 22 + .../src/Contracts/AuthAdapterInterface.php | 16 + .../Contracts/DatabaseAdapterInterface.php | 26 + .../src/Contracts/HttpAdapterInterface.php | 14 + .../src/Contracts/OptionsAdapterInterface.php | 14 + .../src/Domain/CalDavService.php | 166 + .../src/Domain/EventService.php | 709 +++ .../calendar-plugin/src/Domain/IcsService.php | 439 ++ .../src/Domain/RecurrenceExpander.php | 193 + .../src/Domain/SettingsService.php | 91 + .../src/Domain/UserService.php | 473 ++ .../src/Infrastructure/ServiceContainer.php | 25 + .../WordPress/MigrationManager.php | 222 + .../WordPress/WordPressAuthAdapter.php | 31 + .../WordPress/WordPressDatabaseAdapter.php | 60 + .../WordPress/WordPressHttpAdapter.php | 25 + .../WordPress/WordPressOptionsAdapter.php | 25 + .../staging/calendar-plugin/src/Plugin.php | 2744 +++++++++++ .../staging/calendar-plugin/src/bootstrap.php | 18 + package/staging/calendar-plugin/uninstall.php | 51 + read.me | 9 + requirements/api.md | 141 + requirements/architecture.md | 30 + requirements/authentication.md | 72 + requirements/authorization.md | 61 + requirements/caldav.md | 190 + requirements/caldav_endpoints.md | 117 + requirements/caldav_users.md | 130 + requirements/data.md | 91 + requirements/data_schema.md | 138 + requirements/deployment.md | 105 + requirements/editor.md | 93 + requirements/environment.md | 131 + requirements/error_model.md | 73 + requirements/ics.md | 96 + requirements/ics_endpoint.md | 61 + requirements/life_cycle.md | 130 + requirements/local-database.md | 86 + requirements/observability.md | 78 + requirements/packaging.md | 100 + requirements/plugin_description.md | 13 + requirements/recurrence_exceptions.md | 76 + requirements/settings.md | 67 + requirements/template.md | 125 + requirements/test_strategy.md | 84 + requirements/web-ui.md | 151 + tests/api_test_cases.md | 170 + tests/calendar_entries.md | 130 + tests/e2e_test_cases.md | 232 + tests/lifecycle_test_cases.md | 54 + tests/remote_coverage_review.md | 52 + tests/run_remote_tests.sh | 288 ++ tests/smoke_tests.md | 93 + tests/ui_e2e_cases.md | 23 + tests/user_ui_caldav_1hr_spec.md | 77 + 124 files changed, 21629 insertions(+) create mode 100644 .codex create mode 100644 .gitignore create mode 100644 code/calendar-plugin.php create mode 100644 code/src/Contracts/AuthAdapterInterface.php create mode 100644 code/src/Contracts/DatabaseAdapterInterface.php create mode 100644 code/src/Contracts/HttpAdapterInterface.php create mode 100644 code/src/Contracts/OptionsAdapterInterface.php create mode 100644 code/src/Domain/CalDavService.php create mode 100644 code/src/Domain/EventService.php create mode 100644 code/src/Domain/IcsService.php create mode 100644 code/src/Domain/RecurrenceExpander.php create mode 100644 code/src/Domain/SettingsService.php create mode 100644 code/src/Domain/UserService.php create mode 100644 code/src/Infrastructure/ServiceContainer.php create mode 100644 code/src/Infrastructure/WordPress/MigrationManager.php create mode 100644 code/src/Infrastructure/WordPress/WordPressAuthAdapter.php create mode 100644 code/src/Infrastructure/WordPress/WordPressDatabaseAdapter.php create mode 100644 code/src/Infrastructure/WordPress/WordPressHttpAdapter.php create mode 100644 code/src/Infrastructure/WordPress/WordPressOptionsAdapter.php create mode 100644 code/src/Plugin.php create mode 100644 code/src/bootstrap.php create mode 100644 code/uninstall.php create mode 100644 compatibility-layer/README.md create mode 100644 compatibility-layer/__pycache__/server.cpython-313.pyc create mode 100755 compatibility-layer/caldav_client_compat_smoke.sh create mode 100755 compatibility-layer/e2e_wp_emulation.php create mode 100755 compatibility-layer/init.sh create mode 100755 compatibility-layer/reset.sh create mode 100755 compatibility-layer/run.sh create mode 100755 compatibility-layer/security_smoke.sh create mode 100755 compatibility-layer/seed.sh create mode 100755 compatibility-layer/server.py create mode 100755 compatibility-layer/smoke.sh create mode 100755 compatibility-layer/ui_e2e.sh create mode 100755 compatibility-layer/ui_e2e_wp_emulation.php create mode 100644 compatibility-layer/wp-emu-root/wp-admin/includes/upgrade.php create mode 100755 compatibility-layer/wp_emulation.php create mode 100644 credentials/.env create mode 100644 credentials/id_rsa create mode 100644 docs/fixture.md create mode 100644 fixture-tests/README.md create mode 100755 fixture-tests/fixture_caldav_client_compat_smoke.sh create mode 100755 fixture-tests/fixture_security_smoke.sh create mode 100755 fixture-tests/fixture_smoke.sh create mode 100644 fixture/__pycache__/server.cpython-313.pyc create mode 100644 fixture/fixture.db create mode 100644 fixture/http_trace.log create mode 100755 fixture/init.sh create mode 100755 fixture/reset.sh create mode 100755 fixture/run.sh create mode 100755 fixture/seed.sh create mode 100644 fixture/server.py create mode 100644 package/calendar-plugin-0.1.0.manifest.sha256 create mode 100644 package/calendar-plugin-0.1.0.zip create mode 100644 package/calendar-plugin-0.1.1.manifest.sha256 create mode 100644 package/calendar-plugin-0.1.1.zip create mode 100644 package/calendar-plugin-0.1.2.manifest.sha256 create mode 100644 package/calendar-plugin-0.1.2.zip create mode 100644 package/calendar-plugin-0.1.3.manifest.sha256 create mode 100644 package/calendar-plugin-0.1.3.zip create mode 100644 package/calendar-plugin-0.1.4.manifest.sha256 create mode 100644 package/calendar-plugin-0.1.4.zip create mode 100644 package/calendar-plugin-0.1.5.manifest.sha256 create mode 100644 package/calendar-plugin-0.1.5.zip create mode 100644 package/calendar-plugin-0.1.8.manifest.sha256 create mode 100644 package/calendar-plugin-0.1.8.zip create mode 100644 package/calendar-plugin-0.1.9.manifest.sha256 create mode 100644 package/calendar-plugin-0.1.9.zip create mode 100644 package/deployment-record-2026-03-30.md create mode 100644 package/staging/calendar-plugin/calendar-plugin.php create mode 100644 package/staging/calendar-plugin/src/Contracts/AuthAdapterInterface.php create mode 100644 package/staging/calendar-plugin/src/Contracts/DatabaseAdapterInterface.php create mode 100644 package/staging/calendar-plugin/src/Contracts/HttpAdapterInterface.php create mode 100644 package/staging/calendar-plugin/src/Contracts/OptionsAdapterInterface.php create mode 100644 package/staging/calendar-plugin/src/Domain/CalDavService.php create mode 100644 package/staging/calendar-plugin/src/Domain/EventService.php create mode 100644 package/staging/calendar-plugin/src/Domain/IcsService.php create mode 100644 package/staging/calendar-plugin/src/Domain/RecurrenceExpander.php create mode 100644 package/staging/calendar-plugin/src/Domain/SettingsService.php create mode 100644 package/staging/calendar-plugin/src/Domain/UserService.php create mode 100644 package/staging/calendar-plugin/src/Infrastructure/ServiceContainer.php create mode 100644 package/staging/calendar-plugin/src/Infrastructure/WordPress/MigrationManager.php create mode 100644 package/staging/calendar-plugin/src/Infrastructure/WordPress/WordPressAuthAdapter.php create mode 100644 package/staging/calendar-plugin/src/Infrastructure/WordPress/WordPressDatabaseAdapter.php create mode 100644 package/staging/calendar-plugin/src/Infrastructure/WordPress/WordPressHttpAdapter.php create mode 100644 package/staging/calendar-plugin/src/Infrastructure/WordPress/WordPressOptionsAdapter.php create mode 100644 package/staging/calendar-plugin/src/Plugin.php create mode 100644 package/staging/calendar-plugin/src/bootstrap.php create mode 100644 package/staging/calendar-plugin/uninstall.php create mode 100644 read.me create mode 100644 requirements/api.md create mode 100644 requirements/architecture.md create mode 100644 requirements/authentication.md create mode 100644 requirements/authorization.md create mode 100644 requirements/caldav.md create mode 100644 requirements/caldav_endpoints.md create mode 100644 requirements/caldav_users.md create mode 100644 requirements/data.md create mode 100644 requirements/data_schema.md create mode 100644 requirements/deployment.md create mode 100644 requirements/editor.md create mode 100644 requirements/environment.md create mode 100644 requirements/error_model.md create mode 100644 requirements/ics.md create mode 100644 requirements/ics_endpoint.md create mode 100644 requirements/life_cycle.md create mode 100644 requirements/local-database.md create mode 100644 requirements/observability.md create mode 100644 requirements/packaging.md create mode 100644 requirements/plugin_description.md create mode 100644 requirements/recurrence_exceptions.md create mode 100644 requirements/settings.md create mode 100644 requirements/template.md create mode 100644 requirements/test_strategy.md create mode 100644 requirements/web-ui.md create mode 100644 tests/api_test_cases.md create mode 100644 tests/calendar_entries.md create mode 100644 tests/e2e_test_cases.md create mode 100644 tests/lifecycle_test_cases.md create mode 100644 tests/remote_coverage_review.md create mode 100755 tests/run_remote_tests.sh create mode 100644 tests/smoke_tests.md create mode 100644 tests/ui_e2e_cases.md create mode 100644 tests/user_ui_caldav_1hr_spec.md diff --git a/.codex b/.codex new file mode 100644 index 0000000..e69de29 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..90ec22b --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.svn diff --git a/code/calendar-plugin.php b/code/calendar-plugin.php new file mode 100644 index 0000000..8abffcf --- /dev/null +++ b/code/calendar-plugin.php @@ -0,0 +1,22 @@ +events->listEvents() as $event) { + $resource = $this->resourceForEvent($event); + $items[] = [ + 'resource' => $resource, + 'href' => '/caldav/calendars/public/' . $resource, + 'uid' => (string) ($event['uid'] ?? ''), + 'etag' => (string) ($event['etag'] ?? ''), + 'updated_at' => (string) ($event['updated_at'] ?? ''), + 'sync_version' => (int) ($event['sync_version'] ?? 1), + ]; + } + return $items; + } + + public function getObject(string $resource): ?array + { + $event = $this->findEventByResource($resource); + if (!$event) { + return null; + } + $ics = $this->ics->buildCalendar( + [$event], + fn(int $eventId): array => $this->events->getDeletedOccurrenceKeys($eventId) + ); + + return [ + 'resource' => $resource, + 'etag' => (string) ($event['etag'] ?? ''), + 'event' => $event, + 'ics' => $ics, + ]; + } + + public function putObject(string $resource, string $icsPayload, ?string $ifMatch = null, ?string $ifNoneMatch = null, ?int $userId = null): array + { + $payload = $this->ics->parseEventFromIcs($icsPayload); + if ($payload === null) { + return ['error' => ['code' => 'invalid_ics', 'message' => 'invalid iCalendar payload', 'status' => 422]]; + } + + $existing = $this->events->getEventByResource($resource); + if (!$existing) { + $existing = $this->findEventByResource($resource); + } + if ($ifNoneMatch === '*' && $existing) { + return ['error' => ['code' => 'precondition_failed', 'message' => 'resource already exists', 'status' => 412]]; + } + if ($ifMatch !== null) { + if (!$existing) { + return ['error' => ['code' => 'precondition_failed', 'message' => 'resource does not exist', 'status' => 412]]; + } + if ((string) ($existing['etag'] ?? '') !== trim($ifMatch)) { + return ['error' => ['code' => 'precondition_failed', 'message' => 'etag mismatch', 'status' => 412]]; + } + } + + $payload['caldav_resource'] = $resource; + if ($userId !== null) { + $payload['last_modified_by_user_id'] = $userId; + } + + $deleted = (array) ($payload['deleted_occurrence_keys'] ?? []); + unset($payload['deleted_occurrence_keys']); + + if ($existing) { + $nextSyncVersion = ((int) ($existing['sync_version'] ?? 1)) + 1; + $payload['sync_version'] = $nextSyncVersion; + $payload['etag'] = $this->etagFor((string) ($payload['uid'] ?? $existing['uid'] ?? ''), $nextSyncVersion); + $event = $this->events->updateEvent((int) $existing['id'], $payload); + if (!$event) { + return ['error' => ['code' => 'update_failed', 'message' => 'failed to update object', 'status' => 500]]; + } + $this->events->syncDeletedOccurrenceKeys((int) $event['id'], $deleted, true); + $event = $this->events->getEvent((int) $event['id']) ?? $event; + + return ['status' => 204, 'created' => false, 'event' => $event]; + } + + $payload['sync_version'] = 1; + $payload['etag'] = $this->etagFor((string) ($payload['uid'] ?? ''), 1); + $event = $this->events->createEvent($payload); + $this->events->syncDeletedOccurrenceKeys((int) $event['id'], $deleted, true); + $event = $this->events->getEvent((int) $event['id']) ?? $event; + + return ['status' => 201, 'created' => true, 'event' => $event]; + } + + public function deleteObject(string $resource): array + { + $existing = $this->findEventByResource($resource); + if (!$existing) { + return ['error' => ['code' => 'not_found', 'message' => 'resource not found', 'status' => 404]]; + } + + $ok = $this->events->deleteEvent((int) $existing['id']); + if (!$ok) { + return ['error' => ['code' => 'delete_failed', 'message' => 'failed to delete resource', 'status' => 500]]; + } + + return ['status' => 204, 'deleted' => true]; + } + + public function multiget(array $resources): array + { + $out = []; + foreach ($resources as $resource) { + $resource = basename((string) $resource); + if ($resource === '') { + continue; + } + $object = $this->getObject($resource); + if ($object === null) { + $out[] = ['resource' => $resource, 'status' => 404]; + continue; + } + $out[] = [ + 'resource' => $resource, + 'status' => 200, + 'etag' => $object['etag'], + 'ics' => $object['ics'], + ]; + } + return $out; + } + + public function resourceForEvent(array $event): string + { + $resource = trim((string) ($event['caldav_resource'] ?? '')); + if ($resource !== '') { + return $resource; + } + return (string) ($event['uid'] ?? 'event-' . (string) ($event['id'] ?? 0)) . '.ics'; + } + + private function etagFor(string $uid, int $version): string + { + return '"' . substr(sha1($uid . ':' . $version . ':' . gmdate('c')), 0, 16) . '"'; + } + + private function findEventByResource(string $resource): ?array + { + foreach ($this->events->listEvents() as $event) { + if ($this->resourceForEvent($event) === $resource) { + return $event; + } + } + return null; + } +} diff --git a/code/src/Domain/EventService.php b/code/src/Domain/EventService.php new file mode 100644 index 0000000..7e99a9b --- /dev/null +++ b/code/src/Domain/EventService.php @@ -0,0 +1,709 @@ +getPrefix(); + $stem = trim($tableStem, '_'); + $this->eventsTable = $prefix . $stem . '_events'; + $this->exceptionsTable = $prefix . $stem . '_recurrence_exceptions'; + } + + public function listEvents(): array + { + $rows = $this->db->getResults("SELECT * FROM {$this->eventsTable} ORDER BY id ASC"); + return array_map([$this, 'normalizeRow'], $rows); + } + + public function getEvent(int $id): ?array + { + $sql = $this->db->prepare("SELECT * FROM {$this->eventsTable} WHERE id = %d", $id); + $row = $this->db->getRow($sql); + return $row ? $this->normalizeRow($row) : null; + } + + public function createEvent(array $payload): array + { + $now = gmdate('c'); + $uid = (string) ($payload['uid'] ?? bin2hex(random_bytes(10)) . '@calendar-plugin'); + $resource = $this->resourceFromUid($uid); + $title = trim((string) ($payload['title'] ?? 'Untitled')); + $startRaw = (string) ($payload['start_datetime'] ?? ''); + $endRaw = (string) ($payload['end_datetime'] ?? ''); + $start = $this->toLondonDateTimeString($startRaw); + $end = $this->toLondonDateTimeString($endRaw); + if ($start === '' || $end === '') { + throw new \InvalidArgumentException('start_datetime and end_datetime are required'); + } + if (new DateTimeImmutable($end) < new DateTimeImmutable($start)) { + throw new \InvalidArgumentException('end_datetime must be at or after start_datetime'); + } + $repeatType = (string) ($payload['repeat_type'] ?? 'none'); + $repeatInterval = max(1, (int) ($payload['repeat_interval'] ?? 1)); + $repeatNthMode = (string) ($payload['repeat_nth_mode'] ?? ''); + $repeatNthDay = array_key_exists('repeat_nth_day', $payload) && $payload['repeat_nth_day'] !== null && $payload['repeat_nth_day'] !== '' + ? (int) $payload['repeat_nth_day'] + : null; + $repeatNthPos = array_key_exists('repeat_nth_pos', $payload) && $payload['repeat_nth_pos'] !== null && $payload['repeat_nth_pos'] !== '' + ? (int) $payload['repeat_nth_pos'] + : null; + $repeatNthWeekday = array_key_exists('repeat_nth_weekday', $payload) && $payload['repeat_nth_weekday'] !== null && $payload['repeat_nth_weekday'] !== '' + ? (int) $payload['repeat_nth_weekday'] + : null; + [$start, $end] = $this->normalizeMonthlyAnchor( + $start, + $end, + $repeatType, + $repeatInterval, + $repeatNthMode, + $repeatNthDay, + $repeatNthPos, + $repeatNthWeekday + ); + + $data = [ + 'uid' => $uid, + 'title' => $title, + 'description' => (string) ($payload['description'] ?? ''), + 'location' => (string) ($payload['location'] ?? ''), + 'category' => (string) ($payload['category'] ?? ''), + 'all_day_event' => !empty($payload['all_day_event']) ? 1 : 0, + 'start_datetime' => $start, + 'end_datetime' => $end, + 'repeat_type' => $repeatType, + 'repeat_interval' => $repeatInterval, + 'repeat_nth_mode' => $repeatNthMode, + 'repeat_nth_day' => $repeatNthDay, + 'repeat_nth_pos' => $repeatNthPos, + 'repeat_nth_weekday' => $repeatNthWeekday, + 'repeat_range_mode' => $this->canonicalRangeMode((string) ($payload['repeat_range_mode'] ?? 'none')), + 'repeat_count' => isset($payload['repeat_count']) ? (int) $payload['repeat_count'] : null, + 'repeat_until' => !empty($payload['repeat_until']) ? (string) $payload['repeat_until'] : null, + 'timezone' => (string) ($payload['timezone'] ?? 'Europe/London'), + 'caldav_resource' => !empty($payload['caldav_resource']) ? (string) $payload['caldav_resource'] : $resource, + 'etag' => (string) ($payload['etag'] ?? $this->makeEtag($uid, 1, $now)), + 'sync_version' => (int) ($payload['sync_version'] ?? 1), + 'last_modified_by_user_id' => isset($payload['last_modified_by_user_id']) ? (int) $payload['last_modified_by_user_id'] : null, + 'created_at' => $now, + 'updated_at' => $now, + ]; + + $inserted = $this->db->insert($this->eventsTable, $data); + if ($inserted === false) { + throw new \RuntimeException('failed to create event'); + } + return (array) $this->getEvent($this->db->insertId()); + } + + public function updateEvent(int $id, array $payload): ?array + { + $existing = $this->getEvent($id); + if (!$existing) { + return null; + } + $now = gmdate('c'); + $currentResource = trim((string) ($existing['caldav_resource'] ?? '')); + $fallbackResource = $this->resourceFromUid((string) ($existing['uid'] ?? '')); + $start = array_key_exists('start_datetime', $payload) + ? $this->toLondonDateTimeString((string) $payload['start_datetime']) + : (string) $existing['start_datetime']; + $end = array_key_exists('end_datetime', $payload) + ? $this->toLondonDateTimeString((string) $payload['end_datetime']) + : (string) $existing['end_datetime']; + if ($start !== '' && $end !== '' && new DateTimeImmutable($end) < new DateTimeImmutable($start)) { + throw new \InvalidArgumentException('end_datetime must be at or after start_datetime'); + } + $repeatType = (string) ($payload['repeat_type'] ?? $existing['repeat_type']); + $repeatInterval = max(1, (int) ($payload['repeat_interval'] ?? $existing['repeat_interval'])); + $repeatNthMode = (string) ($payload['repeat_nth_mode'] ?? ($existing['repeat_nth_mode'] ?? '')); + $repeatNthDay = array_key_exists('repeat_nth_day', $payload) + ? ($payload['repeat_nth_day'] === null || $payload['repeat_nth_day'] === '' ? null : (int) $payload['repeat_nth_day']) + : ($existing['repeat_nth_day'] ?? null); + $repeatNthPos = array_key_exists('repeat_nth_pos', $payload) + ? ($payload['repeat_nth_pos'] === null || $payload['repeat_nth_pos'] === '' ? null : (int) $payload['repeat_nth_pos']) + : ($existing['repeat_nth_pos'] ?? null); + $repeatNthWeekday = array_key_exists('repeat_nth_weekday', $payload) + ? ($payload['repeat_nth_weekday'] === null || $payload['repeat_nth_weekday'] === '' ? null : (int) $payload['repeat_nth_weekday']) + : ($existing['repeat_nth_weekday'] ?? null); + [$start, $end] = $this->normalizeMonthlyAnchor( + $start, + $end, + $repeatType, + $repeatInterval, + $repeatNthMode, + $repeatNthDay, + $repeatNthPos, + $repeatNthWeekday + ); + $data = [ + 'title' => trim((string) ($payload['title'] ?? $existing['title'])), + 'description' => (string) ($payload['description'] ?? $existing['description']), + 'location' => (string) ($payload['location'] ?? $existing['location']), + 'category' => (string) ($payload['category'] ?? $existing['category']), + 'all_day_event' => array_key_exists('all_day_event', $payload) + ? (!empty($payload['all_day_event']) ? 1 : 0) + : ((bool) $existing['all_day_event'] ? 1 : 0), + 'start_datetime' => $start, + 'end_datetime' => $end, + 'repeat_type' => $repeatType, + 'repeat_interval' => $repeatInterval, + 'repeat_nth_mode' => $repeatNthMode, + 'repeat_nth_day' => $repeatNthDay, + 'repeat_nth_pos' => $repeatNthPos, + 'repeat_nth_weekday' => $repeatNthWeekday, + 'repeat_range_mode' => $this->canonicalRangeMode((string) ($payload['repeat_range_mode'] ?? $existing['repeat_range_mode'])), + 'repeat_count' => array_key_exists('repeat_count', $payload) ? (is_null($payload['repeat_count']) ? null : (int) $payload['repeat_count']) : $existing['repeat_count'], + 'repeat_until' => array_key_exists('repeat_until', $payload) ? (empty($payload['repeat_until']) ? null : (string) $payload['repeat_until']) : $existing['repeat_until'], + 'timezone' => (string) ($payload['timezone'] ?? $existing['timezone']), + 'caldav_resource' => !empty($payload['caldav_resource']) + ? (string) $payload['caldav_resource'] + : ($currentResource !== '' ? $currentResource : $fallbackResource), + 'etag' => (string) ($payload['etag'] ?? $existing['etag'] ?? $this->makeEtag((string) $existing['uid'], (int) ($existing['sync_version'] ?? 1), $now)), + 'sync_version' => (int) ($payload['sync_version'] ?? (($existing['sync_version'] ?? 1) + 1)), + 'last_modified_by_user_id' => array_key_exists('last_modified_by_user_id', $payload) + ? (is_null($payload['last_modified_by_user_id']) ? null : (int) $payload['last_modified_by_user_id']) + : ($existing['last_modified_by_user_id'] ?? null), + 'updated_at' => $now, + ]; + + $this->db->update($this->eventsTable, $data, ['id' => $id]); + return $this->getEvent($id); + } + + public function deleteEvent(int $id): bool + { + $this->db->delete($this->exceptionsTable, ['event_id' => $id]); + $deleted = $this->db->delete($this->eventsTable, ['id' => $id]); + return $deleted !== false; + } + + public function deleteOccurrence(int $eventId, string $occurrenceKey): bool + { + $event = $this->getEvent($eventId); + if (!$event) { + return false; + } + $canonical = $this->canonicalOccurrenceKey($occurrenceKey); + if ($canonical === null) { + return false; + } + if (in_array($canonical, $this->deletedKeysForEvent($eventId), true)) { + return true; + } + $now = gmdate('c'); + $inserted = $this->db->insert( + $this->exceptionsTable, + [ + 'event_id' => $eventId, + 'occurrence_key' => $canonical, + 'exception_type' => 'deleted_occurrence', + 'created_at' => $now, + 'updated_at' => $now, + ] + ); + return $inserted !== false; + } + + public function listEventOccurrences(int $eventId, string $fromDate, int $months = 3): ?array + { + $event = $this->getEvent($eventId); + if (!$event) { + return null; + } + + $tz = new DateTimeZone('Europe/London'); + $start = $this->safeDate($fromDate, $tz)->setTime(0, 0, 0); + $months = max(1, min($months, 24)); + $end = $start->modify('+' . $months . ' month'); + $deleted = $this->deletedKeysForEvent($eventId); + + $items = RecurrenceExpander::expand($event, $start, $end, $deleted); + usort( + $items, + static fn(array $a, array $b): int => strcmp((string) $a['occurrence_start'], (string) $b['occurrence_start']) + ); + return $items; + } + + public function previewOccurrences(array $payload, string $fromDate, int $months = 3): array + { + $startRaw = (string) ($payload['start_datetime'] ?? ''); + $endRaw = (string) ($payload['end_datetime'] ?? ''); + $start = $this->toLondonDateTimeString($startRaw); + $end = $this->toLondonDateTimeString($endRaw); + if ($start === '' || $end === '') { + throw new \InvalidArgumentException('start_datetime and end_datetime are required'); + } + if (new DateTimeImmutable($end) < new DateTimeImmutable($start)) { + throw new \InvalidArgumentException('end_datetime must be at or after start_datetime'); + } + + $repeatType = (string) ($payload['repeat_type'] ?? 'none'); + if ($repeatType === 'none') { + return []; + } + $repeatInterval = max(1, (int) ($payload['repeat_interval'] ?? 1)); + $repeatNthMode = (string) ($payload['repeat_nth_mode'] ?? ''); + $repeatNthDay = array_key_exists('repeat_nth_day', $payload) && $payload['repeat_nth_day'] !== null && $payload['repeat_nth_day'] !== '' + ? (int) $payload['repeat_nth_day'] + : null; + $repeatNthPos = array_key_exists('repeat_nth_pos', $payload) && $payload['repeat_nth_pos'] !== null && $payload['repeat_nth_pos'] !== '' + ? (int) $payload['repeat_nth_pos'] + : null; + $repeatNthWeekday = array_key_exists('repeat_nth_weekday', $payload) && $payload['repeat_nth_weekday'] !== null && $payload['repeat_nth_weekday'] !== '' + ? (int) $payload['repeat_nth_weekday'] + : null; + [$start, $end] = $this->normalizeMonthlyAnchor( + $start, + $end, + $repeatType, + $repeatInterval, + $repeatNthMode, + $repeatNthDay, + $repeatNthPos, + $repeatNthWeekday + ); + $event = [ + 'id' => 0, + 'uid' => 'preview@calendar-plugin', + 'title' => (string) ($payload['title'] ?? ''), + 'description' => (string) ($payload['description'] ?? ''), + 'location' => (string) ($payload['location'] ?? ''), + 'category' => (string) ($payload['category'] ?? ''), + 'all_day_event' => !empty($payload['all_day_event']), + 'start_datetime' => $start, + 'end_datetime' => $end, + 'repeat_type' => $repeatType, + 'repeat_interval' => $repeatInterval, + 'repeat_nth_mode' => $repeatNthMode, + 'repeat_nth_day' => $repeatNthDay, + 'repeat_nth_pos' => $repeatNthPos, + 'repeat_nth_weekday' => $repeatNthWeekday, + 'repeat_range_mode' => $this->canonicalRangeMode((string) ($payload['repeat_range_mode'] ?? 'none')), + 'repeat_count' => isset($payload['repeat_count']) ? (int) $payload['repeat_count'] : null, + 'repeat_until' => !empty($payload['repeat_until']) ? (string) $payload['repeat_until'] : null, + 'timezone' => 'Europe/London', + ]; + + $tz = new DateTimeZone('Europe/London'); + $startWindow = $this->safeDate($fromDate, $tz)->setTime(0, 0, 0); + $months = max(1, min($months, 24)); + $endWindow = $startWindow->modify('+' . $months . ' month'); + $deleted = []; + foreach ((array) ($payload['deleted_occurrence_keys'] ?? []) as $key) { + $canonical = $this->canonicalOccurrenceKey((string) $key); + if ($canonical !== null) { + $deleted[] = $canonical; + } + } + $items = RecurrenceExpander::expand($event, $startWindow, $endWindow, $deleted); + usort( + $items, + static fn(array $a, array $b): int => strcmp((string) $a['occurrence_start'], (string) $b['occurrence_start']) + ); + return $items; + } + + public function getEventByResource(string $resource): ?array + { + $sql = $this->db->prepare("SELECT * FROM {$this->eventsTable} WHERE caldav_resource = %s", $resource); + $row = $this->db->getRow($sql); + return $row ? $this->normalizeRow($row) : null; + } + + public function getDeletedOccurrenceKeys(int $eventId): array + { + return $this->deletedKeysForEvent($eventId); + } + + public function syncDeletedOccurrenceKeys(int $eventId, array $keys, bool $replace = true): void + { + if ($replace) { + $this->db->delete($this->exceptionsTable, ['event_id' => $eventId, 'exception_type' => 'deleted_occurrence']); + } + $now = gmdate('c'); + foreach ($keys as $key) { + $canonical = $this->canonicalOccurrenceKey((string) $key); + if ($canonical === null) { + continue; + } + $this->db->insert( + $this->exceptionsTable, + [ + 'event_id' => $eventId, + 'occurrence_key' => $canonical, + 'exception_type' => 'deleted_occurrence', + 'created_at' => $now, + 'updated_at' => $now, + ] + ); + } + } + + public function listPublicOccurrences(string $view, string $dateAnchor, bool $futureOnly = false): array + { + $tz = new DateTimeZone('Europe/London'); + $anchor = $this->safeDate($dateAnchor, $tz); + if (strtolower($view) === 'list') { + $windowStart = $anchor->setTime(0, 0, 0); + if ($futureOnly) { + $today = new DateTimeImmutable('today', $tz); + if ($today > $windowStart) { + $windowStart = $today; + } + } + $windowEnd = $windowStart->modify('+18 months'); + } else { + [$windowStart, $windowEnd] = $this->windowForView($view, $anchor); + } + $events = $this->listEvents(); + + $out = []; + foreach ($events as $event) { + $deleted = $this->deletedKeysForEvent((int) $event['id']); + $items = RecurrenceExpander::expand($event, $windowStart, $windowEnd, $deleted); + array_push($out, ...$items); + } + + usort( + $out, + static fn(array $a, array $b): int => strcmp((string) $a['occurrence_start'], (string) $b['occurrence_start']) + ); + + return $out; + } + + public function listSidebarUpcoming(int $days = 14): array + { + $tz = new DateTimeZone('Europe/London'); + $start = new DateTimeImmutable('today', $tz); + $end = $start->modify('+' . max(1, $days) . ' days'); + + $events = $this->listEvents(); + $out = []; + foreach ($events as $event) { + $deleted = $this->deletedKeysForEvent((int) $event['id']); + $items = RecurrenceExpander::expand($event, $start, $end, $deleted); + array_push($out, ...$items); + } + + usort( + $out, + static fn(array $a, array $b): int => strcmp((string) $a['occurrence_start'], (string) $b['occurrence_start']) + ); + + return $out; + } + + public function deleteAllEventsData(): int + { + $events = $this->listEvents(); + $count = count($events); + $this->db->query("DELETE FROM {$this->exceptionsTable}"); + $this->db->query("DELETE FROM {$this->eventsTable}"); + return $count; + } + + public function seedDefaultEvents(): int + { + $seed = [ + [ + 'uid' => 'seed-ce-001@calendar-plugin', + 'title' => 'Board Meeting', + 'description' => 'Quarterly board review.', + 'location' => 'Room A', + 'category' => 'Governance', + 'start_datetime' => '2026-04-01T10:00:00+01:00', + 'end_datetime' => '2026-04-01T11:30:00+01:00', + 'repeat_type' => 'none', + ], + [ + 'uid' => 'seed-ce-002@calendar-plugin', + 'title' => 'Office Closed', + 'description' => 'Public holiday closure.', + 'location' => 'HQ', + 'category' => 'Operations', + 'all_day_event' => true, + 'start_datetime' => '2026-05-04T00:00:00+01:00', + 'end_datetime' => '2026-05-05T00:00:00+01:00', + 'repeat_type' => 'none', + ], + [ + 'uid' => 'seed-ce-003@calendar-plugin', + 'title' => 'Daily Standup', + 'description' => '15 minute sync.', + 'location' => 'Online', + 'category' => 'Team', + 'start_datetime' => '2026-04-06T09:00:00+01:00', + 'end_datetime' => '2026-04-06T09:15:00+01:00', + 'repeat_type' => 'daily', + 'repeat_interval' => 1, + 'repeat_range_mode' => 'until', + 'repeat_until' => '2026-04-15', + ], + [ + 'uid' => 'seed-ce-004@calendar-plugin', + 'title' => 'Community Lunch', + 'description' => 'Weekly community lunch.', + 'location' => 'Cafeteria', + 'category' => 'Community', + 'start_datetime' => '2026-04-08T12:30:00+01:00', + 'end_datetime' => '2026-04-08T13:30:00+01:00', + 'repeat_type' => 'weekly', + 'repeat_interval' => 1, + 'repeat_range_mode' => 'until', + 'repeat_until' => '2026-05-06', + ], + [ + 'uid' => 'seed-ce-005@calendar-plugin', + 'title' => 'Finance Close', + 'description' => 'Month-end close process.', + 'location' => 'Finance Office', + 'category' => 'Finance', + 'start_datetime' => '2026-03-31T17:00:00+01:00', + 'end_datetime' => '2026-03-31T18:00:00+01:00', + 'repeat_type' => 'monthly', + 'repeat_interval' => 1, + 'repeat_nth_mode' => 'day_of_month', + 'repeat_nth_day' => 30, + 'repeat_range_mode' => 'until', + 'repeat_until' => '2026-06-30', + ], + ]; + + foreach ($seed as $event) { + $this->createEvent($event); + } + return count($seed); + } + + private function normalizeRow(object $row): array + { + return [ + 'id' => (int) $row->id, + 'uid' => (string) $row->uid, + 'title' => (string) $row->title, + 'description' => (string) $row->description, + 'location' => (string) $row->location, + 'category' => (string) $row->category, + 'all_day_event' => (bool) $row->all_day_event, + 'start_datetime' => (string) $row->start_datetime, + 'end_datetime' => (string) $row->end_datetime, + 'repeat_type' => (string) $row->repeat_type, + 'repeat_interval' => (int) $row->repeat_interval, + 'repeat_nth_mode' => property_exists($row, 'repeat_nth_mode') ? (string) ($row->repeat_nth_mode ?? '') : '', + 'repeat_nth_day' => property_exists($row, 'repeat_nth_day') && $row->repeat_nth_day !== null ? (int) $row->repeat_nth_day : null, + 'repeat_nth_pos' => property_exists($row, 'repeat_nth_pos') && $row->repeat_nth_pos !== null ? (int) $row->repeat_nth_pos : null, + 'repeat_nth_weekday' => property_exists($row, 'repeat_nth_weekday') && $row->repeat_nth_weekday !== null ? (int) $row->repeat_nth_weekday : null, + 'repeat_range_mode' => (string) $row->repeat_range_mode, + 'repeat_count' => is_null($row->repeat_count) ? null : (int) $row->repeat_count, + 'repeat_until' => $row->repeat_until === null ? null : (string) $row->repeat_until, + 'timezone' => (string) $row->timezone, + 'caldav_resource' => property_exists($row, 'caldav_resource') ? (string) ($row->caldav_resource ?? '') : '', + 'etag' => property_exists($row, 'etag') ? (string) ($row->etag ?? '') : '', + 'sync_version' => property_exists($row, 'sync_version') ? (int) ($row->sync_version ?? 1) : 1, + 'last_modified_by_user_id' => property_exists($row, 'last_modified_by_user_id') && $row->last_modified_by_user_id !== null + ? (int) $row->last_modified_by_user_id + : null, + 'created_at' => (string) $row->created_at, + 'updated_at' => (string) $row->updated_at, + ]; + } + + private function deletedKeysForEvent(int $eventId): array + { + $sql = $this->db->prepare( + "SELECT occurrence_key FROM {$this->exceptionsTable} WHERE event_id = %d AND exception_type = 'deleted_occurrence'", + $eventId + ); + $rows = $this->db->getResults($sql); + return array_map(static fn(object $r): string => (string) $r->occurrence_key, $rows); + } + + private function safeDate(string $dateAnchor, DateTimeZone $tz): DateTimeImmutable + { + if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $dateAnchor)) { + return new DateTimeImmutable($dateAnchor . 'T00:00:00', $tz); + } + return new DateTimeImmutable('today', $tz); + } + + private function windowForView(string $view, DateTimeImmutable $anchor): array + { + $view = strtolower($view); + if ($view === 'day') { + $start = $anchor->setTime(0, 0, 0); + return [$start, $start->modify('+1 day')]; + } + if ($view === 'week') { + $weekday = (int) $anchor->format('w'); + $start = $anchor->modify('-' . $weekday . ' day')->setTime(0, 0, 0); + return [$start, $start->modify('+7 days')]; + } + if ($view === 'year') { + $start = $anchor->setDate((int) $anchor->format('Y'), 1, 1)->setTime(0, 0, 0); + return [$start, $start->modify('+1 year')]; + } + $monthStart = $anchor->setDate((int) $anchor->format('Y'), (int) $anchor->format('m'), 1)->setTime(0, 0, 0); + $startWeekday = (int) $monthStart->format('w'); + $gridStart = $monthStart->modify('-' . $startWeekday . ' day'); + $gridEnd = $gridStart->modify('+42 days'); + return [$gridStart, $gridEnd]; + } + + private function canonicalOccurrenceKey(string $value): ?string + { + try { + if (!str_contains($value, 'T') && preg_match('/^\d{4}-\d{2}-\d{2}$/', $value)) { + $dt = new DateTimeImmutable($value . 'T00:00:00', new DateTimeZone('Europe/London')); + return $dt->format('c'); + } + $dt = new DateTimeImmutable($value, new DateTimeZone('Europe/London')); + return $dt->format('c'); + } catch (\Throwable) { + return null; + } + } + + private function makeEtag(string $uid, int $syncVersion, string $stamp): string + { + return '"' . substr(sha1($uid . ':' . $syncVersion . ':' . $stamp), 0, 16) . '"'; + } + + private function resourceFromUid(string $uid): string + { + $uid = trim($uid); + if ($uid === '') { + $uid = bin2hex(random_bytes(10)) . '@calendar-plugin'; + } + return $uid . '.ics'; + } + + private function toLondonDateTimeString(string $value): string + { + $value = trim($value); + if ($value === '') { + return ''; + } + try { + $dt = new DateTimeImmutable($value, new DateTimeZone('Europe/London')); + return $dt->setTimezone(new DateTimeZone('Europe/London'))->format('c'); + } catch (\Throwable) { + throw new \InvalidArgumentException('invalid datetime value'); + } + } + + private function canonicalRangeMode(string $value): string + { + $v = strtolower(trim($value)); + if ($v === 'no_end' || $v === '') { + return 'none'; + } + return in_array($v, ['none', 'count', 'until'], true) ? $v : 'none'; + } + + private function normalizeMonthlyAnchor( + string $startIso, + string $endIso, + string $repeatType, + int $repeatInterval, + string $repeatNthMode, + ?int $repeatNthDay, + ?int $repeatNthPos, + ?int $repeatNthWeekday + ): array { + if ($repeatType !== 'monthly') { + return [$startIso, $endIso]; + } + try { + $tz = new DateTimeZone('Europe/London'); + $start = new DateTimeImmutable($startIso, $tz); + $end = new DateTimeImmutable($endIso, $tz); + $duration = $start->diff($end); + $targetDay = (int) $start->format('j'); + if ($repeatNthMode === 'day_of_month' && $repeatNthDay !== null) { + $daysInMonth = (int) $start->format('t'); + $targetDay = max(1, min($repeatNthDay, $daysInMonth)); + } elseif ($repeatNthMode === 'weekday_of_month' && $repeatNthPos !== null && $repeatNthWeekday !== null) { + $nthDay = $this->nthWeekdayOfMonth((int) $start->format('Y'), (int) $start->format('n'), $repeatNthWeekday, $repeatNthPos); + if ($nthDay === null) { + $year = (int) $start->format('Y'); + $month = (int) $start->format('n'); + $step = max(1, $repeatInterval); + for ($i = 0; $i < 120; $i++) { + [$year, $month] = $this->addMonths($year, $month, $step); + $nthDay = $this->nthWeekdayOfMonth($year, $month, $repeatNthWeekday, $repeatNthPos); + if ($nthDay !== null) { + $start = $start->setDate($year, $month, $nthDay); + $targetDay = $nthDay; + break; + } + } + } else { + $targetDay = $nthDay; + } + } + $anchoredStart = $start->setDate((int) $start->format('Y'), (int) $start->format('n'), $targetDay); + $anchoredEnd = $anchoredStart->add($duration); + return [$anchoredStart->format('c'), $anchoredEnd->format('c')]; + } catch (\Throwable) { + return [$startIso, $endIso]; + } + } + + private function nthWeekdayOfMonth(int $year, int $month, int $weekday, int $pos): ?int + { + $weekday = max(0, min(6, $weekday)); + $tz = new DateTimeZone('Europe/London'); + if ($pos === -1) { + $last = new DateTimeImmutable(sprintf('%04d-%02d-01T00:00:00', $year, $month), $tz); + $last = $last->modify('last day of this month'); + for ($day = (int) $last->format('j'); $day >= 1; $day--) { + $d = $last->setDate($year, $month, $day); + if ((int) $d->format('w') === $weekday) { + return $day; + } + } + return null; + } + + $first = new DateTimeImmutable(sprintf('%04d-%02d-01T00:00:00', $year, $month), $tz); + $daysInMonth = (int) $first->format('t'); + $seen = 0; + for ($day = 1; $day <= $daysInMonth; $day++) { + $d = $first->setDate($year, $month, $day); + if ((int) $d->format('w') !== $weekday) { + continue; + } + $seen++; + if ($seen === $pos) { + return $day; + } + } + return null; + } + + private function addMonths(int $year, int $month, int $delta): array + { + $index = ($year * 12) + ($month - 1) + $delta; + $newYear = (int) floor($index / 12); + $newMonth = ($index % 12) + 1; + if ($newMonth <= 0) { + $newMonth += 12; + $newYear -= 1; + } + return [$newYear, $newMonth]; + } +} diff --git a/code/src/Domain/IcsService.php b/code/src/Domain/IcsService.php new file mode 100644 index 0000000..c974626 --- /dev/null +++ b/code/src/Domain/IcsService.php @@ -0,0 +1,439 @@ +escapeText($calendarName), + 'X-WR-TIMEZONE:Europe/London', + ]; + + foreach ($events as $event) { + $lines = array_merge($lines, $this->eventToLines($event, (array) $deletedKeysProvider((int) ($event['id'] ?? 0)))); + } + + $lines[] = 'END:VCALENDAR'; + + return implode("\r\n", $this->foldLines($lines)) . "\r\n"; + } + + public function parseEventFromIcs(string $ics): ?array + { + $props = $this->extractVeventProperties($ics); + if ($props === null) { + return null; + } + + $uid = (string) ($props['UID'][0] ?? ''); + $summary = (string) ($props['SUMMARY'][0] ?? 'Untitled'); + $description = (string) ($props['DESCRIPTION'][0] ?? ''); + $location = (string) ($props['LOCATION'][0] ?? ''); + $category = (string) ($props['CATEGORIES'][0] ?? ''); + $dtstartRaw = (string) ($props['DTSTART'][0] ?? ''); + $dtendRaw = (string) ($props['DTEND'][0] ?? ''); + if ($dtstartRaw === '' || $dtendRaw === '') { + return null; + } + + $allDay = str_contains((string) ($props['_DTSTART_PARAMS'][0] ?? ''), 'VALUE=DATE'); + $start = $this->parseIcsDateTime($dtstartRaw, $allDay); + $end = $this->parseIcsDateTime($dtendRaw, $allDay); + if ($start === null || $end === null) { + return null; + } + + $payload = [ + 'uid' => $uid !== '' ? $uid : bin2hex(random_bytes(10)) . '@calendar-plugin', + 'title' => $summary, + 'description' => $description, + 'location' => $location, + 'category' => $category, + 'all_day_event' => $allDay, + 'start_datetime' => $start, + 'end_datetime' => $end, + 'repeat_type' => 'none', + 'repeat_interval' => 1, + 'repeat_nth_mode' => '', + 'repeat_nth_day' => null, + 'repeat_nth_pos' => null, + 'repeat_nth_weekday' => null, + 'repeat_range_mode' => 'none', + 'repeat_count' => null, + 'repeat_until' => null, + 'timezone' => 'Europe/London', + ]; + + $rrule = (string) ($props['RRULE'][0] ?? ''); + if ($rrule !== '') { + $payload = array_merge($payload, $this->parseRrule($rrule)); + } + + $exdates = []; + foreach (($props['EXDATE'] ?? []) as $exdateRaw) { + $chunks = array_filter(array_map('trim', explode(',', (string) $exdateRaw))); + foreach ($chunks as $chunk) { + $asDate = $this->parseIcsDateTime($chunk, false); + if ($asDate !== null) { + $exdates[] = $asDate; + } + } + } + $payload['deleted_occurrence_keys'] = $exdates; + + return $payload; + } + + private function eventToLines(array $event, array $deletedKeys): array + { + $uid = (string) ($event['uid'] ?? ''); + $uid = $uid !== '' ? $uid : ('event-' . (string) ($event['id'] ?? 0) . '@calendar-plugin'); + + $start = $this->toDateTime((string) ($event['start_datetime'] ?? '')); + $end = $this->toDateTime((string) ($event['end_datetime'] ?? '')); + if ($start === null || $end === null) { + return []; + } + + $allDay = (bool) ($event['all_day_event'] ?? false); + $updated = $this->toDateTime((string) ($event['updated_at'] ?? '')) ?? new DateTimeImmutable('now', new DateTimeZone('UTC')); + + $lines = [ + 'BEGIN:VEVENT', + 'UID:' . $this->escapeText($uid), + 'SUMMARY:' . $this->escapeText((string) ($event['title'] ?? 'Untitled')), + 'DESCRIPTION:' . $this->escapeText((string) ($event['description'] ?? '')), + 'LOCATION:' . $this->escapeText((string) ($event['location'] ?? '')), + 'CATEGORIES:' . $this->escapeText((string) ($event['category'] ?? '')), + 'DTSTAMP:' . $this->toUtcIcs($updated), + 'LAST-MODIFIED:' . $this->toUtcIcs($updated), + ]; + + if ($allDay) { + $lines[] = 'DTSTART;VALUE=DATE:' . $start->setTimezone(new DateTimeZone('Europe/London'))->format('Ymd'); + $lines[] = 'DTEND;VALUE=DATE:' . $end->setTimezone(new DateTimeZone('Europe/London'))->format('Ymd'); + } else { + $lines[] = 'DTSTART;TZID=Europe/London:' . $start->setTimezone(new DateTimeZone('Europe/London'))->format('Ymd\\THis'); + $lines[] = 'DTEND;TZID=Europe/London:' . $end->setTimezone(new DateTimeZone('Europe/London'))->format('Ymd\\THis'); + } + + $rrule = $this->eventToRrule($event); + if ($rrule !== null) { + $lines[] = 'RRULE:' . $rrule; + } + + if ($deletedKeys) { + $parts = []; + foreach ($deletedKeys as $key) { + $dt = $this->toDateTime((string) $key); + if ($dt === null) { + continue; + } + $parts[] = $dt->setTimezone(new DateTimeZone('Europe/London'))->format('Ymd\\THis'); + } + if ($parts) { + $lines[] = 'EXDATE;TZID=Europe/London:' . implode(',', $parts); + } + } + + $lines[] = 'END:VEVENT'; + return $lines; + } + + private function eventToRrule(array $event): ?string + { + $type = strtolower((string) ($event['repeat_type'] ?? 'none')); + if ($type === 'none') { + return null; + } + + $freq = match ($type) { + 'daily' => 'DAILY', + 'weekly', 'custom' => 'WEEKLY', + 'monthly' => 'MONTHLY', + 'yearly' => 'YEARLY', + default => null, + }; + if ($freq === null) { + return null; + } + + $interval = max(1, (int) ($event['repeat_interval'] ?? 1)); + $parts = ['FREQ=' . $freq, 'INTERVAL=' . $interval]; + if ($type === 'monthly') { + $nthMode = (string) ($event['repeat_nth_mode'] ?? ''); + $nthDay = isset($event['repeat_nth_day']) && $event['repeat_nth_day'] !== null ? (int) $event['repeat_nth_day'] : null; + $nthPos = isset($event['repeat_nth_pos']) && $event['repeat_nth_pos'] !== null ? (int) $event['repeat_nth_pos'] : null; + $nthWeekday = isset($event['repeat_nth_weekday']) && $event['repeat_nth_weekday'] !== null ? (int) $event['repeat_nth_weekday'] : null; + if ($nthMode === 'day_of_month' && $nthDay !== null) { + $parts[] = 'BYMONTHDAY=' . max(1, min(31, $nthDay)); + } elseif ($nthMode === 'weekday_of_month' && $nthPos !== null && $nthWeekday !== null) { + $byDay = $this->weekdayNumToToken($nthWeekday); + if ($byDay !== null) { + $parts[] = 'BYDAY=' . $byDay; + $parts[] = 'BYSETPOS=' . ($nthPos < 0 ? -1 : max(1, min(5, $nthPos))); + } + } + } + + $rangeMode = strtolower((string) ($event['repeat_range_mode'] ?? 'none')); + if ($rangeMode === 'count' && !empty($event['repeat_count'])) { + $parts[] = 'COUNT=' . max(1, (int) $event['repeat_count']); + } + if ($rangeMode === 'until' && !empty($event['repeat_until'])) { + $until = $this->toDateTime((string) $event['repeat_until'] . 'T23:59:59'); + if ($until !== null) { + $parts[] = 'UNTIL=' . $this->toUtcIcs($until); + } + } + + return implode(';', $parts); + } + + private function parseRrule(string $rrule): array + { + $parts = []; + foreach (explode(';', strtoupper(trim($rrule))) as $chunk) { + [$k, $v] = array_pad(explode('=', $chunk, 2), 2, ''); + if ($k !== '') { + $parts[$k] = $v; + } + } + + $repeatType = match ($parts['FREQ'] ?? '') { + 'DAILY' => 'daily', + 'WEEKLY' => 'weekly', + 'MONTHLY' => 'monthly', + 'YEARLY' => 'yearly', + default => 'none', + }; + + $payload = [ + 'repeat_type' => $repeatType, + 'repeat_interval' => max(1, (int) ($parts['INTERVAL'] ?? 1)), + 'repeat_nth_mode' => '', + 'repeat_nth_day' => null, + 'repeat_nth_pos' => null, + 'repeat_nth_weekday' => null, + 'repeat_range_mode' => 'none', + 'repeat_count' => null, + 'repeat_until' => null, + ]; + + if ($repeatType === 'monthly') { + if (!empty($parts['BYMONTHDAY'])) { + $raw = trim(explode(',', (string) $parts['BYMONTHDAY'])[0]); + if (preg_match('/^-?\d+$/', $raw)) { + $payload['repeat_nth_mode'] = 'day_of_month'; + $payload['repeat_nth_day'] = max(1, min(31, (int) $raw)); + } + } elseif (!empty($parts['BYDAY'])) { + $byDayRaw = trim(explode(',', (string) $parts['BYDAY'])[0]); + $pos = null; + $token = $byDayRaw; + if (preg_match('/^(-?\d+)([A-Z]{2})$/', $byDayRaw, $m)) { + $pos = (int) $m[1]; + $token = $m[2]; + } + $weekday = $this->weekdayTokenToNum($token); + if ($weekday !== null) { + $payload['repeat_nth_mode'] = 'weekday_of_month'; + $payload['repeat_nth_weekday'] = $weekday; + if (isset($parts['BYSETPOS']) && preg_match('/^-?\d+$/', (string) $parts['BYSETPOS'])) { + $pos = (int) $parts['BYSETPOS']; + } + $payload['repeat_nth_pos'] = $pos === null ? 1 : ($pos < 0 ? -1 : max(1, min(5, $pos))); + } + } + } + + if (isset($parts['COUNT'])) { + $payload['repeat_range_mode'] = 'count'; + $payload['repeat_count'] = max(1, (int) $parts['COUNT']); + } elseif (isset($parts['UNTIL'])) { + $until = $this->parseIcsDateTime($parts['UNTIL'], false); + if ($until !== null) { + $payload['repeat_range_mode'] = 'until'; + $payload['repeat_until'] = substr($until, 0, 10); + } + } + + return $payload; + } + + private function extractVeventProperties(string $ics): ?array + { + $lines = preg_split('/\r\n|\n|\r/', $ics) ?: []; + $unfolded = []; + foreach ($lines as $line) { + if ($line === '') { + continue; + } + if (($line[0] ?? '') === ' ' && $unfolded) { + $unfolded[count($unfolded) - 1] .= substr($line, 1); + continue; + } + $unfolded[] = $line; + } + + $in = false; + $props = []; + foreach ($unfolded as $line) { + $upper = strtoupper($line); + if ($upper === 'BEGIN:VEVENT') { + $in = true; + continue; + } + if ($upper === 'END:VEVENT') { + break; + } + if (!$in) { + continue; + } + [$left, $value] = array_pad(explode(':', $line, 2), 2, ''); + if ($left === '') { + continue; + } + [$name, $params] = array_pad(explode(';', $left, 2), 2, ''); + $name = strtoupper(trim($name)); + if ($name === '') { + continue; + } + $props[$name][] = $this->unescapeText(trim($value)); + if ($name === 'DTSTART') { + $props['_DTSTART_PARAMS'][] = strtoupper(trim($params)); + } + } + + return $in ? $props : null; + } + + private function parseIcsDateTime(string $value, bool $dateOnly): ?string + { + $value = trim($value); + if ($value === '') { + return null; + } + + try { + if ($dateOnly && preg_match('/^\d{8}$/', $value)) { + $dt = DateTimeImmutable::createFromFormat('Ymd H:i:s', $value . ' 00:00:00', new DateTimeZone('Europe/London')); + if ($dt instanceof DateTimeImmutable) { + return $dt->format('Y-m-d\\T00:00:00P'); + } + } + + if (preg_match('/^\d{8}T\d{6}Z$/', $value)) { + $dt = DateTimeImmutable::createFromFormat('Ymd\\THis\\Z', $value, new DateTimeZone('UTC')); + if ($dt instanceof DateTimeImmutable) { + return $dt->setTimezone(new DateTimeZone('Europe/London'))->format('c'); + } + } + + if (preg_match('/^\d{8}T\d{6}$/', $value)) { + $dt = DateTimeImmutable::createFromFormat('Ymd\\THis', $value, new DateTimeZone('Europe/London')); + if ($dt instanceof DateTimeImmutable) { + return $dt->format('c'); + } + } + + $dt = new DateTimeImmutable($value, new DateTimeZone('Europe/London')); + return $dt->format('c'); + } catch (\Throwable) { + return null; + } + } + + private function toDateTime(string $value): ?DateTimeImmutable + { + if ($value === '') { + return null; + } + try { + return new DateTimeImmutable($value, new DateTimeZone('Europe/London')); + } catch (\Throwable) { + return null; + } + } + + private function toUtcIcs(DateTimeImmutable $dt): string + { + return $dt->setTimezone(new DateTimeZone('UTC'))->format('Ymd\\THis\\Z'); + } + + private function escapeText(string $value): string + { + return str_replace( + ["\\", ";", ",", "\r\n", "\n", "\r"], + ["\\\\", "\\;", "\\,", "\\n", "\\n", "\\n"], + $value + ); + } + + private function unescapeText(string $value): string + { + return str_replace( + ["\\n", "\\N", "\\,", "\\;", "\\\\"], + ["\n", "\n", ",", ";", "\\"], + $value + ); + } + + private function weekdayNumToToken(int $weekday): ?string + { + return match ($weekday) { + 0 => 'SU', + 1 => 'MO', + 2 => 'TU', + 3 => 'WE', + 4 => 'TH', + 5 => 'FR', + 6 => 'SA', + default => null, + }; + } + + private function weekdayTokenToNum(string $token): ?int + { + return match (strtoupper(trim($token))) { + 'SU' => 0, + 'MO' => 1, + 'TU' => 2, + 'WE' => 3, + 'TH' => 4, + 'FR' => 5, + 'SA' => 6, + default => null, + }; + } + + private function foldLines(array $lines): array + { + $out = []; + foreach ($lines as $line) { + if ($line === '') { + $out[] = $line; + continue; + } + while (strlen($line) > 73) { + $out[] = substr($line, 0, 73); + $line = ' ' . substr($line, 73); + } + $out[] = $line; + } + return $out; + } +} diff --git a/code/src/Domain/RecurrenceExpander.php b/code/src/Domain/RecurrenceExpander.php new file mode 100644 index 0000000..71fe24e --- /dev/null +++ b/code/src/Domain/RecurrenceExpander.php @@ -0,0 +1,193 @@ +diff($end); + $repeatType = (string) ($event['repeat_type'] ?? 'none'); + $interval = max(1, (int) ($event['repeat_interval'] ?? 1)); + $rangeMode = (string) ($event['repeat_range_mode'] ?? 'none'); + $repeatCount = isset($event['repeat_count']) ? (int) $event['repeat_count'] : null; + $repeatUntil = null; + if ($rangeMode === 'until' && !empty($event['repeat_until'])) { + $repeatUntil = self::parseDateTime((string) $event['repeat_until'] . 'T23:59:59', $tz); + } + + if ($repeatType === 'none') { + if (self::overlaps($start, $end, $windowStart, $windowEnd)) { + return [self::occurrence($event, $start, $end)]; + } + return []; + } + + $occurrences = []; + $current = $start; + $produced = 0; + + for ($i = 0; $i < self::MAX_ITERATIONS; $i++) { + if ($rangeMode === 'count' && $repeatCount !== null && $produced >= $repeatCount) { + break; + } + if ($repeatUntil && $current > $repeatUntil) { + break; + } + $currentEnd = $current->add($duration); + if (self::overlaps($current, $currentEnd, $windowStart, $windowEnd)) { + $key = $current->format('c'); + if (!isset($deletedMap[$key])) { + $occurrences[] = self::occurrence($event, $current, $currentEnd); + } + } + if ($current > $windowEnd->modify('+400 days')) { + break; + } + $produced++; + $current = self::nextStart($current, $repeatType, $interval, $event); + if (!$current) { + break; + } + } + + return $occurrences; + } + + private static function nextStart(DateTimeImmutable $current, string $repeatType, int $interval, array $event): ?DateTimeImmutable + { + return match ($repeatType) { + 'daily' => $current->add(new DateInterval('P' . $interval . 'D')), + 'weekly', 'custom' => $current->add(new DateInterval('P' . $interval . 'W')), + 'monthly' => self::nextMonthlyStart($current, $interval, $event), + 'yearly' => $current->modify('+' . $interval . ' year') ?: null, + default => null, + }; + } + + private static function nextMonthlyStart(DateTimeImmutable $current, int $interval, array $event): ?DateTimeImmutable + { + $mode = (string) ($event['repeat_nth_mode'] ?? ''); + $next = $current->modify('+' . $interval . ' month'); + if (!$next) { + return null; + } + if ($mode === 'day_of_month' && isset($event['repeat_nth_day']) && $event['repeat_nth_day'] !== null) { + $day = max(1, (int) $event['repeat_nth_day']); + $daysInMonth = (int) $next->format('t'); + return $next->setDate((int) $next->format('Y'), (int) $next->format('n'), min($day, $daysInMonth)); + } + if ($mode === 'weekday_of_month' && isset($event['repeat_nth_pos'], $event['repeat_nth_weekday']) && $event['repeat_nth_pos'] !== null && $event['repeat_nth_weekday'] !== null) { + $year = (int) $current->format('Y'); + $month = (int) $current->format('n'); + for ($i = 0; $i < 120; $i++) { + [$year, $month] = self::addMonths($year, $month, max(1, $interval)); + $day = self::nthWeekdayOfMonth($year, $month, (int) $event['repeat_nth_weekday'], (int) $event['repeat_nth_pos']); + if ($day !== null) { + return $current->setDate($year, $month, $day); + } + } + return null; + } + return $next; + } + + private static function nthWeekdayOfMonth(int $year, int $month, int $weekday, int $pos): ?int + { + $weekday = max(0, min(6, $weekday)); + $tz = new DateTimeZone('Europe/London'); + if ($pos === -1) { + $last = new DateTimeImmutable(sprintf('%04d-%02d-01T00:00:00', $year, $month), $tz); + $last = $last->modify('last day of this month'); + for ($day = (int) $last->format('j'); $day >= 1; $day--) { + $dt = $last->setDate($year, $month, $day); + if ((int) $dt->format('w') === $weekday) { + return $day; + } + } + return null; + } + $first = new DateTimeImmutable(sprintf('%04d-%02d-01T00:00:00', $year, $month), $tz); + $daysInMonth = (int) $first->format('t'); + $seen = 0; + for ($day = 1; $day <= $daysInMonth; $day++) { + $dt = $first->setDate($year, $month, $day); + if ((int) $dt->format('w') !== $weekday) { + continue; + } + $seen++; + if ($seen === $pos) { + return $day; + } + } + return null; + } + + private static function occurrence(array $event, DateTimeImmutable $start, DateTimeImmutable $end): array + { + return [ + 'event_id' => (int) ($event['id'] ?? 0), + 'uid' => (string) ($event['uid'] ?? ''), + 'title' => (string) ($event['title'] ?? ''), + 'description' => (string) ($event['description'] ?? ''), + 'location' => (string) ($event['location'] ?? ''), + 'category' => (string) ($event['category'] ?? ''), + 'all_day_event' => (bool) ($event['all_day_event'] ?? false), + 'occurrence_start' => $start->format('c'), + 'occurrence_end' => $end->format('c'), + 'repeat_type' => (string) ($event['repeat_type'] ?? 'none'), + ]; + } + + private static function overlaps(DateTimeImmutable $start, DateTimeImmutable $end, DateTimeImmutable $windowStart, DateTimeImmutable $windowEnd): bool + { + return $start < $windowEnd && $end > $windowStart; + } + + private static function parseDateTime(string $value, DateTimeZone $tz): ?DateTimeImmutable + { + if ($value === '') { + return null; + } + if (!str_contains($value, 'T') && preg_match('/^\d{4}-\d{2}-\d{2}$/', $value)) { + return new DateTimeImmutable($value . 'T00:00:00', $tz); + } + try { + return new DateTimeImmutable($value, $tz); + } catch (\Throwable) { + return null; + } + } + + private static function addMonths(int $year, int $month, int $delta): array + { + $index = ($year * 12) + ($month - 1) + $delta; + $newYear = (int) floor($index / 12); + $newMonth = ($index % 12) + 1; + if ($newMonth <= 0) { + $newMonth += 12; + $newYear -= 1; + } + return [$newYear, $newMonth]; + } +} diff --git a/code/src/Domain/SettingsService.php b/code/src/Domain/SettingsService.php new file mode 100644 index 0000000..4c24285 --- /dev/null +++ b/code/src/Domain/SettingsService.php @@ -0,0 +1,91 @@ + 'Public Calendar', + 'url_slug' => '', + 'verification_page_path' => '/calendar', + 'ics_access_mode' => 'public_read', + 'diagnostics_enabled' => '1', + 'uninstall_cleanup_mode' => 'keep', + ]; + + private const OPTION_PREFIX = 'calendar_plugin_'; + + public function __construct(private readonly OptionsAdapterInterface $options) + { + } + + public function getAll(): array + { + $out = []; + foreach (self::DEFAULTS as $key => $default) { + $out[$key] = $this->get($key, $default); + } + return $out; + } + + public function get(string $key, mixed $default = null): mixed + { + $fallback = $default ?? (self::DEFAULTS[$key] ?? null); + return $this->options->get(self::OPTION_PREFIX . $key, $fallback); + } + + public function update(array $payload): array + { + $allowed = array_keys(self::DEFAULTS); + $updated = $this->getAll(); + + foreach ($allowed as $key) { + if (!array_key_exists($key, $payload)) { + continue; + } + $value = $this->sanitize($key, $payload[$key]); + $this->options->set(self::OPTION_PREFIX . $key, $value); + $updated[$key] = $value; + } + + return $updated; + } + + private function sanitize(string $key, mixed $value): mixed + { + return match ($key) { + 'caldav_calendar_name' => trim((string) $value) ?: self::DEFAULTS[$key], + 'url_slug' => trim((string) $value, " \t\n\r\0\x0B/"), + 'verification_page_path' => $this->normalizePath((string) $value), + 'ics_access_mode' => in_array((string) $value, ['public_read', 'authenticated_read'], true) + ? (string) $value + : self::DEFAULTS['ics_access_mode'], + 'diagnostics_enabled' => $this->isTruthy($value) ? '1' : '0', + 'uninstall_cleanup_mode' => in_array((string) $value, ['keep', 'remove'], true) ? (string) $value : 'keep', + default => $value, + }; + } + + private function isTruthy(mixed $value): bool + { + return in_array(strtolower(trim((string) $value)), self::TRUE_VALUES, true); + } + + private function normalizePath(string $value): string + { + $path = trim($value); + if ($path === '') { + return self::DEFAULTS['verification_page_path']; + } + if (!str_starts_with($path, '/')) { + $path = '/' . $path; + } + return '/' . trim($path, '/'); + } +} diff --git a/code/src/Domain/UserService.php b/code/src/Domain/UserService.php new file mode 100644 index 0000000..2018195 --- /dev/null +++ b/code/src/Domain/UserService.php @@ -0,0 +1,473 @@ +getPrefix(); + $stem = trim($tableStem, '_'); + $this->usersTable = $prefix . $stem . '_users'; + $this->tokensTable = $prefix . $stem . '_user_tokens'; + $this->auditTable = $prefix . $stem . '_audit_log'; + } + + public function register(string $email, string $password): array + { + $email = $this->normalizeEmail($email); + if ($email === '' || !filter_var($email, FILTER_VALIDATE_EMAIL)) { + return $this->error('validation_error', 'email is required', 422); + } + if (strlen($password) < 8) { + return $this->error('validation_error', 'password must be at least 8 characters', 422); + } + if ($this->isRateLimited('register:' . $email, 10, 3600)) { + return $this->error('rate_limited', 'too many requests', 429); + } + if ($this->findUserByEmail($email)) { + return $this->error('conflict_error', 'account already exists', 409); + } + + $now = gmdate('c'); + $inserted = $this->db->insert( + $this->usersTable, + [ + 'email' => $email, + 'password_hash' => password_hash($password, PASSWORD_DEFAULT), + 'email_verified_at' => null, + 'account_status' => 'pending_approval', + 'created_at' => $now, + 'updated_at' => $now, + ] + ); + if ($inserted === false) { + return $this->error('internal_error', 'unable to create account', 500); + } + + $userId = $this->db->insertId(); + $token = $this->issueToken($userId, 'email_verify', 24 * 3600); + $this->audit('user.register', (string) $userId, 'success', ['email' => $email]); + + return [ + 'ok' => true, + 'user' => $this->publicUser((array) $this->getUserById($userId)), + 'verify_token' => $token, + 'message' => 'registration submitted', + ]; + } + + public function verifyEmail(string $token): array + { + if ($token === '') { + return $this->error('validation_error', 'token is required', 422); + } + $tok = $this->consumeToken($token, 'email_verify'); + if (!$tok) { + return $this->error('validation_error', 'invalid or expired token', 422); + } + $user = $this->getUserById((int) $tok['user_id']); + if (!$user) { + return $this->error('not_found', 'user not found', 404); + } + + $now = gmdate('c'); + $this->db->update( + $this->usersTable, + ['email_verified_at' => $now, 'updated_at' => $now], + ['id' => (int) $user['id']] + ); + $updated = $this->getUserById((int) $user['id']); + $this->audit('user.verify_email', (string) $user['id'], 'success', []); + + return [ + 'ok' => true, + 'user' => $updated ? $this->publicUser($updated) : $this->publicUser($user), + 'message' => 'An admin will review your request and notify you if approved.', + ]; + } + + public function login(string $email, string $password): array + { + $email = $this->normalizeEmail($email); + if ($this->isRateLimited('login:' . $email, 20, 3600)) { + return $this->error('rate_limited', 'too many requests', 429); + } + + $user = $this->findUserByEmail($email); + if (!$user || !password_verify($password, (string) ($user['password_hash'] ?? ''))) { + return $this->error('auth_required', 'Login failure', 401); + } + if (empty($user['email_verified_at'])) { + return $this->error('auth_required', 'Login failure', 401); + } + if ((string) ($user['account_status'] ?? '') !== 'active') { + return $this->error('auth_required', 'Login failure', 401); + } + + $this->audit('user.login', (string) $user['id'], 'success', []); + + return [ + 'ok' => true, + 'user' => $this->publicUser($user), + ]; + } + + public function authenticateActiveUserCredentials(string $email, string $password): ?array + { + $email = $this->normalizeEmail($email); + if ($email === '' || $password === '') { + return null; + } + $user = $this->findUserByEmail($email); + if (!$user) { + return null; + } + if (!password_verify($password, (string) ($user['password_hash'] ?? ''))) { + return null; + } + if (empty($user['email_verified_at'])) { + return null; + } + if ((string) ($user['account_status'] ?? '') !== 'active') { + return null; + } + return $this->publicUser($user); + } + + public function issueSessionToken(int $userId, int $ttlSeconds = 2592000): string + { + if ($userId <= 0) { + return ''; + } + return $this->issueToken($userId, 'session', max(300, $ttlSeconds)); + } + + public function authenticateSessionToken(string $token): ?array + { + $row = $this->findValidToken($token, 'session'); + if ($row === null) { + return null; + } + + $user = $this->getUserById((int) ($row['user_id'] ?? 0)); + if (!$user) { + return null; + } + if (empty($user['email_verified_at'])) { + return null; + } + if ((string) ($user['account_status'] ?? '') !== 'active') { + return null; + } + + return $this->publicUser($user); + } + + public function revokeSessionToken(string $token): void + { + $row = $this->findValidToken($token, 'session'); + if ($row === null) { + return; + } + $this->db->update( + $this->tokensTable, + ['used_at' => gmdate('c')], + ['id' => (int) $row['id']] + ); + } + + public function requestPasswordReset(string $email): array + { + $email = $this->normalizeEmail($email); + if ($email === '' || !filter_var($email, FILTER_VALIDATE_EMAIL)) { + return $this->error('validation_error', 'email is required', 422); + } + if ($this->isRateLimited('reset:' . $email, 10, 3600)) { + return $this->error('rate_limited', 'too many requests', 429); + } + + $user = $this->findUserByEmail($email); + if (!$user) { + return ['ok' => true, 'message' => 'if account exists, reset email will be sent']; + } + + $token = $this->issueToken((int) $user['id'], 'password_reset', 30 * 60); + $this->audit('user.password_reset.request', (string) $user['id'], 'success', []); + + return [ + 'ok' => true, + 'reset_token' => $token, + 'message' => 'password reset requested', + ]; + } + + public function resetPassword(string $token, string $newPassword): array + { + if (strlen($newPassword) < 8) { + return $this->error('validation_error', 'password must be at least 8 characters', 422); + } + + $tok = $this->consumeToken($token, 'password_reset'); + if (!$tok) { + return $this->error('validation_error', 'invalid or expired token', 422); + } + + $user = $this->getUserById((int) $tok['user_id']); + if (!$user) { + return $this->error('not_found', 'user not found', 404); + } + + $this->db->update( + $this->usersTable, + ['password_hash' => password_hash($newPassword, PASSWORD_DEFAULT), 'updated_at' => gmdate('c')], + ['id' => (int) $user['id']] + ); + // Invalidate persistent web sessions after password reset. + $this->db->delete($this->tokensTable, ['user_id' => (int) $user['id'], 'token_type' => 'session']); + $this->audit('user.password_reset.complete', (string) $user['id'], 'success', []); + + return ['ok' => true, 'message' => 'password updated']; + } + + public function listUsers(): array + { + $rows = $this->db->getResults("SELECT * FROM {$this->usersTable} ORDER BY id ASC"); + return array_map(fn(object $r): array => $this->publicUser((array) $r), $rows); + } + + public function approveUser(int $id): ?array + { + $user = $this->getUserById($id); + if (!$user) { + return null; + } + if (empty($user['email_verified_at'])) { + return null; + } + $this->db->update( + $this->usersTable, + ['account_status' => 'active', 'updated_at' => gmdate('c')], + ['id' => $id] + ); + $updated = $this->getUserById($id); + $this->audit('user.approve', (string) $id, 'success', []); + return $updated ? $this->publicUser($updated) : null; + } + + public function removeUser(int $id): bool + { + $deleted = $this->db->delete($this->usersTable, ['id' => $id]); + $this->db->delete($this->tokensTable, ['user_id' => $id]); + if ($deleted !== false) { + $this->audit('user.remove', (string) $id, 'success', []); + return true; + } + return false; + } + + private function findUserByEmail(string $email): ?array + { + foreach ($this->listRawUsers() as $user) { + if (strtolower((string) ($user['email'] ?? '')) === strtolower($email)) { + return $user; + } + } + return null; + } + + private function getUserById(int $id): ?array + { + $sql = $this->db->prepare("SELECT * FROM {$this->usersTable} WHERE id = %d", $id); + $row = $this->db->getRow($sql); + return $row ? (array) $row : null; + } + + private function listRawUsers(): array + { + $rows = $this->db->getResults("SELECT * FROM {$this->usersTable} ORDER BY id ASC"); + return array_map(static fn(object $r): array => (array) $r, $rows); + } + + private function issueToken(int $userId, string $type, int $ttlSeconds): string + { + $token = bin2hex(random_bytes(16)); + $hash = hash('sha256', $token); + $expiresAt = (new DateTimeImmutable('now', new DateTimeZone('UTC')))->modify('+' . $ttlSeconds . ' seconds')->format('c'); + + $this->db->insert( + $this->tokensTable, + [ + 'user_id' => $userId, + 'token_type' => $type, + 'token_hash' => $hash, + 'expires_at' => $expiresAt, + 'used_at' => null, + 'created_at' => gmdate('c'), + ] + ); + + return $token; + } + + private function consumeToken(string $token, string $type): ?array + { + $row = $this->findValidToken($token, $type); + if ($row === null) { + return null; + } + $this->db->update( + $this->tokensTable, + ['used_at' => gmdate('c')], + ['id' => (int) $row['id']] + ); + return $row; + } + + private function findValidToken(string $token, string $type): ?array + { + $raw = trim($token); + if ($raw === '') { + return null; + } + $hash = hash('sha256', $raw); + $now = new DateTimeImmutable('now', new DateTimeZone('UTC')); + + $rows = $this->db->getResults("SELECT * FROM {$this->tokensTable} ORDER BY id ASC"); + foreach ($rows as $rowObj) { + $row = (array) $rowObj; + if ((string) ($row['token_type'] ?? '') !== $type) { + continue; + } + if ((string) ($row['token_hash'] ?? '') !== $hash) { + continue; + } + if (!empty($row['used_at'])) { + return null; + } + try { + $expires = new DateTimeImmutable((string) $row['expires_at'], new DateTimeZone('UTC')); + } catch (\Throwable) { + return null; + } + if ($expires < $now) { + return null; + } + return $row; + } + return null; + } + + private function publicUser(array $user): array + { + return [ + 'id' => (int) ($user['id'] ?? 0), + 'email' => (string) ($user['email'] ?? ''), + 'email_verified_at' => $user['email_verified_at'] ?? null, + 'account_status' => (string) ($user['account_status'] ?? 'pending_approval'), + 'created_at' => (string) ($user['created_at'] ?? ''), + 'updated_at' => (string) ($user['updated_at'] ?? ''), + ]; + } + + private function error(string $code, string $message, int $status): array + { + return ['error' => ['code' => $code, 'message' => $message, 'status' => $status]]; + } + + private function normalizeEmail(string $email): string + { + return strtolower(trim($email)); + } + + private function audit(string $action, string $target, string $result, array $context): void + { + if (!$this->isDiagnosticsEnabled()) { + return; + } + $this->db->insert( + $this->auditTable, + [ + 'actor' => 'plugin', + 'action' => $action, + 'target' => $target, + 'result' => $result, + 'created_at' => gmdate('c'), + 'context_json' => json_encode($context), + ] + ); + } + + private function isDiagnosticsEnabled(): bool + { + $optionsTable = $this->db->getPrefix() . 'options'; + $sql = $this->db->prepare( + "SELECT option_value FROM {$optionsTable} WHERE option_name = %s LIMIT 1", + 'calendar_plugin_diagnostics_enabled' + ); + $row = $this->db->getRow($sql); + if (!$row || !property_exists($row, 'option_value')) { + return true; + } + return in_array(strtolower(trim((string) $row->option_value)), ['1', 'true', 'yes', 'on'], true); + } + + private function isRateLimited(string $bucket, int $limit, int $windowSeconds): bool + { + $now = new DateTimeImmutable('now', new DateTimeZone('UTC')); + $type = 'rate:' . substr(hash('sha256', $bucket), 0, 32); + $sql = $this->db->prepare("SELECT * FROM {$this->tokensTable} WHERE token_type = %s ORDER BY id ASC", $type); + $rows = $this->db->getResults($sql); + + $activeCount = 0; + foreach ($rows as $rowObj) { + $row = (array) $rowObj; + $id = (int) ($row['id'] ?? 0); + $usedAt = (string) ($row['used_at'] ?? ''); + $expiresAtRaw = (string) ($row['expires_at'] ?? ''); + $expired = true; + try { + $expiresAt = new DateTimeImmutable($expiresAtRaw, new DateTimeZone('UTC')); + $expired = $expiresAt < $now; + } catch (\Throwable) { + $expired = true; + } + + if ($id > 0 && ($usedAt !== '' || $expired)) { + $this->db->delete($this->tokensTable, ['id' => $id]); + continue; + } + + if (!$expired && $usedAt === '') { + $activeCount++; + } + } + + if ($activeCount >= $limit) { + return true; + } + + $this->db->insert( + $this->tokensTable, + [ + 'user_id' => 0, + 'token_type' => $type, + 'token_hash' => hash('sha256', bin2hex(random_bytes(16))), + 'expires_at' => $now->modify('+' . max(1, $windowSeconds) . ' seconds')->format('c'), + 'used_at' => null, + 'created_at' => gmdate('c'), + ] + ); + return false; + } +} diff --git a/code/src/Infrastructure/ServiceContainer.php b/code/src/Infrastructure/ServiceContainer.php new file mode 100644 index 0000000..874b495 --- /dev/null +++ b/code/src/Infrastructure/ServiceContainer.php @@ -0,0 +1,25 @@ + */ + private array $services = []; + + public function set(string $id, object $service): void + { + $this->services[$id] = $service; + } + + public function get(string $id): object + { + if (!isset($this->services[$id])) { + throw new \RuntimeException(sprintf('Service not found: %s', $id)); + } + + return $this->services[$id]; + } +} diff --git a/code/src/Infrastructure/WordPress/MigrationManager.php b/code/src/Infrastructure/WordPress/MigrationManager.php new file mode 100644 index 0000000..cb2281c --- /dev/null +++ b/code/src/Infrastructure/WordPress/MigrationManager.php @@ -0,0 +1,222 @@ +get_charset_collate(); + $prefix = $this->db->getPrefix(); + $stem = trim($this->tableStem, '_'); + + $events = $prefix . $stem . '_events'; + $exceptions = $prefix . $stem . '_recurrence_exceptions'; + $users = $prefix . $stem . '_users'; + $tokens = $prefix . $stem . '_user_tokens'; + $audit = $prefix . $stem . '_audit_log'; + + $sqlEvents = "CREATE TABLE {$events} ( + id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, + uid VARCHAR(191) NOT NULL, + title TEXT NOT NULL, + description LONGTEXT NOT NULL, + location TEXT NOT NULL, + category TEXT NOT NULL, + all_day_event TINYINT(1) NOT NULL DEFAULT 0, + start_datetime VARCHAR(64) NOT NULL, + end_datetime VARCHAR(64) NOT NULL, + repeat_type VARCHAR(24) NOT NULL DEFAULT 'none', + repeat_interval INT NOT NULL DEFAULT 1, + repeat_nth_mode VARCHAR(32) NOT NULL DEFAULT '', + repeat_nth_day INT NULL, + repeat_nth_pos INT NULL, + repeat_nth_weekday INT NULL, + repeat_range_mode VARCHAR(24) NOT NULL DEFAULT 'none', + repeat_count INT NULL, + repeat_until VARCHAR(16) NULL, + timezone VARCHAR(64) NOT NULL DEFAULT 'Europe/London', + caldav_resource VARCHAR(191) NULL, + etag VARCHAR(64) NULL, + sync_version INT NOT NULL DEFAULT 1, + last_modified_by_user_id BIGINT UNSIGNED NULL, + created_at VARCHAR(32) NOT NULL, + updated_at VARCHAR(32) NOT NULL, + PRIMARY KEY (id), + UNIQUE KEY uid (uid(191)), + UNIQUE KEY caldav_resource (caldav_resource), + KEY start_datetime (start_datetime(32)), + KEY end_datetime (end_datetime(32)) + ) {$charsetCollate};"; + + $sqlExceptions = "CREATE TABLE {$exceptions} ( + id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, + event_id BIGINT UNSIGNED NOT NULL, + occurrence_key VARCHAR(64) NOT NULL, + exception_type VARCHAR(32) NOT NULL, + created_at VARCHAR(32) NOT NULL, + updated_at VARCHAR(32) NOT NULL, + PRIMARY KEY (id), + UNIQUE KEY event_occurrence (event_id, occurrence_key), + KEY event_id (event_id) + ) {$charsetCollate};"; + + $sqlUsers = "CREATE TABLE {$users} ( + id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, + email VARCHAR(191) NOT NULL, + password_hash VARCHAR(255) NOT NULL, + email_verified_at VARCHAR(32) NULL, + account_status VARCHAR(32) NOT NULL DEFAULT 'pending_approval', + created_at VARCHAR(32) NOT NULL, + updated_at VARCHAR(32) NOT NULL, + PRIMARY KEY (id), + UNIQUE KEY email (email) + ) {$charsetCollate};"; + + $sqlTokens = "CREATE TABLE {$tokens} ( + id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, + user_id BIGINT UNSIGNED NOT NULL, + token_type VARCHAR(32) NOT NULL, + token_hash VARCHAR(255) NOT NULL, + expires_at VARCHAR(32) NOT NULL, + used_at VARCHAR(32) NULL, + created_at VARCHAR(32) NOT NULL, + PRIMARY KEY (id), + KEY user_id (user_id), + KEY token_type (token_type) + ) {$charsetCollate};"; + + $sqlAudit = "CREATE TABLE {$audit} ( + id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, + actor VARCHAR(191) NOT NULL, + action VARCHAR(191) NOT NULL, + target VARCHAR(191) NOT NULL, + result VARCHAR(32) NOT NULL, + created_at VARCHAR(32) NOT NULL, + context_json LONGTEXT NULL, + PRIMARY KEY (id), + KEY action (action), + KEY created_at (created_at) + ) {$charsetCollate};"; + + dbDelta($sqlEvents); + dbDelta($sqlExceptions); + dbDelta($sqlUsers); + dbDelta($sqlTokens); + dbDelta($sqlAudit); + + // Ensure every event has a stable CalDAV object resource name. + $this->db->query( + "UPDATE {$events} + SET caldav_resource = CONCAT(uid, '.ics') + WHERE (caldav_resource IS NULL OR caldav_resource = '') + AND uid IS NOT NULL + AND uid <> ''" + ); + $this->normalizeEventDateTimesToLondon($events); + + update_option(self::STEM_OPTION, $stem); + update_option('calendar_plugin_schema_version', self::SCHEMA_VERSION); + } + + public function assertActivationSafe(): void + { + $stem = trim($this->tableStem, '_'); + $ownedStem = trim((string) get_option(self::STEM_OPTION, ''), '_'); + if ($ownedStem !== '' && $ownedStem !== $stem) { + throw new \RuntimeException( + sprintf( + 'Calendar Plugin is already initialized with table stem "%s". Requested stem "%s" is different.', + $ownedStem, + $stem + ) + ); + } + if ($ownedStem !== $stem && $this->anyTargetTablesExist($stem)) { + $legacySchemaVersion = trim((string) get_option('calendar_plugin_schema_version', '')); + if ($legacySchemaVersion === '') { + throw new \RuntimeException( + sprintf( + 'Calendar Plugin activation blocked: target tables for stem "%s" already exist. Choose another table stem via CALENDAR_PLUGIN_TABLE_STEM.', + $stem + ) + ); + } + } + } + + private function anyTargetTablesExist(string $stem): bool + { + $prefix = $this->db->getPrefix(); + $tables = [ + $prefix . $stem . '_events', + $prefix . $stem . '_recurrence_exceptions', + $prefix . $stem . '_users', + $prefix . $stem . '_user_tokens', + $prefix . $stem . '_audit_log', + ]; + foreach ($tables as $table) { + $sql = $this->db->prepare('SHOW TABLES LIKE %s', $table); + if (count($this->db->getResults($sql)) > 0) { + return true; + } + } + return false; + } + + private function normalizeEventDateTimesToLondon(string $eventsTable): void + { + $rows = $this->db->getResults( + "SELECT id, start_datetime, end_datetime FROM {$eventsTable}" + ); + $tz = new DateTimeZone('Europe/London'); + foreach ($rows as $row) { + $id = (int) ($row->id ?? 0); + if ($id <= 0) { + continue; + } + $start = $this->normalizeDateTimeString((string) ($row->start_datetime ?? ''), $tz); + $end = $this->normalizeDateTimeString((string) ($row->end_datetime ?? ''), $tz); + if ($start === null || $end === null) { + continue; + } + $sql = $this->db->prepare( + "UPDATE {$eventsTable} SET start_datetime = %s, end_datetime = %s WHERE id = %d", + $start, + $end, + $id + ); + $this->db->query($sql); + } + } + + private function normalizeDateTimeString(string $value, DateTimeZone $tz): ?string + { + $value = trim($value); + if ($value === '') { + return null; + } + try { + $dt = new DateTimeImmutable($value, $tz); + return $dt->setTimezone($tz)->format('c'); + } catch (\Throwable) { + return null; + } + } +} diff --git a/code/src/Infrastructure/WordPress/WordPressAuthAdapter.php b/code/src/Infrastructure/WordPress/WordPressAuthAdapter.php new file mode 100644 index 0000000..b1f988f --- /dev/null +++ b/code/src/Infrastructure/WordPress/WordPressAuthAdapter.php @@ -0,0 +1,31 @@ +user_email ?? '') : ''; + } +} diff --git a/code/src/Infrastructure/WordPress/WordPressDatabaseAdapter.php b/code/src/Infrastructure/WordPress/WordPressDatabaseAdapter.php new file mode 100644 index 0000000..4fdc04e --- /dev/null +++ b/code/src/Infrastructure/WordPress/WordPressDatabaseAdapter.php @@ -0,0 +1,60 @@ +wpdb->prefix; + } + + public function prepare(string $query, mixed ...$args): string + { + return (string) $this->wpdb->prepare($query, ...$args); + } + + public function query(string $query): int|false + { + return $this->wpdb->query($query); + } + + public function getResults(string $query): array + { + return $this->wpdb->get_results($query) ?: []; + } + + public function getRow(string $query): ?object + { + $row = $this->wpdb->get_row($query); + return is_object($row) ? $row : null; + } + + public function insert(string $table, array $data, array $formats = []): int|false + { + return $this->wpdb->insert($table, $data, $formats); + } + + public function update(string $table, array $data, array $where, array $formats = [], array $whereFormats = []): int|false + { + return $this->wpdb->update($table, $data, $where, $formats, $whereFormats); + } + + public function delete(string $table, array $where, array $whereFormats = []): int|false + { + return $this->wpdb->delete($table, $where, $whereFormats); + } + + public function insertId(): int + { + return (int) $this->wpdb->insert_id; + } +} diff --git a/code/src/Infrastructure/WordPress/WordPressHttpAdapter.php b/code/src/Infrastructure/WordPress/WordPressHttpAdapter.php new file mode 100644 index 0000000..1157fd7 --- /dev/null +++ b/code/src/Infrastructure/WordPress/WordPressHttpAdapter.php @@ -0,0 +1,25 @@ +tableStem = self::resolveTableStem(); + $this->settingsService = new SettingsService($this->options); + $this->eventService = new EventService($this->db, $this->tableStem); + $this->icsService = new IcsService(); + $this->calDavService = new CalDavService($this->eventService, $this->icsService); + $this->userService = new UserService($this->db, $this->tableStem); + } + + public static function boot(string $pluginFile): void + { + global $wpdb; + + $db = new WordPressDatabaseAdapter($wpdb); + $options = new WordPressOptionsAdapter(); + $auth = new WordPressAuthAdapter(); + $http = new WordPressHttpAdapter(); + + register_activation_hook($pluginFile, [self::class, 'activate']); + register_deactivation_hook($pluginFile, [self::class, 'deactivate']); + + $plugin = new self($db, $options, $auth, $http); + $plugin->register(); + } + + public static function activate(): void + { + try { + global $wpdb; + $db = new WordPressDatabaseAdapter($wpdb); + $tableStem = self::resolveTableStem(); + $migration = new MigrationManager($db, $tableStem); + $migration->assertActivationSafe(); + $migration->migrate(); + } catch (\Throwable $e) { + if (function_exists('wp_die')) { + wp_die( + esc_html($e->getMessage()), + 'Calendar Plugin Activation Blocked', + ['response' => 500, 'back_link' => true] + ); + } + throw $e; + } + } + + public static function deactivate(): void + { + // Intentionally no destructive behavior on deactivate. + } + + public function register(): void + { + $this->http->addAction('init', [$this, 'ensureSchemaCurrent']); + $this->http->addAction('init', [$this, 'onInit']); + $this->http->addAction('rest_api_init', [$this, 'registerRoutes']); + $this->http->addAction('admin_menu', [$this, 'registerAdminMenu']); + $this->http->addAction('admin_post_calendar_plugin_download_diagnostics', [$this, 'handleDiagnosticsDownload']); + $this->http->addAction('template_redirect', [$this, 'maybeServeSpecialEndpoints']); + } + + public function onInit(): void + { + $this->http->addShortcode('calendar', [$this, 'renderCalendarShortcode']); + $this->http->addShortcode('calendar_sidebar_upcoming', [$this, 'renderSidebarShortcode']); + } + + public function ensureSchemaCurrent(): void + { + $version = trim((string) get_option('calendar_plugin_schema_version', '')); + if ($version === '3') { + return; + } + try { + self::activate(); + } catch (\Throwable) { + // Avoid hard-failing page loads; diagnostics/setup will still surface issues. + } + } + + public function registerRoutes(): void + { + $this->registerHealthRoute(); + $this->registerSettingsRoutes(); + $this->registerEventRoutes(); + $this->registerPublicRoutes(); + $this->registerUserRoutes(); + $this->registerIcsRoutes(); + $this->registerCalDavRoutes(); + } + + public function renderCalendarShortcode(): string + { + $caldavRoot = $this->caldavRootPath(); + $icsUrl = $this->icsPath(); + $ns = self::API_NAMESPACE; + $caldavRootEsc = esc_html($caldavRoot); + $icsUrlEsc = esc_html($icsUrl); + $nsEsc = esc_html($ns); + $todayEsc = esc_html(gmdate('Y-m-d')); + + return strtr(<<<'HTML' +
+
+
+ Not logged in + + + CalDAV + ICS +
+
+ +
+ + + + + + + + +
+ + +
+
+ +

+

Events

+
+ + + + + + + + + +
+HTML + , [ + '__CALDAV_ROOT__' => $caldavRootEsc, + '__ICS_URL__' => $icsUrlEsc, + '__TODAY__' => $todayEsc, + '__NS__' => $nsEsc, + ]); + } + + public function renderSidebarShortcode(): string + { + $items = $this->eventService->listSidebarUpcoming(14); + if (!$items) { + return '

No upcoming events.

'; + } + + $rows = []; + foreach ($items as $item) { + $start = (string) ($item['occurrence_start'] ?? ''); + $end = (string) ($item['occurrence_end'] ?? ''); + $startTs = strtotime($start); + $endTs = strtotime($end); + $dateLabel = $startTs !== false ? date('j F Y', $startTs) : substr($start, 0, 10); + $timeLabel = ''; + if ($startTs !== false && $endTs !== false) { + $startTime = date('H:i', $startTs); + $endTime = date('H:i', $endTs); + if ($startTime !== '00:00' || $endTime !== '00:00') { + $timeLabel = $this->formatSidebarTimeRange($startTs, $endTs); + } + } + $title = trim((string) ($item['title'] ?? '')); + $description = trim((string) ($item['description'] ?? '')); + $headline = $dateLabel; + if ($timeLabel !== '') { + $headline .= ', ' . $timeLabel; + } + $headline .= ', ' . ($title !== '' ? $title : $description); + $rows[] = sprintf( + '

%s%s

', + esc_html($headline), + $description !== '' ? '
' . esc_html($description) : '' + ); + } + + return '
' . implode('', $rows) . '
'; + } + + private function formatSidebarTimeRange(int $startTs, int $endTs): string + { + $startMeridiem = strtolower(date('a', $startTs)); + $endMeridiem = strtolower(date('a', $endTs)); + $startLabel = $this->formatSidebarTimeValue($startTs); + $endLabel = $this->formatSidebarTimeValue($endTs); + if ($startMeridiem === $endMeridiem) { + $startLabel = preg_replace('/(am|pm)$/', '', $startLabel) ?: $startLabel; + return $startLabel . '–' . $endLabel; + } + return $startLabel . '–' . $endLabel; + } + + private function formatSidebarTimeValue(int $ts): string + { + $hour = (int) date('G', $ts); + $minute = (int) date('i', $ts); + $meridiem = strtolower(date('a', $ts)); + $hour12 = $hour % 12; + if ($hour12 === 0) { + $hour12 = 12; + } + if ($minute === 0) { + return $hour12 . $meridiem; + } + return $hour12 . '.' . str_pad((string) $minute, 2, '0', STR_PAD_LEFT) . $meridiem; + } + + public function registerAdminMenu(): void + { + if (!function_exists('add_menu_page')) { + return; + } + + add_menu_page( + 'Calendar Plugin', + 'Calendar Plugin', + 'manage_options', + 'calendar-plugin', + [$this, 'renderAdminSetupPage'] + ); + + add_submenu_page( + 'calendar-plugin', + 'Users', + 'Users', + 'manage_options', + 'calendar-plugin-users', + [$this, 'renderAdminUsersPage'] + ); + + add_submenu_page( + 'calendar-plugin', + 'Setup', + 'Setup', + 'manage_options', + 'calendar-plugin-setup', + [$this, 'renderAdminSetupPage'] + ); + + add_submenu_page( + 'calendar-plugin', + 'Diagnostics', + 'Diagnostics', + 'manage_options', + 'calendar-plugin-diagnostics', + [$this, 'renderAdminDiagnosticsPage'] + ); + + if (function_exists('remove_submenu_page')) { + remove_submenu_page('calendar-plugin', 'calendar-plugin'); + } + } + + public function renderAdminSetupPage(): void + { + $settings = $this->settingsService->getAll(); + $currentStem = trim((string) get_option('calendar_plugin_table_stem', $this->tableStem), '_'); + if ($currentStem === '') { + $currentStem = $this->tableStem; + } + $dbPrefix = (string) $this->db->getPrefix(); + $currentTablePrefix = $dbPrefix . $currentStem; + $message = ''; + $error = ''; + + if (strtoupper((string) ($_SERVER['REQUEST_METHOD'] ?? 'GET')) === 'POST') { + $nonce = (string) ($_POST['_calendar_setup_nonce'] ?? ''); + $nonceSupported = function_exists('wp_nonce_field') && function_exists('wp_verify_nonce'); + if ($nonceSupported && ($nonce === '' || !$this->auth->verifyNonce($nonce, 'calendar_setup_update'))) { + $error = 'Invalid setup form submission.'; + } else { + $action = (string) ($_POST['setup_action'] ?? 'save_settings'); + if ($action === 'delete_all_events') { + $deleted = $this->eventService->deleteAllEventsData(); + $message = 'Deleted ' . $deleted . ' events and all recurrence exceptions.'; + } elseif ($action === 'seed_events') { + try { + $seeded = $this->eventService->seedDefaultEvents(); + $message = 'Seeded ' . $seeded . ' default events.'; + } catch (\Throwable $e) { + $error = 'Seed failed: ' . $e->getMessage(); + } + } else { + $payload = [ + 'caldav_calendar_name' => (string) ($_POST['caldav_calendar_name'] ?? ''), + 'url_slug' => (string) ($_POST['url_slug'] ?? ''), + 'verification_page_path' => (string) ($_POST['verification_page_path'] ?? ''), + 'ics_access_mode' => (string) ($_POST['ics_access_mode'] ?? ''), + 'diagnostics_enabled' => isset($_POST['diagnostics_enabled']) ? '1' : '0', + 'uninstall_cleanup_mode' => (string) ($_POST['uninstall_cleanup_mode'] ?? 'keep'), + ]; + $settings = $this->settingsService->update($payload); + $requestedPrefixRaw = strtolower(trim((string) ($_POST['table_prefix'] ?? $currentTablePrefix))); + $requestedPrefix = preg_replace('/[^a-z0-9_]/', '', $requestedPrefixRaw) ?: $currentTablePrefix; + $requestedPrefix = trim($requestedPrefix, '_'); + if ($requestedPrefix === '') { + $requestedPrefix = $currentTablePrefix; + } + if ($requestedPrefix !== $currentTablePrefix) { + $prefixResult = $this->updatePluginTablePrefix($currentStem, $requestedPrefix); + if ($prefixResult['ok']) { + $currentStem = (string) $prefixResult['stem']; + $currentTablePrefix = $dbPrefix . $currentStem; + $message = 'Settings saved. Table prefix updated to ' . $currentTablePrefix . '.'; + } else { + $error = (string) $prefixResult['message']; + } + } else { + $message = 'Settings saved.'; + } + } + } + } + + $nonceField = ''; + if (function_exists('wp_nonce_field')) { + ob_start(); + wp_nonce_field('calendar_setup_update', '_calendar_setup_nonce'); + $nonceField = (string) ob_get_clean(); + } else { + $nonceField = ''; + } + + $saveNote = ''; + if ($error !== '') { + $saveNote .= '

' . esc_html($error) . '

'; + } + if ($message !== '') { + $saveNote .= '

' . esc_html($message) . '

'; + } + + $icsMode = (string) ($settings['ics_access_mode'] ?? 'public_read'); + $icsModePublicSel = $icsMode === 'public_read' ? 'selected' : ''; + $icsModeAuthSel = $icsMode === 'authenticated_read' ? 'selected' : ''; + $diagnosticsChecked = ((string) ($settings['diagnostics_enabled'] ?? '1')) === '1' ? 'checked' : ''; + $cleanupMode = (string) ($settings['uninstall_cleanup_mode'] ?? 'keep'); + $cleanupKeepSel = $cleanupMode === 'keep' ? 'selected' : ''; + $cleanupRemoveSel = $cleanupMode === 'remove' ? 'selected' : ''; + + $form = '
' + . $nonceField + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '

' + . ' ' + . '

' + . '
'; + + echo $this->adminPageShell( + 'Setup', + $saveNote + . '

Configure presentation and endpoint behavior below or via API: /wp-json/' . self::API_NAMESPACE . '/settings.

' + . $form + . '' + ); + } + + public function renderAdminUsersPage(): void + { + $message = ''; + $error = ''; + + if (strtoupper((string) ($_SERVER['REQUEST_METHOD'] ?? 'GET')) === 'POST') { + $nonce = (string) ($_POST['_calendar_users_nonce'] ?? ''); + $nonceSupported = function_exists('wp_nonce_field') && function_exists('wp_verify_nonce'); + if ($nonceSupported && ($nonce === '' || !$this->auth->verifyNonce($nonce, 'calendar_users_action'))) { + $error = 'Invalid users action submission.'; + } else { + $action = (string) ($_POST['users_action'] ?? ''); + $userId = (int) ($_POST['user_id'] ?? 0); + if ($userId <= 0) { + $error = 'Invalid user selection.'; + } elseif ($action === 'approve') { + $user = $this->userService->approveUser($userId); + if ($user === null) { + $error = 'User not found.'; + } else { + $message = 'User approved.'; + } + } elseif ($action === 'remove') { + $ok = $this->userService->removeUser($userId); + if (!$ok) { + $error = 'User not found.'; + } else { + $message = 'User removed.'; + } + } else { + $error = 'Unsupported users action.'; + } + } + } + + $nonceField = ''; + if (function_exists('wp_nonce_field')) { + ob_start(); + wp_nonce_field('calendar_users_action', '_calendar_users_nonce'); + $nonceField = (string) ob_get_clean(); + } else { + $nonceField = ''; + } + + $users = $this->userService->listUsers(); + $rows = ''; + foreach ($users as $u) { + $userId = (int) ($u['id'] ?? 0); + $status = (string) ($u['account_status'] ?? ''); + $isApproved = $status === 'active'; + $isVerified = !empty((string) ($u['email_verified_at'] ?? '')); + $approveButton = $isApproved + ? '' + : ($isVerified + ? '' + : ''); + $removeButton = ''; + $actions = '
' + . $nonceField + . '' + . $approveButton + . $removeButton + . '
'; + $rows .= '' + . '' . $userId . '' + . '' . esc_html((string) $u['email']) . '' + . '' . esc_html($status) . '' + . '' . esc_html((string) ($u['email_verified_at'] ?? '')) . '' + . '' . $actions . '' + . ''; + } + $feedback = ''; + if ($error !== '') { + $feedback .= '

' . esc_html($error) . '

'; + } + if ($message !== '') { + $feedback .= '

' . esc_html($message) . '

'; + } + + $body = $feedback + . '

Approve pending users or remove defunct users.

' + . '' + . ($rows !== '' ? $rows : '') + . '
IDEmailStatusEmail VerifiedActions
No users
'; + echo $this->adminPageShell('Users', $body); + } + + public function renderAdminDiagnosticsPage(): void + { + $enabled = $this->isDiagnosticsEnabled(); + $diag = $this->collectDiagnosticsSnapshot($enabled); + $downloadUrl = admin_url('admin-post.php?action=calendar_plugin_download_diagnostics&_wpnonce=' . wp_create_nonce('calendar_diagnostics_download')); + $downloadButton = $enabled + ? '

Download Diagnostics

' + : '

Enable diagnostics in Setup to collect and download diagnostics.

'; + echo $this->adminPageShell( + 'Diagnostics', + $downloadButton + . '

Runtime diagnostics snapshot:

' . esc_html(json_encode($diag, JSON_PRETTY_PRINT)) . '
' + ); + } + + public function handleDiagnosticsDownload(): void + { + if (!$this->auth->currentUserCan('manage_options')) { + wp_die('Not authorized.', 'Forbidden', ['response' => 403]); + } + $nonce = (string) ($_GET['_wpnonce'] ?? ''); + if (!$this->auth->verifyNonce($nonce, 'calendar_diagnostics_download')) { + wp_die('Invalid diagnostics download request.'); + } + if (!$this->isDiagnosticsEnabled()) { + wp_die('Diagnostics are disabled in Setup.'); + } + + $diag = $this->collectDiagnosticsSnapshot(true); + nocache_headers(); + header('Content-Type: application/json; charset=utf-8'); + header('Content-Disposition: attachment; filename="calendar-diagnostics-' . gmdate('Ymd-His') . '.json"'); + header('X-Content-Type-Options: nosniff'); + echo json_encode($diag, JSON_PRETTY_PRINT); + exit; + } + + private function isDiagnosticsEnabled(): bool + { + return ((string) ($this->settingsService->get('diagnostics_enabled', '1') ?? '1')) === '1'; + } + + public function maybeServeSpecialEndpoints(): void + { + $uri = (string) ($_SERVER['REQUEST_URI'] ?? ''); + $path = parse_url($uri, PHP_URL_PATH); + if (!is_string($path)) { + return; + } + + if ($path === $this->icsPath()) { + $this->serveIcsResponse(); + exit; + } + + if ($path === '/.well-known/caldav') { + wp_redirect($this->caldavRootPath(), 301); + exit; + } + + if ($path === rtrim($this->caldavRootPath(), '/') . '/') { + $method = strtoupper((string) ($_SERVER['REQUEST_METHOD'] ?? 'GET')); + if ($method === 'OPTIONS') { + header('Allow: OPTIONS, PROPFIND, REPORT'); + header('DAV: 1, calendar-access'); + http_response_code(200); + exit; + } + } + + if (str_starts_with($path, rtrim($this->caldavRootPath(), '/'))) { + $this->serveCalDavPath($path, strtoupper((string) ($_SERVER['REQUEST_METHOD'] ?? 'GET'))); + exit; + } + } + + private function registerHealthRoute(): void + { + $this->http->registerRestRoute( + self::API_NAMESPACE, + '/health', + [ + 'methods' => 'GET', + 'permission_callback' => '__return_true', + 'callback' => function (): array { + return [ + 'status' => 'ok', + 'plugin' => 'calendar-plugin', + 'version' => '0.1.15', + 'db_prefix' => $this->db->getPrefix(), + ]; + }, + ] + ); + } + + private function registerSettingsRoutes(): void + { + $this->http->registerRestRoute( + self::API_NAMESPACE, + '/settings', + [ + 'methods' => 'GET', + 'permission_callback' => fn(): bool => $this->auth->currentUserCan('manage_options'), + 'callback' => fn(): array => ['data' => $this->settingsService->getAll()], + ] + ); + + $this->http->registerRestRoute( + self::API_NAMESPACE, + '/settings', + [ + 'methods' => 'PATCH', + 'permission_callback' => fn(): bool => $this->auth->currentUserCan('manage_options'), + 'callback' => function ($request): array|\WP_Error { + $payload = $this->jsonPayload($request); + if ($payload === null) { + return $this->error('validation_error', 'invalid JSON payload', 422); + } + return ['data' => $this->settingsService->update($payload)]; + }, + ] + ); + } + + private function registerEventRoutes(): void + { + $canWrite = fn($request = null): bool => $this->canWriteCalendar($request); + + $this->http->registerRestRoute( + self::API_NAMESPACE, + '/events', + [ + 'methods' => 'GET', + 'permission_callback' => $canWrite, + 'callback' => fn(): array => ['data' => $this->eventService->listEvents()], + ] + ); + + $this->http->registerRestRoute( + self::API_NAMESPACE, + '/events', + [ + 'methods' => 'POST', + 'permission_callback' => $canWrite, + 'callback' => function ($request): array|\WP_Error { + $payload = $this->jsonPayload($request); + if ($payload === null) { + return $this->error('validation_error', 'invalid JSON payload', 422); + } + try { + $deletedKeys = isset($payload['deleted_occurrence_keys']) && is_array($payload['deleted_occurrence_keys']) + ? array_values($payload['deleted_occurrence_keys']) + : []; + unset($payload['deleted_occurrence_keys']); + $created = $this->eventService->createEvent($payload); + if ($deletedKeys !== []) { + $this->eventService->syncDeletedOccurrenceKeys((int) ($created['id'] ?? 0), $deletedKeys, true); + $created = $this->eventService->getEvent((int) ($created['id'] ?? 0)) ?: $created; + } + return ['data' => $created]; + } catch (\InvalidArgumentException $e) { + return $this->error('validation_error', $e->getMessage(), 422); + } catch (\RuntimeException $e) { + return $this->error('internal_error', $e->getMessage(), 500); + } + }, + ] + ); + + $this->http->registerRestRoute( + self::API_NAMESPACE, + '/events/(?P\d+)', + [ + 'methods' => 'GET', + 'permission_callback' => $canWrite, + 'callback' => function ($request): array|\WP_Error { + $id = (int) $request->get_param('id'); + $event = $this->eventService->getEvent($id); + if (!$event) { + return $this->error('not_found', 'event not found', 404); + } + return ['data' => $event]; + }, + ] + ); + + $this->http->registerRestRoute( + self::API_NAMESPACE, + '/events/(?P\d+)', + [ + 'methods' => 'PATCH', + 'permission_callback' => $canWrite, + 'callback' => function ($request): array|\WP_Error { + $id = (int) $request->get_param('id'); + $payload = $this->jsonPayload($request); + if ($payload === null) { + return $this->error('validation_error', 'invalid JSON payload', 422); + } + try { + $deletedKeysProvided = isset($payload['deleted_occurrence_keys']) && is_array($payload['deleted_occurrence_keys']); + $deletedKeys = $deletedKeysProvided ? array_values((array) $payload['deleted_occurrence_keys']) : []; + unset($payload['deleted_occurrence_keys']); + $event = $this->eventService->updateEvent($id, $payload); + } catch (\InvalidArgumentException $e) { + return $this->error('validation_error', $e->getMessage(), 422); + } + if (!$event) { + return $this->error('not_found', 'event not found', 404); + } + if ($deletedKeysProvided) { + $this->eventService->syncDeletedOccurrenceKeys($id, $deletedKeys, true); + $event = $this->eventService->getEvent($id) ?: $event; + } + return ['data' => $event]; + }, + ] + ); + + $this->http->registerRestRoute( + self::API_NAMESPACE, + '/events/(?P\d+)', + [ + 'methods' => 'DELETE', + 'permission_callback' => $canWrite, + 'callback' => function ($request): array|\WP_Error { + $id = (int) $request->get_param('id'); + $ok = $this->eventService->deleteEvent($id); + if (!$ok) { + return $this->error('not_found', 'event not found', 404); + } + return ['data' => ['deleted' => true]]; + }, + ] + ); + + $this->http->registerRestRoute( + self::API_NAMESPACE, + '/events/(?P\d+)/occurrences/(?P[^/]+)', + [ + 'methods' => 'DELETE', + 'permission_callback' => $canWrite, + 'callback' => function ($request): array|\WP_Error { + $id = (int) $request->get_param('id'); + $key = urldecode((string) $request->get_param('occurrence_key')); + $ok = $this->eventService->deleteOccurrence($id, $key); + if (!$ok) { + return $this->error('validation_error', 'invalid occurrence or event', 422); + } + return ['data' => ['deleted' => true]]; + }, + ] + ); + + $this->http->registerRestRoute( + self::API_NAMESPACE, + '/events/(?P\d+)/occurrences', + [ + 'methods' => 'GET', + 'permission_callback' => $canWrite, + 'callback' => function ($request): array|\WP_Error { + $id = (int) $request->get_param('id'); + $from = (string) ($request->get_param('from') ?: gmdate('Y-m-d')); + $months = (int) ($request->get_param('months') ?: 3); + $items = $this->eventService->listEventOccurrences($id, $from, $months); + if ($items === null) { + return $this->error('not_found', 'event not found', 404); + } + return ['data' => $items, 'meta' => ['count' => count($items), 'from' => $from, 'months' => max(1, min($months, 24))]]; + }, + ] + ); + + $this->http->registerRestRoute( + self::API_NAMESPACE, + '/events/preview-occurrences', + [ + 'methods' => 'POST', + 'permission_callback' => '__return_true', + 'callback' => function ($request): array|\WP_Error { + $payload = $this->jsonPayload($request); + if ($payload === null || !is_array($payload)) { + return $this->error('validation_error', 'invalid JSON payload', 422); + } + $event = isset($payload['event']) && is_array($payload['event']) ? (array) $payload['event'] : []; + $from = (string) ($payload['from'] ?? gmdate('Y-m-d')); + $months = (int) ($payload['months'] ?? 3); + try { + $items = $this->eventService->previewOccurrences($event, $from, $months); + } catch (\InvalidArgumentException $e) { + return $this->error('validation_error', $e->getMessage(), 422); + } + return ['data' => $items, 'meta' => ['count' => count($items), 'from' => $from, 'months' => max(1, min($months, 24))]]; + }, + ] + ); + } + + private function registerPublicRoutes(): void + { + $this->http->registerRestRoute( + self::API_NAMESPACE, + '/public/events', + [ + 'methods' => 'GET', + 'permission_callback' => '__return_true', + 'callback' => function ($request): array { + $view = (string) ($request->get_param('view') ?: 'month'); + $date = (string) ($request->get_param('date') ?: gmdate('Y-m-d')); + $futureOnlyRaw = (string) ($request->get_param('future_only') ?? ''); + $futureOnly = in_array(strtolower($futureOnlyRaw), ['1', 'true', 'yes', 'on'], true); + $items = $this->eventService->listPublicOccurrences($view, $date, $futureOnly); + return [ + 'data' => $items, + 'meta' => ['count' => count($items), 'view' => $view, 'future_only' => $futureOnly], + ]; + }, + ] + ); + + $this->http->registerRestRoute( + self::API_NAMESPACE, + '/public/sidebar-events', + [ + 'methods' => 'GET', + 'permission_callback' => '__return_true', + 'callback' => function (): array { + $items = $this->eventService->listSidebarUpcoming(14); + return [ + 'data' => $items, + 'meta' => ['count' => count($items), 'window_days' => 14], + ]; + }, + ] + ); + } + + private function registerUserRoutes(): void + { + $this->http->registerRestRoute( + self::API_NAMESPACE, + '/users/register', + [ + 'methods' => 'POST', + 'permission_callback' => '__return_true', + 'callback' => function ($request): array|\WP_Error { + $payload = $this->jsonPayload($request); + if ($payload === null) { + return $this->error('validation_error', 'invalid JSON payload', 422); + } + $result = $this->userService->register( + (string) ($payload['email'] ?? ''), + (string) ($payload['password'] ?? '') + ); + if (isset($result['error'])) { + return $this->unwrapServiceResult($result); + } + + $debugTokens = $this->allowDebugTokens() && !empty($payload['debug_tokens']); + $verifyToken = (string) ($result['verify_token'] ?? ''); + $email = (string) ($result['user']['email'] ?? ''); + if ($verifyToken !== '' && $email !== '') { + $this->sendUserEmailVerification($email, $verifyToken); + } + + if (!$debugTokens) { + unset($result['verify_token']); + } + return ['data' => $result]; + }, + ] + ); + + $this->http->registerRestRoute( + self::API_NAMESPACE, + '/users/verify', + [ + 'methods' => 'POST', + 'permission_callback' => '__return_true', + 'callback' => function ($request): array|\WP_Error { + $payload = $this->jsonPayload($request); + if ($payload === null) { + return $this->error('validation_error', 'invalid JSON payload', 422); + } + $result = $this->userService->verifyEmail((string) ($payload['token'] ?? '')); + if (!isset($result['error'])) { + $verifiedEmail = (string) ($result['user']['email'] ?? ''); + if ($verifiedEmail !== '') { + $this->sendAdminApprovalRequestEmail($verifiedEmail); + } + } + return $this->unwrapServiceResult($result); + }, + ] + ); + + $this->http->registerRestRoute( + self::API_NAMESPACE, + '/users/login', + [ + 'methods' => 'POST', + 'permission_callback' => '__return_true', + 'callback' => function ($request): array|\WP_Error { + $payload = $this->jsonPayload($request); + if ($payload === null) { + return $this->error('validation_error', 'invalid JSON payload', 422); + } + $result = $this->userService->login( + (string) ($payload['email'] ?? ''), + (string) ($payload['password'] ?? '') + ); + if (isset($result['error'])) { + return $this->unwrapServiceResult($result); + } + $sessionToken = $this->userService->issueSessionToken((int) ($result['user']['id'] ?? 0), self::SESSION_TTL_SECONDS); + if ($sessionToken !== '') { + $this->setCalendarSessionCookie($sessionToken); + } + return ['data' => $result]; + }, + ] + ); + + $this->http->registerRestRoute( + self::API_NAMESPACE, + '/users/logout', + [ + 'methods' => 'POST', + 'permission_callback' => '__return_true', + 'callback' => function (): array { + $token = $this->readCalendarSessionTokenFromRequest(null); + if ($token !== '') { + $this->userService->revokeSessionToken($token); + } + $this->clearCalendarSessionCookie(); + return ['data' => ['logged_out' => true]]; + }, + ] + ); + + $this->http->registerRestRoute( + self::API_NAMESPACE, + '/users/me', + [ + 'methods' => 'GET', + 'permission_callback' => '__return_true', + 'callback' => function ($request): array|\WP_Error { + $user = $this->resolveCalDavUserForRequest($request); + if ($user === null) { + return $this->error('auth_required', 'Login failure', 401); + } + return ['data' => $user]; + }, + ] + ); + + $this->http->registerRestRoute( + self::API_NAMESPACE, + '/users/password/request', + [ + 'methods' => 'POST', + 'permission_callback' => '__return_true', + 'callback' => function ($request): array|\WP_Error { + $payload = $this->jsonPayload($request); + if ($payload === null) { + return $this->error('validation_error', 'invalid JSON payload', 422); + } + $result = $this->userService->requestPasswordReset((string) ($payload['email'] ?? '')); + if (isset($result['error'])) { + return $this->unwrapServiceResult($result); + } + + $debugTokens = $this->allowDebugTokens() && !empty($payload['debug_tokens']); + $email = (string) ($payload['email'] ?? ''); + $token = (string) ($result['reset_token'] ?? ''); + if ($email !== '' && $token !== '') { + $this->sendPasswordResetEmail($email, $token); + } + if (!$debugTokens) { + unset($result['reset_token']); + } + return ['data' => $result]; + }, + ] + ); + + $this->http->registerRestRoute( + self::API_NAMESPACE, + '/users/password/reset', + [ + 'methods' => 'POST', + 'permission_callback' => '__return_true', + 'callback' => function ($request): array|\WP_Error { + $payload = $this->jsonPayload($request); + if ($payload === null) { + return $this->error('validation_error', 'invalid JSON payload', 422); + } + $result = $this->userService->resetPassword( + (string) ($payload['token'] ?? ''), + (string) ($payload['new_password'] ?? '') + ); + return $this->unwrapServiceResult($result); + }, + ] + ); + + $this->http->registerRestRoute( + self::API_NAMESPACE, + '/admin/users', + [ + 'methods' => 'GET', + 'permission_callback' => fn(): bool => $this->auth->currentUserCan('manage_options'), + 'callback' => fn(): array => ['data' => $this->userService->listUsers()], + ] + ); + + $this->http->registerRestRoute( + self::API_NAMESPACE, + '/admin/users/(?P\d+)/approve', + [ + 'methods' => 'PATCH', + 'permission_callback' => fn(): bool => $this->auth->currentUserCan('manage_options'), + 'callback' => function ($request): array|\WP_Error { + $id = (int) $request->get_param('id'); + $user = $this->userService->approveUser($id); + if (!$user) { + return $this->error('not_found', 'user not found', 404); + } + return ['data' => $user]; + }, + ] + ); + + $this->http->registerRestRoute( + self::API_NAMESPACE, + '/admin/users/(?P\d+)', + [ + 'methods' => 'DELETE', + 'permission_callback' => fn(): bool => $this->auth->currentUserCan('manage_options'), + 'callback' => function ($request): array|\WP_Error { + $id = (int) $request->get_param('id'); + $ok = $this->userService->removeUser($id); + if (!$ok) { + return $this->error('not_found', 'user not found', 404); + } + return ['data' => ['deleted' => true]]; + }, + ] + ); + } + + private function registerIcsRoutes(): void + { + $this->http->registerRestRoute( + self::API_NAMESPACE, + '/public/ics', + [ + 'methods' => 'GET', + 'permission_callback' => '__return_true', + 'callback' => function (): array { + $settings = $this->settingsService->getAll(); + $calendarName = (string) ($settings['caldav_calendar_name'] ?? 'Public Calendar'); + $ics = $this->icsService->buildCalendar( + $this->eventService->listEvents(), + fn(int $eventId): array => $this->eventService->getDeletedOccurrenceKeys($eventId), + $calendarName + ); + return ['data' => $ics]; + }, + ] + ); + } + + private function registerCalDavRoutes(): void + { + $canRead = fn($request = null): bool => $this->resolveCalDavUserForRequest($request) !== null; + $canWrite = fn($request = null): bool => $this->canWriteCalendar($request); + + $this->http->registerRestRoute( + self::API_NAMESPACE, + '/caldav/resources', + [ + 'methods' => 'GET', + 'permission_callback' => $canRead, + 'callback' => fn(): array => ['data' => $this->calDavService->listResources()], + ] + ); + + $this->http->registerRestRoute( + self::API_NAMESPACE, + '/caldav/object/(?P[^/]+\.ics)', + [ + 'methods' => 'GET', + 'permission_callback' => $canRead, + 'callback' => function ($request): array|\WP_Error { + $resource = urldecode((string) $request->get_param('resource')); + $obj = $this->calDavService->getObject($resource); + if ($obj === null) { + return $this->error('not_found', 'resource not found', 404); + } + return ['data' => $obj]; + }, + ] + ); + + $this->http->registerRestRoute( + self::API_NAMESPACE, + '/caldav/object/(?P[^/]+\.ics)', + [ + 'methods' => 'PUT', + 'permission_callback' => $canWrite, + 'callback' => function ($request): array|\WP_Error { + $resource = urldecode((string) $request->get_param('resource')); + $payload = $this->jsonPayload($request); + if ($payload === null || !isset($payload['ics']) || !is_string($payload['ics'])) { + return $this->error('validation_error', 'ics payload is required', 422); + } + + $ifMatch = $this->requestHeader($request, 'if-match'); + $ifNoneMatch = $this->requestHeader($request, 'if-none-match'); + $result = $this->calDavService->putObject( + $resource, + $payload['ics'], + $ifMatch !== '' ? $ifMatch : null, + $ifNoneMatch !== '' ? $ifNoneMatch : null, + (int) (($this->resolveCalDavUserForRequest($request)['id'] ?? 0)) + ); + if (isset($result['error'])) { + $error = (array) $result['error']; + return $this->error( + (string) ($error['code'] ?? 'caldav_error'), + (string) ($error['message'] ?? 'caldav operation failed'), + (int) ($error['status'] ?? 500) + ); + } + + return ['data' => $result]; + }, + ] + ); + + $this->http->registerRestRoute( + self::API_NAMESPACE, + '/caldav/object/(?P[^/]+\.ics)', + [ + 'methods' => 'DELETE', + 'permission_callback' => $canWrite, + 'callback' => function ($request): array|\WP_Error { + $resource = urldecode((string) $request->get_param('resource')); + $result = $this->calDavService->deleteObject($resource); + if (isset($result['error'])) { + $error = (array) $result['error']; + return $this->error( + (string) ($error['code'] ?? 'caldav_error'), + (string) ($error['message'] ?? 'caldav operation failed'), + (int) ($error['status'] ?? 500) + ); + } + return ['data' => $result]; + }, + ] + ); + + $this->http->registerRestRoute( + self::API_NAMESPACE, + '/caldav/multiget', + [ + 'methods' => 'POST', + 'permission_callback' => $canRead, + 'callback' => function ($request): array|\WP_Error { + $payload = $this->jsonPayload($request); + if ($payload === null || !isset($payload['resources']) || !is_array($payload['resources'])) { + return $this->error('validation_error', 'resources array is required', 422); + } + return ['data' => $this->calDavService->multiget($payload['resources'])]; + }, + ] + ); + } + + private function serveIcsResponse(): void + { + $settings = $this->settingsService->getAll(); + if ( + (string) ($settings['ics_access_mode'] ?? 'public_read') === 'authenticated_read' + && $this->resolveCalDavUserForRequest(null) === null + ) { + http_response_code(401); + header('Content-Type: application/json; charset=utf-8'); + echo json_encode(['error' => ['code' => 'auth_required', 'message' => 'authentication required']]); + return; + } + + $calendarName = (string) ($settings['caldav_calendar_name'] ?? 'Public Calendar'); + $ics = $this->icsService->buildCalendar( + $this->eventService->listEvents(), + fn(int $eventId): array => $this->eventService->getDeletedOccurrenceKeys($eventId), + $calendarName + ); + $etag = '"' . substr(sha1($ics), 0, 16) . '"'; + $lastModified = gmdate('D, d M Y H:i:s') . ' GMT'; + + http_response_code(200); + header('Content-Type: text/calendar; charset=utf-8'); + header('ETag: ' . $etag); + header('Last-Modified: ' . $lastModified); + header('Cache-Control: public, max-age=120'); + echo $ics; + } + + private function serveCalDavPath(string $path, string $method): void + { + $caldavUser = $this->resolveCalDavUserForRequest(null); + if ($caldavUser === null) { + http_response_code(401); + header('WWW-Authenticate: Basic realm="Calendar CalDAV"'); + header('Content-Type: application/xml; charset=utf-8'); + echo 'auth required'; + return; + } + + $root = rtrim($this->caldavRootPath(), '/'); + $principalCollection = $root . '/principals/'; + $principal = $principalCollection . (int) ($caldavUser['id'] ?? 0) . '/'; + $calendarsRoot = $root . '/calendars/'; + $collection = $root . '/calendars/public/'; + $resourcePrefix = $collection; + + if ($method === 'HEAD') { + if ($path === $root || $path === $root . '/' || $path === $calendarsRoot || $path === rtrim($calendarsRoot, '/') || $path === $collection || $path === rtrim($collection, '/')) { + header('Allow: OPTIONS, PROPFIND, REPORT, GET, PUT, DELETE, HEAD'); + header('DAV: 1, calendar-access'); + http_response_code(200); + return; + } + if (str_starts_with($path, $resourcePrefix) && str_ends_with($path, '.ics')) { + $resource = basename($path); + $obj = $this->calDavService->getObject($resource); + if ($obj === null) { + http_response_code(404); + return; + } + header('Content-Type: text/calendar; charset=utf-8'); + header('ETag: ' . (string) ($obj['etag'] ?? '')); + http_response_code(200); + return; + } + } + + if ($method === 'OPTIONS') { + header('Allow: OPTIONS, PROPFIND, REPORT, GET, PUT, DELETE, HEAD'); + header('DAV: 1, calendar-access'); + http_response_code(200); + return; + } + + if ($method === 'PROPFIND') { + header('Content-Type: application/xml; charset=utf-8'); + http_response_code(207); + if ($path === $root || $path === $root . '/') { + echo $this->caldavPropfindRootXml($root, $principal, $calendarsRoot, $collection); + return; + } + if ($path === $principalCollection || $path === rtrim($principalCollection, '/')) { + echo $this->caldavPropfindPrincipalCollectionXml($principalCollection, $principal); + return; + } + if ($path === $principal || $path === rtrim($principal, '/')) { + echo $this->caldavPropfindPrincipalXml($principal, $calendarsRoot); + return; + } + if ($path === $calendarsRoot || $path === rtrim($calendarsRoot, '/')) { + echo $this->caldavPropfindCalendarsRootXml($calendarsRoot, $collection); + return; + } + if ($path === rtrim($collection, '/')) { + $path = $collection; + } + if ($path === $collection) { + echo $this->caldavPropfindCollectionXml($collection, $this->caldavSyncToken(), true); + return; + } + if (str_starts_with($path, $resourcePrefix) && str_ends_with($path, '.ics')) { + $resource = urldecode(basename($path)); + $obj = $this->calDavService->getObject($resource); + if ($obj === null) { + http_response_code(404); + echo 'not found'; + return; + } + echo $this->caldavPropfindObjectXml($collection . $resource, (string) ($obj['etag'] ?? '')); + return; + } + http_response_code(404); + echo 'not found'; + return; + } + + if ($method === 'REPORT' && $path === $collection) { + $body = (string) file_get_contents('php://input'); + header('Content-Type: application/xml; charset=utf-8'); + http_response_code(207); + echo $this->caldavReportXml($collection, $body, $this->caldavSyncToken()); + return; + } + + if (str_starts_with($path, $resourcePrefix) && str_ends_with($path, '.ics')) { + $resource = urldecode(basename($path)); + if ($method === 'GET') { + $obj = $this->calDavService->getObject($resource); + if ($obj === null) { + http_response_code(404); + return; + } + header('Content-Type: text/calendar; charset=utf-8'); + header('ETag: ' . (string) ($obj['etag'] ?? '')); + http_response_code(200); + echo (string) ($obj['ics'] ?? ''); + return; + } + if ($method === 'PUT') { + $raw = (string) file_get_contents('php://input'); + $ifMatch = trim((string) ($_SERVER['HTTP_IF_MATCH'] ?? '')); + $ifNoneMatch = trim((string) ($_SERVER['HTTP_IF_NONE_MATCH'] ?? '')); + $result = $this->calDavService->putObject( + $resource, + $raw, + $ifMatch !== '' ? $ifMatch : null, + $ifNoneMatch !== '' ? $ifNoneMatch : null, + (int) ($caldavUser['id'] ?? 0) + ); + if (isset($result['error'])) { + $error = (array) $result['error']; + http_response_code((int) ($error['status'] ?? 500)); + header('Content-Type: application/xml; charset=utf-8'); + echo '' . esc_html((string) ($error['message'] ?? 'error')) . ''; + return; + } + $status = (int) ($result['status'] ?? 204); + $event = (array) ($result['event'] ?? []); + if (!empty($event['etag'])) { + header('ETag: ' . (string) $event['etag']); + } + http_response_code($status); + return; + } + if ($method === 'DELETE') { + $result = $this->calDavService->deleteObject($resource); + if (isset($result['error'])) { + $error = (array) $result['error']; + http_response_code((int) ($error['status'] ?? 500)); + return; + } + http_response_code(204); + return; + } + } + + http_response_code(405); + header('Allow: OPTIONS, PROPFIND, REPORT, GET, PUT, DELETE, HEAD'); + } + + private function caldavPropfindRootXml(string $root, string $principal, string $calendarsRoot, string $collection): string + { + return '' + . '' + . '' . htmlspecialchars($root . '/', ENT_XML1 | ENT_QUOTES, 'UTF-8') . '' + . '' + . 'CalDAV Root' + . '' . htmlspecialchars($principal, ENT_XML1 | ENT_QUOTES, 'UTF-8') . '' + . 'HTTP/1.1 200 OK' + . '' . htmlspecialchars($calendarsRoot, ENT_XML1 | ENT_QUOTES, 'UTF-8') . '' + . '' + . 'Calendar Home Set' + . 'HTTP/1.1 200 OK' + . '' . htmlspecialchars($collection, ENT_XML1 | ENT_QUOTES, 'UTF-8') . '' + . '' + . '' . htmlspecialchars((string) $this->settingsService->get('caldav_calendar_name', 'Public Calendar'), ENT_XML1 | ENT_QUOTES, 'UTF-8') . '' + . 'HTTP/1.1 200 OK' + . ''; + } + + private function caldavPropfindPrincipalCollectionXml(string $principalCollection, string $principal): string + { + return '' + . '' + . '' . htmlspecialchars($principalCollection, ENT_XML1 | ENT_QUOTES, 'UTF-8') . '' + . '' + . 'Principals' + . 'HTTP/1.1 200 OK' + . '' . htmlspecialchars($principal, ENT_XML1 | ENT_QUOTES, 'UTF-8') . '' + . '' + . 'HTTP/1.1 200 OK' + . ''; + } + + private function caldavPropfindPrincipalXml(string $principal, string $calendarsRoot): string + { + return '' + . '' + . '' . htmlspecialchars($principal, ENT_XML1 | ENT_QUOTES, 'UTF-8') . '' + . '' + . '' . htmlspecialchars($principal, ENT_XML1 | ENT_QUOTES, 'UTF-8') . '' + . '' . htmlspecialchars($calendarsRoot, ENT_XML1 | ENT_QUOTES, 'UTF-8') . '' + . 'HTTP/1.1 200 OK' + . ''; + } + + private function caldavPropfindCalendarsRootXml(string $calendarsRoot, string $collection): string + { + return '' + . '' + . '' . htmlspecialchars($calendarsRoot, ENT_XML1 | ENT_QUOTES, 'UTF-8') . '' + . '' + . 'Calendar Home Set' + . 'HTTP/1.1 200 OK' + . '' . htmlspecialchars($collection, ENT_XML1 | ENT_QUOTES, 'UTF-8') . '' + . '' + . '' . htmlspecialchars((string) $this->settingsService->get('caldav_calendar_name', 'Public Calendar'), ENT_XML1 | ENT_QUOTES, 'UTF-8') . '' + . '' . htmlspecialchars(sha1($this->caldavSyncToken()), ENT_XML1 | ENT_QUOTES, 'UTF-8') . '' + . '' + . $this->caldavSupportedReportSetXml() + . 'HTTP/1.1 200 OK' + . ''; + } + + private function caldavPropfindCollectionXml(string $collection, string $syncToken, bool $includeMembers = false): string + { + $xml = '' + . '' + . '' . htmlspecialchars($collection, ENT_XML1 | ENT_QUOTES, 'UTF-8') . '' + . '' + . '' . htmlspecialchars((string) $this->settingsService->get('caldav_calendar_name', 'Public Calendar'), ENT_XML1 | ENT_QUOTES, 'UTF-8') . '' + . '' . htmlspecialchars(sha1($syncToken), ENT_XML1 | ENT_QUOTES, 'UTF-8') . '' + . '' . htmlspecialchars(sha1($syncToken), ENT_XML1 | ENT_QUOTES, 'UTF-8') . '' + . '' . htmlspecialchars($syncToken, ENT_XML1 | ENT_QUOTES, 'UTF-8') . '' + . '' + . $this->caldavSupportedReportSetXml() + . 'HTTP/1.1 200 OK'; + + if ($includeMembers) { + $resources = $this->calDavService->listResources(); + foreach ($resources as $r) { + $resource = (string) ($r['resource'] ?? ''); + if ($resource === '') { + continue; + } + $etag = (string) ($r['etag'] ?? ''); + $href = $collection . $resource; + $xml .= '' . htmlspecialchars($href, ENT_XML1 | ENT_QUOTES, 'UTF-8') . '' + . '' + . '' . htmlspecialchars($etag, ENT_XML1 | ENT_QUOTES, 'UTF-8') . '' + . 'text/calendar; charset=utf-8' + . 'HTTP/1.1 200 OK'; + } + } + + return $xml . ''; + } + + private function caldavPropfindObjectXml(string $href, string $etag): string + { + return '' + . '' + . '' . htmlspecialchars($href, ENT_XML1 | ENT_QUOTES, 'UTF-8') . '' + . '' + . '' . htmlspecialchars($etag, ENT_XML1 | ENT_QUOTES, 'UTF-8') . '' + . 'text/calendar; charset=utf-8' + . 'HTTP/1.1 200 OK' + . ''; + } + + private function caldavSupportedReportSetXml(): string + { + return '' + . '' + . '' + . '' + . ''; + } + + private function caldavReportXml(string $collection, string $xmlBody, string $syncToken): string + { + $bodyLower = strtolower($xmlBody); + $resources = array_map(static fn(array $r): string => (string) ($r['resource'] ?? ''), $this->calDavService->listResources()); + $items = []; + + if (str_contains($bodyLower, 'sync-collection')) { + $items = $this->calDavService->multiget($resources); + } elseif (str_contains($bodyLower, 'calendar-query')) { + $items = $this->calDavService->multiget($resources); + if (preg_match('/start\\s*=\\s*"([0-9TzZ]+)"/i', $xmlBody, $s) && preg_match('/end\\s*=\\s*"([0-9TzZ]+)"/i', $xmlBody, $e)) { + $start = $this->icalToIso($s[1]); + $end = $this->icalToIso($e[1]); + $items = array_values(array_filter($items, function (array $it) use ($start, $end): bool { + if ((int) ($it['status'] ?? 404) !== 200) { + return false; + } + $ics = (string) ($it['ics'] ?? ''); + $dt = $this->extractFirstDtStartIso($ics); + if ($dt === null) { + return false; + } + return $dt >= $start && $dt <= $end; + })); + } + } else { + preg_match_all('#<[^>]*href[^>]*>([^<]+)]*href>#i', $xmlBody, $matches); + $requested = array_values(array_filter(array_map(static function (string $href): string { + $trimmed = trim($href); + $path = parse_url($trimmed, PHP_URL_PATH); + $candidate = is_string($path) && $path !== '' ? $path : $trimmed; + return urldecode(basename($candidate)); + }, (array) ($matches[1] ?? [])))); + $items = $requested ? $this->calDavService->multiget($requested) : $this->calDavService->multiget($resources); + } + + $responses = ''; + foreach ($items as $item) { + $status = (int) ($item['status'] ?? 404); + $resource = (string) ($item['resource'] ?? ''); + $responses .= '' . htmlspecialchars($collection . $resource, ENT_XML1 | ENT_QUOTES, 'UTF-8') . ''; + if ($status === 200) { + $responses .= '' . htmlspecialchars((string) ($item['etag'] ?? ''), ENT_XML1 | ENT_QUOTES, 'UTF-8') . '' + . '' . htmlspecialchars((string) ($item['ics'] ?? ''), ENT_XML1 | ENT_QUOTES, 'UTF-8') . ''; + } + $responses .= 'HTTP/1.1 ' . $status . ($status === 200 ? ' OK' : ' Not Found') + . ''; + } + return '' + . htmlspecialchars($syncToken, ENT_XML1 | ENT_QUOTES, 'UTF-8') + . '' + . $responses + . ''; + } + + private function caldavSyncToken(): string + { + $rows = $this->calDavService->listResources(); + $seed = ''; + foreach ($rows as $row) { + $seed .= (string) ($row['resource'] ?? '') . '|' . (string) ($row['etag'] ?? '') . ';'; + } + return 'urn:calendar-plugin:sync:' . sha1($seed); + } + + private function icalToIso(string $value): string + { + $value = trim($value); + if (preg_match('/^\\d{8}T\\d{6}Z$/', $value)) { + $dt = \DateTimeImmutable::createFromFormat('Ymd\\THis\\Z', $value, new \DateTimeZone('UTC')); + if ($dt instanceof \DateTimeImmutable) { + return $dt->setTimezone(new \DateTimeZone('Europe/London'))->format('c'); + } + } + if (preg_match('/^\\d{8}T\\d{6}$/', $value)) { + $dt = \DateTimeImmutable::createFromFormat('Ymd\\THis', $value, new \DateTimeZone('Europe/London')); + if ($dt instanceof \DateTimeImmutable) { + return $dt->format('c'); + } + } + return '1970-01-01T00:00:00+00:00'; + } + + private function extractFirstDtStartIso(string $ics): ?string + { + if (!preg_match('/^DTSTART(?:;[^:]+)?:([0-9TzZ]+)$/mi', $ics, $m)) { + return null; + } + return $this->icalToIso((string) $m[1]); + } + + private function unwrapServiceResult(array $result): array|\WP_Error + { + if (isset($result['error']) && is_array($result['error'])) { + $error = (array) $result['error']; + return $this->error( + (string) ($error['code'] ?? 'service_error'), + (string) ($error['message'] ?? 'request failed'), + (int) ($error['status'] ?? 500) + ); + } + return ['data' => $result]; + } + + private function collectDiagnosticsSnapshot(bool $enabled): array + { + $diag = [ + 'diagnostics_enabled' => $enabled, + 'generated_at_utc' => gmdate('c'), + 'db_prefix' => $this->db->getPrefix(), + 'table_stem' => $this->tableStem, + 'ics_path' => $this->icsPath(), + 'caldav_root' => $this->caldavRootPath(), + 'current_user_id' => $this->auth->currentUserId(), + 'current_user_email' => $this->auth->currentUserEmail(), + ]; + if (!$enabled) { + $diag['note'] = 'Diagnostics collection is disabled in Setup.'; + return $diag; + } + + $auditTable = $this->db->getPrefix() . trim($this->tableStem, '_') . '_audit_log'; + $sql = $this->db->prepare( + "SELECT actor, action, target, result, created_at, context_json FROM {$auditTable} ORDER BY id DESC LIMIT %d", + 200 + ); + $rows = $this->db->getResults($sql); + $diag['audit_log_recent'] = array_map( + static function (object $row): array { + return [ + 'actor' => (string) ($row->actor ?? ''), + 'action' => (string) ($row->action ?? ''), + 'target' => (string) ($row->target ?? ''), + 'result' => (string) ($row->result ?? ''), + 'created_at' => (string) ($row->created_at ?? ''), + 'context_json' => (string) ($row->context_json ?? ''), + ]; + }, + $rows + ); + $diag['audit_log_count'] = count($diag['audit_log_recent']); + return $diag; + } + + private function updatePluginTablePrefix(string $currentStem, string $requestedPrefix): array + { + $dbPrefix = (string) $this->db->getPrefix(); + if (!str_starts_with($requestedPrefix, $dbPrefix)) { + return ['ok' => false, 'message' => 'Table prefix must start with WordPress DB prefix: ' . $dbPrefix]; + } + $newStem = trim(substr($requestedPrefix, strlen($dbPrefix)), '_'); + if ($newStem === '') { + return ['ok' => false, 'message' => 'Invalid table prefix.']; + } + if ($newStem === $currentStem) { + return ['ok' => true, 'stem' => $currentStem]; + } + + $suffixes = ['events', 'recurrence_exceptions', 'users', 'user_tokens', 'audit_log']; + foreach ($suffixes as $suffix) { + $from = $dbPrefix . $currentStem . '_' . $suffix; + $to = $dbPrefix . $newStem . '_' . $suffix; + $existsFrom = $this->db->getResults($this->db->prepare('SHOW TABLES LIKE %s', $from)); + if (count($existsFrom) === 0) { + continue; + } + $existsTo = $this->db->getResults($this->db->prepare('SHOW TABLES LIKE %s', $to)); + if (count($existsTo) > 0) { + return ['ok' => false, 'message' => 'Target table already exists: ' . $to]; + } + $this->db->query(sprintf('RENAME TABLE `%s` TO `%s`', $from, $to)); + } + update_option('calendar_plugin_table_stem', $newStem); + return ['ok' => true, 'stem' => $newStem]; + } + + private function adminPageShell(string $title, string $bodyHtml): string + { + $nav = '

Calendar Plugin

'; + return '

' . esc_html($title) . '

' + . '
' . $nav + . '
' . $bodyHtml . '
'; + } + + private function slugPrefix(): string + { + $slug = trim((string) ($this->settingsService->get('url_slug', '') ?? ''), '/'); + return $slug === '' ? '' : ('/' . $slug); + } + + private function icsPath(): string + { + return $this->slugPrefix() . '/calendar.ics'; + } + + private function caldavRootPath(): string + { + return $this->slugPrefix() . '/caldav/'; + } + + private function canWriteCalendar(mixed $request = null): bool + { + if ($this->auth->currentUserCan('edit_posts')) { + return true; + } + return $this->resolveCalDavUserForRequest($request) !== null; + } + + private static function resolveTableStem(): string + { + $stem = 'cs_calendar'; + $saved = ''; + if (function_exists('get_option')) { + $saved = (string) get_option('calendar_plugin_table_stem', ''); + if ($saved !== '') { + $stem = $saved; + } + } + if (defined('CALENDAR_PLUGIN_TABLE_STEM')) { + $stem = (string) CALENDAR_PLUGIN_TABLE_STEM; + } + if (function_exists('apply_filters')) { + /** @var mixed $filtered */ + $filtered = apply_filters('calendar_plugin_table_stem', $stem); + if (is_string($filtered)) { + $stem = $filtered; + } + } + $stem = strtolower(trim($stem)); + $stem = preg_replace('/[^a-z0-9_]/', '', $stem) ?: 'calendar'; + $stem = trim($stem, '_') ?: 'calendar'; + if ($saved === '') { + $detected = self::detectExistingStem(); + if ($detected !== '') { + return $detected; + } + } + return $stem; + } + + private static function detectExistingStem(): string + { + global $wpdb; + if (!isset($wpdb) || !is_object($wpdb) || !property_exists($wpdb, 'prefix')) { + return ''; + } + $prefix = (string) $wpdb->prefix; + $candidates = ['cs_calendar', 'calendar']; + foreach ($candidates as $candidate) { + $table = $prefix . $candidate . '_events'; + $exists = $wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $table)); + if (is_string($exists) && $exists !== '') { + return $candidate; + } + } + return ''; + } + + private function resolveCalDavUserForRequest(mixed $request = null): ?array + { + if ($this->auth->currentUserId() > 0) { + return [ + 'id' => $this->auth->currentUserId(), + 'email' => $this->auth->currentUserEmail(), + 'source' => 'wp', + ]; + } + + $sessionToken = $this->readCalendarSessionTokenFromRequest($request); + if ($sessionToken !== '') { + $sessionUser = $this->userService->authenticateSessionToken($sessionToken); + if ($sessionUser !== null) { + $sessionUser['source'] = 'calendar_session'; + return $sessionUser; + } + } + + [$email, $password] = $this->readBasicAuthCredentialsFromRequest($request); + if ($email === '' || $password === '') { + return null; + } + $user = $this->userService->authenticateActiveUserCredentials($email, $password); + if (!$user) { + return null; + } + $user['source'] = 'calendar_user'; + return $user; + } + + private function readBasicAuthCredentialsFromRequest(mixed $request = null): array + { + $header = ''; + if (is_object($request) && method_exists($request, 'get_header')) { + $header = (string) $request->get_header('authorization'); + } + if ($header === '') { + $header = (string) ($_SERVER['HTTP_AUTHORIZATION'] ?? ''); + } + if ($header === '' && isset($_SERVER['PHP_AUTH_USER'])) { + return [(string) ($_SERVER['PHP_AUTH_USER'] ?? ''), (string) ($_SERVER['PHP_AUTH_PW'] ?? '')]; + } + if ($header === '' || !str_starts_with(strtolower($header), 'basic ')) { + return ['', '']; + } + $decoded = base64_decode(substr($header, 6), true); + if (!is_string($decoded) || !str_contains($decoded, ':')) { + return ['', '']; + } + [$u, $p] = explode(':', $decoded, 2); + return [(string) $u, (string) $p]; + } + + private function readCalendarSessionTokenFromRequest(mixed $request = null): string + { + $name = self::SESSION_COOKIE; + if (isset($_COOKIE[$name]) && is_string($_COOKIE[$name])) { + return trim((string) $_COOKIE[$name]); + } + + $cookieHeader = ''; + if (is_object($request) && method_exists($request, 'get_header')) { + $cookieHeader = (string) $request->get_header('cookie'); + } + if ($cookieHeader === '') { + $cookieHeader = (string) ($_SERVER['HTTP_COOKIE'] ?? ''); + } + if ($cookieHeader === '') { + return ''; + } + + foreach (explode(';', $cookieHeader) as $pair) { + $parts = explode('=', trim($pair), 2); + if (count($parts) !== 2) { + continue; + } + if (trim((string) $parts[0]) !== $name) { + continue; + } + return trim((string) $parts[1]); + } + + return ''; + } + + private function setCalendarSessionCookie(string $token): void + { + if ($token === '') { + return; + } + $secure = $this->isHttpsRequest(); + setcookie(self::SESSION_COOKIE, $token, [ + 'expires' => time() + self::SESSION_TTL_SECONDS, + 'path' => '/', + 'secure' => $secure, + 'httponly' => true, + 'samesite' => 'Lax', + ]); + } + + private function clearCalendarSessionCookie(): void + { + $secure = $this->isHttpsRequest(); + setcookie(self::SESSION_COOKIE, '', [ + 'expires' => time() - 3600, + 'path' => '/', + 'secure' => $secure, + 'httponly' => true, + 'samesite' => 'Lax', + ]); + } + + private function isHttpsRequest(): bool + { + if (!empty($_SERVER['HTTPS']) && (string) $_SERVER['HTTPS'] !== 'off') { + return true; + } + $forwardedProto = strtolower(trim((string) ($_SERVER['HTTP_X_FORWARDED_PROTO'] ?? ''))); + if ($forwardedProto !== '') { + foreach (explode(',', $forwardedProto) as $proto) { + if (trim($proto) === 'https') { + return true; + } + } + } + if (function_exists('home_url')) { + $home = (string) home_url('/'); + if (str_starts_with(strtolower($home), 'https://')) { + return true; + } + } + return false; + } + + private function allowDebugTokens(): bool + { + return defined('CALENDAR_PLUGIN_ALLOW_DEBUG_TOKENS') && CALENDAR_PLUGIN_ALLOW_DEBUG_TOKENS === true; + } + + private function sendUserEmailVerification(string $email, string $token): void + { + if (!function_exists('wp_mail')) { + return; + } + $verifyUrl = (string) home_url($this->verificationPagePath() . '?calendar_verify_token=' . rawurlencode($token)); + @wp_mail( + $email, + 'Verify your calendar account', + "Please verify your calendar account using this link:\n\n{$verifyUrl}\n\nVerification token (for copy/paste):\n{$token}\n" + ); + } + + private function verificationPagePath(): string + { + $path = trim((string) ($this->settingsService->get('verification_page_path', '/calendar') ?? '/calendar')); + if ($path === '') { + $path = '/calendar'; + } + if (!str_starts_with($path, '/')) { + $path = '/' . $path; + } + return '/' . trim($path, '/'); + } + + private function sendPasswordResetEmail(string $email, string $token): void + { + if (!function_exists('wp_mail')) { + return; + } + $resetUrl = (string) home_url('/?calendar_reset_token=' . rawurlencode($token)); + @wp_mail( + $email, + 'Calendar password reset', + "A password reset was requested for your calendar account.\n\nReset link:\n{$resetUrl}\n" + ); + } + + private function sendAdminApprovalRequestEmail(string $email): void + { + if (!function_exists('wp_mail') || !function_exists('get_option')) { + return; + } + $adminEmail = (string) get_option('admin_email', ''); + if ($adminEmail === '') { + return; + } + $url = (string) admin_url('admin.php?page=calendar-plugin-users'); + @wp_mail( + $adminEmail, + 'Calendar user pending approval', + "A new user is pending approval: {$email}\nReview: {$url}\n" + ); + } + + private function jsonPayload($request): ?array + { + if (!is_object($request) || !method_exists($request, 'get_json_params')) { + return null; + } + $payload = $request->get_json_params(); + return is_array($payload) ? $payload : []; + } + + private function requestHeader(object $request, string $name): string + { + if (method_exists($request, 'get_header')) { + $value = $request->get_header($name); + return is_string($value) ? trim($value) : ''; + } + return ''; + } + + private function error(string $code, string $message, int $status): \WP_Error + { + return new \WP_Error($code, $message, ['status' => $status]); + } +} diff --git a/code/src/bootstrap.php b/code/src/bootstrap.php new file mode 100644 index 0000000..9fa19ca --- /dev/null +++ b/code/src/bootstrap.php @@ -0,0 +1,18 @@ +prefix; + +$tables = [ + $prefix . $stem . '_events', + $prefix . $stem . '_recurrence_exceptions', + $prefix . $stem . '_users', + $prefix . $stem . '_user_tokens', + $prefix . $stem . '_audit_log', +]; + +foreach ($tables as $table) { + $wpdb->query("DROP TABLE IF EXISTS `{$table}`"); +} + +$options = [ + 'calendar_plugin_caldav_calendar_name', + 'calendar_plugin_url_slug', + 'calendar_plugin_ics_access_mode', + 'calendar_plugin_diagnostics_enabled', + 'calendar_plugin_uninstall_cleanup_mode', + 'calendar_plugin_table_stem', + 'calendar_plugin_schema_version', +]; +foreach ($options as $optionName) { + delete_option($optionName); +} diff --git a/compatibility-layer/README.md b/compatibility-layer/README.md new file mode 100644 index 0000000..faaa2fa --- /dev/null +++ b/compatibility-layer/README.md @@ -0,0 +1,26 @@ +# Compatibility Layer + +This directory is the canonical local WordPress emulation entrypoint. + +Purpose: +- run the stand-alone harness used for local deterministic testing +- keep emulation tooling out of deployable plugin code (`code/`) + +Current implementation delegates to the existing harness implementation under `fixture/`. + +Primary commands from repository root: + +```bash +./compatibility-layer/reset.sh +./compatibility-layer/seed.sh +./compatibility-layer/run.sh +``` + +Optional archived local checks (fixture-era): + +```bash +./compatibility-layer/smoke.sh +./compatibility-layer/security_smoke.sh +``` + +These delegate to scripts under `fixture-tests/`. Active development validation now targets the remote server first. diff --git a/compatibility-layer/__pycache__/server.cpython-313.pyc b/compatibility-layer/__pycache__/server.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..97aa4c3c790b84d3ee00d61c354d3ef715fe792c GIT binary patch literal 587 zcmZWkL2DC16n>l8?QS~8f<>wYVXFrTq46LQETvHDMJquv2p$5%wwWdao89nsQrw)1 zmkM4gDEJeq|3UEVZONfJUKH`rTOohI8Io$D59ZDH=6&y*?@cO|c`)($-B%ep0KaW< z9&u2G&hCt!1{6oC0QpZ~;M7Cbbbx};00<W*k@_Hu arMt(qVyQ7+eDGy?>^?p7z+J{gqx=_eb($^! literal 0 HcmV?d00001 diff --git a/compatibility-layer/caldav_client_compat_smoke.sh b/compatibility-layer/caldav_client_compat_smoke.sh new file mode 100755 index 0000000..f1ac725 --- /dev/null +++ b/compatibility-layer/caldav_client_compat_smoke.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash +set -euo pipefail +"$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/../fixture-tests/fixture_caldav_client_compat_smoke.sh" "$@" diff --git a/compatibility-layer/e2e_wp_emulation.php b/compatibility-layer/e2e_wp_emulation.php new file mode 100755 index 0000000..471c044 --- /dev/null +++ b/compatibility-layer/e2e_wp_emulation.php @@ -0,0 +1,346 @@ +#!/usr/bin/env php +data['status'] ?? 500; + fwrite(STDERR, "[FAIL] {$context}: {$res->code} {$res->message} ({$status})\n"); + exit(1); + } + return $res; +} + +require __DIR__ . '/wp_emulation.php'; + +$health = unwrap(request('GET', '/calendar/v1/health'), 'health'); +assert_true(($health['status'] ?? '') === 'ok', 'health status should be ok'); + +$GLOBALS['wp_user'] = [ + 'id' => 0, + 'email' => '', + 'caps' => [], +]; +$unauthCreate = request('POST', '/calendar/v1/events', [ + 'title' => 'No Auth Event', + 'start_datetime' => '2026-05-01T09:00:00+01:00', + 'end_datetime' => '2026-05-01T10:00:00+01:00', +]); +assert_true($unauthCreate instanceof WP_Error, 'unauthenticated event create should be denied'); +assert_true(($unauthCreate->data['status'] ?? 0) === 403, 'unauthenticated event create should return 403'); + +$GLOBALS['wp_user'] = [ + 'id' => 1, + 'email' => 'admin@example.test', + 'caps' => ['manage_options', 'edit_posts'], +]; + +$settingsPatch = unwrap( + request('PATCH', '/calendar/v1/settings', ['caldav_calendar_name' => 'Calendar E2E']), + 'patch settings' +); +assert_true(($settingsPatch['data']['caldav_calendar_name'] ?? '') === 'Calendar E2E', 'settings patch must persist'); + +$settingsGet = unwrap(request('GET', '/calendar/v1/settings'), 'get settings'); +assert_true(($settingsGet['data']['caldav_calendar_name'] ?? '') === 'Calendar E2E', 'settings get should reflect patch'); + +$register = unwrap( + request('POST', '/calendar/v1/users/register', ['email' => 'demo@example.test', 'password' => 'pass12345', 'debug_tokens' => true]), + 'register user' +); +$verifyToken = (string) ($register['data']['verify_token'] ?? ''); +assert_true($verifyToken !== '', 'register should issue verify token'); +$userId = (int) (($register['data']['user']['id'] ?? 0)); +assert_true($userId > 0, 'register should create user id'); + +$loginPending = request('POST', '/calendar/v1/users/login', ['email' => 'demo@example.test', 'password' => 'pass12345']); +assert_true($loginPending instanceof WP_Error, 'pending user login should fail'); +assert_true(($loginPending->data['status'] ?? 0) === 401, 'pending user login should return 401'); + +$verified = unwrap(request('POST', '/calendar/v1/users/verify', ['token' => $verifyToken]), 'verify email'); +assert_true(str_contains((string) ($verified['data']['message'] ?? ''), 'admin will review'), 'verify should return approval message'); + +$approved = unwrap(request('PATCH', '/calendar/v1/admin/users/' . $userId . '/approve', []), 'approve user'); +assert_true((string) ($approved['data']['account_status'] ?? '') === 'active', 'approved user status should be active'); + +$loginActive = unwrap( + request('POST', '/calendar/v1/users/login', ['email' => 'demo@example.test', 'password' => 'pass12345']), + 'active user login' +); +assert_true((bool) (($loginActive['data']['ok'] ?? false) === true), 'active user login should succeed'); + +$resetReq = unwrap( + request('POST', '/calendar/v1/users/password/request', ['email' => 'demo@example.test', 'debug_tokens' => true]), + 'request reset' +); +$resetToken = (string) ($resetReq['data']['reset_token'] ?? ''); +assert_true($resetToken !== '', 'password reset request should return reset token'); + +$resetDone = unwrap( + request('POST', '/calendar/v1/users/password/reset', ['token' => $resetToken, 'new_password' => 'newpass123']), + 'reset password' +); +assert_true((bool) (($resetDone['data']['ok'] ?? false) === true), 'password reset should succeed'); + +$loginNewPass = unwrap( + request('POST', '/calendar/v1/users/login', ['email' => 'demo@example.test', 'password' => 'newpass123']), + 'login with new password' +); +assert_true((bool) (($loginNewPass['data']['ok'] ?? false) === true), 'login should work after password reset'); + +$resetReuse = request('POST', '/calendar/v1/users/password/reset', ['token' => $resetToken, 'new_password' => 'anotherpass123']); +assert_true($resetReuse instanceof WP_Error, 'password reset token should be single-use'); +assert_true(($resetReuse->data['status'] ?? 0) === 422, 'reused reset token should return 422'); + +$usersList = unwrap(request('GET', '/calendar/v1/admin/users'), 'list users'); +assert_true(count((array) ($usersList['data'] ?? [])) >= 1, 'admin users list should return at least one row'); + +$removeUser = unwrap(request('DELETE', '/calendar/v1/admin/users/' . $userId), 'remove user'); +assert_true((bool) (($removeUser['data']['deleted'] ?? false) === true), 'admin remove user should succeed'); +$loginRemoved = request('POST', '/calendar/v1/users/login', ['email' => 'demo@example.test', 'password' => 'newpass123']); +assert_true($loginRemoved instanceof WP_Error, 'removed user login should fail'); +assert_true(($loginRemoved->data['status'] ?? 0) === 401, 'removed user login should return 401'); + +$created = unwrap( + request('POST', '/calendar/v1/events', [ + 'title' => 'Emulated Recurring Event', + 'description' => 'e2e', + 'location' => 'Room 1', + 'category' => 'Test', + '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 event' +); +$eventId = (int) ($created['data']['id'] ?? 0); +assert_true($eventId > 0, 'event id should be generated'); + +$invalidEvent = request('POST', '/calendar/v1/events', [ + 'title' => 'Bad Range', + 'start_datetime' => '2026-04-12T12:00:00+01:00', + 'end_datetime' => '2026-04-12T11:00:00+01:00', +]); +assert_true($invalidEvent instanceof WP_Error, 'invalid event with end before start should fail'); +assert_true(($invalidEvent->data['status'] ?? 0) === 422, 'invalid event should return 422'); + +$public = unwrap(request('GET', '/calendar/v1/public/events', ['view' => 'month', 'date' => '2026-04-01']), 'public events'); +$countBefore = (int) ($public['meta']['count'] ?? 0); +assert_true($countBefore >= 3, 'public month view should include recurrence occurrences'); + +$preview = unwrap( + request('POST', '/calendar/v1/events/preview-occurrences', [ + 'event' => [ + 'title' => 'Preview Event', + 'start_datetime' => '2026-04-05T08:00:00+01:00', + 'end_datetime' => '2026-04-05T09:00:00+01:00', + 'repeat_type' => 'weekly', + 'repeat_interval' => 1, + 'repeat_range_mode' => 'count', + 'repeat_count' => 4, + ], + 'from' => '2026-04-01', + 'months' => 1, + ]), + 'preview occurrences' +); +assert_true((int) ($preview['meta']['count'] ?? 0) >= 1, 'preview occurrences should return generated items'); + +$previewBad = request('POST', '/calendar/v1/events/preview-occurrences', [ + 'event' => [ + 'title' => 'Bad Preview', + 'start_datetime' => '2026-04-05T10:00:00+01:00', + 'end_datetime' => '2026-04-05T09:00:00+01:00', + 'repeat_type' => 'weekly', + ], + 'from' => '2026-04-01', + 'months' => 1, +]); +assert_true($previewBad instanceof WP_Error, 'preview occurrences should validate invalid event range'); +assert_true(($previewBad->data['status'] ?? 0) === 422, 'invalid preview payload should return 422'); + +$deleteOne = unwrap( + request('DELETE', '/calendar/v1/events/' . $eventId . '/occurrences/' . rawurlencode('2026-04-02T10:00:00+01:00')), + 'delete occurrence' +); +assert_true(($deleteOne['data']['deleted'] ?? false) === true, 'delete occurrence should succeed'); + +$deleteSameAgain = unwrap( + request('DELETE', '/calendar/v1/events/' . $eventId . '/occurrences/' . rawurlencode('2026-04-02T10:00:00+01:00')), + 'delete same occurrence again' +); +assert_true(($deleteSameAgain['data']['deleted'] ?? false) === true, 'repeat delete occurrence should be idempotent success'); + +$occurrences = unwrap( + request('GET', '/calendar/v1/events/' . $eventId . '/occurrences', ['from' => '2026-04-01', 'months' => 1]), + 'event occurrences' +); +assert_true((int) ($occurrences['meta']['count'] ?? 0) >= 2, 'event occurrences endpoint should return remaining recurrence instances'); + +$publicAfter = unwrap(request('GET', '/calendar/v1/public/events', ['view' => 'month', 'date' => '2026-04-01']), 'public events after delete'); +$days = []; +foreach (($publicAfter['data'] ?? []) as $item) { + $days[] = substr((string) ($item['occurrence_start'] ?? ''), 0, 10); +} +assert_true(!in_array('2026-04-02', $days, true), 'deleted single occurrence should be excluded'); + +$ics = unwrap(request('GET', '/calendar/v1/public/ics'), 'public ics'); +$icsBody = (string) ($ics['data'] ?? ''); +assert_true(str_contains($icsBody, 'BEGIN:VCALENDAR'), 'ics response should include vcalendar envelope'); +assert_true(str_contains($icsBody, 'RRULE:'), 'ics response should include recurrence rule'); +assert_true(str_contains($icsBody, 'EXDATE'), 'ics response should include deleted occurrence as exdate'); + +$monthlyOrdinal = unwrap( + request('POST', '/calendar/v1/events', [ + 'title' => 'Monthly Ordinal', + 'description' => 'monthly weekday parity', + 'location' => 'Room 2', + 'category' => 'Monthly', + 'all_day_event' => false, + 'start_datetime' => '2026-04-05T09:30:00+01:00', + 'end_datetime' => '2026-04-05T10:30:00+01:00', + 'repeat_type' => 'monthly', + 'repeat_interval' => 1, + 'repeat_nth_mode' => 'weekday_of_month', + 'repeat_nth_pos' => -1, + 'repeat_nth_weekday' => 0, + 'repeat_range_mode' => 'count', + 'repeat_count' => 3, + ]), + 'create monthly ordinal event' +); +$monthlyEventId = (int) ($monthlyOrdinal['data']['id'] ?? 0); +assert_true($monthlyEventId > 0, 'monthly ordinal event id should be generated'); +$monthlyOcc = unwrap( + request('GET', '/calendar/v1/events/' . $monthlyEventId . '/occurrences', ['from' => '2026-04-01', 'months' => 3]), + 'list monthly ordinal occurrences' +); +$monthlyDays = array_map( + static fn(array $row): string => substr((string) ($row['occurrence_start'] ?? ''), 0, 10), + (array) ($monthlyOcc['data'] ?? []) +); +assert_true(in_array('2026-04-26', $monthlyDays, true), 'monthly ordinal should include last Sunday of April 2026'); +assert_true(in_array('2026-05-31', $monthlyDays, true), 'monthly ordinal should include last Sunday of May 2026'); + +$resources = unwrap(request('GET', '/calendar/v1/caldav/resources'), 'list caldav resources'); +$resourceList = (array) ($resources['data'] ?? []); +assert_true(count($resourceList) >= 1, 'caldav resources should list at least one object'); +$firstResource = (string) ($resourceList[0]['resource'] ?? ''); +assert_true($firstResource !== '', 'caldav resource should have object name'); + +$getObject = unwrap(request('GET', '/calendar/v1/caldav/object/' . rawurlencode($firstResource)), 'get caldav object'); +$firstEtag = (string) ($getObject['data']['etag'] ?? ''); +assert_true($firstEtag !== '', 'caldav object should include etag'); + +$newResource = 'new-fixture-event.ics'; +$newIcs = implode("\r\n", [ + 'BEGIN:VCALENDAR', + 'VERSION:2.0', + 'PRODID:-//Calendar Plugin E2E//EN', + 'BEGIN:VEVENT', + 'UID:new-fixture-event@calendar-plugin', + 'SUMMARY:Fixture PUT Event', + 'DESCRIPTION:Created via CalDAV PUT', + 'DTSTART;TZID=Europe/London:20260410T093000', + 'DTEND;TZID=Europe/London:20260410T103000', + 'END:VEVENT', + 'END:VCALENDAR', + '', +]); +$createdViaPut = unwrap( + request('PUT', '/calendar/v1/caldav/object/' . $newResource, ['ics' => $newIcs], ['If-None-Match' => '*']), + 'caldav put create' +); +assert_true((bool) (($createdViaPut['data']['created'] ?? false) === true), 'caldav put should create object with if-none-match'); + +$duplicateBlocked = request( + 'PUT', + '/calendar/v1/caldav/object/' . $newResource, + ['ics' => $newIcs], + ['If-None-Match' => '*'] +); +assert_true($duplicateBlocked instanceof WP_Error, 'duplicate create should fail on if-none-match precondition'); +assert_true(($duplicateBlocked->data['status'] ?? 0) === 412, 'duplicate create should return 412'); + +$updatedIcs = implode("\r\n", [ + 'BEGIN:VCALENDAR', + 'VERSION:2.0', + 'PRODID:-//Calendar Plugin E2E//EN', + 'BEGIN:VEVENT', + 'UID:new-fixture-event@calendar-plugin', + 'SUMMARY:Fixture PUT Event Updated', + 'DESCRIPTION:Updated via CalDAV PUT', + 'DTSTART;TZID=Europe/London:20260410T100000', + 'DTEND;TZID=Europe/London:20260410T110000', + 'END:VEVENT', + 'END:VCALENDAR', + '', +]); +$staleUpdate = request( + 'PUT', + '/calendar/v1/caldav/object/' . $newResource, + ['ics' => $updatedIcs], + ['If-Match' => '"stale-etag"'] +); +assert_true($staleUpdate instanceof WP_Error, 'stale etag update should fail with precondition error'); +assert_true(($staleUpdate->data['status'] ?? 0) === 412, 'stale etag update should return 412'); + +$currentObj = unwrap(request('GET', '/calendar/v1/caldav/object/' . $newResource), 'get new resource object'); +$currentEtag = (string) ($currentObj['data']['etag'] ?? ''); +assert_true($currentEtag !== '', 'newly created object should have etag'); + +$updatedOk = unwrap( + request('PUT', '/calendar/v1/caldav/object/' . $newResource, ['ics' => $updatedIcs], ['If-Match' => $currentEtag]), + 'caldav put update with etag' +); +assert_true((bool) (($updatedOk['data']['created'] ?? true) === false), 'caldav put with if-match should update existing object'); + +$multi = unwrap( + request('POST', '/calendar/v1/caldav/multiget', ['resources' => [$newResource, 'missing-event.ics']]), + 'caldav multiget' +); +$multiData = (array) ($multi['data'] ?? []); +assert_true(count($multiData) === 2, 'multiget should return one row per requested resource'); +assert_true((int) ($multiData[0]['status'] ?? 0) === 200, 'multiget should return 200 for existing resource'); +assert_true((int) ($multiData[1]['status'] ?? 0) === 404, 'multiget should return 404 for missing resource'); + +$deletedObj = unwrap(request('DELETE', '/calendar/v1/caldav/object/' . $newResource), 'caldav delete object'); +assert_true((bool) (($deletedObj['data']['deleted'] ?? false) === true), 'caldav delete should remove object'); + +$deletedNotFound = request('GET', '/calendar/v1/caldav/object/' . $newResource); +assert_true($deletedNotFound instanceof WP_Error, 'deleted caldav object should no longer resolve'); +assert_true(($deletedNotFound->data['status'] ?? 0) === 404, 'deleted caldav object should return 404'); + +$sluggedSettings = unwrap( + request('PATCH', '/calendar/v1/settings', ['url_slug' => 'events']), + 'patch slug setting' +); +assert_true(($sluggedSettings['data']['url_slug'] ?? '') === 'events', 'url_slug setting should persist'); +$sluggedCalendarHtml = do_shortcode('[calendar]'); +assert_true(str_contains($sluggedCalendarHtml, 'href="/events/calendar.ics"'), 'calendar shortcode should use slugged ICS path'); +assert_true(str_contains($sluggedCalendarHtml, 'href="/events/caldav/"'), 'calendar shortcode should use slugged CalDAV path'); + +$sidebarHtml = do_shortcode('[calendar_sidebar_upcoming]'); +assert_true(str_contains($sidebarHtml, '
data['status'] ?? 500; + fwrite(STDERR, "[FAIL] {$context}: {$res->code} {$res->message} ({$status})\n"); + exit(1); + } + return $res; +} + +require __DIR__ . '/wp_emulation.php'; + +$html = do_shortcode('[calendar]'); +assert_true(str_contains($html, 'cp-login-btn'), 'calendar shortcode should render login button'); +assert_true(str_contains($html, 'cp-create-btn'), 'calendar shortcode should render create button'); +assert_true(str_contains($html, 'cp-public-list'), 'calendar shortcode should render public list'); +assert_true(str_contains($html, 'cp-verify-btn'), 'calendar shortcode should render verify action'); +assert_true(str_contains($html, 'cp-reset-request-btn'), 'calendar shortcode should render reset action'); +assert_true(str_contains($html, 'cp-future-only'), 'calendar shortcode should render future-only control'); +assert_true(str_contains($html, 'cp-theme'), 'calendar shortcode should render theme selector'); +assert_true(str_contains($html, 'cp-delete-occ-confirm-btn'), 'calendar shortcode should render delete occurrence control'); + +$reg = unwrap(request('POST', '/calendar/v1/users/register', [ + 'email' => 'ui-emu@example.test', + 'password' => 'pass12345', + 'debug_tokens' => true, +]), 'register'); +$verify = (string) ($reg['data']['verify_token'] ?? ''); +$userId = (int) ($reg['data']['user']['id'] ?? 0); +assert_true($verify !== '' && $userId > 0, 'registration should provide token/id in debug mode'); + +unwrap(request('POST', '/calendar/v1/users/verify', ['token' => $verify]), 'verify'); +unwrap(request('PATCH', '/calendar/v1/admin/users/' . $userId . '/approve', []), 'approve'); + +$GLOBALS['wp_user'] = [ + 'id' => 0, + 'email' => '', + 'caps' => [], +]; + +$authHeader = 'Basic ' . base64_encode('ui-emu@example.test:pass12345'); +$me = unwrap(request('GET', '/calendar/v1/users/me', [], ['Authorization' => $authHeader]), 'users me'); +assert_true((string) ($me['data']['email'] ?? '') === 'ui-emu@example.test', 'me endpoint should resolve basic user'); + +$created = unwrap(request('POST', '/calendar/v1/events', [ + 'title' => 'UI Emu Event', + 'description' => 'ui flow', + 'location' => 'Desk', + 'category' => 'Test', + 'all_day_event' => false, + 'start_datetime' => '2026-06-01T10:00:00+01:00', + 'end_datetime' => '2026-06-01T11:00:00+01:00', + 'repeat_type' => 'daily', + 'repeat_interval' => 1, + 'repeat_range_mode' => 'count', + 'repeat_count' => 3, +], ['Authorization' => $authHeader]), 'create event'); +$eventId = (int) ($created['data']['id'] ?? 0); +assert_true($eventId > 0, 'basic-auth user should be able to create event'); + +unwrap(request('DELETE', '/calendar/v1/events/' . $eventId . '/occurrences/' . rawurlencode('2026-06-02T10:00:00+01:00'), [], ['Authorization' => $authHeader]), 'delete occurrence'); +$occ = unwrap(request('GET', '/calendar/v1/events/' . $eventId . '/occurrences', ['from' => '2026-06-01', 'months' => 1], ['Authorization' => $authHeader]), 'list occurrences'); +$days = []; +foreach ((array) ($occ['data'] ?? []) as $row) { + $days[] = substr((string) ($row['occurrence_start'] ?? ''), 0, 10); +} +assert_true(!in_array('2026-06-02', $days, true), 'deleted occurrence should not remain'); + +unwrap(request('DELETE', '/calendar/v1/events/' . $eventId, [], ['Authorization' => $authHeader]), 'delete event'); +$deletedEvent = request('GET', '/calendar/v1/events/' . $eventId, [], ['Authorization' => $authHeader]); +assert_true($deletedEvent instanceof WP_Error, 'deleted event should not resolve'); +assert_true(($deletedEvent->data['status'] ?? 0) === 404, 'deleted event fetch should return 404'); + +$GLOBALS['wp_user'] = [ + 'id' => 1, + 'email' => 'admin@example.test', + 'caps' => ['manage_options', 'edit_posts'], +]; +unwrap(request('DELETE', '/calendar/v1/admin/users/' . $userId, []), 'remove user'); +$GLOBALS['wp_user'] = [ + 'id' => 0, + 'email' => '', + 'caps' => [], +]; +$meAfterRemove = request('GET', '/calendar/v1/users/me', [], ['Authorization' => $authHeader]); +assert_true($meAfterRemove instanceof WP_Error, 'removed user should not authenticate'); +assert_true(($meAfterRemove->data['status'] ?? 0) === 401, 'removed user me endpoint should return 401'); + +echo "[PASS] ui-e2e wp emulation\n"; diff --git a/compatibility-layer/wp-emu-root/wp-admin/includes/upgrade.php b/compatibility-layer/wp-emu-root/wp-admin/includes/upgrade.php new file mode 100644 index 0000000..7f37a7d --- /dev/null +++ b/compatibility-layer/wp-emu-root/wp-admin/includes/upgrade.php @@ -0,0 +1,2 @@ +params = $jsonParams; + foreach ($headers as $k => $v) { + $this->headers[strtolower((string) $k)] = (string) $v; + } + } + + public function get_method(): string + { + return $this->method; + } + + public function get_route(): string + { + return $this->route; + } + + public function get_json_params(): array + { + return $this->jsonParams; + } + + public function set_param(string $key, mixed $value): void + { + $this->params[$key] = $value; + } + + public function get_param(string $key): mixed + { + return $this->params[$key] ?? null; + } + + public function get_header(string $name): string + { + return $this->headers[strtolower($name)] ?? ''; + } +} + +final class WPDB_Compat +{ + public string $prefix = 'wp_'; + public int $insert_id = 0; + + /** @var array>> */ + private array $tables = []; + + /** @var array */ + private array $autoIds = []; + + public function __construct(string $dbPath) + { + // In-memory emulation; path retained for compatibility. + } + + public function get_charset_collate(): string + { + return ''; + } + + public function prepare(string $query, mixed ...$args): string + { + $out = $query; + foreach ($args as $arg) { + $replacement = is_int($arg) ? (string) $arg : ("'" . str_replace("'", "''", (string) $arg) . "'"); + $out = preg_replace('/%[dsf]/', $replacement, $out, 1) ?? $out; + } + return $out; + } + + public function query(string $query): int|false + { + $trimmed = trim($query); + if (preg_match('/^CREATE TABLE IF NOT EXISTS\s+([a-zA-Z0-9_]+)/i', $trimmed, $m)) { + $table = $m[1]; + $this->tables[$table] = $this->tables[$table] ?? []; + $this->autoIds[$table] = $this->autoIds[$table] ?? 0; + return 0; + } + return 0; + } + + public function get_results(string $query): array + { + $q = trim($query); + + if (preg_match('/^SELECT \* FROM\s+([a-zA-Z0-9_]+)\s+ORDER BY\s+id\s+ASC$/i', $q, $m)) { + $rows = $this->tables[$m[1]] ?? []; + usort($rows, static fn(array $a, array $b): int => ((int) ($a['id'] ?? 0)) <=> ((int) ($b['id'] ?? 0))); + return array_map(static fn(array $r): object => (object) $r, $rows); + } + + if (preg_match('/^SELECT\s+\*\s+FROM\s+([a-zA-Z0-9_]+)\s+WHERE\s+id\s*=\s*(\d+)$/i', $q, $m)) { + $row = $this->findById($m[1], (int) $m[2]); + return $row ? [(object) $row] : []; + } + + if (preg_match('/^SELECT\s+\*\s+FROM\s+([a-zA-Z0-9_]+)\s+WHERE\s+caldav_resource\s*=\s*\'([^\']*)\'$/i', $q, $m)) { + $table = $m[1]; + $needle = str_replace("''", "'", $m[2]); + $rows = $this->tables[$table] ?? []; + foreach ($rows as $row) { + if ((string) ($row['caldav_resource'] ?? '') === $needle) { + return [(object) $row]; + } + } + return []; + } + + if (preg_match('/^SELECT\s+occurrence_key\s+FROM\s+([a-zA-Z0-9_]+)\s+WHERE\s+event_id\s*=\s*(\d+)\s+AND\s+exception_type\s*=\s*\'([^\']+)\'$/i', $q, $m)) { + $table = $m[1]; + $eventId = (int) $m[2]; + $type = $m[3]; + $rows = $this->tables[$table] ?? []; + $filtered = array_values(array_filter( + $rows, + static fn(array $r): bool => ((int) ($r['event_id'] ?? 0) === $eventId) && ((string) ($r['exception_type'] ?? '') === $type) + )); + return array_map( + static fn(array $r): object => (object) ['occurrence_key' => (string) ($r['occurrence_key'] ?? '')], + $filtered + ); + } + + return []; + } + + public function get_row(string $query): ?object + { + $rows = $this->get_results($query); + return $rows[0] ?? null; + } + + public function get_var(string $query): mixed + { + $q = trim($query); + if (preg_match('/^SHOW TABLES LIKE\s*\'([^\']+)\'$/i', $q, $m)) { + $table = str_replace("''", "'", $m[1]); + return array_key_exists($table, $this->tables) ? $table : null; + } + $row = $this->get_row($query); + if (!$row) { + return null; + } + $vars = get_object_vars($row); + foreach ($vars as $value) { + return $value; + } + return null; + } + + public function insert(string $table, array $data, array $formats = []): int|false + { + $this->tables[$table] = $this->tables[$table] ?? []; + $this->autoIds[$table] = $this->autoIds[$table] ?? 0; + + $id = ++$this->autoIds[$table]; + $row = ['id' => $id] + $data; + $this->tables[$table][] = $row; + $this->insert_id = $id; + + return 1; + } + + public function update(string $table, array $data, array $where, array $formats = [], array $whereFormats = []): int|false + { + $rows = $this->tables[$table] ?? []; + $updated = 0; + foreach ($rows as $i => $row) { + if (!$this->matchesWhere($row, $where)) { + continue; + } + $rows[$i] = array_merge($row, $data); + $updated++; + } + $this->tables[$table] = $rows; + return $updated; + } + + public function delete(string $table, array $where, array $whereFormats = []): int|false + { + $rows = $this->tables[$table] ?? []; + $kept = []; + $deleted = 0; + foreach ($rows as $row) { + if ($this->matchesWhere($row, $where)) { + $deleted++; + continue; + } + $kept[] = $row; + } + $this->tables[$table] = $kept; + return $deleted; + } + + private function findById(string $table, int $id): ?array + { + $rows = $this->tables[$table] ?? []; + foreach ($rows as $row) { + if ((int) ($row['id'] ?? 0) === $id) { + return $row; + } + } + return null; + } + + private function matchesWhere(array $row, array $where): bool + { + foreach ($where as $k => $v) { + if (!array_key_exists($k, $row)) { + return false; + } + if ((string) $row[$k] !== (string) $v) { + return false; + } + } + return true; + } +} + +$GLOBALS['wp_actions'] = []; +$GLOBALS['wp_shortcodes'] = []; +$GLOBALS['wp_routes'] = []; +$GLOBALS['wp_options'] = []; +$GLOBALS['wp_mail_outbox'] = []; +$GLOBALS['wp_user'] = [ + 'id' => 1, + 'email' => 'admin@example.test', + 'caps' => ['manage_options', 'edit_posts'], +]; +$GLOBALS['wp_activation_hooks'] = []; +$GLOBALS['wp_admin_menu'] = []; +$GLOBALS['wp_options']['admin_email'] = 'admin@example.test'; + +function add_action(string $hook, callable $callback, int $priority = 10, int $acceptedArgs = 1): void +{ + $GLOBALS['wp_actions'][$hook][] = $callback; +} + +function do_action(string $hook, mixed ...$args): void +{ + foreach (($GLOBALS['wp_actions'][$hook] ?? []) as $cb) { + $cb(...$args); + } +} + +function add_shortcode(string $tag, callable $callback): void +{ + $GLOBALS['wp_shortcodes'][$tag] = $callback; +} + +function do_shortcode(string $content): string +{ + if (preg_match('/\[([a-z0-9_\-]+)\]/i', $content, $m)) { + $tag = $m[1]; + if (isset($GLOBALS['wp_shortcodes'][$tag])) { + return (string) call_user_func($GLOBALS['wp_shortcodes'][$tag]); + } + } + return $content; +} + +function register_rest_route(string $namespace, string $route, array $args): void +{ + $GLOBALS['wp_routes'][] = [ + 'namespace' => '/' . trim($namespace, '/'), + 'route' => $route, + 'args' => $args, + ]; +} + +function rest_do_request(WP_REST_Request $request): array|WP_Error +{ + $method = strtoupper($request->get_method()); + $path = '/' . trim($request->get_route(), '/'); + + foreach ($GLOBALS['wp_routes'] as $entry) { + $namespace = $entry['namespace']; + $routePattern = $entry['route']; + $args = $entry['args']; + + $registeredMethods = strtoupper((string) ($args['methods'] ?? 'GET')); + if ($registeredMethods !== $method) { + continue; + } + + $regex = '#^' . preg_quote($namespace, '#') . preg_replace('#/#', '\\/', $routePattern) . '$#'; + if (!preg_match($regex, $path, $matches)) { + continue; + } + + foreach ($matches as $k => $v) { + if (is_string($k)) { + $request->set_param($k, $v); + } + } + + $perm = $args['permission_callback'] ?? '__return_true'; + $allowed = is_callable($perm) ? (bool) call_user_func($perm, $request) : false; + if (!$allowed) { + return new WP_Error('forbidden', 'Forbidden', ['status' => 403]); + } + + $cb = $args['callback'] ?? null; + if (!is_callable($cb)) { + return new WP_Error('server_error', 'Route callback missing', ['status' => 500]); + } + + return call_user_func($cb, $request); + } + + return new WP_Error('not_found', 'Route not found', ['status' => 404]); +} + +function register_activation_hook(string $pluginFile, callable $callback): void +{ + $GLOBALS['wp_activation_hooks'][$pluginFile] = $callback; +} + +function register_deactivation_hook(string $pluginFile, callable $callback): void +{ + $GLOBALS['wp_activation_hooks'][$pluginFile . ':deactivate'] = $callback; +} + +function get_option(string $key, mixed $default = false): mixed +{ + return array_key_exists($key, $GLOBALS['wp_options']) ? $GLOBALS['wp_options'][$key] : $default; +} + +function update_option(string $key, mixed $value, bool $autoload = true): bool +{ + $GLOBALS['wp_options'][$key] = $value; + return true; +} + +function delete_option(string $key): bool +{ + unset($GLOBALS['wp_options'][$key]); + return true; +} + +function get_current_user_id(): int +{ + return (int) ($GLOBALS['wp_user']['id'] ?? 0); +} + +function current_user_can(string $capability): bool +{ + return in_array($capability, (array) ($GLOBALS['wp_user']['caps'] ?? []), true); +} + +function wp_verify_nonce(string $nonce, string $action): bool +{ + return $nonce === 'ok:' . $action; +} + +function wp_get_current_user(): object +{ + return (object) ['user_email' => (string) ($GLOBALS['wp_user']['email'] ?? '')]; +} + +function esc_html(string $value): string +{ + return htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); +} + +function __return_true(): bool +{ + return true; +} + +if (!function_exists('dbDelta')) { + function dbDelta(string $sql): array + { + return []; + } +} + +function add_menu_page(string $pageTitle, string $menuTitle, string $capability, string $menuSlug, callable $callback): void +{ + $GLOBALS['wp_admin_menu'][$menuSlug] = ['title' => $menuTitle, 'callback' => $callback]; +} + +function add_submenu_page(string $parentSlug, string $pageTitle, string $menuTitle, string $capability, string $menuSlug, callable $callback): void +{ + $GLOBALS['wp_admin_menu'][$menuSlug] = ['title' => $menuTitle, 'parent' => $parentSlug, 'callback' => $callback]; +} + +function admin_url(string $path = ''): string +{ + return '/wp-admin/' . ltrim($path, '/'); +} + +function home_url(string $path = ''): string +{ + return 'http://localhost' . (str_starts_with($path, '/') ? $path : ('/' . $path)); +} + +function wp_mail(string|array $to, string $subject, string $message): bool +{ + $GLOBALS['wp_mail_outbox'][] = ['to' => $to, 'subject' => $subject, 'message' => $message]; + return true; +} + +function wp_generate_password(int $length = 12, bool $specialChars = true): string +{ + $bytes = random_bytes(max(1, intdiv($length + 1, 2))); + return substr(bin2hex($bytes), 0, $length); +} + +if (!defined('ABSPATH')) { +define('ABSPATH', __DIR__ . '/wp-emu-root/'); +define('CALENDAR_PLUGIN_ALLOW_DEBUG_TOKENS', true); +} + +$upgradePath = ABSPATH . 'wp-admin/includes/upgrade.php'; +@mkdir(dirname($upgradePath), 0775, true); +file_put_contents( + $upgradePath, + "query('CREATE TABLE IF NOT EXISTS wp_calendar_events (id INTEGER PRIMARY KEY AUTOINCREMENT)'); +$wpdb->query('CREATE TABLE IF NOT EXISTS wp_calendar_recurrence_exceptions (id INTEGER PRIMARY KEY AUTOINCREMENT)'); + +require_once dirname(__DIR__) . '/code/calendar-plugin.php'; + +do_action('init'); +do_action('rest_api_init'); + +if (PHP_SAPI === 'cli' && basename((string) ($_SERVER['SCRIPT_FILENAME'] ?? '')) === 'wp_emulation.php') { + echo "wp-emulation booted\n"; + echo 'routes=' . count($GLOBALS['wp_routes']) . "\n"; +} diff --git a/credentials/.env b/credentials/.env new file mode 100644 index 0000000..c302f82 --- /dev/null +++ b/credentials/.env @@ -0,0 +1,48 @@ +# Dummy remote testing server environment settings +# Replace all placeholder values before real use. + +# Remote host access +REMOTE_HOST=cs.chezstephens.org.uk +REMOTE_PORT=22 +REMOTE_USER=root +REMOTE_SSH_KEY_PATH=credentials/id_rsa +REMOTE_APP_DIR=/var/www/wordpress/wp-content/plugins/calendar-plugin + +# Remote runtime +REMOTE_ENV=testing +REMOTE_TIMEZONE=Europe/London +REMOTE_PHP_BIN=/usr/bin/php +REMOTE_WP_CLI=/usr/local/bin/wp + +# WordPress context (testing only) +WP_PATH=/var/www/html +WP_URL=https://chezstephens.org.uk +WP_PLUGIN_SLUG=calendar-plugin + +# Database placeholders (testing only) +DB_HOST=127.0.0.1 +DB_PORT=3306 +DB_NAME=wordpress_test +DB_USER=wp_test_user +DB_PASSWORD=change_me + +# Deployment/package settings +PACKAGE_DIR=/home/wp_tester/releases +PACKAGE_NAME=calendar-plugin-0.0.0.zip +KEEP_DATA_ON_UNINSTALL=true + +# Local testing database (Debian 13 / MariaDB) +LOCAL_DB_HOST=localhost +LOCAL_DB_PORT=3306 +LOCAL_DB_NAME=calendar_plugin_test +LOCAL_DB_USER=calendar_plugin_test_user +LOCAL_DB_PASSWORD=38Kgw7WY5w9xJpwyFcnK + +# SMTP settings for fixture email workflows (register/verify/reset/request-write) +SMTP_HOST=mail.chezstephens.org.uk +SMTP_PORT=587 +SMTP_USE_TLS=true +SMTP_USERNAME=adrians@chezstephens.org.uk +SMTP_PASSWORD=buck..it +SMTP_FROM=adrians@chezstephens.org.uk +SMTP_ADMIN_TO=adrians@chezstephens.org.uk diff --git a/credentials/id_rsa b/credentials/id_rsa new file mode 100644 index 0000000..30a58de --- /dev/null +++ b/credentials/id_rsa @@ -0,0 +1,38 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +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 +szaVxAD1qt0hyHAAAACGNzYmFja3VwAQI= +-----END OPENSSH PRIVATE KEY----- diff --git a/docs/fixture.md b/docs/fixture.md new file mode 100644 index 0000000..7f6a9aa --- /dev/null +++ b/docs/fixture.md @@ -0,0 +1,118 @@ +# Local Compatibility Harness + +## Purpose +Run a local, deterministic compatibility harness that exercises the current requirements for: + +- Admin navigation pages (`Edit Calendar`, `Users`, `Setup`) +- API CRUD and user lifecycle endpoints +- Shared-calendar CalDAV read/write flows +- ICS export endpoint + +Seed data includes canonical test fixtures `CE-001` through `CE-010` from `tests/calendar_entries.md`. + +## Quick Start +From repository root: + +```bash +./compatibility-layer/reset.sh +./compatibility-layer/seed.sh +./compatibility-layer/run.sh +``` + +Server default: + +- `http://127.0.0.1:8080` + +## WP Emulation Check +Run the stand-alone local WordPress emulation check (loads plugin code from `code/` and runs a minimal E2E flow): + +```bash +./compatibility-layer/e2e_wp_emulation.php +``` + +## Harness Authentication Model +This harness uses simple test auth headers/credentials to emulate behavior. + +### Admin/API (WordPress-style harness auth) +- Header: `X-WP-User: admin` (full admin) +- Header: `X-WP-User: editor` (editor-limited) + +### CalDAV (plugin user basic auth over local HTTP harness) +Seeded users: + +- `rw_user@example.test` / `rwpass123456` (`write`) +- `ro_user@example.test` / `ropass123456` (`read_only`) + +## Key Endpoints + +### Admin Pages +- `/wp-admin/admin.php?page=calendar-edit&as=admin` +- `/wp-admin/admin.php?page=calendar-users&as=admin` +- `/wp-admin/admin.php?page=calendar-setup&as=admin` + +Interactive pages available: + +- Public calendar UI: `/calendar` +- User self-service portal: `/users` + +If setup `url_slug` is configured (for example `demo`), canonical URLs move under that prefix (for example `/demo/calendar`, `/demo/users`, `/demo/wp-json/calendar/v1/...`, `/demo/calendar.ics`, `/demo/caldav/...`). + +### API +- `/wp-json/calendar/v1/events` +- `/wp-json/calendar/v1/events/{id}` +- `/wp-json/calendar/v1/events/{id}/occurrences/{occurrence_key}` +- `/wp-json/calendar/v1/users/register` +- `/wp-json/calendar/v1/users/verify` +- `/wp-json/calendar/v1/users/forgot-password` +- `/wp-json/calendar/v1/users/reset-password` +- `/wp-json/calendar/v1/users/{id}/request-write` +- `/wp-json/calendar/v1/admin/users` +- `/wp-json/calendar/v1/admin/users/{id}` +- `/wp-json/calendar/v1/admin/setup` +- `/wp-json/calendar/v1/public/events` +- `/wp-json/calendar/v1/users/me` + +### ICS +- `/calendar.ics` + +### CalDAV (single shared public calendar) +- `/caldav/` +- `/caldav/calendars/` +- `/caldav/calendars/public/` +- `/caldav/calendars/public/{object_id}.ics` + +## Smoke Commands + +List events: + +```bash +curl -sS -H 'X-WP-User: admin' http://127.0.0.1:8080/wp-json/calendar/v1/events +``` + +Delete one recurring occurrence as exception: + +```bash +curl -sS -X DELETE \ + -H 'X-WP-User: admin' \ + "http://127.0.0.1:8080/wp-json/calendar/v1/events/10/occurrences/2026-04-17T14:00:00+01:00" \ + -i +``` + +Fetch ICS: + +```bash +curl -sS http://127.0.0.1:8080/calendar.ics +``` + +CalDAV read as read-only user: + +```bash +curl -sS -u ro_user@example.test:ropass123456 \ + http://127.0.0.1:8080/caldav/calendars/public/1.ics -i +``` + +## Notes +- This compatibility harness is intentionally minimal and deterministic for local development. +- It is not a production security implementation. +- It enforces the shared-calendar model with per-user read/write permissions. +- Current implementation delegates to `fixture/` internals via wrapper scripts in `compatibility-layer/`. diff --git a/fixture-tests/README.md b/fixture-tests/README.md new file mode 100644 index 0000000..2cec17d --- /dev/null +++ b/fixture-tests/README.md @@ -0,0 +1,9 @@ +# Fixture Tests (Archived) + +These tests are retained from the local fixture/harness phase. + +- `fixture_smoke.sh` +- `fixture_security_smoke.sh` +- `fixture_caldav_client_compat_smoke.sh` + +Current development validation is remote-server-first. Keep these scripts for legacy comparison/debug only. diff --git a/fixture-tests/fixture_caldav_client_compat_smoke.sh b/fixture-tests/fixture_caldav_client_compat_smoke.sh new file mode 100755 index 0000000..8a8b43d --- /dev/null +++ b/fixture-tests/fixture_caldav_client_compat_smoke.sh @@ -0,0 +1,77 @@ +#!/usr/bin/env bash +set -euo pipefail + +BASE_URL="${BASE_URL:-http://127.0.0.1:8080}" +CALDAV_USER="${CALDAV_USER:-rw_user@example.test}" +CALDAV_PASSWORD="${CALDAV_PASSWORD:-rwpass123456}" + +require_contains() { + local haystack="$1" + local needle="$2" + local msg="$3" + if ! printf '%s' "$haystack" | grep -q "$needle"; then + echo "[caldav-compat] FAIL: $msg" + exit 1 + fi +} + +require_contains_ci() { + local haystack="$1" + local needle="$2" + local msg="$3" + if ! printf '%s' "$haystack" | grep -qi "$needle"; then + echo "[caldav-compat] FAIL: $msg" + exit 1 + fi +} + +echo "[caldav-compat] checking unauthenticated challenge" +unauth_headers="$(curl -sS -i "$BASE_URL/caldav/" | tr -d '\r')" +require_contains "$unauth_headers" ' 401 ' "expected 401 from /caldav/" +require_contains_ci "$unauth_headers" 'www-authenticate: basic' "missing WWW-Authenticate Basic challenge" + +echo "[caldav-compat] checking OPTIONS capability advertisement" +opt_headers="$(curl -sS -i -u "$CALDAV_USER:$CALDAV_PASSWORD" -X OPTIONS "$BASE_URL/caldav/" | tr -d '\r')" +require_contains_ci "$opt_headers" 'dav:' "missing DAV header" +require_contains_ci "$opt_headers" 'calendar-access' "missing DAV calendar-access advertisement" +require_contains_ci "$opt_headers" 'allow: ' "missing Allow header for CalDAV OPTIONS" +require_contains_ci "$opt_headers" 'options' "Allow header missing OPTIONS" +require_contains_ci "$opt_headers" 'propfind' "Allow header missing PROPFIND" +require_contains_ci "$opt_headers" 'report' "Allow header missing REPORT" + +echo "[caldav-compat] checking root PROPFIND discovery" +root_xml="$(curl -fsS -u "$CALDAV_USER:$CALDAV_PASSWORD" -X PROPFIND \ + -H 'Depth: 1' \ + -H 'Content-Type: application/xml' \ + --data '' \ + "$BASE_URL/caldav/")" +require_contains "$root_xml" '' "root discovery missing current-user-principal" +require_contains "$root_xml" '/caldav/calendars/public/' "root discovery missing public calendar href" +require_contains "$root_xml" '' "root discovery missing calendar collection marker" + +principal_href="$(printf '%s' "$root_xml" | grep -oE '/caldav/principals/[^<"]+/' | head -n 1 || true)" +if [ -z "$principal_href" ]; then + echo "[caldav-compat] FAIL: unable to locate principal href in root PROPFIND" + exit 1 +fi + +echo "[caldav-compat] checking principal calendar-home-set" +principal_xml="$(curl -fsS -u "$CALDAV_USER:$CALDAV_PASSWORD" -X PROPFIND \ + -H 'Depth: 0' \ + -H 'Content-Type: application/xml' \ + --data '' \ + "$BASE_URL$principal_href")" +require_contains "$principal_xml" '' "principal missing calendar-home-set" +require_contains "$principal_xml" '/caldav/calendars/' "calendar-home-set does not point to /caldav/calendars/" + +echo "[caldav-compat] checking calendars home-set collection listing" +home_xml="$(curl -fsS -u "$CALDAV_USER:$CALDAV_PASSWORD" -X PROPFIND \ + -H 'Depth: 1' \ + -H 'Content-Type: application/xml' \ + --data '' \ + "$BASE_URL/caldav/calendars/")" +require_contains "$home_xml" '/caldav/calendars/public/' "calendar home-set listing missing public calendar href" +require_contains "$home_xml" '' "calendar home-set listing missing calendar marker" +require_contains "$home_xml" '' "calendar home-set listing missing supported component set" + +echo "[caldav-compat] all checks passed" diff --git a/fixture-tests/fixture_security_smoke.sh b/fixture-tests/fixture_security_smoke.sh new file mode 100755 index 0000000..6b3e8c4 --- /dev/null +++ b/fixture-tests/fixture_security_smoke.sh @@ -0,0 +1,110 @@ +#!/usr/bin/env bash +set -euo pipefail + +BASE_URL="${BASE_URL:-http://127.0.0.1:8080}" +DB_PATH="${DB_PATH:-fixture/fixture.db}" +RUN_ID="${RUN_ID:-$(date +%s)}" + +echo "[security-smoke] checking service readiness" +curl -fsS -H 'X-WP-User: admin' "$BASE_URL/wp-json/calendar/v1/events" >/dev/null + +echo "[security-smoke] checking register token non-disclosure" +register_email="sec-nodisclose-${RUN_ID}@example.test" +reg_code="$(curl -sS -o /tmp/sec_register.json -w '%{http_code}' -H 'Content-Type: application/json' \ + -d "{\"email\":\"${register_email}\",\"password\":\"strongpass123\"}" \ + "$BASE_URL/wp-json/calendar/v1/users/register")" +if [ "$reg_code" != "200" ] && [ "$reg_code" != "201" ] && [ "$reg_code" != "409" ]; then + echo "[security-smoke] FAIL: unexpected register status $reg_code" + exit 1 +fi +reg_payload="$(cat /tmp/sec_register.json)" +if echo "$reg_payload" | grep -q 'verification_token_fixture'; then + echo "[security-smoke] FAIL: register response leaked verification token" + exit 1 +fi + +echo "[security-smoke] checking forgot-password token non-disclosure" +forgot_payload="$(curl -fsS -H 'Content-Type: application/json' \ + -d '{"email":"adrians@chezstephens.org.uk"}' \ + "$BASE_URL/wp-json/calendar/v1/users/forgot-password")" +if echo "$forgot_payload" | grep -q 'reset_token_fixture'; then + echo "[security-smoke] FAIL: forgot-password response leaked reset token" + exit 1 +fi + +echo "[security-smoke] checking register rate limiting" +seen_429=0 +rate_prefix="sec-rate-${RUN_ID}" +for i in $(seq 1 10); do + code="$(curl -sS -o /tmp/sec_reg_$i.json -w '%{http_code}' \ + -H 'Content-Type: application/json' \ + -d "{\"email\":\"${rate_prefix}-$i@example.test\",\"password\":\"strongpass123\"}" \ + "$BASE_URL/wp-json/calendar/v1/users/register")" + if [ "$code" = "429" ]; then + seen_429=1 + break + fi +done +if [ "$seen_429" -ne 1 ]; then + echo "[security-smoke] FAIL: expected 429 from register rate limiter" + exit 1 +fi + +echo "[security-smoke] checking password hash format in database" +python3 - "$DB_PATH" <<'PY' +import sqlite3 +import sys + +db_path = sys.argv[1] +conn = sqlite3.connect(db_path) +cur = conn.cursor() +rows = cur.execute("SELECT email, password_hash FROM caldav_users").fetchall() +conn.close() +if not rows: + print("[security-smoke] FAIL: no users found") + raise SystemExit(1) +for email, pw_hash in rows: + if not isinstance(pw_hash, str) or not pw_hash.startswith("pbkdf2_sha256$"): + print(f"[security-smoke] FAIL: non-pbkdf2 hash for {email}: {pw_hash!r}") + raise SystemExit(1) +print("[security-smoke] password hash format OK") +PY + +echo "[security-smoke] checking strict CalDAV resource filename semantics" +resource="sec-$(date +%s).ics" +cat > /tmp/sec_caldav.ics <<'ICS' +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Security Smoke//EN +BEGIN:VEVENT +UID:sec-smoke-uid@example.test +SUMMARY:Security Smoke Event +DTSTART:20260415T100000 +DTEND:20260415T110000 +END:VEVENT +END:VCALENDAR +ICS +put_code="$(curl -sS -o /tmp/sec_caldav_put.json -w '%{http_code}' \ + -u adrians@chezstephens.org.uk:brillig1 \ + -X PUT --data-binary @/tmp/sec_caldav.ics \ + "$BASE_URL/caldav/calendars/public/$resource")" +if [ "$put_code" != "201" ] && [ "$put_code" != "200" ]; then + echo "[security-smoke] FAIL: expected 200/201 for CalDAV PUT, got $put_code" + exit 1 +fi +get_code="$(curl -sS -o /tmp/sec_caldav_get.ics -w '%{http_code}' \ + -u adrians@chezstephens.org.uk:brillig1 \ + "$BASE_URL/caldav/calendars/public/$resource")" +if [ "$get_code" != "200" ]; then + echo "[security-smoke] FAIL: expected 200 for CalDAV GET on same resource, got $get_code" + exit 1 +fi +missing_code="$(curl -sS -o /tmp/sec_caldav_missing.out -w '%{http_code}' \ + -u adrians@chezstephens.org.uk:brillig1 \ + "$BASE_URL/caldav/calendars/public/999999.ics")" +if [ "$missing_code" != "404" ]; then + echo "[security-smoke] FAIL: expected 404 for unknown numeric resource, got $missing_code" + exit 1 +fi + +echo "[security-smoke] all checks passed" diff --git a/fixture-tests/fixture_smoke.sh b/fixture-tests/fixture_smoke.sh new file mode 100755 index 0000000..b7c0bde --- /dev/null +++ b/fixture-tests/fixture_smoke.sh @@ -0,0 +1,330 @@ +#!/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' + + + + /caldav/calendars/public/4.ics + +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" diff --git a/fixture/__pycache__/server.cpython-313.pyc b/fixture/__pycache__/server.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d4f0cde5e35704c968ae03efe877111b6eed8265 GIT binary patch literal 221673 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\n\n\n \n \n Calendar\n \n\n\n\n
Calendar Fixture Public Calendar UI
\n
\n \n
\n
\n
\n

Account Login

\n
\n
\n
\n
\n
\n
\n \n \n
\n
\n

Register

\n
\n
\n
\n
\n
\n
\n
\n

Password Recovery

\n
\n \n
\n
\n \n
\n
\n
\n
\n
\n

Event Details

\n
\n
\n
\n
\n
\n
\n
\n
\n

Event

\n \n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n \n
\n
\n
\n
\n
\n \n
\n
\n
\n
\n
\n
\n
\n
\n \n
\n
\n
\n
\n
\n
\n
\n
\n \n \n \n \n \n
\n
\n

Delete Single Occurrence (Exception)

\n
\n \n \n
\n
\n \n
\n \n
\n
\n
\n
\n\n\n\n", "body_truncated": false}} +{"ts": "2026-03-30T14:08:54+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:18080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:08:54 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "6585"}, "body": "HTTP/1.0 200 OK\r\nServer: CalendarFixture/0.1 Python/3.13.5\r\nDate: Mon, 30 Mar 2026 14:08:54 GMT\r\nContent-Type: application/json; charset=utf-8\r\nContent-Length: 6585\r\n\r\n{\"data\": [{\"id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"start_datetime\": \"2026-04-01T10:00:00+01:00\", \"end_datetime\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"b2246c0ba36be704\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T14:08:53+00:00\"}, {\"id\": 2, \"uid\": \"fixture-ce-002@calendar-wp-plugin\", \"title\": \"Office Closed\", \"description\": \"Public holiday closure.\", \"location\": \"HQ\", \"category\": \"Operations\", \"all_day_event\": true, \"start_datetime\": \"2026-05-04\", \"end_datetime\": \"2026-05-05\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"97ffb830a549901c\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T14:08:53+00:00\"}, {\"id\": 3, \"uid\": \"fixture-ce-003@calendar-wp-plugin\", \"title\": \"Daily Standup\", \"description\": \"15 minute sync.\", \"location\": \"Online\", \"category\": \"Team\", \"all_day_event\": false, \"start_datetime\": \"2026-04-06T09:00:00+01:00\", \"end_datetime\": \"2026-04-06T09:15:00+01:00\", \"repeat_type\": \"daily\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"count\", \"repeat_count\": 10, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"0f947e3952fcf681\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T14:08:53+00:00\"}, {\"id\": 4, \"uid\": \"fixture-ce-004@calendar-wp-plugin\", \"title\": \"Community Lunch\", \"description\": \"Weekly community lunch.\", \"location\": \"Cafeteria\", \"category\": \"Community\", \"all_day_event\": false, \"start_datetime\": \"2026-04-08T12:30:00+01:00\", \"end_datetime\": \"2026-04-08T13:30:00+01:00\", \"repeat_type\": \"weekly\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"no_end\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"4f06e2ef4d6d8e38\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T14:08:53+00:00\"}, {\"id\": 5, \"uid\": \"fixture-ce-005@calendar-wp-plugin\", \"title\": \"Finance Close\", \"description\": \"Month-end close process.\", \"location\": \"Finance Office\", \"category\": \"Finance\", \"all_day_event\": false, \"start_datetime\": \"2026-04-30T17:00:00+01:00\", \"end_datetime\": \"2026-04-30T18:00:00+01:00\", \"repeat_type\": \"monthly\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"until\", \"repeat_count\": null, \"repeat_until\": \"2026-08-31\", \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"927953eb5c8dd26c\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T14:08:53+00:00\"}, {\"id\": 6, \"uid\": \"fixture-ce-006@calendar-wp-plugin\", \"title\": \"Annual Conference\", \"description\": \"Annual community conference.\", \"location\": \"Main Hall\", \"category\": \"Events\", \"all_day_event\": false, \"start_datetime\": \"2026-06-15T10:00:00+01:00\", \"end_datetime\": \"2026-06-15T17:00:00+01:00\", \"repeat_type\": \"yearly\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"count\", \"repeat_count\": 3, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"93cf1bad962c75f4\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T14:08:53+00:00\"}, {\"id\": 7, \"uid\": \"fixture-ce-007@calendar-wp-plugin\", \"title\": \"Fortnightly Coaching\", \"description\": \"Coaching check-in.\", \"location\": \"Online\", \"category\": \"Training\", \"all_day_event\": false, \"start_datetime\": \"2026-04-07T15:00:00+01:00\", \"end_datetime\": \"2026-04-07T16:00:00+01:00\", \"repeat_type\": \"custom\", \"repeat_interval\": 2, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"until\", \"repeat_count\": null, \"repeat_until\": \"2026-07-31\", \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"7201c1345f1b2573\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T14:08:53+00:00\"}, {\"id\": 8, \"uid\": \"fixture-ce-008@calendar-wp-plugin\", \"title\": \"DST Validation Event\", \"description\": \"Validates DST transition rendering.\", \"location\": \"Lab\", \"category\": \"QA\", \"all_day_event\": false, \"start_datetime\": \"2026-10-25T00:30:00+01:00\", \"end_datetime\": \"2026-10-25T02:30:00+00:00\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"a81452cded01c6a1\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T14:08:53+00:00\"}, {\"id\": 9, \"uid\": \"fixture-ce-009@calendar-wp-plugin\", \"title\": \"Leap Day Marker\", \"description\": \"Leap day recurrence behavior.\", \"location\": \"Calendar\", \"category\": \"QA\", \"all_day_event\": false, \"start_datetime\": \"2028-02-29T09:00:00+00:00\", \"end_datetime\": \"2028-02-29T10:00:00+00:00\", \"repeat_type\": \"yearly\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"count\", \"repeat_count\": 3, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"978f53232a5bb40f\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T14:08:53+00:00\"}, {\"id\": 10, \"uid\": \"fixture-ce-010@calendar-wp-plugin\", \"title\": \"Therapy Session\", \"description\": \"Used for single-occurrence delete exception tests.\", \"location\": \"Clinic\", \"category\": \"Health\", \"all_day_event\": false, \"start_datetime\": \"2026-04-03T14:00:00+01:00\", \"end_datetime\": \"2026-04-03T15:00:00+01:00\", \"repeat_type\": \"weekly\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"count\", \"repeat_count\": 8, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"aff80ba990836966\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T14:08:53+00:00\"}], \"meta\": {\"count\": 10}}", "body_truncated": false}} +{"ts": "2026-03-30T14:08:54+00:00", "client": "127.0.0.1", "method": "GET", "path": "/calendar.ics", "request": {"headers": {"Host": "127.0.0.1:18080", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:08:54 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"c070ea0768ce7e3b\"", "Content-Length": "3132"}, "body": "HTTP/1.0 200 OK\r\nServer: CalendarFixture/0.1 Python/3.13.5\r\nDate: Mon, 30 Mar 2026 14:08:54 GMT\r\nContent-Type: text/calendar; charset=utf-8\r\nETag: \"c070ea0768ce7e3b\"\r\nContent-Length: 3132\r\n\r\nBEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nCALSCALE:GREGORIAN\r\nX-WR-TIMEZONE:Europe/London\r\nBEGIN:VEVENT\r\nUID:fixture-ce-001@calendar-wp-plugin\r\nDTSTAMP:20260330T140854Z\r\nSUMMARY:Board Meeting\r\nDESCRIPTION:Quarterly board review.\r\nLOCATION:Room A\r\nCATEGORIES:Governance\r\nDTSTART;TZID=Europe/London:20260401T100000\r\nDTEND;TZID=Europe/London:20260401T113000\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-002@calendar-wp-plugin\r\nDTSTAMP:20260330T140854Z\r\nSUMMARY:Office Closed\r\nDESCRIPTION:Public holiday closure.\r\nLOCATION:HQ\r\nCATEGORIES:Operations\r\nDTSTART;VALUE=DATE:20260504\r\nDTEND;VALUE=DATE:20260505\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-003@calendar-wp-plugin\r\nDTSTAMP:20260330T140854Z\r\nSUMMARY:Daily Standup\r\nDESCRIPTION:15 minute sync.\r\nLOCATION:Online\r\nCATEGORIES:Team\r\nDTSTART;TZID=Europe/London:20260406T090000\r\nDTEND;TZID=Europe/London:20260406T091500\r\nRRULE:FREQ=DAILY;COUNT=10\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-004@calendar-wp-plugin\r\nDTSTAMP:20260330T140854Z\r\nSUMMARY:Community Lunch\r\nDESCRIPTION:Weekly community lunch.\r\nLOCATION:Cafeteria\r\nCATEGORIES:Community\r\nDTSTART;TZID=Europe/London:20260408T123000\r\nDTEND;TZID=Europe/London:20260408T133000\r\nRRULE:FREQ=WEEKLY\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-005@calendar-wp-plugin\r\nDTSTAMP:20260330T140854Z\r\nSUMMARY:Finance Close\r\nDESCRIPTION:Month-end close process.\r\nLOCATION:Finance Office\r\nCATEGORIES:Finance\r\nDTSTART;TZID=Europe/London:20260430T170000\r\nDTEND;TZID=Europe/London:20260430T180000\r\nRRULE:FREQ=MONTHLY;UNTIL=20260831T235959\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-006@calendar-wp-plugin\r\nDTSTAMP:20260330T140854Z\r\nSUMMARY:Annual Conference\r\nDESCRIPTION:Annual community conference.\r\nLOCATION:Main Hall\r\nCATEGORIES:Events\r\nDTSTART;TZID=Europe/London:20260615T100000\r\nDTEND;TZID=Europe/London:20260615T170000\r\nRRULE:FREQ=YEARLY;COUNT=3\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-007@calendar-wp-plugin\r\nDTSTAMP:20260330T140854Z\r\nSUMMARY:Fortnightly Coaching\r\nDESCRIPTION:Coaching check-in.\r\nLOCATION:Online\r\nCATEGORIES:Training\r\nDTSTART;TZID=Europe/London:20260407T150000\r\nDTEND;TZID=Europe/London:20260407T160000\r\nRRULE:FREQ=WEEKLY;INTERVAL=2;UNTIL=20260731T235959\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-008@calendar-wp-plugin\r\nDTSTAMP:20260330T140854Z\r\nSUMMARY:DST Validation Event\r\nDESCRIPTION:Validates DST transition rendering.\r\nLOCATION:Lab\r\nCATEGORIES:QA\r\nDTSTART;TZID=Europe/London:20261025T003000\r\nDTEND;TZID=Europe/London:20261025T023000\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-009@calendar-wp-plugin\r\nDTSTAMP:20260330T140854Z\r\nSUMMARY:Leap Day Marker\r\nDESCRIPTION:Leap day recurrence behavior.\r\nLOCATION:Calendar\r\nCATEGORIES:QA\r\nDTSTART;TZID=Europe/London:20280229T090000\r\nDTEND;TZID=Europe/London:20280229T100000\r\nRRULE:FREQ=YEARLY;COUNT=3\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-010@calendar-wp-plugin\r\nDTSTAMP:20260330T140854Z\r\nSUMMARY:Therapy Session\r\nDESCRIPTION:Used for single-occurrence delete exception tests.\r\nLOCATION:Clinic\r\nCATEGORIES:Health\r\nDTSTART;TZID=Europe/London:20260403T140000\r\nDTEND;TZID=Europe/London:20260403T150000\r\nRRULE:FREQ=WEEKLY;COUNT=8\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_truncated": false}} +{"ts": "2026-03-30T14:08:54+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/1.ics", "request": {"headers": {"Host": "127.0.0.1:18080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:08:54 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"b2246c0ba36be704\"", "Content-Length": "364"}, "body": "HTTP/1.0 200 OK\r\nServer: CalendarFixture/0.1 Python/3.13.5\r\nDate: Mon, 30 Mar 2026 14:08:54 GMT\r\nContent-Type: text/calendar; charset=utf-8\r\nETag: \"b2246c0ba36be704\"\r\nContent-Length: 364\r\n\r\nBEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-001@calendar-wp-plugin\r\nDTSTAMP:20260330T140854Z\r\nSUMMARY:Board Meeting\r\nDESCRIPTION:Quarterly board review.\r\nLOCATION:Room A\r\nCATEGORIES:Governance\r\nDTSTART;TZID=Europe/London:20260401T100000\r\nDTEND;TZID=Europe/London:20260401T113000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_truncated": false}} +{"ts": "2026-03-30T14:08:54+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/10/occurrences/2026-04-17T14:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:18080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:08:54 GMT"}, "body": "HTTP/1.0 204 No Content\r\nServer: CalendarFixture/0.1 Python/3.13.5\r\nDate: Mon, 30 Mar 2026 14:08:54 GMT\r\n\r\n", "body_truncated": false}} +{"ts": "2026-03-30T14:08:54+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-vtimezone.ics", "request": {"headers": {"Host": "127.0.0.1:18080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "454"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN\nVERSION:2.0\nBEGIN:VTIMEZONE\nTZID:Europe/London\nBEGIN:STANDARD\nDTSTART:18471201T000000\nRRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=9\nTZOFFSETFROM:+0115\nTZOFFSETTO:+0000\nTZNAME:GMT\nEND:STANDARD\nEND:VTIMEZONE\nBEGIN:VEVENT\nUID:smoke-vtimezone-parser-001\nSUMMARY:Smoke VTIMEZONE Parse\nDTSTART;TZID=Europe/London:20260423T150000\nDTEND;TZID=Europe/London:20260423T160000\nEND:VEVENT\nEND:VCALENDAR\n", "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:08:54 GMT", "ETag": "\"864b421e254e30ac\""}, "body": "HTTP/1.0 201 Created\r\nServer: CalendarFixture/0.1 Python/3.13.5\r\nDate: Mon, 30 Mar 2026 14:08:54 GMT\r\nETag: \"864b421e254e30ac\"\r\n\r\n", "body_truncated": false}} +{"ts": "2026-03-30T14:08:54+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-vtimezone.ics", "request": {"headers": {"Host": "127.0.0.1:18080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:08:54 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"864b421e254e30ac\"", "Content-Length": "288"}, "body": "HTTP/1.0 200 OK\r\nServer: CalendarFixture/0.1 Python/3.13.5\r\nDate: Mon, 30 Mar 2026 14:08:54 GMT\r\nContent-Type: text/calendar; charset=utf-8\r\nETag: \"864b421e254e30ac\"\r\nContent-Length: 288\r\n\r\nBEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-vtimezone-parser-001\r\nDTSTAMP:20260330T140854Z\r\nSUMMARY:Smoke VTIMEZONE Parse\r\nDTSTART;TZID=Europe/London:20260423T150000\r\nDTEND;TZID=Europe/London:20260423T160000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_truncated": false}} +{"ts": "2026-03-30T14:08:54+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-monthly-nth.ics", "request": {"headers": {"Host": "127.0.0.1:18080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "261"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-monthly-nth-001\nSUMMARY:Smoke Monthly Nth\nDTSTART;TZID=Europe/London:20260402T150000\nDTEND;TZID=Europe/London:20260402T160000\nRRULE:FREQ=MONTHLY;BYDAY=SU;BYSETPOS=4\nEND:VEVENT\nEND:VCALENDAR\n", "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:08:54 GMT", "ETag": "\"44d2d2c64b2e5412\""}, "body": "HTTP/1.0 201 Created\r\nServer: CalendarFixture/0.1 Python/3.13.5\r\nDate: Mon, 30 Mar 2026 14:08:54 GMT\r\nETag: \"44d2d2c64b2e5412\"\r\n\r\n", "body_truncated": false}} +{"ts": "2026-03-30T14:08:54+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-monthly-nth.ics", "request": {"headers": {"Host": "127.0.0.1:18080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:08:54 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"44d2d2c64b2e5412\"", "Content-Length": "319"}, "body": "HTTP/1.0 200 OK\r\nServer: CalendarFixture/0.1 Python/3.13.5\r\nDate: Mon, 30 Mar 2026 14:08:54 GMT\r\nContent-Type: text/calendar; charset=utf-8\r\nETag: \"44d2d2c64b2e5412\"\r\nContent-Length: 319\r\n\r\nBEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-nth-001\r\nDTSTAMP:20260330T140854Z\r\nSUMMARY:Smoke Monthly Nth\r\nDTSTART;TZID=Europe/London:20260426T150000\r\nDTEND;TZID=Europe/London:20260426T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SU;BYSETPOS=4\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_truncated": false}} +{"ts": "2026-03-30T14:08:54+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-monthly-ordinal.ics", "request": {"headers": {"Host": "127.0.0.1:18080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "259"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-monthly-ordinal-001\nSUMMARY:Smoke Monthly Ordinal\nDTSTART;TZID=Europe/London:20260411T150000\nDTEND;TZID=Europe/London:20260411T160000\nRRULE:FREQ=MONTHLY;BYDAY=2SA\nEND:VEVENT\nEND:VCALENDAR\n", "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:08:54 GMT", "ETag": "\"fa7dfc8cbf381fc0\""}, "body": "HTTP/1.0 201 Created\r\nServer: CalendarFixture/0.1 Python/3.13.5\r\nDate: Mon, 30 Mar 2026 14:08:54 GMT\r\nETag: \"fa7dfc8cbf381fc0\"\r\n\r\n", "body_truncated": false}} +{"ts": "2026-03-30T14:08:54+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-monthly-ordinal.ics", "request": {"headers": {"Host": "127.0.0.1:18080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:08:54 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"fa7dfc8cbf381fc0\"", "Content-Length": "327"}, "body": "HTTP/1.0 200 OK\r\nServer: CalendarFixture/0.1 Python/3.13.5\r\nDate: Mon, 30 Mar 2026 14:08:54 GMT\r\nContent-Type: text/calendar; charset=utf-8\r\nETag: \"fa7dfc8cbf381fc0\"\r\nContent-Length: 327\r\n\r\nBEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-ordinal-001\r\nDTSTAMP:20260330T140854Z\r\nSUMMARY:Smoke Monthly Ordinal\r\nDTSTART;TZID=Europe/London:20260411T150000\r\nDTEND;TZID=Europe/London:20260411T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SA;BYSETPOS=2\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_truncated": false}} +{"ts": "2026-03-30T14:08:54+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-monthly-last.ics", "request": {"headers": {"Host": "127.0.0.1:18080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "264"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-monthly-last-001\nSUMMARY:Smoke Monthly Last\nDTSTART;TZID=Europe/London:20260425T150000\nDTEND;TZID=Europe/London:20260425T160000\nRRULE:FREQ=MONTHLY;BYDAY=SA;BYSETPOS=-1\nEND:VEVENT\nEND:VCALENDAR\n", "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:08:54 GMT", "ETag": "\"3f65af9c5527a154\""}, "body": "HTTP/1.0 201 Created\r\nServer: CalendarFixture/0.1 Python/3.13.5\r\nDate: Mon, 30 Mar 2026 14:08:54 GMT\r\nETag: \"3f65af9c5527a154\"\r\n\r\n", "body_truncated": false}} +{"ts": "2026-03-30T14:08:54+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-monthly-last.ics", "request": {"headers": {"Host": "127.0.0.1:18080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:08:54 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"3f65af9c5527a154\"", "Content-Length": "322"}, "body": "HTTP/1.0 200 OK\r\nServer: CalendarFixture/0.1 Python/3.13.5\r\nDate: Mon, 30 Mar 2026 14:08:54 GMT\r\nContent-Type: text/calendar; charset=utf-8\r\nETag: \"3f65af9c5527a154\"\r\nContent-Length: 322\r\n\r\nBEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-last-001\r\nDTSTAMP:20260330T140854Z\r\nSUMMARY:Smoke Monthly Last\r\nDTSTART;TZID=Europe/London:20260425T150000\r\nDTEND;TZID=Europe/London:20260425T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SA;BYSETPOS=-1\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_truncated": false}} +{"ts": "2026-03-30T14:08:54+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:18080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "X-WP-User": "admin", "Content-Length": "362"}, "body": "{\"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\"}", "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:08:54 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "673"}, "body": "HTTP/1.0 201 Created\r\nServer: CalendarFixture/0.1 Python/3.13.5\r\nDate: Mon, 30 Mar 2026 14:08:54 GMT\r\nContent-Type: application/json; charset=utf-8\r\nContent-Length: 673\r\n\r\n{\"data\": {\"id\": 15, \"uid\": \"98fd8f149a14e2dddeb7@calendar-wp-plugin\", \"title\": \"Smoke 4th Sunday Anchor\", \"description\": \"anchor-normalization\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"start_datetime\": \"2026-05-24T15:00:00+01:00\", \"end_datetime\": \"2026-05-24T16:00:00+01:00\", \"repeat_type\": \"monthly\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"weekday_of_month\", \"repeat_nth_day\": null, \"repeat_nth_pos\": 4, \"repeat_nth_weekday\": 0, \"repeat_range_mode\": \"no_end\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"530723cfffec98c3\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T14:08:54+00:00\"}}", "body_truncated": false}} +{"ts": "2026-03-30T14:08:54+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/15/occurrences/2026-05-24T15:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:18080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:08:54 GMT"}, "body": "HTTP/1.0 204 No Content\r\nServer: CalendarFixture/0.1 Python/3.13.5\r\nDate: Mon, 30 Mar 2026 14:08:54 GMT\r\n\r\n", "body_truncated": false}} +{"ts": "2026-03-30T14:08:54+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/15.ics", "request": {"headers": {"Host": "127.0.0.1:18080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:08:54 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"006c790da9929803\"", "Content-Length": "420"}, "body": "HTTP/1.0 200 OK\r\nServer: CalendarFixture/0.1 Python/3.13.5\r\nDate: Mon, 30 Mar 2026 14:08:54 GMT\r\nContent-Type: text/calendar; charset=utf-8\r\nETag: \"006c790da9929803\"\r\nContent-Length: 420\r\n\r\nBEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:98fd8f149a14e2dddeb7@calendar-wp-plugin\r\nDTSTAMP:20260330T140854Z\r\nSUMMARY:Smoke 4th Sunday Anchor\r\nDESCRIPTION:anchor-normalization\r\nDTSTART;TZID=Europe/London:20260524T150000\r\nDTEND;TZID=Europe/London:20260524T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SU;BYSETPOS=4\r\nEXDATE;TZID=Europe/London:20260524T150000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_truncated": false}} +{"ts": "2026-03-30T14:08:54+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:18080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "X-WP-User": "admin", "Content-Length": "295"}, "body": "{\"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\"}", "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:08:54 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "655"}, "body": "HTTP/1.0 201 Created\r\nServer: CalendarFixture/0.1 Python/3.13.5\r\nDate: Mon, 30 Mar 2026 14:08:54 GMT\r\nContent-Type: application/json; charset=utf-8\r\nContent-Length: 655\r\n\r\n{\"data\": {\"id\": 16, \"uid\": \"ddec804fa982f5df9ed5@calendar-wp-plugin\", \"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_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"until\", \"repeat_count\": null, \"repeat_until\": \"2026-03-19\", \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"5eeeb56da7d6ae79\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T14:08:54+00:00\"}}", "body_truncated": false}} +{"ts": "2026-03-30T14:08:54+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/16/occurrences/2026-03-11T11:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:18080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:08:54 GMT"}, "body": "HTTP/1.0 204 No Content\r\nServer: CalendarFixture/0.1 Python/3.13.5\r\nDate: Mon, 30 Mar 2026 14:08:54 GMT\r\n\r\n", "body_truncated": false}} +{"ts": "2026-03-30T14:08:54+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/16/occurrences?from=2026-03-01&months=1", "request": {"headers": {"Host": "127.0.0.1:18080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:08:54 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "5212"}, "body": "HTTP/1.0 200 OK\r\nServer: CalendarFixture/0.1 Python/3.13.5\r\nDate: Mon, 30 Mar 2026 14:08:54 GMT\r\nContent-Type: application/json; charset=utf-8\r\nContent-Length: 5212\r\n\r\n{\"data\": [{\"event_id\": 16, \"uid\": \"ddec804fa982f5df9ed5@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-02T10:00:00+00:00\", \"occurrence_end\": \"2026-03-02T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"ddec804fa982f5df9ed5@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-03T10:00:00+00:00\", \"occurrence_end\": \"2026-03-03T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"ddec804fa982f5df9ed5@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-04T10:00:00+00:00\", \"occurrence_end\": \"2026-03-04T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"ddec804fa982f5df9ed5@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-05T10:00:00+00:00\", \"occurrence_end\": \"2026-03-05T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"ddec804fa982f5df9ed5@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-06T10:00:00+00:00\", \"occurrence_end\": \"2026-03-06T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"ddec804fa982f5df9ed5@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-07T10:00:00+00:00\", \"occurrence_end\": \"2026-03-07T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"ddec804fa982f5df9ed5@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-08T10:00:00+00:00\", \"occurrence_end\": \"2026-03-08T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"ddec804fa982f5df9ed5@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-09T10:00:00+00:00\", \"occurrence_end\": \"2026-03-09T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"ddec804fa982f5df9ed5@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-10T10:00:00+00:00\", \"occurrence_end\": \"2026-03-10T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"ddec804fa982f5df9ed5@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-12T10:00:00+00:00\", \"occurrence_end\": \"2026-03-12T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"ddec804fa982f5df9ed5@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-13T10:00:00+00:00\", \"occurrence_end\": \"2026-03-13T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"ddec804fa982f5df9ed5@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-14T10:00:00+00:00\", \"occurrence_end\": \"2026-03-14T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"ddec804fa982f5df9ed5@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-15T10:00:00+00:00\", \"occurrence_end\": \"2026-03-15T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"ddec804fa982f5df9ed5@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-16T10:00:00+00:00\", \"occurrence_end\": \"2026-03-16T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"ddec804fa982f5df9ed5@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-17T10:00:00+00:00\", \"occurrence_end\": \"2026-03-17T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"ddec804fa982f5df9ed5@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-18T10:00:00+00:00\", \"occurrence_end\": \"2026-03-18T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"ddec804fa982f5df9ed5@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-19T10:00:00+00:00\", \"occurrence_end\": \"2026-03-19T11:00:00+00:00\", \"repeat_type\": \"daily\"}]}", "body_truncated": false}} +{"ts": "2026-03-30T14:08:54+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/16/occurrences/2026-03-10", "request": {"headers": {"Host": "127.0.0.1:18080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:08:54 GMT"}, "body": "HTTP/1.0 204 No Content\r\nServer: CalendarFixture/0.1 Python/3.13.5\r\nDate: Mon, 30 Mar 2026 14:08:54 GMT\r\n\r\n", "body_truncated": false}} +{"ts": "2026-03-30T14:08:54+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/16/occurrences?from=2026-03-01&months=1", "request": {"headers": {"Host": "127.0.0.1:18080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:08:54 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "4906"}, "body": "HTTP/1.0 200 OK\r\nServer: CalendarFixture/0.1 Python/3.13.5\r\nDate: Mon, 30 Mar 2026 14:08:54 GMT\r\nContent-Type: application/json; charset=utf-8\r\nContent-Length: 4906\r\n\r\n{\"data\": [{\"event_id\": 16, \"uid\": \"ddec804fa982f5df9ed5@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-02T10:00:00+00:00\", \"occurrence_end\": \"2026-03-02T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"ddec804fa982f5df9ed5@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-03T10:00:00+00:00\", \"occurrence_end\": \"2026-03-03T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"ddec804fa982f5df9ed5@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-04T10:00:00+00:00\", \"occurrence_end\": \"2026-03-04T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"ddec804fa982f5df9ed5@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-05T10:00:00+00:00\", \"occurrence_end\": \"2026-03-05T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"ddec804fa982f5df9ed5@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-06T10:00:00+00:00\", \"occurrence_end\": \"2026-03-06T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"ddec804fa982f5df9ed5@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-07T10:00:00+00:00\", \"occurrence_end\": \"2026-03-07T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"ddec804fa982f5df9ed5@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-08T10:00:00+00:00\", \"occurrence_end\": \"2026-03-08T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"ddec804fa982f5df9ed5@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-09T10:00:00+00:00\", \"occurrence_end\": \"2026-03-09T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"ddec804fa982f5df9ed5@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-12T10:00:00+00:00\", \"occurrence_end\": \"2026-03-12T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"ddec804fa982f5df9ed5@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-13T10:00:00+00:00\", \"occurrence_end\": \"2026-03-13T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"ddec804fa982f5df9ed5@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-14T10:00:00+00:00\", \"occurrence_end\": \"2026-03-14T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"ddec804fa982f5df9ed5@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-15T10:00:00+00:00\", \"occurrence_end\": \"2026-03-15T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"ddec804fa982f5df9ed5@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-16T10:00:00+00:00\", \"occurrence_end\": \"2026-03-16T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"ddec804fa982f5df9ed5@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-17T10:00:00+00:00\", \"occurrence_end\": \"2026-03-17T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"ddec804fa982f5df9ed5@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-18T10:00:00+00:00\", \"occurrence_end\": \"2026-03-18T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"ddec804fa982f5df9ed5@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-19T10:00:00+00:00\", \"occurrence_end\": \"2026-03-19T11:00:00+00:00\", \"repeat_type\": \"daily\"}]}", "body_truncated": false}} +{"ts": "2026-03-30T14:08:54+00:00", "client": "127.0.0.1", "method": "GET", "path": "/calendar", "request": {"headers": {"Host": "127.0.0.1:18080", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:08:54 GMT", "Content-Type": "text/html; charset=utf-8", "Content-Length": "43446"}, "body": "HTTP/1.0 200 OK\r\nServer: CalendarFixture/0.1 Python/3.13.5\r\nDate: Mon, 30 Mar 2026 14:08:54 GMT\r\nContent-Type: text/html; charset=utf-8\r\nContent-Length: 43446\r\n\r\n\n\n\n \n \n Calendar\n \n\n\n\n
Calendar Fixture Public Calendar UI
\n
\n
\n
\n
\n
\n \n
\n
\n
\n \n \n \n \n \n
\n
\n ICS Link\n CalDAV\n
\n
\n \n
\n
\n
\n
\n
\n
\n

Account Login

\n
\n
\n
\n
\n
\n
\n \n \n
\n
\n

Register

\n
\n
\n
\n
\n
\n
\n
\n

Password Recovery

\n
\n \n
\n
\n \n
\n
\n
\n
\n
\n

Event Details

\n
\n
\n
\n
\n
\n
\n
\n
\n

Event

\n \n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n \n
\n
\n
\n
\n
\n \n
\n
\n
\n
\n
\n
\n
\n
\n \n
\n
\n
\n
\n
\n
\n
\n
\n \n \n \n \n \n
\n
\n

Delete Single Occurrence (Exception)

\n
\n \n \n
\n
\n \n
\n \n
\n
\n
\n
\n\n\n\n", "body_truncated": false}} +{"ts": "2026-03-30T14:09:43+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/ed617bc7-66c1-474d-a467-86565e680820.ics", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "14085", "If-Match": "\"b448b4adc3c325bc\"", "Origin": "http://localhost:8080", "DNT": "1", "Authorization": "***", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "BEGIN:VCALENDAR\r\nPRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN\r\nVERSION:2.0\r\nBEGIN:VTIMEZONE\r\nTZID:Europe/London\r\nX-TZINFO:Europe/London[2025b]\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:-000115\r\nTZNAME:Europe/London(STD)\r\nDTSTART:18471201T000000\r\nRDATE:18471201T000000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19160521T020000\r\nRDATE:19160521T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19161001T030000\r\nRDATE:19161001T030000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19170408T020000\r\nRDATE:19170408T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19170917T030000\r\nRDATE:19170917T030000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19180324T020000\r\nRDATE:19180324T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19180930T030000\r\nRRULE:FREQ=YEARLY;BYMONTH=9;BYDAY=-1MO;UNTIL=19190929T030000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19190330T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU;UNTIL=19200328T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19201025T030000\r\nRDATE:19201025T030000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19210403T020000\r\nRDATE:19210403T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19211003T030000\r\nRDATE:19211003T030000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19220326T020000\r\nRDATE:19220326T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19221008T030000\r\nRDATE:19221008T030000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19230422T020000\r\nRDATE:19230422T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19240413T020000\r\nRDATE:19240413T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19230916T030000\r\nRRULE:FREQ=YEARLY;BYMONTH=9;BYDAY=3SU;UNTIL=19240921T030000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19250419T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=3SU;UNTIL=19260418T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19270410T020000\r\nRDATE:19270410T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19280422T020000\r\nRDATE:19280422T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19290421T020000\r\nRDATE:19290421T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19300413T020000\r\nRDATE:19300413T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19310419T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=3SU;UNTIL=19320417T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19251004T030000\r\nRRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=1SU;UNTIL=19321002T030000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19330409T020000\r\nRDATE:19330409T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19331008T030000\r\nRDATE:19331008T030000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19340422T020000\r\nRDATE:19340422T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19350414T020000\r\nRDATE:19350414T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19360419T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=3SU;UNTIL=19370418T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19380410T020000\r\nRDATE:19380410T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19341007T030000\r\nRRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=1SU;UNTIL=19381002T030000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19390416T020000\r\nRDATE:19390416T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19400225T020000\r\nRDATE:19400225T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+020000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19410504T020000\r\nRDATE:19410504T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+020000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19410810T030000\r\nRDATE:19410810T030000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+020000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19420405T020000\r\nRDATE:19420405T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+020000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19420809T030000\r\nRDATE:19420809T030000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+020000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19430404T020000\r\nRDATE:19430404T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+020000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19430815T030000\r\nRDATE:19430815T030000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+020000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19440402T020000\r\nRDATE:19440402T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+020000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19440917T030000\r\nRDATE:19440917T030000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+020000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19450402T020000\r\nRDATE:19450402T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19391119T030000\r\nRDATE:19391119T030000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+020000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19450715T030000\r\nRDATE:19450715T030000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19460414T020000\r\nRDATE:19460414T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19470316T020000\r\nRDATE:19470316T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+020000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19470413T020000\r\nRDATE:19470413T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19451007T030000\r\nRRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=1SU;UNTIL=19461006T030000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+020000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19470810T030000\r\nRDATE:19470810T030000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19471102T030000\r\nRDATE:19471102T030000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19480314T020000\r\nRDATE:19480314T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19490403T020000\r\nRDATE:19490403T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19481031T030000\r\nRRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU;UNTIL=19491030T030000\r\nEND:STANDARD\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19501022T030000\r\nRDATE:19501022T030000\r\nEND:STANDARD\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19511021T030000\r\nRDATE:19511021T030000\r\nEND:STANDARD\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19521026T030000\r\nRDATE:19521026T030000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19500416T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=3SU;UNTIL=19530419T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19540411T020000\r\nRDATE:19540411T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19550417T020000\r\nRDATE:19550417T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19560422T020000\r\nRDATE:19560422T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19570414T020000\r\nRDATE:19570414T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19580420T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=3SU;UNTIL=19590419T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19600410T020000\r\nRDATE:19600410T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19531004T030000\r\nRRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=1SU;UNTIL=19601002T030000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19610326T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU;UNTIL=19630331T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19640322T020000\r\nRDATE:19640322T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19611029T030000\r\nRRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU;UNTIL=19641025T030000\r\nEND:STANDARD\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19651024T030000\r\nRRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=4SU;UNTIL=19661023T030000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19650321T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=3SU;UNTIL=19670319T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19671029T030000\r\nRDATE:19671029T030000\r\nEND:STANDARD\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19681027T000000\r\nRDATE:19681027T000000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19680218T020000\r\nRDATE:19680218T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19711031T030000\r\nRRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU;UNTIL=19751026T030000\r\nEND:STANDARD\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19761024T030000\r\nRRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=4SU;UNTIL=19771023T030000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19720319T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=3SU;UNTIL=19800316T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19781029T030000\r\nRRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU;UNTIL=19801026T030000\r\nEND:STANDARD\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19811025T020000\r\nRDATE:19811025T020000\r\nEND:STANDARD\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19821024T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=4SU;UNTIL=19831023T020000\r\nEND:STANDARD\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19841028T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU;UNTIL=19871025T020000\r\nEND:STANDARD\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19881023T020000\r\nRDATE:19881023T020000\r\nEND:STANDARD\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19891029T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU;UNTIL=19921025T020000\r\nEND:STANDARD\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19931024T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=4SU;UNTIL=19951022T020000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19810329T010000\r\nRRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU;UNTIL=19960331T010000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19961027T020000\r\nRDATE:19961027T020000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:(DST)\r\nDTSTART:19970330T010000\r\nRRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:(STD)\r\nDTSTART:19971026T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU\r\nEND:STANDARD\r\nEND:VTIMEZONE\r\nBEGIN:VEVENT\r\nLAST-MODIFIED:20260330T140943Z\r\nDTSTAMP:20260330T140943Z\r\nUID:ed617bc7-66c1-474d-a467-86565e680820\r\nSUMMARY:test 8\r\nRRULE:FREQ=DAILY;UNTIL=20260319T235959\r\nEXDATE;TZID=Europe/London:20260310T160000\r\nDTSTART;TZID=Europe/London:20260302T160000\r\nDTEND;TZID=Europe/London:20260302T170000\r\nDESCRIPTION:Default Mozilla Description\r\nSEQUENCE:1\r\nX-MOZ-GENERATION:1\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:09:43 GMT", "ETag": "\"075d4a0c8d56a83e\""}, "body": "HTTP/1.0 201 Created\r\nServer: CalendarFixture/0.1 Python/3.13.5\r\nDate: Mon, 30 Mar 2026 14:09:43 GMT\r\nETag: \"075d4a0c8d56a83e\"\r\n\r\n", "body_truncated": false}} +{"ts": "2026-03-30T14:09:43+00:00", "client": "127.0.0.1", "method": "REPORT", "path": "/caldav/calendars/public/", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/xml; charset=utf-8", "Content-Length": "266", "Depth": "1", "Origin": "http://localhost:8080", "DNT": "1", "Authorization": "***", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "\n/caldav/calendars/public/ed617bc7-66c1-474d-a467-86565e680820.ics", "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:09:43 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "14799"}, "body": "HTTP/1.0 207 Multi-Status\r\nServer: CalendarFixture/0.1 Python/3.13.5\r\nDate: Mon, 30 Mar 2026 14:09:43 GMT\r\nContent-Type: application/xml; charset=utf-8\r\nContent-Length: 14799\r\n\r\n\n\n\n /caldav/calendars/public/1.ics\n \n \n \"b2246c0ba36be704\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-001@calendar-wp-plugin\r\nDTSTAMP:20260330T140943Z\r\nSUMMARY:Board Meeting\r\nDESCRIPTION:Quarterly board review.\r\nLOCATION:Room A\r\nCATEGORIES:Governance\r\nDTSTART;TZID=Europe/London:20260401T100000\r\nDTEND;TZID=Europe/London:20260401T113000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/2.ics\n \n \n \"97ffb830a549901c\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-002@calendar-wp-plugin\r\nDTSTAMP:20260330T140943Z\r\nSUMMARY:Office Closed\r\nDESCRIPTION:Public holiday closure.\r\nLOCATION:HQ\r\nCATEGORIES:Operations\r\nDTSTART;VALUE=DATE:20260504\r\nDTEND;VALUE=DATE:20260505\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/3.ics\n \n \n \"0f947e3952fcf681\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-003@calendar-wp-plugin\r\nDTSTAMP:20260330T140943Z\r\nSUMMARY:Daily Standup\r\nDESCRIPTION:15 minute sync.\r\nLOCATION:Online\r\nCATEGORIES:Team\r\nDTSTART;TZID=Europe/London:20260406T090000\r\nDTEND;TZID=Europe/London:20260406T091500\r\nRRULE:FREQ=DAILY;COUNT=10\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/4.ics\n \n \n \"4f06e2ef4d6d8e38\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-004@calendar-wp-plugin\r\nDTSTAMP:20260330T140943Z\r\nSUMMARY:Community Lunch\r\nDESCRIPTION:Weekly community lunch.\r\nLOCATION:Cafeteria\r\nCATEGORIES:Community\r\nDTSTART;TZID=Europe/London:20260408T123000\r\nDTEND;TZID=Europe/London:20260408T133000\r\nRRULE:FREQ=WEEKLY\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/5.ics\n \n \n \"927953eb5c8dd26c\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-005@calendar-wp-plugin\r\nDTSTAMP:20260330T140943Z\r\nSUMMARY:Finance Close\r\nDESCRIPTION:Month-end close process.\r\nLOCATION:Finance Office\r\nCATEGORIES:Finance\r\nDTSTART;TZID=Europe/London:20260430T170000\r\nDTEND;TZID=Europe/London:20260430T180000\r\nRRULE:FREQ=MONTHLY;UNTIL=20260831T235959\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/6.ics\n \n \n \"93cf1bad962c75f4\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-006@calendar-wp-plugin\r\nDTSTAMP:20260330T140943Z\r\nSUMMARY:Annual Conference\r\nDESCRIPTION:Annual community conference.\r\nLOCATION:Main Hall\r\nCATEGORIES:Events\r\nDTSTART;TZID=Europe/London:20260615T100000\r\nDTEND;TZID=Europe/London:20260615T170000\r\nRRULE:FREQ=YEARLY;COUNT=3\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/7.ics\n \n \n \"7201c1345f1b2573\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-007@calendar-wp-plugin\r\nDTSTAMP:20260330T140943Z\r\nSUMMARY:Fortnightly Coaching\r\nDESCRIPTION:Coaching check-in.\r\nLOCATION:Online\r\nCATEGORIES:Training\r\nDTSTART;TZID=Europe/London:20260407T150000\r\nDTEND;TZID=Europe/London:20260407T160000\r\nRRULE:FREQ=WEEKLY;INTERVAL=2;UNTIL=20260731T235959\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/8.ics\n \n \n \"a81452cded01c6a1\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-008@calendar-wp-plugin\r\nDTSTAMP:20260330T140943Z\r\nSUMMARY:DST Validation Event\r\nDESCRIPTION:Validates DST transition rendering.\r\nLOCATION:Lab\r\nCATEGORIES:QA\r\nDTSTART;TZID=Europe/London:20261025T003000\r\nDTEND;TZID=Europe/London:20261025T023000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/9.ics\n \n \n \"978f53232a5bb40f\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-009@calendar-wp-plugin\r\nDTSTAMP:20260330T140943Z\r\nSUMMARY:Leap Day Marker\r\nDESCRIPTION:Leap day recurrence behavior.\r\nLOCATION:Calendar\r\nCATEGORIES:QA\r\nDTSTART;TZID=Europe/London:20280229T090000\r\nDTEND;TZID=Europe/London:20280229T100000\r\nRRULE:FREQ=YEARLY;COUNT=3\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/10.ics\n \n \n \"b7eba5ab03b7f422\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-010@calendar-wp-plugin\r\nDTSTAMP:20260330T140943Z\r\nSUMMARY:Therapy Session\r\nDESCRIPTION:Used for single-occurrence delete exception tests.\r\nLOCATION:Clinic\r\nCATEGORIES:Health\r\nDTSTART;TZID=Europe/London:20260403T140000\r\nDTEND;TZID=Europe/London:20260403T150000\r\nRRULE:FREQ=WEEKLY;COUNT=8\r\nEXDATE;TZID=Europe/London:20260417T140000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/smoke-vtimezone.ics\n \n \n \"864b421e254e30ac\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-vtimezone-parser-001\r\nDTSTAMP:20260330T140943Z\r\nSUMMARY:Smoke VTIMEZONE Parse\r\nDTSTART;TZID=Europe/London:20260423T150000\r\nDTEND;TZID=Europe/London:20260423T160000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/smoke-monthly-nth.ics\n \n \n \"44d2d2c64b2e5412\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-nth-001\r\nDTSTAMP:20260330T140943Z\r\nSUMMARY:Smoke Monthly Nth\r\nDTSTART;TZID=Europe/London:20260426T150000\r\nDTEND;TZID=Europe/London:20260426T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SU;BYSETPOS=4\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/smoke-monthly-ordinal.ics\n \n \n \"fa7dfc8cbf381fc0\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-ordinal-001\r\nDTSTAMP:20260330T140943Z\r\nSUMMARY:Smoke Monthly Ordinal\r\nDTSTART;TZID=Europe/London:20260411T150000\r\nDTEND;TZID=Europe/London:20260411T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SA;BYSETPOS=2\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/smoke-monthly-last.ics\n \n \n \"3f65af9c5527a154\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-last-001\r\nDTSTAMP:20260330T140943Z\r\nSUMMARY:Smoke Monthly Last\r\nDTSTART;TZID=Europe/London:20260425T150000\r\nDTEND;TZID=Europe/London:20260425T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SA;BYSETPOS=-1\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/15.ics\n \n \n \"006c790da9929803\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:98fd8f149a14e2dddeb7@calendar-wp-plugin\r\nDTSTAMP:20260330T140943Z\r\nSUMMARY:Smoke 4th Sunday Anchor\r\nDESCRIPTION:anchor-normalization\r\nDTSTART;TZID=Europe/London:20260524T150000\r\nDTEND;TZID=Europe/London:20260524T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SU;BYSETPOS=4\r\nEXDATE;TZID=Europe/London:20260524T150000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/16.ics\n \n \n \"9d5b8db2124188a7\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:ddec804fa982f5df9ed5@calendar-wp-plugin\r\nDTSTAMP:20260330T140943Z\r\nSUMMARY:Smoke Daily Exception TZ\r\nDESCRIPTION:tz-key\r\nDTSTART;TZID=Europe/London:20260302T100000\r\nDTEND;TZID=Europe/London:20260302T110000\r\nRRULE:FREQ=DAILY;UNTIL=20260319T235959\r\nEXDATE;TZID=Europe/London:20260310T100000,20260311T100000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/ed617bc7-66c1-474d-a467-86565e680820.ics\n \n \n \"075d4a0c8d56a83e\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:ed617bc7-66c1-474d-a467-86565e680820\r\nDTSTAMP:20260330T140943Z\r\nSUMMARY:test 8\r\nDESCRIPTION:Default Mozilla Description\r\nDTSTART;TZID=Europe/London:20260302T160000\r\nDTEND;TZID=Europe/London:20260302T170000\r\nRRULE:FREQ=DAILY;UNTIL=20260319T235959\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n", "body_truncated": false}} +{"ts": "2026-03-30T14:09:49+00:00", "client": "127.0.0.1", "method": "PROPFIND", "path": "/caldav/calendars/public/", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/xml; charset=utf-8", "Content-Length": "144", "Depth": "1", "Origin": "http://localhost:8080", "DNT": "1", "Authorization": "***", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "\n", "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:09:49 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "5816"}, "body": "HTTP/1.0 207 Multi-Status\r\nServer: CalendarFixture/0.1 Python/3.13.5\r\nDate: Mon, 30 Mar 2026 14:09:49 GMT\r\nContent-Type: application/xml; charset=utf-8\r\nContent-Length: 5816\r\n\r\n\n\n\n /caldav/calendars/public/\n \n \n Public Calendar\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/1.ics\n \n \n text/calendar; charset=utf-8\"b2246c0ba36be704\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/2.ics\n \n \n text/calendar; charset=utf-8\"97ffb830a549901c\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/3.ics\n \n \n text/calendar; charset=utf-8\"0f947e3952fcf681\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/4.ics\n \n \n text/calendar; charset=utf-8\"4f06e2ef4d6d8e38\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/5.ics\n \n \n text/calendar; charset=utf-8\"927953eb5c8dd26c\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/6.ics\n \n \n text/calendar; charset=utf-8\"93cf1bad962c75f4\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/7.ics\n \n \n text/calendar; charset=utf-8\"7201c1345f1b2573\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/8.ics\n \n \n text/calendar; charset=utf-8\"a81452cded01c6a1\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/9.ics\n \n \n text/calendar; charset=utf-8\"978f53232a5bb40f\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/10.ics\n \n \n text/calendar; charset=utf-8\"b7eba5ab03b7f422\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/smoke-vtimezone.ics\n \n \n text/calendar; charset=utf-8\"864b421e254e30ac\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/smoke-monthly-nth.ics\n \n \n text/calendar; charset=utf-8\"44d2d2c64b2e5412\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/smoke-monthly-ordinal.ics\n \n \n text/calendar; charset=utf-8\"fa7dfc8cbf381fc0\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/smoke-monthly-last.ics\n \n \n text/calendar; charset=utf-8\"3f65af9c5527a154\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/15.ics\n \n \n text/calendar; charset=utf-8\"006c790da9929803\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/16.ics\n \n \n text/calendar; charset=utf-8\"9d5b8db2124188a7\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/ed617bc7-66c1-474d-a467-86565e680820.ics\n \n \n text/calendar; charset=utf-8\"075d4a0c8d56a83e\"\n \n HTTP/1.1 200 OK\n \n\n\n", "body_truncated": false}} +{"ts": "2026-03-30T14:09:49+00:00", "client": "127.0.0.1", "method": "REPORT", "path": "/caldav/calendars/public/", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/xml; charset=utf-8", "Content-Length": "232", "Depth": "1", "Origin": "http://localhost:8080", "DNT": "1", "Authorization": "***", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "\n/caldav/calendars/public/15.ics", "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:09:49 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "14799"}, "body": "HTTP/1.0 207 Multi-Status\r\nServer: CalendarFixture/0.1 Python/3.13.5\r\nDate: Mon, 30 Mar 2026 14:09:49 GMT\r\nContent-Type: application/xml; charset=utf-8\r\nContent-Length: 14799\r\n\r\n\n\n\n /caldav/calendars/public/1.ics\n \n \n \"b2246c0ba36be704\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-001@calendar-wp-plugin\r\nDTSTAMP:20260330T140949Z\r\nSUMMARY:Board Meeting\r\nDESCRIPTION:Quarterly board review.\r\nLOCATION:Room A\r\nCATEGORIES:Governance\r\nDTSTART;TZID=Europe/London:20260401T100000\r\nDTEND;TZID=Europe/London:20260401T113000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/2.ics\n \n \n \"97ffb830a549901c\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-002@calendar-wp-plugin\r\nDTSTAMP:20260330T140949Z\r\nSUMMARY:Office Closed\r\nDESCRIPTION:Public holiday closure.\r\nLOCATION:HQ\r\nCATEGORIES:Operations\r\nDTSTART;VALUE=DATE:20260504\r\nDTEND;VALUE=DATE:20260505\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/3.ics\n \n \n \"0f947e3952fcf681\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-003@calendar-wp-plugin\r\nDTSTAMP:20260330T140949Z\r\nSUMMARY:Daily Standup\r\nDESCRIPTION:15 minute sync.\r\nLOCATION:Online\r\nCATEGORIES:Team\r\nDTSTART;TZID=Europe/London:20260406T090000\r\nDTEND;TZID=Europe/London:20260406T091500\r\nRRULE:FREQ=DAILY;COUNT=10\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/4.ics\n \n \n \"4f06e2ef4d6d8e38\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-004@calendar-wp-plugin\r\nDTSTAMP:20260330T140949Z\r\nSUMMARY:Community Lunch\r\nDESCRIPTION:Weekly community lunch.\r\nLOCATION:Cafeteria\r\nCATEGORIES:Community\r\nDTSTART;TZID=Europe/London:20260408T123000\r\nDTEND;TZID=Europe/London:20260408T133000\r\nRRULE:FREQ=WEEKLY\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/5.ics\n \n \n \"927953eb5c8dd26c\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-005@calendar-wp-plugin\r\nDTSTAMP:20260330T140949Z\r\nSUMMARY:Finance Close\r\nDESCRIPTION:Month-end close process.\r\nLOCATION:Finance Office\r\nCATEGORIES:Finance\r\nDTSTART;TZID=Europe/London:20260430T170000\r\nDTEND;TZID=Europe/London:20260430T180000\r\nRRULE:FREQ=MONTHLY;UNTIL=20260831T235959\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/6.ics\n \n \n \"93cf1bad962c75f4\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-006@calendar-wp-plugin\r\nDTSTAMP:20260330T140949Z\r\nSUMMARY:Annual Conference\r\nDESCRIPTION:Annual community conference.\r\nLOCATION:Main Hall\r\nCATEGORIES:Events\r\nDTSTART;TZID=Europe/London:20260615T100000\r\nDTEND;TZID=Europe/London:20260615T170000\r\nRRULE:FREQ=YEARLY;COUNT=3\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/7.ics\n \n \n \"7201c1345f1b2573\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-007@calendar-wp-plugin\r\nDTSTAMP:20260330T140949Z\r\nSUMMARY:Fortnightly Coaching\r\nDESCRIPTION:Coaching check-in.\r\nLOCATION:Online\r\nCATEGORIES:Training\r\nDTSTART;TZID=Europe/London:20260407T150000\r\nDTEND;TZID=Europe/London:20260407T160000\r\nRRULE:FREQ=WEEKLY;INTERVAL=2;UNTIL=20260731T235959\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/8.ics\n \n \n \"a81452cded01c6a1\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-008@calendar-wp-plugin\r\nDTSTAMP:20260330T140949Z\r\nSUMMARY:DST Validation Event\r\nDESCRIPTION:Validates DST transition rendering.\r\nLOCATION:Lab\r\nCATEGORIES:QA\r\nDTSTART;TZID=Europe/London:20261025T003000\r\nDTEND;TZID=Europe/London:20261025T023000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/9.ics\n \n \n \"978f53232a5bb40f\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-009@calendar-wp-plugin\r\nDTSTAMP:20260330T140949Z\r\nSUMMARY:Leap Day Marker\r\nDESCRIPTION:Leap day recurrence behavior.\r\nLOCATION:Calendar\r\nCATEGORIES:QA\r\nDTSTART;TZID=Europe/London:20280229T090000\r\nDTEND;TZID=Europe/London:20280229T100000\r\nRRULE:FREQ=YEARLY;COUNT=3\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/10.ics\n \n \n \"b7eba5ab03b7f422\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-010@calendar-wp-plugin\r\nDTSTAMP:20260330T140949Z\r\nSUMMARY:Therapy Session\r\nDESCRIPTION:Used for single-occurrence delete exception tests.\r\nLOCATION:Clinic\r\nCATEGORIES:Health\r\nDTSTART;TZID=Europe/London:20260403T140000\r\nDTEND;TZID=Europe/London:20260403T150000\r\nRRULE:FREQ=WEEKLY;COUNT=8\r\nEXDATE;TZID=Europe/London:20260417T140000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/smoke-vtimezone.ics\n \n \n \"864b421e254e30ac\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-vtimezone-parser-001\r\nDTSTAMP:20260330T140949Z\r\nSUMMARY:Smoke VTIMEZONE Parse\r\nDTSTART;TZID=Europe/London:20260423T150000\r\nDTEND;TZID=Europe/London:20260423T160000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/smoke-monthly-nth.ics\n \n \n \"44d2d2c64b2e5412\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-nth-001\r\nDTSTAMP:20260330T140949Z\r\nSUMMARY:Smoke Monthly Nth\r\nDTSTART;TZID=Europe/London:20260426T150000\r\nDTEND;TZID=Europe/London:20260426T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SU;BYSETPOS=4\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/smoke-monthly-ordinal.ics\n \n \n \"fa7dfc8cbf381fc0\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-ordinal-001\r\nDTSTAMP:20260330T140949Z\r\nSUMMARY:Smoke Monthly Ordinal\r\nDTSTART;TZID=Europe/London:20260411T150000\r\nDTEND;TZID=Europe/London:20260411T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SA;BYSETPOS=2\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/smoke-monthly-last.ics\n \n \n \"3f65af9c5527a154\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-last-001\r\nDTSTAMP:20260330T140949Z\r\nSUMMARY:Smoke Monthly Last\r\nDTSTART;TZID=Europe/London:20260425T150000\r\nDTEND;TZID=Europe/London:20260425T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SA;BYSETPOS=-1\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/15.ics\n \n \n \"006c790da9929803\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:98fd8f149a14e2dddeb7@calendar-wp-plugin\r\nDTSTAMP:20260330T140949Z\r\nSUMMARY:Smoke 4th Sunday Anchor\r\nDESCRIPTION:anchor-normalization\r\nDTSTART;TZID=Europe/London:20260524T150000\r\nDTEND;TZID=Europe/London:20260524T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SU;BYSETPOS=4\r\nEXDATE;TZID=Europe/London:20260524T150000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/16.ics\n \n \n \"9d5b8db2124188a7\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:ddec804fa982f5df9ed5@calendar-wp-plugin\r\nDTSTAMP:20260330T140949Z\r\nSUMMARY:Smoke Daily Exception TZ\r\nDESCRIPTION:tz-key\r\nDTSTART;TZID=Europe/London:20260302T100000\r\nDTEND;TZID=Europe/London:20260302T110000\r\nRRULE:FREQ=DAILY;UNTIL=20260319T235959\r\nEXDATE;TZID=Europe/London:20260310T100000,20260311T100000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/ed617bc7-66c1-474d-a467-86565e680820.ics\n \n \n \"075d4a0c8d56a83e\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:ed617bc7-66c1-474d-a467-86565e680820\r\nDTSTAMP:20260330T140949Z\r\nSUMMARY:test 8\r\nDESCRIPTION:Default Mozilla Description\r\nDTSTART;TZID=Europe/London:20260302T160000\r\nDTEND;TZID=Europe/London:20260302T170000\r\nRRULE:FREQ=DAILY;UNTIL=20260319T235959\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n", "body_truncated": false}} +{"ts": "2026-03-30T14:12:40+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/ed617bc7-66c1-474d-a467-86565e680820.ics", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "14085", "If-Match": "\"075d4a0c8d56a83e\"", "Origin": "http://localhost:8080", "DNT": "1", "Authorization": "***", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "BEGIN:VCALENDAR\r\nPRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN\r\nVERSION:2.0\r\nBEGIN:VTIMEZONE\r\nTZID:Europe/London\r\nX-TZINFO:Europe/London[2025b]\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:-000115\r\nTZNAME:Europe/London(STD)\r\nDTSTART:18471201T000000\r\nRDATE:18471201T000000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19160521T020000\r\nRDATE:19160521T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19161001T030000\r\nRDATE:19161001T030000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19170408T020000\r\nRDATE:19170408T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19170917T030000\r\nRDATE:19170917T030000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19180324T020000\r\nRDATE:19180324T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19180930T030000\r\nRRULE:FREQ=YEARLY;BYMONTH=9;BYDAY=-1MO;UNTIL=19190929T030000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19190330T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU;UNTIL=19200328T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19201025T030000\r\nRDATE:19201025T030000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19210403T020000\r\nRDATE:19210403T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19211003T030000\r\nRDATE:19211003T030000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19220326T020000\r\nRDATE:19220326T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19221008T030000\r\nRDATE:19221008T030000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19230422T020000\r\nRDATE:19230422T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19240413T020000\r\nRDATE:19240413T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19230916T030000\r\nRRULE:FREQ=YEARLY;BYMONTH=9;BYDAY=3SU;UNTIL=19240921T030000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19250419T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=3SU;UNTIL=19260418T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19270410T020000\r\nRDATE:19270410T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19280422T020000\r\nRDATE:19280422T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19290421T020000\r\nRDATE:19290421T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19300413T020000\r\nRDATE:19300413T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19310419T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=3SU;UNTIL=19320417T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19251004T030000\r\nRRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=1SU;UNTIL=19321002T030000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19330409T020000\r\nRDATE:19330409T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19331008T030000\r\nRDATE:19331008T030000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19340422T020000\r\nRDATE:19340422T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19350414T020000\r\nRDATE:19350414T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19360419T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=3SU;UNTIL=19370418T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19380410T020000\r\nRDATE:19380410T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19341007T030000\r\nRRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=1SU;UNTIL=19381002T030000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19390416T020000\r\nRDATE:19390416T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19400225T020000\r\nRDATE:19400225T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+020000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19410504T020000\r\nRDATE:19410504T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+020000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19410810T030000\r\nRDATE:19410810T030000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+020000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19420405T020000\r\nRDATE:19420405T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+020000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19420809T030000\r\nRDATE:19420809T030000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+020000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19430404T020000\r\nRDATE:19430404T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+020000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19430815T030000\r\nRDATE:19430815T030000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+020000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19440402T020000\r\nRDATE:19440402T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+020000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19440917T030000\r\nRDATE:19440917T030000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+020000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19450402T020000\r\nRDATE:19450402T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19391119T030000\r\nRDATE:19391119T030000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+020000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19450715T030000\r\nRDATE:19450715T030000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19460414T020000\r\nRDATE:19460414T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19470316T020000\r\nRDATE:19470316T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+020000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19470413T020000\r\nRDATE:19470413T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19451007T030000\r\nRRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=1SU;UNTIL=19461006T030000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+020000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19470810T030000\r\nRDATE:19470810T030000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19471102T030000\r\nRDATE:19471102T030000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19480314T020000\r\nRDATE:19480314T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19490403T020000\r\nRDATE:19490403T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19481031T030000\r\nRRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU;UNTIL=19491030T030000\r\nEND:STANDARD\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19501022T030000\r\nRDATE:19501022T030000\r\nEND:STANDARD\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19511021T030000\r\nRDATE:19511021T030000\r\nEND:STANDARD\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19521026T030000\r\nRDATE:19521026T030000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19500416T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=3SU;UNTIL=19530419T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19540411T020000\r\nRDATE:19540411T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19550417T020000\r\nRDATE:19550417T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19560422T020000\r\nRDATE:19560422T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19570414T020000\r\nRDATE:19570414T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19580420T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=3SU;UNTIL=19590419T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19600410T020000\r\nRDATE:19600410T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19531004T030000\r\nRRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=1SU;UNTIL=19601002T030000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19610326T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU;UNTIL=19630331T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19640322T020000\r\nRDATE:19640322T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19611029T030000\r\nRRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU;UNTIL=19641025T030000\r\nEND:STANDARD\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19651024T030000\r\nRRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=4SU;UNTIL=19661023T030000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19650321T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=3SU;UNTIL=19670319T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19671029T030000\r\nRDATE:19671029T030000\r\nEND:STANDARD\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19681027T000000\r\nRDATE:19681027T000000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19680218T020000\r\nRDATE:19680218T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19711031T030000\r\nRRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU;UNTIL=19751026T030000\r\nEND:STANDARD\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19761024T030000\r\nRRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=4SU;UNTIL=19771023T030000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19720319T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=3SU;UNTIL=19800316T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19781029T030000\r\nRRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU;UNTIL=19801026T030000\r\nEND:STANDARD\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19811025T020000\r\nRDATE:19811025T020000\r\nEND:STANDARD\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19821024T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=4SU;UNTIL=19831023T020000\r\nEND:STANDARD\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19841028T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU;UNTIL=19871025T020000\r\nEND:STANDARD\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19881023T020000\r\nRDATE:19881023T020000\r\nEND:STANDARD\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19891029T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU;UNTIL=19921025T020000\r\nEND:STANDARD\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19931024T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=4SU;UNTIL=19951022T020000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19810329T010000\r\nRRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU;UNTIL=19960331T010000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19961027T020000\r\nRDATE:19961027T020000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:(DST)\r\nDTSTART:19970330T010000\r\nRRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:(STD)\r\nDTSTART:19971026T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU\r\nEND:STANDARD\r\nEND:VTIMEZONE\r\nBEGIN:VEVENT\r\nLAST-MODIFIED:20260330T141240Z\r\nDTSTAMP:20260330T141240Z\r\nUID:ed617bc7-66c1-474d-a467-86565e680820\r\nSUMMARY:test 8\r\nRRULE:FREQ=DAILY;UNTIL=20260319T235959\r\nEXDATE;TZID=Europe/London:20260312T160000\r\nDTSTART;TZID=Europe/London:20260302T160000\r\nDTEND;TZID=Europe/London:20260302T170000\r\nDESCRIPTION:Default Mozilla Description\r\nSEQUENCE:1\r\nX-MOZ-GENERATION:1\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:12:40 GMT", "ETag": "\"4b22d7e9f955d2a5\""}, "body": "HTTP/1.0 200 OK\r\nServer: CalendarFixture/0.1 Python/3.13.5\r\nDate: Mon, 30 Mar 2026 14:12:40 GMT\r\nETag: \"4b22d7e9f955d2a5\"\r\n\r\n", "body_truncated": false}} +{"ts": "2026-03-30T14:12:40+00:00", "client": "127.0.0.1", "method": "REPORT", "path": "/caldav/calendars/public/", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/xml; charset=utf-8", "Content-Length": "266", "Depth": "1", "Origin": "http://localhost:8080", "DNT": "1", "Authorization": "***", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "\n/caldav/calendars/public/ed617bc7-66c1-474d-a467-86565e680820.ics", "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:12:40 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "14799"}, "body": "HTTP/1.0 207 Multi-Status\r\nServer: CalendarFixture/0.1 Python/3.13.5\r\nDate: Mon, 30 Mar 2026 14:12:40 GMT\r\nContent-Type: application/xml; charset=utf-8\r\nContent-Length: 14799\r\n\r\n\n\n\n /caldav/calendars/public/1.ics\n \n \n \"b2246c0ba36be704\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-001@calendar-wp-plugin\r\nDTSTAMP:20260330T141240Z\r\nSUMMARY:Board Meeting\r\nDESCRIPTION:Quarterly board review.\r\nLOCATION:Room A\r\nCATEGORIES:Governance\r\nDTSTART;TZID=Europe/London:20260401T100000\r\nDTEND;TZID=Europe/London:20260401T113000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/2.ics\n \n \n \"97ffb830a549901c\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-002@calendar-wp-plugin\r\nDTSTAMP:20260330T141240Z\r\nSUMMARY:Office Closed\r\nDESCRIPTION:Public holiday closure.\r\nLOCATION:HQ\r\nCATEGORIES:Operations\r\nDTSTART;VALUE=DATE:20260504\r\nDTEND;VALUE=DATE:20260505\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/3.ics\n \n \n \"0f947e3952fcf681\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-003@calendar-wp-plugin\r\nDTSTAMP:20260330T141240Z\r\nSUMMARY:Daily Standup\r\nDESCRIPTION:15 minute sync.\r\nLOCATION:Online\r\nCATEGORIES:Team\r\nDTSTART;TZID=Europe/London:20260406T090000\r\nDTEND;TZID=Europe/London:20260406T091500\r\nRRULE:FREQ=DAILY;COUNT=10\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/4.ics\n \n \n \"4f06e2ef4d6d8e38\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-004@calendar-wp-plugin\r\nDTSTAMP:20260330T141240Z\r\nSUMMARY:Community Lunch\r\nDESCRIPTION:Weekly community lunch.\r\nLOCATION:Cafeteria\r\nCATEGORIES:Community\r\nDTSTART;TZID=Europe/London:20260408T123000\r\nDTEND;TZID=Europe/London:20260408T133000\r\nRRULE:FREQ=WEEKLY\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/5.ics\n \n \n \"927953eb5c8dd26c\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-005@calendar-wp-plugin\r\nDTSTAMP:20260330T141240Z\r\nSUMMARY:Finance Close\r\nDESCRIPTION:Month-end close process.\r\nLOCATION:Finance Office\r\nCATEGORIES:Finance\r\nDTSTART;TZID=Europe/London:20260430T170000\r\nDTEND;TZID=Europe/London:20260430T180000\r\nRRULE:FREQ=MONTHLY;UNTIL=20260831T235959\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/6.ics\n \n \n \"93cf1bad962c75f4\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-006@calendar-wp-plugin\r\nDTSTAMP:20260330T141240Z\r\nSUMMARY:Annual Conference\r\nDESCRIPTION:Annual community conference.\r\nLOCATION:Main Hall\r\nCATEGORIES:Events\r\nDTSTART;TZID=Europe/London:20260615T100000\r\nDTEND;TZID=Europe/London:20260615T170000\r\nRRULE:FREQ=YEARLY;COUNT=3\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/7.ics\n \n \n \"7201c1345f1b2573\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-007@calendar-wp-plugin\r\nDTSTAMP:20260330T141240Z\r\nSUMMARY:Fortnightly Coaching\r\nDESCRIPTION:Coaching check-in.\r\nLOCATION:Online\r\nCATEGORIES:Training\r\nDTSTART;TZID=Europe/London:20260407T150000\r\nDTEND;TZID=Europe/London:20260407T160000\r\nRRULE:FREQ=WEEKLY;INTERVAL=2;UNTIL=20260731T235959\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/8.ics\n \n \n \"a81452cded01c6a1\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-008@calendar-wp-plugin\r\nDTSTAMP:20260330T141240Z\r\nSUMMARY:DST Validation Event\r\nDESCRIPTION:Validates DST transition rendering.\r\nLOCATION:Lab\r\nCATEGORIES:QA\r\nDTSTART;TZID=Europe/London:20261025T003000\r\nDTEND;TZID=Europe/London:20261025T023000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/9.ics\n \n \n \"978f53232a5bb40f\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-009@calendar-wp-plugin\r\nDTSTAMP:20260330T141240Z\r\nSUMMARY:Leap Day Marker\r\nDESCRIPTION:Leap day recurrence behavior.\r\nLOCATION:Calendar\r\nCATEGORIES:QA\r\nDTSTART;TZID=Europe/London:20280229T090000\r\nDTEND;TZID=Europe/London:20280229T100000\r\nRRULE:FREQ=YEARLY;COUNT=3\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/10.ics\n \n \n \"b7eba5ab03b7f422\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-010@calendar-wp-plugin\r\nDTSTAMP:20260330T141240Z\r\nSUMMARY:Therapy Session\r\nDESCRIPTION:Used for single-occurrence delete exception tests.\r\nLOCATION:Clinic\r\nCATEGORIES:Health\r\nDTSTART;TZID=Europe/London:20260403T140000\r\nDTEND;TZID=Europe/London:20260403T150000\r\nRRULE:FREQ=WEEKLY;COUNT=8\r\nEXDATE;TZID=Europe/London:20260417T140000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/smoke-vtimezone.ics\n \n \n \"864b421e254e30ac\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-vtimezone-parser-001\r\nDTSTAMP:20260330T141240Z\r\nSUMMARY:Smoke VTIMEZONE Parse\r\nDTSTART;TZID=Europe/London:20260423T150000\r\nDTEND;TZID=Europe/London:20260423T160000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/smoke-monthly-nth.ics\n \n \n \"44d2d2c64b2e5412\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-nth-001\r\nDTSTAMP:20260330T141240Z\r\nSUMMARY:Smoke Monthly Nth\r\nDTSTART;TZID=Europe/London:20260426T150000\r\nDTEND;TZID=Europe/London:20260426T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SU;BYSETPOS=4\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/smoke-monthly-ordinal.ics\n \n \n \"fa7dfc8cbf381fc0\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-ordinal-001\r\nDTSTAMP:20260330T141240Z\r\nSUMMARY:Smoke Monthly Ordinal\r\nDTSTART;TZID=Europe/London:20260411T150000\r\nDTEND;TZID=Europe/London:20260411T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SA;BYSETPOS=2\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/smoke-monthly-last.ics\n \n \n \"3f65af9c5527a154\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-last-001\r\nDTSTAMP:20260330T141240Z\r\nSUMMARY:Smoke Monthly Last\r\nDTSTART;TZID=Europe/London:20260425T150000\r\nDTEND;TZID=Europe/London:20260425T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SA;BYSETPOS=-1\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/15.ics\n \n \n \"006c790da9929803\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:98fd8f149a14e2dddeb7@calendar-wp-plugin\r\nDTSTAMP:20260330T141240Z\r\nSUMMARY:Smoke 4th Sunday Anchor\r\nDESCRIPTION:anchor-normalization\r\nDTSTART;TZID=Europe/London:20260524T150000\r\nDTEND;TZID=Europe/London:20260524T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SU;BYSETPOS=4\r\nEXDATE;TZID=Europe/London:20260524T150000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/16.ics\n \n \n \"9d5b8db2124188a7\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:ddec804fa982f5df9ed5@calendar-wp-plugin\r\nDTSTAMP:20260330T141240Z\r\nSUMMARY:Smoke Daily Exception TZ\r\nDESCRIPTION:tz-key\r\nDTSTART;TZID=Europe/London:20260302T100000\r\nDTEND;TZID=Europe/London:20260302T110000\r\nRRULE:FREQ=DAILY;UNTIL=20260319T235959\r\nEXDATE;TZID=Europe/London:20260310T100000,20260311T100000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/ed617bc7-66c1-474d-a467-86565e680820.ics\n \n \n \"4b22d7e9f955d2a5\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:ed617bc7-66c1-474d-a467-86565e680820\r\nDTSTAMP:20260330T141240Z\r\nSUMMARY:test 8\r\nDESCRIPTION:Default Mozilla Description\r\nDTSTART;TZID=Europe/London:20260302T160000\r\nDTEND;TZID=Europe/London:20260302T170000\r\nRRULE:FREQ=DAILY;UNTIL=20260319T235959\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n", "body_truncated": false}} +{"ts": "2026-03-30T14:12:45+00:00", "client": "127.0.0.1", "method": "PROPFIND", "path": "/caldav/calendars/public/", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/xml; charset=utf-8", "Content-Length": "144", "Depth": "1", "Origin": "http://localhost:8080", "DNT": "1", "Authorization": "***", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "\n", "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:12:45 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "5816"}, "body": "HTTP/1.0 207 Multi-Status\r\nServer: CalendarFixture/0.1 Python/3.13.5\r\nDate: Mon, 30 Mar 2026 14:12:45 GMT\r\nContent-Type: application/xml; charset=utf-8\r\nContent-Length: 5816\r\n\r\n\n\n\n /caldav/calendars/public/\n \n \n Public Calendar\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/1.ics\n \n \n text/calendar; charset=utf-8\"b2246c0ba36be704\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/2.ics\n \n \n text/calendar; charset=utf-8\"97ffb830a549901c\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/3.ics\n \n \n text/calendar; charset=utf-8\"0f947e3952fcf681\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/4.ics\n \n \n text/calendar; charset=utf-8\"4f06e2ef4d6d8e38\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/5.ics\n \n \n text/calendar; charset=utf-8\"927953eb5c8dd26c\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/6.ics\n \n \n text/calendar; charset=utf-8\"93cf1bad962c75f4\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/7.ics\n \n \n text/calendar; charset=utf-8\"7201c1345f1b2573\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/8.ics\n \n \n text/calendar; charset=utf-8\"a81452cded01c6a1\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/9.ics\n \n \n text/calendar; charset=utf-8\"978f53232a5bb40f\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/10.ics\n \n \n text/calendar; charset=utf-8\"b7eba5ab03b7f422\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/smoke-vtimezone.ics\n \n \n text/calendar; charset=utf-8\"864b421e254e30ac\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/smoke-monthly-nth.ics\n \n \n text/calendar; charset=utf-8\"44d2d2c64b2e5412\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/smoke-monthly-ordinal.ics\n \n \n text/calendar; charset=utf-8\"fa7dfc8cbf381fc0\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/smoke-monthly-last.ics\n \n \n text/calendar; charset=utf-8\"3f65af9c5527a154\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/15.ics\n \n \n text/calendar; charset=utf-8\"006c790da9929803\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/16.ics\n \n \n text/calendar; charset=utf-8\"9d5b8db2124188a7\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/ed617bc7-66c1-474d-a467-86565e680820.ics\n \n \n text/calendar; charset=utf-8\"4b22d7e9f955d2a5\"\n \n HTTP/1.1 200 OK\n \n\n\n", "body_truncated": false}} +{"ts": "2026-03-30T14:14:36+00:00", "client": "127.0.0.1", "method": "GET", "path": "/calendar", "request": {"headers": {"Host": "127.0.0.1:18080", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:14:36 GMT", "Content-Type": "text/html; charset=utf-8", "Content-Length": "43446"}, "body": "HTTP/1.0 200 OK\r\nServer: CalendarFixture/0.1 Python/3.13.5\r\nDate: Mon, 30 Mar 2026 14:14:36 GMT\r\nContent-Type: text/html; charset=utf-8\r\nContent-Length: 43446\r\n\r\n\n\n\n \n \n Calendar\n \n\n\n\n
Calendar Fixture Public Calendar UI
\n
\n
\n
\n
\n
\n \n
\n
\n
\n \n \n \n \n \n
\n
\n ICS Link\n CalDAV\n
\n
\n \n
\n
\n
\n
\n
\n
\n

Account Login

\n
\n
\n
\n
\n
\n
\n \n \n
\n
\n

Register

\n
\n
\n
\n
\n
\n
\n
\n

Password Recovery

\n
\n \n
\n
\n \n
\n
\n
\n
\n
\n

Event Details

\n
\n
\n
\n
\n
\n
\n
\n
\n

Event

\n \n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n \n
\n
\n
\n
\n
\n \n
\n
\n
\n
\n
\n
\n
\n
\n \n
\n
\n
\n
\n
\n
\n
\n
\n \n \n \n \n \n
\n
\n

Delete Single Occurrence (Exception)

\n
\n \n \n
\n
\n \n
\n \n
\n
\n
\n
\n\n\n\n", "body_truncated": false}} +{"ts": "2026-03-30T14:14:36+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:18080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:14:36 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "6585"}, "body": "HTTP/1.0 200 OK\r\nServer: CalendarFixture/0.1 Python/3.13.5\r\nDate: Mon, 30 Mar 2026 14:14:36 GMT\r\nContent-Type: application/json; charset=utf-8\r\nContent-Length: 6585\r\n\r\n{\"data\": [{\"id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"start_datetime\": \"2026-04-01T10:00:00+01:00\", \"end_datetime\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"9e61840edd26b59c\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T14:14:36+00:00\"}, {\"id\": 2, \"uid\": \"fixture-ce-002@calendar-wp-plugin\", \"title\": \"Office Closed\", \"description\": \"Public holiday closure.\", \"location\": \"HQ\", \"category\": \"Operations\", \"all_day_event\": true, \"start_datetime\": \"2026-05-04\", \"end_datetime\": \"2026-05-05\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"6f79e4a470012e8c\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T14:14:36+00:00\"}, {\"id\": 3, \"uid\": \"fixture-ce-003@calendar-wp-plugin\", \"title\": \"Daily Standup\", \"description\": \"15 minute sync.\", \"location\": \"Online\", \"category\": \"Team\", \"all_day_event\": false, \"start_datetime\": \"2026-04-06T09:00:00+01:00\", \"end_datetime\": \"2026-04-06T09:15:00+01:00\", \"repeat_type\": \"daily\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"count\", \"repeat_count\": 10, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"7dd021ab4c51e62e\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T14:14:36+00:00\"}, {\"id\": 4, \"uid\": \"fixture-ce-004@calendar-wp-plugin\", \"title\": \"Community Lunch\", \"description\": \"Weekly community lunch.\", \"location\": \"Cafeteria\", \"category\": \"Community\", \"all_day_event\": false, \"start_datetime\": \"2026-04-08T12:30:00+01:00\", \"end_datetime\": \"2026-04-08T13:30:00+01:00\", \"repeat_type\": \"weekly\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"no_end\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"382b171c7e467f49\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T14:14:36+00:00\"}, {\"id\": 5, \"uid\": \"fixture-ce-005@calendar-wp-plugin\", \"title\": \"Finance Close\", \"description\": \"Month-end close process.\", \"location\": \"Finance Office\", \"category\": \"Finance\", \"all_day_event\": false, \"start_datetime\": \"2026-04-30T17:00:00+01:00\", \"end_datetime\": \"2026-04-30T18:00:00+01:00\", \"repeat_type\": \"monthly\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"until\", \"repeat_count\": null, \"repeat_until\": \"2026-08-31\", \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"6a0b1853010a3060\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T14:14:36+00:00\"}, {\"id\": 6, \"uid\": \"fixture-ce-006@calendar-wp-plugin\", \"title\": \"Annual Conference\", \"description\": \"Annual community conference.\", \"location\": \"Main Hall\", \"category\": \"Events\", \"all_day_event\": false, \"start_datetime\": \"2026-06-15T10:00:00+01:00\", \"end_datetime\": \"2026-06-15T17:00:00+01:00\", \"repeat_type\": \"yearly\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"count\", \"repeat_count\": 3, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"0344b53653b1897e\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T14:14:36+00:00\"}, {\"id\": 7, \"uid\": \"fixture-ce-007@calendar-wp-plugin\", \"title\": \"Fortnightly Coaching\", \"description\": \"Coaching check-in.\", \"location\": \"Online\", \"category\": \"Training\", \"all_day_event\": false, \"start_datetime\": \"2026-04-07T15:00:00+01:00\", \"end_datetime\": \"2026-04-07T16:00:00+01:00\", \"repeat_type\": \"custom\", \"repeat_interval\": 2, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"until\", \"repeat_count\": null, \"repeat_until\": \"2026-07-31\", \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"f3727cb12d14614a\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T14:14:36+00:00\"}, {\"id\": 8, \"uid\": \"fixture-ce-008@calendar-wp-plugin\", \"title\": \"DST Validation Event\", \"description\": \"Validates DST transition rendering.\", \"location\": \"Lab\", \"category\": \"QA\", \"all_day_event\": false, \"start_datetime\": \"2026-10-25T00:30:00+01:00\", \"end_datetime\": \"2026-10-25T02:30:00+00:00\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"2200904959f9c5cd\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T14:14:36+00:00\"}, {\"id\": 9, \"uid\": \"fixture-ce-009@calendar-wp-plugin\", \"title\": \"Leap Day Marker\", \"description\": \"Leap day recurrence behavior.\", \"location\": \"Calendar\", \"category\": \"QA\", \"all_day_event\": false, \"start_datetime\": \"2028-02-29T09:00:00+00:00\", \"end_datetime\": \"2028-02-29T10:00:00+00:00\", \"repeat_type\": \"yearly\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"count\", \"repeat_count\": 3, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"028aa2cacadd930b\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T14:14:36+00:00\"}, {\"id\": 10, \"uid\": \"fixture-ce-010@calendar-wp-plugin\", \"title\": \"Therapy Session\", \"description\": \"Used for single-occurrence delete exception tests.\", \"location\": \"Clinic\", \"category\": \"Health\", \"all_day_event\": false, \"start_datetime\": \"2026-04-03T14:00:00+01:00\", \"end_datetime\": \"2026-04-03T15:00:00+01:00\", \"repeat_type\": \"weekly\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"count\", \"repeat_count\": 8, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"47a32ea36bccb6cf\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T14:14:36+00:00\"}], \"meta\": {\"count\": 10}}", "body_truncated": false}} +{"ts": "2026-03-30T14:14:36+00:00", "client": "127.0.0.1", "method": "GET", "path": "/calendar.ics", "request": {"headers": {"Host": "127.0.0.1:18080", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:14:36 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"b626877cd3d20036\"", "Content-Length": "3132"}, "body": "HTTP/1.0 200 OK\r\nServer: CalendarFixture/0.1 Python/3.13.5\r\nDate: Mon, 30 Mar 2026 14:14:36 GMT\r\nContent-Type: text/calendar; charset=utf-8\r\nETag: \"b626877cd3d20036\"\r\nContent-Length: 3132\r\n\r\nBEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nCALSCALE:GREGORIAN\r\nX-WR-TIMEZONE:Europe/London\r\nBEGIN:VEVENT\r\nUID:fixture-ce-001@calendar-wp-plugin\r\nDTSTAMP:20260330T141436Z\r\nSUMMARY:Board Meeting\r\nDESCRIPTION:Quarterly board review.\r\nLOCATION:Room A\r\nCATEGORIES:Governance\r\nDTSTART;TZID=Europe/London:20260401T100000\r\nDTEND;TZID=Europe/London:20260401T113000\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-002@calendar-wp-plugin\r\nDTSTAMP:20260330T141436Z\r\nSUMMARY:Office Closed\r\nDESCRIPTION:Public holiday closure.\r\nLOCATION:HQ\r\nCATEGORIES:Operations\r\nDTSTART;VALUE=DATE:20260504\r\nDTEND;VALUE=DATE:20260505\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-003@calendar-wp-plugin\r\nDTSTAMP:20260330T141436Z\r\nSUMMARY:Daily Standup\r\nDESCRIPTION:15 minute sync.\r\nLOCATION:Online\r\nCATEGORIES:Team\r\nDTSTART;TZID=Europe/London:20260406T090000\r\nDTEND;TZID=Europe/London:20260406T091500\r\nRRULE:FREQ=DAILY;COUNT=10\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-004@calendar-wp-plugin\r\nDTSTAMP:20260330T141436Z\r\nSUMMARY:Community Lunch\r\nDESCRIPTION:Weekly community lunch.\r\nLOCATION:Cafeteria\r\nCATEGORIES:Community\r\nDTSTART;TZID=Europe/London:20260408T123000\r\nDTEND;TZID=Europe/London:20260408T133000\r\nRRULE:FREQ=WEEKLY\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-005@calendar-wp-plugin\r\nDTSTAMP:20260330T141436Z\r\nSUMMARY:Finance Close\r\nDESCRIPTION:Month-end close process.\r\nLOCATION:Finance Office\r\nCATEGORIES:Finance\r\nDTSTART;TZID=Europe/London:20260430T170000\r\nDTEND;TZID=Europe/London:20260430T180000\r\nRRULE:FREQ=MONTHLY;UNTIL=20260831T235959\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-006@calendar-wp-plugin\r\nDTSTAMP:20260330T141436Z\r\nSUMMARY:Annual Conference\r\nDESCRIPTION:Annual community conference.\r\nLOCATION:Main Hall\r\nCATEGORIES:Events\r\nDTSTART;TZID=Europe/London:20260615T100000\r\nDTEND;TZID=Europe/London:20260615T170000\r\nRRULE:FREQ=YEARLY;COUNT=3\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-007@calendar-wp-plugin\r\nDTSTAMP:20260330T141436Z\r\nSUMMARY:Fortnightly Coaching\r\nDESCRIPTION:Coaching check-in.\r\nLOCATION:Online\r\nCATEGORIES:Training\r\nDTSTART;TZID=Europe/London:20260407T150000\r\nDTEND;TZID=Europe/London:20260407T160000\r\nRRULE:FREQ=WEEKLY;INTERVAL=2;UNTIL=20260731T235959\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-008@calendar-wp-plugin\r\nDTSTAMP:20260330T141436Z\r\nSUMMARY:DST Validation Event\r\nDESCRIPTION:Validates DST transition rendering.\r\nLOCATION:Lab\r\nCATEGORIES:QA\r\nDTSTART;TZID=Europe/London:20261025T003000\r\nDTEND;TZID=Europe/London:20261025T023000\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-009@calendar-wp-plugin\r\nDTSTAMP:20260330T141436Z\r\nSUMMARY:Leap Day Marker\r\nDESCRIPTION:Leap day recurrence behavior.\r\nLOCATION:Calendar\r\nCATEGORIES:QA\r\nDTSTART;TZID=Europe/London:20280229T090000\r\nDTEND;TZID=Europe/London:20280229T100000\r\nRRULE:FREQ=YEARLY;COUNT=3\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-010@calendar-wp-plugin\r\nDTSTAMP:20260330T141436Z\r\nSUMMARY:Therapy Session\r\nDESCRIPTION:Used for single-occurrence delete exception tests.\r\nLOCATION:Clinic\r\nCATEGORIES:Health\r\nDTSTART;TZID=Europe/London:20260403T140000\r\nDTEND;TZID=Europe/London:20260403T150000\r\nRRULE:FREQ=WEEKLY;COUNT=8\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_truncated": false}} +{"ts": "2026-03-30T14:14:36+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/1.ics", "request": {"headers": {"Host": "127.0.0.1:18080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:14:36 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"9e61840edd26b59c\"", "Content-Length": "364"}, "body": "HTTP/1.0 200 OK\r\nServer: CalendarFixture/0.1 Python/3.13.5\r\nDate: Mon, 30 Mar 2026 14:14:36 GMT\r\nContent-Type: text/calendar; charset=utf-8\r\nETag: \"9e61840edd26b59c\"\r\nContent-Length: 364\r\n\r\nBEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-001@calendar-wp-plugin\r\nDTSTAMP:20260330T141436Z\r\nSUMMARY:Board Meeting\r\nDESCRIPTION:Quarterly board review.\r\nLOCATION:Room A\r\nCATEGORIES:Governance\r\nDTSTART;TZID=Europe/London:20260401T100000\r\nDTEND;TZID=Europe/London:20260401T113000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_truncated": false}} +{"ts": "2026-03-30T14:14:36+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/10/occurrences/2026-04-17T14:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:18080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:14:36 GMT"}, "body": "HTTP/1.0 204 No Content\r\nServer: CalendarFixture/0.1 Python/3.13.5\r\nDate: Mon, 30 Mar 2026 14:14:36 GMT\r\n\r\n", "body_truncated": false}} +{"ts": "2026-03-30T14:14:36+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-vtimezone.ics", "request": {"headers": {"Host": "127.0.0.1:18080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "454"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN\nVERSION:2.0\nBEGIN:VTIMEZONE\nTZID:Europe/London\nBEGIN:STANDARD\nDTSTART:18471201T000000\nRRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=9\nTZOFFSETFROM:+0115\nTZOFFSETTO:+0000\nTZNAME:GMT\nEND:STANDARD\nEND:VTIMEZONE\nBEGIN:VEVENT\nUID:smoke-vtimezone-parser-001\nSUMMARY:Smoke VTIMEZONE Parse\nDTSTART;TZID=Europe/London:20260423T150000\nDTEND;TZID=Europe/London:20260423T160000\nEND:VEVENT\nEND:VCALENDAR\n", "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:14:36 GMT", "ETag": "\"e5984c98962fda96\""}, "body": "HTTP/1.0 201 Created\r\nServer: CalendarFixture/0.1 Python/3.13.5\r\nDate: Mon, 30 Mar 2026 14:14:36 GMT\r\nETag: \"e5984c98962fda96\"\r\n\r\n", "body_truncated": false}} +{"ts": "2026-03-30T14:14:36+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-vtimezone.ics", "request": {"headers": {"Host": "127.0.0.1:18080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:14:36 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"e5984c98962fda96\"", "Content-Length": "288"}, "body": "HTTP/1.0 200 OK\r\nServer: CalendarFixture/0.1 Python/3.13.5\r\nDate: Mon, 30 Mar 2026 14:14:36 GMT\r\nContent-Type: text/calendar; charset=utf-8\r\nETag: \"e5984c98962fda96\"\r\nContent-Length: 288\r\n\r\nBEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-vtimezone-parser-001\r\nDTSTAMP:20260330T141436Z\r\nSUMMARY:Smoke VTIMEZONE Parse\r\nDTSTART;TZID=Europe/London:20260423T150000\r\nDTEND;TZID=Europe/London:20260423T160000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_truncated": false}} +{"ts": "2026-03-30T14:14:36+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-monthly-nth.ics", "request": {"headers": {"Host": "127.0.0.1:18080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "261"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-monthly-nth-001\nSUMMARY:Smoke Monthly Nth\nDTSTART;TZID=Europe/London:20260402T150000\nDTEND;TZID=Europe/London:20260402T160000\nRRULE:FREQ=MONTHLY;BYDAY=SU;BYSETPOS=4\nEND:VEVENT\nEND:VCALENDAR\n", "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:14:36 GMT", "ETag": "\"a4f11de675293164\""}, "body": "HTTP/1.0 201 Created\r\nServer: CalendarFixture/0.1 Python/3.13.5\r\nDate: Mon, 30 Mar 2026 14:14:36 GMT\r\nETag: \"a4f11de675293164\"\r\n\r\n", "body_truncated": false}} +{"ts": "2026-03-30T14:14:36+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-monthly-nth.ics", "request": {"headers": {"Host": "127.0.0.1:18080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:14:36 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"a4f11de675293164\"", "Content-Length": "319"}, "body": "HTTP/1.0 200 OK\r\nServer: CalendarFixture/0.1 Python/3.13.5\r\nDate: Mon, 30 Mar 2026 14:14:36 GMT\r\nContent-Type: text/calendar; charset=utf-8\r\nETag: \"a4f11de675293164\"\r\nContent-Length: 319\r\n\r\nBEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-nth-001\r\nDTSTAMP:20260330T141436Z\r\nSUMMARY:Smoke Monthly Nth\r\nDTSTART;TZID=Europe/London:20260426T150000\r\nDTEND;TZID=Europe/London:20260426T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SU;BYSETPOS=4\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_truncated": false}} +{"ts": "2026-03-30T14:14:36+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-monthly-ordinal.ics", "request": {"headers": {"Host": "127.0.0.1:18080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "259"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-monthly-ordinal-001\nSUMMARY:Smoke Monthly Ordinal\nDTSTART;TZID=Europe/London:20260411T150000\nDTEND;TZID=Europe/London:20260411T160000\nRRULE:FREQ=MONTHLY;BYDAY=2SA\nEND:VEVENT\nEND:VCALENDAR\n", "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:14:36 GMT", "ETag": "\"db9706d0865b79d9\""}, "body": "HTTP/1.0 201 Created\r\nServer: CalendarFixture/0.1 Python/3.13.5\r\nDate: Mon, 30 Mar 2026 14:14:36 GMT\r\nETag: \"db9706d0865b79d9\"\r\n\r\n", "body_truncated": false}} +{"ts": "2026-03-30T14:14:36+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-monthly-ordinal.ics", "request": {"headers": {"Host": "127.0.0.1:18080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:14:36 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"db9706d0865b79d9\"", "Content-Length": "327"}, "body": "HTTP/1.0 200 OK\r\nServer: CalendarFixture/0.1 Python/3.13.5\r\nDate: Mon, 30 Mar 2026 14:14:36 GMT\r\nContent-Type: text/calendar; charset=utf-8\r\nETag: \"db9706d0865b79d9\"\r\nContent-Length: 327\r\n\r\nBEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-ordinal-001\r\nDTSTAMP:20260330T141436Z\r\nSUMMARY:Smoke Monthly Ordinal\r\nDTSTART;TZID=Europe/London:20260411T150000\r\nDTEND;TZID=Europe/London:20260411T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SA;BYSETPOS=2\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_truncated": false}} +{"ts": "2026-03-30T14:14:36+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-monthly-last.ics", "request": {"headers": {"Host": "127.0.0.1:18080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "264"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-monthly-last-001\nSUMMARY:Smoke Monthly Last\nDTSTART;TZID=Europe/London:20260425T150000\nDTEND;TZID=Europe/London:20260425T160000\nRRULE:FREQ=MONTHLY;BYDAY=SA;BYSETPOS=-1\nEND:VEVENT\nEND:VCALENDAR\n", "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:14:36 GMT", "ETag": "\"9b7f63d5c08cc7c8\""}, "body": "HTTP/1.0 201 Created\r\nServer: CalendarFixture/0.1 Python/3.13.5\r\nDate: Mon, 30 Mar 2026 14:14:36 GMT\r\nETag: \"9b7f63d5c08cc7c8\"\r\n\r\n", "body_truncated": false}} +{"ts": "2026-03-30T14:14:36+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-monthly-last.ics", "request": {"headers": {"Host": "127.0.0.1:18080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:14:36 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"9b7f63d5c08cc7c8\"", "Content-Length": "322"}, "body": "HTTP/1.0 200 OK\r\nServer: CalendarFixture/0.1 Python/3.13.5\r\nDate: Mon, 30 Mar 2026 14:14:36 GMT\r\nContent-Type: text/calendar; charset=utf-8\r\nETag: \"9b7f63d5c08cc7c8\"\r\nContent-Length: 322\r\n\r\nBEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-last-001\r\nDTSTAMP:20260330T141436Z\r\nSUMMARY:Smoke Monthly Last\r\nDTSTART;TZID=Europe/London:20260425T150000\r\nDTEND;TZID=Europe/London:20260425T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SA;BYSETPOS=-1\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_truncated": false}} +{"ts": "2026-03-30T14:14:36+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:18080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "X-WP-User": "admin", "Content-Length": "362"}, "body": "{\"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\"}", "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:14:36 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "673"}, "body": "HTTP/1.0 201 Created\r\nServer: CalendarFixture/0.1 Python/3.13.5\r\nDate: Mon, 30 Mar 2026 14:14:36 GMT\r\nContent-Type: application/json; charset=utf-8\r\nContent-Length: 673\r\n\r\n{\"data\": {\"id\": 15, \"uid\": \"4e9ee4c86911a55106b9@calendar-wp-plugin\", \"title\": \"Smoke 4th Sunday Anchor\", \"description\": \"anchor-normalization\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"start_datetime\": \"2026-05-24T15:00:00+01:00\", \"end_datetime\": \"2026-05-24T16:00:00+01:00\", \"repeat_type\": \"monthly\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"weekday_of_month\", \"repeat_nth_day\": null, \"repeat_nth_pos\": 4, \"repeat_nth_weekday\": 0, \"repeat_range_mode\": \"no_end\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"0323ee6baedfc4b5\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T14:14:36+00:00\"}}", "body_truncated": false}} +{"ts": "2026-03-30T14:14:36+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/15/occurrences/2026-05-24T15:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:18080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:14:36 GMT"}, "body": "HTTP/1.0 204 No Content\r\nServer: CalendarFixture/0.1 Python/3.13.5\r\nDate: Mon, 30 Mar 2026 14:14:36 GMT\r\n\r\n", "body_truncated": false}} +{"ts": "2026-03-30T14:14:36+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/15.ics", "request": {"headers": {"Host": "127.0.0.1:18080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:14:36 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"e6f09ba57238f931\"", "Content-Length": "420"}, "body": "HTTP/1.0 200 OK\r\nServer: CalendarFixture/0.1 Python/3.13.5\r\nDate: Mon, 30 Mar 2026 14:14:36 GMT\r\nContent-Type: text/calendar; charset=utf-8\r\nETag: \"e6f09ba57238f931\"\r\nContent-Length: 420\r\n\r\nBEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:4e9ee4c86911a55106b9@calendar-wp-plugin\r\nDTSTAMP:20260330T141436Z\r\nSUMMARY:Smoke 4th Sunday Anchor\r\nDESCRIPTION:anchor-normalization\r\nDTSTART;TZID=Europe/London:20260524T150000\r\nDTEND;TZID=Europe/London:20260524T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SU;BYSETPOS=4\r\nEXDATE;TZID=Europe/London:20260524T150000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_truncated": false}} +{"ts": "2026-03-30T14:14:36+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:18080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "X-WP-User": "admin", "Content-Length": "295"}, "body": "{\"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\"}", "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:14:36 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "655"}, "body": "HTTP/1.0 201 Created\r\nServer: CalendarFixture/0.1 Python/3.13.5\r\nDate: Mon, 30 Mar 2026 14:14:36 GMT\r\nContent-Type: application/json; charset=utf-8\r\nContent-Length: 655\r\n\r\n{\"data\": {\"id\": 16, \"uid\": \"771bbfbfb45098265113@calendar-wp-plugin\", \"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_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"until\", \"repeat_count\": null, \"repeat_until\": \"2026-03-19\", \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"bbc40060424cca3d\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T14:14:36+00:00\"}}", "body_truncated": false}} +{"ts": "2026-03-30T14:14:36+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/16/occurrences/2026-03-11T11:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:18080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:14:36 GMT"}, "body": "HTTP/1.0 204 No Content\r\nServer: CalendarFixture/0.1 Python/3.13.5\r\nDate: Mon, 30 Mar 2026 14:14:36 GMT\r\n\r\n", "body_truncated": false}} +{"ts": "2026-03-30T14:14:36+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/16/occurrences?from=2026-03-01&months=1", "request": {"headers": {"Host": "127.0.0.1:18080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:14:36 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "5212"}, "body": "HTTP/1.0 200 OK\r\nServer: CalendarFixture/0.1 Python/3.13.5\r\nDate: Mon, 30 Mar 2026 14:14:36 GMT\r\nContent-Type: application/json; charset=utf-8\r\nContent-Length: 5212\r\n\r\n{\"data\": [{\"event_id\": 16, \"uid\": \"771bbfbfb45098265113@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-02T10:00:00+00:00\", \"occurrence_end\": \"2026-03-02T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"771bbfbfb45098265113@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-03T10:00:00+00:00\", \"occurrence_end\": \"2026-03-03T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"771bbfbfb45098265113@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-04T10:00:00+00:00\", \"occurrence_end\": \"2026-03-04T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"771bbfbfb45098265113@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-05T10:00:00+00:00\", \"occurrence_end\": \"2026-03-05T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"771bbfbfb45098265113@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-06T10:00:00+00:00\", \"occurrence_end\": \"2026-03-06T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"771bbfbfb45098265113@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-07T10:00:00+00:00\", \"occurrence_end\": \"2026-03-07T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"771bbfbfb45098265113@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-08T10:00:00+00:00\", \"occurrence_end\": \"2026-03-08T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"771bbfbfb45098265113@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-09T10:00:00+00:00\", \"occurrence_end\": \"2026-03-09T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"771bbfbfb45098265113@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-10T10:00:00+00:00\", \"occurrence_end\": \"2026-03-10T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"771bbfbfb45098265113@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-12T10:00:00+00:00\", \"occurrence_end\": \"2026-03-12T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"771bbfbfb45098265113@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-13T10:00:00+00:00\", \"occurrence_end\": \"2026-03-13T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"771bbfbfb45098265113@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-14T10:00:00+00:00\", \"occurrence_end\": \"2026-03-14T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"771bbfbfb45098265113@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-15T10:00:00+00:00\", \"occurrence_end\": \"2026-03-15T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"771bbfbfb45098265113@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-16T10:00:00+00:00\", \"occurrence_end\": \"2026-03-16T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"771bbfbfb45098265113@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-17T10:00:00+00:00\", \"occurrence_end\": \"2026-03-17T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"771bbfbfb45098265113@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-18T10:00:00+00:00\", \"occurrence_end\": \"2026-03-18T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"771bbfbfb45098265113@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-19T10:00:00+00:00\", \"occurrence_end\": \"2026-03-19T11:00:00+00:00\", \"repeat_type\": \"daily\"}]}", "body_truncated": false}} +{"ts": "2026-03-30T14:14:36+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/16/occurrences/2026-03-10", "request": {"headers": {"Host": "127.0.0.1:18080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:14:36 GMT"}, "body": "HTTP/1.0 204 No Content\r\nServer: CalendarFixture/0.1 Python/3.13.5\r\nDate: Mon, 30 Mar 2026 14:14:36 GMT\r\n\r\n", "body_truncated": false}} +{"ts": "2026-03-30T14:14:36+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/16/occurrences?from=2026-03-01&months=1", "request": {"headers": {"Host": "127.0.0.1:18080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:14:36 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "4906"}, "body": "HTTP/1.0 200 OK\r\nServer: CalendarFixture/0.1 Python/3.13.5\r\nDate: Mon, 30 Mar 2026 14:14:36 GMT\r\nContent-Type: application/json; charset=utf-8\r\nContent-Length: 4906\r\n\r\n{\"data\": [{\"event_id\": 16, \"uid\": \"771bbfbfb45098265113@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-02T10:00:00+00:00\", \"occurrence_end\": \"2026-03-02T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"771bbfbfb45098265113@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-03T10:00:00+00:00\", \"occurrence_end\": \"2026-03-03T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"771bbfbfb45098265113@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-04T10:00:00+00:00\", \"occurrence_end\": \"2026-03-04T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"771bbfbfb45098265113@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-05T10:00:00+00:00\", \"occurrence_end\": \"2026-03-05T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"771bbfbfb45098265113@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-06T10:00:00+00:00\", \"occurrence_end\": \"2026-03-06T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"771bbfbfb45098265113@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-07T10:00:00+00:00\", \"occurrence_end\": \"2026-03-07T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"771bbfbfb45098265113@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-08T10:00:00+00:00\", \"occurrence_end\": \"2026-03-08T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"771bbfbfb45098265113@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-09T10:00:00+00:00\", \"occurrence_end\": \"2026-03-09T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"771bbfbfb45098265113@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-12T10:00:00+00:00\", \"occurrence_end\": \"2026-03-12T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"771bbfbfb45098265113@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-13T10:00:00+00:00\", \"occurrence_end\": \"2026-03-13T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"771bbfbfb45098265113@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-14T10:00:00+00:00\", \"occurrence_end\": \"2026-03-14T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"771bbfbfb45098265113@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-15T10:00:00+00:00\", \"occurrence_end\": \"2026-03-15T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"771bbfbfb45098265113@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-16T10:00:00+00:00\", \"occurrence_end\": \"2026-03-16T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"771bbfbfb45098265113@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-17T10:00:00+00:00\", \"occurrence_end\": \"2026-03-17T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"771bbfbfb45098265113@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-18T10:00:00+00:00\", \"occurrence_end\": \"2026-03-18T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"771bbfbfb45098265113@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-19T10:00:00+00:00\", \"occurrence_end\": \"2026-03-19T11:00:00+00:00\", \"repeat_type\": \"daily\"}]}", "body_truncated": false}} +{"ts": "2026-03-30T14:14:37+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-exdate-import.ics", "request": {"headers": {"Host": "127.0.0.1:18080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "323"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-exdate-import-001\nSUMMARY:Smoke EXDATE Import\nDTSTART;TZID=Europe/London:20260302T100000\nDTEND;TZID=Europe/London:20260302T110000\nRRULE:FREQ=DAILY;UNTIL=20260319T235959\nEXDATE;TZID=Europe/London:20260310T100000,20260311T100000\nEND:VEVENT\nEND:VCALENDAR\n", "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:14:37 GMT", "ETag": "\"1c3e46425e90df29\""}, "body": "HTTP/1.0 201 Created\r\nServer: CalendarFixture/0.1 Python/3.13.5\r\nDate: Mon, 30 Mar 2026 14:14:37 GMT\r\nETag: \"1c3e46425e90df29\"\r\n\r\n", "body_truncated": false}} +{"ts": "2026-03-30T14:14:37+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:18080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:14:37 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "11006"}, "body": "HTTP/1.0 200 OK\r\nServer: CalendarFixture/0.1 Python/3.13.5\r\nDate: Mon, 30 Mar 2026 14:14:37 GMT\r\nContent-Type: application/json; charset=utf-8\r\nContent-Length: 11006\r\n\r\n{\"data\": [{\"id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"start_datetime\": \"2026-04-01T10:00:00+01:00\", \"end_datetime\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"9e61840edd26b59c\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T14:14:36+00:00\"}, {\"id\": 2, \"uid\": \"fixture-ce-002@calendar-wp-plugin\", \"title\": \"Office Closed\", \"description\": \"Public holiday closure.\", \"location\": \"HQ\", \"category\": \"Operations\", \"all_day_event\": true, \"start_datetime\": \"2026-05-04\", \"end_datetime\": \"2026-05-05\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"6f79e4a470012e8c\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T14:14:36+00:00\"}, {\"id\": 3, \"uid\": \"fixture-ce-003@calendar-wp-plugin\", \"title\": \"Daily Standup\", \"description\": \"15 minute sync.\", \"location\": \"Online\", \"category\": \"Team\", \"all_day_event\": false, \"start_datetime\": \"2026-04-06T09:00:00+01:00\", \"end_datetime\": \"2026-04-06T09:15:00+01:00\", \"repeat_type\": \"daily\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"count\", \"repeat_count\": 10, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"7dd021ab4c51e62e\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T14:14:36+00:00\"}, {\"id\": 4, \"uid\": \"fixture-ce-004@calendar-wp-plugin\", \"title\": \"Community Lunch\", \"description\": \"Weekly community lunch.\", \"location\": \"Cafeteria\", \"category\": \"Community\", \"all_day_event\": false, \"start_datetime\": \"2026-04-08T12:30:00+01:00\", \"end_datetime\": \"2026-04-08T13:30:00+01:00\", \"repeat_type\": \"weekly\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"no_end\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"382b171c7e467f49\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T14:14:36+00:00\"}, {\"id\": 5, \"uid\": \"fixture-ce-005@calendar-wp-plugin\", \"title\": \"Finance Close\", \"description\": \"Month-end close process.\", \"location\": \"Finance Office\", \"category\": \"Finance\", \"all_day_event\": false, \"start_datetime\": \"2026-04-30T17:00:00+01:00\", \"end_datetime\": \"2026-04-30T18:00:00+01:00\", \"repeat_type\": \"monthly\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"until\", \"repeat_count\": null, \"repeat_until\": \"2026-08-31\", \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"6a0b1853010a3060\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T14:14:36+00:00\"}, {\"id\": 6, \"uid\": \"fixture-ce-006@calendar-wp-plugin\", \"title\": \"Annual Conference\", \"description\": \"Annual community conference.\", \"location\": \"Main Hall\", \"category\": \"Events\", \"all_day_event\": false, \"start_datetime\": \"2026-06-15T10:00:00+01:00\", \"end_datetime\": \"2026-06-15T17:00:00+01:00\", \"repeat_type\": \"yearly\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"count\", \"repeat_count\": 3, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"0344b53653b1897e\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T14:14:36+00:00\"}, {\"id\": 7, \"uid\": \"fixture-ce-007@calendar-wp-plugin\", \"title\": \"Fortnightly Coaching\", \"description\": \"Coaching check-in.\", \"location\": \"Online\", \"category\": \"Training\", \"all_day_event\": false, \"start_datetime\": \"2026-04-07T15:00:00+01:00\", \"end_datetime\": \"2026-04-07T16:00:00+01:00\", \"repeat_type\": \"custom\", \"repeat_interval\": 2, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"until\", \"repeat_count\": null, \"repeat_until\": \"2026-07-31\", \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"f3727cb12d14614a\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T14:14:36+00:00\"}, {\"id\": 8, \"uid\": \"fixture-ce-008@calendar-wp-plugin\", \"title\": \"DST Validation Event\", \"description\": \"Validates DST transition rendering.\", \"location\": \"Lab\", \"category\": \"QA\", \"all_day_event\": false, \"start_datetime\": \"2026-10-25T00:30:00+01:00\", \"end_datetime\": \"2026-10-25T02:30:00+00:00\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"2200904959f9c5cd\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T14:14:36+00:00\"}, {\"id\": 9, \"uid\": \"fixture-ce-009@calendar-wp-plugin\", \"title\": \"Leap Day Marker\", \"description\": \"Leap day recurrence behavior.\", \"location\": \"Calendar\", \"category\": \"QA\", \"all_day_event\": false, \"start_datetime\": \"2028-02-29T09:00:00+00:00\", \"end_datetime\": \"2028-02-29T10:00:00+00:00\", \"repeat_type\": \"yearly\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"count\", \"repeat_count\": 3, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"028aa2cacadd930b\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T14:14:36+00:00\"}, {\"id\": 10, \"uid\": \"fixture-ce-010@calendar-wp-plugin\", \"title\": \"Therapy Session\", \"description\": \"Used for single-occurrence delete exception tests.\", \"location\": \"Clinic\", \"category\": \"Health\", \"all_day_event\": false, \"start_datetime\": \"2026-04-03T14:00:00+01:00\", \"end_datetime\": \"2026-04-03T15:00:00+01:00\", \"repeat_type\": \"weekly\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"count\", \"repeat_count\": 8, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"1062edf9fc69e84f\\\"\", \"sync_version\": 2, \"updated_at\": \"2026-03-30T14:14:36+00:00\"}, {\"id\": 11, \"uid\": \"smoke-vtimezone-parser-001\", \"title\": \"Smoke VTIMEZONE Parse\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"start_datetime\": \"2026-04-23T15:00:00+01:00\", \"end_datetime\": \"2026-04-23T16:00:00+01:00\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"e5984c98962fda96\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T14:14:36+00:00\"}, {\"id\": 12, \"uid\": \"smoke-monthly-nth-001\", \"title\": \"Smoke Monthly Nth\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"start_datetime\": \"2026-04-26T15:00:00+01:00\", \"end_datetime\": \"2026-04-26T16:00:00+01:00\", \"repeat_type\": \"monthly\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"weekday_of_month\", \"repeat_nth_day\": null, \"repeat_nth_pos\": 4, \"repeat_nth_weekday\": 0, \"repeat_range_mode\": \"no_end\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"a4f11de675293164\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T14:14:36+00:00\"}, {\"id\": 13, \"uid\": \"smoke-monthly-ordinal-001\", \"title\": \"Smoke Monthly Ordinal\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"start_datetime\": \"2026-04-11T15:00:00+01:00\", \"end_datetime\": \"2026-04-11T16:00:00+01:00\", \"repeat_type\": \"monthly\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"weekday_of_month\", \"repeat_nth_day\": null, \"repeat_nth_pos\": 2, \"repeat_nth_weekday\": 6, \"repeat_range_mode\": \"no_end\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"db9706d0865b79d9\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T14:14:36+00:00\"}, {\"id\": 14, \"uid\": \"smoke-monthly-last-001\", \"title\": \"Smoke Monthly Last\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"start_datetime\": \"2026-04-25T15:00:00+01:00\", \"end_datetime\": \"2026-04-25T16:00:00+01:00\", \"repeat_type\": \"monthly\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"weekday_of_month\", \"repeat_nth_day\": null, \"repeat_nth_pos\": -1, \"repeat_nth_weekday\": 6, \"repeat_range_mode\": \"no_end\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"9b7f63d5c08cc7c8\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T14:14:36+00:00\"}, {\"id\": 15, \"uid\": \"4e9ee4c86911a55106b9@calendar-wp-plugin\", \"title\": \"Smoke 4th Sunday Anchor\", \"description\": \"anchor-normalization\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"start_datetime\": \"2026-05-24T15:00:00+01:00\", \"end_datetime\": \"2026-05-24T16:00:00+01:00\", \"repeat_type\": \"monthly\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"weekday_of_month\", \"repeat_nth_day\": null, \"repeat_nth_pos\": 4, \"repeat_nth_weekday\": 0, \"repeat_range_mode\": \"no_end\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"e6f09ba57238f931\\\"\", \"sync_version\": 2, \"updated_at\": \"2026-03-30T14:14:36+00:00\"}, {\"id\": 16, \"uid\": \"771bbfbfb45098265113@calendar-wp-plugin\", \"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_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"until\", \"repeat_count\": null, \"repeat_until\": \"2026-03-19\", \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"904acb970acebc29\\\"\", \"sync_version\": 3, \"updated_at\": \"2026-03-30T14:14:36+00:00\"}, {\"id\": 17, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"start_datetime\": \"2026-03-02T10:00:00+01:00\", \"end_datetime\": \"2026-03-02T11:00:00+01:00\", \"repeat_type\": \"daily\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"until\", \"repeat_count\": null, \"repeat_until\": \"2026-03-19\", \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"1c3e46425e90df29\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T14:14:37+00:00\"}], \"meta\": {\"count\": 17}}", "body_truncated": false}} +{"ts": "2026-03-30T14:14:37+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/17/occurrences?from=2026-03-01&months=1", "request": {"headers": {"Host": "127.0.0.1:18080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:14:37 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "4474"}, "body": "HTTP/1.0 200 OK\r\nServer: CalendarFixture/0.1 Python/3.13.5\r\nDate: Mon, 30 Mar 2026 14:14:37 GMT\r\nContent-Type: application/json; charset=utf-8\r\nContent-Length: 4474\r\n\r\n{\"data\": [{\"event_id\": 17, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-02T10:00:00+01:00\", \"occurrence_end\": \"2026-03-02T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-03T10:00:00+01:00\", \"occurrence_end\": \"2026-03-03T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-04T10:00:00+01:00\", \"occurrence_end\": \"2026-03-04T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-05T10:00:00+01:00\", \"occurrence_end\": \"2026-03-05T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-06T10:00:00+01:00\", \"occurrence_end\": \"2026-03-06T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-07T10:00:00+01:00\", \"occurrence_end\": \"2026-03-07T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-08T10:00:00+01:00\", \"occurrence_end\": \"2026-03-08T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-09T10:00:00+01:00\", \"occurrence_end\": \"2026-03-09T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-12T10:00:00+01:00\", \"occurrence_end\": \"2026-03-12T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-13T10:00:00+01:00\", \"occurrence_end\": \"2026-03-13T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-14T10:00:00+01:00\", \"occurrence_end\": \"2026-03-14T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-15T10:00:00+01:00\", \"occurrence_end\": \"2026-03-15T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-16T10:00:00+01:00\", \"occurrence_end\": \"2026-03-16T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-17T10:00:00+01:00\", \"occurrence_end\": \"2026-03-17T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-18T10:00:00+01:00\", \"occurrence_end\": \"2026-03-18T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-19T10:00:00+01:00\", \"occurrence_end\": \"2026-03-19T11:00:00+01:00\", \"repeat_type\": \"daily\"}]}", "body_truncated": false}} +{"ts": "2026-03-30T14:14:37+00:00", "client": "127.0.0.1", "method": "GET", "path": "/calendar", "request": {"headers": {"Host": "127.0.0.1:18080", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:14:37 GMT", "Content-Type": "text/html; charset=utf-8", "Content-Length": "43446"}, "body": "HTTP/1.0 200 OK\r\nServer: CalendarFixture/0.1 Python/3.13.5\r\nDate: Mon, 30 Mar 2026 14:14:37 GMT\r\nContent-Type: text/html; charset=utf-8\r\nContent-Length: 43446\r\n\r\n\n\n\n \n \n Calendar\n \n\n\n\n
Calendar Fixture Public Calendar UI
\n
\n
\n
\n
\n
\n \n
\n
\n
\n \n \n \n \n \n
\n
\n ICS Link\n CalDAV\n
\n
\n \n
\n
\n
\n
\n
\n
\n

Account Login

\n
\n
\n
\n
\n
\n
\n \n \n
\n
\n

Register

\n
\n
\n
\n
\n
\n
\n
\n

Password Recovery

\n
\n \n
\n
\n \n
\n
\n
\n
\n
\n

Event Details

\n
\n
\n
\n
\n
\n
\n
\n
\n

Event

\n \n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n \n
\n
\n
\n
\n
\n \n
\n
\n
\n
\n
\n
\n
\n
\n \n
\n
\n
\n
\n
\n
\n
\n
\n \n \n \n \n \n
\n
\n

Delete Single Occurrence (Exception)

\n
\n \n \n
\n
\n \n
\n \n
\n
\n
\n
\n\n\n\n", "body_truncated": false}} +{"ts": "2026-03-30T14:15:07+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/ed617bc7-66c1-474d-a467-86565e680820.ics", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "14085", "If-Match": "\"4b22d7e9f955d2a5\"", "Origin": "http://localhost:8080", "DNT": "1", "Authorization": "***", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "BEGIN:VCALENDAR\r\nPRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN\r\nVERSION:2.0\r\nBEGIN:VTIMEZONE\r\nTZID:Europe/London\r\nX-TZINFO:Europe/London[2025b]\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:-000115\r\nTZNAME:Europe/London(STD)\r\nDTSTART:18471201T000000\r\nRDATE:18471201T000000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19160521T020000\r\nRDATE:19160521T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19161001T030000\r\nRDATE:19161001T030000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19170408T020000\r\nRDATE:19170408T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19170917T030000\r\nRDATE:19170917T030000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19180324T020000\r\nRDATE:19180324T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19180930T030000\r\nRRULE:FREQ=YEARLY;BYMONTH=9;BYDAY=-1MO;UNTIL=19190929T030000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19190330T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU;UNTIL=19200328T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19201025T030000\r\nRDATE:19201025T030000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19210403T020000\r\nRDATE:19210403T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19211003T030000\r\nRDATE:19211003T030000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19220326T020000\r\nRDATE:19220326T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19221008T030000\r\nRDATE:19221008T030000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19230422T020000\r\nRDATE:19230422T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19240413T020000\r\nRDATE:19240413T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19230916T030000\r\nRRULE:FREQ=YEARLY;BYMONTH=9;BYDAY=3SU;UNTIL=19240921T030000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19250419T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=3SU;UNTIL=19260418T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19270410T020000\r\nRDATE:19270410T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19280422T020000\r\nRDATE:19280422T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19290421T020000\r\nRDATE:19290421T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19300413T020000\r\nRDATE:19300413T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19310419T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=3SU;UNTIL=19320417T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19251004T030000\r\nRRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=1SU;UNTIL=19321002T030000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19330409T020000\r\nRDATE:19330409T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19331008T030000\r\nRDATE:19331008T030000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19340422T020000\r\nRDATE:19340422T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19350414T020000\r\nRDATE:19350414T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19360419T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=3SU;UNTIL=19370418T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19380410T020000\r\nRDATE:19380410T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19341007T030000\r\nRRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=1SU;UNTIL=19381002T030000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19390416T020000\r\nRDATE:19390416T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19400225T020000\r\nRDATE:19400225T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+020000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19410504T020000\r\nRDATE:19410504T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+020000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19410810T030000\r\nRDATE:19410810T030000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+020000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19420405T020000\r\nRDATE:19420405T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+020000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19420809T030000\r\nRDATE:19420809T030000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+020000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19430404T020000\r\nRDATE:19430404T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+020000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19430815T030000\r\nRDATE:19430815T030000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+020000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19440402T020000\r\nRDATE:19440402T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+020000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19440917T030000\r\nRDATE:19440917T030000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+020000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19450402T020000\r\nRDATE:19450402T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19391119T030000\r\nRDATE:19391119T030000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+020000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19450715T030000\r\nRDATE:19450715T030000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19460414T020000\r\nRDATE:19460414T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19470316T020000\r\nRDATE:19470316T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+020000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19470413T020000\r\nRDATE:19470413T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19451007T030000\r\nRRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=1SU;UNTIL=19461006T030000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+020000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19470810T030000\r\nRDATE:19470810T030000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19471102T030000\r\nRDATE:19471102T030000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19480314T020000\r\nRDATE:19480314T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19490403T020000\r\nRDATE:19490403T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19481031T030000\r\nRRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU;UNTIL=19491030T030000\r\nEND:STANDARD\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19501022T030000\r\nRDATE:19501022T030000\r\nEND:STANDARD\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19511021T030000\r\nRDATE:19511021T030000\r\nEND:STANDARD\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19521026T030000\r\nRDATE:19521026T030000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19500416T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=3SU;UNTIL=19530419T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19540411T020000\r\nRDATE:19540411T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19550417T020000\r\nRDATE:19550417T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19560422T020000\r\nRDATE:19560422T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19570414T020000\r\nRDATE:19570414T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19580420T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=3SU;UNTIL=19590419T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19600410T020000\r\nRDATE:19600410T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19531004T030000\r\nRRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=1SU;UNTIL=19601002T030000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19610326T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU;UNTIL=19630331T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19640322T020000\r\nRDATE:19640322T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19611029T030000\r\nRRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU;UNTIL=19641025T030000\r\nEND:STANDARD\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19651024T030000\r\nRRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=4SU;UNTIL=19661023T030000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19650321T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=3SU;UNTIL=19670319T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19671029T030000\r\nRDATE:19671029T030000\r\nEND:STANDARD\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19681027T000000\r\nRDATE:19681027T000000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19680218T020000\r\nRDATE:19680218T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19711031T030000\r\nRRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU;UNTIL=19751026T030000\r\nEND:STANDARD\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19761024T030000\r\nRRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=4SU;UNTIL=19771023T030000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19720319T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=3SU;UNTIL=19800316T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19781029T030000\r\nRRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU;UNTIL=19801026T030000\r\nEND:STANDARD\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19811025T020000\r\nRDATE:19811025T020000\r\nEND:STANDARD\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19821024T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=4SU;UNTIL=19831023T020000\r\nEND:STANDARD\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19841028T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU;UNTIL=19871025T020000\r\nEND:STANDARD\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19881023T020000\r\nRDATE:19881023T020000\r\nEND:STANDARD\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19891029T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU;UNTIL=19921025T020000\r\nEND:STANDARD\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19931024T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=4SU;UNTIL=19951022T020000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19810329T010000\r\nRRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU;UNTIL=19960331T010000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19961027T020000\r\nRDATE:19961027T020000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:(DST)\r\nDTSTART:19970330T010000\r\nRRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:(STD)\r\nDTSTART:19971026T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU\r\nEND:STANDARD\r\nEND:VTIMEZONE\r\nBEGIN:VEVENT\r\nLAST-MODIFIED:20260330T141507Z\r\nDTSTAMP:20260330T141507Z\r\nUID:ed617bc7-66c1-474d-a467-86565e680820\r\nSUMMARY:test 8\r\nRRULE:FREQ=DAILY;UNTIL=20260319T235959\r\nEXDATE;TZID=Europe/London:20260312T160000\r\nDTSTART;TZID=Europe/London:20260302T160000\r\nDTEND;TZID=Europe/London:20260302T170000\r\nDESCRIPTION:Default Mozilla Description\r\nSEQUENCE:1\r\nX-MOZ-GENERATION:1\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:15:07 GMT", "ETag": "\"898b4e9c84b4dade\""}, "body": "HTTP/1.0 201 Created\r\nServer: CalendarFixture/0.1 Python/3.13.5\r\nDate: Mon, 30 Mar 2026 14:15:07 GMT\r\nETag: \"898b4e9c84b4dade\"\r\n\r\n", "body_truncated": false}} +{"ts": "2026-03-30T14:15:07+00:00", "client": "127.0.0.1", "method": "REPORT", "path": "/caldav/calendars/public/", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/xml; charset=utf-8", "Content-Length": "266", "Depth": "1", "Origin": "http://localhost:8080", "DNT": "1", "Authorization": "***", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "\n/caldav/calendars/public/ed617bc7-66c1-474d-a467-86565e680820.ics", "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:15:07 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "15724"}, "body": "HTTP/1.0 207 Multi-Status\r\nServer: CalendarFixture/0.1 Python/3.13.5\r\nDate: Mon, 30 Mar 2026 14:15:07 GMT\r\nContent-Type: application/xml; charset=utf-8\r\nContent-Length: 15724\r\n\r\n\n\n\n /caldav/calendars/public/1.ics\n \n \n \"9e61840edd26b59c\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-001@calendar-wp-plugin\r\nDTSTAMP:20260330T141507Z\r\nSUMMARY:Board Meeting\r\nDESCRIPTION:Quarterly board review.\r\nLOCATION:Room A\r\nCATEGORIES:Governance\r\nDTSTART;TZID=Europe/London:20260401T100000\r\nDTEND;TZID=Europe/London:20260401T113000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/2.ics\n \n \n \"6f79e4a470012e8c\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-002@calendar-wp-plugin\r\nDTSTAMP:20260330T141507Z\r\nSUMMARY:Office Closed\r\nDESCRIPTION:Public holiday closure.\r\nLOCATION:HQ\r\nCATEGORIES:Operations\r\nDTSTART;VALUE=DATE:20260504\r\nDTEND;VALUE=DATE:20260505\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/3.ics\n \n \n \"7dd021ab4c51e62e\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-003@calendar-wp-plugin\r\nDTSTAMP:20260330T141507Z\r\nSUMMARY:Daily Standup\r\nDESCRIPTION:15 minute sync.\r\nLOCATION:Online\r\nCATEGORIES:Team\r\nDTSTART;TZID=Europe/London:20260406T090000\r\nDTEND;TZID=Europe/London:20260406T091500\r\nRRULE:FREQ=DAILY;COUNT=10\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/4.ics\n \n \n \"382b171c7e467f49\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-004@calendar-wp-plugin\r\nDTSTAMP:20260330T141507Z\r\nSUMMARY:Community Lunch\r\nDESCRIPTION:Weekly community lunch.\r\nLOCATION:Cafeteria\r\nCATEGORIES:Community\r\nDTSTART;TZID=Europe/London:20260408T123000\r\nDTEND;TZID=Europe/London:20260408T133000\r\nRRULE:FREQ=WEEKLY\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/5.ics\n \n \n \"6a0b1853010a3060\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-005@calendar-wp-plugin\r\nDTSTAMP:20260330T141507Z\r\nSUMMARY:Finance Close\r\nDESCRIPTION:Month-end close process.\r\nLOCATION:Finance Office\r\nCATEGORIES:Finance\r\nDTSTART;TZID=Europe/London:20260430T170000\r\nDTEND;TZID=Europe/London:20260430T180000\r\nRRULE:FREQ=MONTHLY;UNTIL=20260831T235959\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/6.ics\n \n \n \"0344b53653b1897e\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-006@calendar-wp-plugin\r\nDTSTAMP:20260330T141507Z\r\nSUMMARY:Annual Conference\r\nDESCRIPTION:Annual community conference.\r\nLOCATION:Main Hall\r\nCATEGORIES:Events\r\nDTSTART;TZID=Europe/London:20260615T100000\r\nDTEND;TZID=Europe/London:20260615T170000\r\nRRULE:FREQ=YEARLY;COUNT=3\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/7.ics\n \n \n \"f3727cb12d14614a\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-007@calendar-wp-plugin\r\nDTSTAMP:20260330T141507Z\r\nSUMMARY:Fortnightly Coaching\r\nDESCRIPTION:Coaching check-in.\r\nLOCATION:Online\r\nCATEGORIES:Training\r\nDTSTART;TZID=Europe/London:20260407T150000\r\nDTEND;TZID=Europe/London:20260407T160000\r\nRRULE:FREQ=WEEKLY;INTERVAL=2;UNTIL=20260731T235959\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/8.ics\n \n \n \"2200904959f9c5cd\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-008@calendar-wp-plugin\r\nDTSTAMP:20260330T141507Z\r\nSUMMARY:DST Validation Event\r\nDESCRIPTION:Validates DST transition rendering.\r\nLOCATION:Lab\r\nCATEGORIES:QA\r\nDTSTART;TZID=Europe/London:20261025T003000\r\nDTEND;TZID=Europe/London:20261025T023000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/9.ics\n \n \n \"028aa2cacadd930b\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-009@calendar-wp-plugin\r\nDTSTAMP:20260330T141507Z\r\nSUMMARY:Leap Day Marker\r\nDESCRIPTION:Leap day recurrence behavior.\r\nLOCATION:Calendar\r\nCATEGORIES:QA\r\nDTSTART;TZID=Europe/London:20280229T090000\r\nDTEND;TZID=Europe/London:20280229T100000\r\nRRULE:FREQ=YEARLY;COUNT=3\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/10.ics\n \n \n \"1062edf9fc69e84f\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-010@calendar-wp-plugin\r\nDTSTAMP:20260330T141507Z\r\nSUMMARY:Therapy Session\r\nDESCRIPTION:Used for single-occurrence delete exception tests.\r\nLOCATION:Clinic\r\nCATEGORIES:Health\r\nDTSTART;TZID=Europe/London:20260403T140000\r\nDTEND;TZID=Europe/London:20260403T150000\r\nRRULE:FREQ=WEEKLY;COUNT=8\r\nEXDATE;TZID=Europe/London:20260417T140000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/smoke-vtimezone.ics\n \n \n \"e5984c98962fda96\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-vtimezone-parser-001\r\nDTSTAMP:20260330T141507Z\r\nSUMMARY:Smoke VTIMEZONE Parse\r\nDTSTART;TZID=Europe/London:20260423T150000\r\nDTEND;TZID=Europe/London:20260423T160000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/smoke-monthly-nth.ics\n \n \n \"a4f11de675293164\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-nth-001\r\nDTSTAMP:20260330T141507Z\r\nSUMMARY:Smoke Monthly Nth\r\nDTSTART;TZID=Europe/London:20260426T150000\r\nDTEND;TZID=Europe/London:20260426T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SU;BYSETPOS=4\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/smoke-monthly-ordinal.ics\n \n \n \"db9706d0865b79d9\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-ordinal-001\r\nDTSTAMP:20260330T141507Z\r\nSUMMARY:Smoke Monthly Ordinal\r\nDTSTART;TZID=Europe/London:20260411T150000\r\nDTEND;TZID=Europe/London:20260411T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SA;BYSETPOS=2\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/smoke-monthly-last.ics\n \n \n \"9b7f63d5c08cc7c8\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-last-001\r\nDTSTAMP:20260330T141507Z\r\nSUMMARY:Smoke Monthly Last\r\nDTSTART;TZID=Europe/London:20260425T150000\r\nDTEND;TZID=Europe/London:20260425T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SA;BYSETPOS=-1\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/15.ics\n \n \n \"e6f09ba57238f931\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:4e9ee4c86911a55106b9@calendar-wp-plugin\r\nDTSTAMP:20260330T141507Z\r\nSUMMARY:Smoke 4th Sunday Anchor\r\nDESCRIPTION:anchor-normalization\r\nDTSTART;TZID=Europe/London:20260524T150000\r\nDTEND;TZID=Europe/London:20260524T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SU;BYSETPOS=4\r\nEXDATE;TZID=Europe/London:20260524T150000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/16.ics\n \n \n \"904acb970acebc29\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:771bbfbfb45098265113@calendar-wp-plugin\r\nDTSTAMP:20260330T141507Z\r\nSUMMARY:Smoke Daily Exception TZ\r\nDESCRIPTION:tz-key\r\nDTSTART;TZID=Europe/London:20260302T100000\r\nDTEND;TZID=Europe/London:20260302T110000\r\nRRULE:FREQ=DAILY;UNTIL=20260319T235959\r\nEXDATE;TZID=Europe/London:20260310T100000,20260311T100000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/smoke-exdate-import.ics\n \n \n \"1c3e46425e90df29\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-exdate-import-001\r\nDTSTAMP:20260330T141507Z\r\nSUMMARY:Smoke EXDATE Import\r\nDTSTART;TZID=Europe/London:20260302T100000\r\nDTEND;TZID=Europe/London:20260302T110000\r\nRRULE:FREQ=DAILY;UNTIL=20260319T235959\r\nEXDATE;TZID=Europe/London:20260310T100000,20260311T100000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/ed617bc7-66c1-474d-a467-86565e680820.ics\n \n \n \"898b4e9c84b4dade\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:ed617bc7-66c1-474d-a467-86565e680820\r\nDTSTAMP:20260330T141507Z\r\nSUMMARY:test 8\r\nDESCRIPTION:Default Mozilla Description\r\nDTSTART;TZID=Europe/London:20260302T160000\r\nDTEND;TZID=Europe/London:20260302T170000\r\nRRULE:FREQ=DAILY;UNTIL=20260319T235959\r\nEXDATE;TZID=Europe/London:20260312T160000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n", "body_truncated": false}} +{"ts": "2026-03-30T14:15:13+00:00", "client": "127.0.0.1", "method": "PROPFIND", "path": "/caldav/calendars/public/", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/xml; charset=utf-8", "Content-Length": "144", "Depth": "1", "Origin": "http://localhost:8080", "DNT": "1", "Authorization": "***", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "\n", "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:15:13 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "6140"}, "body": "HTTP/1.0 207 Multi-Status\r\nServer: CalendarFixture/0.1 Python/3.13.5\r\nDate: Mon, 30 Mar 2026 14:15:13 GMT\r\nContent-Type: application/xml; charset=utf-8\r\nContent-Length: 6140\r\n\r\n\n\n\n /caldav/calendars/public/\n \n \n Public Calendar\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/1.ics\n \n \n text/calendar; charset=utf-8\"9e61840edd26b59c\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/2.ics\n \n \n text/calendar; charset=utf-8\"6f79e4a470012e8c\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/3.ics\n \n \n text/calendar; charset=utf-8\"7dd021ab4c51e62e\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/4.ics\n \n \n text/calendar; charset=utf-8\"382b171c7e467f49\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/5.ics\n \n \n text/calendar; charset=utf-8\"6a0b1853010a3060\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/6.ics\n \n \n text/calendar; charset=utf-8\"0344b53653b1897e\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/7.ics\n \n \n text/calendar; charset=utf-8\"f3727cb12d14614a\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/8.ics\n \n \n text/calendar; charset=utf-8\"2200904959f9c5cd\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/9.ics\n \n \n text/calendar; charset=utf-8\"028aa2cacadd930b\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/10.ics\n \n \n text/calendar; charset=utf-8\"1062edf9fc69e84f\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/smoke-vtimezone.ics\n \n \n text/calendar; charset=utf-8\"e5984c98962fda96\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/smoke-monthly-nth.ics\n \n \n text/calendar; charset=utf-8\"a4f11de675293164\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/smoke-monthly-ordinal.ics\n \n \n text/calendar; charset=utf-8\"db9706d0865b79d9\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/smoke-monthly-last.ics\n \n \n text/calendar; charset=utf-8\"9b7f63d5c08cc7c8\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/15.ics\n \n \n text/calendar; charset=utf-8\"e6f09ba57238f931\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/16.ics\n \n \n text/calendar; charset=utf-8\"904acb970acebc29\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/smoke-exdate-import.ics\n \n \n text/calendar; charset=utf-8\"1c3e46425e90df29\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/ed617bc7-66c1-474d-a467-86565e680820.ics\n \n \n text/calendar; charset=utf-8\"898b4e9c84b4dade\"\n \n HTTP/1.1 200 OK\n \n\n\n", "body_truncated": false}} +{"ts": "2026-03-30T14:15:13+00:00", "client": "127.0.0.1", "method": "REPORT", "path": "/caldav/calendars/public/", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/xml; charset=utf-8", "Content-Length": "280", "Depth": "1", "Origin": "http://localhost:8080", "DNT": "1", "Authorization": "***", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "\n/caldav/calendars/public/16.ics/caldav/calendars/public/15.ics", "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:15:13 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "15724"}, "body": "HTTP/1.0 207 Multi-Status\r\nServer: CalendarFixture/0.1 Python/3.13.5\r\nDate: Mon, 30 Mar 2026 14:15:13 GMT\r\nContent-Type: application/xml; charset=utf-8\r\nContent-Length: 15724\r\n\r\n\n\n\n /caldav/calendars/public/1.ics\n \n \n \"9e61840edd26b59c\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-001@calendar-wp-plugin\r\nDTSTAMP:20260330T141513Z\r\nSUMMARY:Board Meeting\r\nDESCRIPTION:Quarterly board review.\r\nLOCATION:Room A\r\nCATEGORIES:Governance\r\nDTSTART;TZID=Europe/London:20260401T100000\r\nDTEND;TZID=Europe/London:20260401T113000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/2.ics\n \n \n \"6f79e4a470012e8c\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-002@calendar-wp-plugin\r\nDTSTAMP:20260330T141513Z\r\nSUMMARY:Office Closed\r\nDESCRIPTION:Public holiday closure.\r\nLOCATION:HQ\r\nCATEGORIES:Operations\r\nDTSTART;VALUE=DATE:20260504\r\nDTEND;VALUE=DATE:20260505\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/3.ics\n \n \n \"7dd021ab4c51e62e\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-003@calendar-wp-plugin\r\nDTSTAMP:20260330T141513Z\r\nSUMMARY:Daily Standup\r\nDESCRIPTION:15 minute sync.\r\nLOCATION:Online\r\nCATEGORIES:Team\r\nDTSTART;TZID=Europe/London:20260406T090000\r\nDTEND;TZID=Europe/London:20260406T091500\r\nRRULE:FREQ=DAILY;COUNT=10\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/4.ics\n \n \n \"382b171c7e467f49\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-004@calendar-wp-plugin\r\nDTSTAMP:20260330T141513Z\r\nSUMMARY:Community Lunch\r\nDESCRIPTION:Weekly community lunch.\r\nLOCATION:Cafeteria\r\nCATEGORIES:Community\r\nDTSTART;TZID=Europe/London:20260408T123000\r\nDTEND;TZID=Europe/London:20260408T133000\r\nRRULE:FREQ=WEEKLY\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/5.ics\n \n \n \"6a0b1853010a3060\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-005@calendar-wp-plugin\r\nDTSTAMP:20260330T141513Z\r\nSUMMARY:Finance Close\r\nDESCRIPTION:Month-end close process.\r\nLOCATION:Finance Office\r\nCATEGORIES:Finance\r\nDTSTART;TZID=Europe/London:20260430T170000\r\nDTEND;TZID=Europe/London:20260430T180000\r\nRRULE:FREQ=MONTHLY;UNTIL=20260831T235959\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/6.ics\n \n \n \"0344b53653b1897e\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-006@calendar-wp-plugin\r\nDTSTAMP:20260330T141513Z\r\nSUMMARY:Annual Conference\r\nDESCRIPTION:Annual community conference.\r\nLOCATION:Main Hall\r\nCATEGORIES:Events\r\nDTSTART;TZID=Europe/London:20260615T100000\r\nDTEND;TZID=Europe/London:20260615T170000\r\nRRULE:FREQ=YEARLY;COUNT=3\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/7.ics\n \n \n \"f3727cb12d14614a\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-007@calendar-wp-plugin\r\nDTSTAMP:20260330T141513Z\r\nSUMMARY:Fortnightly Coaching\r\nDESCRIPTION:Coaching check-in.\r\nLOCATION:Online\r\nCATEGORIES:Training\r\nDTSTART;TZID=Europe/London:20260407T150000\r\nDTEND;TZID=Europe/London:20260407T160000\r\nRRULE:FREQ=WEEKLY;INTERVAL=2;UNTIL=20260731T235959\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/8.ics\n \n \n \"2200904959f9c5cd\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-008@calendar-wp-plugin\r\nDTSTAMP:20260330T141513Z\r\nSUMMARY:DST Validation Event\r\nDESCRIPTION:Validates DST transition rendering.\r\nLOCATION:Lab\r\nCATEGORIES:QA\r\nDTSTART;TZID=Europe/London:20261025T003000\r\nDTEND;TZID=Europe/London:20261025T023000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/9.ics\n \n \n \"028aa2cacadd930b\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-009@calendar-wp-plugin\r\nDTSTAMP:20260330T141513Z\r\nSUMMARY:Leap Day Marker\r\nDESCRIPTION:Leap day recurrence behavior.\r\nLOCATION:Calendar\r\nCATEGORIES:QA\r\nDTSTART;TZID=Europe/London:20280229T090000\r\nDTEND;TZID=Europe/London:20280229T100000\r\nRRULE:FREQ=YEARLY;COUNT=3\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/10.ics\n \n \n \"1062edf9fc69e84f\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-010@calendar-wp-plugin\r\nDTSTAMP:20260330T141513Z\r\nSUMMARY:Therapy Session\r\nDESCRIPTION:Used for single-occurrence delete exception tests.\r\nLOCATION:Clinic\r\nCATEGORIES:Health\r\nDTSTART;TZID=Europe/London:20260403T140000\r\nDTEND;TZID=Europe/London:20260403T150000\r\nRRULE:FREQ=WEEKLY;COUNT=8\r\nEXDATE;TZID=Europe/London:20260417T140000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/smoke-vtimezone.ics\n \n \n \"e5984c98962fda96\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-vtimezone-parser-001\r\nDTSTAMP:20260330T141513Z\r\nSUMMARY:Smoke VTIMEZONE Parse\r\nDTSTART;TZID=Europe/London:20260423T150000\r\nDTEND;TZID=Europe/London:20260423T160000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/smoke-monthly-nth.ics\n \n \n \"a4f11de675293164\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-nth-001\r\nDTSTAMP:20260330T141513Z\r\nSUMMARY:Smoke Monthly Nth\r\nDTSTART;TZID=Europe/London:20260426T150000\r\nDTEND;TZID=Europe/London:20260426T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SU;BYSETPOS=4\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/smoke-monthly-ordinal.ics\n \n \n \"db9706d0865b79d9\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-ordinal-001\r\nDTSTAMP:20260330T141513Z\r\nSUMMARY:Smoke Monthly Ordinal\r\nDTSTART;TZID=Europe/London:20260411T150000\r\nDTEND;TZID=Europe/London:20260411T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SA;BYSETPOS=2\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/smoke-monthly-last.ics\n \n \n \"9b7f63d5c08cc7c8\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-last-001\r\nDTSTAMP:20260330T141513Z\r\nSUMMARY:Smoke Monthly Last\r\nDTSTART;TZID=Europe/London:20260425T150000\r\nDTEND;TZID=Europe/London:20260425T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SA;BYSETPOS=-1\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/15.ics\n \n \n \"e6f09ba57238f931\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:4e9ee4c86911a55106b9@calendar-wp-plugin\r\nDTSTAMP:20260330T141513Z\r\nSUMMARY:Smoke 4th Sunday Anchor\r\nDESCRIPTION:anchor-normalization\r\nDTSTART;TZID=Europe/London:20260524T150000\r\nDTEND;TZID=Europe/London:20260524T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SU;BYSETPOS=4\r\nEXDATE;TZID=Europe/London:20260524T150000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/16.ics\n \n \n \"904acb970acebc29\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:771bbfbfb45098265113@calendar-wp-plugin\r\nDTSTAMP:20260330T141513Z\r\nSUMMARY:Smoke Daily Exception TZ\r\nDESCRIPTION:tz-key\r\nDTSTART;TZID=Europe/London:20260302T100000\r\nDTEND;TZID=Europe/London:20260302T110000\r\nRRULE:FREQ=DAILY;UNTIL=20260319T235959\r\nEXDATE;TZID=Europe/London:20260310T100000,20260311T100000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/smoke-exdate-import.ics\n \n \n \"1c3e46425e90df29\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-exdate-import-001\r\nDTSTAMP:20260330T141513Z\r\nSUMMARY:Smoke EXDATE Import\r\nDTSTART;TZID=Europe/London:20260302T100000\r\nDTEND;TZID=Europe/London:20260302T110000\r\nRRULE:FREQ=DAILY;UNTIL=20260319T235959\r\nEXDATE;TZID=Europe/London:20260310T100000,20260311T100000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/ed617bc7-66c1-474d-a467-86565e680820.ics\n \n \n \"898b4e9c84b4dade\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:ed617bc7-66c1-474d-a467-86565e680820\r\nDTSTAMP:20260330T141513Z\r\nSUMMARY:test 8\r\nDESCRIPTION:Default Mozilla Description\r\nDTSTART;TZID=Europe/London:20260302T160000\r\nDTEND;TZID=Europe/London:20260302T170000\r\nRRULE:FREQ=DAILY;UNTIL=20260319T235959\r\nEXDATE;TZID=Europe/London:20260312T160000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n", "body_truncated": false}} +{"ts": "2026-03-30T14:15:29+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/ed617bc7-66c1-474d-a467-86565e680820.ics", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "14128", "If-Match": "\"898b4e9c84b4dade\"", "Origin": "http://localhost:8080", "DNT": "1", "Authorization": "***", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "BEGIN:VCALENDAR\r\nPRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN\r\nVERSION:2.0\r\nBEGIN:VTIMEZONE\r\nTZID:Europe/London\r\nX-TZINFO:Europe/London[2025b]\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:-000115\r\nTZNAME:Europe/London(STD)\r\nDTSTART:18471201T000000\r\nRDATE:18471201T000000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19160521T020000\r\nRDATE:19160521T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19161001T030000\r\nRDATE:19161001T030000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19170408T020000\r\nRDATE:19170408T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19170917T030000\r\nRDATE:19170917T030000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19180324T020000\r\nRDATE:19180324T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19180930T030000\r\nRRULE:FREQ=YEARLY;BYMONTH=9;BYDAY=-1MO;UNTIL=19190929T030000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19190330T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU;UNTIL=19200328T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19201025T030000\r\nRDATE:19201025T030000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19210403T020000\r\nRDATE:19210403T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19211003T030000\r\nRDATE:19211003T030000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19220326T020000\r\nRDATE:19220326T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19221008T030000\r\nRDATE:19221008T030000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19230422T020000\r\nRDATE:19230422T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19240413T020000\r\nRDATE:19240413T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19230916T030000\r\nRRULE:FREQ=YEARLY;BYMONTH=9;BYDAY=3SU;UNTIL=19240921T030000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19250419T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=3SU;UNTIL=19260418T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19270410T020000\r\nRDATE:19270410T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19280422T020000\r\nRDATE:19280422T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19290421T020000\r\nRDATE:19290421T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19300413T020000\r\nRDATE:19300413T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19310419T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=3SU;UNTIL=19320417T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19251004T030000\r\nRRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=1SU;UNTIL=19321002T030000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19330409T020000\r\nRDATE:19330409T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19331008T030000\r\nRDATE:19331008T030000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19340422T020000\r\nRDATE:19340422T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19350414T020000\r\nRDATE:19350414T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19360419T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=3SU;UNTIL=19370418T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19380410T020000\r\nRDATE:19380410T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19341007T030000\r\nRRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=1SU;UNTIL=19381002T030000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19390416T020000\r\nRDATE:19390416T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19400225T020000\r\nRDATE:19400225T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+020000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19410504T020000\r\nRDATE:19410504T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+020000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19410810T030000\r\nRDATE:19410810T030000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+020000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19420405T020000\r\nRDATE:19420405T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+020000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19420809T030000\r\nRDATE:19420809T030000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+020000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19430404T020000\r\nRDATE:19430404T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+020000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19430815T030000\r\nRDATE:19430815T030000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+020000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19440402T020000\r\nRDATE:19440402T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+020000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19440917T030000\r\nRDATE:19440917T030000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+020000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19450402T020000\r\nRDATE:19450402T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19391119T030000\r\nRDATE:19391119T030000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+020000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19450715T030000\r\nRDATE:19450715T030000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19460414T020000\r\nRDATE:19460414T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19470316T020000\r\nRDATE:19470316T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+020000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19470413T020000\r\nRDATE:19470413T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19451007T030000\r\nRRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=1SU;UNTIL=19461006T030000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+020000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19470810T030000\r\nRDATE:19470810T030000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19471102T030000\r\nRDATE:19471102T030000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19480314T020000\r\nRDATE:19480314T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19490403T020000\r\nRDATE:19490403T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19481031T030000\r\nRRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU;UNTIL=19491030T030000\r\nEND:STANDARD\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19501022T030000\r\nRDATE:19501022T030000\r\nEND:STANDARD\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19511021T030000\r\nRDATE:19511021T030000\r\nEND:STANDARD\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19521026T030000\r\nRDATE:19521026T030000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19500416T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=3SU;UNTIL=19530419T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19540411T020000\r\nRDATE:19540411T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19550417T020000\r\nRDATE:19550417T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19560422T020000\r\nRDATE:19560422T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19570414T020000\r\nRDATE:19570414T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19580420T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=3SU;UNTIL=19590419T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19600410T020000\r\nRDATE:19600410T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19531004T030000\r\nRRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=1SU;UNTIL=19601002T030000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19610326T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU;UNTIL=19630331T020000\r\nEND:DAYLIGHT\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19640322T020000\r\nRDATE:19640322T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19611029T030000\r\nRRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU;UNTIL=19641025T030000\r\nEND:STANDARD\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19651024T030000\r\nRRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=4SU;UNTIL=19661023T030000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19650321T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=3SU;UNTIL=19670319T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19671029T030000\r\nRDATE:19671029T030000\r\nEND:STANDARD\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19681027T000000\r\nRDATE:19681027T000000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19680218T020000\r\nRDATE:19680218T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19711031T030000\r\nRRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU;UNTIL=19751026T030000\r\nEND:STANDARD\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19761024T030000\r\nRRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=4SU;UNTIL=19771023T030000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19720319T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=3SU;UNTIL=19800316T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19781029T030000\r\nRRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU;UNTIL=19801026T030000\r\nEND:STANDARD\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19811025T020000\r\nRDATE:19811025T020000\r\nEND:STANDARD\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19821024T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=4SU;UNTIL=19831023T020000\r\nEND:STANDARD\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19841028T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU;UNTIL=19871025T020000\r\nEND:STANDARD\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19881023T020000\r\nRDATE:19881023T020000\r\nEND:STANDARD\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19891029T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU;UNTIL=19921025T020000\r\nEND:STANDARD\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19931024T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=4SU;UNTIL=19951022T020000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19810329T010000\r\nRRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU;UNTIL=19960331T010000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19961027T020000\r\nRDATE:19961027T020000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:(DST)\r\nDTSTART:19970330T010000\r\nRRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:(STD)\r\nDTSTART:19971026T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU\r\nEND:STANDARD\r\nEND:VTIMEZONE\r\nBEGIN:VEVENT\r\nLAST-MODIFIED:20260330T141529Z\r\nDTSTAMP:20260330T141529Z\r\nUID:ed617bc7-66c1-474d-a467-86565e680820\r\nSUMMARY:test 8\r\nRRULE:FREQ=DAILY;UNTIL=20260319T235959\r\nEXDATE;TZID=Europe/London:20260312T160000\r\nEXDATE;TZID=Europe/London:20260313T160000\r\nDTSTART;TZID=Europe/London:20260302T160000\r\nDTEND;TZID=Europe/London:20260302T170000\r\nDESCRIPTION:Default Mozilla Description\r\nSEQUENCE:1\r\nX-MOZ-GENERATION:1\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:15:29 GMT", "ETag": "\"548f92e38ae08226\""}, "body": "HTTP/1.0 200 OK\r\nServer: CalendarFixture/0.1 Python/3.13.5\r\nDate: Mon, 30 Mar 2026 14:15:29 GMT\r\nETag: \"548f92e38ae08226\"\r\n\r\n", "body_truncated": false}} +{"ts": "2026-03-30T14:15:29+00:00", "client": "127.0.0.1", "method": "REPORT", "path": "/caldav/calendars/public/", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/xml; charset=utf-8", "Content-Length": "266", "Depth": "1", "Origin": "http://localhost:8080", "DNT": "1", "Authorization": "***", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "\n/caldav/calendars/public/ed617bc7-66c1-474d-a467-86565e680820.ics", "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:15:29 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "15740"}, "body": "HTTP/1.0 207 Multi-Status\r\nServer: CalendarFixture/0.1 Python/3.13.5\r\nDate: Mon, 30 Mar 2026 14:15:29 GMT\r\nContent-Type: application/xml; charset=utf-8\r\nContent-Length: 15740\r\n\r\n\n\n\n /caldav/calendars/public/1.ics\n \n \n \"9e61840edd26b59c\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-001@calendar-wp-plugin\r\nDTSTAMP:20260330T141529Z\r\nSUMMARY:Board Meeting\r\nDESCRIPTION:Quarterly board review.\r\nLOCATION:Room A\r\nCATEGORIES:Governance\r\nDTSTART;TZID=Europe/London:20260401T100000\r\nDTEND;TZID=Europe/London:20260401T113000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/2.ics\n \n \n \"6f79e4a470012e8c\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-002@calendar-wp-plugin\r\nDTSTAMP:20260330T141529Z\r\nSUMMARY:Office Closed\r\nDESCRIPTION:Public holiday closure.\r\nLOCATION:HQ\r\nCATEGORIES:Operations\r\nDTSTART;VALUE=DATE:20260504\r\nDTEND;VALUE=DATE:20260505\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/3.ics\n \n \n \"7dd021ab4c51e62e\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-003@calendar-wp-plugin\r\nDTSTAMP:20260330T141529Z\r\nSUMMARY:Daily Standup\r\nDESCRIPTION:15 minute sync.\r\nLOCATION:Online\r\nCATEGORIES:Team\r\nDTSTART;TZID=Europe/London:20260406T090000\r\nDTEND;TZID=Europe/London:20260406T091500\r\nRRULE:FREQ=DAILY;COUNT=10\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/4.ics\n \n \n \"382b171c7e467f49\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-004@calendar-wp-plugin\r\nDTSTAMP:20260330T141529Z\r\nSUMMARY:Community Lunch\r\nDESCRIPTION:Weekly community lunch.\r\nLOCATION:Cafeteria\r\nCATEGORIES:Community\r\nDTSTART;TZID=Europe/London:20260408T123000\r\nDTEND;TZID=Europe/London:20260408T133000\r\nRRULE:FREQ=WEEKLY\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/5.ics\n \n \n \"6a0b1853010a3060\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-005@calendar-wp-plugin\r\nDTSTAMP:20260330T141529Z\r\nSUMMARY:Finance Close\r\nDESCRIPTION:Month-end close process.\r\nLOCATION:Finance Office\r\nCATEGORIES:Finance\r\nDTSTART;TZID=Europe/London:20260430T170000\r\nDTEND;TZID=Europe/London:20260430T180000\r\nRRULE:FREQ=MONTHLY;UNTIL=20260831T235959\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/6.ics\n \n \n \"0344b53653b1897e\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-006@calendar-wp-plugin\r\nDTSTAMP:20260330T141529Z\r\nSUMMARY:Annual Conference\r\nDESCRIPTION:Annual community conference.\r\nLOCATION:Main Hall\r\nCATEGORIES:Events\r\nDTSTART;TZID=Europe/London:20260615T100000\r\nDTEND;TZID=Europe/London:20260615T170000\r\nRRULE:FREQ=YEARLY;COUNT=3\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/7.ics\n \n \n \"f3727cb12d14614a\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-007@calendar-wp-plugin\r\nDTSTAMP:20260330T141529Z\r\nSUMMARY:Fortnightly Coaching\r\nDESCRIPTION:Coaching check-in.\r\nLOCATION:Online\r\nCATEGORIES:Training\r\nDTSTART;TZID=Europe/London:20260407T150000\r\nDTEND;TZID=Europe/London:20260407T160000\r\nRRULE:FREQ=WEEKLY;INTERVAL=2;UNTIL=20260731T235959\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/8.ics\n \n \n \"2200904959f9c5cd\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-008@calendar-wp-plugin\r\nDTSTAMP:20260330T141529Z\r\nSUMMARY:DST Validation Event\r\nDESCRIPTION:Validates DST transition rendering.\r\nLOCATION:Lab\r\nCATEGORIES:QA\r\nDTSTART;TZID=Europe/London:20261025T003000\r\nDTEND;TZID=Europe/London:20261025T023000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/9.ics\n \n \n \"028aa2cacadd930b\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-009@calendar-wp-plugin\r\nDTSTAMP:20260330T141529Z\r\nSUMMARY:Leap Day Marker\r\nDESCRIPTION:Leap day recurrence behavior.\r\nLOCATION:Calendar\r\nCATEGORIES:QA\r\nDTSTART;TZID=Europe/London:20280229T090000\r\nDTEND;TZID=Europe/London:20280229T100000\r\nRRULE:FREQ=YEARLY;COUNT=3\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/10.ics\n \n \n \"1062edf9fc69e84f\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-010@calendar-wp-plugin\r\nDTSTAMP:20260330T141529Z\r\nSUMMARY:Therapy Session\r\nDESCRIPTION:Used for single-occurrence delete exception tests.\r\nLOCATION:Clinic\r\nCATEGORIES:Health\r\nDTSTART;TZID=Europe/London:20260403T140000\r\nDTEND;TZID=Europe/London:20260403T150000\r\nRRULE:FREQ=WEEKLY;COUNT=8\r\nEXDATE;TZID=Europe/London:20260417T140000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/smoke-vtimezone.ics\n \n \n \"e5984c98962fda96\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-vtimezone-parser-001\r\nDTSTAMP:20260330T141529Z\r\nSUMMARY:Smoke VTIMEZONE Parse\r\nDTSTART;TZID=Europe/London:20260423T150000\r\nDTEND;TZID=Europe/London:20260423T160000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/smoke-monthly-nth.ics\n \n \n \"a4f11de675293164\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-nth-001\r\nDTSTAMP:20260330T141529Z\r\nSUMMARY:Smoke Monthly Nth\r\nDTSTART;TZID=Europe/London:20260426T150000\r\nDTEND;TZID=Europe/London:20260426T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SU;BYSETPOS=4\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/smoke-monthly-ordinal.ics\n \n \n \"db9706d0865b79d9\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-ordinal-001\r\nDTSTAMP:20260330T141529Z\r\nSUMMARY:Smoke Monthly Ordinal\r\nDTSTART;TZID=Europe/London:20260411T150000\r\nDTEND;TZID=Europe/London:20260411T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SA;BYSETPOS=2\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/smoke-monthly-last.ics\n \n \n \"9b7f63d5c08cc7c8\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-last-001\r\nDTSTAMP:20260330T141529Z\r\nSUMMARY:Smoke Monthly Last\r\nDTSTART;TZID=Europe/London:20260425T150000\r\nDTEND;TZID=Europe/London:20260425T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SA;BYSETPOS=-1\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/15.ics\n \n \n \"e6f09ba57238f931\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:4e9ee4c86911a55106b9@calendar-wp-plugin\r\nDTSTAMP:20260330T141529Z\r\nSUMMARY:Smoke 4th Sunday Anchor\r\nDESCRIPTION:anchor-normalization\r\nDTSTART;TZID=Europe/London:20260524T150000\r\nDTEND;TZID=Europe/London:20260524T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SU;BYSETPOS=4\r\nEXDATE;TZID=Europe/London:20260524T150000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/16.ics\n \n \n \"904acb970acebc29\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:771bbfbfb45098265113@calendar-wp-plugin\r\nDTSTAMP:20260330T141529Z\r\nSUMMARY:Smoke Daily Exception TZ\r\nDESCRIPTION:tz-key\r\nDTSTART;TZID=Europe/London:20260302T100000\r\nDTEND;TZID=Europe/London:20260302T110000\r\nRRULE:FREQ=DAILY;UNTIL=20260319T235959\r\nEXDATE;TZID=Europe/London:20260310T100000,20260311T100000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/smoke-exdate-import.ics\n \n \n \"1c3e46425e90df29\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-exdate-import-001\r\nDTSTAMP:20260330T141529Z\r\nSUMMARY:Smoke EXDATE Import\r\nDTSTART;TZID=Europe/London:20260302T100000\r\nDTEND;TZID=Europe/London:20260302T110000\r\nRRULE:FREQ=DAILY;UNTIL=20260319T235959\r\nEXDATE;TZID=Europe/London:20260310T100000,20260311T100000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/ed617bc7-66c1-474d-a467-86565e680820.ics\n \n \n \"548f92e38ae08226\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:ed617bc7-66c1-474d-a467-86565e680820\r\nDTSTAMP:20260330T141529Z\r\nSUMMARY:test 8\r\nDESCRIPTION:Default Mozilla Description\r\nDTSTART;TZID=Europe/London:20260302T160000\r\nDTEND;TZID=Europe/London:20260302T170000\r\nRRULE:FREQ=DAILY;UNTIL=20260319T235959\r\nEXDATE;TZID=Europe/London:20260312T160000,20260313T160000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n", "body_truncated": false}} +{"ts": "2026-03-30T14:15:35+00:00", "client": "127.0.0.1", "method": "PROPFIND", "path": "/caldav/calendars/public/", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/xml; charset=utf-8", "Content-Length": "144", "Depth": "1", "Origin": "http://localhost:8080", "DNT": "1", "Authorization": "***", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "\n", "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:15:35 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "6140"}, "body": "HTTP/1.0 207 Multi-Status\r\nServer: CalendarFixture/0.1 Python/3.13.5\r\nDate: Mon, 30 Mar 2026 14:15:35 GMT\r\nContent-Type: application/xml; charset=utf-8\r\nContent-Length: 6140\r\n\r\n\n\n\n /caldav/calendars/public/\n \n \n Public Calendar\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/1.ics\n \n \n text/calendar; charset=utf-8\"9e61840edd26b59c\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/2.ics\n \n \n text/calendar; charset=utf-8\"6f79e4a470012e8c\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/3.ics\n \n \n text/calendar; charset=utf-8\"7dd021ab4c51e62e\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/4.ics\n \n \n text/calendar; charset=utf-8\"382b171c7e467f49\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/5.ics\n \n \n text/calendar; charset=utf-8\"6a0b1853010a3060\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/6.ics\n \n \n text/calendar; charset=utf-8\"0344b53653b1897e\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/7.ics\n \n \n text/calendar; charset=utf-8\"f3727cb12d14614a\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/8.ics\n \n \n text/calendar; charset=utf-8\"2200904959f9c5cd\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/9.ics\n \n \n text/calendar; charset=utf-8\"028aa2cacadd930b\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/10.ics\n \n \n text/calendar; charset=utf-8\"1062edf9fc69e84f\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/smoke-vtimezone.ics\n \n \n text/calendar; charset=utf-8\"e5984c98962fda96\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/smoke-monthly-nth.ics\n \n \n text/calendar; charset=utf-8\"a4f11de675293164\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/smoke-monthly-ordinal.ics\n \n \n text/calendar; charset=utf-8\"db9706d0865b79d9\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/smoke-monthly-last.ics\n \n \n text/calendar; charset=utf-8\"9b7f63d5c08cc7c8\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/15.ics\n \n \n text/calendar; charset=utf-8\"e6f09ba57238f931\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/16.ics\n \n \n text/calendar; charset=utf-8\"904acb970acebc29\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/smoke-exdate-import.ics\n \n \n text/calendar; charset=utf-8\"1c3e46425e90df29\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/ed617bc7-66c1-474d-a467-86565e680820.ics\n \n \n text/calendar; charset=utf-8\"548f92e38ae08226\"\n \n HTTP/1.1 200 OK\n \n\n\n", "body_truncated": false}} +{"ts": "2026-03-30T14:20:15+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:20:15 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "6585"}, "body": "{\"data\": [{\"id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"start_datetime\": \"2026-04-01T10:00:00+01:00\", \"end_datetime\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"34af04b9ccbadb9d\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T14:16:53+00:00\"}, {\"id\": 2, \"uid\": \"fixture-ce-002@calendar-wp-plugin\", \"title\": \"Office Closed\", \"description\": \"Public holiday closure.\", \"location\": \"HQ\", \"category\": \"Operations\", \"all_day_event\": true, \"start_datetime\": \"2026-05-04\", \"end_datetime\": \"2026-05-05\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"a33ed", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T14:20:15+00:00", "client": "127.0.0.1", "method": "GET", "path": "/calendar.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:20:15 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"138a8bec591c96d2\"", "Content-Length": "3132"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nCALSCALE:GREGORIAN\r\nX-WR-TIMEZONE:Europe/London\r\nBEGIN:VEVENT\r\nUID:fixture-ce-001@calendar-wp-plugin\r\nDTSTAMP:20260330T142015Z\r\nSUMMARY:Board Meeting\r\nDESCRIPTION:Quarterly board review.\r\nLOCATION:Room A\r\nCATEGORIES:Governance\r\nDTSTART;TZID=Europe/London:20260401T100000\r\nDTEND;TZID=Europe/London:20260401T113000\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-002@calendar-wp-plugin\r\nDTSTAMP:20260330T142015Z\r\nSUMMARY:Office Closed\r\nDESCRIPTION:Public holiday closure.\r\nLOCATION:HQ\r\nCATEGORIES:Operations\r\nDTSTART;VALUE=DATE:20260504\r\nDTEND;VALUE=DATE:20260505\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-003@calendar-wp-plugin\r\nDTSTAMP:20260330T142015Z\r\nSUMMARY:Daily Standup\r\nDESCRIPTION:15 minute sync.\r\nLOCATION:Online\r\nCATEGORIES:Team\r\nDTSTART;TZID=Europe/London:20260406T090000\r\nDTEND;TZID=Europe/London:20260406T091500\r\nRRULE:FREQ=DAILY;COUNT=10\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-004@calendar-wp-plugin\r\nDTSTAMP:20260330T142015Z\r\nSUMMARY:Community Lunch\r\nDESCRIPTION:Weekly community lunch.\r\nLOCATION:Cafeteria\r\nCATEGORIES:Community\r\nDTSTART;TZID=Europe/London:20260408T123000\r\nDTEND;TZID=Europe/London:20260408T133000\r\n ...(truncated)", "body_bytes": 3132, "body_truncated": false}} +{"ts": "2026-03-30T14:20:15+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/1.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:20:15 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"34af04b9ccbadb9d\"", "Content-Length": "364"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-001@calendar-wp-plugin\r\nDTSTAMP:20260330T142015Z\r\nSUMMARY:Board Meeting\r\nDESCRIPTION:Quarterly board review.\r\nLOCATION:Room A\r\nCATEGORIES:Governance\r\nDTSTART;TZID=Europe/London:20260401T100000\r\nDTEND;TZID=Europe/London:20260401T113000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 364, "body_truncated": false}} +{"ts": "2026-03-30T14:20:15+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/10/occurrences/2026-04-17T14:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:20:15 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T14:20:15+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-vtimezone.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "454"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN\nVERSION:2.0\nBEGIN:VTIMEZONE\nTZID:Europe/London\nBEGIN:STANDARD\nDTSTART:18471201T000000\nRRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=9\nTZOFFSETFROM:+0115\nTZOFFSETTO:+0000\nTZNAME:GMT\nEND:STANDARD\nEND:VTIMEZONE\nBEGIN:VEVENT\nUID:smoke-vtimezone-parser-001\nSUMMARY:Smoke VTIMEZONE Parse\nDTSTART;TZID=Europe/London:20260423T150000\nDTEND;TZID=Europe/London:20260423T160000\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 454, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:20:15 GMT", "ETag": "\"e13f867894f5d240\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T14:20:16+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-vtimezone.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:20:16 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"e13f867894f5d240\"", "Content-Length": "288"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-vtimezone-parser-001\r\nDTSTAMP:20260330T142016Z\r\nSUMMARY:Smoke VTIMEZONE Parse\r\nDTSTART;TZID=Europe/London:20260423T150000\r\nDTEND;TZID=Europe/London:20260423T160000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 288, "body_truncated": false}} +{"ts": "2026-03-30T14:20:16+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-monthly-nth.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "261"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-monthly-nth-001\nSUMMARY:Smoke Monthly Nth\nDTSTART;TZID=Europe/London:20260402T150000\nDTEND;TZID=Europe/London:20260402T160000\nRRULE:FREQ=MONTHLY;BYDAY=SU;BYSETPOS=4\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 261, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:20:16 GMT", "ETag": "\"3a6ba02a67b14874\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T14:20:16+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-monthly-nth.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:20:16 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"3a6ba02a67b14874\"", "Content-Length": "319"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-nth-001\r\nDTSTAMP:20260330T142016Z\r\nSUMMARY:Smoke Monthly Nth\r\nDTSTART;TZID=Europe/London:20260426T150000\r\nDTEND;TZID=Europe/London:20260426T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SU;BYSETPOS=4\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 319, "body_truncated": false}} +{"ts": "2026-03-30T14:20:16+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-monthly-ordinal.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "259"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-monthly-ordinal-001\nSUMMARY:Smoke Monthly Ordinal\nDTSTART;TZID=Europe/London:20260411T150000\nDTEND;TZID=Europe/London:20260411T160000\nRRULE:FREQ=MONTHLY;BYDAY=2SA\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 259, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:20:16 GMT", "ETag": "\"1710ae19fe56d567\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T14:20:16+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-monthly-ordinal.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:20:16 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"1710ae19fe56d567\"", "Content-Length": "327"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-ordinal-001\r\nDTSTAMP:20260330T142016Z\r\nSUMMARY:Smoke Monthly Ordinal\r\nDTSTART;TZID=Europe/London:20260411T150000\r\nDTEND;TZID=Europe/London:20260411T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SA;BYSETPOS=2\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 327, "body_truncated": false}} +{"ts": "2026-03-30T14:20:16+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-monthly-last.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "264"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-monthly-last-001\nSUMMARY:Smoke Monthly Last\nDTSTART;TZID=Europe/London:20260425T150000\nDTEND;TZID=Europe/London:20260425T160000\nRRULE:FREQ=MONTHLY;BYDAY=SA;BYSETPOS=-1\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 264, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:20:16 GMT", "ETag": "\"98bbc37be6ca6788\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T14:20:16+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-monthly-last.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:20:16 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"98bbc37be6ca6788\"", "Content-Length": "322"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-last-001\r\nDTSTAMP:20260330T142016Z\r\nSUMMARY:Smoke Monthly Last\r\nDTSTART;TZID=Europe/London:20260425T150000\r\nDTEND;TZID=Europe/London:20260425T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SA;BYSETPOS=-1\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 322, "body_truncated": false}} +{"ts": "2026-03-30T14:20:16+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "X-WP-User": "admin", "Content-Length": "362"}, "body": "{\"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\"}", "body_bytes": 362, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:20:16 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "673"}, "body": "{\"data\": {\"id\": 15, \"uid\": \"809b5d43371ed23850b0@calendar-wp-plugin\", \"title\": \"Smoke 4th Sunday Anchor\", \"description\": \"anchor-normalization\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"start_datetime\": \"2026-05-24T15:00:00+01:00\", \"end_datetime\": \"2026-05-24T16:00:00+01:00\", \"repeat_type\": \"monthly\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"weekday_of_month\", \"repeat_nth_day\": null, \"repeat_nth_pos\": 4, \"repeat_nth_weekday\": 0, \"repeat_range_mode\": \"no_end\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"1f3f52be73f1461b\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T14:20:16+00:00\"}}", "body_bytes": 673, "body_truncated": false}} +{"ts": "2026-03-30T14:20:16+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/15/occurrences/2026-05-24T15:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:20:16 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T14:20:16+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/15.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:20:16 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"190e382546a84122\"", "Content-Length": "420"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:809b5d43371ed23850b0@calendar-wp-plugin\r\nDTSTAMP:20260330T142016Z\r\nSUMMARY:Smoke 4th Sunday Anchor\r\nDESCRIPTION:anchor-normalization\r\nDTSTART;TZID=Europe/London:20260524T150000\r\nDTEND;TZID=Europe/London:20260524T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SU;BYSETPOS=4\r\nEXDATE;TZID=Europe/London:20260524T150000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 420, "body_truncated": false}} +{"ts": "2026-03-30T14:20:16+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "X-WP-User": "admin", "Content-Length": "295"}, "body": "{\"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\"}", "body_bytes": 295, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:20:16 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "655"}, "body": "{\"data\": {\"id\": 16, \"uid\": \"8483fc55d1de7d7606fc@calendar-wp-plugin\", \"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_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"until\", \"repeat_count\": null, \"repeat_until\": \"2026-03-19\", \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"bc3799c352e91742\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T14:20:16+00:00\"}}", "body_bytes": 655, "body_truncated": false}} +{"ts": "2026-03-30T14:20:16+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/16/occurrences/2026-03-11T11:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:20:16 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T14:20:16+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/16/occurrences?from=2026-03-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:20:16 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "5212"}, "body": "{\"data\": [{\"event_id\": 16, \"uid\": \"8483fc55d1de7d7606fc@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-02T10:00:00+00:00\", \"occurrence_end\": \"2026-03-02T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"8483fc55d1de7d7606fc@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-03T10:00:00+00:00\", \"occurrence_end\": \"2026-03-03T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"8483fc55d1de7d7606fc@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-04T10:00:00+00:00\", \"occurrence_end\": \"2026-03-04T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"8483fc55d1de7d7606fc@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-05T10:00:00+00:00\", \"occurrence_end\": \"2026-03-05T11:00:00", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T14:20:16+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/16/occurrences/2026-03-10", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:20:16 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T14:20:16+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/16/occurrences?from=2026-03-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:20:16 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "4906"}, "body": "{\"data\": [{\"event_id\": 16, \"uid\": \"8483fc55d1de7d7606fc@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-02T10:00:00+00:00\", \"occurrence_end\": \"2026-03-02T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"8483fc55d1de7d7606fc@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-03T10:00:00+00:00\", \"occurrence_end\": \"2026-03-03T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"8483fc55d1de7d7606fc@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-04T10:00:00+00:00\", \"occurrence_end\": \"2026-03-04T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"8483fc55d1de7d7606fc@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-05T10:00:00+00:00\", \"occurrence_end\": \"2026-03-05T11:00:00", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T14:20:16+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-exdate-import.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "323"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-exdate-import-001\nSUMMARY:Smoke EXDATE Import\nDTSTART;TZID=Europe/London:20260302T100000\nDTEND;TZID=Europe/London:20260302T110000\nRRULE:FREQ=DAILY;UNTIL=20260319T235959\nEXDATE;TZID=Europe/London:20260310T100000,20260311T100000\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 323, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:20:16 GMT", "ETag": "\"d037ce8fd6055b51\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T14:20:16+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:20:16 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "11006"}, "body": "{\"data\": [{\"id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"start_datetime\": \"2026-04-01T10:00:00+01:00\", \"end_datetime\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"34af04b9ccbadb9d\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T14:16:53+00:00\"}, {\"id\": 2, \"uid\": \"fixture-ce-002@calendar-wp-plugin\", \"title\": \"Office Closed\", \"description\": \"Public holiday closure.\", \"location\": \"HQ\", \"category\": \"Operations\", \"all_day_event\": true, \"start_datetime\": \"2026-05-04\", \"end_datetime\": \"2026-05-05\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"a33ed", "body_bytes": 3927, "body_truncated": true}} +{"ts": "2026-03-30T14:20:16+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/17/occurrences?from=2026-03-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:20:16 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "4474"}, "body": "{\"data\": [{\"event_id\": 17, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-02T10:00:00+01:00\", \"occurrence_end\": \"2026-03-02T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-03T10:00:00+01:00\", \"occurrence_end\": \"2026-03-03T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-04T10:00:00+01:00\", \"occurrence_end\": \"2026-03-04T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-05T10:00:00+01:00\", \"occurrence_end\": \"2026-03-05T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE ", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T14:20:16+00:00", "client": "127.0.0.1", "method": "GET", "path": "/calendar", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:20:16 GMT", "Content-Type": "text/html; charset=utf-8", "Content-Length": "43446"}, "body": "[omitted html payload: 3934 bytes]", "body_bytes": 3934, "body_truncated": true}} +{"ts": "2026-03-30T14:20:26+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/admin/diagnostics?limit=2&as=admin", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:20:26 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "2521"}, "body": "{\"data\": [{\"ts\": \"2026-03-30T14:20:16+00:00\", \"client\": \"127.0.0.1\", \"method\": \"GET\", \"path\": \"/wp-json/calendar/v1/events/17/occurrences?from=2026-03-01&months=1\", \"request\": {\"headers\": {\"Host\": \"127.0.0.1:8080\", \"User-Agent\": \"curl/8.14.1\", \"Accept\": \"*/*\", \"X-WP-User\": \"admin\"}, \"body\": \"\", \"body_bytes\": 0, \"body_truncated\": false}, \"response\": {\"status\": 200, \"headers\": {\"Server\": \"CalendarFixture/0.1 Python/3.13.5\", \"Date\": \"Mon, 30 Mar 2026 14:20:16 GMT\", \"Content-Type\": \"application/json; charset=utf-8\", \"Content-Length\": \"4474\"}, \"body\": \"{\\\"data\\\": [{\\\"event_id\\\": 17, \\\"uid\\\": \\\"smoke-exdate-import-001\\\", \\\"title\\\": \\\"Smoke EXDATE Import\\\", \\\"description\\\": \\\"\\\", \\\"location\\\": \\\"\\\", \\\"category\\\": \\\"\\\", \\\"all_day_event\\\": false, \\\"occurrence_start\\\": \\\"2026-03-02T10:00:00+01:00\\\", \\\"occurrence_end\\\": \\\"2026-03-02T11:00:00+01:00\\\", \\\"repeat_type\\\": \\\"daily\\\"}, {\\\"event_id\\\": 17, \\\"uid\\\": \\\"smoke-exdate-import-001\\\", \\\"title\\\": \\\"Smoke EXDATE Import\\\", \\\"description\\\": \\\"\\\", \\\"location\\\": \\\"\\\", \\\"category\\\": \\\"\\\", \\\"all_day_event\\\": false, \\\"occurrence_start\\\": \\\"2026-03-03T10:00:00+01:00\\\", \\\"occurrence_end\\\": \\\"2026-03-03T11:00:00+01:00\\\", \\\"repeat_type\\\": \\\"daily\\\"}, {\\\"e ...(truncated)", "body_bytes": 2521, "body_truncated": false}} +{"ts": "2026-03-30T14:22:19+00:00", "client": "127.0.0.1", "method": "PROPFIND", "path": "/caldav/calendars/public/", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/xml; charset=utf-8", "Content-Length": "144", "Depth": "1", "Origin": "http://localhost:8080", "DNT": "1", "Authorization": "***", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "\n", "body_bytes": 144, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:22:19 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "5799"}, "body": "\n\n\n /caldav/calendars/public/\n \n \n Public Calendar\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/1.ics\n \n \n text/calendar; charset=utf-8\"34af04b9ccbadb9d\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/2.ics\n \n \n text/calendar; charset=utf-8\"a33ed4025fd8b61d\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/3.ics\n \n \n ...(truncated)", "body_bytes": 3919, "body_truncated": true}} +{"ts": "2026-03-30T14:22:20+00:00", "client": "127.0.0.1", "method": "REPORT", "path": "/caldav/calendars/public/", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/xml; charset=utf-8", "Content-Length": "1071", "Depth": "1", "Origin": "http://localhost:8080", "DNT": "1", "Authorization": "***", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "\n/caldav/calendars/public/smoke-exdate-import.ics/caldav/calendars/public/16.ics/caldav/calendars/public/15.ics/caldav/calendars/public/smoke-monthly-last.ics/caldav/calendars/public/smoke-monthly-ordinal.ics/caldav/calendars/public/smoke-monthly-nth.ics/caldav/calendars/public/smoke-vtimezone.ics/caldav/calendars/public/10.ics/caldav/calendars/public/9.ics/caldav/calendars/public/8.ics/caldav/calendars/public/7.ics/caldav/calendars/public/6.ics/caldav/calendars/public/5.ics/caldav/calendars/public/4.ics/caldav/calendars/public/3.ics/caldav/calendars/public/2.ics/caldav/calendars/public/1.ics", "body_bytes": 1071, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:22:20 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "14800"}, "body": "\n\n\n /caldav/calendars/public/1.ics\n \n \n \"34af04b9ccbadb9d\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-001@calendar-wp-plugin\r\nDTSTAMP:20260330T142220Z\r\nSUMMARY:Board Meeting\r\nDESCRIPTION:Quarterly board review.\r\nLOCATION:Room A\r\nCATEGORIES:Governance\r\nDTSTART;TZID=Europe/London:20260401T100000\r\nDTEND;TZID=Europe/London:20260401T113000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/2.ics\n \n \n \"a33ed4025fd8b61d\"\n ...(truncated)", "body_bytes": 3918, "body_truncated": true}} +{"ts": "2026-03-30T14:22:44+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/caldav/calendars/public/smoke-exdate-import.ics", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "If-Match": "\"d037ce8fd6055b51\"", "Origin": "http://localhost:8080", "DNT": "1", "Authorization": "***", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:22:44 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T14:22:49+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/caldav/calendars/public/16.ics", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "If-Match": "\"d2a6091e74fce26b\"", "Origin": "http://localhost:8080", "DNT": "1", "Authorization": "***", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:22:49 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T14:23:21+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/267d03e6-66cc-458b-aaf2-400f5b0e2108.ics", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "14110", "If-None-Match": "*", "Origin": "http://localhost:8080", "DNT": "1", "Authorization": "***", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "BEGIN:VCALENDAR\r\nPRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN\r\nVERSION:2.0\r\nBEGIN:VTIMEZONE\r\nTZID:Europe/London\r\nX-TZINFO:Europe/London[2025b]\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:-000115\r\nTZNAME:Europe/London(STD)\r\nDTSTART:18471201T000000\r\nRDATE:18471201T000000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19160521T020000\r\nRDATE:19160521T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19161001T030000\r\nRDATE:19161001T030000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19170408T020000\r\nRDATE:19170408T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19170917T030000\r\nRDATE:19170917T030000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19180324T020000\r\nRDATE:19180324T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19180930T030000\r\nRRULE:FREQ=YEARLY;BYMONTH=9;BYDAY=-1MO;UNTIL=19190929 ...(truncated)", "body_bytes": 4097, "body_truncated": true}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:23:21 GMT", "ETag": "\"5e4c0898488797e4\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T14:23:21+00:00", "client": "127.0.0.1", "method": "REPORT", "path": "/caldav/calendars/public/", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/xml; charset=utf-8", "Content-Length": "266", "Depth": "1", "Origin": "http://localhost:8080", "DNT": "1", "Authorization": "***", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "\n/caldav/calendars/public/267d03e6-66cc-458b-aaf2-400f5b0e2108.ics", "body_bytes": 266, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:23:21 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "13893"}, "body": "\n\n\n /caldav/calendars/public/1.ics\n \n \n \"34af04b9ccbadb9d\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-001@calendar-wp-plugin\r\nDTSTAMP:20260330T142321Z\r\nSUMMARY:Board Meeting\r\nDESCRIPTION:Quarterly board review.\r\nLOCATION:Room A\r\nCATEGORIES:Governance\r\nDTSTART;TZID=Europe/London:20260401T100000\r\nDTEND;TZID=Europe/London:20260401T113000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/2.ics\n \n \n \"a33ed4025fd8b61d\"\n ...(truncated)", "body_bytes": 3918, "body_truncated": true}} +{"ts": "2026-03-30T14:23:23+00:00", "client": "127.0.0.1", "method": "PROPFIND", "path": "/caldav/calendars/public/", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/xml; charset=utf-8", "Content-Length": "144", "Depth": "1", "Origin": "http://localhost:8080", "DNT": "1", "Authorization": "***", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "\n", "body_bytes": 144, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:23:23 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "5509"}, "body": "\n\n\n /caldav/calendars/public/\n \n \n Public Calendar\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/1.ics\n \n \n text/calendar; charset=utf-8\"34af04b9ccbadb9d\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/2.ics\n \n \n text/calendar; charset=utf-8\"a33ed4025fd8b61d\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/3.ics\n \n \n ...(truncated)", "body_bytes": 3919, "body_truncated": true}} +{"ts": "2026-03-30T14:23:31+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/267d03e6-66cc-458b-aaf2-400f5b0e2108.ics", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "14085", "If-Match": "\"5e4c0898488797e4\"", "Origin": "http://localhost:8080", "DNT": "1", "Authorization": "***", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "BEGIN:VCALENDAR\r\nPRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN\r\nVERSION:2.0\r\nBEGIN:VTIMEZONE\r\nTZID:Europe/London\r\nX-TZINFO:Europe/London[2025b]\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:-000115\r\nTZNAME:Europe/London(STD)\r\nDTSTART:18471201T000000\r\nRDATE:18471201T000000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19160521T020000\r\nRDATE:19160521T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19161001T030000\r\nRDATE:19161001T030000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19170408T020000\r\nRDATE:19170408T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19170917T030000\r\nRDATE:19170917T030000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19180324T020000\r\nRDATE:19180324T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19180930T030000\r\nRRULE:FREQ=YEARLY;BYMONTH=9;BYDAY=-1MO;UNTIL=19190929 ...(truncated)", "body_bytes": 4097, "body_truncated": true}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:23:31 GMT", "ETag": "\"6638686e8b73b19c\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T14:23:31+00:00", "client": "127.0.0.1", "method": "REPORT", "path": "/caldav/calendars/public/", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/xml; charset=utf-8", "Content-Length": "266", "Depth": "1", "Origin": "http://localhost:8080", "DNT": "1", "Authorization": "***", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "\n/caldav/calendars/public/267d03e6-66cc-458b-aaf2-400f5b0e2108.ics", "body_bytes": 266, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:23:31 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "13936"}, "body": "\n\n\n /caldav/calendars/public/1.ics\n \n \n \"34af04b9ccbadb9d\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-001@calendar-wp-plugin\r\nDTSTAMP:20260330T142331Z\r\nSUMMARY:Board Meeting\r\nDESCRIPTION:Quarterly board review.\r\nLOCATION:Room A\r\nCATEGORIES:Governance\r\nDTSTART;TZID=Europe/London:20260401T100000\r\nDTEND;TZID=Europe/London:20260401T113000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/2.ics\n \n \n \"a33ed4025fd8b61d\"\n ...(truncated)", "body_bytes": 3918, "body_truncated": true}} +{"ts": "2026-03-30T14:23:33+00:00", "client": "127.0.0.1", "method": "PROPFIND", "path": "/caldav/calendars/public/", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/xml; charset=utf-8", "Content-Length": "144", "Depth": "1", "Origin": "http://localhost:8080", "DNT": "1", "Authorization": "***", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "\n", "body_bytes": 144, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:23:33 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "5509"}, "body": "\n\n\n /caldav/calendars/public/\n \n \n Public Calendar\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/1.ics\n \n \n text/calendar; charset=utf-8\"34af04b9ccbadb9d\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/2.ics\n \n \n text/calendar; charset=utf-8\"a33ed4025fd8b61d\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/3.ics\n \n \n ...(truncated)", "body_bytes": 3919, "body_truncated": true}} +{"ts": "2026-03-30T14:23:44+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/267d03e6-66cc-458b-aaf2-400f5b0e2108.ics", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "14128", "If-Match": "\"6638686e8b73b19c\"", "Origin": "http://localhost:8080", "DNT": "1", "Authorization": "***", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "BEGIN:VCALENDAR\r\nPRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN\r\nVERSION:2.0\r\nBEGIN:VTIMEZONE\r\nTZID:Europe/London\r\nX-TZINFO:Europe/London[2025b]\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:-000115\r\nTZNAME:Europe/London(STD)\r\nDTSTART:18471201T000000\r\nRDATE:18471201T000000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19160521T020000\r\nRDATE:19160521T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19161001T030000\r\nRDATE:19161001T030000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19170408T020000\r\nRDATE:19170408T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19170917T030000\r\nRDATE:19170917T030000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19180324T020000\r\nRDATE:19180324T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19180930T030000\r\nRRULE:FREQ=YEARLY;BYMONTH=9;BYDAY=-1MO;UNTIL=19190929 ...(truncated)", "body_bytes": 4097, "body_truncated": true}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:23:44 GMT", "ETag": "\"fc290607cac191f8\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T14:23:44+00:00", "client": "127.0.0.1", "method": "REPORT", "path": "/caldav/calendars/public/", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/xml; charset=utf-8", "Content-Length": "266", "Depth": "1", "Origin": "http://localhost:8080", "DNT": "1", "Authorization": "***", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "\n/caldav/calendars/public/267d03e6-66cc-458b-aaf2-400f5b0e2108.ics", "body_bytes": 266, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:23:44 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "13952"}, "body": "\n\n\n /caldav/calendars/public/1.ics\n \n \n \"34af04b9ccbadb9d\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-001@calendar-wp-plugin\r\nDTSTAMP:20260330T142344Z\r\nSUMMARY:Board Meeting\r\nDESCRIPTION:Quarterly board review.\r\nLOCATION:Room A\r\nCATEGORIES:Governance\r\nDTSTART;TZID=Europe/London:20260401T100000\r\nDTEND;TZID=Europe/London:20260401T113000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/2.ics\n \n \n \"a33ed4025fd8b61d\"\n ...(truncated)", "body_bytes": 3918, "body_truncated": true}} +{"ts": "2026-03-30T14:23:46+00:00", "client": "127.0.0.1", "method": "PROPFIND", "path": "/caldav/calendars/public/", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/xml; charset=utf-8", "Content-Length": "144", "Depth": "1", "Origin": "http://localhost:8080", "DNT": "1", "Authorization": "***", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "\n", "body_bytes": 144, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:23:46 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "5509"}, "body": "\n\n\n /caldav/calendars/public/\n \n \n Public Calendar\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/1.ics\n \n \n text/calendar; charset=utf-8\"34af04b9ccbadb9d\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/2.ics\n \n \n text/calendar; charset=utf-8\"a33ed4025fd8b61d\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/3.ics\n \n \n ...(truncated)", "body_bytes": 3919, "body_truncated": true}} +{"ts": "2026-03-30T14:23:50+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/caldav/calendars/public/267d03e6-66cc-458b-aaf2-400f5b0e2108.ics", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "If-Match": "\"fc290607cac191f8\"", "Origin": "http://localhost:8080", "DNT": "1", "Authorization": "***", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:23:50 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T14:24:33+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/ce2caf8e-6197-40a3-9c7b-e6eae7041903.ics", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "14116", "If-None-Match": "*", "Origin": "http://localhost:8080", "DNT": "1", "Authorization": "***", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "BEGIN:VCALENDAR\r\nPRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN\r\nVERSION:2.0\r\nBEGIN:VTIMEZONE\r\nTZID:Europe/London\r\nX-TZINFO:Europe/London[2025b]\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:-000115\r\nTZNAME:Europe/London(STD)\r\nDTSTART:18471201T000000\r\nRDATE:18471201T000000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19160521T020000\r\nRDATE:19160521T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19161001T030000\r\nRDATE:19161001T030000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19170408T020000\r\nRDATE:19170408T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19170917T030000\r\nRDATE:19170917T030000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19180324T020000\r\nRDATE:19180324T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19180930T030000\r\nRRULE:FREQ=YEARLY;BYMONTH=9;BYDAY=-1MO;UNTIL=19190929 ...(truncated)", "body_bytes": 4097, "body_truncated": true}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:24:33 GMT", "ETag": "\"32987e446d88f0f9\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T14:24:33+00:00", "client": "127.0.0.1", "method": "REPORT", "path": "/caldav/calendars/public/", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/xml; charset=utf-8", "Content-Length": "266", "Depth": "1", "Origin": "http://localhost:8080", "DNT": "1", "Authorization": "***", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "\n/caldav/calendars/public/ce2caf8e-6197-40a3-9c7b-e6eae7041903.ics", "body_bytes": 266, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:24:33 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "13902"}, "body": "\n\n\n /caldav/calendars/public/1.ics\n \n \n \"34af04b9ccbadb9d\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-001@calendar-wp-plugin\r\nDTSTAMP:20260330T142433Z\r\nSUMMARY:Board Meeting\r\nDESCRIPTION:Quarterly board review.\r\nLOCATION:Room A\r\nCATEGORIES:Governance\r\nDTSTART;TZID=Europe/London:20260401T100000\r\nDTEND;TZID=Europe/London:20260401T113000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/2.ics\n \n \n \"a33ed4025fd8b61d\"\n ...(truncated)", "body_bytes": 3918, "body_truncated": true}} +{"ts": "2026-03-30T14:24:36+00:00", "client": "127.0.0.1", "method": "PROPFIND", "path": "/caldav/calendars/public/", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/xml; charset=utf-8", "Content-Length": "144", "Depth": "1", "Origin": "http://localhost:8080", "DNT": "1", "Authorization": "***", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "\n", "body_bytes": 144, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:24:36 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "5509"}, "body": "\n\n\n /caldav/calendars/public/\n \n \n Public Calendar\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/1.ics\n \n \n text/calendar; charset=utf-8\"34af04b9ccbadb9d\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/2.ics\n \n \n text/calendar; charset=utf-8\"a33ed4025fd8b61d\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/3.ics\n \n \n ...(truncated)", "body_bytes": 3919, "body_truncated": true}} +{"ts": "2026-03-30T14:24:53+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/ce2caf8e-6197-40a3-9c7b-e6eae7041903.ics", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "14094", "If-Match": "\"32987e446d88f0f9\"", "Origin": "http://localhost:8080", "DNT": "1", "Authorization": "***", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "BEGIN:VCALENDAR\r\nPRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN\r\nVERSION:2.0\r\nBEGIN:VTIMEZONE\r\nTZID:Europe/London\r\nX-TZINFO:Europe/London[2025b]\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:-000115\r\nTZNAME:Europe/London(STD)\r\nDTSTART:18471201T000000\r\nRDATE:18471201T000000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19160521T020000\r\nRDATE:19160521T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19161001T030000\r\nRDATE:19161001T030000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19170408T020000\r\nRDATE:19170408T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19170917T030000\r\nRDATE:19170917T030000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19180324T020000\r\nRDATE:19180324T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19180930T030000\r\nRRULE:FREQ=YEARLY;BYMONTH=9;BYDAY=-1MO;UNTIL=19190929 ...(truncated)", "body_bytes": 4097, "body_truncated": true}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:24:53 GMT", "ETag": "\"05a6e14b3e8da6a4\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T14:24:53+00:00", "client": "127.0.0.1", "method": "REPORT", "path": "/caldav/calendars/public/", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/xml; charset=utf-8", "Content-Length": "266", "Depth": "1", "Origin": "http://localhost:8080", "DNT": "1", "Authorization": "***", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "\n/caldav/calendars/public/ce2caf8e-6197-40a3-9c7b-e6eae7041903.ics", "body_bytes": 266, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:24:53 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "13945"}, "body": "\n\n\n /caldav/calendars/public/1.ics\n \n \n \"34af04b9ccbadb9d\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-001@calendar-wp-plugin\r\nDTSTAMP:20260330T142453Z\r\nSUMMARY:Board Meeting\r\nDESCRIPTION:Quarterly board review.\r\nLOCATION:Room A\r\nCATEGORIES:Governance\r\nDTSTART;TZID=Europe/London:20260401T100000\r\nDTEND;TZID=Europe/London:20260401T113000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/2.ics\n \n \n \"a33ed4025fd8b61d\"\n ...(truncated)", "body_bytes": 3918, "body_truncated": true}} +{"ts": "2026-03-30T14:25:01+00:00", "client": "127.0.0.1", "method": "PROPFIND", "path": "/caldav/calendars/public/", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/xml; charset=utf-8", "Content-Length": "144", "Depth": "1", "Origin": "http://localhost:8080", "DNT": "1", "Authorization": "***", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "\n", "body_bytes": 144, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:25:01 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "5509"}, "body": "\n\n\n /caldav/calendars/public/\n \n \n Public Calendar\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/1.ics\n \n \n text/calendar; charset=utf-8\"34af04b9ccbadb9d\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/2.ics\n \n \n text/calendar; charset=utf-8\"a33ed4025fd8b61d\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/3.ics\n \n \n ...(truncated)", "body_bytes": 3919, "body_truncated": true}} +{"ts": "2026-03-30T14:25:26+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/ce2caf8e-6197-40a3-9c7b-e6eae7041903.ics", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "14137", "If-Match": "\"05a6e14b3e8da6a4\"", "Origin": "http://localhost:8080", "DNT": "1", "Authorization": "***", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "BEGIN:VCALENDAR\r\nPRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN\r\nVERSION:2.0\r\nBEGIN:VTIMEZONE\r\nTZID:Europe/London\r\nX-TZINFO:Europe/London[2025b]\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:-000115\r\nTZNAME:Europe/London(STD)\r\nDTSTART:18471201T000000\r\nRDATE:18471201T000000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19160521T020000\r\nRDATE:19160521T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19161001T030000\r\nRDATE:19161001T030000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19170408T020000\r\nRDATE:19170408T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19170917T030000\r\nRDATE:19170917T030000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19180324T020000\r\nRDATE:19180324T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19180930T030000\r\nRRULE:FREQ=YEARLY;BYMONTH=9;BYDAY=-1MO;UNTIL=19190929 ...(truncated)", "body_bytes": 4097, "body_truncated": true}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:25:26 GMT", "ETag": "\"92da99cdae305c8c\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T14:25:26+00:00", "client": "127.0.0.1", "method": "REPORT", "path": "/caldav/calendars/public/", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/xml; charset=utf-8", "Content-Length": "266", "Depth": "1", "Origin": "http://localhost:8080", "DNT": "1", "Authorization": "***", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "\n/caldav/calendars/public/ce2caf8e-6197-40a3-9c7b-e6eae7041903.ics", "body_bytes": 266, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:25:26 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "13961"}, "body": "\n\n\n /caldav/calendars/public/1.ics\n \n \n \"34af04b9ccbadb9d\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-001@calendar-wp-plugin\r\nDTSTAMP:20260330T142526Z\r\nSUMMARY:Board Meeting\r\nDESCRIPTION:Quarterly board review.\r\nLOCATION:Room A\r\nCATEGORIES:Governance\r\nDTSTART;TZID=Europe/London:20260401T100000\r\nDTEND;TZID=Europe/London:20260401T113000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/2.ics\n \n \n \"a33ed4025fd8b61d\"\n ...(truncated)", "body_bytes": 3918, "body_truncated": true}} +{"ts": "2026-03-30T14:25:35+00:00", "client": "127.0.0.1", "method": "PROPFIND", "path": "/caldav/calendars/public/", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/xml; charset=utf-8", "Content-Length": "144", "Depth": "1", "Origin": "http://localhost:8080", "DNT": "1", "Authorization": "***", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "\n", "body_bytes": 144, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:25:35 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "5509"}, "body": "\n\n\n /caldav/calendars/public/\n \n \n Public Calendar\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/1.ics\n \n \n text/calendar; charset=utf-8\"34af04b9ccbadb9d\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/2.ics\n \n \n text/calendar; charset=utf-8\"a33ed4025fd8b61d\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/3.ics\n \n \n ...(truncated)", "body_bytes": 3919, "body_truncated": true}} +{"ts": "2026-03-30T14:25:46+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/caldav/calendars/public/ce2caf8e-6197-40a3-9c7b-e6eae7041903.ics", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "If-Match": "\"92da99cdae305c8c\"", "Origin": "http://localhost:8080", "DNT": "1", "Authorization": "***", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:25:46 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T14:26:01+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/7.ics", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "14137", "If-Match": "\"279b10d3b419d6e8\"", "Origin": "http://localhost:8080", "DNT": "1", "Authorization": "***", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "BEGIN:VCALENDAR\r\nPRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN\r\nVERSION:2.0\r\nBEGIN:VTIMEZONE\r\nTZID:Europe/London\r\nX-TZINFO:Europe/London[2025b]\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:-000115\r\nTZNAME:Europe/London(STD)\r\nDTSTART:18471201T000000\r\nRDATE:18471201T000000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19160521T020000\r\nRDATE:19160521T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19161001T030000\r\nRDATE:19161001T030000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19170408T020000\r\nRDATE:19170408T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19170917T030000\r\nRDATE:19170917T030000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19180324T020000\r\nRDATE:19180324T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19180930T030000\r\nRRULE:FREQ=YEARLY;BYMONTH=9;BYDAY=-1MO;UNTIL=19190929 ...(truncated)", "body_bytes": 4097, "body_truncated": true}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:26:01 GMT", "ETag": "\"37b3e390499adf51\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T14:26:01+00:00", "client": "127.0.0.1", "method": "REPORT", "path": "/caldav/calendars/public/", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/xml; charset=utf-8", "Content-Length": "231", "Depth": "1", "Origin": "http://localhost:8080", "DNT": "1", "Authorization": "***", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "\n/caldav/calendars/public/7.ics", "body_bytes": 231, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:26:01 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "13055"}, "body": "\n\n\n /caldav/calendars/public/1.ics\n \n \n \"34af04b9ccbadb9d\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-001@calendar-wp-plugin\r\nDTSTAMP:20260330T142601Z\r\nSUMMARY:Board Meeting\r\nDESCRIPTION:Quarterly board review.\r\nLOCATION:Room A\r\nCATEGORIES:Governance\r\nDTSTART;TZID=Europe/London:20260401T100000\r\nDTEND;TZID=Europe/London:20260401T113000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/2.ics\n \n \n \"a33ed4025fd8b61d\"\n ...(truncated)", "body_bytes": 3918, "body_truncated": true}} +{"ts": "2026-03-30T14:26:11+00:00", "client": "127.0.0.1", "method": "PROPFIND", "path": "/caldav/calendars/public/", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/xml; charset=utf-8", "Content-Length": "144", "Depth": "1", "Origin": "http://localhost:8080", "DNT": "1", "Authorization": "***", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "\n", "body_bytes": 144, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:26:11 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "5168"}, "body": "\n\n\n /caldav/calendars/public/\n \n \n Public Calendar\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/1.ics\n \n \n text/calendar; charset=utf-8\"34af04b9ccbadb9d\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/2.ics\n \n \n text/calendar; charset=utf-8\"a33ed4025fd8b61d\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/3.ics\n \n \n ...(truncated)", "body_bytes": 3919, "body_truncated": true}} +{"ts": "2026-03-30T14:26:23+00:00", "client": "127.0.0.1", "method": "PROPFIND", "path": "/caldav/calendars/public/", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/xml; charset=utf-8", "Content-Length": "144", "Depth": "1", "Origin": "http://localhost:8080", "DNT": "1", "Authorization": "***", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "\n", "body_bytes": 144, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:26:23 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "5168"}, "body": "\n\n\n /caldav/calendars/public/\n \n \n Public Calendar\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/1.ics\n \n \n text/calendar; charset=utf-8\"34af04b9ccbadb9d\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/2.ics\n \n \n text/calendar; charset=utf-8\"a33ed4025fd8b61d\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/3.ics\n \n \n ...(truncated)", "body_bytes": 3919, "body_truncated": true}} +{"ts": "2026-03-30T14:26:38+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/4.ics", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "14108", "If-Match": "\"f4b1379e60ebbe47\"", "Origin": "http://localhost:8080", "DNT": "1", "Authorization": "***", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "BEGIN:VCALENDAR\r\nPRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN\r\nVERSION:2.0\r\nBEGIN:VTIMEZONE\r\nTZID:Europe/London\r\nX-TZINFO:Europe/London[2025b]\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:-000115\r\nTZNAME:Europe/London(STD)\r\nDTSTART:18471201T000000\r\nRDATE:18471201T000000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19160521T020000\r\nRDATE:19160521T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19161001T030000\r\nRDATE:19161001T030000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19170408T020000\r\nRDATE:19170408T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19170917T030000\r\nRDATE:19170917T030000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19180324T020000\r\nRDATE:19180324T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19180930T030000\r\nRRULE:FREQ=YEARLY;BYMONTH=9;BYDAY=-1MO;UNTIL=19190929 ...(truncated)", "body_bytes": 4097, "body_truncated": true}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:26:38 GMT", "ETag": "\"13292670bde364d3\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T14:26:38+00:00", "client": "127.0.0.1", "method": "REPORT", "path": "/caldav/calendars/public/", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/xml; charset=utf-8", "Content-Length": "231", "Depth": "1", "Origin": "http://localhost:8080", "DNT": "1", "Authorization": "***", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "\n/caldav/calendars/public/4.ics", "body_bytes": 231, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:26:38 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "13098"}, "body": "\n\n\n /caldav/calendars/public/1.ics\n \n \n \"34af04b9ccbadb9d\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-001@calendar-wp-plugin\r\nDTSTAMP:20260330T142638Z\r\nSUMMARY:Board Meeting\r\nDESCRIPTION:Quarterly board review.\r\nLOCATION:Room A\r\nCATEGORIES:Governance\r\nDTSTART;TZID=Europe/London:20260401T100000\r\nDTEND;TZID=Europe/London:20260401T113000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/2.ics\n \n \n \"a33ed4025fd8b61d\"\n ...(truncated)", "body_bytes": 3918, "body_truncated": true}} +{"ts": "2026-03-30T14:26:58+00:00", "client": "127.0.0.1", "method": "PROPFIND", "path": "/caldav/calendars/public/", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/xml; charset=utf-8", "Content-Length": "144", "Depth": "1", "Origin": "http://localhost:8080", "DNT": "1", "Authorization": "***", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "\n", "body_bytes": 144, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:26:58 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "5168"}, "body": "\n\n\n /caldav/calendars/public/\n \n \n Public Calendar\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/1.ics\n \n \n text/calendar; charset=utf-8\"34af04b9ccbadb9d\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/2.ics\n \n \n text/calendar; charset=utf-8\"a33ed4025fd8b61d\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/3.ics\n \n \n ...(truncated)", "body_bytes": 3919, "body_truncated": true}} +{"ts": "2026-03-30T14:27:17+00:00", "client": "127.0.0.1", "method": "PROPFIND", "path": "/caldav/calendars/public/", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/xml; charset=utf-8", "Content-Length": "144", "Depth": "1", "Origin": "http://localhost:8080", "DNT": "1", "Authorization": "***", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "\n", "body_bytes": 144, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:27:17 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "5168"}, "body": "\n\n\n /caldav/calendars/public/\n \n \n Public Calendar\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/1.ics\n \n \n text/calendar; charset=utf-8\"34af04b9ccbadb9d\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/2.ics\n \n \n text/calendar; charset=utf-8\"a33ed4025fd8b61d\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/3.ics\n \n \n ...(truncated)", "body_bytes": 3919, "body_truncated": true}} +{"ts": "2026-03-30T14:27:37+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/public/events?view=month&date=2026-04-01", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:148.0) Gecko/20100101 Firefox/148.0", "Accept": "*/*", "Accept-Language": "en-GB,en;q=0.9", "Accept-Encoding": "gzip, deflate, br, zstd", "Referer": "http://localhost:8080/calendar", "DNT": "1", "Sec-GPC": "1", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=0"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:27:37 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "7885"}, "body": "{\"data\": [{\"event_id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-01T10:00:00+01:00\", \"occurrence_end\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\"}, {\"event_id\": 10, \"uid\": \"fixture-ce-010@calendar-wp-plugin\", \"title\": \"Therapy Session\", \"description\": \"Used for single-occurrence delete exception tests.\", \"location\": \"Clinic\", \"category\": \"Health\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-03T14:00:00+01:00\", \"occurrence_end\": \"2026-04-03T15:00:00+01:00\", \"repeat_type\": \"weekly\"}, {\"event_id\": 3, \"uid\": \"fixture-ce-003@calendar-wp-plugin\", \"title\": \"Daily Standup\", \"description\": \"15 minute sync.\", \"location\": \"Online\", \"category\": \"Team\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-06T09:00:00+01:00\", \"occurrence_end\": \"2026-04-06T09:15:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 3, \"uid\": \"fixture-ce-003@calendar-wp-plugin\", \"title\": \"Daily Standup\", \"description\": \"15 minute sync.\", \"location\": \"Online\", \"category\": \"Team\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T14:27:41+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/public/events?view=month&date=2026-05-01", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:148.0) Gecko/20100101 Firefox/148.0", "Accept": "*/*", "Accept-Language": "en-GB,en;q=0.9", "Accept-Encoding": "gzip, deflate, br, zstd", "Referer": "http://localhost:8080/calendar", "DNT": "1", "Sec-GPC": "1", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=0"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:27:41 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "4227"}, "body": "{\"data\": [{\"event_id\": 10, \"uid\": \"fixture-ce-010@calendar-wp-plugin\", \"title\": \"Therapy Session\", \"description\": \"Used for single-occurrence delete exception tests.\", \"location\": \"Clinic\", \"category\": \"Health\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-01T14:00:00+01:00\", \"occurrence_end\": \"2026-05-01T15:00:00+01:00\", \"repeat_type\": \"weekly\"}, {\"event_id\": 2, \"uid\": \"fixture-ce-002@calendar-wp-plugin\", \"title\": \"Office Closed\", \"description\": \"Public holiday closure.\", \"location\": \"HQ\", \"category\": \"Operations\", \"all_day_event\": true, \"occurrence_start\": \"2026-05-04T00:00:00+00:00\", \"occurrence_end\": \"2026-05-05T00:00:00+00:00\", \"repeat_type\": \"none\"}, {\"event_id\": 7, \"uid\": \"fixture-ce-007@calendar-wp-plugin\", \"title\": \"Fortnightly Coaching\", \"description\": \"Coaching check-in.\", \"location\": \"Online\", \"category\": \"Training\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-05T15:00:00+01:00\", \"occurrence_end\": \"2026-05-05T16:00:00+01:00\", \"repeat_type\": \"weekly\"}, {\"event_id\": 10, \"uid\": \"fixture-ce-010@calendar-wp-plugin\", \"title\": \"Therapy Session\", \"description\": \"Used for single-occurrence delete exception tests.\", \"location\": \"Clinic\", \"category\": \"Health\", \"al", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T14:27:57+00:00", "client": "127.0.0.1", "method": "PROPFIND", "path": "/caldav/calendars/public/", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/xml; charset=utf-8", "Content-Length": "144", "Depth": "1", "Origin": "http://localhost:8080", "DNT": "1", "Authorization": "***", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "\n", "body_bytes": 144, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:27:57 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "5168"}, "body": "\n\n\n /caldav/calendars/public/\n \n \n Public Calendar\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/1.ics\n \n \n text/calendar; charset=utf-8\"34af04b9ccbadb9d\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/2.ics\n \n \n text/calendar; charset=utf-8\"a33ed4025fd8b61d\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/3.ics\n \n \n ...(truncated)", "body_bytes": 3919, "body_truncated": true}} +{"ts": "2026-03-30T14:34:54+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:34:54 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "9739"}, "body": "{\"data\": [{\"id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"start_datetime\": \"2026-04-01T10:00:00+01:00\", \"end_datetime\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"34af04b9ccbadb9d\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T14:16:53+00:00\"}, {\"id\": 2, \"uid\": \"fixture-ce-002@calendar-wp-plugin\", \"title\": \"Office Closed\", \"description\": \"Public holiday closure.\", \"location\": \"HQ\", \"category\": \"Operations\", \"all_day_event\": true, \"start_datetime\": \"2026-05-04\", \"end_datetime\": \"2026-05-05\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"a33ed", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T14:34:54+00:00", "client": "127.0.0.1", "method": "GET", "path": "/calendar.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:34:54 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"3db8720849bf27c9\"", "Content-Length": "4502"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nCALSCALE:GREGORIAN\r\nX-WR-TIMEZONE:Europe/London\r\nBEGIN:VEVENT\r\nUID:fixture-ce-001@calendar-wp-plugin\r\nDTSTAMP:20260330T143454Z\r\nSUMMARY:Board Meeting\r\nDESCRIPTION:Quarterly board review.\r\nLOCATION:Room A\r\nCATEGORIES:Governance\r\nDTSTART;TZID=Europe/London:20260401T100000\r\nDTEND;TZID=Europe/London:20260401T113000\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-002@calendar-wp-plugin\r\nDTSTAMP:20260330T143454Z\r\nSUMMARY:Office Closed\r\nDESCRIPTION:Public holiday closure.\r\nLOCATION:HQ\r\nCATEGORIES:Operations\r\nDTSTART;VALUE=DATE:20260504\r\nDTEND;VALUE=DATE:20260505\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-003@calendar-wp-plugin\r\nDTSTAMP:20260330T143454Z\r\nSUMMARY:Daily Standup\r\nDESCRIPTION:15 minute sync.\r\nLOCATION:Online\r\nCATEGORIES:Team\r\nDTSTART;TZID=Europe/London:20260406T090000\r\nDTEND;TZID=Europe/London:20260406T091500\r\nRRULE:FREQ=DAILY;COUNT=10\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-004@calendar-wp-plugin\r\nDTSTAMP:20260330T143454Z\r\nSUMMARY:Community Lunch\r\nDESCRIPTION:Weekly community lunch.\r\nLOCATION:Cafeteria\r\nCATEGORIES:Community\r\nDTSTART;TZID=Europe/London:20260408T123000\r\nDTEND;TZID=Europe/London:20260408T133000\r\n ...(truncated)", "body_bytes": 3905, "body_truncated": true}} +{"ts": "2026-03-30T14:34:54+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/1.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:34:54 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"34af04b9ccbadb9d\"", "Content-Length": "364"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-001@calendar-wp-plugin\r\nDTSTAMP:20260330T143454Z\r\nSUMMARY:Board Meeting\r\nDESCRIPTION:Quarterly board review.\r\nLOCATION:Room A\r\nCATEGORIES:Governance\r\nDTSTART;TZID=Europe/London:20260401T100000\r\nDTEND;TZID=Europe/London:20260401T113000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 364, "body_truncated": false}} +{"ts": "2026-03-30T14:34:54+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/10/occurrences/2026-04-17T14:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 409, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:34:54 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "84"}, "body": "{\"error\": {\"code\": \"conflict_error\", \"message\": \"occurrence already has exception\"}}", "body_bytes": 84, "body_truncated": false}} +{"ts": "2026-03-30T14:35:07+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:35:07 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "6585"}, "body": "{\"data\": [{\"id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"start_datetime\": \"2026-04-01T10:00:00+01:00\", \"end_datetime\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"4b16804fb6cdace4\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T14:34:59+00:00\"}, {\"id\": 2, \"uid\": \"fixture-ce-002@calendar-wp-plugin\", \"title\": \"Office Closed\", \"description\": \"Public holiday closure.\", \"location\": \"HQ\", \"category\": \"Operations\", \"all_day_event\": true, \"start_datetime\": \"2026-05-04\", \"end_datetime\": \"2026-05-05\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"2bc41", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T14:35:07+00:00", "client": "127.0.0.1", "method": "GET", "path": "/calendar.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:35:07 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"b06c8be2295443df\"", "Content-Length": "3132"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nCALSCALE:GREGORIAN\r\nX-WR-TIMEZONE:Europe/London\r\nBEGIN:VEVENT\r\nUID:fixture-ce-001@calendar-wp-plugin\r\nDTSTAMP:20260330T143507Z\r\nSUMMARY:Board Meeting\r\nDESCRIPTION:Quarterly board review.\r\nLOCATION:Room A\r\nCATEGORIES:Governance\r\nDTSTART;TZID=Europe/London:20260401T100000\r\nDTEND;TZID=Europe/London:20260401T113000\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-002@calendar-wp-plugin\r\nDTSTAMP:20260330T143507Z\r\nSUMMARY:Office Closed\r\nDESCRIPTION:Public holiday closure.\r\nLOCATION:HQ\r\nCATEGORIES:Operations\r\nDTSTART;VALUE=DATE:20260504\r\nDTEND;VALUE=DATE:20260505\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-003@calendar-wp-plugin\r\nDTSTAMP:20260330T143507Z\r\nSUMMARY:Daily Standup\r\nDESCRIPTION:15 minute sync.\r\nLOCATION:Online\r\nCATEGORIES:Team\r\nDTSTART;TZID=Europe/London:20260406T090000\r\nDTEND;TZID=Europe/London:20260406T091500\r\nRRULE:FREQ=DAILY;COUNT=10\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-004@calendar-wp-plugin\r\nDTSTAMP:20260330T143507Z\r\nSUMMARY:Community Lunch\r\nDESCRIPTION:Weekly community lunch.\r\nLOCATION:Cafeteria\r\nCATEGORIES:Community\r\nDTSTART;TZID=Europe/London:20260408T123000\r\nDTEND;TZID=Europe/London:20260408T133000\r\n ...(truncated)", "body_bytes": 3132, "body_truncated": false}} +{"ts": "2026-03-30T14:35:07+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/1.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:35:07 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"4b16804fb6cdace4\"", "Content-Length": "364"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-001@calendar-wp-plugin\r\nDTSTAMP:20260330T143507Z\r\nSUMMARY:Board Meeting\r\nDESCRIPTION:Quarterly board review.\r\nLOCATION:Room A\r\nCATEGORIES:Governance\r\nDTSTART;TZID=Europe/London:20260401T100000\r\nDTEND;TZID=Europe/London:20260401T113000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 364, "body_truncated": false}} +{"ts": "2026-03-30T14:35:07+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/10/occurrences/2026-04-17T14:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:35:07 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T14:35:07+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-vtimezone.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "454"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN\nVERSION:2.0\nBEGIN:VTIMEZONE\nTZID:Europe/London\nBEGIN:STANDARD\nDTSTART:18471201T000000\nRRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=9\nTZOFFSETFROM:+0115\nTZOFFSETTO:+0000\nTZNAME:GMT\nEND:STANDARD\nEND:VTIMEZONE\nBEGIN:VEVENT\nUID:smoke-vtimezone-parser-001\nSUMMARY:Smoke VTIMEZONE Parse\nDTSTART;TZID=Europe/London:20260423T150000\nDTEND;TZID=Europe/London:20260423T160000\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 454, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:35:07 GMT", "ETag": "\"f7e38a114c5f0aa1\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T14:35:07+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-vtimezone.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:35:07 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"f7e38a114c5f0aa1\"", "Content-Length": "288"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-vtimezone-parser-001\r\nDTSTAMP:20260330T143507Z\r\nSUMMARY:Smoke VTIMEZONE Parse\r\nDTSTART;TZID=Europe/London:20260423T150000\r\nDTEND;TZID=Europe/London:20260423T160000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 288, "body_truncated": false}} +{"ts": "2026-03-30T14:35:07+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-monthly-nth.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "261"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-monthly-nth-001\nSUMMARY:Smoke Monthly Nth\nDTSTART;TZID=Europe/London:20260402T150000\nDTEND;TZID=Europe/London:20260402T160000\nRRULE:FREQ=MONTHLY;BYDAY=SU;BYSETPOS=4\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 261, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:35:07 GMT", "ETag": "\"afd42f662e664a70\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T14:35:07+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-monthly-nth.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:35:07 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"afd42f662e664a70\"", "Content-Length": "319"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-nth-001\r\nDTSTAMP:20260330T143507Z\r\nSUMMARY:Smoke Monthly Nth\r\nDTSTART;TZID=Europe/London:20260426T150000\r\nDTEND;TZID=Europe/London:20260426T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SU;BYSETPOS=4\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 319, "body_truncated": false}} +{"ts": "2026-03-30T14:35:07+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-monthly-ordinal.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "259"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-monthly-ordinal-001\nSUMMARY:Smoke Monthly Ordinal\nDTSTART;TZID=Europe/London:20260411T150000\nDTEND;TZID=Europe/London:20260411T160000\nRRULE:FREQ=MONTHLY;BYDAY=2SA\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 259, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:35:07 GMT", "ETag": "\"ff1619de2d36d47d\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T14:35:07+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-monthly-ordinal.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:35:07 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"ff1619de2d36d47d\"", "Content-Length": "327"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-ordinal-001\r\nDTSTAMP:20260330T143507Z\r\nSUMMARY:Smoke Monthly Ordinal\r\nDTSTART;TZID=Europe/London:20260411T150000\r\nDTEND;TZID=Europe/London:20260411T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SA;BYSETPOS=2\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 327, "body_truncated": false}} +{"ts": "2026-03-30T14:35:07+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-monthly-last.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "264"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-monthly-last-001\nSUMMARY:Smoke Monthly Last\nDTSTART;TZID=Europe/London:20260425T150000\nDTEND;TZID=Europe/London:20260425T160000\nRRULE:FREQ=MONTHLY;BYDAY=SA;BYSETPOS=-1\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 264, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:35:07 GMT", "ETag": "\"3c34ae5dcf09bc94\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T14:35:07+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-monthly-last.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:35:07 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"3c34ae5dcf09bc94\"", "Content-Length": "322"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-last-001\r\nDTSTAMP:20260330T143507Z\r\nSUMMARY:Smoke Monthly Last\r\nDTSTART;TZID=Europe/London:20260425T150000\r\nDTEND;TZID=Europe/London:20260425T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SA;BYSETPOS=-1\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 322, "body_truncated": false}} +{"ts": "2026-03-30T14:35:07+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "X-WP-User": "admin", "Content-Length": "362"}, "body": "{\"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\"}", "body_bytes": 362, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:35:07 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "673"}, "body": "{\"data\": {\"id\": 15, \"uid\": \"671dfedc5209e0cbc285@calendar-wp-plugin\", \"title\": \"Smoke 4th Sunday Anchor\", \"description\": \"anchor-normalization\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"start_datetime\": \"2026-05-24T15:00:00+01:00\", \"end_datetime\": \"2026-05-24T16:00:00+01:00\", \"repeat_type\": \"monthly\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"weekday_of_month\", \"repeat_nth_day\": null, \"repeat_nth_pos\": 4, \"repeat_nth_weekday\": 0, \"repeat_range_mode\": \"no_end\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"318e71f9de064c57\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T14:35:07+00:00\"}}", "body_bytes": 673, "body_truncated": false}} +{"ts": "2026-03-30T14:35:07+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/15/occurrences/2026-05-24T15:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:35:07 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T14:35:07+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/15.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:35:07 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"5a4321ef70dcf8c4\"", "Content-Length": "420"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:671dfedc5209e0cbc285@calendar-wp-plugin\r\nDTSTAMP:20260330T143507Z\r\nSUMMARY:Smoke 4th Sunday Anchor\r\nDESCRIPTION:anchor-normalization\r\nDTSTART;TZID=Europe/London:20260524T150000\r\nDTEND;TZID=Europe/London:20260524T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SU;BYSETPOS=4\r\nEXDATE;TZID=Europe/London:20260524T150000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 420, "body_truncated": false}} +{"ts": "2026-03-30T14:35:07+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "X-WP-User": "admin", "Content-Length": "295"}, "body": "{\"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\"}", "body_bytes": 295, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:35:07 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "655"}, "body": "{\"data\": {\"id\": 16, \"uid\": \"ebaa6e153c2949c66437@calendar-wp-plugin\", \"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_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"until\", \"repeat_count\": null, \"repeat_until\": \"2026-03-19\", \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"da0528511b5553ab\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T14:35:07+00:00\"}}", "body_bytes": 655, "body_truncated": false}} +{"ts": "2026-03-30T14:35:07+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/16/occurrences/2026-03-11T11:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:35:07 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T14:35:07+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/16/occurrences?from=2026-03-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:35:07 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "5212"}, "body": "{\"data\": [{\"event_id\": 16, \"uid\": \"ebaa6e153c2949c66437@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-02T10:00:00+00:00\", \"occurrence_end\": \"2026-03-02T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"ebaa6e153c2949c66437@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-03T10:00:00+00:00\", \"occurrence_end\": \"2026-03-03T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"ebaa6e153c2949c66437@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-04T10:00:00+00:00\", \"occurrence_end\": \"2026-03-04T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"ebaa6e153c2949c66437@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-05T10:00:00+00:00\", \"occurrence_end\": \"2026-03-05T11:00:00", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T14:35:07+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/16/occurrences/2026-03-10", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:35:07 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T14:35:07+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/16/occurrences?from=2026-03-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:35:07 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "4906"}, "body": "{\"data\": [{\"event_id\": 16, \"uid\": \"ebaa6e153c2949c66437@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-02T10:00:00+00:00\", \"occurrence_end\": \"2026-03-02T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"ebaa6e153c2949c66437@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-03T10:00:00+00:00\", \"occurrence_end\": \"2026-03-03T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"ebaa6e153c2949c66437@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-04T10:00:00+00:00\", \"occurrence_end\": \"2026-03-04T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"ebaa6e153c2949c66437@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-05T10:00:00+00:00\", \"occurrence_end\": \"2026-03-05T11:00:00", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T14:35:07+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-exdate-import.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "323"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-exdate-import-001\nSUMMARY:Smoke EXDATE Import\nDTSTART;TZID=Europe/London:20260302T100000\nDTEND;TZID=Europe/London:20260302T110000\nRRULE:FREQ=DAILY;UNTIL=20260319T235959\nEXDATE;TZID=Europe/London:20260310T100000,20260311T100000\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 323, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:35:07 GMT", "ETag": "\"17595aec3f430fad\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T14:35:07+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:35:07 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "11006"}, "body": "{\"data\": [{\"id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"start_datetime\": \"2026-04-01T10:00:00+01:00\", \"end_datetime\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"4b16804fb6cdace4\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T14:34:59+00:00\"}, {\"id\": 2, \"uid\": \"fixture-ce-002@calendar-wp-plugin\", \"title\": \"Office Closed\", \"description\": \"Public holiday closure.\", \"location\": \"HQ\", \"category\": \"Operations\", \"all_day_event\": true, \"start_datetime\": \"2026-05-04\", \"end_datetime\": \"2026-05-05\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"2bc41", "body_bytes": 3927, "body_truncated": true}} +{"ts": "2026-03-30T14:35:07+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/17/occurrences?from=2026-03-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:35:07 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "4474"}, "body": "{\"data\": [{\"event_id\": 17, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-02T10:00:00+01:00\", \"occurrence_end\": \"2026-03-02T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-03T10:00:00+01:00\", \"occurrence_end\": \"2026-03-03T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-04T10:00:00+01:00\", \"occurrence_end\": \"2026-03-04T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-05T10:00:00+01:00\", \"occurrence_end\": \"2026-03-05T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE ", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T14:35:07+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-cancelled-occurrence.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "467"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-cancelled-occurrence-001\nSUMMARY:Smoke Cancelled Occurrence\nDTSTART;TZID=Europe/London:20260408T123000\nDTEND;TZID=Europe/London:20260408T133000\nRRULE:FREQ=WEEKLY\nEND:VEVENT\nBEGIN:VEVENT\nUID:smoke-cancelled-occurrence-001\nRECURRENCE-ID;TZID=Europe/London:20260506T123000\nDTSTART;TZID=Europe/London:20260506T123000\nDTEND;TZID=Europe/London:20260506T133000\nSTATUS:CANCELLED\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 467, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:35:07 GMT", "ETag": "\"9ad47bc1cfbe571b\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T14:35:07+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-cancelled-occurrence.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:35:07 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"9ad47bc1cfbe571b\"", "Content-Length": "316"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-cancelled-occurrence-001\r\nDTSTAMP:20260330T143507Z\r\nSUMMARY:Smoke Cancelled Occurrence\r\nDTSTART;TZID=Europe/London:20260408T123000\r\nDTEND;TZID=Europe/London:20260408T133000\r\nRRULE:FREQ=WEEKLY\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 316, "body_truncated": false}} +{"ts": "2026-03-30T14:35:44+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/public/events?view=week&date=2026-04-27", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:148.0) Gecko/20100101 Firefox/148.0", "Accept": "*/*", "Accept-Language": "en-GB,en;q=0.9", "Accept-Encoding": "gzip, deflate, br, zstd", "Referer": "http://localhost:8080/calendar", "DNT": "1", "Sec-GPC": "1", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=0"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:35:44 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "1345"}, "body": "{\"data\": [{\"event_id\": 4, \"uid\": \"fixture-ce-004@calendar-wp-plugin\", \"title\": \"Community Lunch\", \"description\": \"Weekly community lunch.\", \"location\": \"Cafeteria\", \"category\": \"Community\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-29T12:30:00+01:00\", \"occurrence_end\": \"2026-04-29T13:30:00+01:00\", \"repeat_type\": \"weekly\"}, {\"event_id\": 18, \"uid\": \"smoke-cancelled-occurrence-001\", \"title\": \"Smoke Cancelled Occurrence\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-29T12:30:00+01:00\", \"occurrence_end\": \"2026-04-29T13:30:00+01:00\", \"repeat_type\": \"weekly\"}, {\"event_id\": 5, \"uid\": \"fixture-ce-005@calendar-wp-plugin\", \"title\": \"Finance Close\", \"description\": \"Month-end close process.\", \"location\": \"Finance Office\", \"category\": \"Finance\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-30T17:00:00+01:00\", \"occurrence_end\": \"2026-04-30T18:00:00+01:00\", \"repeat_type\": \"monthly\"}, {\"event_id\": 10, \"uid\": \"fixture-ce-010@calendar-wp-plugin\", \"title\": \"Therapy Session\", \"description\": \"Used for single-occurrence delete exception tests.\", \"location\": \"Clinic\", \"category\": \"Health\", \"all_day_event\": false, \"occurrence_sta ...(truncated)", "body_bytes": 1345, "body_truncated": false}} +{"ts": "2026-03-30T14:35:54+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/public/events?view=day&date=2026-04-27", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:148.0) Gecko/20100101 Firefox/148.0", "Accept": "*/*", "Accept-Language": "en-GB,en;q=0.9", "Accept-Encoding": "gzip, deflate, br, zstd", "Referer": "http://localhost:8080/calendar", "DNT": "1", "Sec-GPC": "1", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=0"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:35:54 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "49"}, "body": "{\"data\": [], \"meta\": {\"count\": 0, \"view\": \"day\"}}", "body_bytes": 49, "body_truncated": false}} +{"ts": "2026-03-30T14:35:57+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/public/events?view=day&date=2026-04-28", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:148.0) Gecko/20100101 Firefox/148.0", "Accept": "*/*", "Accept-Language": "en-GB,en;q=0.9", "Accept-Encoding": "gzip, deflate, br, zstd", "Referer": "http://localhost:8080/calendar", "DNT": "1", "Sec-GPC": "1", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=0"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:35:57 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "49"}, "body": "{\"data\": [], \"meta\": {\"count\": 0, \"view\": \"day\"}}", "body_bytes": 49, "body_truncated": false}} +{"ts": "2026-03-30T14:35:57+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/public/events?view=day&date=2026-04-29", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:148.0) Gecko/20100101 Firefox/148.0", "Accept": "*/*", "Accept-Language": "en-GB,en;q=0.9", "Accept-Encoding": "gzip, deflate, br, zstd", "Referer": "http://localhost:8080/calendar", "DNT": "1", "Sec-GPC": "1", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=0"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:35:57 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "667"}, "body": "{\"data\": [{\"event_id\": 4, \"uid\": \"fixture-ce-004@calendar-wp-plugin\", \"title\": \"Community Lunch\", \"description\": \"Weekly community lunch.\", \"location\": \"Cafeteria\", \"category\": \"Community\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-29T12:30:00+01:00\", \"occurrence_end\": \"2026-04-29T13:30:00+01:00\", \"repeat_type\": \"weekly\"}, {\"event_id\": 18, \"uid\": \"smoke-cancelled-occurrence-001\", \"title\": \"Smoke Cancelled Occurrence\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-29T12:30:00+01:00\", \"occurrence_end\": \"2026-04-29T13:30:00+01:00\", \"repeat_type\": \"weekly\"}], \"meta\": {\"count\": 2, \"view\": \"day\"}}", "body_bytes": 667, "body_truncated": false}} +{"ts": "2026-03-30T14:35:58+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/public/events?view=day&date=2026-04-30", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:148.0) Gecko/20100101 Firefox/148.0", "Accept": "*/*", "Accept-Language": "en-GB,en;q=0.9", "Accept-Encoding": "gzip, deflate, br, zstd", "Referer": "http://localhost:8080/calendar", "DNT": "1", "Sec-GPC": "1", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=0"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:35:58 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "376"}, "body": "{\"data\": [{\"event_id\": 5, \"uid\": \"fixture-ce-005@calendar-wp-plugin\", \"title\": \"Finance Close\", \"description\": \"Month-end close process.\", \"location\": \"Finance Office\", \"category\": \"Finance\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-30T17:00:00+01:00\", \"occurrence_end\": \"2026-04-30T18:00:00+01:00\", \"repeat_type\": \"monthly\"}], \"meta\": {\"count\": 1, \"view\": \"day\"}}", "body_bytes": 376, "body_truncated": false}} +{"ts": "2026-03-30T14:35:59+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/public/events?view=day&date=2026-05-01", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:148.0) Gecko/20100101 Firefox/148.0", "Accept": "*/*", "Accept-Language": "en-GB,en;q=0.9", "Accept-Encoding": "gzip, deflate, br, zstd", "Referer": "http://localhost:8080/calendar", "DNT": "1", "Sec-GPC": "1", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=0"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:35:59 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "395"}, "body": "{\"data\": [{\"event_id\": 10, \"uid\": \"fixture-ce-010@calendar-wp-plugin\", \"title\": \"Therapy Session\", \"description\": \"Used for single-occurrence delete exception tests.\", \"location\": \"Clinic\", \"category\": \"Health\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-01T14:00:00+01:00\", \"occurrence_end\": \"2026-05-01T15:00:00+01:00\", \"repeat_type\": \"weekly\"}], \"meta\": {\"count\": 1, \"view\": \"day\"}}", "body_bytes": 395, "body_truncated": false}} +{"ts": "2026-03-30T14:36:00+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/public/events?view=day&date=2026-05-02", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:148.0) Gecko/20100101 Firefox/148.0", "Accept": "*/*", "Accept-Language": "en-GB,en;q=0.9", "Accept-Encoding": "gzip, deflate, br, zstd", "Referer": "http://localhost:8080/calendar", "DNT": "1", "Sec-GPC": "1", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=0"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:36:00 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "49"}, "body": "{\"data\": [], \"meta\": {\"count\": 0, \"view\": \"day\"}}", "body_bytes": 49, "body_truncated": false}} +{"ts": "2026-03-30T14:36:02+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/public/events?view=day&date=2026-05-01", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:148.0) Gecko/20100101 Firefox/148.0", "Accept": "*/*", "Accept-Language": "en-GB,en;q=0.9", "Accept-Encoding": "gzip, deflate, br, zstd", "Referer": "http://localhost:8080/calendar", "DNT": "1", "Sec-GPC": "1", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=0"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:36:02 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "395"}, "body": "{\"data\": [{\"event_id\": 10, \"uid\": \"fixture-ce-010@calendar-wp-plugin\", \"title\": \"Therapy Session\", \"description\": \"Used for single-occurrence delete exception tests.\", \"location\": \"Clinic\", \"category\": \"Health\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-01T14:00:00+01:00\", \"occurrence_end\": \"2026-05-01T15:00:00+01:00\", \"repeat_type\": \"weekly\"}], \"meta\": {\"count\": 1, \"view\": \"day\"}}", "body_bytes": 395, "body_truncated": false}} +{"ts": "2026-03-30T14:36:02+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/public/events?view=day&date=2026-04-30", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:148.0) Gecko/20100101 Firefox/148.0", "Accept": "*/*", "Accept-Language": "en-GB,en;q=0.9", "Accept-Encoding": "gzip, deflate, br, zstd", "Referer": "http://localhost:8080/calendar", "DNT": "1", "Sec-GPC": "1", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=0"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:36:02 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "376"}, "body": "{\"data\": [{\"event_id\": 5, \"uid\": \"fixture-ce-005@calendar-wp-plugin\", \"title\": \"Finance Close\", \"description\": \"Month-end close process.\", \"location\": \"Finance Office\", \"category\": \"Finance\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-30T17:00:00+01:00\", \"occurrence_end\": \"2026-04-30T18:00:00+01:00\", \"repeat_type\": \"monthly\"}], \"meta\": {\"count\": 1, \"view\": \"day\"}}", "body_bytes": 376, "body_truncated": false}} +{"ts": "2026-03-30T14:36:20+00:00", "client": "127.0.0.1", "method": "GET", "path": "/calendar", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:148.0) Gecko/20100101 Firefox/148.0", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", "Accept-Language": "en-GB,en;q=0.9", "Accept-Encoding": "gzip, deflate, br, zstd", "Referer": "http://localhost:8080/wp-admin/admin.php?page=calendar-users&as=admin", "DNT": "1", "Sec-GPC": "1", "Connection": "keep-alive", "Upgrade-Insecure-Requests": "1", "Sec-Fetch-Dest": "document", "Sec-Fetch-Mode": "navigate", "Sec-Fetch-Site": "same-origin", "Sec-Fetch-User": "?1", "Priority": "u=0, i", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:36:20 GMT", "Content-Type": "text/html; charset=utf-8", "Content-Length": "43446"}, "body": "[omitted html payload: 3934 bytes]", "body_bytes": 3934, "body_truncated": true}} +{"ts": "2026-03-30T14:36:20+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/public/events?view=month&date=2026-03-30", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:148.0) Gecko/20100101 Firefox/148.0", "Accept": "*/*", "Accept-Language": "en-GB,en;q=0.9", "Accept-Encoding": "gzip, deflate, br, zstd", "Referer": "http://localhost:8080/calendar", "DNT": "1", "Sec-GPC": "1", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:36:20 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "9410"}, "body": "{\"data\": [{\"event_id\": 16, \"uid\": \"ebaa6e153c2949c66437@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-02T10:00:00+00:00\", \"occurrence_end\": \"2026-03-02T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-02T10:00:00+01:00\", \"occurrence_end\": \"2026-03-02T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"ebaa6e153c2949c66437@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-03T10:00:00+00:00\", \"occurrence_end\": \"2026-03-03T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-03T10:00:00+01:00\", \"occurrence_end\": \"2026-03-03T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"ui", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T14:36:20+00:00", "client": "127.0.0.1", "method": "GET", "path": "/favicon.ico", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:148.0) Gecko/20100101 Firefox/148.0", "Accept": "image/avif,image/jxl,image/webp,image/png,image/svg+xml,image/*;q=0.8,*/*;q=0.5", "Accept-Language": "en-GB,en;q=0.9", "Accept-Encoding": "gzip, deflate, br, zstd", "DNT": "1", "Sec-GPC": "1", "Connection": "keep-alive", "Referer": "http://localhost:8080/calendar", "Sec-Fetch-Dest": "image", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=6", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 404, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:36:20 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "62"}, "body": "{\"error\": {\"code\": \"not_found\", \"message\": \"Route not found\"}}", "body_bytes": 62, "body_truncated": false}} +{"ts": "2026-03-30T14:36:24+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/public/events?view=day&date=2026-03-30", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:148.0) Gecko/20100101 Firefox/148.0", "Accept": "*/*", "Accept-Language": "en-GB,en;q=0.9", "Accept-Encoding": "gzip, deflate, br, zstd", "Referer": "http://localhost:8080/calendar", "DNT": "1", "Sec-GPC": "1", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=0"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:36:24 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "49"}, "body": "{\"data\": [], \"meta\": {\"count\": 0, \"view\": \"day\"}}", "body_bytes": 49, "body_truncated": false}} +{"ts": "2026-03-30T14:36:25+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/public/events?view=list&date=2026-03-30", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:148.0) Gecko/20100101 Firefox/148.0", "Accept": "*/*", "Accept-Language": "en-GB,en;q=0.9", "Accept-Encoding": "gzip, deflate, br, zstd", "Referer": "http://localhost:8080/calendar", "DNT": "1", "Sec-GPC": "1", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=0"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:36:25 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "28439"}, "body": "{\"data\": [{\"event_id\": 16, \"uid\": \"ebaa6e153c2949c66437@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-02T10:00:00+00:00\", \"occurrence_end\": \"2026-03-02T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-02T10:00:00+01:00\", \"occurrence_end\": \"2026-03-02T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"ebaa6e153c2949c66437@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-03T10:00:00+00:00\", \"occurrence_end\": \"2026-03-03T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-03T10:00:00+01:00\", \"occurrence_end\": \"2026-03-03T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"ui", "body_bytes": 3927, "body_truncated": true}} +{"ts": "2026-03-30T14:36:30+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/public/events?view=day&date=2026-03-30", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:148.0) Gecko/20100101 Firefox/148.0", "Accept": "*/*", "Accept-Language": "en-GB,en;q=0.9", "Accept-Encoding": "gzip, deflate, br, zstd", "Referer": "http://localhost:8080/calendar", "DNT": "1", "Sec-GPC": "1", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=0"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:36:30 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "49"}, "body": "{\"data\": [], \"meta\": {\"count\": 0, \"view\": \"day\"}}", "body_bytes": 49, "body_truncated": false}} +{"ts": "2026-03-30T14:36:31+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/public/events?view=day&date=2026-03-31", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:148.0) Gecko/20100101 Firefox/148.0", "Accept": "*/*", "Accept-Language": "en-GB,en;q=0.9", "Accept-Encoding": "gzip, deflate, br, zstd", "Referer": "http://localhost:8080/calendar", "DNT": "1", "Sec-GPC": "1", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=0"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:36:31 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "49"}, "body": "{\"data\": [], \"meta\": {\"count\": 0, \"view\": \"day\"}}", "body_bytes": 49, "body_truncated": false}} +{"ts": "2026-03-30T14:36:32+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/public/events?view=day&date=2026-04-01", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:148.0) Gecko/20100101 Firefox/148.0", "Accept": "*/*", "Accept-Language": "en-GB,en;q=0.9", "Accept-Encoding": "gzip, deflate, br, zstd", "Referer": "http://localhost:8080/calendar", "DNT": "1", "Sec-GPC": "1", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=0"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:36:32 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "367"}, "body": "{\"data\": [{\"event_id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-01T10:00:00+01:00\", \"occurrence_end\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\"}], \"meta\": {\"count\": 1, \"view\": \"day\"}}", "body_bytes": 367, "body_truncated": false}} +{"ts": "2026-03-30T14:36:32+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/public/events?view=day&date=2026-04-02", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:148.0) Gecko/20100101 Firefox/148.0", "Accept": "*/*", "Accept-Language": "en-GB,en;q=0.9", "Accept-Encoding": "gzip, deflate, br, zstd", "Referer": "http://localhost:8080/calendar", "DNT": "1", "Sec-GPC": "1", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=0"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:36:32 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "49"}, "body": "{\"data\": [], \"meta\": {\"count\": 0, \"view\": \"day\"}}", "body_bytes": 49, "body_truncated": false}} +{"ts": "2026-03-30T14:36:33+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/public/events?view=day&date=2026-04-01", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:148.0) Gecko/20100101 Firefox/148.0", "Accept": "*/*", "Accept-Language": "en-GB,en;q=0.9", "Accept-Encoding": "gzip, deflate, br, zstd", "Referer": "http://localhost:8080/calendar", "DNT": "1", "Sec-GPC": "1", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=0"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:36:33 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "367"}, "body": "{\"data\": [{\"event_id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-01T10:00:00+01:00\", \"occurrence_end\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\"}], \"meta\": {\"count\": 1, \"view\": \"day\"}}", "body_bytes": 367, "body_truncated": false}} +{"ts": "2026-03-30T14:36:42+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/public/events?view=day&date=2026-04-02", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:148.0) Gecko/20100101 Firefox/148.0", "Accept": "*/*", "Accept-Language": "en-GB,en;q=0.9", "Accept-Encoding": "gzip, deflate, br, zstd", "Referer": "http://localhost:8080/calendar", "DNT": "1", "Sec-GPC": "1", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=0"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:36:42 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "49"}, "body": "{\"data\": [], \"meta\": {\"count\": 0, \"view\": \"day\"}}", "body_bytes": 49, "body_truncated": false}} +{"ts": "2026-03-30T14:36:42+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/public/events?view=day&date=2026-04-03", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:148.0) Gecko/20100101 Firefox/148.0", "Accept": "*/*", "Accept-Language": "en-GB,en;q=0.9", "Accept-Encoding": "gzip, deflate, br, zstd", "Referer": "http://localhost:8080/calendar", "DNT": "1", "Sec-GPC": "1", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=0"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:36:42 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "395"}, "body": "{\"data\": [{\"event_id\": 10, \"uid\": \"fixture-ce-010@calendar-wp-plugin\", \"title\": \"Therapy Session\", \"description\": \"Used for single-occurrence delete exception tests.\", \"location\": \"Clinic\", \"category\": \"Health\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-03T14:00:00+01:00\", \"occurrence_end\": \"2026-04-03T15:00:00+01:00\", \"repeat_type\": \"weekly\"}], \"meta\": {\"count\": 1, \"view\": \"day\"}}", "body_bytes": 395, "body_truncated": false}} +{"ts": "2026-03-30T14:36:42+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/public/events?view=day&date=2026-04-04", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:148.0) Gecko/20100101 Firefox/148.0", "Accept": "*/*", "Accept-Language": "en-GB,en;q=0.9", "Accept-Encoding": "gzip, deflate, br, zstd", "Referer": "http://localhost:8080/calendar", "DNT": "1", "Sec-GPC": "1", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=0"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:36:42 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "49"}, "body": "{\"data\": [], \"meta\": {\"count\": 0, \"view\": \"day\"}}", "body_bytes": 49, "body_truncated": false}} +{"ts": "2026-03-30T14:36:43+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/public/events?view=day&date=2026-04-05", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:148.0) Gecko/20100101 Firefox/148.0", "Accept": "*/*", "Accept-Language": "en-GB,en;q=0.9", "Accept-Encoding": "gzip, deflate, br, zstd", "Referer": "http://localhost:8080/calendar", "DNT": "1", "Sec-GPC": "1", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=0"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:36:43 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "49"}, "body": "{\"data\": [], \"meta\": {\"count\": 0, \"view\": \"day\"}}", "body_bytes": 49, "body_truncated": false}} +{"ts": "2026-03-30T14:36:44+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/public/events?view=day&date=2026-04-06", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:148.0) Gecko/20100101 Firefox/148.0", "Accept": "*/*", "Accept-Language": "en-GB,en;q=0.9", "Accept-Encoding": "gzip, deflate, br, zstd", "Referer": "http://localhost:8080/calendar", "DNT": "1", "Sec-GPC": "1", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=0"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:36:44 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "354"}, "body": "{\"data\": [{\"event_id\": 3, \"uid\": \"fixture-ce-003@calendar-wp-plugin\", \"title\": \"Daily Standup\", \"description\": \"15 minute sync.\", \"location\": \"Online\", \"category\": \"Team\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-06T09:00:00+01:00\", \"occurrence_end\": \"2026-04-06T09:15:00+01:00\", \"repeat_type\": \"daily\"}], \"meta\": {\"count\": 1, \"view\": \"day\"}}", "body_bytes": 354, "body_truncated": false}} +{"ts": "2026-03-30T14:37:16+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:37:16 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "6585"}, "body": "{\"data\": [{\"id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"start_datetime\": \"2026-04-01T10:00:00+01:00\", \"end_datetime\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"d67b5ff89b3e11f4\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T14:37:14+00:00\"}, {\"id\": 2, \"uid\": \"fixture-ce-002@calendar-wp-plugin\", \"title\": \"Office Closed\", \"description\": \"Public holiday closure.\", \"location\": \"HQ\", \"category\": \"Operations\", \"all_day_event\": true, \"start_datetime\": \"2026-05-04\", \"end_datetime\": \"2026-05-05\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"dd76a", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T14:37:16+00:00", "client": "127.0.0.1", "method": "GET", "path": "/calendar.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:37:16 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"6e86a3a3cb79f222\"", "Content-Length": "3132"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nCALSCALE:GREGORIAN\r\nX-WR-TIMEZONE:Europe/London\r\nBEGIN:VEVENT\r\nUID:fixture-ce-001@calendar-wp-plugin\r\nDTSTAMP:20260330T143716Z\r\nSUMMARY:Board Meeting\r\nDESCRIPTION:Quarterly board review.\r\nLOCATION:Room A\r\nCATEGORIES:Governance\r\nDTSTART;TZID=Europe/London:20260401T100000\r\nDTEND;TZID=Europe/London:20260401T113000\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-002@calendar-wp-plugin\r\nDTSTAMP:20260330T143716Z\r\nSUMMARY:Office Closed\r\nDESCRIPTION:Public holiday closure.\r\nLOCATION:HQ\r\nCATEGORIES:Operations\r\nDTSTART;VALUE=DATE:20260504\r\nDTEND;VALUE=DATE:20260505\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-003@calendar-wp-plugin\r\nDTSTAMP:20260330T143716Z\r\nSUMMARY:Daily Standup\r\nDESCRIPTION:15 minute sync.\r\nLOCATION:Online\r\nCATEGORIES:Team\r\nDTSTART;TZID=Europe/London:20260406T090000\r\nDTEND;TZID=Europe/London:20260406T091500\r\nRRULE:FREQ=DAILY;COUNT=10\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-004@calendar-wp-plugin\r\nDTSTAMP:20260330T143716Z\r\nSUMMARY:Community Lunch\r\nDESCRIPTION:Weekly community lunch.\r\nLOCATION:Cafeteria\r\nCATEGORIES:Community\r\nDTSTART;TZID=Europe/London:20260408T123000\r\nDTEND;TZID=Europe/London:20260408T133000\r\n ...(truncated)", "body_bytes": 3132, "body_truncated": false}} +{"ts": "2026-03-30T14:37:16+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/1.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:37:16 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"d67b5ff89b3e11f4\"", "Content-Length": "364"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-001@calendar-wp-plugin\r\nDTSTAMP:20260330T143716Z\r\nSUMMARY:Board Meeting\r\nDESCRIPTION:Quarterly board review.\r\nLOCATION:Room A\r\nCATEGORIES:Governance\r\nDTSTART;TZID=Europe/London:20260401T100000\r\nDTEND;TZID=Europe/London:20260401T113000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 364, "body_truncated": false}} +{"ts": "2026-03-30T14:37:16+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/10/occurrences/2026-04-17T14:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:37:16 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T14:37:16+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-vtimezone.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "454"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN\nVERSION:2.0\nBEGIN:VTIMEZONE\nTZID:Europe/London\nBEGIN:STANDARD\nDTSTART:18471201T000000\nRRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=9\nTZOFFSETFROM:+0115\nTZOFFSETTO:+0000\nTZNAME:GMT\nEND:STANDARD\nEND:VTIMEZONE\nBEGIN:VEVENT\nUID:smoke-vtimezone-parser-001\nSUMMARY:Smoke VTIMEZONE Parse\nDTSTART;TZID=Europe/London:20260423T150000\nDTEND;TZID=Europe/London:20260423T160000\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 454, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:37:16 GMT", "ETag": "\"cb820e20ce5f05f9\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T14:37:16+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-vtimezone.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:37:16 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"cb820e20ce5f05f9\"", "Content-Length": "288"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-vtimezone-parser-001\r\nDTSTAMP:20260330T143716Z\r\nSUMMARY:Smoke VTIMEZONE Parse\r\nDTSTART;TZID=Europe/London:20260423T150000\r\nDTEND;TZID=Europe/London:20260423T160000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 288, "body_truncated": false}} +{"ts": "2026-03-30T14:37:16+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-monthly-nth.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "261"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-monthly-nth-001\nSUMMARY:Smoke Monthly Nth\nDTSTART;TZID=Europe/London:20260402T150000\nDTEND;TZID=Europe/London:20260402T160000\nRRULE:FREQ=MONTHLY;BYDAY=SU;BYSETPOS=4\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 261, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:37:16 GMT", "ETag": "\"fc237d6ea5bab15b\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T14:37:16+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-monthly-nth.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:37:16 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"fc237d6ea5bab15b\"", "Content-Length": "319"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-nth-001\r\nDTSTAMP:20260330T143716Z\r\nSUMMARY:Smoke Monthly Nth\r\nDTSTART;TZID=Europe/London:20260426T150000\r\nDTEND;TZID=Europe/London:20260426T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SU;BYSETPOS=4\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 319, "body_truncated": false}} +{"ts": "2026-03-30T14:37:16+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-monthly-ordinal.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "259"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-monthly-ordinal-001\nSUMMARY:Smoke Monthly Ordinal\nDTSTART;TZID=Europe/London:20260411T150000\nDTEND;TZID=Europe/London:20260411T160000\nRRULE:FREQ=MONTHLY;BYDAY=2SA\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 259, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:37:16 GMT", "ETag": "\"a04576a14aacbb44\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T14:37:16+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-monthly-ordinal.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:37:16 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"a04576a14aacbb44\"", "Content-Length": "327"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-ordinal-001\r\nDTSTAMP:20260330T143716Z\r\nSUMMARY:Smoke Monthly Ordinal\r\nDTSTART;TZID=Europe/London:20260411T150000\r\nDTEND;TZID=Europe/London:20260411T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SA;BYSETPOS=2\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 327, "body_truncated": false}} +{"ts": "2026-03-30T14:37:16+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-monthly-last.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "264"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-monthly-last-001\nSUMMARY:Smoke Monthly Last\nDTSTART;TZID=Europe/London:20260425T150000\nDTEND;TZID=Europe/London:20260425T160000\nRRULE:FREQ=MONTHLY;BYDAY=SA;BYSETPOS=-1\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 264, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:37:16 GMT", "ETag": "\"d4593cbbeb887b80\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T14:37:16+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-monthly-last.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:37:16 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"d4593cbbeb887b80\"", "Content-Length": "322"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-last-001\r\nDTSTAMP:20260330T143716Z\r\nSUMMARY:Smoke Monthly Last\r\nDTSTART;TZID=Europe/London:20260425T150000\r\nDTEND;TZID=Europe/London:20260425T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SA;BYSETPOS=-1\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 322, "body_truncated": false}} +{"ts": "2026-03-30T14:37:16+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "X-WP-User": "admin", "Content-Length": "362"}, "body": "{\"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\"}", "body_bytes": 362, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:37:16 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "673"}, "body": "{\"data\": {\"id\": 15, \"uid\": \"35ef9e3fa5fc95e6feeb@calendar-wp-plugin\", \"title\": \"Smoke 4th Sunday Anchor\", \"description\": \"anchor-normalization\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"start_datetime\": \"2026-05-24T15:00:00+01:00\", \"end_datetime\": \"2026-05-24T16:00:00+01:00\", \"repeat_type\": \"monthly\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"weekday_of_month\", \"repeat_nth_day\": null, \"repeat_nth_pos\": 4, \"repeat_nth_weekday\": 0, \"repeat_range_mode\": \"no_end\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"88afe21d04a4e934\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T14:37:16+00:00\"}}", "body_bytes": 673, "body_truncated": false}} +{"ts": "2026-03-30T14:37:16+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/15/occurrences/2026-05-24T15:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:37:16 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T14:37:16+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/15.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:37:16 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"2c338e3049044bb6\"", "Content-Length": "420"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:35ef9e3fa5fc95e6feeb@calendar-wp-plugin\r\nDTSTAMP:20260330T143716Z\r\nSUMMARY:Smoke 4th Sunday Anchor\r\nDESCRIPTION:anchor-normalization\r\nDTSTART;TZID=Europe/London:20260524T150000\r\nDTEND;TZID=Europe/London:20260524T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SU;BYSETPOS=4\r\nEXDATE;TZID=Europe/London:20260524T150000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 420, "body_truncated": false}} +{"ts": "2026-03-30T14:37:16+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "X-WP-User": "admin", "Content-Length": "295"}, "body": "{\"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\"}", "body_bytes": 295, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:37:16 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "655"}, "body": "{\"data\": {\"id\": 16, \"uid\": \"05e54c633fc9404d7909@calendar-wp-plugin\", \"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_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"until\", \"repeat_count\": null, \"repeat_until\": \"2026-03-19\", \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"870fa79824125dc3\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T14:37:16+00:00\"}}", "body_bytes": 655, "body_truncated": false}} +{"ts": "2026-03-30T14:37:16+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/16/occurrences/2026-03-11T11:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:37:16 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T14:37:16+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/16/occurrences?from=2026-03-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:37:16 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "5212"}, "body": "{\"data\": [{\"event_id\": 16, \"uid\": \"05e54c633fc9404d7909@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-02T10:00:00+00:00\", \"occurrence_end\": \"2026-03-02T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"05e54c633fc9404d7909@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-03T10:00:00+00:00\", \"occurrence_end\": \"2026-03-03T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"05e54c633fc9404d7909@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-04T10:00:00+00:00\", \"occurrence_end\": \"2026-03-04T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"05e54c633fc9404d7909@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-05T10:00:00+00:00\", \"occurrence_end\": \"2026-03-05T11:00:00", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T14:37:16+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/16/occurrences/2026-03-10", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:37:16 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T14:37:16+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/16/occurrences?from=2026-03-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:37:16 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "4906"}, "body": "{\"data\": [{\"event_id\": 16, \"uid\": \"05e54c633fc9404d7909@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-02T10:00:00+00:00\", \"occurrence_end\": \"2026-03-02T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"05e54c633fc9404d7909@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-03T10:00:00+00:00\", \"occurrence_end\": \"2026-03-03T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"05e54c633fc9404d7909@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-04T10:00:00+00:00\", \"occurrence_end\": \"2026-03-04T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"05e54c633fc9404d7909@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-05T10:00:00+00:00\", \"occurrence_end\": \"2026-03-05T11:00:00", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T14:37:16+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-exdate-import.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "323"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-exdate-import-001\nSUMMARY:Smoke EXDATE Import\nDTSTART;TZID=Europe/London:20260302T100000\nDTEND;TZID=Europe/London:20260302T110000\nRRULE:FREQ=DAILY;UNTIL=20260319T235959\nEXDATE;TZID=Europe/London:20260310T100000,20260311T100000\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 323, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:37:16 GMT", "ETag": "\"032ada2f4689a236\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T14:37:16+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:37:16 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "11006"}, "body": "{\"data\": [{\"id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"start_datetime\": \"2026-04-01T10:00:00+01:00\", \"end_datetime\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"d67b5ff89b3e11f4\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T14:37:14+00:00\"}, {\"id\": 2, \"uid\": \"fixture-ce-002@calendar-wp-plugin\", \"title\": \"Office Closed\", \"description\": \"Public holiday closure.\", \"location\": \"HQ\", \"category\": \"Operations\", \"all_day_event\": true, \"start_datetime\": \"2026-05-04\", \"end_datetime\": \"2026-05-05\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"dd76a", "body_bytes": 3927, "body_truncated": true}} +{"ts": "2026-03-30T14:37:16+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/17/occurrences?from=2026-03-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:37:16 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "4474"}, "body": "{\"data\": [{\"event_id\": 17, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-02T10:00:00+01:00\", \"occurrence_end\": \"2026-03-02T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-03T10:00:00+01:00\", \"occurrence_end\": \"2026-03-03T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-04T10:00:00+01:00\", \"occurrence_end\": \"2026-03-04T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-05T10:00:00+01:00\", \"occurrence_end\": \"2026-03-05T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE ", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T14:37:16+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-cancelled-occurrence.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "467"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-cancelled-occurrence-001\nSUMMARY:Smoke Cancelled Occurrence\nDTSTART;TZID=Europe/London:20260408T123000\nDTEND;TZID=Europe/London:20260408T133000\nRRULE:FREQ=WEEKLY\nEND:VEVENT\nBEGIN:VEVENT\nUID:smoke-cancelled-occurrence-001\nRECURRENCE-ID;TZID=Europe/London:20260506T123000\nDTSTART;TZID=Europe/London:20260506T123000\nDTEND;TZID=Europe/London:20260506T133000\nSTATUS:CANCELLED\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 467, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:37:16 GMT", "ETag": "\"41474f0130ac56c2\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T14:37:16+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-cancelled-occurrence.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:37:16 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"41474f0130ac56c2\"", "Content-Length": "359"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-cancelled-occurrence-001\r\nDTSTAMP:20260330T143716Z\r\nSUMMARY:Smoke Cancelled Occurrence\r\nDTSTART;TZID=Europe/London:20260408T123000\r\nDTEND;TZID=Europe/London:20260408T133000\r\nRRULE:FREQ=WEEKLY\r\nEXDATE;TZID=Europe/London:20260506T123000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 359, "body_truncated": false}} +{"ts": "2026-03-30T14:37:16+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:37:16 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "11634"}, "body": "{\"data\": [{\"id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"start_datetime\": \"2026-04-01T10:00:00+01:00\", \"end_datetime\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"d67b5ff89b3e11f4\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T14:37:14+00:00\"}, {\"id\": 2, \"uid\": \"fixture-ce-002@calendar-wp-plugin\", \"title\": \"Office Closed\", \"description\": \"Public holiday closure.\", \"location\": \"HQ\", \"category\": \"Operations\", \"all_day_event\": true, \"start_datetime\": \"2026-05-04\", \"end_datetime\": \"2026-05-05\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"dd76a", "body_bytes": 3927, "body_truncated": true}} +{"ts": "2026-03-30T14:37:16+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/18/occurrences?from=2026-05-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:37:16 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "892"}, "body": "{\"data\": [{\"event_id\": 18, \"uid\": \"smoke-cancelled-occurrence-001\", \"title\": \"Smoke Cancelled Occurrence\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-13T12:30:00+01:00\", \"occurrence_end\": \"2026-05-13T13:30:00+01:00\", \"repeat_type\": \"weekly\"}, {\"event_id\": 18, \"uid\": \"smoke-cancelled-occurrence-001\", \"title\": \"Smoke Cancelled Occurrence\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-20T12:30:00+01:00\", \"occurrence_end\": \"2026-05-20T13:30:00+01:00\", \"repeat_type\": \"weekly\"}, {\"event_id\": 18, \"uid\": \"smoke-cancelled-occurrence-001\", \"title\": \"Smoke Cancelled Occurrence\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-27T12:30:00+01:00\", \"occurrence_end\": \"2026-05-27T13:30:00+01:00\", \"repeat_type\": \"weekly\"}]}", "body_bytes": 892, "body_truncated": false}} +{"ts": "2026-03-30T14:37:16+00:00", "client": "127.0.0.1", "method": "GET", "path": "/calendar", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:37:16 GMT", "Content-Type": "text/html; charset=utf-8", "Content-Length": "43446"}, "body": "[omitted html payload: 3934 bytes]", "body_bytes": 3934, "body_truncated": true}} +{"ts": "2026-03-30T14:38:27+00:00", "client": "127.0.0.1", "method": "PROPFIND", "path": "/caldav/calendars/public/", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/xml; charset=utf-8", "Content-Length": "144", "Depth": "1", "Origin": "http://localhost:8080", "DNT": "1", "Authorization": "***", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "\n", "body_bytes": 144, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:38:27 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "3570"}, "body": "\n\n\n /caldav/calendars/public/\n \n \n Public Calendar\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/1.ics\n \n \n text/calendar; charset=utf-8\"a1c06c7625070956\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/2.ics\n \n \n text/calendar; charset=utf-8\"31413754bab11f12\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/3.ics\n \n \n ...(truncated)", "body_bytes": 3570, "body_truncated": false}} +{"ts": "2026-03-30T14:38:29+00:00", "client": "127.0.0.1", "method": "REPORT", "path": "/caldav/calendars/public/", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/xml; charset=utf-8", "Content-Length": "655", "Depth": "1", "Origin": "http://localhost:8080", "DNT": "1", "Authorization": "***", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "\n/caldav/calendars/public/10.ics/caldav/calendars/public/9.ics/caldav/calendars/public/8.ics/caldav/calendars/public/7.ics/caldav/calendars/public/6.ics/caldav/calendars/public/5.ics/caldav/calendars/public/4.ics/caldav/calendars/public/3.ics/caldav/calendars/public/2.ics/caldav/calendars/public/1.ics", "body_bytes": 655, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:38:29 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "8815"}, "body": "\n\n\n /caldav/calendars/public/1.ics\n \n \n \"a1c06c7625070956\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-001@calendar-wp-plugin\r\nDTSTAMP:20260330T143829Z\r\nSUMMARY:Board Meeting\r\nDESCRIPTION:Quarterly board review.\r\nLOCATION:Room A\r\nCATEGORIES:Governance\r\nDTSTART;TZID=Europe/London:20260401T100000\r\nDTEND;TZID=Europe/London:20260401T113000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/2.ics\n \n \n \"31413754bab11f12\"\n ...(truncated)", "body_bytes": 3919, "body_truncated": true}} +{"ts": "2026-03-30T14:38:37+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/4.ics", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "14108", "If-Match": "\"0959513a689d7faf\"", "Origin": "http://localhost:8080", "DNT": "1", "Authorization": "***", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "BEGIN:VCALENDAR\r\nPRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN\r\nVERSION:2.0\r\nBEGIN:VTIMEZONE\r\nTZID:Europe/London\r\nX-TZINFO:Europe/London[2025b]\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:-000115\r\nTZNAME:Europe/London(STD)\r\nDTSTART:18471201T000000\r\nRDATE:18471201T000000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19160521T020000\r\nRDATE:19160521T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19161001T030000\r\nRDATE:19161001T030000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19170408T020000\r\nRDATE:19170408T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19170917T030000\r\nRDATE:19170917T030000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19180324T020000\r\nRDATE:19180324T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19180930T030000\r\nRRULE:FREQ=YEARLY;BYMONTH=9;BYDAY=-1MO;UNTIL=19190929 ...(truncated)", "body_bytes": 4097, "body_truncated": true}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:38:37 GMT", "ETag": "\"c3b404268b1e2a58\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T14:38:37+00:00", "client": "127.0.0.1", "method": "REPORT", "path": "/caldav/calendars/public/", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/xml; charset=utf-8", "Content-Length": "231", "Depth": "1", "Origin": "http://localhost:8080", "DNT": "1", "Authorization": "***", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "\n/caldav/calendars/public/4.ics", "body_bytes": 231, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:38:37 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "8858"}, "body": "\n\n\n /caldav/calendars/public/1.ics\n \n \n \"a1c06c7625070956\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-001@calendar-wp-plugin\r\nDTSTAMP:20260330T143837Z\r\nSUMMARY:Board Meeting\r\nDESCRIPTION:Quarterly board review.\r\nLOCATION:Room A\r\nCATEGORIES:Governance\r\nDTSTART;TZID=Europe/London:20260401T100000\r\nDTEND;TZID=Europe/London:20260401T113000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/2.ics\n \n \n \"31413754bab11f12\"\n ...(truncated)", "body_bytes": 3919, "body_truncated": true}} +{"ts": "2026-03-30T14:38:43+00:00", "client": "127.0.0.1", "method": "PROPFIND", "path": "/caldav/calendars/public/", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/xml; charset=utf-8", "Content-Length": "144", "Depth": "1", "Origin": "http://localhost:8080", "DNT": "1", "Authorization": "***", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "\n", "body_bytes": 144, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:38:43 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "3570"}, "body": "\n\n\n /caldav/calendars/public/\n \n \n Public Calendar\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/1.ics\n \n \n text/calendar; charset=utf-8\"a1c06c7625070956\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/2.ics\n \n \n text/calendar; charset=utf-8\"31413754bab11f12\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/3.ics\n \n \n ...(truncated)", "body_bytes": 3570, "body_truncated": false}} +{"ts": "2026-03-30T14:38:50+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/public/events?view=month&date=2026-04-01", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:148.0) Gecko/20100101 Firefox/148.0", "Accept": "*/*", "Accept-Language": "en-GB,en;q=0.9", "Accept-Encoding": "gzip, deflate, br, zstd", "Referer": "http://localhost:8080/calendar", "DNT": "1", "Sec-GPC": "1", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=0"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:38:50 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "7109"}, "body": "{\"data\": [{\"event_id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-01T10:00:00+01:00\", \"occurrence_end\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\"}, {\"event_id\": 10, \"uid\": \"fixture-ce-010@calendar-wp-plugin\", \"title\": \"Therapy Session\", \"description\": \"Used for single-occurrence delete exception tests.\", \"location\": \"Clinic\", \"category\": \"Health\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-03T14:00:00+01:00\", \"occurrence_end\": \"2026-04-03T15:00:00+01:00\", \"repeat_type\": \"weekly\"}, {\"event_id\": 3, \"uid\": \"fixture-ce-003@calendar-wp-plugin\", \"title\": \"Daily Standup\", \"description\": \"15 minute sync.\", \"location\": \"Online\", \"category\": \"Team\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-06T09:00:00+01:00\", \"occurrence_end\": \"2026-04-06T09:15:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 3, \"uid\": \"fixture-ce-003@calendar-wp-plugin\", \"title\": \"Daily Standup\", \"description\": \"15 minute sync.\", \"location\": \"Online\", \"category\": \"Team\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T14:38:58+00:00", "client": "127.0.0.1", "method": "GET", "path": "/calendar", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:148.0) Gecko/20100101 Firefox/148.0", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", "Accept-Language": "en-GB,en;q=0.9", "Accept-Encoding": "gzip, deflate, br, zstd", "Referer": "http://localhost:8080/wp-admin/admin.php?page=calendar-users&as=admin", "DNT": "1", "Sec-GPC": "1", "Connection": "keep-alive", "Upgrade-Insecure-Requests": "1", "Sec-Fetch-Dest": "document", "Sec-Fetch-Mode": "navigate", "Sec-Fetch-Site": "same-origin", "Sec-Fetch-User": "?1", "Priority": "u=0, i", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:38:58 GMT", "Content-Type": "text/html; charset=utf-8", "Content-Length": "43446"}, "body": "[omitted html payload: 3934 bytes]", "body_bytes": 3934, "body_truncated": true}} +{"ts": "2026-03-30T14:38:58+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/public/events?view=month&date=2026-03-30", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:148.0) Gecko/20100101 Firefox/148.0", "Accept": "*/*", "Accept-Language": "en-GB,en;q=0.9", "Accept-Encoding": "gzip, deflate, br, zstd", "Referer": "http://localhost:8080/calendar", "DNT": "1", "Sec-GPC": "1", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:38:58 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "51"}, "body": "{\"data\": [], \"meta\": {\"count\": 0, \"view\": \"month\"}}", "body_bytes": 51, "body_truncated": false}} +{"ts": "2026-03-30T14:38:58+00:00", "client": "127.0.0.1", "method": "GET", "path": "/favicon.ico", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:148.0) Gecko/20100101 Firefox/148.0", "Accept": "image/avif,image/jxl,image/webp,image/png,image/svg+xml,image/*;q=0.8,*/*;q=0.5", "Accept-Language": "en-GB,en;q=0.9", "Accept-Encoding": "gzip, deflate, br, zstd", "DNT": "1", "Sec-GPC": "1", "Connection": "keep-alive", "Referer": "http://localhost:8080/calendar", "Sec-Fetch-Dest": "image", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=6", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 404, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:38:58 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "62"}, "body": "{\"error\": {\"code\": \"not_found\", \"message\": \"Route not found\"}}", "body_bytes": 62, "body_truncated": false}} +{"ts": "2026-03-30T14:39:02+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/public/events?view=month&date=2026-04-01", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:148.0) Gecko/20100101 Firefox/148.0", "Accept": "*/*", "Accept-Language": "en-GB,en;q=0.9", "Accept-Encoding": "gzip, deflate, br, zstd", "Referer": "http://localhost:8080/calendar", "DNT": "1", "Sec-GPC": "1", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=0"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:39:02 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "7109"}, "body": "{\"data\": [{\"event_id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-01T10:00:00+01:00\", \"occurrence_end\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\"}, {\"event_id\": 10, \"uid\": \"fixture-ce-010@calendar-wp-plugin\", \"title\": \"Therapy Session\", \"description\": \"Used for single-occurrence delete exception tests.\", \"location\": \"Clinic\", \"category\": \"Health\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-03T14:00:00+01:00\", \"occurrence_end\": \"2026-04-03T15:00:00+01:00\", \"repeat_type\": \"weekly\"}, {\"event_id\": 3, \"uid\": \"fixture-ce-003@calendar-wp-plugin\", \"title\": \"Daily Standup\", \"description\": \"15 minute sync.\", \"location\": \"Online\", \"category\": \"Team\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-06T09:00:00+01:00\", \"occurrence_end\": \"2026-04-06T09:15:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 3, \"uid\": \"fixture-ce-003@calendar-wp-plugin\", \"title\": \"Daily Standup\", \"description\": \"15 minute sync.\", \"location\": \"Online\", \"category\": \"Team\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T14:39:09+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/public/events?view=month&date=2026-05-01", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:148.0) Gecko/20100101 Firefox/148.0", "Accept": "*/*", "Accept-Language": "en-GB,en;q=0.9", "Accept-Encoding": "gzip, deflate, br, zstd", "Referer": "http://localhost:8080/calendar", "DNT": "1", "Sec-GPC": "1", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=0"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:39:09 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "3708"}, "body": "{\"data\": [{\"event_id\": 10, \"uid\": \"fixture-ce-010@calendar-wp-plugin\", \"title\": \"Therapy Session\", \"description\": \"Used for single-occurrence delete exception tests.\", \"location\": \"Clinic\", \"category\": \"Health\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-01T14:00:00+01:00\", \"occurrence_end\": \"2026-05-01T15:00:00+01:00\", \"repeat_type\": \"weekly\"}, {\"event_id\": 2, \"uid\": \"fixture-ce-002@calendar-wp-plugin\", \"title\": \"Office Closed\", \"description\": \"Public holiday closure.\", \"location\": \"HQ\", \"category\": \"Operations\", \"all_day_event\": true, \"occurrence_start\": \"2026-05-04T00:00:00+00:00\", \"occurrence_end\": \"2026-05-05T00:00:00+00:00\", \"repeat_type\": \"none\"}, {\"event_id\": 7, \"uid\": \"fixture-ce-007@calendar-wp-plugin\", \"title\": \"Fortnightly Coaching\", \"description\": \"Coaching check-in.\", \"location\": \"Online\", \"category\": \"Training\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-05T15:00:00+01:00\", \"occurrence_end\": \"2026-05-05T16:00:00+01:00\", \"repeat_type\": \"custom\"}, {\"event_id\": 10, \"uid\": \"fixture-ce-010@calendar-wp-plugin\", \"title\": \"Therapy Session\", \"description\": \"Used for single-occurrence delete exception tests.\", \"location\": \"Clinic\", \"category\": \"Health\", \"al ...(truncated)", "body_bytes": 3708, "body_truncated": false}} +{"ts": "2026-03-30T14:39:24+00:00", "client": "127.0.0.1", "method": "PROPFIND", "path": "/caldav/calendars/public/", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/xml; charset=utf-8", "Content-Length": "144", "Depth": "1", "Origin": "http://localhost:8080", "DNT": "1", "Authorization": "***", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "\n", "body_bytes": 144, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:39:24 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "3570"}, "body": "\n\n\n /caldav/calendars/public/\n \n \n Public Calendar\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/1.ics\n \n \n text/calendar; charset=utf-8\"a1c06c7625070956\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/2.ics\n \n \n text/calendar; charset=utf-8\"31413754bab11f12\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/3.ics\n \n \n ...(truncated)", "body_bytes": 3570, "body_truncated": false}} +{"ts": "2026-03-30T14:42:51+00:00", "client": "127.0.0.1", "method": "REPORT", "path": "/caldav/calendars/public/", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/xml; charset=utf-8", "Depth": "1", "Content-Length": "239"}, "body": "\n\n \n /caldav/calendars/public/4.ics\n\n", "body_bytes": 239, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:42:51 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "8858"}, "body": "\n\n\n /caldav/calendars/public/1.ics\n \n \n \"a1c06c7625070956\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-001@calendar-wp-plugin\r\nDTSTAMP:20260330T144251Z\r\nSUMMARY:Board Meeting\r\nDESCRIPTION:Quarterly board review.\r\nLOCATION:Room A\r\nCATEGORIES:Governance\r\nDTSTART;TZID=Europe/London:20260401T100000\r\nDTEND;TZID=Europe/London:20260401T113000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/2.ics\n \n \n \"31413754bab11f12\"\n ...(truncated)", "body_bytes": 3919, "body_truncated": true}} +{"ts": "2026-03-30T14:43:35+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:43:35 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "6585"}, "body": "{\"data\": [{\"id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"start_datetime\": \"2026-04-01T10:00:00+01:00\", \"end_datetime\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"7751edcdd53548b6\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T14:43:34+00:00\"}, {\"id\": 2, \"uid\": \"fixture-ce-002@calendar-wp-plugin\", \"title\": \"Office Closed\", \"description\": \"Public holiday closure.\", \"location\": \"HQ\", \"category\": \"Operations\", \"all_day_event\": true, \"start_datetime\": \"2026-05-04\", \"end_datetime\": \"2026-05-05\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"052f8", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T14:43:35+00:00", "client": "127.0.0.1", "method": "GET", "path": "/calendar.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:43:35 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"c97f86eaea834f45\"", "Content-Length": "3132"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nCALSCALE:GREGORIAN\r\nX-WR-TIMEZONE:Europe/London\r\nBEGIN:VEVENT\r\nUID:fixture-ce-001@calendar-wp-plugin\r\nDTSTAMP:20260330T144335Z\r\nSUMMARY:Board Meeting\r\nDESCRIPTION:Quarterly board review.\r\nLOCATION:Room A\r\nCATEGORIES:Governance\r\nDTSTART;TZID=Europe/London:20260401T100000\r\nDTEND;TZID=Europe/London:20260401T113000\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-002@calendar-wp-plugin\r\nDTSTAMP:20260330T144335Z\r\nSUMMARY:Office Closed\r\nDESCRIPTION:Public holiday closure.\r\nLOCATION:HQ\r\nCATEGORIES:Operations\r\nDTSTART;VALUE=DATE:20260504\r\nDTEND;VALUE=DATE:20260505\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-003@calendar-wp-plugin\r\nDTSTAMP:20260330T144335Z\r\nSUMMARY:Daily Standup\r\nDESCRIPTION:15 minute sync.\r\nLOCATION:Online\r\nCATEGORIES:Team\r\nDTSTART;TZID=Europe/London:20260406T090000\r\nDTEND;TZID=Europe/London:20260406T091500\r\nRRULE:FREQ=DAILY;COUNT=10\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-004@calendar-wp-plugin\r\nDTSTAMP:20260330T144335Z\r\nSUMMARY:Community Lunch\r\nDESCRIPTION:Weekly community lunch.\r\nLOCATION:Cafeteria\r\nCATEGORIES:Community\r\nDTSTART;TZID=Europe/London:20260408T123000\r\nDTEND;TZID=Europe/London:20260408T133000\r\n ...(truncated)", "body_bytes": 3132, "body_truncated": false}} +{"ts": "2026-03-30T14:43:35+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/1.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:43:35 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"7751edcdd53548b6\"", "Content-Length": "364"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-001@calendar-wp-plugin\r\nDTSTAMP:20260330T144335Z\r\nSUMMARY:Board Meeting\r\nDESCRIPTION:Quarterly board review.\r\nLOCATION:Room A\r\nCATEGORIES:Governance\r\nDTSTART;TZID=Europe/London:20260401T100000\r\nDTEND;TZID=Europe/London:20260401T113000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 364, "body_truncated": false}} +{"ts": "2026-03-30T14:43:36+00:00", "client": "127.0.0.1", "method": "REPORT", "path": "/caldav/calendars/public/", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/xml; charset=utf-8", "Depth": "1", "Content-Length": "239"}, "body": "\n\n \n /caldav/calendars/public/4.ics\n\n", "body_bytes": 239, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:43:36 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "8815"}, "body": "\n\n\n /caldav/calendars/public/1.ics\n \n \n \"7751edcdd53548b6\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-001@calendar-wp-plugin\r\nDTSTAMP:20260330T144336Z\r\nSUMMARY:Board Meeting\r\nDESCRIPTION:Quarterly board review.\r\nLOCATION:Room A\r\nCATEGORIES:Governance\r\nDTSTART;TZID=Europe/London:20260401T100000\r\nDTEND;TZID=Europe/London:20260401T113000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/2.ics\n \n \n \"052f81957f9aae6d\"\n ...(truncated)", "body_bytes": 3919, "body_truncated": true}} +{"ts": "2026-03-30T14:44:20+00:00", "client": "127.0.0.1", "method": "REPORT", "path": "/caldav/calendars/public/", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/xml; charset=utf-8", "Depth": "1", "Content-Length": "239"}, "body": "\n\n \n /caldav/calendars/public/4.ics\n\n", "body_bytes": 239, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:44:20 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "997"}, "body": "\n\n\n /caldav/calendars/public/4.ics\n \n \n \"5ec78c2d14c408f9\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-004@calendar-wp-plugin\r\nDTSTAMP:20260330T144420Z\r\nSUMMARY:Community Lunch\r\nDESCRIPTION:Weekly community lunch.\r\nLOCATION:Cafeteria\r\nCATEGORIES:Community\r\nDTSTART;TZID=Europe/London:20260408T123000\r\nDTEND;TZID=Europe/London:20260408T133000\r\nRRULE:FREQ=WEEKLY\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n", "body_bytes": 997, "body_truncated": false}} +{"ts": "2026-03-30T14:44:29+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:44:29 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "6585"}, "body": "{\"data\": [{\"id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"start_datetime\": \"2026-04-01T10:00:00+01:00\", \"end_datetime\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"7dba3d192408fd8d\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T14:44:28+00:00\"}, {\"id\": 2, \"uid\": \"fixture-ce-002@calendar-wp-plugin\", \"title\": \"Office Closed\", \"description\": \"Public holiday closure.\", \"location\": \"HQ\", \"category\": \"Operations\", \"all_day_event\": true, \"start_datetime\": \"2026-05-04\", \"end_datetime\": \"2026-05-05\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"8dd5f", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T14:44:29+00:00", "client": "127.0.0.1", "method": "GET", "path": "/calendar.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:44:29 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"eb9acb23074ed19b\"", "Content-Length": "3132"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nCALSCALE:GREGORIAN\r\nX-WR-TIMEZONE:Europe/London\r\nBEGIN:VEVENT\r\nUID:fixture-ce-001@calendar-wp-plugin\r\nDTSTAMP:20260330T144429Z\r\nSUMMARY:Board Meeting\r\nDESCRIPTION:Quarterly board review.\r\nLOCATION:Room A\r\nCATEGORIES:Governance\r\nDTSTART;TZID=Europe/London:20260401T100000\r\nDTEND;TZID=Europe/London:20260401T113000\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-002@calendar-wp-plugin\r\nDTSTAMP:20260330T144429Z\r\nSUMMARY:Office Closed\r\nDESCRIPTION:Public holiday closure.\r\nLOCATION:HQ\r\nCATEGORIES:Operations\r\nDTSTART;VALUE=DATE:20260504\r\nDTEND;VALUE=DATE:20260505\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-003@calendar-wp-plugin\r\nDTSTAMP:20260330T144429Z\r\nSUMMARY:Daily Standup\r\nDESCRIPTION:15 minute sync.\r\nLOCATION:Online\r\nCATEGORIES:Team\r\nDTSTART;TZID=Europe/London:20260406T090000\r\nDTEND;TZID=Europe/London:20260406T091500\r\nRRULE:FREQ=DAILY;COUNT=10\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-004@calendar-wp-plugin\r\nDTSTAMP:20260330T144429Z\r\nSUMMARY:Community Lunch\r\nDESCRIPTION:Weekly community lunch.\r\nLOCATION:Cafeteria\r\nCATEGORIES:Community\r\nDTSTART;TZID=Europe/London:20260408T123000\r\nDTEND;TZID=Europe/London:20260408T133000\r\n ...(truncated)", "body_bytes": 3132, "body_truncated": false}} +{"ts": "2026-03-30T14:44:29+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/1.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:44:29 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"7dba3d192408fd8d\"", "Content-Length": "364"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-001@calendar-wp-plugin\r\nDTSTAMP:20260330T144429Z\r\nSUMMARY:Board Meeting\r\nDESCRIPTION:Quarterly board review.\r\nLOCATION:Room A\r\nCATEGORIES:Governance\r\nDTSTART;TZID=Europe/London:20260401T100000\r\nDTEND;TZID=Europe/London:20260401T113000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 364, "body_truncated": false}} +{"ts": "2026-03-30T14:44:30+00:00", "client": "127.0.0.1", "method": "REPORT", "path": "/caldav/calendars/public/", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/xml; charset=utf-8", "Depth": "1", "Content-Length": "239"}, "body": "\n\n \n /caldav/calendars/public/4.ics\n\n", "body_bytes": 239, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:44:30 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "997"}, "body": "\n\n\n /caldav/calendars/public/4.ics\n \n \n \"072b1b494ba4f968\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-004@calendar-wp-plugin\r\nDTSTAMP:20260330T144430Z\r\nSUMMARY:Community Lunch\r\nDESCRIPTION:Weekly community lunch.\r\nLOCATION:Cafeteria\r\nCATEGORIES:Community\r\nDTSTART;TZID=Europe/London:20260408T123000\r\nDTEND;TZID=Europe/London:20260408T133000\r\nRRULE:FREQ=WEEKLY\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n", "body_bytes": 997, "body_truncated": false}} +{"ts": "2026-03-30T14:44:30+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/10/occurrences/2026-04-17T14:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:44:30 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T14:44:30+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-vtimezone.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "454"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN\nVERSION:2.0\nBEGIN:VTIMEZONE\nTZID:Europe/London\nBEGIN:STANDARD\nDTSTART:18471201T000000\nRRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=9\nTZOFFSETFROM:+0115\nTZOFFSETTO:+0000\nTZNAME:GMT\nEND:STANDARD\nEND:VTIMEZONE\nBEGIN:VEVENT\nUID:smoke-vtimezone-parser-001\nSUMMARY:Smoke VTIMEZONE Parse\nDTSTART;TZID=Europe/London:20260423T150000\nDTEND;TZID=Europe/London:20260423T160000\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 454, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:44:30 GMT", "ETag": "\"5adcf0bbdfde8074\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T14:44:30+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-vtimezone.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:44:30 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"5adcf0bbdfde8074\"", "Content-Length": "288"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-vtimezone-parser-001\r\nDTSTAMP:20260330T144430Z\r\nSUMMARY:Smoke VTIMEZONE Parse\r\nDTSTART;TZID=Europe/London:20260423T150000\r\nDTEND;TZID=Europe/London:20260423T160000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 288, "body_truncated": false}} +{"ts": "2026-03-30T14:44:30+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-monthly-nth.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "261"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-monthly-nth-001\nSUMMARY:Smoke Monthly Nth\nDTSTART;TZID=Europe/London:20260402T150000\nDTEND;TZID=Europe/London:20260402T160000\nRRULE:FREQ=MONTHLY;BYDAY=SU;BYSETPOS=4\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 261, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:44:30 GMT", "ETag": "\"1d474bd1d0cd4bf5\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T14:44:30+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-monthly-nth.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:44:30 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"1d474bd1d0cd4bf5\"", "Content-Length": "319"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-nth-001\r\nDTSTAMP:20260330T144430Z\r\nSUMMARY:Smoke Monthly Nth\r\nDTSTART;TZID=Europe/London:20260426T150000\r\nDTEND;TZID=Europe/London:20260426T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SU;BYSETPOS=4\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 319, "body_truncated": false}} +{"ts": "2026-03-30T14:44:30+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-monthly-ordinal.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "259"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-monthly-ordinal-001\nSUMMARY:Smoke Monthly Ordinal\nDTSTART;TZID=Europe/London:20260411T150000\nDTEND;TZID=Europe/London:20260411T160000\nRRULE:FREQ=MONTHLY;BYDAY=2SA\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 259, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:44:30 GMT", "ETag": "\"4bb73834167ee601\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T14:44:30+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-monthly-ordinal.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:44:30 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"4bb73834167ee601\"", "Content-Length": "327"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-ordinal-001\r\nDTSTAMP:20260330T144430Z\r\nSUMMARY:Smoke Monthly Ordinal\r\nDTSTART;TZID=Europe/London:20260411T150000\r\nDTEND;TZID=Europe/London:20260411T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SA;BYSETPOS=2\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 327, "body_truncated": false}} +{"ts": "2026-03-30T14:44:30+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-monthly-last.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "264"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-monthly-last-001\nSUMMARY:Smoke Monthly Last\nDTSTART;TZID=Europe/London:20260425T150000\nDTEND;TZID=Europe/London:20260425T160000\nRRULE:FREQ=MONTHLY;BYDAY=SA;BYSETPOS=-1\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 264, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:44:30 GMT", "ETag": "\"98b19a1e7ae18985\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T14:44:30+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-monthly-last.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:44:30 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"98b19a1e7ae18985\"", "Content-Length": "322"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-last-001\r\nDTSTAMP:20260330T144430Z\r\nSUMMARY:Smoke Monthly Last\r\nDTSTART;TZID=Europe/London:20260425T150000\r\nDTEND;TZID=Europe/London:20260425T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SA;BYSETPOS=-1\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 322, "body_truncated": false}} +{"ts": "2026-03-30T14:44:30+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "X-WP-User": "admin", "Content-Length": "362"}, "body": "{\"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\"}", "body_bytes": 362, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:44:30 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "673"}, "body": "{\"data\": {\"id\": 15, \"uid\": \"a63f8ecbbdc94c49bc3f@calendar-wp-plugin\", \"title\": \"Smoke 4th Sunday Anchor\", \"description\": \"anchor-normalization\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"start_datetime\": \"2026-05-24T15:00:00+01:00\", \"end_datetime\": \"2026-05-24T16:00:00+01:00\", \"repeat_type\": \"monthly\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"weekday_of_month\", \"repeat_nth_day\": null, \"repeat_nth_pos\": 4, \"repeat_nth_weekday\": 0, \"repeat_range_mode\": \"no_end\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"a7edae8a0700321c\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T14:44:30+00:00\"}}", "body_bytes": 673, "body_truncated": false}} +{"ts": "2026-03-30T14:44:30+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/15/occurrences/2026-05-24T15:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:44:30 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T14:44:30+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/15.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:44:30 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"5cdf8a9141d5dbc9\"", "Content-Length": "420"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:a63f8ecbbdc94c49bc3f@calendar-wp-plugin\r\nDTSTAMP:20260330T144430Z\r\nSUMMARY:Smoke 4th Sunday Anchor\r\nDESCRIPTION:anchor-normalization\r\nDTSTART;TZID=Europe/London:20260524T150000\r\nDTEND;TZID=Europe/London:20260524T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SU;BYSETPOS=4\r\nEXDATE;TZID=Europe/London:20260524T150000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 420, "body_truncated": false}} +{"ts": "2026-03-30T14:44:30+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "X-WP-User": "admin", "Content-Length": "295"}, "body": "{\"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\"}", "body_bytes": 295, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:44:30 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "655"}, "body": "{\"data\": {\"id\": 16, \"uid\": \"916c9cd30540959ad63f@calendar-wp-plugin\", \"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_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"until\", \"repeat_count\": null, \"repeat_until\": \"2026-03-19\", \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"fe9f3570de454c2f\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T14:44:30+00:00\"}}", "body_bytes": 655, "body_truncated": false}} +{"ts": "2026-03-30T14:44:30+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/16/occurrences/2026-03-11T11:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:44:30 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T14:44:30+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/16/occurrences?from=2026-03-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:44:30 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "5212"}, "body": "{\"data\": [{\"event_id\": 16, \"uid\": \"916c9cd30540959ad63f@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-02T10:00:00+00:00\", \"occurrence_end\": \"2026-03-02T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"916c9cd30540959ad63f@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-03T10:00:00+00:00\", \"occurrence_end\": \"2026-03-03T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"916c9cd30540959ad63f@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-04T10:00:00+00:00\", \"occurrence_end\": \"2026-03-04T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"916c9cd30540959ad63f@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-05T10:00:00+00:00\", \"occurrence_end\": \"2026-03-05T11:00:00", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T14:44:30+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/16/occurrences/2026-03-10", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:44:30 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T14:44:30+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/16/occurrences?from=2026-03-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:44:30 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "4906"}, "body": "{\"data\": [{\"event_id\": 16, \"uid\": \"916c9cd30540959ad63f@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-02T10:00:00+00:00\", \"occurrence_end\": \"2026-03-02T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"916c9cd30540959ad63f@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-03T10:00:00+00:00\", \"occurrence_end\": \"2026-03-03T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"916c9cd30540959ad63f@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-04T10:00:00+00:00\", \"occurrence_end\": \"2026-03-04T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"916c9cd30540959ad63f@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-05T10:00:00+00:00\", \"occurrence_end\": \"2026-03-05T11:00:00", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T14:44:30+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-exdate-import.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "323"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-exdate-import-001\nSUMMARY:Smoke EXDATE Import\nDTSTART;TZID=Europe/London:20260302T100000\nDTEND;TZID=Europe/London:20260302T110000\nRRULE:FREQ=DAILY;UNTIL=20260319T235959\nEXDATE;TZID=Europe/London:20260310T100000,20260311T100000\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 323, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:44:30 GMT", "ETag": "\"2e770edf61e4a71a\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T14:44:30+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:44:30 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "11006"}, "body": "{\"data\": [{\"id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"start_datetime\": \"2026-04-01T10:00:00+01:00\", \"end_datetime\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"7dba3d192408fd8d\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T14:44:28+00:00\"}, {\"id\": 2, \"uid\": \"fixture-ce-002@calendar-wp-plugin\", \"title\": \"Office Closed\", \"description\": \"Public holiday closure.\", \"location\": \"HQ\", \"category\": \"Operations\", \"all_day_event\": true, \"start_datetime\": \"2026-05-04\", \"end_datetime\": \"2026-05-05\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"8dd5f", "body_bytes": 3927, "body_truncated": true}} +{"ts": "2026-03-30T14:44:30+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/17/occurrences?from=2026-03-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:44:30 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "4474"}, "body": "{\"data\": [{\"event_id\": 17, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-02T10:00:00+01:00\", \"occurrence_end\": \"2026-03-02T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-03T10:00:00+01:00\", \"occurrence_end\": \"2026-03-03T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-04T10:00:00+01:00\", \"occurrence_end\": \"2026-03-04T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-05T10:00:00+01:00\", \"occurrence_end\": \"2026-03-05T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE ", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T14:44:30+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-cancelled-occurrence.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "467"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-cancelled-occurrence-001\nSUMMARY:Smoke Cancelled Occurrence\nDTSTART;TZID=Europe/London:20260408T123000\nDTEND;TZID=Europe/London:20260408T133000\nRRULE:FREQ=WEEKLY\nEND:VEVENT\nBEGIN:VEVENT\nUID:smoke-cancelled-occurrence-001\nRECURRENCE-ID;TZID=Europe/London:20260506T123000\nDTSTART;TZID=Europe/London:20260506T123000\nDTEND;TZID=Europe/London:20260506T133000\nSTATUS:CANCELLED\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 467, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:44:30 GMT", "ETag": "\"5c32f479683cad08\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T14:44:30+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-cancelled-occurrence.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:44:30 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"5c32f479683cad08\"", "Content-Length": "359"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-cancelled-occurrence-001\r\nDTSTAMP:20260330T144430Z\r\nSUMMARY:Smoke Cancelled Occurrence\r\nDTSTART;TZID=Europe/London:20260408T123000\r\nDTEND;TZID=Europe/London:20260408T133000\r\nRRULE:FREQ=WEEKLY\r\nEXDATE;TZID=Europe/London:20260506T123000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 359, "body_truncated": false}} +{"ts": "2026-03-30T14:44:30+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:44:30 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "11634"}, "body": "{\"data\": [{\"id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"start_datetime\": \"2026-04-01T10:00:00+01:00\", \"end_datetime\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"7dba3d192408fd8d\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T14:44:28+00:00\"}, {\"id\": 2, \"uid\": \"fixture-ce-002@calendar-wp-plugin\", \"title\": \"Office Closed\", \"description\": \"Public holiday closure.\", \"location\": \"HQ\", \"category\": \"Operations\", \"all_day_event\": true, \"start_datetime\": \"2026-05-04\", \"end_datetime\": \"2026-05-05\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"8dd5f", "body_bytes": 3927, "body_truncated": true}} +{"ts": "2026-03-30T14:44:30+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/18/occurrences?from=2026-05-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:44:30 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "892"}, "body": "{\"data\": [{\"event_id\": 18, \"uid\": \"smoke-cancelled-occurrence-001\", \"title\": \"Smoke Cancelled Occurrence\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-13T12:30:00+01:00\", \"occurrence_end\": \"2026-05-13T13:30:00+01:00\", \"repeat_type\": \"weekly\"}, {\"event_id\": 18, \"uid\": \"smoke-cancelled-occurrence-001\", \"title\": \"Smoke Cancelled Occurrence\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-20T12:30:00+01:00\", \"occurrence_end\": \"2026-05-20T13:30:00+01:00\", \"repeat_type\": \"weekly\"}, {\"event_id\": 18, \"uid\": \"smoke-cancelled-occurrence-001\", \"title\": \"Smoke Cancelled Occurrence\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-27T12:30:00+01:00\", \"occurrence_end\": \"2026-05-27T13:30:00+01:00\", \"repeat_type\": \"weekly\"}]}", "body_bytes": 892, "body_truncated": false}} +{"ts": "2026-03-30T14:44:30+00:00", "client": "127.0.0.1", "method": "GET", "path": "/calendar", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:44:30 GMT", "Content-Type": "text/html; charset=utf-8", "Content-Length": "43447"}, "body": "[omitted html payload: 3934 bytes]", "body_bytes": 3934, "body_truncated": true}} +{"ts": "2026-03-30T14:44:58+00:00", "client": "127.0.0.1", "method": "PROPFIND", "path": "/caldav/calendars/public/", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/xml; charset=utf-8", "Content-Length": "144", "Depth": "1", "Origin": "http://localhost:8080", "DNT": "1", "Authorization": "***", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "\n", "body_bytes": 144, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:44:58 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "6130"}, "body": "\n\n\n /caldav/calendars/public/\n \n \n Public Calendar\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/1.ics\n \n \n text/calendar; charset=utf-8\"53fb93409a868c3b\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/2.ics\n \n \n text/calendar; charset=utf-8\"15e21dd53bfeddfc\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/3.ics\n \n \n ...(truncated)", "body_bytes": 3919, "body_truncated": true}} +{"ts": "2026-03-30T14:44:58+00:00", "client": "127.0.0.1", "method": "REPORT", "path": "/caldav/calendars/public/", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/xml; charset=utf-8", "Content-Length": "1143", "Depth": "1", "Origin": "http://localhost:8080", "DNT": "1", "Authorization": "***", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "\n/caldav/calendars/public/smoke-cancelled-occurrence.ics/caldav/calendars/public/smoke-exdate-import.ics/caldav/calendars/public/16.ics/caldav/calendars/public/15.ics/caldav/calendars/public/smoke-monthly-last.ics/caldav/calendars/public/smoke-monthly-ordinal.ics/caldav/calendars/public/smoke-monthly-nth.ics/caldav/calendars/public/smoke-vtimezone.ics/caldav/calendars/public/10.ics/caldav/calendars/public/9.ics/caldav/calendars/public/8.ics/caldav/calendars/public/7.ics/caldav/calendars/public/6.ics/caldav/calendars/public/5.ics/caldav/calendars/public/4.ics/caldav/calendars/public/3.ics/caldav/calendars/public/2.ics/caldav/calendars/public/1.ics", "body_bytes": 1143, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:44:58 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "15419"}, "body": "\n\n\n /caldav/calendars/public/1.ics\n \n \n \"53fb93409a868c3b\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-001@calendar-wp-plugin\r\nDTSTAMP:20260330T144458Z\r\nSUMMARY:Board Meeting\r\nDESCRIPTION:Quarterly board review.\r\nLOCATION:Room A\r\nCATEGORIES:Governance\r\nDTSTART;TZID=Europe/London:20260401T100000\r\nDTEND;TZID=Europe/London:20260401T113000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/2.ics\n \n \n \"15e21dd53bfeddfc\"\n ...(truncated)", "body_bytes": 3918, "body_truncated": true}} +{"ts": "2026-03-30T14:45:10+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/4.ics", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "14108", "If-Match": "\"34008884f1eb8394\"", "Origin": "http://localhost:8080", "DNT": "1", "Authorization": "***", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "BEGIN:VCALENDAR\r\nPRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN\r\nVERSION:2.0\r\nBEGIN:VTIMEZONE\r\nTZID:Europe/London\r\nX-TZINFO:Europe/London[2025b]\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:-000115\r\nTZNAME:Europe/London(STD)\r\nDTSTART:18471201T000000\r\nRDATE:18471201T000000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19160521T020000\r\nRDATE:19160521T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19161001T030000\r\nRDATE:19161001T030000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19170408T020000\r\nRDATE:19170408T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19170917T030000\r\nRDATE:19170917T030000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19180324T020000\r\nRDATE:19180324T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19180930T030000\r\nRRULE:FREQ=YEARLY;BYMONTH=9;BYDAY=-1MO;UNTIL=19190929 ...(truncated)", "body_bytes": 4097, "body_truncated": true}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:45:10 GMT", "ETag": "\"2296d29d18e51e25\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T14:45:10+00:00", "client": "127.0.0.1", "method": "REPORT", "path": "/caldav/calendars/public/", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/xml; charset=utf-8", "Content-Length": "231", "Depth": "1", "Origin": "http://localhost:8080", "DNT": "1", "Authorization": "***", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "\n/caldav/calendars/public/4.ics", "body_bytes": 231, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:45:10 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "1040"}, "body": "\n\n\n /caldav/calendars/public/4.ics\n \n \n \"2296d29d18e51e25\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-004@calendar-wp-plugin\r\nDTSTAMP:20260330T144510Z\r\nSUMMARY:Community Lunch\r\nDESCRIPTION:Weekly community lunch.\r\nLOCATION:Cafeteria\r\nCATEGORIES:Community\r\nDTSTART;TZID=Europe/London:20260408T123000\r\nDTEND;TZID=Europe/London:20260408T133000\r\nRRULE:FREQ=WEEKLY\r\nEXDATE;TZID=Europe/London:20260506T123000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n", "body_bytes": 1040, "body_truncated": false}} +{"ts": "2026-03-30T14:45:12+00:00", "client": "127.0.0.1", "method": "PROPFIND", "path": "/caldav/calendars/public/", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/xml; charset=utf-8", "Content-Length": "144", "Depth": "1", "Origin": "http://localhost:8080", "DNT": "1", "Authorization": "***", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "\n", "body_bytes": 144, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:45:12 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "6130"}, "body": "\n\n\n /caldav/calendars/public/\n \n \n Public Calendar\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/1.ics\n \n \n text/calendar; charset=utf-8\"53fb93409a868c3b\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/2.ics\n \n \n text/calendar; charset=utf-8\"15e21dd53bfeddfc\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/3.ics\n \n \n ...(truncated)", "body_bytes": 3919, "body_truncated": true}} +{"ts": "2026-03-30T14:45:54+00:00", "client": "127.0.0.1", "method": "PROPFIND", "path": "/caldav/calendars/public/", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/xml; charset=utf-8", "Content-Length": "144", "Depth": "1", "Origin": "http://localhost:8080", "DNT": "1", "Authorization": "***", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "\n", "body_bytes": 144, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:45:54 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "6130"}, "body": "\n\n\n /caldav/calendars/public/\n \n \n Public Calendar\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/1.ics\n \n \n text/calendar; charset=utf-8\"53fb93409a868c3b\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/2.ics\n \n \n text/calendar; charset=utf-8\"15e21dd53bfeddfc\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/3.ics\n \n \n ...(truncated)", "body_bytes": 3919, "body_truncated": true}} +{"ts": "2026-03-30T14:46:14+00:00", "client": "127.0.0.1", "method": "GET", "path": "/calendar", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:148.0) Gecko/20100101 Firefox/148.0", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", "Accept-Language": "en-GB,en;q=0.9", "Accept-Encoding": "gzip, deflate, br, zstd", "Referer": "http://localhost:8080/wp-admin/admin.php?page=calendar-users&as=admin", "DNT": "1", "Sec-GPC": "1", "Connection": "keep-alive", "Upgrade-Insecure-Requests": "1", "Sec-Fetch-Dest": "document", "Sec-Fetch-Mode": "navigate", "Sec-Fetch-Site": "same-origin", "Sec-Fetch-User": "?1", "Priority": "u=0, i"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:46:14 GMT", "Content-Type": "text/html; charset=utf-8", "Content-Length": "43447"}, "body": "[omitted html payload: 3934 bytes]", "body_bytes": 3934, "body_truncated": true}} +{"ts": "2026-03-30T14:46:14+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/public/events?view=month&date=2026-03-30", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:148.0) Gecko/20100101 Firefox/148.0", "Accept": "*/*", "Accept-Language": "en-GB,en;q=0.9", "Accept-Encoding": "gzip, deflate, br, zstd", "Referer": "http://localhost:8080/calendar", "DNT": "1", "Sec-GPC": "1", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:46:14 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "10580"}, "body": "{\"data\": [{\"event_id\": 16, \"uid\": \"916c9cd30540959ad63f@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-02T10:00:00+00:00\", \"occurrence_end\": \"2026-03-02T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-02T10:00:00+01:00\", \"occurrence_end\": \"2026-03-02T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"916c9cd30540959ad63f@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-03T10:00:00+00:00\", \"occurrence_end\": \"2026-03-03T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-03T10:00:00+01:00\", \"occurrence_end\": \"2026-03-03T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"ui", "body_bytes": 3927, "body_truncated": true}} +{"ts": "2026-03-30T14:46:18+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/public/events?view=month&date=2026-04-01", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:148.0) Gecko/20100101 Firefox/148.0", "Accept": "*/*", "Accept-Language": "en-GB,en;q=0.9", "Accept-Encoding": "gzip, deflate, br, zstd", "Referer": "http://localhost:8080/calendar", "DNT": "1", "Sec-GPC": "1", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=0"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:46:18 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "9409"}, "body": "{\"data\": [{\"event_id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-01T10:00:00+01:00\", \"occurrence_end\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\"}, {\"event_id\": 10, \"uid\": \"fixture-ce-010@calendar-wp-plugin\", \"title\": \"Therapy Session\", \"description\": \"Used for single-occurrence delete exception tests.\", \"location\": \"Clinic\", \"category\": \"Health\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-03T14:00:00+01:00\", \"occurrence_end\": \"2026-04-03T15:00:00+01:00\", \"repeat_type\": \"weekly\"}, {\"event_id\": 3, \"uid\": \"fixture-ce-003@calendar-wp-plugin\", \"title\": \"Daily Standup\", \"description\": \"15 minute sync.\", \"location\": \"Online\", \"category\": \"Team\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-06T09:00:00+01:00\", \"occurrence_end\": \"2026-04-06T09:15:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 3, \"uid\": \"fixture-ce-003@calendar-wp-plugin\", \"title\": \"Daily Standup\", \"description\": \"15 minute sync.\", \"location\": \"Online\", \"category\": \"Team\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T14:46:19+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/public/events?view=month&date=2026-05-01", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:148.0) Gecko/20100101 Firefox/148.0", "Accept": "*/*", "Accept-Language": "en-GB,en;q=0.9", "Accept-Encoding": "gzip, deflate, br, zstd", "Referer": "http://localhost:8080/calendar", "DNT": "1", "Sec-GPC": "1", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=0"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:46:19 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "6046"}, "body": "{\"data\": [{\"event_id\": 10, \"uid\": \"fixture-ce-010@calendar-wp-plugin\", \"title\": \"Therapy Session\", \"description\": \"Used for single-occurrence delete exception tests.\", \"location\": \"Clinic\", \"category\": \"Health\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-01T14:00:00+01:00\", \"occurrence_end\": \"2026-05-01T15:00:00+01:00\", \"repeat_type\": \"weekly\"}, {\"event_id\": 2, \"uid\": \"fixture-ce-002@calendar-wp-plugin\", \"title\": \"Office Closed\", \"description\": \"Public holiday closure.\", \"location\": \"HQ\", \"category\": \"Operations\", \"all_day_event\": true, \"occurrence_start\": \"2026-05-04T00:00:00+00:00\", \"occurrence_end\": \"2026-05-05T00:00:00+00:00\", \"repeat_type\": \"none\"}, {\"event_id\": 7, \"uid\": \"fixture-ce-007@calendar-wp-plugin\", \"title\": \"Fortnightly Coaching\", \"description\": \"Coaching check-in.\", \"location\": \"Online\", \"category\": \"Training\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-05T15:00:00+01:00\", \"occurrence_end\": \"2026-05-05T16:00:00+01:00\", \"repeat_type\": \"custom\"}, {\"event_id\": 18, \"uid\": \"smoke-cancelled-occurrence-001\", \"title\": \"Smoke Cancelled Occurrence\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-06T1", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T14:46:23+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/public/events?view=month&date=2026-06-01", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:148.0) Gecko/20100101 Firefox/148.0", "Accept": "*/*", "Accept-Language": "en-GB,en;q=0.9", "Accept-Encoding": "gzip, deflate, br, zstd", "Referer": "http://localhost:8080/calendar", "DNT": "1", "Sec-GPC": "1", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=0"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:46:23 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "5317"}, "body": "{\"data\": [{\"event_id\": 7, \"uid\": \"fixture-ce-007@calendar-wp-plugin\", \"title\": \"Fortnightly Coaching\", \"description\": \"Coaching check-in.\", \"location\": \"Online\", \"category\": \"Training\", \"all_day_event\": false, \"occurrence_start\": \"2026-06-02T15:00:00+01:00\", \"occurrence_end\": \"2026-06-02T16:00:00+01:00\", \"repeat_type\": \"custom\"}, {\"event_id\": 4, \"uid\": \"fixture-ce-004@calendar-wp-plugin\", \"title\": \"Community Lunch\", \"description\": \"Weekly community lunch.\", \"location\": \"Cafeteria\", \"category\": \"Community\", \"all_day_event\": false, \"occurrence_start\": \"2026-06-03T12:30:00+01:00\", \"occurrence_end\": \"2026-06-03T13:30:00+01:00\", \"repeat_type\": \"weekly\"}, {\"event_id\": 18, \"uid\": \"smoke-cancelled-occurrence-001\", \"title\": \"Smoke Cancelled Occurrence\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-06-03T12:30:00+01:00\", \"occurrence_end\": \"2026-06-03T13:30:00+01:00\", \"repeat_type\": \"weekly\"}, {\"event_id\": 4, \"uid\": \"fixture-ce-004@calendar-wp-plugin\", \"title\": \"Community Lunch\", \"description\": \"Weekly community lunch.\", \"location\": \"Cafeteria\", \"category\": \"Community\", \"all_day_event\": false, \"occurrence_start\": \"2026-06-10T12:30:00+01:", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T14:46:25+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/public/events?view=month&date=2026-05-01", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:148.0) Gecko/20100101 Firefox/148.0", "Accept": "*/*", "Accept-Language": "en-GB,en;q=0.9", "Accept-Encoding": "gzip, deflate, br, zstd", "Referer": "http://localhost:8080/calendar", "DNT": "1", "Sec-GPC": "1", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=0"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:46:25 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "6046"}, "body": "{\"data\": [{\"event_id\": 10, \"uid\": \"fixture-ce-010@calendar-wp-plugin\", \"title\": \"Therapy Session\", \"description\": \"Used for single-occurrence delete exception tests.\", \"location\": \"Clinic\", \"category\": \"Health\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-01T14:00:00+01:00\", \"occurrence_end\": \"2026-05-01T15:00:00+01:00\", \"repeat_type\": \"weekly\"}, {\"event_id\": 2, \"uid\": \"fixture-ce-002@calendar-wp-plugin\", \"title\": \"Office Closed\", \"description\": \"Public holiday closure.\", \"location\": \"HQ\", \"category\": \"Operations\", \"all_day_event\": true, \"occurrence_start\": \"2026-05-04T00:00:00+00:00\", \"occurrence_end\": \"2026-05-05T00:00:00+00:00\", \"repeat_type\": \"none\"}, {\"event_id\": 7, \"uid\": \"fixture-ce-007@calendar-wp-plugin\", \"title\": \"Fortnightly Coaching\", \"description\": \"Coaching check-in.\", \"location\": \"Online\", \"category\": \"Training\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-05T15:00:00+01:00\", \"occurrence_end\": \"2026-05-05T16:00:00+01:00\", \"repeat_type\": \"custom\"}, {\"event_id\": 18, \"uid\": \"smoke-cancelled-occurrence-001\", \"title\": \"Smoke Cancelled Occurrence\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-06T1", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T14:46:25+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/public/events?view=month&date=2026-04-01", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:148.0) Gecko/20100101 Firefox/148.0", "Accept": "*/*", "Accept-Language": "en-GB,en;q=0.9", "Accept-Encoding": "gzip, deflate, br, zstd", "Referer": "http://localhost:8080/calendar", "DNT": "1", "Sec-GPC": "1", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=0"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:46:25 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "9409"}, "body": "{\"data\": [{\"event_id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-01T10:00:00+01:00\", \"occurrence_end\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\"}, {\"event_id\": 10, \"uid\": \"fixture-ce-010@calendar-wp-plugin\", \"title\": \"Therapy Session\", \"description\": \"Used for single-occurrence delete exception tests.\", \"location\": \"Clinic\", \"category\": \"Health\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-03T14:00:00+01:00\", \"occurrence_end\": \"2026-04-03T15:00:00+01:00\", \"repeat_type\": \"weekly\"}, {\"event_id\": 3, \"uid\": \"fixture-ce-003@calendar-wp-plugin\", \"title\": \"Daily Standup\", \"description\": \"15 minute sync.\", \"location\": \"Online\", \"category\": \"Team\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-06T09:00:00+01:00\", \"occurrence_end\": \"2026-04-06T09:15:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 3, \"uid\": \"fixture-ce-003@calendar-wp-plugin\", \"title\": \"Daily Standup\", \"description\": \"15 minute sync.\", \"location\": \"Online\", \"category\": \"Team\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T14:46:34+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/public/events?view=month&date=2026-05-01", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:148.0) Gecko/20100101 Firefox/148.0", "Accept": "*/*", "Accept-Language": "en-GB,en;q=0.9", "Accept-Encoding": "gzip, deflate, br, zstd", "Referer": "http://localhost:8080/calendar", "DNT": "1", "Sec-GPC": "1", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=0"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:46:34 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "6046"}, "body": "{\"data\": [{\"event_id\": 10, \"uid\": \"fixture-ce-010@calendar-wp-plugin\", \"title\": \"Therapy Session\", \"description\": \"Used for single-occurrence delete exception tests.\", \"location\": \"Clinic\", \"category\": \"Health\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-01T14:00:00+01:00\", \"occurrence_end\": \"2026-05-01T15:00:00+01:00\", \"repeat_type\": \"weekly\"}, {\"event_id\": 2, \"uid\": \"fixture-ce-002@calendar-wp-plugin\", \"title\": \"Office Closed\", \"description\": \"Public holiday closure.\", \"location\": \"HQ\", \"category\": \"Operations\", \"all_day_event\": true, \"occurrence_start\": \"2026-05-04T00:00:00+00:00\", \"occurrence_end\": \"2026-05-05T00:00:00+00:00\", \"repeat_type\": \"none\"}, {\"event_id\": 7, \"uid\": \"fixture-ce-007@calendar-wp-plugin\", \"title\": \"Fortnightly Coaching\", \"description\": \"Coaching check-in.\", \"location\": \"Online\", \"category\": \"Training\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-05T15:00:00+01:00\", \"occurrence_end\": \"2026-05-05T16:00:00+01:00\", \"repeat_type\": \"custom\"}, {\"event_id\": 18, \"uid\": \"smoke-cancelled-occurrence-001\", \"title\": \"Smoke Cancelled Occurrence\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-06T1", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T14:46:37+00:00", "client": "127.0.0.1", "method": "GET", "path": "/calendar", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:148.0) Gecko/20100101 Firefox/148.0", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", "Accept-Language": "en-GB,en;q=0.9", "Accept-Encoding": "gzip, deflate, br, zstd", "Referer": "http://localhost:8080/wp-admin/admin.php?page=calendar-users&as=admin", "DNT": "1", "Sec-GPC": "1", "Connection": "keep-alive", "Upgrade-Insecure-Requests": "1", "Sec-Fetch-Dest": "document", "Sec-Fetch-Mode": "navigate", "Sec-Fetch-Site": "same-origin", "Sec-Fetch-User": "?1", "Priority": "u=0, i", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:46:37 GMT", "Content-Type": "text/html; charset=utf-8", "Content-Length": "43447"}, "body": "[omitted html payload: 3934 bytes]", "body_bytes": 3934, "body_truncated": true}} +{"ts": "2026-03-30T14:46:37+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/public/events?view=month&date=2026-03-30", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:148.0) Gecko/20100101 Firefox/148.0", "Accept": "*/*", "Accept-Language": "en-GB,en;q=0.9", "Accept-Encoding": "gzip, deflate, br, zstd", "Referer": "http://localhost:8080/calendar", "DNT": "1", "Sec-GPC": "1", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:46:37 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "10580"}, "body": "{\"data\": [{\"event_id\": 16, \"uid\": \"916c9cd30540959ad63f@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-02T10:00:00+00:00\", \"occurrence_end\": \"2026-03-02T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-02T10:00:00+01:00\", \"occurrence_end\": \"2026-03-02T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"916c9cd30540959ad63f@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-03T10:00:00+00:00\", \"occurrence_end\": \"2026-03-03T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-03T10:00:00+01:00\", \"occurrence_end\": \"2026-03-03T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"ui", "body_bytes": 3927, "body_truncated": true}} +{"ts": "2026-03-30T14:46:37+00:00", "client": "127.0.0.1", "method": "GET", "path": "/favicon.ico", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:148.0) Gecko/20100101 Firefox/148.0", "Accept": "image/avif,image/jxl,image/webp,image/png,image/svg+xml,image/*;q=0.8,*/*;q=0.5", "Accept-Language": "en-GB,en;q=0.9", "Accept-Encoding": "gzip, deflate, br, zstd", "DNT": "1", "Sec-GPC": "1", "Connection": "keep-alive", "Referer": "http://localhost:8080/calendar", "Sec-Fetch-Dest": "image", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=6", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 404, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:46:37 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "62"}, "body": "{\"error\": {\"code\": \"not_found\", \"message\": \"Route not found\"}}", "body_bytes": 62, "body_truncated": false}} +{"ts": "2026-03-30T14:46:38+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/public/events?view=month&date=2026-04-01", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:148.0) Gecko/20100101 Firefox/148.0", "Accept": "*/*", "Accept-Language": "en-GB,en;q=0.9", "Accept-Encoding": "gzip, deflate, br, zstd", "Referer": "http://localhost:8080/calendar", "DNT": "1", "Sec-GPC": "1", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=0"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:46:38 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "9409"}, "body": "{\"data\": [{\"event_id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-01T10:00:00+01:00\", \"occurrence_end\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\"}, {\"event_id\": 10, \"uid\": \"fixture-ce-010@calendar-wp-plugin\", \"title\": \"Therapy Session\", \"description\": \"Used for single-occurrence delete exception tests.\", \"location\": \"Clinic\", \"category\": \"Health\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-03T14:00:00+01:00\", \"occurrence_end\": \"2026-04-03T15:00:00+01:00\", \"repeat_type\": \"weekly\"}, {\"event_id\": 3, \"uid\": \"fixture-ce-003@calendar-wp-plugin\", \"title\": \"Daily Standup\", \"description\": \"15 minute sync.\", \"location\": \"Online\", \"category\": \"Team\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-06T09:00:00+01:00\", \"occurrence_end\": \"2026-04-06T09:15:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 3, \"uid\": \"fixture-ce-003@calendar-wp-plugin\", \"title\": \"Daily Standup\", \"description\": \"15 minute sync.\", \"location\": \"Online\", \"category\": \"Team\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T14:46:39+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/public/events?view=month&date=2026-05-01", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:148.0) Gecko/20100101 Firefox/148.0", "Accept": "*/*", "Accept-Language": "en-GB,en;q=0.9", "Accept-Encoding": "gzip, deflate, br, zstd", "Referer": "http://localhost:8080/calendar", "DNT": "1", "Sec-GPC": "1", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=0"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:46:39 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "6046"}, "body": "{\"data\": [{\"event_id\": 10, \"uid\": \"fixture-ce-010@calendar-wp-plugin\", \"title\": \"Therapy Session\", \"description\": \"Used for single-occurrence delete exception tests.\", \"location\": \"Clinic\", \"category\": \"Health\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-01T14:00:00+01:00\", \"occurrence_end\": \"2026-05-01T15:00:00+01:00\", \"repeat_type\": \"weekly\"}, {\"event_id\": 2, \"uid\": \"fixture-ce-002@calendar-wp-plugin\", \"title\": \"Office Closed\", \"description\": \"Public holiday closure.\", \"location\": \"HQ\", \"category\": \"Operations\", \"all_day_event\": true, \"occurrence_start\": \"2026-05-04T00:00:00+00:00\", \"occurrence_end\": \"2026-05-05T00:00:00+00:00\", \"repeat_type\": \"none\"}, {\"event_id\": 7, \"uid\": \"fixture-ce-007@calendar-wp-plugin\", \"title\": \"Fortnightly Coaching\", \"description\": \"Coaching check-in.\", \"location\": \"Online\", \"category\": \"Training\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-05T15:00:00+01:00\", \"occurrence_end\": \"2026-05-05T16:00:00+01:00\", \"repeat_type\": \"custom\"}, {\"event_id\": 18, \"uid\": \"smoke-cancelled-occurrence-001\", \"title\": \"Smoke Cancelled Occurrence\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-06T1", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T14:47:00+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/public/events?view=week&date=2026-04-27", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:148.0) Gecko/20100101 Firefox/148.0", "Accept": "*/*", "Accept-Language": "en-GB,en;q=0.9", "Accept-Encoding": "gzip, deflate, br, zstd", "Referer": "http://localhost:8080/calendar", "DNT": "1", "Sec-GPC": "1", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=0"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:47:00 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "1345"}, "body": "{\"data\": [{\"event_id\": 4, \"uid\": \"fixture-ce-004@calendar-wp-plugin\", \"title\": \"Community Lunch\", \"description\": \"Weekly community lunch.\", \"location\": \"Cafeteria\", \"category\": \"Community\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-29T12:30:00+01:00\", \"occurrence_end\": \"2026-04-29T13:30:00+01:00\", \"repeat_type\": \"weekly\"}, {\"event_id\": 18, \"uid\": \"smoke-cancelled-occurrence-001\", \"title\": \"Smoke Cancelled Occurrence\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-29T12:30:00+01:00\", \"occurrence_end\": \"2026-04-29T13:30:00+01:00\", \"repeat_type\": \"weekly\"}, {\"event_id\": 5, \"uid\": \"fixture-ce-005@calendar-wp-plugin\", \"title\": \"Finance Close\", \"description\": \"Month-end close process.\", \"location\": \"Finance Office\", \"category\": \"Finance\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-30T17:00:00+01:00\", \"occurrence_end\": \"2026-04-30T18:00:00+01:00\", \"repeat_type\": \"monthly\"}, {\"event_id\": 10, \"uid\": \"fixture-ce-010@calendar-wp-plugin\", \"title\": \"Therapy Session\", \"description\": \"Used for single-occurrence delete exception tests.\", \"location\": \"Clinic\", \"category\": \"Health\", \"all_day_event\": false, \"occurrence_sta ...(truncated)", "body_bytes": 1345, "body_truncated": false}} +{"ts": "2026-03-30T14:47:03+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/public/events?view=month&date=2026-04-01", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:148.0) Gecko/20100101 Firefox/148.0", "Accept": "*/*", "Accept-Language": "en-GB,en;q=0.9", "Accept-Encoding": "gzip, deflate, br, zstd", "Referer": "http://localhost:8080/calendar", "DNT": "1", "Sec-GPC": "1", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=0"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:47:03 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "9409"}, "body": "{\"data\": [{\"event_id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-01T10:00:00+01:00\", \"occurrence_end\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\"}, {\"event_id\": 10, \"uid\": \"fixture-ce-010@calendar-wp-plugin\", \"title\": \"Therapy Session\", \"description\": \"Used for single-occurrence delete exception tests.\", \"location\": \"Clinic\", \"category\": \"Health\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-03T14:00:00+01:00\", \"occurrence_end\": \"2026-04-03T15:00:00+01:00\", \"repeat_type\": \"weekly\"}, {\"event_id\": 3, \"uid\": \"fixture-ce-003@calendar-wp-plugin\", \"title\": \"Daily Standup\", \"description\": \"15 minute sync.\", \"location\": \"Online\", \"category\": \"Team\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-06T09:00:00+01:00\", \"occurrence_end\": \"2026-04-06T09:15:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 3, \"uid\": \"fixture-ce-003@calendar-wp-plugin\", \"title\": \"Daily Standup\", \"description\": \"15 minute sync.\", \"location\": \"Online\", \"category\": \"Team\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T14:47:06+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/public/events?view=month&date=2026-05-01", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:148.0) Gecko/20100101 Firefox/148.0", "Accept": "*/*", "Accept-Language": "en-GB,en;q=0.9", "Accept-Encoding": "gzip, deflate, br, zstd", "Referer": "http://localhost:8080/calendar", "DNT": "1", "Sec-GPC": "1", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=0"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:47:06 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "6046"}, "body": "{\"data\": [{\"event_id\": 10, \"uid\": \"fixture-ce-010@calendar-wp-plugin\", \"title\": \"Therapy Session\", \"description\": \"Used for single-occurrence delete exception tests.\", \"location\": \"Clinic\", \"category\": \"Health\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-01T14:00:00+01:00\", \"occurrence_end\": \"2026-05-01T15:00:00+01:00\", \"repeat_type\": \"weekly\"}, {\"event_id\": 2, \"uid\": \"fixture-ce-002@calendar-wp-plugin\", \"title\": \"Office Closed\", \"description\": \"Public holiday closure.\", \"location\": \"HQ\", \"category\": \"Operations\", \"all_day_event\": true, \"occurrence_start\": \"2026-05-04T00:00:00+00:00\", \"occurrence_end\": \"2026-05-05T00:00:00+00:00\", \"repeat_type\": \"none\"}, {\"event_id\": 7, \"uid\": \"fixture-ce-007@calendar-wp-plugin\", \"title\": \"Fortnightly Coaching\", \"description\": \"Coaching check-in.\", \"location\": \"Online\", \"category\": \"Training\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-05T15:00:00+01:00\", \"occurrence_end\": \"2026-05-05T16:00:00+01:00\", \"repeat_type\": \"custom\"}, {\"event_id\": 18, \"uid\": \"smoke-cancelled-occurrence-001\", \"title\": \"Smoke Cancelled Occurrence\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-06T1", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T14:47:53+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/10.ics", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "14137", "If-Match": "\"f22edec26fd96749\"", "Origin": "http://localhost:8080", "DNT": "1", "Authorization": "***", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "BEGIN:VCALENDAR\r\nPRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN\r\nVERSION:2.0\r\nBEGIN:VTIMEZONE\r\nTZID:Europe/London\r\nX-TZINFO:Europe/London[2025b]\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:-000115\r\nTZNAME:Europe/London(STD)\r\nDTSTART:18471201T000000\r\nRDATE:18471201T000000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19160521T020000\r\nRDATE:19160521T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19161001T030000\r\nRDATE:19161001T030000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19170408T020000\r\nRDATE:19170408T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19170917T030000\r\nRDATE:19170917T030000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19180324T020000\r\nRDATE:19180324T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19180930T030000\r\nRRULE:FREQ=YEARLY;BYMONTH=9;BYDAY=-1MO;UNTIL=19190929 ...(truncated)", "body_bytes": 4097, "body_truncated": true}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:47:53 GMT", "ETag": "\"e6fa695514466881\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T14:47:53+00:00", "client": "127.0.0.1", "method": "REPORT", "path": "/caldav/calendars/public/", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/xml; charset=utf-8", "Content-Length": "232", "Depth": "1", "Origin": "http://localhost:8080", "DNT": "1", "Authorization": "***", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "\n/caldav/calendars/public/10.ics", "body_bytes": 232, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:47:53 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "1070"}, "body": "\n\n\n /caldav/calendars/public/10.ics\n \n \n \"e6fa695514466881\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-010@calendar-wp-plugin\r\nDTSTAMP:20260330T144753Z\r\nSUMMARY:Therapy Session\r\nDESCRIPTION:Used for single-occurrence delete exception tests.\r\nLOCATION:Clinic\r\nCATEGORIES:Health\r\nDTSTART;TZID=Europe/London:20260403T140000\r\nDTEND;TZID=Europe/London:20260403T150000\r\nRRULE:FREQ=WEEKLY;COUNT=8\r\nEXDATE;TZID=Europe/London:20260508T140000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n", "body_bytes": 1070, "body_truncated": false}} +{"ts": "2026-03-30T14:47:53+00:00", "client": "127.0.0.1", "method": "PROPFIND", "path": "/caldav/calendars/public/", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/xml; charset=utf-8", "Content-Length": "144", "Depth": "1", "Origin": "http://localhost:8080", "DNT": "1", "Authorization": "***", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "\n", "body_bytes": 144, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:47:53 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "6130"}, "body": "\n\n\n /caldav/calendars/public/\n \n \n Public Calendar\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/1.ics\n \n \n text/calendar; charset=utf-8\"53fb93409a868c3b\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/2.ics\n \n \n text/calendar; charset=utf-8\"15e21dd53bfeddfc\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/3.ics\n \n \n ...(truncated)", "body_bytes": 3919, "body_truncated": true}} +{"ts": "2026-03-30T14:47:58+00:00", "client": "127.0.0.1", "method": "GET", "path": "/calendar", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:148.0) Gecko/20100101 Firefox/148.0", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", "Accept-Language": "en-GB,en;q=0.9", "Accept-Encoding": "gzip, deflate, br, zstd", "Referer": "http://localhost:8080/wp-admin/admin.php?page=calendar-users&as=admin", "DNT": "1", "Sec-GPC": "1", "Connection": "keep-alive", "Upgrade-Insecure-Requests": "1", "Sec-Fetch-Dest": "document", "Sec-Fetch-Mode": "navigate", "Sec-Fetch-Site": "same-origin", "Sec-Fetch-User": "?1", "Priority": "u=0, i"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:47:58 GMT", "Content-Type": "text/html; charset=utf-8", "Content-Length": "43447"}, "body": "[omitted html payload: 3934 bytes]", "body_bytes": 3934, "body_truncated": true}} +{"ts": "2026-03-30T14:47:58+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/public/events?view=month&date=2026-03-30", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:148.0) Gecko/20100101 Firefox/148.0", "Accept": "*/*", "Accept-Language": "en-GB,en;q=0.9", "Accept-Encoding": "gzip, deflate, br, zstd", "Referer": "http://localhost:8080/calendar", "DNT": "1", "Sec-GPC": "1", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:47:58 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "10580"}, "body": "{\"data\": [{\"event_id\": 16, \"uid\": \"916c9cd30540959ad63f@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-02T10:00:00+00:00\", \"occurrence_end\": \"2026-03-02T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-02T10:00:00+01:00\", \"occurrence_end\": \"2026-03-02T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"916c9cd30540959ad63f@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-03T10:00:00+00:00\", \"occurrence_end\": \"2026-03-03T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-03T10:00:00+01:00\", \"occurrence_end\": \"2026-03-03T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"ui", "body_bytes": 3927, "body_truncated": true}} +{"ts": "2026-03-30T14:48:00+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/public/events?view=month&date=2026-04-01", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:148.0) Gecko/20100101 Firefox/148.0", "Accept": "*/*", "Accept-Language": "en-GB,en;q=0.9", "Accept-Encoding": "gzip, deflate, br, zstd", "Referer": "http://localhost:8080/calendar", "DNT": "1", "Sec-GPC": "1", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=0"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:48:00 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "9409"}, "body": "{\"data\": [{\"event_id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-01T10:00:00+01:00\", \"occurrence_end\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\"}, {\"event_id\": 10, \"uid\": \"fixture-ce-010@calendar-wp-plugin\", \"title\": \"Therapy Session\", \"description\": \"Used for single-occurrence delete exception tests.\", \"location\": \"Clinic\", \"category\": \"Health\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-03T14:00:00+01:00\", \"occurrence_end\": \"2026-04-03T15:00:00+01:00\", \"repeat_type\": \"weekly\"}, {\"event_id\": 3, \"uid\": \"fixture-ce-003@calendar-wp-plugin\", \"title\": \"Daily Standup\", \"description\": \"15 minute sync.\", \"location\": \"Online\", \"category\": \"Team\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-06T09:00:00+01:00\", \"occurrence_end\": \"2026-04-06T09:15:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 3, \"uid\": \"fixture-ce-003@calendar-wp-plugin\", \"title\": \"Daily Standup\", \"description\": \"15 minute sync.\", \"location\": \"Online\", \"category\": \"Team\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T14:48:01+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/public/events?view=month&date=2026-05-01", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:148.0) Gecko/20100101 Firefox/148.0", "Accept": "*/*", "Accept-Language": "en-GB,en;q=0.9", "Accept-Encoding": "gzip, deflate, br, zstd", "Referer": "http://localhost:8080/calendar", "DNT": "1", "Sec-GPC": "1", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=0"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:48:01 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "5698"}, "body": "{\"data\": [{\"event_id\": 10, \"uid\": \"fixture-ce-010@calendar-wp-plugin\", \"title\": \"Therapy Session\", \"description\": \"Used for single-occurrence delete exception tests.\", \"location\": \"Clinic\", \"category\": \"Health\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-01T14:00:00+01:00\", \"occurrence_end\": \"2026-05-01T15:00:00+01:00\", \"repeat_type\": \"weekly\"}, {\"event_id\": 2, \"uid\": \"fixture-ce-002@calendar-wp-plugin\", \"title\": \"Office Closed\", \"description\": \"Public holiday closure.\", \"location\": \"HQ\", \"category\": \"Operations\", \"all_day_event\": true, \"occurrence_start\": \"2026-05-04T00:00:00+00:00\", \"occurrence_end\": \"2026-05-05T00:00:00+00:00\", \"repeat_type\": \"none\"}, {\"event_id\": 7, \"uid\": \"fixture-ce-007@calendar-wp-plugin\", \"title\": \"Fortnightly Coaching\", \"description\": \"Coaching check-in.\", \"location\": \"Online\", \"category\": \"Training\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-05T15:00:00+01:00\", \"occurrence_end\": \"2026-05-05T16:00:00+01:00\", \"repeat_type\": \"custom\"}, {\"event_id\": 18, \"uid\": \"smoke-cancelled-occurrence-001\", \"title\": \"Smoke Cancelled Occurrence\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-06T1", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T14:48:22+00:00", "client": "127.0.0.1", "method": "PROPFIND", "path": "/caldav/calendars/public/", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/xml; charset=utf-8", "Content-Length": "144", "Depth": "1", "Origin": "http://localhost:8080", "DNT": "1", "Authorization": "***", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "\n", "body_bytes": 144, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:48:22 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "6130"}, "body": "\n\n\n /caldav/calendars/public/\n \n \n Public Calendar\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/1.ics\n \n \n text/calendar; charset=utf-8\"55da5569707995bb\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/2.ics\n \n \n text/calendar; charset=utf-8\"527d6e363f17bcfa\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/3.ics\n \n \n ...(truncated)", "body_bytes": 3919, "body_truncated": true}} +{"ts": "2026-03-30T14:48:22+00:00", "client": "127.0.0.1", "method": "REPORT", "path": "/caldav/calendars/public/", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/xml; charset=utf-8", "Content-Length": "655", "Depth": "1", "Origin": "http://localhost:8080", "DNT": "1", "Authorization": "***", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "\n/caldav/calendars/public/10.ics/caldav/calendars/public/9.ics/caldav/calendars/public/8.ics/caldav/calendars/public/7.ics/caldav/calendars/public/6.ics/caldav/calendars/public/5.ics/caldav/calendars/public/4.ics/caldav/calendars/public/3.ics/caldav/calendars/public/2.ics/caldav/calendars/public/1.ics", "body_bytes": 655, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:48:22 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "8815"}, "body": "\n\n\n /caldav/calendars/public/1.ics\n \n \n \"55da5569707995bb\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-001@calendar-wp-plugin\r\nDTSTAMP:20260330T144822Z\r\nSUMMARY:Board Meeting\r\nDESCRIPTION:Quarterly board review.\r\nLOCATION:Room A\r\nCATEGORIES:Governance\r\nDTSTART;TZID=Europe/London:20260401T100000\r\nDTEND;TZID=Europe/London:20260401T113000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/2.ics\n \n \n \"527d6e363f17bcfa\"\n ...(truncated)", "body_bytes": 3919, "body_truncated": true}} +{"ts": "2026-03-30T14:48:32+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/7.ics", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "14137", "If-Match": "\"df74a2a1b310e407\"", "Origin": "http://localhost:8080", "DNT": "1", "Authorization": "***", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "BEGIN:VCALENDAR\r\nPRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN\r\nVERSION:2.0\r\nBEGIN:VTIMEZONE\r\nTZID:Europe/London\r\nX-TZINFO:Europe/London[2025b]\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:-000115\r\nTZNAME:Europe/London(STD)\r\nDTSTART:18471201T000000\r\nRDATE:18471201T000000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19160521T020000\r\nRDATE:19160521T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19161001T030000\r\nRDATE:19161001T030000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19170408T020000\r\nRDATE:19170408T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19170917T030000\r\nRDATE:19170917T030000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19180324T020000\r\nRDATE:19180324T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19180930T030000\r\nRRULE:FREQ=YEARLY;BYMONTH=9;BYDAY=-1MO;UNTIL=19190929 ...(truncated)", "body_bytes": 4097, "body_truncated": true}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:48:32 GMT", "ETag": "\"b18f9e3c652a40d4\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T14:48:32+00:00", "client": "127.0.0.1", "method": "REPORT", "path": "/caldav/calendars/public/", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/xml; charset=utf-8", "Content-Length": "231", "Depth": "1", "Origin": "http://localhost:8080", "DNT": "1", "Authorization": "***", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "\n/caldav/calendars/public/7.ics", "body_bytes": 231, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:48:32 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "1069"}, "body": "\n\n\n /caldav/calendars/public/7.ics\n \n \n \"b18f9e3c652a40d4\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-007@calendar-wp-plugin\r\nDTSTAMP:20260330T144832Z\r\nSUMMARY:Fortnightly Coaching\r\nDESCRIPTION:Coaching check-in.\r\nLOCATION:Online\r\nCATEGORIES:Training\r\nDTSTART;TZID=Europe/London:20260407T150000\r\nDTEND;TZID=Europe/London:20260407T160000\r\nRRULE:FREQ=WEEKLY;INTERVAL=2;UNTIL=20260731T235959\r\nEXDATE;TZID=Europe/London:20260519T150000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n", "body_bytes": 1069, "body_truncated": false}} +{"ts": "2026-03-30T14:48:32+00:00", "client": "127.0.0.1", "method": "PROPFIND", "path": "/caldav/calendars/public/", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/xml; charset=utf-8", "Content-Length": "144", "Depth": "1", "Origin": "http://localhost:8080", "DNT": "1", "Authorization": "***", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "\n", "body_bytes": 144, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:48:32 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "6130"}, "body": "\n\n\n /caldav/calendars/public/\n \n \n Public Calendar\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/1.ics\n \n \n text/calendar; charset=utf-8\"55da5569707995bb\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/2.ics\n \n \n text/calendar; charset=utf-8\"527d6e363f17bcfa\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/3.ics\n \n \n ...(truncated)", "body_bytes": 3919, "body_truncated": true}} +{"ts": "2026-03-30T14:48:37+00:00", "client": "127.0.0.1", "method": "GET", "path": "/calendar", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:148.0) Gecko/20100101 Firefox/148.0", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", "Accept-Language": "en-GB,en;q=0.9", "Accept-Encoding": "gzip, deflate, br, zstd", "Referer": "http://localhost:8080/wp-admin/admin.php?page=calendar-users&as=admin", "DNT": "1", "Sec-GPC": "1", "Connection": "keep-alive", "Upgrade-Insecure-Requests": "1", "Sec-Fetch-Dest": "document", "Sec-Fetch-Mode": "navigate", "Sec-Fetch-Site": "same-origin", "Sec-Fetch-User": "?1", "Priority": "u=0, i", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:48:37 GMT", "Content-Type": "text/html; charset=utf-8", "Content-Length": "43447"}, "body": "[omitted html payload: 3934 bytes]", "body_bytes": 3934, "body_truncated": true}} +{"ts": "2026-03-30T14:48:37+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/public/events?view=month&date=2026-03-30", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:148.0) Gecko/20100101 Firefox/148.0", "Accept": "*/*", "Accept-Language": "en-GB,en;q=0.9", "Accept-Encoding": "gzip, deflate, br, zstd", "Referer": "http://localhost:8080/calendar", "DNT": "1", "Sec-GPC": "1", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:48:37 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "10580"}, "body": "{\"data\": [{\"event_id\": 16, \"uid\": \"916c9cd30540959ad63f@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-02T10:00:00+00:00\", \"occurrence_end\": \"2026-03-02T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-02T10:00:00+01:00\", \"occurrence_end\": \"2026-03-02T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"916c9cd30540959ad63f@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-03T10:00:00+00:00\", \"occurrence_end\": \"2026-03-03T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-03T10:00:00+01:00\", \"occurrence_end\": \"2026-03-03T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"ui", "body_bytes": 3927, "body_truncated": true}} +{"ts": "2026-03-30T14:48:37+00:00", "client": "127.0.0.1", "method": "GET", "path": "/favicon.ico", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:148.0) Gecko/20100101 Firefox/148.0", "Accept": "image/avif,image/jxl,image/webp,image/png,image/svg+xml,image/*;q=0.8,*/*;q=0.5", "Accept-Language": "en-GB,en;q=0.9", "Accept-Encoding": "gzip, deflate, br, zstd", "DNT": "1", "Sec-GPC": "1", "Connection": "keep-alive", "Referer": "http://localhost:8080/calendar", "Sec-Fetch-Dest": "image", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=6", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 404, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:48:37 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "62"}, "body": "{\"error\": {\"code\": \"not_found\", \"message\": \"Route not found\"}}", "body_bytes": 62, "body_truncated": false}} +{"ts": "2026-03-30T14:48:39+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/public/events?view=month&date=2026-04-01", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:148.0) Gecko/20100101 Firefox/148.0", "Accept": "*/*", "Accept-Language": "en-GB,en;q=0.9", "Accept-Encoding": "gzip, deflate, br, zstd", "Referer": "http://localhost:8080/calendar", "DNT": "1", "Sec-GPC": "1", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=0"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:48:39 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "9409"}, "body": "{\"data\": [{\"event_id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-01T10:00:00+01:00\", \"occurrence_end\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\"}, {\"event_id\": 10, \"uid\": \"fixture-ce-010@calendar-wp-plugin\", \"title\": \"Therapy Session\", \"description\": \"Used for single-occurrence delete exception tests.\", \"location\": \"Clinic\", \"category\": \"Health\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-03T14:00:00+01:00\", \"occurrence_end\": \"2026-04-03T15:00:00+01:00\", \"repeat_type\": \"weekly\"}, {\"event_id\": 3, \"uid\": \"fixture-ce-003@calendar-wp-plugin\", \"title\": \"Daily Standup\", \"description\": \"15 minute sync.\", \"location\": \"Online\", \"category\": \"Team\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-06T09:00:00+01:00\", \"occurrence_end\": \"2026-04-06T09:15:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 3, \"uid\": \"fixture-ce-003@calendar-wp-plugin\", \"title\": \"Daily Standup\", \"description\": \"15 minute sync.\", \"location\": \"Online\", \"category\": \"Team\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T14:48:41+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/public/events?view=month&date=2026-05-01", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:148.0) Gecko/20100101 Firefox/148.0", "Accept": "*/*", "Accept-Language": "en-GB,en;q=0.9", "Accept-Encoding": "gzip, deflate, br, zstd", "Referer": "http://localhost:8080/calendar", "DNT": "1", "Sec-GPC": "1", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=0"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:48:41 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "6050"}, "body": "{\"data\": [{\"event_id\": 10, \"uid\": \"fixture-ce-010@calendar-wp-plugin\", \"title\": \"Therapy Session\", \"description\": \"Used for single-occurrence delete exception tests.\", \"location\": \"Clinic\", \"category\": \"Health\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-01T14:00:00+01:00\", \"occurrence_end\": \"2026-05-01T15:00:00+01:00\", \"repeat_type\": \"weekly\"}, {\"event_id\": 2, \"uid\": \"fixture-ce-002@calendar-wp-plugin\", \"title\": \"Office Closed\", \"description\": \"Public holiday closure.\", \"location\": \"HQ\", \"category\": \"Operations\", \"all_day_event\": true, \"occurrence_start\": \"2026-05-04T00:00:00+00:00\", \"occurrence_end\": \"2026-05-05T00:00:00+00:00\", \"repeat_type\": \"none\"}, {\"event_id\": 7, \"uid\": \"fixture-ce-007@calendar-wp-plugin\", \"title\": \"Fortnightly Coaching\", \"description\": \"Coaching check-in.\", \"location\": \"Online\", \"category\": \"Training\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-05T15:00:00+01:00\", \"occurrence_end\": \"2026-05-05T16:00:00+01:00\", \"repeat_type\": \"weekly\"}, {\"event_id\": 4, \"uid\": \"fixture-ce-004@calendar-wp-plugin\", \"title\": \"Community Lunch\", \"description\": \"Weekly community lunch.\", \"location\": \"Cafeteria\", \"category\": \"Community\", \"all_day_event\": false, \"", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T14:49:47+00:00", "client": "127.0.0.1", "method": "GET", "path": "/calendar.ics", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:148.0) Gecko/20100101 Firefox/148.0", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", "Accept-Language": "en-GB,en;q=0.9", "Accept-Encoding": "gzip, deflate, br, zstd", "DNT": "1", "Sec-GPC": "1", "Connection": "keep-alive", "Upgrade-Insecure-Requests": "1", "Sec-Fetch-Dest": "document", "Sec-Fetch-Mode": "navigate", "Sec-Fetch-Site": "none", "Priority": "u=0, i"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:49:47 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"bca3fc6eaa239803\"", "Content-Length": "5115"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nCALSCALE:GREGORIAN\r\nX-WR-TIMEZONE:Europe/London\r\nBEGIN:VEVENT\r\nUID:fixture-ce-001@calendar-wp-plugin\r\nDTSTAMP:20260330T144947Z\r\nSUMMARY:Board Meeting\r\nDESCRIPTION:Quarterly board review.\r\nLOCATION:Room A\r\nCATEGORIES:Governance\r\nDTSTART;TZID=Europe/London:20260401T100000\r\nDTEND;TZID=Europe/London:20260401T113000\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-002@calendar-wp-plugin\r\nDTSTAMP:20260330T144947Z\r\nSUMMARY:Office Closed\r\nDESCRIPTION:Public holiday closure.\r\nLOCATION:HQ\r\nCATEGORIES:Operations\r\nDTSTART;VALUE=DATE:20260504\r\nDTEND;VALUE=DATE:20260505\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-003@calendar-wp-plugin\r\nDTSTAMP:20260330T144947Z\r\nSUMMARY:Daily Standup\r\nDESCRIPTION:15 minute sync.\r\nLOCATION:Online\r\nCATEGORIES:Team\r\nDTSTART;TZID=Europe/London:20260406T090000\r\nDTEND;TZID=Europe/London:20260406T091500\r\nRRULE:FREQ=DAILY;COUNT=10\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-004@calendar-wp-plugin\r\nDTSTAMP:20260330T144947Z\r\nSUMMARY:Community Lunch\r\nDESCRIPTION:Weekly community lunch.\r\nLOCATION:Cafeteria\r\nCATEGORIES:Community\r\nDTSTART;TZID=Europe/London:20260408T123000\r\nDTEND;TZID=Europe/London:20260408T133000\r\n ...(truncated)", "body_bytes": 3905, "body_truncated": true}} +{"ts": "2026-03-30T14:50:56+00:00", "client": "127.0.0.1", "method": "GET", "path": "/calendar.ics", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/calendar, application/ics, text/plain;q=0.9", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "DNT": "1", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:50:56 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"584d529babd5cae6\"", "Content-Length": "5115"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nCALSCALE:GREGORIAN\r\nX-WR-TIMEZONE:Europe/London\r\nBEGIN:VEVENT\r\nUID:fixture-ce-001@calendar-wp-plugin\r\nDTSTAMP:20260330T145056Z\r\nSUMMARY:Board Meeting\r\nDESCRIPTION:Quarterly board review.\r\nLOCATION:Room A\r\nCATEGORIES:Governance\r\nDTSTART;TZID=Europe/London:20260401T100000\r\nDTEND;TZID=Europe/London:20260401T113000\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-002@calendar-wp-plugin\r\nDTSTAMP:20260330T145056Z\r\nSUMMARY:Office Closed\r\nDESCRIPTION:Public holiday closure.\r\nLOCATION:HQ\r\nCATEGORIES:Operations\r\nDTSTART;VALUE=DATE:20260504\r\nDTEND;VALUE=DATE:20260505\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-003@calendar-wp-plugin\r\nDTSTAMP:20260330T145056Z\r\nSUMMARY:Daily Standup\r\nDESCRIPTION:15 minute sync.\r\nLOCATION:Online\r\nCATEGORIES:Team\r\nDTSTART;TZID=Europe/London:20260406T090000\r\nDTEND;TZID=Europe/London:20260406T091500\r\nRRULE:FREQ=DAILY;COUNT=10\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-004@calendar-wp-plugin\r\nDTSTAMP:20260330T145056Z\r\nSUMMARY:Community Lunch\r\nDESCRIPTION:Weekly community lunch.\r\nLOCATION:Cafeteria\r\nCATEGORIES:Community\r\nDTSTART;TZID=Europe/London:20260408T123000\r\nDTEND;TZID=Europe/London:20260408T133000\r\n ...(truncated)", "body_bytes": 3905, "body_truncated": true}} +{"ts": "2026-03-30T14:50:56+00:00", "client": "127.0.0.1", "method": "PROPFIND", "path": "/calendar.ics", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/xml; charset=utf-8", "Content-Length": "314", "Depth": "0", "Origin": "http://localhost:8080", "DNT": "1", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "\n", "body_bytes": 314, "body_truncated": false}, "response": {"status": 404, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:50:56 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "62"}, "body": "{\"error\": {\"code\": \"not_found\", \"message\": \"Route not found\"}}", "body_bytes": 62, "body_truncated": false}} +{"ts": "2026-03-30T14:50:56+00:00", "client": "127.0.0.1", "method": "PROPFIND", "path": "/.well-known/caldav", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/xml; charset=utf-8", "Content-Length": "314", "Depth": "0", "Origin": "http://localhost:8080", "DNT": "1", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "\n", "body_bytes": 314, "body_truncated": false}, "response": {"status": 404, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:50:56 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "62"}, "body": "{\"error\": {\"code\": \"not_found\", \"message\": \"Route not found\"}}", "body_bytes": 62, "body_truncated": false}} +{"ts": "2026-03-30T14:50:56+00:00", "client": "127.0.0.1", "method": "PROPFIND", "path": "/", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/xml; charset=utf-8", "Content-Length": "314", "Depth": "0", "Origin": "http://localhost:8080", "DNT": "1", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "\n", "body_bytes": 314, "body_truncated": false}, "response": {"status": 404, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:50:56 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "62"}, "body": "{\"error\": {\"code\": \"not_found\", \"message\": \"Route not found\"}}", "body_bytes": 62, "body_truncated": false}} +{"ts": "2026-03-30T14:50:59+00:00", "client": "127.0.0.1", "method": "GET", "path": "/calendar.ics", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/calendar,text/plain;q=0.8,*/*;q=0.5", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "DNT": "1", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "none", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 14:50:59 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"c047ccb96f0fbd50\"", "Content-Length": "5115"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nCALSCALE:GREGORIAN\r\nX-WR-TIMEZONE:Europe/London\r\nBEGIN:VEVENT\r\nUID:fixture-ce-001@calendar-wp-plugin\r\nDTSTAMP:20260330T145059Z\r\nSUMMARY:Board Meeting\r\nDESCRIPTION:Quarterly board review.\r\nLOCATION:Room A\r\nCATEGORIES:Governance\r\nDTSTART;TZID=Europe/London:20260401T100000\r\nDTEND;TZID=Europe/London:20260401T113000\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-002@calendar-wp-plugin\r\nDTSTAMP:20260330T145059Z\r\nSUMMARY:Office Closed\r\nDESCRIPTION:Public holiday closure.\r\nLOCATION:HQ\r\nCATEGORIES:Operations\r\nDTSTART;VALUE=DATE:20260504\r\nDTEND;VALUE=DATE:20260505\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-003@calendar-wp-plugin\r\nDTSTAMP:20260330T145059Z\r\nSUMMARY:Daily Standup\r\nDESCRIPTION:15 minute sync.\r\nLOCATION:Online\r\nCATEGORIES:Team\r\nDTSTART;TZID=Europe/London:20260406T090000\r\nDTEND;TZID=Europe/London:20260406T091500\r\nRRULE:FREQ=DAILY;COUNT=10\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-004@calendar-wp-plugin\r\nDTSTAMP:20260330T145059Z\r\nSUMMARY:Community Lunch\r\nDESCRIPTION:Weekly community lunch.\r\nLOCATION:Cafeteria\r\nCATEGORIES:Community\r\nDTSTART;TZID=Europe/London:20260408T123000\r\nDTEND;TZID=Europe/London:20260408T133000\r\n ...(truncated)", "body_bytes": 3905, "body_truncated": true}} +{"ts": "2026-03-30T15:21:37+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:21:37 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "6585"}, "body": "{\"data\": [{\"id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"start_datetime\": \"2026-04-01T10:00:00+01:00\", \"end_datetime\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"49da7b660d2eb90b\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T15:20:48+00:00\"}, {\"id\": 2, \"uid\": \"fixture-ce-002@calendar-wp-plugin\", \"title\": \"Office Closed\", \"description\": \"Public holiday closure.\", \"location\": \"HQ\", \"category\": \"Operations\", \"all_day_event\": true, \"start_datetime\": \"2026-05-04\", \"end_datetime\": \"2026-05-05\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"14f25", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T15:21:37+00:00", "client": "127.0.0.1", "method": "GET", "path": "/calendar.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:21:37 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"76e9509bbd366af2\"", "Content-Length": "3132"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nCALSCALE:GREGORIAN\r\nX-WR-TIMEZONE:Europe/London\r\nBEGIN:VEVENT\r\nUID:fixture-ce-001@calendar-wp-plugin\r\nDTSTAMP:20260330T152137Z\r\nSUMMARY:Board Meeting\r\nDESCRIPTION:Quarterly board review.\r\nLOCATION:Room A\r\nCATEGORIES:Governance\r\nDTSTART;TZID=Europe/London:20260401T100000\r\nDTEND;TZID=Europe/London:20260401T113000\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-002@calendar-wp-plugin\r\nDTSTAMP:20260330T152137Z\r\nSUMMARY:Office Closed\r\nDESCRIPTION:Public holiday closure.\r\nLOCATION:HQ\r\nCATEGORIES:Operations\r\nDTSTART;VALUE=DATE:20260504\r\nDTEND;VALUE=DATE:20260505\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-003@calendar-wp-plugin\r\nDTSTAMP:20260330T152137Z\r\nSUMMARY:Daily Standup\r\nDESCRIPTION:15 minute sync.\r\nLOCATION:Online\r\nCATEGORIES:Team\r\nDTSTART;TZID=Europe/London:20260406T090000\r\nDTEND;TZID=Europe/London:20260406T091500\r\nRRULE:FREQ=DAILY;COUNT=10\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-004@calendar-wp-plugin\r\nDTSTAMP:20260330T152137Z\r\nSUMMARY:Community Lunch\r\nDESCRIPTION:Weekly community lunch.\r\nLOCATION:Cafeteria\r\nCATEGORIES:Community\r\nDTSTART;TZID=Europe/London:20260408T123000\r\nDTEND;TZID=Europe/London:20260408T133000\r\n ...(truncated)", "body_bytes": 3132, "body_truncated": false}} +{"ts": "2026-03-30T15:21:37+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/1.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:21:37 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"49da7b660d2eb90b\"", "Content-Length": "364"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-001@calendar-wp-plugin\r\nDTSTAMP:20260330T152137Z\r\nSUMMARY:Board Meeting\r\nDESCRIPTION:Quarterly board review.\r\nLOCATION:Room A\r\nCATEGORIES:Governance\r\nDTSTART;TZID=Europe/London:20260401T100000\r\nDTEND;TZID=Europe/London:20260401T113000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 364, "body_truncated": false}} +{"ts": "2026-03-30T15:21:37+00:00", "client": "127.0.0.1", "method": "REPORT", "path": "/caldav/calendars/public/", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/xml; charset=utf-8", "Depth": "1", "Content-Length": "239"}, "body": "\n\n \n /caldav/calendars/public/4.ics\n\n", "body_bytes": 239, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:21:37 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "997"}, "body": "\n\n\n /caldav/calendars/public/4.ics\n \n \n \"fa44a32e2b54fe98\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-004@calendar-wp-plugin\r\nDTSTAMP:20260330T152137Z\r\nSUMMARY:Community Lunch\r\nDESCRIPTION:Weekly community lunch.\r\nLOCATION:Cafeteria\r\nCATEGORIES:Community\r\nDTSTART;TZID=Europe/London:20260408T123000\r\nDTEND;TZID=Europe/London:20260408T133000\r\nRRULE:FREQ=WEEKLY\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n", "body_bytes": 997, "body_truncated": false}} +{"ts": "2026-03-30T15:21:37+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/10/occurrences/2026-04-17T14:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:21:37 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T15:21:37+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-vtimezone.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "454"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN\nVERSION:2.0\nBEGIN:VTIMEZONE\nTZID:Europe/London\nBEGIN:STANDARD\nDTSTART:18471201T000000\nRRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=9\nTZOFFSETFROM:+0115\nTZOFFSETTO:+0000\nTZNAME:GMT\nEND:STANDARD\nEND:VTIMEZONE\nBEGIN:VEVENT\nUID:smoke-vtimezone-parser-001\nSUMMARY:Smoke VTIMEZONE Parse\nDTSTART;TZID=Europe/London:20260423T150000\nDTEND;TZID=Europe/London:20260423T160000\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 454, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:21:37 GMT", "ETag": "\"4740b3cc34a2cc56\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T15:21:37+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-vtimezone.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:21:37 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"4740b3cc34a2cc56\"", "Content-Length": "288"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-vtimezone-parser-001\r\nDTSTAMP:20260330T152137Z\r\nSUMMARY:Smoke VTIMEZONE Parse\r\nDTSTART;TZID=Europe/London:20260423T150000\r\nDTEND;TZID=Europe/London:20260423T160000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 288, "body_truncated": false}} +{"ts": "2026-03-30T15:21:37+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-monthly-nth.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "261"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-monthly-nth-001\nSUMMARY:Smoke Monthly Nth\nDTSTART;TZID=Europe/London:20260402T150000\nDTEND;TZID=Europe/London:20260402T160000\nRRULE:FREQ=MONTHLY;BYDAY=SU;BYSETPOS=4\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 261, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:21:37 GMT", "ETag": "\"2be1c63715d4fd1d\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T15:21:37+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-monthly-nth.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:21:37 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"2be1c63715d4fd1d\"", "Content-Length": "319"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-nth-001\r\nDTSTAMP:20260330T152137Z\r\nSUMMARY:Smoke Monthly Nth\r\nDTSTART;TZID=Europe/London:20260426T150000\r\nDTEND;TZID=Europe/London:20260426T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SU;BYSETPOS=4\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 319, "body_truncated": false}} +{"ts": "2026-03-30T15:21:37+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-monthly-ordinal.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "259"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-monthly-ordinal-001\nSUMMARY:Smoke Monthly Ordinal\nDTSTART;TZID=Europe/London:20260411T150000\nDTEND;TZID=Europe/London:20260411T160000\nRRULE:FREQ=MONTHLY;BYDAY=2SA\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 259, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:21:37 GMT", "ETag": "\"cfd13b1114a34fa6\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T15:21:37+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-monthly-ordinal.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:21:37 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"cfd13b1114a34fa6\"", "Content-Length": "327"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-ordinal-001\r\nDTSTAMP:20260330T152137Z\r\nSUMMARY:Smoke Monthly Ordinal\r\nDTSTART;TZID=Europe/London:20260411T150000\r\nDTEND;TZID=Europe/London:20260411T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SA;BYSETPOS=2\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 327, "body_truncated": false}} +{"ts": "2026-03-30T15:21:37+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-monthly-last.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "264"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-monthly-last-001\nSUMMARY:Smoke Monthly Last\nDTSTART;TZID=Europe/London:20260425T150000\nDTEND;TZID=Europe/London:20260425T160000\nRRULE:FREQ=MONTHLY;BYDAY=SA;BYSETPOS=-1\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 264, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:21:37 GMT", "ETag": "\"67d3e29da97d3248\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T15:21:37+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-monthly-last.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:21:37 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"67d3e29da97d3248\"", "Content-Length": "322"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-last-001\r\nDTSTAMP:20260330T152137Z\r\nSUMMARY:Smoke Monthly Last\r\nDTSTART;TZID=Europe/London:20260425T150000\r\nDTEND;TZID=Europe/London:20260425T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SA;BYSETPOS=-1\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 322, "body_truncated": false}} +{"ts": "2026-03-30T15:21:37+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "X-WP-User": "admin", "Content-Length": "362"}, "body": "{\"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\"}", "body_bytes": 362, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:21:37 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "673"}, "body": "{\"data\": {\"id\": 15, \"uid\": \"fd51d305728f2287d9eb@calendar-wp-plugin\", \"title\": \"Smoke 4th Sunday Anchor\", \"description\": \"anchor-normalization\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"start_datetime\": \"2026-05-24T15:00:00+01:00\", \"end_datetime\": \"2026-05-24T16:00:00+01:00\", \"repeat_type\": \"monthly\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"weekday_of_month\", \"repeat_nth_day\": null, \"repeat_nth_pos\": 4, \"repeat_nth_weekday\": 0, \"repeat_range_mode\": \"no_end\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"15832d093f3d58d8\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T15:21:37+00:00\"}}", "body_bytes": 673, "body_truncated": false}} +{"ts": "2026-03-30T15:21:37+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/15/occurrences/2026-05-24T15:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:21:37 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T15:21:37+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/15.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:21:37 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"0dd759e3487894b3\"", "Content-Length": "420"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fd51d305728f2287d9eb@calendar-wp-plugin\r\nDTSTAMP:20260330T152137Z\r\nSUMMARY:Smoke 4th Sunday Anchor\r\nDESCRIPTION:anchor-normalization\r\nDTSTART;TZID=Europe/London:20260524T150000\r\nDTEND;TZID=Europe/London:20260524T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SU;BYSETPOS=4\r\nEXDATE;TZID=Europe/London:20260524T150000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 420, "body_truncated": false}} +{"ts": "2026-03-30T15:21:37+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "X-WP-User": "admin", "Content-Length": "295"}, "body": "{\"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\"}", "body_bytes": 295, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:21:37 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "655"}, "body": "{\"data\": {\"id\": 16, \"uid\": \"bad9b0a4bf071e1cf316@calendar-wp-plugin\", \"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_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"until\", \"repeat_count\": null, \"repeat_until\": \"2026-03-19\", \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"0bf138cc955a676d\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T15:21:37+00:00\"}}", "body_bytes": 655, "body_truncated": false}} +{"ts": "2026-03-30T15:21:37+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/16/occurrences/2026-03-11T11:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:21:37 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T15:21:37+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/16/occurrences?from=2026-03-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:21:37 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "5212"}, "body": "{\"data\": [{\"event_id\": 16, \"uid\": \"bad9b0a4bf071e1cf316@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-02T10:00:00+00:00\", \"occurrence_end\": \"2026-03-02T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"bad9b0a4bf071e1cf316@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-03T10:00:00+00:00\", \"occurrence_end\": \"2026-03-03T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"bad9b0a4bf071e1cf316@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-04T10:00:00+00:00\", \"occurrence_end\": \"2026-03-04T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"bad9b0a4bf071e1cf316@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-05T10:00:00+00:00\", \"occurrence_end\": \"2026-03-05T11:00:00", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T15:21:37+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/16/occurrences/2026-03-10", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:21:37 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T15:21:37+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/16/occurrences?from=2026-03-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:21:37 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "4906"}, "body": "{\"data\": [{\"event_id\": 16, \"uid\": \"bad9b0a4bf071e1cf316@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-02T10:00:00+00:00\", \"occurrence_end\": \"2026-03-02T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"bad9b0a4bf071e1cf316@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-03T10:00:00+00:00\", \"occurrence_end\": \"2026-03-03T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"bad9b0a4bf071e1cf316@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-04T10:00:00+00:00\", \"occurrence_end\": \"2026-03-04T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"bad9b0a4bf071e1cf316@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-05T10:00:00+00:00\", \"occurrence_end\": \"2026-03-05T11:00:00", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T15:21:37+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-exdate-import.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "323"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-exdate-import-001\nSUMMARY:Smoke EXDATE Import\nDTSTART;TZID=Europe/London:20260302T100000\nDTEND;TZID=Europe/London:20260302T110000\nRRULE:FREQ=DAILY;UNTIL=20260319T235959\nEXDATE;TZID=Europe/London:20260310T100000,20260311T100000\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 323, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:21:37 GMT", "ETag": "\"ccabcbefa3ae427d\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T15:21:37+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:21:37 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "11006"}, "body": "{\"data\": [{\"id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"start_datetime\": \"2026-04-01T10:00:00+01:00\", \"end_datetime\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"49da7b660d2eb90b\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T15:20:48+00:00\"}, {\"id\": 2, \"uid\": \"fixture-ce-002@calendar-wp-plugin\", \"title\": \"Office Closed\", \"description\": \"Public holiday closure.\", \"location\": \"HQ\", \"category\": \"Operations\", \"all_day_event\": true, \"start_datetime\": \"2026-05-04\", \"end_datetime\": \"2026-05-05\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"14f25", "body_bytes": 3927, "body_truncated": true}} +{"ts": "2026-03-30T15:21:37+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/17/occurrences?from=2026-03-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:21:37 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "4474"}, "body": "{\"data\": [{\"event_id\": 17, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-02T10:00:00+01:00\", \"occurrence_end\": \"2026-03-02T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-03T10:00:00+01:00\", \"occurrence_end\": \"2026-03-03T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-04T10:00:00+01:00\", \"occurrence_end\": \"2026-03-04T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-05T10:00:00+01:00\", \"occurrence_end\": \"2026-03-05T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE ", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T15:21:37+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-cancelled-occurrence.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "467"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-cancelled-occurrence-001\nSUMMARY:Smoke Cancelled Occurrence\nDTSTART;TZID=Europe/London:20260408T123000\nDTEND;TZID=Europe/London:20260408T133000\nRRULE:FREQ=WEEKLY\nEND:VEVENT\nBEGIN:VEVENT\nUID:smoke-cancelled-occurrence-001\nRECURRENCE-ID;TZID=Europe/London:20260506T123000\nDTSTART;TZID=Europe/London:20260506T123000\nDTEND;TZID=Europe/London:20260506T133000\nSTATUS:CANCELLED\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 467, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:21:37 GMT", "ETag": "\"ecce7dcc33a5384e\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T15:21:37+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-cancelled-occurrence.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:21:37 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"ecce7dcc33a5384e\"", "Content-Length": "359"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-cancelled-occurrence-001\r\nDTSTAMP:20260330T152137Z\r\nSUMMARY:Smoke Cancelled Occurrence\r\nDTSTART;TZID=Europe/London:20260408T123000\r\nDTEND;TZID=Europe/London:20260408T133000\r\nRRULE:FREQ=WEEKLY\r\nEXDATE;TZID=Europe/London:20260506T123000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 359, "body_truncated": false}} +{"ts": "2026-03-30T15:21:37+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:21:37 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "11634"}, "body": "{\"data\": [{\"id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"start_datetime\": \"2026-04-01T10:00:00+01:00\", \"end_datetime\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"49da7b660d2eb90b\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T15:20:48+00:00\"}, {\"id\": 2, \"uid\": \"fixture-ce-002@calendar-wp-plugin\", \"title\": \"Office Closed\", \"description\": \"Public holiday closure.\", \"location\": \"HQ\", \"category\": \"Operations\", \"all_day_event\": true, \"start_datetime\": \"2026-05-04\", \"end_datetime\": \"2026-05-05\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"14f25", "body_bytes": 3927, "body_truncated": true}} +{"ts": "2026-03-30T15:21:37+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/18/occurrences?from=2026-05-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:21:37 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "892"}, "body": "{\"data\": [{\"event_id\": 18, \"uid\": \"smoke-cancelled-occurrence-001\", \"title\": \"Smoke Cancelled Occurrence\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-13T12:30:00+01:00\", \"occurrence_end\": \"2026-05-13T13:30:00+01:00\", \"repeat_type\": \"weekly\"}, {\"event_id\": 18, \"uid\": \"smoke-cancelled-occurrence-001\", \"title\": \"Smoke Cancelled Occurrence\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-20T12:30:00+01:00\", \"occurrence_end\": \"2026-05-20T13:30:00+01:00\", \"repeat_type\": \"weekly\"}, {\"event_id\": 18, \"uid\": \"smoke-cancelled-occurrence-001\", \"title\": \"Smoke Cancelled Occurrence\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-27T12:30:00+01:00\", \"occurrence_end\": \"2026-05-27T13:30:00+01:00\", \"repeat_type\": \"weekly\"}]}", "body_bytes": 892, "body_truncated": false}} +{"ts": "2026-03-30T15:21:38+00:00", "client": "127.0.0.1", "method": "GET", "path": "/calendar", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:21:38 GMT", "Content-Type": "text/html; charset=utf-8", "Content-Length": "43447"}, "body": "[omitted html payload: 3934 bytes]", "body_bytes": 3934, "body_truncated": true}} +{"ts": "2026-03-30T15:44:42+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:44:42 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "11634"}, "body": "{\"data\": [{\"id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"start_datetime\": \"2026-04-01T10:00:00+01:00\", \"end_datetime\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"49da7b660d2eb90b\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T15:20:48+00:00\"}, {\"id\": 2, \"uid\": \"fixture-ce-002@calendar-wp-plugin\", \"title\": \"Office Closed\", \"description\": \"Public holiday closure.\", \"location\": \"HQ\", \"category\": \"Operations\", \"all_day_event\": true, \"start_datetime\": \"2026-05-04\", \"end_datetime\": \"2026-05-05\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"14f25", "body_bytes": 3927, "body_truncated": true}} +{"ts": "2026-03-30T15:44:42+00:00", "client": "127.0.0.1", "method": "GET", "path": "/calendar.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:44:42 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"ec592f4817d711b6\"", "Content-Length": "5319"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nCALSCALE:GREGORIAN\r\nX-WR-TIMEZONE:Europe/London\r\nBEGIN:VEVENT\r\nUID:fixture-ce-001@calendar-wp-plugin\r\nDTSTAMP:20260330T154442Z\r\nSUMMARY:Board Meeting\r\nDESCRIPTION:Quarterly board review.\r\nLOCATION:Room A\r\nCATEGORIES:Governance\r\nDTSTART;TZID=Europe/London:20260401T100000\r\nDTEND;TZID=Europe/London:20260401T113000\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-002@calendar-wp-plugin\r\nDTSTAMP:20260330T154442Z\r\nSUMMARY:Office Closed\r\nDESCRIPTION:Public holiday closure.\r\nLOCATION:HQ\r\nCATEGORIES:Operations\r\nDTSTART;VALUE=DATE:20260504\r\nDTEND;VALUE=DATE:20260505\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-003@calendar-wp-plugin\r\nDTSTAMP:20260330T154442Z\r\nSUMMARY:Daily Standup\r\nDESCRIPTION:15 minute sync.\r\nLOCATION:Online\r\nCATEGORIES:Team\r\nDTSTART;TZID=Europe/London:20260406T090000\r\nDTEND;TZID=Europe/London:20260406T091500\r\nRRULE:FREQ=DAILY;COUNT=10\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-004@calendar-wp-plugin\r\nDTSTAMP:20260330T154442Z\r\nSUMMARY:Community Lunch\r\nDESCRIPTION:Weekly community lunch.\r\nLOCATION:Cafeteria\r\nCATEGORIES:Community\r\nDTSTART;TZID=Europe/London:20260408T123000\r\nDTEND;TZID=Europe/London:20260408T133000\r\n ...(truncated)", "body_bytes": 3905, "body_truncated": true}} +{"ts": "2026-03-30T15:44:42+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/1.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:44:42 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"49da7b660d2eb90b\"", "Content-Length": "364"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-001@calendar-wp-plugin\r\nDTSTAMP:20260330T154442Z\r\nSUMMARY:Board Meeting\r\nDESCRIPTION:Quarterly board review.\r\nLOCATION:Room A\r\nCATEGORIES:Governance\r\nDTSTART;TZID=Europe/London:20260401T100000\r\nDTEND;TZID=Europe/London:20260401T113000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 364, "body_truncated": false}} +{"ts": "2026-03-30T15:44:42+00:00", "client": "127.0.0.1", "method": "REPORT", "path": "/caldav/calendars/public/", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/xml; charset=utf-8", "Depth": "1", "Content-Length": "239"}, "body": "\n\n \n /caldav/calendars/public/4.ics\n\n", "body_bytes": 239, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:44:42 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "997"}, "body": "\n\n\n /caldav/calendars/public/4.ics\n \n \n \"fa44a32e2b54fe98\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-004@calendar-wp-plugin\r\nDTSTAMP:20260330T154442Z\r\nSUMMARY:Community Lunch\r\nDESCRIPTION:Weekly community lunch.\r\nLOCATION:Cafeteria\r\nCATEGORIES:Community\r\nDTSTART;TZID=Europe/London:20260408T123000\r\nDTEND;TZID=Europe/London:20260408T133000\r\nRRULE:FREQ=WEEKLY\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n", "body_bytes": 997, "body_truncated": false}} +{"ts": "2026-03-30T15:44:42+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/10/occurrences/2026-04-17T14:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 409, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:44:42 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "84"}, "body": "{\"error\": {\"code\": \"conflict_error\", \"message\": \"occurrence already has exception\"}}", "body_bytes": 84, "body_truncated": false}} +{"ts": "2026-03-30T15:45:58+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:45:58 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "6585"}, "body": "{\"data\": [{\"id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"start_datetime\": \"2026-04-01T10:00:00+01:00\", \"end_datetime\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"4cd72d479d507116\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T15:45:56+00:00\"}, {\"id\": 2, \"uid\": \"fixture-ce-002@calendar-wp-plugin\", \"title\": \"Office Closed\", \"description\": \"Public holiday closure.\", \"location\": \"HQ\", \"category\": \"Operations\", \"all_day_event\": true, \"start_datetime\": \"2026-05-04\", \"end_datetime\": \"2026-05-05\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"402be", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T15:45:58+00:00", "client": "127.0.0.1", "method": "GET", "path": "/calendar.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:45:58 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"0a0ab4f2ca5feab6\"", "Content-Length": "3132"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nCALSCALE:GREGORIAN\r\nX-WR-TIMEZONE:Europe/London\r\nBEGIN:VEVENT\r\nUID:fixture-ce-001@calendar-wp-plugin\r\nDTSTAMP:20260330T154558Z\r\nSUMMARY:Board Meeting\r\nDESCRIPTION:Quarterly board review.\r\nLOCATION:Room A\r\nCATEGORIES:Governance\r\nDTSTART;TZID=Europe/London:20260401T100000\r\nDTEND;TZID=Europe/London:20260401T113000\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-002@calendar-wp-plugin\r\nDTSTAMP:20260330T154558Z\r\nSUMMARY:Office Closed\r\nDESCRIPTION:Public holiday closure.\r\nLOCATION:HQ\r\nCATEGORIES:Operations\r\nDTSTART;VALUE=DATE:20260504\r\nDTEND;VALUE=DATE:20260505\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-003@calendar-wp-plugin\r\nDTSTAMP:20260330T154558Z\r\nSUMMARY:Daily Standup\r\nDESCRIPTION:15 minute sync.\r\nLOCATION:Online\r\nCATEGORIES:Team\r\nDTSTART;TZID=Europe/London:20260406T090000\r\nDTEND;TZID=Europe/London:20260406T091500\r\nRRULE:FREQ=DAILY;COUNT=10\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-004@calendar-wp-plugin\r\nDTSTAMP:20260330T154558Z\r\nSUMMARY:Community Lunch\r\nDESCRIPTION:Weekly community lunch.\r\nLOCATION:Cafeteria\r\nCATEGORIES:Community\r\nDTSTART;TZID=Europe/London:20260408T123000\r\nDTEND;TZID=Europe/London:20260408T133000\r\n ...(truncated)", "body_bytes": 3132, "body_truncated": false}} +{"ts": "2026-03-30T15:45:58+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/1.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:45:58 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"4cd72d479d507116\"", "Content-Length": "364"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-001@calendar-wp-plugin\r\nDTSTAMP:20260330T154558Z\r\nSUMMARY:Board Meeting\r\nDESCRIPTION:Quarterly board review.\r\nLOCATION:Room A\r\nCATEGORIES:Governance\r\nDTSTART;TZID=Europe/London:20260401T100000\r\nDTEND;TZID=Europe/London:20260401T113000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 364, "body_truncated": false}} +{"ts": "2026-03-30T15:45:58+00:00", "client": "127.0.0.1", "method": "REPORT", "path": "/caldav/calendars/public/", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/xml; charset=utf-8", "Depth": "1", "Content-Length": "239"}, "body": "\n\n \n /caldav/calendars/public/4.ics\n\n", "body_bytes": 239, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:45:58 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "997"}, "body": "\n\n\n /caldav/calendars/public/4.ics\n \n \n \"318ecd47574f78e8\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-004@calendar-wp-plugin\r\nDTSTAMP:20260330T154558Z\r\nSUMMARY:Community Lunch\r\nDESCRIPTION:Weekly community lunch.\r\nLOCATION:Cafeteria\r\nCATEGORIES:Community\r\nDTSTART;TZID=Europe/London:20260408T123000\r\nDTEND;TZID=Europe/London:20260408T133000\r\nRRULE:FREQ=WEEKLY\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n", "body_bytes": 997, "body_truncated": false}} +{"ts": "2026-03-30T15:45:58+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/10/occurrences/2026-04-17T14:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:45:58 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T15:45:58+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-vtimezone.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "454"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN\nVERSION:2.0\nBEGIN:VTIMEZONE\nTZID:Europe/London\nBEGIN:STANDARD\nDTSTART:18471201T000000\nRRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=9\nTZOFFSETFROM:+0115\nTZOFFSETTO:+0000\nTZNAME:GMT\nEND:STANDARD\nEND:VTIMEZONE\nBEGIN:VEVENT\nUID:smoke-vtimezone-parser-001\nSUMMARY:Smoke VTIMEZONE Parse\nDTSTART;TZID=Europe/London:20260423T150000\nDTEND;TZID=Europe/London:20260423T160000\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 454, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:45:58 GMT", "ETag": "\"d846dc38796f779e\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T15:45:58+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-vtimezone.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:45:58 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"d846dc38796f779e\"", "Content-Length": "288"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-vtimezone-parser-001\r\nDTSTAMP:20260330T154558Z\r\nSUMMARY:Smoke VTIMEZONE Parse\r\nDTSTART;TZID=Europe/London:20260423T150000\r\nDTEND;TZID=Europe/London:20260423T160000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 288, "body_truncated": false}} +{"ts": "2026-03-30T15:45:58+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-monthly-nth.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "261"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-monthly-nth-001\nSUMMARY:Smoke Monthly Nth\nDTSTART;TZID=Europe/London:20260402T150000\nDTEND;TZID=Europe/London:20260402T160000\nRRULE:FREQ=MONTHLY;BYDAY=SU;BYSETPOS=4\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 261, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:45:58 GMT", "ETag": "\"c80614f78133f2b1\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T15:45:58+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-monthly-nth.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:45:58 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"c80614f78133f2b1\"", "Content-Length": "319"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-nth-001\r\nDTSTAMP:20260330T154558Z\r\nSUMMARY:Smoke Monthly Nth\r\nDTSTART;TZID=Europe/London:20260426T150000\r\nDTEND;TZID=Europe/London:20260426T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SU;BYSETPOS=4\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 319, "body_truncated": false}} +{"ts": "2026-03-30T15:45:58+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-monthly-ordinal.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "259"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-monthly-ordinal-001\nSUMMARY:Smoke Monthly Ordinal\nDTSTART;TZID=Europe/London:20260411T150000\nDTEND;TZID=Europe/London:20260411T160000\nRRULE:FREQ=MONTHLY;BYDAY=2SA\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 259, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:45:58 GMT", "ETag": "\"40fb6f8f93bddee4\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T15:45:58+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-monthly-ordinal.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:45:58 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"40fb6f8f93bddee4\"", "Content-Length": "327"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-ordinal-001\r\nDTSTAMP:20260330T154558Z\r\nSUMMARY:Smoke Monthly Ordinal\r\nDTSTART;TZID=Europe/London:20260411T150000\r\nDTEND;TZID=Europe/London:20260411T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SA;BYSETPOS=2\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 327, "body_truncated": false}} +{"ts": "2026-03-30T15:45:58+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-monthly-last.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "264"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-monthly-last-001\nSUMMARY:Smoke Monthly Last\nDTSTART;TZID=Europe/London:20260425T150000\nDTEND;TZID=Europe/London:20260425T160000\nRRULE:FREQ=MONTHLY;BYDAY=SA;BYSETPOS=-1\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 264, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:45:58 GMT", "ETag": "\"e437395a7324b090\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T15:45:58+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-monthly-last.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:45:58 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"e437395a7324b090\"", "Content-Length": "322"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-last-001\r\nDTSTAMP:20260330T154558Z\r\nSUMMARY:Smoke Monthly Last\r\nDTSTART;TZID=Europe/London:20260425T150000\r\nDTEND;TZID=Europe/London:20260425T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SA;BYSETPOS=-1\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 322, "body_truncated": false}} +{"ts": "2026-03-30T15:45:58+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "X-WP-User": "admin", "Content-Length": "362"}, "body": "{\"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\"}", "body_bytes": 362, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:45:58 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "673"}, "body": "{\"data\": {\"id\": 15, \"uid\": \"89ac5e3af1c07027b9b8@calendar-wp-plugin\", \"title\": \"Smoke 4th Sunday Anchor\", \"description\": \"anchor-normalization\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"start_datetime\": \"2026-05-24T15:00:00+01:00\", \"end_datetime\": \"2026-05-24T16:00:00+01:00\", \"repeat_type\": \"monthly\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"weekday_of_month\", \"repeat_nth_day\": null, \"repeat_nth_pos\": 4, \"repeat_nth_weekday\": 0, \"repeat_range_mode\": \"no_end\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"2ca833fe353a18f5\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T15:45:58+00:00\"}}", "body_bytes": 673, "body_truncated": false}} +{"ts": "2026-03-30T15:45:58+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/15/occurrences/2026-05-24T15:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:45:58 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T15:45:58+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/15.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:45:58 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"ce147ab01d399c28\"", "Content-Length": "420"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:89ac5e3af1c07027b9b8@calendar-wp-plugin\r\nDTSTAMP:20260330T154558Z\r\nSUMMARY:Smoke 4th Sunday Anchor\r\nDESCRIPTION:anchor-normalization\r\nDTSTART;TZID=Europe/London:20260524T150000\r\nDTEND;TZID=Europe/London:20260524T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SU;BYSETPOS=4\r\nEXDATE;TZID=Europe/London:20260524T150000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 420, "body_truncated": false}} +{"ts": "2026-03-30T15:45:58+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "X-WP-User": "admin", "Content-Length": "295"}, "body": "{\"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\"}", "body_bytes": 295, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:45:58 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "655"}, "body": "{\"data\": {\"id\": 16, \"uid\": \"4d11fce48beae092f87b@calendar-wp-plugin\", \"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_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"until\", \"repeat_count\": null, \"repeat_until\": \"2026-03-19\", \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"dda7eb88fc839463\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T15:45:58+00:00\"}}", "body_bytes": 655, "body_truncated": false}} +{"ts": "2026-03-30T15:45:58+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/16/occurrences/2026-03-11T11:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:45:58 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T15:45:58+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/16/occurrences?from=2026-03-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:45:58 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "5212"}, "body": "{\"data\": [{\"event_id\": 16, \"uid\": \"4d11fce48beae092f87b@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-02T10:00:00+00:00\", \"occurrence_end\": \"2026-03-02T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"4d11fce48beae092f87b@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-03T10:00:00+00:00\", \"occurrence_end\": \"2026-03-03T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"4d11fce48beae092f87b@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-04T10:00:00+00:00\", \"occurrence_end\": \"2026-03-04T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"4d11fce48beae092f87b@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-05T10:00:00+00:00\", \"occurrence_end\": \"2026-03-05T11:00:00", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T15:45:59+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/16/occurrences/2026-03-10", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:45:59 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T15:45:59+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/16/occurrences?from=2026-03-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:45:59 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "4906"}, "body": "{\"data\": [{\"event_id\": 16, \"uid\": \"4d11fce48beae092f87b@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-02T10:00:00+00:00\", \"occurrence_end\": \"2026-03-02T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"4d11fce48beae092f87b@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-03T10:00:00+00:00\", \"occurrence_end\": \"2026-03-03T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"4d11fce48beae092f87b@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-04T10:00:00+00:00\", \"occurrence_end\": \"2026-03-04T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 16, \"uid\": \"4d11fce48beae092f87b@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-05T10:00:00+00:00\", \"occurrence_end\": \"2026-03-05T11:00:00", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T15:45:59+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-exdate-import.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "323"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-exdate-import-001\nSUMMARY:Smoke EXDATE Import\nDTSTART;TZID=Europe/London:20260302T100000\nDTEND;TZID=Europe/London:20260302T110000\nRRULE:FREQ=DAILY;UNTIL=20260319T235959\nEXDATE;TZID=Europe/London:20260310T100000,20260311T100000\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 323, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:45:59 GMT", "ETag": "\"81bd198374822883\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T15:45:59+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:45:59 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "11006"}, "body": "{\"data\": [{\"id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"start_datetime\": \"2026-04-01T10:00:00+01:00\", \"end_datetime\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"4cd72d479d507116\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T15:45:56+00:00\"}, {\"id\": 2, \"uid\": \"fixture-ce-002@calendar-wp-plugin\", \"title\": \"Office Closed\", \"description\": \"Public holiday closure.\", \"location\": \"HQ\", \"category\": \"Operations\", \"all_day_event\": true, \"start_datetime\": \"2026-05-04\", \"end_datetime\": \"2026-05-05\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"402be", "body_bytes": 3927, "body_truncated": true}} +{"ts": "2026-03-30T15:45:59+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/17/occurrences?from=2026-03-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:45:59 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "4474"}, "body": "{\"data\": [{\"event_id\": 17, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-02T10:00:00+01:00\", \"occurrence_end\": \"2026-03-02T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-03T10:00:00+01:00\", \"occurrence_end\": \"2026-03-03T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-04T10:00:00+01:00\", \"occurrence_end\": \"2026-03-04T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-05T10:00:00+01:00\", \"occurrence_end\": \"2026-03-05T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE ", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T15:45:59+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-cancelled-occurrence.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "467"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-cancelled-occurrence-001\nSUMMARY:Smoke Cancelled Occurrence\nDTSTART;TZID=Europe/London:20260408T123000\nDTEND;TZID=Europe/London:20260408T133000\nRRULE:FREQ=WEEKLY\nEND:VEVENT\nBEGIN:VEVENT\nUID:smoke-cancelled-occurrence-001\nRECURRENCE-ID;TZID=Europe/London:20260506T123000\nDTSTART;TZID=Europe/London:20260506T123000\nDTEND;TZID=Europe/London:20260506T133000\nSTATUS:CANCELLED\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 467, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:45:59 GMT", "ETag": "\"c920974d8ef1116c\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T15:45:59+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-cancelled-occurrence.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:45:59 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"c920974d8ef1116c\"", "Content-Length": "359"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-cancelled-occurrence-001\r\nDTSTAMP:20260330T154559Z\r\nSUMMARY:Smoke Cancelled Occurrence\r\nDTSTART;TZID=Europe/London:20260408T123000\r\nDTEND;TZID=Europe/London:20260408T133000\r\nRRULE:FREQ=WEEKLY\r\nEXDATE;TZID=Europe/London:20260506T123000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 359, "body_truncated": false}} +{"ts": "2026-03-30T15:45:59+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:45:59 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "11634"}, "body": "{\"data\": [{\"id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"start_datetime\": \"2026-04-01T10:00:00+01:00\", \"end_datetime\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"4cd72d479d507116\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T15:45:56+00:00\"}, {\"id\": 2, \"uid\": \"fixture-ce-002@calendar-wp-plugin\", \"title\": \"Office Closed\", \"description\": \"Public holiday closure.\", \"location\": \"HQ\", \"category\": \"Operations\", \"all_day_event\": true, \"start_datetime\": \"2026-05-04\", \"end_datetime\": \"2026-05-05\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"402be", "body_bytes": 3927, "body_truncated": true}} +{"ts": "2026-03-30T15:45:59+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/18/occurrences?from=2026-05-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:45:59 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "892"}, "body": "{\"data\": [{\"event_id\": 18, \"uid\": \"smoke-cancelled-occurrence-001\", \"title\": \"Smoke Cancelled Occurrence\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-13T12:30:00+01:00\", \"occurrence_end\": \"2026-05-13T13:30:00+01:00\", \"repeat_type\": \"weekly\"}, {\"event_id\": 18, \"uid\": \"smoke-cancelled-occurrence-001\", \"title\": \"Smoke Cancelled Occurrence\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-20T12:30:00+01:00\", \"occurrence_end\": \"2026-05-20T13:30:00+01:00\", \"repeat_type\": \"weekly\"}, {\"event_id\": 18, \"uid\": \"smoke-cancelled-occurrence-001\", \"title\": \"Smoke Cancelled Occurrence\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-27T12:30:00+01:00\", \"occurrence_end\": \"2026-05-27T13:30:00+01:00\", \"repeat_type\": \"weekly\"}]}", "body_bytes": 892, "body_truncated": false}} +{"ts": "2026-03-30T15:45:59+00:00", "client": "127.0.0.1", "method": "GET", "path": "/calendar", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:45:59 GMT", "Content-Type": "text/html; charset=utf-8", "Content-Length": "43447"}, "body": "[omitted html payload: 3934 bytes]", "body_bytes": 3934, "body_truncated": true}} +{"ts": "2026-03-30T15:45:59+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:45:59 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "11634"}, "body": "{\"data\": [{\"id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"start_datetime\": \"2026-04-01T10:00:00+01:00\", \"end_datetime\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"4cd72d479d507116\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T15:45:56+00:00\"}, {\"id\": 2, \"uid\": \"fixture-ce-002@calendar-wp-plugin\", \"title\": \"Office Closed\", \"description\": \"Public holiday closure.\", \"location\": \"HQ\", \"category\": \"Operations\", \"all_day_event\": true, \"start_datetime\": \"2026-05-04\", \"end_datetime\": \"2026-05-05\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"402be", "body_bytes": 3927, "body_truncated": true}} +{"ts": "2026-03-30T15:45:59+00:00", "client": "127.0.0.1", "method": "GET", "path": "/calendar.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:45:59 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"18a0ba0a5476aa53\"", "Content-Length": "5319"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nCALSCALE:GREGORIAN\r\nX-WR-TIMEZONE:Europe/London\r\nBEGIN:VEVENT\r\nUID:fixture-ce-001@calendar-wp-plugin\r\nDTSTAMP:20260330T154559Z\r\nSUMMARY:Board Meeting\r\nDESCRIPTION:Quarterly board review.\r\nLOCATION:Room A\r\nCATEGORIES:Governance\r\nDTSTART;TZID=Europe/London:20260401T100000\r\nDTEND;TZID=Europe/London:20260401T113000\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-002@calendar-wp-plugin\r\nDTSTAMP:20260330T154559Z\r\nSUMMARY:Office Closed\r\nDESCRIPTION:Public holiday closure.\r\nLOCATION:HQ\r\nCATEGORIES:Operations\r\nDTSTART;VALUE=DATE:20260504\r\nDTEND;VALUE=DATE:20260505\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-003@calendar-wp-plugin\r\nDTSTAMP:20260330T154559Z\r\nSUMMARY:Daily Standup\r\nDESCRIPTION:15 minute sync.\r\nLOCATION:Online\r\nCATEGORIES:Team\r\nDTSTART;TZID=Europe/London:20260406T090000\r\nDTEND;TZID=Europe/London:20260406T091500\r\nRRULE:FREQ=DAILY;COUNT=10\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-004@calendar-wp-plugin\r\nDTSTAMP:20260330T154559Z\r\nSUMMARY:Community Lunch\r\nDESCRIPTION:Weekly community lunch.\r\nLOCATION:Cafeteria\r\nCATEGORIES:Community\r\nDTSTART;TZID=Europe/London:20260408T123000\r\nDTEND;TZID=Europe/London:20260408T133000\r\n ...(truncated)", "body_bytes": 3905, "body_truncated": true}} +{"ts": "2026-03-30T15:45:59+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/1.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:45:59 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"4cd72d479d507116\"", "Content-Length": "364"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-001@calendar-wp-plugin\r\nDTSTAMP:20260330T154559Z\r\nSUMMARY:Board Meeting\r\nDESCRIPTION:Quarterly board review.\r\nLOCATION:Room A\r\nCATEGORIES:Governance\r\nDTSTART;TZID=Europe/London:20260401T100000\r\nDTEND;TZID=Europe/London:20260401T113000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 364, "body_truncated": false}} +{"ts": "2026-03-30T15:45:59+00:00", "client": "127.0.0.1", "method": "REPORT", "path": "/caldav/calendars/public/", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/xml; charset=utf-8", "Depth": "1", "Content-Length": "239"}, "body": "\n\n \n /caldav/calendars/public/4.ics\n\n", "body_bytes": 239, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:45:59 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "997"}, "body": "\n\n\n /caldav/calendars/public/4.ics\n \n \n \"318ecd47574f78e8\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-004@calendar-wp-plugin\r\nDTSTAMP:20260330T154559Z\r\nSUMMARY:Community Lunch\r\nDESCRIPTION:Weekly community lunch.\r\nLOCATION:Cafeteria\r\nCATEGORIES:Community\r\nDTSTART;TZID=Europe/London:20260408T123000\r\nDTEND;TZID=Europe/London:20260408T133000\r\nRRULE:FREQ=WEEKLY\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n", "body_bytes": 997, "body_truncated": false}} +{"ts": "2026-03-30T15:45:59+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/10/occurrences/2026-04-17T14:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:45:59 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T15:45:59+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-vtimezone.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "454"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN\nVERSION:2.0\nBEGIN:VTIMEZONE\nTZID:Europe/London\nBEGIN:STANDARD\nDTSTART:18471201T000000\nRRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=9\nTZOFFSETFROM:+0115\nTZOFFSETTO:+0000\nTZNAME:GMT\nEND:STANDARD\nEND:VTIMEZONE\nBEGIN:VEVENT\nUID:smoke-vtimezone-parser-001\nSUMMARY:Smoke VTIMEZONE Parse\nDTSTART;TZID=Europe/London:20260423T150000\nDTEND;TZID=Europe/London:20260423T160000\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 454, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:45:59 GMT", "ETag": "\"fe2e1195eb2a1dfe\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T15:45:59+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-vtimezone.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:45:59 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"fe2e1195eb2a1dfe\"", "Content-Length": "288"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-vtimezone-parser-001\r\nDTSTAMP:20260330T154559Z\r\nSUMMARY:Smoke VTIMEZONE Parse\r\nDTSTART;TZID=Europe/London:20260423T150000\r\nDTEND;TZID=Europe/London:20260423T160000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 288, "body_truncated": false}} +{"ts": "2026-03-30T15:45:59+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-monthly-nth.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "261"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-monthly-nth-001\nSUMMARY:Smoke Monthly Nth\nDTSTART;TZID=Europe/London:20260402T150000\nDTEND;TZID=Europe/London:20260402T160000\nRRULE:FREQ=MONTHLY;BYDAY=SU;BYSETPOS=4\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 261, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:45:59 GMT", "ETag": "\"f9b9356a1517a241\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T15:45:59+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-monthly-nth.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:45:59 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"f9b9356a1517a241\"", "Content-Length": "319"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-nth-001\r\nDTSTAMP:20260330T154559Z\r\nSUMMARY:Smoke Monthly Nth\r\nDTSTART;TZID=Europe/London:20260426T150000\r\nDTEND;TZID=Europe/London:20260426T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SU;BYSETPOS=4\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 319, "body_truncated": false}} +{"ts": "2026-03-30T15:45:59+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-monthly-ordinal.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "259"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-monthly-ordinal-001\nSUMMARY:Smoke Monthly Ordinal\nDTSTART;TZID=Europe/London:20260411T150000\nDTEND;TZID=Europe/London:20260411T160000\nRRULE:FREQ=MONTHLY;BYDAY=2SA\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 259, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:45:59 GMT", "ETag": "\"f36d586abbe055b0\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T15:45:59+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-monthly-ordinal.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:45:59 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"f36d586abbe055b0\"", "Content-Length": "327"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-ordinal-001\r\nDTSTAMP:20260330T154559Z\r\nSUMMARY:Smoke Monthly Ordinal\r\nDTSTART;TZID=Europe/London:20260411T150000\r\nDTEND;TZID=Europe/London:20260411T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SA;BYSETPOS=2\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 327, "body_truncated": false}} +{"ts": "2026-03-30T15:45:59+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-monthly-last.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "264"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-monthly-last-001\nSUMMARY:Smoke Monthly Last\nDTSTART;TZID=Europe/London:20260425T150000\nDTEND;TZID=Europe/London:20260425T160000\nRRULE:FREQ=MONTHLY;BYDAY=SA;BYSETPOS=-1\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 264, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:45:59 GMT", "ETag": "\"2bc1acbc670b28f9\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T15:45:59+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-monthly-last.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:45:59 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"2bc1acbc670b28f9\"", "Content-Length": "322"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-last-001\r\nDTSTAMP:20260330T154559Z\r\nSUMMARY:Smoke Monthly Last\r\nDTSTART;TZID=Europe/London:20260425T150000\r\nDTEND;TZID=Europe/London:20260425T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SA;BYSETPOS=-1\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 322, "body_truncated": false}} +{"ts": "2026-03-30T15:45:59+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "X-WP-User": "admin", "Content-Length": "362"}, "body": "{\"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\"}", "body_bytes": 362, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:45:59 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "673"}, "body": "{\"data\": {\"id\": 19, \"uid\": \"b397f1a2bd1d27bd909e@calendar-wp-plugin\", \"title\": \"Smoke 4th Sunday Anchor\", \"description\": \"anchor-normalization\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"start_datetime\": \"2026-05-24T15:00:00+01:00\", \"end_datetime\": \"2026-05-24T16:00:00+01:00\", \"repeat_type\": \"monthly\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"weekday_of_month\", \"repeat_nth_day\": null, \"repeat_nth_pos\": 4, \"repeat_nth_weekday\": 0, \"repeat_range_mode\": \"no_end\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"5cbce152b812cece\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T15:45:59+00:00\"}}", "body_bytes": 673, "body_truncated": false}} +{"ts": "2026-03-30T15:45:59+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/19/occurrences/2026-05-24T15:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:45:59 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T15:45:59+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/19.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:45:59 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"0a27b46fe6ea00d7\"", "Content-Length": "420"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:b397f1a2bd1d27bd909e@calendar-wp-plugin\r\nDTSTAMP:20260330T154559Z\r\nSUMMARY:Smoke 4th Sunday Anchor\r\nDESCRIPTION:anchor-normalization\r\nDTSTART;TZID=Europe/London:20260524T150000\r\nDTEND;TZID=Europe/London:20260524T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SU;BYSETPOS=4\r\nEXDATE;TZID=Europe/London:20260524T150000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 420, "body_truncated": false}} +{"ts": "2026-03-30T15:45:59+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "X-WP-User": "admin", "Content-Length": "295"}, "body": "{\"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\"}", "body_bytes": 295, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:45:59 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "655"}, "body": "{\"data\": {\"id\": 20, \"uid\": \"42cf8591d4bbfc538331@calendar-wp-plugin\", \"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_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"until\", \"repeat_count\": null, \"repeat_until\": \"2026-03-19\", \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"58df47550d8b8d1e\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T15:45:59+00:00\"}}", "body_bytes": 655, "body_truncated": false}} +{"ts": "2026-03-30T15:45:59+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/20/occurrences/2026-03-11T11:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:45:59 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T15:45:59+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/20/occurrences?from=2026-03-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:45:59 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "5212"}, "body": "{\"data\": [{\"event_id\": 20, \"uid\": \"42cf8591d4bbfc538331@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-02T10:00:00+00:00\", \"occurrence_end\": \"2026-03-02T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 20, \"uid\": \"42cf8591d4bbfc538331@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-03T10:00:00+00:00\", \"occurrence_end\": \"2026-03-03T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 20, \"uid\": \"42cf8591d4bbfc538331@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-04T10:00:00+00:00\", \"occurrence_end\": \"2026-03-04T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 20, \"uid\": \"42cf8591d4bbfc538331@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-05T10:00:00+00:00\", \"occurrence_end\": \"2026-03-05T11:00:00", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T15:45:59+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/20/occurrences/2026-03-10", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:45:59 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T15:45:59+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/20/occurrences?from=2026-03-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:45:59 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "4906"}, "body": "{\"data\": [{\"event_id\": 20, \"uid\": \"42cf8591d4bbfc538331@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-02T10:00:00+00:00\", \"occurrence_end\": \"2026-03-02T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 20, \"uid\": \"42cf8591d4bbfc538331@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-03T10:00:00+00:00\", \"occurrence_end\": \"2026-03-03T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 20, \"uid\": \"42cf8591d4bbfc538331@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-04T10:00:00+00:00\", \"occurrence_end\": \"2026-03-04T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 20, \"uid\": \"42cf8591d4bbfc538331@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-05T10:00:00+00:00\", \"occurrence_end\": \"2026-03-05T11:00:00", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T15:45:59+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-exdate-import.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "323"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-exdate-import-001\nSUMMARY:Smoke EXDATE Import\nDTSTART;TZID=Europe/London:20260302T100000\nDTEND;TZID=Europe/London:20260302T110000\nRRULE:FREQ=DAILY;UNTIL=20260319T235959\nEXDATE;TZID=Europe/London:20260310T100000,20260311T100000\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 323, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:45:59 GMT", "ETag": "\"aba0878b20403f58\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T15:45:59+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:45:59 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "12946"}, "body": "{\"data\": [{\"id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"start_datetime\": \"2026-04-01T10:00:00+01:00\", \"end_datetime\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"4cd72d479d507116\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T15:45:56+00:00\"}, {\"id\": 2, \"uid\": \"fixture-ce-002@calendar-wp-plugin\", \"title\": \"Office Closed\", \"description\": \"Public holiday closure.\", \"location\": \"HQ\", \"category\": \"Operations\", \"all_day_event\": true, \"start_datetime\": \"2026-05-04\", \"end_datetime\": \"2026-05-05\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"402be", "body_bytes": 3927, "body_truncated": true}} +{"ts": "2026-03-30T15:45:59+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/17/occurrences?from=2026-03-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:45:59 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "4474"}, "body": "{\"data\": [{\"event_id\": 17, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-02T10:00:00+01:00\", \"occurrence_end\": \"2026-03-02T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-03T10:00:00+01:00\", \"occurrence_end\": \"2026-03-03T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-04T10:00:00+01:00\", \"occurrence_end\": \"2026-03-04T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-05T10:00:00+01:00\", \"occurrence_end\": \"2026-03-05T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE ", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T15:45:59+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-cancelled-occurrence.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "467"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-cancelled-occurrence-001\nSUMMARY:Smoke Cancelled Occurrence\nDTSTART;TZID=Europe/London:20260408T123000\nDTEND;TZID=Europe/London:20260408T133000\nRRULE:FREQ=WEEKLY\nEND:VEVENT\nBEGIN:VEVENT\nUID:smoke-cancelled-occurrence-001\nRECURRENCE-ID;TZID=Europe/London:20260506T123000\nDTSTART;TZID=Europe/London:20260506T123000\nDTEND;TZID=Europe/London:20260506T133000\nSTATUS:CANCELLED\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 467, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:45:59 GMT", "ETag": "\"27e69cf4e5e13efe\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T15:46:00+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-cancelled-occurrence.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:46:00 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"27e69cf4e5e13efe\"", "Content-Length": "359"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-cancelled-occurrence-001\r\nDTSTAMP:20260330T154600Z\r\nSUMMARY:Smoke Cancelled Occurrence\r\nDTSTART;TZID=Europe/London:20260408T123000\r\nDTEND;TZID=Europe/London:20260408T133000\r\nRRULE:FREQ=WEEKLY\r\nEXDATE;TZID=Europe/London:20260506T123000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 359, "body_truncated": false}} +{"ts": "2026-03-30T15:46:00+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:46:00 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "12946"}, "body": "{\"data\": [{\"id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"start_datetime\": \"2026-04-01T10:00:00+01:00\", \"end_datetime\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"4cd72d479d507116\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T15:45:56+00:00\"}, {\"id\": 2, \"uid\": \"fixture-ce-002@calendar-wp-plugin\", \"title\": \"Office Closed\", \"description\": \"Public holiday closure.\", \"location\": \"HQ\", \"category\": \"Operations\", \"all_day_event\": true, \"start_datetime\": \"2026-05-04\", \"end_datetime\": \"2026-05-05\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"402be", "body_bytes": 3927, "body_truncated": true}} +{"ts": "2026-03-30T15:46:00+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/18/occurrences?from=2026-05-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:46:00 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "892"}, "body": "{\"data\": [{\"event_id\": 18, \"uid\": \"smoke-cancelled-occurrence-001\", \"title\": \"Smoke Cancelled Occurrence\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-13T12:30:00+01:00\", \"occurrence_end\": \"2026-05-13T13:30:00+01:00\", \"repeat_type\": \"weekly\"}, {\"event_id\": 18, \"uid\": \"smoke-cancelled-occurrence-001\", \"title\": \"Smoke Cancelled Occurrence\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-20T12:30:00+01:00\", \"occurrence_end\": \"2026-05-20T13:30:00+01:00\", \"repeat_type\": \"weekly\"}, {\"event_id\": 18, \"uid\": \"smoke-cancelled-occurrence-001\", \"title\": \"Smoke Cancelled Occurrence\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-27T12:30:00+01:00\", \"occurrence_end\": \"2026-05-27T13:30:00+01:00\", \"repeat_type\": \"weekly\"}]}", "body_bytes": 892, "body_truncated": false}} +{"ts": "2026-03-30T15:46:00+00:00", "client": "127.0.0.1", "method": "GET", "path": "/calendar", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:46:00 GMT", "Content-Type": "text/html; charset=utf-8", "Content-Length": "43447"}, "body": "[omitted html payload: 3934 bytes]", "body_bytes": 3934, "body_truncated": true}} +{"ts": "2026-03-30T15:48:25+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:48:25 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "6585"}, "body": "{\"data\": [{\"id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"start_datetime\": \"2026-04-01T10:00:00+01:00\", \"end_datetime\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"5e82b8a608ec2f69\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T15:48:22+00:00\"}, {\"id\": 2, \"uid\": \"fixture-ce-002@calendar-wp-plugin\", \"title\": \"Office Closed\", \"description\": \"Public holiday closure.\", \"location\": \"HQ\", \"category\": \"Operations\", \"all_day_event\": true, \"start_datetime\": \"2026-05-04\", \"end_datetime\": \"2026-05-05\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"9e96e", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T15:48:25+00:00", "client": "127.0.0.1", "method": "GET", "path": "/calendar.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:48:25 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"9f62fea5ac3aa027\"", "Content-Length": "3132"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nCALSCALE:GREGORIAN\r\nX-WR-TIMEZONE:Europe/London\r\nBEGIN:VEVENT\r\nUID:fixture-ce-001@calendar-wp-plugin\r\nDTSTAMP:20260330T154825Z\r\nSUMMARY:Board Meeting\r\nDESCRIPTION:Quarterly board review.\r\nLOCATION:Room A\r\nCATEGORIES:Governance\r\nDTSTART;TZID=Europe/London:20260401T100000\r\nDTEND;TZID=Europe/London:20260401T113000\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-002@calendar-wp-plugin\r\nDTSTAMP:20260330T154825Z\r\nSUMMARY:Office Closed\r\nDESCRIPTION:Public holiday closure.\r\nLOCATION:HQ\r\nCATEGORIES:Operations\r\nDTSTART;VALUE=DATE:20260504\r\nDTEND;VALUE=DATE:20260505\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-003@calendar-wp-plugin\r\nDTSTAMP:20260330T154825Z\r\nSUMMARY:Daily Standup\r\nDESCRIPTION:15 minute sync.\r\nLOCATION:Online\r\nCATEGORIES:Team\r\nDTSTART;TZID=Europe/London:20260406T090000\r\nDTEND;TZID=Europe/London:20260406T091500\r\nRRULE:FREQ=DAILY;COUNT=10\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-004@calendar-wp-plugin\r\nDTSTAMP:20260330T154825Z\r\nSUMMARY:Community Lunch\r\nDESCRIPTION:Weekly community lunch.\r\nLOCATION:Cafeteria\r\nCATEGORIES:Community\r\nDTSTART;TZID=Europe/London:20260408T123000\r\nDTEND;TZID=Europe/London:20260408T133000\r\n ...(truncated)", "body_bytes": 3132, "body_truncated": false}} +{"ts": "2026-03-30T15:48:25+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/1.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:48:25 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"5e82b8a608ec2f69\"", "Content-Length": "364"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-001@calendar-wp-plugin\r\nDTSTAMP:20260330T154825Z\r\nSUMMARY:Board Meeting\r\nDESCRIPTION:Quarterly board review.\r\nLOCATION:Room A\r\nCATEGORIES:Governance\r\nDTSTART;TZID=Europe/London:20260401T100000\r\nDTEND;TZID=Europe/London:20260401T113000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 364, "body_truncated": false}} +{"ts": "2026-03-30T15:48:25+00:00", "client": "127.0.0.1", "method": "REPORT", "path": "/caldav/calendars/public/", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/xml; charset=utf-8", "Depth": "1", "Content-Length": "239"}, "body": "\n\n \n /caldav/calendars/public/4.ics\n\n", "body_bytes": 239, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:48:25 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "997"}, "body": "\n\n\n /caldav/calendars/public/4.ics\n \n \n \"ed65e21db7fdf5b1\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-004@calendar-wp-plugin\r\nDTSTAMP:20260330T154825Z\r\nSUMMARY:Community Lunch\r\nDESCRIPTION:Weekly community lunch.\r\nLOCATION:Cafeteria\r\nCATEGORIES:Community\r\nDTSTART;TZID=Europe/London:20260408T123000\r\nDTEND;TZID=Europe/London:20260408T133000\r\nRRULE:FREQ=WEEKLY\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n", "body_bytes": 997, "body_truncated": false}} +{"ts": "2026-03-30T15:48:25+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/10/occurrences/2026-04-17T14:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:48:25 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T15:48:25+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "X-WP-User": "admin", "Content-Length": "287"}, "body": "{\"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}", "body_bytes": 287, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:48:25 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "647"}, "body": "{\"data\": {\"id\": 11, \"uid\": \"3256d792f303942ee5ec@calendar-wp-plugin\", \"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_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"count\", \"repeat_count\": 3, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"05e6f17954013126\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T15:48:25+00:00\"}}", "body_bytes": 647, "body_truncated": false}} +{"ts": "2026-03-30T15:48:25+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/11/occurrences?from=2026-04-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:48:25 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "937"}, "body": "{\"data\": [{\"event_id\": 11, \"uid\": \"3256d792f303942ee5ec@calendar-wp-plugin\", \"title\": \"Smoke Occurrences API\", \"description\": \"occ-endpoint\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-01T10:00:00+01:00\", \"occurrence_end\": \"2026-04-01T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 11, \"uid\": \"3256d792f303942ee5ec@calendar-wp-plugin\", \"title\": \"Smoke Occurrences API\", \"description\": \"occ-endpoint\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-02T10:00:00+01:00\", \"occurrence_end\": \"2026-04-02T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 11, \"uid\": \"3256d792f303942ee5ec@calendar-wp-plugin\", \"title\": \"Smoke Occurrences API\", \"description\": \"occ-endpoint\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-03T10:00:00+01:00\", \"occurrence_end\": \"2026-04-03T11:00:00+01:00\", \"repeat_type\": \"daily\"}]}", "body_bytes": 937, "body_truncated": false}} +{"ts": "2026-03-30T15:48:39+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:48:39 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "6585"}, "body": "{\"data\": [{\"id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"start_datetime\": \"2026-04-01T10:00:00+01:00\", \"end_datetime\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"732ad7c020d51395\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T15:48:37+00:00\"}, {\"id\": 2, \"uid\": \"fixture-ce-002@calendar-wp-plugin\", \"title\": \"Office Closed\", \"description\": \"Public holiday closure.\", \"location\": \"HQ\", \"category\": \"Operations\", \"all_day_event\": true, \"start_datetime\": \"2026-05-04\", \"end_datetime\": \"2026-05-05\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"996ea", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T15:48:39+00:00", "client": "127.0.0.1", "method": "GET", "path": "/calendar.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:48:39 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"84d45e72b4e73efa\"", "Content-Length": "3132"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nCALSCALE:GREGORIAN\r\nX-WR-TIMEZONE:Europe/London\r\nBEGIN:VEVENT\r\nUID:fixture-ce-001@calendar-wp-plugin\r\nDTSTAMP:20260330T154839Z\r\nSUMMARY:Board Meeting\r\nDESCRIPTION:Quarterly board review.\r\nLOCATION:Room A\r\nCATEGORIES:Governance\r\nDTSTART;TZID=Europe/London:20260401T100000\r\nDTEND;TZID=Europe/London:20260401T113000\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-002@calendar-wp-plugin\r\nDTSTAMP:20260330T154839Z\r\nSUMMARY:Office Closed\r\nDESCRIPTION:Public holiday closure.\r\nLOCATION:HQ\r\nCATEGORIES:Operations\r\nDTSTART;VALUE=DATE:20260504\r\nDTEND;VALUE=DATE:20260505\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-003@calendar-wp-plugin\r\nDTSTAMP:20260330T154839Z\r\nSUMMARY:Daily Standup\r\nDESCRIPTION:15 minute sync.\r\nLOCATION:Online\r\nCATEGORIES:Team\r\nDTSTART;TZID=Europe/London:20260406T090000\r\nDTEND;TZID=Europe/London:20260406T091500\r\nRRULE:FREQ=DAILY;COUNT=10\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-004@calendar-wp-plugin\r\nDTSTAMP:20260330T154839Z\r\nSUMMARY:Community Lunch\r\nDESCRIPTION:Weekly community lunch.\r\nLOCATION:Cafeteria\r\nCATEGORIES:Community\r\nDTSTART;TZID=Europe/London:20260408T123000\r\nDTEND;TZID=Europe/London:20260408T133000\r\n ...(truncated)", "body_bytes": 3132, "body_truncated": false}} +{"ts": "2026-03-30T15:48:39+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/1.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:48:39 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"732ad7c020d51395\"", "Content-Length": "364"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-001@calendar-wp-plugin\r\nDTSTAMP:20260330T154839Z\r\nSUMMARY:Board Meeting\r\nDESCRIPTION:Quarterly board review.\r\nLOCATION:Room A\r\nCATEGORIES:Governance\r\nDTSTART;TZID=Europe/London:20260401T100000\r\nDTEND;TZID=Europe/London:20260401T113000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 364, "body_truncated": false}} +{"ts": "2026-03-30T15:48:39+00:00", "client": "127.0.0.1", "method": "REPORT", "path": "/caldav/calendars/public/", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/xml; charset=utf-8", "Depth": "1", "Content-Length": "239"}, "body": "\n\n \n /caldav/calendars/public/4.ics\n\n", "body_bytes": 239, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:48:39 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "997"}, "body": "\n\n\n /caldav/calendars/public/4.ics\n \n \n \"a7862a0f1a83e217\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-004@calendar-wp-plugin\r\nDTSTAMP:20260330T154839Z\r\nSUMMARY:Community Lunch\r\nDESCRIPTION:Weekly community lunch.\r\nLOCATION:Cafeteria\r\nCATEGORIES:Community\r\nDTSTART;TZID=Europe/London:20260408T123000\r\nDTEND;TZID=Europe/London:20260408T133000\r\nRRULE:FREQ=WEEKLY\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n", "body_bytes": 997, "body_truncated": false}} +{"ts": "2026-03-30T15:48:39+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/10/occurrences/2026-04-17T14:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:48:39 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T15:48:39+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "X-WP-User": "admin", "Content-Length": "287"}, "body": "{\"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}", "body_bytes": 287, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:48:39 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "647"}, "body": "{\"data\": {\"id\": 11, \"uid\": \"54b5dbcbbac70e6b80a0@calendar-wp-plugin\", \"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_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"count\", \"repeat_count\": 3, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"d62c926281c67f37\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T15:48:39+00:00\"}}", "body_bytes": 647, "body_truncated": false}} +{"ts": "2026-03-30T15:48:40+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/11/occurrences?from=2026-04-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:48:40 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "937"}, "body": "{\"data\": [{\"event_id\": 11, \"uid\": \"54b5dbcbbac70e6b80a0@calendar-wp-plugin\", \"title\": \"Smoke Occurrences API\", \"description\": \"occ-endpoint\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-01T10:00:00+01:00\", \"occurrence_end\": \"2026-04-01T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 11, \"uid\": \"54b5dbcbbac70e6b80a0@calendar-wp-plugin\", \"title\": \"Smoke Occurrences API\", \"description\": \"occ-endpoint\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-02T10:00:00+01:00\", \"occurrence_end\": \"2026-04-02T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 11, \"uid\": \"54b5dbcbbac70e6b80a0@calendar-wp-plugin\", \"title\": \"Smoke Occurrences API\", \"description\": \"occ-endpoint\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-03T10:00:00+01:00\", \"occurrence_end\": \"2026-04-03T11:00:00+01:00\", \"repeat_type\": \"daily\"}]}", "body_bytes": 937, "body_truncated": false}} +{"ts": "2026-03-30T15:48:40+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/11/occurrences/2026-04-02T10:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:48:40 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T15:48:40+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/11/occurrences/2026-04-02T10:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:48:40 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T15:48:40+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/11/occurrences?from=2026-04-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:48:40 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "628"}, "body": "{\"data\": [{\"event_id\": 11, \"uid\": \"54b5dbcbbac70e6b80a0@calendar-wp-plugin\", \"title\": \"Smoke Occurrences API\", \"description\": \"occ-endpoint\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-01T10:00:00+01:00\", \"occurrence_end\": \"2026-04-01T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 11, \"uid\": \"54b5dbcbbac70e6b80a0@calendar-wp-plugin\", \"title\": \"Smoke Occurrences API\", \"description\": \"occ-endpoint\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-03T10:00:00+01:00\", \"occurrence_end\": \"2026-04-03T11:00:00+01:00\", \"repeat_type\": \"daily\"}]}", "body_bytes": 628, "body_truncated": false}} +{"ts": "2026-03-30T15:48:40+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-vtimezone.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "454"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN\nVERSION:2.0\nBEGIN:VTIMEZONE\nTZID:Europe/London\nBEGIN:STANDARD\nDTSTART:18471201T000000\nRRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=9\nTZOFFSETFROM:+0115\nTZOFFSETTO:+0000\nTZNAME:GMT\nEND:STANDARD\nEND:VTIMEZONE\nBEGIN:VEVENT\nUID:smoke-vtimezone-parser-001\nSUMMARY:Smoke VTIMEZONE Parse\nDTSTART;TZID=Europe/London:20260423T150000\nDTEND;TZID=Europe/London:20260423T160000\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 454, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:48:40 GMT", "ETag": "\"03110b46b4736657\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T15:48:40+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-vtimezone.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:48:40 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"03110b46b4736657\"", "Content-Length": "288"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-vtimezone-parser-001\r\nDTSTAMP:20260330T154840Z\r\nSUMMARY:Smoke VTIMEZONE Parse\r\nDTSTART;TZID=Europe/London:20260423T150000\r\nDTEND;TZID=Europe/London:20260423T160000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 288, "body_truncated": false}} +{"ts": "2026-03-30T15:48:40+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-monthly-nth.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "261"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-monthly-nth-001\nSUMMARY:Smoke Monthly Nth\nDTSTART;TZID=Europe/London:20260402T150000\nDTEND;TZID=Europe/London:20260402T160000\nRRULE:FREQ=MONTHLY;BYDAY=SU;BYSETPOS=4\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 261, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:48:40 GMT", "ETag": "\"b1d8098f2c401ca5\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T15:48:40+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-monthly-nth.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:48:40 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"b1d8098f2c401ca5\"", "Content-Length": "319"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-nth-001\r\nDTSTAMP:20260330T154840Z\r\nSUMMARY:Smoke Monthly Nth\r\nDTSTART;TZID=Europe/London:20260426T150000\r\nDTEND;TZID=Europe/London:20260426T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SU;BYSETPOS=4\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 319, "body_truncated": false}} +{"ts": "2026-03-30T15:48:40+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-monthly-ordinal.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "259"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-monthly-ordinal-001\nSUMMARY:Smoke Monthly Ordinal\nDTSTART;TZID=Europe/London:20260411T150000\nDTEND;TZID=Europe/London:20260411T160000\nRRULE:FREQ=MONTHLY;BYDAY=2SA\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 259, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:48:40 GMT", "ETag": "\"e35466e93feda311\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T15:48:40+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-monthly-ordinal.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:48:40 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"e35466e93feda311\"", "Content-Length": "327"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-ordinal-001\r\nDTSTAMP:20260330T154840Z\r\nSUMMARY:Smoke Monthly Ordinal\r\nDTSTART;TZID=Europe/London:20260411T150000\r\nDTEND;TZID=Europe/London:20260411T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SA;BYSETPOS=2\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 327, "body_truncated": false}} +{"ts": "2026-03-30T15:48:40+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-monthly-last.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "264"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-monthly-last-001\nSUMMARY:Smoke Monthly Last\nDTSTART;TZID=Europe/London:20260425T150000\nDTEND;TZID=Europe/London:20260425T160000\nRRULE:FREQ=MONTHLY;BYDAY=SA;BYSETPOS=-1\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 264, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:48:40 GMT", "ETag": "\"ca53342667463a28\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T15:48:40+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-monthly-last.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:48:40 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"ca53342667463a28\"", "Content-Length": "322"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-last-001\r\nDTSTAMP:20260330T154840Z\r\nSUMMARY:Smoke Monthly Last\r\nDTSTART;TZID=Europe/London:20260425T150000\r\nDTEND;TZID=Europe/London:20260425T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SA;BYSETPOS=-1\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 322, "body_truncated": false}} +{"ts": "2026-03-30T15:48:40+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "X-WP-User": "admin", "Content-Length": "362"}, "body": "{\"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\"}", "body_bytes": 362, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:48:40 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "673"}, "body": "{\"data\": {\"id\": 16, \"uid\": \"c497eb71f64543b5c7e6@calendar-wp-plugin\", \"title\": \"Smoke 4th Sunday Anchor\", \"description\": \"anchor-normalization\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"start_datetime\": \"2026-05-24T15:00:00+01:00\", \"end_datetime\": \"2026-05-24T16:00:00+01:00\", \"repeat_type\": \"monthly\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"weekday_of_month\", \"repeat_nth_day\": null, \"repeat_nth_pos\": 4, \"repeat_nth_weekday\": 0, \"repeat_range_mode\": \"no_end\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"ee105eed23956cc3\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T15:48:40+00:00\"}}", "body_bytes": 673, "body_truncated": false}} +{"ts": "2026-03-30T15:48:40+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/16/occurrences/2026-05-24T15:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:48:40 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T15:48:40+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/16.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:48:40 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"1efae4799ce0bd1e\"", "Content-Length": "420"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:c497eb71f64543b5c7e6@calendar-wp-plugin\r\nDTSTAMP:20260330T154840Z\r\nSUMMARY:Smoke 4th Sunday Anchor\r\nDESCRIPTION:anchor-normalization\r\nDTSTART;TZID=Europe/London:20260524T150000\r\nDTEND;TZID=Europe/London:20260524T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SU;BYSETPOS=4\r\nEXDATE;TZID=Europe/London:20260524T150000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 420, "body_truncated": false}} +{"ts": "2026-03-30T15:48:40+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "X-WP-User": "admin", "Content-Length": "295"}, "body": "{\"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\"}", "body_bytes": 295, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:48:40 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "655"}, "body": "{\"data\": {\"id\": 17, \"uid\": \"22636e248d3c1a08f8a3@calendar-wp-plugin\", \"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_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"until\", \"repeat_count\": null, \"repeat_until\": \"2026-03-19\", \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"c92a15baad77b616\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T15:48:40+00:00\"}}", "body_bytes": 655, "body_truncated": false}} +{"ts": "2026-03-30T15:48:40+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/17/occurrences/2026-03-11T11:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:48:40 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T15:48:40+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/17/occurrences?from=2026-03-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:48:40 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "5212"}, "body": "{\"data\": [{\"event_id\": 17, \"uid\": \"22636e248d3c1a08f8a3@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-02T10:00:00+00:00\", \"occurrence_end\": \"2026-03-02T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"22636e248d3c1a08f8a3@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-03T10:00:00+00:00\", \"occurrence_end\": \"2026-03-03T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"22636e248d3c1a08f8a3@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-04T10:00:00+00:00\", \"occurrence_end\": \"2026-03-04T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"22636e248d3c1a08f8a3@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-05T10:00:00+00:00\", \"occurrence_end\": \"2026-03-05T11:00:00", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T15:48:40+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/17/occurrences/2026-03-10", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:48:40 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T15:48:40+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/17/occurrences?from=2026-03-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:48:40 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "4906"}, "body": "{\"data\": [{\"event_id\": 17, \"uid\": \"22636e248d3c1a08f8a3@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-02T10:00:00+00:00\", \"occurrence_end\": \"2026-03-02T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"22636e248d3c1a08f8a3@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-03T10:00:00+00:00\", \"occurrence_end\": \"2026-03-03T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"22636e248d3c1a08f8a3@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-04T10:00:00+00:00\", \"occurrence_end\": \"2026-03-04T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"22636e248d3c1a08f8a3@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-05T10:00:00+00:00\", \"occurrence_end\": \"2026-03-05T11:00:00", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T15:48:40+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-exdate-import.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "323"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-exdate-import-001\nSUMMARY:Smoke EXDATE Import\nDTSTART;TZID=Europe/London:20260302T100000\nDTEND;TZID=Europe/London:20260302T110000\nRRULE:FREQ=DAILY;UNTIL=20260319T235959\nEXDATE;TZID=Europe/London:20260310T100000,20260311T100000\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 323, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:48:40 GMT", "ETag": "\"1075b85ca18a439c\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T15:48:40+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:48:40 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "11645"}, "body": "{\"data\": [{\"id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"start_datetime\": \"2026-04-01T10:00:00+01:00\", \"end_datetime\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"732ad7c020d51395\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T15:48:37+00:00\"}, {\"id\": 2, \"uid\": \"fixture-ce-002@calendar-wp-plugin\", \"title\": \"Office Closed\", \"description\": \"Public holiday closure.\", \"location\": \"HQ\", \"category\": \"Operations\", \"all_day_event\": true, \"start_datetime\": \"2026-05-04\", \"end_datetime\": \"2026-05-05\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"996ea", "body_bytes": 3927, "body_truncated": true}} +{"ts": "2026-03-30T15:48:40+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/18/occurrences?from=2026-03-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:48:40 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "4474"}, "body": "{\"data\": [{\"event_id\": 18, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-02T10:00:00+01:00\", \"occurrence_end\": \"2026-03-02T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 18, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-03T10:00:00+01:00\", \"occurrence_end\": \"2026-03-03T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 18, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-04T10:00:00+01:00\", \"occurrence_end\": \"2026-03-04T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 18, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-05T10:00:00+01:00\", \"occurrence_end\": \"2026-03-05T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 18, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE ", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T15:48:40+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-cancelled-occurrence.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "467"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-cancelled-occurrence-001\nSUMMARY:Smoke Cancelled Occurrence\nDTSTART;TZID=Europe/London:20260408T123000\nDTEND;TZID=Europe/London:20260408T133000\nRRULE:FREQ=WEEKLY\nEND:VEVENT\nBEGIN:VEVENT\nUID:smoke-cancelled-occurrence-001\nRECURRENCE-ID;TZID=Europe/London:20260506T123000\nDTSTART;TZID=Europe/London:20260506T123000\nDTEND;TZID=Europe/London:20260506T133000\nSTATUS:CANCELLED\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 467, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:48:40 GMT", "ETag": "\"a580ff422270d850\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T15:48:40+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-cancelled-occurrence.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:48:40 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"a580ff422270d850\"", "Content-Length": "359"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-cancelled-occurrence-001\r\nDTSTAMP:20260330T154840Z\r\nSUMMARY:Smoke Cancelled Occurrence\r\nDTSTART;TZID=Europe/London:20260408T123000\r\nDTEND;TZID=Europe/London:20260408T133000\r\nRRULE:FREQ=WEEKLY\r\nEXDATE;TZID=Europe/London:20260506T123000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 359, "body_truncated": false}} +{"ts": "2026-03-30T15:48:40+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:48:40 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "12273"}, "body": "{\"data\": [{\"id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"start_datetime\": \"2026-04-01T10:00:00+01:00\", \"end_datetime\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"732ad7c020d51395\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T15:48:37+00:00\"}, {\"id\": 2, \"uid\": \"fixture-ce-002@calendar-wp-plugin\", \"title\": \"Office Closed\", \"description\": \"Public holiday closure.\", \"location\": \"HQ\", \"category\": \"Operations\", \"all_day_event\": true, \"start_datetime\": \"2026-05-04\", \"end_datetime\": \"2026-05-05\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"996ea", "body_bytes": 3927, "body_truncated": true}} +{"ts": "2026-03-30T15:48:40+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/19/occurrences?from=2026-05-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:48:40 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "892"}, "body": "{\"data\": [{\"event_id\": 19, \"uid\": \"smoke-cancelled-occurrence-001\", \"title\": \"Smoke Cancelled Occurrence\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-13T12:30:00+01:00\", \"occurrence_end\": \"2026-05-13T13:30:00+01:00\", \"repeat_type\": \"weekly\"}, {\"event_id\": 19, \"uid\": \"smoke-cancelled-occurrence-001\", \"title\": \"Smoke Cancelled Occurrence\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-20T12:30:00+01:00\", \"occurrence_end\": \"2026-05-20T13:30:00+01:00\", \"repeat_type\": \"weekly\"}, {\"event_id\": 19, \"uid\": \"smoke-cancelled-occurrence-001\", \"title\": \"Smoke Cancelled Occurrence\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-27T12:30:00+01:00\", \"occurrence_end\": \"2026-05-27T13:30:00+01:00\", \"repeat_type\": \"weekly\"}]}", "body_bytes": 892, "body_truncated": false}} +{"ts": "2026-03-30T15:48:40+00:00", "client": "127.0.0.1", "method": "GET", "path": "/calendar", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:48:40 GMT", "Content-Type": "text/html; charset=utf-8", "Content-Length": "43447"}, "body": "[omitted html payload: 3934 bytes]", "body_bytes": 3934, "body_truncated": true}} +{"ts": "2026-03-30T15:48:59+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:48:59 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "6585"}, "body": "{\"data\": [{\"id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"start_datetime\": \"2026-04-01T10:00:00+01:00\", \"end_datetime\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"e6a46365a4181790\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T15:48:57+00:00\"}, {\"id\": 2, \"uid\": \"fixture-ce-002@calendar-wp-plugin\", \"title\": \"Office Closed\", \"description\": \"Public holiday closure.\", \"location\": \"HQ\", \"category\": \"Operations\", \"all_day_event\": true, \"start_datetime\": \"2026-05-04\", \"end_datetime\": \"2026-05-05\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"d80b6", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T15:48:59+00:00", "client": "127.0.0.1", "method": "GET", "path": "/calendar.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:48:59 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"e80ad55b0cf34f87\"", "Content-Length": "3132"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nCALSCALE:GREGORIAN\r\nX-WR-TIMEZONE:Europe/London\r\nBEGIN:VEVENT\r\nUID:fixture-ce-001@calendar-wp-plugin\r\nDTSTAMP:20260330T154859Z\r\nSUMMARY:Board Meeting\r\nDESCRIPTION:Quarterly board review.\r\nLOCATION:Room A\r\nCATEGORIES:Governance\r\nDTSTART;TZID=Europe/London:20260401T100000\r\nDTEND;TZID=Europe/London:20260401T113000\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-002@calendar-wp-plugin\r\nDTSTAMP:20260330T154859Z\r\nSUMMARY:Office Closed\r\nDESCRIPTION:Public holiday closure.\r\nLOCATION:HQ\r\nCATEGORIES:Operations\r\nDTSTART;VALUE=DATE:20260504\r\nDTEND;VALUE=DATE:20260505\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-003@calendar-wp-plugin\r\nDTSTAMP:20260330T154859Z\r\nSUMMARY:Daily Standup\r\nDESCRIPTION:15 minute sync.\r\nLOCATION:Online\r\nCATEGORIES:Team\r\nDTSTART;TZID=Europe/London:20260406T090000\r\nDTEND;TZID=Europe/London:20260406T091500\r\nRRULE:FREQ=DAILY;COUNT=10\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-004@calendar-wp-plugin\r\nDTSTAMP:20260330T154859Z\r\nSUMMARY:Community Lunch\r\nDESCRIPTION:Weekly community lunch.\r\nLOCATION:Cafeteria\r\nCATEGORIES:Community\r\nDTSTART;TZID=Europe/London:20260408T123000\r\nDTEND;TZID=Europe/London:20260408T133000\r\n ...(truncated)", "body_bytes": 3132, "body_truncated": false}} +{"ts": "2026-03-30T15:48:59+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/1.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:48:59 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"e6a46365a4181790\"", "Content-Length": "364"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-001@calendar-wp-plugin\r\nDTSTAMP:20260330T154859Z\r\nSUMMARY:Board Meeting\r\nDESCRIPTION:Quarterly board review.\r\nLOCATION:Room A\r\nCATEGORIES:Governance\r\nDTSTART;TZID=Europe/London:20260401T100000\r\nDTEND;TZID=Europe/London:20260401T113000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 364, "body_truncated": false}} +{"ts": "2026-03-30T15:48:59+00:00", "client": "127.0.0.1", "method": "REPORT", "path": "/caldav/calendars/public/", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/xml; charset=utf-8", "Depth": "1", "Content-Length": "239"}, "body": "\n\n \n /caldav/calendars/public/4.ics\n\n", "body_bytes": 239, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:48:59 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "997"}, "body": "\n\n\n /caldav/calendars/public/4.ics\n \n \n \"3d214f4f81cae67d\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-004@calendar-wp-plugin\r\nDTSTAMP:20260330T154859Z\r\nSUMMARY:Community Lunch\r\nDESCRIPTION:Weekly community lunch.\r\nLOCATION:Cafeteria\r\nCATEGORIES:Community\r\nDTSTART;TZID=Europe/London:20260408T123000\r\nDTEND;TZID=Europe/London:20260408T133000\r\nRRULE:FREQ=WEEKLY\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n", "body_bytes": 997, "body_truncated": false}} +{"ts": "2026-03-30T15:48:59+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/10/occurrences/2026-04-17T14:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:48:59 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T15:48:59+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "X-WP-User": "admin", "Content-Length": "287"}, "body": "{\"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}", "body_bytes": 287, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:48:59 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "647"}, "body": "{\"data\": {\"id\": 11, \"uid\": \"b1d82373467e236cc487@calendar-wp-plugin\", \"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_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"count\", \"repeat_count\": 3, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"f1958b4788675043\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T15:48:59+00:00\"}}", "body_bytes": 647, "body_truncated": false}} +{"ts": "2026-03-30T15:48:59+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/11/occurrences?from=2026-04-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:48:59 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "937"}, "body": "{\"data\": [{\"event_id\": 11, \"uid\": \"b1d82373467e236cc487@calendar-wp-plugin\", \"title\": \"Smoke Occurrences API\", \"description\": \"occ-endpoint\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-01T10:00:00+01:00\", \"occurrence_end\": \"2026-04-01T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 11, \"uid\": \"b1d82373467e236cc487@calendar-wp-plugin\", \"title\": \"Smoke Occurrences API\", \"description\": \"occ-endpoint\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-02T10:00:00+01:00\", \"occurrence_end\": \"2026-04-02T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 11, \"uid\": \"b1d82373467e236cc487@calendar-wp-plugin\", \"title\": \"Smoke Occurrences API\", \"description\": \"occ-endpoint\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-03T10:00:00+01:00\", \"occurrence_end\": \"2026-04-03T11:00:00+01:00\", \"repeat_type\": \"daily\"}]}", "body_bytes": 937, "body_truncated": false}} +{"ts": "2026-03-30T15:48:59+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/11/occurrences/2026-04-02T10:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:48:59 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T15:48:59+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/11/occurrences/2026-04-02T10:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:48:59 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T15:48:59+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/11/occurrences?from=2026-04-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:48:59 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "628"}, "body": "{\"data\": [{\"event_id\": 11, \"uid\": \"b1d82373467e236cc487@calendar-wp-plugin\", \"title\": \"Smoke Occurrences API\", \"description\": \"occ-endpoint\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-01T10:00:00+01:00\", \"occurrence_end\": \"2026-04-01T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 11, \"uid\": \"b1d82373467e236cc487@calendar-wp-plugin\", \"title\": \"Smoke Occurrences API\", \"description\": \"occ-endpoint\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-03T10:00:00+01:00\", \"occurrence_end\": \"2026-04-03T11:00:00+01:00\", \"repeat_type\": \"daily\"}]}", "body_bytes": 628, "body_truncated": false}} +{"ts": "2026-03-30T15:48:59+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-vtimezone.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "454"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN\nVERSION:2.0\nBEGIN:VTIMEZONE\nTZID:Europe/London\nBEGIN:STANDARD\nDTSTART:18471201T000000\nRRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=9\nTZOFFSETFROM:+0115\nTZOFFSETTO:+0000\nTZNAME:GMT\nEND:STANDARD\nEND:VTIMEZONE\nBEGIN:VEVENT\nUID:smoke-vtimezone-parser-001\nSUMMARY:Smoke VTIMEZONE Parse\nDTSTART;TZID=Europe/London:20260423T150000\nDTEND;TZID=Europe/London:20260423T160000\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 454, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:48:59 GMT", "ETag": "\"5f98042911cdc2aa\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T15:48:59+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-vtimezone.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:48:59 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"5f98042911cdc2aa\"", "Content-Length": "288"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-vtimezone-parser-001\r\nDTSTAMP:20260330T154859Z\r\nSUMMARY:Smoke VTIMEZONE Parse\r\nDTSTART;TZID=Europe/London:20260423T150000\r\nDTEND;TZID=Europe/London:20260423T160000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 288, "body_truncated": false}} +{"ts": "2026-03-30T15:48:59+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-monthly-nth.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "261"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-monthly-nth-001\nSUMMARY:Smoke Monthly Nth\nDTSTART;TZID=Europe/London:20260402T150000\nDTEND;TZID=Europe/London:20260402T160000\nRRULE:FREQ=MONTHLY;BYDAY=SU;BYSETPOS=4\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 261, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:48:59 GMT", "ETag": "\"3eb343a6df4e1f4a\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T15:48:59+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-monthly-nth.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:48:59 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"3eb343a6df4e1f4a\"", "Content-Length": "319"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-nth-001\r\nDTSTAMP:20260330T154859Z\r\nSUMMARY:Smoke Monthly Nth\r\nDTSTART;TZID=Europe/London:20260426T150000\r\nDTEND;TZID=Europe/London:20260426T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SU;BYSETPOS=4\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 319, "body_truncated": false}} +{"ts": "2026-03-30T15:48:59+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-monthly-ordinal.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "259"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-monthly-ordinal-001\nSUMMARY:Smoke Monthly Ordinal\nDTSTART;TZID=Europe/London:20260411T150000\nDTEND;TZID=Europe/London:20260411T160000\nRRULE:FREQ=MONTHLY;BYDAY=2SA\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 259, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:48:59 GMT", "ETag": "\"8eaecebe6704179d\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T15:48:59+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-monthly-ordinal.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:48:59 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"8eaecebe6704179d\"", "Content-Length": "327"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-ordinal-001\r\nDTSTAMP:20260330T154859Z\r\nSUMMARY:Smoke Monthly Ordinal\r\nDTSTART;TZID=Europe/London:20260411T150000\r\nDTEND;TZID=Europe/London:20260411T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SA;BYSETPOS=2\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 327, "body_truncated": false}} +{"ts": "2026-03-30T15:48:59+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-monthly-last.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "264"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-monthly-last-001\nSUMMARY:Smoke Monthly Last\nDTSTART;TZID=Europe/London:20260425T150000\nDTEND;TZID=Europe/London:20260425T160000\nRRULE:FREQ=MONTHLY;BYDAY=SA;BYSETPOS=-1\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 264, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:48:59 GMT", "ETag": "\"1b74dcc0ab5f571c\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T15:48:59+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-monthly-last.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:48:59 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"1b74dcc0ab5f571c\"", "Content-Length": "322"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-last-001\r\nDTSTAMP:20260330T154859Z\r\nSUMMARY:Smoke Monthly Last\r\nDTSTART;TZID=Europe/London:20260425T150000\r\nDTEND;TZID=Europe/London:20260425T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SA;BYSETPOS=-1\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 322, "body_truncated": false}} +{"ts": "2026-03-30T15:48:59+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "X-WP-User": "admin", "Content-Length": "362"}, "body": "{\"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\"}", "body_bytes": 362, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:48:59 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "673"}, "body": "{\"data\": {\"id\": 16, \"uid\": \"aaa8340a3d7ccd6a8df7@calendar-wp-plugin\", \"title\": \"Smoke 4th Sunday Anchor\", \"description\": \"anchor-normalization\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"start_datetime\": \"2026-05-24T15:00:00+01:00\", \"end_datetime\": \"2026-05-24T16:00:00+01:00\", \"repeat_type\": \"monthly\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"weekday_of_month\", \"repeat_nth_day\": null, \"repeat_nth_pos\": 4, \"repeat_nth_weekday\": 0, \"repeat_range_mode\": \"no_end\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"d7f9a72c4aaad948\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T15:48:59+00:00\"}}", "body_bytes": 673, "body_truncated": false}} +{"ts": "2026-03-30T15:49:00+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/16/occurrences/2026-05-24T15:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:49:00 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T15:49:00+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/16.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:49:00 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"058abb73473f9ec4\"", "Content-Length": "420"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:aaa8340a3d7ccd6a8df7@calendar-wp-plugin\r\nDTSTAMP:20260330T154900Z\r\nSUMMARY:Smoke 4th Sunday Anchor\r\nDESCRIPTION:anchor-normalization\r\nDTSTART;TZID=Europe/London:20260524T150000\r\nDTEND;TZID=Europe/London:20260524T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SU;BYSETPOS=4\r\nEXDATE;TZID=Europe/London:20260524T150000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 420, "body_truncated": false}} +{"ts": "2026-03-30T15:49:00+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "X-WP-User": "admin", "Content-Length": "295"}, "body": "{\"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\"}", "body_bytes": 295, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:49:00 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "655"}, "body": "{\"data\": {\"id\": 17, \"uid\": \"6570fd6aeace58578ae1@calendar-wp-plugin\", \"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_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"until\", \"repeat_count\": null, \"repeat_until\": \"2026-03-19\", \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"29e14a872aca1250\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T15:49:00+00:00\"}}", "body_bytes": 655, "body_truncated": false}} +{"ts": "2026-03-30T15:49:00+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/17/occurrences/2026-03-11T11:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:49:00 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T15:49:00+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/17/occurrences?from=2026-03-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:49:00 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "5212"}, "body": "{\"data\": [{\"event_id\": 17, \"uid\": \"6570fd6aeace58578ae1@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-02T10:00:00+00:00\", \"occurrence_end\": \"2026-03-02T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"6570fd6aeace58578ae1@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-03T10:00:00+00:00\", \"occurrence_end\": \"2026-03-03T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"6570fd6aeace58578ae1@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-04T10:00:00+00:00\", \"occurrence_end\": \"2026-03-04T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"6570fd6aeace58578ae1@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-05T10:00:00+00:00\", \"occurrence_end\": \"2026-03-05T11:00:00", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T15:49:00+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/17/occurrences/2026-03-10", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:49:00 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T15:49:00+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/17/occurrences?from=2026-03-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:49:00 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "4906"}, "body": "{\"data\": [{\"event_id\": 17, \"uid\": \"6570fd6aeace58578ae1@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-02T10:00:00+00:00\", \"occurrence_end\": \"2026-03-02T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"6570fd6aeace58578ae1@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-03T10:00:00+00:00\", \"occurrence_end\": \"2026-03-03T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"6570fd6aeace58578ae1@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-04T10:00:00+00:00\", \"occurrence_end\": \"2026-03-04T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"6570fd6aeace58578ae1@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-05T10:00:00+00:00\", \"occurrence_end\": \"2026-03-05T11:00:00", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T15:49:00+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-exdate-import.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "323"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-exdate-import-001\nSUMMARY:Smoke EXDATE Import\nDTSTART;TZID=Europe/London:20260302T100000\nDTEND;TZID=Europe/London:20260302T110000\nRRULE:FREQ=DAILY;UNTIL=20260319T235959\nEXDATE;TZID=Europe/London:20260310T100000,20260311T100000\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 323, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:49:00 GMT", "ETag": "\"031e00c7b2437a31\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T15:49:00+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:49:00 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "11645"}, "body": "{\"data\": [{\"id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"start_datetime\": \"2026-04-01T10:00:00+01:00\", \"end_datetime\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"e6a46365a4181790\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T15:48:57+00:00\"}, {\"id\": 2, \"uid\": \"fixture-ce-002@calendar-wp-plugin\", \"title\": \"Office Closed\", \"description\": \"Public holiday closure.\", \"location\": \"HQ\", \"category\": \"Operations\", \"all_day_event\": true, \"start_datetime\": \"2026-05-04\", \"end_datetime\": \"2026-05-05\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"d80b6", "body_bytes": 3927, "body_truncated": true}} +{"ts": "2026-03-30T15:49:00+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/18/occurrences?from=2026-03-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:49:00 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "4474"}, "body": "{\"data\": [{\"event_id\": 18, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-02T10:00:00+01:00\", \"occurrence_end\": \"2026-03-02T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 18, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-03T10:00:00+01:00\", \"occurrence_end\": \"2026-03-03T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 18, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-04T10:00:00+01:00\", \"occurrence_end\": \"2026-03-04T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 18, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-05T10:00:00+01:00\", \"occurrence_end\": \"2026-03-05T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 18, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE ", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T15:49:00+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-cancelled-occurrence.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "467"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-cancelled-occurrence-001\nSUMMARY:Smoke Cancelled Occurrence\nDTSTART;TZID=Europe/London:20260408T123000\nDTEND;TZID=Europe/London:20260408T133000\nRRULE:FREQ=WEEKLY\nEND:VEVENT\nBEGIN:VEVENT\nUID:smoke-cancelled-occurrence-001\nRECURRENCE-ID;TZID=Europe/London:20260506T123000\nDTSTART;TZID=Europe/London:20260506T123000\nDTEND;TZID=Europe/London:20260506T133000\nSTATUS:CANCELLED\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 467, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:49:00 GMT", "ETag": "\"e715872930057161\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T15:49:00+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-cancelled-occurrence.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:49:00 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"e715872930057161\"", "Content-Length": "359"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-cancelled-occurrence-001\r\nDTSTAMP:20260330T154900Z\r\nSUMMARY:Smoke Cancelled Occurrence\r\nDTSTART;TZID=Europe/London:20260408T123000\r\nDTEND;TZID=Europe/London:20260408T133000\r\nRRULE:FREQ=WEEKLY\r\nEXDATE;TZID=Europe/London:20260506T123000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 359, "body_truncated": false}} +{"ts": "2026-03-30T15:49:00+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:49:00 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "12273"}, "body": "{\"data\": [{\"id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"start_datetime\": \"2026-04-01T10:00:00+01:00\", \"end_datetime\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"e6a46365a4181790\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T15:48:57+00:00\"}, {\"id\": 2, \"uid\": \"fixture-ce-002@calendar-wp-plugin\", \"title\": \"Office Closed\", \"description\": \"Public holiday closure.\", \"location\": \"HQ\", \"category\": \"Operations\", \"all_day_event\": true, \"start_datetime\": \"2026-05-04\", \"end_datetime\": \"2026-05-05\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"d80b6", "body_bytes": 3927, "body_truncated": true}} +{"ts": "2026-03-30T15:49:00+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/19/occurrences?from=2026-05-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:49:00 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "892"}, "body": "{\"data\": [{\"event_id\": 19, \"uid\": \"smoke-cancelled-occurrence-001\", \"title\": \"Smoke Cancelled Occurrence\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-13T12:30:00+01:00\", \"occurrence_end\": \"2026-05-13T13:30:00+01:00\", \"repeat_type\": \"weekly\"}, {\"event_id\": 19, \"uid\": \"smoke-cancelled-occurrence-001\", \"title\": \"Smoke Cancelled Occurrence\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-20T12:30:00+01:00\", \"occurrence_end\": \"2026-05-20T13:30:00+01:00\", \"repeat_type\": \"weekly\"}, {\"event_id\": 19, \"uid\": \"smoke-cancelled-occurrence-001\", \"title\": \"Smoke Cancelled Occurrence\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-27T12:30:00+01:00\", \"occurrence_end\": \"2026-05-27T13:30:00+01:00\", \"repeat_type\": \"weekly\"}]}", "body_bytes": 892, "body_truncated": false}} +{"ts": "2026-03-30T15:49:00+00:00", "client": "127.0.0.1", "method": "GET", "path": "/calendar", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:49:00 GMT", "Content-Type": "text/html; charset=utf-8", "Content-Length": "43447"}, "body": "[omitted html payload: 3934 bytes]", "body_bytes": 3934, "body_truncated": true}} +{"ts": "2026-03-30T15:54:59+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:54:59 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "6585"}, "body": "{\"data\": [{\"id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"start_datetime\": \"2026-04-01T10:00:00+01:00\", \"end_datetime\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"a7b4a62ce837068b\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T15:54:56+00:00\"}, {\"id\": 2, \"uid\": \"fixture-ce-002@calendar-wp-plugin\", \"title\": \"Office Closed\", \"description\": \"Public holiday closure.\", \"location\": \"HQ\", \"category\": \"Operations\", \"all_day_event\": true, \"start_datetime\": \"2026-05-04\", \"end_datetime\": \"2026-05-05\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"3d75a", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T15:54:59+00:00", "client": "127.0.0.1", "method": "GET", "path": "/calendar.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:54:59 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"48ac2dd451d7868f\"", "Content-Length": "3132"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nCALSCALE:GREGORIAN\r\nX-WR-TIMEZONE:Europe/London\r\nBEGIN:VEVENT\r\nUID:fixture-ce-001@calendar-wp-plugin\r\nDTSTAMP:20260330T155459Z\r\nSUMMARY:Board Meeting\r\nDESCRIPTION:Quarterly board review.\r\nLOCATION:Room A\r\nCATEGORIES:Governance\r\nDTSTART;TZID=Europe/London:20260401T100000\r\nDTEND;TZID=Europe/London:20260401T113000\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-002@calendar-wp-plugin\r\nDTSTAMP:20260330T155459Z\r\nSUMMARY:Office Closed\r\nDESCRIPTION:Public holiday closure.\r\nLOCATION:HQ\r\nCATEGORIES:Operations\r\nDTSTART;VALUE=DATE:20260504\r\nDTEND;VALUE=DATE:20260505\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-003@calendar-wp-plugin\r\nDTSTAMP:20260330T155459Z\r\nSUMMARY:Daily Standup\r\nDESCRIPTION:15 minute sync.\r\nLOCATION:Online\r\nCATEGORIES:Team\r\nDTSTART;TZID=Europe/London:20260406T090000\r\nDTEND;TZID=Europe/London:20260406T091500\r\nRRULE:FREQ=DAILY;COUNT=10\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-004@calendar-wp-plugin\r\nDTSTAMP:20260330T155459Z\r\nSUMMARY:Community Lunch\r\nDESCRIPTION:Weekly community lunch.\r\nLOCATION:Cafeteria\r\nCATEGORIES:Community\r\nDTSTART;TZID=Europe/London:20260408T123000\r\nDTEND;TZID=Europe/London:20260408T133000\r\n ...(truncated)", "body_bytes": 3132, "body_truncated": false}} +{"ts": "2026-03-30T15:54:59+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/1.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:54:59 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"a7b4a62ce837068b\"", "Content-Length": "364"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-001@calendar-wp-plugin\r\nDTSTAMP:20260330T155459Z\r\nSUMMARY:Board Meeting\r\nDESCRIPTION:Quarterly board review.\r\nLOCATION:Room A\r\nCATEGORIES:Governance\r\nDTSTART;TZID=Europe/London:20260401T100000\r\nDTEND;TZID=Europe/London:20260401T113000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 364, "body_truncated": false}} +{"ts": "2026-03-30T15:54:59+00:00", "client": "127.0.0.1", "method": "REPORT", "path": "/caldav/calendars/public/", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/xml; charset=utf-8", "Depth": "1", "Content-Length": "239"}, "body": "\n\n \n /caldav/calendars/public/4.ics\n\n", "body_bytes": 239, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:54:59 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "997"}, "body": "\n\n\n /caldav/calendars/public/4.ics\n \n \n \"ab57181d2c00fcd5\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-004@calendar-wp-plugin\r\nDTSTAMP:20260330T155459Z\r\nSUMMARY:Community Lunch\r\nDESCRIPTION:Weekly community lunch.\r\nLOCATION:Cafeteria\r\nCATEGORIES:Community\r\nDTSTART;TZID=Europe/London:20260408T123000\r\nDTEND;TZID=Europe/London:20260408T133000\r\nRRULE:FREQ=WEEKLY\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n", "body_bytes": 997, "body_truncated": false}} +{"ts": "2026-03-30T15:54:59+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/10/occurrences/2026-04-17T14:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:54:59 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T15:54:59+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "X-WP-User": "admin", "Content-Length": "287"}, "body": "{\"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}", "body_bytes": 287, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:54:59 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "647"}, "body": "{\"data\": {\"id\": 11, \"uid\": \"d9d622c02ebfdbf83dab@calendar-wp-plugin\", \"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_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"count\", \"repeat_count\": 3, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"cbd5aab08f2de7f2\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T15:54:59+00:00\"}}", "body_bytes": 647, "body_truncated": false}} +{"ts": "2026-03-30T15:54:59+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/11/occurrences?from=2026-04-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:54:59 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "937"}, "body": "{\"data\": [{\"event_id\": 11, \"uid\": \"d9d622c02ebfdbf83dab@calendar-wp-plugin\", \"title\": \"Smoke Occurrences API\", \"description\": \"occ-endpoint\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-01T10:00:00+01:00\", \"occurrence_end\": \"2026-04-01T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 11, \"uid\": \"d9d622c02ebfdbf83dab@calendar-wp-plugin\", \"title\": \"Smoke Occurrences API\", \"description\": \"occ-endpoint\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-02T10:00:00+01:00\", \"occurrence_end\": \"2026-04-02T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 11, \"uid\": \"d9d622c02ebfdbf83dab@calendar-wp-plugin\", \"title\": \"Smoke Occurrences API\", \"description\": \"occ-endpoint\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-03T10:00:00+01:00\", \"occurrence_end\": \"2026-04-03T11:00:00+01:00\", \"repeat_type\": \"daily\"}]}", "body_bytes": 937, "body_truncated": false}} +{"ts": "2026-03-30T15:54:59+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/11/occurrences/2026-04-02T10:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:54:59 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T15:54:59+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/11/occurrences/2026-04-02T10:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:54:59 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T15:54:59+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/11/occurrences?from=2026-04-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:54:59 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "628"}, "body": "{\"data\": [{\"event_id\": 11, \"uid\": \"d9d622c02ebfdbf83dab@calendar-wp-plugin\", \"title\": \"Smoke Occurrences API\", \"description\": \"occ-endpoint\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-01T10:00:00+01:00\", \"occurrence_end\": \"2026-04-01T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 11, \"uid\": \"d9d622c02ebfdbf83dab@calendar-wp-plugin\", \"title\": \"Smoke Occurrences API\", \"description\": \"occ-endpoint\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-03T10:00:00+01:00\", \"occurrence_end\": \"2026-04-03T11:00:00+01:00\", \"repeat_type\": \"daily\"}]}", "body_bytes": 628, "body_truncated": false}} +{"ts": "2026-03-30T15:54:59+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-vtimezone.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "454"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN\nVERSION:2.0\nBEGIN:VTIMEZONE\nTZID:Europe/London\nBEGIN:STANDARD\nDTSTART:18471201T000000\nRRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=9\nTZOFFSETFROM:+0115\nTZOFFSETTO:+0000\nTZNAME:GMT\nEND:STANDARD\nEND:VTIMEZONE\nBEGIN:VEVENT\nUID:smoke-vtimezone-parser-001\nSUMMARY:Smoke VTIMEZONE Parse\nDTSTART;TZID=Europe/London:20260423T150000\nDTEND;TZID=Europe/London:20260423T160000\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 454, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:54:59 GMT", "ETag": "\"a11c28068216c2d6\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T15:54:59+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-vtimezone.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:54:59 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"a11c28068216c2d6\"", "Content-Length": "288"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-vtimezone-parser-001\r\nDTSTAMP:20260330T155459Z\r\nSUMMARY:Smoke VTIMEZONE Parse\r\nDTSTART;TZID=Europe/London:20260423T150000\r\nDTEND;TZID=Europe/London:20260423T160000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 288, "body_truncated": false}} +{"ts": "2026-03-30T15:54:59+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-monthly-nth.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "261"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-monthly-nth-001\nSUMMARY:Smoke Monthly Nth\nDTSTART;TZID=Europe/London:20260402T150000\nDTEND;TZID=Europe/London:20260402T160000\nRRULE:FREQ=MONTHLY;BYDAY=SU;BYSETPOS=4\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 261, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:54:59 GMT", "ETag": "\"d96489542d0c9095\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T15:54:59+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-monthly-nth.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:54:59 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"d96489542d0c9095\"", "Content-Length": "319"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-nth-001\r\nDTSTAMP:20260330T155459Z\r\nSUMMARY:Smoke Monthly Nth\r\nDTSTART;TZID=Europe/London:20260426T150000\r\nDTEND;TZID=Europe/London:20260426T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SU;BYSETPOS=4\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 319, "body_truncated": false}} +{"ts": "2026-03-30T15:54:59+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-monthly-ordinal.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "259"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-monthly-ordinal-001\nSUMMARY:Smoke Monthly Ordinal\nDTSTART;TZID=Europe/London:20260411T150000\nDTEND;TZID=Europe/London:20260411T160000\nRRULE:FREQ=MONTHLY;BYDAY=2SA\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 259, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:54:59 GMT", "ETag": "\"6e95caf3538b8583\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T15:54:59+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-monthly-ordinal.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:54:59 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"6e95caf3538b8583\"", "Content-Length": "327"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-ordinal-001\r\nDTSTAMP:20260330T155459Z\r\nSUMMARY:Smoke Monthly Ordinal\r\nDTSTART;TZID=Europe/London:20260411T150000\r\nDTEND;TZID=Europe/London:20260411T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SA;BYSETPOS=2\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 327, "body_truncated": false}} +{"ts": "2026-03-30T15:54:59+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-monthly-last.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "264"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-monthly-last-001\nSUMMARY:Smoke Monthly Last\nDTSTART;TZID=Europe/London:20260425T150000\nDTEND;TZID=Europe/London:20260425T160000\nRRULE:FREQ=MONTHLY;BYDAY=SA;BYSETPOS=-1\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 264, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:54:59 GMT", "ETag": "\"bf238a72a05154c6\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T15:54:59+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-monthly-last.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:54:59 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"bf238a72a05154c6\"", "Content-Length": "322"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-last-001\r\nDTSTAMP:20260330T155459Z\r\nSUMMARY:Smoke Monthly Last\r\nDTSTART;TZID=Europe/London:20260425T150000\r\nDTEND;TZID=Europe/London:20260425T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SA;BYSETPOS=-1\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 322, "body_truncated": false}} +{"ts": "2026-03-30T15:54:59+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "X-WP-User": "admin", "Content-Length": "362"}, "body": "{\"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\"}", "body_bytes": 362, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:54:59 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "673"}, "body": "{\"data\": {\"id\": 16, \"uid\": \"84911fe28ff2f9fbdb45@calendar-wp-plugin\", \"title\": \"Smoke 4th Sunday Anchor\", \"description\": \"anchor-normalization\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"start_datetime\": \"2026-05-24T15:00:00+01:00\", \"end_datetime\": \"2026-05-24T16:00:00+01:00\", \"repeat_type\": \"monthly\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"weekday_of_month\", \"repeat_nth_day\": null, \"repeat_nth_pos\": 4, \"repeat_nth_weekday\": 0, \"repeat_range_mode\": \"no_end\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"54261a0506aa9388\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T15:54:59+00:00\"}}", "body_bytes": 673, "body_truncated": false}} +{"ts": "2026-03-30T15:54:59+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/16/occurrences/2026-05-24T15:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:54:59 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T15:54:59+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/16.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:54:59 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"26ffe89dee62df60\"", "Content-Length": "420"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:84911fe28ff2f9fbdb45@calendar-wp-plugin\r\nDTSTAMP:20260330T155459Z\r\nSUMMARY:Smoke 4th Sunday Anchor\r\nDESCRIPTION:anchor-normalization\r\nDTSTART;TZID=Europe/London:20260524T150000\r\nDTEND;TZID=Europe/London:20260524T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SU;BYSETPOS=4\r\nEXDATE;TZID=Europe/London:20260524T150000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 420, "body_truncated": false}} +{"ts": "2026-03-30T15:54:59+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "X-WP-User": "admin", "Content-Length": "295"}, "body": "{\"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\"}", "body_bytes": 295, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:54:59 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "655"}, "body": "{\"data\": {\"id\": 17, \"uid\": \"8af4eae9f0c214d146d5@calendar-wp-plugin\", \"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_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"until\", \"repeat_count\": null, \"repeat_until\": \"2026-03-19\", \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"7bc1ad808fbfd5fd\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T15:54:59+00:00\"}}", "body_bytes": 655, "body_truncated": false}} +{"ts": "2026-03-30T15:54:59+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/17/occurrences/2026-03-11T11:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:54:59 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T15:54:59+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/17/occurrences?from=2026-03-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:54:59 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "5212"}, "body": "{\"data\": [{\"event_id\": 17, \"uid\": \"8af4eae9f0c214d146d5@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-02T10:00:00+00:00\", \"occurrence_end\": \"2026-03-02T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"8af4eae9f0c214d146d5@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-03T10:00:00+00:00\", \"occurrence_end\": \"2026-03-03T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"8af4eae9f0c214d146d5@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-04T10:00:00+00:00\", \"occurrence_end\": \"2026-03-04T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"8af4eae9f0c214d146d5@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-05T10:00:00+00:00\", \"occurrence_end\": \"2026-03-05T11:00:00", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T15:54:59+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/17/occurrences/2026-03-10", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:54:59 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T15:54:59+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/17/occurrences?from=2026-03-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:54:59 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "4906"}, "body": "{\"data\": [{\"event_id\": 17, \"uid\": \"8af4eae9f0c214d146d5@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-02T10:00:00+00:00\", \"occurrence_end\": \"2026-03-02T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"8af4eae9f0c214d146d5@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-03T10:00:00+00:00\", \"occurrence_end\": \"2026-03-03T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"8af4eae9f0c214d146d5@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-04T10:00:00+00:00\", \"occurrence_end\": \"2026-03-04T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"8af4eae9f0c214d146d5@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-05T10:00:00+00:00\", \"occurrence_end\": \"2026-03-05T11:00:00", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T15:54:59+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-exdate-import.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "323"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-exdate-import-001\nSUMMARY:Smoke EXDATE Import\nDTSTART;TZID=Europe/London:20260302T100000\nDTEND;TZID=Europe/London:20260302T110000\nRRULE:FREQ=DAILY;UNTIL=20260319T235959\nEXDATE;TZID=Europe/London:20260310T100000,20260311T100000\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 323, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:54:59 GMT", "ETag": "\"fe95f3bc4c2e74e1\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T15:54:59+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:54:59 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "11645"}, "body": "{\"data\": [{\"id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"start_datetime\": \"2026-04-01T10:00:00+01:00\", \"end_datetime\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"a7b4a62ce837068b\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T15:54:56+00:00\"}, {\"id\": 2, \"uid\": \"fixture-ce-002@calendar-wp-plugin\", \"title\": \"Office Closed\", \"description\": \"Public holiday closure.\", \"location\": \"HQ\", \"category\": \"Operations\", \"all_day_event\": true, \"start_datetime\": \"2026-05-04\", \"end_datetime\": \"2026-05-05\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"3d75a", "body_bytes": 3927, "body_truncated": true}} +{"ts": "2026-03-30T15:54:59+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/18/occurrences?from=2026-03-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:54:59 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "4474"}, "body": "{\"data\": [{\"event_id\": 18, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-02T10:00:00+01:00\", \"occurrence_end\": \"2026-03-02T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 18, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-03T10:00:00+01:00\", \"occurrence_end\": \"2026-03-03T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 18, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-04T10:00:00+01:00\", \"occurrence_end\": \"2026-03-04T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 18, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-05T10:00:00+01:00\", \"occurrence_end\": \"2026-03-05T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 18, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE ", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T15:54:59+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-cancelled-occurrence.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "467"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-cancelled-occurrence-001\nSUMMARY:Smoke Cancelled Occurrence\nDTSTART;TZID=Europe/London:20260408T123000\nDTEND;TZID=Europe/London:20260408T133000\nRRULE:FREQ=WEEKLY\nEND:VEVENT\nBEGIN:VEVENT\nUID:smoke-cancelled-occurrence-001\nRECURRENCE-ID;TZID=Europe/London:20260506T123000\nDTSTART;TZID=Europe/London:20260506T123000\nDTEND;TZID=Europe/London:20260506T133000\nSTATUS:CANCELLED\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 467, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:54:59 GMT", "ETag": "\"0bbc85496d9bb64a\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T15:54:59+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-cancelled-occurrence.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:54:59 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"0bbc85496d9bb64a\"", "Content-Length": "359"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-cancelled-occurrence-001\r\nDTSTAMP:20260330T155459Z\r\nSUMMARY:Smoke Cancelled Occurrence\r\nDTSTART;TZID=Europe/London:20260408T123000\r\nDTEND;TZID=Europe/London:20260408T133000\r\nRRULE:FREQ=WEEKLY\r\nEXDATE;TZID=Europe/London:20260506T123000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 359, "body_truncated": false}} +{"ts": "2026-03-30T15:55:00+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:55:00 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "12273"}, "body": "{\"data\": [{\"id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"start_datetime\": \"2026-04-01T10:00:00+01:00\", \"end_datetime\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"a7b4a62ce837068b\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T15:54:56+00:00\"}, {\"id\": 2, \"uid\": \"fixture-ce-002@calendar-wp-plugin\", \"title\": \"Office Closed\", \"description\": \"Public holiday closure.\", \"location\": \"HQ\", \"category\": \"Operations\", \"all_day_event\": true, \"start_datetime\": \"2026-05-04\", \"end_datetime\": \"2026-05-05\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"3d75a", "body_bytes": 3927, "body_truncated": true}} +{"ts": "2026-03-30T15:55:00+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/19/occurrences?from=2026-05-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:55:00 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "892"}, "body": "{\"data\": [{\"event_id\": 19, \"uid\": \"smoke-cancelled-occurrence-001\", \"title\": \"Smoke Cancelled Occurrence\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-13T12:30:00+01:00\", \"occurrence_end\": \"2026-05-13T13:30:00+01:00\", \"repeat_type\": \"weekly\"}, {\"event_id\": 19, \"uid\": \"smoke-cancelled-occurrence-001\", \"title\": \"Smoke Cancelled Occurrence\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-20T12:30:00+01:00\", \"occurrence_end\": \"2026-05-20T13:30:00+01:00\", \"repeat_type\": \"weekly\"}, {\"event_id\": 19, \"uid\": \"smoke-cancelled-occurrence-001\", \"title\": \"Smoke Cancelled Occurrence\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-27T12:30:00+01:00\", \"occurrence_end\": \"2026-05-27T13:30:00+01:00\", \"repeat_type\": \"weekly\"}]}", "body_bytes": 892, "body_truncated": false}} +{"ts": "2026-03-30T15:55:00+00:00", "client": "127.0.0.1", "method": "GET", "path": "/calendar", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:55:00 GMT", "Content-Type": "text/html; charset=utf-8", "Content-Length": "43447"}, "body": "[omitted html payload: 3934 bytes]", "body_bytes": 3934, "body_truncated": true}} +{"ts": "2026-03-30T15:56:45+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:56:45 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "6585"}, "body": "{\"data\": [{\"id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"start_datetime\": \"2026-04-01T10:00:00+01:00\", \"end_datetime\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"a7bc6e565475a9a8\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T15:56:42+00:00\"}, {\"id\": 2, \"uid\": \"fixture-ce-002@calendar-wp-plugin\", \"title\": \"Office Closed\", \"description\": \"Public holiday closure.\", \"location\": \"HQ\", \"category\": \"Operations\", \"all_day_event\": true, \"start_datetime\": \"2026-05-04\", \"end_datetime\": \"2026-05-05\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"0388f", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T15:56:45+00:00", "client": "127.0.0.1", "method": "GET", "path": "/calendar.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:56:45 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"75c7a06e3f9dde8c\"", "Content-Length": "3132"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nCALSCALE:GREGORIAN\r\nX-WR-TIMEZONE:Europe/London\r\nBEGIN:VEVENT\r\nUID:fixture-ce-001@calendar-wp-plugin\r\nDTSTAMP:20260330T155645Z\r\nSUMMARY:Board Meeting\r\nDESCRIPTION:Quarterly board review.\r\nLOCATION:Room A\r\nCATEGORIES:Governance\r\nDTSTART;TZID=Europe/London:20260401T100000\r\nDTEND;TZID=Europe/London:20260401T113000\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-002@calendar-wp-plugin\r\nDTSTAMP:20260330T155645Z\r\nSUMMARY:Office Closed\r\nDESCRIPTION:Public holiday closure.\r\nLOCATION:HQ\r\nCATEGORIES:Operations\r\nDTSTART;VALUE=DATE:20260504\r\nDTEND;VALUE=DATE:20260505\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-003@calendar-wp-plugin\r\nDTSTAMP:20260330T155645Z\r\nSUMMARY:Daily Standup\r\nDESCRIPTION:15 minute sync.\r\nLOCATION:Online\r\nCATEGORIES:Team\r\nDTSTART;TZID=Europe/London:20260406T090000\r\nDTEND;TZID=Europe/London:20260406T091500\r\nRRULE:FREQ=DAILY;COUNT=10\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-004@calendar-wp-plugin\r\nDTSTAMP:20260330T155645Z\r\nSUMMARY:Community Lunch\r\nDESCRIPTION:Weekly community lunch.\r\nLOCATION:Cafeteria\r\nCATEGORIES:Community\r\nDTSTART;TZID=Europe/London:20260408T123000\r\nDTEND;TZID=Europe/London:20260408T133000\r\n ...(truncated)", "body_bytes": 3132, "body_truncated": false}} +{"ts": "2026-03-30T15:56:45+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/1.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:56:45 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"a7bc6e565475a9a8\"", "Content-Length": "364"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-001@calendar-wp-plugin\r\nDTSTAMP:20260330T155645Z\r\nSUMMARY:Board Meeting\r\nDESCRIPTION:Quarterly board review.\r\nLOCATION:Room A\r\nCATEGORIES:Governance\r\nDTSTART;TZID=Europe/London:20260401T100000\r\nDTEND;TZID=Europe/London:20260401T113000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 364, "body_truncated": false}} +{"ts": "2026-03-30T15:56:45+00:00", "client": "127.0.0.1", "method": "REPORT", "path": "/caldav/calendars/public/", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/xml; charset=utf-8", "Depth": "1", "Content-Length": "239"}, "body": "\n\n \n /caldav/calendars/public/4.ics\n\n", "body_bytes": 239, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:56:45 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "997"}, "body": "\n\n\n /caldav/calendars/public/4.ics\n \n \n \"dba8ac559404c969\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-004@calendar-wp-plugin\r\nDTSTAMP:20260330T155645Z\r\nSUMMARY:Community Lunch\r\nDESCRIPTION:Weekly community lunch.\r\nLOCATION:Cafeteria\r\nCATEGORIES:Community\r\nDTSTART;TZID=Europe/London:20260408T123000\r\nDTEND;TZID=Europe/London:20260408T133000\r\nRRULE:FREQ=WEEKLY\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n", "body_bytes": 997, "body_truncated": false}} +{"ts": "2026-03-30T15:56:45+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/10/occurrences/2026-04-17T14:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:56:45 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T15:56:45+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "X-WP-User": "admin", "Content-Length": "287"}, "body": "{\"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}", "body_bytes": 287, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:56:45 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "647"}, "body": "{\"data\": {\"id\": 11, \"uid\": \"c5a0ca5d685c8908a802@calendar-wp-plugin\", \"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_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"count\", \"repeat_count\": 3, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"01c1e859a0c1d3ee\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T15:56:45+00:00\"}}", "body_bytes": 647, "body_truncated": false}} +{"ts": "2026-03-30T15:56:45+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/11/occurrences?from=2026-04-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:56:45 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "937"}, "body": "{\"data\": [{\"event_id\": 11, \"uid\": \"c5a0ca5d685c8908a802@calendar-wp-plugin\", \"title\": \"Smoke Occurrences API\", \"description\": \"occ-endpoint\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-01T10:00:00+01:00\", \"occurrence_end\": \"2026-04-01T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 11, \"uid\": \"c5a0ca5d685c8908a802@calendar-wp-plugin\", \"title\": \"Smoke Occurrences API\", \"description\": \"occ-endpoint\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-02T10:00:00+01:00\", \"occurrence_end\": \"2026-04-02T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 11, \"uid\": \"c5a0ca5d685c8908a802@calendar-wp-plugin\", \"title\": \"Smoke Occurrences API\", \"description\": \"occ-endpoint\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-03T10:00:00+01:00\", \"occurrence_end\": \"2026-04-03T11:00:00+01:00\", \"repeat_type\": \"daily\"}]}", "body_bytes": 937, "body_truncated": false}} +{"ts": "2026-03-30T15:56:45+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/11/occurrences/2026-04-02T10:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:56:45 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T15:56:45+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/11/occurrences/2026-04-02T10:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:56:45 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T15:56:45+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/11/occurrences?from=2026-04-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:56:45 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "628"}, "body": "{\"data\": [{\"event_id\": 11, \"uid\": \"c5a0ca5d685c8908a802@calendar-wp-plugin\", \"title\": \"Smoke Occurrences API\", \"description\": \"occ-endpoint\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-01T10:00:00+01:00\", \"occurrence_end\": \"2026-04-01T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 11, \"uid\": \"c5a0ca5d685c8908a802@calendar-wp-plugin\", \"title\": \"Smoke Occurrences API\", \"description\": \"occ-endpoint\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-03T10:00:00+01:00\", \"occurrence_end\": \"2026-04-03T11:00:00+01:00\", \"repeat_type\": \"daily\"}]}", "body_bytes": 628, "body_truncated": false}} +{"ts": "2026-03-30T15:56:45+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-vtimezone.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "454"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN\nVERSION:2.0\nBEGIN:VTIMEZONE\nTZID:Europe/London\nBEGIN:STANDARD\nDTSTART:18471201T000000\nRRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=9\nTZOFFSETFROM:+0115\nTZOFFSETTO:+0000\nTZNAME:GMT\nEND:STANDARD\nEND:VTIMEZONE\nBEGIN:VEVENT\nUID:smoke-vtimezone-parser-001\nSUMMARY:Smoke VTIMEZONE Parse\nDTSTART;TZID=Europe/London:20260423T150000\nDTEND;TZID=Europe/London:20260423T160000\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 454, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:56:45 GMT", "ETag": "\"e2be2b99c39985f7\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T15:56:45+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-vtimezone.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:56:45 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"e2be2b99c39985f7\"", "Content-Length": "288"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-vtimezone-parser-001\r\nDTSTAMP:20260330T155645Z\r\nSUMMARY:Smoke VTIMEZONE Parse\r\nDTSTART;TZID=Europe/London:20260423T150000\r\nDTEND;TZID=Europe/London:20260423T160000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 288, "body_truncated": false}} +{"ts": "2026-03-30T15:56:45+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-monthly-nth.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "261"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-monthly-nth-001\nSUMMARY:Smoke Monthly Nth\nDTSTART;TZID=Europe/London:20260402T150000\nDTEND;TZID=Europe/London:20260402T160000\nRRULE:FREQ=MONTHLY;BYDAY=SU;BYSETPOS=4\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 261, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:56:45 GMT", "ETag": "\"a380dceb2ca70302\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T15:56:45+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-monthly-nth.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:56:45 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"a380dceb2ca70302\"", "Content-Length": "319"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-nth-001\r\nDTSTAMP:20260330T155645Z\r\nSUMMARY:Smoke Monthly Nth\r\nDTSTART;TZID=Europe/London:20260426T150000\r\nDTEND;TZID=Europe/London:20260426T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SU;BYSETPOS=4\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 319, "body_truncated": false}} +{"ts": "2026-03-30T15:56:45+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-monthly-ordinal.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "259"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-monthly-ordinal-001\nSUMMARY:Smoke Monthly Ordinal\nDTSTART;TZID=Europe/London:20260411T150000\nDTEND;TZID=Europe/London:20260411T160000\nRRULE:FREQ=MONTHLY;BYDAY=2SA\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 259, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:56:45 GMT", "ETag": "\"62cfa347f4a3e87c\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T15:56:45+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-monthly-ordinal.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:56:45 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"62cfa347f4a3e87c\"", "Content-Length": "327"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-ordinal-001\r\nDTSTAMP:20260330T155645Z\r\nSUMMARY:Smoke Monthly Ordinal\r\nDTSTART;TZID=Europe/London:20260411T150000\r\nDTEND;TZID=Europe/London:20260411T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SA;BYSETPOS=2\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 327, "body_truncated": false}} +{"ts": "2026-03-30T15:56:45+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-monthly-last.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "264"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-monthly-last-001\nSUMMARY:Smoke Monthly Last\nDTSTART;TZID=Europe/London:20260425T150000\nDTEND;TZID=Europe/London:20260425T160000\nRRULE:FREQ=MONTHLY;BYDAY=SA;BYSETPOS=-1\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 264, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:56:45 GMT", "ETag": "\"3e18aabb2c471d1b\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T15:56:45+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-monthly-last.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:56:45 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"3e18aabb2c471d1b\"", "Content-Length": "322"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-last-001\r\nDTSTAMP:20260330T155645Z\r\nSUMMARY:Smoke Monthly Last\r\nDTSTART;TZID=Europe/London:20260425T150000\r\nDTEND;TZID=Europe/London:20260425T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SA;BYSETPOS=-1\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 322, "body_truncated": false}} +{"ts": "2026-03-30T15:56:45+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "X-WP-User": "admin", "Content-Length": "362"}, "body": "{\"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\"}", "body_bytes": 362, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:56:45 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "673"}, "body": "{\"data\": {\"id\": 16, \"uid\": \"21ed32483f6fe0854775@calendar-wp-plugin\", \"title\": \"Smoke 4th Sunday Anchor\", \"description\": \"anchor-normalization\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"start_datetime\": \"2026-05-24T15:00:00+01:00\", \"end_datetime\": \"2026-05-24T16:00:00+01:00\", \"repeat_type\": \"monthly\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"weekday_of_month\", \"repeat_nth_day\": null, \"repeat_nth_pos\": 4, \"repeat_nth_weekday\": 0, \"repeat_range_mode\": \"no_end\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"e195c8c718c74ea2\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T15:56:45+00:00\"}}", "body_bytes": 673, "body_truncated": false}} +{"ts": "2026-03-30T15:56:45+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/16/occurrences/2026-05-24T15:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:56:45 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T15:56:45+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/16.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:56:45 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"5aeda0097db3158a\"", "Content-Length": "420"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:21ed32483f6fe0854775@calendar-wp-plugin\r\nDTSTAMP:20260330T155645Z\r\nSUMMARY:Smoke 4th Sunday Anchor\r\nDESCRIPTION:anchor-normalization\r\nDTSTART;TZID=Europe/London:20260524T150000\r\nDTEND;TZID=Europe/London:20260524T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SU;BYSETPOS=4\r\nEXDATE;TZID=Europe/London:20260524T150000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 420, "body_truncated": false}} +{"ts": "2026-03-30T15:56:45+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "X-WP-User": "admin", "Content-Length": "295"}, "body": "{\"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\"}", "body_bytes": 295, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:56:45 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "655"}, "body": "{\"data\": {\"id\": 17, \"uid\": \"ff4ad7f18ee79cf13356@calendar-wp-plugin\", \"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_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"until\", \"repeat_count\": null, \"repeat_until\": \"2026-03-19\", \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"107f90ee4fdcd3da\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T15:56:45+00:00\"}}", "body_bytes": 655, "body_truncated": false}} +{"ts": "2026-03-30T15:56:45+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/17/occurrences/2026-03-11T11:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:56:45 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T15:56:45+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/17/occurrences?from=2026-03-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:56:45 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "5212"}, "body": "{\"data\": [{\"event_id\": 17, \"uid\": \"ff4ad7f18ee79cf13356@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-02T10:00:00+00:00\", \"occurrence_end\": \"2026-03-02T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"ff4ad7f18ee79cf13356@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-03T10:00:00+00:00\", \"occurrence_end\": \"2026-03-03T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"ff4ad7f18ee79cf13356@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-04T10:00:00+00:00\", \"occurrence_end\": \"2026-03-04T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"ff4ad7f18ee79cf13356@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-05T10:00:00+00:00\", \"occurrence_end\": \"2026-03-05T11:00:00", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T15:56:45+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/17/occurrences/2026-03-10", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:56:45 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T15:56:45+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/17/occurrences?from=2026-03-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:56:45 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "4906"}, "body": "{\"data\": [{\"event_id\": 17, \"uid\": \"ff4ad7f18ee79cf13356@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-02T10:00:00+00:00\", \"occurrence_end\": \"2026-03-02T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"ff4ad7f18ee79cf13356@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-03T10:00:00+00:00\", \"occurrence_end\": \"2026-03-03T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"ff4ad7f18ee79cf13356@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-04T10:00:00+00:00\", \"occurrence_end\": \"2026-03-04T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"ff4ad7f18ee79cf13356@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-05T10:00:00+00:00\", \"occurrence_end\": \"2026-03-05T11:00:00", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T15:56:45+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-exdate-import.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "323"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-exdate-import-001\nSUMMARY:Smoke EXDATE Import\nDTSTART;TZID=Europe/London:20260302T100000\nDTEND;TZID=Europe/London:20260302T110000\nRRULE:FREQ=DAILY;UNTIL=20260319T235959\nEXDATE;TZID=Europe/London:20260310T100000,20260311T100000\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 323, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:56:45 GMT", "ETag": "\"25be1f0412a1607c\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T15:56:45+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:56:45 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "11645"}, "body": "{\"data\": [{\"id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"start_datetime\": \"2026-04-01T10:00:00+01:00\", \"end_datetime\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"a7bc6e565475a9a8\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T15:56:42+00:00\"}, {\"id\": 2, \"uid\": \"fixture-ce-002@calendar-wp-plugin\", \"title\": \"Office Closed\", \"description\": \"Public holiday closure.\", \"location\": \"HQ\", \"category\": \"Operations\", \"all_day_event\": true, \"start_datetime\": \"2026-05-04\", \"end_datetime\": \"2026-05-05\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"0388f", "body_bytes": 3927, "body_truncated": true}} +{"ts": "2026-03-30T15:56:45+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/18/occurrences?from=2026-03-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:56:45 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "4474"}, "body": "{\"data\": [{\"event_id\": 18, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-02T10:00:00+01:00\", \"occurrence_end\": \"2026-03-02T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 18, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-03T10:00:00+01:00\", \"occurrence_end\": \"2026-03-03T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 18, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-04T10:00:00+01:00\", \"occurrence_end\": \"2026-03-04T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 18, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-05T10:00:00+01:00\", \"occurrence_end\": \"2026-03-05T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 18, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE ", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T15:56:45+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-cancelled-occurrence.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "467"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-cancelled-occurrence-001\nSUMMARY:Smoke Cancelled Occurrence\nDTSTART;TZID=Europe/London:20260408T123000\nDTEND;TZID=Europe/London:20260408T133000\nRRULE:FREQ=WEEKLY\nEND:VEVENT\nBEGIN:VEVENT\nUID:smoke-cancelled-occurrence-001\nRECURRENCE-ID;TZID=Europe/London:20260506T123000\nDTSTART;TZID=Europe/London:20260506T123000\nDTEND;TZID=Europe/London:20260506T133000\nSTATUS:CANCELLED\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 467, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:56:45 GMT", "ETag": "\"b4d712a7912ed997\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T15:56:45+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-cancelled-occurrence.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:56:45 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"b4d712a7912ed997\"", "Content-Length": "359"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-cancelled-occurrence-001\r\nDTSTAMP:20260330T155645Z\r\nSUMMARY:Smoke Cancelled Occurrence\r\nDTSTART;TZID=Europe/London:20260408T123000\r\nDTEND;TZID=Europe/London:20260408T133000\r\nRRULE:FREQ=WEEKLY\r\nEXDATE;TZID=Europe/London:20260506T123000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 359, "body_truncated": false}} +{"ts": "2026-03-30T15:56:45+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:56:45 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "12273"}, "body": "{\"data\": [{\"id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"start_datetime\": \"2026-04-01T10:00:00+01:00\", \"end_datetime\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"a7bc6e565475a9a8\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T15:56:42+00:00\"}, {\"id\": 2, \"uid\": \"fixture-ce-002@calendar-wp-plugin\", \"title\": \"Office Closed\", \"description\": \"Public holiday closure.\", \"location\": \"HQ\", \"category\": \"Operations\", \"all_day_event\": true, \"start_datetime\": \"2026-05-04\", \"end_datetime\": \"2026-05-05\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"0388f", "body_bytes": 3927, "body_truncated": true}} +{"ts": "2026-03-30T15:56:45+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/19/occurrences?from=2026-05-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:56:45 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "892"}, "body": "{\"data\": [{\"event_id\": 19, \"uid\": \"smoke-cancelled-occurrence-001\", \"title\": \"Smoke Cancelled Occurrence\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-13T12:30:00+01:00\", \"occurrence_end\": \"2026-05-13T13:30:00+01:00\", \"repeat_type\": \"weekly\"}, {\"event_id\": 19, \"uid\": \"smoke-cancelled-occurrence-001\", \"title\": \"Smoke Cancelled Occurrence\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-20T12:30:00+01:00\", \"occurrence_end\": \"2026-05-20T13:30:00+01:00\", \"repeat_type\": \"weekly\"}, {\"event_id\": 19, \"uid\": \"smoke-cancelled-occurrence-001\", \"title\": \"Smoke Cancelled Occurrence\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-27T12:30:00+01:00\", \"occurrence_end\": \"2026-05-27T13:30:00+01:00\", \"repeat_type\": \"weekly\"}]}", "body_bytes": 892, "body_truncated": false}} +{"ts": "2026-03-30T15:56:45+00:00", "client": "127.0.0.1", "method": "GET", "path": "/calendar", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 15:56:45 GMT", "Content-Type": "text/html; charset=utf-8", "Content-Length": "43447"}, "body": "[omitted html payload: 3934 bytes]", "body_bytes": 3934, "body_truncated": true}} +{"ts": "2026-03-30T16:00:13+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 16:00:13 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "6585"}, "body": "{\"data\": [{\"id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"start_datetime\": \"2026-04-01T10:00:00+01:00\", \"end_datetime\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"50884b9c7f52d7b4\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T16:00:11+00:00\"}, {\"id\": 2, \"uid\": \"fixture-ce-002@calendar-wp-plugin\", \"title\": \"Office Closed\", \"description\": \"Public holiday closure.\", \"location\": \"HQ\", \"category\": \"Operations\", \"all_day_event\": true, \"start_datetime\": \"2026-05-04\", \"end_datetime\": \"2026-05-05\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"9cc94", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T16:00:13+00:00", "client": "127.0.0.1", "method": "GET", "path": "/calendar.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 16:00:13 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"1d9cf2007a92fe76\"", "Content-Length": "3132"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nCALSCALE:GREGORIAN\r\nX-WR-TIMEZONE:Europe/London\r\nBEGIN:VEVENT\r\nUID:fixture-ce-001@calendar-wp-plugin\r\nDTSTAMP:20260330T160013Z\r\nSUMMARY:Board Meeting\r\nDESCRIPTION:Quarterly board review.\r\nLOCATION:Room A\r\nCATEGORIES:Governance\r\nDTSTART;TZID=Europe/London:20260401T100000\r\nDTEND;TZID=Europe/London:20260401T113000\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-002@calendar-wp-plugin\r\nDTSTAMP:20260330T160013Z\r\nSUMMARY:Office Closed\r\nDESCRIPTION:Public holiday closure.\r\nLOCATION:HQ\r\nCATEGORIES:Operations\r\nDTSTART;VALUE=DATE:20260504\r\nDTEND;VALUE=DATE:20260505\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-003@calendar-wp-plugin\r\nDTSTAMP:20260330T160013Z\r\nSUMMARY:Daily Standup\r\nDESCRIPTION:15 minute sync.\r\nLOCATION:Online\r\nCATEGORIES:Team\r\nDTSTART;TZID=Europe/London:20260406T090000\r\nDTEND;TZID=Europe/London:20260406T091500\r\nRRULE:FREQ=DAILY;COUNT=10\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-004@calendar-wp-plugin\r\nDTSTAMP:20260330T160013Z\r\nSUMMARY:Community Lunch\r\nDESCRIPTION:Weekly community lunch.\r\nLOCATION:Cafeteria\r\nCATEGORIES:Community\r\nDTSTART;TZID=Europe/London:20260408T123000\r\nDTEND;TZID=Europe/London:20260408T133000\r\n ...(truncated)", "body_bytes": 3132, "body_truncated": false}} +{"ts": "2026-03-30T16:00:13+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/1.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 16:00:13 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"50884b9c7f52d7b4\"", "Content-Length": "364"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-001@calendar-wp-plugin\r\nDTSTAMP:20260330T160013Z\r\nSUMMARY:Board Meeting\r\nDESCRIPTION:Quarterly board review.\r\nLOCATION:Room A\r\nCATEGORIES:Governance\r\nDTSTART;TZID=Europe/London:20260401T100000\r\nDTEND;TZID=Europe/London:20260401T113000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 364, "body_truncated": false}} +{"ts": "2026-03-30T16:00:13+00:00", "client": "127.0.0.1", "method": "REPORT", "path": "/caldav/calendars/public/", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/xml; charset=utf-8", "Depth": "1", "Content-Length": "239"}, "body": "\n\n \n /caldav/calendars/public/4.ics\n\n", "body_bytes": 239, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 16:00:13 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "997"}, "body": "\n\n\n /caldav/calendars/public/4.ics\n \n \n \"d227b3c11080654f\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-004@calendar-wp-plugin\r\nDTSTAMP:20260330T160013Z\r\nSUMMARY:Community Lunch\r\nDESCRIPTION:Weekly community lunch.\r\nLOCATION:Cafeteria\r\nCATEGORIES:Community\r\nDTSTART;TZID=Europe/London:20260408T123000\r\nDTEND;TZID=Europe/London:20260408T133000\r\nRRULE:FREQ=WEEKLY\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n", "body_bytes": 997, "body_truncated": false}} +{"ts": "2026-03-30T16:00:13+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/10/occurrences/2026-04-17T14:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 16:00:13 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T16:00:13+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "X-WP-User": "admin", "Content-Length": "287"}, "body": "{\"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}", "body_bytes": 287, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 16:00:13 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "647"}, "body": "{\"data\": {\"id\": 11, \"uid\": \"76660280451647fdf0ba@calendar-wp-plugin\", \"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_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"count\", \"repeat_count\": 3, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"8bbc978d26a619c8\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T16:00:13+00:00\"}}", "body_bytes": 647, "body_truncated": false}} +{"ts": "2026-03-30T16:00:13+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/11/occurrences?from=2026-04-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 16:00:13 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "937"}, "body": "{\"data\": [{\"event_id\": 11, \"uid\": \"76660280451647fdf0ba@calendar-wp-plugin\", \"title\": \"Smoke Occurrences API\", \"description\": \"occ-endpoint\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-01T10:00:00+01:00\", \"occurrence_end\": \"2026-04-01T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 11, \"uid\": \"76660280451647fdf0ba@calendar-wp-plugin\", \"title\": \"Smoke Occurrences API\", \"description\": \"occ-endpoint\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-02T10:00:00+01:00\", \"occurrence_end\": \"2026-04-02T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 11, \"uid\": \"76660280451647fdf0ba@calendar-wp-plugin\", \"title\": \"Smoke Occurrences API\", \"description\": \"occ-endpoint\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-03T10:00:00+01:00\", \"occurrence_end\": \"2026-04-03T11:00:00+01:00\", \"repeat_type\": \"daily\"}]}", "body_bytes": 937, "body_truncated": false}} +{"ts": "2026-03-30T16:00:13+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/11/occurrences/2026-04-02T10:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 16:00:13 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T16:00:13+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/11/occurrences/2026-04-02T10:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 16:00:13 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T16:00:13+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/11/occurrences?from=2026-04-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 16:00:13 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "628"}, "body": "{\"data\": [{\"event_id\": 11, \"uid\": \"76660280451647fdf0ba@calendar-wp-plugin\", \"title\": \"Smoke Occurrences API\", \"description\": \"occ-endpoint\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-01T10:00:00+01:00\", \"occurrence_end\": \"2026-04-01T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 11, \"uid\": \"76660280451647fdf0ba@calendar-wp-plugin\", \"title\": \"Smoke Occurrences API\", \"description\": \"occ-endpoint\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-03T10:00:00+01:00\", \"occurrence_end\": \"2026-04-03T11:00:00+01:00\", \"repeat_type\": \"daily\"}]}", "body_bytes": 628, "body_truncated": false}} +{"ts": "2026-03-30T16:00:13+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-vtimezone.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "454"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN\nVERSION:2.0\nBEGIN:VTIMEZONE\nTZID:Europe/London\nBEGIN:STANDARD\nDTSTART:18471201T000000\nRRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=9\nTZOFFSETFROM:+0115\nTZOFFSETTO:+0000\nTZNAME:GMT\nEND:STANDARD\nEND:VTIMEZONE\nBEGIN:VEVENT\nUID:smoke-vtimezone-parser-001\nSUMMARY:Smoke VTIMEZONE Parse\nDTSTART;TZID=Europe/London:20260423T150000\nDTEND;TZID=Europe/London:20260423T160000\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 454, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 16:00:13 GMT", "ETag": "\"5e3a16a0bd9129c8\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T16:00:13+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-vtimezone.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 16:00:13 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"5e3a16a0bd9129c8\"", "Content-Length": "288"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-vtimezone-parser-001\r\nDTSTAMP:20260330T160013Z\r\nSUMMARY:Smoke VTIMEZONE Parse\r\nDTSTART;TZID=Europe/London:20260423T150000\r\nDTEND;TZID=Europe/London:20260423T160000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 288, "body_truncated": false}} +{"ts": "2026-03-30T16:00:13+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-monthly-nth.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "261"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-monthly-nth-001\nSUMMARY:Smoke Monthly Nth\nDTSTART;TZID=Europe/London:20260402T150000\nDTEND;TZID=Europe/London:20260402T160000\nRRULE:FREQ=MONTHLY;BYDAY=SU;BYSETPOS=4\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 261, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 16:00:13 GMT", "ETag": "\"cc6a3dc3e8885595\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T16:00:13+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-monthly-nth.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 16:00:13 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"cc6a3dc3e8885595\"", "Content-Length": "319"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-nth-001\r\nDTSTAMP:20260330T160013Z\r\nSUMMARY:Smoke Monthly Nth\r\nDTSTART;TZID=Europe/London:20260426T150000\r\nDTEND;TZID=Europe/London:20260426T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SU;BYSETPOS=4\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 319, "body_truncated": false}} +{"ts": "2026-03-30T16:00:13+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-monthly-ordinal.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "259"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-monthly-ordinal-001\nSUMMARY:Smoke Monthly Ordinal\nDTSTART;TZID=Europe/London:20260411T150000\nDTEND;TZID=Europe/London:20260411T160000\nRRULE:FREQ=MONTHLY;BYDAY=2SA\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 259, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 16:00:13 GMT", "ETag": "\"39ef190e9e85067e\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T16:00:13+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-monthly-ordinal.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 16:00:13 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"39ef190e9e85067e\"", "Content-Length": "327"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-ordinal-001\r\nDTSTAMP:20260330T160013Z\r\nSUMMARY:Smoke Monthly Ordinal\r\nDTSTART;TZID=Europe/London:20260411T150000\r\nDTEND;TZID=Europe/London:20260411T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SA;BYSETPOS=2\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 327, "body_truncated": false}} +{"ts": "2026-03-30T16:00:13+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-monthly-last.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "264"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-monthly-last-001\nSUMMARY:Smoke Monthly Last\nDTSTART;TZID=Europe/London:20260425T150000\nDTEND;TZID=Europe/London:20260425T160000\nRRULE:FREQ=MONTHLY;BYDAY=SA;BYSETPOS=-1\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 264, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 16:00:13 GMT", "ETag": "\"57c287b65a632230\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T16:00:13+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-monthly-last.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 16:00:13 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"57c287b65a632230\"", "Content-Length": "322"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-last-001\r\nDTSTAMP:20260330T160013Z\r\nSUMMARY:Smoke Monthly Last\r\nDTSTART;TZID=Europe/London:20260425T150000\r\nDTEND;TZID=Europe/London:20260425T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SA;BYSETPOS=-1\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 322, "body_truncated": false}} +{"ts": "2026-03-30T16:00:13+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "X-WP-User": "admin", "Content-Length": "362"}, "body": "{\"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\"}", "body_bytes": 362, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 16:00:13 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "673"}, "body": "{\"data\": {\"id\": 16, \"uid\": \"0609308738390cd66d4c@calendar-wp-plugin\", \"title\": \"Smoke 4th Sunday Anchor\", \"description\": \"anchor-normalization\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"start_datetime\": \"2026-05-24T15:00:00+01:00\", \"end_datetime\": \"2026-05-24T16:00:00+01:00\", \"repeat_type\": \"monthly\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"weekday_of_month\", \"repeat_nth_day\": null, \"repeat_nth_pos\": 4, \"repeat_nth_weekday\": 0, \"repeat_range_mode\": \"no_end\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"71eca1563d48f3ec\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T16:00:13+00:00\"}}", "body_bytes": 673, "body_truncated": false}} +{"ts": "2026-03-30T16:00:13+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/16/occurrences/2026-05-24T15:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 16:00:13 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T16:00:13+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/16.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 16:00:13 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"614335fbe22791d5\"", "Content-Length": "420"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:0609308738390cd66d4c@calendar-wp-plugin\r\nDTSTAMP:20260330T160013Z\r\nSUMMARY:Smoke 4th Sunday Anchor\r\nDESCRIPTION:anchor-normalization\r\nDTSTART;TZID=Europe/London:20260524T150000\r\nDTEND;TZID=Europe/London:20260524T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SU;BYSETPOS=4\r\nEXDATE;TZID=Europe/London:20260524T150000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 420, "body_truncated": false}} +{"ts": "2026-03-30T16:00:13+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "X-WP-User": "admin", "Content-Length": "295"}, "body": "{\"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\"}", "body_bytes": 295, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 16:00:13 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "655"}, "body": "{\"data\": {\"id\": 17, \"uid\": \"6bf853766043d7c101b6@calendar-wp-plugin\", \"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_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"until\", \"repeat_count\": null, \"repeat_until\": \"2026-03-19\", \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"406094a1e9d195af\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T16:00:13+00:00\"}}", "body_bytes": 655, "body_truncated": false}} +{"ts": "2026-03-30T16:00:13+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/17/occurrences/2026-03-11T11:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 16:00:13 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T16:00:13+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/17/occurrences?from=2026-03-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 16:00:13 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "5212"}, "body": "{\"data\": [{\"event_id\": 17, \"uid\": \"6bf853766043d7c101b6@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-02T10:00:00+00:00\", \"occurrence_end\": \"2026-03-02T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"6bf853766043d7c101b6@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-03T10:00:00+00:00\", \"occurrence_end\": \"2026-03-03T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"6bf853766043d7c101b6@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-04T10:00:00+00:00\", \"occurrence_end\": \"2026-03-04T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"6bf853766043d7c101b6@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-05T10:00:00+00:00\", \"occurrence_end\": \"2026-03-05T11:00:00", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T16:00:13+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/17/occurrences/2026-03-10", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 16:00:13 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T16:00:13+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/17/occurrences?from=2026-03-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 16:00:13 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "4906"}, "body": "{\"data\": [{\"event_id\": 17, \"uid\": \"6bf853766043d7c101b6@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-02T10:00:00+00:00\", \"occurrence_end\": \"2026-03-02T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"6bf853766043d7c101b6@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-03T10:00:00+00:00\", \"occurrence_end\": \"2026-03-03T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"6bf853766043d7c101b6@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-04T10:00:00+00:00\", \"occurrence_end\": \"2026-03-04T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"6bf853766043d7c101b6@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-05T10:00:00+00:00\", \"occurrence_end\": \"2026-03-05T11:00:00", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T16:00:13+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-exdate-import.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "323"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-exdate-import-001\nSUMMARY:Smoke EXDATE Import\nDTSTART;TZID=Europe/London:20260302T100000\nDTEND;TZID=Europe/London:20260302T110000\nRRULE:FREQ=DAILY;UNTIL=20260319T235959\nEXDATE;TZID=Europe/London:20260310T100000,20260311T100000\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 323, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 16:00:13 GMT", "ETag": "\"0afcaa4cdff054dd\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T16:00:13+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 16:00:13 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "11645"}, "body": "{\"data\": [{\"id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"start_datetime\": \"2026-04-01T10:00:00+01:00\", \"end_datetime\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"50884b9c7f52d7b4\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T16:00:11+00:00\"}, {\"id\": 2, \"uid\": \"fixture-ce-002@calendar-wp-plugin\", \"title\": \"Office Closed\", \"description\": \"Public holiday closure.\", \"location\": \"HQ\", \"category\": \"Operations\", \"all_day_event\": true, \"start_datetime\": \"2026-05-04\", \"end_datetime\": \"2026-05-05\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"9cc94", "body_bytes": 3927, "body_truncated": true}} +{"ts": "2026-03-30T16:00:13+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/18/occurrences?from=2026-03-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 16:00:13 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "4474"}, "body": "{\"data\": [{\"event_id\": 18, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-02T10:00:00+01:00\", \"occurrence_end\": \"2026-03-02T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 18, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-03T10:00:00+01:00\", \"occurrence_end\": \"2026-03-03T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 18, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-04T10:00:00+01:00\", \"occurrence_end\": \"2026-03-04T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 18, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-05T10:00:00+01:00\", \"occurrence_end\": \"2026-03-05T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 18, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE ", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T16:00:14+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-cancelled-occurrence.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "467"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-cancelled-occurrence-001\nSUMMARY:Smoke Cancelled Occurrence\nDTSTART;TZID=Europe/London:20260408T123000\nDTEND;TZID=Europe/London:20260408T133000\nRRULE:FREQ=WEEKLY\nEND:VEVENT\nBEGIN:VEVENT\nUID:smoke-cancelled-occurrence-001\nRECURRENCE-ID;TZID=Europe/London:20260506T123000\nDTSTART;TZID=Europe/London:20260506T123000\nDTEND;TZID=Europe/London:20260506T133000\nSTATUS:CANCELLED\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 467, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 16:00:14 GMT", "ETag": "\"16f6332f4cb07a1b\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T16:00:14+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-cancelled-occurrence.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 16:00:14 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"16f6332f4cb07a1b\"", "Content-Length": "359"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-cancelled-occurrence-001\r\nDTSTAMP:20260330T160014Z\r\nSUMMARY:Smoke Cancelled Occurrence\r\nDTSTART;TZID=Europe/London:20260408T123000\r\nDTEND;TZID=Europe/London:20260408T133000\r\nRRULE:FREQ=WEEKLY\r\nEXDATE;TZID=Europe/London:20260506T123000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 359, "body_truncated": false}} +{"ts": "2026-03-30T16:00:14+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 16:00:14 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "12273"}, "body": "{\"data\": [{\"id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"start_datetime\": \"2026-04-01T10:00:00+01:00\", \"end_datetime\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"50884b9c7f52d7b4\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T16:00:11+00:00\"}, {\"id\": 2, \"uid\": \"fixture-ce-002@calendar-wp-plugin\", \"title\": \"Office Closed\", \"description\": \"Public holiday closure.\", \"location\": \"HQ\", \"category\": \"Operations\", \"all_day_event\": true, \"start_datetime\": \"2026-05-04\", \"end_datetime\": \"2026-05-05\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"9cc94", "body_bytes": 3927, "body_truncated": true}} +{"ts": "2026-03-30T16:00:14+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/19/occurrences?from=2026-05-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 16:00:14 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "892"}, "body": "{\"data\": [{\"event_id\": 19, \"uid\": \"smoke-cancelled-occurrence-001\", \"title\": \"Smoke Cancelled Occurrence\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-13T12:30:00+01:00\", \"occurrence_end\": \"2026-05-13T13:30:00+01:00\", \"repeat_type\": \"weekly\"}, {\"event_id\": 19, \"uid\": \"smoke-cancelled-occurrence-001\", \"title\": \"Smoke Cancelled Occurrence\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-20T12:30:00+01:00\", \"occurrence_end\": \"2026-05-20T13:30:00+01:00\", \"repeat_type\": \"weekly\"}, {\"event_id\": 19, \"uid\": \"smoke-cancelled-occurrence-001\", \"title\": \"Smoke Cancelled Occurrence\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-27T12:30:00+01:00\", \"occurrence_end\": \"2026-05-27T13:30:00+01:00\", \"repeat_type\": \"weekly\"}]}", "body_bytes": 892, "body_truncated": false}} +{"ts": "2026-03-30T16:00:14+00:00", "client": "127.0.0.1", "method": "GET", "path": "/calendar", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 16:00:14 GMT", "Content-Type": "text/html; charset=utf-8", "Content-Length": "43447"}, "body": "[omitted html payload: 3934 bytes]", "body_bytes": 3934, "body_truncated": true}} +{"ts": "2026-03-30T16:01:58+00:00", "client": "127.0.0.1", "method": "GET", "path": "/calendar", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:148.0) Gecko/20100101 Firefox/148.0", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", "Accept-Language": "en-GB,en;q=0.9", "Accept-Encoding": "gzip, deflate, br, zstd", "DNT": "1", "Sec-GPC": "1", "Connection": "keep-alive", "Upgrade-Insecure-Requests": "1", "Sec-Fetch-Dest": "document", "Sec-Fetch-Mode": "navigate", "Sec-Fetch-Site": "none", "Priority": "u=0, i"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 16:01:58 GMT", "Content-Type": "text/html; charset=utf-8", "Content-Length": "43447"}, "body": "[omitted html payload: 3934 bytes]", "body_bytes": 3934, "body_truncated": true}} +{"ts": "2026-03-30T16:01:58+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/public/events?view=month&date=2026-03-30", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:148.0) Gecko/20100101 Firefox/148.0", "Accept": "*/*", "Accept-Language": "en-GB,en;q=0.9", "Accept-Encoding": "gzip, deflate, br, zstd", "Referer": "http://localhost:8080/calendar", "DNT": "1", "Sec-GPC": "1", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 16:01:58 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "51"}, "body": "{\"data\": [], \"meta\": {\"count\": 0, \"view\": \"month\"}}", "body_bytes": 51, "body_truncated": false}} +{"ts": "2026-03-30T16:02:01+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/public/events?view=month&date=2026-04-01", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:148.0) Gecko/20100101 Firefox/148.0", "Accept": "*/*", "Accept-Language": "en-GB,en;q=0.9", "Accept-Encoding": "gzip, deflate, br, zstd", "Referer": "http://localhost:8080/calendar", "DNT": "1", "Sec-GPC": "1", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=0"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 16:02:01 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "7109"}, "body": "{\"data\": [{\"event_id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-01T10:00:00+01:00\", \"occurrence_end\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\"}, {\"event_id\": 10, \"uid\": \"fixture-ce-010@calendar-wp-plugin\", \"title\": \"Therapy Session\", \"description\": \"Used for single-occurrence delete exception tests.\", \"location\": \"Clinic\", \"category\": \"Health\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-03T14:00:00+01:00\", \"occurrence_end\": \"2026-04-03T15:00:00+01:00\", \"repeat_type\": \"weekly\"}, {\"event_id\": 3, \"uid\": \"fixture-ce-003@calendar-wp-plugin\", \"title\": \"Daily Standup\", \"description\": \"15 minute sync.\", \"location\": \"Online\", \"category\": \"Team\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-06T09:00:00+01:00\", \"occurrence_end\": \"2026-04-06T09:15:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 3, \"uid\": \"fixture-ce-003@calendar-wp-plugin\", \"title\": \"Daily Standup\", \"description\": \"15 minute sync.\", \"location\": \"Online\", \"category\": \"Team\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T16:02:03+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/public/events?view=month&date=2026-05-01", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:148.0) Gecko/20100101 Firefox/148.0", "Accept": "*/*", "Accept-Language": "en-GB,en;q=0.9", "Accept-Encoding": "gzip, deflate, br, zstd", "Referer": "http://localhost:8080/calendar", "DNT": "1", "Sec-GPC": "1", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=0"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 16:02:03 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "4034"}, "body": "{\"data\": [{\"event_id\": 10, \"uid\": \"fixture-ce-010@calendar-wp-plugin\", \"title\": \"Therapy Session\", \"description\": \"Used for single-occurrence delete exception tests.\", \"location\": \"Clinic\", \"category\": \"Health\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-01T14:00:00+01:00\", \"occurrence_end\": \"2026-05-01T15:00:00+01:00\", \"repeat_type\": \"weekly\"}, {\"event_id\": 2, \"uid\": \"fixture-ce-002@calendar-wp-plugin\", \"title\": \"Office Closed\", \"description\": \"Public holiday closure.\", \"location\": \"HQ\", \"category\": \"Operations\", \"all_day_event\": true, \"occurrence_start\": \"2026-05-04T00:00:00+00:00\", \"occurrence_end\": \"2026-05-05T00:00:00+00:00\", \"repeat_type\": \"none\"}, {\"event_id\": 7, \"uid\": \"fixture-ce-007@calendar-wp-plugin\", \"title\": \"Fortnightly Coaching\", \"description\": \"Coaching check-in.\", \"location\": \"Online\", \"category\": \"Training\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-05T15:00:00+01:00\", \"occurrence_end\": \"2026-05-05T16:00:00+01:00\", \"repeat_type\": \"custom\"}, {\"event_id\": 4, \"uid\": \"fixture-ce-004@calendar-wp-plugin\", \"title\": \"Community Lunch\", \"description\": \"Weekly community lunch.\", \"location\": \"Cafeteria\", \"category\": \"Community\", \"all_day_event\": false, \"", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T16:02:04+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/public/events?view=month&date=2026-04-01", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:148.0) Gecko/20100101 Firefox/148.0", "Accept": "*/*", "Accept-Language": "en-GB,en;q=0.9", "Accept-Encoding": "gzip, deflate, br, zstd", "Referer": "http://localhost:8080/calendar", "DNT": "1", "Sec-GPC": "1", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=0"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 16:02:04 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "7109"}, "body": "{\"data\": [{\"event_id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-01T10:00:00+01:00\", \"occurrence_end\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\"}, {\"event_id\": 10, \"uid\": \"fixture-ce-010@calendar-wp-plugin\", \"title\": \"Therapy Session\", \"description\": \"Used for single-occurrence delete exception tests.\", \"location\": \"Clinic\", \"category\": \"Health\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-03T14:00:00+01:00\", \"occurrence_end\": \"2026-04-03T15:00:00+01:00\", \"repeat_type\": \"weekly\"}, {\"event_id\": 3, \"uid\": \"fixture-ce-003@calendar-wp-plugin\", \"title\": \"Daily Standup\", \"description\": \"15 minute sync.\", \"location\": \"Online\", \"category\": \"Team\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-06T09:00:00+01:00\", \"occurrence_end\": \"2026-04-06T09:15:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 3, \"uid\": \"fixture-ce-003@calendar-wp-plugin\", \"title\": \"Daily Standup\", \"description\": \"15 minute sync.\", \"location\": \"Online\", \"category\": \"Team\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T16:02:15+00:00", "client": "127.0.0.1", "method": "GET", "path": "/admin.php?as=admin", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:148.0) Gecko/20100101 Firefox/148.0", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", "Accept-Language": "en-GB,en;q=0.9", "Accept-Encoding": "gzip, deflate, br, zstd", "DNT": "1", "Sec-GPC": "1", "Connection": "keep-alive", "Upgrade-Insecure-Requests": "1", "Sec-Fetch-Dest": "document", "Sec-Fetch-Mode": "navigate", "Sec-Fetch-Site": "none", "Sec-Fetch-User": "?1", "Priority": "u=0, i"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 16:02:15 GMT", "Content-Type": "text/html; charset=utf-8", "Content-Length": "7183"}, "body": "[omitted html payload: 3935 bytes]", "body_bytes": 3935, "body_truncated": true}} +{"ts": "2026-03-30T16:02:17+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-admin/admin.php?page=calendar-diagnostics&as=admin", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:148.0) Gecko/20100101 Firefox/148.0", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", "Accept-Language": "en-GB,en;q=0.9", "Accept-Encoding": "gzip, deflate, br, zstd", "DNT": "1", "Sec-GPC": "1", "Connection": "keep-alive", "Referer": "http://localhost:8080/admin.php?as=admin", "Upgrade-Insecure-Requests": "1", "Sec-Fetch-Dest": "document", "Sec-Fetch-Mode": "navigate", "Sec-Fetch-Site": "same-origin", "Sec-Fetch-User": "?1", "Priority": "u=0, i"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 16:02:17 GMT", "Content-Type": "text/html; charset=utf-8", "Content-Length": "8898"}, "body": "[omitted html payload: 3935 bytes]", "body_bytes": 3935, "body_truncated": true}} +{"ts": "2026-03-30T16:02:17+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/admin/diagnostics?limit=20&as=admin", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:148.0) Gecko/20100101 Firefox/148.0", "Accept": "*/*", "Accept-Language": "en-GB,en;q=0.9", "Accept-Encoding": "gzip, deflate, br, zstd", "Referer": "http://localhost:8080/wp-admin/admin.php?page=calendar-diagnostics&as=admin", "Content-Type": "application/json", "X-WP-User": "admin", "DNT": "1", "Sec-GPC": "1", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 16:02:17 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "28256"}, "body": "{\"data\": [{\"ts\": \"2026-03-30T16:00:13+00:00\", \"client\": \"127.0.0.1\", \"method\": \"POST\", \"path\": \"/wp-json/calendar/v1/events\", \"request\": {\"headers\": {\"Host\": \"127.0.0.1:8080\", \"User-Agent\": \"curl/8.14.1\", \"Accept\": \"*/*\", \"Content-Type\": \"application/json\", \"X-WP-User\": \"admin\", \"Content-Length\": \"295\"}, \"body\": \"{\\\"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\\\"}\", \"body_bytes\": 295, \"body_truncated\": false}, \"response\": {\"status\": 201, \"headers\": {\"Server\": \"CalendarFixture/0.1 Python/3.13.5\", \"Date\": \"Mon, 30 Mar 2026 16:00:13 GMT\", \"Content-Type\": \"application/json; charset=utf-8\", \"Content-Length\": \"655\"}, \"body\": \"{\\\"data\\\": {\\\"id\\\": 17, \\\"uid\\\": \\\"6bf853766043d7c101b6@calendar-wp-plugin\\\", \\\"title\\\": \\\"Smoke Daily Exception TZ\\\", \\\"description\\\": \\\"tz-key\\\", \\\"location\\\": \\\"\\\", \\\"category\\\": \\\"\\\", \\\"all_day_event\\\": false, \\\"start_datetime\\\": \\\"2026-03-02T10:00:00+00:00\\\", \\\"", "body_bytes": 3927, "body_truncated": true}} +{"ts": "2026-03-30T16:02:24+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-admin/admin.php?page=calendar-users&as=admin", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:148.0) Gecko/20100101 Firefox/148.0", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", "Accept-Language": "en-GB,en;q=0.9", "Accept-Encoding": "gzip, deflate, br, zstd", "DNT": "1", "Sec-GPC": "1", "Connection": "keep-alive", "Referer": "http://localhost:8080/wp-admin/admin.php?page=calendar-diagnostics&as=admin", "Upgrade-Insecure-Requests": "1", "Sec-Fetch-Dest": "document", "Sec-Fetch-Mode": "navigate", "Sec-Fetch-Site": "same-origin", "Sec-Fetch-User": "?1", "Priority": "u=0, i"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 16:02:24 GMT", "Content-Type": "text/html; charset=utf-8", "Content-Length": "9478"}, "body": "[omitted html payload: 3935 bytes]", "body_bytes": 3935, "body_truncated": true}} +{"ts": "2026-03-30T16:02:24+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/admin/users?as=admin", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:148.0) Gecko/20100101 Firefox/148.0", "Accept": "*/*", "Accept-Language": "en-GB,en;q=0.9", "Accept-Encoding": "gzip, deflate, br, zstd", "Referer": "http://localhost:8080/wp-admin/admin.php?page=calendar-users&as=admin", "Content-Type": "application/json", "X-WP-User": "admin", "DNT": "1", "Sec-GPC": "1", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 16:02:24 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "527"}, "body": "{\"data\": [{\"id\": 1, \"email\": \"rw_user@example.test\", \"email_verified_at\": \"2026-03-30T16:01:41+00:00\", \"account_status\": \"active\", \"updated_at\": \"2026-03-30T16:01:41+00:00\"}, {\"id\": 2, \"email\": \"adrians@chezstephens.org.uk\", \"email_verified_at\": \"2026-03-30T16:01:41+00:00\", \"account_status\": \"active\", \"updated_at\": \"2026-03-30T16:01:41+00:00\"}, {\"id\": 3, \"email\": \"pending_user@example.test\", \"email_verified_at\": \"2026-03-30T16:01:41+00:00\", \"account_status\": \"pending_approval\", \"updated_at\": \"2026-03-30T16:01:41+00:00\"}]}", "body_bytes": 527, "body_truncated": false}} +{"ts": "2026-03-30T16:02:28+00:00", "client": "127.0.0.1", "method": "GET", "path": "/calendar", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:148.0) Gecko/20100101 Firefox/148.0", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", "Accept-Language": "en-GB,en;q=0.9", "Accept-Encoding": "gzip, deflate, br, zstd", "DNT": "1", "Sec-GPC": "1", "Connection": "keep-alive", "Referer": "http://localhost:8080/wp-admin/admin.php?page=calendar-users&as=admin", "Upgrade-Insecure-Requests": "1", "Sec-Fetch-Dest": "document", "Sec-Fetch-Mode": "navigate", "Sec-Fetch-Site": "same-origin", "Sec-Fetch-User": "?1", "Priority": "u=0, i"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 16:02:28 GMT", "Content-Type": "text/html; charset=utf-8", "Content-Length": "43447"}, "body": "[omitted html payload: 3934 bytes]", "body_bytes": 3934, "body_truncated": true}} +{"ts": "2026-03-30T16:02:28+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/public/events?view=month&date=2026-03-30", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:148.0) Gecko/20100101 Firefox/148.0", "Accept": "*/*", "Accept-Language": "en-GB,en;q=0.9", "Accept-Encoding": "gzip, deflate, br, zstd", "Referer": "http://localhost:8080/calendar", "DNT": "1", "Sec-GPC": "1", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 16:02:28 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "51"}, "body": "{\"data\": [], \"meta\": {\"count\": 0, \"view\": \"month\"}}", "body_bytes": 51, "body_truncated": false}} +{"ts": "2026-03-30T16:03:20+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/calendar, application/ics, text/plain;q=0.9", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "DNT": "1", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 401, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 16:03:20 GMT", "WWW-Authenticate": "Basic realm=\"calendar-caldav-fixture\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T16:03:20+00:00", "client": "127.0.0.1", "method": "PROPFIND", "path": "/caldav/", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/xml; charset=utf-8", "Content-Length": "203", "Depth": "0", "Origin": "http://127.0.0.1:8080", "DNT": "1", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "\n", "body_bytes": 203, "body_truncated": false}, "response": {"status": 401, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 16:03:20 GMT", "WWW-Authenticate": "Basic realm=\"calendar-caldav-fixture\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T16:03:20+00:00", "client": "127.0.0.1", "method": "PROPFIND", "path": "/caldav/", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/xml; charset=utf-8", "Content-Length": "314", "Depth": "0", "Origin": "http://127.0.0.1:8080", "DNT": "1", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "\n", "body_bytes": 314, "body_truncated": false}, "response": {"status": 401, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 16:03:20 GMT", "WWW-Authenticate": "Basic realm=\"calendar-caldav-fixture\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T16:03:25+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/calendar, application/ics, text/plain;q=0.9", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "DNT": "1", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 401, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 16:03:25 GMT", "WWW-Authenticate": "Basic realm=\"calendar-caldav-fixture\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T16:03:25+00:00", "client": "127.0.0.1", "method": "PROPFIND", "path": "/caldav/", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/xml; charset=utf-8", "Content-Length": "314", "Depth": "0", "Origin": "http://127.0.0.1:8080", "DNT": "1", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "\n", "body_bytes": 314, "body_truncated": false}, "response": {"status": 401, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 16:03:25 GMT", "WWW-Authenticate": "Basic realm=\"calendar-caldav-fixture\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T16:03:25+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/calendar, application/ics, text/plain;q=0.9", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "DNT": "1", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache", "Authorization": "***"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 16:03:25 GMT", "Content-Type": "text/plain; charset=utf-8", "Content-Length": "30"}, "body": "caldav shared public calendar\n", "body_bytes": 30, "body_truncated": false}} +{"ts": "2026-03-30T16:03:25+00:00", "client": "127.0.0.1", "method": "PROPFIND", "path": "/caldav/", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/xml; charset=utf-8", "Content-Length": "314", "Depth": "0", "Origin": "http://127.0.0.1:8080", "DNT": "1", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache", "Authorization": "***"}, "body": "\n", "body_bytes": 314, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 16:03:25 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "431"}, "body": "\n\n\n /caldav/\n \n \n /caldav/principals/user/\n \n HTTP/1.1 200 OK\n \n\n\n", "body_bytes": 431, "body_truncated": false}} +{"ts": "2026-03-30T16:03:25+00:00", "client": "127.0.0.1", "method": "PROPFIND", "path": "/caldav/", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/xml; charset=utf-8", "Content-Length": "203", "Depth": "0", "Origin": "http://127.0.0.1:8080", "DNT": "1", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "\n", "body_bytes": 203, "body_truncated": false}, "response": {"status": 401, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 16:03:25 GMT", "WWW-Authenticate": "Basic realm=\"calendar-caldav-fixture\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T16:03:25+00:00", "client": "127.0.0.1", "method": "PROPFIND", "path": "/caldav/principals/user/", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/xml; charset=utf-8", "Content-Length": "158", "Depth": "0", "Origin": "http://127.0.0.1:8080", "DNT": "1", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "\n", "body_bytes": 158, "body_truncated": false}, "response": {"status": 401, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 16:03:25 GMT", "WWW-Authenticate": "Basic realm=\"calendar-caldav-fixture\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T16:03:25+00:00", "client": "127.0.0.1", "method": "PROPFIND", "path": "/caldav/", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/xml; charset=utf-8", "Content-Length": "203", "Depth": "0", "Origin": "http://127.0.0.1:8080", "DNT": "1", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache", "Authorization": "***"}, "body": "\n", "body_bytes": 203, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 16:03:25 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "431"}, "body": "\n\n\n /caldav/\n \n \n /caldav/principals/user/\n \n HTTP/1.1 200 OK\n \n\n\n", "body_bytes": 431, "body_truncated": false}} +{"ts": "2026-03-30T16:03:26+00:00", "client": "127.0.0.1", "method": "PROPFIND", "path": "/caldav/principals/user/", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/xml; charset=utf-8", "Content-Length": "158", "Depth": "0", "Origin": "http://127.0.0.1:8080", "DNT": "1", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache", "Authorization": "***"}, "body": "\n", "body_bytes": 158, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 16:03:26 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "445"}, "body": "\n\n\n /caldav/principals/user/\n \n \n /caldav/calendars/\n \n HTTP/1.1 200 OK\n \n\n\n", "body_bytes": 445, "body_truncated": false}} +{"ts": "2026-03-30T16:03:26+00:00", "client": "127.0.0.1", "method": "PROPFIND", "path": "/caldav/", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/xml; charset=utf-8", "Content-Length": "217", "Depth": "1", "Origin": "http://127.0.0.1:8080", "DNT": "1", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "\n", "body_bytes": 217, "body_truncated": false}, "response": {"status": 401, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 16:03:26 GMT", "WWW-Authenticate": "Basic realm=\"calendar-caldav-fixture\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T16:03:26+00:00", "client": "127.0.0.1", "method": "PROPFIND", "path": "/caldav/calendars/", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/xml; charset=utf-8", "Content-Length": "215", "Depth": "1", "Origin": "http://127.0.0.1:8080", "DNT": "1", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "\n", "body_bytes": 215, "body_truncated": false}, "response": {"status": 401, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 16:03:26 GMT", "WWW-Authenticate": "Basic realm=\"calendar-caldav-fixture\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T16:03:26+00:00", "client": "127.0.0.1", "method": "PROPFIND", "path": "/caldav/calendars/", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/xml; charset=utf-8", "Content-Length": "215", "Depth": "1", "Origin": "http://127.0.0.1:8080", "DNT": "1", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache", "Authorization": "***"}, "body": "\n", "body_bytes": 215, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 16:03:26 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "728"}, "body": "\n\n\n /caldav/calendars/\n \n \n \n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/\n \n \n Public Calendar\n \n HTTP/1.1 200 OK\n \n\n\n", "body_bytes": 728, "body_truncated": false}} +{"ts": "2026-03-30T16:03:26+00:00", "client": "127.0.0.1", "method": "PROPFIND", "path": "/caldav/", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/xml; charset=utf-8", "Content-Length": "217", "Depth": "1", "Origin": "http://127.0.0.1:8080", "DNT": "1", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache", "Authorization": "***"}, "body": "\n", "body_bytes": 217, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 16:03:26 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "1348"}, "body": "\n\n\n /caldav/\n \n \n /caldav/principals/user/\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/principals/user/\n \n \n /caldav/calendars/\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/\n \n \n \n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/\n \n \n Public Calendar\n", "body_bytes": 337, "body_truncated": false}, "response": {"status": 401, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 16:03:28 GMT", "WWW-Authenticate": "Basic realm=\"calendar-caldav-fixture\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T16:03:28+00:00", "client": "127.0.0.1", "method": "PROPFIND", "path": "/caldav/calendars/public/", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/xml; charset=utf-8", "Content-Length": "337", "Depth": "0", "Origin": "http://127.0.0.1:8080", "DNT": "1", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache", "Authorization": "***"}, "body": "\n", "body_bytes": 337, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 16:03:28 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "509"}, "body": "\n\n\n /caldav/calendars/public/\n \n \n Public Calendar\n \n HTTP/1.1 200 OK\n \n\n\n", "body_bytes": 509, "body_truncated": false}} +{"ts": "2026-03-30T16:03:28+00:00", "client": "127.0.0.1", "method": "OPTIONS", "path": "/caldav/calendars/", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Origin": "http://127.0.0.1:8080", "DNT": "1", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 16:03:28 GMT", "DAV": "1, 2, calendar-access", "Allow": "OPTIONS, PROPFIND, REPORT, GET, PUT, DELETE"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T16:03:28+00:00", "client": "127.0.0.1", "method": "PROPFIND", "path": "/caldav/calendars/public/", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/xml; charset=utf-8", "Content-Length": "144", "Depth": "1", "Origin": "http://127.0.0.1:8080", "DNT": "1", "Authorization": "***", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "\n", "body_bytes": 144, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 16:03:28 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "3570"}, "body": "\n\n\n /caldav/calendars/public/\n \n \n Public Calendar\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/1.ics\n \n \n text/calendar; charset=utf-8\"7afd6bfad476334a\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/2.ics\n \n \n text/calendar; charset=utf-8\"bd549a6776fb7591\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/3.ics\n \n \n ...(truncated)", "body_bytes": 3570, "body_truncated": false}} +{"ts": "2026-03-30T16:03:28+00:00", "client": "127.0.0.1", "method": "REPORT", "path": "/caldav/calendars/public/", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/xml; charset=utf-8", "Content-Length": "655", "Depth": "1", "Origin": "http://127.0.0.1:8080", "DNT": "1", "Authorization": "***", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "\n/caldav/calendars/public/10.ics/caldav/calendars/public/9.ics/caldav/calendars/public/8.ics/caldav/calendars/public/7.ics/caldav/calendars/public/6.ics/caldav/calendars/public/5.ics/caldav/calendars/public/4.ics/caldav/calendars/public/3.ics/caldav/calendars/public/2.ics/caldav/calendars/public/1.ics", "body_bytes": 655, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 16:03:28 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "8815"}, "body": "\n\n\n /caldav/calendars/public/1.ics\n \n \n \"7afd6bfad476334a\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-001@calendar-wp-plugin\r\nDTSTAMP:20260330T160328Z\r\nSUMMARY:Board Meeting\r\nDESCRIPTION:Quarterly board review.\r\nLOCATION:Room A\r\nCATEGORIES:Governance\r\nDTSTART;TZID=Europe/London:20260401T100000\r\nDTEND;TZID=Europe/London:20260401T113000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/2.ics\n \n \n \"bd549a6776fb7591\"\n ...(truncated)", "body_bytes": 3919, "body_truncated": true}} +{"ts": "2026-03-30T16:03:43+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/3.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "14098", "If-Match": "\"39d8705698ac45a3\"", "Origin": "http://127.0.0.1:8080", "DNT": "1", "Authorization": "***", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "BEGIN:VCALENDAR\r\nPRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN\r\nVERSION:2.0\r\nBEGIN:VTIMEZONE\r\nTZID:Europe/London\r\nX-TZINFO:Europe/London[2025b]\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:-000115\r\nTZNAME:Europe/London(STD)\r\nDTSTART:18471201T000000\r\nRDATE:18471201T000000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19160521T020000\r\nRDATE:19160521T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19161001T030000\r\nRDATE:19161001T030000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19170408T020000\r\nRDATE:19170408T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19170917T030000\r\nRDATE:19170917T030000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19180324T020000\r\nRDATE:19180324T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19180930T030000\r\nRRULE:FREQ=YEARLY;BYMONTH=9;BYDAY=-1MO;UNTIL=19190929 ...(truncated)", "body_bytes": 4097, "body_truncated": true}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 16:03:43 GMT", "ETag": "\"1b040a223eb85dbe\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T16:03:43+00:00", "client": "127.0.0.1", "method": "REPORT", "path": "/caldav/calendars/public/", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/xml; charset=utf-8", "Content-Length": "231", "Depth": "1", "Origin": "http://127.0.0.1:8080", "DNT": "1", "Authorization": "***", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "\n/caldav/calendars/public/3.ics", "body_bytes": 231, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 16:03:43 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "1030"}, "body": "\n\n\n /caldav/calendars/public/3.ics\n \n \n \"1b040a223eb85dbe\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-003@calendar-wp-plugin\r\nDTSTAMP:20260330T160343Z\r\nSUMMARY:Daily Standup\r\nDESCRIPTION:15 minute sync.\r\nLOCATION:Online\r\nCATEGORIES:Team\r\nDTSTART;TZID=Europe/London:20260406T090000\r\nDTEND;TZID=Europe/London:20260406T091500\r\nRRULE:FREQ=DAILY;COUNT=10\r\nEXDATE;TZID=Europe/London:20260414T090000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n", "body_bytes": 1030, "body_truncated": false}} +{"ts": "2026-03-30T16:03:43+00:00", "client": "127.0.0.1", "method": "PROPFIND", "path": "/caldav/calendars/public/", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/xml; charset=utf-8", "Content-Length": "144", "Depth": "1", "Origin": "http://127.0.0.1:8080", "DNT": "1", "Authorization": "***", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "\n", "body_bytes": 144, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 16:03:43 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "3570"}, "body": "\n\n\n /caldav/calendars/public/\n \n \n Public Calendar\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/1.ics\n \n \n text/calendar; charset=utf-8\"7afd6bfad476334a\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/2.ics\n \n \n text/calendar; charset=utf-8\"bd549a6776fb7591\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/3.ics\n \n \n ...(truncated)", "body_bytes": 3570, "body_truncated": false}} +{"ts": "2026-03-30T16:03:50+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/4.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "14108", "If-Match": "\"195229643e51b679\"", "Origin": "http://127.0.0.1:8080", "DNT": "1", "Authorization": "***", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "BEGIN:VCALENDAR\r\nPRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN\r\nVERSION:2.0\r\nBEGIN:VTIMEZONE\r\nTZID:Europe/London\r\nX-TZINFO:Europe/London[2025b]\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:-000115\r\nTZNAME:Europe/London(STD)\r\nDTSTART:18471201T000000\r\nRDATE:18471201T000000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19160521T020000\r\nRDATE:19160521T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19161001T030000\r\nRDATE:19161001T030000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19170408T020000\r\nRDATE:19170408T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19170917T030000\r\nRDATE:19170917T030000\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:+010000\r\nTZOFFSETFROM:+000000\r\nTZNAME:Europe/London(DST)\r\nDTSTART:19180324T020000\r\nRDATE:19180324T020000\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETTO:+000000\r\nTZOFFSETFROM:+010000\r\nTZNAME:Europe/London(STD)\r\nDTSTART:19180930T030000\r\nRRULE:FREQ=YEARLY;BYMONTH=9;BYDAY=-1MO;UNTIL=19190929 ...(truncated)", "body_bytes": 4097, "body_truncated": true}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 16:03:50 GMT", "ETag": "\"2c3b4b86c7b93404\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T16:03:50+00:00", "client": "127.0.0.1", "method": "REPORT", "path": "/caldav/calendars/public/", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/xml; charset=utf-8", "Content-Length": "231", "Depth": "1", "Origin": "http://127.0.0.1:8080", "DNT": "1", "Authorization": "***", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "\n/caldav/calendars/public/4.ics", "body_bytes": 231, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 16:03:50 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "1040"}, "body": "\n\n\n /caldav/calendars/public/4.ics\n \n \n \"2c3b4b86c7b93404\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-004@calendar-wp-plugin\r\nDTSTAMP:20260330T160350Z\r\nSUMMARY:Community Lunch\r\nDESCRIPTION:Weekly community lunch.\r\nLOCATION:Cafeteria\r\nCATEGORIES:Community\r\nDTSTART;TZID=Europe/London:20260408T123000\r\nDTEND;TZID=Europe/London:20260408T133000\r\nRRULE:FREQ=WEEKLY\r\nEXDATE;TZID=Europe/London:20260422T123000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n", "body_bytes": 1040, "body_truncated": false}} +{"ts": "2026-03-30T16:03:51+00:00", "client": "127.0.0.1", "method": "PROPFIND", "path": "/caldav/calendars/public/", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/xml; charset=utf-8", "Content-Length": "144", "Depth": "1", "Origin": "http://127.0.0.1:8080", "DNT": "1", "Authorization": "***", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "\n", "body_bytes": 144, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 16:03:51 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "3570"}, "body": "\n\n\n /caldav/calendars/public/\n \n \n Public Calendar\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/1.ics\n \n \n text/calendar; charset=utf-8\"7afd6bfad476334a\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/2.ics\n \n \n text/calendar; charset=utf-8\"bd549a6776fb7591\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/3.ics\n \n \n ...(truncated)", "body_bytes": 3570, "body_truncated": false}} +{"ts": "2026-03-30T16:03:56+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/public/events?view=month&date=2026-04-01", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:148.0) Gecko/20100101 Firefox/148.0", "Accept": "*/*", "Accept-Language": "en-GB,en;q=0.9", "Accept-Encoding": "gzip, deflate, br, zstd", "Referer": "http://localhost:8080/calendar", "DNT": "1", "Sec-GPC": "1", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=0"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 16:03:56 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "6476"}, "body": "{\"data\": [{\"event_id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-01T10:00:00+01:00\", \"occurrence_end\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\"}, {\"event_id\": 10, \"uid\": \"fixture-ce-010@calendar-wp-plugin\", \"title\": \"Therapy Session\", \"description\": \"Used for single-occurrence delete exception tests.\", \"location\": \"Clinic\", \"category\": \"Health\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-03T14:00:00+01:00\", \"occurrence_end\": \"2026-04-03T15:00:00+01:00\", \"repeat_type\": \"weekly\"}, {\"event_id\": 3, \"uid\": \"fixture-ce-003@calendar-wp-plugin\", \"title\": \"Daily Standup\", \"description\": \"15 minute sync.\", \"location\": \"Online\", \"category\": \"Team\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-06T09:00:00+01:00\", \"occurrence_end\": \"2026-04-06T09:15:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 3, \"uid\": \"fixture-ce-003@calendar-wp-plugin\", \"title\": \"Daily Standup\", \"description\": \"15 minute sync.\", \"location\": \"Online\", \"category\": \"Team\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T16:04:58+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/users/me", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:148.0) Gecko/20100101 Firefox/148.0", "Accept": "*/*", "Accept-Language": "en-GB,en;q=0.9", "Accept-Encoding": "gzip, deflate, br, zstd", "Referer": "http://localhost:8080/calendar", "Authorization": "***", "DNT": "1", "Sec-GPC": "1", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=0"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 16:04:58 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "137"}, "body": "{\"data\": {\"id\": 2, \"email\": \"adrians@chezstephens.org.uk\", \"account_status\": \"active\", \"email_verified_at\": \"2026-03-30T16:01:41+00:00\"}}", "body_bytes": 137, "body_truncated": false}} +{"ts": "2026-03-30T16:04:58+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/public/events?view=month&date=2026-04-01", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:148.0) Gecko/20100101 Firefox/148.0", "Accept": "*/*", "Accept-Language": "en-GB,en;q=0.9", "Accept-Encoding": "gzip, deflate, br, zstd", "Referer": "http://localhost:8080/calendar", "DNT": "1", "Sec-GPC": "1", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 16:04:58 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "6476"}, "body": "{\"data\": [{\"event_id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-01T10:00:00+01:00\", \"occurrence_end\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\"}, {\"event_id\": 10, \"uid\": \"fixture-ce-010@calendar-wp-plugin\", \"title\": \"Therapy Session\", \"description\": \"Used for single-occurrence delete exception tests.\", \"location\": \"Clinic\", \"category\": \"Health\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-03T14:00:00+01:00\", \"occurrence_end\": \"2026-04-03T15:00:00+01:00\", \"repeat_type\": \"weekly\"}, {\"event_id\": 3, \"uid\": \"fixture-ce-003@calendar-wp-plugin\", \"title\": \"Daily Standup\", \"description\": \"15 minute sync.\", \"location\": \"Online\", \"category\": \"Team\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-06T09:00:00+01:00\", \"occurrence_end\": \"2026-04-06T09:15:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 3, \"uid\": \"fixture-ce-003@calendar-wp-plugin\", \"title\": \"Daily Standup\", \"description\": \"15 minute sync.\", \"location\": \"Online\", \"category\": \"Team\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T16:05:01+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/10", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:148.0) Gecko/20100101 Firefox/148.0", "Accept": "*/*", "Accept-Language": "en-GB,en;q=0.9", "Accept-Encoding": "gzip, deflate, br, zstd", "Referer": "http://localhost:8080/calendar", "Authorization": "***", "DNT": "1", "Sec-GPC": "1", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=0"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 16:05:01 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "686"}, "body": "{\"data\": {\"id\": 10, \"uid\": \"fixture-ce-010@calendar-wp-plugin\", \"title\": \"Therapy Session\", \"description\": \"Used for single-occurrence delete exception tests.\", \"location\": \"Clinic\", \"category\": \"Health\", \"all_day_event\": false, \"start_datetime\": \"2026-04-03T14:00:00+01:00\", \"end_datetime\": \"2026-04-03T15:00:00+01:00\", \"repeat_type\": \"weekly\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"count\", \"repeat_count\": 8, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"2972ba81ca1c2300\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T16:01:41+00:00\"}}", "body_bytes": 686, "body_truncated": false}} +{"ts": "2026-03-30T16:05:07+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/10/occurrences?from=2026-04-01&months=3", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:148.0) Gecko/20100101 Firefox/148.0", "Accept": "*/*", "Accept-Language": "en-GB,en;q=0.9", "Accept-Encoding": "gzip, deflate, br, zstd", "Referer": "http://localhost:8080/calendar", "Authorization": "***", "DNT": "1", "Sec-GPC": "1", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=0"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 16:05:07 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "2794"}, "body": "{\"data\": [{\"event_id\": 10, \"uid\": \"fixture-ce-010@calendar-wp-plugin\", \"title\": \"Therapy Session\", \"description\": \"Used for single-occurrence delete exception tests.\", \"location\": \"Clinic\", \"category\": \"Health\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-03T14:00:00+01:00\", \"occurrence_end\": \"2026-04-03T15:00:00+01:00\", \"repeat_type\": \"weekly\"}, {\"event_id\": 10, \"uid\": \"fixture-ce-010@calendar-wp-plugin\", \"title\": \"Therapy Session\", \"description\": \"Used for single-occurrence delete exception tests.\", \"location\": \"Clinic\", \"category\": \"Health\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-10T14:00:00+01:00\", \"occurrence_end\": \"2026-04-10T15:00:00+01:00\", \"repeat_type\": \"weekly\"}, {\"event_id\": 10, \"uid\": \"fixture-ce-010@calendar-wp-plugin\", \"title\": \"Therapy Session\", \"description\": \"Used for single-occurrence delete exception tests.\", \"location\": \"Clinic\", \"category\": \"Health\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-17T14:00:00+01:00\", \"occurrence_end\": \"2026-04-17T15:00:00+01:00\", \"repeat_type\": \"weekly\"}, {\"event_id\": 10, \"uid\": \"fixture-ce-010@calendar-wp-plugin\", \"title\": \"Therapy Session\", \"description\": \"Used for single-occurrence delete excepti ...(truncated)", "body_bytes": 2794, "body_truncated": false}} +{"ts": "2026-03-30T16:05:11+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/10/occurrences/2026-04-17T14%3A00%3A00%2B01%3A00", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:148.0) Gecko/20100101 Firefox/148.0", "Accept": "*/*", "Accept-Language": "en-GB,en;q=0.9", "Accept-Encoding": "gzip, deflate, br, zstd", "Referer": "http://localhost:8080/calendar", "Content-Type": "application/json", "Authorization": "***", "Origin": "http://localhost:8080", "DNT": "1", "Sec-GPC": "1", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=0"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 16:05:11 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T16:05:11+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/10/occurrences?from=2026-04-01&months=3", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:148.0) Gecko/20100101 Firefox/148.0", "Accept": "*/*", "Accept-Language": "en-GB,en;q=0.9", "Accept-Encoding": "gzip, deflate, br, zstd", "Referer": "http://localhost:8080/calendar", "Authorization": "***", "DNT": "1", "Sec-GPC": "1", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 16:05:11 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "2446"}, "body": "{\"data\": [{\"event_id\": 10, \"uid\": \"fixture-ce-010@calendar-wp-plugin\", \"title\": \"Therapy Session\", \"description\": \"Used for single-occurrence delete exception tests.\", \"location\": \"Clinic\", \"category\": \"Health\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-03T14:00:00+01:00\", \"occurrence_end\": \"2026-04-03T15:00:00+01:00\", \"repeat_type\": \"weekly\"}, {\"event_id\": 10, \"uid\": \"fixture-ce-010@calendar-wp-plugin\", \"title\": \"Therapy Session\", \"description\": \"Used for single-occurrence delete exception tests.\", \"location\": \"Clinic\", \"category\": \"Health\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-10T14:00:00+01:00\", \"occurrence_end\": \"2026-04-10T15:00:00+01:00\", \"repeat_type\": \"weekly\"}, {\"event_id\": 10, \"uid\": \"fixture-ce-010@calendar-wp-plugin\", \"title\": \"Therapy Session\", \"description\": \"Used for single-occurrence delete exception tests.\", \"location\": \"Clinic\", \"category\": \"Health\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-24T14:00:00+01:00\", \"occurrence_end\": \"2026-04-24T15:00:00+01:00\", \"repeat_type\": \"weekly\"}, {\"event_id\": 10, \"uid\": \"fixture-ce-010@calendar-wp-plugin\", \"title\": \"Therapy Session\", \"description\": \"Used for single-occurrence delete excepti ...(truncated)", "body_bytes": 2446, "body_truncated": false}} +{"ts": "2026-03-30T16:05:11+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/public/events?view=month&date=2026-04-01", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:148.0) Gecko/20100101 Firefox/148.0", "Accept": "*/*", "Accept-Language": "en-GB,en;q=0.9", "Accept-Encoding": "gzip, deflate, br, zstd", "Referer": "http://localhost:8080/calendar", "DNT": "1", "Sec-GPC": "1", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 16:05:11 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "6128"}, "body": "{\"data\": [{\"event_id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-01T10:00:00+01:00\", \"occurrence_end\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\"}, {\"event_id\": 10, \"uid\": \"fixture-ce-010@calendar-wp-plugin\", \"title\": \"Therapy Session\", \"description\": \"Used for single-occurrence delete exception tests.\", \"location\": \"Clinic\", \"category\": \"Health\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-03T14:00:00+01:00\", \"occurrence_end\": \"2026-04-03T15:00:00+01:00\", \"repeat_type\": \"weekly\"}, {\"event_id\": 3, \"uid\": \"fixture-ce-003@calendar-wp-plugin\", \"title\": \"Daily Standup\", \"description\": \"15 minute sync.\", \"location\": \"Online\", \"category\": \"Team\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-06T09:00:00+01:00\", \"occurrence_end\": \"2026-04-06T09:15:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 3, \"uid\": \"fixture-ce-003@calendar-wp-plugin\", \"title\": \"Daily Standup\", \"description\": \"15 minute sync.\", \"location\": \"Online\", \"category\": \"Team\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T16:33:31+00:00", "client": "127.0.0.1", "method": "PROPFIND", "path": "/caldav/calendars/public/", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/xml; charset=utf-8", "Content-Length": "144", "Depth": "1", "Origin": "http://127.0.0.1:8080", "DNT": "1", "Authorization": "***", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "\n", "body_bytes": 144, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 16:33:31 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "3570"}, "body": "\n\n\n /caldav/calendars/public/\n \n \n Public Calendar\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/1.ics\n \n \n text/calendar; charset=utf-8\"7afd6bfad476334a\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/2.ics\n \n \n text/calendar; charset=utf-8\"bd549a6776fb7591\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/3.ics\n \n \n ...(truncated)", "body_bytes": 3570, "body_truncated": false}} +{"ts": "2026-03-30T16:33:31+00:00", "client": "127.0.0.1", "method": "REPORT", "path": "/caldav/calendars/public/", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/xml; charset=utf-8", "Content-Length": "232", "Depth": "1", "Origin": "http://127.0.0.1:8080", "DNT": "1", "Authorization": "***", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "\n/caldav/calendars/public/10.ics", "body_bytes": 232, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 16:33:31 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "1070"}, "body": "\n\n\n /caldav/calendars/public/10.ics\n \n \n \"41b7bbfae11cdaad\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-010@calendar-wp-plugin\r\nDTSTAMP:20260330T163331Z\r\nSUMMARY:Therapy Session\r\nDESCRIPTION:Used for single-occurrence delete exception tests.\r\nLOCATION:Clinic\r\nCATEGORIES:Health\r\nDTSTART;TZID=Europe/London:20260403T140000\r\nDTEND;TZID=Europe/London:20260403T150000\r\nRRULE:FREQ=WEEKLY;COUNT=8\r\nEXDATE;TZID=Europe/London:20260417T140000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n", "body_bytes": 1070, "body_truncated": false}} +{"ts": "2026-03-30T17:03:35+00:00", "client": "127.0.0.1", "method": "PROPFIND", "path": "/caldav/calendars/public/", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/xml; charset=utf-8", "Content-Length": "144", "Depth": "1", "Origin": "http://127.0.0.1:8080", "DNT": "1", "Authorization": "***", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "\n", "body_bytes": 144, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:03:35 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "3570"}, "body": "\n\n\n /caldav/calendars/public/\n \n \n Public Calendar\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/1.ics\n \n \n text/calendar; charset=utf-8\"7afd6bfad476334a\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/2.ics\n \n \n text/calendar; charset=utf-8\"bd549a6776fb7591\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/3.ics\n \n \n ...(truncated)", "body_bytes": 3570, "body_truncated": false}} +{"ts": "2026-03-30T17:29:02+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:29:02 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "6585"}, "body": "{\"data\": [{\"id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"start_datetime\": \"2026-04-01T10:00:00+01:00\", \"end_datetime\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"b382c678635b840b\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T17:29:00+00:00\"}, {\"id\": 2, \"uid\": \"fixture-ce-002@calendar-wp-plugin\", \"title\": \"Office Closed\", \"description\": \"Public holiday closure.\", \"location\": \"HQ\", \"category\": \"Operations\", \"all_day_event\": true, \"start_datetime\": \"2026-05-04\", \"end_datetime\": \"2026-05-05\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"1d2c2", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T17:29:02+00:00", "client": "127.0.0.1", "method": "GET", "path": "/calendar.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:29:02 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"bb575c8c2154be15\"", "Content-Length": "3132"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nCALSCALE:GREGORIAN\r\nX-WR-TIMEZONE:Europe/London\r\nBEGIN:VEVENT\r\nUID:fixture-ce-001@calendar-wp-plugin\r\nDTSTAMP:20260330T172902Z\r\nSUMMARY:Board Meeting\r\nDESCRIPTION:Quarterly board review.\r\nLOCATION:Room A\r\nCATEGORIES:Governance\r\nDTSTART;TZID=Europe/London:20260401T100000\r\nDTEND;TZID=Europe/London:20260401T113000\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-002@calendar-wp-plugin\r\nDTSTAMP:20260330T172902Z\r\nSUMMARY:Office Closed\r\nDESCRIPTION:Public holiday closure.\r\nLOCATION:HQ\r\nCATEGORIES:Operations\r\nDTSTART;VALUE=DATE:20260504\r\nDTEND;VALUE=DATE:20260505\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-003@calendar-wp-plugin\r\nDTSTAMP:20260330T172902Z\r\nSUMMARY:Daily Standup\r\nDESCRIPTION:15 minute sync.\r\nLOCATION:Online\r\nCATEGORIES:Team\r\nDTSTART;TZID=Europe/London:20260406T090000\r\nDTEND;TZID=Europe/London:20260406T091500\r\nRRULE:FREQ=DAILY;COUNT=10\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-004@calendar-wp-plugin\r\nDTSTAMP:20260330T172902Z\r\nSUMMARY:Community Lunch\r\nDESCRIPTION:Weekly community lunch.\r\nLOCATION:Cafeteria\r\nCATEGORIES:Community\r\nDTSTART;TZID=Europe/London:20260408T123000\r\nDTEND;TZID=Europe/London:20260408T133000\r\n ...(truncated)", "body_bytes": 3132, "body_truncated": false}} +{"ts": "2026-03-30T17:29:02+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/1.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:29:02 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"b382c678635b840b\"", "Content-Length": "364"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-001@calendar-wp-plugin\r\nDTSTAMP:20260330T172902Z\r\nSUMMARY:Board Meeting\r\nDESCRIPTION:Quarterly board review.\r\nLOCATION:Room A\r\nCATEGORIES:Governance\r\nDTSTART;TZID=Europe/London:20260401T100000\r\nDTEND;TZID=Europe/London:20260401T113000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 364, "body_truncated": false}} +{"ts": "2026-03-30T17:29:02+00:00", "client": "127.0.0.1", "method": "REPORT", "path": "/caldav/calendars/public/", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/xml; charset=utf-8", "Depth": "1", "Content-Length": "239"}, "body": "\n\n \n /caldav/calendars/public/4.ics\n\n", "body_bytes": 239, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:29:02 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "997"}, "body": "\n\n\n /caldav/calendars/public/4.ics\n \n \n \"5e18ae38222b91d2\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-004@calendar-wp-plugin\r\nDTSTAMP:20260330T172902Z\r\nSUMMARY:Community Lunch\r\nDESCRIPTION:Weekly community lunch.\r\nLOCATION:Cafeteria\r\nCATEGORIES:Community\r\nDTSTART;TZID=Europe/London:20260408T123000\r\nDTEND;TZID=Europe/London:20260408T133000\r\nRRULE:FREQ=WEEKLY\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n", "body_bytes": 997, "body_truncated": false}} +{"ts": "2026-03-30T17:29:02+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/10/occurrences/2026-04-17T14:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:29:02 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T17:29:02+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "X-WP-User": "admin", "Content-Length": "287"}, "body": "{\"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}", "body_bytes": 287, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:29:02 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "647"}, "body": "{\"data\": {\"id\": 11, \"uid\": \"00c5f32762e8c0b85a1c@calendar-wp-plugin\", \"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_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"count\", \"repeat_count\": 3, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"c7092bf3eb675ef3\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T17:29:02+00:00\"}}", "body_bytes": 647, "body_truncated": false}} +{"ts": "2026-03-30T17:29:02+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/11/occurrences?from=2026-04-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:29:02 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "937"}, "body": "{\"data\": [{\"event_id\": 11, \"uid\": \"00c5f32762e8c0b85a1c@calendar-wp-plugin\", \"title\": \"Smoke Occurrences API\", \"description\": \"occ-endpoint\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-01T10:00:00+01:00\", \"occurrence_end\": \"2026-04-01T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 11, \"uid\": \"00c5f32762e8c0b85a1c@calendar-wp-plugin\", \"title\": \"Smoke Occurrences API\", \"description\": \"occ-endpoint\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-02T10:00:00+01:00\", \"occurrence_end\": \"2026-04-02T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 11, \"uid\": \"00c5f32762e8c0b85a1c@calendar-wp-plugin\", \"title\": \"Smoke Occurrences API\", \"description\": \"occ-endpoint\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-03T10:00:00+01:00\", \"occurrence_end\": \"2026-04-03T11:00:00+01:00\", \"repeat_type\": \"daily\"}]}", "body_bytes": 937, "body_truncated": false}} +{"ts": "2026-03-30T17:29:02+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/11/occurrences/2026-04-02T10:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:29:02 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T17:29:02+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/11/occurrences/2026-04-02T10:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:29:02 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T17:29:02+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/11/occurrences?from=2026-04-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:29:02 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "628"}, "body": "{\"data\": [{\"event_id\": 11, \"uid\": \"00c5f32762e8c0b85a1c@calendar-wp-plugin\", \"title\": \"Smoke Occurrences API\", \"description\": \"occ-endpoint\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-01T10:00:00+01:00\", \"occurrence_end\": \"2026-04-01T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 11, \"uid\": \"00c5f32762e8c0b85a1c@calendar-wp-plugin\", \"title\": \"Smoke Occurrences API\", \"description\": \"occ-endpoint\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-03T10:00:00+01:00\", \"occurrence_end\": \"2026-04-03T11:00:00+01:00\", \"repeat_type\": \"daily\"}]}", "body_bytes": 628, "body_truncated": false}} +{"ts": "2026-03-30T17:29:02+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-vtimezone.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "454"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN\nVERSION:2.0\nBEGIN:VTIMEZONE\nTZID:Europe/London\nBEGIN:STANDARD\nDTSTART:18471201T000000\nRRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=9\nTZOFFSETFROM:+0115\nTZOFFSETTO:+0000\nTZNAME:GMT\nEND:STANDARD\nEND:VTIMEZONE\nBEGIN:VEVENT\nUID:smoke-vtimezone-parser-001\nSUMMARY:Smoke VTIMEZONE Parse\nDTSTART;TZID=Europe/London:20260423T150000\nDTEND;TZID=Europe/London:20260423T160000\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 454, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:29:02 GMT", "ETag": "\"2105e046d5fa798f\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T17:29:02+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-vtimezone.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:29:02 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"2105e046d5fa798f\"", "Content-Length": "288"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-vtimezone-parser-001\r\nDTSTAMP:20260330T172902Z\r\nSUMMARY:Smoke VTIMEZONE Parse\r\nDTSTART;TZID=Europe/London:20260423T150000\r\nDTEND;TZID=Europe/London:20260423T160000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 288, "body_truncated": false}} +{"ts": "2026-03-30T17:29:02+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-monthly-nth.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "261"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-monthly-nth-001\nSUMMARY:Smoke Monthly Nth\nDTSTART;TZID=Europe/London:20260402T150000\nDTEND;TZID=Europe/London:20260402T160000\nRRULE:FREQ=MONTHLY;BYDAY=SU;BYSETPOS=4\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 261, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:29:02 GMT", "ETag": "\"c13600e6f9aee896\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T17:29:02+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-monthly-nth.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:29:02 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"c13600e6f9aee896\"", "Content-Length": "319"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-nth-001\r\nDTSTAMP:20260330T172902Z\r\nSUMMARY:Smoke Monthly Nth\r\nDTSTART;TZID=Europe/London:20260426T150000\r\nDTEND;TZID=Europe/London:20260426T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SU;BYSETPOS=4\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 319, "body_truncated": false}} +{"ts": "2026-03-30T17:29:02+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-monthly-ordinal.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "259"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-monthly-ordinal-001\nSUMMARY:Smoke Monthly Ordinal\nDTSTART;TZID=Europe/London:20260411T150000\nDTEND;TZID=Europe/London:20260411T160000\nRRULE:FREQ=MONTHLY;BYDAY=2SA\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 259, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:29:02 GMT", "ETag": "\"685db0d2f5593d0b\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T17:29:02+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-monthly-ordinal.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:29:02 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"685db0d2f5593d0b\"", "Content-Length": "327"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-ordinal-001\r\nDTSTAMP:20260330T172902Z\r\nSUMMARY:Smoke Monthly Ordinal\r\nDTSTART;TZID=Europe/London:20260411T150000\r\nDTEND;TZID=Europe/London:20260411T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SA;BYSETPOS=2\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 327, "body_truncated": false}} +{"ts": "2026-03-30T17:29:03+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-monthly-last.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "264"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-monthly-last-001\nSUMMARY:Smoke Monthly Last\nDTSTART;TZID=Europe/London:20260425T150000\nDTEND;TZID=Europe/London:20260425T160000\nRRULE:FREQ=MONTHLY;BYDAY=SA;BYSETPOS=-1\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 264, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:29:03 GMT", "ETag": "\"5ed4a7bad55cd265\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T17:29:03+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-monthly-last.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:29:03 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"5ed4a7bad55cd265\"", "Content-Length": "322"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-last-001\r\nDTSTAMP:20260330T172903Z\r\nSUMMARY:Smoke Monthly Last\r\nDTSTART;TZID=Europe/London:20260425T150000\r\nDTEND;TZID=Europe/London:20260425T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SA;BYSETPOS=-1\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 322, "body_truncated": false}} +{"ts": "2026-03-30T17:29:03+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "X-WP-User": "admin", "Content-Length": "362"}, "body": "{\"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\"}", "body_bytes": 362, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:29:03 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "673"}, "body": "{\"data\": {\"id\": 16, \"uid\": \"2ea801a45310bab83a50@calendar-wp-plugin\", \"title\": \"Smoke 4th Sunday Anchor\", \"description\": \"anchor-normalization\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"start_datetime\": \"2026-05-24T15:00:00+01:00\", \"end_datetime\": \"2026-05-24T16:00:00+01:00\", \"repeat_type\": \"monthly\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"weekday_of_month\", \"repeat_nth_day\": null, \"repeat_nth_pos\": 4, \"repeat_nth_weekday\": 0, \"repeat_range_mode\": \"no_end\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"cec610533c64bc27\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T17:29:03+00:00\"}}", "body_bytes": 673, "body_truncated": false}} +{"ts": "2026-03-30T17:29:03+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/16/occurrences/2026-05-24T15:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:29:03 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T17:29:03+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/16.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:29:03 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"c02269cb3a8e8d71\"", "Content-Length": "420"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:2ea801a45310bab83a50@calendar-wp-plugin\r\nDTSTAMP:20260330T172903Z\r\nSUMMARY:Smoke 4th Sunday Anchor\r\nDESCRIPTION:anchor-normalization\r\nDTSTART;TZID=Europe/London:20260524T150000\r\nDTEND;TZID=Europe/London:20260524T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SU;BYSETPOS=4\r\nEXDATE;TZID=Europe/London:20260524T150000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 420, "body_truncated": false}} +{"ts": "2026-03-30T17:29:03+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "X-WP-User": "admin", "Content-Length": "295"}, "body": "{\"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\"}", "body_bytes": 295, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:29:03 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "655"}, "body": "{\"data\": {\"id\": 17, \"uid\": \"371b0caae432edbf9a59@calendar-wp-plugin\", \"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_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"until\", \"repeat_count\": null, \"repeat_until\": \"2026-03-19\", \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"f28f7a5c7c266a7e\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T17:29:03+00:00\"}}", "body_bytes": 655, "body_truncated": false}} +{"ts": "2026-03-30T17:29:03+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/17/occurrences/2026-03-11T11:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:29:03 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T17:29:03+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/17/occurrences?from=2026-03-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:29:03 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "5212"}, "body": "{\"data\": [{\"event_id\": 17, \"uid\": \"371b0caae432edbf9a59@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-02T10:00:00+00:00\", \"occurrence_end\": \"2026-03-02T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"371b0caae432edbf9a59@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-03T10:00:00+00:00\", \"occurrence_end\": \"2026-03-03T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"371b0caae432edbf9a59@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-04T10:00:00+00:00\", \"occurrence_end\": \"2026-03-04T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"371b0caae432edbf9a59@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-05T10:00:00+00:00\", \"occurrence_end\": \"2026-03-05T11:00:00", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T17:29:03+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/17/occurrences/2026-03-10", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:29:03 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T17:29:03+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/17/occurrences?from=2026-03-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:29:03 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "4906"}, "body": "{\"data\": [{\"event_id\": 17, \"uid\": \"371b0caae432edbf9a59@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-02T10:00:00+00:00\", \"occurrence_end\": \"2026-03-02T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"371b0caae432edbf9a59@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-03T10:00:00+00:00\", \"occurrence_end\": \"2026-03-03T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"371b0caae432edbf9a59@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-04T10:00:00+00:00\", \"occurrence_end\": \"2026-03-04T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"371b0caae432edbf9a59@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-05T10:00:00+00:00\", \"occurrence_end\": \"2026-03-05T11:00:00", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T17:29:03+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-exdate-import.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "323"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-exdate-import-001\nSUMMARY:Smoke EXDATE Import\nDTSTART;TZID=Europe/London:20260302T100000\nDTEND;TZID=Europe/London:20260302T110000\nRRULE:FREQ=DAILY;UNTIL=20260319T235959\nEXDATE;TZID=Europe/London:20260310T100000,20260311T100000\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 323, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:29:03 GMT", "ETag": "\"e70d8316450066f2\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T17:29:03+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:29:03 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "11645"}, "body": "{\"data\": [{\"id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"start_datetime\": \"2026-04-01T10:00:00+01:00\", \"end_datetime\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"b382c678635b840b\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T17:29:00+00:00\"}, {\"id\": 2, \"uid\": \"fixture-ce-002@calendar-wp-plugin\", \"title\": \"Office Closed\", \"description\": \"Public holiday closure.\", \"location\": \"HQ\", \"category\": \"Operations\", \"all_day_event\": true, \"start_datetime\": \"2026-05-04\", \"end_datetime\": \"2026-05-05\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"1d2c2", "body_bytes": 3927, "body_truncated": true}} +{"ts": "2026-03-30T17:29:03+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/18/occurrences?from=2026-03-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:29:03 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "4474"}, "body": "{\"data\": [{\"event_id\": 18, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-02T10:00:00+01:00\", \"occurrence_end\": \"2026-03-02T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 18, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-03T10:00:00+01:00\", \"occurrence_end\": \"2026-03-03T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 18, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-04T10:00:00+01:00\", \"occurrence_end\": \"2026-03-04T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 18, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-05T10:00:00+01:00\", \"occurrence_end\": \"2026-03-05T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 18, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE ", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T17:29:03+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-cancelled-occurrence.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "467"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-cancelled-occurrence-001\nSUMMARY:Smoke Cancelled Occurrence\nDTSTART;TZID=Europe/London:20260408T123000\nDTEND;TZID=Europe/London:20260408T133000\nRRULE:FREQ=WEEKLY\nEND:VEVENT\nBEGIN:VEVENT\nUID:smoke-cancelled-occurrence-001\nRECURRENCE-ID;TZID=Europe/London:20260506T123000\nDTSTART;TZID=Europe/London:20260506T123000\nDTEND;TZID=Europe/London:20260506T133000\nSTATUS:CANCELLED\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 467, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:29:03 GMT", "ETag": "\"159009a65264b9e1\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T17:29:03+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-cancelled-occurrence.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:29:03 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"159009a65264b9e1\"", "Content-Length": "359"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-cancelled-occurrence-001\r\nDTSTAMP:20260330T172903Z\r\nSUMMARY:Smoke Cancelled Occurrence\r\nDTSTART;TZID=Europe/London:20260408T123000\r\nDTEND;TZID=Europe/London:20260408T133000\r\nRRULE:FREQ=WEEKLY\r\nEXDATE;TZID=Europe/London:20260506T123000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 359, "body_truncated": false}} +{"ts": "2026-03-30T17:29:03+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:29:03 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "12273"}, "body": "{\"data\": [{\"id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"start_datetime\": \"2026-04-01T10:00:00+01:00\", \"end_datetime\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"b382c678635b840b\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T17:29:00+00:00\"}, {\"id\": 2, \"uid\": \"fixture-ce-002@calendar-wp-plugin\", \"title\": \"Office Closed\", \"description\": \"Public holiday closure.\", \"location\": \"HQ\", \"category\": \"Operations\", \"all_day_event\": true, \"start_datetime\": \"2026-05-04\", \"end_datetime\": \"2026-05-05\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"1d2c2", "body_bytes": 3927, "body_truncated": true}} +{"ts": "2026-03-30T17:29:03+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/19/occurrences?from=2026-05-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:29:03 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "892"}, "body": "{\"data\": [{\"event_id\": 19, \"uid\": \"smoke-cancelled-occurrence-001\", \"title\": \"Smoke Cancelled Occurrence\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-13T12:30:00+01:00\", \"occurrence_end\": \"2026-05-13T13:30:00+01:00\", \"repeat_type\": \"weekly\"}, {\"event_id\": 19, \"uid\": \"smoke-cancelled-occurrence-001\", \"title\": \"Smoke Cancelled Occurrence\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-20T12:30:00+01:00\", \"occurrence_end\": \"2026-05-20T13:30:00+01:00\", \"repeat_type\": \"weekly\"}, {\"event_id\": 19, \"uid\": \"smoke-cancelled-occurrence-001\", \"title\": \"Smoke Cancelled Occurrence\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-27T12:30:00+01:00\", \"occurrence_end\": \"2026-05-27T13:30:00+01:00\", \"repeat_type\": \"weekly\"}]}", "body_bytes": 892, "body_truncated": false}} +{"ts": "2026-03-30T17:29:03+00:00", "client": "127.0.0.1", "method": "GET", "path": "/calendar", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:29:03 GMT", "Content-Type": "text/html; charset=utf-8", "Content-Length": "43447"}, "body": "[omitted html payload: 3934 bytes]", "body_bytes": 3934, "body_truncated": true}} +{"ts": "2026-03-30T17:29:03+00:00", "client": "127.0.0.1", "method": "GET", "path": "/calendar", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:29:03 GMT", "Content-Type": "text/html; charset=utf-8", "Content-Length": "43447"}, "body": "[omitted html payload: 3934 bytes]", "body_bytes": 3934, "body_truncated": true}} +{"ts": "2026-03-30T17:30:35+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:30:35 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "6585"}, "body": "{\"data\": [{\"id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"start_datetime\": \"2026-04-01T10:00:00+01:00\", \"end_datetime\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"465df57ed39797a5\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T17:30:33+00:00\"}, {\"id\": 2, \"uid\": \"fixture-ce-002@calendar-wp-plugin\", \"title\": \"Office Closed\", \"description\": \"Public holiday closure.\", \"location\": \"HQ\", \"category\": \"Operations\", \"all_day_event\": true, \"start_datetime\": \"2026-05-04\", \"end_datetime\": \"2026-05-05\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"651ef", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T17:30:35+00:00", "client": "127.0.0.1", "method": "GET", "path": "/calendar.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:30:35 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"4be039fb110f13fc\"", "Content-Length": "3132"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nCALSCALE:GREGORIAN\r\nX-WR-TIMEZONE:Europe/London\r\nBEGIN:VEVENT\r\nUID:fixture-ce-001@calendar-wp-plugin\r\nDTSTAMP:20260330T173035Z\r\nSUMMARY:Board Meeting\r\nDESCRIPTION:Quarterly board review.\r\nLOCATION:Room A\r\nCATEGORIES:Governance\r\nDTSTART;TZID=Europe/London:20260401T100000\r\nDTEND;TZID=Europe/London:20260401T113000\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-002@calendar-wp-plugin\r\nDTSTAMP:20260330T173035Z\r\nSUMMARY:Office Closed\r\nDESCRIPTION:Public holiday closure.\r\nLOCATION:HQ\r\nCATEGORIES:Operations\r\nDTSTART;VALUE=DATE:20260504\r\nDTEND;VALUE=DATE:20260505\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-003@calendar-wp-plugin\r\nDTSTAMP:20260330T173035Z\r\nSUMMARY:Daily Standup\r\nDESCRIPTION:15 minute sync.\r\nLOCATION:Online\r\nCATEGORIES:Team\r\nDTSTART;TZID=Europe/London:20260406T090000\r\nDTEND;TZID=Europe/London:20260406T091500\r\nRRULE:FREQ=DAILY;COUNT=10\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-004@calendar-wp-plugin\r\nDTSTAMP:20260330T173035Z\r\nSUMMARY:Community Lunch\r\nDESCRIPTION:Weekly community lunch.\r\nLOCATION:Cafeteria\r\nCATEGORIES:Community\r\nDTSTART;TZID=Europe/London:20260408T123000\r\nDTEND;TZID=Europe/London:20260408T133000\r\n ...(truncated)", "body_bytes": 3132, "body_truncated": false}} +{"ts": "2026-03-30T17:30:35+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/1.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:30:35 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"465df57ed39797a5\"", "Content-Length": "364"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-001@calendar-wp-plugin\r\nDTSTAMP:20260330T173035Z\r\nSUMMARY:Board Meeting\r\nDESCRIPTION:Quarterly board review.\r\nLOCATION:Room A\r\nCATEGORIES:Governance\r\nDTSTART;TZID=Europe/London:20260401T100000\r\nDTEND;TZID=Europe/London:20260401T113000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 364, "body_truncated": false}} +{"ts": "2026-03-30T17:30:35+00:00", "client": "127.0.0.1", "method": "REPORT", "path": "/caldav/calendars/public/", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/xml; charset=utf-8", "Depth": "1", "Content-Length": "239"}, "body": "\n\n \n /caldav/calendars/public/4.ics\n\n", "body_bytes": 239, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:30:35 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "997"}, "body": "\n\n\n /caldav/calendars/public/4.ics\n \n \n \"2c12143b41cf6e99\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-004@calendar-wp-plugin\r\nDTSTAMP:20260330T173035Z\r\nSUMMARY:Community Lunch\r\nDESCRIPTION:Weekly community lunch.\r\nLOCATION:Cafeteria\r\nCATEGORIES:Community\r\nDTSTART;TZID=Europe/London:20260408T123000\r\nDTEND;TZID=Europe/London:20260408T133000\r\nRRULE:FREQ=WEEKLY\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n", "body_bytes": 997, "body_truncated": false}} +{"ts": "2026-03-30T17:30:35+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/10/occurrences/2026-04-17T14:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:30:35 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T17:30:35+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "X-WP-User": "admin", "Content-Length": "287"}, "body": "{\"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}", "body_bytes": 287, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:30:35 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "647"}, "body": "{\"data\": {\"id\": 11, \"uid\": \"d90ef129e8487aac2a02@calendar-wp-plugin\", \"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_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"count\", \"repeat_count\": 3, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"3b5c92f57b27a7e7\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T17:30:35+00:00\"}}", "body_bytes": 647, "body_truncated": false}} +{"ts": "2026-03-30T17:30:35+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/11/occurrences?from=2026-04-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:30:35 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "937"}, "body": "{\"data\": [{\"event_id\": 11, \"uid\": \"d90ef129e8487aac2a02@calendar-wp-plugin\", \"title\": \"Smoke Occurrences API\", \"description\": \"occ-endpoint\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-01T10:00:00+01:00\", \"occurrence_end\": \"2026-04-01T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 11, \"uid\": \"d90ef129e8487aac2a02@calendar-wp-plugin\", \"title\": \"Smoke Occurrences API\", \"description\": \"occ-endpoint\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-02T10:00:00+01:00\", \"occurrence_end\": \"2026-04-02T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 11, \"uid\": \"d90ef129e8487aac2a02@calendar-wp-plugin\", \"title\": \"Smoke Occurrences API\", \"description\": \"occ-endpoint\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-03T10:00:00+01:00\", \"occurrence_end\": \"2026-04-03T11:00:00+01:00\", \"repeat_type\": \"daily\"}]}", "body_bytes": 937, "body_truncated": false}} +{"ts": "2026-03-30T17:30:35+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/11/occurrences/2026-04-02T10:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:30:35 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T17:30:35+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/11/occurrences/2026-04-02T10:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:30:35 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T17:30:35+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/11/occurrences?from=2026-04-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:30:35 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "628"}, "body": "{\"data\": [{\"event_id\": 11, \"uid\": \"d90ef129e8487aac2a02@calendar-wp-plugin\", \"title\": \"Smoke Occurrences API\", \"description\": \"occ-endpoint\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-01T10:00:00+01:00\", \"occurrence_end\": \"2026-04-01T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 11, \"uid\": \"d90ef129e8487aac2a02@calendar-wp-plugin\", \"title\": \"Smoke Occurrences API\", \"description\": \"occ-endpoint\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-03T10:00:00+01:00\", \"occurrence_end\": \"2026-04-03T11:00:00+01:00\", \"repeat_type\": \"daily\"}]}", "body_bytes": 628, "body_truncated": false}} +{"ts": "2026-03-30T17:30:36+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-vtimezone.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "454"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN\nVERSION:2.0\nBEGIN:VTIMEZONE\nTZID:Europe/London\nBEGIN:STANDARD\nDTSTART:18471201T000000\nRRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=9\nTZOFFSETFROM:+0115\nTZOFFSETTO:+0000\nTZNAME:GMT\nEND:STANDARD\nEND:VTIMEZONE\nBEGIN:VEVENT\nUID:smoke-vtimezone-parser-001\nSUMMARY:Smoke VTIMEZONE Parse\nDTSTART;TZID=Europe/London:20260423T150000\nDTEND;TZID=Europe/London:20260423T160000\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 454, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:30:36 GMT", "ETag": "\"48303f38375ccfe9\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T17:30:36+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-vtimezone.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:30:36 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"48303f38375ccfe9\"", "Content-Length": "288"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-vtimezone-parser-001\r\nDTSTAMP:20260330T173036Z\r\nSUMMARY:Smoke VTIMEZONE Parse\r\nDTSTART;TZID=Europe/London:20260423T150000\r\nDTEND;TZID=Europe/London:20260423T160000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 288, "body_truncated": false}} +{"ts": "2026-03-30T17:30:36+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-monthly-nth.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "261"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-monthly-nth-001\nSUMMARY:Smoke Monthly Nth\nDTSTART;TZID=Europe/London:20260402T150000\nDTEND;TZID=Europe/London:20260402T160000\nRRULE:FREQ=MONTHLY;BYDAY=SU;BYSETPOS=4\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 261, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:30:36 GMT", "ETag": "\"0a5f4111009ba192\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T17:30:36+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-monthly-nth.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:30:36 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"0a5f4111009ba192\"", "Content-Length": "319"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-nth-001\r\nDTSTAMP:20260330T173036Z\r\nSUMMARY:Smoke Monthly Nth\r\nDTSTART;TZID=Europe/London:20260426T150000\r\nDTEND;TZID=Europe/London:20260426T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SU;BYSETPOS=4\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 319, "body_truncated": false}} +{"ts": "2026-03-30T17:30:36+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-monthly-ordinal.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "259"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-monthly-ordinal-001\nSUMMARY:Smoke Monthly Ordinal\nDTSTART;TZID=Europe/London:20260411T150000\nDTEND;TZID=Europe/London:20260411T160000\nRRULE:FREQ=MONTHLY;BYDAY=2SA\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 259, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:30:36 GMT", "ETag": "\"7802f78bb8d6e8f3\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T17:30:36+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-monthly-ordinal.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:30:36 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"7802f78bb8d6e8f3\"", "Content-Length": "327"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-ordinal-001\r\nDTSTAMP:20260330T173036Z\r\nSUMMARY:Smoke Monthly Ordinal\r\nDTSTART;TZID=Europe/London:20260411T150000\r\nDTEND;TZID=Europe/London:20260411T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SA;BYSETPOS=2\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 327, "body_truncated": false}} +{"ts": "2026-03-30T17:30:36+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-monthly-last.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "264"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-monthly-last-001\nSUMMARY:Smoke Monthly Last\nDTSTART;TZID=Europe/London:20260425T150000\nDTEND;TZID=Europe/London:20260425T160000\nRRULE:FREQ=MONTHLY;BYDAY=SA;BYSETPOS=-1\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 264, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:30:36 GMT", "ETag": "\"4e08622f0a349692\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T17:30:36+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-monthly-last.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:30:36 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"4e08622f0a349692\"", "Content-Length": "322"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-last-001\r\nDTSTAMP:20260330T173036Z\r\nSUMMARY:Smoke Monthly Last\r\nDTSTART;TZID=Europe/London:20260425T150000\r\nDTEND;TZID=Europe/London:20260425T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SA;BYSETPOS=-1\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 322, "body_truncated": false}} +{"ts": "2026-03-30T17:30:36+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "X-WP-User": "admin", "Content-Length": "362"}, "body": "{\"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\"}", "body_bytes": 362, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:30:36 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "673"}, "body": "{\"data\": {\"id\": 16, \"uid\": \"f7dee92cb08d5885ad53@calendar-wp-plugin\", \"title\": \"Smoke 4th Sunday Anchor\", \"description\": \"anchor-normalization\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"start_datetime\": \"2026-05-24T15:00:00+01:00\", \"end_datetime\": \"2026-05-24T16:00:00+01:00\", \"repeat_type\": \"monthly\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"weekday_of_month\", \"repeat_nth_day\": null, \"repeat_nth_pos\": 4, \"repeat_nth_weekday\": 0, \"repeat_range_mode\": \"no_end\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"f74a13c50da08642\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T17:30:36+00:00\"}}", "body_bytes": 673, "body_truncated": false}} +{"ts": "2026-03-30T17:30:36+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/16/occurrences/2026-05-24T15:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:30:36 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T17:30:36+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/16.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:30:36 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"d7729a08966aa16f\"", "Content-Length": "420"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:f7dee92cb08d5885ad53@calendar-wp-plugin\r\nDTSTAMP:20260330T173036Z\r\nSUMMARY:Smoke 4th Sunday Anchor\r\nDESCRIPTION:anchor-normalization\r\nDTSTART;TZID=Europe/London:20260524T150000\r\nDTEND;TZID=Europe/London:20260524T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SU;BYSETPOS=4\r\nEXDATE;TZID=Europe/London:20260524T150000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 420, "body_truncated": false}} +{"ts": "2026-03-30T17:30:36+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "X-WP-User": "admin", "Content-Length": "295"}, "body": "{\"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\"}", "body_bytes": 295, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:30:36 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "655"}, "body": "{\"data\": {\"id\": 17, \"uid\": \"47420837950da56232ab@calendar-wp-plugin\", \"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_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"until\", \"repeat_count\": null, \"repeat_until\": \"2026-03-19\", \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"bbadcd70ff645ca1\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T17:30:36+00:00\"}}", "body_bytes": 655, "body_truncated": false}} +{"ts": "2026-03-30T17:30:36+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/17/occurrences/2026-03-11T11:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:30:36 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T17:30:36+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/17/occurrences?from=2026-03-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:30:36 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "5212"}, "body": "{\"data\": [{\"event_id\": 17, \"uid\": \"47420837950da56232ab@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-02T10:00:00+00:00\", \"occurrence_end\": \"2026-03-02T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"47420837950da56232ab@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-03T10:00:00+00:00\", \"occurrence_end\": \"2026-03-03T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"47420837950da56232ab@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-04T10:00:00+00:00\", \"occurrence_end\": \"2026-03-04T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"47420837950da56232ab@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-05T10:00:00+00:00\", \"occurrence_end\": \"2026-03-05T11:00:00", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T17:30:36+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/17/occurrences/2026-03-10", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:30:36 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T17:30:36+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/17/occurrences?from=2026-03-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:30:36 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "4906"}, "body": "{\"data\": [{\"event_id\": 17, \"uid\": \"47420837950da56232ab@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-02T10:00:00+00:00\", \"occurrence_end\": \"2026-03-02T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"47420837950da56232ab@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-03T10:00:00+00:00\", \"occurrence_end\": \"2026-03-03T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"47420837950da56232ab@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-04T10:00:00+00:00\", \"occurrence_end\": \"2026-03-04T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"47420837950da56232ab@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-05T10:00:00+00:00\", \"occurrence_end\": \"2026-03-05T11:00:00", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T17:30:36+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-exdate-import.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "323"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-exdate-import-001\nSUMMARY:Smoke EXDATE Import\nDTSTART;TZID=Europe/London:20260302T100000\nDTEND;TZID=Europe/London:20260302T110000\nRRULE:FREQ=DAILY;UNTIL=20260319T235959\nEXDATE;TZID=Europe/London:20260310T100000,20260311T100000\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 323, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:30:36 GMT", "ETag": "\"e5bd42e8bfd1b0d1\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T17:30:36+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:30:36 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "11645"}, "body": "{\"data\": [{\"id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"start_datetime\": \"2026-04-01T10:00:00+01:00\", \"end_datetime\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"465df57ed39797a5\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T17:30:33+00:00\"}, {\"id\": 2, \"uid\": \"fixture-ce-002@calendar-wp-plugin\", \"title\": \"Office Closed\", \"description\": \"Public holiday closure.\", \"location\": \"HQ\", \"category\": \"Operations\", \"all_day_event\": true, \"start_datetime\": \"2026-05-04\", \"end_datetime\": \"2026-05-05\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"651ef", "body_bytes": 3927, "body_truncated": true}} +{"ts": "2026-03-30T17:30:36+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/18/occurrences?from=2026-03-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:30:36 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "4474"}, "body": "{\"data\": [{\"event_id\": 18, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-02T10:00:00+01:00\", \"occurrence_end\": \"2026-03-02T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 18, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-03T10:00:00+01:00\", \"occurrence_end\": \"2026-03-03T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 18, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-04T10:00:00+01:00\", \"occurrence_end\": \"2026-03-04T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 18, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-05T10:00:00+01:00\", \"occurrence_end\": \"2026-03-05T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 18, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE ", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T17:30:36+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-cancelled-occurrence.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "467"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-cancelled-occurrence-001\nSUMMARY:Smoke Cancelled Occurrence\nDTSTART;TZID=Europe/London:20260408T123000\nDTEND;TZID=Europe/London:20260408T133000\nRRULE:FREQ=WEEKLY\nEND:VEVENT\nBEGIN:VEVENT\nUID:smoke-cancelled-occurrence-001\nRECURRENCE-ID;TZID=Europe/London:20260506T123000\nDTSTART;TZID=Europe/London:20260506T123000\nDTEND;TZID=Europe/London:20260506T133000\nSTATUS:CANCELLED\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 467, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:30:36 GMT", "ETag": "\"d3ca454bd41251d6\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T17:30:36+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-cancelled-occurrence.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:30:36 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"d3ca454bd41251d6\"", "Content-Length": "359"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-cancelled-occurrence-001\r\nDTSTAMP:20260330T173036Z\r\nSUMMARY:Smoke Cancelled Occurrence\r\nDTSTART;TZID=Europe/London:20260408T123000\r\nDTEND;TZID=Europe/London:20260408T133000\r\nRRULE:FREQ=WEEKLY\r\nEXDATE;TZID=Europe/London:20260506T123000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 359, "body_truncated": false}} +{"ts": "2026-03-30T17:30:36+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:30:36 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "12273"}, "body": "{\"data\": [{\"id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"start_datetime\": \"2026-04-01T10:00:00+01:00\", \"end_datetime\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"465df57ed39797a5\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T17:30:33+00:00\"}, {\"id\": 2, \"uid\": \"fixture-ce-002@calendar-wp-plugin\", \"title\": \"Office Closed\", \"description\": \"Public holiday closure.\", \"location\": \"HQ\", \"category\": \"Operations\", \"all_day_event\": true, \"start_datetime\": \"2026-05-04\", \"end_datetime\": \"2026-05-05\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"651ef", "body_bytes": 3927, "body_truncated": true}} +{"ts": "2026-03-30T17:30:36+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/19/occurrences?from=2026-05-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:30:36 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "892"}, "body": "{\"data\": [{\"event_id\": 19, \"uid\": \"smoke-cancelled-occurrence-001\", \"title\": \"Smoke Cancelled Occurrence\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-13T12:30:00+01:00\", \"occurrence_end\": \"2026-05-13T13:30:00+01:00\", \"repeat_type\": \"weekly\"}, {\"event_id\": 19, \"uid\": \"smoke-cancelled-occurrence-001\", \"title\": \"Smoke Cancelled Occurrence\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-20T12:30:00+01:00\", \"occurrence_end\": \"2026-05-20T13:30:00+01:00\", \"repeat_type\": \"weekly\"}, {\"event_id\": 19, \"uid\": \"smoke-cancelled-occurrence-001\", \"title\": \"Smoke Cancelled Occurrence\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-27T12:30:00+01:00\", \"occurrence_end\": \"2026-05-27T13:30:00+01:00\", \"repeat_type\": \"weekly\"}]}", "body_bytes": 892, "body_truncated": false}} +{"ts": "2026-03-30T17:30:36+00:00", "client": "127.0.0.1", "method": "GET", "path": "/calendar", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:30:36 GMT", "Content-Type": "text/html; charset=utf-8", "Content-Length": "43447"}, "body": "[omitted html payload: 3934 bytes]", "body_bytes": 3934, "body_truncated": true}} +{"ts": "2026-03-30T17:33:35+00:00", "client": "127.0.0.1", "method": "PROPFIND", "path": "/caldav/calendars/public/", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/xml; charset=utf-8", "Content-Length": "144", "Depth": "1", "Origin": "http://127.0.0.1:8080", "DNT": "1", "Authorization": "***", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "\n", "body_bytes": 144, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:33:35 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "6437"}, "body": "\n\n\n /caldav/calendars/public/\n \n \n Public Calendar\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/1.ics\n \n \n text/calendar; charset=utf-8\"465df57ed39797a5\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/2.ics\n \n \n text/calendar; charset=utf-8\"651ef0024e9c7082\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/3.ics\n \n \n ...(truncated)", "body_bytes": 3919, "body_truncated": true}} +{"ts": "2026-03-30T17:33:35+00:00", "client": "127.0.0.1", "method": "REPORT", "path": "/caldav/calendars/public/", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/xml; charset=utf-8", "Content-Length": "1191", "Depth": "1", "Origin": "http://127.0.0.1:8080", "DNT": "1", "Authorization": "***", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "\n/caldav/calendars/public/smoke-cancelled-occurrence.ics/caldav/calendars/public/smoke-exdate-import.ics/caldav/calendars/public/17.ics/caldav/calendars/public/16.ics/caldav/calendars/public/smoke-monthly-last.ics/caldav/calendars/public/smoke-monthly-ordinal.ics/caldav/calendars/public/smoke-monthly-nth.ics/caldav/calendars/public/smoke-vtimezone.ics/caldav/calendars/public/11.ics/caldav/calendars/public/10.ics/caldav/calendars/public/9.ics/caldav/calendars/public/8.ics/caldav/calendars/public/7.ics/caldav/calendars/public/6.ics/caldav/calendars/public/5.ics/caldav/calendars/public/4.ics/caldav/calendars/public/3.ics/caldav/calendars/public/2.ics/caldav/calendars/public/1.ics", "body_bytes": 1191, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:33:35 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "16545"}, "body": "\n\n\n /caldav/calendars/public/1.ics\n \n \n \"465df57ed39797a5\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-001@calendar-wp-plugin\r\nDTSTAMP:20260330T173335Z\r\nSUMMARY:Board Meeting\r\nDESCRIPTION:Quarterly board review.\r\nLOCATION:Room A\r\nCATEGORIES:Governance\r\nDTSTART;TZID=Europe/London:20260401T100000\r\nDTEND;TZID=Europe/London:20260401T113000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/2.ics\n \n \n \"651ef0024e9c7082\"\n ...(truncated)", "body_bytes": 3918, "body_truncated": true}} +{"ts": "2026-03-30T17:38:34+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:38:34 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "6585"}, "body": "{\"data\": [{\"id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"start_datetime\": \"2026-04-01T10:00:00+01:00\", \"end_datetime\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"f7ad3938cf996256\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T17:38:32+00:00\"}, {\"id\": 2, \"uid\": \"fixture-ce-002@calendar-wp-plugin\", \"title\": \"Office Closed\", \"description\": \"Public holiday closure.\", \"location\": \"HQ\", \"category\": \"Operations\", \"all_day_event\": true, \"start_datetime\": \"2026-05-04\", \"end_datetime\": \"2026-05-05\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"1c535", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T17:38:34+00:00", "client": "127.0.0.1", "method": "GET", "path": "/calendar.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:38:34 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"d8126b67df01f0e2\"", "Content-Length": "3132"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nCALSCALE:GREGORIAN\r\nX-WR-TIMEZONE:Europe/London\r\nBEGIN:VEVENT\r\nUID:fixture-ce-001@calendar-wp-plugin\r\nDTSTAMP:20260330T173834Z\r\nSUMMARY:Board Meeting\r\nDESCRIPTION:Quarterly board review.\r\nLOCATION:Room A\r\nCATEGORIES:Governance\r\nDTSTART;TZID=Europe/London:20260401T100000\r\nDTEND;TZID=Europe/London:20260401T113000\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-002@calendar-wp-plugin\r\nDTSTAMP:20260330T173834Z\r\nSUMMARY:Office Closed\r\nDESCRIPTION:Public holiday closure.\r\nLOCATION:HQ\r\nCATEGORIES:Operations\r\nDTSTART;VALUE=DATE:20260504\r\nDTEND;VALUE=DATE:20260505\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-003@calendar-wp-plugin\r\nDTSTAMP:20260330T173834Z\r\nSUMMARY:Daily Standup\r\nDESCRIPTION:15 minute sync.\r\nLOCATION:Online\r\nCATEGORIES:Team\r\nDTSTART;TZID=Europe/London:20260406T090000\r\nDTEND;TZID=Europe/London:20260406T091500\r\nRRULE:FREQ=DAILY;COUNT=10\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-004@calendar-wp-plugin\r\nDTSTAMP:20260330T173834Z\r\nSUMMARY:Community Lunch\r\nDESCRIPTION:Weekly community lunch.\r\nLOCATION:Cafeteria\r\nCATEGORIES:Community\r\nDTSTART;TZID=Europe/London:20260408T123000\r\nDTEND;TZID=Europe/London:20260408T133000\r\n ...(truncated)", "body_bytes": 3132, "body_truncated": false}} +{"ts": "2026-03-30T17:38:34+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/1.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:38:34 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"f7ad3938cf996256\"", "Content-Length": "364"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-001@calendar-wp-plugin\r\nDTSTAMP:20260330T173834Z\r\nSUMMARY:Board Meeting\r\nDESCRIPTION:Quarterly board review.\r\nLOCATION:Room A\r\nCATEGORIES:Governance\r\nDTSTART;TZID=Europe/London:20260401T100000\r\nDTEND;TZID=Europe/London:20260401T113000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 364, "body_truncated": false}} +{"ts": "2026-03-30T17:38:34+00:00", "client": "127.0.0.1", "method": "REPORT", "path": "/caldav/calendars/public/", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/xml; charset=utf-8", "Depth": "1", "Content-Length": "239"}, "body": "\n\n \n /caldav/calendars/public/4.ics\n\n", "body_bytes": 239, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:38:34 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "997"}, "body": "\n\n\n /caldav/calendars/public/4.ics\n \n \n \"4a24c041bfbc3ec2\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-004@calendar-wp-plugin\r\nDTSTAMP:20260330T173834Z\r\nSUMMARY:Community Lunch\r\nDESCRIPTION:Weekly community lunch.\r\nLOCATION:Cafeteria\r\nCATEGORIES:Community\r\nDTSTART;TZID=Europe/London:20260408T123000\r\nDTEND;TZID=Europe/London:20260408T133000\r\nRRULE:FREQ=WEEKLY\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n", "body_bytes": 997, "body_truncated": false}} +{"ts": "2026-03-30T17:38:34+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/10/occurrences/2026-04-17T14:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:38:34 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T17:38:34+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "X-WP-User": "admin", "Content-Length": "287"}, "body": "{\"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}", "body_bytes": 287, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:38:34 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "647"}, "body": "{\"data\": {\"id\": 11, \"uid\": \"3c9e7cfeb04a4a52d8d6@calendar-wp-plugin\", \"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_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"count\", \"repeat_count\": 3, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"ac348cbf9c8977b5\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T17:38:34+00:00\"}}", "body_bytes": 647, "body_truncated": false}} +{"ts": "2026-03-30T17:38:34+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/11/occurrences?from=2026-04-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:38:34 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "937"}, "body": "{\"data\": [{\"event_id\": 11, \"uid\": \"3c9e7cfeb04a4a52d8d6@calendar-wp-plugin\", \"title\": \"Smoke Occurrences API\", \"description\": \"occ-endpoint\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-01T10:00:00+01:00\", \"occurrence_end\": \"2026-04-01T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 11, \"uid\": \"3c9e7cfeb04a4a52d8d6@calendar-wp-plugin\", \"title\": \"Smoke Occurrences API\", \"description\": \"occ-endpoint\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-02T10:00:00+01:00\", \"occurrence_end\": \"2026-04-02T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 11, \"uid\": \"3c9e7cfeb04a4a52d8d6@calendar-wp-plugin\", \"title\": \"Smoke Occurrences API\", \"description\": \"occ-endpoint\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-03T10:00:00+01:00\", \"occurrence_end\": \"2026-04-03T11:00:00+01:00\", \"repeat_type\": \"daily\"}]}", "body_bytes": 937, "body_truncated": false}} +{"ts": "2026-03-30T17:38:34+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/11/occurrences/2026-04-02T10:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:38:34 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T17:38:34+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/11/occurrences/2026-04-02T10:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:38:34 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T17:38:34+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/11/occurrences?from=2026-04-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:38:34 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "628"}, "body": "{\"data\": [{\"event_id\": 11, \"uid\": \"3c9e7cfeb04a4a52d8d6@calendar-wp-plugin\", \"title\": \"Smoke Occurrences API\", \"description\": \"occ-endpoint\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-01T10:00:00+01:00\", \"occurrence_end\": \"2026-04-01T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 11, \"uid\": \"3c9e7cfeb04a4a52d8d6@calendar-wp-plugin\", \"title\": \"Smoke Occurrences API\", \"description\": \"occ-endpoint\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-03T10:00:00+01:00\", \"occurrence_end\": \"2026-04-03T11:00:00+01:00\", \"repeat_type\": \"daily\"}]}", "body_bytes": 628, "body_truncated": false}} +{"ts": "2026-03-30T17:38:34+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-vtimezone.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "454"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN\nVERSION:2.0\nBEGIN:VTIMEZONE\nTZID:Europe/London\nBEGIN:STANDARD\nDTSTART:18471201T000000\nRRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=9\nTZOFFSETFROM:+0115\nTZOFFSETTO:+0000\nTZNAME:GMT\nEND:STANDARD\nEND:VTIMEZONE\nBEGIN:VEVENT\nUID:smoke-vtimezone-parser-001\nSUMMARY:Smoke VTIMEZONE Parse\nDTSTART;TZID=Europe/London:20260423T150000\nDTEND;TZID=Europe/London:20260423T160000\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 454, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:38:34 GMT", "ETag": "\"1506481eda0c5763\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T17:38:34+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-vtimezone.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:38:34 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"1506481eda0c5763\"", "Content-Length": "288"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-vtimezone-parser-001\r\nDTSTAMP:20260330T173834Z\r\nSUMMARY:Smoke VTIMEZONE Parse\r\nDTSTART;TZID=Europe/London:20260423T150000\r\nDTEND;TZID=Europe/London:20260423T160000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 288, "body_truncated": false}} +{"ts": "2026-03-30T17:38:34+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-monthly-nth.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "261"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-monthly-nth-001\nSUMMARY:Smoke Monthly Nth\nDTSTART;TZID=Europe/London:20260402T150000\nDTEND;TZID=Europe/London:20260402T160000\nRRULE:FREQ=MONTHLY;BYDAY=SU;BYSETPOS=4\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 261, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:38:34 GMT", "ETag": "\"cb739417b1b3a094\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T17:38:34+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-monthly-nth.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:38:34 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"cb739417b1b3a094\"", "Content-Length": "319"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-nth-001\r\nDTSTAMP:20260330T173834Z\r\nSUMMARY:Smoke Monthly Nth\r\nDTSTART;TZID=Europe/London:20260426T150000\r\nDTEND;TZID=Europe/London:20260426T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SU;BYSETPOS=4\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 319, "body_truncated": false}} +{"ts": "2026-03-30T17:38:34+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-monthly-ordinal.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "259"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-monthly-ordinal-001\nSUMMARY:Smoke Monthly Ordinal\nDTSTART;TZID=Europe/London:20260411T150000\nDTEND;TZID=Europe/London:20260411T160000\nRRULE:FREQ=MONTHLY;BYDAY=2SA\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 259, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:38:34 GMT", "ETag": "\"44d87bb8dd04cf86\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T17:38:34+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-monthly-ordinal.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:38:34 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"44d87bb8dd04cf86\"", "Content-Length": "327"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-ordinal-001\r\nDTSTAMP:20260330T173834Z\r\nSUMMARY:Smoke Monthly Ordinal\r\nDTSTART;TZID=Europe/London:20260411T150000\r\nDTEND;TZID=Europe/London:20260411T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SA;BYSETPOS=2\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 327, "body_truncated": false}} +{"ts": "2026-03-30T17:38:35+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-monthly-last.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "264"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-monthly-last-001\nSUMMARY:Smoke Monthly Last\nDTSTART;TZID=Europe/London:20260425T150000\nDTEND;TZID=Europe/London:20260425T160000\nRRULE:FREQ=MONTHLY;BYDAY=SA;BYSETPOS=-1\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 264, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:38:35 GMT", "ETag": "\"9dd5cba7baa23fc5\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T17:38:35+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-monthly-last.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:38:35 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"9dd5cba7baa23fc5\"", "Content-Length": "322"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-last-001\r\nDTSTAMP:20260330T173835Z\r\nSUMMARY:Smoke Monthly Last\r\nDTSTART;TZID=Europe/London:20260425T150000\r\nDTEND;TZID=Europe/London:20260425T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SA;BYSETPOS=-1\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 322, "body_truncated": false}} +{"ts": "2026-03-30T17:38:35+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "X-WP-User": "admin", "Content-Length": "362"}, "body": "{\"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\"}", "body_bytes": 362, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:38:35 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "673"}, "body": "{\"data\": {\"id\": 16, \"uid\": \"d682d215052e032021f3@calendar-wp-plugin\", \"title\": \"Smoke 4th Sunday Anchor\", \"description\": \"anchor-normalization\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"start_datetime\": \"2026-05-24T15:00:00+01:00\", \"end_datetime\": \"2026-05-24T16:00:00+01:00\", \"repeat_type\": \"monthly\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"weekday_of_month\", \"repeat_nth_day\": null, \"repeat_nth_pos\": 4, \"repeat_nth_weekday\": 0, \"repeat_range_mode\": \"no_end\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"62e14734e3ca22de\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T17:38:35+00:00\"}}", "body_bytes": 673, "body_truncated": false}} +{"ts": "2026-03-30T17:38:35+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/16/occurrences/2026-05-24T15:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:38:35 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T17:38:35+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/16.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:38:35 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"694b221e5c8ab44a\"", "Content-Length": "420"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:d682d215052e032021f3@calendar-wp-plugin\r\nDTSTAMP:20260330T173835Z\r\nSUMMARY:Smoke 4th Sunday Anchor\r\nDESCRIPTION:anchor-normalization\r\nDTSTART;TZID=Europe/London:20260524T150000\r\nDTEND;TZID=Europe/London:20260524T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SU;BYSETPOS=4\r\nEXDATE;TZID=Europe/London:20260524T150000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 420, "body_truncated": false}} +{"ts": "2026-03-30T17:38:35+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "X-WP-User": "admin", "Content-Length": "295"}, "body": "{\"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\"}", "body_bytes": 295, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:38:35 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "655"}, "body": "{\"data\": {\"id\": 17, \"uid\": \"82cb03deb443fd591f6d@calendar-wp-plugin\", \"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_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"until\", \"repeat_count\": null, \"repeat_until\": \"2026-03-19\", \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"63022f41345a4800\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T17:38:35+00:00\"}}", "body_bytes": 655, "body_truncated": false}} +{"ts": "2026-03-30T17:38:35+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/17/occurrences/2026-03-11T11:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:38:35 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T17:38:35+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/17/occurrences?from=2026-03-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:38:35 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "5212"}, "body": "{\"data\": [{\"event_id\": 17, \"uid\": \"82cb03deb443fd591f6d@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-02T10:00:00+00:00\", \"occurrence_end\": \"2026-03-02T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"82cb03deb443fd591f6d@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-03T10:00:00+00:00\", \"occurrence_end\": \"2026-03-03T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"82cb03deb443fd591f6d@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-04T10:00:00+00:00\", \"occurrence_end\": \"2026-03-04T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"82cb03deb443fd591f6d@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-05T10:00:00+00:00\", \"occurrence_end\": \"2026-03-05T11:00:00", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T17:38:35+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/17/occurrences/2026-03-10", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:38:35 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T17:38:35+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/17/occurrences?from=2026-03-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:38:35 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "4906"}, "body": "{\"data\": [{\"event_id\": 17, \"uid\": \"82cb03deb443fd591f6d@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-02T10:00:00+00:00\", \"occurrence_end\": \"2026-03-02T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"82cb03deb443fd591f6d@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-03T10:00:00+00:00\", \"occurrence_end\": \"2026-03-03T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"82cb03deb443fd591f6d@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-04T10:00:00+00:00\", \"occurrence_end\": \"2026-03-04T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"82cb03deb443fd591f6d@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-05T10:00:00+00:00\", \"occurrence_end\": \"2026-03-05T11:00:00", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T17:38:35+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-exdate-import.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "323"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-exdate-import-001\nSUMMARY:Smoke EXDATE Import\nDTSTART;TZID=Europe/London:20260302T100000\nDTEND;TZID=Europe/London:20260302T110000\nRRULE:FREQ=DAILY;UNTIL=20260319T235959\nEXDATE;TZID=Europe/London:20260310T100000,20260311T100000\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 323, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:38:35 GMT", "ETag": "\"5bb55dd889f66154\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T17:38:35+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:38:35 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "11645"}, "body": "{\"data\": [{\"id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"start_datetime\": \"2026-04-01T10:00:00+01:00\", \"end_datetime\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"f7ad3938cf996256\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T17:38:32+00:00\"}, {\"id\": 2, \"uid\": \"fixture-ce-002@calendar-wp-plugin\", \"title\": \"Office Closed\", \"description\": \"Public holiday closure.\", \"location\": \"HQ\", \"category\": \"Operations\", \"all_day_event\": true, \"start_datetime\": \"2026-05-04\", \"end_datetime\": \"2026-05-05\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"1c535", "body_bytes": 3927, "body_truncated": true}} +{"ts": "2026-03-30T17:38:35+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/18/occurrences?from=2026-03-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:38:35 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "4474"}, "body": "{\"data\": [{\"event_id\": 18, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-02T10:00:00+01:00\", \"occurrence_end\": \"2026-03-02T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 18, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-03T10:00:00+01:00\", \"occurrence_end\": \"2026-03-03T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 18, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-04T10:00:00+01:00\", \"occurrence_end\": \"2026-03-04T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 18, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-05T10:00:00+01:00\", \"occurrence_end\": \"2026-03-05T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 18, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE ", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T17:38:35+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-cancelled-occurrence.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "467"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-cancelled-occurrence-001\nSUMMARY:Smoke Cancelled Occurrence\nDTSTART;TZID=Europe/London:20260408T123000\nDTEND;TZID=Europe/London:20260408T133000\nRRULE:FREQ=WEEKLY\nEND:VEVENT\nBEGIN:VEVENT\nUID:smoke-cancelled-occurrence-001\nRECURRENCE-ID;TZID=Europe/London:20260506T123000\nDTSTART;TZID=Europe/London:20260506T123000\nDTEND;TZID=Europe/London:20260506T133000\nSTATUS:CANCELLED\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 467, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:38:35 GMT", "ETag": "\"05afd85905bfe8d9\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T17:38:35+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-cancelled-occurrence.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:38:35 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"05afd85905bfe8d9\"", "Content-Length": "359"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-cancelled-occurrence-001\r\nDTSTAMP:20260330T173835Z\r\nSUMMARY:Smoke Cancelled Occurrence\r\nDTSTART;TZID=Europe/London:20260408T123000\r\nDTEND;TZID=Europe/London:20260408T133000\r\nRRULE:FREQ=WEEKLY\r\nEXDATE;TZID=Europe/London:20260506T123000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 359, "body_truncated": false}} +{"ts": "2026-03-30T17:38:35+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:38:35 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "12273"}, "body": "{\"data\": [{\"id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"start_datetime\": \"2026-04-01T10:00:00+01:00\", \"end_datetime\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"f7ad3938cf996256\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T17:38:32+00:00\"}, {\"id\": 2, \"uid\": \"fixture-ce-002@calendar-wp-plugin\", \"title\": \"Office Closed\", \"description\": \"Public holiday closure.\", \"location\": \"HQ\", \"category\": \"Operations\", \"all_day_event\": true, \"start_datetime\": \"2026-05-04\", \"end_datetime\": \"2026-05-05\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"1c535", "body_bytes": 3927, "body_truncated": true}} +{"ts": "2026-03-30T17:38:35+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/19/occurrences?from=2026-05-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:38:35 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "892"}, "body": "{\"data\": [{\"event_id\": 19, \"uid\": \"smoke-cancelled-occurrence-001\", \"title\": \"Smoke Cancelled Occurrence\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-13T12:30:00+01:00\", \"occurrence_end\": \"2026-05-13T13:30:00+01:00\", \"repeat_type\": \"weekly\"}, {\"event_id\": 19, \"uid\": \"smoke-cancelled-occurrence-001\", \"title\": \"Smoke Cancelled Occurrence\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-20T12:30:00+01:00\", \"occurrence_end\": \"2026-05-20T13:30:00+01:00\", \"repeat_type\": \"weekly\"}, {\"event_id\": 19, \"uid\": \"smoke-cancelled-occurrence-001\", \"title\": \"Smoke Cancelled Occurrence\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-27T12:30:00+01:00\", \"occurrence_end\": \"2026-05-27T13:30:00+01:00\", \"repeat_type\": \"weekly\"}]}", "body_bytes": 892, "body_truncated": false}} +{"ts": "2026-03-30T17:38:35+00:00", "client": "127.0.0.1", "method": "GET", "path": "/calendar", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:38:35 GMT", "Content-Type": "text/html; charset=utf-8", "Content-Length": "43447"}, "body": "[omitted html payload: 3934 bytes]", "body_bytes": 3934, "body_truncated": true}} +{"ts": "2026-03-30T17:40:12+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:40:12 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "6585"}, "body": "{\"data\": [{\"id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"start_datetime\": \"2026-04-01T10:00:00+01:00\", \"end_datetime\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"eb53d018557c8fca\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T17:40:10+00:00\"}, {\"id\": 2, \"uid\": \"fixture-ce-002@calendar-wp-plugin\", \"title\": \"Office Closed\", \"description\": \"Public holiday closure.\", \"location\": \"HQ\", \"category\": \"Operations\", \"all_day_event\": true, \"start_datetime\": \"2026-05-04\", \"end_datetime\": \"2026-05-05\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"190a4", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T17:40:12+00:00", "client": "127.0.0.1", "method": "GET", "path": "/calendar.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:40:12 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"509f2bd7d2009ce8\"", "Content-Length": "3132"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nCALSCALE:GREGORIAN\r\nX-WR-TIMEZONE:Europe/London\r\nBEGIN:VEVENT\r\nUID:fixture-ce-001@calendar-wp-plugin\r\nDTSTAMP:20260330T174012Z\r\nSUMMARY:Board Meeting\r\nDESCRIPTION:Quarterly board review.\r\nLOCATION:Room A\r\nCATEGORIES:Governance\r\nDTSTART;TZID=Europe/London:20260401T100000\r\nDTEND;TZID=Europe/London:20260401T113000\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-002@calendar-wp-plugin\r\nDTSTAMP:20260330T174012Z\r\nSUMMARY:Office Closed\r\nDESCRIPTION:Public holiday closure.\r\nLOCATION:HQ\r\nCATEGORIES:Operations\r\nDTSTART;VALUE=DATE:20260504\r\nDTEND;VALUE=DATE:20260505\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-003@calendar-wp-plugin\r\nDTSTAMP:20260330T174012Z\r\nSUMMARY:Daily Standup\r\nDESCRIPTION:15 minute sync.\r\nLOCATION:Online\r\nCATEGORIES:Team\r\nDTSTART;TZID=Europe/London:20260406T090000\r\nDTEND;TZID=Europe/London:20260406T091500\r\nRRULE:FREQ=DAILY;COUNT=10\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-004@calendar-wp-plugin\r\nDTSTAMP:20260330T174012Z\r\nSUMMARY:Community Lunch\r\nDESCRIPTION:Weekly community lunch.\r\nLOCATION:Cafeteria\r\nCATEGORIES:Community\r\nDTSTART;TZID=Europe/London:20260408T123000\r\nDTEND;TZID=Europe/London:20260408T133000\r\n ...(truncated)", "body_bytes": 3132, "body_truncated": false}} +{"ts": "2026-03-30T17:40:12+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/1.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:40:12 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"eb53d018557c8fca\"", "Content-Length": "364"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-001@calendar-wp-plugin\r\nDTSTAMP:20260330T174012Z\r\nSUMMARY:Board Meeting\r\nDESCRIPTION:Quarterly board review.\r\nLOCATION:Room A\r\nCATEGORIES:Governance\r\nDTSTART;TZID=Europe/London:20260401T100000\r\nDTEND;TZID=Europe/London:20260401T113000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 364, "body_truncated": false}} +{"ts": "2026-03-30T17:40:12+00:00", "client": "127.0.0.1", "method": "REPORT", "path": "/caldav/calendars/public/", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/xml; charset=utf-8", "Depth": "1", "Content-Length": "239"}, "body": "\n\n \n /caldav/calendars/public/4.ics\n\n", "body_bytes": 239, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:40:12 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "997"}, "body": "\n\n\n /caldav/calendars/public/4.ics\n \n \n \"691a56c3c4084218\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-004@calendar-wp-plugin\r\nDTSTAMP:20260330T174012Z\r\nSUMMARY:Community Lunch\r\nDESCRIPTION:Weekly community lunch.\r\nLOCATION:Cafeteria\r\nCATEGORIES:Community\r\nDTSTART;TZID=Europe/London:20260408T123000\r\nDTEND;TZID=Europe/London:20260408T133000\r\nRRULE:FREQ=WEEKLY\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n", "body_bytes": 997, "body_truncated": false}} +{"ts": "2026-03-30T17:40:12+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/10/occurrences/2026-04-17T14:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:40:12 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T17:40:12+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "X-WP-User": "admin", "Content-Length": "287"}, "body": "{\"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}", "body_bytes": 287, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:40:12 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "647"}, "body": "{\"data\": {\"id\": 11, \"uid\": \"2ccb5f2076be62c35936@calendar-wp-plugin\", \"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_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"count\", \"repeat_count\": 3, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"e6c9698e1085c76b\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T17:40:12+00:00\"}}", "body_bytes": 647, "body_truncated": false}} +{"ts": "2026-03-30T17:40:12+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/11/occurrences?from=2026-04-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:40:12 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "937"}, "body": "{\"data\": [{\"event_id\": 11, \"uid\": \"2ccb5f2076be62c35936@calendar-wp-plugin\", \"title\": \"Smoke Occurrences API\", \"description\": \"occ-endpoint\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-01T10:00:00+01:00\", \"occurrence_end\": \"2026-04-01T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 11, \"uid\": \"2ccb5f2076be62c35936@calendar-wp-plugin\", \"title\": \"Smoke Occurrences API\", \"description\": \"occ-endpoint\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-02T10:00:00+01:00\", \"occurrence_end\": \"2026-04-02T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 11, \"uid\": \"2ccb5f2076be62c35936@calendar-wp-plugin\", \"title\": \"Smoke Occurrences API\", \"description\": \"occ-endpoint\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-03T10:00:00+01:00\", \"occurrence_end\": \"2026-04-03T11:00:00+01:00\", \"repeat_type\": \"daily\"}]}", "body_bytes": 937, "body_truncated": false}} +{"ts": "2026-03-30T17:40:12+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/11/occurrences/2026-04-02T10:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:40:12 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T17:40:12+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/11/occurrences/2026-04-02T10:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:40:12 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T17:40:12+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/11/occurrences?from=2026-04-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:40:12 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "628"}, "body": "{\"data\": [{\"event_id\": 11, \"uid\": \"2ccb5f2076be62c35936@calendar-wp-plugin\", \"title\": \"Smoke Occurrences API\", \"description\": \"occ-endpoint\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-01T10:00:00+01:00\", \"occurrence_end\": \"2026-04-01T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 11, \"uid\": \"2ccb5f2076be62c35936@calendar-wp-plugin\", \"title\": \"Smoke Occurrences API\", \"description\": \"occ-endpoint\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-03T10:00:00+01:00\", \"occurrence_end\": \"2026-04-03T11:00:00+01:00\", \"repeat_type\": \"daily\"}]}", "body_bytes": 628, "body_truncated": false}} +{"ts": "2026-03-30T17:40:12+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-vtimezone.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "454"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN\nVERSION:2.0\nBEGIN:VTIMEZONE\nTZID:Europe/London\nBEGIN:STANDARD\nDTSTART:18471201T000000\nRRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=9\nTZOFFSETFROM:+0115\nTZOFFSETTO:+0000\nTZNAME:GMT\nEND:STANDARD\nEND:VTIMEZONE\nBEGIN:VEVENT\nUID:smoke-vtimezone-parser-001\nSUMMARY:Smoke VTIMEZONE Parse\nDTSTART;TZID=Europe/London:20260423T150000\nDTEND;TZID=Europe/London:20260423T160000\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 454, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:40:12 GMT", "ETag": "\"ff359fb275c089c9\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T17:40:12+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-vtimezone.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:40:12 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"ff359fb275c089c9\"", "Content-Length": "288"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-vtimezone-parser-001\r\nDTSTAMP:20260330T174012Z\r\nSUMMARY:Smoke VTIMEZONE Parse\r\nDTSTART;TZID=Europe/London:20260423T150000\r\nDTEND;TZID=Europe/London:20260423T160000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 288, "body_truncated": false}} +{"ts": "2026-03-30T17:40:12+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-monthly-nth.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "261"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-monthly-nth-001\nSUMMARY:Smoke Monthly Nth\nDTSTART;TZID=Europe/London:20260402T150000\nDTEND;TZID=Europe/London:20260402T160000\nRRULE:FREQ=MONTHLY;BYDAY=SU;BYSETPOS=4\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 261, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:40:12 GMT", "ETag": "\"2764dba1363599bf\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T17:40:12+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-monthly-nth.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:40:12 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"2764dba1363599bf\"", "Content-Length": "319"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-nth-001\r\nDTSTAMP:20260330T174012Z\r\nSUMMARY:Smoke Monthly Nth\r\nDTSTART;TZID=Europe/London:20260426T150000\r\nDTEND;TZID=Europe/London:20260426T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SU;BYSETPOS=4\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 319, "body_truncated": false}} +{"ts": "2026-03-30T17:40:12+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-monthly-ordinal.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "259"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-monthly-ordinal-001\nSUMMARY:Smoke Monthly Ordinal\nDTSTART;TZID=Europe/London:20260411T150000\nDTEND;TZID=Europe/London:20260411T160000\nRRULE:FREQ=MONTHLY;BYDAY=2SA\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 259, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:40:12 GMT", "ETag": "\"dca2e1bdfd4a6703\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T17:40:12+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-monthly-ordinal.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:40:12 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"dca2e1bdfd4a6703\"", "Content-Length": "327"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-ordinal-001\r\nDTSTAMP:20260330T174012Z\r\nSUMMARY:Smoke Monthly Ordinal\r\nDTSTART;TZID=Europe/London:20260411T150000\r\nDTEND;TZID=Europe/London:20260411T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SA;BYSETPOS=2\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 327, "body_truncated": false}} +{"ts": "2026-03-30T17:40:12+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-monthly-last.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "264"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-monthly-last-001\nSUMMARY:Smoke Monthly Last\nDTSTART;TZID=Europe/London:20260425T150000\nDTEND;TZID=Europe/London:20260425T160000\nRRULE:FREQ=MONTHLY;BYDAY=SA;BYSETPOS=-1\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 264, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:40:12 GMT", "ETag": "\"f7121f9cf89d5d41\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T17:40:12+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-monthly-last.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:40:12 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"f7121f9cf89d5d41\"", "Content-Length": "322"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-last-001\r\nDTSTAMP:20260330T174012Z\r\nSUMMARY:Smoke Monthly Last\r\nDTSTART;TZID=Europe/London:20260425T150000\r\nDTEND;TZID=Europe/London:20260425T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SA;BYSETPOS=-1\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 322, "body_truncated": false}} +{"ts": "2026-03-30T17:40:12+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "X-WP-User": "admin", "Content-Length": "362"}, "body": "{\"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\"}", "body_bytes": 362, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:40:12 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "673"}, "body": "{\"data\": {\"id\": 16, \"uid\": \"bafafa747a751cf525e1@calendar-wp-plugin\", \"title\": \"Smoke 4th Sunday Anchor\", \"description\": \"anchor-normalization\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"start_datetime\": \"2026-05-24T15:00:00+01:00\", \"end_datetime\": \"2026-05-24T16:00:00+01:00\", \"repeat_type\": \"monthly\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"weekday_of_month\", \"repeat_nth_day\": null, \"repeat_nth_pos\": 4, \"repeat_nth_weekday\": 0, \"repeat_range_mode\": \"no_end\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"8f8aa2c3e6011a47\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T17:40:12+00:00\"}}", "body_bytes": 673, "body_truncated": false}} +{"ts": "2026-03-30T17:40:12+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/16/occurrences/2026-05-24T15:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:40:12 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T17:40:12+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/16.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:40:12 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"57d6b2e2283725e0\"", "Content-Length": "420"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:bafafa747a751cf525e1@calendar-wp-plugin\r\nDTSTAMP:20260330T174012Z\r\nSUMMARY:Smoke 4th Sunday Anchor\r\nDESCRIPTION:anchor-normalization\r\nDTSTART;TZID=Europe/London:20260524T150000\r\nDTEND;TZID=Europe/London:20260524T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SU;BYSETPOS=4\r\nEXDATE;TZID=Europe/London:20260524T150000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 420, "body_truncated": false}} +{"ts": "2026-03-30T17:40:12+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "X-WP-User": "admin", "Content-Length": "295"}, "body": "{\"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\"}", "body_bytes": 295, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:40:12 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "655"}, "body": "{\"data\": {\"id\": 17, \"uid\": \"5b12ff0070324c83a9d7@calendar-wp-plugin\", \"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_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"until\", \"repeat_count\": null, \"repeat_until\": \"2026-03-19\", \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"38b30150be27a031\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T17:40:12+00:00\"}}", "body_bytes": 655, "body_truncated": false}} +{"ts": "2026-03-30T17:40:12+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/17/occurrences/2026-03-11T11:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:40:12 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T17:40:12+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/17/occurrences?from=2026-03-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:40:12 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "5212"}, "body": "{\"data\": [{\"event_id\": 17, \"uid\": \"5b12ff0070324c83a9d7@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-02T10:00:00+00:00\", \"occurrence_end\": \"2026-03-02T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"5b12ff0070324c83a9d7@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-03T10:00:00+00:00\", \"occurrence_end\": \"2026-03-03T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"5b12ff0070324c83a9d7@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-04T10:00:00+00:00\", \"occurrence_end\": \"2026-03-04T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"5b12ff0070324c83a9d7@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-05T10:00:00+00:00\", \"occurrence_end\": \"2026-03-05T11:00:00", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T17:40:12+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/17/occurrences/2026-03-10", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:40:12 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T17:40:12+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/17/occurrences?from=2026-03-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:40:12 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "4906"}, "body": "{\"data\": [{\"event_id\": 17, \"uid\": \"5b12ff0070324c83a9d7@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-02T10:00:00+00:00\", \"occurrence_end\": \"2026-03-02T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"5b12ff0070324c83a9d7@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-03T10:00:00+00:00\", \"occurrence_end\": \"2026-03-03T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"5b12ff0070324c83a9d7@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-04T10:00:00+00:00\", \"occurrence_end\": \"2026-03-04T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"5b12ff0070324c83a9d7@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-05T10:00:00+00:00\", \"occurrence_end\": \"2026-03-05T11:00:00", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T17:40:12+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-exdate-import.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "323"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-exdate-import-001\nSUMMARY:Smoke EXDATE Import\nDTSTART;TZID=Europe/London:20260302T100000\nDTEND;TZID=Europe/London:20260302T110000\nRRULE:FREQ=DAILY;UNTIL=20260319T235959\nEXDATE;TZID=Europe/London:20260310T100000,20260311T100000\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 323, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:40:12 GMT", "ETag": "\"5409204c5488ce37\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T17:40:12+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:40:12 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "11645"}, "body": "{\"data\": [{\"id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"start_datetime\": \"2026-04-01T10:00:00+01:00\", \"end_datetime\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"eb53d018557c8fca\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T17:40:10+00:00\"}, {\"id\": 2, \"uid\": \"fixture-ce-002@calendar-wp-plugin\", \"title\": \"Office Closed\", \"description\": \"Public holiday closure.\", \"location\": \"HQ\", \"category\": \"Operations\", \"all_day_event\": true, \"start_datetime\": \"2026-05-04\", \"end_datetime\": \"2026-05-05\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"190a4", "body_bytes": 3927, "body_truncated": true}} +{"ts": "2026-03-30T17:40:12+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/18/occurrences?from=2026-03-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:40:12 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "4474"}, "body": "{\"data\": [{\"event_id\": 18, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-02T10:00:00+01:00\", \"occurrence_end\": \"2026-03-02T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 18, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-03T10:00:00+01:00\", \"occurrence_end\": \"2026-03-03T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 18, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-04T10:00:00+01:00\", \"occurrence_end\": \"2026-03-04T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 18, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-05T10:00:00+01:00\", \"occurrence_end\": \"2026-03-05T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 18, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE ", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T17:40:13+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-cancelled-occurrence.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "467"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-cancelled-occurrence-001\nSUMMARY:Smoke Cancelled Occurrence\nDTSTART;TZID=Europe/London:20260408T123000\nDTEND;TZID=Europe/London:20260408T133000\nRRULE:FREQ=WEEKLY\nEND:VEVENT\nBEGIN:VEVENT\nUID:smoke-cancelled-occurrence-001\nRECURRENCE-ID;TZID=Europe/London:20260506T123000\nDTSTART;TZID=Europe/London:20260506T123000\nDTEND;TZID=Europe/London:20260506T133000\nSTATUS:CANCELLED\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 467, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:40:13 GMT", "ETag": "\"f3393675b839a951\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T17:40:13+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-cancelled-occurrence.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:40:13 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"f3393675b839a951\"", "Content-Length": "359"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-cancelled-occurrence-001\r\nDTSTAMP:20260330T174013Z\r\nSUMMARY:Smoke Cancelled Occurrence\r\nDTSTART;TZID=Europe/London:20260408T123000\r\nDTEND;TZID=Europe/London:20260408T133000\r\nRRULE:FREQ=WEEKLY\r\nEXDATE;TZID=Europe/London:20260506T123000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 359, "body_truncated": false}} +{"ts": "2026-03-30T17:40:13+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:40:13 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "12273"}, "body": "{\"data\": [{\"id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"start_datetime\": \"2026-04-01T10:00:00+01:00\", \"end_datetime\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"eb53d018557c8fca\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T17:40:10+00:00\"}, {\"id\": 2, \"uid\": \"fixture-ce-002@calendar-wp-plugin\", \"title\": \"Office Closed\", \"description\": \"Public holiday closure.\", \"location\": \"HQ\", \"category\": \"Operations\", \"all_day_event\": true, \"start_datetime\": \"2026-05-04\", \"end_datetime\": \"2026-05-05\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"190a4", "body_bytes": 3927, "body_truncated": true}} +{"ts": "2026-03-30T17:40:13+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/19/occurrences?from=2026-05-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:40:13 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "892"}, "body": "{\"data\": [{\"event_id\": 19, \"uid\": \"smoke-cancelled-occurrence-001\", \"title\": \"Smoke Cancelled Occurrence\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-13T12:30:00+01:00\", \"occurrence_end\": \"2026-05-13T13:30:00+01:00\", \"repeat_type\": \"weekly\"}, {\"event_id\": 19, \"uid\": \"smoke-cancelled-occurrence-001\", \"title\": \"Smoke Cancelled Occurrence\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-20T12:30:00+01:00\", \"occurrence_end\": \"2026-05-20T13:30:00+01:00\", \"repeat_type\": \"weekly\"}, {\"event_id\": 19, \"uid\": \"smoke-cancelled-occurrence-001\", \"title\": \"Smoke Cancelled Occurrence\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-27T12:30:00+01:00\", \"occurrence_end\": \"2026-05-27T13:30:00+01:00\", \"repeat_type\": \"weekly\"}]}", "body_bytes": 892, "body_truncated": false}} +{"ts": "2026-03-30T17:40:13+00:00", "client": "127.0.0.1", "method": "GET", "path": "/calendar", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:40:13 GMT", "Content-Type": "text/html; charset=utf-8", "Content-Length": "43447"}, "body": "[omitted html payload: 3934 bytes]", "body_bytes": 3934, "body_truncated": true}} +{"ts": "2026-03-30T17:40:13+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:40:13 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "12273"}, "body": "{\"data\": [{\"id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"start_datetime\": \"2026-04-01T10:00:00+01:00\", \"end_datetime\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"eb53d018557c8fca\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T17:40:10+00:00\"}, {\"id\": 2, \"uid\": \"fixture-ce-002@calendar-wp-plugin\", \"title\": \"Office Closed\", \"description\": \"Public holiday closure.\", \"location\": \"HQ\", \"category\": \"Operations\", \"all_day_event\": true, \"start_datetime\": \"2026-05-04\", \"end_datetime\": \"2026-05-05\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"190a4", "body_bytes": 3927, "body_truncated": true}} +{"ts": "2026-03-30T17:40:13+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/users/register", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "Content-Length": "66"}, "body": "{\"email\": \"sec-nodisclose@example.test\", \"password\": \"***\"}", "body_bytes": 66, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:40:13 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "98"}, "body": "{\"data\": {\"user_id\": 4, \"status\": \"pending_approval\", \"email_status\": \"sent\", \"email_sent\": true}}", "body_bytes": 98, "body_truncated": false}} +{"ts": "2026-03-30T17:40:14+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/users/forgot-password", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "Content-Length": "39"}, "body": "{\"email\": \"adrians@chezstephens.org.uk\"}", "body_bytes": 39, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:40:14 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "70"}, "body": "{\"data\": {\"status\": \"ok\", \"email_status\": \"sent\", \"email_sent\": true}}", "body_bytes": 70, "body_truncated": false}} +{"ts": "2026-03-30T17:40:14+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/users/register", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "Content-Length": "62"}, "body": "{\"email\": \"sec-rate-1@example.test\", \"password\": \"***\"}", "body_bytes": 62, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:40:14 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "98"}, "body": "{\"data\": {\"user_id\": 5, \"status\": \"pending_approval\", \"email_status\": \"sent\", \"email_sent\": true}}", "body_bytes": 98, "body_truncated": false}} +{"ts": "2026-03-30T17:40:14+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/users/register", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "Content-Length": "62"}, "body": "{\"email\": \"sec-rate-2@example.test\", \"password\": \"***\"}", "body_bytes": 62, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:40:14 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "98"}, "body": "{\"data\": {\"user_id\": 6, \"status\": \"pending_approval\", \"email_status\": \"sent\", \"email_sent\": true}}", "body_bytes": 98, "body_truncated": false}} +{"ts": "2026-03-30T17:40:14+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/users/register", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "Content-Length": "62"}, "body": "{\"email\": \"sec-rate-3@example.test\", \"password\": \"***\"}", "body_bytes": 62, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:40:14 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "98"}, "body": "{\"data\": {\"user_id\": 7, \"status\": \"pending_approval\", \"email_status\": \"sent\", \"email_sent\": true}}", "body_bytes": 98, "body_truncated": false}} +{"ts": "2026-03-30T17:40:14+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/users/register", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "Content-Length": "62"}, "body": "{\"email\": \"sec-rate-4@example.test\", \"password\": \"***\"}", "body_bytes": 62, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:40:14 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "98"}, "body": "{\"data\": {\"user_id\": 8, \"status\": \"pending_approval\", \"email_status\": \"sent\", \"email_sent\": true}}", "body_bytes": 98, "body_truncated": false}} +{"ts": "2026-03-30T17:40:15+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/users/register", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "Content-Length": "62"}, "body": "{\"email\": \"sec-rate-5@example.test\", \"password\": \"***\"}", "body_bytes": 62, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:40:15 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "98"}, "body": "{\"data\": {\"user_id\": 9, \"status\": \"pending_approval\", \"email_status\": \"sent\", \"email_sent\": true}}", "body_bytes": 98, "body_truncated": false}} +{"ts": "2026-03-30T17:40:15+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/users/register", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "Content-Length": "62"}, "body": "{\"email\": \"sec-rate-6@example.test\", \"password\": \"***\"}", "body_bytes": 62, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:40:15 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "99"}, "body": "{\"data\": {\"user_id\": 10, \"status\": \"pending_approval\", \"email_status\": \"sent\", \"email_sent\": true}}", "body_bytes": 99, "body_truncated": false}} +{"ts": "2026-03-30T17:40:15+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/users/register", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "Content-Length": "62"}, "body": "{\"email\": \"sec-rate-7@example.test\", \"password\": \"***\"}", "body_bytes": 62, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:40:15 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "99"}, "body": "{\"data\": {\"user_id\": 11, \"status\": \"pending_approval\", \"email_status\": \"sent\", \"email_sent\": true}}", "body_bytes": 99, "body_truncated": false}} +{"ts": "2026-03-30T17:40:15+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/users/register", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "Content-Length": "62"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 429, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:40:15 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "98"}, "body": "{\"error\": {\"code\": \"rate_limited\", \"message\": \"Too many registration attempts. Try again later.\"}}", "body_bytes": 98, "body_truncated": false}} +{"ts": "2026-03-30T17:40:15+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/sec-1774892415.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Length": "201", "Content-Type": "application/x-www-form-urlencoded"}, "body": "[omitted non-text payload: 201 bytes, content-type=application/x-www-form-urlencoded]", "body_bytes": 201, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:40:15 GMT", "ETag": "\"3cc5e1cc73127fbf\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T17:40:15+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/sec-1774892415.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:40:15 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"3cc5e1cc73127fbf\"", "Content-Length": "287"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:sec-smoke-uid@example.test\r\nDTSTAMP:20260330T174015Z\r\nSUMMARY:Security Smoke Event\r\nDTSTART;TZID=Europe/London:20260415T100000\r\nDTEND;TZID=Europe/London:20260415T110000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 287, "body_truncated": false}} +{"ts": "2026-03-30T17:40:15+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/999999.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 404, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 17:40:15 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "62"}, "body": "{\"error\": {\"code\": \"not_found\", \"message\": \"Route not found\"}}", "body_bytes": 62, "body_truncated": false}} +{"ts": "2026-03-30T18:03:41+00:00", "client": "127.0.0.1", "method": "PROPFIND", "path": "/caldav/calendars/public/", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/xml; charset=utf-8", "Content-Length": "144", "Depth": "1", "Origin": "http://127.0.0.1:8080", "DNT": "1", "Authorization": "***", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "\n", "body_bytes": 144, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 18:03:41 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "6756"}, "body": "\n\n\n /caldav/calendars/public/\n \n \n Public Calendar\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/1.ics\n \n \n text/calendar; charset=utf-8\"eb53d018557c8fca\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/2.ics\n \n \n text/calendar; charset=utf-8\"190a40db82a13a9b\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/3.ics\n \n \n ...(truncated)", "body_bytes": 3919, "body_truncated": true}} +{"ts": "2026-03-30T18:03:41+00:00", "client": "127.0.0.1", "method": "REPORT", "path": "/caldav/calendars/public/", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/xml; charset=utf-8", "Content-Length": "1251", "Depth": "1", "Origin": "http://127.0.0.1:8080", "DNT": "1", "Authorization": "***", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "\n/caldav/calendars/public/sec-1774892415.ics/caldav/calendars/public/smoke-cancelled-occurrence.ics/caldav/calendars/public/smoke-exdate-import.ics/caldav/calendars/public/17.ics/caldav/calendars/public/16.ics/caldav/calendars/public/smoke-monthly-last.ics/caldav/calendars/public/smoke-monthly-ordinal.ics/caldav/calendars/public/smoke-monthly-nth.ics/caldav/calendars/public/smoke-vtimezone.ics/caldav/calendars/public/11.ics/caldav/calendars/public/10.ics/caldav/calendars/public/9.ics/caldav/calendars/public/8.ics/caldav/calendars/public/7.ics/caldav/calendars/public/6.ics/caldav/calendars/public/5.ics/caldav/calendars/public/4.ics/caldav/calendars/public/3.ics/caldav/calendars/public/2.ics/caldav/ca ...(truncated)", "body_bytes": 1251, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 18:03:41 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "17327"}, "body": "\n\n\n /caldav/calendars/public/1.ics\n \n \n \"eb53d018557c8fca\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-001@calendar-wp-plugin\r\nDTSTAMP:20260330T180341Z\r\nSUMMARY:Board Meeting\r\nDESCRIPTION:Quarterly board review.\r\nLOCATION:Room A\r\nCATEGORIES:Governance\r\nDTSTART;TZID=Europe/London:20260401T100000\r\nDTEND;TZID=Europe/London:20260401T113000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/2.ics\n \n \n \"190a40db82a13a9b\"\n ...(truncated)", "body_bytes": 3918, "body_truncated": true}} +{"ts": "2026-03-30T18:07:05+00:00", "client": "127.0.0.1", "method": "GET", "path": "/calendar", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:148.0) Gecko/20100101 Firefox/148.0", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", "Accept-Language": "en-GB,en;q=0.9", "Accept-Encoding": "gzip, deflate, br, zstd", "Referer": "http://localhost:8080/wp-admin/admin.php?page=calendar-users&as=admin", "DNT": "1", "Sec-GPC": "1", "Connection": "keep-alive", "Upgrade-Insecure-Requests": "1", "Sec-Fetch-Dest": "document", "Sec-Fetch-Mode": "navigate", "Sec-Fetch-Site": "same-origin", "Sec-Fetch-User": "?1", "Priority": "u=0, i"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 18:07:05 GMT", "Content-Type": "text/html; charset=utf-8", "Content-Length": "43447"}, "body": "[omitted html payload: 3934 bytes]", "body_bytes": 3934, "body_truncated": true}} +{"ts": "2026-03-30T18:07:05+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/public/events?view=month&date=2026-03-30", "request": {"headers": {"Host": "localhost:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:148.0) Gecko/20100101 Firefox/148.0", "Accept": "*/*", "Accept-Language": "en-GB,en;q=0.9", "Accept-Encoding": "gzip, deflate, br, zstd", "Referer": "http://localhost:8080/calendar", "DNT": "1", "Sec-GPC": "1", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 18:07:05 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "9410"}, "body": "{\"data\": [{\"event_id\": 17, \"uid\": \"5b12ff0070324c83a9d7@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-02T10:00:00+00:00\", \"occurrence_end\": \"2026-03-02T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 18, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-02T10:00:00+01:00\", \"occurrence_end\": \"2026-03-02T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"uid\": \"5b12ff0070324c83a9d7@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-03T10:00:00+00:00\", \"occurrence_end\": \"2026-03-03T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 18, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-03T10:00:00+01:00\", \"occurrence_end\": \"2026-03-03T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 17, \"ui", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T18:33:43+00:00", "client": "127.0.0.1", "method": "PROPFIND", "path": "/caldav/calendars/public/", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/xml; charset=utf-8", "Content-Length": "144", "Depth": "1", "Origin": "http://127.0.0.1:8080", "DNT": "1", "Authorization": "***", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "\n", "body_bytes": 144, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 18:33:43 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "6756"}, "body": "\n\n\n /caldav/calendars/public/\n \n \n Public Calendar\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/1.ics\n \n \n text/calendar; charset=utf-8\"eb53d018557c8fca\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/2.ics\n \n \n text/calendar; charset=utf-8\"190a40db82a13a9b\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/3.ics\n \n \n ...(truncated)", "body_bytes": 3919, "body_truncated": true}} +{"ts": "2026-03-30T18:33:43+00:00", "client": "127.0.0.1", "method": "REPORT", "path": "/caldav/calendars/public/", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/xml; charset=utf-8", "Content-Length": "328", "Depth": "1", "Origin": "http://127.0.0.1:8080", "DNT": "1", "Authorization": "***", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "\n/caldav/calendars/public/17.ics/caldav/calendars/public/16.ics/caldav/calendars/public/11.ics", "body_bytes": 328, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 18:33:43 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "2816"}, "body": "\n\n\n /caldav/calendars/public/11.ics\n \n \n \"1dc48ba9c8e4c1e0\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:2ccb5f2076be62c35936@calendar-wp-plugin\r\nDTSTAMP:20260330T183343Z\r\nSUMMARY:Smoke Occurrences API\r\nDESCRIPTION:occ-endpoint\r\nDTSTART;TZID=Europe/London:20260401T100000\r\nDTEND;TZID=Europe/London:20260401T110000\r\nRRULE:FREQ=DAILY;COUNT=3\r\nEXDATE;TZID=Europe/London:20260402T100000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/16.ics\n \n \n \"57d6 ...(truncated)", "body_bytes": 2816, "body_truncated": false}} +{"ts": "2026-03-30T18:58:19+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 401, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 18:58:19 GMT", "WWW-Authenticate": "Basic realm=\"calendar-caldav-fixture\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T18:58:23+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 18:58:23 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "12887"}, "body": "{\"data\": [{\"id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"start_datetime\": \"2026-04-01T10:00:00+01:00\", \"end_datetime\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"eb53d018557c8fca\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T17:40:10+00:00\"}, {\"id\": 2, \"uid\": \"fixture-ce-002@calendar-wp-plugin\", \"title\": \"Office Closed\", \"description\": \"Public holiday closure.\", \"location\": \"HQ\", \"category\": \"Operations\", \"all_day_event\": true, \"start_datetime\": \"2026-05-04\", \"end_datetime\": \"2026-05-05\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"190a4", "body_bytes": 3927, "body_truncated": true}} +{"ts": "2026-03-30T18:58:23+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/users/register", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "Content-Length": "66"}, "body": "{\"email\": \"sec-nodisclose@example.test\", \"password\": \"***\"}", "body_bytes": 66, "body_truncated": false}, "response": {"status": 409, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 18:58:23 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "71"}, "body": "{\"error\": {\"code\": \"conflict_error\", \"message\": \"user already exists\"}}", "body_bytes": 71, "body_truncated": false}} +{"ts": "2026-03-30T18:58:24+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 18:58:24 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "12887"}, "body": "{\"data\": [{\"id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"start_datetime\": \"2026-04-01T10:00:00+01:00\", \"end_datetime\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"eb53d018557c8fca\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T17:40:10+00:00\"}, {\"id\": 2, \"uid\": \"fixture-ce-002@calendar-wp-plugin\", \"title\": \"Office Closed\", \"description\": \"Public holiday closure.\", \"location\": \"HQ\", \"category\": \"Operations\", \"all_day_event\": true, \"start_datetime\": \"2026-05-04\", \"end_datetime\": \"2026-05-05\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"190a4", "body_bytes": 3927, "body_truncated": true}} +{"ts": "2026-03-30T18:58:24+00:00", "client": "127.0.0.1", "method": "GET", "path": "/calendar.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 18:58:24 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"2b728e795356b3a1\"", "Content-Length": "5828"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nCALSCALE:GREGORIAN\r\nX-WR-TIMEZONE:Europe/London\r\nBEGIN:VEVENT\r\nUID:fixture-ce-001@calendar-wp-plugin\r\nDTSTAMP:20260330T185824Z\r\nSUMMARY:Board Meeting\r\nDESCRIPTION:Quarterly board review.\r\nLOCATION:Room A\r\nCATEGORIES:Governance\r\nDTSTART;TZID=Europe/London:20260401T100000\r\nDTEND;TZID=Europe/London:20260401T113000\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-002@calendar-wp-plugin\r\nDTSTAMP:20260330T185824Z\r\nSUMMARY:Office Closed\r\nDESCRIPTION:Public holiday closure.\r\nLOCATION:HQ\r\nCATEGORIES:Operations\r\nDTSTART;VALUE=DATE:20260504\r\nDTEND;VALUE=DATE:20260505\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-003@calendar-wp-plugin\r\nDTSTAMP:20260330T185824Z\r\nSUMMARY:Daily Standup\r\nDESCRIPTION:15 minute sync.\r\nLOCATION:Online\r\nCATEGORIES:Team\r\nDTSTART;TZID=Europe/London:20260406T090000\r\nDTEND;TZID=Europe/London:20260406T091500\r\nRRULE:FREQ=DAILY;COUNT=10\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-004@calendar-wp-plugin\r\nDTSTAMP:20260330T185824Z\r\nSUMMARY:Community Lunch\r\nDESCRIPTION:Weekly community lunch.\r\nLOCATION:Cafeteria\r\nCATEGORIES:Community\r\nDTSTART;TZID=Europe/London:20260408T123000\r\nDTEND;TZID=Europe/London:20260408T133000\r\n ...(truncated)", "body_bytes": 3905, "body_truncated": true}} +{"ts": "2026-03-30T18:58:24+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/1.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 18:58:24 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"eb53d018557c8fca\"", "Content-Length": "364"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-001@calendar-wp-plugin\r\nDTSTAMP:20260330T185824Z\r\nSUMMARY:Board Meeting\r\nDESCRIPTION:Quarterly board review.\r\nLOCATION:Room A\r\nCATEGORIES:Governance\r\nDTSTART;TZID=Europe/London:20260401T100000\r\nDTEND;TZID=Europe/London:20260401T113000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 364, "body_truncated": false}} +{"ts": "2026-03-30T18:58:24+00:00", "client": "127.0.0.1", "method": "REPORT", "path": "/caldav/calendars/public/", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/xml; charset=utf-8", "Depth": "1", "Content-Length": "239"}, "body": "\n\n \n /caldav/calendars/public/4.ics\n\n", "body_bytes": 239, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 18:58:24 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "997"}, "body": "\n\n\n /caldav/calendars/public/4.ics\n \n \n \"691a56c3c4084218\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-004@calendar-wp-plugin\r\nDTSTAMP:20260330T185824Z\r\nSUMMARY:Community Lunch\r\nDESCRIPTION:Weekly community lunch.\r\nLOCATION:Cafeteria\r\nCATEGORIES:Community\r\nDTSTART;TZID=Europe/London:20260408T123000\r\nDTEND;TZID=Europe/London:20260408T133000\r\nRRULE:FREQ=WEEKLY\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n", "body_bytes": 997, "body_truncated": false}} +{"ts": "2026-03-30T18:58:24+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/10/occurrences/2026-04-17T14:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 18:58:24 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T18:58:24+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "X-WP-User": "admin", "Content-Length": "287"}, "body": "{\"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}", "body_bytes": 287, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 18:58:24 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "647"}, "body": "{\"data\": {\"id\": 21, \"uid\": \"31f9cb4f86804b4a5b99@calendar-wp-plugin\", \"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_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"count\", \"repeat_count\": 3, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"074904f1a79b3def\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T18:58:24+00:00\"}}", "body_bytes": 647, "body_truncated": false}} +{"ts": "2026-03-30T18:58:24+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/21/occurrences?from=2026-04-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 18:58:24 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "937"}, "body": "{\"data\": [{\"event_id\": 21, \"uid\": \"31f9cb4f86804b4a5b99@calendar-wp-plugin\", \"title\": \"Smoke Occurrences API\", \"description\": \"occ-endpoint\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-01T10:00:00+01:00\", \"occurrence_end\": \"2026-04-01T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 21, \"uid\": \"31f9cb4f86804b4a5b99@calendar-wp-plugin\", \"title\": \"Smoke Occurrences API\", \"description\": \"occ-endpoint\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-02T10:00:00+01:00\", \"occurrence_end\": \"2026-04-02T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 21, \"uid\": \"31f9cb4f86804b4a5b99@calendar-wp-plugin\", \"title\": \"Smoke Occurrences API\", \"description\": \"occ-endpoint\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-03T10:00:00+01:00\", \"occurrence_end\": \"2026-04-03T11:00:00+01:00\", \"repeat_type\": \"daily\"}]}", "body_bytes": 937, "body_truncated": false}} +{"ts": "2026-03-30T18:58:24+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/21/occurrences/2026-04-02T10:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 18:58:24 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T18:58:24+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/21/occurrences/2026-04-02T10:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 18:58:24 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T18:58:24+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/21/occurrences?from=2026-04-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 18:58:24 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "628"}, "body": "{\"data\": [{\"event_id\": 21, \"uid\": \"31f9cb4f86804b4a5b99@calendar-wp-plugin\", \"title\": \"Smoke Occurrences API\", \"description\": \"occ-endpoint\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-01T10:00:00+01:00\", \"occurrence_end\": \"2026-04-01T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 21, \"uid\": \"31f9cb4f86804b4a5b99@calendar-wp-plugin\", \"title\": \"Smoke Occurrences API\", \"description\": \"occ-endpoint\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-03T10:00:00+01:00\", \"occurrence_end\": \"2026-04-03T11:00:00+01:00\", \"repeat_type\": \"daily\"}]}", "body_bytes": 628, "body_truncated": false}} +{"ts": "2026-03-30T18:58:24+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-vtimezone.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "454"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN\nVERSION:2.0\nBEGIN:VTIMEZONE\nTZID:Europe/London\nBEGIN:STANDARD\nDTSTART:18471201T000000\nRRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=9\nTZOFFSETFROM:+0115\nTZOFFSETTO:+0000\nTZNAME:GMT\nEND:STANDARD\nEND:VTIMEZONE\nBEGIN:VEVENT\nUID:smoke-vtimezone-parser-001\nSUMMARY:Smoke VTIMEZONE Parse\nDTSTART;TZID=Europe/London:20260423T150000\nDTEND;TZID=Europe/London:20260423T160000\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 454, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 18:58:24 GMT", "ETag": "\"fd09919574f61846\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T18:58:24+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-vtimezone.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 18:58:24 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"fd09919574f61846\"", "Content-Length": "288"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-vtimezone-parser-001\r\nDTSTAMP:20260330T185824Z\r\nSUMMARY:Smoke VTIMEZONE Parse\r\nDTSTART;TZID=Europe/London:20260423T150000\r\nDTEND;TZID=Europe/London:20260423T160000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 288, "body_truncated": false}} +{"ts": "2026-03-30T18:58:24+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-monthly-nth.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "261"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-monthly-nth-001\nSUMMARY:Smoke Monthly Nth\nDTSTART;TZID=Europe/London:20260402T150000\nDTEND;TZID=Europe/London:20260402T160000\nRRULE:FREQ=MONTHLY;BYDAY=SU;BYSETPOS=4\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 261, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 18:58:24 GMT", "ETag": "\"55b725a1dc9a9f7c\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T18:58:24+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-monthly-nth.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 18:58:24 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"55b725a1dc9a9f7c\"", "Content-Length": "319"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-nth-001\r\nDTSTAMP:20260330T185824Z\r\nSUMMARY:Smoke Monthly Nth\r\nDTSTART;TZID=Europe/London:20260426T150000\r\nDTEND;TZID=Europe/London:20260426T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SU;BYSETPOS=4\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 319, "body_truncated": false}} +{"ts": "2026-03-30T18:58:24+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-monthly-ordinal.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "259"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-monthly-ordinal-001\nSUMMARY:Smoke Monthly Ordinal\nDTSTART;TZID=Europe/London:20260411T150000\nDTEND;TZID=Europe/London:20260411T160000\nRRULE:FREQ=MONTHLY;BYDAY=2SA\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 259, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 18:58:24 GMT", "ETag": "\"93bc4c15adc79a61\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T18:58:24+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-monthly-ordinal.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 18:58:24 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"93bc4c15adc79a61\"", "Content-Length": "327"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-ordinal-001\r\nDTSTAMP:20260330T185824Z\r\nSUMMARY:Smoke Monthly Ordinal\r\nDTSTART;TZID=Europe/London:20260411T150000\r\nDTEND;TZID=Europe/London:20260411T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SA;BYSETPOS=2\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 327, "body_truncated": false}} +{"ts": "2026-03-30T18:58:25+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-monthly-last.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "264"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-monthly-last-001\nSUMMARY:Smoke Monthly Last\nDTSTART;TZID=Europe/London:20260425T150000\nDTEND;TZID=Europe/London:20260425T160000\nRRULE:FREQ=MONTHLY;BYDAY=SA;BYSETPOS=-1\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 264, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 18:58:25 GMT", "ETag": "\"3a4eeb42242c3576\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T18:58:25+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-monthly-last.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 18:58:25 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"3a4eeb42242c3576\"", "Content-Length": "322"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-last-001\r\nDTSTAMP:20260330T185825Z\r\nSUMMARY:Smoke Monthly Last\r\nDTSTART;TZID=Europe/London:20260425T150000\r\nDTEND;TZID=Europe/London:20260425T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SA;BYSETPOS=-1\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 322, "body_truncated": false}} +{"ts": "2026-03-30T18:58:25+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "X-WP-User": "admin", "Content-Length": "362"}, "body": "{\"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\"}", "body_bytes": 362, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 18:58:25 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "673"}, "body": "{\"data\": {\"id\": 22, \"uid\": \"43a308b5b85320514c11@calendar-wp-plugin\", \"title\": \"Smoke 4th Sunday Anchor\", \"description\": \"anchor-normalization\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"start_datetime\": \"2026-05-24T15:00:00+01:00\", \"end_datetime\": \"2026-05-24T16:00:00+01:00\", \"repeat_type\": \"monthly\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"weekday_of_month\", \"repeat_nth_day\": null, \"repeat_nth_pos\": 4, \"repeat_nth_weekday\": 0, \"repeat_range_mode\": \"no_end\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"3f98362302bad93a\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T18:58:25+00:00\"}}", "body_bytes": 673, "body_truncated": false}} +{"ts": "2026-03-30T18:58:25+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/22/occurrences/2026-05-24T15:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 18:58:25 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T18:58:25+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/22.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 18:58:25 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"10c5aa3ff8a36c74\"", "Content-Length": "420"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:43a308b5b85320514c11@calendar-wp-plugin\r\nDTSTAMP:20260330T185825Z\r\nSUMMARY:Smoke 4th Sunday Anchor\r\nDESCRIPTION:anchor-normalization\r\nDTSTART;TZID=Europe/London:20260524T150000\r\nDTEND;TZID=Europe/London:20260524T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SU;BYSETPOS=4\r\nEXDATE;TZID=Europe/London:20260524T150000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 420, "body_truncated": false}} +{"ts": "2026-03-30T18:58:25+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "X-WP-User": "admin", "Content-Length": "295"}, "body": "{\"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\"}", "body_bytes": 295, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 18:58:25 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "655"}, "body": "{\"data\": {\"id\": 23, \"uid\": \"57e4605078dcee8f54a2@calendar-wp-plugin\", \"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_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"until\", \"repeat_count\": null, \"repeat_until\": \"2026-03-19\", \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"b1b9932905a7b74c\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T18:58:25+00:00\"}}", "body_bytes": 655, "body_truncated": false}} +{"ts": "2026-03-30T18:58:25+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/23/occurrences/2026-03-11T11:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 18:58:25 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T18:58:25+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/23/occurrences?from=2026-03-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 18:58:25 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "5212"}, "body": "{\"data\": [{\"event_id\": 23, \"uid\": \"57e4605078dcee8f54a2@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-02T10:00:00+00:00\", \"occurrence_end\": \"2026-03-02T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 23, \"uid\": \"57e4605078dcee8f54a2@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-03T10:00:00+00:00\", \"occurrence_end\": \"2026-03-03T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 23, \"uid\": \"57e4605078dcee8f54a2@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-04T10:00:00+00:00\", \"occurrence_end\": \"2026-03-04T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 23, \"uid\": \"57e4605078dcee8f54a2@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-05T10:00:00+00:00\", \"occurrence_end\": \"2026-03-05T11:00:00", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T18:58:25+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/23/occurrences/2026-03-10", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 18:58:25 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T18:58:25+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/23/occurrences?from=2026-03-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 18:58:25 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "4906"}, "body": "{\"data\": [{\"event_id\": 23, \"uid\": \"57e4605078dcee8f54a2@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-02T10:00:00+00:00\", \"occurrence_end\": \"2026-03-02T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 23, \"uid\": \"57e4605078dcee8f54a2@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-03T10:00:00+00:00\", \"occurrence_end\": \"2026-03-03T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 23, \"uid\": \"57e4605078dcee8f54a2@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-04T10:00:00+00:00\", \"occurrence_end\": \"2026-03-04T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 23, \"uid\": \"57e4605078dcee8f54a2@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-05T10:00:00+00:00\", \"occurrence_end\": \"2026-03-05T11:00:00", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T18:58:25+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-exdate-import.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "323"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-exdate-import-001\nSUMMARY:Smoke EXDATE Import\nDTSTART;TZID=Europe/London:20260302T100000\nDTEND;TZID=Europe/London:20260302T110000\nRRULE:FREQ=DAILY;UNTIL=20260319T235959\nEXDATE;TZID=Europe/London:20260310T100000,20260311T100000\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 323, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 18:58:25 GMT", "ETag": "\"ad165d5f81f5c072\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T18:58:25+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 18:58:25 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "14838"}, "body": "{\"data\": [{\"id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"start_datetime\": \"2026-04-01T10:00:00+01:00\", \"end_datetime\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"eb53d018557c8fca\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T17:40:10+00:00\"}, {\"id\": 2, \"uid\": \"fixture-ce-002@calendar-wp-plugin\", \"title\": \"Office Closed\", \"description\": \"Public holiday closure.\", \"location\": \"HQ\", \"category\": \"Operations\", \"all_day_event\": true, \"start_datetime\": \"2026-05-04\", \"end_datetime\": \"2026-05-05\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"190a4", "body_bytes": 3927, "body_truncated": true}} +{"ts": "2026-03-30T18:58:25+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/18/occurrences?from=2026-03-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 18:58:25 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "4474"}, "body": "{\"data\": [{\"event_id\": 18, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-02T10:00:00+01:00\", \"occurrence_end\": \"2026-03-02T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 18, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-03T10:00:00+01:00\", \"occurrence_end\": \"2026-03-03T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 18, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-04T10:00:00+01:00\", \"occurrence_end\": \"2026-03-04T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 18, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-05T10:00:00+01:00\", \"occurrence_end\": \"2026-03-05T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 18, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE ", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T18:58:25+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-cancelled-occurrence.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "467"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-cancelled-occurrence-001\nSUMMARY:Smoke Cancelled Occurrence\nDTSTART;TZID=Europe/London:20260408T123000\nDTEND;TZID=Europe/London:20260408T133000\nRRULE:FREQ=WEEKLY\nEND:VEVENT\nBEGIN:VEVENT\nUID:smoke-cancelled-occurrence-001\nRECURRENCE-ID;TZID=Europe/London:20260506T123000\nDTSTART;TZID=Europe/London:20260506T123000\nDTEND;TZID=Europe/London:20260506T133000\nSTATUS:CANCELLED\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 467, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 18:58:25 GMT", "ETag": "\"7d8fff7329f9a530\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T18:58:25+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-cancelled-occurrence.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 18:58:25 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"7d8fff7329f9a530\"", "Content-Length": "359"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-cancelled-occurrence-001\r\nDTSTAMP:20260330T185825Z\r\nSUMMARY:Smoke Cancelled Occurrence\r\nDTSTART;TZID=Europe/London:20260408T123000\r\nDTEND;TZID=Europe/London:20260408T133000\r\nRRULE:FREQ=WEEKLY\r\nEXDATE;TZID=Europe/London:20260506T123000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 359, "body_truncated": false}} +{"ts": "2026-03-30T18:58:25+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 18:58:25 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "14838"}, "body": "{\"data\": [{\"id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"start_datetime\": \"2026-04-01T10:00:00+01:00\", \"end_datetime\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"eb53d018557c8fca\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T17:40:10+00:00\"}, {\"id\": 2, \"uid\": \"fixture-ce-002@calendar-wp-plugin\", \"title\": \"Office Closed\", \"description\": \"Public holiday closure.\", \"location\": \"HQ\", \"category\": \"Operations\", \"all_day_event\": true, \"start_datetime\": \"2026-05-04\", \"end_datetime\": \"2026-05-05\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"190a4", "body_bytes": 3927, "body_truncated": true}} +{"ts": "2026-03-30T18:58:25+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/19/occurrences?from=2026-05-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 18:58:25 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "892"}, "body": "{\"data\": [{\"event_id\": 19, \"uid\": \"smoke-cancelled-occurrence-001\", \"title\": \"Smoke Cancelled Occurrence\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-13T12:30:00+01:00\", \"occurrence_end\": \"2026-05-13T13:30:00+01:00\", \"repeat_type\": \"weekly\"}, {\"event_id\": 19, \"uid\": \"smoke-cancelled-occurrence-001\", \"title\": \"Smoke Cancelled Occurrence\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-20T12:30:00+01:00\", \"occurrence_end\": \"2026-05-20T13:30:00+01:00\", \"repeat_type\": \"weekly\"}, {\"event_id\": 19, \"uid\": \"smoke-cancelled-occurrence-001\", \"title\": \"Smoke Cancelled Occurrence\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-27T12:30:00+01:00\", \"occurrence_end\": \"2026-05-27T13:30:00+01:00\", \"repeat_type\": \"weekly\"}]}", "body_bytes": 892, "body_truncated": false}} +{"ts": "2026-03-30T18:58:25+00:00", "client": "127.0.0.1", "method": "GET", "path": "/calendar", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 18:58:25 GMT", "Content-Type": "text/html; charset=utf-8", "Content-Length": "43447"}, "body": "[omitted html payload: 3934 bytes]", "body_bytes": 3934, "body_truncated": true}} +{"ts": "2026-03-30T18:59:04+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 18:59:04 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "14838"}, "body": "{\"data\": [{\"id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"start_datetime\": \"2026-04-01T10:00:00+01:00\", \"end_datetime\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"eb53d018557c8fca\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T17:40:10+00:00\"}, {\"id\": 2, \"uid\": \"fixture-ce-002@calendar-wp-plugin\", \"title\": \"Office Closed\", \"description\": \"Public holiday closure.\", \"location\": \"HQ\", \"category\": \"Operations\", \"all_day_event\": true, \"start_datetime\": \"2026-05-04\", \"end_datetime\": \"2026-05-05\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"190a4", "body_bytes": 3927, "body_truncated": true}} +{"ts": "2026-03-30T18:59:04+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 401, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 18:59:04 GMT", "WWW-Authenticate": "Basic realm=\"calendar-caldav-fixture\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T18:59:04+00:00", "client": "127.0.0.1", "method": "OPTIONS", "path": "/caldav/", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 18:59:04 GMT", "DAV": "1, 2, calendar-access", "Allow": "OPTIONS, PROPFIND, REPORT, GET, PUT, DELETE"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T18:59:07+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/users/register", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "Content-Length": "77"}, "body": "{\"email\": \"sec-nodisclose-1774897144@example.test\", \"password\": \"***\"}", "body_bytes": 77, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 18:59:07 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "99"}, "body": "{\"data\": {\"user_id\": 12, \"status\": \"pending_approval\", \"email_status\": \"sent\", \"email_sent\": true}}", "body_bytes": 99, "body_truncated": false}} +{"ts": "2026-03-30T18:59:07+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/users/forgot-password", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "Content-Length": "39"}, "body": "{\"email\": \"adrians@chezstephens.org.uk\"}", "body_bytes": 39, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 18:59:07 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "70"}, "body": "{\"data\": {\"status\": \"ok\", \"email_status\": \"sent\", \"email_sent\": true}}", "body_bytes": 70, "body_truncated": false}} +{"ts": "2026-03-30T18:59:07+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/users/register", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "Content-Length": "73"}, "body": "{\"email\": \"sec-rate-1774897144-1@example.test\", \"password\": \"***\"}", "body_bytes": 73, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 18:59:07 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "99"}, "body": "{\"data\": {\"user_id\": 13, \"status\": \"pending_approval\", \"email_status\": \"sent\", \"email_sent\": true}}", "body_bytes": 99, "body_truncated": false}} +{"ts": "2026-03-30T18:59:07+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/users/register", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "Content-Length": "73"}, "body": "{\"email\": \"sec-rate-1774897144-2@example.test\", \"password\": \"***\"}", "body_bytes": 73, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 18:59:07 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "99"}, "body": "{\"data\": {\"user_id\": 14, \"status\": \"pending_approval\", \"email_status\": \"sent\", \"email_sent\": true}}", "body_bytes": 99, "body_truncated": false}} +{"ts": "2026-03-30T18:59:07+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/users/register", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "Content-Length": "73"}, "body": "{\"email\": \"sec-rate-1774897144-3@example.test\", \"password\": \"***\"}", "body_bytes": 73, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 18:59:07 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "99"}, "body": "{\"data\": {\"user_id\": 15, \"status\": \"pending_approval\", \"email_status\": \"sent\", \"email_sent\": true}}", "body_bytes": 99, "body_truncated": false}} +{"ts": "2026-03-30T18:59:08+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/users/register", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "Content-Length": "73"}, "body": "{\"email\": \"sec-rate-1774897144-4@example.test\", \"password\": \"***\"}", "body_bytes": 73, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 18:59:08 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "99"}, "body": "{\"data\": {\"user_id\": 16, \"status\": \"pending_approval\", \"email_status\": \"sent\", \"email_sent\": true}}", "body_bytes": 99, "body_truncated": false}} +{"ts": "2026-03-30T18:59:08+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/users/register", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "Content-Length": "73"}, "body": "{\"email\": \"sec-rate-1774897144-5@example.test\", \"password\": \"***\"}", "body_bytes": 73, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 18:59:08 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "99"}, "body": "{\"data\": {\"user_id\": 17, \"status\": \"pending_approval\", \"email_status\": \"sent\", \"email_sent\": true}}", "body_bytes": 99, "body_truncated": false}} +{"ts": "2026-03-30T18:59:08+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/users/register", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "Content-Length": "73"}, "body": "{\"email\": \"sec-rate-1774897144-6@example.test\", \"password\": \"***\"}", "body_bytes": 73, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 18:59:08 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "99"}, "body": "{\"data\": {\"user_id\": 18, \"status\": \"pending_approval\", \"email_status\": \"sent\", \"email_sent\": true}}", "body_bytes": 99, "body_truncated": false}} +{"ts": "2026-03-30T18:59:08+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/users/register", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "Content-Length": "73"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 429, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 18:59:08 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "98"}, "body": "{\"error\": {\"code\": \"rate_limited\", \"message\": \"Too many registration attempts. Try again later.\"}}", "body_bytes": 98, "body_truncated": false}} +{"ts": "2026-03-30T18:59:08+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/sec-1774897148.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Length": "201", "Content-Type": "application/x-www-form-urlencoded"}, "body": "[omitted non-text payload: 201 bytes, content-type=application/x-www-form-urlencoded]", "body_bytes": 201, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 18:59:08 GMT", "ETag": "\"74426c4ef3fe3bdd\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T18:59:08+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/sec-1774897148.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 18:59:08 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"74426c4ef3fe3bdd\"", "Content-Length": "287"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:sec-smoke-uid@example.test\r\nDTSTAMP:20260330T185908Z\r\nSUMMARY:Security Smoke Event\r\nDTSTART;TZID=Europe/London:20260415T100000\r\nDTEND;TZID=Europe/London:20260415T110000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 287, "body_truncated": false}} +{"ts": "2026-03-30T18:59:08+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/999999.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 404, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 18:59:08 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "62"}, "body": "{\"error\": {\"code\": \"not_found\", \"message\": \"Route not found\"}}", "body_bytes": 62, "body_truncated": false}} +{"ts": "2026-03-30T18:59:17+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 401, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 18:59:17 GMT", "WWW-Authenticate": "Basic realm=\"calendar-caldav-fixture\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T18:59:17+00:00", "client": "127.0.0.1", "method": "OPTIONS", "path": "/caldav/", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 18:59:17 GMT", "DAV": "1, 2, calendar-access", "Allow": "OPTIONS, PROPFIND, REPORT, GET, PUT, DELETE"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T18:59:17+00:00", "client": "127.0.0.1", "method": "PROPFIND", "path": "/caldav/", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Depth": "1", "Content-Type": "application/xml", "Content-Length": "117"}, "body": "", "body_bytes": 117, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 18:59:17 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "1348"}, "body": "\n\n\n /caldav/\n \n \n /caldav/principals/user/\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/principals/user/\n \n \n /caldav/calendars/\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/\n \n \n \n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/\n \n \n Public Calendar", "body_bytes": 117, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 18:59:27 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "1348"}, "body": "\n\n\n /caldav/\n \n \n /caldav/principals/user/\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/principals/user/\n \n \n /caldav/calendars/\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/\n \n \n \n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/\n \n \n Public Calendar", "body_bytes": 119, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 18:59:27 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "445"}, "body": "\n\n\n /caldav/principals/user/\n \n \n /caldav/calendars/\n \n HTTP/1.1 200 OK\n \n\n\n", "body_bytes": 445, "body_truncated": false}} +{"ts": "2026-03-30T18:59:27+00:00", "client": "127.0.0.1", "method": "PROPFIND", "path": "/caldav/calendars/", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Depth": "1", "Content-Type": "application/xml", "Content-Length": "167"}, "body": "", "body_bytes": 167, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 18:59:27 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "728"}, "body": "\n\n\n /caldav/calendars/\n \n \n \n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/\n \n \n Public Calendar\n \n HTTP/1.1 200 OK\n \n\n\n", "body_bytes": 728, "body_truncated": false}} +{"ts": "2026-03-30T19:03:43+00:00", "client": "127.0.0.1", "method": "PROPFIND", "path": "/caldav/calendars/public/", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/xml; charset=utf-8", "Content-Length": "144", "Depth": "1", "Origin": "http://127.0.0.1:8080", "DNT": "1", "Authorization": "***", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "\n", "body_bytes": 144, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:03:43 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "7677"}, "body": "\n\n\n /caldav/calendars/public/\n \n \n Public Calendar\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/1.ics\n \n \n text/calendar; charset=utf-8\"eb53d018557c8fca\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/2.ics\n \n \n text/calendar; charset=utf-8\"190a40db82a13a9b\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/3.ics\n \n \n ...(truncated)", "body_bytes": 3919, "body_truncated": true}} +{"ts": "2026-03-30T19:03:43+00:00", "client": "127.0.0.1", "method": "REPORT", "path": "/caldav/calendars/public/", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/xml; charset=utf-8", "Content-Length": "780", "Depth": "1", "Origin": "http://127.0.0.1:8080", "DNT": "1", "Authorization": "***", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "\n/caldav/calendars/public/23.ics/caldav/calendars/public/22.ics/caldav/calendars/public/21.ics/caldav/calendars/public/sec-1774897148.ics/caldav/calendars/public/smoke-cancelled-occurrence.ics/caldav/calendars/public/smoke-exdate-import.ics/caldav/calendars/public/smoke-monthly-last.ics/caldav/calendars/public/smoke-monthly-ordinal.ics/caldav/calendars/public/smoke-monthly-nth.ics/caldav/calendars/public/smoke-vtimezone.ics", "body_bytes": 780, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:03:43 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "8597"}, "body": "\n\n\n /caldav/calendars/public/smoke-vtimezone.ics\n \n \n \"fd09919574f61846\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-vtimezone-parser-001\r\nDTSTAMP:20260330T190343Z\r\nSUMMARY:Smoke VTIMEZONE Parse\r\nDTSTART;TZID=Europe/London:20260423T150000\r\nDTEND;TZID=Europe/London:20260423T160000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/smoke-monthly-nth.ics\n \n \n \"55b725a1dc9a9f7c\"\n BEGIN:V ...(truncated)", "body_bytes": 3919, "body_truncated": true}} +{"ts": "2026-03-30T19:06:40+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 401, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:06:40 GMT", "WWW-Authenticate": "Basic realm=\"calendar-caldav-fixture\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:06:40+00:00", "client": "127.0.0.1", "method": "OPTIONS", "path": "/caldav/", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:06:40 GMT", "DAV": "1, 2, calendar-access", "Allow": "OPTIONS, PROPFIND, REPORT, GET, PUT, DELETE"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:06:40+00:00", "client": "127.0.0.1", "method": "PROPFIND", "path": "/caldav/", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Depth": "1", "Content-Type": "application/xml", "Content-Length": "117"}, "body": "", "body_bytes": 117, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:06:40 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "1348"}, "body": "\n\n\n /caldav/\n \n \n /caldav/principals/user/\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/principals/user/\n \n \n /caldav/calendars/\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/\n \n \n \n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/\n \n \n Public Calendar", "body_bytes": 119, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:06:40 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "445"}, "body": "\n\n\n /caldav/principals/user/\n \n \n /caldav/calendars/\n \n HTTP/1.1 200 OK\n \n\n\n", "body_bytes": 445, "body_truncated": false}} +{"ts": "2026-03-30T19:06:40+00:00", "client": "127.0.0.1", "method": "PROPFIND", "path": "/caldav/calendars/", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Depth": "1", "Content-Type": "application/xml", "Content-Length": "167"}, "body": "", "body_bytes": 167, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:06:40 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "728"}, "body": "\n\n\n /caldav/calendars/\n \n \n \n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/\n \n \n Public Calendar\n \n HTTP/1.1 200 OK\n \n\n\n", "body_bytes": 728, "body_truncated": false}} +{"ts": "2026-03-30T19:16:05+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 401, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:16:05 GMT", "WWW-Authenticate": "Basic realm=\"calendar-caldav-fixture\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:16:05+00:00", "client": "127.0.0.1", "method": "OPTIONS", "path": "/caldav/", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:16:05 GMT", "DAV": "1, 2, calendar-access", "Allow": "OPTIONS, PROPFIND, REPORT, GET, PUT, DELETE"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:16:05+00:00", "client": "127.0.0.1", "method": "PROPFIND", "path": "/caldav/", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Depth": "1", "Content-Type": "application/xml", "Content-Length": "117"}, "body": "", "body_bytes": 117, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:16:05 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "1348"}, "body": "\n\n\n /caldav/\n \n \n /caldav/principals/user/\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/principals/user/\n \n \n /caldav/calendars/\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/\n \n \n \n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/\n \n \n Public Calendar", "body_bytes": 119, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:16:05 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "445"}, "body": "\n\n\n /caldav/principals/user/\n \n \n /caldav/calendars/\n \n HTTP/1.1 200 OK\n \n\n\n", "body_bytes": 445, "body_truncated": false}} +{"ts": "2026-03-30T19:16:05+00:00", "client": "127.0.0.1", "method": "PROPFIND", "path": "/caldav/calendars/", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Depth": "1", "Content-Type": "application/xml", "Content-Length": "167"}, "body": "", "body_bytes": 167, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:16:05 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "728"}, "body": "\n\n\n /caldav/calendars/\n \n \n \n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/\n \n \n Public Calendar\n \n HTTP/1.1 200 OK\n \n\n\n", "body_bytes": 728, "body_truncated": false}} +{"ts": "2026-03-30T19:27:35+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:27:35 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "14838"}, "body": "{\"data\": [{\"id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"start_datetime\": \"2026-04-01T10:00:00+01:00\", \"end_datetime\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"eb53d018557c8fca\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T17:40:10+00:00\"}, {\"id\": 2, \"uid\": \"fixture-ce-002@calendar-wp-plugin\", \"title\": \"Office Closed\", \"description\": \"Public holiday closure.\", \"location\": \"HQ\", \"category\": \"Operations\", \"all_day_event\": true, \"start_datetime\": \"2026-05-04\", \"end_datetime\": \"2026-05-05\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"190a4", "body_bytes": 3927, "body_truncated": true}} +{"ts": "2026-03-30T19:27:35+00:00", "client": "127.0.0.1", "method": "GET", "path": "/calendar.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:27:35 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"435242ad54fe20be\"", "Content-Length": "6806"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nCALSCALE:GREGORIAN\r\nX-WR-TIMEZONE:Europe/London\r\nBEGIN:VEVENT\r\nUID:fixture-ce-001@calendar-wp-plugin\r\nDTSTAMP:20260330T192735Z\r\nSUMMARY:Board Meeting\r\nDESCRIPTION:Quarterly board review.\r\nLOCATION:Room A\r\nCATEGORIES:Governance\r\nDTSTART;TZID=Europe/London:20260401T100000\r\nDTEND;TZID=Europe/London:20260401T113000\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-002@calendar-wp-plugin\r\nDTSTAMP:20260330T192735Z\r\nSUMMARY:Office Closed\r\nDESCRIPTION:Public holiday closure.\r\nLOCATION:HQ\r\nCATEGORIES:Operations\r\nDTSTART;VALUE=DATE:20260504\r\nDTEND;VALUE=DATE:20260505\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-003@calendar-wp-plugin\r\nDTSTAMP:20260330T192735Z\r\nSUMMARY:Daily Standup\r\nDESCRIPTION:15 minute sync.\r\nLOCATION:Online\r\nCATEGORIES:Team\r\nDTSTART;TZID=Europe/London:20260406T090000\r\nDTEND;TZID=Europe/London:20260406T091500\r\nRRULE:FREQ=DAILY;COUNT=10\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-004@calendar-wp-plugin\r\nDTSTAMP:20260330T192735Z\r\nSUMMARY:Community Lunch\r\nDESCRIPTION:Weekly community lunch.\r\nLOCATION:Cafeteria\r\nCATEGORIES:Community\r\nDTSTART;TZID=Europe/London:20260408T123000\r\nDTEND;TZID=Europe/London:20260408T133000\r\n ...(truncated)", "body_bytes": 3905, "body_truncated": true}} +{"ts": "2026-03-30T19:27:35+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/1.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:27:35 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"eb53d018557c8fca\"", "Content-Length": "364"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-001@calendar-wp-plugin\r\nDTSTAMP:20260330T192735Z\r\nSUMMARY:Board Meeting\r\nDESCRIPTION:Quarterly board review.\r\nLOCATION:Room A\r\nCATEGORIES:Governance\r\nDTSTART;TZID=Europe/London:20260401T100000\r\nDTEND;TZID=Europe/London:20260401T113000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 364, "body_truncated": false}} +{"ts": "2026-03-30T19:27:35+00:00", "client": "127.0.0.1", "method": "REPORT", "path": "/caldav/calendars/public/", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/xml; charset=utf-8", "Depth": "1", "Content-Length": "239"}, "body": "\n\n \n /caldav/calendars/public/4.ics\n\n", "body_bytes": 239, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:27:35 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "997"}, "body": "\n\n\n /caldav/calendars/public/4.ics\n \n \n \"691a56c3c4084218\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-004@calendar-wp-plugin\r\nDTSTAMP:20260330T192735Z\r\nSUMMARY:Community Lunch\r\nDESCRIPTION:Weekly community lunch.\r\nLOCATION:Cafeteria\r\nCATEGORIES:Community\r\nDTSTART;TZID=Europe/London:20260408T123000\r\nDTEND;TZID=Europe/London:20260408T133000\r\nRRULE:FREQ=WEEKLY\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n", "body_bytes": 997, "body_truncated": false}} +{"ts": "2026-03-30T19:27:35+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/10/occurrences/2026-04-17T14:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:27:35 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:27:35+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "X-WP-User": "admin", "Content-Length": "287"}, "body": "{\"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}", "body_bytes": 287, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:27:35 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "647"}, "body": "{\"data\": {\"id\": 24, \"uid\": \"ff961c0d028da4c33d68@calendar-wp-plugin\", \"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_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"count\", \"repeat_count\": 3, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"13736f3159f811a0\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T19:27:35+00:00\"}}", "body_bytes": 647, "body_truncated": false}} +{"ts": "2026-03-30T19:27:35+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/24/occurrences?from=2026-04-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:27:35 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "937"}, "body": "{\"data\": [{\"event_id\": 24, \"uid\": \"ff961c0d028da4c33d68@calendar-wp-plugin\", \"title\": \"Smoke Occurrences API\", \"description\": \"occ-endpoint\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-01T10:00:00+01:00\", \"occurrence_end\": \"2026-04-01T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 24, \"uid\": \"ff961c0d028da4c33d68@calendar-wp-plugin\", \"title\": \"Smoke Occurrences API\", \"description\": \"occ-endpoint\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-02T10:00:00+01:00\", \"occurrence_end\": \"2026-04-02T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 24, \"uid\": \"ff961c0d028da4c33d68@calendar-wp-plugin\", \"title\": \"Smoke Occurrences API\", \"description\": \"occ-endpoint\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-03T10:00:00+01:00\", \"occurrence_end\": \"2026-04-03T11:00:00+01:00\", \"repeat_type\": \"daily\"}]}", "body_bytes": 937, "body_truncated": false}} +{"ts": "2026-03-30T19:27:35+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/24/occurrences/2026-04-02T10:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:27:35 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:27:35+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/24/occurrences/2026-04-02T10:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:27:35 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:27:35+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/24/occurrences?from=2026-04-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:27:35 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "628"}, "body": "{\"data\": [{\"event_id\": 24, \"uid\": \"ff961c0d028da4c33d68@calendar-wp-plugin\", \"title\": \"Smoke Occurrences API\", \"description\": \"occ-endpoint\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-01T10:00:00+01:00\", \"occurrence_end\": \"2026-04-01T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 24, \"uid\": \"ff961c0d028da4c33d68@calendar-wp-plugin\", \"title\": \"Smoke Occurrences API\", \"description\": \"occ-endpoint\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-03T10:00:00+01:00\", \"occurrence_end\": \"2026-04-03T11:00:00+01:00\", \"repeat_type\": \"daily\"}]}", "body_bytes": 628, "body_truncated": false}} +{"ts": "2026-03-30T19:27:35+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-vtimezone.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "454"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN\nVERSION:2.0\nBEGIN:VTIMEZONE\nTZID:Europe/London\nBEGIN:STANDARD\nDTSTART:18471201T000000\nRRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=9\nTZOFFSETFROM:+0115\nTZOFFSETTO:+0000\nTZNAME:GMT\nEND:STANDARD\nEND:VTIMEZONE\nBEGIN:VEVENT\nUID:smoke-vtimezone-parser-001\nSUMMARY:Smoke VTIMEZONE Parse\nDTSTART;TZID=Europe/London:20260423T150000\nDTEND;TZID=Europe/London:20260423T160000\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 454, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:27:35 GMT", "ETag": "\"227c6db7bb9af481\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:27:35+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-vtimezone.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:27:35 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"227c6db7bb9af481\"", "Content-Length": "288"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-vtimezone-parser-001\r\nDTSTAMP:20260330T192735Z\r\nSUMMARY:Smoke VTIMEZONE Parse\r\nDTSTART;TZID=Europe/London:20260423T150000\r\nDTEND;TZID=Europe/London:20260423T160000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 288, "body_truncated": false}} +{"ts": "2026-03-30T19:27:35+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-monthly-nth.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "261"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-monthly-nth-001\nSUMMARY:Smoke Monthly Nth\nDTSTART;TZID=Europe/London:20260402T150000\nDTEND;TZID=Europe/London:20260402T160000\nRRULE:FREQ=MONTHLY;BYDAY=SU;BYSETPOS=4\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 261, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:27:35 GMT", "ETag": "\"e59b158ebd60c483\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:27:35+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-monthly-nth.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:27:35 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"e59b158ebd60c483\"", "Content-Length": "319"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-nth-001\r\nDTSTAMP:20260330T192735Z\r\nSUMMARY:Smoke Monthly Nth\r\nDTSTART;TZID=Europe/London:20260426T150000\r\nDTEND;TZID=Europe/London:20260426T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SU;BYSETPOS=4\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 319, "body_truncated": false}} +{"ts": "2026-03-30T19:27:35+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-monthly-ordinal.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "259"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-monthly-ordinal-001\nSUMMARY:Smoke Monthly Ordinal\nDTSTART;TZID=Europe/London:20260411T150000\nDTEND;TZID=Europe/London:20260411T160000\nRRULE:FREQ=MONTHLY;BYDAY=2SA\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 259, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:27:35 GMT", "ETag": "\"0d9783a0beedd0ef\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:27:35+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-monthly-ordinal.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:27:35 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"0d9783a0beedd0ef\"", "Content-Length": "327"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-ordinal-001\r\nDTSTAMP:20260330T192735Z\r\nSUMMARY:Smoke Monthly Ordinal\r\nDTSTART;TZID=Europe/London:20260411T150000\r\nDTEND;TZID=Europe/London:20260411T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SA;BYSETPOS=2\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 327, "body_truncated": false}} +{"ts": "2026-03-30T19:27:35+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-monthly-last.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "264"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-monthly-last-001\nSUMMARY:Smoke Monthly Last\nDTSTART;TZID=Europe/London:20260425T150000\nDTEND;TZID=Europe/London:20260425T160000\nRRULE:FREQ=MONTHLY;BYDAY=SA;BYSETPOS=-1\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 264, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:27:35 GMT", "ETag": "\"de783c88365b72e6\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:27:35+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-monthly-last.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:27:35 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"de783c88365b72e6\"", "Content-Length": "322"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-last-001\r\nDTSTAMP:20260330T192735Z\r\nSUMMARY:Smoke Monthly Last\r\nDTSTART;TZID=Europe/London:20260425T150000\r\nDTEND;TZID=Europe/London:20260425T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SA;BYSETPOS=-1\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 322, "body_truncated": false}} +{"ts": "2026-03-30T19:27:35+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "X-WP-User": "admin", "Content-Length": "362"}, "body": "{\"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\"}", "body_bytes": 362, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:27:35 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "673"}, "body": "{\"data\": {\"id\": 25, \"uid\": \"3c229224b9c6e55380f3@calendar-wp-plugin\", \"title\": \"Smoke 4th Sunday Anchor\", \"description\": \"anchor-normalization\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"start_datetime\": \"2026-05-24T15:00:00+01:00\", \"end_datetime\": \"2026-05-24T16:00:00+01:00\", \"repeat_type\": \"monthly\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"weekday_of_month\", \"repeat_nth_day\": null, \"repeat_nth_pos\": 4, \"repeat_nth_weekday\": 0, \"repeat_range_mode\": \"no_end\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"11dc511cc506316c\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T19:27:35+00:00\"}}", "body_bytes": 673, "body_truncated": false}} +{"ts": "2026-03-30T19:27:35+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/25/occurrences/2026-05-24T15:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:27:35 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:27:35+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/25.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:27:35 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"7c606408b74ecb81\"", "Content-Length": "420"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:3c229224b9c6e55380f3@calendar-wp-plugin\r\nDTSTAMP:20260330T192735Z\r\nSUMMARY:Smoke 4th Sunday Anchor\r\nDESCRIPTION:anchor-normalization\r\nDTSTART;TZID=Europe/London:20260524T150000\r\nDTEND;TZID=Europe/London:20260524T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SU;BYSETPOS=4\r\nEXDATE;TZID=Europe/London:20260524T150000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 420, "body_truncated": false}} +{"ts": "2026-03-30T19:27:35+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "X-WP-User": "admin", "Content-Length": "295"}, "body": "{\"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\"}", "body_bytes": 295, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:27:35 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "655"}, "body": "{\"data\": {\"id\": 26, \"uid\": \"5d29e505c8af669ff7f1@calendar-wp-plugin\", \"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_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"until\", \"repeat_count\": null, \"repeat_until\": \"2026-03-19\", \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"6e797a165f88a673\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T19:27:35+00:00\"}}", "body_bytes": 655, "body_truncated": false}} +{"ts": "2026-03-30T19:27:35+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/26/occurrences/2026-03-11T11:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:27:35 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:27:35+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/26/occurrences?from=2026-03-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:27:35 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "5212"}, "body": "{\"data\": [{\"event_id\": 26, \"uid\": \"5d29e505c8af669ff7f1@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-02T10:00:00+00:00\", \"occurrence_end\": \"2026-03-02T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 26, \"uid\": \"5d29e505c8af669ff7f1@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-03T10:00:00+00:00\", \"occurrence_end\": \"2026-03-03T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 26, \"uid\": \"5d29e505c8af669ff7f1@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-04T10:00:00+00:00\", \"occurrence_end\": \"2026-03-04T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 26, \"uid\": \"5d29e505c8af669ff7f1@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-05T10:00:00+00:00\", \"occurrence_end\": \"2026-03-05T11:00:00", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T19:27:35+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/26/occurrences/2026-03-10", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:27:35 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:27:35+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/26/occurrences?from=2026-03-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:27:35 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "4906"}, "body": "{\"data\": [{\"event_id\": 26, \"uid\": \"5d29e505c8af669ff7f1@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-02T10:00:00+00:00\", \"occurrence_end\": \"2026-03-02T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 26, \"uid\": \"5d29e505c8af669ff7f1@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-03T10:00:00+00:00\", \"occurrence_end\": \"2026-03-03T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 26, \"uid\": \"5d29e505c8af669ff7f1@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-04T10:00:00+00:00\", \"occurrence_end\": \"2026-03-04T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 26, \"uid\": \"5d29e505c8af669ff7f1@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-05T10:00:00+00:00\", \"occurrence_end\": \"2026-03-05T11:00:00", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T19:27:36+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-exdate-import.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "323"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-exdate-import-001\nSUMMARY:Smoke EXDATE Import\nDTSTART;TZID=Europe/London:20260302T100000\nDTEND;TZID=Europe/London:20260302T110000\nRRULE:FREQ=DAILY;UNTIL=20260319T235959\nEXDATE;TZID=Europe/London:20260310T100000,20260311T100000\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 323, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:27:36 GMT", "ETag": "\"7675cb635bbcecc0\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:27:36+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:27:36 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "16789"}, "body": "{\"data\": [{\"id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"start_datetime\": \"2026-04-01T10:00:00+01:00\", \"end_datetime\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"eb53d018557c8fca\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T17:40:10+00:00\"}, {\"id\": 2, \"uid\": \"fixture-ce-002@calendar-wp-plugin\", \"title\": \"Office Closed\", \"description\": \"Public holiday closure.\", \"location\": \"HQ\", \"category\": \"Operations\", \"all_day_event\": true, \"start_datetime\": \"2026-05-04\", \"end_datetime\": \"2026-05-05\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"190a4", "body_bytes": 3927, "body_truncated": true}} +{"ts": "2026-03-30T19:27:36+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/18/occurrences?from=2026-03-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:27:36 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "4474"}, "body": "{\"data\": [{\"event_id\": 18, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-02T10:00:00+01:00\", \"occurrence_end\": \"2026-03-02T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 18, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-03T10:00:00+01:00\", \"occurrence_end\": \"2026-03-03T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 18, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-04T10:00:00+01:00\", \"occurrence_end\": \"2026-03-04T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 18, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-05T10:00:00+01:00\", \"occurrence_end\": \"2026-03-05T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 18, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE ", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T19:27:36+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-cancelled-occurrence.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "467"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-cancelled-occurrence-001\nSUMMARY:Smoke Cancelled Occurrence\nDTSTART;TZID=Europe/London:20260408T123000\nDTEND;TZID=Europe/London:20260408T133000\nRRULE:FREQ=WEEKLY\nEND:VEVENT\nBEGIN:VEVENT\nUID:smoke-cancelled-occurrence-001\nRECURRENCE-ID;TZID=Europe/London:20260506T123000\nDTSTART;TZID=Europe/London:20260506T123000\nDTEND;TZID=Europe/London:20260506T133000\nSTATUS:CANCELLED\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 467, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:27:36 GMT", "ETag": "\"4a343b62858a1e3f\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:27:36+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-cancelled-occurrence.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:27:36 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"4a343b62858a1e3f\"", "Content-Length": "359"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-cancelled-occurrence-001\r\nDTSTAMP:20260330T192736Z\r\nSUMMARY:Smoke Cancelled Occurrence\r\nDTSTART;TZID=Europe/London:20260408T123000\r\nDTEND;TZID=Europe/London:20260408T133000\r\nRRULE:FREQ=WEEKLY\r\nEXDATE;TZID=Europe/London:20260506T123000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 359, "body_truncated": false}} +{"ts": "2026-03-30T19:27:36+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:27:36 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "16789"}, "body": "{\"data\": [{\"id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"start_datetime\": \"2026-04-01T10:00:00+01:00\", \"end_datetime\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"eb53d018557c8fca\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T17:40:10+00:00\"}, {\"id\": 2, \"uid\": \"fixture-ce-002@calendar-wp-plugin\", \"title\": \"Office Closed\", \"description\": \"Public holiday closure.\", \"location\": \"HQ\", \"category\": \"Operations\", \"all_day_event\": true, \"start_datetime\": \"2026-05-04\", \"end_datetime\": \"2026-05-05\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"190a4", "body_bytes": 3927, "body_truncated": true}} +{"ts": "2026-03-30T19:27:36+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/19/occurrences?from=2026-05-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:27:36 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "892"}, "body": "{\"data\": [{\"event_id\": 19, \"uid\": \"smoke-cancelled-occurrence-001\", \"title\": \"Smoke Cancelled Occurrence\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-13T12:30:00+01:00\", \"occurrence_end\": \"2026-05-13T13:30:00+01:00\", \"repeat_type\": \"weekly\"}, {\"event_id\": 19, \"uid\": \"smoke-cancelled-occurrence-001\", \"title\": \"Smoke Cancelled Occurrence\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-20T12:30:00+01:00\", \"occurrence_end\": \"2026-05-20T13:30:00+01:00\", \"repeat_type\": \"weekly\"}, {\"event_id\": 19, \"uid\": \"smoke-cancelled-occurrence-001\", \"title\": \"Smoke Cancelled Occurrence\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-27T12:30:00+01:00\", \"occurrence_end\": \"2026-05-27T13:30:00+01:00\", \"repeat_type\": \"weekly\"}]}", "body_bytes": 892, "body_truncated": false}} +{"ts": "2026-03-30T19:27:36+00:00", "client": "127.0.0.1", "method": "GET", "path": "/calendar", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:27:36 GMT", "Content-Type": "text/html; charset=utf-8", "Content-Length": "43447"}, "body": "[omitted html payload: 3934 bytes]", "body_bytes": 3934, "body_truncated": true}} +{"ts": "2026-03-30T19:33:43+00:00", "client": "127.0.0.1", "method": "PROPFIND", "path": "/caldav/calendars/public/", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/xml; charset=utf-8", "Content-Length": "144", "Depth": "1", "Origin": "http://127.0.0.1:8080", "DNT": "1", "Authorization": "***", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "\n", "body_bytes": 144, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:33:43 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "8598"}, "body": "\n\n\n /caldav/calendars/public/\n \n \n Public Calendar\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/1.ics\n \n \n text/calendar; charset=utf-8\"eb53d018557c8fca\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/2.ics\n \n \n text/calendar; charset=utf-8\"190a40db82a13a9b\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/3.ics\n \n \n ...(truncated)", "body_bytes": 3919, "body_truncated": true}} +{"ts": "2026-03-30T19:33:43+00:00", "client": "127.0.0.1", "method": "REPORT", "path": "/caldav/calendars/public/", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/xml; charset=utf-8", "Content-Length": "720", "Depth": "1", "Origin": "http://127.0.0.1:8080", "DNT": "1", "Authorization": "***", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "\n/caldav/calendars/public/26.ics/caldav/calendars/public/25.ics/caldav/calendars/public/24.ics/caldav/calendars/public/smoke-cancelled-occurrence.ics/caldav/calendars/public/smoke-exdate-import.ics/caldav/calendars/public/smoke-monthly-last.ics/caldav/calendars/public/smoke-monthly-ordinal.ics/caldav/calendars/public/smoke-monthly-nth.ics/caldav/calendars/public/smoke-vtimezone.ics", "body_bytes": 720, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:33:43 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "7815"}, "body": "\n\n\n /caldav/calendars/public/smoke-vtimezone.ics\n \n \n \"227c6db7bb9af481\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-vtimezone-parser-001\r\nDTSTAMP:20260330T193343Z\r\nSUMMARY:Smoke VTIMEZONE Parse\r\nDTSTART;TZID=Europe/London:20260423T150000\r\nDTEND;TZID=Europe/London:20260423T160000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/smoke-monthly-nth.ics\n \n \n \"e59b158ebd60c483\"\n BEGIN:V ...(truncated)", "body_bytes": 3919, "body_truncated": true}} +{"ts": "2026-03-30T19:33:52+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:33:52 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "16789"}, "body": "{\"data\": [{\"id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"start_datetime\": \"2026-04-01T10:00:00+01:00\", \"end_datetime\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"eb53d018557c8fca\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T17:40:10+00:00\"}, {\"id\": 2, \"uid\": \"fixture-ce-002@calendar-wp-plugin\", \"title\": \"Office Closed\", \"description\": \"Public holiday closure.\", \"location\": \"HQ\", \"category\": \"Operations\", \"all_day_event\": true, \"start_datetime\": \"2026-05-04\", \"end_datetime\": \"2026-05-05\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"190a4", "body_bytes": 3927, "body_truncated": true}} +{"ts": "2026-03-30T19:33:52+00:00", "client": "127.0.0.1", "method": "GET", "path": "/calendar.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:33:52 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"f0955aca05643a7e\"", "Content-Length": "7784"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nCALSCALE:GREGORIAN\r\nX-WR-TIMEZONE:Europe/London\r\nBEGIN:VEVENT\r\nUID:fixture-ce-001@calendar-wp-plugin\r\nDTSTAMP:20260330T193352Z\r\nSUMMARY:Board Meeting\r\nDESCRIPTION:Quarterly board review.\r\nLOCATION:Room A\r\nCATEGORIES:Governance\r\nDTSTART;TZID=Europe/London:20260401T100000\r\nDTEND;TZID=Europe/London:20260401T113000\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-002@calendar-wp-plugin\r\nDTSTAMP:20260330T193352Z\r\nSUMMARY:Office Closed\r\nDESCRIPTION:Public holiday closure.\r\nLOCATION:HQ\r\nCATEGORIES:Operations\r\nDTSTART;VALUE=DATE:20260504\r\nDTEND;VALUE=DATE:20260505\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-003@calendar-wp-plugin\r\nDTSTAMP:20260330T193352Z\r\nSUMMARY:Daily Standup\r\nDESCRIPTION:15 minute sync.\r\nLOCATION:Online\r\nCATEGORIES:Team\r\nDTSTART;TZID=Europe/London:20260406T090000\r\nDTEND;TZID=Europe/London:20260406T091500\r\nRRULE:FREQ=DAILY;COUNT=10\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-004@calendar-wp-plugin\r\nDTSTAMP:20260330T193352Z\r\nSUMMARY:Community Lunch\r\nDESCRIPTION:Weekly community lunch.\r\nLOCATION:Cafeteria\r\nCATEGORIES:Community\r\nDTSTART;TZID=Europe/London:20260408T123000\r\nDTEND;TZID=Europe/London:20260408T133000\r\n ...(truncated)", "body_bytes": 3905, "body_truncated": true}} +{"ts": "2026-03-30T19:33:52+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/1.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:33:52 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"eb53d018557c8fca\"", "Content-Length": "364"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-001@calendar-wp-plugin\r\nDTSTAMP:20260330T193352Z\r\nSUMMARY:Board Meeting\r\nDESCRIPTION:Quarterly board review.\r\nLOCATION:Room A\r\nCATEGORIES:Governance\r\nDTSTART;TZID=Europe/London:20260401T100000\r\nDTEND;TZID=Europe/London:20260401T113000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 364, "body_truncated": false}} +{"ts": "2026-03-30T19:33:52+00:00", "client": "127.0.0.1", "method": "REPORT", "path": "/caldav/calendars/public/", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/xml; charset=utf-8", "Depth": "1", "Content-Length": "239"}, "body": "\n\n \n /caldav/calendars/public/4.ics\n\n", "body_bytes": 239, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:33:52 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "997"}, "body": "\n\n\n /caldav/calendars/public/4.ics\n \n \n \"691a56c3c4084218\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-004@calendar-wp-plugin\r\nDTSTAMP:20260330T193352Z\r\nSUMMARY:Community Lunch\r\nDESCRIPTION:Weekly community lunch.\r\nLOCATION:Cafeteria\r\nCATEGORIES:Community\r\nDTSTART;TZID=Europe/London:20260408T123000\r\nDTEND;TZID=Europe/London:20260408T133000\r\nRRULE:FREQ=WEEKLY\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n", "body_bytes": 997, "body_truncated": false}} +{"ts": "2026-03-30T19:33:52+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/10/occurrences/2026-04-17T14:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:33:52 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:33:52+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "X-WP-User": "admin", "Content-Length": "287"}, "body": "{\"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}", "body_bytes": 287, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:33:52 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "647"}, "body": "{\"data\": {\"id\": 27, \"uid\": \"fa1d8a27cfbf4aa40bd7@calendar-wp-plugin\", \"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_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"count\", \"repeat_count\": 3, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"a36c468e33bf09f5\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T19:33:52+00:00\"}}", "body_bytes": 647, "body_truncated": false}} +{"ts": "2026-03-30T19:33:52+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/27/occurrences?from=2026-04-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:33:52 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "937"}, "body": "{\"data\": [{\"event_id\": 27, \"uid\": \"fa1d8a27cfbf4aa40bd7@calendar-wp-plugin\", \"title\": \"Smoke Occurrences API\", \"description\": \"occ-endpoint\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-01T10:00:00+01:00\", \"occurrence_end\": \"2026-04-01T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 27, \"uid\": \"fa1d8a27cfbf4aa40bd7@calendar-wp-plugin\", \"title\": \"Smoke Occurrences API\", \"description\": \"occ-endpoint\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-02T10:00:00+01:00\", \"occurrence_end\": \"2026-04-02T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 27, \"uid\": \"fa1d8a27cfbf4aa40bd7@calendar-wp-plugin\", \"title\": \"Smoke Occurrences API\", \"description\": \"occ-endpoint\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-03T10:00:00+01:00\", \"occurrence_end\": \"2026-04-03T11:00:00+01:00\", \"repeat_type\": \"daily\"}]}", "body_bytes": 937, "body_truncated": false}} +{"ts": "2026-03-30T19:33:52+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/27/occurrences/2026-04-02T10:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:33:52 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:33:52+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/27/occurrences/2026-04-02T10:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:33:52 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:33:52+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/27/occurrences?from=2026-04-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:33:52 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "628"}, "body": "{\"data\": [{\"event_id\": 27, \"uid\": \"fa1d8a27cfbf4aa40bd7@calendar-wp-plugin\", \"title\": \"Smoke Occurrences API\", \"description\": \"occ-endpoint\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-01T10:00:00+01:00\", \"occurrence_end\": \"2026-04-01T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 27, \"uid\": \"fa1d8a27cfbf4aa40bd7@calendar-wp-plugin\", \"title\": \"Smoke Occurrences API\", \"description\": \"occ-endpoint\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-03T10:00:00+01:00\", \"occurrence_end\": \"2026-04-03T11:00:00+01:00\", \"repeat_type\": \"daily\"}]}", "body_bytes": 628, "body_truncated": false}} +{"ts": "2026-03-30T19:33:52+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-vtimezone.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "454"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN\nVERSION:2.0\nBEGIN:VTIMEZONE\nTZID:Europe/London\nBEGIN:STANDARD\nDTSTART:18471201T000000\nRRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=9\nTZOFFSETFROM:+0115\nTZOFFSETTO:+0000\nTZNAME:GMT\nEND:STANDARD\nEND:VTIMEZONE\nBEGIN:VEVENT\nUID:smoke-vtimezone-parser-001\nSUMMARY:Smoke VTIMEZONE Parse\nDTSTART;TZID=Europe/London:20260423T150000\nDTEND;TZID=Europe/London:20260423T160000\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 454, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:33:52 GMT", "ETag": "\"6350421b1463753a\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:33:52+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-vtimezone.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:33:52 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"6350421b1463753a\"", "Content-Length": "288"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-vtimezone-parser-001\r\nDTSTAMP:20260330T193352Z\r\nSUMMARY:Smoke VTIMEZONE Parse\r\nDTSTART;TZID=Europe/London:20260423T150000\r\nDTEND;TZID=Europe/London:20260423T160000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 288, "body_truncated": false}} +{"ts": "2026-03-30T19:33:52+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-monthly-nth.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "261"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-monthly-nth-001\nSUMMARY:Smoke Monthly Nth\nDTSTART;TZID=Europe/London:20260402T150000\nDTEND;TZID=Europe/London:20260402T160000\nRRULE:FREQ=MONTHLY;BYDAY=SU;BYSETPOS=4\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 261, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:33:52 GMT", "ETag": "\"5cfa99671f4b7beb\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:33:52+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-monthly-nth.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:33:52 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"5cfa99671f4b7beb\"", "Content-Length": "319"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-nth-001\r\nDTSTAMP:20260330T193352Z\r\nSUMMARY:Smoke Monthly Nth\r\nDTSTART;TZID=Europe/London:20260426T150000\r\nDTEND;TZID=Europe/London:20260426T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SU;BYSETPOS=4\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 319, "body_truncated": false}} +{"ts": "2026-03-30T19:33:52+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-monthly-ordinal.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "259"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-monthly-ordinal-001\nSUMMARY:Smoke Monthly Ordinal\nDTSTART;TZID=Europe/London:20260411T150000\nDTEND;TZID=Europe/London:20260411T160000\nRRULE:FREQ=MONTHLY;BYDAY=2SA\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 259, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:33:52 GMT", "ETag": "\"65be3c124db419c5\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:33:52+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-monthly-ordinal.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:33:52 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"65be3c124db419c5\"", "Content-Length": "327"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-ordinal-001\r\nDTSTAMP:20260330T193352Z\r\nSUMMARY:Smoke Monthly Ordinal\r\nDTSTART;TZID=Europe/London:20260411T150000\r\nDTEND;TZID=Europe/London:20260411T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SA;BYSETPOS=2\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 327, "body_truncated": false}} +{"ts": "2026-03-30T19:33:52+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-monthly-last.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "264"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-monthly-last-001\nSUMMARY:Smoke Monthly Last\nDTSTART;TZID=Europe/London:20260425T150000\nDTEND;TZID=Europe/London:20260425T160000\nRRULE:FREQ=MONTHLY;BYDAY=SA;BYSETPOS=-1\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 264, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:33:52 GMT", "ETag": "\"3905a2d554e7b5ba\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:33:52+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-monthly-last.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:33:52 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"3905a2d554e7b5ba\"", "Content-Length": "322"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-last-001\r\nDTSTAMP:20260330T193352Z\r\nSUMMARY:Smoke Monthly Last\r\nDTSTART;TZID=Europe/London:20260425T150000\r\nDTEND;TZID=Europe/London:20260425T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SA;BYSETPOS=-1\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 322, "body_truncated": false}} +{"ts": "2026-03-30T19:33:52+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "X-WP-User": "admin", "Content-Length": "362"}, "body": "{\"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\"}", "body_bytes": 362, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:33:52 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "673"}, "body": "{\"data\": {\"id\": 28, \"uid\": \"aea231c47fe7081e8d96@calendar-wp-plugin\", \"title\": \"Smoke 4th Sunday Anchor\", \"description\": \"anchor-normalization\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"start_datetime\": \"2026-05-24T15:00:00+01:00\", \"end_datetime\": \"2026-05-24T16:00:00+01:00\", \"repeat_type\": \"monthly\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"weekday_of_month\", \"repeat_nth_day\": null, \"repeat_nth_pos\": 4, \"repeat_nth_weekday\": 0, \"repeat_range_mode\": \"no_end\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"5820328298180a9e\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T19:33:52+00:00\"}}", "body_bytes": 673, "body_truncated": false}} +{"ts": "2026-03-30T19:33:52+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/28/occurrences/2026-05-24T15:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:33:52 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:33:52+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/28.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:33:52 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"c73446c499d71fd1\"", "Content-Length": "420"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:aea231c47fe7081e8d96@calendar-wp-plugin\r\nDTSTAMP:20260330T193352Z\r\nSUMMARY:Smoke 4th Sunday Anchor\r\nDESCRIPTION:anchor-normalization\r\nDTSTART;TZID=Europe/London:20260524T150000\r\nDTEND;TZID=Europe/London:20260524T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SU;BYSETPOS=4\r\nEXDATE;TZID=Europe/London:20260524T150000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 420, "body_truncated": false}} +{"ts": "2026-03-30T19:33:52+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "X-WP-User": "admin", "Content-Length": "295"}, "body": "{\"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\"}", "body_bytes": 295, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:33:52 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "655"}, "body": "{\"data\": {\"id\": 29, \"uid\": \"66c44f2dcba00c571e7c@calendar-wp-plugin\", \"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_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"until\", \"repeat_count\": null, \"repeat_until\": \"2026-03-19\", \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"82b317029b0183ad\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T19:33:52+00:00\"}}", "body_bytes": 655, "body_truncated": false}} +{"ts": "2026-03-30T19:33:53+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/29/occurrences/2026-03-11T11:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:33:53 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:33:53+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/29/occurrences?from=2026-03-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:33:53 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "5212"}, "body": "{\"data\": [{\"event_id\": 29, \"uid\": \"66c44f2dcba00c571e7c@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-02T10:00:00+00:00\", \"occurrence_end\": \"2026-03-02T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 29, \"uid\": \"66c44f2dcba00c571e7c@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-03T10:00:00+00:00\", \"occurrence_end\": \"2026-03-03T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 29, \"uid\": \"66c44f2dcba00c571e7c@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-04T10:00:00+00:00\", \"occurrence_end\": \"2026-03-04T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 29, \"uid\": \"66c44f2dcba00c571e7c@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-05T10:00:00+00:00\", \"occurrence_end\": \"2026-03-05T11:00:00", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T19:33:53+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/29/occurrences/2026-03-10", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:33:53 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:33:53+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/29/occurrences?from=2026-03-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:33:53 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "4906"}, "body": "{\"data\": [{\"event_id\": 29, \"uid\": \"66c44f2dcba00c571e7c@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-02T10:00:00+00:00\", \"occurrence_end\": \"2026-03-02T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 29, \"uid\": \"66c44f2dcba00c571e7c@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-03T10:00:00+00:00\", \"occurrence_end\": \"2026-03-03T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 29, \"uid\": \"66c44f2dcba00c571e7c@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-04T10:00:00+00:00\", \"occurrence_end\": \"2026-03-04T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 29, \"uid\": \"66c44f2dcba00c571e7c@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-05T10:00:00+00:00\", \"occurrence_end\": \"2026-03-05T11:00:00", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T19:33:53+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-exdate-import.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "323"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-exdate-import-001\nSUMMARY:Smoke EXDATE Import\nDTSTART;TZID=Europe/London:20260302T100000\nDTEND;TZID=Europe/London:20260302T110000\nRRULE:FREQ=DAILY;UNTIL=20260319T235959\nEXDATE;TZID=Europe/London:20260310T100000,20260311T100000\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 323, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:33:53 GMT", "ETag": "\"3a78837c720108cb\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:33:53+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:33:53 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "18740"}, "body": "{\"data\": [{\"id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"start_datetime\": \"2026-04-01T10:00:00+01:00\", \"end_datetime\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"eb53d018557c8fca\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T17:40:10+00:00\"}, {\"id\": 2, \"uid\": \"fixture-ce-002@calendar-wp-plugin\", \"title\": \"Office Closed\", \"description\": \"Public holiday closure.\", \"location\": \"HQ\", \"category\": \"Operations\", \"all_day_event\": true, \"start_datetime\": \"2026-05-04\", \"end_datetime\": \"2026-05-05\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"190a4", "body_bytes": 3927, "body_truncated": true}} +{"ts": "2026-03-30T19:33:53+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/18/occurrences?from=2026-03-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:33:53 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "4474"}, "body": "{\"data\": [{\"event_id\": 18, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-02T10:00:00+01:00\", \"occurrence_end\": \"2026-03-02T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 18, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-03T10:00:00+01:00\", \"occurrence_end\": \"2026-03-03T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 18, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-04T10:00:00+01:00\", \"occurrence_end\": \"2026-03-04T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 18, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-05T10:00:00+01:00\", \"occurrence_end\": \"2026-03-05T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 18, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE ", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T19:33:53+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-cancelled-occurrence.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "467"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-cancelled-occurrence-001\nSUMMARY:Smoke Cancelled Occurrence\nDTSTART;TZID=Europe/London:20260408T123000\nDTEND;TZID=Europe/London:20260408T133000\nRRULE:FREQ=WEEKLY\nEND:VEVENT\nBEGIN:VEVENT\nUID:smoke-cancelled-occurrence-001\nRECURRENCE-ID;TZID=Europe/London:20260506T123000\nDTSTART;TZID=Europe/London:20260506T123000\nDTEND;TZID=Europe/London:20260506T133000\nSTATUS:CANCELLED\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 467, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:33:53 GMT", "ETag": "\"cefb5ce31068ad03\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:33:53+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-cancelled-occurrence.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:33:53 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"cefb5ce31068ad03\"", "Content-Length": "359"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-cancelled-occurrence-001\r\nDTSTAMP:20260330T193353Z\r\nSUMMARY:Smoke Cancelled Occurrence\r\nDTSTART;TZID=Europe/London:20260408T123000\r\nDTEND;TZID=Europe/London:20260408T133000\r\nRRULE:FREQ=WEEKLY\r\nEXDATE;TZID=Europe/London:20260506T123000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 359, "body_truncated": false}} +{"ts": "2026-03-30T19:33:53+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:33:53 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "18740"}, "body": "{\"data\": [{\"id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"start_datetime\": \"2026-04-01T10:00:00+01:00\", \"end_datetime\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"eb53d018557c8fca\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T17:40:10+00:00\"}, {\"id\": 2, \"uid\": \"fixture-ce-002@calendar-wp-plugin\", \"title\": \"Office Closed\", \"description\": \"Public holiday closure.\", \"location\": \"HQ\", \"category\": \"Operations\", \"all_day_event\": true, \"start_datetime\": \"2026-05-04\", \"end_datetime\": \"2026-05-05\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"190a4", "body_bytes": 3927, "body_truncated": true}} +{"ts": "2026-03-30T19:33:53+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/19/occurrences?from=2026-05-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:33:53 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "892"}, "body": "{\"data\": [{\"event_id\": 19, \"uid\": \"smoke-cancelled-occurrence-001\", \"title\": \"Smoke Cancelled Occurrence\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-13T12:30:00+01:00\", \"occurrence_end\": \"2026-05-13T13:30:00+01:00\", \"repeat_type\": \"weekly\"}, {\"event_id\": 19, \"uid\": \"smoke-cancelled-occurrence-001\", \"title\": \"Smoke Cancelled Occurrence\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-20T12:30:00+01:00\", \"occurrence_end\": \"2026-05-20T13:30:00+01:00\", \"repeat_type\": \"weekly\"}, {\"event_id\": 19, \"uid\": \"smoke-cancelled-occurrence-001\", \"title\": \"Smoke Cancelled Occurrence\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-27T12:30:00+01:00\", \"occurrence_end\": \"2026-05-27T13:30:00+01:00\", \"repeat_type\": \"weekly\"}]}", "body_bytes": 892, "body_truncated": false}} +{"ts": "2026-03-30T19:33:53+00:00", "client": "127.0.0.1", "method": "GET", "path": "/calendar", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:33:53 GMT", "Content-Type": "text/html; charset=utf-8", "Content-Length": "43447"}, "body": "[omitted html payload: 3934 bytes]", "body_bytes": 3934, "body_truncated": true}} +{"ts": "2026-03-30T19:35:18+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:35:18 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "18740"}, "body": "{\"data\": [{\"id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"start_datetime\": \"2026-04-01T10:00:00+01:00\", \"end_datetime\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"eb53d018557c8fca\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T17:40:10+00:00\"}, {\"id\": 2, \"uid\": \"fixture-ce-002@calendar-wp-plugin\", \"title\": \"Office Closed\", \"description\": \"Public holiday closure.\", \"location\": \"HQ\", \"category\": \"Operations\", \"all_day_event\": true, \"start_datetime\": \"2026-05-04\", \"end_datetime\": \"2026-05-05\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"190a4", "body_bytes": 3927, "body_truncated": true}} +{"ts": "2026-03-30T19:35:18+00:00", "client": "127.0.0.1", "method": "GET", "path": "/calendar.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:35:18 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"9602848cf7b23ee1\"", "Content-Length": "8762"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nCALSCALE:GREGORIAN\r\nX-WR-TIMEZONE:Europe/London\r\nBEGIN:VEVENT\r\nUID:fixture-ce-001@calendar-wp-plugin\r\nDTSTAMP:20260330T193518Z\r\nSUMMARY:Board Meeting\r\nDESCRIPTION:Quarterly board review.\r\nLOCATION:Room A\r\nCATEGORIES:Governance\r\nDTSTART;TZID=Europe/London:20260401T100000\r\nDTEND;TZID=Europe/London:20260401T113000\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-002@calendar-wp-plugin\r\nDTSTAMP:20260330T193518Z\r\nSUMMARY:Office Closed\r\nDESCRIPTION:Public holiday closure.\r\nLOCATION:HQ\r\nCATEGORIES:Operations\r\nDTSTART;VALUE=DATE:20260504\r\nDTEND;VALUE=DATE:20260505\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-003@calendar-wp-plugin\r\nDTSTAMP:20260330T193518Z\r\nSUMMARY:Daily Standup\r\nDESCRIPTION:15 minute sync.\r\nLOCATION:Online\r\nCATEGORIES:Team\r\nDTSTART;TZID=Europe/London:20260406T090000\r\nDTEND;TZID=Europe/London:20260406T091500\r\nRRULE:FREQ=DAILY;COUNT=10\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-004@calendar-wp-plugin\r\nDTSTAMP:20260330T193518Z\r\nSUMMARY:Community Lunch\r\nDESCRIPTION:Weekly community lunch.\r\nLOCATION:Cafeteria\r\nCATEGORIES:Community\r\nDTSTART;TZID=Europe/London:20260408T123000\r\nDTEND;TZID=Europe/London:20260408T133000\r\n ...(truncated)", "body_bytes": 3905, "body_truncated": true}} +{"ts": "2026-03-30T19:35:18+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/1.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:35:18 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"eb53d018557c8fca\"", "Content-Length": "364"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-001@calendar-wp-plugin\r\nDTSTAMP:20260330T193518Z\r\nSUMMARY:Board Meeting\r\nDESCRIPTION:Quarterly board review.\r\nLOCATION:Room A\r\nCATEGORIES:Governance\r\nDTSTART;TZID=Europe/London:20260401T100000\r\nDTEND;TZID=Europe/London:20260401T113000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 364, "body_truncated": false}} +{"ts": "2026-03-30T19:35:18+00:00", "client": "127.0.0.1", "method": "REPORT", "path": "/caldav/calendars/public/", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/xml; charset=utf-8", "Depth": "1", "Content-Length": "239"}, "body": "\n\n \n /caldav/calendars/public/4.ics\n\n", "body_bytes": 239, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:35:18 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "997"}, "body": "\n\n\n /caldav/calendars/public/4.ics\n \n \n \"691a56c3c4084218\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-004@calendar-wp-plugin\r\nDTSTAMP:20260330T193518Z\r\nSUMMARY:Community Lunch\r\nDESCRIPTION:Weekly community lunch.\r\nLOCATION:Cafeteria\r\nCATEGORIES:Community\r\nDTSTART;TZID=Europe/London:20260408T123000\r\nDTEND;TZID=Europe/London:20260408T133000\r\nRRULE:FREQ=WEEKLY\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n", "body_bytes": 997, "body_truncated": false}} +{"ts": "2026-03-30T19:35:18+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/10/occurrences/2026-04-17T14:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:35:18 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:35:18+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "X-WP-User": "admin", "Content-Length": "287"}, "body": "{\"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}", "body_bytes": 287, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:35:18 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "647"}, "body": "{\"data\": {\"id\": 30, \"uid\": \"372f5751ecd17b5b1356@calendar-wp-plugin\", \"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_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"count\", \"repeat_count\": 3, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"9310ebe908eeec52\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T19:35:18+00:00\"}}", "body_bytes": 647, "body_truncated": false}} +{"ts": "2026-03-30T19:35:18+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/30/occurrences?from=2026-04-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:35:18 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "937"}, "body": "{\"data\": [{\"event_id\": 30, \"uid\": \"372f5751ecd17b5b1356@calendar-wp-plugin\", \"title\": \"Smoke Occurrences API\", \"description\": \"occ-endpoint\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-01T10:00:00+01:00\", \"occurrence_end\": \"2026-04-01T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 30, \"uid\": \"372f5751ecd17b5b1356@calendar-wp-plugin\", \"title\": \"Smoke Occurrences API\", \"description\": \"occ-endpoint\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-02T10:00:00+01:00\", \"occurrence_end\": \"2026-04-02T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 30, \"uid\": \"372f5751ecd17b5b1356@calendar-wp-plugin\", \"title\": \"Smoke Occurrences API\", \"description\": \"occ-endpoint\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-03T10:00:00+01:00\", \"occurrence_end\": \"2026-04-03T11:00:00+01:00\", \"repeat_type\": \"daily\"}]}", "body_bytes": 937, "body_truncated": false}} +{"ts": "2026-03-30T19:35:18+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/30/occurrences/2026-04-02T10:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:35:18 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:35:18+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/30/occurrences/2026-04-02T10:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:35:18 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:35:18+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/30/occurrences?from=2026-04-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:35:18 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "628"}, "body": "{\"data\": [{\"event_id\": 30, \"uid\": \"372f5751ecd17b5b1356@calendar-wp-plugin\", \"title\": \"Smoke Occurrences API\", \"description\": \"occ-endpoint\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-01T10:00:00+01:00\", \"occurrence_end\": \"2026-04-01T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 30, \"uid\": \"372f5751ecd17b5b1356@calendar-wp-plugin\", \"title\": \"Smoke Occurrences API\", \"description\": \"occ-endpoint\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-03T10:00:00+01:00\", \"occurrence_end\": \"2026-04-03T11:00:00+01:00\", \"repeat_type\": \"daily\"}]}", "body_bytes": 628, "body_truncated": false}} +{"ts": "2026-03-30T19:35:18+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-vtimezone.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "454"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN\nVERSION:2.0\nBEGIN:VTIMEZONE\nTZID:Europe/London\nBEGIN:STANDARD\nDTSTART:18471201T000000\nRRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=9\nTZOFFSETFROM:+0115\nTZOFFSETTO:+0000\nTZNAME:GMT\nEND:STANDARD\nEND:VTIMEZONE\nBEGIN:VEVENT\nUID:smoke-vtimezone-parser-001\nSUMMARY:Smoke VTIMEZONE Parse\nDTSTART;TZID=Europe/London:20260423T150000\nDTEND;TZID=Europe/London:20260423T160000\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 454, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:35:18 GMT", "ETag": "\"5bdb56afb01d0635\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:35:18+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-vtimezone.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:35:18 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"5bdb56afb01d0635\"", "Content-Length": "288"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-vtimezone-parser-001\r\nDTSTAMP:20260330T193518Z\r\nSUMMARY:Smoke VTIMEZONE Parse\r\nDTSTART;TZID=Europe/London:20260423T150000\r\nDTEND;TZID=Europe/London:20260423T160000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 288, "body_truncated": false}} +{"ts": "2026-03-30T19:35:18+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-monthly-nth.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "261"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-monthly-nth-001\nSUMMARY:Smoke Monthly Nth\nDTSTART;TZID=Europe/London:20260402T150000\nDTEND;TZID=Europe/London:20260402T160000\nRRULE:FREQ=MONTHLY;BYDAY=SU;BYSETPOS=4\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 261, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:35:18 GMT", "ETag": "\"d944656d7163bc58\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:35:18+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-monthly-nth.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:35:18 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"d944656d7163bc58\"", "Content-Length": "319"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-nth-001\r\nDTSTAMP:20260330T193518Z\r\nSUMMARY:Smoke Monthly Nth\r\nDTSTART;TZID=Europe/London:20260426T150000\r\nDTEND;TZID=Europe/London:20260426T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SU;BYSETPOS=4\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 319, "body_truncated": false}} +{"ts": "2026-03-30T19:35:19+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-monthly-ordinal.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "259"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-monthly-ordinal-001\nSUMMARY:Smoke Monthly Ordinal\nDTSTART;TZID=Europe/London:20260411T150000\nDTEND;TZID=Europe/London:20260411T160000\nRRULE:FREQ=MONTHLY;BYDAY=2SA\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 259, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:35:19 GMT", "ETag": "\"e4df6ba9e5bc5ebc\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:35:19+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-monthly-ordinal.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:35:19 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"e4df6ba9e5bc5ebc\"", "Content-Length": "327"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-ordinal-001\r\nDTSTAMP:20260330T193519Z\r\nSUMMARY:Smoke Monthly Ordinal\r\nDTSTART;TZID=Europe/London:20260411T150000\r\nDTEND;TZID=Europe/London:20260411T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SA;BYSETPOS=2\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 327, "body_truncated": false}} +{"ts": "2026-03-30T19:35:19+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-monthly-last.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "264"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-monthly-last-001\nSUMMARY:Smoke Monthly Last\nDTSTART;TZID=Europe/London:20260425T150000\nDTEND;TZID=Europe/London:20260425T160000\nRRULE:FREQ=MONTHLY;BYDAY=SA;BYSETPOS=-1\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 264, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:35:19 GMT", "ETag": "\"de42e0a9050f4298\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:35:19+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-monthly-last.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:35:19 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"de42e0a9050f4298\"", "Content-Length": "322"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-last-001\r\nDTSTAMP:20260330T193519Z\r\nSUMMARY:Smoke Monthly Last\r\nDTSTART;TZID=Europe/London:20260425T150000\r\nDTEND;TZID=Europe/London:20260425T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SA;BYSETPOS=-1\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 322, "body_truncated": false}} +{"ts": "2026-03-30T19:35:19+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "X-WP-User": "admin", "Content-Length": "362"}, "body": "{\"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\"}", "body_bytes": 362, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:35:19 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "673"}, "body": "{\"data\": {\"id\": 31, \"uid\": \"57916be3224eecf16f71@calendar-wp-plugin\", \"title\": \"Smoke 4th Sunday Anchor\", \"description\": \"anchor-normalization\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"start_datetime\": \"2026-05-24T15:00:00+01:00\", \"end_datetime\": \"2026-05-24T16:00:00+01:00\", \"repeat_type\": \"monthly\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"weekday_of_month\", \"repeat_nth_day\": null, \"repeat_nth_pos\": 4, \"repeat_nth_weekday\": 0, \"repeat_range_mode\": \"no_end\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"c671f8dde75b0a3e\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T19:35:19+00:00\"}}", "body_bytes": 673, "body_truncated": false}} +{"ts": "2026-03-30T19:35:19+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/31/occurrences/2026-05-24T15:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:35:19 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:35:19+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/31.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:35:19 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"1d4c253672ed1649\"", "Content-Length": "420"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:57916be3224eecf16f71@calendar-wp-plugin\r\nDTSTAMP:20260330T193519Z\r\nSUMMARY:Smoke 4th Sunday Anchor\r\nDESCRIPTION:anchor-normalization\r\nDTSTART;TZID=Europe/London:20260524T150000\r\nDTEND;TZID=Europe/London:20260524T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SU;BYSETPOS=4\r\nEXDATE;TZID=Europe/London:20260524T150000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 420, "body_truncated": false}} +{"ts": "2026-03-30T19:35:19+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "X-WP-User": "admin", "Content-Length": "295"}, "body": "{\"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\"}", "body_bytes": 295, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:35:19 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "655"}, "body": "{\"data\": {\"id\": 32, \"uid\": \"875b585d579132363b4b@calendar-wp-plugin\", \"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_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"until\", \"repeat_count\": null, \"repeat_until\": \"2026-03-19\", \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"bc93364ad884fc9a\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T19:35:19+00:00\"}}", "body_bytes": 655, "body_truncated": false}} +{"ts": "2026-03-30T19:35:19+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/32/occurrences/2026-03-11T11:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:35:19 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:35:19+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/32/occurrences?from=2026-03-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:35:19 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "5212"}, "body": "{\"data\": [{\"event_id\": 32, \"uid\": \"875b585d579132363b4b@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-02T10:00:00+00:00\", \"occurrence_end\": \"2026-03-02T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 32, \"uid\": \"875b585d579132363b4b@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-03T10:00:00+00:00\", \"occurrence_end\": \"2026-03-03T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 32, \"uid\": \"875b585d579132363b4b@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-04T10:00:00+00:00\", \"occurrence_end\": \"2026-03-04T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 32, \"uid\": \"875b585d579132363b4b@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-05T10:00:00+00:00\", \"occurrence_end\": \"2026-03-05T11:00:00", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T19:35:19+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/32/occurrences/2026-03-10", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:35:19 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:35:19+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/32/occurrences?from=2026-03-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:35:19 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "4906"}, "body": "{\"data\": [{\"event_id\": 32, \"uid\": \"875b585d579132363b4b@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-02T10:00:00+00:00\", \"occurrence_end\": \"2026-03-02T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 32, \"uid\": \"875b585d579132363b4b@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-03T10:00:00+00:00\", \"occurrence_end\": \"2026-03-03T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 32, \"uid\": \"875b585d579132363b4b@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-04T10:00:00+00:00\", \"occurrence_end\": \"2026-03-04T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 32, \"uid\": \"875b585d579132363b4b@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-05T10:00:00+00:00\", \"occurrence_end\": \"2026-03-05T11:00:00", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T19:35:19+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-exdate-import.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "323"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-exdate-import-001\nSUMMARY:Smoke EXDATE Import\nDTSTART;TZID=Europe/London:20260302T100000\nDTEND;TZID=Europe/London:20260302T110000\nRRULE:FREQ=DAILY;UNTIL=20260319T235959\nEXDATE;TZID=Europe/London:20260310T100000,20260311T100000\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 323, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:35:19 GMT", "ETag": "\"7a677c31a5560dce\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:35:19+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:35:19 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "20691"}, "body": "{\"data\": [{\"id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"start_datetime\": \"2026-04-01T10:00:00+01:00\", \"end_datetime\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"eb53d018557c8fca\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T17:40:10+00:00\"}, {\"id\": 2, \"uid\": \"fixture-ce-002@calendar-wp-plugin\", \"title\": \"Office Closed\", \"description\": \"Public holiday closure.\", \"location\": \"HQ\", \"category\": \"Operations\", \"all_day_event\": true, \"start_datetime\": \"2026-05-04\", \"end_datetime\": \"2026-05-05\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"190a4", "body_bytes": 3927, "body_truncated": true}} +{"ts": "2026-03-30T19:35:19+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/18/occurrences?from=2026-03-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:35:19 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "4474"}, "body": "{\"data\": [{\"event_id\": 18, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-02T10:00:00+01:00\", \"occurrence_end\": \"2026-03-02T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 18, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-03T10:00:00+01:00\", \"occurrence_end\": \"2026-03-03T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 18, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-04T10:00:00+01:00\", \"occurrence_end\": \"2026-03-04T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 18, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-05T10:00:00+01:00\", \"occurrence_end\": \"2026-03-05T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 18, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE ", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T19:35:19+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-cancelled-occurrence.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "467"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-cancelled-occurrence-001\nSUMMARY:Smoke Cancelled Occurrence\nDTSTART;TZID=Europe/London:20260408T123000\nDTEND;TZID=Europe/London:20260408T133000\nRRULE:FREQ=WEEKLY\nEND:VEVENT\nBEGIN:VEVENT\nUID:smoke-cancelled-occurrence-001\nRECURRENCE-ID;TZID=Europe/London:20260506T123000\nDTSTART;TZID=Europe/London:20260506T123000\nDTEND;TZID=Europe/London:20260506T133000\nSTATUS:CANCELLED\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 467, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:35:19 GMT", "ETag": "\"0333f0a3bac7134c\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:35:19+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-cancelled-occurrence.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:35:19 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"0333f0a3bac7134c\"", "Content-Length": "359"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-cancelled-occurrence-001\r\nDTSTAMP:20260330T193519Z\r\nSUMMARY:Smoke Cancelled Occurrence\r\nDTSTART;TZID=Europe/London:20260408T123000\r\nDTEND;TZID=Europe/London:20260408T133000\r\nRRULE:FREQ=WEEKLY\r\nEXDATE;TZID=Europe/London:20260506T123000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 359, "body_truncated": false}} +{"ts": "2026-03-30T19:35:19+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:35:19 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "20691"}, "body": "{\"data\": [{\"id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"start_datetime\": \"2026-04-01T10:00:00+01:00\", \"end_datetime\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"eb53d018557c8fca\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T17:40:10+00:00\"}, {\"id\": 2, \"uid\": \"fixture-ce-002@calendar-wp-plugin\", \"title\": \"Office Closed\", \"description\": \"Public holiday closure.\", \"location\": \"HQ\", \"category\": \"Operations\", \"all_day_event\": true, \"start_datetime\": \"2026-05-04\", \"end_datetime\": \"2026-05-05\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"190a4", "body_bytes": 3927, "body_truncated": true}} +{"ts": "2026-03-30T19:35:19+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/19/occurrences?from=2026-05-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:35:19 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "892"}, "body": "{\"data\": [{\"event_id\": 19, \"uid\": \"smoke-cancelled-occurrence-001\", \"title\": \"Smoke Cancelled Occurrence\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-13T12:30:00+01:00\", \"occurrence_end\": \"2026-05-13T13:30:00+01:00\", \"repeat_type\": \"weekly\"}, {\"event_id\": 19, \"uid\": \"smoke-cancelled-occurrence-001\", \"title\": \"Smoke Cancelled Occurrence\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-20T12:30:00+01:00\", \"occurrence_end\": \"2026-05-20T13:30:00+01:00\", \"repeat_type\": \"weekly\"}, {\"event_id\": 19, \"uid\": \"smoke-cancelled-occurrence-001\", \"title\": \"Smoke Cancelled Occurrence\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-27T12:30:00+01:00\", \"occurrence_end\": \"2026-05-27T13:30:00+01:00\", \"repeat_type\": \"weekly\"}]}", "body_bytes": 892, "body_truncated": false}} +{"ts": "2026-03-30T19:35:19+00:00", "client": "127.0.0.1", "method": "GET", "path": "/calendar", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:35:19 GMT", "Content-Type": "text/html; charset=utf-8", "Content-Length": "43447"}, "body": "[omitted html payload: 3934 bytes]", "body_bytes": 3934, "body_truncated": true}} +{"ts": "2026-03-30T19:37:42+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 401, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:37:42 GMT", "WWW-Authenticate": "Basic realm=\"calendar-caldav-fixture\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:37:42+00:00", "client": "127.0.0.1", "method": "OPTIONS", "path": "/caldav/", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:37:42 GMT", "DAV": "1, 2, calendar-access", "Allow": "OPTIONS, PROPFIND, REPORT, GET, PUT, DELETE"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:37:42+00:00", "client": "127.0.0.1", "method": "PROPFIND", "path": "/caldav/", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Depth": "1", "Content-Type": "application/xml", "Content-Length": "117"}, "body": "", "body_bytes": 117, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:37:42 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "1348"}, "body": "\n\n\n /caldav/\n \n \n /caldav/principals/user/\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/principals/user/\n \n \n /caldav/calendars/\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/\n \n \n \n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/\n \n \n Public Calendar", "body_bytes": 119, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:37:42 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "445"}, "body": "\n\n\n /caldav/principals/user/\n \n \n /caldav/calendars/\n \n HTTP/1.1 200 OK\n \n\n\n", "body_bytes": 445, "body_truncated": false}} +{"ts": "2026-03-30T19:37:42+00:00", "client": "127.0.0.1", "method": "PROPFIND", "path": "/caldav/calendars/", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Depth": "1", "Content-Type": "application/xml", "Content-Length": "167"}, "body": "", "body_bytes": 167, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:37:42 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "728"}, "body": "\n\n\n /caldav/calendars/\n \n \n \n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/\n \n \n Public Calendar\n \n HTTP/1.1 200 OK\n \n\n\n", "body_bytes": 728, "body_truncated": false}} +{"ts": "2026-03-30T19:43:23+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:43:23 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "20691"}, "body": "{\"data\": [{\"id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"start_datetime\": \"2026-04-01T10:00:00+01:00\", \"end_datetime\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"eb53d018557c8fca\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T17:40:10+00:00\"}, {\"id\": 2, \"uid\": \"fixture-ce-002@calendar-wp-plugin\", \"title\": \"Office Closed\", \"description\": \"Public holiday closure.\", \"location\": \"HQ\", \"category\": \"Operations\", \"all_day_event\": true, \"start_datetime\": \"2026-05-04\", \"end_datetime\": \"2026-05-05\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"190a4", "body_bytes": 3927, "body_truncated": true}} +{"ts": "2026-03-30T19:43:23+00:00", "client": "127.0.0.1", "method": "GET", "path": "/calendar.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:43:23 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"fe1ebad7407f5a79\"", "Content-Length": "9740"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nCALSCALE:GREGORIAN\r\nX-WR-TIMEZONE:Europe/London\r\nBEGIN:VEVENT\r\nUID:fixture-ce-001@calendar-wp-plugin\r\nDTSTAMP:20260330T194323Z\r\nSUMMARY:Board Meeting\r\nDESCRIPTION:Quarterly board review.\r\nLOCATION:Room A\r\nCATEGORIES:Governance\r\nDTSTART;TZID=Europe/London:20260401T100000\r\nDTEND;TZID=Europe/London:20260401T113000\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-002@calendar-wp-plugin\r\nDTSTAMP:20260330T194323Z\r\nSUMMARY:Office Closed\r\nDESCRIPTION:Public holiday closure.\r\nLOCATION:HQ\r\nCATEGORIES:Operations\r\nDTSTART;VALUE=DATE:20260504\r\nDTEND;VALUE=DATE:20260505\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-003@calendar-wp-plugin\r\nDTSTAMP:20260330T194323Z\r\nSUMMARY:Daily Standup\r\nDESCRIPTION:15 minute sync.\r\nLOCATION:Online\r\nCATEGORIES:Team\r\nDTSTART;TZID=Europe/London:20260406T090000\r\nDTEND;TZID=Europe/London:20260406T091500\r\nRRULE:FREQ=DAILY;COUNT=10\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-004@calendar-wp-plugin\r\nDTSTAMP:20260330T194323Z\r\nSUMMARY:Community Lunch\r\nDESCRIPTION:Weekly community lunch.\r\nLOCATION:Cafeteria\r\nCATEGORIES:Community\r\nDTSTART;TZID=Europe/London:20260408T123000\r\nDTEND;TZID=Europe/London:20260408T133000\r\n ...(truncated)", "body_bytes": 3905, "body_truncated": true}} +{"ts": "2026-03-30T19:43:23+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/1.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:43:23 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"eb53d018557c8fca\"", "Content-Length": "364"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-001@calendar-wp-plugin\r\nDTSTAMP:20260330T194323Z\r\nSUMMARY:Board Meeting\r\nDESCRIPTION:Quarterly board review.\r\nLOCATION:Room A\r\nCATEGORIES:Governance\r\nDTSTART;TZID=Europe/London:20260401T100000\r\nDTEND;TZID=Europe/London:20260401T113000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 364, "body_truncated": false}} +{"ts": "2026-03-30T19:43:23+00:00", "client": "127.0.0.1", "method": "REPORT", "path": "/caldav/calendars/public/", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/xml; charset=utf-8", "Depth": "1", "Content-Length": "239"}, "body": "\n\n \n /caldav/calendars/public/4.ics\n\n", "body_bytes": 239, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:43:23 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "997"}, "body": "\n\n\n /caldav/calendars/public/4.ics\n \n \n \"691a56c3c4084218\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-004@calendar-wp-plugin\r\nDTSTAMP:20260330T194323Z\r\nSUMMARY:Community Lunch\r\nDESCRIPTION:Weekly community lunch.\r\nLOCATION:Cafeteria\r\nCATEGORIES:Community\r\nDTSTART;TZID=Europe/London:20260408T123000\r\nDTEND;TZID=Europe/London:20260408T133000\r\nRRULE:FREQ=WEEKLY\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n", "body_bytes": 997, "body_truncated": false}} +{"ts": "2026-03-30T19:43:23+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/10/occurrences/2026-04-17T14:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:43:23 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:43:23+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "X-WP-User": "admin", "Content-Length": "287"}, "body": "{\"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}", "body_bytes": 287, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:43:23 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "647"}, "body": "{\"data\": {\"id\": 33, \"uid\": \"9ba79385ddc86c0ed142@calendar-wp-plugin\", \"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_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"count\", \"repeat_count\": 3, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"47d5fe1d950c41e8\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T19:43:23+00:00\"}}", "body_bytes": 647, "body_truncated": false}} +{"ts": "2026-03-30T19:43:23+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/33/occurrences?from=2026-04-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:43:23 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "937"}, "body": "{\"data\": [{\"event_id\": 33, \"uid\": \"9ba79385ddc86c0ed142@calendar-wp-plugin\", \"title\": \"Smoke Occurrences API\", \"description\": \"occ-endpoint\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-01T10:00:00+01:00\", \"occurrence_end\": \"2026-04-01T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 33, \"uid\": \"9ba79385ddc86c0ed142@calendar-wp-plugin\", \"title\": \"Smoke Occurrences API\", \"description\": \"occ-endpoint\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-02T10:00:00+01:00\", \"occurrence_end\": \"2026-04-02T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 33, \"uid\": \"9ba79385ddc86c0ed142@calendar-wp-plugin\", \"title\": \"Smoke Occurrences API\", \"description\": \"occ-endpoint\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-03T10:00:00+01:00\", \"occurrence_end\": \"2026-04-03T11:00:00+01:00\", \"repeat_type\": \"daily\"}]}", "body_bytes": 937, "body_truncated": false}} +{"ts": "2026-03-30T19:43:23+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/33/occurrences/2026-04-02T10:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:43:23 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:43:23+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/33/occurrences/2026-04-02T10:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:43:23 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:43:23+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/33/occurrences?from=2026-04-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:43:23 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "628"}, "body": "{\"data\": [{\"event_id\": 33, \"uid\": \"9ba79385ddc86c0ed142@calendar-wp-plugin\", \"title\": \"Smoke Occurrences API\", \"description\": \"occ-endpoint\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-01T10:00:00+01:00\", \"occurrence_end\": \"2026-04-01T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 33, \"uid\": \"9ba79385ddc86c0ed142@calendar-wp-plugin\", \"title\": \"Smoke Occurrences API\", \"description\": \"occ-endpoint\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-03T10:00:00+01:00\", \"occurrence_end\": \"2026-04-03T11:00:00+01:00\", \"repeat_type\": \"daily\"}]}", "body_bytes": 628, "body_truncated": false}} +{"ts": "2026-03-30T19:43:23+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-vtimezone.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "454"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN\nVERSION:2.0\nBEGIN:VTIMEZONE\nTZID:Europe/London\nBEGIN:STANDARD\nDTSTART:18471201T000000\nRRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=9\nTZOFFSETFROM:+0115\nTZOFFSETTO:+0000\nTZNAME:GMT\nEND:STANDARD\nEND:VTIMEZONE\nBEGIN:VEVENT\nUID:smoke-vtimezone-parser-001\nSUMMARY:Smoke VTIMEZONE Parse\nDTSTART;TZID=Europe/London:20260423T150000\nDTEND;TZID=Europe/London:20260423T160000\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 454, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:43:23 GMT", "ETag": "\"428b0774334217a0\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:43:23+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-vtimezone.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:43:23 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"428b0774334217a0\"", "Content-Length": "288"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-vtimezone-parser-001\r\nDTSTAMP:20260330T194323Z\r\nSUMMARY:Smoke VTIMEZONE Parse\r\nDTSTART;TZID=Europe/London:20260423T150000\r\nDTEND;TZID=Europe/London:20260423T160000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 288, "body_truncated": false}} +{"ts": "2026-03-30T19:43:23+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-monthly-nth.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "261"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-monthly-nth-001\nSUMMARY:Smoke Monthly Nth\nDTSTART;TZID=Europe/London:20260402T150000\nDTEND;TZID=Europe/London:20260402T160000\nRRULE:FREQ=MONTHLY;BYDAY=SU;BYSETPOS=4\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 261, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:43:23 GMT", "ETag": "\"566c179900289f19\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:43:23+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-monthly-nth.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:43:23 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"566c179900289f19\"", "Content-Length": "319"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-nth-001\r\nDTSTAMP:20260330T194323Z\r\nSUMMARY:Smoke Monthly Nth\r\nDTSTART;TZID=Europe/London:20260426T150000\r\nDTEND;TZID=Europe/London:20260426T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SU;BYSETPOS=4\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 319, "body_truncated": false}} +{"ts": "2026-03-30T19:43:23+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-monthly-ordinal.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "259"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-monthly-ordinal-001\nSUMMARY:Smoke Monthly Ordinal\nDTSTART;TZID=Europe/London:20260411T150000\nDTEND;TZID=Europe/London:20260411T160000\nRRULE:FREQ=MONTHLY;BYDAY=2SA\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 259, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:43:23 GMT", "ETag": "\"5f03a7c2f0425eed\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:43:23+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-monthly-ordinal.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:43:23 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"5f03a7c2f0425eed\"", "Content-Length": "327"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-ordinal-001\r\nDTSTAMP:20260330T194323Z\r\nSUMMARY:Smoke Monthly Ordinal\r\nDTSTART;TZID=Europe/London:20260411T150000\r\nDTEND;TZID=Europe/London:20260411T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SA;BYSETPOS=2\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 327, "body_truncated": false}} +{"ts": "2026-03-30T19:43:23+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-monthly-last.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "264"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-monthly-last-001\nSUMMARY:Smoke Monthly Last\nDTSTART;TZID=Europe/London:20260425T150000\nDTEND;TZID=Europe/London:20260425T160000\nRRULE:FREQ=MONTHLY;BYDAY=SA;BYSETPOS=-1\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 264, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:43:23 GMT", "ETag": "\"cbde1523187e131a\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:43:24+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-monthly-last.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:43:24 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"cbde1523187e131a\"", "Content-Length": "322"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-last-001\r\nDTSTAMP:20260330T194324Z\r\nSUMMARY:Smoke Monthly Last\r\nDTSTART;TZID=Europe/London:20260425T150000\r\nDTEND;TZID=Europe/London:20260425T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SA;BYSETPOS=-1\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 322, "body_truncated": false}} +{"ts": "2026-03-30T19:43:24+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "X-WP-User": "admin", "Content-Length": "362"}, "body": "{\"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\"}", "body_bytes": 362, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:43:24 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "673"}, "body": "{\"data\": {\"id\": 34, \"uid\": \"6792256d04b9f2249471@calendar-wp-plugin\", \"title\": \"Smoke 4th Sunday Anchor\", \"description\": \"anchor-normalization\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"start_datetime\": \"2026-05-24T15:00:00+01:00\", \"end_datetime\": \"2026-05-24T16:00:00+01:00\", \"repeat_type\": \"monthly\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"weekday_of_month\", \"repeat_nth_day\": null, \"repeat_nth_pos\": 4, \"repeat_nth_weekday\": 0, \"repeat_range_mode\": \"no_end\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"09fbd59129f4f866\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T19:43:24+00:00\"}}", "body_bytes": 673, "body_truncated": false}} +{"ts": "2026-03-30T19:43:24+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/34/occurrences/2026-05-24T15:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:43:24 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:43:24+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/34.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:43:24 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"cccf56daf59f3994\"", "Content-Length": "420"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:6792256d04b9f2249471@calendar-wp-plugin\r\nDTSTAMP:20260330T194324Z\r\nSUMMARY:Smoke 4th Sunday Anchor\r\nDESCRIPTION:anchor-normalization\r\nDTSTART;TZID=Europe/London:20260524T150000\r\nDTEND;TZID=Europe/London:20260524T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SU;BYSETPOS=4\r\nEXDATE;TZID=Europe/London:20260524T150000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 420, "body_truncated": false}} +{"ts": "2026-03-30T19:43:24+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "X-WP-User": "admin", "Content-Length": "295"}, "body": "{\"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\"}", "body_bytes": 295, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:43:24 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "655"}, "body": "{\"data\": {\"id\": 35, \"uid\": \"999a1dcfe70585b069f1@calendar-wp-plugin\", \"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_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"until\", \"repeat_count\": null, \"repeat_until\": \"2026-03-19\", \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"664c73b2a2759081\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T19:43:24+00:00\"}}", "body_bytes": 655, "body_truncated": false}} +{"ts": "2026-03-30T19:43:24+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/35/occurrences/2026-03-11T11:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:43:24 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:43:24+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/35/occurrences?from=2026-03-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:43:24 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "5212"}, "body": "{\"data\": [{\"event_id\": 35, \"uid\": \"999a1dcfe70585b069f1@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-02T10:00:00+00:00\", \"occurrence_end\": \"2026-03-02T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 35, \"uid\": \"999a1dcfe70585b069f1@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-03T10:00:00+00:00\", \"occurrence_end\": \"2026-03-03T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 35, \"uid\": \"999a1dcfe70585b069f1@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-04T10:00:00+00:00\", \"occurrence_end\": \"2026-03-04T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 35, \"uid\": \"999a1dcfe70585b069f1@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-05T10:00:00+00:00\", \"occurrence_end\": \"2026-03-05T11:00:00", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T19:43:24+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/35/occurrences/2026-03-10", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:43:24 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:43:24+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/35/occurrences?from=2026-03-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:43:24 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "4906"}, "body": "{\"data\": [{\"event_id\": 35, \"uid\": \"999a1dcfe70585b069f1@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-02T10:00:00+00:00\", \"occurrence_end\": \"2026-03-02T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 35, \"uid\": \"999a1dcfe70585b069f1@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-03T10:00:00+00:00\", \"occurrence_end\": \"2026-03-03T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 35, \"uid\": \"999a1dcfe70585b069f1@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-04T10:00:00+00:00\", \"occurrence_end\": \"2026-03-04T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 35, \"uid\": \"999a1dcfe70585b069f1@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-05T10:00:00+00:00\", \"occurrence_end\": \"2026-03-05T11:00:00", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T19:43:24+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-exdate-import.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "323"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-exdate-import-001\nSUMMARY:Smoke EXDATE Import\nDTSTART;TZID=Europe/London:20260302T100000\nDTEND;TZID=Europe/London:20260302T110000\nRRULE:FREQ=DAILY;UNTIL=20260319T235959\nEXDATE;TZID=Europe/London:20260310T100000,20260311T100000\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 323, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:43:24 GMT", "ETag": "\"46a37a3457db1c66\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:43:24+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:43:24 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "22642"}, "body": "{\"data\": [{\"id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"start_datetime\": \"2026-04-01T10:00:00+01:00\", \"end_datetime\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"eb53d018557c8fca\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T17:40:10+00:00\"}, {\"id\": 2, \"uid\": \"fixture-ce-002@calendar-wp-plugin\", \"title\": \"Office Closed\", \"description\": \"Public holiday closure.\", \"location\": \"HQ\", \"category\": \"Operations\", \"all_day_event\": true, \"start_datetime\": \"2026-05-04\", \"end_datetime\": \"2026-05-05\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"190a4", "body_bytes": 3927, "body_truncated": true}} +{"ts": "2026-03-30T19:43:24+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/18/occurrences?from=2026-03-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:43:24 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "4474"}, "body": "{\"data\": [{\"event_id\": 18, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-02T10:00:00+01:00\", \"occurrence_end\": \"2026-03-02T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 18, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-03T10:00:00+01:00\", \"occurrence_end\": \"2026-03-03T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 18, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-04T10:00:00+01:00\", \"occurrence_end\": \"2026-03-04T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 18, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-05T10:00:00+01:00\", \"occurrence_end\": \"2026-03-05T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 18, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE ", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T19:43:24+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-cancelled-occurrence.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "467"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-cancelled-occurrence-001\nSUMMARY:Smoke Cancelled Occurrence\nDTSTART;TZID=Europe/London:20260408T123000\nDTEND;TZID=Europe/London:20260408T133000\nRRULE:FREQ=WEEKLY\nEND:VEVENT\nBEGIN:VEVENT\nUID:smoke-cancelled-occurrence-001\nRECURRENCE-ID;TZID=Europe/London:20260506T123000\nDTSTART;TZID=Europe/London:20260506T123000\nDTEND;TZID=Europe/London:20260506T133000\nSTATUS:CANCELLED\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 467, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:43:24 GMT", "ETag": "\"5649d2d0c6078ad2\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:43:24+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-cancelled-occurrence.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:43:24 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"5649d2d0c6078ad2\"", "Content-Length": "359"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-cancelled-occurrence-001\r\nDTSTAMP:20260330T194324Z\r\nSUMMARY:Smoke Cancelled Occurrence\r\nDTSTART;TZID=Europe/London:20260408T123000\r\nDTEND;TZID=Europe/London:20260408T133000\r\nRRULE:FREQ=WEEKLY\r\nEXDATE;TZID=Europe/London:20260506T123000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 359, "body_truncated": false}} +{"ts": "2026-03-30T19:43:24+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:43:24 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "22642"}, "body": "{\"data\": [{\"id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"start_datetime\": \"2026-04-01T10:00:00+01:00\", \"end_datetime\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"eb53d018557c8fca\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T17:40:10+00:00\"}, {\"id\": 2, \"uid\": \"fixture-ce-002@calendar-wp-plugin\", \"title\": \"Office Closed\", \"description\": \"Public holiday closure.\", \"location\": \"HQ\", \"category\": \"Operations\", \"all_day_event\": true, \"start_datetime\": \"2026-05-04\", \"end_datetime\": \"2026-05-05\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"190a4", "body_bytes": 3927, "body_truncated": true}} +{"ts": "2026-03-30T19:43:24+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/19/occurrences?from=2026-05-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:43:24 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "892"}, "body": "{\"data\": [{\"event_id\": 19, \"uid\": \"smoke-cancelled-occurrence-001\", \"title\": \"Smoke Cancelled Occurrence\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-13T12:30:00+01:00\", \"occurrence_end\": \"2026-05-13T13:30:00+01:00\", \"repeat_type\": \"weekly\"}, {\"event_id\": 19, \"uid\": \"smoke-cancelled-occurrence-001\", \"title\": \"Smoke Cancelled Occurrence\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-20T12:30:00+01:00\", \"occurrence_end\": \"2026-05-20T13:30:00+01:00\", \"repeat_type\": \"weekly\"}, {\"event_id\": 19, \"uid\": \"smoke-cancelled-occurrence-001\", \"title\": \"Smoke Cancelled Occurrence\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-27T12:30:00+01:00\", \"occurrence_end\": \"2026-05-27T13:30:00+01:00\", \"repeat_type\": \"weekly\"}]}", "body_bytes": 892, "body_truncated": false}} +{"ts": "2026-03-30T19:43:24+00:00", "client": "127.0.0.1", "method": "GET", "path": "/calendar", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:43:24 GMT", "Content-Type": "text/html; charset=utf-8", "Content-Length": "43447"}, "body": "[omitted html payload: 3934 bytes]", "body_bytes": 3934, "body_truncated": true}} +{"ts": "2026-03-30T19:43:38+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:43:38 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "22642"}, "body": "{\"data\": [{\"id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"start_datetime\": \"2026-04-01T10:00:00+01:00\", \"end_datetime\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"eb53d018557c8fca\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T17:40:10+00:00\"}, {\"id\": 2, \"uid\": \"fixture-ce-002@calendar-wp-plugin\", \"title\": \"Office Closed\", \"description\": \"Public holiday closure.\", \"location\": \"HQ\", \"category\": \"Operations\", \"all_day_event\": true, \"start_datetime\": \"2026-05-04\", \"end_datetime\": \"2026-05-05\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"190a4", "body_bytes": 3927, "body_truncated": true}} +{"ts": "2026-03-30T19:43:38+00:00", "client": "127.0.0.1", "method": "GET", "path": "/calendar.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:43:38 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"65fca5862933b6d8\"", "Content-Length": "10718"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nCALSCALE:GREGORIAN\r\nX-WR-TIMEZONE:Europe/London\r\nBEGIN:VEVENT\r\nUID:fixture-ce-001@calendar-wp-plugin\r\nDTSTAMP:20260330T194338Z\r\nSUMMARY:Board Meeting\r\nDESCRIPTION:Quarterly board review.\r\nLOCATION:Room A\r\nCATEGORIES:Governance\r\nDTSTART;TZID=Europe/London:20260401T100000\r\nDTEND;TZID=Europe/London:20260401T113000\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-002@calendar-wp-plugin\r\nDTSTAMP:20260330T194338Z\r\nSUMMARY:Office Closed\r\nDESCRIPTION:Public holiday closure.\r\nLOCATION:HQ\r\nCATEGORIES:Operations\r\nDTSTART;VALUE=DATE:20260504\r\nDTEND;VALUE=DATE:20260505\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-003@calendar-wp-plugin\r\nDTSTAMP:20260330T194338Z\r\nSUMMARY:Daily Standup\r\nDESCRIPTION:15 minute sync.\r\nLOCATION:Online\r\nCATEGORIES:Team\r\nDTSTART;TZID=Europe/London:20260406T090000\r\nDTEND;TZID=Europe/London:20260406T091500\r\nRRULE:FREQ=DAILY;COUNT=10\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-004@calendar-wp-plugin\r\nDTSTAMP:20260330T194338Z\r\nSUMMARY:Community Lunch\r\nDESCRIPTION:Weekly community lunch.\r\nLOCATION:Cafeteria\r\nCATEGORIES:Community\r\nDTSTART;TZID=Europe/London:20260408T123000\r\nDTEND;TZID=Europe/London:20260408T133000\r\n ...(truncated)", "body_bytes": 3904, "body_truncated": true}} +{"ts": "2026-03-30T19:43:38+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/1.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:43:38 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"eb53d018557c8fca\"", "Content-Length": "364"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-001@calendar-wp-plugin\r\nDTSTAMP:20260330T194338Z\r\nSUMMARY:Board Meeting\r\nDESCRIPTION:Quarterly board review.\r\nLOCATION:Room A\r\nCATEGORIES:Governance\r\nDTSTART;TZID=Europe/London:20260401T100000\r\nDTEND;TZID=Europe/London:20260401T113000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 364, "body_truncated": false}} +{"ts": "2026-03-30T19:43:38+00:00", "client": "127.0.0.1", "method": "REPORT", "path": "/caldav/calendars/public/", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/xml; charset=utf-8", "Depth": "1", "Content-Length": "239"}, "body": "\n\n \n /caldav/calendars/public/4.ics\n\n", "body_bytes": 239, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:43:38 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "997"}, "body": "\n\n\n /caldav/calendars/public/4.ics\n \n \n \"691a56c3c4084218\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-004@calendar-wp-plugin\r\nDTSTAMP:20260330T194338Z\r\nSUMMARY:Community Lunch\r\nDESCRIPTION:Weekly community lunch.\r\nLOCATION:Cafeteria\r\nCATEGORIES:Community\r\nDTSTART;TZID=Europe/London:20260408T123000\r\nDTEND;TZID=Europe/London:20260408T133000\r\nRRULE:FREQ=WEEKLY\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n", "body_bytes": 997, "body_truncated": false}} +{"ts": "2026-03-30T19:43:38+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/10/occurrences/2026-04-17T14:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:43:38 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:43:38+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "X-WP-User": "admin", "Content-Length": "287"}, "body": "{\"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}", "body_bytes": 287, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:43:38 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "647"}, "body": "{\"data\": {\"id\": 36, \"uid\": \"9ba924e112c6328c7991@calendar-wp-plugin\", \"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_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"count\", \"repeat_count\": 3, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"631138c973aa434a\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T19:43:38+00:00\"}}", "body_bytes": 647, "body_truncated": false}} +{"ts": "2026-03-30T19:43:38+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/36/occurrences?from=2026-04-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:43:38 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "937"}, "body": "{\"data\": [{\"event_id\": 36, \"uid\": \"9ba924e112c6328c7991@calendar-wp-plugin\", \"title\": \"Smoke Occurrences API\", \"description\": \"occ-endpoint\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-01T10:00:00+01:00\", \"occurrence_end\": \"2026-04-01T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 36, \"uid\": \"9ba924e112c6328c7991@calendar-wp-plugin\", \"title\": \"Smoke Occurrences API\", \"description\": \"occ-endpoint\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-02T10:00:00+01:00\", \"occurrence_end\": \"2026-04-02T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 36, \"uid\": \"9ba924e112c6328c7991@calendar-wp-plugin\", \"title\": \"Smoke Occurrences API\", \"description\": \"occ-endpoint\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-03T10:00:00+01:00\", \"occurrence_end\": \"2026-04-03T11:00:00+01:00\", \"repeat_type\": \"daily\"}]}", "body_bytes": 937, "body_truncated": false}} +{"ts": "2026-03-30T19:43:38+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/36/occurrences/2026-04-02T10:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:43:38 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:43:38+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/36/occurrences/2026-04-02T10:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:43:38 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:43:38+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/36/occurrences?from=2026-04-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:43:38 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "628"}, "body": "{\"data\": [{\"event_id\": 36, \"uid\": \"9ba924e112c6328c7991@calendar-wp-plugin\", \"title\": \"Smoke Occurrences API\", \"description\": \"occ-endpoint\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-01T10:00:00+01:00\", \"occurrence_end\": \"2026-04-01T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 36, \"uid\": \"9ba924e112c6328c7991@calendar-wp-plugin\", \"title\": \"Smoke Occurrences API\", \"description\": \"occ-endpoint\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-03T10:00:00+01:00\", \"occurrence_end\": \"2026-04-03T11:00:00+01:00\", \"repeat_type\": \"daily\"}]}", "body_bytes": 628, "body_truncated": false}} +{"ts": "2026-03-30T19:43:38+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-vtimezone.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "454"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN\nVERSION:2.0\nBEGIN:VTIMEZONE\nTZID:Europe/London\nBEGIN:STANDARD\nDTSTART:18471201T000000\nRRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=9\nTZOFFSETFROM:+0115\nTZOFFSETTO:+0000\nTZNAME:GMT\nEND:STANDARD\nEND:VTIMEZONE\nBEGIN:VEVENT\nUID:smoke-vtimezone-parser-001\nSUMMARY:Smoke VTIMEZONE Parse\nDTSTART;TZID=Europe/London:20260423T150000\nDTEND;TZID=Europe/London:20260423T160000\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 454, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:43:38 GMT", "ETag": "\"d4b92d5e931bb87e\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:43:38+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-vtimezone.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:43:38 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"d4b92d5e931bb87e\"", "Content-Length": "288"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-vtimezone-parser-001\r\nDTSTAMP:20260330T194338Z\r\nSUMMARY:Smoke VTIMEZONE Parse\r\nDTSTART;TZID=Europe/London:20260423T150000\r\nDTEND;TZID=Europe/London:20260423T160000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 288, "body_truncated": false}} +{"ts": "2026-03-30T19:43:38+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-monthly-nth.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "261"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-monthly-nth-001\nSUMMARY:Smoke Monthly Nth\nDTSTART;TZID=Europe/London:20260402T150000\nDTEND;TZID=Europe/London:20260402T160000\nRRULE:FREQ=MONTHLY;BYDAY=SU;BYSETPOS=4\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 261, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:43:38 GMT", "ETag": "\"0ffe94cdaf00da8f\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:43:38+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-monthly-nth.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:43:38 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"0ffe94cdaf00da8f\"", "Content-Length": "319"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-nth-001\r\nDTSTAMP:20260330T194338Z\r\nSUMMARY:Smoke Monthly Nth\r\nDTSTART;TZID=Europe/London:20260426T150000\r\nDTEND;TZID=Europe/London:20260426T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SU;BYSETPOS=4\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 319, "body_truncated": false}} +{"ts": "2026-03-30T19:43:39+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-monthly-ordinal.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "259"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-monthly-ordinal-001\nSUMMARY:Smoke Monthly Ordinal\nDTSTART;TZID=Europe/London:20260411T150000\nDTEND;TZID=Europe/London:20260411T160000\nRRULE:FREQ=MONTHLY;BYDAY=2SA\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 259, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:43:39 GMT", "ETag": "\"deaa13cf4c78a1a2\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:43:39+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-monthly-ordinal.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:43:39 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"deaa13cf4c78a1a2\"", "Content-Length": "327"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-ordinal-001\r\nDTSTAMP:20260330T194339Z\r\nSUMMARY:Smoke Monthly Ordinal\r\nDTSTART;TZID=Europe/London:20260411T150000\r\nDTEND;TZID=Europe/London:20260411T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SA;BYSETPOS=2\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 327, "body_truncated": false}} +{"ts": "2026-03-30T19:43:39+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-monthly-last.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "264"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-monthly-last-001\nSUMMARY:Smoke Monthly Last\nDTSTART;TZID=Europe/London:20260425T150000\nDTEND;TZID=Europe/London:20260425T160000\nRRULE:FREQ=MONTHLY;BYDAY=SA;BYSETPOS=-1\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 264, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:43:39 GMT", "ETag": "\"b14dcf1638b1116b\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:43:39+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-monthly-last.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:43:39 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"b14dcf1638b1116b\"", "Content-Length": "322"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-last-001\r\nDTSTAMP:20260330T194339Z\r\nSUMMARY:Smoke Monthly Last\r\nDTSTART;TZID=Europe/London:20260425T150000\r\nDTEND;TZID=Europe/London:20260425T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SA;BYSETPOS=-1\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 322, "body_truncated": false}} +{"ts": "2026-03-30T19:43:39+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "X-WP-User": "admin", "Content-Length": "362"}, "body": "{\"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\"}", "body_bytes": 362, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:43:39 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "673"}, "body": "{\"data\": {\"id\": 37, \"uid\": \"dc869cdd20b57c4ce060@calendar-wp-plugin\", \"title\": \"Smoke 4th Sunday Anchor\", \"description\": \"anchor-normalization\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"start_datetime\": \"2026-05-24T15:00:00+01:00\", \"end_datetime\": \"2026-05-24T16:00:00+01:00\", \"repeat_type\": \"monthly\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"weekday_of_month\", \"repeat_nth_day\": null, \"repeat_nth_pos\": 4, \"repeat_nth_weekday\": 0, \"repeat_range_mode\": \"no_end\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"a42acbb8f1012c50\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T19:43:39+00:00\"}}", "body_bytes": 673, "body_truncated": false}} +{"ts": "2026-03-30T19:43:39+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/37/occurrences/2026-05-24T15:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:43:39 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:43:39+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/37.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:43:39 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"7ec2bdd719b303a2\"", "Content-Length": "420"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:dc869cdd20b57c4ce060@calendar-wp-plugin\r\nDTSTAMP:20260330T194339Z\r\nSUMMARY:Smoke 4th Sunday Anchor\r\nDESCRIPTION:anchor-normalization\r\nDTSTART;TZID=Europe/London:20260524T150000\r\nDTEND;TZID=Europe/London:20260524T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SU;BYSETPOS=4\r\nEXDATE;TZID=Europe/London:20260524T150000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 420, "body_truncated": false}} +{"ts": "2026-03-30T19:43:39+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "X-WP-User": "admin", "Content-Length": "295"}, "body": "{\"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\"}", "body_bytes": 295, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:43:39 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "655"}, "body": "{\"data\": {\"id\": 38, \"uid\": \"08597e0cd8484e4afa74@calendar-wp-plugin\", \"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_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"until\", \"repeat_count\": null, \"repeat_until\": \"2026-03-19\", \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"f35557f2b1ee363d\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T19:43:39+00:00\"}}", "body_bytes": 655, "body_truncated": false}} +{"ts": "2026-03-30T19:43:39+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/38/occurrences/2026-03-11T11:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:43:39 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:43:39+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/38/occurrences?from=2026-03-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:43:39 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "5212"}, "body": "{\"data\": [{\"event_id\": 38, \"uid\": \"08597e0cd8484e4afa74@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-02T10:00:00+00:00\", \"occurrence_end\": \"2026-03-02T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 38, \"uid\": \"08597e0cd8484e4afa74@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-03T10:00:00+00:00\", \"occurrence_end\": \"2026-03-03T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 38, \"uid\": \"08597e0cd8484e4afa74@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-04T10:00:00+00:00\", \"occurrence_end\": \"2026-03-04T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 38, \"uid\": \"08597e0cd8484e4afa74@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-05T10:00:00+00:00\", \"occurrence_end\": \"2026-03-05T11:00:00", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T19:43:39+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/38/occurrences/2026-03-10", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:43:39 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:43:39+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/38/occurrences?from=2026-03-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:43:39 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "4906"}, "body": "{\"data\": [{\"event_id\": 38, \"uid\": \"08597e0cd8484e4afa74@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-02T10:00:00+00:00\", \"occurrence_end\": \"2026-03-02T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 38, \"uid\": \"08597e0cd8484e4afa74@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-03T10:00:00+00:00\", \"occurrence_end\": \"2026-03-03T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 38, \"uid\": \"08597e0cd8484e4afa74@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-04T10:00:00+00:00\", \"occurrence_end\": \"2026-03-04T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 38, \"uid\": \"08597e0cd8484e4afa74@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-05T10:00:00+00:00\", \"occurrence_end\": \"2026-03-05T11:00:00", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T19:43:39+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-exdate-import.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "323"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-exdate-import-001\nSUMMARY:Smoke EXDATE Import\nDTSTART;TZID=Europe/London:20260302T100000\nDTEND;TZID=Europe/London:20260302T110000\nRRULE:FREQ=DAILY;UNTIL=20260319T235959\nEXDATE;TZID=Europe/London:20260310T100000,20260311T100000\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 323, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:43:39 GMT", "ETag": "\"0670fbaf6ffb7eb5\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:43:39+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:43:39 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "24593"}, "body": "{\"data\": [{\"id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"start_datetime\": \"2026-04-01T10:00:00+01:00\", \"end_datetime\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"eb53d018557c8fca\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T17:40:10+00:00\"}, {\"id\": 2, \"uid\": \"fixture-ce-002@calendar-wp-plugin\", \"title\": \"Office Closed\", \"description\": \"Public holiday closure.\", \"location\": \"HQ\", \"category\": \"Operations\", \"all_day_event\": true, \"start_datetime\": \"2026-05-04\", \"end_datetime\": \"2026-05-05\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"190a4", "body_bytes": 3927, "body_truncated": true}} +{"ts": "2026-03-30T19:43:39+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/18/occurrences?from=2026-03-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:43:39 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "4474"}, "body": "{\"data\": [{\"event_id\": 18, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-02T10:00:00+01:00\", \"occurrence_end\": \"2026-03-02T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 18, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-03T10:00:00+01:00\", \"occurrence_end\": \"2026-03-03T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 18, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-04T10:00:00+01:00\", \"occurrence_end\": \"2026-03-04T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 18, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-05T10:00:00+01:00\", \"occurrence_end\": \"2026-03-05T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 18, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE ", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T19:43:39+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-cancelled-occurrence.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "467"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-cancelled-occurrence-001\nSUMMARY:Smoke Cancelled Occurrence\nDTSTART;TZID=Europe/London:20260408T123000\nDTEND;TZID=Europe/London:20260408T133000\nRRULE:FREQ=WEEKLY\nEND:VEVENT\nBEGIN:VEVENT\nUID:smoke-cancelled-occurrence-001\nRECURRENCE-ID;TZID=Europe/London:20260506T123000\nDTSTART;TZID=Europe/London:20260506T123000\nDTEND;TZID=Europe/London:20260506T133000\nSTATUS:CANCELLED\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 467, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:43:39 GMT", "ETag": "\"69637d4b3fac5b1c\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:43:39+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-cancelled-occurrence.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:43:39 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"69637d4b3fac5b1c\"", "Content-Length": "359"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-cancelled-occurrence-001\r\nDTSTAMP:20260330T194339Z\r\nSUMMARY:Smoke Cancelled Occurrence\r\nDTSTART;TZID=Europe/London:20260408T123000\r\nDTEND;TZID=Europe/London:20260408T133000\r\nRRULE:FREQ=WEEKLY\r\nEXDATE;TZID=Europe/London:20260506T123000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 359, "body_truncated": false}} +{"ts": "2026-03-30T19:43:39+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:43:39 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "24593"}, "body": "{\"data\": [{\"id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"start_datetime\": \"2026-04-01T10:00:00+01:00\", \"end_datetime\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"eb53d018557c8fca\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T17:40:10+00:00\"}, {\"id\": 2, \"uid\": \"fixture-ce-002@calendar-wp-plugin\", \"title\": \"Office Closed\", \"description\": \"Public holiday closure.\", \"location\": \"HQ\", \"category\": \"Operations\", \"all_day_event\": true, \"start_datetime\": \"2026-05-04\", \"end_datetime\": \"2026-05-05\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"190a4", "body_bytes": 3927, "body_truncated": true}} +{"ts": "2026-03-30T19:43:39+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/19/occurrences?from=2026-05-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:43:39 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "892"}, "body": "{\"data\": [{\"event_id\": 19, \"uid\": \"smoke-cancelled-occurrence-001\", \"title\": \"Smoke Cancelled Occurrence\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-13T12:30:00+01:00\", \"occurrence_end\": \"2026-05-13T13:30:00+01:00\", \"repeat_type\": \"weekly\"}, {\"event_id\": 19, \"uid\": \"smoke-cancelled-occurrence-001\", \"title\": \"Smoke Cancelled Occurrence\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-20T12:30:00+01:00\", \"occurrence_end\": \"2026-05-20T13:30:00+01:00\", \"repeat_type\": \"weekly\"}, {\"event_id\": 19, \"uid\": \"smoke-cancelled-occurrence-001\", \"title\": \"Smoke Cancelled Occurrence\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-27T12:30:00+01:00\", \"occurrence_end\": \"2026-05-27T13:30:00+01:00\", \"repeat_type\": \"weekly\"}]}", "body_bytes": 892, "body_truncated": false}} +{"ts": "2026-03-30T19:43:39+00:00", "client": "127.0.0.1", "method": "GET", "path": "/calendar", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:43:39 GMT", "Content-Type": "text/html; charset=utf-8", "Content-Length": "43447"}, "body": "[omitted html payload: 3934 bytes]", "body_bytes": 3934, "body_truncated": true}} +{"ts": "2026-03-30T19:45:22+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:45:22 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "24593"}, "body": "{\"data\": [{\"id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"start_datetime\": \"2026-04-01T10:00:00+01:00\", \"end_datetime\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"eb53d018557c8fca\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T17:40:10+00:00\"}, {\"id\": 2, \"uid\": \"fixture-ce-002@calendar-wp-plugin\", \"title\": \"Office Closed\", \"description\": \"Public holiday closure.\", \"location\": \"HQ\", \"category\": \"Operations\", \"all_day_event\": true, \"start_datetime\": \"2026-05-04\", \"end_datetime\": \"2026-05-05\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"190a4", "body_bytes": 3927, "body_truncated": true}} +{"ts": "2026-03-30T19:45:22+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:45:22 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "24593"}, "body": "{\"data\": [{\"id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"start_datetime\": \"2026-04-01T10:00:00+01:00\", \"end_datetime\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"eb53d018557c8fca\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T17:40:10+00:00\"}, {\"id\": 2, \"uid\": \"fixture-ce-002@calendar-wp-plugin\", \"title\": \"Office Closed\", \"description\": \"Public holiday closure.\", \"location\": \"HQ\", \"category\": \"Operations\", \"all_day_event\": true, \"start_datetime\": \"2026-05-04\", \"end_datetime\": \"2026-05-05\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"190a4", "body_bytes": 3927, "body_truncated": true}} +{"ts": "2026-03-30T19:45:22+00:00", "client": "127.0.0.1", "method": "GET", "path": "/calendar.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:45:22 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"b9666df54fddc7d2\"", "Content-Length": "11696"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nCALSCALE:GREGORIAN\r\nX-WR-TIMEZONE:Europe/London\r\nBEGIN:VEVENT\r\nUID:fixture-ce-001@calendar-wp-plugin\r\nDTSTAMP:20260330T194522Z\r\nSUMMARY:Board Meeting\r\nDESCRIPTION:Quarterly board review.\r\nLOCATION:Room A\r\nCATEGORIES:Governance\r\nDTSTART;TZID=Europe/London:20260401T100000\r\nDTEND;TZID=Europe/London:20260401T113000\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-002@calendar-wp-plugin\r\nDTSTAMP:20260330T194522Z\r\nSUMMARY:Office Closed\r\nDESCRIPTION:Public holiday closure.\r\nLOCATION:HQ\r\nCATEGORIES:Operations\r\nDTSTART;VALUE=DATE:20260504\r\nDTEND;VALUE=DATE:20260505\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-003@calendar-wp-plugin\r\nDTSTAMP:20260330T194522Z\r\nSUMMARY:Daily Standup\r\nDESCRIPTION:15 minute sync.\r\nLOCATION:Online\r\nCATEGORIES:Team\r\nDTSTART;TZID=Europe/London:20260406T090000\r\nDTEND;TZID=Europe/London:20260406T091500\r\nRRULE:FREQ=DAILY;COUNT=10\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-004@calendar-wp-plugin\r\nDTSTAMP:20260330T194522Z\r\nSUMMARY:Community Lunch\r\nDESCRIPTION:Weekly community lunch.\r\nLOCATION:Cafeteria\r\nCATEGORIES:Community\r\nDTSTART;TZID=Europe/London:20260408T123000\r\nDTEND;TZID=Europe/London:20260408T133000\r\n ...(truncated)", "body_bytes": 3904, "body_truncated": true}} +{"ts": "2026-03-30T19:45:22+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 401, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:45:22 GMT", "WWW-Authenticate": "Basic realm=\"calendar-caldav-fixture\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:45:22+00:00", "client": "127.0.0.1", "method": "OPTIONS", "path": "/caldav/", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:45:22 GMT", "DAV": "1, 2, calendar-access", "Allow": "OPTIONS, PROPFIND, REPORT, GET, PUT, DELETE"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:45:22+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/1.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:45:22 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"eb53d018557c8fca\"", "Content-Length": "364"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-001@calendar-wp-plugin\r\nDTSTAMP:20260330T194522Z\r\nSUMMARY:Board Meeting\r\nDESCRIPTION:Quarterly board review.\r\nLOCATION:Room A\r\nCATEGORIES:Governance\r\nDTSTART;TZID=Europe/London:20260401T100000\r\nDTEND;TZID=Europe/London:20260401T113000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 364, "body_truncated": false}} +{"ts": "2026-03-30T19:45:22+00:00", "client": "127.0.0.1", "method": "PROPFIND", "path": "/caldav/", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Depth": "1", "Content-Type": "application/xml", "Content-Length": "117"}, "body": "", "body_bytes": 117, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:45:22 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "1348"}, "body": "\n\n\n /caldav/\n \n \n /caldav/principals/user/\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/principals/user/\n \n \n /caldav/calendars/\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/\n \n \n \n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/\n \n \n Public Calendar\n\n \n /caldav/calendars/public/4.ics\n\n", "body_bytes": 239, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:45:22 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "997"}, "body": "\n\n\n /caldav/calendars/public/4.ics\n \n \n \"691a56c3c4084218\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-004@calendar-wp-plugin\r\nDTSTAMP:20260330T194522Z\r\nSUMMARY:Community Lunch\r\nDESCRIPTION:Weekly community lunch.\r\nLOCATION:Cafeteria\r\nCATEGORIES:Community\r\nDTSTART;TZID=Europe/London:20260408T123000\r\nDTEND;TZID=Europe/London:20260408T133000\r\nRRULE:FREQ=WEEKLY\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n", "body_bytes": 997, "body_truncated": false}} +{"ts": "2026-03-30T19:45:22+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/10/occurrences/2026-04-17T14:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:45:22 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:45:22+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "X-WP-User": "admin", "Content-Length": "287"}, "body": "{\"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}", "body_bytes": 287, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:45:22 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "647"}, "body": "{\"data\": {\"id\": 39, \"uid\": \"5583f348e82b4d30498e@calendar-wp-plugin\", \"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_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"count\", \"repeat_count\": 3, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"6afb4a6b08afb64f\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T19:45:22+00:00\"}}", "body_bytes": 647, "body_truncated": false}} +{"ts": "2026-03-30T19:45:22+00:00", "client": "127.0.0.1", "method": "PROPFIND", "path": "/caldav/principals/user/", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Depth": "0", "Content-Type": "application/xml", "Content-Length": "119"}, "body": "", "body_bytes": 119, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:45:22 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "445"}, "body": "\n\n\n /caldav/principals/user/\n \n \n /caldav/calendars/\n \n HTTP/1.1 200 OK\n \n\n\n", "body_bytes": 445, "body_truncated": false}} +{"ts": "2026-03-30T19:45:22+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/39/occurrences?from=2026-04-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:45:22 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "937"}, "body": "{\"data\": [{\"event_id\": 39, \"uid\": \"5583f348e82b4d30498e@calendar-wp-plugin\", \"title\": \"Smoke Occurrences API\", \"description\": \"occ-endpoint\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-01T10:00:00+01:00\", \"occurrence_end\": \"2026-04-01T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 39, \"uid\": \"5583f348e82b4d30498e@calendar-wp-plugin\", \"title\": \"Smoke Occurrences API\", \"description\": \"occ-endpoint\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-02T10:00:00+01:00\", \"occurrence_end\": \"2026-04-02T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 39, \"uid\": \"5583f348e82b4d30498e@calendar-wp-plugin\", \"title\": \"Smoke Occurrences API\", \"description\": \"occ-endpoint\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-03T10:00:00+01:00\", \"occurrence_end\": \"2026-04-03T11:00:00+01:00\", \"repeat_type\": \"daily\"}]}", "body_bytes": 937, "body_truncated": false}} +{"ts": "2026-03-30T19:45:22+00:00", "client": "127.0.0.1", "method": "PROPFIND", "path": "/caldav/calendars/", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Depth": "1", "Content-Type": "application/xml", "Content-Length": "167"}, "body": "", "body_bytes": 167, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:45:22 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "728"}, "body": "\n\n\n /caldav/calendars/\n \n \n \n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/\n \n \n Public Calendar\n \n HTTP/1.1 200 OK\n \n\n\n", "body_bytes": 728, "body_truncated": false}} +{"ts": "2026-03-30T19:45:22+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/39/occurrences/2026-04-02T10:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:45:22 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:45:22+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/39/occurrences/2026-04-02T10:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:45:22 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:45:22+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/39/occurrences?from=2026-04-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:45:22 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "628"}, "body": "{\"data\": [{\"event_id\": 39, \"uid\": \"5583f348e82b4d30498e@calendar-wp-plugin\", \"title\": \"Smoke Occurrences API\", \"description\": \"occ-endpoint\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-01T10:00:00+01:00\", \"occurrence_end\": \"2026-04-01T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 39, \"uid\": \"5583f348e82b4d30498e@calendar-wp-plugin\", \"title\": \"Smoke Occurrences API\", \"description\": \"occ-endpoint\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-03T10:00:00+01:00\", \"occurrence_end\": \"2026-04-03T11:00:00+01:00\", \"repeat_type\": \"daily\"}]}", "body_bytes": 628, "body_truncated": false}} +{"ts": "2026-03-30T19:45:22+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-vtimezone.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "454"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN\nVERSION:2.0\nBEGIN:VTIMEZONE\nTZID:Europe/London\nBEGIN:STANDARD\nDTSTART:18471201T000000\nRRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=9\nTZOFFSETFROM:+0115\nTZOFFSETTO:+0000\nTZNAME:GMT\nEND:STANDARD\nEND:VTIMEZONE\nBEGIN:VEVENT\nUID:smoke-vtimezone-parser-001\nSUMMARY:Smoke VTIMEZONE Parse\nDTSTART;TZID=Europe/London:20260423T150000\nDTEND;TZID=Europe/London:20260423T160000\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 454, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:45:22 GMT", "ETag": "\"e54b1ce109a46cc8\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:45:22+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-vtimezone.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:45:22 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"e54b1ce109a46cc8\"", "Content-Length": "288"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-vtimezone-parser-001\r\nDTSTAMP:20260330T194522Z\r\nSUMMARY:Smoke VTIMEZONE Parse\r\nDTSTART;TZID=Europe/London:20260423T150000\r\nDTEND;TZID=Europe/London:20260423T160000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 288, "body_truncated": false}} +{"ts": "2026-03-30T19:45:22+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-monthly-nth.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "261"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-monthly-nth-001\nSUMMARY:Smoke Monthly Nth\nDTSTART;TZID=Europe/London:20260402T150000\nDTEND;TZID=Europe/London:20260402T160000\nRRULE:FREQ=MONTHLY;BYDAY=SU;BYSETPOS=4\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 261, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:45:22 GMT", "ETag": "\"3ca3370cf9a8bc3d\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:45:22+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-monthly-nth.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:45:22 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"3ca3370cf9a8bc3d\"", "Content-Length": "319"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-nth-001\r\nDTSTAMP:20260330T194522Z\r\nSUMMARY:Smoke Monthly Nth\r\nDTSTART;TZID=Europe/London:20260426T150000\r\nDTEND;TZID=Europe/London:20260426T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SU;BYSETPOS=4\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 319, "body_truncated": false}} +{"ts": "2026-03-30T19:45:22+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-monthly-ordinal.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "259"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-monthly-ordinal-001\nSUMMARY:Smoke Monthly Ordinal\nDTSTART;TZID=Europe/London:20260411T150000\nDTEND;TZID=Europe/London:20260411T160000\nRRULE:FREQ=MONTHLY;BYDAY=2SA\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 259, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:45:22 GMT", "ETag": "\"391d992cae22bea5\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:45:22+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-monthly-ordinal.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:45:22 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"391d992cae22bea5\"", "Content-Length": "327"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-ordinal-001\r\nDTSTAMP:20260330T194522Z\r\nSUMMARY:Smoke Monthly Ordinal\r\nDTSTART;TZID=Europe/London:20260411T150000\r\nDTEND;TZID=Europe/London:20260411T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SA;BYSETPOS=2\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 327, "body_truncated": false}} +{"ts": "2026-03-30T19:45:22+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-monthly-last.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "264"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-monthly-last-001\nSUMMARY:Smoke Monthly Last\nDTSTART;TZID=Europe/London:20260425T150000\nDTEND;TZID=Europe/London:20260425T160000\nRRULE:FREQ=MONTHLY;BYDAY=SA;BYSETPOS=-1\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 264, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:45:22 GMT", "ETag": "\"0f6f0463ee50274c\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:45:22+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-monthly-last.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:45:22 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"0f6f0463ee50274c\"", "Content-Length": "322"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-last-001\r\nDTSTAMP:20260330T194522Z\r\nSUMMARY:Smoke Monthly Last\r\nDTSTART;TZID=Europe/London:20260425T150000\r\nDTEND;TZID=Europe/London:20260425T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SA;BYSETPOS=-1\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 322, "body_truncated": false}} +{"ts": "2026-03-30T19:45:22+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "X-WP-User": "admin", "Content-Length": "362"}, "body": "{\"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\"}", "body_bytes": 362, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:45:22 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "673"}, "body": "{\"data\": {\"id\": 40, \"uid\": \"780f5c74fb0ff81f4d77@calendar-wp-plugin\", \"title\": \"Smoke 4th Sunday Anchor\", \"description\": \"anchor-normalization\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"start_datetime\": \"2026-05-24T15:00:00+01:00\", \"end_datetime\": \"2026-05-24T16:00:00+01:00\", \"repeat_type\": \"monthly\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"weekday_of_month\", \"repeat_nth_day\": null, \"repeat_nth_pos\": 4, \"repeat_nth_weekday\": 0, \"repeat_range_mode\": \"no_end\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"601dc1b8caa7090a\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T19:45:22+00:00\"}}", "body_bytes": 673, "body_truncated": false}} +{"ts": "2026-03-30T19:45:22+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/40/occurrences/2026-05-24T15:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:45:22 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:45:22+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/40.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:45:22 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"9af632dd6f1dd8f5\"", "Content-Length": "420"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:780f5c74fb0ff81f4d77@calendar-wp-plugin\r\nDTSTAMP:20260330T194522Z\r\nSUMMARY:Smoke 4th Sunday Anchor\r\nDESCRIPTION:anchor-normalization\r\nDTSTART;TZID=Europe/London:20260524T150000\r\nDTEND;TZID=Europe/London:20260524T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SU;BYSETPOS=4\r\nEXDATE;TZID=Europe/London:20260524T150000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 420, "body_truncated": false}} +{"ts": "2026-03-30T19:45:22+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "X-WP-User": "admin", "Content-Length": "295"}, "body": "{\"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\"}", "body_bytes": 295, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:45:22 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "655"}, "body": "{\"data\": {\"id\": 41, \"uid\": \"a2738714eb50fc15415c@calendar-wp-plugin\", \"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_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"until\", \"repeat_count\": null, \"repeat_until\": \"2026-03-19\", \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"8cce090cd63776d1\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T19:45:22+00:00\"}}", "body_bytes": 655, "body_truncated": false}} +{"ts": "2026-03-30T19:45:22+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/41/occurrences/2026-03-11T11:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:45:22 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:45:22+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/41/occurrences?from=2026-03-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:45:22 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "5212"}, "body": "{\"data\": [{\"event_id\": 41, \"uid\": \"a2738714eb50fc15415c@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-02T10:00:00+00:00\", \"occurrence_end\": \"2026-03-02T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 41, \"uid\": \"a2738714eb50fc15415c@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-03T10:00:00+00:00\", \"occurrence_end\": \"2026-03-03T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 41, \"uid\": \"a2738714eb50fc15415c@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-04T10:00:00+00:00\", \"occurrence_end\": \"2026-03-04T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 41, \"uid\": \"a2738714eb50fc15415c@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-05T10:00:00+00:00\", \"occurrence_end\": \"2026-03-05T11:00:00", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T19:45:22+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/41/occurrences/2026-03-10", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:45:22 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:45:22+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/41/occurrences?from=2026-03-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:45:22 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "4906"}, "body": "{\"data\": [{\"event_id\": 41, \"uid\": \"a2738714eb50fc15415c@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-02T10:00:00+00:00\", \"occurrence_end\": \"2026-03-02T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 41, \"uid\": \"a2738714eb50fc15415c@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-03T10:00:00+00:00\", \"occurrence_end\": \"2026-03-03T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 41, \"uid\": \"a2738714eb50fc15415c@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-04T10:00:00+00:00\", \"occurrence_end\": \"2026-03-04T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 41, \"uid\": \"a2738714eb50fc15415c@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-05T10:00:00+00:00\", \"occurrence_end\": \"2026-03-05T11:00:00", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T19:45:22+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-exdate-import.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "323"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-exdate-import-001\nSUMMARY:Smoke EXDATE Import\nDTSTART;TZID=Europe/London:20260302T100000\nDTEND;TZID=Europe/London:20260302T110000\nRRULE:FREQ=DAILY;UNTIL=20260319T235959\nEXDATE;TZID=Europe/London:20260310T100000,20260311T100000\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 323, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:45:22 GMT", "ETag": "\"cc710e449dbfa1ae\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:45:22+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:45:22 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "26544"}, "body": "{\"data\": [{\"id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"start_datetime\": \"2026-04-01T10:00:00+01:00\", \"end_datetime\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"eb53d018557c8fca\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T17:40:10+00:00\"}, {\"id\": 2, \"uid\": \"fixture-ce-002@calendar-wp-plugin\", \"title\": \"Office Closed\", \"description\": \"Public holiday closure.\", \"location\": \"HQ\", \"category\": \"Operations\", \"all_day_event\": true, \"start_datetime\": \"2026-05-04\", \"end_datetime\": \"2026-05-05\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"190a4", "body_bytes": 3927, "body_truncated": true}} +{"ts": "2026-03-30T19:45:22+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/18/occurrences?from=2026-03-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:45:22 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "4474"}, "body": "{\"data\": [{\"event_id\": 18, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-02T10:00:00+01:00\", \"occurrence_end\": \"2026-03-02T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 18, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-03T10:00:00+01:00\", \"occurrence_end\": \"2026-03-03T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 18, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-04T10:00:00+01:00\", \"occurrence_end\": \"2026-03-04T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 18, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-05T10:00:00+01:00\", \"occurrence_end\": \"2026-03-05T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 18, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE ", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T19:45:22+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-cancelled-occurrence.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "467"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-cancelled-occurrence-001\nSUMMARY:Smoke Cancelled Occurrence\nDTSTART;TZID=Europe/London:20260408T123000\nDTEND;TZID=Europe/London:20260408T133000\nRRULE:FREQ=WEEKLY\nEND:VEVENT\nBEGIN:VEVENT\nUID:smoke-cancelled-occurrence-001\nRECURRENCE-ID;TZID=Europe/London:20260506T123000\nDTSTART;TZID=Europe/London:20260506T123000\nDTEND;TZID=Europe/London:20260506T133000\nSTATUS:CANCELLED\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 467, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:45:22 GMT", "ETag": "\"050cf9004963ffed\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:45:22+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-cancelled-occurrence.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:45:22 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"050cf9004963ffed\"", "Content-Length": "359"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-cancelled-occurrence-001\r\nDTSTAMP:20260330T194522Z\r\nSUMMARY:Smoke Cancelled Occurrence\r\nDTSTART;TZID=Europe/London:20260408T123000\r\nDTEND;TZID=Europe/London:20260408T133000\r\nRRULE:FREQ=WEEKLY\r\nEXDATE;TZID=Europe/London:20260506T123000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 359, "body_truncated": false}} +{"ts": "2026-03-30T19:45:23+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:45:23 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "26544"}, "body": "{\"data\": [{\"id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"start_datetime\": \"2026-04-01T10:00:00+01:00\", \"end_datetime\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"eb53d018557c8fca\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T17:40:10+00:00\"}, {\"id\": 2, \"uid\": \"fixture-ce-002@calendar-wp-plugin\", \"title\": \"Office Closed\", \"description\": \"Public holiday closure.\", \"location\": \"HQ\", \"category\": \"Operations\", \"all_day_event\": true, \"start_datetime\": \"2026-05-04\", \"end_datetime\": \"2026-05-05\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"190a4", "body_bytes": 3927, "body_truncated": true}} +{"ts": "2026-03-30T19:45:23+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/users/register", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "Content-Length": "77"}, "body": "{\"email\": \"sec-nodisclose-1774899922@example.test\", \"password\": \"***\"}", "body_bytes": 77, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:45:23 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "99"}, "body": "{\"data\": {\"user_id\": 19, \"status\": \"pending_approval\", \"email_status\": \"sent\", \"email_sent\": true}}", "body_bytes": 99, "body_truncated": false}} +{"ts": "2026-03-30T19:45:23+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/19/occurrences?from=2026-05-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:45:23 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "892"}, "body": "{\"data\": [{\"event_id\": 19, \"uid\": \"smoke-cancelled-occurrence-001\", \"title\": \"Smoke Cancelled Occurrence\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-13T12:30:00+01:00\", \"occurrence_end\": \"2026-05-13T13:30:00+01:00\", \"repeat_type\": \"weekly\"}, {\"event_id\": 19, \"uid\": \"smoke-cancelled-occurrence-001\", \"title\": \"Smoke Cancelled Occurrence\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-20T12:30:00+01:00\", \"occurrence_end\": \"2026-05-20T13:30:00+01:00\", \"repeat_type\": \"weekly\"}, {\"event_id\": 19, \"uid\": \"smoke-cancelled-occurrence-001\", \"title\": \"Smoke Cancelled Occurrence\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-27T12:30:00+01:00\", \"occurrence_end\": \"2026-05-27T13:30:00+01:00\", \"repeat_type\": \"weekly\"}]}", "body_bytes": 892, "body_truncated": false}} +{"ts": "2026-03-30T19:45:23+00:00", "client": "127.0.0.1", "method": "GET", "path": "/calendar", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:45:23 GMT", "Content-Type": "text/html; charset=utf-8", "Content-Length": "43447"}, "body": "[omitted html payload: 3934 bytes]", "body_bytes": 3934, "body_truncated": true}} +{"ts": "2026-03-30T19:45:24+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/users/forgot-password", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "Content-Length": "39"}, "body": "{\"email\": \"adrians@chezstephens.org.uk\"}", "body_bytes": 39, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:45:24 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "70"}, "body": "{\"data\": {\"status\": \"ok\", \"email_status\": \"sent\", \"email_sent\": true}}", "body_bytes": 70, "body_truncated": false}} +{"ts": "2026-03-30T19:45:24+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/users/register", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "Content-Length": "73"}, "body": "{\"email\": \"sec-rate-1774899922-1@example.test\", \"password\": \"***\"}", "body_bytes": 73, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:45:24 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "99"}, "body": "{\"data\": {\"user_id\": 20, \"status\": \"pending_approval\", \"email_status\": \"sent\", \"email_sent\": true}}", "body_bytes": 99, "body_truncated": false}} +{"ts": "2026-03-30T19:45:24+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/users/register", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "Content-Length": "73"}, "body": "{\"email\": \"sec-rate-1774899922-2@example.test\", \"password\": \"***\"}", "body_bytes": 73, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:45:24 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "99"}, "body": "{\"data\": {\"user_id\": 21, \"status\": \"pending_approval\", \"email_status\": \"sent\", \"email_sent\": true}}", "body_bytes": 99, "body_truncated": false}} +{"ts": "2026-03-30T19:45:25+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/users/register", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "Content-Length": "73"}, "body": "{\"email\": \"sec-rate-1774899922-3@example.test\", \"password\": \"***\"}", "body_bytes": 73, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:45:25 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "99"}, "body": "{\"data\": {\"user_id\": 22, \"status\": \"pending_approval\", \"email_status\": \"sent\", \"email_sent\": true}}", "body_bytes": 99, "body_truncated": false}} +{"ts": "2026-03-30T19:45:25+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/users/register", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "Content-Length": "73"}, "body": "{\"email\": \"sec-rate-1774899922-4@example.test\", \"password\": \"***\"}", "body_bytes": 73, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:45:25 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "99"}, "body": "{\"data\": {\"user_id\": 23, \"status\": \"pending_approval\", \"email_status\": \"sent\", \"email_sent\": true}}", "body_bytes": 99, "body_truncated": false}} +{"ts": "2026-03-30T19:45:25+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/users/register", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "Content-Length": "73"}, "body": "{\"email\": \"sec-rate-1774899922-5@example.test\", \"password\": \"***\"}", "body_bytes": 73, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:45:25 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "99"}, "body": "{\"data\": {\"user_id\": 24, \"status\": \"pending_approval\", \"email_status\": \"sent\", \"email_sent\": true}}", "body_bytes": 99, "body_truncated": false}} +{"ts": "2026-03-30T19:45:25+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/users/register", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "Content-Length": "73"}, "body": "{\"email\": \"sec-rate-1774899922-6@example.test\", \"password\": \"***\"}", "body_bytes": 73, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:45:25 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "99"}, "body": "{\"data\": {\"user_id\": 25, \"status\": \"pending_approval\", \"email_status\": \"sent\", \"email_sent\": true}}", "body_bytes": 99, "body_truncated": false}} +{"ts": "2026-03-30T19:45:26+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/users/register", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "Content-Length": "73"}, "body": "{\"email\": \"sec-rate-1774899922-7@example.test\", \"password\": \"***\"}", "body_bytes": 73, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:45:26 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "99"}, "body": "{\"data\": {\"user_id\": 26, \"status\": \"pending_approval\", \"email_status\": \"sent\", \"email_sent\": true}}", "body_bytes": 99, "body_truncated": false}} +{"ts": "2026-03-30T19:45:26+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/users/register", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "Content-Length": "73"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 429, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:45:26 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "98"}, "body": "{\"error\": {\"code\": \"rate_limited\", \"message\": \"Too many registration attempts. Try again later.\"}}", "body_bytes": 98, "body_truncated": false}} +{"ts": "2026-03-30T19:45:26+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/sec-1774899926.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Length": "201", "Content-Type": "application/x-www-form-urlencoded"}, "body": "[omitted non-text payload: 201 bytes, content-type=application/x-www-form-urlencoded]", "body_bytes": 201, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:45:26 GMT", "ETag": "\"60902c44e50b1c11\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:45:26+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/sec-1774899926.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:45:26 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"60902c44e50b1c11\"", "Content-Length": "287"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:sec-smoke-uid@example.test\r\nDTSTAMP:20260330T194526Z\r\nSUMMARY:Security Smoke Event\r\nDTSTART;TZID=Europe/London:20260415T100000\r\nDTEND;TZID=Europe/London:20260415T110000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 287, "body_truncated": false}} +{"ts": "2026-03-30T19:45:26+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/999999.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 404, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:45:26 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "62"}, "body": "{\"error\": {\"code\": \"not_found\", \"message\": \"Route not found\"}}", "body_bytes": 62, "body_truncated": false}} +{"ts": "2026-03-30T19:47:39+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:47:39 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "26544"}, "body": "{\"data\": [{\"id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"start_datetime\": \"2026-04-01T10:00:00+01:00\", \"end_datetime\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"eb53d018557c8fca\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T17:40:10+00:00\"}, {\"id\": 2, \"uid\": \"fixture-ce-002@calendar-wp-plugin\", \"title\": \"Office Closed\", \"description\": \"Public holiday closure.\", \"location\": \"HQ\", \"category\": \"Operations\", \"all_day_event\": true, \"start_datetime\": \"2026-05-04\", \"end_datetime\": \"2026-05-05\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"190a4", "body_bytes": 3927, "body_truncated": true}} +{"ts": "2026-03-30T19:47:39+00:00", "client": "127.0.0.1", "method": "GET", "path": "/calendar.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:47:39 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"e2b1a46432ffc137\"", "Content-Length": "12674"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nCALSCALE:GREGORIAN\r\nX-WR-TIMEZONE:Europe/London\r\nBEGIN:VEVENT\r\nUID:fixture-ce-001@calendar-wp-plugin\r\nDTSTAMP:20260330T194739Z\r\nSUMMARY:Board Meeting\r\nDESCRIPTION:Quarterly board review.\r\nLOCATION:Room A\r\nCATEGORIES:Governance\r\nDTSTART;TZID=Europe/London:20260401T100000\r\nDTEND;TZID=Europe/London:20260401T113000\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-002@calendar-wp-plugin\r\nDTSTAMP:20260330T194739Z\r\nSUMMARY:Office Closed\r\nDESCRIPTION:Public holiday closure.\r\nLOCATION:HQ\r\nCATEGORIES:Operations\r\nDTSTART;VALUE=DATE:20260504\r\nDTEND;VALUE=DATE:20260505\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-003@calendar-wp-plugin\r\nDTSTAMP:20260330T194739Z\r\nSUMMARY:Daily Standup\r\nDESCRIPTION:15 minute sync.\r\nLOCATION:Online\r\nCATEGORIES:Team\r\nDTSTART;TZID=Europe/London:20260406T090000\r\nDTEND;TZID=Europe/London:20260406T091500\r\nRRULE:FREQ=DAILY;COUNT=10\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-004@calendar-wp-plugin\r\nDTSTAMP:20260330T194739Z\r\nSUMMARY:Community Lunch\r\nDESCRIPTION:Weekly community lunch.\r\nLOCATION:Cafeteria\r\nCATEGORIES:Community\r\nDTSTART;TZID=Europe/London:20260408T123000\r\nDTEND;TZID=Europe/London:20260408T133000\r\n ...(truncated)", "body_bytes": 3904, "body_truncated": true}} +{"ts": "2026-03-30T19:47:39+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/1.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:47:39 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"eb53d018557c8fca\"", "Content-Length": "364"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-001@calendar-wp-plugin\r\nDTSTAMP:20260330T194739Z\r\nSUMMARY:Board Meeting\r\nDESCRIPTION:Quarterly board review.\r\nLOCATION:Room A\r\nCATEGORIES:Governance\r\nDTSTART;TZID=Europe/London:20260401T100000\r\nDTEND;TZID=Europe/London:20260401T113000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 364, "body_truncated": false}} +{"ts": "2026-03-30T19:47:39+00:00", "client": "127.0.0.1", "method": "REPORT", "path": "/caldav/calendars/public/", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/xml; charset=utf-8", "Depth": "1", "Content-Length": "239"}, "body": "\n\n \n /caldav/calendars/public/4.ics\n\n", "body_bytes": 239, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:47:39 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "997"}, "body": "\n\n\n /caldav/calendars/public/4.ics\n \n \n \"691a56c3c4084218\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-004@calendar-wp-plugin\r\nDTSTAMP:20260330T194739Z\r\nSUMMARY:Community Lunch\r\nDESCRIPTION:Weekly community lunch.\r\nLOCATION:Cafeteria\r\nCATEGORIES:Community\r\nDTSTART;TZID=Europe/London:20260408T123000\r\nDTEND;TZID=Europe/London:20260408T133000\r\nRRULE:FREQ=WEEKLY\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n", "body_bytes": 997, "body_truncated": false}} +{"ts": "2026-03-30T19:47:39+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/10/occurrences/2026-04-17T14:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:47:39 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:47:39+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "X-WP-User": "admin", "Content-Length": "287"}, "body": "{\"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}", "body_bytes": 287, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:47:39 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "647"}, "body": "{\"data\": {\"id\": 42, \"uid\": \"cc7484132e5ceca0e789@calendar-wp-plugin\", \"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_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"count\", \"repeat_count\": 3, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"ae03a0cef700c3da\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T19:47:39+00:00\"}}", "body_bytes": 647, "body_truncated": false}} +{"ts": "2026-03-30T19:47:39+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/42/occurrences?from=2026-04-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:47:39 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "937"}, "body": "{\"data\": [{\"event_id\": 42, \"uid\": \"cc7484132e5ceca0e789@calendar-wp-plugin\", \"title\": \"Smoke Occurrences API\", \"description\": \"occ-endpoint\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-01T10:00:00+01:00\", \"occurrence_end\": \"2026-04-01T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 42, \"uid\": \"cc7484132e5ceca0e789@calendar-wp-plugin\", \"title\": \"Smoke Occurrences API\", \"description\": \"occ-endpoint\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-02T10:00:00+01:00\", \"occurrence_end\": \"2026-04-02T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 42, \"uid\": \"cc7484132e5ceca0e789@calendar-wp-plugin\", \"title\": \"Smoke Occurrences API\", \"description\": \"occ-endpoint\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-03T10:00:00+01:00\", \"occurrence_end\": \"2026-04-03T11:00:00+01:00\", \"repeat_type\": \"daily\"}]}", "body_bytes": 937, "body_truncated": false}} +{"ts": "2026-03-30T19:47:39+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/42/occurrences/2026-04-02T10:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:47:39 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:47:39+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/42/occurrences/2026-04-02T10:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:47:39 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:47:39+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/42/occurrences?from=2026-04-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:47:39 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "628"}, "body": "{\"data\": [{\"event_id\": 42, \"uid\": \"cc7484132e5ceca0e789@calendar-wp-plugin\", \"title\": \"Smoke Occurrences API\", \"description\": \"occ-endpoint\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-01T10:00:00+01:00\", \"occurrence_end\": \"2026-04-01T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 42, \"uid\": \"cc7484132e5ceca0e789@calendar-wp-plugin\", \"title\": \"Smoke Occurrences API\", \"description\": \"occ-endpoint\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-03T10:00:00+01:00\", \"occurrence_end\": \"2026-04-03T11:00:00+01:00\", \"repeat_type\": \"daily\"}]}", "body_bytes": 628, "body_truncated": false}} +{"ts": "2026-03-30T19:47:39+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-vtimezone.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "454"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN\nVERSION:2.0\nBEGIN:VTIMEZONE\nTZID:Europe/London\nBEGIN:STANDARD\nDTSTART:18471201T000000\nRRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=9\nTZOFFSETFROM:+0115\nTZOFFSETTO:+0000\nTZNAME:GMT\nEND:STANDARD\nEND:VTIMEZONE\nBEGIN:VEVENT\nUID:smoke-vtimezone-parser-001\nSUMMARY:Smoke VTIMEZONE Parse\nDTSTART;TZID=Europe/London:20260423T150000\nDTEND;TZID=Europe/London:20260423T160000\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 454, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:47:39 GMT", "ETag": "\"17ccc871adec4bc3\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:47:39+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-vtimezone.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:47:39 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"17ccc871adec4bc3\"", "Content-Length": "288"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-vtimezone-parser-001\r\nDTSTAMP:20260330T194739Z\r\nSUMMARY:Smoke VTIMEZONE Parse\r\nDTSTART;TZID=Europe/London:20260423T150000\r\nDTEND;TZID=Europe/London:20260423T160000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 288, "body_truncated": false}} +{"ts": "2026-03-30T19:47:39+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-monthly-nth.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "261"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-monthly-nth-001\nSUMMARY:Smoke Monthly Nth\nDTSTART;TZID=Europe/London:20260402T150000\nDTEND;TZID=Europe/London:20260402T160000\nRRULE:FREQ=MONTHLY;BYDAY=SU;BYSETPOS=4\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 261, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:47:39 GMT", "ETag": "\"f3ef2b7fc18dcec7\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:47:39+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-monthly-nth.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:47:39 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"f3ef2b7fc18dcec7\"", "Content-Length": "319"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-nth-001\r\nDTSTAMP:20260330T194739Z\r\nSUMMARY:Smoke Monthly Nth\r\nDTSTART;TZID=Europe/London:20260426T150000\r\nDTEND;TZID=Europe/London:20260426T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SU;BYSETPOS=4\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 319, "body_truncated": false}} +{"ts": "2026-03-30T19:47:39+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-monthly-ordinal.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "259"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-monthly-ordinal-001\nSUMMARY:Smoke Monthly Ordinal\nDTSTART;TZID=Europe/London:20260411T150000\nDTEND;TZID=Europe/London:20260411T160000\nRRULE:FREQ=MONTHLY;BYDAY=2SA\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 259, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:47:39 GMT", "ETag": "\"f5d439a17623207e\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:47:39+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-monthly-ordinal.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:47:39 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"f5d439a17623207e\"", "Content-Length": "327"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-ordinal-001\r\nDTSTAMP:20260330T194739Z\r\nSUMMARY:Smoke Monthly Ordinal\r\nDTSTART;TZID=Europe/London:20260411T150000\r\nDTEND;TZID=Europe/London:20260411T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SA;BYSETPOS=2\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 327, "body_truncated": false}} +{"ts": "2026-03-30T19:47:39+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-monthly-last.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "264"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-monthly-last-001\nSUMMARY:Smoke Monthly Last\nDTSTART;TZID=Europe/London:20260425T150000\nDTEND;TZID=Europe/London:20260425T160000\nRRULE:FREQ=MONTHLY;BYDAY=SA;BYSETPOS=-1\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 264, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:47:39 GMT", "ETag": "\"ec62ff1e4b136e42\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:47:39+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-monthly-last.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:47:39 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"ec62ff1e4b136e42\"", "Content-Length": "322"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-last-001\r\nDTSTAMP:20260330T194739Z\r\nSUMMARY:Smoke Monthly Last\r\nDTSTART;TZID=Europe/London:20260425T150000\r\nDTEND;TZID=Europe/London:20260425T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SA;BYSETPOS=-1\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 322, "body_truncated": false}} +{"ts": "2026-03-30T19:47:39+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "X-WP-User": "admin", "Content-Length": "362"}, "body": "{\"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\"}", "body_bytes": 362, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:47:39 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "673"}, "body": "{\"data\": {\"id\": 43, \"uid\": \"672c78f46dcc88e86186@calendar-wp-plugin\", \"title\": \"Smoke 4th Sunday Anchor\", \"description\": \"anchor-normalization\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"start_datetime\": \"2026-05-24T15:00:00+01:00\", \"end_datetime\": \"2026-05-24T16:00:00+01:00\", \"repeat_type\": \"monthly\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"weekday_of_month\", \"repeat_nth_day\": null, \"repeat_nth_pos\": 4, \"repeat_nth_weekday\": 0, \"repeat_range_mode\": \"no_end\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"773becc6bc4fddc5\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T19:47:39+00:00\"}}", "body_bytes": 673, "body_truncated": false}} +{"ts": "2026-03-30T19:47:40+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/43/occurrences/2026-05-24T15:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:47:40 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:47:40+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/43.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:47:40 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"90276a2d43ab3d9f\"", "Content-Length": "420"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:672c78f46dcc88e86186@calendar-wp-plugin\r\nDTSTAMP:20260330T194740Z\r\nSUMMARY:Smoke 4th Sunday Anchor\r\nDESCRIPTION:anchor-normalization\r\nDTSTART;TZID=Europe/London:20260524T150000\r\nDTEND;TZID=Europe/London:20260524T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SU;BYSETPOS=4\r\nEXDATE;TZID=Europe/London:20260524T150000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 420, "body_truncated": false}} +{"ts": "2026-03-30T19:47:40+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "X-WP-User": "admin", "Content-Length": "295"}, "body": "{\"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\"}", "body_bytes": 295, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:47:40 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "655"}, "body": "{\"data\": {\"id\": 44, \"uid\": \"4d93e11a90cee95c5c1a@calendar-wp-plugin\", \"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_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"until\", \"repeat_count\": null, \"repeat_until\": \"2026-03-19\", \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"32aa098f1ad6c404\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T19:47:40+00:00\"}}", "body_bytes": 655, "body_truncated": false}} +{"ts": "2026-03-30T19:47:40+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/44/occurrences/2026-03-11T11:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:47:40 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:47:40+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/44/occurrences?from=2026-03-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:47:40 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "5212"}, "body": "{\"data\": [{\"event_id\": 44, \"uid\": \"4d93e11a90cee95c5c1a@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-02T10:00:00+00:00\", \"occurrence_end\": \"2026-03-02T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 44, \"uid\": \"4d93e11a90cee95c5c1a@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-03T10:00:00+00:00\", \"occurrence_end\": \"2026-03-03T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 44, \"uid\": \"4d93e11a90cee95c5c1a@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-04T10:00:00+00:00\", \"occurrence_end\": \"2026-03-04T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 44, \"uid\": \"4d93e11a90cee95c5c1a@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-05T10:00:00+00:00\", \"occurrence_end\": \"2026-03-05T11:00:00", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T19:47:40+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/44/occurrences/2026-03-10", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:47:40 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:47:40+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/44/occurrences?from=2026-03-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:47:40 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "4906"}, "body": "{\"data\": [{\"event_id\": 44, \"uid\": \"4d93e11a90cee95c5c1a@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-02T10:00:00+00:00\", \"occurrence_end\": \"2026-03-02T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 44, \"uid\": \"4d93e11a90cee95c5c1a@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-03T10:00:00+00:00\", \"occurrence_end\": \"2026-03-03T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 44, \"uid\": \"4d93e11a90cee95c5c1a@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-04T10:00:00+00:00\", \"occurrence_end\": \"2026-03-04T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 44, \"uid\": \"4d93e11a90cee95c5c1a@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-05T10:00:00+00:00\", \"occurrence_end\": \"2026-03-05T11:00:00", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T19:47:40+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-exdate-import.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "323"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-exdate-import-001\nSUMMARY:Smoke EXDATE Import\nDTSTART;TZID=Europe/London:20260302T100000\nDTEND;TZID=Europe/London:20260302T110000\nRRULE:FREQ=DAILY;UNTIL=20260319T235959\nEXDATE;TZID=Europe/London:20260310T100000,20260311T100000\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 323, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:47:40 GMT", "ETag": "\"f9cb0b8409520dff\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:47:40+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:47:40 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "28495"}, "body": "{\"data\": [{\"id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"start_datetime\": \"2026-04-01T10:00:00+01:00\", \"end_datetime\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"eb53d018557c8fca\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T17:40:10+00:00\"}, {\"id\": 2, \"uid\": \"fixture-ce-002@calendar-wp-plugin\", \"title\": \"Office Closed\", \"description\": \"Public holiday closure.\", \"location\": \"HQ\", \"category\": \"Operations\", \"all_day_event\": true, \"start_datetime\": \"2026-05-04\", \"end_datetime\": \"2026-05-05\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"190a4", "body_bytes": 3927, "body_truncated": true}} +{"ts": "2026-03-30T19:47:40+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/18/occurrences?from=2026-03-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:47:40 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "4474"}, "body": "{\"data\": [{\"event_id\": 18, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-02T10:00:00+01:00\", \"occurrence_end\": \"2026-03-02T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 18, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-03T10:00:00+01:00\", \"occurrence_end\": \"2026-03-03T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 18, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-04T10:00:00+01:00\", \"occurrence_end\": \"2026-03-04T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 18, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-05T10:00:00+01:00\", \"occurrence_end\": \"2026-03-05T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 18, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE ", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T19:47:40+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-cancelled-occurrence.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "467"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-cancelled-occurrence-001\nSUMMARY:Smoke Cancelled Occurrence\nDTSTART;TZID=Europe/London:20260408T123000\nDTEND;TZID=Europe/London:20260408T133000\nRRULE:FREQ=WEEKLY\nEND:VEVENT\nBEGIN:VEVENT\nUID:smoke-cancelled-occurrence-001\nRECURRENCE-ID;TZID=Europe/London:20260506T123000\nDTSTART;TZID=Europe/London:20260506T123000\nDTEND;TZID=Europe/London:20260506T133000\nSTATUS:CANCELLED\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 467, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:47:40 GMT", "ETag": "\"43a1d243dfe2e00d\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:47:40+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-cancelled-occurrence.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:47:40 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"43a1d243dfe2e00d\"", "Content-Length": "359"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-cancelled-occurrence-001\r\nDTSTAMP:20260330T194740Z\r\nSUMMARY:Smoke Cancelled Occurrence\r\nDTSTART;TZID=Europe/London:20260408T123000\r\nDTEND;TZID=Europe/London:20260408T133000\r\nRRULE:FREQ=WEEKLY\r\nEXDATE;TZID=Europe/London:20260506T123000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 359, "body_truncated": false}} +{"ts": "2026-03-30T19:47:40+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:47:40 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "28495"}, "body": "{\"data\": [{\"id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"start_datetime\": \"2026-04-01T10:00:00+01:00\", \"end_datetime\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"eb53d018557c8fca\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T17:40:10+00:00\"}, {\"id\": 2, \"uid\": \"fixture-ce-002@calendar-wp-plugin\", \"title\": \"Office Closed\", \"description\": \"Public holiday closure.\", \"location\": \"HQ\", \"category\": \"Operations\", \"all_day_event\": true, \"start_datetime\": \"2026-05-04\", \"end_datetime\": \"2026-05-05\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"190a4", "body_bytes": 3927, "body_truncated": true}} +{"ts": "2026-03-30T19:47:40+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/19/occurrences?from=2026-05-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:47:40 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "892"}, "body": "{\"data\": [{\"event_id\": 19, \"uid\": \"smoke-cancelled-occurrence-001\", \"title\": \"Smoke Cancelled Occurrence\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-13T12:30:00+01:00\", \"occurrence_end\": \"2026-05-13T13:30:00+01:00\", \"repeat_type\": \"weekly\"}, {\"event_id\": 19, \"uid\": \"smoke-cancelled-occurrence-001\", \"title\": \"Smoke Cancelled Occurrence\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-20T12:30:00+01:00\", \"occurrence_end\": \"2026-05-20T13:30:00+01:00\", \"repeat_type\": \"weekly\"}, {\"event_id\": 19, \"uid\": \"smoke-cancelled-occurrence-001\", \"title\": \"Smoke Cancelled Occurrence\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-27T12:30:00+01:00\", \"occurrence_end\": \"2026-05-27T13:30:00+01:00\", \"repeat_type\": \"weekly\"}]}", "body_bytes": 892, "body_truncated": false}} +{"ts": "2026-03-30T19:47:40+00:00", "client": "127.0.0.1", "method": "GET", "path": "/calendar", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:47:40 GMT", "Content-Type": "text/html; charset=utf-8", "Content-Length": "43447"}, "body": "[omitted html payload: 3934 bytes]", "body_bytes": 3934, "body_truncated": true}} +{"ts": "2026-03-30T19:56:58+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:56:58 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "28495"}, "body": "{\"data\": [{\"id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"start_datetime\": \"2026-04-01T10:00:00+01:00\", \"end_datetime\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"eb53d018557c8fca\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T17:40:10+00:00\"}, {\"id\": 2, \"uid\": \"fixture-ce-002@calendar-wp-plugin\", \"title\": \"Office Closed\", \"description\": \"Public holiday closure.\", \"location\": \"HQ\", \"category\": \"Operations\", \"all_day_event\": true, \"start_datetime\": \"2026-05-04\", \"end_datetime\": \"2026-05-05\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"190a4", "body_bytes": 3927, "body_truncated": true}} +{"ts": "2026-03-30T19:56:58+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:56:58 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "28495"}, "body": "{\"data\": [{\"id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"start_datetime\": \"2026-04-01T10:00:00+01:00\", \"end_datetime\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"eb53d018557c8fca\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T17:40:10+00:00\"}, {\"id\": 2, \"uid\": \"fixture-ce-002@calendar-wp-plugin\", \"title\": \"Office Closed\", \"description\": \"Public holiday closure.\", \"location\": \"HQ\", \"category\": \"Operations\", \"all_day_event\": true, \"start_datetime\": \"2026-05-04\", \"end_datetime\": \"2026-05-05\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"190a4", "body_bytes": 3927, "body_truncated": true}} +{"ts": "2026-03-30T19:56:58+00:00", "client": "127.0.0.1", "method": "GET", "path": "/calendar.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:56:58 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"8651b7f9d8461cda\"", "Content-Length": "13652"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nCALSCALE:GREGORIAN\r\nX-WR-TIMEZONE:Europe/London\r\nBEGIN:VEVENT\r\nUID:fixture-ce-001@calendar-wp-plugin\r\nDTSTAMP:20260330T195658Z\r\nSUMMARY:Board Meeting\r\nDESCRIPTION:Quarterly board review.\r\nLOCATION:Room A\r\nCATEGORIES:Governance\r\nDTSTART;TZID=Europe/London:20260401T100000\r\nDTEND;TZID=Europe/London:20260401T113000\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-002@calendar-wp-plugin\r\nDTSTAMP:20260330T195658Z\r\nSUMMARY:Office Closed\r\nDESCRIPTION:Public holiday closure.\r\nLOCATION:HQ\r\nCATEGORIES:Operations\r\nDTSTART;VALUE=DATE:20260504\r\nDTEND;VALUE=DATE:20260505\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-003@calendar-wp-plugin\r\nDTSTAMP:20260330T195658Z\r\nSUMMARY:Daily Standup\r\nDESCRIPTION:15 minute sync.\r\nLOCATION:Online\r\nCATEGORIES:Team\r\nDTSTART;TZID=Europe/London:20260406T090000\r\nDTEND;TZID=Europe/London:20260406T091500\r\nRRULE:FREQ=DAILY;COUNT=10\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:fixture-ce-004@calendar-wp-plugin\r\nDTSTAMP:20260330T195658Z\r\nSUMMARY:Community Lunch\r\nDESCRIPTION:Weekly community lunch.\r\nLOCATION:Cafeteria\r\nCATEGORIES:Community\r\nDTSTART;TZID=Europe/London:20260408T123000\r\nDTEND;TZID=Europe/London:20260408T133000\r\n ...(truncated)", "body_bytes": 3904, "body_truncated": true}} +{"ts": "2026-03-30T19:56:58+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 401, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:56:58 GMT", "WWW-Authenticate": "Basic realm=\"calendar-caldav-fixture\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:56:58+00:00", "client": "127.0.0.1", "method": "OPTIONS", "path": "/caldav/", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:56:58 GMT", "DAV": "1, 2, calendar-access", "Allow": "OPTIONS, PROPFIND, REPORT, GET, PUT, DELETE"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:56:58+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/1.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:56:58 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"eb53d018557c8fca\"", "Content-Length": "364"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-001@calendar-wp-plugin\r\nDTSTAMP:20260330T195658Z\r\nSUMMARY:Board Meeting\r\nDESCRIPTION:Quarterly board review.\r\nLOCATION:Room A\r\nCATEGORIES:Governance\r\nDTSTART;TZID=Europe/London:20260401T100000\r\nDTEND;TZID=Europe/London:20260401T113000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 364, "body_truncated": false}} +{"ts": "2026-03-30T19:56:58+00:00", "client": "127.0.0.1", "method": "PROPFIND", "path": "/caldav/", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Depth": "1", "Content-Type": "application/xml", "Content-Length": "117"}, "body": "", "body_bytes": 117, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:56:58 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "1348"}, "body": "\n\n\n /caldav/\n \n \n /caldav/principals/user/\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/principals/user/\n \n \n /caldav/calendars/\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/\n \n \n \n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/\n \n \n Public Calendar\n\n \n /caldav/calendars/public/4.ics\n\n", "body_bytes": 239, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:56:58 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "997"}, "body": "\n\n\n /caldav/calendars/public/4.ics\n \n \n \"691a56c3c4084218\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:fixture-ce-004@calendar-wp-plugin\r\nDTSTAMP:20260330T195658Z\r\nSUMMARY:Community Lunch\r\nDESCRIPTION:Weekly community lunch.\r\nLOCATION:Cafeteria\r\nCATEGORIES:Community\r\nDTSTART;TZID=Europe/London:20260408T123000\r\nDTEND;TZID=Europe/London:20260408T133000\r\nRRULE:FREQ=WEEKLY\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n", "body_bytes": 997, "body_truncated": false}} +{"ts": "2026-03-30T19:56:58+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/10/occurrences/2026-04-17T14:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:56:58 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:56:58+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "X-WP-User": "admin", "Content-Length": "287"}, "body": "{\"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}", "body_bytes": 287, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:56:58 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "647"}, "body": "{\"data\": {\"id\": 45, \"uid\": \"ae50f5d7efb7ef19f4cf@calendar-wp-plugin\", \"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_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"count\", \"repeat_count\": 3, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"0896ee7c0c565fe2\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T19:56:58+00:00\"}}", "body_bytes": 647, "body_truncated": false}} +{"ts": "2026-03-30T19:56:58+00:00", "client": "127.0.0.1", "method": "PROPFIND", "path": "/caldav/principals/user/", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Depth": "0", "Content-Type": "application/xml", "Content-Length": "119"}, "body": "", "body_bytes": 119, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:56:58 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "445"}, "body": "\n\n\n /caldav/principals/user/\n \n \n /caldav/calendars/\n \n HTTP/1.1 200 OK\n \n\n\n", "body_bytes": 445, "body_truncated": false}} +{"ts": "2026-03-30T19:56:58+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/45/occurrences?from=2026-04-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:56:58 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "937"}, "body": "{\"data\": [{\"event_id\": 45, \"uid\": \"ae50f5d7efb7ef19f4cf@calendar-wp-plugin\", \"title\": \"Smoke Occurrences API\", \"description\": \"occ-endpoint\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-01T10:00:00+01:00\", \"occurrence_end\": \"2026-04-01T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 45, \"uid\": \"ae50f5d7efb7ef19f4cf@calendar-wp-plugin\", \"title\": \"Smoke Occurrences API\", \"description\": \"occ-endpoint\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-02T10:00:00+01:00\", \"occurrence_end\": \"2026-04-02T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 45, \"uid\": \"ae50f5d7efb7ef19f4cf@calendar-wp-plugin\", \"title\": \"Smoke Occurrences API\", \"description\": \"occ-endpoint\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-03T10:00:00+01:00\", \"occurrence_end\": \"2026-04-03T11:00:00+01:00\", \"repeat_type\": \"daily\"}]}", "body_bytes": 937, "body_truncated": false}} +{"ts": "2026-03-30T19:56:58+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/45/occurrences/2026-04-02T10:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:56:58 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:56:58+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/45/occurrences/2026-04-02T10:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:56:58 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:56:58+00:00", "client": "127.0.0.1", "method": "PROPFIND", "path": "/caldav/calendars/", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Depth": "1", "Content-Type": "application/xml", "Content-Length": "167"}, "body": "", "body_bytes": 167, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:56:58 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "728"}, "body": "\n\n\n /caldav/calendars/\n \n \n \n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/\n \n \n Public Calendar\n \n HTTP/1.1 200 OK\n \n\n\n", "body_bytes": 728, "body_truncated": false}} +{"ts": "2026-03-30T19:56:58+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/45/occurrences?from=2026-04-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:56:58 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "628"}, "body": "{\"data\": [{\"event_id\": 45, \"uid\": \"ae50f5d7efb7ef19f4cf@calendar-wp-plugin\", \"title\": \"Smoke Occurrences API\", \"description\": \"occ-endpoint\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-01T10:00:00+01:00\", \"occurrence_end\": \"2026-04-01T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 45, \"uid\": \"ae50f5d7efb7ef19f4cf@calendar-wp-plugin\", \"title\": \"Smoke Occurrences API\", \"description\": \"occ-endpoint\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-04-03T10:00:00+01:00\", \"occurrence_end\": \"2026-04-03T11:00:00+01:00\", \"repeat_type\": \"daily\"}]}", "body_bytes": 628, "body_truncated": false}} +{"ts": "2026-03-30T19:56:58+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-vtimezone.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "454"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN\nVERSION:2.0\nBEGIN:VTIMEZONE\nTZID:Europe/London\nBEGIN:STANDARD\nDTSTART:18471201T000000\nRRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=9\nTZOFFSETFROM:+0115\nTZOFFSETTO:+0000\nTZNAME:GMT\nEND:STANDARD\nEND:VTIMEZONE\nBEGIN:VEVENT\nUID:smoke-vtimezone-parser-001\nSUMMARY:Smoke VTIMEZONE Parse\nDTSTART;TZID=Europe/London:20260423T150000\nDTEND;TZID=Europe/London:20260423T160000\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 454, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:56:58 GMT", "ETag": "\"4777d0172be8ffc5\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:56:58+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-vtimezone.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:56:58 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"4777d0172be8ffc5\"", "Content-Length": "288"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-vtimezone-parser-001\r\nDTSTAMP:20260330T195658Z\r\nSUMMARY:Smoke VTIMEZONE Parse\r\nDTSTART;TZID=Europe/London:20260423T150000\r\nDTEND;TZID=Europe/London:20260423T160000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 288, "body_truncated": false}} +{"ts": "2026-03-30T19:56:58+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-monthly-nth.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "261"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-monthly-nth-001\nSUMMARY:Smoke Monthly Nth\nDTSTART;TZID=Europe/London:20260402T150000\nDTEND;TZID=Europe/London:20260402T160000\nRRULE:FREQ=MONTHLY;BYDAY=SU;BYSETPOS=4\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 261, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:56:58 GMT", "ETag": "\"93aae3c5c0e310d1\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:56:58+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-monthly-nth.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:56:58 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"93aae3c5c0e310d1\"", "Content-Length": "319"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-nth-001\r\nDTSTAMP:20260330T195658Z\r\nSUMMARY:Smoke Monthly Nth\r\nDTSTART;TZID=Europe/London:20260426T150000\r\nDTEND;TZID=Europe/London:20260426T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SU;BYSETPOS=4\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 319, "body_truncated": false}} +{"ts": "2026-03-30T19:56:59+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-monthly-ordinal.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "259"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-monthly-ordinal-001\nSUMMARY:Smoke Monthly Ordinal\nDTSTART;TZID=Europe/London:20260411T150000\nDTEND;TZID=Europe/London:20260411T160000\nRRULE:FREQ=MONTHLY;BYDAY=2SA\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 259, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:56:59 GMT", "ETag": "\"3d1c488be8943444\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:56:59+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-monthly-ordinal.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:56:59 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"3d1c488be8943444\"", "Content-Length": "327"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-ordinal-001\r\nDTSTAMP:20260330T195659Z\r\nSUMMARY:Smoke Monthly Ordinal\r\nDTSTART;TZID=Europe/London:20260411T150000\r\nDTEND;TZID=Europe/London:20260411T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SA;BYSETPOS=2\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 327, "body_truncated": false}} +{"ts": "2026-03-30T19:56:59+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-monthly-last.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "264"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-monthly-last-001\nSUMMARY:Smoke Monthly Last\nDTSTART;TZID=Europe/London:20260425T150000\nDTEND;TZID=Europe/London:20260425T160000\nRRULE:FREQ=MONTHLY;BYDAY=SA;BYSETPOS=-1\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 264, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:56:59 GMT", "ETag": "\"bce963d66714a73b\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:56:59+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-monthly-last.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:56:59 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"bce963d66714a73b\"", "Content-Length": "322"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-monthly-last-001\r\nDTSTAMP:20260330T195659Z\r\nSUMMARY:Smoke Monthly Last\r\nDTSTART;TZID=Europe/London:20260425T150000\r\nDTEND;TZID=Europe/London:20260425T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SA;BYSETPOS=-1\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 322, "body_truncated": false}} +{"ts": "2026-03-30T19:56:59+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "X-WP-User": "admin", "Content-Length": "362"}, "body": "{\"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\"}", "body_bytes": 362, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:56:59 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "673"}, "body": "{\"data\": {\"id\": 46, \"uid\": \"9fd41c1cc728b146f791@calendar-wp-plugin\", \"title\": \"Smoke 4th Sunday Anchor\", \"description\": \"anchor-normalization\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"start_datetime\": \"2026-05-24T15:00:00+01:00\", \"end_datetime\": \"2026-05-24T16:00:00+01:00\", \"repeat_type\": \"monthly\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"weekday_of_month\", \"repeat_nth_day\": null, \"repeat_nth_pos\": 4, \"repeat_nth_weekday\": 0, \"repeat_range_mode\": \"no_end\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"65b610e6c9d5be76\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T19:56:59+00:00\"}}", "body_bytes": 673, "body_truncated": false}} +{"ts": "2026-03-30T19:56:59+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/46/occurrences/2026-05-24T15:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:56:59 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:56:59+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/46.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:56:59 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"cb0262906b4e16d2\"", "Content-Length": "420"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:9fd41c1cc728b146f791@calendar-wp-plugin\r\nDTSTAMP:20260330T195659Z\r\nSUMMARY:Smoke 4th Sunday Anchor\r\nDESCRIPTION:anchor-normalization\r\nDTSTART;TZID=Europe/London:20260524T150000\r\nDTEND;TZID=Europe/London:20260524T160000\r\nRRULE:FREQ=MONTHLY;BYDAY=SU;BYSETPOS=4\r\nEXDATE;TZID=Europe/London:20260524T150000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 420, "body_truncated": false}} +{"ts": "2026-03-30T19:56:59+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "X-WP-User": "admin", "Content-Length": "295"}, "body": "{\"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\"}", "body_bytes": 295, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:56:59 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "655"}, "body": "{\"data\": {\"id\": 47, \"uid\": \"5058caddf2fc8c58b4dc@calendar-wp-plugin\", \"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_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"until\", \"repeat_count\": null, \"repeat_until\": \"2026-03-19\", \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"e65810e02d6e9a50\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T19:56:59+00:00\"}}", "body_bytes": 655, "body_truncated": false}} +{"ts": "2026-03-30T19:56:59+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/47/occurrences/2026-03-11T11:00:00+01:00", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:56:59 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:56:59+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/47/occurrences?from=2026-03-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:56:59 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "5212"}, "body": "{\"data\": [{\"event_id\": 47, \"uid\": \"5058caddf2fc8c58b4dc@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-02T10:00:00+00:00\", \"occurrence_end\": \"2026-03-02T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 47, \"uid\": \"5058caddf2fc8c58b4dc@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-03T10:00:00+00:00\", \"occurrence_end\": \"2026-03-03T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 47, \"uid\": \"5058caddf2fc8c58b4dc@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-04T10:00:00+00:00\", \"occurrence_end\": \"2026-03-04T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 47, \"uid\": \"5058caddf2fc8c58b4dc@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-05T10:00:00+00:00\", \"occurrence_end\": \"2026-03-05T11:00:00", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T19:56:59+00:00", "client": "127.0.0.1", "method": "DELETE", "path": "/wp-json/calendar/v1/events/47/occurrences/2026-03-10", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 204, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:56:59 GMT"}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:56:59+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/47/occurrences?from=2026-03-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:56:59 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "4906"}, "body": "{\"data\": [{\"event_id\": 47, \"uid\": \"5058caddf2fc8c58b4dc@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-02T10:00:00+00:00\", \"occurrence_end\": \"2026-03-02T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 47, \"uid\": \"5058caddf2fc8c58b4dc@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-03T10:00:00+00:00\", \"occurrence_end\": \"2026-03-03T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 47, \"uid\": \"5058caddf2fc8c58b4dc@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-04T10:00:00+00:00\", \"occurrence_end\": \"2026-03-04T11:00:00+00:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 47, \"uid\": \"5058caddf2fc8c58b4dc@calendar-wp-plugin\", \"title\": \"Smoke Daily Exception TZ\", \"description\": \"tz-key\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-05T10:00:00+00:00\", \"occurrence_end\": \"2026-03-05T11:00:00", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T19:56:59+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-exdate-import.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "323"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-exdate-import-001\nSUMMARY:Smoke EXDATE Import\nDTSTART;TZID=Europe/London:20260302T100000\nDTEND;TZID=Europe/London:20260302T110000\nRRULE:FREQ=DAILY;UNTIL=20260319T235959\nEXDATE;TZID=Europe/London:20260310T100000,20260311T100000\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 323, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:56:59 GMT", "ETag": "\"3894b8874c8aec1f\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:56:59+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:56:59 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "30451"}, "body": "{\"data\": [{\"id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"start_datetime\": \"2026-04-01T10:00:00+01:00\", \"end_datetime\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"eb53d018557c8fca\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T17:40:10+00:00\"}, {\"id\": 2, \"uid\": \"fixture-ce-002@calendar-wp-plugin\", \"title\": \"Office Closed\", \"description\": \"Public holiday closure.\", \"location\": \"HQ\", \"category\": \"Operations\", \"all_day_event\": true, \"start_datetime\": \"2026-05-04\", \"end_datetime\": \"2026-05-05\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"190a4", "body_bytes": 3927, "body_truncated": true}} +{"ts": "2026-03-30T19:56:59+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/18/occurrences?from=2026-03-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:56:59 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "4474"}, "body": "{\"data\": [{\"event_id\": 18, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-02T10:00:00+01:00\", \"occurrence_end\": \"2026-03-02T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 18, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-03T10:00:00+01:00\", \"occurrence_end\": \"2026-03-03T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 18, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-04T10:00:00+01:00\", \"occurrence_end\": \"2026-03-04T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 18, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE Import\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-03-05T10:00:00+01:00\", \"occurrence_end\": \"2026-03-05T11:00:00+01:00\", \"repeat_type\": \"daily\"}, {\"event_id\": 18, \"uid\": \"smoke-exdate-import-001\", \"title\": \"Smoke EXDATE ", "body_bytes": 3928, "body_truncated": true}} +{"ts": "2026-03-30T19:56:59+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/smoke-cancelled-occurrence.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "text/calendar; charset=utf-8", "Content-Length": "467"}, "body": "BEGIN:VCALENDAR\nPRODID:-//Smoke//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:smoke-cancelled-occurrence-001\nSUMMARY:Smoke Cancelled Occurrence\nDTSTART;TZID=Europe/London:20260408T123000\nDTEND;TZID=Europe/London:20260408T133000\nRRULE:FREQ=WEEKLY\nEND:VEVENT\nBEGIN:VEVENT\nUID:smoke-cancelled-occurrence-001\nRECURRENCE-ID;TZID=Europe/London:20260506T123000\nDTSTART;TZID=Europe/London:20260506T123000\nDTEND;TZID=Europe/London:20260506T133000\nSTATUS:CANCELLED\nEND:VEVENT\nEND:VCALENDAR\n", "body_bytes": 467, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:56:59 GMT", "ETag": "\"14340d12265ad2dd\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:56:59+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/smoke-cancelled-occurrence.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:56:59 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"14340d12265ad2dd\"", "Content-Length": "359"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-cancelled-occurrence-001\r\nDTSTAMP:20260330T195659Z\r\nSUMMARY:Smoke Cancelled Occurrence\r\nDTSTART;TZID=Europe/London:20260408T123000\r\nDTEND;TZID=Europe/London:20260408T133000\r\nRRULE:FREQ=WEEKLY\r\nEXDATE;TZID=Europe/London:20260506T123000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 359, "body_truncated": false}} +{"ts": "2026-03-30T19:56:59+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:56:59 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "30452"}, "body": "{\"data\": [{\"id\": 1, \"uid\": \"fixture-ce-001@calendar-wp-plugin\", \"title\": \"Board Meeting\", \"description\": \"Quarterly board review.\", \"location\": \"Room A\", \"category\": \"Governance\", \"all_day_event\": false, \"start_datetime\": \"2026-04-01T10:00:00+01:00\", \"end_datetime\": \"2026-04-01T11:30:00+01:00\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"eb53d018557c8fca\\\"\", \"sync_version\": 1, \"updated_at\": \"2026-03-30T17:40:10+00:00\"}, {\"id\": 2, \"uid\": \"fixture-ce-002@calendar-wp-plugin\", \"title\": \"Office Closed\", \"description\": \"Public holiday closure.\", \"location\": \"HQ\", \"category\": \"Operations\", \"all_day_event\": true, \"start_datetime\": \"2026-05-04\", \"end_datetime\": \"2026-05-05\", \"repeat_type\": \"none\", \"repeat_interval\": 1, \"repeat_nth_mode\": \"\", \"repeat_nth_day\": null, \"repeat_nth_pos\": null, \"repeat_nth_weekday\": null, \"repeat_range_mode\": \"none\", \"repeat_count\": null, \"repeat_until\": null, \"timezone\": \"Europe/London\", \"calendar_id\": \"public\", \"etag\": \"\\\"190a4", "body_bytes": 3927, "body_truncated": true}} +{"ts": "2026-03-30T19:56:59+00:00", "client": "127.0.0.1", "method": "GET", "path": "/wp-json/calendar/v1/events/19/occurrences?from=2026-05-01&months=1", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "X-WP-User": "admin"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:56:59 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "892"}, "body": "{\"data\": [{\"event_id\": 19, \"uid\": \"smoke-cancelled-occurrence-001\", \"title\": \"Smoke Cancelled Occurrence\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-13T12:30:00+01:00\", \"occurrence_end\": \"2026-05-13T13:30:00+01:00\", \"repeat_type\": \"weekly\"}, {\"event_id\": 19, \"uid\": \"smoke-cancelled-occurrence-001\", \"title\": \"Smoke Cancelled Occurrence\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-20T12:30:00+01:00\", \"occurrence_end\": \"2026-05-20T13:30:00+01:00\", \"repeat_type\": \"weekly\"}, {\"event_id\": 19, \"uid\": \"smoke-cancelled-occurrence-001\", \"title\": \"Smoke Cancelled Occurrence\", \"description\": \"\", \"location\": \"\", \"category\": \"\", \"all_day_event\": false, \"occurrence_start\": \"2026-05-27T12:30:00+01:00\", \"occurrence_end\": \"2026-05-27T13:30:00+01:00\", \"repeat_type\": \"weekly\"}]}", "body_bytes": 892, "body_truncated": false}} +{"ts": "2026-03-30T19:56:59+00:00", "client": "127.0.0.1", "method": "GET", "path": "/calendar", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:56:59 GMT", "Content-Type": "text/html; charset=utf-8", "Content-Length": "43447"}, "body": "[omitted html payload: 3934 bytes]", "body_bytes": 3934, "body_truncated": true}} +{"ts": "2026-03-30T19:57:00+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/users/register", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "Content-Length": "77"}, "body": "{\"email\": \"sec-nodisclose-1774900618@example.test\", \"password\": \"***\"}", "body_bytes": 77, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:57:00 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "99"}, "body": "{\"data\": {\"user_id\": 27, \"status\": \"pending_approval\", \"email_status\": \"sent\", \"email_sent\": true}}", "body_bytes": 99, "body_truncated": false}} +{"ts": "2026-03-30T19:57:00+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/users/forgot-password", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "Content-Length": "39"}, "body": "{\"email\": \"adrians@chezstephens.org.uk\"}", "body_bytes": 39, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:57:00 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "70"}, "body": "{\"data\": {\"status\": \"ok\", \"email_status\": \"sent\", \"email_sent\": true}}", "body_bytes": 70, "body_truncated": false}} +{"ts": "2026-03-30T19:57:00+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/users/register", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "Content-Length": "73"}, "body": "{\"email\": \"sec-rate-1774900618-1@example.test\", \"password\": \"***\"}", "body_bytes": 73, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:57:00 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "99"}, "body": "{\"data\": {\"user_id\": 28, \"status\": \"pending_approval\", \"email_status\": \"sent\", \"email_sent\": true}}", "body_bytes": 99, "body_truncated": false}} +{"ts": "2026-03-30T19:57:00+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/users/register", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "Content-Length": "73"}, "body": "{\"email\": \"sec-rate-1774900618-2@example.test\", \"password\": \"***\"}", "body_bytes": 73, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:57:00 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "99"}, "body": "{\"data\": {\"user_id\": 29, \"status\": \"pending_approval\", \"email_status\": \"sent\", \"email_sent\": true}}", "body_bytes": 99, "body_truncated": false}} +{"ts": "2026-03-30T19:57:00+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/users/register", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "Content-Length": "73"}, "body": "{\"email\": \"sec-rate-1774900618-3@example.test\", \"password\": \"***\"}", "body_bytes": 73, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:57:00 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "99"}, "body": "{\"data\": {\"user_id\": 30, \"status\": \"pending_approval\", \"email_status\": \"sent\", \"email_sent\": true}}", "body_bytes": 99, "body_truncated": false}} +{"ts": "2026-03-30T19:57:01+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/users/register", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "Content-Length": "73"}, "body": "{\"email\": \"sec-rate-1774900618-4@example.test\", \"password\": \"***\"}", "body_bytes": 73, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:57:01 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "99"}, "body": "{\"data\": {\"user_id\": 31, \"status\": \"pending_approval\", \"email_status\": \"sent\", \"email_sent\": true}}", "body_bytes": 99, "body_truncated": false}} +{"ts": "2026-03-30T19:57:01+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/users/register", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "Content-Length": "73"}, "body": "{\"email\": \"sec-rate-1774900618-5@example.test\", \"password\": \"***\"}", "body_bytes": 73, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:57:01 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "99"}, "body": "{\"data\": {\"user_id\": 32, \"status\": \"pending_approval\", \"email_status\": \"sent\", \"email_sent\": true}}", "body_bytes": 99, "body_truncated": false}} +{"ts": "2026-03-30T19:57:01+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/users/register", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "Content-Length": "73"}, "body": "{\"email\": \"sec-rate-1774900618-6@example.test\", \"password\": \"***\"}", "body_bytes": 73, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:57:01 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "99"}, "body": "{\"data\": {\"user_id\": 33, \"status\": \"pending_approval\", \"email_status\": \"sent\", \"email_sent\": true}}", "body_bytes": 99, "body_truncated": false}} +{"ts": "2026-03-30T19:57:01+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/users/register", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "Content-Length": "73"}, "body": "{\"email\": \"sec-rate-1774900618-7@example.test\", \"password\": \"***\"}", "body_bytes": 73, "body_truncated": false}, "response": {"status": 201, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:57:01 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "99"}, "body": "{\"data\": {\"user_id\": 34, \"status\": \"pending_approval\", \"email_status\": \"sent\", \"email_sent\": true}}", "body_bytes": 99, "body_truncated": false}} +{"ts": "2026-03-30T19:57:01+00:00", "client": "127.0.0.1", "method": "POST", "path": "/wp-json/calendar/v1/users/register", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Type": "application/json", "Content-Length": "73"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 429, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:57:01 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "98"}, "body": "{\"error\": {\"code\": \"rate_limited\", \"message\": \"Too many registration attempts. Try again later.\"}}", "body_bytes": 98, "body_truncated": false}} +{"ts": "2026-03-30T19:57:01+00:00", "client": "127.0.0.1", "method": "PUT", "path": "/caldav/calendars/public/sec-1774900621.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*", "Content-Length": "201", "Content-Type": "application/x-www-form-urlencoded"}, "body": "[omitted non-text payload: 201 bytes, content-type=application/x-www-form-urlencoded]", "body_bytes": 201, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:57:01 GMT", "ETag": "\"32633048dcc96299\""}, "body": "", "body_bytes": 0, "body_truncated": false}} +{"ts": "2026-03-30T19:57:02+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/sec-1774900621.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 200, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:57:02 GMT", "Content-Type": "text/calendar; charset=utf-8", "ETag": "\"32633048dcc96299\"", "Content-Length": "287"}, "body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:sec-smoke-uid@example.test\r\nDTSTAMP:20260330T195702Z\r\nSUMMARY:Security Smoke Event\r\nDTSTART;TZID=Europe/London:20260415T100000\r\nDTEND;TZID=Europe/London:20260415T110000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", "body_bytes": 287, "body_truncated": false}} +{"ts": "2026-03-30T19:57:02+00:00", "client": "127.0.0.1", "method": "GET", "path": "/caldav/calendars/public/999999.ics", "request": {"headers": {"Host": "127.0.0.1:8080", "Authorization": "***", "User-Agent": "curl/8.14.1", "Accept": "*/*"}, "body": "", "body_bytes": 0, "body_truncated": false}, "response": {"status": 404, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Mon, 30 Mar 2026 19:57:02 GMT", "Content-Type": "application/json; charset=utf-8", "Content-Length": "62"}, "body": "{\"error\": {\"code\": \"not_found\", \"message\": \"Route not found\"}}", "body_bytes": 62, "body_truncated": false}} +{"ts": "2026-03-31T04:26:37+00:00", "client": "127.0.0.1", "method": "PROPFIND", "path": "/caldav/calendars/public/", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/xml; charset=utf-8", "Content-Length": "144", "Depth": "1", "Origin": "http://127.0.0.1:8080", "DNT": "1", "Authorization": "***", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "\n", "body_bytes": 144, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Tue, 31 Mar 2026 04:26:37 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "15045"}, "body": "\n\n\n /caldav/calendars/public/\n \n \n Public Calendar\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/1.ics\n \n \n text/calendar; charset=utf-8\"eb53d018557c8fca\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/2.ics\n \n \n text/calendar; charset=utf-8\"190a40db82a13a9b\"\n \n HTTP/1.1 200 OK\n \n\n\n /caldav/calendars/public/3.ics\n \n \n ...(truncated)", "body_bytes": 3918, "body_truncated": true}} +{"ts": "2026-03-31T04:26:37+00:00", "client": "127.0.0.1", "method": "REPORT", "path": "/caldav/calendars/public/", "request": {"headers": {"Host": "127.0.0.1:8080", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0 Thunderbird/140.8.1", "Accept": "text/xml", "Accept-Language": "en-GB,en;q=0.5", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Charset": "utf-8,*;q=0.1", "Content-Type": "text/xml; charset=utf-8", "Content-Length": "1644", "Depth": "1", "Origin": "http://127.0.0.1:8080", "DNT": "1", "Authorization": "***", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "no-cors", "Sec-Fetch-Site": "same-origin", "Priority": "u=4", "Pragma": "no-cache", "Cache-Control": "no-cache"}, "body": "\n/caldav/calendars/public/47.ics/caldav/calendars/public/46.ics/caldav/calendars/public/45.ics/caldav/calendars/public/44.ics/caldav/calendars/public/43.ics/caldav/calendars/public/42.ics/caldav/calendars/public/41.ics/caldav/calendars/public/40.ics/caldav/calendars/public/39.ics/caldav/calendars/public/38.ics/caldav/calendars/public/37.ics/caldav/calendars/public/36.ics/caldav/calendars/public/35.ics/caldav/calendars/public/34.ics/caldav/calendars/public/33.ics/caldav/calendars/public/32.ics/caldav/calendars/public/31.ics/caldav/calendars/public/30.ics/caldav/calendars/public/29.ics/caldav/calendars/public/28.ics/caldav/calendars/public/27.ics/caldav/calendars/publ ...(truncated)", "body_bytes": 1644, "body_truncated": false}, "response": {"status": 207, "headers": {"Server": "CalendarFixture/0.1 Python/3.13.5", "Date": "Tue, 31 Mar 2026 04:26:37 GMT", "Content-Type": "application/xml; charset=utf-8", "Content-Length": "24725"}, "body": "\n\n\n /caldav/calendars/public/smoke-vtimezone.ics\n \n \n \"4777d0172be8ffc5\"\n BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\nBEGIN:VEVENT\r\nUID:smoke-vtimezone-parser-001\r\nDTSTAMP:20260331T042637Z\r\nSUMMARY:Smoke VTIMEZONE Parse\r\nDTSTART;TZID=Europe/London:20260423T150000\r\nDTEND;TZID=Europe/London:20260423T160000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n\n \n HTTP/1.1 200 OK\n \n \n\n /caldav/calendars/public/smoke-monthly-nth.ics\n \n \n \"93aae3c5c0e310d1\"\n BEGIN:V ...(truncated)", "body_bytes": 3918, "body_truncated": true}} diff --git a/fixture/init.sh b/fixture/init.sh new file mode 100755 index 0000000..a56c6ca --- /dev/null +++ b/fixture/init.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +set -euo pipefail +cd "$(dirname "$0")/.." +python3 fixture/server.py init diff --git a/fixture/reset.sh b/fixture/reset.sh new file mode 100755 index 0000000..71b24f5 --- /dev/null +++ b/fixture/reset.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +set -euo pipefail +cd "$(dirname "$0")/.." +python3 fixture/server.py reset diff --git a/fixture/run.sh b/fixture/run.sh new file mode 100755 index 0000000..9e2ee07 --- /dev/null +++ b/fixture/run.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +set -euo pipefail +cd "$(dirname "$0")/.." +python3 fixture/server.py run --host "${FIXTURE_HOST:-127.0.0.1}" --port "${FIXTURE_PORT:-8080}" diff --git a/fixture/seed.sh b/fixture/seed.sh new file mode 100755 index 0000000..5b060a0 --- /dev/null +++ b/fixture/seed.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +set -euo pipefail +cd "$(dirname "$0")/.." +python3 fixture/server.py seed diff --git a/fixture/server.py b/fixture/server.py new file mode 100644 index 0000000..208a9a8 --- /dev/null +++ b/fixture/server.py @@ -0,0 +1,4230 @@ +#!/usr/bin/env python3 +""" +Local fixture harness for the calendar plugin requirements. + +Commands: + python3 fixture/server.py init + python3 fixture/server.py reset + python3 fixture/server.py seed + python3 fixture/server.py run --host 127.0.0.1 --port 8080 +""" + +from __future__ import annotations + +import argparse +import base64 +import hashlib +import hmac +import json +import os +import re +import secrets +import smtplib +import sqlite3 +import ssl +import textwrap +import time +import calendar as pycalendar +from collections import deque +from email.message import EmailMessage +from dataclasses import dataclass +from datetime import date, datetime, timedelta, timezone +from http import HTTPStatus +from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer +from pathlib import Path +from typing import Any, Dict, List, Optional, Tuple +from urllib.parse import parse_qs, unquote, urlparse + + +ROOT = Path(__file__).resolve().parent +DB_PATH = ROOT / "fixture.db" +DEFAULT_TIMEZONE = "Europe/London" +SHARED_CALENDAR_ID = "public" +API_BASE = "/wp-json/calendar/v1" +PBKDF2_ITERATIONS = 210_000 +VERIFY_TOKEN_TTL_SECONDS = 24 * 60 * 60 +RESET_TOKEN_TTL_SECONDS = 30 * 60 +_RATE_LIMIT_STATE: Dict[str, List[float]] = {} + + +def fixture_trace_log_path() -> Path: + configured = (os.getenv("FIXTURE_HTTP_TRACE_LOG") or "").strip() + if configured: + p = Path(configured) + return p if p.is_absolute() else (ROOT / p) + return ROOT / "http_trace.log" + + +def utc_now_iso() -> str: + return datetime.now(timezone.utc).replace(microsecond=0).isoformat() + + +def hash_password(value: str) -> str: + # Stronger hash format for fixture: pbkdf2_sha256$iterations$salt_hex$digest_hex + salt = secrets.token_bytes(16) + digest = hashlib.pbkdf2_hmac("sha256", value.encode("utf-8"), salt, PBKDF2_ITERATIONS) + return f"pbkdf2_sha256${PBKDF2_ITERATIONS}${salt.hex()}${digest.hex()}" + + +def verify_password(value: str, stored_hash: str) -> bool: + if not stored_hash: + return False + if stored_hash.startswith("pbkdf2_sha256$"): + parts = stored_hash.split("$") + if len(parts) != 4: + return False + try: + iterations = int(parts[1]) + salt = bytes.fromhex(parts[2]) + expected = bytes.fromhex(parts[3]) + except ValueError: + return False + actual = hashlib.pbkdf2_hmac("sha256", value.encode("utf-8"), salt, iterations) + return hmac.compare_digest(actual, expected) + # Backward-compatible legacy support for old unsalted SHA-256 hashes. + if re.fullmatch(r"[0-9a-f]{64}", stored_hash): + legacy = hashlib.sha256(value.encode("utf-8")).hexdigest() + return hmac.compare_digest(legacy, stored_hash) + return False + + +def _rate_limited(scope: str, key: str, limit: int, window_seconds: int) -> bool: + now = time.monotonic() + bucket = f"{scope}:{key}" + hits = _RATE_LIMIT_STATE.get(bucket, []) + cutoff = now - window_seconds + hits = [t for t in hits if t >= cutoff] + blocked = len(hits) >= limit + hits.append(now) + _RATE_LIMIT_STATE[bucket] = hits[-max(limit * 2, 16) :] + return blocked + + +def mk_etag(seed: str) -> str: + digest = hashlib.sha256(seed.encode("utf-8")).hexdigest()[:16] + return f'"{digest}"' + + +def parse_basic_auth(header_value: Optional[str]) -> Optional[Tuple[str, str]]: + if not header_value or not header_value.startswith("Basic "): + return None + encoded = header_value[6:].strip() + try: + raw = base64.b64decode(encoded).decode("utf-8") + except Exception: + return None + if ":" not in raw: + return None + username, password = raw.split(":", 1) + return username, password + + +def _read_env_file(path: Path) -> Dict[str, str]: + out: Dict[str, str] = {} + if not path.exists(): + return out + for raw in path.read_text(encoding="utf-8").splitlines(): + line = raw.strip() + if not line or line.startswith("#") or "=" not in line: + continue + key, value = line.split("=", 1) + out[key.strip()] = value.strip().strip('"').strip("'") + return out + + +def _smtp_settings() -> Dict[str, str]: + env = _read_env_file(ROOT.parent / "credentials" / ".env") + merged = dict(env) + merged.update({k: v for k, v in os.environ.items() if k.startswith("SMTP_")}) + return merged + + +def send_fixture_email(to_email: str, subject: str, body_text: str) -> Tuple[bool, str]: + cfg = _smtp_settings() + host = (cfg.get("SMTP_HOST") or "").strip() + port = int((cfg.get("SMTP_PORT") or "587").strip()) + username = (cfg.get("SMTP_USERNAME") or "").strip() + password = cfg.get("SMTP_PASSWORD") or "" + from_email = (cfg.get("SMTP_FROM") or "").strip() + use_tls = (cfg.get("SMTP_USE_TLS") or "true").strip().lower() in {"1", "true", "yes", "on"} + if not host or not from_email or not to_email: + return False, "smtp_not_configured" + msg = EmailMessage() + msg["From"] = from_email + msg["To"] = to_email + msg["Subject"] = subject + msg.set_content(body_text) + try: + with smtplib.SMTP(host, port, timeout=10) as server: + if use_tls: + server.starttls(context=ssl.create_default_context()) + if username: + server.login(username, password) + server.send_message(msg) + return True, "sent" + except Exception as exc: # pragma: no cover - environment dependent + return False, f"smtp_error:{exc.__class__.__name__}:{exc}" + + +def ensure_db() -> sqlite3.Connection: + conn = sqlite3.connect(DB_PATH) + conn.row_factory = sqlite3.Row + return conn + + +def create_schema(conn: sqlite3.Connection) -> None: + cur = conn.cursor() + cur.executescript( + """ + CREATE TABLE IF NOT EXISTS events ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + uid TEXT NOT NULL UNIQUE, + title TEXT NOT NULL, + description TEXT NOT NULL DEFAULT '', + location TEXT NOT NULL DEFAULT '', + category TEXT NOT NULL DEFAULT '', + all_day_event INTEGER NOT NULL DEFAULT 0, + start_datetime TEXT NOT NULL, + end_datetime TEXT NOT NULL, + repeat_type TEXT NOT NULL DEFAULT 'none', + repeat_interval INTEGER NOT NULL DEFAULT 1, + repeat_nth_mode TEXT NOT NULL DEFAULT '', + repeat_nth_day INTEGER NULL, + repeat_nth_pos INTEGER NULL, + repeat_nth_weekday INTEGER NULL, + repeat_range_mode TEXT NOT NULL DEFAULT 'none', + repeat_count INTEGER NULL, + repeat_until TEXT NULL, + timezone TEXT NOT NULL DEFAULT 'Europe/London', + calendar_id TEXT NOT NULL DEFAULT 'public', + etag TEXT NOT NULL, + sync_version INTEGER NOT NULL DEFAULT 1, + last_modified_by_user_id INTEGER NULL, + created_at TEXT NOT NULL, + updated_at TEXT NOT NULL + ); + + CREATE TABLE IF NOT EXISTS recurrence_exceptions ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + event_id INTEGER NOT NULL, + occurrence_key TEXT NOT NULL, + exception_type TEXT NOT NULL, + override_payload TEXT NULL, + created_at TEXT NOT NULL, + updated_at TEXT NOT NULL, + UNIQUE (event_id, occurrence_key) + ); + + CREATE TABLE IF NOT EXISTS caldav_users ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + email TEXT NOT NULL UNIQUE, + password_hash TEXT NOT NULL, + email_verified_at TEXT NULL, + account_status TEXT NOT NULL, + access_level TEXT NOT NULL, + request_state TEXT NOT NULL DEFAULT 'none', + created_at TEXT NOT NULL, + updated_at TEXT NOT NULL + ); + + CREATE TABLE IF NOT EXISTS user_tokens ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL, + token_type TEXT NOT NULL, + token_hash TEXT NOT NULL, + expires_at TEXT NOT NULL, + used_at TEXT NULL, + created_at TEXT NOT NULL + ); + + CREATE TABLE IF NOT EXISTS audit_log ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + actor_type TEXT NOT NULL, + actor_id TEXT NOT NULL, + action TEXT NOT NULL, + target_type TEXT NOT NULL, + target_id TEXT NOT NULL, + result TEXT NOT NULL, + context_json TEXT NOT NULL, + created_at TEXT NOT NULL + ); + + CREATE TABLE IF NOT EXISTS plugin_settings ( + key TEXT PRIMARY KEY, + value TEXT NOT NULL, + updated_at TEXT NOT NULL + ); + """ + ) + cols = {r["name"] for r in conn.execute("PRAGMA table_info(events)").fetchall()} + if "caldav_resource" not in cols: + conn.execute("ALTER TABLE events ADD COLUMN caldav_resource TEXT NULL") + conn.execute("CREATE UNIQUE INDEX IF NOT EXISTS idx_events_caldav_resource ON events(caldav_resource)") + conn.execute("UPDATE events SET caldav_resource = id || '.ics' WHERE caldav_resource IS NULL") + conn.commit() + + +def drop_all(conn: sqlite3.Connection) -> None: + cur = conn.cursor() + cur.executescript( + """ + DROP TABLE IF EXISTS audit_log; + DROP TABLE IF EXISTS user_tokens; + DROP TABLE IF EXISTS caldav_users; + DROP TABLE IF EXISTS recurrence_exceptions; + DROP TABLE IF EXISTS events; + DROP TABLE IF EXISTS plugin_settings; + """ + ) + conn.commit() + + +def log_audit( + conn: sqlite3.Connection, + actor_type: str, + actor_id: str, + action: str, + target_type: str, + target_id: str, + result: str, + context: Dict[str, Any], +) -> None: + conn.execute( + """ + INSERT INTO audit_log (actor_type, actor_id, action, target_type, target_id, result, context_json, created_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?) + """, + ( + actor_type, + actor_id, + action, + target_type, + target_id, + result, + json.dumps(context, sort_keys=True), + utc_now_iso(), + ), + ) + conn.commit() + + +def seed_data(conn: sqlite3.Connection) -> None: + now = utc_now_iso() + users = [ + ("rw_user@example.test", "rwpass123456", now, "active", "write", "approved"), + ("adrians@chezstephens.org.uk", "brillig1", now, "active", "write", "approved"), + ("pending_user@example.test", "pending123456", now, "pending_approval", "write", "requested"), + ] + for email, pw, verified, status, level, request_state in users: + conn.execute( + """ + INSERT OR IGNORE INTO caldav_users + (email, password_hash, email_verified_at, account_status, access_level, request_state, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?) + """, + (email, hash_password(pw), verified, status, level, request_state, now, now), + ) + + fixture_events: List[Dict[str, Any]] = [ + { + "id": 1, + "uid": "fixture-ce-001@calendar-wp-plugin", + "title": "Board Meeting", + "description": "Quarterly board review.", + "location": "Room A", + "category": "Governance", + "all_day_event": 0, + "start_datetime": "2026-04-01T10:00:00+01:00", + "end_datetime": "2026-04-01T11:30:00+01:00", + "repeat_type": "none", + "repeat_interval": 1, + "repeat_range_mode": "none", + "repeat_count": None, + "repeat_until": None, + }, + { + "id": 2, + "uid": "fixture-ce-002@calendar-wp-plugin", + "title": "Office Closed", + "description": "Public holiday closure.", + "location": "HQ", + "category": "Operations", + "all_day_event": 1, + "start_datetime": "2026-05-04", + "end_datetime": "2026-05-05", + "repeat_type": "none", + "repeat_interval": 1, + "repeat_range_mode": "none", + "repeat_count": None, + "repeat_until": None, + }, + { + "id": 3, + "uid": "fixture-ce-003@calendar-wp-plugin", + "title": "Daily Standup", + "description": "15 minute sync.", + "location": "Online", + "category": "Team", + "all_day_event": 0, + "start_datetime": "2026-04-06T09:00:00+01:00", + "end_datetime": "2026-04-06T09:15:00+01:00", + "repeat_type": "daily", + "repeat_interval": 1, + "repeat_range_mode": "count", + "repeat_count": 10, + "repeat_until": None, + }, + { + "id": 4, + "uid": "fixture-ce-004@calendar-wp-plugin", + "title": "Community Lunch", + "description": "Weekly community lunch.", + "location": "Cafeteria", + "category": "Community", + "all_day_event": 0, + "start_datetime": "2026-04-08T12:30:00+01:00", + "end_datetime": "2026-04-08T13:30:00+01:00", + "repeat_type": "weekly", + "repeat_interval": 1, + "repeat_range_mode": "no_end", + "repeat_count": None, + "repeat_until": None, + }, + { + "id": 5, + "uid": "fixture-ce-005@calendar-wp-plugin", + "title": "Finance Close", + "description": "Month-end close process.", + "location": "Finance Office", + "category": "Finance", + "all_day_event": 0, + "start_datetime": "2026-04-30T17:00:00+01:00", + "end_datetime": "2026-04-30T18:00:00+01:00", + "repeat_type": "monthly", + "repeat_interval": 1, + "repeat_range_mode": "until", + "repeat_count": None, + "repeat_until": "2026-08-31", + }, + { + "id": 6, + "uid": "fixture-ce-006@calendar-wp-plugin", + "title": "Annual Conference", + "description": "Annual community conference.", + "location": "Main Hall", + "category": "Events", + "all_day_event": 0, + "start_datetime": "2026-06-15T10:00:00+01:00", + "end_datetime": "2026-06-15T17:00:00+01:00", + "repeat_type": "yearly", + "repeat_interval": 1, + "repeat_range_mode": "count", + "repeat_count": 3, + "repeat_until": None, + }, + { + "id": 7, + "uid": "fixture-ce-007@calendar-wp-plugin", + "title": "Fortnightly Coaching", + "description": "Coaching check-in.", + "location": "Online", + "category": "Training", + "all_day_event": 0, + "start_datetime": "2026-04-07T15:00:00+01:00", + "end_datetime": "2026-04-07T16:00:00+01:00", + "repeat_type": "custom", + "repeat_interval": 2, + "repeat_range_mode": "until", + "repeat_count": None, + "repeat_until": "2026-07-31", + }, + { + "id": 8, + "uid": "fixture-ce-008@calendar-wp-plugin", + "title": "DST Validation Event", + "description": "Validates DST transition rendering.", + "location": "Lab", + "category": "QA", + "all_day_event": 0, + "start_datetime": "2026-10-25T00:30:00+01:00", + "end_datetime": "2026-10-25T02:30:00+00:00", + "repeat_type": "none", + "repeat_interval": 1, + "repeat_range_mode": "none", + "repeat_count": None, + "repeat_until": None, + }, + { + "id": 9, + "uid": "fixture-ce-009@calendar-wp-plugin", + "title": "Leap Day Marker", + "description": "Leap day recurrence behavior.", + "location": "Calendar", + "category": "QA", + "all_day_event": 0, + "start_datetime": "2028-02-29T09:00:00+00:00", + "end_datetime": "2028-02-29T10:00:00+00:00", + "repeat_type": "yearly", + "repeat_interval": 1, + "repeat_range_mode": "count", + "repeat_count": 3, + "repeat_until": None, + }, + { + "id": 10, + "uid": "fixture-ce-010@calendar-wp-plugin", + "title": "Therapy Session", + "description": "Used for single-occurrence delete exception tests.", + "location": "Clinic", + "category": "Health", + "all_day_event": 0, + "start_datetime": "2026-04-03T14:00:00+01:00", + "end_datetime": "2026-04-03T15:00:00+01:00", + "repeat_type": "weekly", + "repeat_interval": 1, + "repeat_range_mode": "count", + "repeat_count": 8, + "repeat_until": None, + }, + ] + + for event in fixture_events: + etag = mk_etag(f"{event['uid']}:{now}:1") + conn.execute( + """ + INSERT OR REPLACE INTO events + (id, uid, title, description, location, category, all_day_event, start_datetime, end_datetime, + repeat_type, repeat_interval, repeat_nth_mode, repeat_nth_day, repeat_nth_pos, repeat_nth_weekday, + repeat_range_mode, repeat_count, repeat_until, timezone, + calendar_id, caldav_resource, etag, sync_version, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + """, + ( + event["id"], + event["uid"], + event["title"], + event["description"], + event["location"], + event["category"], + event["all_day_event"], + event["start_datetime"], + event["end_datetime"], + event["repeat_type"], + event["repeat_interval"], + event.get("repeat_nth_mode", ""), + event.get("repeat_nth_day"), + event.get("repeat_nth_pos"), + event.get("repeat_nth_weekday"), + event["repeat_range_mode"], + event["repeat_count"], + event["repeat_until"], + DEFAULT_TIMEZONE, + SHARED_CALENDAR_ID, + f"{event['id']}.ics", + etag, + 1, + now, + now, + ), + ) + + conn.execute("DELETE FROM recurrence_exceptions") + conn.execute("INSERT OR REPLACE INTO plugin_settings (key, value, updated_at) VALUES (?, ?, ?)", ("presentation_name", "Calendar", now)) + conn.execute("INSERT OR REPLACE INTO plugin_settings (key, value, updated_at) VALUES (?, ?, ?)", ("url_slug", "", now)) + conn.execute("INSERT OR REPLACE INTO plugin_settings (key, value, updated_at) VALUES (?, ?, ?)", ("ics_access_mode", "public_read", now)) + conn.execute("INSERT OR REPLACE INTO plugin_settings (key, value, updated_at) VALUES (?, ?, ?)", ("caldav_calendar_name", "Public Calendar", now)) + conn.commit() + + +def event_to_dict(row: sqlite3.Row) -> Dict[str, Any]: + return { + "id": row["id"], + "uid": row["uid"], + "title": row["title"], + "description": row["description"], + "location": row["location"], + "category": row["category"], + "all_day_event": bool(row["all_day_event"]), + "start_datetime": row["start_datetime"], + "end_datetime": row["end_datetime"], + "repeat_type": row["repeat_type"], + "repeat_interval": row["repeat_interval"], + "repeat_nth_mode": row["repeat_nth_mode"], + "repeat_nth_day": row["repeat_nth_day"], + "repeat_nth_pos": row["repeat_nth_pos"], + "repeat_nth_weekday": row["repeat_nth_weekday"], + "repeat_range_mode": row["repeat_range_mode"], + "repeat_count": row["repeat_count"], + "repeat_until": row["repeat_until"], + "timezone": row["timezone"], + "calendar_id": row["calendar_id"], + "etag": row["etag"], + "sync_version": row["sync_version"], + "updated_at": row["updated_at"], + } + + +def get_setting(conn: sqlite3.Connection, key: str, default_value: str) -> str: + row = conn.execute("SELECT value FROM plugin_settings WHERE key = ?", (key,)).fetchone() + if not row: + return default_value + return row["value"] + + +def set_setting(conn: sqlite3.Connection, key: str, value: str) -> None: + conn.execute( + "INSERT OR REPLACE INTO plugin_settings (key, value, updated_at) VALUES (?, ?, ?)", + (key, value, utc_now_iso()), + ) + conn.commit() + + +def _parse_dt_maybe_date(value: str) -> datetime: + if "T" in value: + return datetime.fromisoformat(value.replace("Z", "+00:00")) + return datetime.fromisoformat(f"{value}T00:00:00+00:00") + + +def _canonical_occurrence_key(value: str) -> Optional[str]: + raw = (value or "").strip() + if not raw: + return None + try: + dt = _parse_dt_maybe_date(raw) + except ValueError: + return None + return dt.astimezone(timezone.utc).replace(microsecond=0).isoformat() + + +def _normalize_occurrence_key_for_event(value: str, event_start_iso: str) -> Optional[str]: + raw = (value or "").strip() + if not raw: + return None + if "T" in raw: + return _canonical_occurrence_key(raw) + # Allow date-only delete keys by inheriting time+offset from event start. + try: + base = _parse_dt_maybe_date(event_start_iso) + d = date.fromisoformat(raw) + except ValueError: + return None + candidate = base.replace(year=d.year, month=d.month, day=d.day) + return candidate.astimezone(timezone.utc).replace(microsecond=0).isoformat() + + +def _normalize_monthly_anchor( + start_iso: str, + end_iso: str, + repeat_type: str, + repeat_nth_mode: str, + repeat_nth_day: Optional[int], + repeat_nth_pos: Optional[int], + repeat_nth_weekday: Optional[int], +) -> Tuple[str, str]: + if repeat_type != "monthly": + return start_iso, end_iso + try: + start_dt = datetime.fromisoformat(start_iso.replace("Z", "+00:00")) + end_dt = datetime.fromisoformat(end_iso.replace("Z", "+00:00")) + except ValueError: + return start_iso, end_iso + duration = end_dt - start_dt + adjusted_start = start_dt + if repeat_nth_mode == "day_of_month" and repeat_nth_day: + day = max(1, min(int(repeat_nth_day), pycalendar.monthrange(start_dt.year, start_dt.month)[1])) + adjusted_start = start_dt.replace(day=day) + elif repeat_nth_mode == "weekday_of_month" and repeat_nth_pos and repeat_nth_weekday is not None: + nth_day = _nth_weekday_of_month(start_dt.year, start_dt.month, int(repeat_nth_weekday), int(repeat_nth_pos)) + if nth_day is not None: + adjusted_start = start_dt.replace(day=nth_day) + adjusted_end = adjusted_start + duration + return adjusted_start.isoformat(), adjusted_end.isoformat() + + +def _add_months(dt: datetime, months: int) -> datetime: + year = dt.year + (dt.month - 1 + months) // 12 + month = (dt.month - 1 + months) % 12 + 1 + day = min(dt.day, pycalendar.monthrange(year, month)[1]) + return dt.replace(year=year, month=month, day=day) + + +def _add_years(dt: datetime, years: int) -> datetime: + try: + return dt.replace(year=dt.year + years) + except ValueError: + # Leap-day fallback + return dt.replace(month=2, day=28, year=dt.year + years) + + +def _nth_weekday_of_month(year: int, month: int, weekday: int, pos: int) -> Optional[int]: + # weekday: 0=Sunday..6=Saturday, pos: 1..5 or -1 (last) + if not (0 <= weekday <= 6 and (1 <= pos <= 5 or pos == -1)): + return None + py_weekday = (weekday - 1) % 7 # python: Monday=0..Sunday=6 + if pos == -1: + days_in_month = pycalendar.monthrange(year, month)[1] + for day in range(days_in_month, 0, -1): + if date(year, month, day).weekday() == py_weekday: + return day + return None + day = 1 + hits = 0 + days_in_month = pycalendar.monthrange(year, month)[1] + while day <= days_in_month: + dt = date(year, month, day) + if dt.weekday() == py_weekday: + hits += 1 + if hits == pos: + return day + day += 1 + return None + + +def _window_for_view(view: str, anchor: date) -> Tuple[datetime, datetime]: + if view == "day": + start = datetime.fromisoformat(f"{anchor.isoformat()}T00:00:00+00:00") + return start, start + timedelta(days=1) + if view == "week": + monday = anchor - timedelta(days=anchor.weekday()) + start = datetime.fromisoformat(f"{monday.isoformat()}T00:00:00+00:00") + return start, start + timedelta(days=7) + if view == "month": + first = anchor.replace(day=1) + start = datetime.fromisoformat(f"{first.isoformat()}T00:00:00+00:00") + end = _add_months(start, 1) + return start, end + if view == "year": + first = date(anchor.year, 1, 1) + start = datetime.fromisoformat(f"{first.isoformat()}T00:00:00+00:00") + end = _add_years(start, 1) + return start, end + start = datetime.fromisoformat(f"{anchor.isoformat()}T00:00:00+00:00") + return start - timedelta(days=30), start + timedelta(days=90) + + +def _occurrence_overlap(s1: datetime, e1: datetime, s2: datetime, e2: datetime) -> bool: + return s1 < e2 and e1 > s2 + + +def make_uid(seed: str) -> str: + digest = hashlib.sha256(seed.encode("utf-8")).hexdigest()[:20] + return f"{digest}@calendar-wp-plugin" + + +def create_ics_event(event: Dict[str, Any], exdates: List[str]) -> str: + lines = [ + "BEGIN:VEVENT", + f"UID:{event['uid']}", + f"DTSTAMP:{_to_ics_ts(utc_now_iso())}", + f"SUMMARY:{_ics_escape(event['title'])}", + ] + if event.get("description"): + lines.append(f"DESCRIPTION:{_ics_escape(event['description'])}") + if event.get("location"): + lines.append(f"LOCATION:{_ics_escape(event['location'])}") + if event.get("category"): + lines.append(f"CATEGORIES:{_ics_escape(event['category'])}") + + if event.get("all_day_event"): + lines.append(f"DTSTART;VALUE=DATE:{_to_ics_date(event['start_datetime'])}") + lines.append(f"DTEND;VALUE=DATE:{_to_ics_date(event['end_datetime'])}") + else: + lines.append(f"DTSTART;TZID={DEFAULT_TIMEZONE}:{_to_ics_local(event['start_datetime'])}") + lines.append(f"DTEND;TZID={DEFAULT_TIMEZONE}:{_to_ics_local(event['end_datetime'])}") + + rrule = _build_rrule(event) + if rrule: + lines.append(f"RRULE:{rrule}") + if exdates: + start_tz = None + try: + start_tz = datetime.fromisoformat(event["start_datetime"].replace("Z", "+00:00")).tzinfo + except ValueError: + start_tz = None + normalized_exdates = [] + for x in exdates: + try: + dt = datetime.fromisoformat(x.replace("Z", "+00:00")) + if start_tz is not None and dt.tzinfo is not None: + dt = dt.astimezone(start_tz) + normalized_exdates.append(dt.strftime("%Y%m%dT%H%M%S")) + except ValueError: + continue + joined = ",".join(normalized_exdates) + if joined: + lines.append(f"EXDATE;TZID={DEFAULT_TIMEZONE}:{joined}") + + lines.append("END:VEVENT") + return "\r\n".join(lines) + + +def _build_rrule(event: Dict[str, Any]) -> Optional[str]: + freq_map = { + "daily": "DAILY", + "weekly": "WEEKLY", + "monthly": "MONTHLY", + "yearly": "YEARLY", + "custom": "WEEKLY", # First-pass simplification for fixture. + } + repeat_type = event.get("repeat_type") + if repeat_type not in freq_map or repeat_type == "none": + return None + parts = [f"FREQ={freq_map[repeat_type]}"] + interval = int(event.get("repeat_interval") or 1) + if interval > 1: + parts.append(f"INTERVAL={interval}") + if repeat_type == "monthly": + nth_mode = event.get("repeat_nth_mode") or "" + if nth_mode == "day_of_month" and event.get("repeat_nth_day"): + parts.append(f"BYMONTHDAY={int(event['repeat_nth_day'])}") + elif nth_mode == "weekday_of_month" and event.get("repeat_nth_pos") and event.get("repeat_nth_weekday") is not None: + weekday_map = ["SU", "MO", "TU", "WE", "TH", "FR", "SA"] + pos = int(event["repeat_nth_pos"]) + wd = int(event["repeat_nth_weekday"]) + if 0 <= wd <= 6 and (1 <= pos <= 5 or pos == -1): + parts.append(f"BYDAY={weekday_map[wd]}") + parts.append(f"BYSETPOS={pos}") + mode = event.get("repeat_range_mode") + if mode == "count" and event.get("repeat_count"): + parts.append(f"COUNT={int(event['repeat_count'])}") + elif mode == "until" and event.get("repeat_until"): + # date-only UNTIL for first pass + until = event["repeat_until"].replace("-", "") + parts.append(f"UNTIL={until}T235959") + return ";".join(parts) + + +def _to_ics_ts(iso_ts: str) -> str: + dt = datetime.fromisoformat(iso_ts.replace("Z", "+00:00")).astimezone(timezone.utc) + return dt.strftime("%Y%m%dT%H%M%SZ") + + +def _to_ics_local(iso_ts: str) -> str: + dt = datetime.fromisoformat(iso_ts.replace("Z", "+00:00")) + return dt.strftime("%Y%m%dT%H%M%S") + + +def _to_ics_date(iso_ts: str) -> str: + if "T" in iso_ts: + dt = datetime.fromisoformat(iso_ts.replace("Z", "+00:00")) + return dt.strftime("%Y%m%d") + return iso_ts.replace("-", "") + + +def _ics_escape(value: str) -> str: + return ( + value.replace("\\", "\\\\") + .replace(";", r"\;") + .replace(",", r"\,") + .replace("\n", r"\n") + ) + + +@dataclass +class AuthContext: + actor_type: str + actor_id: str + role: str + + +class _TraceWriter: + def __init__(self, handler: "FixtureHandler", raw): + self._handler = handler + self._raw = raw + + def write(self, data): + self._handler._trace_capture_response_bytes(data) + return self._raw.write(data) + + def flush(self): + return self._raw.flush() + + def __getattr__(self, name): + return getattr(self._raw, name) + + +class FixtureHandler(BaseHTTPRequestHandler): + server_version = "CalendarFixture/0.1" + _trace_body_cap = 4096 + _trace_body_preview_cap = 1200 + + def setup(self) -> None: + super().setup() + self.wfile = _TraceWriter(self, self.wfile) + self._trace_started = False + self._trace_request_body = b"" + self._trace_response_status: Optional[int] = None + self._trace_response_headers: List[Tuple[str, str]] = [] + self._trace_response_body = bytearray() + self._cached_request_body: Optional[bytes] = None + + def _trace_start(self) -> None: + self._trace_started = True + self._trace_request_body = b"" + self._trace_response_status = None + self._trace_response_headers = [] + self._trace_response_body = bytearray() + self._cached_request_body = None + + def _trace_capture_response_bytes(self, data: Any) -> None: + if not self._trace_started: + return + raw = data.encode("utf-8", errors="replace") if isinstance(data, str) else bytes(data) + remaining = self._trace_body_cap - len(self._trace_response_body) + if remaining > 0: + self._trace_response_body.extend(raw[:remaining]) + + def send_response(self, code: int, message: Optional[str] = None) -> None: + if getattr(self, "_trace_started", False): + self._trace_response_status = int(code) + super().send_response(code, message) + + def send_header(self, keyword: str, value: str) -> None: + if getattr(self, "_trace_started", False): + self._trace_response_headers.append((keyword, value)) + super().send_header(keyword, value) + + def _trace_log_path(self) -> Path: + return fixture_trace_log_path() + + def _redact_headers(self, headers: Dict[str, str]) -> Dict[str, str]: + out: Dict[str, str] = {} + for k, v in headers.items(): + if k.lower() in {"authorization", "cookie", "set-cookie"}: + out[k] = "***" + else: + out[k] = v + return out + + def _sanitize_json_for_log(self, value: Any) -> Any: + if isinstance(value, dict): + out: Dict[str, Any] = {} + for k, v in value.items(): + if str(k).lower() in {"password", "new_password", "token"}: + out[k] = "***" + else: + out[k] = self._sanitize_json_for_log(v) + return out + if isinstance(value, list): + return [self._sanitize_json_for_log(v) for v in value] + return value + + def _decode_body_for_log(self, body: bytes, content_type: str) -> str: + if not body: + return "" + ct = (content_type or "").lower() + raw = body[: self._trace_body_cap] + text = raw.decode("utf-8", errors="replace") + if "application/json" in ct: + try: + compact = json.dumps(self._sanitize_json_for_log(json.loads(text)), ensure_ascii=True) + if len(compact) > self._trace_body_preview_cap: + return compact[: self._trace_body_preview_cap] + " ...(truncated)" + return compact + except json.JSONDecodeError: + return text[: self._trace_body_preview_cap] + if "text/html" in ct: + return f"[omitted html payload: {len(body)} bytes]" + if "application/xml" in ct or "text/xml" in ct: + return text[: self._trace_body_preview_cap] + (" ...(truncated)" if len(text) > self._trace_body_preview_cap else "") + if "text/calendar" in ct: + return text[: self._trace_body_preview_cap] + (" ...(truncated)" if len(text) > self._trace_body_preview_cap else "") + if ct.startswith("text/"): + return text[: self._trace_body_preview_cap] + (" ...(truncated)" if len(text) > self._trace_body_preview_cap else "") + return f"[omitted non-text payload: {len(body)} bytes, content-type={content_type or 'unknown'}]" + + def _strip_http_response_preamble(self, raw: bytes) -> bytes: + if not raw.startswith(b"HTTP/"): + return raw + marker = b"\r\n\r\n" + idx = raw.find(marker) + if idx == -1: + return raw + return raw[idx + len(marker) :] + + def _read_trace_entries(self, limit: int = 20) -> List[Dict[str, Any]]: + path = self._trace_log_path() + if not path.exists(): + return [] + out: deque[Dict[str, Any]] = deque(maxlen=max(1, min(limit, 200))) + with path.open("r", encoding="utf-8") as f: + for line in f: + line = line.strip() + if not line: + continue + try: + out.append(json.loads(line)) + except json.JSONDecodeError: + continue + return list(out) + + def _trace_finish(self) -> None: + if not self._trace_started: + return + req_headers = self._redact_headers({k: v for (k, v) in self.headers.items()}) + resp_headers: Dict[str, str] = {} + for k, v in self._trace_response_headers: + resp_headers[k] = "***" if k.lower() == "set-cookie" else v + req_ct = req_headers.get("Content-Type", "") + resp_ct = resp_headers.get("Content-Type", "") + response_raw = bytes(self._trace_response_body) + response_body_raw = self._strip_http_response_preamble(response_raw) + entry = { + "ts": utc_now_iso(), + "client": self.client_address[0] if self.client_address else "", + "method": self.command, + "path": self.path, + "request": { + "headers": req_headers, + "body": self._decode_body_for_log(self._trace_request_body, req_ct), + "body_bytes": len(self._trace_request_body), + "body_truncated": len(self._trace_request_body) > self._trace_body_cap, + }, + "response": { + "status": self._trace_response_status, + "headers": resp_headers, + "body": self._decode_body_for_log(response_body_raw, resp_ct), + "body_bytes": len(response_body_raw), + "body_truncated": len(self._trace_response_body) >= self._trace_body_cap, + }, + } + path = self._trace_log_path() + path.parent.mkdir(parents=True, exist_ok=True) + with path.open("a", encoding="utf-8") as f: + f.write(json.dumps(entry, ensure_ascii=True) + "\n") + self._trace_started = False + + def _read_request_body_bytes(self) -> bytes: + if self._cached_request_body is not None: + return self._cached_request_body + length = int(self.headers.get("Content-Length", "0") or "0") + if length <= 0: + data = b"" + else: + data = self.rfile.read(length) + self._cached_request_body = data + self._trace_request_body = data[: self._trace_body_cap + 1] + return data + + def _slug_value(self) -> str: + conn = ensure_db() + try: + raw = get_setting(conn, "url_slug", "").strip() + finally: + conn.close() + raw = raw.strip("/") + return raw + + def _slug_prefix(self) -> str: + slug = self._slug_value() + return f"/{slug}" if slug else "" + + def _strip_slug(self, path: str) -> Optional[str]: + prefix = self._slug_prefix() + if not prefix: + return path + if path == prefix: + return "/" + if path.startswith(prefix + "/"): + return path[len(prefix) :] + return None + + def _with_slug(self, path: str) -> str: + prefix = self._slug_prefix() + return f"{prefix}{path}" if prefix else path + + def _request_query(self) -> Dict[str, List[str]]: + return parse_qs(urlparse(self.path).query) + + def _json_body(self) -> Dict[str, Any]: + data = self._read_request_body_bytes() + if not data: + return {} + return json.loads(data.decode("utf-8")) + + def _write_json(self, status: int, payload: Dict[str, Any]) -> None: + body = json.dumps(payload).encode("utf-8") + self.send_response(status) + self.send_header("Content-Type", "application/json; charset=utf-8") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + + def _write_text(self, status: int, text: str, content_type: str = "text/plain; charset=utf-8") -> None: + body = text.encode("utf-8") + self.send_response(status) + self.send_header("Content-Type", content_type) + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + + def _not_found(self) -> None: + self._write_json(404, {"error": {"code": "not_found", "message": "Route not found"}}) + + def _authenticate_wp_admin(self) -> Optional[AuthContext]: + # Fixture-only auth: header X-WP-User or query ?as=admin|editor. + wp_user = self.headers.get("X-WP-User", "") + if not wp_user: + wp_user = (self._request_query().get("as") or [""])[0] + if wp_user == "admin": + return AuthContext("wp_user", "admin", "wp_admin") + if wp_user == "editor": + return AuthContext("wp_user", "editor", "wp_editor") + return None + + def _authenticate_caldav_user(self) -> Optional[AuthContext]: + creds = parse_basic_auth(self.headers.get("Authorization")) + if not creds: + return None + email, password = creds + conn = ensure_db() + row = conn.execute( + """ + SELECT id, email, password_hash, account_status, email_verified_at + FROM caldav_users WHERE email = ? + """, + (email,), + ).fetchone() + if not row: + conn.close() + return None + if not verify_password(password, row["password_hash"]): + conn.close() + return None + if row["account_status"] != "active" or not row["email_verified_at"]: + conn.close() + return None + # Transparent legacy-hash upgrade on successful login. + if re.fullmatch(r"[0-9a-f]{64}", row["password_hash"]): + conn.execute( + "UPDATE caldav_users SET password_hash = ?, updated_at = ? WHERE id = ?", + (hash_password(password), utc_now_iso(), int(row["id"])), + ) + conn.commit() + conn.close() + return AuthContext("caldav_user", str(row["id"]), "caldav_write") + + def _require_api_admin(self) -> Optional[AuthContext]: + auth = self._authenticate_wp_admin() + if not auth: + self._write_json(401, {"error": {"code": "authentication_error", "message": "Admin authentication required"}}) + return None + return auth + + def _require_event_read_auth(self) -> Optional[AuthContext]: + wp = self._authenticate_wp_admin() + if wp: + return wp + caldav = self._authenticate_caldav_user() + if caldav: + return caldav + self._write_json(401, {"error": {"code": "authentication_error", "message": "Login required"}}) + return None + + def _require_event_write_auth(self) -> Optional[AuthContext]: + wp = self._authenticate_wp_admin() + if wp and wp.role in {"wp_admin", "wp_editor"}: + return wp + caldav = self._authenticate_caldav_user() + if caldav and caldav.role == "caldav_write": + return caldav + if wp or caldav: + self._write_json(403, {"error": {"code": "authorization_error", "message": "write capability required"}}) + return None + self._write_json(401, {"error": {"code": "authentication_error", "message": "Login required"}}) + return None + + def _validate_event_payload(self, payload: Dict[str, Any]) -> Optional[Dict[str, Any]]: + title = (payload.get("title") or "").strip() + start = payload.get("start_datetime") + end = payload.get("end_datetime") + if not title: + self._write_json(422, {"error": {"code": "validation_error", "message": "title is required"}}) + return None + if not start or not end: + self._write_json(422, {"error": {"code": "validation_error", "message": "start_datetime and end_datetime are required"}}) + return None + try: + ds = datetime.fromisoformat(start.replace("Z", "+00:00")) + de = datetime.fromisoformat(end.replace("Z", "+00:00")) + except ValueError: + self._write_json(422, {"error": {"code": "validation_error", "message": "invalid datetime format"}}) + return None + if de < ds: + self._write_json(422, {"error": {"code": "validation_error", "message": "end_datetime must be >= start_datetime"}}) + return None + normalized = { + "title": title, + "description": payload.get("description", ""), + "location": payload.get("location", ""), + "category": payload.get("category", ""), + "all_day_event": 1 if payload.get("all_day_event") else 0, + "start_datetime": start, + "end_datetime": end, + "repeat_type": payload.get("repeat_type", "none"), + "repeat_interval": int(payload.get("repeat_interval", 1)), + "repeat_nth_mode": payload.get("repeat_nth_mode", "") or "", + "repeat_nth_day": payload.get("repeat_nth_day"), + "repeat_nth_pos": payload.get("repeat_nth_pos"), + "repeat_nth_weekday": payload.get("repeat_nth_weekday"), + "repeat_range_mode": payload.get("repeat_range_mode", "none"), + "repeat_count": payload.get("repeat_count"), + "repeat_until": payload.get("repeat_until"), + "timezone": payload.get("timezone", DEFAULT_TIMEZONE), + } + normalized_start, normalized_end = _normalize_monthly_anchor( + normalized["start_datetime"], + normalized["end_datetime"], + str(normalized["repeat_type"] or "none"), + str(normalized["repeat_nth_mode"] or ""), + int(normalized["repeat_nth_day"]) if normalized["repeat_nth_day"] not in (None, "") else None, + int(normalized["repeat_nth_pos"]) if normalized["repeat_nth_pos"] not in (None, "") else None, + int(normalized["repeat_nth_weekday"]) if normalized["repeat_nth_weekday"] not in (None, "") else None, + ) + normalized["start_datetime"] = normalized_start + normalized["end_datetime"] = normalized_end + return normalized + + def do_OPTIONS(self) -> None: + self._trace_start() + try: + path = self._strip_slug(urlparse(self.path).path) + if path is None: + self.send_response(404) + self.end_headers() + return + if path.startswith("/caldav"): + self.send_response(200) + self.send_header("DAV", "1, 2, calendar-access") + self.send_header("Allow", "OPTIONS, PROPFIND, REPORT, GET, PUT, DELETE") + self.end_headers() + return + self.send_response(200) + self.send_header("Allow", "GET, POST, PUT, PATCH, DELETE, OPTIONS") + self.end_headers() + finally: + self._trace_finish() + + def do_GET(self) -> None: + self._trace_start() + try: + parsed = urlparse(self.path) + path = self._strip_slug(parsed.path) + if path is None: + self._not_found() + return + + if path == "/": + prefix = self._slug_prefix() + self._write_text( + 200, + "calendar fixture is running\n" + f"slug prefix: {prefix or '(none)'}\n" + f"admin: {self._with_slug('/admin.php')}?as=admin\n" + f"public calendar: {self._with_slug('/calendar')}\n" + f"api: {self._with_slug('/wp-json/calendar/v1/events')}\n" + f"ics: {self._with_slug('/calendar.ics')}\n" + f"caldav: {self._with_slug('/caldav/')}\n", + ) + return + + if path in {"/admin.php", "/admin.php/"}: + self._handle_admin_page(parsed) + return + + if path.startswith("/wp-admin/admin.php"): + self._handle_admin_page(parsed) + return + + if path == "/calendar.ics": + self._handle_ics_get() + return + + if path in {"/calendar", "/calendar/"}: + self._handle_public_calendar_page() + return + + if path in {"/calendar-sidebar", "/calendar-sidebar/"}: + self._handle_sidebar_shortcode_page() + return + + if path.startswith(API_BASE): + self._handle_api_get(path, parsed) + return + + if path.startswith("/caldav/"): + self._handle_caldav_get(path) + return + + self._not_found() + finally: + self._trace_finish() + + def _base_page(self, title: str, body_html: str) -> str: + return f""" + + + + + {_html_escape(title)} + + + +{body_html} + +""" + + def _handle_public_calendar_page(self) -> None: + page_html = """ +
Calendar Fixture Public Calendar UI
+
+
+
+
+
+ +
+
+
+ + + + + +
+
+ ICS Link + CalDAV +
+
+ +
+
+
+
+ + + +
+ +""" + page_html = page_html.replace("__ICS__", self._with_slug("/calendar.ics")) + page_html = page_html.replace("__CALDAV__", self._with_slug("/caldav/")) + page_html = page_html.replace("__ROOT__", json.dumps(self._slug_prefix())) + html = self._base_page("Calendar", page_html) + self._write_text(200, html, "text/html; charset=utf-8") + + def _handle_sidebar_shortcode_page(self) -> None: + page_html = """ +
Calendar Fixture Sidebar Shortcode Preview
+
+
+
Shortcode: [calendar_sidebar_upcoming]
+
Window: next 14 days
+
+
+
+ +""" + page_html = page_html.replace("__ROOT__", json.dumps(self._slug_prefix())) + html = self._base_page("Calendar Sidebar Shortcode", page_html) + self._write_text(200, html, "text/html; charset=utf-8") + + def do_POST(self) -> None: + self._trace_start() + try: + path = self._strip_slug(urlparse(self.path).path) + if path is None: + self._not_found() + return + if path.startswith(API_BASE): + self._handle_api_post(path) + return + self._not_found() + finally: + self._trace_finish() + + def do_PUT(self) -> None: + self._trace_start() + try: + path = self._strip_slug(urlparse(self.path).path) + if path is None: + self._not_found() + return + if path.startswith(API_BASE): + self._handle_api_put(path) + return + if path.startswith("/caldav/"): + self._handle_caldav_put(path) + return + self._not_found() + finally: + self._trace_finish() + + def do_PATCH(self) -> None: + self._trace_start() + try: + path = self._strip_slug(urlparse(self.path).path) + if path is None: + self._not_found() + return + if path.startswith(API_BASE): + self._handle_api_patch(path) + return + self._not_found() + finally: + self._trace_finish() + + def do_DELETE(self) -> None: + self._trace_start() + try: + path = self._strip_slug(urlparse(self.path).path) + if path is None: + self._not_found() + return + if path.startswith(API_BASE): + self._handle_api_delete(path) + return + if path.startswith("/caldav/"): + self._handle_caldav_delete(path) + return + self._not_found() + finally: + self._trace_finish() + + def do_PROPFIND(self) -> None: + self._trace_start() + try: + self._read_request_body_bytes() + path = self._strip_slug(urlparse(self.path).path) + if path is None: + self._not_found() + return + if not path.startswith("/caldav/"): + self._not_found() + return + auth = self._authenticate_caldav_user() + if not auth: + self.send_response(401) + self.send_header("WWW-Authenticate", 'Basic realm="calendar-caldav-fixture"') + self.end_headers() + return + prefix = self._slug_prefix() + caldav_root = f"{prefix}/caldav/" + principals_root = f"{prefix}/caldav/principals/" + principal_href = f"{prefix}/caldav/principals/user/" + calendars_root = f"{prefix}/caldav/calendars/" + public_calendar = f"{prefix}/caldav/calendars/public/" + conn = ensure_db() + caldav_calendar_name = get_setting(conn, "caldav_calendar_name", "Public Calendar") + conn.close() + def response_block(href: str, prop_xml: str) -> str: + return textwrap.dedent( + f"""\ + + {href} + + + {prop_xml} + + HTTP/1.1 200 OK + + + """ + ).strip() + + root_props = ( + "" + f"{principal_href}" + ) + principal_props = ( + "" + f"{calendars_root}" + ) + calendar_home_props = "" + public_calendar_props = ( + "" + f"{_xml_escape(caldav_calendar_name)}" + "" + "" + "" + ) + + responses: List[str] = [] + canonical = path if path.endswith("/") else f"{path}/" + if canonical in {"/caldav/", "/caldav"}: + responses.append(response_block(caldav_root, root_props)) + if self.headers.get("Depth", "0") != "0": + responses.append(response_block(principal_href, principal_props)) + responses.append(response_block(calendars_root, calendar_home_props)) + responses.append(response_block(public_calendar, public_calendar_props)) + elif canonical == "/caldav/principals/": + responses.append(response_block(principals_root, "")) + if self.headers.get("Depth", "0") != "0": + responses.append(response_block(principal_href, principal_props)) + elif canonical == "/caldav/principals/user/": + responses.append(response_block(principal_href, principal_props)) + elif canonical == "/caldav/calendars/": + responses.append(response_block(calendars_root, calendar_home_props)) + if self.headers.get("Depth", "0") != "0": + responses.append(response_block(public_calendar, public_calendar_props)) + elif canonical == "/caldav/calendars/public/": + responses.append(response_block(public_calendar, public_calendar_props)) + if self.headers.get("Depth", "0") != "0": + conn = ensure_db() + rows = conn.execute( + "SELECT id, etag, caldav_resource AS resource FROM events WHERE calendar_id = ? AND caldav_resource IS NOT NULL ORDER BY id ASC", + (SHARED_CALENDAR_ID,), + ).fetchall() + conn.close() + for row in rows: + href = f"{prefix}/caldav/calendars/public/{row['resource']}" + item_props = ( + "" + "text/calendar; charset=utf-8" + f"{row['etag']}" + ) + responses.append(response_block(href, item_props)) + else: + self._not_found() + return + + body = ( + '\n' + '\n' + + "\n".join(responses) + + "\n\n" + ) + self.send_response(207) + self.send_header("Content-Type", "application/xml; charset=utf-8") + self.send_header("Content-Length", str(len(body.encode("utf-8")))) + self.end_headers() + self.wfile.write(body.encode("utf-8")) + finally: + self._trace_finish() + + def do_REPORT(self) -> None: + self._trace_start() + try: + request_body = self._read_request_body_bytes().decode("utf-8", errors="replace") + path = self._strip_slug(urlparse(self.path).path) + if path is None: + self._not_found() + return + if not path.startswith("/caldav/"): + self._not_found() + return + auth = self._authenticate_caldav_user() + if not auth: + self.send_response(401) + self.send_header("WWW-Authenticate", 'Basic realm="calendar-caldav-fixture"') + self.end_headers() + return + conn = ensure_db() + all_rows = conn.execute( + "SELECT * FROM events WHERE calendar_id = ? AND caldav_resource IS NOT NULL ORDER BY id ASC", (SHARED_CALENDAR_ID,) + ).fetchall() + conn.close() + rows = list(all_rows) + requested_hrefs = re.findall(r"\s*([^<]+)\s*", request_body, re.IGNORECASE) + if "calendar-multiget" in request_body.lower() and requested_hrefs: + wanted_resources: set[str] = set() + for href in requested_hrefs: + href_path = urlparse(href.strip()).path + stripped = self._strip_slug(href_path) + if stripped is None: + stripped = href_path + filename = _caldav_filename_from_path(stripped) + if filename: + wanted_resources.add(filename) + if wanted_resources: + rows = [r for r in all_rows if str(r["caldav_resource"]) in wanted_resources] + else: + rows = [] + prefix = self._slug_prefix() + responses = [] + for row in rows: + evt = event_to_dict(row) + href = f"{prefix}/caldav/calendars/public/{row['caldav_resource']}" + ics = self._event_as_ics(evt) + responses.append( + textwrap.dedent( + f"""\ + + {href} + + + {row["etag"]} + {_xml_escape(ics)} + + HTTP/1.1 200 OK + + + """ + ).strip() + ) + body = ( + '\n' + '\n' + + "\n".join(responses) + + "\n\n" + ) + self.send_response(207) + self.send_header("Content-Type", "application/xml; charset=utf-8") + self.send_header("Content-Length", str(len(body.encode("utf-8")))) + self.end_headers() + self.wfile.write(body.encode("utf-8")) + finally: + self._trace_finish() + + def _handle_admin_page(self, parsed) -> None: + auth = self._authenticate_wp_admin() + if not auth: + html = self._base_page( + "Admin Access", + f""" +
Calendar Fixture Admin
+
+
+
Admin auth required (use ?as=admin or ?as=editor in fixture).
+

Useful Links

+ +
+
+""", + ) + self._write_text(401, html, "text/html; charset=utf-8") + return + query = parse_qs(parsed.query) + page_values = query.get("page") or [] + page = page_values[0] if page_values else "" + as_user = (query.get("as") or ["admin"])[0] + allowed = {"calendar-users", "calendar-setup", "calendar-diagnostics"} + if page and page not in allowed: + self._write_text(404, "unknown admin page") + return + if page in {"calendar-users", "calendar-setup", "calendar-diagnostics"} and auth.role != "wp_admin": + self._write_text(403, "forbidden") + return + nav = f""" +
+ Calendar Fixture Admin   + Users + Setup + Diagnostics + Public Calendar +
+
+
Authenticated as: {auth.actor_id} ({auth.role})
+""" + if not page: + body = nav + f""" +
+

Admin Pages

+ +
+
+""" + html = self._base_page("Admin Index", body) + if page == "calendar-users": + body = nav + self._admin_users_body(as_user) + "" + html = self._base_page("Users", body) + elif page == "calendar-setup": + body = nav + self._admin_setup_body(as_user) + "" + html = self._base_page("Setup", body) + elif page == "calendar-diagnostics": + body = nav + self._admin_diagnostics_body(as_user) + "" + html = self._base_page("Diagnostics", body) + self._write_text(200, html, "text/html; charset=utf-8") + + def _admin_edit_body(self, as_user: str) -> str: + return f""" +
+

Events

+
+
+
+ + +
+
+
+

Events

+
TitleStartRepeatActions
+
+ +

+  
+"""
+
+    def _admin_users_body(self, as_user: str) -> str:
+        return f"""
+  
+

Users

+ + + + +
IDEmailEmail VerifiedStatusAction
+
+

+  
+"""
+
+    def _admin_setup_body(self, as_user: str) -> str:
+        return f"""
+  
+

Setup

+
+
+
+
+ + +
+
+

Shortcode Semantics

+

[calendar] renders the full interactive calendar page (views, filters, login, and event edit for approved users).

+

[calendar_sidebar_upcoming] renders a compact upcoming-events list for sidebar/widget use, showing occurrences for the next 14 days.

+

If URL Slug is set, all plugin routes are prefixed with that slug (for example: /my-slug/calendar, /my-slug/calendar-sidebar, /my-slug/calendar.ics, /my-slug/caldav/).

+
+

+  
+"""
+
+    def _admin_diagnostics_body(self, as_user: str) -> str:
+        return f"""
+  
+

Diagnostics

+

Shows the most recent HTTP trace events (requests/responses) from the fixture log.

+ + + + +
TimestampMethodPathStatusRequestResponse
+
+

+  
+"""
+
+    def _handle_api_get(self, path: str, parsed) -> None:
+        if path == f"{API_BASE}/public/events":
+            query = parse_qs(parsed.query)
+            view = (query.get("view") or ["month"])[0]
+            date_str = (query.get("date") or [date.today().isoformat()])[0]
+            try:
+                anchor = date.fromisoformat(date_str)
+            except ValueError:
+                self._write_json(422, {"error": {"code": "validation_error", "message": "invalid date"}})
+                return
+            window_start, window_end = _window_for_view(view, anchor)
+            conn = ensure_db()
+            rows = conn.execute("SELECT * FROM events WHERE calendar_id = ? ORDER BY id ASC", (SHARED_CALENDAR_ID,)).fetchall()
+            ex_rows = conn.execute(
+                "SELECT event_id, occurrence_key FROM recurrence_exceptions WHERE exception_type = 'deleted_occurrence'"
+            ).fetchall()
+            conn.close()
+            ex_map: Dict[int, set] = {}
+            for ex in ex_rows:
+                ex_map.setdefault(ex["event_id"], set()).add(ex["occurrence_key"])
+            occurrences: List[Dict[str, Any]] = []
+            for row in rows:
+                evt = event_to_dict(row)
+                occurrences.extend(self._expand_event(evt, window_start, window_end, ex_map.get(evt["id"], set())))
+            occurrences.sort(key=lambda x: x["occurrence_start"])
+            self._write_json(200, {"data": occurrences, "meta": {"count": len(occurrences), "view": view}})
+            return
+
+        if path == f"{API_BASE}/public/sidebar-events":
+            anchor = date.today()
+            window_start = datetime.fromisoformat(f"{anchor.isoformat()}T00:00:00+00:00")
+            window_end = window_start + timedelta(days=14)
+            conn = ensure_db()
+            rows = conn.execute("SELECT * FROM events WHERE calendar_id = ? ORDER BY id ASC", (SHARED_CALENDAR_ID,)).fetchall()
+            ex_rows = conn.execute(
+                "SELECT event_id, occurrence_key FROM recurrence_exceptions WHERE exception_type = 'deleted_occurrence'"
+            ).fetchall()
+            conn.close()
+            ex_map: Dict[int, set] = {}
+            for ex in ex_rows:
+                ex_map.setdefault(ex["event_id"], set()).add(ex["occurrence_key"])
+            occurrences: List[Dict[str, Any]] = []
+            for row in rows:
+                evt = event_to_dict(row)
+                occurrences.extend(self._expand_event(evt, window_start, window_end, ex_map.get(evt["id"], set())))
+            occurrences.sort(key=lambda x: x["occurrence_start"])
+            self._write_json(200, {"data": occurrences, "meta": {"count": len(occurrences), "window_days": 14}})
+            return
+
+        if path == f"{API_BASE}/events":
+            auth = self._require_event_read_auth()
+            if not auth:
+                return
+            conn = ensure_db()
+            rows = conn.execute(
+                "SELECT * FROM events WHERE calendar_id = ? ORDER BY id ASC", (SHARED_CALENDAR_ID,)
+            ).fetchall()
+            conn.close()
+            items = [event_to_dict(r) for r in rows]
+            self._write_json(200, {"data": items, "meta": {"count": len(items)}})
+            return
+
+        m = re.fullmatch(rf"{re.escape(API_BASE)}/events/(\d+)", path)
+        if m:
+            auth = self._require_event_read_auth()
+            if not auth:
+                return
+            event_id = int(m.group(1))
+            conn = ensure_db()
+            row = conn.execute(
+                "SELECT * FROM events WHERE id = ? AND calendar_id = ?", (event_id, SHARED_CALENDAR_ID)
+            ).fetchone()
+            conn.close()
+            if not row:
+                self._write_json(404, {"error": {"code": "not_found", "message": "event not found"}})
+                return
+            self._write_json(200, {"data": event_to_dict(row)})
+            return
+
+        if path == f"{API_BASE}/admin/users":
+            auth = self._require_api_admin()
+            if not auth:
+                return
+            if auth.role != "wp_admin":
+                self._write_json(403, {"error": {"code": "authorization_error", "message": "admin role required"}})
+                return
+            conn = ensure_db()
+            rows = conn.execute(
+                "SELECT id, email, email_verified_at, account_status, updated_at FROM caldav_users ORDER BY id ASC"
+            ).fetchall()
+            conn.close()
+            self._write_json(200, {"data": [dict(r) for r in rows]})
+            return
+
+        m = re.fullmatch(rf"{re.escape(API_BASE)}/events/(\d+)/occurrences", path)
+        if m:
+            auth = self._require_event_read_auth()
+            if not auth:
+                return
+            event_id = int(m.group(1))
+            query = parse_qs(parsed.query)
+            from_s = (query.get("from") or [date.today().isoformat()])[0]
+            months = int((query.get("months") or ["3"])[0])
+            try:
+                from_d = date.fromisoformat(from_s)
+            except ValueError:
+                self._write_json(422, {"error": {"code": "validation_error", "message": "invalid from date"}})
+                return
+            start = datetime.fromisoformat(f"{from_d.isoformat()}T00:00:00+00:00")
+            end = _add_months(start, max(1, min(months, 12)))
+            conn = ensure_db()
+            row = conn.execute("SELECT * FROM events WHERE id = ? AND calendar_id = ?", (event_id, SHARED_CALENDAR_ID)).fetchone()
+            if not row:
+                conn.close()
+                self._write_json(404, {"error": {"code": "not_found", "message": "event not found"}})
+                return
+            ex_rows = conn.execute(
+                "SELECT occurrence_key FROM recurrence_exceptions WHERE event_id = ? AND exception_type = 'deleted_occurrence'",
+                (event_id,),
+            ).fetchall()
+            conn.close()
+            deleted = {r["occurrence_key"] for r in ex_rows}
+            occ = self._expand_event(event_to_dict(row), start, end, deleted)
+            self._write_json(200, {"data": occ})
+            return
+
+        if path == f"{API_BASE}/admin/setup":
+            auth = self._require_api_admin()
+            if not auth:
+                return
+            if auth.role != "wp_admin":
+                self._write_json(403, {"error": {"code": "authorization_error", "message": "admin role required"}})
+                return
+            conn = ensure_db()
+            payload = {
+                "presentation_name": get_setting(conn, "presentation_name", "Calendar"),
+                "caldav_calendar_name": get_setting(conn, "caldav_calendar_name", "Public Calendar"),
+                "url_slug": get_setting(conn, "url_slug", ""),
+                "ics_access_mode": get_setting(conn, "ics_access_mode", "public_read"),
+            }
+            conn.close()
+            self._write_json(200, {"data": payload})
+            return
+
+        if path == f"{API_BASE}/admin/diagnostics":
+            auth = self._require_api_admin()
+            if not auth:
+                return
+            if auth.role != "wp_admin":
+                self._write_json(403, {"error": {"code": "authorization_error", "message": "admin role required"}})
+                return
+            query = parse_qs(parsed.query)
+            try:
+                limit = int((query.get("limit") or ["20"])[0])
+            except ValueError:
+                self._write_json(422, {"error": {"code": "validation_error", "message": "invalid limit"}})
+                return
+            entries = self._read_trace_entries(limit=max(1, min(limit, 200)))
+            self._write_json(200, {"data": entries, "meta": {"count": len(entries)}})
+            return
+
+        if path == f"{API_BASE}/users/me":
+            creds = parse_basic_auth(self.headers.get("Authorization"))
+            email_hint = (creds[0].strip().lower() if creds else "unknown")
+            if _rate_limited("user_login", f"{self.client_address[0]}:{email_hint}", limit=20, window_seconds=300):
+                self._write_json(429, {"error": {"code": "rate_limited", "message": "Too many login attempts. Try again later."}})
+                return
+            auth = self._authenticate_caldav_user()
+            if not auth:
+                self._write_json(401, {"error": {"code": "authentication_error", "message": "Login failure"}})
+                return
+            conn = ensure_db()
+            row = conn.execute(
+                "SELECT id, email, account_status, email_verified_at FROM caldav_users WHERE id = ?",
+                (int(auth.actor_id),),
+            ).fetchone()
+            conn.close()
+            self._write_json(200, {"data": dict(row)})
+            return
+
+        self._not_found()
+
+    def _handle_api_post(self, path: str) -> None:
+        if path == f"{API_BASE}/events":
+            auth = self._require_event_write_auth()
+            if not auth:
+                return
+            payload = self._json_body()
+            normalized = self._validate_event_payload(payload)
+            if normalized is None:
+                return
+            now = utc_now_iso()
+            uid = payload.get("uid") or make_uid(f"{normalized['title']}:{normalized['start_datetime']}:{now}")
+            etag = mk_etag(f"{uid}:{now}:1")
+            conn = ensure_db()
+            cur = conn.execute(
+                """
+                INSERT INTO events
+                  (uid, title, description, location, category, all_day_event, start_datetime, end_datetime,
+                   repeat_type, repeat_interval, repeat_nth_mode, repeat_nth_day, repeat_nth_pos, repeat_nth_weekday,
+                   repeat_range_mode, repeat_count, repeat_until, timezone,
+                   calendar_id, caldav_resource, etag, sync_version, created_at, updated_at)
+                VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
+                """,
+                (
+                    uid,
+                    normalized["title"],
+                    normalized["description"],
+                    normalized["location"],
+                    normalized["category"],
+                    normalized["all_day_event"],
+                    normalized["start_datetime"],
+                    normalized["end_datetime"],
+                    normalized["repeat_type"],
+                    normalized["repeat_interval"],
+                    normalized["repeat_nth_mode"],
+                    normalized["repeat_nth_day"],
+                    normalized["repeat_nth_pos"],
+                    normalized["repeat_nth_weekday"],
+                    normalized["repeat_range_mode"],
+                    normalized["repeat_count"],
+                    normalized["repeat_until"],
+                    normalized["timezone"],
+                    SHARED_CALENDAR_ID,
+                    None,
+                    etag,
+                    1,
+                    now,
+                    now,
+                ),
+            )
+            event_id = cur.lastrowid
+            conn.execute("UPDATE events SET caldav_resource = ? WHERE id = ?", (f"{event_id}.ics", event_id))
+            conn.commit()
+            row = conn.execute("SELECT * FROM events WHERE id = ?", (event_id,)).fetchone()
+            log_audit(conn, auth.actor_type, auth.actor_id, "event.create", "event", str(event_id), "success", {})
+            conn.close()
+            self._write_json(201, {"data": event_to_dict(row)})
+            return
+
+        if path == f"{API_BASE}/users/register":
+            if _rate_limited("user_register", self.client_address[0], limit=8, window_seconds=300):
+                self._write_json(429, {"error": {"code": "rate_limited", "message": "Too many registration attempts. Try again later."}})
+                return
+            payload = self._json_body()
+            email = (payload.get("email") or "").strip().lower()
+            password = payload.get("password") or ""
+            if not email or len(password) < 8:
+                self._write_json(422, {"error": {"code": "validation_error", "message": "email and strong password required"}})
+                return
+            now = utc_now_iso()
+            conn = ensure_db()
+            try:
+                cur = conn.execute(
+                    """
+                    INSERT INTO caldav_users
+                      (email, password_hash, email_verified_at, account_status, access_level, request_state, created_at, updated_at)
+                    VALUES (?, ?, NULL, 'pending_approval', 'write', 'requested', ?, ?)
+                    """,
+                    (email, hash_password(password), now, now),
+                )
+            except sqlite3.IntegrityError:
+                conn.close()
+                self._write_json(409, {"error": {"code": "conflict_error", "message": "user already exists"}})
+                return
+            user_id = cur.lastrowid
+            token_plain = secrets.token_urlsafe(24)
+            conn.execute(
+                """
+                INSERT INTO user_tokens (user_id, token_type, token_hash, expires_at, used_at, created_at)
+                VALUES (?, 'verify_email', ?, ?, NULL, ?)
+                """,
+                (user_id, hash_password(token_plain), (datetime.now(timezone.utc) + timedelta(seconds=VERIFY_TOKEN_TTL_SECONDS)).replace(microsecond=0).isoformat(), now),
+            )
+            conn.commit()
+            conn.close()
+            sent, send_info = send_fixture_email(
+                email,
+                "Calendar account verification",
+                f"Your verification token is: {token_plain}\nUse this token in the calendar login dialog to verify your email.",
+            )
+            self._write_json(
+                201,
+                {
+                    "data": {
+                        "user_id": user_id,
+                        "status": "pending_approval",
+                        "email_status": send_info,
+                        "email_sent": sent,
+                    }
+                },
+            )
+            return
+
+        if path == f"{API_BASE}/users/verify":
+            if _rate_limited("user_verify", self.client_address[0], limit=20, window_seconds=300):
+                self._write_json(429, {"error": {"code": "rate_limited", "message": "Too many verification attempts. Try again later."}})
+                return
+            payload = self._json_body()
+            token = payload.get("token") or ""
+            conn = ensure_db()
+            now = utc_now_iso()
+            candidates = conn.execute(
+                """
+                SELECT id, user_id, token_hash, expires_at FROM user_tokens
+                WHERE token_type = 'verify_email' AND used_at IS NULL
+                ORDER BY id DESC
+                """
+            ).fetchall()
+            row = None
+            for cand in candidates:
+                if cand["expires_at"] < now:
+                    continue
+                if verify_password(token, cand["token_hash"]):
+                    row = cand
+                    break
+            if not row:
+                conn.close()
+                self._write_json(422, {"error": {"code": "validation_error", "message": "invalid token"}})
+                return
+            conn.execute("UPDATE user_tokens SET used_at = ? WHERE id = ?", (now, row["id"]))
+            conn.execute(
+                "UPDATE caldav_users SET email_verified_at = ?, account_status = 'pending_approval', request_state = 'requested', updated_at = ? WHERE id = ?",
+                (now, now, row["user_id"]),
+            )
+            conn.commit()
+            requester = conn.execute("SELECT email FROM caldav_users WHERE id = ?", (row["user_id"],)).fetchone()
+            conn.close()
+            cfg = _smtp_settings()
+            admin_to = (cfg.get("SMTP_ADMIN_TO") or "").strip()
+            sent = False
+            send_info = "admin_email_not_configured"
+            if admin_to:
+                req_email = requester["email"] if requester else f"user_id={row['user_id']}"
+                host = (self.headers.get("Host") or "127.0.0.1:8080").strip()
+                proto = (self.headers.get("X-Forwarded-Proto") or "http").strip().lower()
+                scheme = "https" if proto == "https" else "http"
+                approval_path = self._with_slug("/wp-admin/admin.php?page=calendar-users") + "&as=admin"
+                approval_url = f"{scheme}://{host}{approval_path}"
+                sent, send_info = send_fixture_email(
+                    admin_to,
+                    "Calendar registration pending approval",
+                    f"User {req_email} (id {row['user_id']}) verified email and is awaiting approval.\nApprove here: {approval_url}",
+                )
+            self._write_json(200, {"data": {"status": "pending_approval", "email_status": send_info, "email_sent": sent}})
+            return
+
+        if path == f"{API_BASE}/users/forgot-password":
+            if _rate_limited("user_forgot", self.client_address[0], limit=12, window_seconds=300):
+                self._write_json(429, {"error": {"code": "rate_limited", "message": "Too many password reset requests. Try again later."}})
+                return
+            payload = self._json_body()
+            email = (payload.get("email") or "").strip().lower()
+            conn = ensure_db()
+            row = conn.execute("SELECT id FROM caldav_users WHERE email = ?", (email,)).fetchone()
+            token_plain = None
+            if row:
+                now = utc_now_iso()
+                token_plain = secrets.token_urlsafe(24)
+                conn.execute(
+                    """
+                    INSERT INTO user_tokens (user_id, token_type, token_hash, expires_at, used_at, created_at)
+                    VALUES (?, 'reset_password', ?, ?, NULL, ?)
+                    """,
+                    (row["id"], hash_password(token_plain), (datetime.now(timezone.utc) + timedelta(seconds=RESET_TOKEN_TTL_SECONDS)).replace(microsecond=0).isoformat(), now),
+                )
+                conn.commit()
+            conn.close()
+            sent = False
+            send_info = "no_matching_user"
+            if token_plain:
+                sent, send_info = send_fixture_email(
+                    email,
+                    "Calendar password reset",
+                    f"Your password reset token is: {token_plain}\nUse this token in the calendar login dialog to set a new password.",
+                )
+            self._write_json(200, {"data": {"status": "ok", "email_status": send_info, "email_sent": sent}})
+            return
+
+        if path == f"{API_BASE}/users/reset-password":
+            if _rate_limited("user_reset", self.client_address[0], limit=20, window_seconds=300):
+                self._write_json(429, {"error": {"code": "rate_limited", "message": "Too many reset attempts. Try again later."}})
+                return
+            payload = self._json_body()
+            token = payload.get("token") or ""
+            new_password = payload.get("new_password") or ""
+            if len(new_password) < 8:
+                self._write_json(422, {"error": {"code": "validation_error", "message": "weak password"}})
+                return
+            conn = ensure_db()
+            now = utc_now_iso()
+            candidates = conn.execute(
+                """
+                SELECT id, user_id, token_hash, expires_at FROM user_tokens
+                WHERE token_type = 'reset_password' AND used_at IS NULL
+                ORDER BY id DESC
+                """
+            ).fetchall()
+            row = None
+            for cand in candidates:
+                if cand["expires_at"] < now:
+                    continue
+                if verify_password(token, cand["token_hash"]):
+                    row = cand
+                    break
+            if not row:
+                conn.close()
+                self._write_json(422, {"error": {"code": "validation_error", "message": "invalid token"}})
+                return
+            conn.execute("UPDATE user_tokens SET used_at = ? WHERE id = ?", (now, row["id"]))
+            conn.execute("UPDATE caldav_users SET password_hash = ?, updated_at = ? WHERE id = ?", (hash_password(new_password), now, row["user_id"]))
+            conn.commit()
+            conn.close()
+            self._write_json(200, {"data": {"status": "password_reset"}})
+            return
+
+        self._not_found()
+
+    def _handle_api_put(self, path: str) -> None:
+        self._handle_api_patch(path)
+
+    def _handle_api_patch(self, path: str) -> None:
+        m = re.fullmatch(rf"{re.escape(API_BASE)}/events/(\d+)", path)
+        if m:
+            auth = self._require_event_write_auth()
+            if not auth:
+                return
+            event_id = int(m.group(1))
+            payload = self._json_body()
+            normalized = self._validate_event_payload(payload)
+            if normalized is None:
+                return
+            conn = ensure_db()
+            row = conn.execute("SELECT * FROM events WHERE id = ? AND calendar_id = ?", (event_id, SHARED_CALENDAR_ID)).fetchone()
+            if not row:
+                conn.close()
+                self._write_json(404, {"error": {"code": "not_found", "message": "event not found"}})
+                return
+            if_match = self.headers.get("If-Match")
+            if if_match and if_match != row["etag"]:
+                conn.close()
+                self._write_json(412, {"error": {"code": "precondition_failed", "message": "etag mismatch"}})
+                return
+            now = utc_now_iso()
+            sync_version = int(row["sync_version"]) + 1
+            etag = mk_etag(f"{row['uid']}:{now}:{sync_version}")
+            conn.execute(
+                """
+                UPDATE events
+                SET title = ?, description = ?, location = ?, category = ?, all_day_event = ?,
+                    start_datetime = ?, end_datetime = ?, repeat_type = ?, repeat_interval = ?,
+                    repeat_nth_mode = ?, repeat_nth_day = ?, repeat_nth_pos = ?, repeat_nth_weekday = ?,
+                    repeat_range_mode = ?, repeat_count = ?, repeat_until = ?, timezone = ?,
+                    etag = ?, sync_version = ?, updated_at = ?
+                WHERE id = ?
+                """,
+                (
+                    normalized["title"],
+                    normalized["description"],
+                    normalized["location"],
+                    normalized["category"],
+                    normalized["all_day_event"],
+                    normalized["start_datetime"],
+                    normalized["end_datetime"],
+                    normalized["repeat_type"],
+                    normalized["repeat_interval"],
+                    normalized["repeat_nth_mode"],
+                    normalized["repeat_nth_day"],
+                    normalized["repeat_nth_pos"],
+                    normalized["repeat_nth_weekday"],
+                    normalized["repeat_range_mode"],
+                    normalized["repeat_count"],
+                    normalized["repeat_until"],
+                    normalized["timezone"],
+                    etag,
+                    sync_version,
+                    now,
+                    event_id,
+                ),
+            )
+            conn.commit()
+            updated = conn.execute("SELECT * FROM events WHERE id = ?", (event_id,)).fetchone()
+            log_audit(conn, auth.actor_type, auth.actor_id, "event.update", "event", str(event_id), "success", {})
+            conn.close()
+            self._write_json(200, {"data": event_to_dict(updated)})
+            return
+
+        m = re.fullmatch(rf"{re.escape(API_BASE)}/admin/users/(\d+)", path)
+        if m:
+            auth = self._require_api_admin()
+            if not auth:
+                return
+            if auth.role != "wp_admin":
+                self._write_json(403, {"error": {"code": "authorization_error", "message": "admin role required"}})
+                return
+            user_id = int(m.group(1))
+            payload = self._json_body()
+            account_status = payload.get("account_status")
+            conn = ensure_db()
+            row = conn.execute("SELECT * FROM caldav_users WHERE id = ?", (user_id,)).fetchone()
+            if not row:
+                conn.close()
+                self._write_json(404, {"error": {"code": "not_found", "message": "user not found"}})
+                return
+            updates = []
+            params: List[Any] = []
+            if account_status in {"pending_approval", "active"}:
+                if account_status == "active" and not row["email_verified_at"]:
+                    conn.close()
+                    self._write_json(422, {"error": {"code": "validation_error", "message": "email must be verified before approval"}})
+                    return
+                updates.append("account_status = ?")
+                params.append(account_status)
+                updates.append("request_state = ?")
+                params.append("approved" if account_status == "active" else "requested")
+            if not updates:
+                conn.close()
+                self._write_json(422, {"error": {"code": "validation_error", "message": "valid account_status required"}})
+                return
+            if account_status == "active":
+                updates.append("access_level = ?")
+                params.append("write")
+            updates.append("updated_at = ?")
+            params.append(utc_now_iso())
+            params.append(user_id)
+            conn.execute(f"UPDATE caldav_users SET {', '.join(updates)} WHERE id = ?", params)
+            conn.commit()
+            updated = conn.execute(
+                "SELECT id, email, email_verified_at, account_status, updated_at FROM caldav_users WHERE id = ?",
+                (user_id,),
+            ).fetchone()
+            log_audit(conn, auth.actor_type, auth.actor_id, "user.update", "caldav_user", str(user_id), "success", payload)
+            conn.close()
+            self._write_json(200, {"data": dict(updated)})
+            return
+
+        if path == f"{API_BASE}/admin/setup":
+            auth = self._require_api_admin()
+            if not auth:
+                return
+            if auth.role != "wp_admin":
+                self._write_json(403, {"error": {"code": "authorization_error", "message": "admin role required"}})
+                return
+            payload = self._json_body()
+            presentation_name = (payload.get("presentation_name") or "").strip()
+            caldav_calendar_name = (payload.get("caldav_calendar_name") or "").strip()
+            raw_slug = (payload.get("url_slug") or "").strip().strip("/")
+            ics_access_mode = payload.get("ics_access_mode")
+            if not presentation_name:
+                self._write_json(422, {"error": {"code": "validation_error", "message": "presentation_name required"}})
+                return
+            if not caldav_calendar_name:
+                self._write_json(422, {"error": {"code": "validation_error", "message": "caldav_calendar_name required"}})
+                return
+            if raw_slug and not re.fullmatch(r"[a-z0-9-]+", raw_slug):
+                self._write_json(422, {"error": {"code": "validation_error", "message": "url_slug must match [a-z0-9-]+"}})
+                return
+            if ics_access_mode not in {"public_read", "authenticated_read"}:
+                self._write_json(422, {"error": {"code": "validation_error", "message": "invalid ics_access_mode"}})
+                return
+            conn = ensure_db()
+            set_setting(conn, "presentation_name", presentation_name)
+            set_setting(conn, "caldav_calendar_name", caldav_calendar_name)
+            set_setting(conn, "url_slug", raw_slug)
+            set_setting(conn, "ics_access_mode", ics_access_mode)
+            log_audit(conn, auth.actor_type, auth.actor_id, "setup.update", "plugin_settings", "global", "success", payload)
+            conn.close()
+            self._write_json(200, {"data": {"presentation_name": presentation_name, "caldav_calendar_name": caldav_calendar_name, "url_slug": raw_slug, "ics_access_mode": ics_access_mode}})
+            return
+
+        self._not_found()
+
+    def _handle_api_delete(self, path: str) -> None:
+        m = re.fullmatch(rf"{re.escape(API_BASE)}/admin/users/(\d+)$", path)
+        if m:
+            auth = self._require_api_admin()
+            if not auth:
+                return
+            if auth.role != "wp_admin":
+                self._write_json(403, {"error": {"code": "authorization_error", "message": "admin role required"}})
+                return
+            user_id = int(m.group(1))
+            conn = ensure_db()
+            row = conn.execute("SELECT id FROM caldav_users WHERE id = ?", (user_id,)).fetchone()
+            if not row:
+                conn.close()
+                self._write_json(404, {"error": {"code": "not_found", "message": "user not found"}})
+                return
+            conn.execute("DELETE FROM user_tokens WHERE user_id = ?", (user_id,))
+            conn.execute("DELETE FROM caldav_users WHERE id = ?", (user_id,))
+            conn.commit()
+            log_audit(conn, auth.actor_type, auth.actor_id, "user.delete", "caldav_user", str(user_id), "success", {})
+            conn.close()
+            self.send_response(204)
+            self.end_headers()
+            return
+
+        m = re.fullmatch(rf"{re.escape(API_BASE)}/events/(\d+)$", path)
+        if m:
+            auth = self._require_event_write_auth()
+            if not auth:
+                return
+            event_id = int(m.group(1))
+            conn = ensure_db()
+            row = conn.execute("SELECT * FROM events WHERE id = ? AND calendar_id = ?", (event_id, SHARED_CALENDAR_ID)).fetchone()
+            if not row:
+                conn.close()
+                self._write_json(404, {"error": {"code": "not_found", "message": "event not found"}})
+                return
+            conn.execute("DELETE FROM recurrence_exceptions WHERE event_id = ?", (event_id,))
+            conn.execute("DELETE FROM events WHERE id = ?", (event_id,))
+            conn.commit()
+            log_audit(conn, auth.actor_type, auth.actor_id, "event.delete", "event", str(event_id), "success", {})
+            conn.close()
+            self.send_response(204)
+            self.end_headers()
+            return
+
+        m = re.fullmatch(rf"{re.escape(API_BASE)}/events/(\d+)/occurrences/(.+)$", path)
+        if m:
+            auth = self._require_event_write_auth()
+            if not auth:
+                return
+            event_id = int(m.group(1))
+            occurrence_key = unquote(m.group(2))
+            conn = ensure_db()
+            row = conn.execute("SELECT * FROM events WHERE id = ? AND calendar_id = ?", (event_id, SHARED_CALENDAR_ID)).fetchone()
+            if not row:
+                conn.close()
+                self._write_json(404, {"error": {"code": "not_found", "message": "event not found"}})
+                return
+            if row["repeat_type"] == "none":
+                conn.close()
+                self._write_json(422, {"error": {"code": "validation_error", "message": "event is not recurring"}})
+                return
+            normalized_occurrence_key = _normalize_occurrence_key_for_event(occurrence_key, row["start_datetime"])
+            if not normalized_occurrence_key:
+                conn.close()
+                self._write_json(422, {"error": {"code": "validation_error", "message": "invalid occurrence key"}})
+                return
+            now = utc_now_iso()
+            try:
+                conn.execute(
+                    """
+                    INSERT INTO recurrence_exceptions (event_id, occurrence_key, exception_type, override_payload, created_at, updated_at)
+                    VALUES (?, ?, 'deleted_occurrence', NULL, ?, ?)
+                    """,
+                    (event_id, normalized_occurrence_key, now, now),
+                )
+                sync_version = int(row["sync_version"]) + 1
+                etag = mk_etag(f"{row['uid']}:{now}:{sync_version}")
+                conn.execute("UPDATE events SET etag = ?, sync_version = ?, updated_at = ? WHERE id = ?", (etag, sync_version, now, event_id))
+                conn.commit()
+                log_audit(
+                    conn,
+                    auth.actor_type,
+                    auth.actor_id,
+                    "event.occurrence.delete",
+                    "event",
+                    str(event_id),
+                    "success",
+                    {"occurrence_key": normalized_occurrence_key},
+                )
+            except sqlite3.IntegrityError:
+                conn.close()
+                # Idempotent behavior: deleting an already-excepted occurrence is still success.
+                self.send_response(204)
+                self.end_headers()
+                return
+            conn.close()
+            self.send_response(204)
+            self.end_headers()
+            return
+
+        self._not_found()
+
+    def _expand_event(
+        self,
+        evt: Dict[str, Any],
+        window_start: datetime,
+        window_end: datetime,
+        deleted_occurrence_keys: set,
+    ) -> List[Dict[str, Any]]:
+        deleted_canonical = set()
+        for key in deleted_occurrence_keys:
+            deleted_canonical.add(str(key))
+            canonical = _canonical_occurrence_key(str(key))
+            if canonical:
+                deleted_canonical.add(canonical)
+
+        def is_deleted(occ_start: datetime) -> bool:
+            raw = occ_start.isoformat()
+            if raw in deleted_canonical:
+                return True
+            canonical = _canonical_occurrence_key(raw)
+            return canonical in deleted_canonical if canonical else False
+
+        start = _parse_dt_maybe_date(evt["start_datetime"])
+        end = _parse_dt_maybe_date(evt["end_datetime"])
+        duration = end - start
+        repeat_type = evt.get("repeat_type", "none")
+        interval = max(1, int(evt.get("repeat_interval") or 1))
+        repeat_mode = evt.get("repeat_range_mode") or "none"
+        max_count: Optional[int] = None
+        until_dt: Optional[datetime] = None
+        if repeat_mode == "count" and evt.get("repeat_count"):
+            max_count = int(evt["repeat_count"])
+        if repeat_mode == "until" and evt.get("repeat_until"):
+            try:
+                until_dt = _parse_dt_maybe_date(f"{evt['repeat_until']}T23:59:59+00:00")
+            except ValueError:
+                until_dt = None
+
+        def make_occurrence(occ_start: datetime) -> Dict[str, Any]:
+            occ_end = occ_start + duration
+            return {
+                "event_id": evt["id"],
+                "uid": evt["uid"],
+                "title": evt["title"],
+                "description": evt["description"],
+                "location": evt["location"],
+                "category": evt["category"],
+                "all_day_event": bool(evt.get("all_day_event")),
+                "occurrence_start": occ_start.isoformat(),
+                "occurrence_end": occ_end.isoformat(),
+                "repeat_type": repeat_type,
+            }
+
+        occurrences: List[Dict[str, Any]] = []
+        if repeat_type == "none":
+            if _occurrence_overlap(start, end, window_start, window_end):
+                if not is_deleted(start):
+                    occurrences.append(make_occurrence(start))
+            return occurrences
+
+        current = start
+        produced = 0
+        for _ in range(0, 512):
+            if max_count is not None and produced >= max_count:
+                break
+            if until_dt is not None and current > until_dt:
+                break
+            occ_end = current + duration
+            if _occurrence_overlap(current, occ_end, window_start, window_end):
+                if not is_deleted(current):
+                    occurrences.append(make_occurrence(current))
+            if current > window_end + timedelta(days=400):
+                break
+            produced += 1
+            if repeat_type == "daily":
+                current = current + timedelta(days=interval)
+            elif repeat_type in {"weekly", "custom"}:
+                current = current + timedelta(weeks=interval)
+            elif repeat_type == "monthly":
+                nth_mode = evt.get("repeat_nth_mode") or ""
+                if nth_mode == "day_of_month" and evt.get("repeat_nth_day"):
+                    next_month = _add_months(current, interval)
+                    day = int(evt["repeat_nth_day"])
+                    day = max(1, min(day, pycalendar.monthrange(next_month.year, next_month.month)[1]))
+                    current = next_month.replace(day=day)
+                elif nth_mode == "weekday_of_month" and evt.get("repeat_nth_pos") and evt.get("repeat_nth_weekday") is not None:
+                    next_month = _add_months(current, interval)
+                    pos = int(evt["repeat_nth_pos"])
+                    wd = int(evt["repeat_nth_weekday"])
+                    nth_day = _nth_weekday_of_month(next_month.year, next_month.month, wd, pos)
+                    if nth_day is None:
+                        current = next_month
+                    else:
+                        current = next_month.replace(day=nth_day)
+                else:
+                    current = _add_months(current, interval)
+            elif repeat_type == "yearly":
+                current = _add_years(current, interval)
+            else:
+                break
+        return occurrences
+
+    def _handle_ics_get(self) -> None:
+        conn = ensure_db()
+        ics_mode = get_setting(conn, "ics_access_mode", "public_read")
+        if ics_mode == "authenticated_read":
+            auth = self._authenticate_caldav_user() or self._authenticate_wp_admin()
+            if not auth:
+                conn.close()
+                self.send_response(401)
+                self.send_header("WWW-Authenticate", 'Basic realm="calendar-ics-fixture"')
+                self.end_headers()
+                return
+        rows = conn.execute(
+            "SELECT * FROM events WHERE calendar_id = ? ORDER BY id ASC", (SHARED_CALENDAR_ID,)
+        ).fetchall()
+        payloads = []
+        for row in rows:
+            evt = event_to_dict(row)
+            ex_rows = conn.execute(
+                "SELECT occurrence_key FROM recurrence_exceptions WHERE event_id = ? AND exception_type = 'deleted_occurrence' ORDER BY occurrence_key",
+                (row["id"],),
+            ).fetchall()
+            exdates = [r["occurrence_key"] for r in ex_rows]
+            payloads.append(create_ics_event(evt, exdates))
+        conn.close()
+        body = (
+            "BEGIN:VCALENDAR\r\n"
+            "VERSION:2.0\r\n"
+            "PRODID:-//Calendar WP Plugin Fixture//EN\r\n"
+            "CALSCALE:GREGORIAN\r\n"
+            f"X-WR-TIMEZONE:{DEFAULT_TIMEZONE}\r\n"
+            + ("\r\n".join(payloads) + "\r\n" if payloads else "")
+            + "END:VCALENDAR\r\n"
+        )
+        encoded = body.encode("utf-8")
+        etag = mk_etag(body)
+        self.send_response(200)
+        self.send_header("Content-Type", "text/calendar; charset=utf-8")
+        self.send_header("ETag", etag)
+        self.send_header("Content-Length", str(len(encoded)))
+        self.end_headers()
+        self.wfile.write(encoded)
+
+    def _event_as_ics(self, evt: Dict[str, Any]) -> str:
+        conn = ensure_db()
+        ex_rows = conn.execute(
+            "SELECT occurrence_key FROM recurrence_exceptions WHERE event_id = ? AND exception_type = 'deleted_occurrence' ORDER BY occurrence_key",
+            (evt["id"],),
+        ).fetchall()
+        conn.close()
+        exdates = [r["occurrence_key"] for r in ex_rows]
+        event_ics = create_ics_event(evt, exdates)
+        return "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Calendar WP Plugin Fixture//EN\r\n" + event_ics + "\r\nEND:VCALENDAR\r\n"
+
+    def _require_caldav_auth(self, write: bool = False) -> Optional[AuthContext]:
+        auth = self._authenticate_caldav_user()
+        if not auth:
+            self.send_response(401)
+            self.send_header("WWW-Authenticate", 'Basic realm="calendar-caldav-fixture"')
+            self.end_headers()
+            return None
+        if write and auth.role != "caldav_write":
+            self.send_response(403)
+            self.end_headers()
+            return None
+        return auth
+
+    def _handle_caldav_get(self, path: str) -> None:
+        auth = self._require_caldav_auth(write=False)
+        if not auth:
+            return
+
+        if path in {"/caldav/", "/caldav/calendars/", "/caldav/calendars/public/"}:
+            self._write_text(200, "caldav shared public calendar\n")
+            return
+
+        filename = _caldav_filename_from_path(path)
+        if not filename:
+            self._not_found()
+            return
+        conn = ensure_db()
+        row = _caldav_lookup_event(conn, filename)
+        conn.close()
+        if not row:
+            self._not_found()
+            return
+        evt = event_to_dict(row)
+        body = self._event_as_ics(evt).encode("utf-8")
+        self.send_response(200)
+        self.send_header("Content-Type", "text/calendar; charset=utf-8")
+        self.send_header("ETag", row["etag"])
+        self.send_header("Content-Length", str(len(body)))
+        self.end_headers()
+        self.wfile.write(body)
+
+    def _handle_caldav_put(self, path: str) -> None:
+        auth = self._require_caldav_auth(write=True)
+        if not auth:
+            return
+        filename = _caldav_filename_from_path(path)
+        if not filename:
+            self._not_found()
+            return
+        raw = self._read_request_body_bytes().decode("utf-8", errors="replace")
+        parsed = self._parse_ics_payload(raw)
+        if parsed is None:
+            self.send_response(415)
+            self.end_headers()
+            return
+        conn = ensure_db()
+        row = _caldav_lookup_event(conn, filename)
+        now = utc_now_iso()
+        status = 200
+        existing_by_uid = None
+        if row is None and parsed.get("uid"):
+            existing_by_uid = conn.execute(
+                "SELECT * FROM events WHERE calendar_id = ? AND uid = ?",
+                (SHARED_CALENDAR_ID, parsed["uid"]),
+            ).fetchone()
+        if row is None:
+            if existing_by_uid is not None:
+                row = existing_by_uid
+            else:
+                uid = parsed["uid"] or make_uid(f"caldav:{filename}:{now}")
+                etag = mk_etag(f"{uid}:{now}:1")
+                conn.execute(
+                    """
+                    INSERT INTO events
+                      (uid, title, description, location, category, all_day_event, start_datetime, end_datetime,
+                       repeat_type, repeat_interval, repeat_nth_mode, repeat_nth_day, repeat_nth_pos, repeat_nth_weekday,
+                       repeat_range_mode, repeat_count, repeat_until, timezone,
+                       calendar_id, caldav_resource, etag, sync_version, last_modified_by_user_id, created_at, updated_at)
+                    VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
+                    """,
+                    (
+                        uid,
+                        parsed["summary"],
+                        parsed["description"],
+                        parsed["location"],
+                        "",
+                        0,
+                        parsed["start_datetime"],
+                        parsed["end_datetime"],
+                        parsed["repeat_type"],
+                        parsed["repeat_interval"],
+                        parsed["repeat_nth_mode"],
+                        parsed["repeat_nth_day"],
+                        parsed["repeat_nth_pos"],
+                        parsed["repeat_nth_weekday"],
+                        parsed["repeat_range_mode"],
+                        parsed["repeat_count"],
+                        parsed["repeat_until"],
+                        DEFAULT_TIMEZONE,
+                        SHARED_CALENDAR_ID,
+                        filename,
+                        etag,
+                        1,
+                        int(auth.actor_id),
+                        now,
+                        now,
+                    ),
+                )
+                status = 201
+                conn.commit()
+                row = conn.execute("SELECT * FROM events WHERE id = last_insert_rowid()").fetchone()
+                if row is not None and parsed.get("exdates_specified"):
+                    _sync_deleted_occurrences(
+                        conn,
+                        int(row["id"]),
+                        list(parsed.get("exdates") or []),
+                        True,
+                        now,
+                    )
+                    conn.commit()
+        if row is not None and status != 201:
+            if_match = self.headers.get("If-Match")
+            if if_match and if_match != row["etag"]:
+                conn.close()
+                self.send_response(412)
+                self.end_headers()
+                return
+            sync_version = int(row["sync_version"]) + 1
+            etag = mk_etag(f"{row['uid']}:{now}:{sync_version}")
+            conn.execute(
+                """
+                UPDATE events SET title = ?, description = ?, location = ?, start_datetime = ?, end_datetime = ?,
+                  repeat_type = ?, repeat_interval = ?, repeat_nth_mode = ?, repeat_nth_day = ?, repeat_nth_pos = ?, repeat_nth_weekday = ?,
+                  repeat_range_mode = ?, repeat_count = ?, repeat_until = ?,
+                  caldav_resource = ?, etag = ?, sync_version = ?, last_modified_by_user_id = ?, updated_at = ?
+                WHERE id = ?
+                """,
+                (
+                    parsed["summary"],
+                    parsed["description"],
+                    parsed["location"],
+                    parsed["start_datetime"],
+                    parsed["end_datetime"],
+                    parsed["repeat_type"],
+                    parsed["repeat_interval"],
+                    parsed["repeat_nth_mode"],
+                    parsed["repeat_nth_day"],
+                    parsed["repeat_nth_pos"],
+                    parsed["repeat_nth_weekday"],
+                    parsed["repeat_range_mode"],
+                    parsed["repeat_count"],
+                    parsed["repeat_until"],
+                    filename,
+                    etag,
+                    sync_version,
+                    int(auth.actor_id),
+                    now,
+                    int(row["id"]),
+                ),
+            )
+            if parsed.get("exdates_specified"):
+                _sync_deleted_occurrences(
+                    conn,
+                    int(row["id"]),
+                    list(parsed.get("exdates") or []),
+                    True,
+                    now,
+                )
+            conn.commit()
+        updated = conn.execute("SELECT id, etag FROM events WHERE id = ?", (int(row["id"]),)).fetchone()
+        log_audit(conn, auth.actor_type, auth.actor_id, "caldav.put", "event", str(updated["id"]), "success", {"resource": filename})
+        conn.close()
+        self.send_response(status)
+        self.send_header("ETag", updated["etag"])
+        self.end_headers()
+        return
+
+    def _handle_caldav_delete(self, path: str) -> None:
+        auth = self._require_caldav_auth(write=True)
+        if not auth:
+            return
+        filename = _caldav_filename_from_path(path)
+        if not filename:
+            self._not_found()
+            return
+        conn = ensure_db()
+        row = _caldav_lookup_event(conn, filename)
+        if not row:
+            conn.close()
+            self._not_found()
+            return
+        event_id = int(row["id"])
+        conn.execute("DELETE FROM recurrence_exceptions WHERE event_id = ?", (event_id,))
+        conn.execute("DELETE FROM events WHERE id = ?", (event_id,))
+        conn.commit()
+        log_audit(conn, auth.actor_type, auth.actor_id, "caldav.delete", "event", str(event_id), "success", {"resource": filename})
+        conn.close()
+        self.send_response(204)
+        self.end_headers()
+
+    def _parse_ics_payload(self, raw: str) -> Optional[Dict[str, Any]]:
+        vevents = _extract_vevents(raw)
+        vevent = _select_master_vevent(vevents)
+        if not vevent:
+            return None
+        summary = _extract_line(vevent, "SUMMARY:") or "Untitled"
+        description = _extract_line(vevent, "DESCRIPTION:") or ""
+        location = _extract_line(vevent, "LOCATION:") or ""
+        uid = _extract_line(vevent, "UID:")
+        dtstart = _extract_dt(vevent, "DTSTART")
+        dtend = _extract_dt(vevent, "DTEND")
+        if not dtstart or not dtend:
+            return None
+        rrule = _extract_line(vevent, "RRULE:")
+        exdates = _extract_exdates(vevent)
+        cancelled_occurrence_exdates = _extract_cancelled_recurrence_ids(vevents)
+        if cancelled_occurrence_exdates:
+            dedup = set(exdates)
+            for ex in cancelled_occurrence_exdates:
+                if ex not in dedup:
+                    exdates.append(ex)
+                    dedup.add(ex)
+        repeat_type = "none"
+        repeat_interval = 1
+        repeat_nth_mode = ""
+        repeat_nth_day = None
+        repeat_nth_pos = None
+        repeat_nth_weekday = None
+        repeat_range_mode = "none"
+        repeat_count = None
+        repeat_until = None
+        if rrule:
+            parts = {p.split("=", 1)[0]: p.split("=", 1)[1] for p in rrule.split(";") if "=" in p}
+            freq = parts.get("FREQ", "")
+            freq_map = {"DAILY": "daily", "WEEKLY": "weekly", "MONTHLY": "monthly", "YEARLY": "yearly"}
+            repeat_type = freq_map.get(freq, "custom")
+            if "INTERVAL" in parts:
+                try:
+                    repeat_interval = max(1, int(parts["INTERVAL"]))
+                except ValueError:
+                    repeat_interval = 1
+            if repeat_type == "monthly":
+                bymonthday = parts.get("BYMONTHDAY")
+                byday = parts.get("BYDAY", "")
+                bysetpos = parts.get("BYSETPOS")
+                if bymonthday:
+                    raw_day = bymonthday.split(",")[0].strip()
+                    try:
+                        parsed_day = int(raw_day)
+                    except ValueError:
+                        parsed_day = None
+                    if parsed_day is not None and 1 <= parsed_day <= 31:
+                        repeat_nth_mode = "day_of_month"
+                        repeat_nth_day = parsed_day
+                elif byday:
+                    day_map = {"SU": 0, "MO": 1, "TU": 2, "WE": 3, "TH": 4, "FR": 5, "SA": 6}
+                    token = byday.split(",")[0].strip().upper()
+                    m = re.fullmatch(r"([+-]?\d+)?(SU|MO|TU|WE|TH|FR|SA)", token)
+                    if m:
+                        wd = day_map[m.group(2)]
+                        pos_raw = m.group(1)
+                        if not pos_raw and bysetpos:
+                            pos_raw = bysetpos.split(",")[0].strip()
+                        try:
+                            pos = int(pos_raw) if pos_raw else None
+                        except (TypeError, ValueError):
+                            pos = None
+                        if pos is not None and (1 <= pos <= 5 or pos == -1):
+                            repeat_nth_mode = "weekday_of_month"
+                            repeat_nth_pos = pos
+                            repeat_nth_weekday = wd
+            if "COUNT" in parts:
+                repeat_range_mode = "count"
+                repeat_count = int(parts["COUNT"])
+            elif "UNTIL" in parts:
+                repeat_range_mode = "until"
+                until_raw = parts["UNTIL"].strip()
+                if len(until_raw) >= 8:
+                    repeat_until = f"{until_raw[0:4]}-{until_raw[4:6]}-{until_raw[6:8]}"
+            else:
+                repeat_range_mode = "no_end"
+        dtstart, dtend = _normalize_monthly_anchor(
+            dtstart,
+            dtend,
+            repeat_type,
+            repeat_nth_mode,
+            repeat_nth_day,
+            repeat_nth_pos,
+            repeat_nth_weekday,
+        )
+        return {
+            "uid": uid,
+            "summary": summary,
+            "description": description,
+            "location": location,
+            "start_datetime": dtstart,
+            "end_datetime": dtend,
+            "repeat_type": repeat_type,
+            "repeat_interval": repeat_interval,
+            "repeat_nth_mode": repeat_nth_mode,
+            "repeat_nth_day": repeat_nth_day,
+            "repeat_nth_pos": repeat_nth_pos,
+            "repeat_nth_weekday": repeat_nth_weekday,
+            "repeat_range_mode": repeat_range_mode,
+            "repeat_count": repeat_count,
+            "repeat_until": repeat_until,
+            "exdates": exdates,
+            "exdates_specified": bool(_extract_prop_values(vevent, "EXDATE") or cancelled_occurrence_exdates),
+        }
+
+    def log_message(self, fmt: str, *args: Any) -> None:
+        # Keep output concise for fixture runs.
+        print(f"[fixture] {self.address_string()} - {fmt % args}")
+
+
+def _extract_line(raw: str, prefix: str) -> Optional[str]:
+    for line in raw.splitlines():
+        if line.startswith(prefix):
+            return line[len(prefix) :].strip()
+    return None
+
+
+def _extract_prop_values(raw: str, name: str) -> List[str]:
+    out: List[str] = []
+    pattern = re.compile(rf"^{re.escape(name)}(?:;[^:]+)?:([^\r\n]+)$", re.IGNORECASE)
+    for line in raw.splitlines():
+        m = pattern.match(line.strip())
+        if m:
+            out.append(m.group(1).strip())
+    return out
+
+
+def _extract_first_vevent(raw: str) -> Optional[str]:
+    vevents = _extract_vevents(raw)
+    return vevents[0] if vevents else None
+
+
+def _extract_vevents(raw: str) -> List[str]:
+    out: List[str] = []
+    for m in re.finditer(r"BEGIN:VEVENT\r?\n(.*?)\r?\nEND:VEVENT", raw, re.DOTALL):
+        out.append("BEGIN:VEVENT\n" + m.group(1) + "\nEND:VEVENT")
+    return out
+
+
+def _is_cancelled_vevent(vevent: str) -> bool:
+    status = (_extract_line(vevent, "STATUS:") or "").strip().upper()
+    return status == "CANCELLED"
+
+
+def _select_master_vevent(vevents: List[str]) -> str:
+    if not vevents:
+        return ""
+    # Prefer the true series master, not detached/cancelled overrides.
+    for v in vevents:
+        if not _extract_prop_values(v, "RECURRENCE-ID") and not _is_cancelled_vevent(v):
+            return v
+    for v in vevents:
+        if not _extract_prop_values(v, "RECURRENCE-ID"):
+            return v
+    for v in vevents:
+        if _extract_line(v, "RRULE:") and not _is_cancelled_vevent(v):
+            return v
+    for v in vevents:
+        if not _is_cancelled_vevent(v):
+            return v
+    return vevents[0]
+
+
+def _parse_ics_token_to_iso(value: str) -> Optional[str]:
+    token = (value or "").strip()
+    if not token:
+        return None
+    # DATE value
+    if "T" not in token:
+        if re.fullmatch(r"\d{8}", token):
+            return f"{token[0:4]}-{token[4:6]}-{token[6:8]}T00:00:00+00:00"
+        return None
+    # DATE-TIME value
+    if token.endswith("Z"):
+        core = token[:-1]
+        try:
+            dt = datetime.strptime(core, "%Y%m%dT%H%M%S").replace(tzinfo=timezone.utc)
+            return dt.isoformat()
+        except ValueError:
+            return None
+    try:
+        dt = datetime.strptime(token[:15], "%Y%m%dT%H%M%S")
+        return dt.strftime("%Y-%m-%dT%H:%M:%S+01:00")
+    except ValueError:
+        return None
+
+
+def _extract_exdates(raw: str) -> List[str]:
+    values = _extract_prop_values(raw, "EXDATE")
+    out: List[str] = []
+    for value in values:
+        for token in value.split(","):
+            iso = _parse_ics_token_to_iso(token)
+            if iso:
+                out.append(iso)
+    return out
+
+
+def _extract_cancelled_recurrence_ids(vevents: List[str]) -> List[str]:
+    out: List[str] = []
+    for vevent in vevents:
+        rec_values = _extract_prop_values(vevent, "RECURRENCE-ID")
+        if not rec_values:
+            continue
+        if not _is_cancelled_vevent(vevent):
+            continue
+        for value in rec_values:
+            for token in value.split(","):
+                iso = _parse_ics_token_to_iso(token)
+                if iso:
+                    out.append(iso)
+    return out
+
+
+def _extract_dt(raw: str, field: str) -> Optional[str]:
+    pattern = re.compile(rf"^{field}(?:;[^:]+)?:([0-9TzZ]+)$")
+    for line in raw.splitlines():
+        m = pattern.match(line.strip())
+        if m:
+            value = m.group(1)
+            if "T" in value:
+                # Interpret as local naive fixture time and inject +01:00 offset for test determinism.
+                try:
+                    dt = datetime.strptime(value[:15], "%Y%m%dT%H%M%S")
+                    return dt.strftime("%Y-%m-%dT%H:%M:%S+01:00")
+                except ValueError:
+                    return None
+    return None
+
+
+def _sync_deleted_occurrences(
+    conn: sqlite3.Connection,
+    event_id: int,
+    exdates: List[str],
+    replace: bool,
+    now: str,
+) -> None:
+    if replace:
+        conn.execute(
+            "DELETE FROM recurrence_exceptions WHERE event_id = ? AND exception_type = 'deleted_occurrence'",
+            (event_id,),
+        )
+    if not exdates:
+        return
+    for ex in exdates:
+        canonical = _canonical_occurrence_key(ex)
+        if not canonical:
+            continue
+        conn.execute(
+            """
+            INSERT OR IGNORE INTO recurrence_exceptions
+              (event_id, occurrence_key, exception_type, override_payload, created_at, updated_at)
+            VALUES (?, ?, 'deleted_occurrence', NULL, ?, ?)
+            """,
+            (event_id, canonical, now, now),
+        )
+
+
+def _xml_escape(value: str) -> str:
+    return (
+        value.replace("&", "&")
+        .replace("<", "<")
+        .replace(">", ">")
+        .replace('"', """)
+        .replace("'", "'")
+    )
+
+
+def _html_escape(value: str) -> str:
+    return _xml_escape(value)
+
+
+def _caldav_filename_from_path(path: str) -> Optional[str]:
+    m = re.fullmatch(r"/caldav/calendars/public/([^/]+\.ics)", path)
+    if not m:
+        return None
+    return unquote(m.group(1))
+
+
+def _caldav_lookup_event(conn: sqlite3.Connection, filename: str) -> Optional[sqlite3.Row]:
+    return conn.execute(
+        "SELECT * FROM events WHERE calendar_id = ? AND caldav_resource = ?",
+        (SHARED_CALENDAR_ID, filename),
+    ).fetchone()
+
+
+def cmd_init(_: argparse.Namespace) -> int:
+    conn = ensure_db()
+    create_schema(conn)
+    conn.close()
+    print(f"initialized fixture db at {DB_PATH}")
+    return 0
+
+
+def cmd_reset(_: argparse.Namespace) -> int:
+    conn = ensure_db()
+    drop_all(conn)
+    create_schema(conn)
+    conn.close()
+    print(f"reset fixture db at {DB_PATH}")
+    return 0
+
+
+def cmd_seed(_: argparse.Namespace) -> int:
+    conn = ensure_db()
+    create_schema(conn)
+    seed_data(conn)
+    conn.close()
+    print("seeded fixture data")
+    return 0
+
+
+def cmd_run(args: argparse.Namespace) -> int:
+    conn = ensure_db()
+    create_schema(conn)
+    conn.close()
+    server = ThreadingHTTPServer((args.host, args.port), FixtureHandler)
+    trace_log = fixture_trace_log_path()
+    trace_log.parent.mkdir(parents=True, exist_ok=True)
+    print(f"fixture server listening at http://{args.host}:{args.port}")
+    print(f"http trace log: {trace_log}")
+    try:
+        server.serve_forever()
+    except KeyboardInterrupt:
+        pass
+    finally:
+        server.server_close()
+    return 0
+
+
+def build_parser() -> argparse.ArgumentParser:
+    parser = argparse.ArgumentParser(description="Calendar plugin local fixture server")
+    sub = parser.add_subparsers(dest="command", required=True)
+
+    p_init = sub.add_parser("init", help="initialize fixture db schema")
+    p_init.set_defaults(func=cmd_init)
+
+    p_reset = sub.add_parser("reset", help="reset fixture db schema")
+    p_reset.set_defaults(func=cmd_reset)
+
+    p_seed = sub.add_parser("seed", help="seed fixture db data")
+    p_seed.set_defaults(func=cmd_seed)
+
+    p_run = sub.add_parser("run", help="run fixture server")
+    p_run.add_argument("--host", default="127.0.0.1")
+    p_run.add_argument("--port", type=int, default=8080)
+    p_run.set_defaults(func=cmd_run)
+
+    return parser
+
+
+def main(argv: Optional[List[str]] = None) -> int:
+    parser = build_parser()
+    args = parser.parse_args(argv)
+    return args.func(args)
+
+
+if __name__ == "__main__":
+    raise SystemExit(main())
diff --git a/package/calendar-plugin-0.1.0.manifest.sha256 b/package/calendar-plugin-0.1.0.manifest.sha256
new file mode 100644
index 0000000..350033f
--- /dev/null
+++ b/package/calendar-plugin-0.1.0.manifest.sha256
@@ -0,0 +1,19 @@
+e44decf1154a146e77f0c759c26455ae22ad54bff7ea175dea1c76dd95257c7b  calendar-plugin.php
+1daa5861c0d10258c0d16c5c88c1a18fb3c8df7b590f7facfe7c28f678643bdb  src/Contracts/AuthAdapterInterface.php
+25cff4b0fc2ee292b53c152edd083c6af66200a25eec28f8dfce37d126a8892d  src/Contracts/DatabaseAdapterInterface.php
+4f0f4caa5ac98499854336f5b74af55ce889653f3956e5df10910f869a23fdba  src/Contracts/HttpAdapterInterface.php
+15e8f58c7360d6cd0c76c945abfeb026f8278d40b330e99b67955eb2f85f5563  src/Contracts/OptionsAdapterInterface.php
+47d55e845b8a696c55fa86f597bb2760b6e35d974b70f365593d38697ef398c8  src/Domain/CalDavService.php
+58038a39389008eb1b3c4bc57a9daf6e672bf3d7ec216d16a8e2f1429ea5054e  src/Domain/EventService.php
+412a22ecd910535c7ace2549a86eacf08cc9cd824f1767e60116d8593355f57f  src/Domain/IcsService.php
+a6bc2e7b2d1862603d5709ee2860ff3a28cd128b6eb15ee2e56b6bbd2ae806eb  src/Domain/RecurrenceExpander.php
+a107073f4d114daf22b6bdd79b1347634f102116b046e9d60316e69b30fde805  src/Domain/SettingsService.php
+232961b0e6a0087f0b0069f52ec3db156e19b04004c0b13d4397bc1635c5ac14  src/Domain/UserService.php
+5aaac066919b60461bde2e96cbfb4de66a5a28e89d2a61b78f0e2d346f23395b  src/Infrastructure/ServiceContainer.php
+7686d0778fc27a24ef6e6683e79dfed9d61bf40c64fab8a2793f291bdcf2935b  src/Infrastructure/WordPress/MigrationManager.php
+8e6c95e9e1c051606e66d95cf0bcf92b2ca087bc491f32ab4921e0898cf77b81  src/Infrastructure/WordPress/WordPressAuthAdapter.php
+68c0ca15ad2c8b6363a2578b85f8daf0d3a094e612a120a2cdd2a2bfd8fe5e3c  src/Infrastructure/WordPress/WordPressDatabaseAdapter.php
+8da85db3c1e69c2c5f01aaa2f558aa8f0323446d8af0aec5b34d4607cd4afe1b  src/Infrastructure/WordPress/WordPressHttpAdapter.php
+cf9fddcecb07af2c03ad2c0e448be12a6b45dd936efc8bc1fd46a52b4af864ea  src/Infrastructure/WordPress/WordPressOptionsAdapter.php
+faa235d11b0fcb3a197091bf79ce3184132427d6ec7e732e5f4f5a3abb379c50  src/Plugin.php
+4e6930c79d9ce1045be6863ddd7546e19f7ef7e839d41a41014a3efb3183eaf0  src/bootstrap.php
diff --git a/package/calendar-plugin-0.1.0.zip b/package/calendar-plugin-0.1.0.zip
new file mode 100644
index 0000000000000000000000000000000000000000..e1f72fa7e70f288ae0c73ae97ec8e4c062b53b1a
GIT binary patch
literal 45752
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
L0ssod-@g4n0z+mH

literal 0
HcmV?d00001

diff --git a/package/calendar-plugin-0.1.2.manifest.sha256 b/package/calendar-plugin-0.1.2.manifest.sha256
new file mode 100644
index 0000000..9875b22
--- /dev/null
+++ b/package/calendar-plugin-0.1.2.manifest.sha256
@@ -0,0 +1,19 @@
+af462e458d03144996d4fc18da700e49b9285a2f03941a30df4f5b6775edd1e6  calendar-plugin.php
+1daa5861c0d10258c0d16c5c88c1a18fb3c8df7b590f7facfe7c28f678643bdb  src/Contracts/AuthAdapterInterface.php
+25cff4b0fc2ee292b53c152edd083c6af66200a25eec28f8dfce37d126a8892d  src/Contracts/DatabaseAdapterInterface.php
+4f0f4caa5ac98499854336f5b74af55ce889653f3956e5df10910f869a23fdba  src/Contracts/HttpAdapterInterface.php
+15e8f58c7360d6cd0c76c945abfeb026f8278d40b330e99b67955eb2f85f5563  src/Contracts/OptionsAdapterInterface.php
+47d55e845b8a696c55fa86f597bb2760b6e35d974b70f365593d38697ef398c8  src/Domain/CalDavService.php
+58038a39389008eb1b3c4bc57a9daf6e672bf3d7ec216d16a8e2f1429ea5054e  src/Domain/EventService.php
+412a22ecd910535c7ace2549a86eacf08cc9cd824f1767e60116d8593355f57f  src/Domain/IcsService.php
+a6bc2e7b2d1862603d5709ee2860ff3a28cd128b6eb15ee2e56b6bbd2ae806eb  src/Domain/RecurrenceExpander.php
+633a781fff77c39c4e06b1a04f9972c2b8bcfb21095031c5b4f6f4de65cdee9a  src/Domain/SettingsService.php
+232961b0e6a0087f0b0069f52ec3db156e19b04004c0b13d4397bc1635c5ac14  src/Domain/UserService.php
+5aaac066919b60461bde2e96cbfb4de66a5a28e89d2a61b78f0e2d346f23395b  src/Infrastructure/ServiceContainer.php
+7686d0778fc27a24ef6e6683e79dfed9d61bf40c64fab8a2793f291bdcf2935b  src/Infrastructure/WordPress/MigrationManager.php
+8e6c95e9e1c051606e66d95cf0bcf92b2ca087bc491f32ab4921e0898cf77b81  src/Infrastructure/WordPress/WordPressAuthAdapter.php
+68c0ca15ad2c8b6363a2578b85f8daf0d3a094e612a120a2cdd2a2bfd8fe5e3c  src/Infrastructure/WordPress/WordPressDatabaseAdapter.php
+8da85db3c1e69c2c5f01aaa2f558aa8f0323446d8af0aec5b34d4607cd4afe1b  src/Infrastructure/WordPress/WordPressHttpAdapter.php
+cf9fddcecb07af2c03ad2c0e448be12a6b45dd936efc8bc1fd46a52b4af864ea  src/Infrastructure/WordPress/WordPressOptionsAdapter.php
+0a10c002ffabbe349bc706f9c122d6d83c6a6a4ea6edf5fe892657ba64135c63  src/Plugin.php
+4e6930c79d9ce1045be6863ddd7546e19f7ef7e839d41a41014a3efb3183eaf0  src/bootstrap.php
diff --git a/package/calendar-plugin-0.1.2.zip b/package/calendar-plugin-0.1.2.zip
new file mode 100644
index 0000000000000000000000000000000000000000..9e27d190c379ee6e440a87258ff8f76fbedca047
GIT binary patch
literal 45804
zcmbrkV~`+Gwl!L|UDajVwrzFUwv8^^wr$(C-DTU>*E9FV%zXFFyt(sU{y32tk+Ji{
zT07U)k(UAnfdcr;1svd`_1ByKUJwAV0Sxu6jBSkc9cb*VoK4Mb=v0&;0f3W}#?37j
z#?77GpaB3uPJjUb{<_Hj&63{#u!QnYEIB$D{+BhOgwmWw_};
zFl1eU)thG$M-YCwmQ>kHaEAB=x^H_jJ;UE31f9%!2$3t1nl?=5AS!uQU=b)jtS556
z^f{&5T`P*l1GOr$uBo3$)vmz;k~*&r1gle&ak0Zz2h^CmRLT%vY=Sq+^I*l~F+J=}
zYz1vrTTCle=kWL87Gz31=G2==8RUIKahVt@i-KoEJ)Gzh0bX|7X2N2H?S`Gs%D
z*a0>|OpI)gcx|JqK$Pr*a
z(bizoawkh@At6#POWqvN2x-tIB^8jD`-w<2y#)Lh
z`2P-5YYF03`yZGj|G>ohZ($N}b~5`sm_y2Cz(bqZ)wm)ayl{0S{;W0tC;
zriSDScq16*&+$1~1R&-MwRZ!ytq%0JAVSyA8uUWGpTQ5tIf$Zn-cvoO+opPKRz5G#
zj!drqH&Q0N$)lTpkn;auAtmnQWcQyU%vh{VBVrQHu11E6<1m6xNh~+4RJFBID_$#0Rg*27|Q|Zp_|Lr)B-CP
z(~-A{AA{&AE>FD~cZ?aBUy0ZVl5+sAd1V_5sjkn9Oi24-3byljr)mLj^q3(+@u^c0
zHDo*Yn+y^@Z~Yxk>oJz>Gfb1OGAM#LA`cX$Z;euOiREL#8}RR7{zaEXgMN-c{{iwh
z5C8z@zjZrdeJ6bbeMjSe3Fu$c`=7*utUnS9%%$T$x
zTC>WOHbZ~x$!Kq^ZLC=O9sfsU=^Ptwe$WMq8vD`1ON-t@yW$RfSTG{@3k3892)2Pl
z$O2bcS4MeiG)F_Cgb*`E$CY=Tz+p&2xK;F(v@Gf4Zv{z@jLc(^RGgq>ERSKi%c!Mk
z^R^|w+x;Z#aFmogfrr(mX=n*ulrc7RDK%C=!Ia1GVR{V?MuU{NqT9x}%xV#-SP>{O;<=@pnh+`k9S3FV!03%hj*
z2sAYej(_h#oi(#>?4`fwd-~i{<-BfUx~1p__}8LK^e^C-urYDa{}aia4V|1FjQ_3j
z{#W$=pD~x_e`oxst%H%ggR$d(3;bW`Ok4V=q5Z#<3*?UrwHf5YNdIx6y+1A_@}GX@
zZy)>zMf;oBr~LEGA}Ur+YJeYM==z;Pl$;rm$IUt_4+XwUoNliOfi**-qdtxLU5sM>
z`ij_GvPyAtpr7=*YkSgj^l}n8A9bbX34+t_A$h52#CA2?4iG#kqwvY*3>7kq3whusAWk6&#hL1g64
zHJTVX^*?nEeOC=2C_QW$MI&`u#~4eCZeO;Mva!l!RvzewKwai(pwvgYDm^YHaa}ehCek1rT=-<_VG0;Z~dV0)w`45#uU;qFT
z|DUP+A1d)TI%iV;sW=oR_C;sVBLrV_4`H+aN{m{xY+fr}FITjw(D5*%JlOGTWgN!I
zFez}^?n=OzjYmkg;lL*_>AuF>xbZP|Weq1j(m)`cP8jz-f=AoD)Aqm<5mi2^97V(p
z5+wrVIW1N;XqRN2zCh
z19*JmI>O!SO4@i>#KotZBd-viIfZ)6EQDsSPN&di!J!k>K?jy_OB1|s9pB`^eszU^
zTJ+>I-a*>14x=FWn#0F2kuNh%q|cc#6e#kn_z=E|<*{O%DRWB=z8rtq4w{7<0fy=H
zn{AeDcXQ?|uxxZi7Zf0dV;jYYQWp;Q;g&9nZW@-(Hc>A1m)=JQya6rep_^EUF>ogN!UWadI$75*SS9Y;6z(^@qVr7#vokgJ8@e`TXEZVo+*W|$xqk-`
z+)dzQEQbstJJiEcPf!+vKAN=4^FGhAhixR`%Pf?$^wImgIC`29?_#bX34vpb;5(V#G&qR$57<;S9vv(wS^v_g>9FGT9uwBOD^
z8E7$!789RK#x0K_>Jm!`1>RB1dBR7j^Q$di`!A637WT}(i8%#u+P0w0DBl*=gPioSpN_@0SHs6|*+l(p_2R>3g@H!^4;#83}mhM2^jX(1;
zY~Mazoimng$x{U~EP9HmAoIzM-ovsXUmJg74fXja%6V0$XVQaRxLyH)Wan(W$X#mO
z>%`I3O3<{va!jTMBer1$0t$a5+thVOtRPscVJL-|$*QnWM57;WQW9Gsvp-?&#oQzb
zO#<9ep`Se*;o4uWi4GOCkIFx?jT&I5($}!y(wGQe?GDE5e+jvMAby(o2nQcGPgE@E
ze{U-((T0l~_2QM)nJ{Y41V0Jxqfn>!{5ZVcyI_$f$zcoMCKeLi4vy85&D+RO;z>q#
zR3^zbjnJhkp7^B};YlNymq0QqXXGf(E-j}8XFtVy17Ay9l<-jwV4h#(C{^2S_nIN4{N%Sf&q&OQB=gSNCVxxF_-a29NZP^ER=`Id$
z?^d}!AvEqKofpIvh@=<&DMZf!#cwvu;lBHXSGg$q0h^n^_2bMJR$y04^y}3wb#o2p
zmV@<9r)u^t8Li=Ciyyy1XdpctA2wosJiP0E36&8TO^J{Zjajao3Fulx4MkwqA%bH@
z`2$Ed@qOXs)HilwW#dJ@tN?IOcpTRWJo$NYj2N+vrID
zPG4t`=93KD*e6Bv8aspA8xq(Y1^(>01vO?*s`$3dJi{0@5V3>%Gk-UYQxudk2J*XJ
zM=yq*4Sc$rRJG7UpBf1~}vymf^aw(s(Bvus>)=Fsm
zq@?;obf>n>B9)+8`AR`_@E=mcUQ)k<)97&bgXin3kC<|(Ya7zUBfN4Ni@0L9Z71{~
zf*eiBsyD?n0)2>Yv4gtZTr_^UQL6r8+7@8qbK*sO=A25Lhrmb|BAgbGlL>~f*2&*u
zguxVGhj@*j&EKr#iS*d?#zRi#C(ICg4kxwehI!`cfk{0Los${yP
zgX8ks0ruA#!Uc8T5wZ$@u__+bH27(ErQ#|L5+7g_>2m>md2eQ}z&9Immw!T$LHQMl
zCR0J=lp30&G#cv8bIZvFr(8kc2s1!P%$Q9CCBjVI%;=OX*G7u5Qi2jGepDTSrvSbl
zYKPRdDy=^0QSXJixH8e9`K-J9&HcICLo6z%!?m^0&<&^ynnLNzLQ0+t%!c#qgc9`5
zt`MjTxe}-RE?8$3Ii1b!R0bP>yvz;?i%bbFmx7w70F#{qQ(2gnA13{25-Cy`_bM6L
z{II1RXWFg+$vVLdf73_=-R>N2*cuLWH3)Q$6|e
z!y=&AQl(6%B$Phn2i0?9I?y7CmK-!oIq#o9-%@~g|8_SEG<($qpb4_KV@jvz*75O
z2>3|(a54Pv_FQNa`}001+TT?_@=1Pwjv+ltYKLyyzg&(z9en>idHzcsq~Nj@%K0aG
zF8%*i2mM38;%|A*zo>(};~8xG|1>ULzr%lE@Q=$cHLl^NYz?coqLr9
zQhs@61%i|d@~ADJZar_U`S^srmnITi#+s)GRwXM&$ITn?dMb|Z`zZ>`+=NGW3g~nc
zH~Vzlp&DMB`Pj6pUOR3-t?D}SZ@pO&Fi7;CCz9it*I-i$G|y+C+Zn#
zKl<|si-F(8@1O~KQ;kxwa+$IyN39qHfHRw{Wwp3boA!jSyUit=mHZ4$XTVRMp+
zf8wK4(WU&9#TMW(lwe!p^RGqt-&=2$+(D^Oe~1(LL!9`3nwUu1Ut=Q11*_g8WwC+iG}?;WFrhd;tAG;5!ZY3W*#JsD3MuVh)r=k;9E_0FA&i
zqnSkTr66zmvBLwWEoptp{jvibAd1YY}u
zR|$*wm&(_CG$EbkwUqM!l}9%CRrPb$ujG~PZJrZ@%!PBzOtR_7<&0~WDF+wN!XeW|
zM|zFd3G;bMlS$TX70@*asGR_DEL>GVXzndh9m|UO(j#Gun#xu}hw^LkT4LFT=&|XV8Nmf8>{H^XXw-=aebKV|IZx}+klI@X5cXuNDZ>{jphSG%d`
zd?a%`tp0X|!B`a^^Ck}u&nE|7cff!hR=3+O(ntf5!H|S-mQgCT5YjVO)K&y~HxH4$
z8P_0tGtXX!%fnoTTq(TFee283x&01V+$HhtN`X5gkHaNB!CV?Vogw~Qg&0R2vZv9=
zfuC6qsl*F^Vg!AjA6`w6Gt#7$aB~;3r@cfzXcaiOHUghI@YQ74%CB}}3Bk`X&fFYB
zq$R(s*Zt98$ZW1LZbDsOc*18x;k}o0aLzC*lu_`F7BXTF43{T~^jk4Sa`IlHGV!+?
zaj0^SU&S$Hww_?A;@Cej$0M%kU|yh!+G~*w*a5OJ#PDWG*JQaOf2k2+$Tu&ZhF4o>vU{_xfn-=
zuG;K)N)_Zv44Y^v)6k?>P7jkRBx**)o;TG=WT7siRxq;SER8mj$EW|ua8y5T+Buo~
z2&;okKa86QoK$?B6vK6ny#4KJS0A+-JBHTOZvdcSp+Y;p>LfO0(Fo;hXa&}p2c~i?
z7A5l|dl~;!E5D8-s05}&-)qK>ErB9RGeDXlx_YagUc3x@?*{y_$xoYLIf%_{kwQYjflX*TJ>
zC5m8^N&|~*Cx^rs2UNhIC>@6}7Q&cL13V!|!>$6LK?7-eMwD7mgsi;7;g!z#KtKGoC$t
z7`=LY(w;4f22iTGd#n&7_ym)oQiheO%pS^dA7&iJac~*mcv!>oPLY=S(}`qG@l7?G
zzwHpwan$)iV0l{J#5^9KlbRG((1deA`BoD+6$Xh!Y;90`-B58;*Rq$768|~akZgLf
zwp&N0Ug1NpYR(!1gP|f%@iBxda+|?qxVD
z@Gnf>SJkIj5@if0#zrAd>UnYXSCmO)q#BV47t=+&=}^8EM9%(Y_|;Z88u_DH;LjhT
zgGGq;C|f2^4#Kll2@|pGi%+uNY%rxc2VSZWsZu5~w>q)`U+*9&>l*Xl5+9;$mfZ==6_l$N+Z_-g5wL~*YuLWTtW8vb|
zSiwn%Z}q7D&i)wgxkD^|Rkp*s`d0_dMv}R5w9z!;4sds-c$Y@yPB89^$DGFUv%Eyg}@CsY1h(#zV*Z|PF1hX8PmbnhwKP|T_+2Qnliit
zld@SSX5;ytHUf!5rzy`tl8dd~EW3ymcMy!wW9sR&*FxSp(|V3TE#!gBb-+X5aZc)A
z^U~kp7hUXW&d0>%&)~c<%Z#m)rQOAc0|)O?;t5n6PILkAy8J-%24JMOz*Y*41f!X{
z)7)u|(0DfJ!tC!+i
zl?yPc_l9+`SNqXR#v_WnBPDEYh!tnQjSUwQvz3GW$@<}BZ_k3A4(voH)`N4gjy2n`
zKe2l(cZ~?cYeEm+fv>{4{UNkDxiZ>D-{eK8DFMK=Ez43)3};6NokgzO)wy~8@%yR_
zm=2$5VP0QXUx?<9o;WAqzim=ILBm1p`G$Aq9+0k3>d;_7Z)5-yz-Mw#Vs+wP+$BTt
z@62Ln;%7AJ=_#xn?YO#W1F{2aW~PH3C$UqA`Z;3CqAV9(gEMVX6BtTAn_*VOALK?*>xa4YI$QrK6E
z^;MbI%IQsX0Po`jFdLk9Jpe%kq~_DQj65W_@<2Vx0|5d;=SiSU
zdbj+L5p7;t-55ww)o9>1dpcs`
zyK)(h1N=6Q<}&OuTj4H`OOxqRju=A6KZ!Np@e_VKqVR3jlp_rkJ_IvLRUGf38srTh
z34UB)O|8Nif(>&jc8fZLELQN#OgJN|HE-~Ujn<};`%tw7Ie2p6bd9~Bu
z*}`dv`^@b)hAVog{TW_ggg{-hpr=U@eIFW7)A9n+fqs(Hd$oi{%Msz
zJwd!Dk>`cZtqID-h9O%VM$a@DJ`gacL$3kVmUDl
zWQ|S9)b_BzIbZq8K-PK4#kkLq<0g9}DSRwoU$1ziUU36BW&O>{(Cdgaz8xrfe6|#Q
z@NQBoQtHVxzOS8LWz0Xy8uED+ZR#+z>j>*-9F2@2nva`mnW6`vwG?;37{ZZ}S0i$4o9-^N(d!QG
zDZCq;;|(p830Op9>#1b9_lN^VWQdoc2>=~H7qfpIgHp2z^HyObMYct4laGK395(Y+
z;%65MFjbMGiK~Yas$vgl|I_yO{Hm2FW7L={WIG_?DlJ|Wuq{}5jb-Z
z>=5#e!B0{h95g8l0RD{XBjRd5w>)(=@g8;tV8^Ci1<6O1E<1rERL
z?Vq_9$cl+)W|tjB7Jjr*ayf@cw==h8cclbtTx(7o_bbylr;X7YI|KPj7o=U$qil67D={7JARV{lqM@(Wb8sKD1?c`N#AT5q^+d?Ll7X6QnM8pwE>
zh1Og8O=eHvhl%ZrlbwaU*;>~mW(yANaGCr#wGEK(!^cBaoKbJA#T%9b6U4F?ETy!=)>Xoj#h}r{~E8I-cKjp
zMFh4wN4IzFjl)O^D9$;CWX
zePRztzvmIxA>^GS?A9Wl^g|=!89aJ=$`kHAIfv2@5`f3NdyYKgy*@tBRHG*eqzkOHoH8=UJ72-FxuR+Q8&_~MU%%zz*>y)bzN>~iu
z#b{dpy8?!QO7QmWY0nX_zAdf~SGwGhJrEvkUNwFtwM{y@EYgkKaUY?#n;`brt>#rM
zJ~j4C0fsc>wR28r#NZCX8I(K8IRjsGtws%A+zntN}LjPY7SdU{LIR&$0jmiA(Y
z{qYNmL}=Gato{>`S+O@S%wo%MB2}VJWo>*y?S^?odHRmbY)Srca9q)yZgN4C3rv$e
z!7>i-6shPMaP>71`l&%+Fx7%2?=R$1-N+IC35yTI9#~)4Sa&-YcY;NwN6v88d-3U1
zy@u3Bqomi?jtSJfbl%iim5xp0Z>1zoS@BUW2XkUB+Av6Y2EZ#JylT&uMW@h(?e1Ugbo?hZ1QfSVZKWcin;^xj=uL3^}zo7E@^zj%(={EY;}
z^AP+Ba5FLZ$bOgCD5I-n;!{#UT%QMC|NV#swfJ=-$`h+EC;`%rSg8FSPXjJy-j*3-
zZ%gKGXuda`q*p@dqMRf+b$+S?+#f&Kk@wbW0NJUJHaa+1A78c4hP_Sv
z;x(hmZ?$d17VR>9s}As`UI`QgPJmffS);6+9*LIQu(@Em4&#oqRwuU2q{S8s#uRUN
zfTH~#*jNLF(81+?aP56%p20_$UWd!ZDNSfKOp{fFjNtO;?kJ{o51_L``-QQIh22oy
zapWy_mBu!pFm7Yb0?nsBuf)Vh`RJn4B<`~g#TA`|TtKAo{!W~>MG5PzeS0Oy7gGAs
zE*z97ZI4%|Tdc)1=!W$>lWZYDQEs^6CuX0|u5>_h;OayLnG)wguA~r0t$d>Jv_%k#
z1J<~YCn!$eb$jEJPiks?i60!GP{HUSv~rcQv%FVs^{10-S)DAr7tePe4@BcSE)+C$
zRF)G>UQZliX9rWDYUW+zu
zHPgt5Z4bnu%g(!+0nxi4hvR&{Uyo3s+>;ES>#uWzN5_kfj!3_)0`693
zC^Vo5TyIo9Bt@5gl^B0xQ0@{NG@B7tBZ2uqQz3yMWWz*WxHn9GqHlfV-hIQQY|NnO
zo0PbN?J&uOW~+mXaOSB0yMTYCh+pIU3RwnaHQh**@<
zQBaO^ve3C&?6ScHYnXF}ROd93aS0hWk+2>@k$`+mU@A!S9;Q53!C>kddy7Or@lUX$
zBLj0N3w(9)I;dFj&e~UZ=2wi#qQOsTTm{n0+t{1DnGBDlt|HQhmR^e;+RDnb$So~c
zUAr$M15OrQikDfcg|&{mP#Q|VUnB6`&B65hB8$#;j2*!OS6A1p
zyR?6Fa^ZeRu7aLvSa)n(DVdXLD5x}k0mWCf#~*sadBU=UVi*5#I2-U|G2Xbu~^x^(Ol{R0Z)#UAClq=os|T*^-x#0WpQxl@Bq
zeL-?0N>qFj!d@v7NHeO)vdlIsD*!m|;Zr=um3qdr;hi6aK$VR3!zcD8psk9bq}{<9
z6cE6P(o)Dc&PY`n13%GjSu9^-U>@agTzr|o1dh1g&FJh2y2@X!`LuVxd&9qLse;gU!A?;Nh0w&YA8Z3hVGDFlnyX-UM~;B
zTXbkMurPIuJq^WRHCm3-k0D6eYfw)&#LcR?d09yRfdH>rD)&0cXsd@&0d@Kf=ikv)
z-Pt}w-8`Xlh=-GM_uIx;keaB_-R5Tw)I_d%{KnF8A7d#}K!=v;p$kj1^TKO_z=2lo
z#IcS|GSZ0+uMk!!vrKYsJt)!s>^b-rG
z)B~zBl*~!TdyRq(N)VmmXZ1O_xeYg#fI!1`hDt7~ON_3sa*bE%8;hH)uJ@H(yhhUc
z^;KbK8sp-R?D<=2viFl3|l47`3!`^x4@=iFHJ9$4za&3jV
z`dBnO6qi>j?LveV5V|}_crQX*R{@56O_gH!w~%Yw7>SRlezw0%Dw0DAReJwXQo3zr
z+`i^&f8TFg&+YA%;V9;>@2>EwnDh0hzSv^(8NGXXeApQhKk$qvk3buAPrt5JofS(V
z_VMHU&X8jqAKfum6XHur4RcpmQUYtQF1>3>B6DV}DA9YO*5X&U%a|fdlG5bT#%-x!
zk)`J&!%_qIpn!`6b}|jMwwrwmyr-`Nz;)Hfb9J+(M@^ihE+!x?AzpJ{Xd9NorZmbU>L+Oi`LOFVPx%G+BT(iH?;=eGt2I
zStpm}@_8u}W8jb`vKdR?_vWjIfjo_`F%9pgbqIrDV(=?*u^cU@3)VQosNc{4jFX&-L2%_
zU^~#h>rw@6(r=dCHqu>5X2geS{{|*2e*$<7zz6+f@)&@v;5_A}a_Y^_4jC_Ik3mpS
zgmG6SX#jCP_IuL+3PAOK73T>P|Ic^Jr{+cd2{U8v2)goDafNwQZaHiPBQorh*CD2<
zm;;K}IPPNwi997Ltq7DGp{O5`$-XaJRZR0%?F>gNcIRfhD^6k!`t*>VSJtqi4!4!w
zEp~<5>HTp&GB`T%s)6B^fi57`j24)TMCC=KziEOedE-le~v#9SyLFhTrE;lNv7a50vfC61xYHrWb1N@0Ya6GHriD4o;S08
zJ1-EohwBO_;4sb!lpyxfd}?F%4`aw`Ug|Ckg?F<{vDx`l6t@sIP39j~IBV@1CF5-9*iB!tzo`Pf}*|{sG2rDo(?Q1AWFgdfDk@yHunbNzU*dj&o$sE0F
zz{0`qVT#j87bffXmHScjjA?z&J*u@^*^tCDBjFtWctb=;Masf3KI;;;t)Ks~w`nw2
z6`sdVr$~{fuPQYqjL0$aOI1|$fN~6OzKJ!K-6-6%@nE>R*{wP{vQkuC#H_?DkHe;1
zHf5QMrASMg@(VXHg-~JN=4-s3ntepPrz`}lRzpxVD^lM8GQrw$av}w>%AKh1Lyaq{J2VY&2i+(&BbE1o-C}exeeB1&wCGo(LwsRn{4(!PI5HT9G9&j>L
zHUIMSdKIxKDfqCe3NA0gcB97Qc}dw=SB?nI<{CT@w~FDJqt4A@t>4mxWqk8?bLeaD
zQ=4mR+bx+^;ecZdo*%Pu2%DsA9{e-4H_SzG~ej%MYedZ6E
zE@)HPul3q3uH4G(B2QXoZa7*#^qhy_RNUsW?}9)mTdyC=>`}vD`f|J`Jcc3VjKtOQ
zu6AM9CEoXObREIumX$1LAh;!9U+i${d(bWxoPOWBrAgVohKaiN;;F9y`;W48HwYNo
zaR5AmuNUyT?M3PFjp6X5o#>xl0AP(35XBj-Q412Dj~w6iPs(1ea6R~xDK;J-
zv7y$i5XKQZIQ^eAy4H!+-V%?AypxndG}OVp{q2ez5rx5o!};Y!M>_~*e3$mcj$~_D
zz~afTb47|)<5Z-*RHQ#-hwdR-T~$8}eg0G{V#o<$dPEzqfeuK;&&>GQI|*g0St
zNp-#73tMd5oZ;bAMhkeMQY42b<47$T{WzHtN|%R#;VaZS6yoG;05pIOz6e+I3hm$F
z2J(ard(dTTGNjl#$ucabmXb`je`{cnr7ZUlp|1mxri(w#SgQM!AGx9>P73$@kfvMz
zKy5?DW_rss|Ip~U*)R4dQx9Ewq+Cr>1$w5&)?+=9sNfL@l8$;Idk3{%V$>)Bt$zi*
zvkczZ0Iiq1g49YEO2a6l?GbJ$E647UQlk+ny^`Em;=reuMsxoqf};lruGR|3u?i5u
z(klpC%MMK44-i4y3k0n!>$Fpf0Ira?^u-$v=Eqk~>1B)%Ijz5If=SmQpzc|&oj2LQ
zRQ8h$z5%_ijdSG*P-Xt(EhDn@l|bDchAVGcF~p+J(H>J^#FJYRQ{_|8QmvAFQirRM
z8tGl=oqV4`utiVW&4P
z=vjQ#JQ^NyZN=Z6FRX#VyW!_lR8&C*r&1DRvk9@DSPSz(`@2GbUc9`T$z
z&)fIW-xS>HrlT`W77Qee{?agLOOa!A1ePYf`0G~C{Q0*b_UB++E0a$RvVO4j`%;Zu
zCqAh-S+m^5U_V5g^e=;1diu9KUXnXQ7pWe%4fe>yLTWexgCjX|B~?_}Rsj6Wf41TD
zn;eeW-i|X%Rt8<`@!5e-04}#qnc=I5|5PgQE4*ahlV#bDwjOyi0VNtrGi+7^niVF}
zfVmJ949g>NCm6H0zl%!daJM^Wr%@@|(jbt(f}wIb$2p
z^FVyeNDBCrZHcF)q!~{u7-n~zcM}L-Nj^Xfy7egqU2~KY3Au=)Y_ERT8AD|RJc)#u
zWFs{tpS-14IvrqEM*|ZSh|zFhNmgQ;Ine^aQ7R{abef8z))*dx5qr~G4yv1#36d(C
zFq;6a0GxVp=iLCKy&bpyWIoen`|fD?2D=H8CgAR<-je@5&*y4!Wnzv;M{rE!0xqph
z1tpq&`4x796m93Iso6&`z;^cA8kZwkOIGzvB++7!N+CEN=9vLGtBse|^=xaa4tm<-yA*
z!G++@T|EFb(Y81SK!NbD((A~4ieih9V|oN9&kW%dd9D~2X?@m8Vq%WC3WLzKq~GkX
zB@K4;))KUKly|31bL%NxyX@mEyDXRt+X3}we|)w4us&{4?YlK#LP(SCeih_(P*tN^
zx{Qg>$B)=*R^hk4QGrtGDRHG@7E;=wz}Tfv^D|zU+Sr~B;cC^@(}Y}>fx6LcjBH)x
z-BF1e3HB^e@hZ|mW5c^eOGn0h!VncvmMJukQEd*_H@E+L|FLeC4y%l7_7=0-0VyQwTtyDZDJvcJ&mRLVfVH`T-1dz32IN=5q
zU1Q-)7&=MDT*;6mO$OmGBjofBwQC-tvxcQM1(m0v&>FN;%YmdIi+GT+A4Rz2kox`B
zEKK+IbMRQxHY7Ha#!Huo0L7z!elC3)o2K%yTiHmy_Jqq)TI~u&Eiq^^lvE&A4Q<4`
z&Q%GOgSL8jMNtyACTX=9L>psF%*h>=ju`svTh$iP00!D}0h!}7d6ZMF@idbhB!^a{qxqouAY^SGUDP$FqTZ)VKe-+*tIIoa=|#VIUzhk;<9fO_*e
zZqjrI_Bg1P&;_01Ht>I?P;!d8vG=HX+~p%J@!m
zHz*_%moygi&w$Glr#F#aL)mFg#zZjaS|r#n0cM~D+Aa2rSizl{uSA48yL3H^V{?=~
z{rgHOKoSEJ^+`>;{P0Gq0y(H-z?nK@Lh1bba{DU-<(bV@43;&Dx)_F)TbZ^fdnR=I
z3av4Pk_Y|af&-G>=&`0ASOH|um0#69Q=>GlZ;&n_Iv;XAx01BDncXz&wG6tdtccn4w#gg+cBW6??eB=}?{2;@+YHsf
z?cgeJul4p6Dm{qW{e8GRNj1*yQB*9+am6~~@5rw
zsC*VZk%~l61RQ{YxAP(!U*F{HOzdWEfhp}1aD`}-SA;vwQhk^P7-;UaxEK%>ZTItR
z*Q+1`7N7Uy_hsPMD|vWHt>g5qMR(j&)i3G&r1UAwKkrW}W`hizFv{2JYs{tzE>S1f
zhfb`n9FGH?EvjS8BhB`rq}CHdg_4Ca|1wDp@T2yowZ}nJb|`{%4?_|E$nz)L`B*Sx
z#T}RvUI*=*zS@oH2pPHBz52$!bZOr@-@Kam#OYT0s@)J-Y|;RLw*}ao9Oem^?<%`0SA?X0r@(NJ1z&0;mXm_;h(IMjwRloGsPGc?=VkS-
zUGMJgIay*eBi-$O^cE2bMm
zB{THsfiob+4p#Q|c^_0ByEU4bM;TBq5-_lSyrMXQUMe@cH*{{XA{h%fKuw$Mg8fg}
zyS*_FmvAY1P*Fz}0azAblrRY=klt0Xcd)GGNJ1XOcW5m;tPBQ59q+I+DFRMo#?Gt9
zqU8#WWVA{SCMkwHq`6b0wI39v6)Mn?V!dFoi1Ne<
z9iCq{yYCAtpu32Vm|u~jik5@^amohS{pgd%@#6h#mE@jH(O64Jhj|$_@d@%ehLE9I
zOonQTL}2HREC?^a+%ekgiVS|M6F7kY7b@?vlLKZnC80iH4oN$)lWd=)x;`clA*eP`
zhf~osR%BbqEQ*iL%=88h1Xzib!RfYG%a|4I04~0`ZMKBR6f@rMqnGQ*=t$s*Q0hDlrF&sPT
zX>OOQ1bADX&l|0qE-15*btD2o4=pSrQa&--mzsY
zV&wvlY%5QQRX$G0wML&M=jw+_l=|q|!~bf~1bgV7^5F=Pw<_rSb4;Y3>lc*#1vZ3@
z6m5lob*(-PNZR~z-DFF&q$Z~gSI31uTFXa^-=qvZM?EZ8628xRx@^HSWL1s*1qQ1i
z1_4520}ia2PZ3g~60wvHn2twCUyLX@R>2b|W)x}wZ1X*Y>75l|?vYYE=Ps}wF4)-H
z;q@xsDt|vgv6rnYzqCW(L)V;MOxH_M(4j~*?KwM|_S)+~EQ7ZjSKq44ZC(sT+;neOZm3!mI*E!6o!e+I694u+*h
zZ-E4ES0D7=v_`KZyAlAEDh*9|nOAW*-XF*>H*@7ZaUyoK<^5W(f?-lk
z1ZU`GCxh)U9N>5Zk`v3k;;YJyh>psl6+as*hvSRg6j2_k$b_kJ8jrB~
z_c?kwqXa%zF24%;-e~?=B4K&hz^0`zJj?T0gWG(BEtCOSz*78#YPRu$
zIi#I|vNr}oVH8ahR$tfjm|;)jK{>xg0>JbPeo;Qp{hllOjUZl-Y6UG4K5}Db$mEoE
zDA?No3jayqIQF6Abh%qu9C67+Hv}9XJG})5);|};c_J#BxQrmS)}h{#4)UT=-t7nl
z=@$SZv8{hcBWqePjjhHt+YXbeq!S6lhk+N|_hM)S7-rTaDbqox{|SHC31O?84clZmu%Pe5kf
z@tOt?q+j=_EUyaKcnQL;JC#!zR%hG_8hVgQMCx~TYj8&UXFZx`eBG8+yf56nK6*8d
zcjGE^5?GjN7J8Gljrld`$8z0>jg0+Xhta6&7LZ3
zA+UjNh54b9bIaGU*A7l&HHl!uJfk8Z+H84sZKJv_kP)38r#Ho^?lC@@CA`7=x<@0H
ze90V*I=+iWCsS!TSpOQh8TMrjE~$f$at9!H*H7M69@{|%rz#({Ojo%0sCVXdN>UyG
ziIQ=8%`0t0eC?G
zB{T_W@xD|k&Xmz-pu-7mKz&#Tt#dD-d25V)Ce--v{tx#ReJ_krsybdfr1F!gg+B!r
zBZ%ojh{f|xsL~-CvAr8hzn7V&(~dP0zmV!%2U8D*U4e`i^(fS>6kuWVOL4fWCc9`F
zA6l&+PbJzypJz*Kx%;%5Khuy*iTW!v=y;@reacR#1yg7cyzBS!4&o9i?P7F@oqQJ?
z>awo_8`AsE7C@?IcC`N5&N4lTAvq35QQ7Du>x`)QaZVLzIfFGTfbb@#)GqiMEL(Fp
zqjg*8naUq!FFYr-)grC#Pl`aBqQBrGcXPeGuyOt1*$#8i-~Du}jiW8NMy!y3Ov|lY
zW8q$5tQ{?`d3Uldx&)q
z%rpJg?g0?Jy5AT8N?0R+b>{P(kfp3_jgFztEZ(;v&WieP;F&(*7(yiG;K}*>S~5dA
z9-l8ZwmTRI!5yfzTl>>&h&yo`770Eaw=`ny!NgY+coCs+h75SnAea%w2eN{*)J}
zG%+t}kpbEdFB#Xnp#pVSC|;PDlBCkyw@maF9pD^nla~#VQN9h6bQSRfcBGC?_Tfv@
z!x6(+S?!}#J%$-B-8NX(HI!5(EC3H83O0t=Y0C$89DA7-Xp34fYNRaKH=Y#GFSky0(&G5oY*_jdJOk1G9lCU&B0?&#EhxXe1RbKWHfmw5d&BIg}kYk5@SJKOo
zsf-+wm~rM|a#o6}7!9}!!lb!BOzZo-wfoC?=PF@i(*7&4jp&{#zlGf2i)7E?77po$
zv>Kj;30bOfrM6r=NZOcDMDAV${~nJFDihiYFq_NcV|gz5vLR(z3j%ua@&?~0MKQ$B
zOML!bm`g^8nF8Dnam9sGk9mPO94rydtq0$%oi@HV_)r4!FVV`P(QBOoaCBIg!L=V_
zt%wKWk=q`UZY535Y7j9pW5mCa_3J?;=bdu8QwS?drnBSc52}!h8$r6SyzAJ#wQ}??
zeSOn&3py3xLa7eInYuRem9Y)ECqM`uFHzd|v9Ft*f5%AtH*PW6E=r8_VnRFW(;(He
zu*0Jc@nzHQQ_eCr*ks3OIkcS6uT1*!K*2PU3JXk~%q0BIK#K_=pf;6Z_a&raT1K|e
z{30F^?g3&Yeuxfyeek036ABf?SvG!uUU6qD;o7LQoA1#yx%0+do3Q@rLbl)cnY}e<
z_UqvA0UAx~F?NW-SV_TUxK2Oon6=VdXg*fR1A`@OAbf#-i6w_ylS$dI>%fVUV5xJj
z!{N8d;4nG}oXhe6
zj}hzy-rO{7FA^>`y~Qm;k9URI>w-T%y=|=zr-8^Q(0$kWt8D*f`WB3a{8vzHNZo7S
zF&kkS%J)7tS?EpipQBgVxnFnIxF(m3OsdQ&Yd@p=cvrfe@S${aHqM;t8gcz}Www=>
ze%96$v%!EKgdW+bmXm|R18-KrK;@VKAt2&D4MreQd%36dX+n>7qpd;^
z(eX@%P6SFHL^R|=UPs0U`*w}X28*a%A0M>C?4
zS#sGSmc-oQG;hX{Cud62a-EcYabStiM?OHg#00CgS=b~N?%?N
zRVZdk)j4l79hdl7w-sDawVm#n&fE-Jaj2)K(dX1=Q1>qkUTa5V+I2J;;o5H}&-tA}
z*J^9!T_7u?DtErd+H|BkS4LI6*t0D|!Y%Mt{GUSw>vHi4cx#+rfFaMni+U?q-}RaS
z`eC{7c5>$8Tkqn|d?KEsT%>Et1YzQ&X{h5V!8GLLh;pdJOMxpY{thii=5i9RvS#}P
z^T`_`{erPBog@%6V9dS@tp(jZG}3Edv~C#XrD*=Y5H*LEK>a%@0W)0UDG@UE^uB(m
zBTIh4{%8zRa^vjK#ZQsWZ6hC-lFYF@IlzV9+#aHhbDNhC1-N+;z{XpL4r0Q7f?XWgND1fqXJ7vswk4sD%3>?nmRIa>kDbK^8+
zHf9zv5z2z!1W1T=>f~W~yh)hu1Lv;#=MNugJrN3Y+B_o{9_*7n3Kj(BuZu?XBp4}^
zS0fdo{6UC2P)1^$i<3*S8^wjpYR6TyI|_-NyNvyjKwayW8;=RmnxR$A>5tsZ&*^8b0AZUQUgJJj>04_NRClZ)+B*_BgtimqP8k1U5_r#7l{zIc#Ak=>xi{t
zy&=3ZNMvM^(Y&YUtunO+W5d6ygCX(H0sSQl)1sFcD&FYx^9D_M&M1
z;_G4#dQt;TUVE@hb5uf!4%e?_+kx`EWG4Q*b>fw~y#-$Bg3`owdZeOv@z!39Hdn~V
z@x_bq1mOjr!uukB5Xws7i;7Ci-If=Pb;(Sdpb^oL0laf`ca5bjTJKBf&4rj4Lobn=
z5I1DHNVqM?A8b)=GgXi_Td!sfigVQHDR%yy4@{%T^q6EDyt16R}nX
z9}9pfNe&(=qQ$wpLcLXvp#Ny1hU!&vRl1m6b4>mcn%!c)-9Nc3m3m9c>##rHE@k&T
zZoIF1tixn-`$RqtUe;tGwlNI!q>GmJe_9B^kcaY%TyNL>-7y}Yb%+W?G(_ks
zWW{Lgg)GgwfT89Ds*v?w>Jg#>KPx%K;1;pC?z_`SMBTMPn_Mg(9*
zD&JgVAUTSF-mTS@2`yd|4E+KyOyz?ZtMo+>xAGxoHb*hwRA^qm=#ULaT=#Jc_f_0f
z(am=)_%B?{9=_{GH5Ux2hQI}3V%FKvESeuLdCC{r#!(_HJzDTiIjD{@R2m`~euLb1
z+C%9565sj`-Cj?@lTL6v>IwKV!&u+Sc-Q7msgT4gzXV;tynh#%mSIluTL6Xdtf(^C
z`E0@JSk`6X(pcnz^!OplcMIGTFIQH5>#k++$$lo8XDV`0efP0GQuOSA0=~yFAEU^5
zP?~86{W5NLzU0^aOqA$Fy#+XngNYCamsbn?69II{hz&?Cug;nH$38JkhVNs*XQ-PJT)DMkq(XXc}Xwu4t=!E-)5)Z3>ad_K0O}17FVrGB&TEX8Jh>Iv%K%RBi~`qjr2J$BWPjgo$F>@8;`TQm-`wiD5hk$aNlusLTCER3Aq7zus
z4AQ;&6(|obZI#xyFr}vb4NFX-ShY~O6W+8gQZ+LFV%G>KJB?P@YFm~3qVfKV)^ok~
z6BKlNEsMNyE%WW4Fq$Ic$2
zdOW&~pIVzRpiN@xZ*e(s*2H2-n!k>d?
znW$TSHfk@cgW_Zj
zYtBIFwedAv2Dqdf~u%$y}rF0EpLQN|Kx`nH%`N*K{-E6K(&8v~J
zIYtX<#ZlJ)F|fy7MKRV8EbX*)6^P-TGat2QtLVUW6imy=EFrNPJRp@;@}1KlZHYo*
zj3$b8fbOyEW4N&tN^oOX@WVgUlZP;QWM@=+6_su7@+dYvtDQ#BfTPoxKZ0P
zfU{F4HUZhqL9q_4gvLKsHH7_fNQFcUv1UeWY8e0&Y!2am_UBZOT+T9C6oR}kNj=4oiBi>4$ZT>McB%$^sRIY958t`4
zn?2xh;67vA*$PsVB?X@57<2%GRQDqg69MYe0S&GMr9C~tMoK}BJvd}BgE$V^`WwJ14=XRP(L*we=XOkXbZ
zt@}LE9!buv1Q&-bA6*D7tuhfKmS7?@FU!kZ@GZb}gI3_rnKS@At0oJ5NTR>3i)$sV
zNOd8lf!B||yvuKg!)neZAm)R{i{5KW^r
zg<=Y_QjdkY{J+#`k!u<}stN-Q^bPAD(e&X=888)v#v7b2?zVHR=(4|Z?q(h#h$(>c3_u}YWJbD6a
zrWen5N`V6(!}}t#YfVGry>!R)CFXHx?v!c5i!gvHzM?(Lbtk=ht0LsCm`bauD{>7Up;moC&8hHXFs7B7HCZFm~A}XdLYhbFN#lAaFWJO4m
zk0Xm$oFGBa^%`JC0@pp*3naoNKY+0=xI^S)b*=1sc}_A=ALpdS66X0LN5b
zPYJ~zw^I$iUaotQ!d%Qyw2J|LP@@B=<}O`F@FgJv?90+W3Uv{(F*E)!o+)<}YGKp-
z;#&{T-Q2*Wc+a_`Mt?Z-YT4%A_q;xBU#>8WgNA9d<2}Zg!Jh!maiF^nA*p|UwpLD7
zTHWcnxim}s^5p-8ZVNlUB2WduBg~j7aR7|_jpbzK+Lm>@tkc@IuEGCZ0BQGXq{L-N
z#S%j52Be7CPCq{|Qdm?n4$0sUElCX8cvc9l<%4b(*A
zc$zvNQ=&?!@DFUkpkIW|Lx282qsP5OR?j%Ika})gaMj2L$l4E}u
zy%%ArW(ovi&+6NdGK9LA>cU_6UaVgbYRwIJWCvIw{}Fr{$g!P!{VHe6d?~Af
z09#wUKYo$xW^DJFj2HeV>|IWO^*;lI(=j5j_kaqu!|`1A301SWV#_%?YPgRO*O!~T
zlX7uhnET5tzu9Qgo~G2%o){`H8^Touoik75U!?sTgvT_wng7}og#f`
z>n!5x0Gb?RZf%v)33XJnzA(0OW>OouyioOWW*Qo~+;n7U@f5=lhduqA@(6=(hpClx+GxC
z;m<&t=G)#nx*3@{z~Ba6LT>XWN>muR8WzpPN6!{klkbP!ouUMEnw`*eou)^&TDup>
zV%q2L_MPFQxveK@a%;@f8>W5pQb_9wEc7-M96jg#-q+0gjwY~Tg=hT}cd*|aU=raJ
zsbpU=uq^!MkypR>CZ(Z~_U)-#Pu2RY8QY5
z1J8&F^j_H7b#M4IHoBbQc5!|f+(xf%fBY=hte)TWux=qa@m)qNt`BfVkZa)5$W(l
zj_qT*#=#J)si_!GGap&0~%BzD@pW==^lLEf9baf$dwf
zMlWX1Rhb7u--R5!MCG|kw$OIVEMl_S-S4kYT5zRzqE!N1&G@7hYPzuxlDf1zrX>(+
zM>pe8k~7UqHDbt5pe*=J*6(}|IaLuS^ZV$9NR(Nr1-}K)_o*Z)08dp)%omH_I>IZz
zkzMD*d`4zndbID=XHu+?)*Rg>IjIoN05CZqn@J7;{|Tn
z4H^#be+IU}%s^JHg=#P;gY6m`Xy;K(O{>z&Org>6r$B07*!^J^uWmh|c`@lQPABtD
zHlb8540A#$+!_&xl4L;R5$k-~$KjF;z`XB%o}|Y`qm{5`Q}h*r!5sC&WbG(Im2zZ2
z90V_=$_V%zLUZIT|hl*MHR5n;GfT?3u6&
zGYNLIG#=BRN0-MSE`03$4#*`hs0U-&`zh-=5T
z8vphGP+>P@*US={16W8Ct~4hzg`ugMPB>6
z1@`2o9!;i;S}C{>bCjbFO-?UQD=Tv$I9-g@n}vz&%LZ_`zTk8=G@4eggwe9I*VFQ4
zyqZ9+?j~~kTzwhBOX3AY^pDqmpLDPAf0|s{8aoehacLXfs0Wz9}W%7)PjP5`PqdaKoHU;aM0y7uyY^k3fm
z8l@_J15~4@7PiZXne4iWehc?M7%@Q3x>p?zTL)
zm06u)!JSA@k&NEfcM3LJ9|OEZu-nr)Rj@Z=I7ALQg6*H_JYJt&pOFm@TyH9n0&3z|
zsSZwj-(W%=rzrgTRdT}tuXU!C+!!Ivf^-@b;-!fb~4xABcMi4
z+q!WqO14&ZN}s06T0=$c_BsaUb$Ch_{EKWd)Mjxjom<>|o(M8Ya+wv9F382~bInA|
zn$bupn)^zM_@|#0XU@Q8&S(|oPBOF&kT<@UjijA$ox1KrFB0NX62B(NmdB@S1*z~E
zEoVxWk0&>Ff;43;&lodBuBIs=2QHKRE^rUsm8?;6G?O9?B3*{d=Ao5Dy>(XHoS#+2TZ!r!Q%RGRmd85s#Vy_^WSej2e@$~k{avhIpNr5C
z`?ORjnh+?dL;6&3P07DK2RwSA|Hc3BaKZm5M2HLdzw3;!|37d+VLO|D6v6*!S>Vf>
zu+WzOFS>}5|Dnxkco!5}5C8z~H2?sp|Lcc}xSH5H|4&@u|H9=({O1t!Ya9!uEzuXQ
z-U5es@kTtHk;@6&ef^bsiQ#66DVt3&3IK1>prha>!qw&u-qO(xU#NmoeFP
zhBCe0!q4sP40^a!waM!0sFKH`0*7s7j}7U}=Y$pT#QDYQY9fs}&-9|CF7cD5v4o3h
zZ&820=`0?>@P`UZX{D#@8HOU;hPlY*Sr+;;D!X=k
zN@zj#QQz<(X=2w%;1JuHr;8z{YRo$-s5Fb5H)IjUY`-fxW-r0q8e_yhNy;mXZRmwm
zc9!_#oQP5m(pD-tGN1O2&N2(pOOND^Di2q86ZCW>?8Yw-Q^iJ)Ch;Y5;!nxx@ywx>
z=1o;lvu>Q@X%3l%`&McxDn*!P|K^V#%Q*9%SVwW17+B|1IOwt%lh3B{hw6r3?NuaOI{ke7Nm{{p!6or)3C^V*IOoR=?FKG;k=Q%jINzgs+-y(d&
zDvQP@hMx?#=Qwk&qDAw9{wBUObqdoP6V#gP)Knz3P?O|(t0YI>KGJ}FgzO)|R&ZgZ
z#ZrUpd5i3#zp0@`OC!rvoUpe;h6N8QsYirxaQTn#WrQQ=%z$x`6Mv}glzf#KSE`Di
zdIn$^4Ht>D{v|S#%}kjUrzyu{_(hA~^+j&Gn~g7lytAm`2Z#0)+UcgMN&hgJt(;sA
zuvmOg%s
zFCCJC-kTX`U1Yw~1nw&sTU!>YC)n2jYrDabN#99w$(5dLz#~EKgN-W(-4rIv`XgV`
zn$$D<@p=&w2%8`?02en4vMnt7r%`E!+4juHS>?2PSBddnD`i@DdUZDPsiMjdI5o|Z
z*T}qzyqeGpJV+#XBrQECB&{VlWT>QkK7Py+F@hr}NW#-}YM5OLF2{g^bv{BrH1MtR
zPu4tVwWG5y#L*)RXVeoET82kDSvETgKL8+hajHo(9FhFL61H(~xRaShgP{f)^hwj|
zdGBsE>6FmX;z#pH%n`(idHlhD(ztj&4tQ#l
zh)0_CoC%iA*-ls#!(D$Pq_V6X)-tkomF
zp!J%j24PM-X-A-i!~D}1iG0!9g0548u{!Kc>gq|kq>mY{#yg{VW6%{QTa6@U&*&DC
z3du+}Io;vS6#gF5oC(sEL_3HFDrm7C*Z|)3QFwi6?4~V?a0YK8+F#K^{&atsSQx>||F2a-XO8
z#ZxOOlNg=71DnNjF}rdyc%39!1>ZRpuAPRL6w~m=e;}$0Z5c`&LvdVtTmRXCcj>vs
ze8=WJgLk{S@JYC9_q~Gc`oW-Lk-?y58SvinNkFD(u
z!r0oQ4`xqa5%*9`w#SpIFD_sGvI+dKWfIhcE)jlY68HP{3p}MTqO?AveA$>W7jN!T
z_xZqI8rIb=A|0g%s&l`#nvjh8Y-92*8r~gxvWEO71HUe{-?wK2X5-}cIzTPhU>;}3
z`?PK}9b;}FELi60r;e#H8nkbay=dZ|SwU{={iVDq6u}z++#!>pP&*FX;{hD~vwst!
z+Ly&e=7A~(y|y2dCVhIdb#I9+>@C|i`G?Fbo?B(S(P@?^N`I1k&T`@`OS8IUp4T71
zhAl;t?mcVmZ!x&K@JOkzsN0)$Ww%-W66kHB`oU&ObPwP|kL)ohF&zt%cXBgZr6lN`
zTL&BsgzQ7}ItXj)D%dP=G;6feOgB*>k&Tojk7sowrk1wWG#YGdStzRs=-kmT%0I*e
zn=1CWTflrxFBP>F;|NWmoPb)Q%{Zpi2kdTa&?pk-Viws76wU$10T(Ze4|j3SFKF8yS0#
zUjZ5x!CyGH#@oe_E7O*fO~z-R$l??(QnLIbdl_8qk-}k67YnWudXyH~Y*iN5oel2Dyj5R%fH@fdhPkFC_qXjFl#0iJU+`
z*xn^#-sfL6PM(XR-ueM-)x|c`s#9I(R_@~hcvIlEZgx0H$RtOR6P(xxsnb>djGIZ1
z4Y**SmZD(|
zkwmwM44=(Vf*~gtr+qsB#`P%c%LTBCbxAm$X#IjNI*hCFXJ4Fv<9uz`#sF*qDq^v}
zpIUQ?A+Jv*qu}7#?sYIp+dr
zw2OO>jDTa5fj=z*Kdz>3NQ1AXvwgtI_S<4XV4U|{W~hPRVB1~}ItU74BZwz`nAVT_
znD|-Q#h9ZJqL>g$TbN+USa#D{kAQk`HphE255-=jO!UBaUCC#Ay(Fe&VY~waO5q>-delaT@0w#ed-eKut!#Sf^JLgn
zz(!Xjn-r$1*L_SUZhvI~z08!EhgLJ@t+1=4U$z&97k`a2oyXhk7Ao04u5xc1^m4au
z8ggTYXR~Ju>Sf8D-_YB}w`GG0PMz)S@P9d)6CcS&_9m#CR84dHudF%3RDg-{HW;GI
zgBOh(V$3)&p0eGinj>mG8$%MMA5=h1&{L2DjAwi%&F_KlHy6Q`THS)t6Qic{a4Zha
z$Qr2m#K6X<>2i!Dq6!A0Ipt=-Am7myDB4<77CVEuO*IF
zFdtMk1mIJ%@Z{6jZ{&9gFpU=G3;pype3WQvRRoPL!l3m$mr6VY1cM0oFGMQkh`~IR
zWL35LX9ai4dS_gVdUa~%h&Fb(6xz62YIQ7=-s@YBc6|U3vAPe_
z79n38Syt+S9aLi4GYKfdswZ6218j@8wc6N4es7JRh=hV
z!6Pzkn)}zsgyN0hQrY-jkwqpDCUB3-`Nfs5Ix`vQz4oa?V|Ze!SGR~be;mO*q4epM
zXUzdj-?(miiJq1jj*>pO;gAvNKAggJfjaC0+1qLWAqNL)ycK(T6T)(K+>sTF@Em{(
zb=D)adKt3`efsZTE8X){?edaxYo#EYZuoGE-Ep&S>7Cgt2(p`
z6*_H))z=+qMG^O>soQ3^uTvlcz9K`-g~L@eEpF$uhmCNG@su`1=*j;_4Xujt@EgQR
z`mcfb$zl!{`op(m*J$_NL8$9ll@0)9JzL>J+G&XxAA9-do0!=M#~5lT|Ng43&|`y2
z@V4zWNO-X@^G`(;=w!&gYfMhxvgyrBB5h*J)y1^DLYegBdXgUJVK%~d^~`n5B%XWP
za|Qo0EYreT6g$uv+bPU3GsGHiLM~ru5t50xcmta!+X{w4Y&JLtQBz?NV?gux08)?_V4Q|gW)awxFC
zubW1aNkbF1{DrBE!r{?+|6L5uT?$v*z2X4>J_SPO3VEIjberK@HbtJhTGGWRpQi!W
zHS0?+h6Wy6GhAz>dorE|3`ViHqBpc52*?vMi>BqbRGZzs{dLVQE@VH{C|~^1G&lnOny1mviVo<
zhV*lz2WaL^x=c}X)g_%d^wQSC{2awRz9!4%79Ii|A2%IDq|&24-~4{t3hhS%5P(8E
zYQa?sA1+Oc7CGF*-3g$FH&uIzjScunoWaC4xi~l5?ag7fzB98oJR&M>Mw6+O8&8Z?
z&z^U_S=I)12tPU=6~ZWwqH)+-NHU{zak?IAl(7goy=K6F${{6Z^kxRNrF&;^N8Jfy
zcIC`U?MKIi_e&nWKR&_|Wg*PpM?-wel%iYrME0~kf{zWt`^eG-=$XKTMdHvz(iZX-
zVGUTy(utzY1^IY{9O_IEQRJz27XUCF%r3FIjW6M(;XZdPNn)Sgv&u
z@9!bnta+->6G;LKHVY}E)WPyRKCS>lsG3#8j1~NALjJae%112;T{=WNdetw=aWu;}
ze!>#P4{{q)V$1Pcx!5?F=yba}*tmE&IEj3F^t^w*uAe6zottzlf|Z)s$ReY-+71W^
z6jAy3h79g1D~)N&aPUebtpNa^dv!yV;4^O=bQ}oU{`@d5^>Db+n5sQiHRi}<5OhLf
zh|=>b{$&+G7t6Xpn~+5|HPg>EGoX>y*yl3w?RB$vBXxWo6>@anIA50Aqquh}5_D_+@KBdvPjiWumyr)>iaJ;zAFWC_hS)+*rbU0{9P30BuE?uEH%Izs+bT@
zCt(xe&;TIRmEr$_S-0fze)xV$*SK$G7~KOu_O?c34verMyPe=u(DNT-1XJFLdud{$#rzL
z3q6zU`4k~%fR~puuN-on`Y6*3`0(i;H|5&kiZt+T<--elW|$=}pm=@B!l>tlWQ0RL
z$AVl!+
zJj-zoW)n!v5d?(>@hLc@hDmE8IZi7vhXE43Gy%*js)2VUY00j{EXHZk`*@k+%`HU+
zQ@IHw!DAd^3gK({aQ+T~R^iLtZvCfV-Xgc{&+Z0$QP>X$^xYsaiCdWgfe+S$X2Ik|
z-Ver4#?b(JT=ik%t=h{b-o}=(DxwRyL1kHEVUdvW3Uy-#}jwKL~r!NY1bg@Aw{gIt&2j0$^ZupHHRRh{l1Z
z3*I*BiYajPuVZBWgtzACW-@+F297
z2v)#1scn>#v4EQ>ujx?Ysq(wqI;eu|Rw%>(>`u7*%rK=~Qr`bog>J0WfpmKx+8%!w
zzrSKVFhNmQB^qNZHPMF#)#gCRJUChnh=Vb1Vz%~;tehwnE+~IY;pKv%+`9|+kk6_%
zf+$dxHbdPc4w;;VU<;oVtwVKokK-gqkW5XzVTW(WmnHD|#f
zzgK3+{^r#z;&Mq*YyCz5u6O)KzbO5pv=l%;55ga@qpqc?tV&JDbh%6Io`g_yVmJYl~_HM5`EUEQ9
zx;?9I8l(%KT6qxBOzr@tRDDnk8onx?W@Lv7EcU=wt2E-NM}plS`f;l*ZDxp@P4ncE
zNEqL=g+YyQWa3dJZO@^a-QK>&FoQwENk}=cZpPqLk2*JQo@vgriN?o>0-*D&HFkX}
zp05j+nerI8G+HDuUFfMn;QSMxb;k6C%|)wfNEKvqgvWsn{WJz58pA7qha=&Gdyc#G
znECb!sV}5DT{BqCsl;5;RpQA%c@}K!a~e2ww=WRQ{(c@GeSrjC;k~Qf>5Id>E?FKh
zMUX?IpEkV_B;dz!P7LI-hBBj9+8yi?6zYc!#myzCUx02jhPCq+H_dB0ql$xD&zY|^
zH=kE@SRjpbu3j}}F0hN6?ii;xPHlUXyu`F1gaA^C8OgfJxaIOXg_vLq=Od@Fa!vWK
zfiA6dg@h6~0U9mi-!tW0c6D+Olw22nYar>5u6m47>lZ(_$>0F!e02glD_H{_JtQrn
zfpOi`G`$L9<&=Xsq~w7h
zR6o5)@@eY=BO7~#x`g>QC4G>#zH-@H1%YK3v9T*tVf#yfoHPTI
zdtE9^KmNxVhwgPc%j{K>`YsM2JOf@BIQmOfd%g1sPkO7ST+Ssf3+G!9j
zw6(RD_qB7xaJy|hyGc&(lLJyc1zqonuT!Y<3!X7
z-=Zyo^aJJcCp>5eBz|fC!(9addf5w~nUc`=UOfuzTot?VV8X>~+GLZ?iWLP?6eDL3UH(G{P5gYzBAX
zkB}c2B;IwU6D-&o+U7oaaUbrl9@CWu$C?iQ1Erc~xTl|1NSMU_-+{P(A!@$ZZYt$^yN4P{uwt&`1*`wh$>P3O}ptZhiV_$%S$8CU)qtP{6A#(5BoE7QOCGE6LxFu)V&*U3O&|e@c+AE
z4kg%En?s}xBn%V);J=l2)?slcPr{$z9%OJQXmEFeyGw9qkOXINcPBt_cbDK2+(K{(
z1PcTY?t~!Wo&5H0H?o_(yBD6BXXX$3Q(fI%)m>Ee*Ay23VEFx^M9kP)+}P#kV~Jbp
z-#*5?K}*+;f*a_wt;thvX<;b?rLqvJj9IFn8Byk{dl0T|10yM4G+l%5=&6d9nDwC^nX-KO;!gkziQ*ut|J`8uE1FuH%p}#u&tF)Z!qj
z6kpjhPSCxO8KFGBas_QKUBH*2fZ4IL-^aSqWV-0d#}+jO_+lzR3lx~m
z8Wc`{>NRjEl}N@%O4A*ZyN*ZlI+C93~k=c|?^!22>CCh;n~%!3KyGY2*;@OEkBKZO
z9O1?lzFdvnO6FKTkjJeVa9BH$5=fS4J#fiB78Oe-|MC#V)Z$kLA{+V!<8Eai$Yd3tymcaS~=!|R(6RXaz
zmRQjWlg>3p8E_!=(b@aqBvkkyl3IeONoqR!lAa;+Gj|=CB4%Ys&6_zfkC%v!Hdp2D
z@|N$j4w7Bl3Rp}%{9E*#&qr~Zrx;DPy#5{O%CN63gRNxalRWh;dP;XIJPSbKkZUAS
zA%z@VkF(wLR*pLCY^38c{I#f~sqRAFMbm*RlMu?m`LdR3{+8=eq&_rt30n2JCiy{a
zeJRhN=As}ws2iy&ld|rf)!Mk}MwhDq9kyUrWyy4gTzaH-x0V<0-byRM*5$!C)0mrh
z0Ahn%*-(d7*&gnjC$ykp2#Q6ru%<9S)=pp-*`T8-Zx>(uq+li4F*weGduuuXRhXMj
zKt$OJ7fODOp-@Dk#ECYdRyp&Wh41q#s)1jdNrRm$$Wur
zy39dRA^?-ck${`$GKz|(AUtkQ4!thQEChvk2<&82Fr>8Z;JeIitr54IrHnK@x^Fbv
z7)B_fF!(n7kikO<{!K1owoVCI<5pRO&HHsi7Os#})W@F^u@zzEu_|}^r%CUwD#*S>
z8@=mFSJzv2imd5f4qvdp7IL^YXWfk%FTfzB`anuDzGMmhY)IL9gi%eRaGib`mk@kO
zSXrBKQExSqODxk+8g1w0q<6U#6MG`7r1C5^L9lZI@6npu9V>W{^Zhasm?s23aT=!z
zjr|TVzfoG?r!_4nXN^G0Q>*8Z&AViLSGIaQdG@NNFQBA2zZMPLT^1&`SwH{|tm**t
zV)U`nv^y^*++gBuf?jKSIgGG>0DfgvV(y?fM{b_hTu?^v=p|G_6AHM|&_0V|8{pFx{=vPhoudpxn!|gR`>O-OxE>jIUcWWil&D
zx9GAxbSD`3qIS0Eao7M0t3lpJRrlsYwwvW0uc!6%dFO!{7*kDNi#6Upb!l)%3DgvZ
zP%5;Lszh~$gk#;iD~p!q;0wuUHDmO^8%ax)4>uN-yRRR4xNxSZt+X#+j+BWl-nW%u
zE$fbz)ihls9=6F5jM?nj`|1Jd81ULz{ri8&
z&G|C|)$PVX#Yr>6Y~|tV2zg{9eRNUd{>z>E@Pe}mOd==wIKKFZ_2(BWb~ON6bY|cI
z&*vSEaKo+-{U)w8ZZ0DC;QD5LYn#zQ4kXnzkUN3mS^2H8NNsQXR`={vq7j)!grYuo
zEOE*#*d6!!Tj0R@x*16jt1<)o8WBA2NiXlt`1Y_sbIhQK)s-n`@8GNg9()`$J1BKJ
z+Z>njj!5zCEU7M<7Y-yMG?;0HY0=^&B*1@2${!lDxIB-+&TqY&nYr8r>oC~R7SmJe
zR2Nn3p3*mV!4_p6u#7y#;bu>;!5)5Au_+%&n>WN8jUO(pXxRAJ@$vyf<}T+tw{*>u(0_M~|`L?J#;A%teFfnP7-P?hH_xQXZ%EwU@zNIgzcWHscWAaRt5388CWpyNz
zM@nkU7E1`e|F)d5-#s7kf}>f+F}c1hVPlxXrRNKUta;xeN%6Yd>xon9aC1Eg;H-5(
zAwFulBM5C)BG$Y>3fnuK+G!3W3!&RSP2?5vs_2Z7t{+y(d{B7Dh84@`%6h3$tb|-A44%@YfoC{DZt)ea*D`IL7TW
zGH(_~eZ8zKmr&B#jARFrEsWGe$`La4X?y=mHW}VS{Wko|aql^49O67P0lB0y^KbJu=N7w2VS7bUq_E0Kl0EWJy#7u{}~%DG6@y#$}(
ztPvf82A@Ae)G+hW(&6OReI1UgFtEMt34IVuk=?3|(=C<&!+xjkr@z4$T0QUL(cR|M
z=d;K2-nh+IuEWg=!0Y1Ud>7mMba}BqYVE67!R{s0Ls8Df^-JU0F9T`_1*3g~1rLJN
z_X#9veAL4FE{(+3xPuW!yg375ULZ!Ousx&&e*)x;$e@v|59gR75o-zO24DnJQz2ur
z5Cu}&q9BNK;?e{_L;Fdildoo_QjghHZ^eE|Wr-w{g+Z1eAi#AglZc5)qUrKLC@9;|l2{h9T=BQHH1%0aX
zu29`V0hWS*W&uJ3GDAyg@{sfjIwTe~+7CCxSh!^ZX&m^iA_jwuX-U(IAg`tn9&Tfp
z+%1Rbz~Jowg)Q4*aznf-d);F>V>+2v6)rNYtgtiCzUt1xf>8278ao*7x?>9HAo0|4
zdxYfEo_GumU5bJ=`PROfq%TbuN!LO*VhS8%m
z@nPZU$>m!NEO~Fa5kXMY?4oR_cn;?+z~efvdUi+Cub=hIY}6{MhIR9`*BvGjxU(Fy
zwq#bV^{e7Nf`?|;8&tLEo)C(A_gm2)DLi61sd`Bn52ni&EQummi{1=)rt9jQas7Dk
z{qy`4Q^#t}ZW6O~&$+2nF&$*j!@_&GXw{~W{q`eNFV0Fs$n}59??gCC8;IU4;-GmK
zq9~M1=p{a0Jz9td;i=-O#L@e?+vLI;)yU}eUM#R7dzror7yf*%hm%5+{3T)5k>XW-
zqBHaDC0)Xq-A(&?YsP8nP&_Mi%|Kcjb{$a=X~hXwI!cqljkBaXAQpx};5COjGT&_KBu>5%YBEH*|p
zDzh0-Ea5B`HkQ^z4y-{)WW_u1Pvx(wuQiPh69|v@T)3L9ZPJ6Og^6j13z0}+K~o9+wuC#bRBiNZB?{6hwWM^E3jy!r6;E&K&-aRUi@eNK^qxjUOhLFa`>=We|d
z!jLZc!MXLv&S@(W=~$%rhwX>T`6+SfM);c^xlO7Sy-z>CV+y0KniMi@6Qs2pLe*(N
zkdit`%&~}LTx#lFe)5p@i*jX`)}8+fVUKYyTySjoOm`1hM>1elZ4OovSbYfqi!+O}
zpc9zCx=>iEPYF#sd3{8**h>K~CeeigD%_`iip!Tlqn(IWe_=H9fOK}sUu6M&746Ja
zc8)%ZTFwM3!stEwjyjVC2?=*vI7dRn(JA=kV;_YCm+Z74}H4k*1H8C
znjTSkZBM#;W$Nv8*a(IdP^KGC=-9+WyZ#p
zbVHSo;K^dH|7y%(@@#yZDfTUv+}F4Buog|9yHewg4I6?K>U|Ihng()%N7*_i_cDl8
zoho->;`&rwPlX7t_Fz+)PvJEldkR^lIbg7lq(egZ%%MrF=fRY`U!GbGy!L!LI_DH<
zeq{vxjx8cD8p-z_v^}%3nw`e3B0=4H*a2OB?%w*ojQVj2nv06<*%CDNmpQ+PcZxu6
z=ZbR69pqGdTYrbhBGOEfK!+ABVV0>ZqsdC^1%A)`0pgN7ch!1a+-_^e^Vq6z*Ne;a
zz8;&@9nI-?IG3f5zC;u{#E3G%#uCS7Vy)j`zGL)E>fie
zMW`t@prDU}$}zuM$xTeO4^R5U1?O{lVW1r*=R&5I;*x}{kp61c$x^0o!;68sP7aq(
zLW(=ses{Nwhn^R%Ag}#~o}{&KC%X0E#mgs`nhIOw{;yYI5-}`-~P%
zoBIbsP*byQXZ3AtPqz=)9TbB38Fa;s(fFe}b}wn}l1KwYDA8VIDopG5MjUay^?V>J$eAP=9k#SlkAR#J^Z^q{Lum9Hv`vm!GX#jsG&w;xVIK?{;N*%4JJr&9z^
z;1x)*b|LGu+t^;p!^)UGueP_PL1lt)@u&uCGsz6}Ttui;oNb`6k?7haa60FGkA=gz
zQ74{=qkm>gW`Zp?jr$aTj{CKd?1(5cv1hb}@2yQg3!7`&1%Z;Mciwbr56&2(H~;m)
zwabY6dqjS1@;H~2j1M2szOr{%=PozQA3^u>3#gxkr~X`|p)-|O55+hsTe+tiQAnqun-Q;{ohH<;yy-z6kT6d?fe)nP2F>;j{AuWN6W0T50hp%3i{+b&_*+-%~!3_j#5G1i{
z$~b_&E|lmz?!27eGMqk;ky|$sSU-7iGjPSzd!61h2xJW5VyF)a%(5xfYZ8&M>p;><
z=um$Ptn5+b+gzV5GE@$6FaY~Q9wNl7yu^;+NcGXRX4A`|lq2Kw6*6$>SP1Rr@rB%1
z$q=?%=zO1Il+K>jrrnj>yFrc}p;J&4>eH8LUK9*k)elGD%q!|sL!9x8UCvd`RPJ0F
zgf;3xVjf)95#xvIGvS)mVkSfRUh^VfC3uUi;5SVF_m2DKxd
z#?XNg+#0uQYQ<5lov7sWYMqkjY1yTx3t#qzhY*KtB0u01a&uIre4V5JVz4@im#{ll
zTc1dfoS^Jx-Ed*}SKKZaa+167HJRv#0|2NA0RYzDpX5|Qrp}IzAX`(Agqyvwtr^Ji
z&uWXTzdzA6XxZ2;a-l_+m%Ym^(`IP%Me8Al!uIYY0I
zNax3t3Bb19+8S3LsbL{v46QOSDn~QrrQDE=&x`e!yQ6HfgP?dI(Mn&R&z+|1>L_g#
zZ4AbsIZ8W~PuzCU^~Rc0h?Vf^WHLs8Qj*l?u8`XVwwNk|uPq&V25sIgZC-ab{Pn`j
zTT*_Mh>__$rx@!gy-Hfj*-~^8;yo=q<0o*@Zv}Igp6`VYJj6hq3Uwvg7Td$+IF21a
zl9uN|^r0ezi_oj4e)CM?S$+a;HtRiZ5}o_25!cK}3MKZ_{U)G&DfDs`UWzi58(w(t
z`Z~&XR6<1+3TAsIndZFXf%L930EP%51Do%OR1Et`kP#MkPZK-!gsoJR=mv=;sMALR
zUKL7Ua7E2#pzk>7Y9k1j7uIfu=S9Ab%)vB$hD`FTxN#cR{)&G@(ITtZx{}#6(4ej=BMg5&E4{cA>vQSEZoo6Pa?Bd_c+ex
zrD1b3u($g2!1`izBBdNMM=~VqQ7w;M1+?J}@@wAN6o9g{i3ui|J@a(An6Nxe5<;gs
zk;{rFo>|Y9)Ys^I)WF7*)6RkmRuX@dni(~+2IDJn%1Fr#>}2aYoHf}wel_fG&V;N~
z9we%iy|Cl{N?C>y_p!zp4vt2$u3e6+7lC6EhFzk8sOU~8JN;P)VrO4#tYGu4okvJu
zSLMRkhS_s-E6H(~CH8)C!g{dZN2-+9F-IIg+C-iSg>K8T+GLj=ISffh{pvE+1KDk+
zK)!cwOE~`T83T&6{4
z$n(PVIUT-Y27mYjlQGQKO5-LCh>aEoZB{Wa+c6b&D)!@t{E#BCtBS~J^7`{r|wu~9t$9<&6|^5dw5J!2XuInhO!C}yBv)Jl*!-qQ
zDOFy2Gku)^pYY&EAlSr}qfA7Hth8(HwxJb1(WhIS&Vj~P)n*lb^kK5bNe25m>S#PI
zbiei076tjn~*Yaavhq(C?f%2(6_kd7YUA(pJT?f8{3>|2($Pd#+;M^nnP=jIk~frC65RXTX%R8=^i|96J%?%eurM0TF{i7G%$=ff~t~?9wW3t0hrBCab)Me*0U#HFL==G-Q
zYOfPCdaZpi_dUiTI-QB`d9$gXC4?{}xOBneL39WIm%nUe@zoP79D54~4FGf^002zC
z_m^rQFxbl0;+OCz%~MG2J6Qr^Kyaj6mMbNIkkJPje5ya;Pb-sfDWokwQ#F}_qYp*&iyN6W8W<2xB@ow*45yI?dvKF{Z2
zkDCWUGMBrpIpV?4eEBGYoY-WhWl0o<4*1IjghN!48?8;QnX>t`?0jwGC`yGL5mx5H
zA7pN2&{3u_nM%pDo<2VX4A{3!JD`4_HpPGw;SI$#cpu)psCz&g_LwKLje1=
zi+d^*>XfoBWv#j(TPLM~#G7;?QG9IH50Vv2&%~MaGjj{{!lm=6ap#s+<)so>C-@c$
z9Ktv7q>wC^qM~{A->MU)L&tX@gz4W(lqYS_CLm$3K2YD%hQ7ikqc_d2ag~4+*r-B-
zJP(s!xPE?;w>iFO=h|5#Wl+gZ&k)}Bcr;wW@vZf=G!Pr`*kYiXjPYJQf|z%W1i#q!
z5iMD!?v3#+b_zZDt&wbrD)_zhtLGV81AGyAWtuV(oAYT`1mbL&vmI&*1J$qU4)ET@
zPUxlN_^am%&gWS45v6m!CuOeburpLZ(+<_Sxz2`qNv};dvK*&IgxNxY=G?nNR#j6|
zH4-e`(0^040Q12+cYkK8oDCYXZ-El{hDM5egnyh{d*a6osZu39k+M^m_|9kq26
ztYDTgnJI1MHNq_}f1h*QvKaF!_HfV=os@sS86?Mwv#H(~RoyK&{F>`5=!$f=7NgC<
z1`KlSf1PfItH@`-&O4&Re{+dqQ#!aRtgiMJ^&XknYg=VPa+e@~g}ta`2A0m#8E=E4
zjJLkspH^+*GAXp>vgs)sM`z62-@khA&0hY6p?Yey@CzZ&TErP6)FrCjoXI@XD}@}
zVatQ_y&rn^B+k`8e{$Bf-*s?4j*Xf+ozleKYC%*J_hw_SIuyAwxNO007)W0RWHwX0@3b
zTZ3%Pj2#*6t(`5bZ2#PknyC+4eS7`1#57d(Ure*0dyQ9XWnJe|Te%C{NSYo_ex`3?yYCYDPhP`Kf^
zM0i=?a<L6Ps>#|8a-Dq4jqjAZ;Uu2)hMsxV)%UfiOVfQ`EG>w`#woUADc
zc(m_p?e?wU%5M(H#GS{nB%QmHzF@fl9}Bt@yQM|OAzJHhS3K+svu?Ne`cbrQ?_EtI
zv7aQxM{Ser7?oVKB_v}@eY>3ck|-*Y#IA58tK-77nL>?0I7wr>iOuCEg|nvcn0Y=c
z?b--<-$u=5)BTbYaT(1cERWstOqxiu$xQ2GsdDf44t{X3!6?g=XZNvdxq}}MgvVPH
z_Sgp#-}iJ~dIYi@z8=>k3*&(8)}|r$t)%L0!!e9-zU51OGy4X0CFrP2N&sGKA#g%q
zl2K;g*7K!goap><^b0ySMlpOQw4FN>M_dVyhwGY1pBHinUYx#Ir115^Q=mSkhw?eD
z=+!^xkGPG^tJl8)wdmOlVPfJHZh%_rxL(!UBifyXX=K|i@>lGi9S_yU@GuRJmtPdu
zn%z86mU{q20{us!I&|QVh3e=4Jis46=zu$sZ=ZgeOORT1SO9cV;?U2N==)ZUergr?
zL#wiX)2frB=^rhNCH+IoK*(Pq0RAH{_W0Tt$hTI=3LyWlmLVSj_5LaU(6Wi09oPx%
zXl(z-O8EB<{Hzv|A3CrPq5Z6uZygY`vxPuR!A}38q`$hd?X?|O4-uLj(jxB!cwoW2u#`X|9C}RtGGdDH`{d*Vx!Z#OVEVn}NvJgDi5BTR0_xpRih%?yo
z|A%cZlr;DO!VdU_oe1-9Y-upq{{PDUHa8`MHQyjZxJwWv^Zn58wQspPKUz=bABR@l
z7;J1}>;(Ep{y)y(zh7~WewbRu@PE&ju{C!zhD>2+Q?Rom=wIg7AE2KXA>$9Aj{g}*
z)6UUM*%9RQUsL)Y`}W72f`RC^#Uu|!9-`Ydh;Ai*&@B~YZ}@k*{i$z%A^-gJ-CX-~
zdME#r3Hn16wjduplJ1^jwf4Fve@uA1Kff35lY9{9xLx4>U2z#zPD6=1*N{hH4CJ?~w}Z+T+B
zCU-)J-;z6jL;N+R=zC&M)NhGDCm2C+-x7>|gZnk^{XH%!?YFppih@J1-=g5Z!TuUP
z_#WGq`&;ba1`{C6{{$0$3?zJS2Y)?8eb0Pc_$%|L<^QK$`J3aEvK%bL;Q#>mkY6KY
L0KldA+qeG*-vWFV

literal 0
HcmV?d00001

diff --git a/package/calendar-plugin-0.1.3.manifest.sha256 b/package/calendar-plugin-0.1.3.manifest.sha256
new file mode 100644
index 0000000..3baaf23
--- /dev/null
+++ b/package/calendar-plugin-0.1.3.manifest.sha256
@@ -0,0 +1,20 @@
+10c82cc04fa26fdea842d59090dccf5adb452705792b045108749a6d64d2d710  calendar-plugin.php
+1daa5861c0d10258c0d16c5c88c1a18fb3c8df7b590f7facfe7c28f678643bdb  src/Contracts/AuthAdapterInterface.php
+25cff4b0fc2ee292b53c152edd083c6af66200a25eec28f8dfce37d126a8892d  src/Contracts/DatabaseAdapterInterface.php
+4f0f4caa5ac98499854336f5b74af55ce889653f3956e5df10910f869a23fdba  src/Contracts/HttpAdapterInterface.php
+15e8f58c7360d6cd0c76c945abfeb026f8278d40b330e99b67955eb2f85f5563  src/Contracts/OptionsAdapterInterface.php
+47d55e845b8a696c55fa86f597bb2760b6e35d974b70f365593d38697ef398c8  src/Domain/CalDavService.php
+6e18669ebbf7dad5fc6a402954428283ada4f2def369206e5065f4a37554437d  src/Domain/EventService.php
+412a22ecd910535c7ace2549a86eacf08cc9cd824f1767e60116d8593355f57f  src/Domain/IcsService.php
+a6bc2e7b2d1862603d5709ee2860ff3a28cd128b6eb15ee2e56b6bbd2ae806eb  src/Domain/RecurrenceExpander.php
+633a781fff77c39c4e06b1a04f9972c2b8bcfb21095031c5b4f6f4de65cdee9a  src/Domain/SettingsService.php
+c870fc51a6ab8d1b2306ce61e2c94c443a954204305b14743cb913ce243a90f6  src/Domain/UserService.php
+5aaac066919b60461bde2e96cbfb4de66a5a28e89d2a61b78f0e2d346f23395b  src/Infrastructure/ServiceContainer.php
+70334df8ca06c8fe61d81f24cb0d8f19285180f9950054bd86af0083adf8b4c8  src/Infrastructure/WordPress/MigrationManager.php
+8e6c95e9e1c051606e66d95cf0bcf92b2ca087bc491f32ab4921e0898cf77b81  src/Infrastructure/WordPress/WordPressAuthAdapter.php
+68c0ca15ad2c8b6363a2578b85f8daf0d3a094e612a120a2cdd2a2bfd8fe5e3c  src/Infrastructure/WordPress/WordPressDatabaseAdapter.php
+8da85db3c1e69c2c5f01aaa2f558aa8f0323446d8af0aec5b34d4607cd4afe1b  src/Infrastructure/WordPress/WordPressHttpAdapter.php
+cf9fddcecb07af2c03ad2c0e448be12a6b45dd936efc8bc1fd46a52b4af864ea  src/Infrastructure/WordPress/WordPressOptionsAdapter.php
+773eb382da988a3fa283c0041d91b008497e3157bbe5171be5a4622239c05074  src/Plugin.php
+4e6930c79d9ce1045be6863ddd7546e19f7ef7e839d41a41014a3efb3183eaf0  src/bootstrap.php
+4217aa37b3f979f9d16b5a821c9b7d9e7d30b4dc615389495ff5f380c3080c75  uninstall.php
diff --git a/package/calendar-plugin-0.1.3.zip b/package/calendar-plugin-0.1.3.zip
new file mode 100644
index 0000000000000000000000000000000000000000..63d9288783a9af7d4ecd0ed37f354567291e400b
GIT binary patch
literal 46717
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
CQZ?HE

literal 0
HcmV?d00001

diff --git a/package/calendar-plugin-0.1.4.manifest.sha256 b/package/calendar-plugin-0.1.4.manifest.sha256
new file mode 100644
index 0000000..d078325
--- /dev/null
+++ b/package/calendar-plugin-0.1.4.manifest.sha256
@@ -0,0 +1,20 @@
+21c6e7f75de826055d42ff3a530770266870652c47537002c2a78a18f1536176  calendar-plugin.php
+1daa5861c0d10258c0d16c5c88c1a18fb3c8df7b590f7facfe7c28f678643bdb  src/Contracts/AuthAdapterInterface.php
+25cff4b0fc2ee292b53c152edd083c6af66200a25eec28f8dfce37d126a8892d  src/Contracts/DatabaseAdapterInterface.php
+4f0f4caa5ac98499854336f5b74af55ce889653f3956e5df10910f869a23fdba  src/Contracts/HttpAdapterInterface.php
+15e8f58c7360d6cd0c76c945abfeb026f8278d40b330e99b67955eb2f85f5563  src/Contracts/OptionsAdapterInterface.php
+47d55e845b8a696c55fa86f597bb2760b6e35d974b70f365593d38697ef398c8  src/Domain/CalDavService.php
+6e18669ebbf7dad5fc6a402954428283ada4f2def369206e5065f4a37554437d  src/Domain/EventService.php
+412a22ecd910535c7ace2549a86eacf08cc9cd824f1767e60116d8593355f57f  src/Domain/IcsService.php
+a6bc2e7b2d1862603d5709ee2860ff3a28cd128b6eb15ee2e56b6bbd2ae806eb  src/Domain/RecurrenceExpander.php
+633a781fff77c39c4e06b1a04f9972c2b8bcfb21095031c5b4f6f4de65cdee9a  src/Domain/SettingsService.php
+c870fc51a6ab8d1b2306ce61e2c94c443a954204305b14743cb913ce243a90f6  src/Domain/UserService.php
+5aaac066919b60461bde2e96cbfb4de66a5a28e89d2a61b78f0e2d346f23395b  src/Infrastructure/ServiceContainer.php
+70334df8ca06c8fe61d81f24cb0d8f19285180f9950054bd86af0083adf8b4c8  src/Infrastructure/WordPress/MigrationManager.php
+8e6c95e9e1c051606e66d95cf0bcf92b2ca087bc491f32ab4921e0898cf77b81  src/Infrastructure/WordPress/WordPressAuthAdapter.php
+68c0ca15ad2c8b6363a2578b85f8daf0d3a094e612a120a2cdd2a2bfd8fe5e3c  src/Infrastructure/WordPress/WordPressDatabaseAdapter.php
+8da85db3c1e69c2c5f01aaa2f558aa8f0323446d8af0aec5b34d4607cd4afe1b  src/Infrastructure/WordPress/WordPressHttpAdapter.php
+cf9fddcecb07af2c03ad2c0e448be12a6b45dd936efc8bc1fd46a52b4af864ea  src/Infrastructure/WordPress/WordPressOptionsAdapter.php
+8d8eb0518838370e636a1e32b82e61d5c1ece6875f85c333a6df7f8c7d46b60a  src/Plugin.php
+4e6930c79d9ce1045be6863ddd7546e19f7ef7e839d41a41014a3efb3183eaf0  src/bootstrap.php
+bfecf2aa282942bbf143725152d3c7f682927c40c9febd19f2925b42f32c2c6d  uninstall.php
diff --git a/package/calendar-plugin-0.1.4.zip b/package/calendar-plugin-0.1.4.zip
new file mode 100644
index 0000000000000000000000000000000000000000..d45909d32c05463716828d318f1aa3bc76312c76
GIT binary patch
literal 47633
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 JFZ=%A{{yvna2Ego literal 0 HcmV?d00001 diff --git a/package/calendar-plugin-0.1.8.manifest.sha256 b/package/calendar-plugin-0.1.8.manifest.sha256 new file mode 100644 index 0000000..0dc6263 --- /dev/null +++ b/package/calendar-plugin-0.1.8.manifest.sha256 @@ -0,0 +1,20 @@ +1bf98ffa4bf22472be330b49aeee42507aef988cb56f1dc15415e903b4e74b6a calendar-plugin.php +1daa5861c0d10258c0d16c5c88c1a18fb3c8df7b590f7facfe7c28f678643bdb src/Contracts/AuthAdapterInterface.php +25cff4b0fc2ee292b53c152edd083c6af66200a25eec28f8dfce37d126a8892d src/Contracts/DatabaseAdapterInterface.php +4f0f4caa5ac98499854336f5b74af55ce889653f3956e5df10910f869a23fdba src/Contracts/HttpAdapterInterface.php +15e8f58c7360d6cd0c76c945abfeb026f8278d40b330e99b67955eb2f85f5563 src/Contracts/OptionsAdapterInterface.php +47d55e845b8a696c55fa86f597bb2760b6e35d974b70f365593d38697ef398c8 src/Domain/CalDavService.php +37a811d322ef40dc0c3af0b62cb5ad8e19c004e0fbf76d5b46fc46f4a1c47398 src/Domain/EventService.php +412a22ecd910535c7ace2549a86eacf08cc9cd824f1767e60116d8593355f57f src/Domain/IcsService.php +a6bc2e7b2d1862603d5709ee2860ff3a28cd128b6eb15ee2e56b6bbd2ae806eb src/Domain/RecurrenceExpander.php +1f337ca1d51e39bbf16e2e5d17ebb232a9a1a5b5fd1924f3800c8b6fc12c1760 src/Domain/SettingsService.php +c870fc51a6ab8d1b2306ce61e2c94c443a954204305b14743cb913ce243a90f6 src/Domain/UserService.php +5aaac066919b60461bde2e96cbfb4de66a5a28e89d2a61b78f0e2d346f23395b src/Infrastructure/ServiceContainer.php +70334df8ca06c8fe61d81f24cb0d8f19285180f9950054bd86af0083adf8b4c8 src/Infrastructure/WordPress/MigrationManager.php +8e6c95e9e1c051606e66d95cf0bcf92b2ca087bc491f32ab4921e0898cf77b81 src/Infrastructure/WordPress/WordPressAuthAdapter.php +68c0ca15ad2c8b6363a2578b85f8daf0d3a094e612a120a2cdd2a2bfd8fe5e3c src/Infrastructure/WordPress/WordPressDatabaseAdapter.php +8da85db3c1e69c2c5f01aaa2f558aa8f0323446d8af0aec5b34d4607cd4afe1b src/Infrastructure/WordPress/WordPressHttpAdapter.php +cf9fddcecb07af2c03ad2c0e448be12a6b45dd936efc8bc1fd46a52b4af864ea src/Infrastructure/WordPress/WordPressOptionsAdapter.php +a1c7f4a793d3647cb4c24f3f3cef61e6a11eb45cc559ab528ebfd15cddf005da src/Plugin.php +4e6930c79d9ce1045be6863ddd7546e19f7ef7e839d41a41014a3efb3183eaf0 src/bootstrap.php +893c6df62beed87a981d372c473e5012d1b5d1c254d23b39cda44ae8a08cd16c uninstall.php diff --git a/package/calendar-plugin-0.1.8.zip b/package/calendar-plugin-0.1.8.zip new file mode 100644 index 0000000000000000000000000000000000000000..5a12fd266d2082a08614606e097e6b0ac2fdfd40 GIT binary patch literal 48825 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 `200` +- `/caldav/` (unauthenticated) -> `401` +- `/wp-json/calendar/v1/health` -> `200` +- `/wp-json/calendar/v1/public/ics` -> `200` +- Authenticated CalDAV checks: + - `/caldav/` -> `405` + - `PROPFIND /caldav/calendars/public/` -> `207` + - `GET /caldav/calendars/public/deploy-smoke.ics` -> `200` + +## Backup/Rollback +- Previous deployment backup path (if existed): + - `/var/www/wordpress/wp-content/plugins/calendar-plugin.backup.prev` + +## Redeploy Update (Same Day) +- Redeployed latest local UI/auth shortcode fixes from current workspace artifact. +- Remote sync to `/var/www/wordpress/wp-content/plugins/calendar-plugin` completed. +- Exact-match manifest validation re-run: PASS (`DEPLOY_AND_HASH_MATCH_OK`). +- Stale duplicate plugin directory removed: + - `/var/www/wordpress/wp-content/plugins/calendar-plugin.backup.prev` +- Remote validation after cleanup: + - only `calendar-plugin` present under plugins directory + - `wp plugin list` shows `calendar-plugin,active,0.1.0` + - `GET /calendar.ics` -> `200` + - `GET /wp-json/calendar/v1/health` -> `200` diff --git a/package/staging/calendar-plugin/calendar-plugin.php b/package/staging/calendar-plugin/calendar-plugin.php new file mode 100644 index 0000000..8abffcf --- /dev/null +++ b/package/staging/calendar-plugin/calendar-plugin.php @@ -0,0 +1,22 @@ +events->listEvents() as $event) { + $resource = $this->resourceForEvent($event); + $items[] = [ + 'resource' => $resource, + 'href' => '/caldav/calendars/public/' . $resource, + 'uid' => (string) ($event['uid'] ?? ''), + 'etag' => (string) ($event['etag'] ?? ''), + 'updated_at' => (string) ($event['updated_at'] ?? ''), + 'sync_version' => (int) ($event['sync_version'] ?? 1), + ]; + } + return $items; + } + + public function getObject(string $resource): ?array + { + $event = $this->findEventByResource($resource); + if (!$event) { + return null; + } + $ics = $this->ics->buildCalendar( + [$event], + fn(int $eventId): array => $this->events->getDeletedOccurrenceKeys($eventId) + ); + + return [ + 'resource' => $resource, + 'etag' => (string) ($event['etag'] ?? ''), + 'event' => $event, + 'ics' => $ics, + ]; + } + + public function putObject(string $resource, string $icsPayload, ?string $ifMatch = null, ?string $ifNoneMatch = null, ?int $userId = null): array + { + $payload = $this->ics->parseEventFromIcs($icsPayload); + if ($payload === null) { + return ['error' => ['code' => 'invalid_ics', 'message' => 'invalid iCalendar payload', 'status' => 422]]; + } + + $existing = $this->events->getEventByResource($resource); + if (!$existing) { + $existing = $this->findEventByResource($resource); + } + if ($ifNoneMatch === '*' && $existing) { + return ['error' => ['code' => 'precondition_failed', 'message' => 'resource already exists', 'status' => 412]]; + } + if ($ifMatch !== null) { + if (!$existing) { + return ['error' => ['code' => 'precondition_failed', 'message' => 'resource does not exist', 'status' => 412]]; + } + if ((string) ($existing['etag'] ?? '') !== trim($ifMatch)) { + return ['error' => ['code' => 'precondition_failed', 'message' => 'etag mismatch', 'status' => 412]]; + } + } + + $payload['caldav_resource'] = $resource; + if ($userId !== null) { + $payload['last_modified_by_user_id'] = $userId; + } + + $deleted = (array) ($payload['deleted_occurrence_keys'] ?? []); + unset($payload['deleted_occurrence_keys']); + + if ($existing) { + $nextSyncVersion = ((int) ($existing['sync_version'] ?? 1)) + 1; + $payload['sync_version'] = $nextSyncVersion; + $payload['etag'] = $this->etagFor((string) ($payload['uid'] ?? $existing['uid'] ?? ''), $nextSyncVersion); + $event = $this->events->updateEvent((int) $existing['id'], $payload); + if (!$event) { + return ['error' => ['code' => 'update_failed', 'message' => 'failed to update object', 'status' => 500]]; + } + $this->events->syncDeletedOccurrenceKeys((int) $event['id'], $deleted, true); + $event = $this->events->getEvent((int) $event['id']) ?? $event; + + return ['status' => 204, 'created' => false, 'event' => $event]; + } + + $payload['sync_version'] = 1; + $payload['etag'] = $this->etagFor((string) ($payload['uid'] ?? ''), 1); + $event = $this->events->createEvent($payload); + $this->events->syncDeletedOccurrenceKeys((int) $event['id'], $deleted, true); + $event = $this->events->getEvent((int) $event['id']) ?? $event; + + return ['status' => 201, 'created' => true, 'event' => $event]; + } + + public function deleteObject(string $resource): array + { + $existing = $this->findEventByResource($resource); + if (!$existing) { + return ['error' => ['code' => 'not_found', 'message' => 'resource not found', 'status' => 404]]; + } + + $ok = $this->events->deleteEvent((int) $existing['id']); + if (!$ok) { + return ['error' => ['code' => 'delete_failed', 'message' => 'failed to delete resource', 'status' => 500]]; + } + + return ['status' => 204, 'deleted' => true]; + } + + public function multiget(array $resources): array + { + $out = []; + foreach ($resources as $resource) { + $resource = basename((string) $resource); + if ($resource === '') { + continue; + } + $object = $this->getObject($resource); + if ($object === null) { + $out[] = ['resource' => $resource, 'status' => 404]; + continue; + } + $out[] = [ + 'resource' => $resource, + 'status' => 200, + 'etag' => $object['etag'], + 'ics' => $object['ics'], + ]; + } + return $out; + } + + public function resourceForEvent(array $event): string + { + $resource = trim((string) ($event['caldav_resource'] ?? '')); + if ($resource !== '') { + return $resource; + } + return (string) ($event['uid'] ?? 'event-' . (string) ($event['id'] ?? 0)) . '.ics'; + } + + private function etagFor(string $uid, int $version): string + { + return '"' . substr(sha1($uid . ':' . $version . ':' . gmdate('c')), 0, 16) . '"'; + } + + private function findEventByResource(string $resource): ?array + { + foreach ($this->events->listEvents() as $event) { + if ($this->resourceForEvent($event) === $resource) { + return $event; + } + } + return null; + } +} diff --git a/package/staging/calendar-plugin/src/Domain/EventService.php b/package/staging/calendar-plugin/src/Domain/EventService.php new file mode 100644 index 0000000..7e99a9b --- /dev/null +++ b/package/staging/calendar-plugin/src/Domain/EventService.php @@ -0,0 +1,709 @@ +getPrefix(); + $stem = trim($tableStem, '_'); + $this->eventsTable = $prefix . $stem . '_events'; + $this->exceptionsTable = $prefix . $stem . '_recurrence_exceptions'; + } + + public function listEvents(): array + { + $rows = $this->db->getResults("SELECT * FROM {$this->eventsTable} ORDER BY id ASC"); + return array_map([$this, 'normalizeRow'], $rows); + } + + public function getEvent(int $id): ?array + { + $sql = $this->db->prepare("SELECT * FROM {$this->eventsTable} WHERE id = %d", $id); + $row = $this->db->getRow($sql); + return $row ? $this->normalizeRow($row) : null; + } + + public function createEvent(array $payload): array + { + $now = gmdate('c'); + $uid = (string) ($payload['uid'] ?? bin2hex(random_bytes(10)) . '@calendar-plugin'); + $resource = $this->resourceFromUid($uid); + $title = trim((string) ($payload['title'] ?? 'Untitled')); + $startRaw = (string) ($payload['start_datetime'] ?? ''); + $endRaw = (string) ($payload['end_datetime'] ?? ''); + $start = $this->toLondonDateTimeString($startRaw); + $end = $this->toLondonDateTimeString($endRaw); + if ($start === '' || $end === '') { + throw new \InvalidArgumentException('start_datetime and end_datetime are required'); + } + if (new DateTimeImmutable($end) < new DateTimeImmutable($start)) { + throw new \InvalidArgumentException('end_datetime must be at or after start_datetime'); + } + $repeatType = (string) ($payload['repeat_type'] ?? 'none'); + $repeatInterval = max(1, (int) ($payload['repeat_interval'] ?? 1)); + $repeatNthMode = (string) ($payload['repeat_nth_mode'] ?? ''); + $repeatNthDay = array_key_exists('repeat_nth_day', $payload) && $payload['repeat_nth_day'] !== null && $payload['repeat_nth_day'] !== '' + ? (int) $payload['repeat_nth_day'] + : null; + $repeatNthPos = array_key_exists('repeat_nth_pos', $payload) && $payload['repeat_nth_pos'] !== null && $payload['repeat_nth_pos'] !== '' + ? (int) $payload['repeat_nth_pos'] + : null; + $repeatNthWeekday = array_key_exists('repeat_nth_weekday', $payload) && $payload['repeat_nth_weekday'] !== null && $payload['repeat_nth_weekday'] !== '' + ? (int) $payload['repeat_nth_weekday'] + : null; + [$start, $end] = $this->normalizeMonthlyAnchor( + $start, + $end, + $repeatType, + $repeatInterval, + $repeatNthMode, + $repeatNthDay, + $repeatNthPos, + $repeatNthWeekday + ); + + $data = [ + 'uid' => $uid, + 'title' => $title, + 'description' => (string) ($payload['description'] ?? ''), + 'location' => (string) ($payload['location'] ?? ''), + 'category' => (string) ($payload['category'] ?? ''), + 'all_day_event' => !empty($payload['all_day_event']) ? 1 : 0, + 'start_datetime' => $start, + 'end_datetime' => $end, + 'repeat_type' => $repeatType, + 'repeat_interval' => $repeatInterval, + 'repeat_nth_mode' => $repeatNthMode, + 'repeat_nth_day' => $repeatNthDay, + 'repeat_nth_pos' => $repeatNthPos, + 'repeat_nth_weekday' => $repeatNthWeekday, + 'repeat_range_mode' => $this->canonicalRangeMode((string) ($payload['repeat_range_mode'] ?? 'none')), + 'repeat_count' => isset($payload['repeat_count']) ? (int) $payload['repeat_count'] : null, + 'repeat_until' => !empty($payload['repeat_until']) ? (string) $payload['repeat_until'] : null, + 'timezone' => (string) ($payload['timezone'] ?? 'Europe/London'), + 'caldav_resource' => !empty($payload['caldav_resource']) ? (string) $payload['caldav_resource'] : $resource, + 'etag' => (string) ($payload['etag'] ?? $this->makeEtag($uid, 1, $now)), + 'sync_version' => (int) ($payload['sync_version'] ?? 1), + 'last_modified_by_user_id' => isset($payload['last_modified_by_user_id']) ? (int) $payload['last_modified_by_user_id'] : null, + 'created_at' => $now, + 'updated_at' => $now, + ]; + + $inserted = $this->db->insert($this->eventsTable, $data); + if ($inserted === false) { + throw new \RuntimeException('failed to create event'); + } + return (array) $this->getEvent($this->db->insertId()); + } + + public function updateEvent(int $id, array $payload): ?array + { + $existing = $this->getEvent($id); + if (!$existing) { + return null; + } + $now = gmdate('c'); + $currentResource = trim((string) ($existing['caldav_resource'] ?? '')); + $fallbackResource = $this->resourceFromUid((string) ($existing['uid'] ?? '')); + $start = array_key_exists('start_datetime', $payload) + ? $this->toLondonDateTimeString((string) $payload['start_datetime']) + : (string) $existing['start_datetime']; + $end = array_key_exists('end_datetime', $payload) + ? $this->toLondonDateTimeString((string) $payload['end_datetime']) + : (string) $existing['end_datetime']; + if ($start !== '' && $end !== '' && new DateTimeImmutable($end) < new DateTimeImmutable($start)) { + throw new \InvalidArgumentException('end_datetime must be at or after start_datetime'); + } + $repeatType = (string) ($payload['repeat_type'] ?? $existing['repeat_type']); + $repeatInterval = max(1, (int) ($payload['repeat_interval'] ?? $existing['repeat_interval'])); + $repeatNthMode = (string) ($payload['repeat_nth_mode'] ?? ($existing['repeat_nth_mode'] ?? '')); + $repeatNthDay = array_key_exists('repeat_nth_day', $payload) + ? ($payload['repeat_nth_day'] === null || $payload['repeat_nth_day'] === '' ? null : (int) $payload['repeat_nth_day']) + : ($existing['repeat_nth_day'] ?? null); + $repeatNthPos = array_key_exists('repeat_nth_pos', $payload) + ? ($payload['repeat_nth_pos'] === null || $payload['repeat_nth_pos'] === '' ? null : (int) $payload['repeat_nth_pos']) + : ($existing['repeat_nth_pos'] ?? null); + $repeatNthWeekday = array_key_exists('repeat_nth_weekday', $payload) + ? ($payload['repeat_nth_weekday'] === null || $payload['repeat_nth_weekday'] === '' ? null : (int) $payload['repeat_nth_weekday']) + : ($existing['repeat_nth_weekday'] ?? null); + [$start, $end] = $this->normalizeMonthlyAnchor( + $start, + $end, + $repeatType, + $repeatInterval, + $repeatNthMode, + $repeatNthDay, + $repeatNthPos, + $repeatNthWeekday + ); + $data = [ + 'title' => trim((string) ($payload['title'] ?? $existing['title'])), + 'description' => (string) ($payload['description'] ?? $existing['description']), + 'location' => (string) ($payload['location'] ?? $existing['location']), + 'category' => (string) ($payload['category'] ?? $existing['category']), + 'all_day_event' => array_key_exists('all_day_event', $payload) + ? (!empty($payload['all_day_event']) ? 1 : 0) + : ((bool) $existing['all_day_event'] ? 1 : 0), + 'start_datetime' => $start, + 'end_datetime' => $end, + 'repeat_type' => $repeatType, + 'repeat_interval' => $repeatInterval, + 'repeat_nth_mode' => $repeatNthMode, + 'repeat_nth_day' => $repeatNthDay, + 'repeat_nth_pos' => $repeatNthPos, + 'repeat_nth_weekday' => $repeatNthWeekday, + 'repeat_range_mode' => $this->canonicalRangeMode((string) ($payload['repeat_range_mode'] ?? $existing['repeat_range_mode'])), + 'repeat_count' => array_key_exists('repeat_count', $payload) ? (is_null($payload['repeat_count']) ? null : (int) $payload['repeat_count']) : $existing['repeat_count'], + 'repeat_until' => array_key_exists('repeat_until', $payload) ? (empty($payload['repeat_until']) ? null : (string) $payload['repeat_until']) : $existing['repeat_until'], + 'timezone' => (string) ($payload['timezone'] ?? $existing['timezone']), + 'caldav_resource' => !empty($payload['caldav_resource']) + ? (string) $payload['caldav_resource'] + : ($currentResource !== '' ? $currentResource : $fallbackResource), + 'etag' => (string) ($payload['etag'] ?? $existing['etag'] ?? $this->makeEtag((string) $existing['uid'], (int) ($existing['sync_version'] ?? 1), $now)), + 'sync_version' => (int) ($payload['sync_version'] ?? (($existing['sync_version'] ?? 1) + 1)), + 'last_modified_by_user_id' => array_key_exists('last_modified_by_user_id', $payload) + ? (is_null($payload['last_modified_by_user_id']) ? null : (int) $payload['last_modified_by_user_id']) + : ($existing['last_modified_by_user_id'] ?? null), + 'updated_at' => $now, + ]; + + $this->db->update($this->eventsTable, $data, ['id' => $id]); + return $this->getEvent($id); + } + + public function deleteEvent(int $id): bool + { + $this->db->delete($this->exceptionsTable, ['event_id' => $id]); + $deleted = $this->db->delete($this->eventsTable, ['id' => $id]); + return $deleted !== false; + } + + public function deleteOccurrence(int $eventId, string $occurrenceKey): bool + { + $event = $this->getEvent($eventId); + if (!$event) { + return false; + } + $canonical = $this->canonicalOccurrenceKey($occurrenceKey); + if ($canonical === null) { + return false; + } + if (in_array($canonical, $this->deletedKeysForEvent($eventId), true)) { + return true; + } + $now = gmdate('c'); + $inserted = $this->db->insert( + $this->exceptionsTable, + [ + 'event_id' => $eventId, + 'occurrence_key' => $canonical, + 'exception_type' => 'deleted_occurrence', + 'created_at' => $now, + 'updated_at' => $now, + ] + ); + return $inserted !== false; + } + + public function listEventOccurrences(int $eventId, string $fromDate, int $months = 3): ?array + { + $event = $this->getEvent($eventId); + if (!$event) { + return null; + } + + $tz = new DateTimeZone('Europe/London'); + $start = $this->safeDate($fromDate, $tz)->setTime(0, 0, 0); + $months = max(1, min($months, 24)); + $end = $start->modify('+' . $months . ' month'); + $deleted = $this->deletedKeysForEvent($eventId); + + $items = RecurrenceExpander::expand($event, $start, $end, $deleted); + usort( + $items, + static fn(array $a, array $b): int => strcmp((string) $a['occurrence_start'], (string) $b['occurrence_start']) + ); + return $items; + } + + public function previewOccurrences(array $payload, string $fromDate, int $months = 3): array + { + $startRaw = (string) ($payload['start_datetime'] ?? ''); + $endRaw = (string) ($payload['end_datetime'] ?? ''); + $start = $this->toLondonDateTimeString($startRaw); + $end = $this->toLondonDateTimeString($endRaw); + if ($start === '' || $end === '') { + throw new \InvalidArgumentException('start_datetime and end_datetime are required'); + } + if (new DateTimeImmutable($end) < new DateTimeImmutable($start)) { + throw new \InvalidArgumentException('end_datetime must be at or after start_datetime'); + } + + $repeatType = (string) ($payload['repeat_type'] ?? 'none'); + if ($repeatType === 'none') { + return []; + } + $repeatInterval = max(1, (int) ($payload['repeat_interval'] ?? 1)); + $repeatNthMode = (string) ($payload['repeat_nth_mode'] ?? ''); + $repeatNthDay = array_key_exists('repeat_nth_day', $payload) && $payload['repeat_nth_day'] !== null && $payload['repeat_nth_day'] !== '' + ? (int) $payload['repeat_nth_day'] + : null; + $repeatNthPos = array_key_exists('repeat_nth_pos', $payload) && $payload['repeat_nth_pos'] !== null && $payload['repeat_nth_pos'] !== '' + ? (int) $payload['repeat_nth_pos'] + : null; + $repeatNthWeekday = array_key_exists('repeat_nth_weekday', $payload) && $payload['repeat_nth_weekday'] !== null && $payload['repeat_nth_weekday'] !== '' + ? (int) $payload['repeat_nth_weekday'] + : null; + [$start, $end] = $this->normalizeMonthlyAnchor( + $start, + $end, + $repeatType, + $repeatInterval, + $repeatNthMode, + $repeatNthDay, + $repeatNthPos, + $repeatNthWeekday + ); + $event = [ + 'id' => 0, + 'uid' => 'preview@calendar-plugin', + 'title' => (string) ($payload['title'] ?? ''), + 'description' => (string) ($payload['description'] ?? ''), + 'location' => (string) ($payload['location'] ?? ''), + 'category' => (string) ($payload['category'] ?? ''), + 'all_day_event' => !empty($payload['all_day_event']), + 'start_datetime' => $start, + 'end_datetime' => $end, + 'repeat_type' => $repeatType, + 'repeat_interval' => $repeatInterval, + 'repeat_nth_mode' => $repeatNthMode, + 'repeat_nth_day' => $repeatNthDay, + 'repeat_nth_pos' => $repeatNthPos, + 'repeat_nth_weekday' => $repeatNthWeekday, + 'repeat_range_mode' => $this->canonicalRangeMode((string) ($payload['repeat_range_mode'] ?? 'none')), + 'repeat_count' => isset($payload['repeat_count']) ? (int) $payload['repeat_count'] : null, + 'repeat_until' => !empty($payload['repeat_until']) ? (string) $payload['repeat_until'] : null, + 'timezone' => 'Europe/London', + ]; + + $tz = new DateTimeZone('Europe/London'); + $startWindow = $this->safeDate($fromDate, $tz)->setTime(0, 0, 0); + $months = max(1, min($months, 24)); + $endWindow = $startWindow->modify('+' . $months . ' month'); + $deleted = []; + foreach ((array) ($payload['deleted_occurrence_keys'] ?? []) as $key) { + $canonical = $this->canonicalOccurrenceKey((string) $key); + if ($canonical !== null) { + $deleted[] = $canonical; + } + } + $items = RecurrenceExpander::expand($event, $startWindow, $endWindow, $deleted); + usort( + $items, + static fn(array $a, array $b): int => strcmp((string) $a['occurrence_start'], (string) $b['occurrence_start']) + ); + return $items; + } + + public function getEventByResource(string $resource): ?array + { + $sql = $this->db->prepare("SELECT * FROM {$this->eventsTable} WHERE caldav_resource = %s", $resource); + $row = $this->db->getRow($sql); + return $row ? $this->normalizeRow($row) : null; + } + + public function getDeletedOccurrenceKeys(int $eventId): array + { + return $this->deletedKeysForEvent($eventId); + } + + public function syncDeletedOccurrenceKeys(int $eventId, array $keys, bool $replace = true): void + { + if ($replace) { + $this->db->delete($this->exceptionsTable, ['event_id' => $eventId, 'exception_type' => 'deleted_occurrence']); + } + $now = gmdate('c'); + foreach ($keys as $key) { + $canonical = $this->canonicalOccurrenceKey((string) $key); + if ($canonical === null) { + continue; + } + $this->db->insert( + $this->exceptionsTable, + [ + 'event_id' => $eventId, + 'occurrence_key' => $canonical, + 'exception_type' => 'deleted_occurrence', + 'created_at' => $now, + 'updated_at' => $now, + ] + ); + } + } + + public function listPublicOccurrences(string $view, string $dateAnchor, bool $futureOnly = false): array + { + $tz = new DateTimeZone('Europe/London'); + $anchor = $this->safeDate($dateAnchor, $tz); + if (strtolower($view) === 'list') { + $windowStart = $anchor->setTime(0, 0, 0); + if ($futureOnly) { + $today = new DateTimeImmutable('today', $tz); + if ($today > $windowStart) { + $windowStart = $today; + } + } + $windowEnd = $windowStart->modify('+18 months'); + } else { + [$windowStart, $windowEnd] = $this->windowForView($view, $anchor); + } + $events = $this->listEvents(); + + $out = []; + foreach ($events as $event) { + $deleted = $this->deletedKeysForEvent((int) $event['id']); + $items = RecurrenceExpander::expand($event, $windowStart, $windowEnd, $deleted); + array_push($out, ...$items); + } + + usort( + $out, + static fn(array $a, array $b): int => strcmp((string) $a['occurrence_start'], (string) $b['occurrence_start']) + ); + + return $out; + } + + public function listSidebarUpcoming(int $days = 14): array + { + $tz = new DateTimeZone('Europe/London'); + $start = new DateTimeImmutable('today', $tz); + $end = $start->modify('+' . max(1, $days) . ' days'); + + $events = $this->listEvents(); + $out = []; + foreach ($events as $event) { + $deleted = $this->deletedKeysForEvent((int) $event['id']); + $items = RecurrenceExpander::expand($event, $start, $end, $deleted); + array_push($out, ...$items); + } + + usort( + $out, + static fn(array $a, array $b): int => strcmp((string) $a['occurrence_start'], (string) $b['occurrence_start']) + ); + + return $out; + } + + public function deleteAllEventsData(): int + { + $events = $this->listEvents(); + $count = count($events); + $this->db->query("DELETE FROM {$this->exceptionsTable}"); + $this->db->query("DELETE FROM {$this->eventsTable}"); + return $count; + } + + public function seedDefaultEvents(): int + { + $seed = [ + [ + 'uid' => 'seed-ce-001@calendar-plugin', + 'title' => 'Board Meeting', + 'description' => 'Quarterly board review.', + 'location' => 'Room A', + 'category' => 'Governance', + 'start_datetime' => '2026-04-01T10:00:00+01:00', + 'end_datetime' => '2026-04-01T11:30:00+01:00', + 'repeat_type' => 'none', + ], + [ + 'uid' => 'seed-ce-002@calendar-plugin', + 'title' => 'Office Closed', + 'description' => 'Public holiday closure.', + 'location' => 'HQ', + 'category' => 'Operations', + 'all_day_event' => true, + 'start_datetime' => '2026-05-04T00:00:00+01:00', + 'end_datetime' => '2026-05-05T00:00:00+01:00', + 'repeat_type' => 'none', + ], + [ + 'uid' => 'seed-ce-003@calendar-plugin', + 'title' => 'Daily Standup', + 'description' => '15 minute sync.', + 'location' => 'Online', + 'category' => 'Team', + 'start_datetime' => '2026-04-06T09:00:00+01:00', + 'end_datetime' => '2026-04-06T09:15:00+01:00', + 'repeat_type' => 'daily', + 'repeat_interval' => 1, + 'repeat_range_mode' => 'until', + 'repeat_until' => '2026-04-15', + ], + [ + 'uid' => 'seed-ce-004@calendar-plugin', + 'title' => 'Community Lunch', + 'description' => 'Weekly community lunch.', + 'location' => 'Cafeteria', + 'category' => 'Community', + 'start_datetime' => '2026-04-08T12:30:00+01:00', + 'end_datetime' => '2026-04-08T13:30:00+01:00', + 'repeat_type' => 'weekly', + 'repeat_interval' => 1, + 'repeat_range_mode' => 'until', + 'repeat_until' => '2026-05-06', + ], + [ + 'uid' => 'seed-ce-005@calendar-plugin', + 'title' => 'Finance Close', + 'description' => 'Month-end close process.', + 'location' => 'Finance Office', + 'category' => 'Finance', + 'start_datetime' => '2026-03-31T17:00:00+01:00', + 'end_datetime' => '2026-03-31T18:00:00+01:00', + 'repeat_type' => 'monthly', + 'repeat_interval' => 1, + 'repeat_nth_mode' => 'day_of_month', + 'repeat_nth_day' => 30, + 'repeat_range_mode' => 'until', + 'repeat_until' => '2026-06-30', + ], + ]; + + foreach ($seed as $event) { + $this->createEvent($event); + } + return count($seed); + } + + private function normalizeRow(object $row): array + { + return [ + 'id' => (int) $row->id, + 'uid' => (string) $row->uid, + 'title' => (string) $row->title, + 'description' => (string) $row->description, + 'location' => (string) $row->location, + 'category' => (string) $row->category, + 'all_day_event' => (bool) $row->all_day_event, + 'start_datetime' => (string) $row->start_datetime, + 'end_datetime' => (string) $row->end_datetime, + 'repeat_type' => (string) $row->repeat_type, + 'repeat_interval' => (int) $row->repeat_interval, + 'repeat_nth_mode' => property_exists($row, 'repeat_nth_mode') ? (string) ($row->repeat_nth_mode ?? '') : '', + 'repeat_nth_day' => property_exists($row, 'repeat_nth_day') && $row->repeat_nth_day !== null ? (int) $row->repeat_nth_day : null, + 'repeat_nth_pos' => property_exists($row, 'repeat_nth_pos') && $row->repeat_nth_pos !== null ? (int) $row->repeat_nth_pos : null, + 'repeat_nth_weekday' => property_exists($row, 'repeat_nth_weekday') && $row->repeat_nth_weekday !== null ? (int) $row->repeat_nth_weekday : null, + 'repeat_range_mode' => (string) $row->repeat_range_mode, + 'repeat_count' => is_null($row->repeat_count) ? null : (int) $row->repeat_count, + 'repeat_until' => $row->repeat_until === null ? null : (string) $row->repeat_until, + 'timezone' => (string) $row->timezone, + 'caldav_resource' => property_exists($row, 'caldav_resource') ? (string) ($row->caldav_resource ?? '') : '', + 'etag' => property_exists($row, 'etag') ? (string) ($row->etag ?? '') : '', + 'sync_version' => property_exists($row, 'sync_version') ? (int) ($row->sync_version ?? 1) : 1, + 'last_modified_by_user_id' => property_exists($row, 'last_modified_by_user_id') && $row->last_modified_by_user_id !== null + ? (int) $row->last_modified_by_user_id + : null, + 'created_at' => (string) $row->created_at, + 'updated_at' => (string) $row->updated_at, + ]; + } + + private function deletedKeysForEvent(int $eventId): array + { + $sql = $this->db->prepare( + "SELECT occurrence_key FROM {$this->exceptionsTable} WHERE event_id = %d AND exception_type = 'deleted_occurrence'", + $eventId + ); + $rows = $this->db->getResults($sql); + return array_map(static fn(object $r): string => (string) $r->occurrence_key, $rows); + } + + private function safeDate(string $dateAnchor, DateTimeZone $tz): DateTimeImmutable + { + if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $dateAnchor)) { + return new DateTimeImmutable($dateAnchor . 'T00:00:00', $tz); + } + return new DateTimeImmutable('today', $tz); + } + + private function windowForView(string $view, DateTimeImmutable $anchor): array + { + $view = strtolower($view); + if ($view === 'day') { + $start = $anchor->setTime(0, 0, 0); + return [$start, $start->modify('+1 day')]; + } + if ($view === 'week') { + $weekday = (int) $anchor->format('w'); + $start = $anchor->modify('-' . $weekday . ' day')->setTime(0, 0, 0); + return [$start, $start->modify('+7 days')]; + } + if ($view === 'year') { + $start = $anchor->setDate((int) $anchor->format('Y'), 1, 1)->setTime(0, 0, 0); + return [$start, $start->modify('+1 year')]; + } + $monthStart = $anchor->setDate((int) $anchor->format('Y'), (int) $anchor->format('m'), 1)->setTime(0, 0, 0); + $startWeekday = (int) $monthStart->format('w'); + $gridStart = $monthStart->modify('-' . $startWeekday . ' day'); + $gridEnd = $gridStart->modify('+42 days'); + return [$gridStart, $gridEnd]; + } + + private function canonicalOccurrenceKey(string $value): ?string + { + try { + if (!str_contains($value, 'T') && preg_match('/^\d{4}-\d{2}-\d{2}$/', $value)) { + $dt = new DateTimeImmutable($value . 'T00:00:00', new DateTimeZone('Europe/London')); + return $dt->format('c'); + } + $dt = new DateTimeImmutable($value, new DateTimeZone('Europe/London')); + return $dt->format('c'); + } catch (\Throwable) { + return null; + } + } + + private function makeEtag(string $uid, int $syncVersion, string $stamp): string + { + return '"' . substr(sha1($uid . ':' . $syncVersion . ':' . $stamp), 0, 16) . '"'; + } + + private function resourceFromUid(string $uid): string + { + $uid = trim($uid); + if ($uid === '') { + $uid = bin2hex(random_bytes(10)) . '@calendar-plugin'; + } + return $uid . '.ics'; + } + + private function toLondonDateTimeString(string $value): string + { + $value = trim($value); + if ($value === '') { + return ''; + } + try { + $dt = new DateTimeImmutable($value, new DateTimeZone('Europe/London')); + return $dt->setTimezone(new DateTimeZone('Europe/London'))->format('c'); + } catch (\Throwable) { + throw new \InvalidArgumentException('invalid datetime value'); + } + } + + private function canonicalRangeMode(string $value): string + { + $v = strtolower(trim($value)); + if ($v === 'no_end' || $v === '') { + return 'none'; + } + return in_array($v, ['none', 'count', 'until'], true) ? $v : 'none'; + } + + private function normalizeMonthlyAnchor( + string $startIso, + string $endIso, + string $repeatType, + int $repeatInterval, + string $repeatNthMode, + ?int $repeatNthDay, + ?int $repeatNthPos, + ?int $repeatNthWeekday + ): array { + if ($repeatType !== 'monthly') { + return [$startIso, $endIso]; + } + try { + $tz = new DateTimeZone('Europe/London'); + $start = new DateTimeImmutable($startIso, $tz); + $end = new DateTimeImmutable($endIso, $tz); + $duration = $start->diff($end); + $targetDay = (int) $start->format('j'); + if ($repeatNthMode === 'day_of_month' && $repeatNthDay !== null) { + $daysInMonth = (int) $start->format('t'); + $targetDay = max(1, min($repeatNthDay, $daysInMonth)); + } elseif ($repeatNthMode === 'weekday_of_month' && $repeatNthPos !== null && $repeatNthWeekday !== null) { + $nthDay = $this->nthWeekdayOfMonth((int) $start->format('Y'), (int) $start->format('n'), $repeatNthWeekday, $repeatNthPos); + if ($nthDay === null) { + $year = (int) $start->format('Y'); + $month = (int) $start->format('n'); + $step = max(1, $repeatInterval); + for ($i = 0; $i < 120; $i++) { + [$year, $month] = $this->addMonths($year, $month, $step); + $nthDay = $this->nthWeekdayOfMonth($year, $month, $repeatNthWeekday, $repeatNthPos); + if ($nthDay !== null) { + $start = $start->setDate($year, $month, $nthDay); + $targetDay = $nthDay; + break; + } + } + } else { + $targetDay = $nthDay; + } + } + $anchoredStart = $start->setDate((int) $start->format('Y'), (int) $start->format('n'), $targetDay); + $anchoredEnd = $anchoredStart->add($duration); + return [$anchoredStart->format('c'), $anchoredEnd->format('c')]; + } catch (\Throwable) { + return [$startIso, $endIso]; + } + } + + private function nthWeekdayOfMonth(int $year, int $month, int $weekday, int $pos): ?int + { + $weekday = max(0, min(6, $weekday)); + $tz = new DateTimeZone('Europe/London'); + if ($pos === -1) { + $last = new DateTimeImmutable(sprintf('%04d-%02d-01T00:00:00', $year, $month), $tz); + $last = $last->modify('last day of this month'); + for ($day = (int) $last->format('j'); $day >= 1; $day--) { + $d = $last->setDate($year, $month, $day); + if ((int) $d->format('w') === $weekday) { + return $day; + } + } + return null; + } + + $first = new DateTimeImmutable(sprintf('%04d-%02d-01T00:00:00', $year, $month), $tz); + $daysInMonth = (int) $first->format('t'); + $seen = 0; + for ($day = 1; $day <= $daysInMonth; $day++) { + $d = $first->setDate($year, $month, $day); + if ((int) $d->format('w') !== $weekday) { + continue; + } + $seen++; + if ($seen === $pos) { + return $day; + } + } + return null; + } + + private function addMonths(int $year, int $month, int $delta): array + { + $index = ($year * 12) + ($month - 1) + $delta; + $newYear = (int) floor($index / 12); + $newMonth = ($index % 12) + 1; + if ($newMonth <= 0) { + $newMonth += 12; + $newYear -= 1; + } + return [$newYear, $newMonth]; + } +} diff --git a/package/staging/calendar-plugin/src/Domain/IcsService.php b/package/staging/calendar-plugin/src/Domain/IcsService.php new file mode 100644 index 0000000..c974626 --- /dev/null +++ b/package/staging/calendar-plugin/src/Domain/IcsService.php @@ -0,0 +1,439 @@ +escapeText($calendarName), + 'X-WR-TIMEZONE:Europe/London', + ]; + + foreach ($events as $event) { + $lines = array_merge($lines, $this->eventToLines($event, (array) $deletedKeysProvider((int) ($event['id'] ?? 0)))); + } + + $lines[] = 'END:VCALENDAR'; + + return implode("\r\n", $this->foldLines($lines)) . "\r\n"; + } + + public function parseEventFromIcs(string $ics): ?array + { + $props = $this->extractVeventProperties($ics); + if ($props === null) { + return null; + } + + $uid = (string) ($props['UID'][0] ?? ''); + $summary = (string) ($props['SUMMARY'][0] ?? 'Untitled'); + $description = (string) ($props['DESCRIPTION'][0] ?? ''); + $location = (string) ($props['LOCATION'][0] ?? ''); + $category = (string) ($props['CATEGORIES'][0] ?? ''); + $dtstartRaw = (string) ($props['DTSTART'][0] ?? ''); + $dtendRaw = (string) ($props['DTEND'][0] ?? ''); + if ($dtstartRaw === '' || $dtendRaw === '') { + return null; + } + + $allDay = str_contains((string) ($props['_DTSTART_PARAMS'][0] ?? ''), 'VALUE=DATE'); + $start = $this->parseIcsDateTime($dtstartRaw, $allDay); + $end = $this->parseIcsDateTime($dtendRaw, $allDay); + if ($start === null || $end === null) { + return null; + } + + $payload = [ + 'uid' => $uid !== '' ? $uid : bin2hex(random_bytes(10)) . '@calendar-plugin', + 'title' => $summary, + 'description' => $description, + 'location' => $location, + 'category' => $category, + 'all_day_event' => $allDay, + 'start_datetime' => $start, + 'end_datetime' => $end, + 'repeat_type' => 'none', + 'repeat_interval' => 1, + 'repeat_nth_mode' => '', + 'repeat_nth_day' => null, + 'repeat_nth_pos' => null, + 'repeat_nth_weekday' => null, + 'repeat_range_mode' => 'none', + 'repeat_count' => null, + 'repeat_until' => null, + 'timezone' => 'Europe/London', + ]; + + $rrule = (string) ($props['RRULE'][0] ?? ''); + if ($rrule !== '') { + $payload = array_merge($payload, $this->parseRrule($rrule)); + } + + $exdates = []; + foreach (($props['EXDATE'] ?? []) as $exdateRaw) { + $chunks = array_filter(array_map('trim', explode(',', (string) $exdateRaw))); + foreach ($chunks as $chunk) { + $asDate = $this->parseIcsDateTime($chunk, false); + if ($asDate !== null) { + $exdates[] = $asDate; + } + } + } + $payload['deleted_occurrence_keys'] = $exdates; + + return $payload; + } + + private function eventToLines(array $event, array $deletedKeys): array + { + $uid = (string) ($event['uid'] ?? ''); + $uid = $uid !== '' ? $uid : ('event-' . (string) ($event['id'] ?? 0) . '@calendar-plugin'); + + $start = $this->toDateTime((string) ($event['start_datetime'] ?? '')); + $end = $this->toDateTime((string) ($event['end_datetime'] ?? '')); + if ($start === null || $end === null) { + return []; + } + + $allDay = (bool) ($event['all_day_event'] ?? false); + $updated = $this->toDateTime((string) ($event['updated_at'] ?? '')) ?? new DateTimeImmutable('now', new DateTimeZone('UTC')); + + $lines = [ + 'BEGIN:VEVENT', + 'UID:' . $this->escapeText($uid), + 'SUMMARY:' . $this->escapeText((string) ($event['title'] ?? 'Untitled')), + 'DESCRIPTION:' . $this->escapeText((string) ($event['description'] ?? '')), + 'LOCATION:' . $this->escapeText((string) ($event['location'] ?? '')), + 'CATEGORIES:' . $this->escapeText((string) ($event['category'] ?? '')), + 'DTSTAMP:' . $this->toUtcIcs($updated), + 'LAST-MODIFIED:' . $this->toUtcIcs($updated), + ]; + + if ($allDay) { + $lines[] = 'DTSTART;VALUE=DATE:' . $start->setTimezone(new DateTimeZone('Europe/London'))->format('Ymd'); + $lines[] = 'DTEND;VALUE=DATE:' . $end->setTimezone(new DateTimeZone('Europe/London'))->format('Ymd'); + } else { + $lines[] = 'DTSTART;TZID=Europe/London:' . $start->setTimezone(new DateTimeZone('Europe/London'))->format('Ymd\\THis'); + $lines[] = 'DTEND;TZID=Europe/London:' . $end->setTimezone(new DateTimeZone('Europe/London'))->format('Ymd\\THis'); + } + + $rrule = $this->eventToRrule($event); + if ($rrule !== null) { + $lines[] = 'RRULE:' . $rrule; + } + + if ($deletedKeys) { + $parts = []; + foreach ($deletedKeys as $key) { + $dt = $this->toDateTime((string) $key); + if ($dt === null) { + continue; + } + $parts[] = $dt->setTimezone(new DateTimeZone('Europe/London'))->format('Ymd\\THis'); + } + if ($parts) { + $lines[] = 'EXDATE;TZID=Europe/London:' . implode(',', $parts); + } + } + + $lines[] = 'END:VEVENT'; + return $lines; + } + + private function eventToRrule(array $event): ?string + { + $type = strtolower((string) ($event['repeat_type'] ?? 'none')); + if ($type === 'none') { + return null; + } + + $freq = match ($type) { + 'daily' => 'DAILY', + 'weekly', 'custom' => 'WEEKLY', + 'monthly' => 'MONTHLY', + 'yearly' => 'YEARLY', + default => null, + }; + if ($freq === null) { + return null; + } + + $interval = max(1, (int) ($event['repeat_interval'] ?? 1)); + $parts = ['FREQ=' . $freq, 'INTERVAL=' . $interval]; + if ($type === 'monthly') { + $nthMode = (string) ($event['repeat_nth_mode'] ?? ''); + $nthDay = isset($event['repeat_nth_day']) && $event['repeat_nth_day'] !== null ? (int) $event['repeat_nth_day'] : null; + $nthPos = isset($event['repeat_nth_pos']) && $event['repeat_nth_pos'] !== null ? (int) $event['repeat_nth_pos'] : null; + $nthWeekday = isset($event['repeat_nth_weekday']) && $event['repeat_nth_weekday'] !== null ? (int) $event['repeat_nth_weekday'] : null; + if ($nthMode === 'day_of_month' && $nthDay !== null) { + $parts[] = 'BYMONTHDAY=' . max(1, min(31, $nthDay)); + } elseif ($nthMode === 'weekday_of_month' && $nthPos !== null && $nthWeekday !== null) { + $byDay = $this->weekdayNumToToken($nthWeekday); + if ($byDay !== null) { + $parts[] = 'BYDAY=' . $byDay; + $parts[] = 'BYSETPOS=' . ($nthPos < 0 ? -1 : max(1, min(5, $nthPos))); + } + } + } + + $rangeMode = strtolower((string) ($event['repeat_range_mode'] ?? 'none')); + if ($rangeMode === 'count' && !empty($event['repeat_count'])) { + $parts[] = 'COUNT=' . max(1, (int) $event['repeat_count']); + } + if ($rangeMode === 'until' && !empty($event['repeat_until'])) { + $until = $this->toDateTime((string) $event['repeat_until'] . 'T23:59:59'); + if ($until !== null) { + $parts[] = 'UNTIL=' . $this->toUtcIcs($until); + } + } + + return implode(';', $parts); + } + + private function parseRrule(string $rrule): array + { + $parts = []; + foreach (explode(';', strtoupper(trim($rrule))) as $chunk) { + [$k, $v] = array_pad(explode('=', $chunk, 2), 2, ''); + if ($k !== '') { + $parts[$k] = $v; + } + } + + $repeatType = match ($parts['FREQ'] ?? '') { + 'DAILY' => 'daily', + 'WEEKLY' => 'weekly', + 'MONTHLY' => 'monthly', + 'YEARLY' => 'yearly', + default => 'none', + }; + + $payload = [ + 'repeat_type' => $repeatType, + 'repeat_interval' => max(1, (int) ($parts['INTERVAL'] ?? 1)), + 'repeat_nth_mode' => '', + 'repeat_nth_day' => null, + 'repeat_nth_pos' => null, + 'repeat_nth_weekday' => null, + 'repeat_range_mode' => 'none', + 'repeat_count' => null, + 'repeat_until' => null, + ]; + + if ($repeatType === 'monthly') { + if (!empty($parts['BYMONTHDAY'])) { + $raw = trim(explode(',', (string) $parts['BYMONTHDAY'])[0]); + if (preg_match('/^-?\d+$/', $raw)) { + $payload['repeat_nth_mode'] = 'day_of_month'; + $payload['repeat_nth_day'] = max(1, min(31, (int) $raw)); + } + } elseif (!empty($parts['BYDAY'])) { + $byDayRaw = trim(explode(',', (string) $parts['BYDAY'])[0]); + $pos = null; + $token = $byDayRaw; + if (preg_match('/^(-?\d+)([A-Z]{2})$/', $byDayRaw, $m)) { + $pos = (int) $m[1]; + $token = $m[2]; + } + $weekday = $this->weekdayTokenToNum($token); + if ($weekday !== null) { + $payload['repeat_nth_mode'] = 'weekday_of_month'; + $payload['repeat_nth_weekday'] = $weekday; + if (isset($parts['BYSETPOS']) && preg_match('/^-?\d+$/', (string) $parts['BYSETPOS'])) { + $pos = (int) $parts['BYSETPOS']; + } + $payload['repeat_nth_pos'] = $pos === null ? 1 : ($pos < 0 ? -1 : max(1, min(5, $pos))); + } + } + } + + if (isset($parts['COUNT'])) { + $payload['repeat_range_mode'] = 'count'; + $payload['repeat_count'] = max(1, (int) $parts['COUNT']); + } elseif (isset($parts['UNTIL'])) { + $until = $this->parseIcsDateTime($parts['UNTIL'], false); + if ($until !== null) { + $payload['repeat_range_mode'] = 'until'; + $payload['repeat_until'] = substr($until, 0, 10); + } + } + + return $payload; + } + + private function extractVeventProperties(string $ics): ?array + { + $lines = preg_split('/\r\n|\n|\r/', $ics) ?: []; + $unfolded = []; + foreach ($lines as $line) { + if ($line === '') { + continue; + } + if (($line[0] ?? '') === ' ' && $unfolded) { + $unfolded[count($unfolded) - 1] .= substr($line, 1); + continue; + } + $unfolded[] = $line; + } + + $in = false; + $props = []; + foreach ($unfolded as $line) { + $upper = strtoupper($line); + if ($upper === 'BEGIN:VEVENT') { + $in = true; + continue; + } + if ($upper === 'END:VEVENT') { + break; + } + if (!$in) { + continue; + } + [$left, $value] = array_pad(explode(':', $line, 2), 2, ''); + if ($left === '') { + continue; + } + [$name, $params] = array_pad(explode(';', $left, 2), 2, ''); + $name = strtoupper(trim($name)); + if ($name === '') { + continue; + } + $props[$name][] = $this->unescapeText(trim($value)); + if ($name === 'DTSTART') { + $props['_DTSTART_PARAMS'][] = strtoupper(trim($params)); + } + } + + return $in ? $props : null; + } + + private function parseIcsDateTime(string $value, bool $dateOnly): ?string + { + $value = trim($value); + if ($value === '') { + return null; + } + + try { + if ($dateOnly && preg_match('/^\d{8}$/', $value)) { + $dt = DateTimeImmutable::createFromFormat('Ymd H:i:s', $value . ' 00:00:00', new DateTimeZone('Europe/London')); + if ($dt instanceof DateTimeImmutable) { + return $dt->format('Y-m-d\\T00:00:00P'); + } + } + + if (preg_match('/^\d{8}T\d{6}Z$/', $value)) { + $dt = DateTimeImmutable::createFromFormat('Ymd\\THis\\Z', $value, new DateTimeZone('UTC')); + if ($dt instanceof DateTimeImmutable) { + return $dt->setTimezone(new DateTimeZone('Europe/London'))->format('c'); + } + } + + if (preg_match('/^\d{8}T\d{6}$/', $value)) { + $dt = DateTimeImmutable::createFromFormat('Ymd\\THis', $value, new DateTimeZone('Europe/London')); + if ($dt instanceof DateTimeImmutable) { + return $dt->format('c'); + } + } + + $dt = new DateTimeImmutable($value, new DateTimeZone('Europe/London')); + return $dt->format('c'); + } catch (\Throwable) { + return null; + } + } + + private function toDateTime(string $value): ?DateTimeImmutable + { + if ($value === '') { + return null; + } + try { + return new DateTimeImmutable($value, new DateTimeZone('Europe/London')); + } catch (\Throwable) { + return null; + } + } + + private function toUtcIcs(DateTimeImmutable $dt): string + { + return $dt->setTimezone(new DateTimeZone('UTC'))->format('Ymd\\THis\\Z'); + } + + private function escapeText(string $value): string + { + return str_replace( + ["\\", ";", ",", "\r\n", "\n", "\r"], + ["\\\\", "\\;", "\\,", "\\n", "\\n", "\\n"], + $value + ); + } + + private function unescapeText(string $value): string + { + return str_replace( + ["\\n", "\\N", "\\,", "\\;", "\\\\"], + ["\n", "\n", ",", ";", "\\"], + $value + ); + } + + private function weekdayNumToToken(int $weekday): ?string + { + return match ($weekday) { + 0 => 'SU', + 1 => 'MO', + 2 => 'TU', + 3 => 'WE', + 4 => 'TH', + 5 => 'FR', + 6 => 'SA', + default => null, + }; + } + + private function weekdayTokenToNum(string $token): ?int + { + return match (strtoupper(trim($token))) { + 'SU' => 0, + 'MO' => 1, + 'TU' => 2, + 'WE' => 3, + 'TH' => 4, + 'FR' => 5, + 'SA' => 6, + default => null, + }; + } + + private function foldLines(array $lines): array + { + $out = []; + foreach ($lines as $line) { + if ($line === '') { + $out[] = $line; + continue; + } + while (strlen($line) > 73) { + $out[] = substr($line, 0, 73); + $line = ' ' . substr($line, 73); + } + $out[] = $line; + } + return $out; + } +} diff --git a/package/staging/calendar-plugin/src/Domain/RecurrenceExpander.php b/package/staging/calendar-plugin/src/Domain/RecurrenceExpander.php new file mode 100644 index 0000000..71fe24e --- /dev/null +++ b/package/staging/calendar-plugin/src/Domain/RecurrenceExpander.php @@ -0,0 +1,193 @@ +diff($end); + $repeatType = (string) ($event['repeat_type'] ?? 'none'); + $interval = max(1, (int) ($event['repeat_interval'] ?? 1)); + $rangeMode = (string) ($event['repeat_range_mode'] ?? 'none'); + $repeatCount = isset($event['repeat_count']) ? (int) $event['repeat_count'] : null; + $repeatUntil = null; + if ($rangeMode === 'until' && !empty($event['repeat_until'])) { + $repeatUntil = self::parseDateTime((string) $event['repeat_until'] . 'T23:59:59', $tz); + } + + if ($repeatType === 'none') { + if (self::overlaps($start, $end, $windowStart, $windowEnd)) { + return [self::occurrence($event, $start, $end)]; + } + return []; + } + + $occurrences = []; + $current = $start; + $produced = 0; + + for ($i = 0; $i < self::MAX_ITERATIONS; $i++) { + if ($rangeMode === 'count' && $repeatCount !== null && $produced >= $repeatCount) { + break; + } + if ($repeatUntil && $current > $repeatUntil) { + break; + } + $currentEnd = $current->add($duration); + if (self::overlaps($current, $currentEnd, $windowStart, $windowEnd)) { + $key = $current->format('c'); + if (!isset($deletedMap[$key])) { + $occurrences[] = self::occurrence($event, $current, $currentEnd); + } + } + if ($current > $windowEnd->modify('+400 days')) { + break; + } + $produced++; + $current = self::nextStart($current, $repeatType, $interval, $event); + if (!$current) { + break; + } + } + + return $occurrences; + } + + private static function nextStart(DateTimeImmutable $current, string $repeatType, int $interval, array $event): ?DateTimeImmutable + { + return match ($repeatType) { + 'daily' => $current->add(new DateInterval('P' . $interval . 'D')), + 'weekly', 'custom' => $current->add(new DateInterval('P' . $interval . 'W')), + 'monthly' => self::nextMonthlyStart($current, $interval, $event), + 'yearly' => $current->modify('+' . $interval . ' year') ?: null, + default => null, + }; + } + + private static function nextMonthlyStart(DateTimeImmutable $current, int $interval, array $event): ?DateTimeImmutable + { + $mode = (string) ($event['repeat_nth_mode'] ?? ''); + $next = $current->modify('+' . $interval . ' month'); + if (!$next) { + return null; + } + if ($mode === 'day_of_month' && isset($event['repeat_nth_day']) && $event['repeat_nth_day'] !== null) { + $day = max(1, (int) $event['repeat_nth_day']); + $daysInMonth = (int) $next->format('t'); + return $next->setDate((int) $next->format('Y'), (int) $next->format('n'), min($day, $daysInMonth)); + } + if ($mode === 'weekday_of_month' && isset($event['repeat_nth_pos'], $event['repeat_nth_weekday']) && $event['repeat_nth_pos'] !== null && $event['repeat_nth_weekday'] !== null) { + $year = (int) $current->format('Y'); + $month = (int) $current->format('n'); + for ($i = 0; $i < 120; $i++) { + [$year, $month] = self::addMonths($year, $month, max(1, $interval)); + $day = self::nthWeekdayOfMonth($year, $month, (int) $event['repeat_nth_weekday'], (int) $event['repeat_nth_pos']); + if ($day !== null) { + return $current->setDate($year, $month, $day); + } + } + return null; + } + return $next; + } + + private static function nthWeekdayOfMonth(int $year, int $month, int $weekday, int $pos): ?int + { + $weekday = max(0, min(6, $weekday)); + $tz = new DateTimeZone('Europe/London'); + if ($pos === -1) { + $last = new DateTimeImmutable(sprintf('%04d-%02d-01T00:00:00', $year, $month), $tz); + $last = $last->modify('last day of this month'); + for ($day = (int) $last->format('j'); $day >= 1; $day--) { + $dt = $last->setDate($year, $month, $day); + if ((int) $dt->format('w') === $weekday) { + return $day; + } + } + return null; + } + $first = new DateTimeImmutable(sprintf('%04d-%02d-01T00:00:00', $year, $month), $tz); + $daysInMonth = (int) $first->format('t'); + $seen = 0; + for ($day = 1; $day <= $daysInMonth; $day++) { + $dt = $first->setDate($year, $month, $day); + if ((int) $dt->format('w') !== $weekday) { + continue; + } + $seen++; + if ($seen === $pos) { + return $day; + } + } + return null; + } + + private static function occurrence(array $event, DateTimeImmutable $start, DateTimeImmutable $end): array + { + return [ + 'event_id' => (int) ($event['id'] ?? 0), + 'uid' => (string) ($event['uid'] ?? ''), + 'title' => (string) ($event['title'] ?? ''), + 'description' => (string) ($event['description'] ?? ''), + 'location' => (string) ($event['location'] ?? ''), + 'category' => (string) ($event['category'] ?? ''), + 'all_day_event' => (bool) ($event['all_day_event'] ?? false), + 'occurrence_start' => $start->format('c'), + 'occurrence_end' => $end->format('c'), + 'repeat_type' => (string) ($event['repeat_type'] ?? 'none'), + ]; + } + + private static function overlaps(DateTimeImmutable $start, DateTimeImmutable $end, DateTimeImmutable $windowStart, DateTimeImmutable $windowEnd): bool + { + return $start < $windowEnd && $end > $windowStart; + } + + private static function parseDateTime(string $value, DateTimeZone $tz): ?DateTimeImmutable + { + if ($value === '') { + return null; + } + if (!str_contains($value, 'T') && preg_match('/^\d{4}-\d{2}-\d{2}$/', $value)) { + return new DateTimeImmutable($value . 'T00:00:00', $tz); + } + try { + return new DateTimeImmutable($value, $tz); + } catch (\Throwable) { + return null; + } + } + + private static function addMonths(int $year, int $month, int $delta): array + { + $index = ($year * 12) + ($month - 1) + $delta; + $newYear = (int) floor($index / 12); + $newMonth = ($index % 12) + 1; + if ($newMonth <= 0) { + $newMonth += 12; + $newYear -= 1; + } + return [$newYear, $newMonth]; + } +} diff --git a/package/staging/calendar-plugin/src/Domain/SettingsService.php b/package/staging/calendar-plugin/src/Domain/SettingsService.php new file mode 100644 index 0000000..4c24285 --- /dev/null +++ b/package/staging/calendar-plugin/src/Domain/SettingsService.php @@ -0,0 +1,91 @@ + 'Public Calendar', + 'url_slug' => '', + 'verification_page_path' => '/calendar', + 'ics_access_mode' => 'public_read', + 'diagnostics_enabled' => '1', + 'uninstall_cleanup_mode' => 'keep', + ]; + + private const OPTION_PREFIX = 'calendar_plugin_'; + + public function __construct(private readonly OptionsAdapterInterface $options) + { + } + + public function getAll(): array + { + $out = []; + foreach (self::DEFAULTS as $key => $default) { + $out[$key] = $this->get($key, $default); + } + return $out; + } + + public function get(string $key, mixed $default = null): mixed + { + $fallback = $default ?? (self::DEFAULTS[$key] ?? null); + return $this->options->get(self::OPTION_PREFIX . $key, $fallback); + } + + public function update(array $payload): array + { + $allowed = array_keys(self::DEFAULTS); + $updated = $this->getAll(); + + foreach ($allowed as $key) { + if (!array_key_exists($key, $payload)) { + continue; + } + $value = $this->sanitize($key, $payload[$key]); + $this->options->set(self::OPTION_PREFIX . $key, $value); + $updated[$key] = $value; + } + + return $updated; + } + + private function sanitize(string $key, mixed $value): mixed + { + return match ($key) { + 'caldav_calendar_name' => trim((string) $value) ?: self::DEFAULTS[$key], + 'url_slug' => trim((string) $value, " \t\n\r\0\x0B/"), + 'verification_page_path' => $this->normalizePath((string) $value), + 'ics_access_mode' => in_array((string) $value, ['public_read', 'authenticated_read'], true) + ? (string) $value + : self::DEFAULTS['ics_access_mode'], + 'diagnostics_enabled' => $this->isTruthy($value) ? '1' : '0', + 'uninstall_cleanup_mode' => in_array((string) $value, ['keep', 'remove'], true) ? (string) $value : 'keep', + default => $value, + }; + } + + private function isTruthy(mixed $value): bool + { + return in_array(strtolower(trim((string) $value)), self::TRUE_VALUES, true); + } + + private function normalizePath(string $value): string + { + $path = trim($value); + if ($path === '') { + return self::DEFAULTS['verification_page_path']; + } + if (!str_starts_with($path, '/')) { + $path = '/' . $path; + } + return '/' . trim($path, '/'); + } +} diff --git a/package/staging/calendar-plugin/src/Domain/UserService.php b/package/staging/calendar-plugin/src/Domain/UserService.php new file mode 100644 index 0000000..2018195 --- /dev/null +++ b/package/staging/calendar-plugin/src/Domain/UserService.php @@ -0,0 +1,473 @@ +getPrefix(); + $stem = trim($tableStem, '_'); + $this->usersTable = $prefix . $stem . '_users'; + $this->tokensTable = $prefix . $stem . '_user_tokens'; + $this->auditTable = $prefix . $stem . '_audit_log'; + } + + public function register(string $email, string $password): array + { + $email = $this->normalizeEmail($email); + if ($email === '' || !filter_var($email, FILTER_VALIDATE_EMAIL)) { + return $this->error('validation_error', 'email is required', 422); + } + if (strlen($password) < 8) { + return $this->error('validation_error', 'password must be at least 8 characters', 422); + } + if ($this->isRateLimited('register:' . $email, 10, 3600)) { + return $this->error('rate_limited', 'too many requests', 429); + } + if ($this->findUserByEmail($email)) { + return $this->error('conflict_error', 'account already exists', 409); + } + + $now = gmdate('c'); + $inserted = $this->db->insert( + $this->usersTable, + [ + 'email' => $email, + 'password_hash' => password_hash($password, PASSWORD_DEFAULT), + 'email_verified_at' => null, + 'account_status' => 'pending_approval', + 'created_at' => $now, + 'updated_at' => $now, + ] + ); + if ($inserted === false) { + return $this->error('internal_error', 'unable to create account', 500); + } + + $userId = $this->db->insertId(); + $token = $this->issueToken($userId, 'email_verify', 24 * 3600); + $this->audit('user.register', (string) $userId, 'success', ['email' => $email]); + + return [ + 'ok' => true, + 'user' => $this->publicUser((array) $this->getUserById($userId)), + 'verify_token' => $token, + 'message' => 'registration submitted', + ]; + } + + public function verifyEmail(string $token): array + { + if ($token === '') { + return $this->error('validation_error', 'token is required', 422); + } + $tok = $this->consumeToken($token, 'email_verify'); + if (!$tok) { + return $this->error('validation_error', 'invalid or expired token', 422); + } + $user = $this->getUserById((int) $tok['user_id']); + if (!$user) { + return $this->error('not_found', 'user not found', 404); + } + + $now = gmdate('c'); + $this->db->update( + $this->usersTable, + ['email_verified_at' => $now, 'updated_at' => $now], + ['id' => (int) $user['id']] + ); + $updated = $this->getUserById((int) $user['id']); + $this->audit('user.verify_email', (string) $user['id'], 'success', []); + + return [ + 'ok' => true, + 'user' => $updated ? $this->publicUser($updated) : $this->publicUser($user), + 'message' => 'An admin will review your request and notify you if approved.', + ]; + } + + public function login(string $email, string $password): array + { + $email = $this->normalizeEmail($email); + if ($this->isRateLimited('login:' . $email, 20, 3600)) { + return $this->error('rate_limited', 'too many requests', 429); + } + + $user = $this->findUserByEmail($email); + if (!$user || !password_verify($password, (string) ($user['password_hash'] ?? ''))) { + return $this->error('auth_required', 'Login failure', 401); + } + if (empty($user['email_verified_at'])) { + return $this->error('auth_required', 'Login failure', 401); + } + if ((string) ($user['account_status'] ?? '') !== 'active') { + return $this->error('auth_required', 'Login failure', 401); + } + + $this->audit('user.login', (string) $user['id'], 'success', []); + + return [ + 'ok' => true, + 'user' => $this->publicUser($user), + ]; + } + + public function authenticateActiveUserCredentials(string $email, string $password): ?array + { + $email = $this->normalizeEmail($email); + if ($email === '' || $password === '') { + return null; + } + $user = $this->findUserByEmail($email); + if (!$user) { + return null; + } + if (!password_verify($password, (string) ($user['password_hash'] ?? ''))) { + return null; + } + if (empty($user['email_verified_at'])) { + return null; + } + if ((string) ($user['account_status'] ?? '') !== 'active') { + return null; + } + return $this->publicUser($user); + } + + public function issueSessionToken(int $userId, int $ttlSeconds = 2592000): string + { + if ($userId <= 0) { + return ''; + } + return $this->issueToken($userId, 'session', max(300, $ttlSeconds)); + } + + public function authenticateSessionToken(string $token): ?array + { + $row = $this->findValidToken($token, 'session'); + if ($row === null) { + return null; + } + + $user = $this->getUserById((int) ($row['user_id'] ?? 0)); + if (!$user) { + return null; + } + if (empty($user['email_verified_at'])) { + return null; + } + if ((string) ($user['account_status'] ?? '') !== 'active') { + return null; + } + + return $this->publicUser($user); + } + + public function revokeSessionToken(string $token): void + { + $row = $this->findValidToken($token, 'session'); + if ($row === null) { + return; + } + $this->db->update( + $this->tokensTable, + ['used_at' => gmdate('c')], + ['id' => (int) $row['id']] + ); + } + + public function requestPasswordReset(string $email): array + { + $email = $this->normalizeEmail($email); + if ($email === '' || !filter_var($email, FILTER_VALIDATE_EMAIL)) { + return $this->error('validation_error', 'email is required', 422); + } + if ($this->isRateLimited('reset:' . $email, 10, 3600)) { + return $this->error('rate_limited', 'too many requests', 429); + } + + $user = $this->findUserByEmail($email); + if (!$user) { + return ['ok' => true, 'message' => 'if account exists, reset email will be sent']; + } + + $token = $this->issueToken((int) $user['id'], 'password_reset', 30 * 60); + $this->audit('user.password_reset.request', (string) $user['id'], 'success', []); + + return [ + 'ok' => true, + 'reset_token' => $token, + 'message' => 'password reset requested', + ]; + } + + public function resetPassword(string $token, string $newPassword): array + { + if (strlen($newPassword) < 8) { + return $this->error('validation_error', 'password must be at least 8 characters', 422); + } + + $tok = $this->consumeToken($token, 'password_reset'); + if (!$tok) { + return $this->error('validation_error', 'invalid or expired token', 422); + } + + $user = $this->getUserById((int) $tok['user_id']); + if (!$user) { + return $this->error('not_found', 'user not found', 404); + } + + $this->db->update( + $this->usersTable, + ['password_hash' => password_hash($newPassword, PASSWORD_DEFAULT), 'updated_at' => gmdate('c')], + ['id' => (int) $user['id']] + ); + // Invalidate persistent web sessions after password reset. + $this->db->delete($this->tokensTable, ['user_id' => (int) $user['id'], 'token_type' => 'session']); + $this->audit('user.password_reset.complete', (string) $user['id'], 'success', []); + + return ['ok' => true, 'message' => 'password updated']; + } + + public function listUsers(): array + { + $rows = $this->db->getResults("SELECT * FROM {$this->usersTable} ORDER BY id ASC"); + return array_map(fn(object $r): array => $this->publicUser((array) $r), $rows); + } + + public function approveUser(int $id): ?array + { + $user = $this->getUserById($id); + if (!$user) { + return null; + } + if (empty($user['email_verified_at'])) { + return null; + } + $this->db->update( + $this->usersTable, + ['account_status' => 'active', 'updated_at' => gmdate('c')], + ['id' => $id] + ); + $updated = $this->getUserById($id); + $this->audit('user.approve', (string) $id, 'success', []); + return $updated ? $this->publicUser($updated) : null; + } + + public function removeUser(int $id): bool + { + $deleted = $this->db->delete($this->usersTable, ['id' => $id]); + $this->db->delete($this->tokensTable, ['user_id' => $id]); + if ($deleted !== false) { + $this->audit('user.remove', (string) $id, 'success', []); + return true; + } + return false; + } + + private function findUserByEmail(string $email): ?array + { + foreach ($this->listRawUsers() as $user) { + if (strtolower((string) ($user['email'] ?? '')) === strtolower($email)) { + return $user; + } + } + return null; + } + + private function getUserById(int $id): ?array + { + $sql = $this->db->prepare("SELECT * FROM {$this->usersTable} WHERE id = %d", $id); + $row = $this->db->getRow($sql); + return $row ? (array) $row : null; + } + + private function listRawUsers(): array + { + $rows = $this->db->getResults("SELECT * FROM {$this->usersTable} ORDER BY id ASC"); + return array_map(static fn(object $r): array => (array) $r, $rows); + } + + private function issueToken(int $userId, string $type, int $ttlSeconds): string + { + $token = bin2hex(random_bytes(16)); + $hash = hash('sha256', $token); + $expiresAt = (new DateTimeImmutable('now', new DateTimeZone('UTC')))->modify('+' . $ttlSeconds . ' seconds')->format('c'); + + $this->db->insert( + $this->tokensTable, + [ + 'user_id' => $userId, + 'token_type' => $type, + 'token_hash' => $hash, + 'expires_at' => $expiresAt, + 'used_at' => null, + 'created_at' => gmdate('c'), + ] + ); + + return $token; + } + + private function consumeToken(string $token, string $type): ?array + { + $row = $this->findValidToken($token, $type); + if ($row === null) { + return null; + } + $this->db->update( + $this->tokensTable, + ['used_at' => gmdate('c')], + ['id' => (int) $row['id']] + ); + return $row; + } + + private function findValidToken(string $token, string $type): ?array + { + $raw = trim($token); + if ($raw === '') { + return null; + } + $hash = hash('sha256', $raw); + $now = new DateTimeImmutable('now', new DateTimeZone('UTC')); + + $rows = $this->db->getResults("SELECT * FROM {$this->tokensTable} ORDER BY id ASC"); + foreach ($rows as $rowObj) { + $row = (array) $rowObj; + if ((string) ($row['token_type'] ?? '') !== $type) { + continue; + } + if ((string) ($row['token_hash'] ?? '') !== $hash) { + continue; + } + if (!empty($row['used_at'])) { + return null; + } + try { + $expires = new DateTimeImmutable((string) $row['expires_at'], new DateTimeZone('UTC')); + } catch (\Throwable) { + return null; + } + if ($expires < $now) { + return null; + } + return $row; + } + return null; + } + + private function publicUser(array $user): array + { + return [ + 'id' => (int) ($user['id'] ?? 0), + 'email' => (string) ($user['email'] ?? ''), + 'email_verified_at' => $user['email_verified_at'] ?? null, + 'account_status' => (string) ($user['account_status'] ?? 'pending_approval'), + 'created_at' => (string) ($user['created_at'] ?? ''), + 'updated_at' => (string) ($user['updated_at'] ?? ''), + ]; + } + + private function error(string $code, string $message, int $status): array + { + return ['error' => ['code' => $code, 'message' => $message, 'status' => $status]]; + } + + private function normalizeEmail(string $email): string + { + return strtolower(trim($email)); + } + + private function audit(string $action, string $target, string $result, array $context): void + { + if (!$this->isDiagnosticsEnabled()) { + return; + } + $this->db->insert( + $this->auditTable, + [ + 'actor' => 'plugin', + 'action' => $action, + 'target' => $target, + 'result' => $result, + 'created_at' => gmdate('c'), + 'context_json' => json_encode($context), + ] + ); + } + + private function isDiagnosticsEnabled(): bool + { + $optionsTable = $this->db->getPrefix() . 'options'; + $sql = $this->db->prepare( + "SELECT option_value FROM {$optionsTable} WHERE option_name = %s LIMIT 1", + 'calendar_plugin_diagnostics_enabled' + ); + $row = $this->db->getRow($sql); + if (!$row || !property_exists($row, 'option_value')) { + return true; + } + return in_array(strtolower(trim((string) $row->option_value)), ['1', 'true', 'yes', 'on'], true); + } + + private function isRateLimited(string $bucket, int $limit, int $windowSeconds): bool + { + $now = new DateTimeImmutable('now', new DateTimeZone('UTC')); + $type = 'rate:' . substr(hash('sha256', $bucket), 0, 32); + $sql = $this->db->prepare("SELECT * FROM {$this->tokensTable} WHERE token_type = %s ORDER BY id ASC", $type); + $rows = $this->db->getResults($sql); + + $activeCount = 0; + foreach ($rows as $rowObj) { + $row = (array) $rowObj; + $id = (int) ($row['id'] ?? 0); + $usedAt = (string) ($row['used_at'] ?? ''); + $expiresAtRaw = (string) ($row['expires_at'] ?? ''); + $expired = true; + try { + $expiresAt = new DateTimeImmutable($expiresAtRaw, new DateTimeZone('UTC')); + $expired = $expiresAt < $now; + } catch (\Throwable) { + $expired = true; + } + + if ($id > 0 && ($usedAt !== '' || $expired)) { + $this->db->delete($this->tokensTable, ['id' => $id]); + continue; + } + + if (!$expired && $usedAt === '') { + $activeCount++; + } + } + + if ($activeCount >= $limit) { + return true; + } + + $this->db->insert( + $this->tokensTable, + [ + 'user_id' => 0, + 'token_type' => $type, + 'token_hash' => hash('sha256', bin2hex(random_bytes(16))), + 'expires_at' => $now->modify('+' . max(1, $windowSeconds) . ' seconds')->format('c'), + 'used_at' => null, + 'created_at' => gmdate('c'), + ] + ); + return false; + } +} diff --git a/package/staging/calendar-plugin/src/Infrastructure/ServiceContainer.php b/package/staging/calendar-plugin/src/Infrastructure/ServiceContainer.php new file mode 100644 index 0000000..874b495 --- /dev/null +++ b/package/staging/calendar-plugin/src/Infrastructure/ServiceContainer.php @@ -0,0 +1,25 @@ + */ + private array $services = []; + + public function set(string $id, object $service): void + { + $this->services[$id] = $service; + } + + public function get(string $id): object + { + if (!isset($this->services[$id])) { + throw new \RuntimeException(sprintf('Service not found: %s', $id)); + } + + return $this->services[$id]; + } +} diff --git a/package/staging/calendar-plugin/src/Infrastructure/WordPress/MigrationManager.php b/package/staging/calendar-plugin/src/Infrastructure/WordPress/MigrationManager.php new file mode 100644 index 0000000..cb2281c --- /dev/null +++ b/package/staging/calendar-plugin/src/Infrastructure/WordPress/MigrationManager.php @@ -0,0 +1,222 @@ +get_charset_collate(); + $prefix = $this->db->getPrefix(); + $stem = trim($this->tableStem, '_'); + + $events = $prefix . $stem . '_events'; + $exceptions = $prefix . $stem . '_recurrence_exceptions'; + $users = $prefix . $stem . '_users'; + $tokens = $prefix . $stem . '_user_tokens'; + $audit = $prefix . $stem . '_audit_log'; + + $sqlEvents = "CREATE TABLE {$events} ( + id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, + uid VARCHAR(191) NOT NULL, + title TEXT NOT NULL, + description LONGTEXT NOT NULL, + location TEXT NOT NULL, + category TEXT NOT NULL, + all_day_event TINYINT(1) NOT NULL DEFAULT 0, + start_datetime VARCHAR(64) NOT NULL, + end_datetime VARCHAR(64) NOT NULL, + repeat_type VARCHAR(24) NOT NULL DEFAULT 'none', + repeat_interval INT NOT NULL DEFAULT 1, + repeat_nth_mode VARCHAR(32) NOT NULL DEFAULT '', + repeat_nth_day INT NULL, + repeat_nth_pos INT NULL, + repeat_nth_weekday INT NULL, + repeat_range_mode VARCHAR(24) NOT NULL DEFAULT 'none', + repeat_count INT NULL, + repeat_until VARCHAR(16) NULL, + timezone VARCHAR(64) NOT NULL DEFAULT 'Europe/London', + caldav_resource VARCHAR(191) NULL, + etag VARCHAR(64) NULL, + sync_version INT NOT NULL DEFAULT 1, + last_modified_by_user_id BIGINT UNSIGNED NULL, + created_at VARCHAR(32) NOT NULL, + updated_at VARCHAR(32) NOT NULL, + PRIMARY KEY (id), + UNIQUE KEY uid (uid(191)), + UNIQUE KEY caldav_resource (caldav_resource), + KEY start_datetime (start_datetime(32)), + KEY end_datetime (end_datetime(32)) + ) {$charsetCollate};"; + + $sqlExceptions = "CREATE TABLE {$exceptions} ( + id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, + event_id BIGINT UNSIGNED NOT NULL, + occurrence_key VARCHAR(64) NOT NULL, + exception_type VARCHAR(32) NOT NULL, + created_at VARCHAR(32) NOT NULL, + updated_at VARCHAR(32) NOT NULL, + PRIMARY KEY (id), + UNIQUE KEY event_occurrence (event_id, occurrence_key), + KEY event_id (event_id) + ) {$charsetCollate};"; + + $sqlUsers = "CREATE TABLE {$users} ( + id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, + email VARCHAR(191) NOT NULL, + password_hash VARCHAR(255) NOT NULL, + email_verified_at VARCHAR(32) NULL, + account_status VARCHAR(32) NOT NULL DEFAULT 'pending_approval', + created_at VARCHAR(32) NOT NULL, + updated_at VARCHAR(32) NOT NULL, + PRIMARY KEY (id), + UNIQUE KEY email (email) + ) {$charsetCollate};"; + + $sqlTokens = "CREATE TABLE {$tokens} ( + id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, + user_id BIGINT UNSIGNED NOT NULL, + token_type VARCHAR(32) NOT NULL, + token_hash VARCHAR(255) NOT NULL, + expires_at VARCHAR(32) NOT NULL, + used_at VARCHAR(32) NULL, + created_at VARCHAR(32) NOT NULL, + PRIMARY KEY (id), + KEY user_id (user_id), + KEY token_type (token_type) + ) {$charsetCollate};"; + + $sqlAudit = "CREATE TABLE {$audit} ( + id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, + actor VARCHAR(191) NOT NULL, + action VARCHAR(191) NOT NULL, + target VARCHAR(191) NOT NULL, + result VARCHAR(32) NOT NULL, + created_at VARCHAR(32) NOT NULL, + context_json LONGTEXT NULL, + PRIMARY KEY (id), + KEY action (action), + KEY created_at (created_at) + ) {$charsetCollate};"; + + dbDelta($sqlEvents); + dbDelta($sqlExceptions); + dbDelta($sqlUsers); + dbDelta($sqlTokens); + dbDelta($sqlAudit); + + // Ensure every event has a stable CalDAV object resource name. + $this->db->query( + "UPDATE {$events} + SET caldav_resource = CONCAT(uid, '.ics') + WHERE (caldav_resource IS NULL OR caldav_resource = '') + AND uid IS NOT NULL + AND uid <> ''" + ); + $this->normalizeEventDateTimesToLondon($events); + + update_option(self::STEM_OPTION, $stem); + update_option('calendar_plugin_schema_version', self::SCHEMA_VERSION); + } + + public function assertActivationSafe(): void + { + $stem = trim($this->tableStem, '_'); + $ownedStem = trim((string) get_option(self::STEM_OPTION, ''), '_'); + if ($ownedStem !== '' && $ownedStem !== $stem) { + throw new \RuntimeException( + sprintf( + 'Calendar Plugin is already initialized with table stem "%s". Requested stem "%s" is different.', + $ownedStem, + $stem + ) + ); + } + if ($ownedStem !== $stem && $this->anyTargetTablesExist($stem)) { + $legacySchemaVersion = trim((string) get_option('calendar_plugin_schema_version', '')); + if ($legacySchemaVersion === '') { + throw new \RuntimeException( + sprintf( + 'Calendar Plugin activation blocked: target tables for stem "%s" already exist. Choose another table stem via CALENDAR_PLUGIN_TABLE_STEM.', + $stem + ) + ); + } + } + } + + private function anyTargetTablesExist(string $stem): bool + { + $prefix = $this->db->getPrefix(); + $tables = [ + $prefix . $stem . '_events', + $prefix . $stem . '_recurrence_exceptions', + $prefix . $stem . '_users', + $prefix . $stem . '_user_tokens', + $prefix . $stem . '_audit_log', + ]; + foreach ($tables as $table) { + $sql = $this->db->prepare('SHOW TABLES LIKE %s', $table); + if (count($this->db->getResults($sql)) > 0) { + return true; + } + } + return false; + } + + private function normalizeEventDateTimesToLondon(string $eventsTable): void + { + $rows = $this->db->getResults( + "SELECT id, start_datetime, end_datetime FROM {$eventsTable}" + ); + $tz = new DateTimeZone('Europe/London'); + foreach ($rows as $row) { + $id = (int) ($row->id ?? 0); + if ($id <= 0) { + continue; + } + $start = $this->normalizeDateTimeString((string) ($row->start_datetime ?? ''), $tz); + $end = $this->normalizeDateTimeString((string) ($row->end_datetime ?? ''), $tz); + if ($start === null || $end === null) { + continue; + } + $sql = $this->db->prepare( + "UPDATE {$eventsTable} SET start_datetime = %s, end_datetime = %s WHERE id = %d", + $start, + $end, + $id + ); + $this->db->query($sql); + } + } + + private function normalizeDateTimeString(string $value, DateTimeZone $tz): ?string + { + $value = trim($value); + if ($value === '') { + return null; + } + try { + $dt = new DateTimeImmutable($value, $tz); + return $dt->setTimezone($tz)->format('c'); + } catch (\Throwable) { + return null; + } + } +} diff --git a/package/staging/calendar-plugin/src/Infrastructure/WordPress/WordPressAuthAdapter.php b/package/staging/calendar-plugin/src/Infrastructure/WordPress/WordPressAuthAdapter.php new file mode 100644 index 0000000..b1f988f --- /dev/null +++ b/package/staging/calendar-plugin/src/Infrastructure/WordPress/WordPressAuthAdapter.php @@ -0,0 +1,31 @@ +user_email ?? '') : ''; + } +} diff --git a/package/staging/calendar-plugin/src/Infrastructure/WordPress/WordPressDatabaseAdapter.php b/package/staging/calendar-plugin/src/Infrastructure/WordPress/WordPressDatabaseAdapter.php new file mode 100644 index 0000000..4fdc04e --- /dev/null +++ b/package/staging/calendar-plugin/src/Infrastructure/WordPress/WordPressDatabaseAdapter.php @@ -0,0 +1,60 @@ +wpdb->prefix; + } + + public function prepare(string $query, mixed ...$args): string + { + return (string) $this->wpdb->prepare($query, ...$args); + } + + public function query(string $query): int|false + { + return $this->wpdb->query($query); + } + + public function getResults(string $query): array + { + return $this->wpdb->get_results($query) ?: []; + } + + public function getRow(string $query): ?object + { + $row = $this->wpdb->get_row($query); + return is_object($row) ? $row : null; + } + + public function insert(string $table, array $data, array $formats = []): int|false + { + return $this->wpdb->insert($table, $data, $formats); + } + + public function update(string $table, array $data, array $where, array $formats = [], array $whereFormats = []): int|false + { + return $this->wpdb->update($table, $data, $where, $formats, $whereFormats); + } + + public function delete(string $table, array $where, array $whereFormats = []): int|false + { + return $this->wpdb->delete($table, $where, $whereFormats); + } + + public function insertId(): int + { + return (int) $this->wpdb->insert_id; + } +} diff --git a/package/staging/calendar-plugin/src/Infrastructure/WordPress/WordPressHttpAdapter.php b/package/staging/calendar-plugin/src/Infrastructure/WordPress/WordPressHttpAdapter.php new file mode 100644 index 0000000..1157fd7 --- /dev/null +++ b/package/staging/calendar-plugin/src/Infrastructure/WordPress/WordPressHttpAdapter.php @@ -0,0 +1,25 @@ +tableStem = self::resolveTableStem(); + $this->settingsService = new SettingsService($this->options); + $this->eventService = new EventService($this->db, $this->tableStem); + $this->icsService = new IcsService(); + $this->calDavService = new CalDavService($this->eventService, $this->icsService); + $this->userService = new UserService($this->db, $this->tableStem); + } + + public static function boot(string $pluginFile): void + { + global $wpdb; + + $db = new WordPressDatabaseAdapter($wpdb); + $options = new WordPressOptionsAdapter(); + $auth = new WordPressAuthAdapter(); + $http = new WordPressHttpAdapter(); + + register_activation_hook($pluginFile, [self::class, 'activate']); + register_deactivation_hook($pluginFile, [self::class, 'deactivate']); + + $plugin = new self($db, $options, $auth, $http); + $plugin->register(); + } + + public static function activate(): void + { + try { + global $wpdb; + $db = new WordPressDatabaseAdapter($wpdb); + $tableStem = self::resolveTableStem(); + $migration = new MigrationManager($db, $tableStem); + $migration->assertActivationSafe(); + $migration->migrate(); + } catch (\Throwable $e) { + if (function_exists('wp_die')) { + wp_die( + esc_html($e->getMessage()), + 'Calendar Plugin Activation Blocked', + ['response' => 500, 'back_link' => true] + ); + } + throw $e; + } + } + + public static function deactivate(): void + { + // Intentionally no destructive behavior on deactivate. + } + + public function register(): void + { + $this->http->addAction('init', [$this, 'ensureSchemaCurrent']); + $this->http->addAction('init', [$this, 'onInit']); + $this->http->addAction('rest_api_init', [$this, 'registerRoutes']); + $this->http->addAction('admin_menu', [$this, 'registerAdminMenu']); + $this->http->addAction('admin_post_calendar_plugin_download_diagnostics', [$this, 'handleDiagnosticsDownload']); + $this->http->addAction('template_redirect', [$this, 'maybeServeSpecialEndpoints']); + } + + public function onInit(): void + { + $this->http->addShortcode('calendar', [$this, 'renderCalendarShortcode']); + $this->http->addShortcode('calendar_sidebar_upcoming', [$this, 'renderSidebarShortcode']); + } + + public function ensureSchemaCurrent(): void + { + $version = trim((string) get_option('calendar_plugin_schema_version', '')); + if ($version === '3') { + return; + } + try { + self::activate(); + } catch (\Throwable) { + // Avoid hard-failing page loads; diagnostics/setup will still surface issues. + } + } + + public function registerRoutes(): void + { + $this->registerHealthRoute(); + $this->registerSettingsRoutes(); + $this->registerEventRoutes(); + $this->registerPublicRoutes(); + $this->registerUserRoutes(); + $this->registerIcsRoutes(); + $this->registerCalDavRoutes(); + } + + public function renderCalendarShortcode(): string + { + $caldavRoot = $this->caldavRootPath(); + $icsUrl = $this->icsPath(); + $ns = self::API_NAMESPACE; + $caldavRootEsc = esc_html($caldavRoot); + $icsUrlEsc = esc_html($icsUrl); + $nsEsc = esc_html($ns); + $todayEsc = esc_html(gmdate('Y-m-d')); + + return strtr(<<<'HTML' +

+
+
+ Not logged in + + + CalDAV + ICS +
+
+ +
+ + + + + + + + +
+ + +
+
+ +

+

Events

+
+
    + + + + + + + + +
    +HTML + , [ + '__CALDAV_ROOT__' => $caldavRootEsc, + '__ICS_URL__' => $icsUrlEsc, + '__TODAY__' => $todayEsc, + '__NS__' => $nsEsc, + ]); + } + + public function renderSidebarShortcode(): string + { + $items = $this->eventService->listSidebarUpcoming(14); + if (!$items) { + return '

    No upcoming events.

    '; + } + + $rows = []; + foreach ($items as $item) { + $start = (string) ($item['occurrence_start'] ?? ''); + $end = (string) ($item['occurrence_end'] ?? ''); + $startTs = strtotime($start); + $endTs = strtotime($end); + $dateLabel = $startTs !== false ? date('j F Y', $startTs) : substr($start, 0, 10); + $timeLabel = ''; + if ($startTs !== false && $endTs !== false) { + $startTime = date('H:i', $startTs); + $endTime = date('H:i', $endTs); + if ($startTime !== '00:00' || $endTime !== '00:00') { + $timeLabel = $this->formatSidebarTimeRange($startTs, $endTs); + } + } + $title = trim((string) ($item['title'] ?? '')); + $description = trim((string) ($item['description'] ?? '')); + $headline = $dateLabel; + if ($timeLabel !== '') { + $headline .= ', ' . $timeLabel; + } + $headline .= ', ' . ($title !== '' ? $title : $description); + $rows[] = sprintf( + '

    %s%s

    ', + esc_html($headline), + $description !== '' ? '
    ' . esc_html($description) : '' + ); + } + + return '
    ' . implode('', $rows) . '
    '; + } + + private function formatSidebarTimeRange(int $startTs, int $endTs): string + { + $startMeridiem = strtolower(date('a', $startTs)); + $endMeridiem = strtolower(date('a', $endTs)); + $startLabel = $this->formatSidebarTimeValue($startTs); + $endLabel = $this->formatSidebarTimeValue($endTs); + if ($startMeridiem === $endMeridiem) { + $startLabel = preg_replace('/(am|pm)$/', '', $startLabel) ?: $startLabel; + return $startLabel . '–' . $endLabel; + } + return $startLabel . '–' . $endLabel; + } + + private function formatSidebarTimeValue(int $ts): string + { + $hour = (int) date('G', $ts); + $minute = (int) date('i', $ts); + $meridiem = strtolower(date('a', $ts)); + $hour12 = $hour % 12; + if ($hour12 === 0) { + $hour12 = 12; + } + if ($minute === 0) { + return $hour12 . $meridiem; + } + return $hour12 . '.' . str_pad((string) $minute, 2, '0', STR_PAD_LEFT) . $meridiem; + } + + public function registerAdminMenu(): void + { + if (!function_exists('add_menu_page')) { + return; + } + + add_menu_page( + 'Calendar Plugin', + 'Calendar Plugin', + 'manage_options', + 'calendar-plugin', + [$this, 'renderAdminSetupPage'] + ); + + add_submenu_page( + 'calendar-plugin', + 'Users', + 'Users', + 'manage_options', + 'calendar-plugin-users', + [$this, 'renderAdminUsersPage'] + ); + + add_submenu_page( + 'calendar-plugin', + 'Setup', + 'Setup', + 'manage_options', + 'calendar-plugin-setup', + [$this, 'renderAdminSetupPage'] + ); + + add_submenu_page( + 'calendar-plugin', + 'Diagnostics', + 'Diagnostics', + 'manage_options', + 'calendar-plugin-diagnostics', + [$this, 'renderAdminDiagnosticsPage'] + ); + + if (function_exists('remove_submenu_page')) { + remove_submenu_page('calendar-plugin', 'calendar-plugin'); + } + } + + public function renderAdminSetupPage(): void + { + $settings = $this->settingsService->getAll(); + $currentStem = trim((string) get_option('calendar_plugin_table_stem', $this->tableStem), '_'); + if ($currentStem === '') { + $currentStem = $this->tableStem; + } + $dbPrefix = (string) $this->db->getPrefix(); + $currentTablePrefix = $dbPrefix . $currentStem; + $message = ''; + $error = ''; + + if (strtoupper((string) ($_SERVER['REQUEST_METHOD'] ?? 'GET')) === 'POST') { + $nonce = (string) ($_POST['_calendar_setup_nonce'] ?? ''); + $nonceSupported = function_exists('wp_nonce_field') && function_exists('wp_verify_nonce'); + if ($nonceSupported && ($nonce === '' || !$this->auth->verifyNonce($nonce, 'calendar_setup_update'))) { + $error = 'Invalid setup form submission.'; + } else { + $action = (string) ($_POST['setup_action'] ?? 'save_settings'); + if ($action === 'delete_all_events') { + $deleted = $this->eventService->deleteAllEventsData(); + $message = 'Deleted ' . $deleted . ' events and all recurrence exceptions.'; + } elseif ($action === 'seed_events') { + try { + $seeded = $this->eventService->seedDefaultEvents(); + $message = 'Seeded ' . $seeded . ' default events.'; + } catch (\Throwable $e) { + $error = 'Seed failed: ' . $e->getMessage(); + } + } else { + $payload = [ + 'caldav_calendar_name' => (string) ($_POST['caldav_calendar_name'] ?? ''), + 'url_slug' => (string) ($_POST['url_slug'] ?? ''), + 'verification_page_path' => (string) ($_POST['verification_page_path'] ?? ''), + 'ics_access_mode' => (string) ($_POST['ics_access_mode'] ?? ''), + 'diagnostics_enabled' => isset($_POST['diagnostics_enabled']) ? '1' : '0', + 'uninstall_cleanup_mode' => (string) ($_POST['uninstall_cleanup_mode'] ?? 'keep'), + ]; + $settings = $this->settingsService->update($payload); + $requestedPrefixRaw = strtolower(trim((string) ($_POST['table_prefix'] ?? $currentTablePrefix))); + $requestedPrefix = preg_replace('/[^a-z0-9_]/', '', $requestedPrefixRaw) ?: $currentTablePrefix; + $requestedPrefix = trim($requestedPrefix, '_'); + if ($requestedPrefix === '') { + $requestedPrefix = $currentTablePrefix; + } + if ($requestedPrefix !== $currentTablePrefix) { + $prefixResult = $this->updatePluginTablePrefix($currentStem, $requestedPrefix); + if ($prefixResult['ok']) { + $currentStem = (string) $prefixResult['stem']; + $currentTablePrefix = $dbPrefix . $currentStem; + $message = 'Settings saved. Table prefix updated to ' . $currentTablePrefix . '.'; + } else { + $error = (string) $prefixResult['message']; + } + } else { + $message = 'Settings saved.'; + } + } + } + } + + $nonceField = ''; + if (function_exists('wp_nonce_field')) { + ob_start(); + wp_nonce_field('calendar_setup_update', '_calendar_setup_nonce'); + $nonceField = (string) ob_get_clean(); + } else { + $nonceField = ''; + } + + $saveNote = ''; + if ($error !== '') { + $saveNote .= '

    ' . esc_html($error) . '

    '; + } + if ($message !== '') { + $saveNote .= '

    ' . esc_html($message) . '

    '; + } + + $icsMode = (string) ($settings['ics_access_mode'] ?? 'public_read'); + $icsModePublicSel = $icsMode === 'public_read' ? 'selected' : ''; + $icsModeAuthSel = $icsMode === 'authenticated_read' ? 'selected' : ''; + $diagnosticsChecked = ((string) ($settings['diagnostics_enabled'] ?? '1')) === '1' ? 'checked' : ''; + $cleanupMode = (string) ($settings['uninstall_cleanup_mode'] ?? 'keep'); + $cleanupKeepSel = $cleanupMode === 'keep' ? 'selected' : ''; + $cleanupRemoveSel = $cleanupMode === 'remove' ? 'selected' : ''; + + $form = '
    ' + . $nonceField + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '

    ' + . ' ' + . '

    ' + . '
    '; + + echo $this->adminPageShell( + 'Setup', + $saveNote + . '

    Configure presentation and endpoint behavior below or via API: /wp-json/' . self::API_NAMESPACE . '/settings.

    ' + . $form + . '
      ' + . '
    • Calendar shortcode: [calendar]
    • ' + . '
    • Sidebar shortcode: [calendar_sidebar_upcoming] (next 14 days list)
    • ' + . '
    • ICS endpoint: ' . esc_html($this->icsPath()) . '
    • ' + . '
    • CalDAV root: ' . esc_html($this->caldavRootPath()) . '
    • ' + . '
    ' + ); + } + + public function renderAdminUsersPage(): void + { + $message = ''; + $error = ''; + + if (strtoupper((string) ($_SERVER['REQUEST_METHOD'] ?? 'GET')) === 'POST') { + $nonce = (string) ($_POST['_calendar_users_nonce'] ?? ''); + $nonceSupported = function_exists('wp_nonce_field') && function_exists('wp_verify_nonce'); + if ($nonceSupported && ($nonce === '' || !$this->auth->verifyNonce($nonce, 'calendar_users_action'))) { + $error = 'Invalid users action submission.'; + } else { + $action = (string) ($_POST['users_action'] ?? ''); + $userId = (int) ($_POST['user_id'] ?? 0); + if ($userId <= 0) { + $error = 'Invalid user selection.'; + } elseif ($action === 'approve') { + $user = $this->userService->approveUser($userId); + if ($user === null) { + $error = 'User not found.'; + } else { + $message = 'User approved.'; + } + } elseif ($action === 'remove') { + $ok = $this->userService->removeUser($userId); + if (!$ok) { + $error = 'User not found.'; + } else { + $message = 'User removed.'; + } + } else { + $error = 'Unsupported users action.'; + } + } + } + + $nonceField = ''; + if (function_exists('wp_nonce_field')) { + ob_start(); + wp_nonce_field('calendar_users_action', '_calendar_users_nonce'); + $nonceField = (string) ob_get_clean(); + } else { + $nonceField = ''; + } + + $users = $this->userService->listUsers(); + $rows = ''; + foreach ($users as $u) { + $userId = (int) ($u['id'] ?? 0); + $status = (string) ($u['account_status'] ?? ''); + $isApproved = $status === 'active'; + $isVerified = !empty((string) ($u['email_verified_at'] ?? '')); + $approveButton = $isApproved + ? '' + : ($isVerified + ? '' + : ''); + $removeButton = ''; + $actions = '
    ' + . $nonceField + . '' + . $approveButton + . $removeButton + . '
    '; + $rows .= '' + . '' . $userId . '' + . '' . esc_html((string) $u['email']) . '' + . '' . esc_html($status) . '' + . '' . esc_html((string) ($u['email_verified_at'] ?? '')) . '' + . '' . $actions . '' + . ''; + } + $feedback = ''; + if ($error !== '') { + $feedback .= '

    ' . esc_html($error) . '

    '; + } + if ($message !== '') { + $feedback .= '

    ' . esc_html($message) . '

    '; + } + + $body = $feedback + . '

    Approve pending users or remove defunct users.

    ' + . '' + . ($rows !== '' ? $rows : '') + . '
    IDEmailStatusEmail VerifiedActions
    No users
    '; + echo $this->adminPageShell('Users', $body); + } + + public function renderAdminDiagnosticsPage(): void + { + $enabled = $this->isDiagnosticsEnabled(); + $diag = $this->collectDiagnosticsSnapshot($enabled); + $downloadUrl = admin_url('admin-post.php?action=calendar_plugin_download_diagnostics&_wpnonce=' . wp_create_nonce('calendar_diagnostics_download')); + $downloadButton = $enabled + ? '

    Download Diagnostics

    ' + : '

    Enable diagnostics in Setup to collect and download diagnostics.

    '; + echo $this->adminPageShell( + 'Diagnostics', + $downloadButton + . '

    Runtime diagnostics snapshot:

    ' . esc_html(json_encode($diag, JSON_PRETTY_PRINT)) . '
    ' + ); + } + + public function handleDiagnosticsDownload(): void + { + if (!$this->auth->currentUserCan('manage_options')) { + wp_die('Not authorized.', 'Forbidden', ['response' => 403]); + } + $nonce = (string) ($_GET['_wpnonce'] ?? ''); + if (!$this->auth->verifyNonce($nonce, 'calendar_diagnostics_download')) { + wp_die('Invalid diagnostics download request.'); + } + if (!$this->isDiagnosticsEnabled()) { + wp_die('Diagnostics are disabled in Setup.'); + } + + $diag = $this->collectDiagnosticsSnapshot(true); + nocache_headers(); + header('Content-Type: application/json; charset=utf-8'); + header('Content-Disposition: attachment; filename="calendar-diagnostics-' . gmdate('Ymd-His') . '.json"'); + header('X-Content-Type-Options: nosniff'); + echo json_encode($diag, JSON_PRETTY_PRINT); + exit; + } + + private function isDiagnosticsEnabled(): bool + { + return ((string) ($this->settingsService->get('diagnostics_enabled', '1') ?? '1')) === '1'; + } + + public function maybeServeSpecialEndpoints(): void + { + $uri = (string) ($_SERVER['REQUEST_URI'] ?? ''); + $path = parse_url($uri, PHP_URL_PATH); + if (!is_string($path)) { + return; + } + + if ($path === $this->icsPath()) { + $this->serveIcsResponse(); + exit; + } + + if ($path === '/.well-known/caldav') { + wp_redirect($this->caldavRootPath(), 301); + exit; + } + + if ($path === rtrim($this->caldavRootPath(), '/') . '/') { + $method = strtoupper((string) ($_SERVER['REQUEST_METHOD'] ?? 'GET')); + if ($method === 'OPTIONS') { + header('Allow: OPTIONS, PROPFIND, REPORT'); + header('DAV: 1, calendar-access'); + http_response_code(200); + exit; + } + } + + if (str_starts_with($path, rtrim($this->caldavRootPath(), '/'))) { + $this->serveCalDavPath($path, strtoupper((string) ($_SERVER['REQUEST_METHOD'] ?? 'GET'))); + exit; + } + } + + private function registerHealthRoute(): void + { + $this->http->registerRestRoute( + self::API_NAMESPACE, + '/health', + [ + 'methods' => 'GET', + 'permission_callback' => '__return_true', + 'callback' => function (): array { + return [ + 'status' => 'ok', + 'plugin' => 'calendar-plugin', + 'version' => '0.1.15', + 'db_prefix' => $this->db->getPrefix(), + ]; + }, + ] + ); + } + + private function registerSettingsRoutes(): void + { + $this->http->registerRestRoute( + self::API_NAMESPACE, + '/settings', + [ + 'methods' => 'GET', + 'permission_callback' => fn(): bool => $this->auth->currentUserCan('manage_options'), + 'callback' => fn(): array => ['data' => $this->settingsService->getAll()], + ] + ); + + $this->http->registerRestRoute( + self::API_NAMESPACE, + '/settings', + [ + 'methods' => 'PATCH', + 'permission_callback' => fn(): bool => $this->auth->currentUserCan('manage_options'), + 'callback' => function ($request): array|\WP_Error { + $payload = $this->jsonPayload($request); + if ($payload === null) { + return $this->error('validation_error', 'invalid JSON payload', 422); + } + return ['data' => $this->settingsService->update($payload)]; + }, + ] + ); + } + + private function registerEventRoutes(): void + { + $canWrite = fn($request = null): bool => $this->canWriteCalendar($request); + + $this->http->registerRestRoute( + self::API_NAMESPACE, + '/events', + [ + 'methods' => 'GET', + 'permission_callback' => $canWrite, + 'callback' => fn(): array => ['data' => $this->eventService->listEvents()], + ] + ); + + $this->http->registerRestRoute( + self::API_NAMESPACE, + '/events', + [ + 'methods' => 'POST', + 'permission_callback' => $canWrite, + 'callback' => function ($request): array|\WP_Error { + $payload = $this->jsonPayload($request); + if ($payload === null) { + return $this->error('validation_error', 'invalid JSON payload', 422); + } + try { + $deletedKeys = isset($payload['deleted_occurrence_keys']) && is_array($payload['deleted_occurrence_keys']) + ? array_values($payload['deleted_occurrence_keys']) + : []; + unset($payload['deleted_occurrence_keys']); + $created = $this->eventService->createEvent($payload); + if ($deletedKeys !== []) { + $this->eventService->syncDeletedOccurrenceKeys((int) ($created['id'] ?? 0), $deletedKeys, true); + $created = $this->eventService->getEvent((int) ($created['id'] ?? 0)) ?: $created; + } + return ['data' => $created]; + } catch (\InvalidArgumentException $e) { + return $this->error('validation_error', $e->getMessage(), 422); + } catch (\RuntimeException $e) { + return $this->error('internal_error', $e->getMessage(), 500); + } + }, + ] + ); + + $this->http->registerRestRoute( + self::API_NAMESPACE, + '/events/(?P\d+)', + [ + 'methods' => 'GET', + 'permission_callback' => $canWrite, + 'callback' => function ($request): array|\WP_Error { + $id = (int) $request->get_param('id'); + $event = $this->eventService->getEvent($id); + if (!$event) { + return $this->error('not_found', 'event not found', 404); + } + return ['data' => $event]; + }, + ] + ); + + $this->http->registerRestRoute( + self::API_NAMESPACE, + '/events/(?P\d+)', + [ + 'methods' => 'PATCH', + 'permission_callback' => $canWrite, + 'callback' => function ($request): array|\WP_Error { + $id = (int) $request->get_param('id'); + $payload = $this->jsonPayload($request); + if ($payload === null) { + return $this->error('validation_error', 'invalid JSON payload', 422); + } + try { + $deletedKeysProvided = isset($payload['deleted_occurrence_keys']) && is_array($payload['deleted_occurrence_keys']); + $deletedKeys = $deletedKeysProvided ? array_values((array) $payload['deleted_occurrence_keys']) : []; + unset($payload['deleted_occurrence_keys']); + $event = $this->eventService->updateEvent($id, $payload); + } catch (\InvalidArgumentException $e) { + return $this->error('validation_error', $e->getMessage(), 422); + } + if (!$event) { + return $this->error('not_found', 'event not found', 404); + } + if ($deletedKeysProvided) { + $this->eventService->syncDeletedOccurrenceKeys($id, $deletedKeys, true); + $event = $this->eventService->getEvent($id) ?: $event; + } + return ['data' => $event]; + }, + ] + ); + + $this->http->registerRestRoute( + self::API_NAMESPACE, + '/events/(?P\d+)', + [ + 'methods' => 'DELETE', + 'permission_callback' => $canWrite, + 'callback' => function ($request): array|\WP_Error { + $id = (int) $request->get_param('id'); + $ok = $this->eventService->deleteEvent($id); + if (!$ok) { + return $this->error('not_found', 'event not found', 404); + } + return ['data' => ['deleted' => true]]; + }, + ] + ); + + $this->http->registerRestRoute( + self::API_NAMESPACE, + '/events/(?P\d+)/occurrences/(?P[^/]+)', + [ + 'methods' => 'DELETE', + 'permission_callback' => $canWrite, + 'callback' => function ($request): array|\WP_Error { + $id = (int) $request->get_param('id'); + $key = urldecode((string) $request->get_param('occurrence_key')); + $ok = $this->eventService->deleteOccurrence($id, $key); + if (!$ok) { + return $this->error('validation_error', 'invalid occurrence or event', 422); + } + return ['data' => ['deleted' => true]]; + }, + ] + ); + + $this->http->registerRestRoute( + self::API_NAMESPACE, + '/events/(?P\d+)/occurrences', + [ + 'methods' => 'GET', + 'permission_callback' => $canWrite, + 'callback' => function ($request): array|\WP_Error { + $id = (int) $request->get_param('id'); + $from = (string) ($request->get_param('from') ?: gmdate('Y-m-d')); + $months = (int) ($request->get_param('months') ?: 3); + $items = $this->eventService->listEventOccurrences($id, $from, $months); + if ($items === null) { + return $this->error('not_found', 'event not found', 404); + } + return ['data' => $items, 'meta' => ['count' => count($items), 'from' => $from, 'months' => max(1, min($months, 24))]]; + }, + ] + ); + + $this->http->registerRestRoute( + self::API_NAMESPACE, + '/events/preview-occurrences', + [ + 'methods' => 'POST', + 'permission_callback' => '__return_true', + 'callback' => function ($request): array|\WP_Error { + $payload = $this->jsonPayload($request); + if ($payload === null || !is_array($payload)) { + return $this->error('validation_error', 'invalid JSON payload', 422); + } + $event = isset($payload['event']) && is_array($payload['event']) ? (array) $payload['event'] : []; + $from = (string) ($payload['from'] ?? gmdate('Y-m-d')); + $months = (int) ($payload['months'] ?? 3); + try { + $items = $this->eventService->previewOccurrences($event, $from, $months); + } catch (\InvalidArgumentException $e) { + return $this->error('validation_error', $e->getMessage(), 422); + } + return ['data' => $items, 'meta' => ['count' => count($items), 'from' => $from, 'months' => max(1, min($months, 24))]]; + }, + ] + ); + } + + private function registerPublicRoutes(): void + { + $this->http->registerRestRoute( + self::API_NAMESPACE, + '/public/events', + [ + 'methods' => 'GET', + 'permission_callback' => '__return_true', + 'callback' => function ($request): array { + $view = (string) ($request->get_param('view') ?: 'month'); + $date = (string) ($request->get_param('date') ?: gmdate('Y-m-d')); + $futureOnlyRaw = (string) ($request->get_param('future_only') ?? ''); + $futureOnly = in_array(strtolower($futureOnlyRaw), ['1', 'true', 'yes', 'on'], true); + $items = $this->eventService->listPublicOccurrences($view, $date, $futureOnly); + return [ + 'data' => $items, + 'meta' => ['count' => count($items), 'view' => $view, 'future_only' => $futureOnly], + ]; + }, + ] + ); + + $this->http->registerRestRoute( + self::API_NAMESPACE, + '/public/sidebar-events', + [ + 'methods' => 'GET', + 'permission_callback' => '__return_true', + 'callback' => function (): array { + $items = $this->eventService->listSidebarUpcoming(14); + return [ + 'data' => $items, + 'meta' => ['count' => count($items), 'window_days' => 14], + ]; + }, + ] + ); + } + + private function registerUserRoutes(): void + { + $this->http->registerRestRoute( + self::API_NAMESPACE, + '/users/register', + [ + 'methods' => 'POST', + 'permission_callback' => '__return_true', + 'callback' => function ($request): array|\WP_Error { + $payload = $this->jsonPayload($request); + if ($payload === null) { + return $this->error('validation_error', 'invalid JSON payload', 422); + } + $result = $this->userService->register( + (string) ($payload['email'] ?? ''), + (string) ($payload['password'] ?? '') + ); + if (isset($result['error'])) { + return $this->unwrapServiceResult($result); + } + + $debugTokens = $this->allowDebugTokens() && !empty($payload['debug_tokens']); + $verifyToken = (string) ($result['verify_token'] ?? ''); + $email = (string) ($result['user']['email'] ?? ''); + if ($verifyToken !== '' && $email !== '') { + $this->sendUserEmailVerification($email, $verifyToken); + } + + if (!$debugTokens) { + unset($result['verify_token']); + } + return ['data' => $result]; + }, + ] + ); + + $this->http->registerRestRoute( + self::API_NAMESPACE, + '/users/verify', + [ + 'methods' => 'POST', + 'permission_callback' => '__return_true', + 'callback' => function ($request): array|\WP_Error { + $payload = $this->jsonPayload($request); + if ($payload === null) { + return $this->error('validation_error', 'invalid JSON payload', 422); + } + $result = $this->userService->verifyEmail((string) ($payload['token'] ?? '')); + if (!isset($result['error'])) { + $verifiedEmail = (string) ($result['user']['email'] ?? ''); + if ($verifiedEmail !== '') { + $this->sendAdminApprovalRequestEmail($verifiedEmail); + } + } + return $this->unwrapServiceResult($result); + }, + ] + ); + + $this->http->registerRestRoute( + self::API_NAMESPACE, + '/users/login', + [ + 'methods' => 'POST', + 'permission_callback' => '__return_true', + 'callback' => function ($request): array|\WP_Error { + $payload = $this->jsonPayload($request); + if ($payload === null) { + return $this->error('validation_error', 'invalid JSON payload', 422); + } + $result = $this->userService->login( + (string) ($payload['email'] ?? ''), + (string) ($payload['password'] ?? '') + ); + if (isset($result['error'])) { + return $this->unwrapServiceResult($result); + } + $sessionToken = $this->userService->issueSessionToken((int) ($result['user']['id'] ?? 0), self::SESSION_TTL_SECONDS); + if ($sessionToken !== '') { + $this->setCalendarSessionCookie($sessionToken); + } + return ['data' => $result]; + }, + ] + ); + + $this->http->registerRestRoute( + self::API_NAMESPACE, + '/users/logout', + [ + 'methods' => 'POST', + 'permission_callback' => '__return_true', + 'callback' => function (): array { + $token = $this->readCalendarSessionTokenFromRequest(null); + if ($token !== '') { + $this->userService->revokeSessionToken($token); + } + $this->clearCalendarSessionCookie(); + return ['data' => ['logged_out' => true]]; + }, + ] + ); + + $this->http->registerRestRoute( + self::API_NAMESPACE, + '/users/me', + [ + 'methods' => 'GET', + 'permission_callback' => '__return_true', + 'callback' => function ($request): array|\WP_Error { + $user = $this->resolveCalDavUserForRequest($request); + if ($user === null) { + return $this->error('auth_required', 'Login failure', 401); + } + return ['data' => $user]; + }, + ] + ); + + $this->http->registerRestRoute( + self::API_NAMESPACE, + '/users/password/request', + [ + 'methods' => 'POST', + 'permission_callback' => '__return_true', + 'callback' => function ($request): array|\WP_Error { + $payload = $this->jsonPayload($request); + if ($payload === null) { + return $this->error('validation_error', 'invalid JSON payload', 422); + } + $result = $this->userService->requestPasswordReset((string) ($payload['email'] ?? '')); + if (isset($result['error'])) { + return $this->unwrapServiceResult($result); + } + + $debugTokens = $this->allowDebugTokens() && !empty($payload['debug_tokens']); + $email = (string) ($payload['email'] ?? ''); + $token = (string) ($result['reset_token'] ?? ''); + if ($email !== '' && $token !== '') { + $this->sendPasswordResetEmail($email, $token); + } + if (!$debugTokens) { + unset($result['reset_token']); + } + return ['data' => $result]; + }, + ] + ); + + $this->http->registerRestRoute( + self::API_NAMESPACE, + '/users/password/reset', + [ + 'methods' => 'POST', + 'permission_callback' => '__return_true', + 'callback' => function ($request): array|\WP_Error { + $payload = $this->jsonPayload($request); + if ($payload === null) { + return $this->error('validation_error', 'invalid JSON payload', 422); + } + $result = $this->userService->resetPassword( + (string) ($payload['token'] ?? ''), + (string) ($payload['new_password'] ?? '') + ); + return $this->unwrapServiceResult($result); + }, + ] + ); + + $this->http->registerRestRoute( + self::API_NAMESPACE, + '/admin/users', + [ + 'methods' => 'GET', + 'permission_callback' => fn(): bool => $this->auth->currentUserCan('manage_options'), + 'callback' => fn(): array => ['data' => $this->userService->listUsers()], + ] + ); + + $this->http->registerRestRoute( + self::API_NAMESPACE, + '/admin/users/(?P\d+)/approve', + [ + 'methods' => 'PATCH', + 'permission_callback' => fn(): bool => $this->auth->currentUserCan('manage_options'), + 'callback' => function ($request): array|\WP_Error { + $id = (int) $request->get_param('id'); + $user = $this->userService->approveUser($id); + if (!$user) { + return $this->error('not_found', 'user not found', 404); + } + return ['data' => $user]; + }, + ] + ); + + $this->http->registerRestRoute( + self::API_NAMESPACE, + '/admin/users/(?P\d+)', + [ + 'methods' => 'DELETE', + 'permission_callback' => fn(): bool => $this->auth->currentUserCan('manage_options'), + 'callback' => function ($request): array|\WP_Error { + $id = (int) $request->get_param('id'); + $ok = $this->userService->removeUser($id); + if (!$ok) { + return $this->error('not_found', 'user not found', 404); + } + return ['data' => ['deleted' => true]]; + }, + ] + ); + } + + private function registerIcsRoutes(): void + { + $this->http->registerRestRoute( + self::API_NAMESPACE, + '/public/ics', + [ + 'methods' => 'GET', + 'permission_callback' => '__return_true', + 'callback' => function (): array { + $settings = $this->settingsService->getAll(); + $calendarName = (string) ($settings['caldav_calendar_name'] ?? 'Public Calendar'); + $ics = $this->icsService->buildCalendar( + $this->eventService->listEvents(), + fn(int $eventId): array => $this->eventService->getDeletedOccurrenceKeys($eventId), + $calendarName + ); + return ['data' => $ics]; + }, + ] + ); + } + + private function registerCalDavRoutes(): void + { + $canRead = fn($request = null): bool => $this->resolveCalDavUserForRequest($request) !== null; + $canWrite = fn($request = null): bool => $this->canWriteCalendar($request); + + $this->http->registerRestRoute( + self::API_NAMESPACE, + '/caldav/resources', + [ + 'methods' => 'GET', + 'permission_callback' => $canRead, + 'callback' => fn(): array => ['data' => $this->calDavService->listResources()], + ] + ); + + $this->http->registerRestRoute( + self::API_NAMESPACE, + '/caldav/object/(?P[^/]+\.ics)', + [ + 'methods' => 'GET', + 'permission_callback' => $canRead, + 'callback' => function ($request): array|\WP_Error { + $resource = urldecode((string) $request->get_param('resource')); + $obj = $this->calDavService->getObject($resource); + if ($obj === null) { + return $this->error('not_found', 'resource not found', 404); + } + return ['data' => $obj]; + }, + ] + ); + + $this->http->registerRestRoute( + self::API_NAMESPACE, + '/caldav/object/(?P[^/]+\.ics)', + [ + 'methods' => 'PUT', + 'permission_callback' => $canWrite, + 'callback' => function ($request): array|\WP_Error { + $resource = urldecode((string) $request->get_param('resource')); + $payload = $this->jsonPayload($request); + if ($payload === null || !isset($payload['ics']) || !is_string($payload['ics'])) { + return $this->error('validation_error', 'ics payload is required', 422); + } + + $ifMatch = $this->requestHeader($request, 'if-match'); + $ifNoneMatch = $this->requestHeader($request, 'if-none-match'); + $result = $this->calDavService->putObject( + $resource, + $payload['ics'], + $ifMatch !== '' ? $ifMatch : null, + $ifNoneMatch !== '' ? $ifNoneMatch : null, + (int) (($this->resolveCalDavUserForRequest($request)['id'] ?? 0)) + ); + if (isset($result['error'])) { + $error = (array) $result['error']; + return $this->error( + (string) ($error['code'] ?? 'caldav_error'), + (string) ($error['message'] ?? 'caldav operation failed'), + (int) ($error['status'] ?? 500) + ); + } + + return ['data' => $result]; + }, + ] + ); + + $this->http->registerRestRoute( + self::API_NAMESPACE, + '/caldav/object/(?P[^/]+\.ics)', + [ + 'methods' => 'DELETE', + 'permission_callback' => $canWrite, + 'callback' => function ($request): array|\WP_Error { + $resource = urldecode((string) $request->get_param('resource')); + $result = $this->calDavService->deleteObject($resource); + if (isset($result['error'])) { + $error = (array) $result['error']; + return $this->error( + (string) ($error['code'] ?? 'caldav_error'), + (string) ($error['message'] ?? 'caldav operation failed'), + (int) ($error['status'] ?? 500) + ); + } + return ['data' => $result]; + }, + ] + ); + + $this->http->registerRestRoute( + self::API_NAMESPACE, + '/caldav/multiget', + [ + 'methods' => 'POST', + 'permission_callback' => $canRead, + 'callback' => function ($request): array|\WP_Error { + $payload = $this->jsonPayload($request); + if ($payload === null || !isset($payload['resources']) || !is_array($payload['resources'])) { + return $this->error('validation_error', 'resources array is required', 422); + } + return ['data' => $this->calDavService->multiget($payload['resources'])]; + }, + ] + ); + } + + private function serveIcsResponse(): void + { + $settings = $this->settingsService->getAll(); + if ( + (string) ($settings['ics_access_mode'] ?? 'public_read') === 'authenticated_read' + && $this->resolveCalDavUserForRequest(null) === null + ) { + http_response_code(401); + header('Content-Type: application/json; charset=utf-8'); + echo json_encode(['error' => ['code' => 'auth_required', 'message' => 'authentication required']]); + return; + } + + $calendarName = (string) ($settings['caldav_calendar_name'] ?? 'Public Calendar'); + $ics = $this->icsService->buildCalendar( + $this->eventService->listEvents(), + fn(int $eventId): array => $this->eventService->getDeletedOccurrenceKeys($eventId), + $calendarName + ); + $etag = '"' . substr(sha1($ics), 0, 16) . '"'; + $lastModified = gmdate('D, d M Y H:i:s') . ' GMT'; + + http_response_code(200); + header('Content-Type: text/calendar; charset=utf-8'); + header('ETag: ' . $etag); + header('Last-Modified: ' . $lastModified); + header('Cache-Control: public, max-age=120'); + echo $ics; + } + + private function serveCalDavPath(string $path, string $method): void + { + $caldavUser = $this->resolveCalDavUserForRequest(null); + if ($caldavUser === null) { + http_response_code(401); + header('WWW-Authenticate: Basic realm="Calendar CalDAV"'); + header('Content-Type: application/xml; charset=utf-8'); + echo 'auth required'; + return; + } + + $root = rtrim($this->caldavRootPath(), '/'); + $principalCollection = $root . '/principals/'; + $principal = $principalCollection . (int) ($caldavUser['id'] ?? 0) . '/'; + $calendarsRoot = $root . '/calendars/'; + $collection = $root . '/calendars/public/'; + $resourcePrefix = $collection; + + if ($method === 'HEAD') { + if ($path === $root || $path === $root . '/' || $path === $calendarsRoot || $path === rtrim($calendarsRoot, '/') || $path === $collection || $path === rtrim($collection, '/')) { + header('Allow: OPTIONS, PROPFIND, REPORT, GET, PUT, DELETE, HEAD'); + header('DAV: 1, calendar-access'); + http_response_code(200); + return; + } + if (str_starts_with($path, $resourcePrefix) && str_ends_with($path, '.ics')) { + $resource = basename($path); + $obj = $this->calDavService->getObject($resource); + if ($obj === null) { + http_response_code(404); + return; + } + header('Content-Type: text/calendar; charset=utf-8'); + header('ETag: ' . (string) ($obj['etag'] ?? '')); + http_response_code(200); + return; + } + } + + if ($method === 'OPTIONS') { + header('Allow: OPTIONS, PROPFIND, REPORT, GET, PUT, DELETE, HEAD'); + header('DAV: 1, calendar-access'); + http_response_code(200); + return; + } + + if ($method === 'PROPFIND') { + header('Content-Type: application/xml; charset=utf-8'); + http_response_code(207); + if ($path === $root || $path === $root . '/') { + echo $this->caldavPropfindRootXml($root, $principal, $calendarsRoot, $collection); + return; + } + if ($path === $principalCollection || $path === rtrim($principalCollection, '/')) { + echo $this->caldavPropfindPrincipalCollectionXml($principalCollection, $principal); + return; + } + if ($path === $principal || $path === rtrim($principal, '/')) { + echo $this->caldavPropfindPrincipalXml($principal, $calendarsRoot); + return; + } + if ($path === $calendarsRoot || $path === rtrim($calendarsRoot, '/')) { + echo $this->caldavPropfindCalendarsRootXml($calendarsRoot, $collection); + return; + } + if ($path === rtrim($collection, '/')) { + $path = $collection; + } + if ($path === $collection) { + echo $this->caldavPropfindCollectionXml($collection, $this->caldavSyncToken(), true); + return; + } + if (str_starts_with($path, $resourcePrefix) && str_ends_with($path, '.ics')) { + $resource = urldecode(basename($path)); + $obj = $this->calDavService->getObject($resource); + if ($obj === null) { + http_response_code(404); + echo 'not found'; + return; + } + echo $this->caldavPropfindObjectXml($collection . $resource, (string) ($obj['etag'] ?? '')); + return; + } + http_response_code(404); + echo 'not found'; + return; + } + + if ($method === 'REPORT' && $path === $collection) { + $body = (string) file_get_contents('php://input'); + header('Content-Type: application/xml; charset=utf-8'); + http_response_code(207); + echo $this->caldavReportXml($collection, $body, $this->caldavSyncToken()); + return; + } + + if (str_starts_with($path, $resourcePrefix) && str_ends_with($path, '.ics')) { + $resource = urldecode(basename($path)); + if ($method === 'GET') { + $obj = $this->calDavService->getObject($resource); + if ($obj === null) { + http_response_code(404); + return; + } + header('Content-Type: text/calendar; charset=utf-8'); + header('ETag: ' . (string) ($obj['etag'] ?? '')); + http_response_code(200); + echo (string) ($obj['ics'] ?? ''); + return; + } + if ($method === 'PUT') { + $raw = (string) file_get_contents('php://input'); + $ifMatch = trim((string) ($_SERVER['HTTP_IF_MATCH'] ?? '')); + $ifNoneMatch = trim((string) ($_SERVER['HTTP_IF_NONE_MATCH'] ?? '')); + $result = $this->calDavService->putObject( + $resource, + $raw, + $ifMatch !== '' ? $ifMatch : null, + $ifNoneMatch !== '' ? $ifNoneMatch : null, + (int) ($caldavUser['id'] ?? 0) + ); + if (isset($result['error'])) { + $error = (array) $result['error']; + http_response_code((int) ($error['status'] ?? 500)); + header('Content-Type: application/xml; charset=utf-8'); + echo '' . esc_html((string) ($error['message'] ?? 'error')) . ''; + return; + } + $status = (int) ($result['status'] ?? 204); + $event = (array) ($result['event'] ?? []); + if (!empty($event['etag'])) { + header('ETag: ' . (string) $event['etag']); + } + http_response_code($status); + return; + } + if ($method === 'DELETE') { + $result = $this->calDavService->deleteObject($resource); + if (isset($result['error'])) { + $error = (array) $result['error']; + http_response_code((int) ($error['status'] ?? 500)); + return; + } + http_response_code(204); + return; + } + } + + http_response_code(405); + header('Allow: OPTIONS, PROPFIND, REPORT, GET, PUT, DELETE, HEAD'); + } + + private function caldavPropfindRootXml(string $root, string $principal, string $calendarsRoot, string $collection): string + { + return '' + . '' + . '' . htmlspecialchars($root . '/', ENT_XML1 | ENT_QUOTES, 'UTF-8') . '' + . '' + . 'CalDAV Root' + . '' . htmlspecialchars($principal, ENT_XML1 | ENT_QUOTES, 'UTF-8') . '' + . 'HTTP/1.1 200 OK' + . '' . htmlspecialchars($calendarsRoot, ENT_XML1 | ENT_QUOTES, 'UTF-8') . '' + . '' + . 'Calendar Home Set' + . 'HTTP/1.1 200 OK' + . '' . htmlspecialchars($collection, ENT_XML1 | ENT_QUOTES, 'UTF-8') . '' + . '' + . '' . htmlspecialchars((string) $this->settingsService->get('caldav_calendar_name', 'Public Calendar'), ENT_XML1 | ENT_QUOTES, 'UTF-8') . '' + . 'HTTP/1.1 200 OK' + . ''; + } + + private function caldavPropfindPrincipalCollectionXml(string $principalCollection, string $principal): string + { + return '' + . '' + . '' . htmlspecialchars($principalCollection, ENT_XML1 | ENT_QUOTES, 'UTF-8') . '' + . '' + . 'Principals' + . 'HTTP/1.1 200 OK' + . '' . htmlspecialchars($principal, ENT_XML1 | ENT_QUOTES, 'UTF-8') . '' + . '' + . 'HTTP/1.1 200 OK' + . ''; + } + + private function caldavPropfindPrincipalXml(string $principal, string $calendarsRoot): string + { + return '' + . '' + . '' . htmlspecialchars($principal, ENT_XML1 | ENT_QUOTES, 'UTF-8') . '' + . '' + . '' . htmlspecialchars($principal, ENT_XML1 | ENT_QUOTES, 'UTF-8') . '' + . '' . htmlspecialchars($calendarsRoot, ENT_XML1 | ENT_QUOTES, 'UTF-8') . '' + . 'HTTP/1.1 200 OK' + . ''; + } + + private function caldavPropfindCalendarsRootXml(string $calendarsRoot, string $collection): string + { + return '' + . '' + . '' . htmlspecialchars($calendarsRoot, ENT_XML1 | ENT_QUOTES, 'UTF-8') . '' + . '' + . 'Calendar Home Set' + . 'HTTP/1.1 200 OK' + . '' . htmlspecialchars($collection, ENT_XML1 | ENT_QUOTES, 'UTF-8') . '' + . '' + . '' . htmlspecialchars((string) $this->settingsService->get('caldav_calendar_name', 'Public Calendar'), ENT_XML1 | ENT_QUOTES, 'UTF-8') . '' + . '' . htmlspecialchars(sha1($this->caldavSyncToken()), ENT_XML1 | ENT_QUOTES, 'UTF-8') . '' + . '' + . $this->caldavSupportedReportSetXml() + . 'HTTP/1.1 200 OK' + . ''; + } + + private function caldavPropfindCollectionXml(string $collection, string $syncToken, bool $includeMembers = false): string + { + $xml = '' + . '' + . '' . htmlspecialchars($collection, ENT_XML1 | ENT_QUOTES, 'UTF-8') . '' + . '' + . '' . htmlspecialchars((string) $this->settingsService->get('caldav_calendar_name', 'Public Calendar'), ENT_XML1 | ENT_QUOTES, 'UTF-8') . '' + . '' . htmlspecialchars(sha1($syncToken), ENT_XML1 | ENT_QUOTES, 'UTF-8') . '' + . '' . htmlspecialchars(sha1($syncToken), ENT_XML1 | ENT_QUOTES, 'UTF-8') . '' + . '' . htmlspecialchars($syncToken, ENT_XML1 | ENT_QUOTES, 'UTF-8') . '' + . '' + . $this->caldavSupportedReportSetXml() + . 'HTTP/1.1 200 OK'; + + if ($includeMembers) { + $resources = $this->calDavService->listResources(); + foreach ($resources as $r) { + $resource = (string) ($r['resource'] ?? ''); + if ($resource === '') { + continue; + } + $etag = (string) ($r['etag'] ?? ''); + $href = $collection . $resource; + $xml .= '' . htmlspecialchars($href, ENT_XML1 | ENT_QUOTES, 'UTF-8') . '' + . '' + . '' . htmlspecialchars($etag, ENT_XML1 | ENT_QUOTES, 'UTF-8') . '' + . 'text/calendar; charset=utf-8' + . 'HTTP/1.1 200 OK'; + } + } + + return $xml . ''; + } + + private function caldavPropfindObjectXml(string $href, string $etag): string + { + return '' + . '' + . '' . htmlspecialchars($href, ENT_XML1 | ENT_QUOTES, 'UTF-8') . '' + . '' + . '' . htmlspecialchars($etag, ENT_XML1 | ENT_QUOTES, 'UTF-8') . '' + . 'text/calendar; charset=utf-8' + . 'HTTP/1.1 200 OK' + . ''; + } + + private function caldavSupportedReportSetXml(): string + { + return '' + . '' + . '' + . '' + . ''; + } + + private function caldavReportXml(string $collection, string $xmlBody, string $syncToken): string + { + $bodyLower = strtolower($xmlBody); + $resources = array_map(static fn(array $r): string => (string) ($r['resource'] ?? ''), $this->calDavService->listResources()); + $items = []; + + if (str_contains($bodyLower, 'sync-collection')) { + $items = $this->calDavService->multiget($resources); + } elseif (str_contains($bodyLower, 'calendar-query')) { + $items = $this->calDavService->multiget($resources); + if (preg_match('/start\\s*=\\s*"([0-9TzZ]+)"/i', $xmlBody, $s) && preg_match('/end\\s*=\\s*"([0-9TzZ]+)"/i', $xmlBody, $e)) { + $start = $this->icalToIso($s[1]); + $end = $this->icalToIso($e[1]); + $items = array_values(array_filter($items, function (array $it) use ($start, $end): bool { + if ((int) ($it['status'] ?? 404) !== 200) { + return false; + } + $ics = (string) ($it['ics'] ?? ''); + $dt = $this->extractFirstDtStartIso($ics); + if ($dt === null) { + return false; + } + return $dt >= $start && $dt <= $end; + })); + } + } else { + preg_match_all('#<[^>]*href[^>]*>([^<]+)]*href>#i', $xmlBody, $matches); + $requested = array_values(array_filter(array_map(static function (string $href): string { + $trimmed = trim($href); + $path = parse_url($trimmed, PHP_URL_PATH); + $candidate = is_string($path) && $path !== '' ? $path : $trimmed; + return urldecode(basename($candidate)); + }, (array) ($matches[1] ?? [])))); + $items = $requested ? $this->calDavService->multiget($requested) : $this->calDavService->multiget($resources); + } + + $responses = ''; + foreach ($items as $item) { + $status = (int) ($item['status'] ?? 404); + $resource = (string) ($item['resource'] ?? ''); + $responses .= '' . htmlspecialchars($collection . $resource, ENT_XML1 | ENT_QUOTES, 'UTF-8') . ''; + if ($status === 200) { + $responses .= '' . htmlspecialchars((string) ($item['etag'] ?? ''), ENT_XML1 | ENT_QUOTES, 'UTF-8') . '' + . '' . htmlspecialchars((string) ($item['ics'] ?? ''), ENT_XML1 | ENT_QUOTES, 'UTF-8') . ''; + } + $responses .= 'HTTP/1.1 ' . $status . ($status === 200 ? ' OK' : ' Not Found') + . ''; + } + return '' + . htmlspecialchars($syncToken, ENT_XML1 | ENT_QUOTES, 'UTF-8') + . '' + . $responses + . ''; + } + + private function caldavSyncToken(): string + { + $rows = $this->calDavService->listResources(); + $seed = ''; + foreach ($rows as $row) { + $seed .= (string) ($row['resource'] ?? '') . '|' . (string) ($row['etag'] ?? '') . ';'; + } + return 'urn:calendar-plugin:sync:' . sha1($seed); + } + + private function icalToIso(string $value): string + { + $value = trim($value); + if (preg_match('/^\\d{8}T\\d{6}Z$/', $value)) { + $dt = \DateTimeImmutable::createFromFormat('Ymd\\THis\\Z', $value, new \DateTimeZone('UTC')); + if ($dt instanceof \DateTimeImmutable) { + return $dt->setTimezone(new \DateTimeZone('Europe/London'))->format('c'); + } + } + if (preg_match('/^\\d{8}T\\d{6}$/', $value)) { + $dt = \DateTimeImmutable::createFromFormat('Ymd\\THis', $value, new \DateTimeZone('Europe/London')); + if ($dt instanceof \DateTimeImmutable) { + return $dt->format('c'); + } + } + return '1970-01-01T00:00:00+00:00'; + } + + private function extractFirstDtStartIso(string $ics): ?string + { + if (!preg_match('/^DTSTART(?:;[^:]+)?:([0-9TzZ]+)$/mi', $ics, $m)) { + return null; + } + return $this->icalToIso((string) $m[1]); + } + + private function unwrapServiceResult(array $result): array|\WP_Error + { + if (isset($result['error']) && is_array($result['error'])) { + $error = (array) $result['error']; + return $this->error( + (string) ($error['code'] ?? 'service_error'), + (string) ($error['message'] ?? 'request failed'), + (int) ($error['status'] ?? 500) + ); + } + return ['data' => $result]; + } + + private function collectDiagnosticsSnapshot(bool $enabled): array + { + $diag = [ + 'diagnostics_enabled' => $enabled, + 'generated_at_utc' => gmdate('c'), + 'db_prefix' => $this->db->getPrefix(), + 'table_stem' => $this->tableStem, + 'ics_path' => $this->icsPath(), + 'caldav_root' => $this->caldavRootPath(), + 'current_user_id' => $this->auth->currentUserId(), + 'current_user_email' => $this->auth->currentUserEmail(), + ]; + if (!$enabled) { + $diag['note'] = 'Diagnostics collection is disabled in Setup.'; + return $diag; + } + + $auditTable = $this->db->getPrefix() . trim($this->tableStem, '_') . '_audit_log'; + $sql = $this->db->prepare( + "SELECT actor, action, target, result, created_at, context_json FROM {$auditTable} ORDER BY id DESC LIMIT %d", + 200 + ); + $rows = $this->db->getResults($sql); + $diag['audit_log_recent'] = array_map( + static function (object $row): array { + return [ + 'actor' => (string) ($row->actor ?? ''), + 'action' => (string) ($row->action ?? ''), + 'target' => (string) ($row->target ?? ''), + 'result' => (string) ($row->result ?? ''), + 'created_at' => (string) ($row->created_at ?? ''), + 'context_json' => (string) ($row->context_json ?? ''), + ]; + }, + $rows + ); + $diag['audit_log_count'] = count($diag['audit_log_recent']); + return $diag; + } + + private function updatePluginTablePrefix(string $currentStem, string $requestedPrefix): array + { + $dbPrefix = (string) $this->db->getPrefix(); + if (!str_starts_with($requestedPrefix, $dbPrefix)) { + return ['ok' => false, 'message' => 'Table prefix must start with WordPress DB prefix: ' . $dbPrefix]; + } + $newStem = trim(substr($requestedPrefix, strlen($dbPrefix)), '_'); + if ($newStem === '') { + return ['ok' => false, 'message' => 'Invalid table prefix.']; + } + if ($newStem === $currentStem) { + return ['ok' => true, 'stem' => $currentStem]; + } + + $suffixes = ['events', 'recurrence_exceptions', 'users', 'user_tokens', 'audit_log']; + foreach ($suffixes as $suffix) { + $from = $dbPrefix . $currentStem . '_' . $suffix; + $to = $dbPrefix . $newStem . '_' . $suffix; + $existsFrom = $this->db->getResults($this->db->prepare('SHOW TABLES LIKE %s', $from)); + if (count($existsFrom) === 0) { + continue; + } + $existsTo = $this->db->getResults($this->db->prepare('SHOW TABLES LIKE %s', $to)); + if (count($existsTo) > 0) { + return ['ok' => false, 'message' => 'Target table already exists: ' . $to]; + } + $this->db->query(sprintf('RENAME TABLE `%s` TO `%s`', $from, $to)); + } + update_option('calendar_plugin_table_stem', $newStem); + return ['ok' => true, 'stem' => $newStem]; + } + + private function adminPageShell(string $title, string $bodyHtml): string + { + $nav = '

    Calendar Plugin

    '; + return '

    ' . esc_html($title) . '

    ' + . '
    ' . $nav + . '
    ' . $bodyHtml . '
    '; + } + + private function slugPrefix(): string + { + $slug = trim((string) ($this->settingsService->get('url_slug', '') ?? ''), '/'); + return $slug === '' ? '' : ('/' . $slug); + } + + private function icsPath(): string + { + return $this->slugPrefix() . '/calendar.ics'; + } + + private function caldavRootPath(): string + { + return $this->slugPrefix() . '/caldav/'; + } + + private function canWriteCalendar(mixed $request = null): bool + { + if ($this->auth->currentUserCan('edit_posts')) { + return true; + } + return $this->resolveCalDavUserForRequest($request) !== null; + } + + private static function resolveTableStem(): string + { + $stem = 'cs_calendar'; + $saved = ''; + if (function_exists('get_option')) { + $saved = (string) get_option('calendar_plugin_table_stem', ''); + if ($saved !== '') { + $stem = $saved; + } + } + if (defined('CALENDAR_PLUGIN_TABLE_STEM')) { + $stem = (string) CALENDAR_PLUGIN_TABLE_STEM; + } + if (function_exists('apply_filters')) { + /** @var mixed $filtered */ + $filtered = apply_filters('calendar_plugin_table_stem', $stem); + if (is_string($filtered)) { + $stem = $filtered; + } + } + $stem = strtolower(trim($stem)); + $stem = preg_replace('/[^a-z0-9_]/', '', $stem) ?: 'calendar'; + $stem = trim($stem, '_') ?: 'calendar'; + if ($saved === '') { + $detected = self::detectExistingStem(); + if ($detected !== '') { + return $detected; + } + } + return $stem; + } + + private static function detectExistingStem(): string + { + global $wpdb; + if (!isset($wpdb) || !is_object($wpdb) || !property_exists($wpdb, 'prefix')) { + return ''; + } + $prefix = (string) $wpdb->prefix; + $candidates = ['cs_calendar', 'calendar']; + foreach ($candidates as $candidate) { + $table = $prefix . $candidate . '_events'; + $exists = $wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $table)); + if (is_string($exists) && $exists !== '') { + return $candidate; + } + } + return ''; + } + + private function resolveCalDavUserForRequest(mixed $request = null): ?array + { + if ($this->auth->currentUserId() > 0) { + return [ + 'id' => $this->auth->currentUserId(), + 'email' => $this->auth->currentUserEmail(), + 'source' => 'wp', + ]; + } + + $sessionToken = $this->readCalendarSessionTokenFromRequest($request); + if ($sessionToken !== '') { + $sessionUser = $this->userService->authenticateSessionToken($sessionToken); + if ($sessionUser !== null) { + $sessionUser['source'] = 'calendar_session'; + return $sessionUser; + } + } + + [$email, $password] = $this->readBasicAuthCredentialsFromRequest($request); + if ($email === '' || $password === '') { + return null; + } + $user = $this->userService->authenticateActiveUserCredentials($email, $password); + if (!$user) { + return null; + } + $user['source'] = 'calendar_user'; + return $user; + } + + private function readBasicAuthCredentialsFromRequest(mixed $request = null): array + { + $header = ''; + if (is_object($request) && method_exists($request, 'get_header')) { + $header = (string) $request->get_header('authorization'); + } + if ($header === '') { + $header = (string) ($_SERVER['HTTP_AUTHORIZATION'] ?? ''); + } + if ($header === '' && isset($_SERVER['PHP_AUTH_USER'])) { + return [(string) ($_SERVER['PHP_AUTH_USER'] ?? ''), (string) ($_SERVER['PHP_AUTH_PW'] ?? '')]; + } + if ($header === '' || !str_starts_with(strtolower($header), 'basic ')) { + return ['', '']; + } + $decoded = base64_decode(substr($header, 6), true); + if (!is_string($decoded) || !str_contains($decoded, ':')) { + return ['', '']; + } + [$u, $p] = explode(':', $decoded, 2); + return [(string) $u, (string) $p]; + } + + private function readCalendarSessionTokenFromRequest(mixed $request = null): string + { + $name = self::SESSION_COOKIE; + if (isset($_COOKIE[$name]) && is_string($_COOKIE[$name])) { + return trim((string) $_COOKIE[$name]); + } + + $cookieHeader = ''; + if (is_object($request) && method_exists($request, 'get_header')) { + $cookieHeader = (string) $request->get_header('cookie'); + } + if ($cookieHeader === '') { + $cookieHeader = (string) ($_SERVER['HTTP_COOKIE'] ?? ''); + } + if ($cookieHeader === '') { + return ''; + } + + foreach (explode(';', $cookieHeader) as $pair) { + $parts = explode('=', trim($pair), 2); + if (count($parts) !== 2) { + continue; + } + if (trim((string) $parts[0]) !== $name) { + continue; + } + return trim((string) $parts[1]); + } + + return ''; + } + + private function setCalendarSessionCookie(string $token): void + { + if ($token === '') { + return; + } + $secure = $this->isHttpsRequest(); + setcookie(self::SESSION_COOKIE, $token, [ + 'expires' => time() + self::SESSION_TTL_SECONDS, + 'path' => '/', + 'secure' => $secure, + 'httponly' => true, + 'samesite' => 'Lax', + ]); + } + + private function clearCalendarSessionCookie(): void + { + $secure = $this->isHttpsRequest(); + setcookie(self::SESSION_COOKIE, '', [ + 'expires' => time() - 3600, + 'path' => '/', + 'secure' => $secure, + 'httponly' => true, + 'samesite' => 'Lax', + ]); + } + + private function isHttpsRequest(): bool + { + if (!empty($_SERVER['HTTPS']) && (string) $_SERVER['HTTPS'] !== 'off') { + return true; + } + $forwardedProto = strtolower(trim((string) ($_SERVER['HTTP_X_FORWARDED_PROTO'] ?? ''))); + if ($forwardedProto !== '') { + foreach (explode(',', $forwardedProto) as $proto) { + if (trim($proto) === 'https') { + return true; + } + } + } + if (function_exists('home_url')) { + $home = (string) home_url('/'); + if (str_starts_with(strtolower($home), 'https://')) { + return true; + } + } + return false; + } + + private function allowDebugTokens(): bool + { + return defined('CALENDAR_PLUGIN_ALLOW_DEBUG_TOKENS') && CALENDAR_PLUGIN_ALLOW_DEBUG_TOKENS === true; + } + + private function sendUserEmailVerification(string $email, string $token): void + { + if (!function_exists('wp_mail')) { + return; + } + $verifyUrl = (string) home_url($this->verificationPagePath() . '?calendar_verify_token=' . rawurlencode($token)); + @wp_mail( + $email, + 'Verify your calendar account', + "Please verify your calendar account using this link:\n\n{$verifyUrl}\n\nVerification token (for copy/paste):\n{$token}\n" + ); + } + + private function verificationPagePath(): string + { + $path = trim((string) ($this->settingsService->get('verification_page_path', '/calendar') ?? '/calendar')); + if ($path === '') { + $path = '/calendar'; + } + if (!str_starts_with($path, '/')) { + $path = '/' . $path; + } + return '/' . trim($path, '/'); + } + + private function sendPasswordResetEmail(string $email, string $token): void + { + if (!function_exists('wp_mail')) { + return; + } + $resetUrl = (string) home_url('/?calendar_reset_token=' . rawurlencode($token)); + @wp_mail( + $email, + 'Calendar password reset', + "A password reset was requested for your calendar account.\n\nReset link:\n{$resetUrl}\n" + ); + } + + private function sendAdminApprovalRequestEmail(string $email): void + { + if (!function_exists('wp_mail') || !function_exists('get_option')) { + return; + } + $adminEmail = (string) get_option('admin_email', ''); + if ($adminEmail === '') { + return; + } + $url = (string) admin_url('admin.php?page=calendar-plugin-users'); + @wp_mail( + $adminEmail, + 'Calendar user pending approval', + "A new user is pending approval: {$email}\nReview: {$url}\n" + ); + } + + private function jsonPayload($request): ?array + { + if (!is_object($request) || !method_exists($request, 'get_json_params')) { + return null; + } + $payload = $request->get_json_params(); + return is_array($payload) ? $payload : []; + } + + private function requestHeader(object $request, string $name): string + { + if (method_exists($request, 'get_header')) { + $value = $request->get_header($name); + return is_string($value) ? trim($value) : ''; + } + return ''; + } + + private function error(string $code, string $message, int $status): \WP_Error + { + return new \WP_Error($code, $message, ['status' => $status]); + } +} diff --git a/package/staging/calendar-plugin/src/bootstrap.php b/package/staging/calendar-plugin/src/bootstrap.php new file mode 100644 index 0000000..9fa19ca --- /dev/null +++ b/package/staging/calendar-plugin/src/bootstrap.php @@ -0,0 +1,18 @@ +prefix; + +$tables = [ + $prefix . $stem . '_events', + $prefix . $stem . '_recurrence_exceptions', + $prefix . $stem . '_users', + $prefix . $stem . '_user_tokens', + $prefix . $stem . '_audit_log', +]; + +foreach ($tables as $table) { + $wpdb->query("DROP TABLE IF EXISTS `{$table}`"); +} + +$options = [ + 'calendar_plugin_caldav_calendar_name', + 'calendar_plugin_url_slug', + 'calendar_plugin_ics_access_mode', + 'calendar_plugin_diagnostics_enabled', + 'calendar_plugin_uninstall_cleanup_mode', + 'calendar_plugin_table_stem', + 'calendar_plugin_schema_version', +]; +foreach ($options as $optionName) { + delete_option($optionName); +} diff --git a/read.me b/read.me new file mode 100644 index 0000000..8ba4ea2 --- /dev/null +++ b/read.me @@ -0,0 +1,9 @@ +Status as of 2026-03-31 when codex credit ran out: +1. Removal of plugin did not work. Need to set all files owner to www-data. + +2. Deletion of event in ui doesn't delete event in thunderbird +3. Cannot subscribe to an empty calendar in thunderbird +4. Click inside month cell doesn't add event. +5. Login pane hidden under website hero/banner image +6. Updating an events description via caldav creates weird sequences, e.g. +a space ends up as: text/html,%C2%A0": diff --git a/requirements/api.md b/requirements/api.md new file mode 100644 index 0000000..075a6d6 --- /dev/null +++ b/requirements/api.md @@ -0,0 +1,141 @@ +# API Requirements + +## Purpose +Define the non-CalDAV HTTP API contract for calendar, user-access workflow, and operational endpoints used by the plugin UI and tests. + +## Scope +This document covers: + +- API base path and versioning +- Event CRUD endpoints +- CalDAV-user workflow endpoints +- Standard request/response and error contracts +- Authn/Authz expectations for API calls + +## Normative Boundaries +- API authentication and token/session behavior are defined in `requirements/authentication.md`. +- Authorization matrix and role constraints are defined in `requirements/authorization.md`. +- Error payload/status normalization is defined in `requirements/error_model.md`. +- Recurrence exception behavior is defined in `requirements/recurrence_exceptions.md`. +- Data persistence schema is defined in `requirements/data_schema.md`. + +## API Baseline +- Base path: `/wp-json/calendar/v1` +- If `url_slug` is configured in setup, canonical API paths are prefixed: `//wp-json/calendar/v1`. +- Content type: `application/json; charset=utf-8` +- Time format: ISO 8601 with timezone offset +- All endpoints must be deterministic under `Europe/London` default timezone assumptions unless a timezone is explicitly supplied. + +## Versioning +- Breaking changes require a new version namespace (`v2`, etc.). +- Non-breaking additions are permitted in the current version. +- Deprecated fields/endpoints must remain for at least one release cycle with documentation notice. + +## Event Endpoints + +### Create Event +- `POST /events` +- Requires calendar write capability. +- Request body supports fields in `requirements/editor.md`. +- Response: `201` with created event payload and identifiers. + +### List Events +- `GET /events` +- Supports query params: + - `from` (optional) + - `to` (optional) + - `view` (optional: `list`, `day`, `week`, `month`, `year`) + - `page` and `per_page` (optional) +- Response: `200` with array plus pagination metadata if paged. + +### Get Event +- `GET /events/{event_id}` +- Response: `200` with event payload or `404`. + +### Update Event +- `PUT /events/{event_id}` or `PATCH /events/{event_id}` +- Requires calendar write capability. +- Must enforce optimistic concurrency via version/etag precondition checks. +- Response: `200` with updated event payload. + +### Delete Event +- `DELETE /events/{event_id}` +- Requires calendar write capability. +- Response: `204` on success. + +### Delete Single Occurrence +- `DELETE /events/{event_id}/occurrences/{occurrence_key}` +- Requires calendar write capability. +- Deletes only one occurrence in a recurring series by creating an exception. +- Must not split the underlying recurring series. +- Response: `204` on success. + +## CalDAV User Workflow Endpoints + +### Register +- `POST /users/register` +- Public endpoint with abuse controls. +- Creates account in `pending_approval`. +- Registration is implicitly a write-access request; no separate request endpoint exists. + +### Verify Email +- `POST /users/verify` +- Consumes single-use verification token. +- Marks email as verified while account remains `pending_approval` until admin approval. + +### Forgot Password +- `POST /users/forgot-password` +- Issues password reset token by email. + +### Reset Password +- `POST /users/reset-password` +- Consumes single-use reset token. + +### Admin User List/Update/Delete +- `GET /admin/users` +- `PATCH /admin/users/{user_id}` +- `DELETE /admin/users/{user_id}` +- Admin-only endpoints for approval state transitions and user removal. + +### Admin Diagnostics +- `GET /admin/diagnostics?limit=20` +- Admin-only endpoint. +- Returns recent request/response trace entries for operational troubleshooting. +- Sensitive fields must remain redacted per `requirements/observability.md`. + +## Response Contract +Success responses should include: + +- `data`: endpoint payload +- `meta`: optional metadata (pagination, timestamps, version) + +Error responses should include: + +- `error.code` (stable machine-readable code) +- `error.message` (human-readable summary) +- `error.details` (optional field-level/context details) + +## Validation and Error Statuses +- `400` malformed request +- `401` unauthenticated +- `403` unauthorized +- `404` not found +- `409` conflict (state transition conflict) +- `412` precondition failed (etag/version mismatch) +- `422` semantic validation failure +- `429` rate-limited +- `500` internal server error + +## Security Requirements +- HTTPS required for all authenticated API operations. +- CSRF/nonce protections for cookie-authenticated endpoints. +- Rate limiting for register/verify/reset/login-like flows. +- Error responses must avoid user enumeration leakage. + +## Verification Requirements +Acceptance should verify: + +- Endpoint paths and methods behave as documented. +- Validation and error payloads are consistent. +- Single-occurrence delete creates recurrence exception rather than split series. +- Authz rules enforce role/access constraints. diff --git a/requirements/architecture.md b/requirements/architecture.md new file mode 100644 index 0000000..a48270e --- /dev/null +++ b/requirements/architecture.md @@ -0,0 +1,30 @@ +# Architecture and Runtime Separation Requirements + +## Purpose +Define strict boundaries between deployable plugin code and local WordPress emulation/testing support. + +## Core Separation Rule +- `code/` contains deployable WordPress plugin code only. +- `compatibility-layer/` contains local emulation/shims/test harness support only. + +## Deployable Code Requirements (`code/`) +- Code under `code/` must be deployable to real WordPress without modification. +- Code under `code/` must not import, include, or require files from `compatibility-layer/`. +- Runtime behavior in `code/` must not branch on "test vs production" environment flags. +- Plugin logic in `code/` should depend on WordPress APIs/contracts, not on harness-specific APIs. + +## Compatibility Layer Requirements (`compatibility-layer/`) +- Provides stand-alone/local execution support by emulating required WordPress behavior. +- Must adapt to `code/` contracts; `code/` must not adapt to compatibility-layer internals. +- May include local HTTP harness, fake WP functions, and local data/bootstrap tooling for tests. + +## Packaging and Deployment Boundary +- Build/package inputs for production come from `code/` (plus approved runtime assets) only. +- `compatibility-layer/`, `tests/`, and local tooling are excluded from production artifacts. + +## Verification Requirements +Acceptance must verify: + +1. The same `code/` content runs in real WordPress without source edits. +2. Production package contains no files from `compatibility-layer/`. +3. No `code/` references to `compatibility-layer/` paths/symbols are present. diff --git a/requirements/authentication.md b/requirements/authentication.md new file mode 100644 index 0000000..6fba507 --- /dev/null +++ b/requirements/authentication.md @@ -0,0 +1,72 @@ +# Authentication Requirements + +## Purpose +Define authentication behavior for admin UI, API, CalDAV, and ICS access. + +## Scope +This document covers: + +- Credential types and login flows +- Session/token behavior +- Password rules and recovery +- Abuse controls and lockout behavior + +## Credential Domains +Authentication domains: + +- WordPress admin authentication (for plugin admin pages) +- Plugin CalDAV user authentication (for CalDAV access) +- API authentication (WordPress auth for admin API and plugin user auth where relevant) + +Credentials must not be shared in plaintext between domains. + +## Admin UI Authentication +- Admin pages (`Users`, `Setup`) require valid WordPress authenticated session. +- Capability checks are enforced after authentication. +- Non-authenticated access redirects/fails using WordPress-standard behavior. + +## CalDAV Authentication +- CalDAV endpoints require authenticated plugin CalDAV user credentials over HTTPS. +- Supported first-pass mechanism: HTTP Basic over TLS against plugin user store. +- Password verification must use secure hash comparison. +- Unverified or non-approved (`pending_approval`) users cannot authenticate. + +## API Authentication +- Admin API endpoints require WordPress-authenticated context plus nonce/CSRF protections where cookie auth is used. +- User lifecycle endpoints may allow public access only where explicitly required (`register`, `verify`, `forgot/reset`) with abuse controls. +- Authenticated user endpoints require either WordPress user context or plugin user context as documented per endpoint. + +## Password Policy +- Minimum length: 8 characters. +- Passwords must be stored only as secure hashes (never reversible encryption). +- Password reset rotates credentials immediately. + +## Token Policy +- Verification and reset tokens must be single-use, random, and time-limited. +- Token reuse must fail deterministically. +- Expired tokens must produce actionable but non-sensitive error messaging. + +## Session and Revocation +- After password reset, prior login sessions/tokens for that identity should be invalidated. +- Users not in `active` status lose access immediately for new requests. + +## Abuse Controls +- Rate limiting required for: + - registration + - verification attempts + - forgot/reset flows + - login attempts +- Lockout/backoff behavior must be documented in operational docs. + +## Logging and Privacy +- Authentication failures should be logged with timestamp and source context. +- Logs must not contain plaintext passwords or tokens. +- Responses must avoid user enumeration details. + +## Verification Requirements +Acceptance should verify: + +- Valid credentials authenticate to intended surfaces only. +- Invalid credentials fail safely. +- Pending/unverified users are denied. +- Password reset and token expiry behavior works as specified. diff --git a/requirements/authorization.md b/requirements/authorization.md new file mode 100644 index 0000000..85e826e --- /dev/null +++ b/requirements/authorization.md @@ -0,0 +1,61 @@ +# Authorization Requirements + +## Purpose +Define role/capability-based authorization rules for admin UI, API, CalDAV, and ICS behavior. + +## Scope +This document covers: + +- Authorization matrix by actor and operation +- Admin override behavior +- Default-deny rules + +## Authorization Model +- Authorization is explicit and default-deny. +- Authentication is required before capability checks. +- Least privilege is required for all operations. + +## Actor Types +- `wp_admin`: WordPress user with calendar admin capability +- `wp_editor`: WordPress user with editor-level calendar capability (if enabled) +- `caldav_write`: plugin CalDAV user with approved write access +- `public`: unauthenticated user + +## Capability Matrix + +### Admin UI +- `Users`: `wp_admin` only +- `Setup`: `wp_admin` only + +### Event Data Operations +- Create/update/delete events in admin/API: requires write calendar capability +- Delete one recurrence occurrence: requires write capability and must create exception + +### CalDAV Operations +- Discovery/read/query (`OPTIONS`, `PROPFIND`, `REPORT`, `GET`): allowed for authenticated approved users +- Write operations (`PUT`, `DELETE`): allowed for authenticated approved users + +### User Management +- Approve/remove users: `wp_admin` only + +### ICS Access +- Public ICS endpoint visibility is governed by setup configuration. +- If endpoint is public: `public` may read only approved public events. +- If endpoint requires auth: enforce configured auth policy consistently. + +## Ownership and Scope Rules +- CalDAV users operate against the single shared `public` calendar collection. +- Access control is enforced by account approval state (`pending_approval` vs `active`) rather than per-user private calendars. +- Admin actions must not silently grant broader rights than requested. + +## State Transition Rules +- User transition `pending_approval` -> `active` requires explicit admin action. +- Unauthorized transition attempts return `403`. + +## Verification Requirements +Acceptance should verify: + +- Capability matrix is enforced across all surfaces. +- Pending/unverified users cannot perform CalDAV operations. +- `Users` and `Setup` cannot be accessed by unauthorized roles. +- Public/authorized ICS access respects configured policy. diff --git a/requirements/caldav.md b/requirements/caldav.md new file mode 100644 index 0000000..7d267dc --- /dev/null +++ b/requirements/caldav.md @@ -0,0 +1,190 @@ +# CalDAV Requirements + +## Purpose +Define requirements for exposing the plugin calendar through a CalDAV endpoint with read/write behavior and standards-compatible resource representations. + +## Standards and RFC +CalDAV behavior must be standards-compatible with: + +- RFC 4791: Calendaring Extensions to WebDAV (CalDAV) +- RFC 4918: HTTP Extensions for Web Distributed Authoring and Versioning (WebDAV) +- RFC 5545: Internet Calendaring and Scheduling Core Object Specification (iCalendar) +- RFC 6578: Collection Synchronization for WebDAV (sync report) +- RFC 7232: HTTP conditional requests (ETag/If-Match semantics) + +If additional CalDAV/WebDAV extensions are used, they must be documented and not break baseline client interoperability. + +## Timezone Assumption +Unless explicitly overridden by a future requirement, all plugin dates and times are assumed to be in the `Europe/London` timezone. + +## Scope +This document covers: + +- CalDAV endpoint structure and resources +- Required WebDAV/CalDAV operations +- Mapping between plugin data and CalDAV/iCalendar resources +- Concurrency, sync, and error behavior + +## Normative Boundaries +To avoid ambiguity across requirement documents: + +- Exact CalDAV URI layout, method matrix, and DAV property/report behavior are defined in `requirements/caldav_endpoints.md`. +- Authentication and authorization policy is defined in `requirements/authentication.md` and `requirements/authorization.md`. +- Error response normalization is defined in `requirements/error_model.md`. +- Recurrence exception semantics are defined in `requirements/recurrence_exceptions.md`. +- Concrete persistence schema is defined in `requirements/data_schema.md`. + +## Endpoint and Resource Model +The plugin must expose a CalDAV hierarchy with authenticated user principals and calendar collections. + +Minimum resource model: + +- Principal resource per authenticated CalDAV user +- Shared calendar home set +- Single shared `public` calendar collection +- Event resources as `text/calendar` (`VEVENT`-based `.ics` objects) within collections + +URI requirements: + +- Resource URIs must be stable for the lifetime of each object. +- Event resource URI should be derived from a stable internal event identifier. +- Recurrence exceptions must remain part of the same logical series and not create split series artifacts. + +## Authentication and Authorization +- CalDAV access must require HTTPS and authenticated credentials. +- Authorization must enforce account approval state (`pending_approval` vs `active`). +- Unverified or non-approved users must be denied authentication/authorization. +- `active` users are write-enabled for the shared calendar. +- All authenticated users access the same shared calendar collection. + +## Required CalDAV/WebDAV Operations +The endpoint must support these operations at minimum. + +### Discovery and Collection Introspection +- `OPTIONS`: advertise DAV capabilities, including CalDAV support. +- `PROPFIND`: + - discover principals, calendar home sets, and calendar collections + - retrieve core properties (display name, resource type, ctag/sync metadata where available) + +### Read and Query +- `REPORT` (`calendar-query`): return events in collection, including time-range filtering. +- `REPORT` (`calendar-multiget`): fetch specific event resources by href. +- `GET`: retrieve individual event resource (`text/calendar`). + +### Create and Update +- `PUT`: create new event resource or replace an existing event resource. +- `PUT` updates must preserve recurrence/exception semantics from iCalendar input. +- Write operations must require approved authenticated access. + +### Delete +- `DELETE`: remove event resource when permitted. +- Deleting a single occurrence of a recurring event must be represented as a recurrence exception in the series (`EXDATE` and/or `RECURRENCE-ID` override pattern), not by splitting into multiple independent series. + +### Concurrency and Sync +- `ETag` must be emitted for event resources. +- `If-Match`/`If-None-Match` preconditions must be honored for safe updates/creates. +- `REPORT` (`sync-collection`) should be supported for incremental sync tokens. +- Sync token invalidation/rotation behavior must be deterministic and documented. + +## iCalendar Representation Requirements +CalDAV event payloads must be standards-compatible `VCALENDAR` with `VEVENT` components. + +Minimum mapping expectations: + +- Internal stable event id -> `UID` +- Title -> `SUMMARY` +- Description -> `DESCRIPTION` +- Location -> `LOCATION` +- Category -> `CATEGORIES` +- Start/end -> `DTSTART` / `DTEND` +- Last modification timestamp -> `DTSTAMP` (and `LAST-MODIFIED` when available) +- Recurrence rules -> `RRULE` +- Recurrence exceptions -> `EXDATE` and/or additional `VEVENT` with matching `UID` plus `RECURRENCE-ID` + +Recurrence behavior: + +- Series-level recurrence remains a single logical event sequence keyed by `UID`. +- Exception instances must be represented as exceptions to that `UID`, not a new split sequence. +- Sequence/version metadata should be updated on write operations so clients detect changes. +- Monthly ordinal rules must round-trip accurately, including `BYDAY=2SA`-style forms and `BYSETPOS=-1` (`last` weekday in month). + +## Plugin Data Mapping Requirements +Plugin persistence must represent CalDAV resources in a way that supports idempotent read/write sync. + +Required persisted mapping fields (direct columns or normalized equivalents): + +- Internal event id +- CalDAV resource path/name +- `UID` +- Current `ETag` +- Calendar collection id +- Last-modified-by user id (nullable for system/import operations) +- Serialized recurrence rule data +- Recurrence exception records (date-only exceptions and/or overridden instances) +- Created/updated timestamps + +Behavior requirements: + +- Importing/updating from CalDAV must map to existing records by stable identifiers (`UID` + resource identity rules). +- Duplicate creation from repeated client retries must be prevented. +- Data model must preserve enough metadata to regenerate standards-compliant responses. + +## Error Handling Requirements +- Malformed iCalendar payloads must return appropriate client error responses. +- Authorization failures must return appropriate auth status without leaking sensitive details. +- Write precondition failures (etag mismatch) must return precondition errors and no partial write. +- Server errors must be logged with enough detail for troubleshooting. + +## Security and Privacy +- Transport must be TLS-only for credentials and calendar data. +- Sensitive tokens/credentials must not be logged in plaintext. +- Responses must not leak admin-only or internal plugin metadata. + +## Interoperability Targets +The implementation should interoperate with common CalDAV clients, including: + +- Apple Calendar +- Thunderbird +- DAVx5-class clients + +Client-specific workarounds, if required, must be documented. + +## Majority-Client Compatibility Strategy +There is no single guaranteed feature set that satisfies every CalDAV client implementation, but interoperability for the majority can be managed by combining: + +- strict baseline standards compliance (RFC 4791 + RFC 4918 + RFC 5545) +- a stable compatibility profile for discovery/auth/report/write behavior +- continuous regression tests against representative client patterns + +The project must maintain a compatibility profile with three levels: + +- Level A (required for release): standards-critical discovery/auth/read + - `401` with `WWW-Authenticate` on unauthenticated CalDAV access + - principal discovery (`current-user-principal`) + - principal `calendar-home-set` + - discoverable calendar collection with `` + - `REPORT` support for `calendar-query` and `calendar-multiget` +- Level B (required for release): practical write/sync interoperability + - `PUT`/`DELETE` with stable ETag behavior + - recurrence + exception round-trip fidelity + - sync collection stability for incremental updates +- Level C (best effort): client-specific ergonomics/extensions + - optional properties beyond baseline RFC surface + - minor behavior adjustments for specific client quirks that do not break A/B + +Release gating must include at least: + +- `fixture-tests/fixture_caldav_client_compat_smoke.sh` (legacy local discovery/auth compatibility; retained for archival comparison) +- existing fixture smoke + security smoke suites +- at least one real-client manual smoke (for example Thunderbird or Apple Calendar) for release candidates + +## Verification Requirements +Acceptance should verify: + +- Principal and calendar discovery works via `OPTIONS` and `PROPFIND`. +- Calendar query and multiget reports return correct data for time-range and href selection. +- `GET`, `PUT`, and `DELETE` behaviors match access level permissions. +- ETag and conditional writes prevent lost updates. +- Sync collection reports provide incremental changes. +- Recurring-event single-occurrence delete results in exception representation, not sequence split. +- Returned iCalendar validates against RFC 5545 expectations and is accepted by target clients. diff --git a/requirements/caldav_endpoints.md b/requirements/caldav_endpoints.md new file mode 100644 index 0000000..07770e0 --- /dev/null +++ b/requirements/caldav_endpoints.md @@ -0,0 +1,117 @@ +# CalDAV Endpoint Requirements + +## Purpose +Define exact CalDAV URI structure, required methods/reports/properties, and expected status behavior for interoperability. + +## Scope +This document covers: + +- CalDAV URI layout +- Method support by resource type +- Required WebDAV/CalDAV properties and reports +- Required status-code behavior + +## Normative Boundaries +- This document is authoritative for CalDAV URI structure and method/property/report support. +- Event/recurrence data semantics are defined in `requirements/caldav.md` and `requirements/recurrence_exceptions.md`. +- Authentication/authorization and error behavior norms are defined in: + - `requirements/authentication.md` + - `requirements/authorization.md` + - `requirements/error_model.md` + +## Endpoint Layout +Base CalDAV root: + +- `/caldav/` +- If `url_slug` is configured in setup, canonical CalDAV root is `//caldav/`. + +Resource hierarchy: + +- Principal collection: `/caldav/principals/` +- User principal: `/caldav/principals/{user_id}/` +- Calendar home set: `/caldav/calendars/` +- Shared public calendar collection: `/caldav/calendars/public/` +- Event object resource: `/caldav/calendars/public/{object_id}.ics` + +URI rules: + +- `{user_id}` is stable and URL-safe. +- `{object_id}` is stable for object lifetime. +- All authenticated principals discover the same shared calendar home set and `public` collection. +- Object rename/move behavior is unsupported in first pass unless explicitly implemented. + +## Methods by Resource Type + +### `/caldav/` +- `OPTIONS` +- `PROPFIND` (Depth 0/1) + +### Principal resources +- `PROPFIND` +- `REPORT` where applicable for principal discovery support + +### Calendar collection resources +- `OPTIONS` +- `PROPFIND` +- `REPORT` (`calendar-query`, `calendar-multiget`, `sync-collection`) + +### Event object resources +- `GET` +- `PUT` +- `DELETE` +- `PROPFIND` (Depth 0) + +## Required DAV/CalDAV Properties +Calendar collection and principal responses must support, at minimum, these properties (where applicable): + +- `resourcetype` +- `displayname` +- `current-user-principal` +- `principal-URL` +- `calendar-home-set` +- `supported-calendar-component-set` +- `getctag` (or equivalent documented change tag) +- `getetag` for object resources +- `sync-token` for collections supporting sync + +## REPORT Support +- `calendar-query` with time-range filtering +- `calendar-multiget` by href set +- `sync-collection` for incremental changes since sync token + +If a report is unsupported for a resource, server returns standards-appropriate error status with DAV error body. + +## Status Behavior +- `200` successful read/report/property retrieval +- `201` object created by `PUT` +- `204` successful delete/update with no body where applicable +- `207` multi-status for PROPFIND/REPORT responses +- `401` unauthenticated +- `403` authenticated but forbidden +- `404` resource not found +- `405` method not allowed on resource +- `409` parent/resource state conflict +- `412` precondition failed (etag/if-match semantics) +- `415` unsupported media type + +## Content Handling +- Event objects use `text/calendar` payloads with RFC 5545-compatible `VCALENDAR`. +- Unsupported component types should be rejected unless explicitly mapped. +- Server should normalize line endings/content as required by iCalendar compatibility. + +## Concurrency +- Object resources must emit `ETag`. +- `If-Match` and `If-None-Match` must be honored on `PUT`. +- Lost-update prevention is required on concurrent writes. + +## Recurrence Exception Behavior +- Deleting one recurrence occurrence must be represented as an exception for the existing series. +- The resulting data must remain one logical series (same `UID`), without splitting into separate series resources unless explicitly required by standards-compatible override semantics. + +## Verification Requirements +Acceptance should verify: + +- URI layout and discovery flows are stable. +- Required methods return expected statuses. +- REPORT responses include correct event sets. +- Conditional write and etag behavior prevents stale overwrite. diff --git a/requirements/caldav_users.md b/requirements/caldav_users.md new file mode 100644 index 0000000..33d01e5 --- /dev/null +++ b/requirements/caldav_users.md @@ -0,0 +1,130 @@ +# CalDAV User Access Requirements + +## Purpose +Define requirements for CalDAV user accounts and permissions for the calendar plugin, with minimal maintenance burden for non-technical users. + +## Scope +This document covers: + +- Self-service account creation for calendar app access +- Email verification and password recovery +- WordPress admin approval and user state management +- CalDAV authentication and authorization behavior + +## Normative Boundaries +- User lifecycle and state expectations are defined here. +- Detailed authentication controls are defined in `requirements/authentication.md`. +- Detailed authorization matrix is defined in `requirements/authorization.md`. +- Concrete persisted schema is defined in `requirements/data_schema.md`. + +## Admin Navigation +CalDAV user administration must be provided through the shared calendar plugin admin menu. + +Requirements: + +- User administration appears in a `Users` sub-entry under the calendar plugin main menu. +- User administration is not implemented as a separate top-level plugin menu. +- Access to `Users` is restricted to authorized WordPress admin roles/capabilities. + +## User Experience Goals +The access model must prioritize low-friction onboarding and low ongoing maintenance. + +Requirements: + +- A user can create a calendar access account without admin intervention. +- A user can recover access independently using password reset. +- A user does not need to understand WordPress internals to use CalDAV access. +- Error messages and emails must be plain language and action-oriented. + +## Account Model +CalDAV access must use plugin-managed user accounts tied to identity and approval state. + +Required account fields: + +- Unique login identifier (email address) +- Password hash (never plaintext) +- Email verification status +- Account status (`pending_approval`, `active`) +- Created/updated timestamps + +Notes: + +- Registration is implicitly a request for write access. +- There is no separate `read_only` approval tier in the user model. + +## Registration and Email Verification +Users must be able to self-register and verify their email before admin approval. + +Requirements: + +- Registration requires email address and password. +- Registration creates account state `pending_approval`. +- A verification email is sent with a single-use, time-limited token/link. +- CalDAV authentication must fail until email verification is complete. +- Successful verification marks account email as verified while remaining `pending_approval`. +- Expired or invalid verification links must return a deterministic validation error. + +## Password Recovery +Users must be able to recover account access without administrator support. + +Requirements: + +- "Forgot password" flow is available from the login/access page. +- Password reset uses a single-use, time-limited token sent by email. +- Password policy minimum length is 8 characters. +- Reset token invalidation occurs immediately after successful password change. +- Existing sessions/tokens should be revoked after password reset. +- Clear success/failure messaging is required. + +## Approval Workflow +WordPress admins must have a dedicated interface to manage pending and approved users. + +Requirements: + +- Admin view lists users with email, verification state, and account status. +- Admin can approve a verified pending user by setting status to `active`. +- Admin can remove defunct users. +- Admin actions must be logged with actor, timestamp, and action outcome. +- State transitions must be explicit and validated. + +## CalDAV Authentication and Authorization +CalDAV endpoint access must authenticate users and authorize operations by approval state. + +Requirements: + +- CalDAV endpoint requires HTTPS and authenticated credentials. +- Authentication uses plugin account credentials. +- Authorization checks apply on every CalDAV request. +- Accounts that are unverified or not `active` must be denied. +- `active` users are write-enabled for the shared calendar. + +## Email Delivery Requirements +Email-dependent workflows must be reliable and understandable. + +Requirements: + +- Emails are sent for verification and password reset. +- Admin notification email is sent when a user completes verification and is pending approval. +- Templates must include clear subject lines and concise next-step instructions. +- Email sends must fail closed (no auth bypass) when delivery is unavailable. + +## Security and Abuse Controls +The account system must include baseline controls to reduce abuse risk. + +Requirements: + +- Passwords must meet minimum strength requirements. +- Registration, login, and password-reset request endpoints should include rate limiting. +- Tokens must be cryptographically strong and time-limited. +- Sensitive actions must use CSRF protections in web forms. +- User enumeration should be minimized in public responses. + +## Verification Requirements +Acceptance should verify: + +- A new user can register, verify email, and become pending approval. +- Unverified or pending users cannot authenticate to CalDAV. +- Password reset succeeds end-to-end and invalidates prior sessions/tokens. +- Admin can approve (`active`) and remove users. +- Approved users can authenticate and perform CalDAV writes. +- Admin user list accurately reflects user states and recent actions. diff --git a/requirements/data.md b/requirements/data.md new file mode 100644 index 0000000..3c838a5 --- /dev/null +++ b/requirements/data.md @@ -0,0 +1,91 @@ +# Data Requirements + +## Purpose +Define requirements for calendar data storage in WordPress and for local testing data behavior. + +## Scope +This document covers: + +- Persistent plugin data in WordPress database +- Data model expectations for calendar entries and recurrence +- Data handling for local compatibility-harness testing + +## Normative Boundaries +- This document defines high-level data requirements and test-data expectations. +- Concrete schema fields, constraints, and indexing are defined in `requirements/data_schema.md`. +- Recurrence exception semantics are defined in `requirements/recurrence_exceptions.md`. +- Runtime/code separation constraints are defined in `requirements/architecture.md`. + +## Timezone Assumption +Unless explicitly overridden by a future requirement, all plugin dates and times are assumed to be in the `Europe/London` timezone. + +## WordPress Database Storage +Plugin data must be stored using WordPress-compatible database access patterns. + +Requirements: + +- Plugin-owned tables use WordPress table prefix conventions (`$wpdb->prefix`). +- Schema creation/migration uses WordPress APIs (see lifecycle requirements). +- Data access uses WordPress database APIs (`$wpdb`) with prepared/safe queries. +- Stored records support full CRUD and recurrence features required by editor/web UI. + +## Minimum Event Data Model +The stored model must support these fields (direct columns or normalized equivalents): + +- Event identifier (internal primary key) +- `title` +- `location` +- `category` +- `all_day_event` flag +- `start_datetime` +- `end_datetime` +- `repeat_type` (`none`, `daily`, `weekly`, `monthly`, `yearly`, `custom`) +- Recurrence configuration (`interval`, `range_mode`, `count`, `until_date`, base unit) +- `description` +- Created/updated timestamps + +## Data Integrity Requirements +- `title` and start time/date must be required. +- End must not be before start. +- Recurrence values must be internally consistent (e.g., positive interval/count). +- Deletes must not leave orphaned recurrence metadata or exception records. +- Migrations must preserve existing user data. + +## Query and Retrieval Requirements +- Data model must support efficient retrieval by date range for day/week/month/year UI windows. +- Data model must support generation of recurrence occurrences for both web UI and ICS output. +- Queries for public UI must return only intended public event data. + +## Local Testing Data Requirements +The local compatibility-harness test environment must support deterministic data setup and teardown. + +Requirements: + +- Provide seeded test data covering: + - Single non-recurring events + - All-day events + - Each recurrence type + - Custom recurrence with each range mode + - Edge cases (month boundaries, leap year, DST transitions where relevant) +- Test runs must isolate state between cases (clean database state or controlled fixtures). +- Test data setup scripts/fixtures must be version-controlled under `tests/` and/or `compatibility-layer/`. + +## Test Validation Data Requirements +Automated/local verification should include assertions for: + +- CRUD persistence correctness +- Recurrence expansion correctness in 3-month preview windows +- Correct behavior of keep/remove data paths during uninstall +- Correct mapping readiness for ICS export fields + +## Backup and Recovery (Server Data) +- Production/test server data changes should be recoverable via normal backup processes. +- Destructive operations (e.g., uninstall data removal) require explicit intent and must be test-covered. + +## Documentation Requirements +Project docs must include: + +- Current schema description +- Migration/versioning approach +- Local compatibility-harness usage +- Data retention behavior across install/upgrade/uninstall diff --git a/requirements/data_schema.md b/requirements/data_schema.md new file mode 100644 index 0000000..fda8f31 --- /dev/null +++ b/requirements/data_schema.md @@ -0,0 +1,138 @@ +# Data Schema Requirements + +## Purpose +Define concrete schema requirements for events, recurrence exceptions, CalDAV metadata, and user-access lifecycle state. + +## Scope +This document covers: + +- Required tables/entities and key fields +- Indexing and uniqueness rules +- Migration/versioning expectations +- Data integrity constraints + +## Schema Baseline +- All plugin tables must use WordPress prefix (`$wpdb->prefix`). +- Schema creation/migration uses WordPress mechanisms (`dbDelta`, controlled migrations). +- Charset/collation should follow WordPress defaults. + +## Required Logical Entities + +### Events +Required fields: +- `id` (PK) +- `uid` (stable iCalendar UID) +- `title` +- `description` +- `location` +- `category` +- `all_day_event` +- `start_datetime` +- `end_datetime` +- `repeat_type` +- `repeat_interval` +- `repeat_range_mode` +- `repeat_count` (nullable) +- `repeat_until` (nullable) +- `timezone` (default `Europe/London`) +- `created_at` +- `updated_at` + +Constraints: +- `title`, `start_datetime` required +- `end_datetime >= start_datetime` +- recurrence fields internally consistent + +### Recurrence Exceptions +Required fields: +- `id` (PK) +- `event_id` (FK -> events.id) +- `occurrence_key` (canonical occurrence datetime key) +- `exception_type` (`deleted_occurrence`, `override_occurrence`) +- `override_payload` (nullable structured data for modified occurrence) +- `created_at` +- `updated_at` + +Constraints: +- unique (`event_id`, `occurrence_key`) +- deleted-occurrence exception must suppress that one occurrence without splitting series + +### CalDAV Objects +Required fields: +- `id` (PK) +- `event_id` (FK -> events.id) +- `calendar_id` +- `resource_path` +- `etag` +- `sync_version` or equivalent change sequence +- `last_modified_by_user_id` (FK -> caldav_users.id, nullable) +- `created_at` +- `updated_at` + +Constraints: +- unique (`calendar_id`, `resource_path`) +- unique `etag` progression by object version + +### CalDAV Users +Required fields: +- `id` (PK) +- `email` (unique) +- `password_hash` +- `email_verified_at` (nullable) +- `account_status` (`pending_approval`, `active`) +- `access_level` (implementation detail; approved users are write-enabled) +- `request_state` (implementation detail; tracks approval pipeline when present) +- `created_at` +- `updated_at` + +### User Tokens +Required fields: +- `id` (PK) +- `user_id` (FK -> caldav_users.id) +- `token_type` (`verify_email`, `reset_password`) +- `token_hash` +- `expires_at` +- `used_at` (nullable) +- `created_at` + +Constraints: +- tokens are single-use +- expired/used tokens are invalid + +### Audit Log +Required fields: +- `id` (PK) +- `actor_type` (`wp_user`, `caldav_user`, `system`) +- `actor_id` +- `action` +- `target_type` +- `target_id` +- `result` (`success`, `failure`) +- `context_json` +- `created_at` + +## Indexing Requirements +- Events index on `start_datetime`, `end_datetime` +- Events unique index on `uid` where logical uniqueness is required +- Exceptions index on `event_id` +- CalDAV objects index on `calendar_id`, `resource_path`, `etag` +- Users unique index on `email` +- Tokens index on `user_id`, `token_type`, `expires_at` +- Audit log index on `created_at`, `actor_id`, `action` + +## Migration and Versioning +- Schema version must be stored in plugin options. +- Upgrades must be incremental, idempotent, and logged. +- Downgrade strategy must be documented; if unsupported, explicit warning required. + +## Data Retention +- Behavior on uninstall follows lifecycle requirements. +- If removal is selected, plugin-owned tables and options are removed safely. +- If retention is selected, schema/data remains for future reactivation. + +## Verification Requirements +Acceptance should verify: + +- Fresh install creates expected schema. +- Upgrade applies required structural changes without data loss. +- Constraints enforce recurrence exception uniqueness and no split-series artifacts. diff --git a/requirements/deployment.md b/requirements/deployment.md new file mode 100644 index 0000000..a165cb0 --- /dev/null +++ b/requirements/deployment.md @@ -0,0 +1,105 @@ +# Deployment Requirements + +## Purpose +Define how the plugin is deployed to the remote WordPress host and how deployment correctness is verified, including an exact-match validation between the approved artifact and deployed runtime files. + +## Scope +This document covers: + +- Remote host deployment target and access assumptions +- Artifact-only deployment model +- Pre-deploy checks +- Post-deploy validation +- Exact-match validation requirements +- Rollback requirements + +This document does not define packaging rules (see `requirements/packaging.md`) or runtime feature behavior. + +## Normative References +- Packaging requirements: `requirements/packaging.md` +- Environment/runtime requirements: `requirements/environment.md` +- Architecture/runtime separation requirements: `requirements/architecture.md` +- Smoke/regression expectations: `tests/smoke_tests.md` + +## Deployment Model +- Deployments must use a built plugin artifact (zip) produced from approved repository content. +- Direct ad-hoc editing of production plugin files is not permitted. +- Deployment target path must point to the active WordPress plugin directory. +- Deployable runtime must come from `code/` only; compatibility/emulation assets are not deployable. + +For the current remote test target: +- WordPress root: `/var/www/wordpress` +- Plugin directory root: `/var/www/wordpress/wp-content/plugins` +- Plugin deploy directory: `/var/www/wordpress/wp-content/plugins/calendar-plugin` + +## Pre-Deployment Requirements +Before deployment: + +1. Package artifact has passed packaging validation. +2. Local and remote smoke checks required for the release scope are green. +3. Remote path existence/permissions are verified. +4. Backup or rollback artifact for currently deployed version is available. +5. Deployment record includes target host, artifact name, version, timestamp, and operator. + +## Deployment Procedure Requirements +Required high-level procedure: + +1. Transfer approved artifact to remote host staging area. +2. Extract artifact to a clean temporary directory on remote host. +3. Validate extracted plugin directory structure. +4. Synchronize extracted plugin directory to deploy directory. +5. Run post-deploy verification checks. + +## Exact-Match Validation (Required) +After deployment, deployed plugin files must exactly match the approved artifact contents (excluding allowed mutable runtime files if any are explicitly listed). + +Validation must include: + +1. File set equality: +- No missing files in deployment compared to artifact. +- No extra files in deployment compared to artifact. + +2. File content equality: +- Each deployed file content hash must match artifact file hash. + +3. Optional metadata check (recommended): +- File mode/permissions match expected deployment policy. + +Accepted implementation options: +- Manifest-based validation: generate a sorted list of ` ` from extracted artifact and deployed directory and compare byte-for-byte. +- Rsync dry-run checksum validation (for example `rsync -avznc --delete`) plus explicit failure on any reported delta. + +Any mismatch must fail deployment validation and trigger rollback decision. + +## Post-Deployment Verification Requirements +After exact-match validation: + +1. Plugin is present and loadable by WordPress. +2. Plugin activation state is verified (as required by release process). +3. CalDAV endpoint discovery and ICS endpoint health checks pass. +4. Critical UI/API smoke checks pass. + +## Rollback Requirements +If deployment validation or post-deploy checks fail: + +1. Revert to previous known-good plugin artifact. +2. Re-run minimum smoke checks to confirm recovery. +3. Record incident details and remediation before next deploy attempt. + +## Audit and Traceability Requirements +Each deployment must record: + +- artifact name/version +- source revision/tag +- target host/path +- deploy timestamp +- validation result (including exact-match evidence) +- rollback status if applicable + +## Acceptance Criteria +Deployment process is acceptable only if: + +1. Artifact-only deployment is enforced. +2. Exact-match file and hash validation is performed and passes. +3. Required post-deploy smoke checks pass. +4. Deployment record contains all traceability fields. diff --git a/requirements/editor.md b/requirements/editor.md new file mode 100644 index 0000000..34ec166 --- /dev/null +++ b/requirements/editor.md @@ -0,0 +1,93 @@ +# Calendar Editor Requirements + +## Purpose +Define implemented requirements for the authenticated calendar editor used from the `/calendar` UI. + +## Scope +This document covers: + +- Event CRUD in the modal editor +- Supported event fields and validation +- Implemented recurrence options +- Recurrence preview and single-occurrence deletion UX + +## Access Model +- Public users are read-only. +- Plugin users in `active` status can create, update, and delete events. +- WordPress users with `edit_posts` capability can also perform write actions. + +## CRUD Requirements +The editor must support: + +- Create event +- Edit event +- Delete event +- Delete one occurrence from a recurring event (exception-based, no series split) + +Behavior requirements: + +- Create/edit use modal overlay forms. +- Save/update/delete actions show status feedback. +- Delete event requires explicit user confirmation. +- Single-occurrence delete requires selecting a specific occurrence key. + +## Event Fields +Supported fields: + +- `title` (required) +- `description` +- `location` +- `category` +- `all_day_event` +- `start_datetime` (required) +- `end_datetime` (required, must be on/after start) +- `repeat_type` +- `repeat_interval` +- `repeat_range_mode` +- `repeat_count` +- `repeat_until` +- Monthly pattern fields: `repeat_nth_mode`, `repeat_nth_day`, `repeat_nth_pos`, `repeat_nth_weekday` + +UI behavior requirements: + +- Start and end use separate date and time controls. +- Date controls use date-picker inputs. +- Time controls hide when `all_day_event` is selected. +- Floating/persistent labels keep field purpose visible. +- Internal event ID is hidden from users. + +## Recurrence Model +Supported `repeat_type` values: + +- `none` +- `daily` +- `weekly` +- `monthly` +- `yearly` + +Supported monthly modes: + +- `day_of_month` (nth day) +- `weekday_of_month` (nth weekday, including `last` via negative position) + +Supported range modes: + +- `none` +- `count` +- `until` + +## Recurrence Preview and Occurrence Selection +- For recurring events, editor shows a 3-month occurrence chooser grid. +- Grid supports previous/next month paging. +- Selecting an occurrence sets the occurrence key used by `Delete Occurrence`. +- Deselect/reselect behavior must be supported. +- For `repeat_type=none`, occurrence controls remain hidden and delete-occurrence is disabled. + +## Verification Requirements +Acceptance should verify: + +- Approved/authenticated users can complete full CRUD. +- Recurring events render selectable occurrences in preview grid. +- Deleting one occurrence creates an exception while preserving the series. +- Monthly ordinal patterns save and reload correctly. +- Validation blocks invalid start/end ranges. diff --git a/requirements/environment.md b/requirements/environment.md new file mode 100644 index 0000000..c48e956 --- /dev/null +++ b/requirements/environment.md @@ -0,0 +1,131 @@ +# Development Environment + +## Purpose +Define the standard development environment for the WordPress calendar plugin so that development, testing, packaging, and deployment are consistent across contributors. + +## Repository Layout +The repository is organized into the following top-level folders: + +- `requirements/`: Functional and non-functional requirements. +- `docs/`: Documentation (test execution, installation, data structures, workflows). +- `code/`: Plugin source code. +- `compatibility-layer/`: Local WordPress emulation and stand-alone harness/shim code for tests. +- `tests/`: Automated and manual test assets. +- `package/`: Build outputs and distributable plugin package artifacts. + +Note: the project convention may refer to `documentation/`; in this workspace the equivalent directory is `docs/`. + +## Supported Development Platforms +Developers should use one of: + +- Linux (preferred) +- macOS + +Windows is acceptable when using WSL2 with a Linux userland. + +## Core Tooling +Minimum expected tools: + +- `bash` shell +- `ssh` client for remote test server access +- `zip` (or equivalent) for plugin packaging +- `rsync` or `scp` for deployment transfer +- Subversion (`svn`) and/or Git as required by the hosting/deployment workflow + +If project scripts introduce additional dependencies (for example PHP CLI, Composer, Node.js, or WP-CLI), those requirements must be documented in `docs/` and kept aligned with this environment spec. + +## Runtime Targets +Development and verification occur against two runtime targets: + +1. Local compatibility harness (`compatibility-layer/`) +2. Remote WordPress test server (via SSH) + +Production deployment is by packaged plugin artifact, not by direct source sync from `code/`. + +Strict runtime/code separation requirements are defined in `requirements/architecture.md`. + +## Local Development Workflow Expectations +- Implement plugin changes in `code/`. +- Keep requirements updates in `requirements/`. +- Add or update tests in `tests/`. +- Validate behavior first in the local compatibility harness (`compatibility-layer/`). +- Build distributable package into `package/`. + +## Local Compatibility Harness Environment +The local harness in `compatibility-layer/` must emulate the WordPress functions/APIs needed by the plugin. + +Environment expectations: + +- Deterministic startup and teardown for repeatable tests. +- Isolated test data/state between runs. +- Ability to run plugin unit/integration-style tests without external network dependencies unless explicitly required. +- Clear commands documented in `docs/` for: + - harness startup + - test execution + - harness reset/cleanup + +## Data Access Boundaries (Compatibility Harness and Tests) +When code under `code/` is exercised via the compatibility harness, data access must remain within approved WordPress mechanisms. + +Requirements: + +- Plugin code must access persistent data through WordPress-supported mechanisms (for example `$wpdb`, WordPress options/settings APIs, and documented plugin tables via WordPress DB access patterns). +- Plugin code must not bypass WordPress mechanisms by directly accessing external/non-WordPress data stores during compatibility-harness execution. +- Compatibility-harness and test runs must not depend on or read data outside the harness-managed test database/state. +- Any integration that requires external data access must be explicitly documented and excluded from default regression runs unless intentionally enabled. + +## Remote Test Server Environment +Remote test validation is performed over SSH to a dedicated test environment. + +Environment expectations: + +- SSH access is key-based and restricted to authorized developers. +- Test server WordPress and PHP versions should mirror production as closely as practical. +- Deployment to test should use packaged plugin artifacts or an equivalent controlled sync process. +- Test data handling must avoid accidental production data access. + +Required documentation in `docs/`: + +- Host access method +- Deployment command(s) +- Rollback approach +- Post-deploy smoke test checklist + +## Packaging and Deployment Environment +Production release uses a packaged plugin artifact produced from repository sources. + +Detailed remote deployment and exact-match validation requirements are defined in `requirements/deployment.md`. + +Packaging expectations: + +- Package is generated in `package/` with versioned naming. +- Artifact contains only required plugin files. +- Development-only files (temporary files, compatibility-layer artifacts, etc.) are excluded. +- Package integrity is validated before deployment. + +Deployment expectations: + +- Upload/install package on production server via approved operational process. +- Record deployed version and deployment timestamp. +- Maintain a rollback artifact for the previous known-good release. + +## Configuration and Secrets +- Do not commit secrets to source control. +- SSH credentials/keys, server addresses, and environment-specific configuration must be stored outside the repo (or in approved secret management). +- Local and remote environment variables should be documented by name and purpose in `docs/` without exposing secret values. +- Compatibility-harness email workflows use SMTP variables sourced from `credentials/.env` (or process env): +- `SMTP_HOST`, `SMTP_PORT`, `SMTP_USE_TLS`, `SMTP_USERNAME`, `SMTP_PASSWORD`, `SMTP_FROM`, `SMTP_ADMIN_TO` + +## Quality Gates +Before packaging for test or production: + +- Requirements impacted by change are updated. +- Relevant tests in `tests/` pass in local compatibility harness. +- Remote test server smoke checks pass. +- Package contents are verified. + +## Change Control +Any material change to tools, runtime versions, harness behavior, or deployment process must update: + +- this file (`requirements/environment.md`) +- corresponding operational docs in `docs/` diff --git a/requirements/error_model.md b/requirements/error_model.md new file mode 100644 index 0000000..dff9a54 --- /dev/null +++ b/requirements/error_model.md @@ -0,0 +1,73 @@ +# Error Model Requirements + +## Purpose +Define a consistent error taxonomy and response structure across admin UI, API, CalDAV, and ICS surfaces. + +## Scope +This document covers: + +- Error categories and stable codes +- API/CalDAV/ICS status behavior +- UI-facing error messaging principles +- Logging expectations for failures + +## Error Categories +- `validation_error` +- `authentication_error` +- `authorization_error` +- `not_found` +- `conflict_error` +- `precondition_failed` +- `rate_limited` +- `integration_error` +- `internal_error` + +## API Error Contract +JSON errors must include: + +- `error.code` (stable machine identifier) +- `error.message` (safe human message) +- `error.details` (optional object/list with field-level data) +- `error.request_id` (optional correlation id) + +Status mapping: + +- `400` malformed payload/query +- `401` unauthenticated +- `403` unauthorized +- `404` not found +- `409` conflict +- `412` precondition failed +- `422` validation error +- `429` rate-limited +- `500` internal error + +## CalDAV/WebDAV Error Behavior +- Use standards-appropriate HTTP status codes and DAV error bodies. +- On multistatus operations, each href must report accurate per-resource status. +- Precondition failures must be explicit for conditional writes. + +## ICS Error Behavior +- Non-success ICS responses should return clear status (`4xx/5xx`) and not malformed calendar text. +- Internal diagnostics go to logs, not to public response bodies. + +## Admin UI Error Messaging +- Messages must be actionable and non-technical for end users where possible. +- Field-level validation errors must indicate affected field and remedy. +- Authentication/authorization failures should avoid revealing sensitive internals. + +## Retry Guidance +- Transient errors (rate limit, temporary integration failure) should indicate retry expectation. +- Permanent validation errors should not be presented as retriable without change. + +## Logging Requirements +- All `5xx` and security-relevant `4xx` failures must be logged. +- Logs should include actor, endpoint/action, status, timestamp, and request id. +- Logs must not include plaintext secrets/tokens/passwords. + +## Verification Requirements +Acceptance should verify: + +- Error payload shape is consistent for API endpoints. +- CalDAV and ICS failures return standards-appropriate statuses. +- UI displays user-safe messages while logs retain troubleshooting detail. diff --git a/requirements/ics.md b/requirements/ics.md new file mode 100644 index 0000000..d07fbde --- /dev/null +++ b/requirements/ics.md @@ -0,0 +1,96 @@ +# ICS Export Requirements + +## Purpose +Define requirements for generating iCalendar (.ics) output from plugin calendar data. + +## Standards and RFC +ICS output must comply with the iCalendar specification: + +- RFC 5545: Internet Calendaring and Scheduling Core Object Specification (iCalendar) + +Where additional iCalendar properties are used beyond RFC 5545 core, they must be documented and standards-compatible. + +## Timezone Assumption +Unless explicitly overridden by a future requirement, all plugin dates and times are assumed to be in the `Europe/London` timezone. + +## Scope +This document covers: + +- ICS feed/file generation +- Mapping from plugin event data to iCalendar properties +- Recurrence representation +- Validation and interoperability expectations + +## Output Requirements +- Content type must be `text/calendar`. +- Character encoding must be UTF-8. +- Calendar payload must include required VCALENDAR envelope properties. +- Output must be consumable by common calendar clients (Apple, Google, Outlook-class clients). + +## VCALENDAR Requirements +The ICS output must include, at minimum: + +- `BEGIN:VCALENDAR` +- `VERSION:2.0` +- `PRODID` identifying the plugin/system +- `CALSCALE:GREGORIAN` (recommended default) +- `VTIMEZONE` definition for `Europe/London` +- `END:VCALENDAR` + +## VEVENT Mapping +Each plugin calendar entry maps to at least one `VEVENT`. + +Field mapping requirements: + +- Plugin `title` -> `SUMMARY` +- Plugin `description` -> `DESCRIPTION` +- Plugin `location` -> `LOCATION` +- Plugin start date/time -> `DTSTART` +- Plugin end date/time -> `DTEND` +- Plugin category -> `CATEGORIES` +- Stable unique event identifier -> `UID` +- Last update timestamp -> `DTSTAMP` + +Additional mapping guidance: + +- All-day events use date-based `DTSTART`/`DTEND` semantics per RFC 5545. +- Timed events must use `DTSTART;TZID=Europe/London` and `DTEND;TZID=Europe/London`. +- `DTEND` must represent a valid end boundary and not precede `DTSTART`. + +## Recurrence Mapping +Recurring entries should be represented with `RRULE` where possible. + +Mapping expectations: + +- Daily/weekly/monthly/yearly repeat -> corresponding `FREQ` values. +- Custom interval (`every n`) -> `INTERVAL=n`. +- Range modes: + - No end date -> RRULE without `UNTIL` or `COUNT`. + - Create `` appointments -> RRULE with `COUNT=n`. + - Repeat until `` -> RRULE with `UNTIL=`. + +If specific recurrence shapes cannot be represented in a single RRULE, plugin may emit standards-compliant expanded VEVENT instances as fallback, documented in implementation notes. + +## Timezone Requirements +- ICS generation must use a deterministic timezone strategy documented in settings/docs. +- Current project baseline timezone is `Europe/London` unless explicitly overridden by a future requirement. +- For the baseline configuration, ICS output must include a `VTIMEZONE` block describing `Europe/London`. +- For the baseline configuration, timed `DTSTART` and `DTEND` values must use `TZID=Europe/London`. +- If a future configurable timezone mode is introduced, output must remain standards-compliant and subscriber-coherent. + +## Data Quality and Escaping +- Text values must be escaped/formatted according to iCalendar rules (e.g., commas, semicolons, line folding). +- Invalid or incomplete records must not produce malformed ICS output. +- Generation errors should fail safely with observable error handling. + +## Security and Privacy +- Export must include only events intended for public/user-facing distribution. +- Internal/admin-only metadata must not leak into ICS properties. + +## Verification Requirements +Acceptance should verify: + +- Output validates as RFC 5545-compatible ICS. +- Field mappings are correct for single and recurring events. +- All-day and timed events render correctly in major clients. +- Recurrence limits (`COUNT`/`UNTIL`) behave as configured. diff --git a/requirements/ics_endpoint.md b/requirements/ics_endpoint.md new file mode 100644 index 0000000..b252bbf --- /dev/null +++ b/requirements/ics_endpoint.md @@ -0,0 +1,61 @@ +# ICS Endpoint Requirements + +## Purpose +Define the ICS endpoint path, visibility model, caching behavior, and synchronization expectations. + +## Scope +This document covers: + +- ICS endpoint routing and response contract +- Public vs authenticated access policy +- Caching/refresh behavior +- Multi-calendar considerations + +## Normative Boundaries +- This document is authoritative for ICS endpoint path/access/cache behavior. +- ICS payload formatting and field mapping are defined in `requirements/ics.md`. +- Recurrence exception export behavior is defined in `requirements/recurrence_exceptions.md`. +- Authorization policy integration is defined in `requirements/authorization.md`. +- Error normalization is defined in `requirements/error_model.md`. + +## Endpoint Contract +- Default endpoint path: `/calendar.ics` +- If `url_slug` is configured in setup, canonical endpoint path is `//calendar.ics`. +- Alternative implementation path may be used if documented; one canonical URL must be exposed in UI. +- Response content type: `text/calendar; charset=utf-8` + +## Access Policy +- Access mode is configurable in `Setup`: + - `public_read`: endpoint accessible without auth, only public events included + - `authenticated_read`: endpoint requires authenticated access +- Default mode: `public_read` unless overridden by policy requirements. + +## Data Scope +- ICS output includes only events intended for the selected endpoint audience. +- Internal/admin-only metadata must never appear in output. +- Recurrence and exception semantics follow `requirements/ics.md` and `requirements/recurrence_exceptions.md`. + +## Stability and Subscription +- Endpoint URL should remain stable for long-lived subscriptions. +- If URL must change, documented migration/redirection behavior is required. + +## Caching and Freshness +- Responses should include deterministic cache headers. +- `ETag` and/or `Last-Modified` should be emitted where practical. +- Cache policy must balance freshness and performance for subscriber clients. + +## Failure Behavior +- On generation failure, return a clear HTTP error status and log details. +- Endpoint must not return malformed partial ICS payload. + +## Multi-Calendar Support +- First pass supports single canonical calendar feed. +- If multiple feeds are added later, each feed requires stable URL and explicit audience rules. + +## Verification Requirements +Acceptance should verify: + +- Canonical ICS URL is discoverable from web UI. +- Access policy mode is enforced correctly. +- Payload is valid and subscriber-compatible. +- ETag/Last-Modified behavior supports efficient refresh checks. diff --git a/requirements/life_cycle.md b/requirements/life_cycle.md new file mode 100644 index 0000000..160d87d --- /dev/null +++ b/requirements/life_cycle.md @@ -0,0 +1,130 @@ +# Plugin Life Cycle (Production) + +## Purpose +Define the production-server lifecycle for the calendar plugin, including installation, configuration, ongoing data maintenance, and uninstallation behavior. + +## Scope +This requirement applies to: + +- First-time installation on production +- Plugin upgrades on production +- Operational maintenance of plugin-owned data +- Uninstallation from production + +## Preconditions +Before installation: + +- The production WordPress instance is healthy and backed up. +- The plugin package artifact has been built and verified. +- Deployment user has appropriate WordPress admin permissions. +- Database user configured for WordPress has permission to create/alter/drop plugin tables. + +## Installation (Production) +Installation must be performed using the packaged plugin artifact through approved production procedures. + +Required behavior on activation: + +- Register plugin activation logic using WordPress plugin lifecycle hooks. +- Create/update required plugin database schema using WordPress APIs only. +- Initialize plugin default options/settings in WordPress options storage. +- Record plugin schema/version metadata for future migrations. + +## Database Table Creation (WP APIs Only) +Plugin-owned tables must be created and migrated via WordPress APIs only; direct shell/database tooling is not part of normal runtime lifecycle. + +Requirements: + +- Use `$wpdb` for table naming with WordPress table prefix support. +- Use `dbDelta()` (from WordPress upgrade API) for table creation and compatible schema updates. +- Use WordPress charset/collation helpers (e.g., `$wpdb->get_charset_collate()`). +- Version schema changes using a stored option (for example, plugin schema version option). +- Table creation/migration must be idempotent and safe to run multiple times. + +Non-requirements: + +- No raw CLI SQL execution as part of plugin activation/deactivation lifecycle. + +## Server Configuration and Settings +On production install, plugin settings must be configurable in WordPress admin and persisted using WordPress settings/options APIs. + +Requirements: + +- Register settings via WordPress Settings API. +- Validate and sanitize all user-provided settings before persistence. +- Apply secure defaults during first activation. +- Restrict configuration UI/actions to authorized WordPress capabilities. +- Document required settings and operational defaults in project docs. + +## Data Maintenance on Server +The plugin must maintain its production data safely across normal operation and upgrades. + +Requirements: + +- Maintain backward-compatible migrations for schema evolution where practical. +- Preserve data on plugin upgrades by default. +- Avoid destructive data operations during activation/deactivation unless explicitly required by migration logic. +- Provide routine cleanup/retention behavior only through explicit plugin logic (e.g., scheduled cleanup), not implicit uninstall behavior. +- Log or surface migration/maintenance failures via WordPress-compatible error/reporting paths. + +## Upgrade Lifecycle +On plugin update: + +- Activation/upgrade routine checks stored plugin/schema version. +- Required migrations run in deterministic order. +- Migration completion updates stored schema/version markers. +- Existing user settings are preserved unless a documented migration transforms them. + +## Uninstallation (Production) +Uninstall must support two outcomes: + +1. Remove plugin code but keep plugin database data. +2. Remove plugin code and remove plugin database data. + +Uninstall behavior must be explicit and predictable. + +### Data Retention Option +Requirements: + +- Provide an administrator-controlled setting (or equivalent explicit control) named clearly for data retention on uninstall. +- Default behavior should be conservative: keep data unless administrator explicitly chooses removal. +- The retention setting must be read during uninstall execution. + +### Uninstall Execution +Requirements: + +- Implement uninstall logic using WordPress uninstall mechanisms (`uninstall.php` and/or uninstall hook). +- Always remove plugin options/settings that are not required after uninstall when full removal is selected. +- If “keep data” is selected: + - Leave plugin-owned database tables and business data intact. + - Remove only runtime/transient/cache entries where appropriate. +- If “remove data” is selected: + - Drop plugin-owned tables via WordPress database APIs (`$wpdb`), respecting table prefixes. + - Delete plugin-owned options, metadata, and scheduled tasks. + - Ensure cleanup is scoped only to this plugin’s data. + +## Safety and Recovery +Requirements: + +- Uninstall must never affect WordPress core tables or other plugins’ data. +- Destructive cleanup must require explicit administrator intent through the retention setting. +- Operational runbooks should include pre-uninstall backup guidance. +- If uninstall cleanup partially fails, failures must be detectable (admin notice/log entry) for manual remediation. + +## Verification Requirements +Production lifecycle acceptance should verify: + +- Fresh install creates required tables/options. +- Re-activation is idempotent. +- Upgrade runs required migrations without data loss. +- Uninstall with “keep data” preserves plugin tables/data. +- Uninstall with “remove data” removes plugin-owned tables/options/scheduled tasks. + +## Documentation Requirements +The following must be documented in project docs: + +- Production installation steps +- Required server permissions +- Configuration options and defaults +- Upgrade/migration behavior +- Uninstall procedure with keep/remove data choice +- Recovery procedure for failed migration or uninstall cleanup diff --git a/requirements/local-database.md b/requirements/local-database.md new file mode 100644 index 0000000..e46d52e --- /dev/null +++ b/requirements/local-database.md @@ -0,0 +1,86 @@ +# Local Database Requirements (Testing) + +## Purpose +Define local database requirements for reliable, repeatable testing of the calendar plugin. + +## Scope +This document applies to local development and automated test execution using the local compatibility harness. + +It covers: + +- Local test database setup +- Schema creation and migration behavior +- Test data seeding and isolation +- Reset/cleanup requirements between test runs + +## Environment Assumptions +- Database runs locally or in a local containerized service. +- Test database is separate from production/staging databases. +- All plugin date/time values are treated as `Europe/London` unless a test explicitly validates timezone conversion behavior. + +## Database Access Requirements +- Tests must connect using non-production credentials. +- Credentials are provided via local environment configuration (e.g., `credentials/.env`) and must not be hardcoded in source. +- Test runner/harness must fail fast with clear errors if DB connectivity is unavailable. + +## Schema Requirements +- Plugin schema must be created using WordPress APIs used in runtime lifecycle (`$wpdb`, `dbDelta()`), not ad-hoc raw SQL scripts as the primary path. +- Schema initialization for tests must be idempotent. +- Schema versioning must be testable (fresh install and upgrade path). + +## Required Data Coverage +Seed test data must include: + +- Single non-recurring event +- All-day event +- Daily recurrence +- Weekly recurrence +- Monthly recurrence +- Yearly recurrence +- Custom recurrence with: + - No end date + - Count-limited (`create appointments`) + - Date-limited (`repeat until `) +- Edge cases: + - Month-end boundaries + - Leap-year behavior + - DST boundary behavior for `Europe/London` + +## Isolation and Determinism +- Each test run must be deterministic. +- Test cases must not depend on leftover data from previous runs. +- Harness must provide one of: + - Full DB reset before suite/test group, or + - Transactional rollback strategy. + +## Reset and Cleanup Requirements +- A documented command/process must reset local test DB to a known baseline. +- Cleanup must remove plugin test data and temporary artifacts. +- Cleanup must not affect non-test databases. + +## Performance Expectations +- Local test database setup + seed should complete fast enough for iterative development. +- Query performance should be sufficient for day/week/month/year view tests without timeouts under expected local data volumes. + +## Security Requirements +- No real production data may be used in local tests. +- Sensitive values (passwords, hostnames) must be stored outside committed requirement docs and source. +- Example values in docs must be clearly dummy placeholders. + +## Validation Requirements +Test workflow must validate: + +- Successful schema creation from clean state +- Successful migration from older schema version(s) +- CRUD correctness +- Recurrence expansion correctness +- Correct behavior of uninstall keep/remove data options +- ICS field mapping readiness based on stored data + +## Documentation Requirements +Project docs must describe: + +- Local DB engine/version prerequisites +- Connection settings required for compatibility harness +- Commands for setup, seed, reset, and teardown +- Troubleshooting steps for common DB issues in local testing diff --git a/requirements/observability.md b/requirements/observability.md new file mode 100644 index 0000000..c418041 --- /dev/null +++ b/requirements/observability.md @@ -0,0 +1,78 @@ +# Observability Requirements + +## Purpose +Define logging, auditability, and operational visibility requirements for calendar, user access, API, CalDAV, and ICS behavior. + +## Scope +This document covers: + +- Operational logs +- Audit logs for privileged actions +- Metrics and health signals +- Retention and privacy controls + +## Logging Principles +- Logs must support troubleshooting and security review. +- Log format should be structured where practical. +- Sensitive values must be redacted. +- Payload capture should be bounded and summarized to avoid oversized/noisy traces (for example large HTML responses). + +## Required Operational Logs +Must log: + +- Plugin startup/activation and migration outcomes +- API/CalDAV/ICS request summaries with status and latency +- Error events (`5xx`, auth failures, precondition conflicts where relevant) +- Email workflow outcomes (verification/reset/admin-approval notifications) + +Recommended fields: + +- timestamp +- request id/correlation id +- actor type/id (if available) +- endpoint/action +- status code/outcome +- latency + +## Required Audit Logs +Must audit: + +- Event create/update/delete actions from admin/API +- Single-occurrence delete/edit exception actions +- CalDAV user state transitions (`pending_approval`, `active`) +- Admin approval/removal actions for CalDAV users +- Setup/configuration changes + +Audit entry minimum fields: + +- actor identity +- action +- target identity +- prior state (where applicable) +- resulting state +- timestamp + +## Health and Metrics +At minimum expose/log: + +- request counts by surface (admin/api/caldav/ics) +- error rates by category +- average and p95 latency for key endpoints +- queue/dispatch failures for email workflows + +## Retention and Access +- Log retention duration must be documented in operational docs. +- Access to detailed logs should be restricted to authorized operators/admins. +- Audit logs should be tamper-evident by process and protected from casual deletion. +- Fixture admin diagnostics UI may expose only a recent bounded window (for example last 20 entries) for quick troubleshooting. + +## Privacy and Compliance +- Do not log passwords, token raw values, or full credential headers. +- PII in logs should be minimized to necessary operational scope. + +## Verification Requirements +Acceptance should verify: + +- Required events produce expected logs/audit records. +- Security-sensitive data is redacted. +- Operators can trace a failed user flow end-to-end using request/audit identifiers. diff --git a/requirements/packaging.md b/requirements/packaging.md new file mode 100644 index 0000000..4cb9956 --- /dev/null +++ b/requirements/packaging.md @@ -0,0 +1,100 @@ +# Plugin Packaging Requirements + +## Purpose +Define the required process for creating a production-ready WordPress plugin package artifact from this repository. + +## Scope +This document covers: + +- Preparing package contents from repository sources +- Building a distributable archive +- Validating the archive before deployment + +It does not cover: + +- Production installation steps +- Runtime plugin behavior + +Deployment execution and remote exact-match validation are defined in `requirements/deployment.md`. +Runtime/code separation constraints are defined in `requirements/architecture.md`. + +## Source and Output Locations +- Source code root: `code/` +- Packaging workspace/output: `package/` +- Tests used for pre-package validation: `tests/` + +## Packaging Preconditions +Before creating a package: + +- Relevant tests pass in the local compatibility harness. +- Plugin version for release is set in plugin metadata/files. +- Working tree contains intended release changes only. +- Required build tools (`zip` or equivalent) are available. + +## Package Content Rules +The package must include only files required to run the plugin in WordPress. + +Required inclusions: + +- Main plugin file and all runtime PHP source under `code/` +- Runtime assets (CSS/JS/images) required by plugin features +- Any required vendor/runtime dependencies needed in production +- License/readme files required by project policy + +Required exclusions: + +- `tests/`, `compatibility-layer/`, and local development-only assets +- Temporary files, editor files, OS metadata files +- Build scripts and internal notes not needed at runtime +- Secrets, credentials, or environment-specific private data + +## Package Structure Requirements +- The archive must expand into a single plugin directory. +- The plugin directory name must be stable and suitable for WordPress plugin installation. +- Directory structure inside the package must preserve runtime-relative paths expected by plugin code. + +## Build Procedure Requirements +Packaging process must be deterministic and repeatable. + +Required steps: + +1. Create/clean a staging folder under `package/`. +2. Copy approved runtime files from `code/` into staging. +3. Apply exclusion rules to remove non-runtime artifacts. +4. Create a versioned zip archive in `package/`. +5. Record artifact name and version in release notes/changelog. + +Artifact naming requirement: + +- Use a versioned filename pattern, for example: `-.zip` + +## Validation Requirements +Before the artifact is accepted: + +- Archive can be opened successfully. +- Archive root contains exactly one plugin folder. +- Main plugin entry file exists at expected location. +- No excluded directories/files are present. +- No files from `compatibility-layer/` are present. +- Plugin activates successfully in local compatibility-harness/test environment. + +## Integrity and Traceability +- Each package build must be traceable to a source revision/tag. +- Build date/time and source revision should be recorded with the artifact. +- Rebuilding from the same revision should produce functionally equivalent contents. + +## Failure Handling +If packaging fails validation: + +- Artifact must not be promoted to test or production. +- Failures must be documented with cause and remediation. +- Packaging is re-run only after corrective changes are applied. + +## Documentation Requirements +Project docs must include: + +- Exact packaging command(s) used +- Exclusion/inclusion rules +- Artifact naming convention +- Validation checklist +- Location of produced archives diff --git a/requirements/plugin_description.md b/requirements/plugin_description.md new file mode 100644 index 0000000..bfc46e1 --- /dev/null +++ b/requirements/plugin_description.md @@ -0,0 +1,13 @@ +# Plugin Description Requirement + +## Purpose +Define the WordPress plugin Description text shown on the installed plugins screen. + +## Required Description Text +The plugin description must clearly summarize the feature set in about 50 words: + +"Provides a single shared calendar for WordPress with public display, authenticated event editing, user approval workflow, ICS publishing, and CalDAV read/write sync. Supports recurring events, single-occurrence exceptions, admin setup and diagnostics pages, and shortcode rendering for full calendar and upcoming-events sidebar views." + +## Acceptance +- The description appears in the plugin header metadata (`Description:`) and is visible in `wp-admin` Plugins list. +- The text remains concise (target ~50 words) and reflects current capabilities. diff --git a/requirements/recurrence_exceptions.md b/requirements/recurrence_exceptions.md new file mode 100644 index 0000000..bacce61 --- /dev/null +++ b/requirements/recurrence_exceptions.md @@ -0,0 +1,76 @@ +# Recurrence Exception Requirements + +## Purpose +Define normative behavior for recurring-event exception operations across admin UI, API, CalDAV, and ICS. + +## Scope +This document covers: + +- Single-occurrence delete and edit semantics +- Exception storage model +- Merge/precedence rules for recurrence expansion +- Cross-surface consistency requirements + +## Core Principle +- A recurring event is one logical series keyed by stable series identity (`event_id`/`UID`). +- Exception operations modify occurrence behavior within that series. +- Exception operations must not split one series into multiple independent series unless explicitly invoked by a future "split series" feature. + +## Exception Types +- `deleted_occurrence`: one generated occurrence is suppressed. +- `override_occurrence`: one generated occurrence is replaced with modified instance values. + +## Delete-One-Occurrence Semantics +When a user deletes one occurrence of a recurring series: + +- Add `deleted_occurrence` exception for the target occurrence key. +- Keep original series RRULE/repeat definition unchanged. +- Do not create a second series record. +- Future edits to series must still consider existing exceptions. + +## Edit-One-Occurrence Semantics +When a user edits one occurrence in a recurring series: + +- Add/update `override_occurrence` exception for the target occurrence key. +- Override payload contains changed fields for that occurrence. +- Parent series remains the source for non-overridden fields. + +## Expansion Precedence Rules +Occurrence generation order: + +1. Generate base occurrences from series rule. +2. Remove occurrences matching `deleted_occurrence`. +3. Apply `override_occurrence` records to matching occurrence keys. +4. Return sorted final set for window/query. + +If both delete and override exist for the same occurrence key, the system must reject the conflicting write and require explicit conflict resolution. + +## Validation Rules +- Exception target must correspond to a valid generated occurrence in series context. +- Duplicate exception entries for same occurrence key are not allowed. +- Exception operations on non-recurring events are rejected. + +## CalDAV Representation +- Series remains same `UID`. +- Deleted occurrence represented with `EXDATE` and/or standards-compliant override pattern. +- Overridden occurrence represented with `RECURRENCE-ID` VEVENT override. +- Do not emit split UID sequences for single-occurrence deletes. + +## ICS Representation +- Export includes base RRULE for series. +- Deleted occurrences exported as exceptions. +- Overridden occurrences exported as recurrence-id overrides. +- Consumer synchronization should observe missing/changed occurrence without series duplication. + +## UI/API Behavior +- UI and API must expose "this occurrence only" actions distinctly from "entire series" actions. +- Confirmation messaging must clearly indicate operation scope. +- Audit records must capture actor, series, target occurrence key, and operation type. + +## Verification Requirements +Acceptance should verify: + +- Delete-one-occurrence removes only target occurrence. +- Series remains unified in storage and output. +- Override-one-occurrence updates only target occurrence. +- CalDAV/ICS outputs reflect same exception semantics as admin/API views. diff --git a/requirements/settings.md b/requirements/settings.md new file mode 100644 index 0000000..fc42c55 --- /dev/null +++ b/requirements/settings.md @@ -0,0 +1,67 @@ +# Plugin Settings Requirements + +## Purpose +Define implemented setup/settings and diagnostics behavior for the calendar plugin admin pages. + +## Scope +This document covers: + +- Admin menu structure for plugin management pages +- Setup page controls and persisted settings +- Diagnostics page and diagnostics download behavior + +## Admin Navigation +The plugin must provide a single WordPress admin menu entry with these pages: + +- Top-level menu: `Calendar Plugin` +- Sub-pages: `Setup`, `Users`, `Diagnostics` +- Access capability: `manage_options` + +The top-level duplicate submenu entry should be removed so only the explicit pages appear in navigation. + +## Setup Page +The Setup page must support: + +- Saving plugin settings +- Seeding default events +- Deleting all event and recurrence-exception data + +Setup form controls: + +- `CalDAV/ICS Calendar Name` (`caldav_calendar_name`) +- `URL Slug Prefix` (`url_slug`) +- `Verification Page Path` (`verification_page_path`) +- `Plugin Table Prefix` (`table_prefix`) +- `ICS Access Mode` (`ics_access_mode`: `public_read` or `authenticated_read`) +- `Diagnostics Enabled` (`diagnostics_enabled`) +- `Uninstall Cleanup` (`uninstall_cleanup_mode`: `keep` or `remove`) + +Behavior requirements: + +- Save, seed, and delete-all actions provide success/failure messaging on the Setup page. +- Setup POST actions are nonce-protected. +- `table_prefix` updates rename plugin tables (events, exceptions, users, tokens, audit log) without dropping existing data. +- Prefix changes must fail safely if target tables already exist. +- `url_slug` affects canonical routed paths for ICS and CalDAV endpoints. + +## Settings Persistence +- Settings are persisted through plugin options storage and returned by the admin settings API (`/wp-json/calendar/v1/settings`). +- Runtime endpoints and UI links must reflect the latest saved settings. + +## Diagnostics Page +The Diagnostics page must be admin-only and show: + +- Whether diagnostics are enabled +- Runtime metadata snapshot (generation time, endpoint paths, user context, table stem) +- Recent audit log rows when diagnostics are enabled + +Diagnostics download behavior: + +- Download action is available only when `diagnostics_enabled=1` +- Download uses a nonce-protected admin-post action +- Response is JSON attachment (`calendar-diagnostics-*.json`) + +## Security and Access +- Setup, Users, and Diagnostics pages require `manage_options`. +- Form actions must validate WordPress nonces. +- Inputs must be sanitized/normalized before persistence. diff --git a/requirements/template.md b/requirements/template.md new file mode 100644 index 0000000..4cc59bf --- /dev/null +++ b/requirements/template.md @@ -0,0 +1,125 @@ +# + +## Purpose +Describe why this requirement document exists and what decision or behavior it defines. + +## Scope +Define what is included and excluded. + +Included: + +- +- + +Excluded: + +- +- + +## Definitions +Define project-specific terms used in this document. + +- ``: +- ``: + +## Stakeholders +List roles responsible for implementation, approval, and operations. + +- Product/Owner: +- Engineering: +- Operations: +- QA/Test: + +## Preconditions +State assumptions that must be true before this requirement applies. + +- +- + +## Requirements +List clear, testable requirements. + +### Functional Requirements +- FR-001: +- FR-002: + +### Non-Functional Requirements +- NFR-001: +- NFR-002: + +### Constraints +- C-001: +- C-002: + +## Data Requirements +Describe data model, persistence, retention, and migration expectations. + +- +- + +## Configuration Requirements +Describe configurable settings, defaults, validation, and permissions. + +- +- + +## Error Handling and Recovery +Define expected failure behavior and recovery paths. + +- +- + +## Security and Access +Define capability checks, secret handling, and least-privilege requirements. + +- +- + +## Operational Requirements +Describe deployment, monitoring, maintenance, and rollback expectations. + +- +- + +## Verification and Acceptance +Define how compliance will be validated. + +### Test Cases +- TC-001: +- TC-002: + +### Acceptance Criteria +- AC-001: +- AC-002: + +## Dependencies +List dependencies on systems, services, plugins, APIs, or teams. + +- +- + +## Risks and Mitigations +Document known risks and planned mitigations. + +- Risk: + Mitigation: +- Risk: + Mitigation: + +## Open Questions +Track unresolved decisions. + +- OQ-001: +- OQ-002: + +## Change Log +Record meaningful revisions to the requirement. + +- - - +- - - + +## References +Link related requirement, documentation, test, or implementation files. + +- +- diff --git a/requirements/test_strategy.md b/requirements/test_strategy.md new file mode 100644 index 0000000..e0c04fe --- /dev/null +++ b/requirements/test_strategy.md @@ -0,0 +1,84 @@ +# Test Strategy Requirements + +## Purpose +Define mandatory test execution policy, suite composition, and quality gates for preventing regressions. + +## Scope +This document covers: + +- Test levels and ownership +- Required suites by pipeline stage +- Compatibility-harness isolation/reset strategy +- Pass/fail and release gates + +## Test Levels +- Unit tests: pure logic and transformation behavior +- Integration tests: plugin + compatibility-harness DB/APIs +- End-to-end tests: UI/API/CalDAV/ICS end-to-end flows +- Smoke tests: fast high-signal regression checks + +Reference documents: + +- `tests/calendar_entries.md` +- `tests/e2e_test_cases.md` +- `tests/api_test_cases.md` +- `tests/smoke_tests.md` +- `tests/lifecycle_test_cases.md` +- `tests/user_ui_caldav_1hr_spec.md` +- `requirements/architecture.md` + +## Execution Policy + +### Per Commit / PR +Must run: + +- lint/static checks (when available) +- smoke suite +- changed-area unit/integration tests + +### Nightly +Must run: + +- full unit + integration suite +- full E2E suite +- full API suite (if API enabled) +- CalDAV and ICS interoperability checks + +### Pre-Release +Must run: + +- full nightly suite +- upgrade/migration tests +- rollback sanity checks +- packaging/install/uninstall validation +- 60-minute user functional sweep (UI + CalDAV) using `tests/user_ui_caldav_1hr_spec.md` + +## Harness Determinism +- Tests must run against isolated compatibility-harness-managed state. +- Reset to known baseline before suite groups. +- No test may depend on leftover state from prior runs. +- Default CI runs must not depend on external systems. + +## Flake Management +- Flaky tests must be tagged and tracked with issue references. +- Repeatedly flaky tests cannot remain in required gates without mitigation. + +## Coverage Expectations +- CRUD flows for single and recurring events are mandatory coverage. +- Recurrence exception behavior (single occurrence delete without split) is mandatory coverage. +- Authn/Authz paths for admin, API, and CalDAV roles are mandatory coverage. +- Error-model contract coverage is mandatory for API endpoints. + +## Pass/Fail Gates +- Any required suite failure blocks merge/release as applicable. +- Failures must capture logs/artifacts sufficient for debugging. + +## Reporting +- CI should publish suite summary with: + - passed/failed/skipped counts + - runtime + - links to failure artifacts + +## Change Management +- New features must include updated tests and requirement-traceable cases. +- Requirement updates that change behavior must update corresponding test docs and automation. diff --git a/requirements/web-ui.md b/requirements/web-ui.md new file mode 100644 index 0000000..c4f5589 --- /dev/null +++ b/requirements/web-ui.md @@ -0,0 +1,151 @@ +# Web UI Requirements + +## Purpose +Define requirements for the public-facing calendar web UI rendered by the plugin. + +## Scope +This document covers: + +- Shortcode-based rendering of calendar UI content +- View modes for event presentation +- Interactive event create/edit flows in the web UI for authorized users +- Public ICS subscription/export link behavior +- User login/registration/verification entry points used by the calendar page + +## Shortcode Integration +The plugin must export a shortcode that renders the calendar web UI into page/post content. + +Requirements: + +- A plugin shortcode is registered and publicly documented (see `requirements/settings.md`). +- Rendering the shortcode generates HTML output representing calendar data. +- Shortcode output is safe for inclusion in standard WordPress pages/posts. +- Shortcode rendering must gracefully handle empty/no-event states. + +## UI Views +The web UI must support selectable calendar views: + +- Event list view +- Day view +- Week view +- Month view +- Year view + +Behavior requirements: + +- Users can switch between supported views from within the UI. +- View selection updates displayed event data accordingly. +- Current view state is visually clear. +- If no events match the current window/filter, show a clear empty-state message. + +### Graphical View Requirements +- `week` view must render seven day columns. +- Timed events in `week` view must be positioned vertically by start/end time. +- All-day events in `week` view must appear at the top of each day column. +- `week` view must include a visible time scale on the left. +- Overlapping timed events in `week` view must remain readable (side-by-side or equivalent non-obscuring layout). +- `month` view must render a day-of-week grid where each cell represents one date in the month window. +- `month` view cells must list that day’s events in start-time order. +- `year` view must render twelve month blocks in a grid. +- Each year-view month block must render day cells in day-of-week order. +- In `year` view, day numbers with one or more events must be visually emphasized (for example bold text). +- Clicking a day in `year` view must switch to `week` view anchored to a week that includes the selected date. +- In `week` and `month` views, event display text must include start time before title for timed events. + +### Display Filters +- UI must provide a `future dates only` filter control that limits display to today or later. +- In the current UX model, `future dates only` is visible and active only in list mode. + +### Theme Selector +- UI must provide a theme selector with predefined visual themes. +- Theme switching updates shell/panel/list styling without reloading the page. + +### Date Navigation Controls +- UI must provide previous and next navigation controls (left/right arrows) around a `today` control/icon. +- Previous/next controls shift the anchor date by one unit based on current view mode: +- Day/List: +/- 1 day +- Week: +/- 1 week +- Month: +/- 1 month +- Year: +/- 1 year +- UI must expose a single `Date` picker control. +- Arrow and `today` actions update `Date` to the first date of the current period for the selected view. +- Choosing a date must select the enclosing week/month/year period for those views, or the exact day in day view. + +## Data Display Requirements +For each displayed event, UI should present core event information suitable for end users. + +Minimum display requirements: + +- Title +- Start date/time (or all-day presentation) +- End date/time where applicable +- Location (if provided) +- Category (if provided) +- Description excerpt/summary (if configured for display) + +## Web Event Editing UX (Authorized Users) +Where the viewer has write permission, the web UI must support event creation/editing. + +Requirements: + +- Create and edit actions open in an overlay/modal form. +- Start and end are entered with separate date and time controls. +- Date controls must use date-picker inputs. +- Irrelevant recurrence fields must be hidden based on current recurrence selections. +- Form controls use persistent inline/floating labels so field purpose remains visible after input. +- Event ID must not be shown in the UI. +- `location` and `category` should be rendered on one row. +- `start date/time` and `end date/time` should be rendered on one row. +- `repeat type` and `every` interval should be rendered on one row. +- `range mode` and `repeat until` should be rendered on one row. +- Recurrence interval label text uses `Every` and includes a readable interval summary (for example `1 week`, `2 weeks`). +- The readable interval unit text should be integrated into the same interval control rather than a separate display-only field. +- Monthly recurrence must support both `nth day of month` and `nth weekday of month` patterns. +- A `Delete a Single Occurrence` action must be available when editing recurring events. +- `Delete Occurrence` may be shown inline with main modal action buttons. +- Single-occurrence controls appear whenever repeat type is not `none`. +- Single-occurrence controls must remain hidden when repeat type is `none`. +- Single-occurrence selection must be user-friendly via a date list or compact calendar selection. +- The compact selection mode should support a 3-month grid with previous/next navigation. +- The compact selection grid must remain readable in modal layout. + +## Login and Access Modes +- `/calendar` must support two user modes: public (not logged in) and logged-in. +- Public mode is read-only. +- A `Login` action must open an in-page dialog. +- Login dialog must support credential login. +- Login dialog must support registration (registration is a request for approval/write access). +- Login dialog must support email verification token submission. +- Login dialog must support password-reset request initiation. +- Logged-in but non-approved users remain read-only. +- Approved users can perform event CRUD. + +## ICS Link in Web UI +The UI must include a user-visible link to an ICS representation of calendar data. + +Requirements: + +- A link/button is presented in the web UI for calendar subscription/export. +- Link target returns `text/calendar` output suitable for device calendar subscription/import. +- Link should be stable enough for user bookmarking/subscription where feasible. +- Canonical endpoint path and access policy must follow `requirements/ics_endpoint.md`. +- Link behavior is documented for users/admins in project docs. + +## Accessibility and UX +- Controls for view selection must be keyboard-accessible. +- Output should be readable across desktop and mobile screen sizes. +- Time/date presentation should follow site locale/timezone behavior defined by plugin settings. +- Action buttons should be disabled (visibly greyed) when required input/permissions are not present. + +## Security and Performance +- Shortcode rendering must sanitize/escape output appropriately. +- Querying calendar data for UI views should be efficient for expected dataset sizes. +- Public UI endpoints and ICS link handling must avoid leaking non-public admin-only data. + +## Verification Requirements +Acceptance should verify: + +- Shortcode renders without errors on standard WordPress pages. +- All five view modes are selectable and render expected event windows. +- Empty-state behavior is clear and user-friendly. +- ICS link is present and returns valid calendar payload. diff --git a/tests/api_test_cases.md b/tests/api_test_cases.md new file mode 100644 index 0000000..03a977b --- /dev/null +++ b/tests/api_test_cases.md @@ -0,0 +1,170 @@ +# API Test Cases + +## Purpose +Define detailed API tests if an HTTP API is exposed for calendar/admin/caldav-support operations. + +If no API is exposed beyond CalDAV and ICS endpoints, this file remains as the contract for future API introduction and should be marked `not-applicable` in CI. + +## Assumed Endpoint Groups +- Admin calendar entries API (example: `/api/calendar/events`) +- Admin users/access API (example: `/api/calendar/users`) +- ICS endpoint (example: `/calendar.ics` or `/api/calendar/ics`) +- CalDAV endpoint (separately tested in E2E/CalDAV suites) + +Adjust paths to actual implementation while preserving case coverage. + +## Common Assertions +- AuthN/AuthZ enforced for each endpoint. +- Validation errors return structured error payloads. +- Deterministic timezone behavior (`Europe/London` default). +- No sensitive internals leaked in responses. + +## Event API Tests + +### API-EVT-001 Create Valid Event +- Fixture: CE-001 +- Method: `POST /api/calendar/events` +- Assertions: + - `201` created. + - Response includes stable id and canonicalized fields. + - Event visible in admin UI and public UI after create. + +### API-EVT-002 Create Invalid Event +- Payload: missing `title` or end before start +- Assertions: + - `400`/`422` validation failure. + - Actionable field-level errors returned. + - No partial data persisted. + +### API-EVT-002b Unauthorized Event Write +- Method: `POST /wp-json/calendar/v1/events` without plugin or WordPress write auth +- Assertions: + - Request is denied (`403`). + - No event record is persisted. + +### API-EVT-003 Read/List with Filters +- Fixtures: CE-002..CE-007 +- Method: `GET /api/calendar/events?from=...&to=...&view=month` +- Assertions: + - Date-window filtering includes expected occurrences. + - Pagination/sorting (if present) stable and documented. + +### API-EVT-004 Update with Concurrency +- Fixture: CE-001 +- Method: `PUT/PATCH /api/calendar/events/{id}` +- Assertions: + - Valid update returns success and modified timestamp/version. + - Stale ETag/version returns precondition/conflict. + - No silent overwrite on concurrent edits. + +### API-EVT-005 Delete Single Event +- Fixture: CE-001 +- Method: `DELETE /api/calendar/events/{id}` +- Assertions: + - Delete succeeds with expected status. + - Event absent from list, UI, and ICS. + +### API-EVT-006 Delete One Recurrence Occurrence +- Fixture: CE-010 +- Method: `DELETE /api/calendar/events/{id}/occurrences/{occurrence_key}` (or equivalent) +- Assertions: + - Operation creates exception record. + - Remaining series unchanged. + - No split-series records created. + +### API-EVT-007 Preview Occurrences (Valid + Invalid) +- Method: `POST /wp-json/calendar/v1/events/preview-occurrences` +- Assertions: + - Valid recurrence payload returns computed preview items in requested window. + - Invalid range (`end < start`) returns `422`. + - Preview endpoint is non-destructive (does not create records). + +### API-EVT-008 Monthly Ordinal Recurrence Parity +- Method: `POST /wp-json/calendar/v1/events` + `GET /events/{id}/occurrences` +- Assertions: + - `repeat_nth_mode=weekday_of_month` with `repeat_nth_pos=-1` and weekday set maps to expected monthly dates. + - Saved ordinal recurrence reloads without drift. + - Series stays single UID-based sequence. + +## User/Access API Tests + +### API-USR-001 Register User +- Method: `POST /api/calendar/users/register` +- Assertions: + - Account created as `pending_approval`. + - Verification email dispatch recorded. + +### API-USR-002 Verify Email +- Method: tokenized verification endpoint +- Assertions: + - Valid token marks email verified while user remains `pending_approval`. + - Reused/expired token fails cleanly. + +### API-USR-003 Password Recovery +- Methods: forgot + reset endpoints +- Assertions: + - Token issued and reset succeeds once. + - Previous sessions/tokens invalidated. + +### API-USR-004 Admin Approve/Remove +- Method: admin-only action endpoint +- Assertions: + - Admin can set `pending_approval`/`active`. + - Admin can remove defunct users. + - Unauthorized user receives denial. + - Audit log record created. + +### API-USR-005 Remove User Revokes Access +- Method: admin `DELETE /wp-json/calendar/v1/admin/users/{id}` then user login/me checks +- Assertions: + - Removed user can no longer authenticate (`401`). + - Removed user sessions/tokens do not continue to authorize requests. + +## ICS Endpoint API Tests + +### API-ICS-001 Basic Response +- Method: `GET /calendar.ics` +- Assertions: + - `200` status, `text/calendar` content type, UTF-8. + - Valid VCALENDAR envelope. + +### API-ICS-002 Recurrence + Exception Mapping +- Fixture: CE-010 with deleted occurrence exception +- Assertions: + - RRULE is present for series. + - Exception for deleted occurrence is exported. + - No split-series artifact in output. + +### API-ICS-003 Slugged Path Link Contract +- Method: set `url_slug`, render shortcode, inspect rendered links +- Assertions: + - ICS link uses `//calendar.ics`. + - CalDAV link uses `//caldav/`. + - Link path changes match configured slug value. + +## Negative and Security Tests + +### API-SEC-001 Unauthorized Access +- Assertions: + - Unauthenticated requests blocked where required. + - No data leakage in error responses. + +### API-SEC-002 Input Fuzz/Injection +- Assertions: + - Script/SQL-like payloads are rejected or safely encoded. + - No server error or malformed persistence. + +### API-SEC-003 Rate Limit Behavior +- Assertions: + - Login/reset/verification endpoints throttle abusive attempts. + +### API-SET-001 Table Prefix Setting +- Method: `GET/PATCH /wp-json/calendar/v1/settings` (admin context) +- Assertions: + - `table_prefix` is visible in setup/admin workflow and defaults to `wp_cs_calendar`. + - Changing table prefix to another valid value triggers table rename migration. + - Existing data remains accessible after prefix change. + +## Execution Cadence +- Run full API suite on PR and nightly builds. +- Run a trimmed API smoke subset on each commit (see `tests/smoke_tests.md`). diff --git a/tests/calendar_entries.md b/tests/calendar_entries.md new file mode 100644 index 0000000..e93faab --- /dev/null +++ b/tests/calendar_entries.md @@ -0,0 +1,130 @@ +# Calendar Entry Test Fixtures + +## Purpose +Define canonical calendar entries used across admin UI, public UI, CalDAV, ICS, and API tests so scenarios are consistent end-to-end. + +## Global Assumptions +- Timezone baseline: `Europe/London` +- Date format: ISO 8601 in docs, converted as needed by UI/API +- IDs/UIDs are stable in test environments + +## Fixture Set + +### CE-001 Single Timed Event +- Title: `Board Meeting` +- Location: `Room A` +- Category: `Governance` +- Start: `2026-04-01T10:00:00+01:00` +- End: `2026-04-01T11:30:00+01:00` +- Repeat: `none` +- Description: `Quarterly board review.` + +### CE-002 All-Day Event +- Title: `Office Closed` +- Location: `HQ` +- Category: `Operations` +- Start: `2026-05-04` (all day) +- End: `2026-05-05` (exclusive end for all-day semantics) +- Repeat: `none` +- Description: `Public holiday closure.` + +### CE-003 Daily Recurrence (Count-Limited) +- Title: `Daily Standup` +- Location: `Online` +- Category: `Team` +- Start: `2026-04-06T09:00:00+01:00` +- End: `2026-04-06T09:15:00+01:00` +- Repeat: `daily` +- Custom interval: `1` +- Range mode: `count` +- Count: `10` +- Description: `15 minute sync.` + +### CE-004 Weekly Recurrence (No End) +- Title: `Community Lunch` +- Location: `Cafeteria` +- Category: `Community` +- Start: `2026-04-08T12:30:00+01:00` +- End: `2026-04-08T13:30:00+01:00` +- Repeat: `weekly` +- Custom interval: `1` +- Range mode: `no_end` +- Description: `Weekly community lunch.` + +### CE-005 Monthly Recurrence (Until Date) +- Title: `Finance Close` +- Location: `Finance Office` +- Category: `Finance` +- Start: `2026-04-30T17:00:00+01:00` +- End: `2026-04-30T18:00:00+01:00` +- Repeat: `monthly` +- Custom interval: `1` +- Range mode: `until` +- Until: `2026-08-31` +- Description: `Month-end close process.` + +### CE-006 Yearly Recurrence +- Title: `Annual Conference` +- Location: `Main Hall` +- Category: `Events` +- Start: `2026-06-15T10:00:00+01:00` +- End: `2026-06-15T17:00:00+01:00` +- Repeat: `yearly` +- Custom interval: `1` +- Range mode: `count` +- Count: `3` +- Description: `Annual community conference.` + +### CE-007 Custom Every 2 Weeks (Until Date) +- Title: `Fortnightly Coaching` +- Location: `Online` +- Category: `Training` +- Start: `2026-04-07T15:00:00+01:00` +- End: `2026-04-07T16:00:00+01:00` +- Repeat: `custom` +- Base unit: `weekly` +- Interval: `2` +- Range mode: `until` +- Until: `2026-07-31` +- Description: `Coaching check-in.` + +### CE-008 DST Boundary Event +- Title: `DST Validation Event` +- Location: `Lab` +- Category: `QA` +- Start: `2026-10-25T00:30:00+01:00` +- End: `2026-10-25T02:30:00+00:00` +- Repeat: `none` +- Description: `Validates DST transition rendering.` + +### CE-009 Leap-Year Annual Event +- Title: `Leap Day Marker` +- Location: `Calendar` +- Category: `QA` +- Start: `2028-02-29T09:00:00+00:00` +- End: `2028-02-29T10:00:00+00:00` +- Repeat: `yearly` +- Custom interval: `1` +- Range mode: `count` +- Count: `3` +- Description: `Leap day recurrence behavior.` + +### CE-010 Recurring Event with Exception Delete +- Title: `Therapy Session` +- Location: `Clinic` +- Category: `Health` +- Start: `2026-04-03T14:00:00+01:00` +- End: `2026-04-03T15:00:00+01:00` +- Repeat: `weekly` +- Custom interval: `1` +- Range mode: `count` +- Count: `8` +- Description: `Used for single-occurrence delete exception tests.` +- Exception target occurrence: `2026-04-17T14:00:00+01:00` (delete this occurrence only) + +## Core Assertions by Fixture +- CE-001/CE-002 validate single-event CRUD, all-day handling, UI display. +- CE-003..CE-007 validate recurrence creation, expansion, update, and persistence. +- CE-008 validates DST render/sync behavior. +- CE-009 validates leap-year recurrence behavior. +- CE-010 validates recurrence exception behavior (no series split). diff --git a/tests/e2e_test_cases.md b/tests/e2e_test_cases.md new file mode 100644 index 0000000..1885ae4 --- /dev/null +++ b/tests/e2e_test_cases.md @@ -0,0 +1,232 @@ +# End-to-End Test Cases + +## Purpose +Define end-to-end scenarios that exercise: +- Admin entry CRUD +- Public/user display +- CalDAV CRUD +- Display/sync behavior from ICS + +This suite uses fixtures in `tests/calendar_entries.md`. + +## Pre-Run Setup +- Reset DB to known baseline. +- Seed users: + - `admin_user` with calendar admin capability + - `pending_user` with CalDAV `pending_approval` + - `rw_user` with CalDAV `active` +- Enable plugin and ensure admin menu with `Users`, `Setup`, `Diagnostics`. + +## Admin Entry CRUD + +### E2E-ADM-001 Create Single Event +- Fixture: CE-001 +- Steps: + 1. Admin logs in. + 2. Open `/calendar` as a write-enabled user. + 3. Create event using CE-001. + 4. Save. +- Assertions: + - Success feedback shown. + - Event appears in admin list. + - Event appears in public list/day/week/month/year views. + +### E2E-ADM-002 Update Single Event +- Fixture: CE-001 +- Steps: + 1. Open CE-001 in editor. + 2. Change title to `Board Meeting (Updated)` and location. + 3. Save. +- Assertions: + - Updated values shown in admin list and public views. + - Last-modified metadata changes. + +### E2E-ADM-003 Delete Single Event +- Fixture: CE-001 +- Steps: + 1. Delete CE-001 from admin list and confirm. +- Assertions: + - Event removed from admin list. + - Event removed from public views. + - Event removed from ICS and CalDAV query results. + +### E2E-ADM-004 Create Recurrence Types +- Fixtures: CE-003, CE-004, CE-005, CE-006, CE-007 +- Steps: + 1. Create each recurring event via editor. + 2. Re-open each and verify saved recurrence config. +- Assertions: + - Persisted values match fixture. + - Preview highlights expected near-term occurrences. + - UI views render expected occurrences. + +### E2E-ADM-005 Delete One Occurrence as Exception +- Fixture: CE-010 +- Steps: + 1. Create CE-010. + 2. Delete only occurrence `2026-04-17T14:00:00+01:00`. + 3. Reload series in editor and public views. +- Assertions: + - Only selected occurrence is removed. + - Series remains a single sequence (not split). + - Exception is represented in sync outputs (CalDAV/ICS rules). + +### E2E-ADM-006 Table Prefix Configuration +- Steps: + 1. Open Setup page. + 2. Verify plugin table prefix defaults to `wp_cs_calendar`. + 3. Change prefix to a valid alternative (staging only) and save. + 4. Reload calendar/admin views. +- Assertions: + - Plugin continues to operate without data loss. + - Event/user records remain available after prefix migration. + +### E2E-ADM-007 Setup Title Removal +- Steps: + 1. Open Setup page. + 2. Inspect available controls. + 3. Render a page containing `[calendar]` with a custom page heading in content. +- Assertions: + - Setup does not show a `Calendar Title` field. + - Calendar shortcode does not inject its own top heading. + - Page/theme heading remains the source of title styling. + +### E2E-ADM-008 User Removal Revokes Access +- Steps: + 1. Register, verify, and approve a plugin user. + 2. Confirm login/me works. + 3. Remove the same user from `Users` admin. +- Assertions: + - Subsequent login and `/users/me` fail for removed user. + - Removed user cannot perform event write actions. + +## Public/User Display + +### E2E-UI-001 Cross-View Rendering +- Fixtures: CE-002..CE-007 +- Steps: + 1. Open public calendar UI. + 2. Switch list/day/week/month/year views. +- Assertions: + - Required fields render correctly (title/time/location/category/description excerpt). + - All-day semantics for CE-002 are correct. + - Empty-state messaging works when date window has no matches. + +### E2E-UI-002 DST and Leap-Year Display +- Fixtures: CE-008, CE-009 +- Steps: + 1. Navigate to date windows covering fixture dates. +- Assertions: + - CE-008 shows expected local times around DST boundary. + - CE-009 recurrence behavior is correct for non-leap years. + +### E2E-UI-003 Calendar Shell Control Presence +- Steps: + 1. Render `[calendar]` shortcode. +- Assertions: + - Login/register/verify/reset controls are present. + - Future-only filter and theme selector controls are present. + - Recurrence occurrence-delete controls are present in editor shell markup. + +## CalDAV CRUD + +### E2E-CDV-001 Auth Gate (Pending User) +- User: `pending_user` +- Fixtures: CE-002, CE-003 +- Steps: + 1. Connect CalDAV client as `pending_user`. +- Assertions: + - Authentication is denied until admin approval. + +### E2E-CDV-002 Create/Update/Delete (Write User) +- User: `rw_user` +- Fixture: CE-001 as CalDAV-created item +- Steps: + 1. Create event via CalDAV `PUT`. + 2. Modify via CalDAV `PUT` with valid ETag precondition. + 3. Delete via CalDAV `DELETE`. +- Assertions: + - Admin UI reflects CalDAV-created and updated data. + - Public UI reflects changes. + - Deletion propagates to admin/public views. + +### E2E-CDV-003 Recurrence Exception Round-Trip +- User: `rw_user` +- Fixture: CE-010 +- Steps: + 1. Create CE-010 via admin or CalDAV. + 2. Delete a single occurrence from CalDAV client. + 3. Sync and inspect in admin editor. +- Assertions: + - Exception is present for deleted occurrence. + - Series stays unified (single UID sequence). + - No split-series artifact created. + +### E2E-CDV-004 CalDAV Single-Occurrence Delete Sync Persistence +- User: `rw_user` +- Fixture: CE-010 +- Steps: + 1. Create recurring event via UI or CalDAV. + 2. In CalDAV client, delete one occurrence only (client emits `EXDATE` and/or cancelled `RECURRENCE-ID` component). + 3. Force sync/refresh in client and fixture UI. + 4. Restart fixture and refresh again. +- Assertions: + - Deleted occurrence remains deleted after sync cycles and restart. + - Persisted exception exists in fixture data store (`recurrence_exceptions`). + - No duplicate recurrence series is created. + +### E2E-CDV-005 Monthly Ordinal Round-Trip +- User: `rw_user` +- Steps: + 1. Create a monthly ordinal recurring event (`weekday_of_month`, `last`, chosen weekday). + 2. Sync via CalDAV and read back object. + 3. Validate resulting occurrence dates in UI/API. +- Assertions: + - Monthly ordinal rule round-trips without changing meaning. + - Expected target dates (for example last Sunday of month) are preserved. + - No split or duplicate series created. + +## ICS Display and Sync + +### E2E-ICS-001 ICS Link Health and Parsing +- Fixtures: CE-002..CE-007 +- Steps: + 1. Fetch public ICS URL. + 2. Parse payload with validator/parser. +- Assertions: + - HTTP content type is `text/calendar`. + - VCALENDAR envelope is valid. + - VEVENT fields map correctly from fixtures. + +### E2E-ICS-002 Recurrence and Exception Export +- Fixture: CE-010 with deleted occurrence exception +- Steps: + 1. Fetch ICS after exception delete. +- Assertions: + - Recurrence is represented with RRULE. + - Deleted occurrence is represented as exception (EXDATE and/or RECURRENCE-ID pattern). + - No split into independent series. + +### E2E-ICS-003 Consumer Sync +- Fixtures: CE-002, CE-003, CE-010 +- Steps: + 1. Subscribe external test calendar to ICS feed. + 2. Update source entries in admin. + 3. Refresh subscriber. +- Assertions: + - Create/update/delete changes propagate. + - Exception delete for CE-010 propagates as missing occurrence in subscriber. + +### E2E-ICS-004 Slug Path Link Consistency +- Steps: + 1. Set `url_slug` in Setup. + 2. Render `[calendar]`. +- Assertions: + - Rendered ICS and CalDAV links use slugged path. + - Links remain navigable and consistent with configured endpoint prefix. + +## Traceability +- Admin CRUD/lifecycle: E2E-ADM-001..008 +- User display/UI shell: E2E-UI-001..003 +- CalDAV CRUD/sync: E2E-CDV-001..005 +- ICS display/sync/pathing: E2E-ICS-001..004 diff --git a/tests/lifecycle_test_cases.md b/tests/lifecycle_test_cases.md new file mode 100644 index 0000000..772553f --- /dev/null +++ b/tests/lifecycle_test_cases.md @@ -0,0 +1,54 @@ +# Plugin Lifecycle Test Cases + +## Purpose +Verify activation, deactivation, and removal semantics, including table cleanup behavior controlled from Setup (`uninstall_cleanup_mode`). + +## Preconditions +- Run on a disposable/staging WordPress environment. +- Plugin package installed but tests may deactivate/remove plugin. +- Record current value of `calendar_plugin_uninstall_cleanup_mode`. + +## LIF-001 First-Time Activation +- Steps: + 1. Ensure plugin is not active. + 2. Activate plugin. + 3. Open Setup page once. +- Assertions: + - Activation succeeds without fatal errors. + - Required plugin tables exist (`*_events`, `*_recurrence_exceptions`, `*_users`, `*_user_tokens`, `*_audit_log`). + - Default setup values exist, including `uninstall_cleanup_mode=keep`. + +## LIF-002 Deactivation (No Data Loss) +- Steps: + 1. Seed at least one event and one user. + 2. Deactivate plugin. + 3. Re-activate plugin. +- Assertions: + - Deactivation does not drop plugin tables. + - Seeded data remains available after re-activation. + +## LIF-003 Removal With Keep Mode +- Steps: + 1. Set Setup -> Uninstall Cleanup = `keep`. + 2. Remove/delete plugin from WordPress. + 3. Re-install and activate plugin. +- Assertions: + - Plugin-owned tables are preserved. + - Existing plugin data remains. + +## LIF-004 Removal With Remove Mode +- Steps: + 1. Set Setup -> Uninstall Cleanup = `remove`. + 2. Remove/delete plugin from WordPress. + 3. Inspect database. +- Assertions: + - Plugin-owned tables are dropped. + - Plugin-owned options are removed. + - No non-plugin tables are affected. + +## LIF-005 Idempotence/Safety +- Steps: + 1. Run remove flow twice in `remove` mode. +- Assertions: + - No fatal errors on repeated cleanup. + - Cleanup remains scoped to plugin-owned objects only. diff --git a/tests/remote_coverage_review.md b/tests/remote_coverage_review.md new file mode 100644 index 0000000..7d06c8b --- /dev/null +++ b/tests/remote_coverage_review.md @@ -0,0 +1,52 @@ +# Remote Coverage Review (2026-03-31, Updated) + +## Scope +Review current API and E2E test definitions for remote-server-first validation and identify additional high-value coverage. + +## Current Coverage Strengths +- Event CRUD and recurrence exception behavior are covered. +- User lifecycle (register, verify, approve, reset) is covered. +- ICS and CalDAV interoperability paths are covered. +- Security smoke items include token leakage, rate limit, password hashing, and CalDAV resource handling. + +## Coverage Gaps Identified + +### API Gaps +- Unauthorized write-path checks were under-specified for event create/update paths. +- Validation-path checks were missing for: + - invalid event time range (`end < start`) + - invalid `preview-occurrences` payloads +- Single-use token behavior needed an explicit regression check for password reset token reuse. +- Monthly ordinal recurrence parity needed executable checks (last weekday-of-month behavior). +- Slug-path link contract in rendered shortcode output needed explicit assertions. +- Post-removal auth revocation for plugin users needed explicit checks. + +### E2E Gaps +- UI shell control presence checks were incomplete (verify/reset/future/theme/occurrence-delete controls). +- User lifecycle e2e missed explicit “remove user -> auth denied” verification. +- Event deletion e2e missed explicit not-found checks for deleted records. + +## New Coverage Added (This Update) + +### Executable Coverage +- `compatibility-layer/e2e_wp_emulation.php` now includes: + - unauthenticated event create denied (`403`) + - invalid event range returns `422` + - preview-occurrences valid + invalid-path checks + - password reset token single-use check (`422` on reuse) + - remove-user revokes login access + - monthly ordinal recurrence date assertions + - shortcode link assertions for `url_slug` pathing +- `compatibility-layer/ui_e2e_wp_emulation.php` now includes: + - UI shell control presence checks (verify/reset/future/theme/occurrence delete) + - deleted-event not-found check + - remove-user auth denial via `/users/me` + +### Test Spec Updates +- `tests/api_test_cases.md` expanded with API-EVT/API-USR/API-ICS additions for the above gaps. +- `tests/e2e_test_cases.md` expanded with E2E-ADM-008, E2E-UI-003, E2E-CDV-005, E2E-ICS-004. +- `tests/ui_e2e_cases.md` updated to reflect new automated UI cases. + +## Remaining High-Value Gaps +- Diagnostics admin-page/download flow still needs a dedicated authenticated admin-browser or WP-cookie harness test. +- Remote runner still does not exercise nonce-protected diagnostics download end-to-end. diff --git a/tests/run_remote_tests.sh b/tests/run_remote_tests.sh new file mode 100755 index 0000000..b9a7ad6 --- /dev/null +++ b/tests/run_remote_tests.sh @@ -0,0 +1,288 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)" +ENV_FILE="${ROOT_DIR}/credentials/.env" + +if [[ -f "${ENV_FILE}" ]]; then + # shellcheck disable=SC1090 + source "${ENV_FILE}" +fi + +BASE_URL="${BASE_URL:-${WP_URL:-}}" +AUTH_USER="${CAL_TEST_USER:-adrians@chezstephens.org.uk}" +AUTH_PASS="${CAL_TEST_PASSWORD:-brillig1}" +EXPECTED_TABLE_PREFIX="${CAL_TABLE_PREFIX_EXPECTED:-wp_cs_calendar}" +ENABLE_PREFIX_CHECK="${CAL_ENABLE_PREFIX_CHECK:-1}" +REMOTE_WP_PATH="${REMOTE_WP_PATH:-}" +if [[ -z "${REMOTE_WP_PATH}" ]] && [[ -n "${REMOTE_APP_DIR:-}" ]]; then + REMOTE_WP_PATH="$(dirname "$(dirname "${REMOTE_APP_DIR}")")" +fi +if [[ -z "${REMOTE_WP_PATH}" ]]; then + REMOTE_WP_PATH="/var/www/wordpress" +fi +FAILURES=0 +CREATED_EVENT_ID="" +CREATED_REC_EVENT_ID="" + +usage() { + cat <<'TXT' +Remote API+E2E smoke runner for calendar plugin. + +Usage: + tests/run_remote_tests.sh [--base-url URL] [--user EMAIL] [--password PASS] + +Env overrides: + BASE_URL + CAL_TEST_USER + CAL_TEST_PASSWORD + CAL_TABLE_PREFIX_EXPECTED + CAL_ENABLE_PREFIX_CHECK + +Defaults: + BASE_URL -> credentials/.env:WP_URL + user/password -> seeded smoke account +TXT +} + +while [[ $# -gt 0 ]]; do + case "$1" in + --base-url) + BASE_URL="${2:-}" + shift 2 + ;; + --user) + AUTH_USER="${2:-}" + shift 2 + ;; + --password) + AUTH_PASS="${2:-}" + shift 2 + ;; + --help|-h) + usage + exit 0 + ;; + *) + echo "[remote-tests] unknown argument: $1" >&2 + usage + exit 2 + ;; + esac +done + +if [[ -z "${BASE_URL}" ]]; then + echo "[remote-tests] BASE_URL is required (set WP_URL in credentials/.env or pass --base-url)" >&2 + exit 2 +fi + +BASE_URL="${BASE_URL%/}" + +step() { + printf '\n[remote-tests] %s\n' "$1" +} + +record_fail() { + FAILURES=$((FAILURES + 1)) + echo "[remote-tests] FAIL: $1" >&2 +} + +json_assert() { + local file="$1" + local expr="$2" + python3 - "$file" "$expr" <<'PY' +import json, sys +path, expr = sys.argv[1], sys.argv[2] +data = json.load(open(path)) +safe = {"bool": bool, "isinstance": isinstance, "list": list, "dict": dict, "str": str, "int": int} +ok = eval(expr, {"__builtins__": safe}, {"data": data}) +if not ok: + raise SystemExit(1) +PY +} + +cleanup() { + if [[ -n "${CREATED_EVENT_ID}" ]]; then + curl -sS -u "${AUTH_USER}:${AUTH_PASS}" -X DELETE \ + "${BASE_URL}/wp-json/calendar/v1/events/${CREATED_EVENT_ID}" >/dev/null || true + fi + if [[ -n "${CREATED_REC_EVENT_ID}" ]]; then + curl -sS -u "${AUTH_USER}:${AUTH_PASS}" -X DELETE \ + "${BASE_URL}/wp-json/calendar/v1/events/${CREATED_REC_EVENT_ID}" >/dev/null || true + fi +} +trap cleanup EXIT + +step "health" +if curl -fsS "${BASE_URL}/wp-json/calendar/v1/health" >/tmp/remote_test_health.json; then + if ! json_assert /tmp/remote_test_health.json "data.get('status') == 'ok'"; then + record_fail "health payload invalid" + fi +else + record_fail "health endpoint unreachable" +fi + +if [[ "${ENABLE_PREFIX_CHECK}" == "1" ]] && [[ -n "${REMOTE_HOST:-}" ]] && [[ -n "${REMOTE_USER:-}" ]] && [[ -n "${REMOTE_SSH_KEY_PATH:-}" ]] && [[ -n "${REMOTE_PORT:-}" ]] && [[ -n "${REMOTE_WP_CLI:-}" ]]; then + step "table prefix configuration" + SSH_KEY_PATH="${REMOTE_SSH_KEY_PATH}" + if [[ "${SSH_KEY_PATH}" != /* ]]; then + SSH_KEY_PATH="${ROOT_DIR}/${SSH_KEY_PATH}" + fi + SSH_PREFIX=(ssh -F /dev/null -i "${SSH_KEY_PATH}" -p "${REMOTE_PORT}" -o StrictHostKeyChecking=accept-new "${REMOTE_USER}@${REMOTE_HOST}") + if ! "${SSH_PREFIX[@]}" "echo ok" >/dev/null 2>&1; then + echo "[remote-tests] WARN: SSH unavailable, skipping table prefix check" + else + STEM="$("${SSH_PREFIX[@]}" "cd ${REMOTE_WP_PATH} && ${REMOTE_WP_CLI} option get calendar_plugin_table_stem --allow-root 2>/dev/null || true" | tr -d '\r' | tail -n1)" + if [[ -n "${STEM}" ]] && [[ "${STEM}" != "cs_calendar" ]] && [[ "${STEM}" != "calendar" ]]; then + record_fail "table stem option expected cs_calendar/calendar got '${STEM}'" + fi + TABLE_LIST="$("${SSH_PREFIX[@]}" "cd ${REMOTE_WP_PATH} && ${REMOTE_WP_CLI} db query \"SHOW TABLES;\" --allow-root --silent --skip-column-names 2>/dev/null || true" | tr -d '\r')" + TABLE_EXISTS="$(printf '%s\n' "${TABLE_LIST}" | grep -Fx "${EXPECTED_TABLE_PREFIX}_events" | head -n1 || true)" + LEGACY_EXISTS="$(printf '%s\n' "${TABLE_LIST}" | grep -Fx "wp_calendar_events" | head -n1 || true)" + if [[ "${TABLE_EXISTS}" != "${EXPECTED_TABLE_PREFIX}_events" ]] && [[ "${LEGACY_EXISTS}" != "wp_calendar_events" ]]; then + record_fail "expected table prefix '${EXPECTED_TABLE_PREFIX}' (or legacy wp_calendar) not found" + fi + fi +fi + +step "public read month" +if curl -fsS "${BASE_URL}/wp-json/calendar/v1/public/events?view=month&date=2026-04-01" >/tmp/remote_test_public.json; then + if ! json_assert /tmp/remote_test_public.json "isinstance(data.get('data'), list)"; then + record_fail "public events payload invalid" + fi +else + record_fail "public events endpoint failed" +fi + +step "authenticated /users/me" +if curl -fsS -u "${AUTH_USER}:${AUTH_PASS}" "${BASE_URL}/wp-json/calendar/v1/users/me" >/tmp/remote_test_me.json; then + if ! json_assert /tmp/remote_test_me.json "bool(data.get('data', {}).get('email'))"; then + record_fail "auth user context invalid" + fi +else + record_fail "auth /users/me failed" +fi + +step "event CRUD" +EVENT_UID="remote-api-e2e-$(date +%s)@calendar-plugin" +CREATE_JSON=$(cat <`) and supported component set (`VEVENT`). +- Legacy local harness check is archived at `fixture-tests/fixture_caldav_client_compat_smoke.sh`. + +### SMK-010 Lifecycle Controls (Staging Only) +- Verify Setup exposes `Uninstall Cleanup` setting with: + - `keep` (default) + - `remove` +- Verify deactivation does not remove tables/data. +- Verify uninstall behavior follows selected mode (`keep` preserves tables, `remove` drops plugin-owned tables/options). +- Full destructive flow is defined in `tests/lifecycle_test_cases.md`. + +## Failure Handling +- Any smoke failure blocks merge/deploy. +- Capture artifact bundle: logs, request/response snippets, and failing fixture payload. + +## Suggested Run Schedule +- Per commit: SMK-001..007 +- Pre-release: smoke + full E2E + full API suite + SMK-009 +- Nightly: full E2E + compatibility checks with target CalDAV/ICS clients + +## Remote Runner +- Automated remote API+E2E smoke runner: `./tests/run_remote_tests.sh` +- Optional overrides: + - `./tests/run_remote_tests.sh --base-url https://chezstephens.org.uk --user --password ` diff --git a/tests/ui_e2e_cases.md b/tests/ui_e2e_cases.md new file mode 100644 index 0000000..897e7a3 --- /dev/null +++ b/tests/ui_e2e_cases.md @@ -0,0 +1,23 @@ +# UI E2E Cases + +Focused UI contract checks for the plugin calendar page and related user flows. + +## Scope +- Calendar page controls render (`/calendar`) +- Account actions used by UI (register, login, reset) +- Event CRUD and single-occurrence delete semantics used by UI +- Admin user approval/removal flow that unblocks UI write access + +## Automated Script +- `compatibility-layer/ui_e2e.sh` + +## Cases +1. Calendar page renders login/event controls. +2. Calendar page renders verify/reset/future-only/theme and recurrence-delete controls. +3. Register user and verify email token flow. +4. Admin approves user. +5. Approved user login succeeds via `/users/me`. +6. User creates recurring event. +7. Single occurrence delete removes only selected occurrence. +8. User deletes event and resource becomes not found. +9. Admin removes user and `/users/me` authentication fails afterward. diff --git a/tests/user_ui_caldav_1hr_spec.md b/tests/user_ui_caldav_1hr_spec.md new file mode 100644 index 0000000..8053830 --- /dev/null +++ b/tests/user_ui_caldav_1hr_spec.md @@ -0,0 +1,77 @@ +# User Functional Test Spec (UI + CalDAV, ~1 Hour) + +## Goal +Exercise the highest-value user flows across web UI, admin setup/users pages, recurrence logic, ICS, and CalDAV synchronization within 60 minutes. + +## Preconditions (5 minutes) +- Tester has: + - WordPress admin account + - one approved calendar user account (for UI + CalDAV write tests) + - one pending user account (for approval flow checks) +- Test calendar client available (for example Thunderbird). +- Plugin active and calendar page with `[calendar]` exists. + +## Test Plan + +### 1. Setup Page (8 minutes) +- Open Setup. +- Verify controls are present and usable: + - CalDAV/ICS calendar name + - URL slug + - plugin table prefix + - diagnostics enabled + - uninstall cleanup mode + - buttons: Save Settings, Seed Events, Delete All Events Data +- Click `Seed Events`. +- Confirm success message appears. + +### 2. Users Admin Flow (6 minutes) +- Open Users page. +- Verify pending user appears. +- Approve pending user. +- Remove a defunct test user (if present) and verify list refreshes. + +### 3. Calendar UI Auth + CRUD (12 minutes) +- Open calendar page in logged-out mode; verify read-only behavior. +- Log in as approved user. +- Create a single timed event. +- Edit same event (title/category/location/description). +- Delete the event. +- Confirm list/month views update after each operation. + +### 4. Recurrence + Exception UX (12 minutes) +- Create a recurring event (daily or weekly). +- Re-open event and confirm recurrence preview pane is visible. +- Select one occurrence in preview (verify visual highlight). +- Deselect and reselect to verify toggle behavior. +- Click `Delete Occurrence` and confirm selected occurrence disappears while series remains. + +### 5. CalDAV Round-Trip (10 minutes) +- In CalDAV client: + - subscribe/connect using approved account. + - create one event. + - edit one event. + - delete one single occurrence from a recurring series. +- In web UI: + - refresh and confirm all CalDAV changes are reflected. + +### 6. ICS + Diagnostics (7 minutes) +- Open ICS URL and confirm file downloads and contains seeded + user-created events. +- Open Diagnostics page: + - verify diagnostics snapshot renders. + - click `Download Diagnostics` and confirm JSON file downloads. + +## Pass Criteria +- No fatal errors. +- CRUD works from UI and CalDAV. +- Single-occurrence delete creates exception without splitting series. +- Admin controls behave as expected. +- Diagnostics download works as attachment. + +## Failure Notes Template +- Step: +- Expected: +- Actual: +- URL/page/client: +- Timestamp (local): +- Screenshot/log reference: