;; lib/dream/tests/session.sx — cookies, store, session round-trip, signed cookies. (define dream-ss-pass 0) (define dream-ss-fail 0) (define dream-ss-fails (list)) (define dream-ss-test (fn (name actual expected) (if (= actual expected) (set! dream-ss-pass (+ dream-ss-pass 1)) (begin (set! dream-ss-fail (+ dream-ss-fail 1)) (append! dream-ss-fails {:name name :actual actual :expected expected}))))) ;; ── cookie parsing ───────────────────────────────────────────────── (define dream-ss-creq (dream-request "GET" "/" {:Cookie "a=1; b=2; dream.session=s9"} "")) (dream-ss-test "parse cookie a" (dream-cookie dream-ss-creq "a") "1") (dream-ss-test "parse cookie b" (dream-cookie dream-ss-creq "b") "2") (dream-ss-test "parse session cookie" (dream-cookie dream-ss-creq "dream.session") "s9") (dream-ss-test "missing cookie nil" (dream-cookie dream-ss-creq "z") nil) (dream-ss-test "no cookie header" (dream-cookie (dream-request "GET" "/" {} "") "a") nil) ;; ── cookie building ──────────────────────────────────────────────── (dream-ss-test "build basic cookie" (dr/build-cookie "k" "v" {}) "k=v; Path=/") (dream-ss-test "build httponly samesite" (dr/build-cookie "sid" "x" {:http-only true :same-site "Lax"}) "sid=x; Path=/; HttpOnly; SameSite=Lax") (dream-ss-test "build max-age" (dr/build-cookie "k" "v" {:max-age 0}) "k=v; Path=/; Max-Age=0") (dream-ss-test "set-cookie appends" (len (dream-resp-cookies (dream-set-cookie (dream-html "x") "k" "v" {}))) 1) (dream-ss-test "set-cookie two" (len (dream-resp-cookies (dream-set-cookie (dream-set-cookie (dream-html "x") "a" "1" {}) "b" "2" {}))) 2) (dream-ss-test "drop cookie max-age 0" (contains? (first (dream-resp-cookies (dream-drop-cookie (dream-html "x") "k"))) "Max-Age=0") true) ;; ── signed cookie values ─────────────────────────────────────────── (dream-ss-test "sign/unsign roundtrip" (dream-cookie-unsign "k" (dream-cookie-sign "k" "s5")) "s5") (dream-ss-test "unsign wrong secret" (dream-cookie-unsign "k2" (dream-cookie-sign "k" "s5")) nil) (dream-ss-test "unsign tampered" (dream-cookie-unsign "k" "s5.999") nil) (dream-ss-test "unsign no dot" (dream-cookie-unsign "k" "s5") nil) (dream-ss-test "unsign nil" (dream-cookie-unsign "k" nil) nil) ;; ── in-memory store ──────────────────────────────────────────────── (define dream-ss-store (dream-memory-sessions)) (define dream-ss-sid (dream-ss-store {:op "session/create"})) (dream-ss-test "create returns id" dream-ss-sid "s1") (dream-ss-test "new session exists" (dream-ss-store {:op "session/exists" :sid "s1"}) true) (dream-ss-test "absent session not exists" (dream-ss-store {:op "session/exists" :sid "s99"}) false) (dream-ss-test "get missing key nil" (dream-ss-store {:key "k" :op "session/get" :sid "s1"}) nil) (dream-ss-store {:val "ada" :key "user" :op "session/set" :sid "s1"}) (dream-ss-test "set then get" (dream-ss-store {:key "user" :op "session/get" :sid "s1"}) "ada") (dream-ss-store {:val "admin" :key "role" :op "session/set" :sid "s1"}) (dream-ss-test "load all fields" (dream-ss-store {:op "session/load" :sid "s1"}) {:role "admin" :user "ada"}) (dream-ss-test "second create distinct" (dream-ss-store {:op "session/create"}) "s2") (dream-ss-store {:op "session/clear" :sid "s1"}) (dream-ss-test "clear removes" (dream-ss-store {:op "session/exists" :sid "s1"}) false) ;; ── middleware round-trip ────────────────────────────────────────── (define dream-ss-backend (dream-memory-sessions)) (define dream-ss-counter-h (fn (req) (let ((n (or (dream-session-field req "count") 0))) (begin (dream-set-session-field req "count" (+ n 1)) (dream-text (str "count=" (+ n 1))))))) (define dream-ss-app ((dream-sessions dream-ss-backend) dream-ss-counter-h)) (define dream-ss-r1 (dream-ss-app (dream-request "GET" "/" {} ""))) (dream-ss-test "first body count=1" (dream-resp-body dream-ss-r1) "count=1") (dream-ss-test "first sets one cookie" (len (dream-resp-cookies dream-ss-r1)) 1) (dream-ss-test "session cookie name+id" (contains? (first (dream-resp-cookies dream-ss-r1)) "dream.session=s1") true) (dream-ss-test "session cookie httponly" (contains? (first (dream-resp-cookies dream-ss-r1)) "HttpOnly") true) (define dream-ss-r2 (dream-ss-app (dream-request "GET" "/" {:Cookie "dream.session=s1"} ""))) (dream-ss-test "second body count=2" (dream-resp-body dream-ss-r2) "count=2") (dream-ss-test "second sets no cookie" (len (dream-resp-cookies dream-ss-r2)) 0) (define dream-ss-r3 (dream-ss-app (dream-request "GET" "/" {:Cookie "dream.session=s1"} ""))) (dream-ss-test "third body count=3" (dream-resp-body dream-ss-r3) "count=3") (define dream-ss-r4 (dream-ss-app (dream-request "GET" "/" {:Cookie "dream.session=bogus"} ""))) (dream-ss-test "bogus id starts fresh" (dream-resp-body dream-ss-r4) "count=1") (dream-ss-test "bogus id gets new cookie" (len (dream-resp-cookies dream-ss-r4)) 1) ;; ── session-all + invalidate via middleware ──────────────────────── (dream-ss-test "session-all shows count" (dream-session-all (assoc (dream-request "GET" "/" {} "") :dream-session {:io dream-ss-backend :sid "s1"})) {:count 3}) (define dream-ss-invalidate-h (fn (req) (begin (dream-invalidate-session req) (dream-text "bye")))) (define dream-ss-app3 ((dream-sessions dream-ss-backend) dream-ss-invalidate-h)) (dream-ss-app3 (dream-request "GET" "/" {:Cookie "dream.session=s1"} "")) (dream-ss-test "invalidate clears store" (dream-ss-backend {:op "session/exists" :sid "s1"}) false) ;; ── signed session middleware ────────────────────────────────────── (define dream-ss-sbackend (dream-memory-sessions)) (define dream-ss-sapp ((dream-sessions-signed dream-ss-sbackend "topsecret") (fn (req) (dream-text (dream-session-id req))))) (define dream-ss-sr1 (dream-ss-sapp (dream-request "GET" "/" {} ""))) (dream-ss-test "signed first sid" (dream-resp-body dream-ss-sr1) "s1") (dream-ss-test "signed cookie is signed" (contains? (first (dream-resp-cookies dream-ss-sr1)) "dream.session=s1.") true) ;; forged plaintext sid (no signature) is rejected -> a fresh session is made (dream-ss-test "forged plaintext rejected -> new session" (dream-resp-body (dream-ss-sapp (dream-request "GET" "/" {:Cookie "dream.session=s1"} ""))) "s2") ;; a validly-signed cookie reuses the session (define dream-ss-signed-val (dream-cookie-sign "topsecret" "s1")) (define dream-ss-sr3 (dream-ss-sapp (dream-request "GET" "/" {:Cookie (str "dream.session=" dream-ss-signed-val)} ""))) (dream-ss-test "valid signed reuses s1" (dream-resp-body dream-ss-sr3) "s1") (dream-ss-test "valid signed sets no new cookie" (len (dream-resp-cookies dream-ss-sr3)) 0) ;; a cookie signed with the wrong secret is rejected (dream-ss-test "wrong-secret signed rejected" (= (dream-resp-body (dream-ss-sapp (dream-request "GET" "/" {:Cookie (str "dream.session=" (dream-cookie-sign "other" "s1"))} ""))) "s1") false) (define dream-ss-tests-run! (fn () {:total (+ dream-ss-pass dream-ss-fail) :passed dream-ss-pass :failed dream-ss-fail :fails dream-ss-fails}))