;; lib/dream/tests/router.sx — routing dispatch, path params, scopes. (define dream-rt-pass 0) (define dream-rt-fail 0) (define dream-rt-fails (list)) (define dream-rt-test (fn (name actual expected) (if (= actual expected) (set! dream-rt-pass (+ dream-rt-pass 1)) (begin (set! dream-rt-fail (+ dream-rt-fail 1)) (append! dream-rt-fails {:name name :actual actual :expected expected}))))) (define dream-rt-req (fn (method target) (dream-request method target {} ""))) ;; ── basic dispatch ───────────────────────────────────────────────── (define dream-rt-app (dream-router (list (dream-get "/" (fn (req) (dream-text "home"))) (dream-get "/about" (fn (req) (dream-text "about"))) (dream-post "/submit" (fn (req) (dream-text "posted")))))) (dream-rt-test "GET / -> home" (dream-resp-body (dream-rt-app (dream-rt-req "GET" "/"))) "home") (dream-rt-test "GET /about" (dream-resp-body (dream-rt-app (dream-rt-req "GET" "/about"))) "about") (dream-rt-test "POST /submit" (dream-resp-body (dream-rt-app (dream-rt-req "POST" "/submit"))) "posted") (dream-rt-test "unknown path 404" (dream-status (dream-rt-app (dream-rt-req "GET" "/nope"))) 404) (dream-rt-test "wrong method 404" (dream-status (dream-rt-app (dream-rt-req "GET" "/submit"))) 404) (dream-rt-test "trailing slash equiv" (dream-resp-body (dream-rt-app (dream-rt-req "GET" "/about/"))) "about") (dream-rt-test "query ignored for routing" (dream-resp-body (dream-rt-app (dream-rt-req "GET" "/about?x=1"))) "about") ;; ── path params ──────────────────────────────────────────────────── (define dream-rt-papp (dream-router (list (dream-get "/users/:id" (fn (req) (dream-text (dream-param req "id")))) (dream-get "/users/:id/posts/:pid" (fn (req) (dream-text (str (dream-param req "id") "-" (dream-param req "pid"))))) (dream-get "/files/**" (fn (req) (dream-text (dream-param req "**"))))))) (dream-rt-test "single param" (dream-resp-body (dream-rt-papp (dream-rt-req "GET" "/users/42"))) "42") (dream-rt-test "two params" (dream-resp-body (dream-rt-papp (dream-rt-req "GET" "/users/7/posts/9"))) "7-9") (dream-rt-test "param no over-match" (dream-status (dream-rt-papp (dream-rt-req "GET" "/users/7/extra"))) 404) (dream-rt-test "catch-all captures rest" (dream-resp-body (dream-rt-papp (dream-rt-req "GET" "/files/a/b/c.txt"))) "a/b/c.txt") (dream-rt-test "catch-all empty rest" (dream-resp-body (dream-rt-papp (dream-rt-req "GET" "/files/"))) "") ;; ── route order: first match wins ────────────────────────────────── (define dream-rt-order (dream-router (list (dream-get "/x/specific" (fn (req) (dream-text "specific"))) (dream-get "/x/:slug" (fn (req) (dream-text "generic")))))) (dream-rt-test "first match wins" (dream-resp-body (dream-rt-order (dream-rt-req "GET" "/x/specific"))) "specific") (dream-rt-test "fallthrough to param" (dream-resp-body (dream-rt-order (dream-rt-req "GET" "/x/other"))) "generic") ;; ── ANY method ───────────────────────────────────────────────────── (define dream-rt-any (dream-router (list (dream-any "/ping" (fn (req) (dream-text (dream-method req))))))) (dream-rt-test "ANY matches GET" (dream-resp-body (dream-rt-any (dream-rt-req "GET" "/ping"))) "GET") (dream-rt-test "ANY matches DELETE" (dream-resp-body (dream-rt-any (dream-rt-req "DELETE" "/ping"))) "DELETE") ;; ── handler returns bare string (coerced) ────────────────────────── (define dream-rt-coerce (dream-router (list (dream-get "/s" (fn (req) "bare"))))) (dream-rt-test "string coerced to 200" (dream-status (dream-rt-coerce (dream-rt-req "GET" "/s"))) 200) (dream-rt-test "string coerced body" (dream-resp-body (dream-rt-coerce (dream-rt-req "GET" "/s"))) "bare") ;; ── scope: prefix mount ──────────────────────────────────────────── (define dream-rt-scoped (dream-router (list (dream-get "/" (fn (req) (dream-text "root"))) (dream-scope "/api" (list) (list (dream-get "/users" (fn (req) (dream-text "api-users"))) (dream-get "/users/:id" (fn (req) (dream-text (str "api-user-" (dream-param req "id")))))))))) (dream-rt-test "scope root still works" (dream-resp-body (dream-rt-scoped (dream-rt-req "GET" "/"))) "root") (dream-rt-test "scope prefix path" (dream-resp-body (dream-rt-scoped (dream-rt-req "GET" "/api/users"))) "api-users") (dream-rt-test "scope prefix param" (dream-resp-body (dream-rt-scoped (dream-rt-req "GET" "/api/users/5"))) "api-user-5") (dream-rt-test "scope unprefixed 404" (dream-status (dream-rt-scoped (dream-rt-req "GET" "/users"))) 404) ;; ── scope: middleware applied to all routes ──────────────────────── (define dream-rt-mw (fn (next) (fn (req) (dream-add-header (next req) "X-Scope" "on")))) (define dream-rt-mwapp (dream-router (list (dream-scope "/v1" (list dream-rt-mw) (list (dream-get "/a" (fn (req) (dream-text "a")))))))) (dream-rt-test "scope mw header" (dream-resp-header (dream-rt-mwapp (dream-rt-req "GET" "/v1/a")) "x-scope") "on") (dream-rt-test "scope mw body intact" (dream-resp-body (dream-rt-mwapp (dream-rt-req "GET" "/v1/a"))) "a") ;; ── nested scopes ────────────────────────────────────────────────── (define dream-rt-outer (fn (next) (fn (req) (dream-add-header (next req) "X-Outer" "1")))) (define dream-rt-inner (fn (next) (fn (req) (dream-add-header (next req) "X-Inner" "1")))) (define dream-rt-nested (dream-router (list (dream-scope "/api" (list dream-rt-outer) (list (dream-scope "/v2" (list dream-rt-inner) (list (dream-get "/thing" (fn (req) (dream-text "thing")))))))))) (dream-rt-test "nested path" (dream-resp-body (dream-rt-nested (dream-rt-req "GET" "/api/v2/thing"))) "thing") (dream-rt-test "nested outer mw" (dream-resp-header (dream-rt-nested (dream-rt-req "GET" "/api/v2/thing")) "x-outer") "1") (dream-rt-test "nested inner mw" (dream-resp-header (dream-rt-nested (dream-rt-req "GET" "/api/v2/thing")) "x-inner") "1") (define dream-rt-tests-run! (fn () {:total (+ dream-rt-pass dream-rt-fail) :passed dream-rt-pass :failed dream-rt-fail :fails dream-rt-fails}))