Files
rose-ash/lib/events/tests/api.sx
giles 24d4db3f0d
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 35s
events: wire persist-backed booking into api.sx + 10 tests
Durable booking path alongside in-memory: ev/book-occ!, ev/cancel-occ!,
ev/roster-occ, ev/seats-left-occ (capacity from scheduled event); ev/free-p?,
ev/next-free-p, ev/conflicts-p derive availability by replaying persist
booking streams. Reordered conformance preloads. 120/120 green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 02:39:19 +00:00

272 lines
8.7 KiB
Plaintext

;; lib/events/tests/api.sx — public events facade (schedule/agenda/free/book).
(define ev-api-pass 0)
(define ev-api-fail 0)
(define ev-api-failures (list))
(define
ev-api-check!
(fn
(name got expected)
(if
(= got expected)
(set! ev-api-pass (+ ev-api-pass 1))
(do
(set! ev-api-fail (+ ev-api-fail 1))
(append!
ev-api-failures
(str name "\n expected: " expected "\n got: " got))))))
;; A store with a weekly yoga class (Mon+Wed 18:00, 60m, 4 occurrences).
(define
ev-api-store
(fn
()
(ev/schedule
(ev/empty)
(quote yoga)
(ev-dt 2026 6 1 18 0)
60
{:freq :weekly :count 4 :byday (list 0 2)}
20)))
(define
ev-api-run-all!
(fn
()
(let
((s0 (ev-api-store)))
(let
((occs (ev/agenda s0 (ev-date 2026 6 1) (ev-date 2026 7 1))))
(let
((s1 (ev/book (ev/book s0 (quote nia) (ev-occ-key (first occs))) (quote nia) (ev-occ-key (first (rest occs))))))
(do
(ev-api-check!
"agenda expands weekly class to four occurrences"
(map (fn (o) (ev-dt->civil (get o :start))) occs)
(list
(list 2026 6 1)
(list 2026 6 3)
(list 2026 6 8)
(list 2026 6 10)))
(ev-api-check!
"empty store has empty agenda"
(ev/agenda
(ev/empty)
(ev-date 2026 6 1)
(ev-date 2026 7 1))
(list))
(ev-api-check!
"max duration reflects scheduled events"
(ev/store-max-duration s0)
60)
(ev-api-check!
"max duration of empty store is zero"
(ev/store-max-duration (ev/empty))
0)
(ev-api-check!
"event-by-id finds the scheduled event"
(get (ev/event-by-id s0 (quote yoga)) :capacity)
20)
(ev-api-check!
"event-by-id is nil for unknown id"
(ev/event-by-id s0 (quote nope))
nil)
(ev-api-check!
"agenda-for lists only booked occurrences"
(map
(fn (o) (ev-dt->civil (get o :start)))
(ev/agenda-for
s1
(quote nia)
(ev-date 2026 6 1)
(ev-date 2026 7 1)))
(list
(list 2026 6 1)
(list 2026 6 3)))
(ev-api-check!
"agenda-for empty for unbooked actor"
(ev/agenda-for
s1
(quote zed)
(ev-date 2026 6 1)
(ev-date 2026 7 1))
(list))
(ev-api-check!
"free? false during a booked occurrence"
(ev/free?
s1
(quote nia)
(ev-dt 2026 6 1 18 30)
(ev-dt 2026 6 1 19 0))
false)
(ev-api-check!
"free? true in an open window"
(ev/free?
s1
(quote nia)
(ev-dt 2026 6 1 9 0)
(ev-dt 2026 6 1 10 0))
true)
(ev-api-check!
"free? half-open at occurrence end"
(ev/free?
s1
(quote nia)
(ev-dt 2026 6 1 19 0)
(ev-dt 2026 6 1 20 0))
true)
(ev-api-check!
"free? true for an actor who booked nothing"
(ev/free?
s1
(quote zed)
(ev-dt 2026 6 1 18 0)
(ev-dt 2026 6 1 19 0))
true)
(ev-api-check!
"next-free skips the booked slot to the hour after"
(ev-dt-tod
(ev/next-free
s1
(quote nia)
(ev-dt
2026
6
1
18
0)
60
(ev-dt
2026
6
1
23
0)))
(* 19 60))
(ev-api-check!
"next-free returns `after` when already open"
(ev/next-free
s1
(quote nia)
(ev-dt 2026 6 1 9 0)
60
(ev-dt 2026 6 1 18 0))
(ev-dt 2026 6 1 9 0))
(ev-api-check!
"no conflict among disjoint bookings"
(ev/has-conflict?
s1
(quote nia)
(ev-date 2026 6 1)
(ev-date 2026 7 1))
false)
(let
((sc (ev/book (ev/schedule s1 (quote talk) (ev-dt 2026 6 1 18 30) 60 nil 5) (quote nia) (ev-occ-key (ev-occ (quote talk) (ev-dt 2026 6 1 18 30) 60)))))
(ev-api-check!
"overlapping second booking creates a conflict"
(ev/has-conflict?
sc
(quote nia)
(ev-date 2026 6 1)
(ev-date 2026 7 1))
true))
(let
((b (persist/open)) (occ1 (first occs)))
(do
(let
((sp (ev/schedule (ev/empty) (quote clinic) (ev-dt 2026 6 5 9 0) 30 nil 2)))
(let
((occ (ev-occ (quote clinic) (ev-dt 2026 6 5 9 0) 30)))
(do
(ev-api-check!
"durable book returns booked"
(get (ev/book-occ! b sp (quote a) occ) :status)
:booked)
(ev/book-occ! b sp (quote c) occ)
(ev-api-check!
"durable book past capacity is full"
(get (ev/book-occ! b sp (quote d) occ) :status)
:full)
(ev-api-check!
"durable roster reflects persisted bookings"
(ev/roster-occ b occ)
(list (quote a) (quote c)))
(ev-api-check!
"durable seats-left honours capacity"
(ev/seats-left-occ b sp occ)
0)
(ev-api-check!
"persist free? false during a durable booking"
(ev/free-p?
b
sp
(quote a)
(ev-dt
2026
6
5
9
10)
(ev-dt
2026
6
5
9
20))
false)
(ev-api-check!
"persist free? true in an open window"
(ev/free-p?
b
sp
(quote a)
(ev-dt
2026
6
5
10
0)
(ev-dt
2026
6
5
10
30))
true)
(ev/cancel-occ! b sp (quote a) occ)
(ev-api-check!
"durable cancel frees a seat"
(ev/seats-left-occ b sp occ)
1)
(ev-api-check!
"persist free? true after cancellation"
(ev/free-p?
b
sp
(quote a)
(ev-dt
2026
6
5
9
10)
(ev-dt
2026
6
5
9
20))
true))))))))))))
(define
ev-api-tests-run!
(fn
()
(do
(set! ev-api-pass 0)
(set! ev-api-fail 0)
(set! ev-api-failures (list))
(ev-api-run-all!)
{:failures ev-api-failures :total (+ ev-api-pass ev-api-fail) :passed ev-api-pass :failed ev-api-fail})))