AtlatestRepositorysigil-http
sigil-http / tree / testtest-request.sgl
1
;;; Test suite for (sigil http request)3
(import (sigil test)4
(sigil http request))6
;; ============================================================7
;;; Request Line Parsing Tests8
;; ============================================================10
(test "parse simple GET request line"11
(let ((result (parse-request-line "GET /path HTTP/1.1")))12
(assert-true result)13
(assert-equal (car result) 'GET)14
(assert-equal (cadr result) "/path")15
(assert-equal (caddr result) #f) ; no query16
(assert-equal (cadddr result) "HTTP/1.1")))18
(test "parse GET request line with query string"19
(let ((result (parse-request-line "GET /search?q=hello&page=1 HTTP/1.1")))20
(assert-true result)21
(assert-equal (car result) 'GET)22
(assert-equal (cadr result) "/search")23
(assert-equal (caddr result) "q=hello&page=1")24
(assert-equal (cadddr result) "HTTP/1.1")))26
(test "parse POST request line"27
(let ((result (parse-request-line "POST /api/users HTTP/1.1")))28
(assert-true result)29
(assert-equal (car result) 'POST)30
(assert-equal (cadr result) "/api/users")))32
(test "parse various HTTP methods"33
(assert-equal (car (parse-request-line "PUT /resource HTTP/1.1")) 'PUT)34
(assert-equal (car (parse-request-line "DELETE /resource HTTP/1.1")) 'DELETE)35
(assert-equal (car (parse-request-line "HEAD /resource HTTP/1.1")) 'HEAD)36
(assert-equal (car (parse-request-line "OPTIONS /resource HTTP/1.1")) 'OPTIONS)37
(assert-equal (car (parse-request-line "PATCH /resource HTTP/1.1")) 'PATCH))39
(test "parse request line with HTTP/1.0"40
(let ((result (parse-request-line "GET / HTTP/1.0")))41
(assert-true result)42
(assert-equal (cadddr result) "HTTP/1.0")))44
(test "reject invalid request line - missing parts"45
(assert-false (parse-request-line "GET /path"))46
(assert-false (parse-request-line "GET"))47
(assert-false (parse-request-line "")))49
(test "reject invalid request line - unknown method"50
(assert-false (parse-request-line "INVALID /path HTTP/1.1")))52
;; ============================================================53
;;; Request Record Tests54
;; ============================================================56
(test "create request with all fields"57
(let ((req (http-request58
method: 'GET59
path: "/test"60
query: "foo=bar"61
version: "HTTP/1.1"62
headers: #{ host: "example.com" }63
body: #f)))64
(assert-equal (http-request-method req) 'GET)65
(assert-equal (http-request-path req) "/test")66
(assert-equal (http-request-query req) "foo=bar")67
(assert-equal (http-request-version req) "HTTP/1.1")68
(assert-equal (http-request-body req) #f)))70
(test "request context defaults to empty dict"71
(let ((req (http-request72
method: 'GET73
path: "/"74
query: #f75
version: "HTTP/1.1"76
headers: #{}77
body: #f)))78
(assert-true (dict? (http-request-context req)))79
(assert-true (dict-empty? (http-request-context req)))))81
;; ============================================================82
;;; Header Lookup Tests83
;; ============================================================85
(test "header lookup is case-insensitive"86
(let ((req (http-request87
method: 'GET88
path: "/"89
query: #f90
version: "HTTP/1.1"91
headers: #{ content-type: "text/html"92
x-custom-header: "value" }93
body: #f)))94
(assert-equal (http-request-header req "Content-Type") "text/html")95
(assert-equal (http-request-header req "content-type") "text/html")96
(assert-equal (http-request-header req "CONTENT-TYPE") "text/html")97
(assert-equal (http-request-header req "x-custom-header") "value")))99
(test "header lookup returns #f for missing header"100
(let ((req (http-request101
method: 'GET102
path: "/"103
query: #f104
version: "HTTP/1.1"105
headers: #{ host: "example.com" }106
body: #f)))107
(assert-false (http-request-header req "Content-Type"))108
(assert-false (http-request-header req "X-Missing"))))110
;; ============================================================111
;;; URI Construction Tests112
;; ============================================================114
(test "uri without query string"115
(let ((req (http-request116
method: 'GET117
path: "/path/to/resource"118
query: #f119
version: "HTTP/1.1"120
headers: #{}121
body: #f)))122
(assert-equal (http-request-uri req) "/path/to/resource")))124
(test "uri with query string"125
(let ((req (http-request126
method: 'GET127
path: "/search"128
query: "q=test&limit=10"129
version: "HTTP/1.1"130
headers: #{}131
body: #f)))132
(assert-equal (http-request-uri req) "/search?q=test&limit=10")))134
;; ============================================================135
;;; Content-Length and Content-Type Tests136
;; ============================================================138
(test "content-length parsing"139
(let ((req (http-request140
method: 'POST141
path: "/"142
query: #f143
version: "HTTP/1.1"144
headers: #{ content-length: "42" }145
body: #f)))146
(assert-equal (http-request-content-length req) 42)))148
(test "content-length returns #f when missing"149
(let ((req (http-request150
method: 'GET151
path: "/"152
query: #f153
version: "HTTP/1.1"154
headers: #{}155
body: #f)))156
(assert-false (http-request-content-length req))))158
(test "content-type retrieval"159
(let ((req (http-request160
method: 'POST161
path: "/"162
query: #f163
version: "HTTP/1.1"164
headers: #{ content-type: "application/json" }165
body: #f)))166
(assert-equal (http-request-content-type req) "application/json")))168
;; ============================================================169
;;; Context Tests170
;; ============================================================172
(test "context-ref returns value for existing key"173
(let ((req (http-request174
method: 'GET175
path: "/"176
query: #f177
version: "HTTP/1.1"178
headers: #{}179
body: #f180
context: #{ user: "alice" role: "admin" })))181
(assert-equal (http-request-context-ref req user:) "alice")182
(assert-equal (http-request-context-ref req role:) "admin")))184
(test "context-ref returns #f for missing key"185
(let ((req (http-request186
method: 'GET187
path: "/"188
query: #f189
version: "HTTP/1.1"190
headers: #{}191
body: #f)))192
(assert-false (http-request-context-ref req missing:))))194
(test "context-ref returns default for missing key"195
(let ((req (http-request196
method: 'GET197
path: "/"198
query: #f199
version: "HTTP/1.1"200
headers: #{}201
body: #f)))202
(assert-equal (http-request-context-ref req missing: "default") "default")))204
(test "with-context adds to context immutably"205
(let* ((req1 (http-request206
method: 'GET207
path: "/"208
query: #f209
version: "HTTP/1.1"210
headers: #{}211
body: #f))212
(req2 (http-request-with-context req1 user: "alice")))213
;; Original unchanged214
(assert-false (http-request-context-ref req1 user:))215
;; New request has context216
(assert-equal (http-request-context-ref req2 user:) "alice")217
;; Other fields preserved218
(assert-equal (http-request-method req2) 'GET)219
(assert-equal (http-request-path req2) "/")))221
;; ============================================================222
;; URL Encoding Tests223
;; ============================================================225
(test "url-encode-value passes unreserved characters through"226
(assert-equal (url-encode-value "hello") "hello")227
(assert-equal (url-encode-value "Test123") "Test123")228
(assert-equal (url-encode-value "a-b_c.d~e") "a-b_c.d~e"))230
(test "url-encode-value encodes spaces as +"231
(assert-equal (url-encode-value "hello world") "hello+world")232
(assert-equal (url-encode-value "a b c") "a+b+c"))234
(test "url-encode-value encodes special characters"235
(assert-equal (url-encode-value "a&b") "a%26b")236
(assert-equal (url-encode-value "a=b") "a%3Db")237
(assert-equal (url-encode-value "a+b") "a%2Bb")238
(assert-equal (url-encode-value "100%") "100%25"))240
(test "url-encode-value encodes unicode as UTF-8 bytes"241
(assert-equal (url-encode-value "café") "caf%C3%A9")242
(assert-equal (url-encode-value "日本") "%E6%97%A5%E6%9C%AC"))244
(test "url-encode-value handles empty string"245
(assert-equal (url-encode-value "") ""))247
(test "url-encode-value encodes path-like characters"248
(assert-equal (url-encode-value "/foo/bar") "%2Ffoo%2Fbar")249
(assert-equal (url-encode-value "a?b") "a%3Fb"))251
;; ============================================================252
;; Query String Building Tests253
;; ============================================================255
(test "build-query-string with simple pairs"256
(assert-equal (build-query-string '(("q" . "hello") ("page" . "1")))257
"?q=hello&page=1"))259
(test "build-query-string encodes values"260
(assert-equal (build-query-string '(("q" . "hello world")))261
"?q=hello+world"))263
(test "build-query-string encodes keys"264
(assert-equal (build-query-string '(("my key" . "val")))265
"?my+key=val"))267
(test "build-query-string omits pairs with #f values"268
(assert-equal (build-query-string '(("q" . "test") ("lang" . #f)))269
"?q=test"))271
(test "build-query-string returns empty string for empty list"272
(assert-equal (build-query-string '()) ""))274
(test "build-query-string returns empty string when all values are #f"275
(assert-equal (build-query-string '(("a" . #f) ("b" . #f))) ""))277
(test "build-query-string handles special characters in values"278
(assert-equal (build-query-string '(("url" . "https://example.com")))279
"?url=https%3A%2F%2Fexample.com"))281
;; ============================================================282
;;; build-repeated-params283
;; ============================================================285
(test-group "build-repeated-params"287
(test "single value"288
(assert-equal (build-repeated-params "id" '("123")) "id=123"))290
(test "multiple values"291
(assert-equal (build-repeated-params "id" '("123" "456" "789"))292
"id=123&id=456&id=789"))294
(test "encodes special characters"295
(assert-equal (build-repeated-params "q" '("hello world"))296
"q=hello+world")))299
;; ============================================================300
;;; ensure-list301
;; ============================================================303
(test-group "ensure-list"305
(test "wraps string in list"306
(assert-equal (ensure-list "hello") '("hello")))308
(test "passes list through"309
(assert-equal (ensure-list '("a" "b")) '("a" "b")))311
(test "passes empty list through"312
(assert-equal (ensure-list '()) '())))315
(run-tests)