;; lib/dream/tests/static.sx — content types, etags, 304, ranges, traversal.

(define dream-st-pass 0)
(define dream-st-fail 0)
(define dream-st-fails (list))

(define
  dream-st-test
  (fn
    (name actual expected)
    (if
      (= actual expected)
      (set! dream-st-pass (+ dream-st-pass 1))
      (begin
        (set! dream-st-fail (+ dream-st-fail 1))
        (append! dream-st-fails {:name name :actual actual :expected expected})))))

;; ── content type + ext ─────────────────────────────────────────────
(dream-st-test "ext css" (dr/ext-of "a/b/style.css") "css")
(dream-st-test "ext multi-dot" (dr/ext-of "a.min.js") "js")
(dream-st-test "ext none" (dr/ext-of "README") "")
(dream-st-test
  "ctype css"
  (dream-content-type-for "x.css")
  "text/css; charset=utf-8")
(dream-st-test
  "ctype html"
  (dream-content-type-for "x.html")
  "text/html; charset=utf-8")
(dream-st-test "ctype png" (dream-content-type-for "x.png") "image/png")
(dream-st-test
  "ctype unknown"
  (dream-content-type-for "x.bin")
  "application/octet-stream")

;; ── etag ───────────────────────────────────────────────────────────
(dream-st-test
  "etag deterministic"
  (= (dr/etag-of "abc") (dr/etag-of "abc"))
  true)
(dream-st-test
  "etag content-sensitive"
  (= (dr/etag-of "abc") (dr/etag-of "abd"))
  false)
(dream-st-test
  "etag length-sensitive"
  (= (dr/etag-of "ab") (dr/etag-of "abc"))
  false)

;; ── serving via router mount ───────────────────────────────────────
(define dream-st-files {:/srv/app.css "body{color:red}" :/srv/index.html "<h1>Hi</h1>"})
(define dream-st-fs (dream-memory-fs dream-st-files))
(define
  dream-st-app
  (dream-router
    (list (dream-get "/static/**" (dream-static-with "/srv" dream-st-fs)))))
(define
  dream-st-get
  (fn
    (target headers)
    (dream-st-app (dream-request "GET" target headers ""))))

(define dream-st-css (dream-st-get "/static/app.css" {}))
(dream-st-test "serve status 200" (dream-status dream-st-css) 200)
(dream-st-test "serve body" (dream-resp-body dream-st-css) "body{color:red}")
(dream-st-test
  "serve content-type"
  (dream-resp-header dream-st-css "content-type")
  "text/css; charset=utf-8")
(dream-st-test
  "serve accept-ranges"
  (dream-resp-header dream-st-css "accept-ranges")
  "bytes")
(dream-st-test
  "serve has etag"
  (not (nil? (dream-resp-header dream-st-css "etag")))
  true)
(dream-st-test
  "missing file 404"
  (dream-status (dream-st-get "/static/nope.txt" {}))
  404)
(dream-st-test
  "traversal blocked 403"
  (dream-status (dream-st-get "/static/../secret" {}))
  403)

;; ── conditional: If-None-Match -> 304 ──────────────────────────────
(define dream-st-etag (dream-resp-header dream-st-css "etag"))
(define dream-st-304 (dream-st-get "/static/app.css" {:If-None-Match dream-st-etag}))
(dream-st-test "matching etag 304" (dream-status dream-st-304) 304)
(dream-st-test "304 empty body" (dream-resp-body dream-st-304) "")
(dream-st-test
  "stale etag 200"
  (dream-status (dream-st-get "/static/app.css" {:If-None-Match "\"stale\""}))
  200)
(dream-st-test
  "star etag 304"
  (dream-status (dream-st-get "/static/app.css" {:If-None-Match "*"}))
  304)

;; ── range requests ─────────────────────────────────────────────────
(define dream-st-range (dream-st-get "/static/app.css" {:Range "bytes=0-3"}))
(dream-st-test "range status 206" (dream-status dream-st-range) 206)
(dream-st-test "range body slice" (dream-resp-body dream-st-range) "body")
(dream-st-test
  "range content-range"
  (dream-resp-header dream-st-range "content-range")
  "bytes 0-3/15")
(define dream-st-open (dream-st-get "/static/app.css" {:Range "bytes=5-"}))
(dream-st-test "open range body" (dream-resp-body dream-st-open) "color:red}")
(dream-st-test
  "open range header"
  (dream-resp-header dream-st-open "content-range")
  "bytes 5-14/15")
(define dream-st-bad (dream-st-get "/static/app.css" {:Range "bytes=20-30"}))
(dream-st-test
  "unsatisfiable range 416"
  (dream-status dream-st-bad)
  416)
(dream-st-test
  "416 content-range"
  (dream-resp-header dream-st-bad "content-range")
  "bytes */15")

(define dream-st-tests-run! (fn () {:total (+ dream-st-pass dream-st-fail) :passed dream-st-pass :failed dream-st-fail :fails dream-st-fails}))
