dream: hello + counter demos + 10 end-to-end tests
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 1m6s
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 1m6s
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -31,6 +31,8 @@ MODULES=(
|
||||
"lib/dream/websocket.sx"
|
||||
"lib/dream/static.sx"
|
||||
"lib/dream/run.sx"
|
||||
"lib/dream/demos/hello.sx"
|
||||
"lib/dream/demos/counter.sx"
|
||||
)
|
||||
|
||||
# Suites: NAME RUNNER-FN PATH
|
||||
@@ -44,6 +46,7 @@ SUITES=(
|
||||
"websocket dream-ws-tests-run! lib/dream/tests/websocket.sx"
|
||||
"static dream-st-tests-run! lib/dream/tests/static.sx"
|
||||
"run dream-rn-tests-run! lib/dream/tests/run.sx"
|
||||
"demos dream-dm-tests-run! lib/dream/tests/demos.sx"
|
||||
)
|
||||
|
||||
TMPFILE=$(mktemp); trap "rm -f $TMPFILE" EXIT
|
||||
|
||||
35
lib/dream/demos/counter.sx
Normal file
35
lib/dream/demos/counter.sx
Normal file
@@ -0,0 +1,35 @@
|
||||
;; lib/dream/demos/counter.sx — per-session visit counter (counter.ml).
|
||||
;; Demonstrates the session middleware: each browser session keeps its own count.
|
||||
|
||||
(define
|
||||
dream-counter-handler
|
||||
(fn
|
||||
(req)
|
||||
(let
|
||||
((n (+ 1 (or (dream-session-field req "count") 0))))
|
||||
(begin
|
||||
(dream-set-session-field req "count" n)
|
||||
(dream-html (str "<p>You have visited this page " n " time(s).</p>"))))))
|
||||
|
||||
;; reset clears the session counter
|
||||
(define
|
||||
dream-counter-reset
|
||||
(fn
|
||||
(req)
|
||||
(begin
|
||||
(dream-set-session-field req "count" 0)
|
||||
(dream-redirect "/"))))
|
||||
|
||||
(define
|
||||
dream-counter-app-with
|
||||
(fn
|
||||
(backend)
|
||||
((dream-sessions backend)
|
||||
(dream-router
|
||||
(list
|
||||
(dream-get "/" dream-counter-handler)
|
||||
(dream-post "/reset" dream-counter-reset))))))
|
||||
|
||||
(define dream-counter-app (dream-counter-app-with (dream-memory-sessions)))
|
||||
|
||||
;; entry point: (dream-run (dream-counter-app-with (dream-memory-sessions)))
|
||||
16
lib/dream/demos/hello.sx
Normal file
16
lib/dream/demos/hello.sx
Normal file
@@ -0,0 +1,16 @@
|
||||
;; lib/dream/demos/hello.sx — the canonical Dream "Hello, World!" (hello.ml).
|
||||
;; Dream.run (Dream.router [Dream.get "/" (fun _ -> Dream.html "Hello!")]).
|
||||
|
||||
(define
|
||||
dream-hello-app
|
||||
(dream-router
|
||||
(list
|
||||
(dream-get "/" (fn (req) (dream-html "<h1>Hello, World!</h1>")))
|
||||
(dream-get
|
||||
"/hello/:name"
|
||||
(fn
|
||||
(req)
|
||||
(dream-html (str "<h1>Hello, " (dream-param req "name") "!</h1>")))))))
|
||||
|
||||
;; entry point (installs the handler on the host):
|
||||
;; (dream-run dream-hello-app)
|
||||
83
lib/dream/tests/demos.sx
Normal file
83
lib/dream/tests/demos.sx
Normal file
@@ -0,0 +1,83 @@
|
||||
;; lib/dream/tests/demos.sx — end-to-end demo apps exercising the full stack.
|
||||
|
||||
(define dream-dm-pass 0)
|
||||
(define dream-dm-fail 0)
|
||||
(define dream-dm-fails (list))
|
||||
|
||||
(define
|
||||
dream-dm-test
|
||||
(fn
|
||||
(name actual expected)
|
||||
(if
|
||||
(= actual expected)
|
||||
(set! dream-dm-pass (+ dream-dm-pass 1))
|
||||
(begin
|
||||
(set! dream-dm-fail (+ dream-dm-fail 1))
|
||||
(append! dream-dm-fails {:name name :actual actual :expected expected})))))
|
||||
|
||||
(define
|
||||
dream-dm-req
|
||||
(fn (method target headers) (dream-request method target headers "")))
|
||||
|
||||
;; ── hello ──────────────────────────────────────────────────────────
|
||||
(dream-dm-test
|
||||
"hello root"
|
||||
(dream-resp-body (dream-hello-app (dream-dm-req "GET" "/" {})))
|
||||
"<h1>Hello, World!</h1>")
|
||||
(dream-dm-test
|
||||
"hello name"
|
||||
(dream-resp-body
|
||||
(dream-hello-app (dream-dm-req "GET" "/hello/Ada" {})))
|
||||
"<h1>Hello, Ada!</h1>")
|
||||
(dream-dm-test
|
||||
"hello content-type"
|
||||
(dream-resp-header
|
||||
(dream-hello-app (dream-dm-req "GET" "/" {}))
|
||||
"content-type")
|
||||
"text/html; charset=utf-8")
|
||||
|
||||
;; ── counter (sessions) ─────────────────────────────────────────────
|
||||
(define dream-dm-cbackend (dream-memory-sessions))
|
||||
(define dream-dm-capp (dream-counter-app-with dream-dm-cbackend))
|
||||
|
||||
;; first visit: no cookie -> count 1, session cookie set
|
||||
(define dream-dm-c1 (dream-dm-capp (dream-dm-req "GET" "/" {})))
|
||||
(dream-dm-test
|
||||
"counter first visit"
|
||||
(dream-resp-body dream-dm-c1)
|
||||
"<p>You have visited this page 1 time(s).</p>")
|
||||
(dream-dm-test
|
||||
"counter sets cookie"
|
||||
(len (dream-resp-cookies dream-dm-c1))
|
||||
1)
|
||||
|
||||
;; subsequent visits with the cookie increment
|
||||
(dream-dm-test
|
||||
"counter second visit"
|
||||
(dream-resp-body (dream-dm-capp (dream-dm-req "GET" "/" {:Cookie "dream.session=s1"})))
|
||||
"<p>You have visited this page 2 time(s).</p>")
|
||||
(dream-dm-test
|
||||
"counter third visit"
|
||||
(dream-resp-body (dream-dm-capp (dream-dm-req "GET" "/" {:Cookie "dream.session=s1"})))
|
||||
"<p>You have visited this page 3 time(s).</p>")
|
||||
|
||||
;; reset zeroes the counter then redirects
|
||||
(define
|
||||
dream-dm-reset
|
||||
(dream-dm-capp (dream-dm-req "POST" "/reset" {:Cookie "dream.session=s1"})))
|
||||
(dream-dm-test
|
||||
"counter reset redirects"
|
||||
(dream-status dream-dm-reset)
|
||||
303)
|
||||
(dream-dm-test
|
||||
"counter after reset"
|
||||
(dream-resp-body (dream-dm-capp (dream-dm-req "GET" "/" {:Cookie "dream.session=s1"})))
|
||||
"<p>You have visited this page 1 time(s).</p>")
|
||||
|
||||
;; a different session is independent
|
||||
(dream-dm-test
|
||||
"counter distinct session"
|
||||
(dream-resp-body (dream-dm-capp (dream-dm-req "GET" "/" {})))
|
||||
"<p>You have visited this page 1 time(s).</p>")
|
||||
|
||||
(define dream-dm-tests-run! (fn () {:total (+ dream-dm-pass dream-dm-fail) :passed dream-dm-pass :failed dream-dm-fail :fails dream-dm-fails}))
|
||||
@@ -74,11 +74,11 @@ The five types: `request`, `response`, `handler = request -> response`, `middlew
|
||||
- `dream-send ws msg`, `dream-receive ws`, `dream-close ws`.
|
||||
- [x] **Static files:** `dream-static root-path` — serves files, ETags, range requests.
|
||||
- [x] **`dream-run`**: wires root handler into SX's `perform (:http-listen ...)`.
|
||||
- [ ] **Demos** in `lib/dream/demos/`:
|
||||
- `hello.ml` → `lib/dream/demos/hello.sx`: "Hello, World!" route.
|
||||
- `counter.ml` → `lib/dream/demos/counter.sx`: in-memory counter with sessions.
|
||||
- `chat.ml` → `lib/dream/demos/chat.sx`: multi-room WebSocket chat.
|
||||
- `todo.ml` → `lib/dream/demos/todo.sx`: CRUD list with forms + CSRF.
|
||||
- [~] **Demos** in `lib/dream/demos/`:
|
||||
- [x] `hello.ml` → `lib/dream/demos/hello.sx`: "Hello, World!" route.
|
||||
- [x] `counter.ml` → `lib/dream/demos/counter.sx`: in-memory counter with sessions.
|
||||
- [ ] `chat.ml` → `lib/dream/demos/chat.sx`: multi-room WebSocket chat.
|
||||
- [ ] `todo.ml` → `lib/dream/demos/todo.sx`: CRUD list with forms + CSRF.
|
||||
- [ ] Tests in `lib/dream/tests/`: routing dispatch, middleware composition, session round-trip, CSRF accept/reject, flash read-after-write — 60+ tests.
|
||||
|
||||
## Stdlib additions Dream will need
|
||||
@@ -198,6 +198,11 @@ Confirm scope before starting; some of these may be addable as Dream-internal he
|
||||
set-cookies (from session/flash) flow through. Listen transport injectable
|
||||
(`dream-run-with`) so the full wiring is tested with a mock that captures the op and
|
||||
re-runs the captured app. `dream-run-port` / `dream-run-opts` variants.
|
||||
- **2026-06-07 — Demos: hello + counter** (`lib/dream/demos/`, 10 tests). `hello.sx`
|
||||
is the canonical router with a `:name` param route. `counter.sx` is a per-session
|
||||
visit counter on the session middleware (+ a `/reset` POST that redirects),
|
||||
demonstrating session isolation across browsers. End-to-end tests drive both apps
|
||||
as the host would. chat (ws) + todo (forms+CSRF) next.
|
||||
|
||||
## Blockers
|
||||
|
||||
|
||||
Reference in New Issue
Block a user