events: reschedule notifications + 7 tests
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 31s
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 31s
ev/reschedule-notifications: when an event carries per-occurrence overrides, reads the roster at each overridden occurrence's original occ-key and emits a reschedule message per booked attendee (old-start/new-start/new-duration). Idempotency key = original-key/reschedule/new-start. 272/272 green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -94,3 +94,54 @@
|
||||
;; A single digest message summarising an actor's upcoming booked occurrences.
|
||||
;; :items is ({:event :start} ...); empty when the actor has nothing booked.
|
||||
(define ev/agenda-digest (fn (b store actor ws we) {:items (map (fn (occ) {:event (get occ :id) :start (get occ :start)}) (ev/agenda-for-p b store actor ws we)) :id (str actor "/digest/" ws "-" we) :recipient actor}))
|
||||
|
||||
;; ---- reschedule notifications ----
|
||||
;; When an event carries per-occurrence overrides (ev-with-override), every
|
||||
;; attendee booked at the ORIGINAL start should be told the new time. Bookings
|
||||
;; were made against the original occ-key (id@orig-start), so we read that
|
||||
;; roster. Idempotency key encodes the original key and the new start, so
|
||||
;; re-deriving the same reschedule never double-notifies.
|
||||
(define
|
||||
ev/reschedule-notifications
|
||||
(fn
|
||||
(b event)
|
||||
(let
|
||||
((overrides (ev-or (get event :overrides) (list)))
|
||||
(evid (get event :id))
|
||||
(dur (get event :duration)))
|
||||
(reduce
|
||||
(fn
|
||||
(acc entry)
|
||||
(let
|
||||
((orig-start (first entry))
|
||||
(ov (first (rest entry))))
|
||||
(let
|
||||
((occ (ev-occ evid orig-start dur))
|
||||
(new-start (get ov :start))
|
||||
(new-duration (get ov :duration)))
|
||||
(let
|
||||
((key (ev-occ-key occ)))
|
||||
(append
|
||||
acc
|
||||
(map
|
||||
(fn
|
||||
(actor)
|
||||
{:id (str key "/reschedule/" new-start)
|
||||
:recipient actor
|
||||
:event evid
|
||||
:old-start orig-start
|
||||
:new-start new-start
|
||||
:new-duration new-duration})
|
||||
(ev/roster-occ b occ)))))))
|
||||
(list)
|
||||
overrides))))
|
||||
|
||||
;; Project a reschedule notification to notify's (id recipient body) shape.
|
||||
(define
|
||||
ev/reschedule-notify->msg
|
||||
(fn
|
||||
(r)
|
||||
(list
|
||||
(get r :id)
|
||||
(get r :recipient)
|
||||
(list :rescheduled (get r :event) (get r :old-start) (get r :new-start)))))
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"lang": "events",
|
||||
"total_passed": 265,
|
||||
"total_passed": 272,
|
||||
"total_failed": 0,
|
||||
"total": 265,
|
||||
"total": 272,
|
||||
"suites": [
|
||||
{"name":"calendar","passed":51,"failed":0,"total":51},
|
||||
{"name":"availability","passed":22,"failed":0,"total":22},
|
||||
@@ -11,8 +11,8 @@
|
||||
{"name":"booking-notify","passed":11,"failed":0,"total":11},
|
||||
{"name":"ticket","passed":31,"failed":0,"total":31},
|
||||
{"name":"notify","passed":7,"failed":0,"total":7},
|
||||
{"name":"reminders","passed":14,"failed":0,"total":14},
|
||||
{"name":"reminders","passed":21,"failed":0,"total":21},
|
||||
{"name":"federation","passed":23,"failed":0,"total":23}
|
||||
],
|
||||
"generated": "2026-06-07T07:20:13+00:00"
|
||||
"generated": "2026-06-07T07:46:42+00:00"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# events scoreboard
|
||||
|
||||
**265 / 265 passing** (0 failure(s)).
|
||||
**272 / 272 passing** (0 failure(s)).
|
||||
|
||||
| Suite | Passed | Total | Status |
|
||||
|-------|--------|-------|--------|
|
||||
@@ -11,5 +11,5 @@
|
||||
| booking-notify | 11 | 11 | ok |
|
||||
| ticket | 31 | 31 | ok |
|
||||
| notify | 7 | 7 | ok |
|
||||
| reminders | 14 | 14 | ok |
|
||||
| reminders | 21 | 21 | ok |
|
||||
| federation | 23 | 23 | ok |
|
||||
|
||||
@@ -208,6 +208,61 @@
|
||||
(get empty-dig :items)
|
||||
(list)))))))))
|
||||
|
||||
;; ---- reschedule notifications ----
|
||||
(define
|
||||
ev-rm-rs-run-all!
|
||||
(fn
|
||||
()
|
||||
(let
|
||||
((b (persist/open))
|
||||
(ev (ev-event (quote yoga) (ev-dt 2026 6 1 9 0) 60 {:freq :daily :count 3} 20)))
|
||||
(let
|
||||
((occ2 (ev-occ (quote yoga) (ev-dt 2026 6 2 9 0) 60)))
|
||||
(do
|
||||
(ev/book-occ! b (ev/add-event (ev/empty) ev) (quote nia) occ2)
|
||||
(ev/book-occ! b (ev/add-event (ev/empty) ev) (quote ola) occ2)
|
||||
;; reschedule the Jun 2 occurrence to 14:00 / 90 min
|
||||
(let
|
||||
((moved (ev-with-override ev (ev-dt 2026 6 2 9 0) (ev-dt 2026 6 2 14 0) 90)))
|
||||
(let
|
||||
((ns (ev/reschedule-notifications b moved)))
|
||||
(do
|
||||
(ev-rm-check!
|
||||
"every booked attendee is notified of the reschedule"
|
||||
(map (fn (n) (get n :recipient)) ns)
|
||||
(list (quote nia) (quote ola)))
|
||||
(ev-rm-check!
|
||||
"reschedule carries old and new start"
|
||||
(list (get (first ns) :old-start) (get (first ns) :new-start))
|
||||
(list (ev-dt 2026 6 2 9 0) (ev-dt 2026 6 2 14 0)))
|
||||
(ev-rm-check!
|
||||
"reschedule carries the new duration"
|
||||
(get (first ns) :new-duration)
|
||||
90)
|
||||
(ev-rm-check!
|
||||
"reschedule idempotency key encodes original key + new start"
|
||||
(get (first ns) :id)
|
||||
(str (ev-occ-key occ2) "/reschedule/" (ev-dt 2026 6 2 14 0)))
|
||||
(ev-rm-check!
|
||||
"reschedule projects to notify wire shape"
|
||||
(ev/reschedule-notify->msg (first ns))
|
||||
(list
|
||||
(str (ev-occ-key occ2) "/reschedule/" (ev-dt 2026 6 2 14 0))
|
||||
(quote nia)
|
||||
(list :rescheduled (quote yoga) (ev-dt 2026 6 2 9 0) (ev-dt 2026 6 2 14 0)))))))
|
||||
;; an override on an occurrence nobody booked notifies no one
|
||||
(let
|
||||
((moved2 (ev-with-override ev (ev-dt 2026 6 3 9 0) (ev-dt 2026 6 3 10 0) 60)))
|
||||
(ev-rm-check!
|
||||
"rescheduling an unbooked occurrence notifies no one"
|
||||
(len (ev/reschedule-notifications b moved2))
|
||||
0))
|
||||
;; an event with no overrides yields no reschedule notifications
|
||||
(ev-rm-check!
|
||||
"event without overrides has no reschedule notifications"
|
||||
(len (ev/reschedule-notifications b ev))
|
||||
0))))))
|
||||
|
||||
(define
|
||||
ev-reminders-tests-run!
|
||||
(fn
|
||||
@@ -217,4 +272,5 @@
|
||||
(set! ev-rm-fail 0)
|
||||
(set! ev-rm-failures (list))
|
||||
(ev-rm-run-all!)
|
||||
(ev-rm-rs-run-all!)
|
||||
{:failures ev-rm-failures :total (+ ev-rm-pass ev-rm-fail) :passed ev-rm-pass :failed ev-rm-fail})))
|
||||
|
||||
Reference in New Issue
Block a user