馃 A practical web framework for Gleam
3
fork

Configure Feed

Select the types of activity you want to include in your feed.

at 9541370af4e061ed6fb68106423918540adc6905 1022 lines 27 kB view raw
1import gleam/bit_array 2import gleam/crypto 3import gleam/dict 4import gleam/dynamic.{type Dynamic} 5import gleam/erlang 6import gleam/http 7import gleam/http/request 8import gleam/http/response.{Response} 9import gleam/int 10import gleam/list 11import gleam/set 12import gleam/string 13import gleam/string_builder 14import gleeunit 15import gleeunit/should 16import simplifile 17import wisp 18import wisp/testing 19 20pub fn main() { 21 wisp.configure_logger() 22 gleeunit.main() 23} 24 25fn form_handler( 26 request: wisp.Request, 27 callback: fn(wisp.FormData) -> anything, 28) -> wisp.Response { 29 use form <- wisp.require_form(request) 30 callback(form) 31 wisp.ok() 32} 33 34fn json_handler( 35 request: wisp.Request, 36 callback: fn(Dynamic) -> anything, 37) -> wisp.Response { 38 use json <- wisp.require_json(request) 39 callback(json) 40 wisp.ok() 41} 42 43pub fn ok_test() { 44 wisp.ok() 45 |> should.equal(Response(200, [], wisp.Empty)) 46} 47 48pub fn created_test() { 49 wisp.created() 50 |> should.equal(Response(201, [], wisp.Empty)) 51} 52 53pub fn accepted_test() { 54 wisp.accepted() 55 |> should.equal(Response(202, [], wisp.Empty)) 56} 57 58pub fn no_content_test() { 59 wisp.no_content() 60 |> should.equal(Response(204, [], wisp.Empty)) 61} 62 63pub fn redirect_test() { 64 wisp.redirect(to: "https://example.com/wibble") 65 |> should.equal(Response( 66 303, 67 [#("location", "https://example.com/wibble")], 68 wisp.Empty, 69 )) 70} 71 72pub fn moved_permanently_test() { 73 wisp.moved_permanently(to: "https://example.com/wobble") 74 |> should.equal(Response( 75 308, 76 [#("location", "https://example.com/wobble")], 77 wisp.Empty, 78 )) 79} 80 81pub fn internal_server_error_test() { 82 wisp.internal_server_error() 83 |> should.equal(Response(500, [], wisp.Empty)) 84} 85 86pub fn entity_too_large_test() { 87 wisp.entity_too_large() 88 |> should.equal(Response(413, [], wisp.Empty)) 89} 90 91pub fn bad_request_test() { 92 wisp.bad_request() 93 |> should.equal(Response(400, [], wisp.Empty)) 94} 95 96pub fn not_found_test() { 97 wisp.not_found() 98 |> should.equal(Response(404, [], wisp.Empty)) 99} 100 101pub fn method_not_allowed_test() { 102 wisp.method_not_allowed([http.Get, http.Patch, http.Delete]) 103 |> should.equal(Response(405, [#("allow", "DELETE, GET, PATCH")], wisp.Empty)) 104} 105 106pub fn unsupported_media_type_test() { 107 wisp.unsupported_media_type(accept: ["application/json", "text/plain"]) 108 |> should.equal(Response( 109 415, 110 [#("accept", "application/json, text/plain")], 111 wisp.Empty, 112 )) 113} 114 115pub fn unprocessable_entity_test() { 116 wisp.unprocessable_entity() 117 |> should.equal(Response(422, [], wisp.Empty)) 118} 119 120pub fn json_response_test() { 121 let body = string_builder.from_string("{\"one\":1,\"two\":2}") 122 let response = wisp.json_response(body, 201) 123 response.status 124 |> should.equal(201) 125 response.headers 126 |> should.equal([#("content-type", "application/json")]) 127 response 128 |> testing.string_body 129 |> should.equal("{\"one\":1,\"two\":2}") 130} 131 132pub fn html_response_test() { 133 let body = string_builder.from_string("Hello, world!") 134 let response = wisp.html_response(body, 200) 135 response.status 136 |> should.equal(200) 137 response.headers 138 |> should.equal([#("content-type", "text/html")]) 139 response 140 |> testing.string_body 141 |> should.equal("Hello, world!") 142} 143 144pub fn html_body_test() { 145 let body = string_builder.from_string("Hello, world!") 146 let response = 147 wisp.method_not_allowed([http.Get]) 148 |> wisp.html_body(body) 149 response.status 150 |> should.equal(405) 151 response.headers 152 |> should.equal([#("allow", "GET"), #("content-type", "text/html")]) 153 response 154 |> testing.string_body 155 |> should.equal("Hello, world!") 156} 157 158pub fn random_string_test() { 159 let count = 10_000 160 let new = fn(_) { 161 let random = wisp.random_string(64) 162 string.length(random) 163 |> should.equal(64) 164 random 165 } 166 167 list.repeat(Nil, count) 168 |> list.map(new) 169 |> set.from_list 170 |> set.size 171 |> should.equal(count) 172} 173 174pub fn set_get_secret_key_base_test() { 175 let request = testing.get("/", []) 176 let valid = wisp.random_string(64) 177 let too_short = wisp.random_string(63) 178 179 request 180 |> wisp.get_secret_key_base 181 |> should.equal(testing.default_secret_key_base) 182 183 request 184 |> wisp.set_secret_key_base(valid) 185 |> wisp.get_secret_key_base 186 |> should.equal(valid) 187 188 // Panics if the key is too short 189 erlang.rescue(fn() { wisp.set_secret_key_base(request, too_short) }) 190 |> should.be_error 191} 192 193pub fn set_get_max_body_size_test() { 194 let request = testing.get("/", []) 195 196 request 197 |> wisp.get_max_body_size 198 |> should.equal(8_000_000) 199 200 request 201 |> wisp.set_max_body_size(10) 202 |> wisp.get_max_body_size 203 |> should.equal(10) 204} 205 206pub fn set_get_max_files_size_test() { 207 let request = testing.get("/", []) 208 209 request 210 |> wisp.get_max_files_size 211 |> should.equal(32_000_000) 212 213 request 214 |> wisp.set_max_files_size(10) 215 |> wisp.get_max_files_size 216 |> should.equal(10) 217} 218 219pub fn set_get_read_chunk_size_test() { 220 let request = testing.get("/", []) 221 222 request 223 |> wisp.get_read_chunk_size 224 |> should.equal(1_000_000) 225 226 request 227 |> wisp.set_read_chunk_size(10) 228 |> wisp.get_read_chunk_size 229 |> should.equal(10) 230} 231 232pub fn path_segments_test() { 233 request.new() 234 |> request.set_path("/one/two/three") 235 |> wisp.path_segments 236 |> should.equal(["one", "two", "three"]) 237} 238 239pub fn method_override_test() { 240 // These methods can be overridden to 241 use method <- list.each([http.Put, http.Delete, http.Patch]) 242 243 let request = 244 request.new() 245 |> request.set_method(method) 246 |> request.set_query([#("_method", http.method_to_string(method))]) 247 request 248 |> wisp.method_override 249 |> should.equal(request.set_method(request, method)) 250} 251 252pub fn method_override_unacceptable_unoriginal_method_test() { 253 // These methods are not allowed to be overridden 254 use method <- list.each([ 255 http.Head, 256 http.Put, 257 http.Delete, 258 http.Trace, 259 http.Connect, 260 http.Options, 261 http.Patch, 262 http.Other("MYSTERY"), 263 ]) 264 265 let request = 266 request.new() 267 |> request.set_method(method) 268 |> request.set_query([#("_method", "DELETE")]) 269 request 270 |> wisp.method_override 271 |> should.equal(request) 272} 273 274pub fn method_override_unacceptable_target_method_test() { 275 // These methods are not allowed to be overridden to 276 use method <- list.each([ 277 http.Get, 278 http.Head, 279 http.Trace, 280 http.Connect, 281 http.Options, 282 http.Other("MYSTERY"), 283 ]) 284 285 let request = 286 request.new() 287 |> request.set_method(http.Post) 288 |> request.set_query([#("_method", http.method_to_string(method))]) 289 request 290 |> wisp.method_override 291 |> should.equal(request) 292} 293 294pub fn require_method_test() { 295 { 296 let request = request.new() 297 use <- wisp.require_method(request, http.Get) 298 wisp.ok() 299 } 300 |> should.equal(wisp.ok()) 301} 302 303pub fn require_method_invalid_test() { 304 { 305 let request = request.set_method(request.new(), http.Post) 306 use <- wisp.require_method(request, http.Get) 307 panic as "should be unreachable" 308 } 309 |> should.equal(wisp.method_not_allowed([http.Get])) 310} 311 312pub fn require_string_body_test() { 313 { 314 let request = testing.post("/", [], "Hello, Joe!") 315 use body <- wisp.require_string_body(request) 316 body 317 |> should.equal("Hello, Joe!") 318 wisp.accepted() 319 } 320 |> should.equal(wisp.accepted()) 321} 322 323pub fn require_string_body_invalid_test() { 324 { 325 let request = testing.request(http.Post, "/", [], <<254>>) 326 use _ <- wisp.require_string_body(request) 327 panic as "should be unreachable" 328 } 329 |> should.equal(wisp.bad_request()) 330} 331 332pub fn rescue_crashes_error_test() { 333 // TODO: Determine how to silence the logger for this test. 334 { 335 use <- wisp.rescue_crashes 336 panic as "we need to crash to test the middleware" 337 } 338 |> should.equal(wisp.internal_server_error()) 339} 340 341pub fn rescue_crashes_ok_test() { 342 { 343 use <- wisp.rescue_crashes 344 wisp.ok() 345 } 346 |> should.equal(wisp.ok()) 347} 348 349pub fn serve_static_test() { 350 let handler = fn(request) { 351 use <- wisp.serve_static(request, under: "/stuff", from: "./") 352 wisp.ok() 353 } 354 355 // Get a text file 356 let response = 357 testing.get("/stuff/test/fixture.txt", []) 358 |> handler 359 response.status 360 |> should.equal(200) 361 response.headers 362 |> should.equal([#("content-type", "text/plain")]) 363 response.body 364 |> should.equal(wisp.File("./test/fixture.txt")) 365 366 // Get a json file 367 let response = 368 testing.get("/stuff/test/fixture.json", []) 369 |> handler 370 response.status 371 |> should.equal(200) 372 response.headers 373 |> should.equal([#("content-type", "application/json")]) 374 response.body 375 |> should.equal(wisp.File("./test/fixture.json")) 376 377 // Get something not handled by the static file server 378 let response = 379 testing.get("/stuff/this-does-not-exist", []) 380 |> handler 381 response.status 382 |> should.equal(200) 383 response.headers 384 |> should.equal([]) 385 response.body 386 |> should.equal(wisp.Empty) 387} 388 389pub fn serve_static_under_has_no_trailing_slash_test() { 390 let request = 391 testing.get("/", []) 392 |> request.set_path("/stuff/test/fixture.txt") 393 let response = { 394 use <- wisp.serve_static(request, under: "stuff", from: "./") 395 wisp.ok() 396 } 397 response.status 398 |> should.equal(200) 399 response.headers 400 |> should.equal([#("content-type", "text/plain")]) 401 response.body 402 |> should.equal(wisp.File("./test/fixture.txt")) 403} 404 405pub fn serve_static_from_has_no_trailing_slash_test() { 406 let request = 407 testing.get("/", []) 408 |> request.set_path("/stuff/test/fixture.txt") 409 let response = { 410 use <- wisp.serve_static(request, under: "stuff", from: ".") 411 wisp.ok() 412 } 413 response.status 414 |> should.equal(200) 415 response.headers 416 |> should.equal([#("content-type", "text/plain")]) 417 response.body 418 |> should.equal(wisp.File("./test/fixture.txt")) 419} 420 421pub fn serve_static_not_found_test() { 422 let request = 423 testing.get("/", []) 424 |> request.set_path("/stuff/credit_card_details.txt") 425 { 426 use <- wisp.serve_static(request, under: "/stuff", from: "./") 427 wisp.ok() 428 } 429 |> should.equal(wisp.ok()) 430} 431 432pub fn serve_static_go_up_test() { 433 let request = 434 testing.get("/", []) 435 |> request.set_path("/../test/fixture.txt") 436 { 437 use <- wisp.serve_static(request, under: "/stuff", from: "./src/") 438 wisp.ok() 439 } 440 |> should.equal(wisp.ok()) 441} 442 443pub fn temporary_file_test() { 444 // Create tmp files for a first request 445 let request1 = testing.get("/", []) 446 let assert Ok(request1_file1) = wisp.new_temporary_file(request1) 447 let assert Ok(request1_file2) = wisp.new_temporary_file(request1) 448 449 // The files exist 450 request1_file1 451 |> should.not_equal(request1_file2) 452 let assert Ok(_) = simplifile.read(request1_file1) 453 let assert Ok(_) = simplifile.read(request1_file2) 454 455 // Create tmp files for a second request 456 let request2 = testing.get("/", []) 457 let assert Ok(request2_file1) = wisp.new_temporary_file(request2) 458 let assert Ok(request2_file2) = wisp.new_temporary_file(request2) 459 460 // The files exist 461 request2_file1 462 |> should.not_equal(request1_file2) 463 let assert Ok(_) = simplifile.read(request2_file1) 464 let assert Ok(_) = simplifile.read(request2_file2) 465 466 // Delete the files for the first request 467 let assert Ok(_) = wisp.delete_temporary_files(request1) 468 469 // They no longer exist 470 let assert Error(simplifile.Enoent) = simplifile.read(request1_file1) 471 let assert Error(simplifile.Enoent) = simplifile.read(request1_file2) 472 473 // The files for the second request still exist 474 let assert Ok(_) = simplifile.read(request2_file1) 475 let assert Ok(_) = simplifile.read(request2_file2) 476 477 // Delete the files for the first request 478 let assert Ok(_) = wisp.delete_temporary_files(request2) 479 480 // They no longer exist 481 let assert Error(simplifile.Enoent) = simplifile.read(request2_file1) 482 let assert Error(simplifile.Enoent) = simplifile.read(request2_file2) 483} 484 485pub fn require_content_type_test() { 486 { 487 let request = testing.get("/", [#("content-type", "text/plain")]) 488 use <- wisp.require_content_type(request, "text/plain") 489 wisp.ok() 490 } 491 |> should.equal(wisp.ok()) 492} 493 494pub fn require_content_type_missing_test() { 495 { 496 let request = testing.get("/", []) 497 use <- wisp.require_content_type(request, "text/plain") 498 wisp.ok() 499 } 500 |> should.equal(wisp.unsupported_media_type(["text/plain"])) 501} 502 503pub fn require_content_type_invalid_test() { 504 { 505 let request = testing.get("/", [#("content-type", "text/plain")]) 506 use <- wisp.require_content_type(request, "text/html") 507 panic as "should be unreachable" 508 } 509 |> should.equal(wisp.unsupported_media_type(["text/html"])) 510} 511 512pub fn json_test() { 513 testing.post("/", [], "{\"one\":1,\"two\":2}") 514 |> request.set_header("content-type", "application/json") 515 |> json_handler(fn(json) { 516 json 517 |> should.equal(dynamic.from(dict.from_list([#("one", 1), #("two", 2)]))) 518 }) 519 |> should.equal(wisp.ok()) 520} 521 522pub fn json_wrong_content_type_test() { 523 testing.post("/", [], "{\"one\":1,\"two\":2}") 524 |> request.set_header("content-type", "text/plain") 525 |> json_handler(fn(_) { panic as "should be unreachable" }) 526 |> should.equal(wisp.unsupported_media_type(["application/json"])) 527} 528 529pub fn json_no_content_type_test() { 530 testing.post("/", [], "{\"one\":1,\"two\":2}") 531 |> json_handler(fn(_) { panic as "should be unreachable" }) 532 |> should.equal(wisp.unsupported_media_type(["application/json"])) 533} 534 535pub fn json_too_big_test() { 536 testing.post("/", [], "{\"one\":1,\"two\":2}") 537 |> wisp.set_max_body_size(1) 538 |> request.set_header("content-type", "application/json") 539 |> json_handler(fn(_) { panic as "should be unreachable" }) 540 |> should.equal(Response(413, [], wisp.Empty)) 541} 542 543pub fn json_syntax_error_test() { 544 testing.post("/", [], "{\"one\":1,\"two\":2") 545 |> request.set_header("content-type", "application/json") 546 |> json_handler(fn(_) { panic as "should be unreachable" }) 547 |> should.equal(Response(400, [], wisp.Empty)) 548} 549 550pub fn urlencoded_form_test() { 551 testing.post("/", [], "one=1&two=2") 552 |> request.set_header("content-type", "application/x-www-form-urlencoded") 553 |> form_handler(fn(form) { 554 form 555 |> should.equal(wisp.FormData([#("one", "1"), #("two", "2")], [])) 556 }) 557 |> should.equal(wisp.ok()) 558} 559 560pub fn urlencoded_form_with_charset_test() { 561 testing.post("/", [], "one=1&two=2") 562 |> request.set_header( 563 "content-type", 564 "application/x-www-form-urlencoded; charset=UTF-8", 565 ) 566 |> form_handler(fn(form) { 567 form 568 |> should.equal(wisp.FormData([#("one", "1"), #("two", "2")], [])) 569 }) 570 |> should.equal(wisp.ok()) 571} 572 573pub fn urlencoded_too_big_form_test() { 574 testing.post("/", [], "12") 575 |> request.set_header("content-type", "application/x-www-form-urlencoded") 576 |> wisp.set_max_body_size(1) 577 |> form_handler(fn(_) { panic as "should be unreachable" }) 578 |> should.equal(Response(413, [], wisp.Empty)) 579} 580 581pub fn multipart_form_test() { 582 "--theboundary\r 583Content-Disposition: form-data; name=\"one\"\r 584\r 5851\r 586--theboundary\r 587Content-Disposition: form-data; name=\"two\"\r 588\r 5892\r 590--theboundary--\r 591" 592 |> testing.post("/", [], _) 593 |> request.set_header( 594 "content-type", 595 "multipart/form-data; boundary=theboundary", 596 ) 597 |> form_handler(fn(form) { 598 form 599 |> should.equal(wisp.FormData([#("one", "1"), #("two", "2")], [])) 600 }) 601 |> should.equal(wisp.ok()) 602} 603 604pub fn multipart_form_too_big_test() { 605 "--theboundary\r 606Content-Disposition: form-data; name=\"one\"\r 607\r 6081\r 609--theboundary--\r 610" 611 |> testing.post("/", [], _) 612 |> wisp.set_max_body_size(1) 613 |> request.set_header( 614 "content-type", 615 "multipart/form-data; boundary=theboundary", 616 ) 617 |> form_handler(fn(_) { panic as "should be unreachable" }) 618 |> should.equal(Response(413, [], wisp.Empty)) 619} 620 621pub fn multipart_form_no_boundary_test() { 622 "--theboundary\r 623Content-Disposition: form-data; name=\"one\"\r 624\r 6251\r 626--theboundary--\r 627" 628 |> testing.post("/", [], _) 629 |> request.set_header("content-type", "multipart/form-data") 630 |> form_handler(fn(_) { panic as "should be unreachable" }) 631 |> should.equal(Response(400, [], wisp.Empty)) 632} 633 634pub fn multipart_form_invalid_format_test() { 635 "--theboundary\r\n--theboundary--\r\n" 636 |> testing.post("/", [], _) 637 |> request.set_header( 638 "content-type", 639 "multipart/form-data; boundary=theboundary", 640 ) 641 |> form_handler(fn(_) { panic as "should be unreachable" }) 642 |> should.equal(Response(400, [], wisp.Empty)) 643} 644 645pub fn form_unknown_content_type_test() { 646 "one=1&two=2" 647 |> testing.post("/", [], _) 648 |> request.set_header("content-type", "text/form") 649 |> form_handler(fn(_) { panic as "should be unreachable" }) 650 |> should.equal(Response( 651 415, 652 [#("accept", "application/x-www-form-urlencoded, multipart/form-data")], 653 wisp.Empty, 654 )) 655} 656 657pub fn multipart_form_with_files_test() { 658 "--theboundary\r 659Content-Disposition: form-data; name=\"one\"\r 660\r 6611\r 662--theboundary\r 663Content-Disposition: form-data; name=\"two\"; filename=\"file.txt\"\r 664\r 665file contents\r 666--theboundary--\r 667" 668 |> testing.post("/", [], _) 669 |> request.set_header( 670 "content-type", 671 "multipart/form-data; boundary=theboundary", 672 ) 673 |> form_handler(fn(form) { 674 let assert [#("one", "1")] = form.values 675 let assert [#("two", wisp.UploadedFile("file.txt", path))] = form.files 676 let assert Ok("file contents") = simplifile.read(path) 677 }) 678 |> should.equal(wisp.ok()) 679} 680 681pub fn multipart_form_files_too_big_test() { 682 let testcase = fn(limit, callback) { 683 "--theboundary\r 684Content-Disposition: form-data; name=\"two\"; filename=\"file.txt\"\r 685\r 68612\r 687--theboundary\r 688Content-Disposition: form-data; name=\"two\"\r 689\r 690this one isn't a file. If it was it would use the entire quota.\r 691--theboundary\r 692Content-Disposition: form-data; name=\"two\"; filename=\"another.txt\"\r 693\r 69434\r 695--theboundary--\r 696" 697 |> testing.post("/", [], _) 698 |> wisp.set_max_files_size(limit) 699 |> request.set_header( 700 "content-type", 701 "multipart/form-data; boundary=theboundary", 702 ) 703 |> form_handler(callback) 704 } 705 706 testcase(1, fn(_) { panic as "should be unreachable for limit of 1" }) 707 |> should.equal(Response(413, [], wisp.Empty)) 708 709 testcase(2, fn(_) { panic as "should be unreachable for limit of 2" }) 710 |> should.equal(Response(413, [], wisp.Empty)) 711 712 testcase(3, fn(_) { panic as "should be unreachable for limit of 3" }) 713 |> should.equal(Response(413, [], wisp.Empty)) 714 715 testcase(4, fn(_) { Nil }) 716 |> should.equal(Response(200, [], wisp.Empty)) 717} 718 719pub fn handle_head_test() { 720 let handler = fn(request, header) { 721 use request <- wisp.handle_head(request) 722 use <- wisp.require_method(request, http.Get) 723 724 list.key_find(request.headers, "x-original-method") 725 |> should.equal(header) 726 727 string_builder.from_string("Hello!") 728 |> wisp.html_response(201) 729 } 730 731 testing.get("/", []) 732 |> request.set_method(http.Get) 733 |> handler(Error(Nil)) 734 |> should.equal(Response( 735 201, 736 [#("content-type", "text/html")], 737 wisp.Text(string_builder.from_string("Hello!")), 738 )) 739 740 testing.get("/", []) 741 |> request.set_method(http.Head) 742 |> handler(Ok("HEAD")) 743 |> should.equal(Response(201, [#("content-type", "text/html")], wisp.Empty)) 744 745 testing.get("/", []) 746 |> request.set_method(http.Post) 747 |> handler(Error(Nil)) 748 |> should.equal(Response(405, [#("allow", "GET")], wisp.Empty)) 749} 750 751pub fn multipart_form_fields_are_sorted_test() { 752 "--theboundary\r 753Content-Disposition: form-data; name=\"xx\"\r 754\r 755XX\r 756--theboundary\r 757Content-Disposition: form-data; name=\"zz\"\r 758\r 759ZZ\r 760--theboundary\r 761Content-Disposition: form-data; name=\"yy\"\r 762\r 763YY\r 764--theboundary\r 765Content-Disposition: form-data; name=\"cc\"; filename=\"file.txt\"\r 766\r 767CC\r 768--theboundary\r 769Content-Disposition: form-data; name=\"aa\"; filename=\"file.txt\"\r 770\r 771AA\r 772--theboundary\r 773Content-Disposition: form-data; name=\"bb\"; filename=\"file.txt\"\r 774\r 775BB\r 776--theboundary--\r 777" 778 |> testing.post("/", [], _) 779 |> request.set_header( 780 "content-type", 781 "multipart/form-data; boundary=theboundary", 782 ) 783 |> form_handler(fn(form) { 784 // Fields are sorted by name. 785 let assert [#("xx", "XX"), #("yy", "YY"), #("zz", "ZZ")] = form.values 786 let assert [ 787 #("aa", wisp.UploadedFile("file.txt", path_a)), 788 #("bb", wisp.UploadedFile("file.txt", path_b)), 789 #("cc", wisp.UploadedFile("file.txt", path_c)), 790 ] = form.files 791 let assert Ok("AA") = simplifile.read(path_a) 792 let assert Ok("BB") = simplifile.read(path_b) 793 let assert Ok("CC") = simplifile.read(path_c) 794 }) 795 |> should.equal(wisp.ok()) 796} 797 798pub fn urlencoded_form_fields_are_sorted_test() { 799 "xx=XX&zz=ZZ&yy=YY&cc=CC&aa=AA&bb=BB" 800 |> testing.post("/", [], _) 801 |> request.set_header("content-type", "application/x-www-form-urlencoded") 802 |> form_handler(fn(form) { 803 // Fields are sorted by name. 804 let assert [ 805 #("aa", "AA"), 806 #("bb", "BB"), 807 #("cc", "CC"), 808 #("xx", "XX"), 809 #("yy", "YY"), 810 #("zz", "ZZ"), 811 ] = form.values 812 }) 813 |> should.equal(wisp.ok()) 814} 815 816pub fn message_signing_test() { 817 let request = testing.get("/", []) 818 let request1 = wisp.set_secret_key_base(request, wisp.random_string(64)) 819 let request2 = wisp.set_secret_key_base(request, wisp.random_string(64)) 820 821 let signed1 = wisp.sign_message(request1, <<"a":utf8>>, crypto.Sha512) 822 let signed2 = wisp.sign_message(request2, <<"b":utf8>>, crypto.Sha512) 823 824 let assert Ok(<<"a":utf8>>) = wisp.verify_signed_message(request1, signed1) 825 let assert Ok(<<"b":utf8>>) = wisp.verify_signed_message(request2, signed2) 826 827 let assert Error(Nil) = wisp.verify_signed_message(request1, signed2) 828 let assert Error(Nil) = wisp.verify_signed_message(request2, signed1) 829} 830 831pub fn create_canned_connection_test() { 832 let secret = wisp.random_string(64) 833 let connection = wisp.create_canned_connection(<<"Hello!":utf8>>, secret) 834 let request = request.set_body(request.new(), connection) 835 836 request 837 |> wisp.get_secret_key_base 838 |> should.equal(secret) 839 840 request 841 |> wisp.read_body_to_bitstring 842 |> should.equal(Ok(<<"Hello!":utf8>>)) 843} 844 845pub fn escape_html_test() { 846 "<script>alert('&');</script>" 847 |> wisp.escape_html 848 |> should.equal("&lt;script&gt;alert('&amp;');&lt;/script&gt;") 849} 850 851pub fn set_header_test() { 852 wisp.ok() 853 |> wisp.set_header("accept", "application/json") 854 |> wisp.set_header("accept", "text/plain") 855 |> wisp.set_header("content-type", "text/html") 856 |> should.equal(Response( 857 200, 858 [#("accept", "text/plain"), #("content-type", "text/html")], 859 wisp.Empty, 860 )) 861} 862 863pub fn string_body_test() { 864 wisp.ok() 865 |> wisp.string_body("Hello, world!") 866 |> should.equal(Response( 867 200, 868 [], 869 wisp.Text(string_builder.from_string("Hello, world!")), 870 )) 871} 872 873pub fn string_builder_body_test() { 874 wisp.ok() 875 |> wisp.string_builder_body(string_builder.from_string("Hello, world!")) 876 |> should.equal(Response( 877 200, 878 [], 879 wisp.Text(string_builder.from_string("Hello, world!")), 880 )) 881} 882 883pub fn json_body_test() { 884 wisp.ok() 885 |> wisp.json_body(string_builder.from_string("{\"one\":1,\"two\":2}")) 886 |> should.equal(Response( 887 200, 888 [#("content-type", "application/json")], 889 wisp.Text(string_builder.from_string("{\"one\":1,\"two\":2}")), 890 )) 891} 892 893pub fn priv_directory_test() { 894 let assert Error(Nil) = wisp.priv_directory("unknown_application") 895 896 let assert Ok(dir) = wisp.priv_directory("wisp") 897 let assert True = string.ends_with(dir, "/wisp/priv") 898 899 let assert Ok(dir) = wisp.priv_directory("gleam_erlang") 900 let assert True = string.ends_with(dir, "/gleam_erlang/priv") 901 902 let assert Ok(dir) = wisp.priv_directory("gleam_stdlib") 903 let assert True = string.ends_with(dir, "/gleam_stdlib/priv") 904} 905 906pub fn set_cookie_plain_test() { 907 let req = testing.get("/", []) 908 let response = 909 wisp.ok() 910 |> wisp.set_cookie(req, "id", "123", wisp.PlainText, 60 * 60 * 24 * 365) 911 |> wisp.set_cookie(req, "flash", "hi-there", wisp.PlainText, 60) 912 913 response.headers 914 |> should.equal([ 915 #( 916 "set-cookie", 917 "flash=aGktdGhlcmU; Max-Age=60; Path=/; Secure; HttpOnly; SameSite=Lax", 918 ), 919 #( 920 "set-cookie", 921 "id=MTIz; Max-Age=31536000; Path=/; Secure; HttpOnly; SameSite=Lax", 922 ), 923 ]) 924} 925 926pub fn set_cookie_signed_test() { 927 let req = testing.get("/", []) 928 let response = 929 wisp.ok() 930 |> wisp.set_cookie(req, "id", "123", wisp.Signed, 60 * 60 * 24 * 365) 931 |> wisp.set_cookie(req, "flash", "hi-there", wisp.Signed, 60) 932 933 response.headers 934 |> should.equal([ 935 #( 936 "set-cookie", 937 "flash=SFM1MTI.aGktdGhlcmU.uWUWvrAleKQ2jsWcU97HzGgPqtLjjUgl4oe40-RPJ5qRRcE_soXPacgmaHTLxK3xZbOJ5DOTIRMI0szD4Re7wA; Max-Age=60; Path=/; Secure; HttpOnly; SameSite=Lax", 938 ), 939 #( 940 "set-cookie", 941 "id=SFM1MTI.MTIz.LT5VxVwopQ7VhZ3OzF6Pgy3sfIIQaiUH5anHXNRt6o3taBMfCNBQskZ-EIkodchsPGSu_AJrAHjMfYPV7D5ogg; Max-Age=31536000; Path=/; Secure; HttpOnly; SameSite=Lax", 942 ), 943 ]) 944} 945 946pub fn get_cookie_test() { 947 let request = 948 testing.get("/", [ 949 // Plain text 950 #("cookie", "plain=MTIz"), 951 // Signed 952 #( 953 "cookie", 954 "signed=SFM1MTI.aGktdGhlcmU.uWUWvrAleKQ2jsWcU97HzGgPqtLjjUgl4oe40-RPJ5qRRcE_soXPacgmaHTLxK3xZbOJ5DOTIRMI0szD4Re7wA", 955 ), 956 // Signed but tampered with 957 #( 958 "cookie", 959 "signed-and-tampered-with=SFM1MTI.aGktdGhlcmU.uWUWvrAleKQ2jsWcU97HzGgPqtLjjUgl4oe40-RPJ5qRRcE_soXPacgmaHTLxK3xZbOJ5DOTIRMI0szD4Re7wAA", 960 ), 961 ]) 962 963 request 964 |> wisp.get_cookie("plain", wisp.PlainText) 965 |> should.equal(Ok("123")) 966 request 967 |> wisp.get_cookie("plain", wisp.Signed) 968 |> should.equal(Error(Nil)) 969 970 request 971 |> wisp.get_cookie("signed", wisp.PlainText) 972 |> should.equal(Error(Nil)) 973 request 974 |> wisp.get_cookie("signed", wisp.Signed) 975 |> should.equal(Ok("hi-there")) 976 977 request 978 |> wisp.get_cookie("signed-and-tampered-with", wisp.PlainText) 979 |> should.equal(Error(Nil)) 980 request 981 |> wisp.get_cookie("signed-and-tampered-with", wisp.Signed) 982 |> should.equal(Error(Nil)) 983 984 request 985 |> wisp.get_cookie("unknown", wisp.PlainText) 986 |> should.equal(Error(Nil)) 987 request 988 |> wisp.get_cookie("unknown", wisp.Signed) 989 |> should.equal(Error(Nil)) 990} 991 992// Let's roundtrip signing and verification a bunch of times to have confidence 993// it works, and that we detect any regressions. 994pub fn cookie_sign_roundtrip_test() { 995 use _ <- list.each(list.repeat(1, 10_000)) 996 let message = 997 <<int.to_string(int.random(1_000_000_000_000_000)):utf8>> 998 |> bit_array.base64_encode(True) 999 let req = testing.get("/", []) 1000 let signed = wisp.sign_message(req, <<message:utf8>>, crypto.Sha512) 1001 let req = testing.get("/", [#("cookie", "message=" <> signed)]) 1002 let assert Ok(out) = wisp.get_cookie(req, "message", wisp.Signed) 1003 out 1004 |> should.equal(message) 1005} 1006 1007pub fn get_query_test() { 1008 testing.get("/wibble?wobble=1&wubble=2&wobble=3&wabble", []) 1009 |> wisp.get_query 1010 |> should.equal([ 1011 #("wobble", "1"), 1012 #("wubble", "2"), 1013 #("wobble", "3"), 1014 #("wabble", ""), 1015 ]) 1016} 1017 1018pub fn get_query_no_query_test() { 1019 testing.get("/wibble", []) 1020 |> wisp.get_query 1021 |> should.equal([]) 1022}