AtlatestRepositorycourier
1
(import (sigil test)2
(sigil string)3
(courier poller))5
;; ============================================================6
;; Poller event wire format (child stdout protocol)7
;; ============================================================9
(test-group "make-poller-message-event"10
(test "message event round-trips through JSON"11
(let ((event (parse-poller-event12
(make-poller-message-event13
"hello leader" "daviwil" "12345" "67890"))))14
(assert-equal "message" (dict-ref event type:))15
(assert-equal "hello leader" (dict-ref event text:))16
(assert-equal "daviwil" (dict-ref event sender:))17
(assert-equal "12345" (dict-ref event sender_id:))18
(assert-equal "67890" (dict-ref event chat_id:))))20
(test "event is a single line"21
;; The stdout protocol is newline-delimited; an embedded newline22
;; in the JSON itself would split one event into two bad lines.23
(let ((json (make-poller-message-event "a" "b" "c" "d")))24
(assert-false (string-contains? json "\n"))))26
(test "message text with newlines stays one line"27
;; Newlines inside the text must be escaped by json-encode, not28
;; emitted literally.29
(let* ((json (make-poller-message-event "line1\nline2" "s" "1" "2"))30
(event (parse-poller-event json)))31
(assert-false (string-contains? json "\n"))32
(assert-equal "line1\nline2" (dict-ref event text:)))))34
(test-group "parse-poller-event"35
(test "heartbeat event parses"36
(let ((event (parse-poller-event "{\"type\":\"heartbeat\"}")))37
(assert-equal "heartbeat" (dict-ref event type:))))39
(test "hello event carries pid"40
(let ((event (parse-poller-event "{\"type\":\"hello\",\"pid\":4242}")))41
(assert-equal "hello" (dict-ref event type:))42
(assert-equal 4242 (dict-ref event pid:))))44
(test "garbage line returns #f instead of raising"45
;; A corrupt child line must never crash the supervisor's46
;; event loop.47
(assert-false (parse-poller-event "not json at all"))48
(assert-false (parse-poller-event "")))50
(test "non-object JSON returns #f"51
(assert-false (parse-poller-event "[1,2,3]"))52
(assert-false (parse-poller-event "42"))))54
;; ============================================================55
;; Watchdog staleness decision56
;; ============================================================58
(test-group "poller-stale?"59
(test "fresh output is not stale"60
(assert-false (poller-stale? 100 99)))62
(test "output at the threshold is not stale"63
;; Threshold is 30s; exactly 30s of silence is still alive64
;; (heartbeat cadence is ~2s, so a healthy child never gets here)65
(assert-false (poller-stale? 130 100)))67
(test "silence past the threshold is stale"68
(assert-true (poller-stale? 131 100))))70
;; ============================================================71
;; Supervisor restart backoff72
;; ============================================================74
(test-group "poller-next-restart-delay"75
(test "crash-looping doubles the delay"76
(assert-equal 2 (poller-next-restart-delay 1 0))77
(assert-equal 4 (poller-next-restart-delay 2 5)))79
(test "delay is capped"80
(assert-equal 60 (poller-next-restart-delay 60 0))81
(assert-equal 60 (poller-next-restart-delay 40 0)))83
(test "healthy uptime resets the backoff"84
(assert-equal 1 (poller-next-restart-delay 60 61))85
(assert-equal 1 (poller-next-restart-delay 8 3600))))87
(run-tests)