import exception
import gleam/bit_array
import gleam/crypto
import gleam/dict
import gleam/dynamic.{type Dynamic}
import gleam/erlang
import gleam/http
import gleam/http/request
import gleam/http/response.{Response}
import gleam/int
import gleam/list
import gleam/set
import gleam/string
import gleam/string_tree
import gleeunit
import gleeunit/should
import simplifile
import wisp
import wisp/testing
pub fn main() {
wisp.configure_logger()
gleeunit.main()
}
fn form_handler(
request: wisp.Request,
callback: fn(wisp.FormData) -> anything,
) -> wisp.Response {
use form <- wisp.require_form(request)
callback(form)
wisp.ok()
}
fn json_handler(
request: wisp.Request,
callback: fn(Dynamic) -> anything,
) -> wisp.Response {
use json <- wisp.require_json(request)
callback(json)
wisp.ok()
}
pub fn ok_test() {
wisp.ok()
|> should.equal(Response(200, [], wisp.Empty))
}
pub fn created_test() {
wisp.created()
|> should.equal(Response(201, [], wisp.Empty))
}
pub fn accepted_test() {
wisp.accepted()
|> should.equal(Response(202, [], wisp.Empty))
}
pub fn no_content_test() {
wisp.no_content()
|> should.equal(Response(204, [], wisp.Empty))
}
pub fn redirect_test() {
wisp.redirect(to: "https://example.com/wibble")
|> should.equal(Response(
303,
[#("location", "https://example.com/wibble")],
wisp.Empty,
))
}
pub fn moved_permanently_test() {
wisp.moved_permanently(to: "https://example.com/wobble")
|> should.equal(Response(
308,
[#("location", "https://example.com/wobble")],
wisp.Empty,
))
}
pub fn internal_server_error_test() {
wisp.internal_server_error()
|> should.equal(Response(500, [], wisp.Empty))
}
pub fn entity_too_large_test() {
wisp.entity_too_large()
|> should.equal(Response(413, [], wisp.Empty))
}
pub fn bad_request_test() {
wisp.bad_request()
|> should.equal(Response(400, [], wisp.Empty))
}
pub fn not_found_test() {
wisp.not_found()
|> should.equal(Response(404, [], wisp.Empty))
}
pub fn method_not_allowed_test() {
wisp.method_not_allowed([http.Get, http.Patch, http.Delete])
|> should.equal(Response(405, [#("allow", "DELETE, GET, PATCH")], wisp.Empty))
}
pub fn unsupported_media_type_test() {
wisp.unsupported_media_type(accept: ["application/json", "text/plain"])
|> should.equal(Response(
415,
[#("accept", "application/json, text/plain")],
wisp.Empty,
))
}
pub fn unprocessable_entity_test() {
wisp.unprocessable_entity()
|> should.equal(Response(422, [], wisp.Empty))
}
pub fn json_response_test() {
let body = string_tree.from_string("{\"one\":1,\"two\":2}")
let response = wisp.json_response(body, 201)
response.status
|> should.equal(201)
response.headers
|> should.equal([#("content-type", "application/json; charset=utf-8")])
response
|> testing.string_body
|> should.equal("{\"one\":1,\"two\":2}")
}
pub fn html_response_test() {
let body = string_tree.from_string("Hello, world!")
let response = wisp.html_response(body, 200)
response.status
|> should.equal(200)
response.headers
|> should.equal([#("content-type", "text/html; charset=utf-8")])
response
|> testing.string_body
|> should.equal("Hello, world!")
}
pub fn html_body_test() {
let body = string_tree.from_string("Hello, world!")
let response =
wisp.method_not_allowed([http.Get])
|> wisp.html_body(body)
response.status
|> should.equal(405)
response.headers
|> should.equal([
#("allow", "GET"),
#("content-type", "text/html; charset=utf-8"),
])
response
|> testing.string_body
|> should.equal("Hello, world!")
}
pub fn random_string_test() {
let count = 10_000
let new = fn(_) {
let random = wisp.random_string(64)
string.length(random)
|> should.equal(64)
random
}
list.repeat(Nil, count)
|> list.map(new)
|> set.from_list
|> set.size
|> should.equal(count)
}
pub fn set_get_secret_key_base_test() {
let request = testing.get("/", [])
let valid = wisp.random_string(64)
let too_short = wisp.random_string(63)
request
|> wisp.get_secret_key_base
|> should.equal(testing.default_secret_key_base)
request
|> wisp.set_secret_key_base(valid)
|> wisp.get_secret_key_base
|> should.equal(valid)
// Panics if the key is too short
erlang.rescue(fn() { wisp.set_secret_key_base(request, too_short) })
|> should.be_error
}
pub fn set_get_max_body_size_test() {
let request = testing.get("/", [])
request
|> wisp.get_max_body_size
|> should.equal(8_000_000)
request
|> wisp.set_max_body_size(10)
|> wisp.get_max_body_size
|> should.equal(10)
}
pub fn set_get_max_files_size_test() {
let request = testing.get("/", [])
request
|> wisp.get_max_files_size
|> should.equal(32_000_000)
request
|> wisp.set_max_files_size(10)
|> wisp.get_max_files_size
|> should.equal(10)
}
pub fn set_get_read_chunk_size_test() {
let request = testing.get("/", [])
request
|> wisp.get_read_chunk_size
|> should.equal(1_000_000)
request
|> wisp.set_read_chunk_size(10)
|> wisp.get_read_chunk_size
|> should.equal(10)
}
pub fn path_segments_test() {
request.new()
|> request.set_path("/one/two/three")
|> wisp.path_segments
|> should.equal(["one", "two", "three"])
}
pub fn method_override_test() {
// These methods can be overridden to
use method <- list.each([http.Put, http.Delete, http.Patch])
let request =
request.new()
|> request.set_method(method)
|> request.set_query([#("_method", http.method_to_string(method))])
request
|> wisp.method_override
|> should.equal(request.set_method(request, method))
}
pub fn method_override_unacceptable_unoriginal_method_test() {
// These methods are not allowed to be overridden
use method <- list.each([
http.Head,
http.Put,
http.Delete,
http.Trace,
http.Connect,
http.Options,
http.Patch,
http.Other("MYSTERY"),
])
let request =
request.new()
|> request.set_method(method)
|> request.set_query([#("_method", "DELETE")])
request
|> wisp.method_override
|> should.equal(request)
}
pub fn method_override_unacceptable_target_method_test() {
// These methods are not allowed to be overridden to
use method <- list.each([
http.Get,
http.Head,
http.Trace,
http.Connect,
http.Options,
http.Other("MYSTERY"),
])
let request =
request.new()
|> request.set_method(http.Post)
|> request.set_query([#("_method", http.method_to_string(method))])
request
|> wisp.method_override
|> should.equal(request)
}
pub fn require_method_test() {
{
let request = request.new()
use <- wisp.require_method(request, http.Get)
wisp.ok()
}
|> should.equal(wisp.ok())
}
pub fn require_method_invalid_test() {
{
let request = request.set_method(request.new(), http.Post)
use <- wisp.require_method(request, http.Get)
panic as "should be unreachable"
}
|> should.equal(wisp.method_not_allowed([http.Get]))
}
pub fn require_string_body_test() {
{
let request = testing.post("/", [], "Hello, Joe!")
use body <- wisp.require_string_body(request)
body
|> should.equal("Hello, Joe!")
wisp.accepted()
}
|> should.equal(wisp.accepted())
}
pub fn require_string_body_invalid_test() {
{
let request = testing.request(http.Post, "/", [], <<254>>)
use _ <- wisp.require_string_body(request)
panic as "should be unreachable"
}
|> should.equal(wisp.bad_request())
}
pub fn rescue_crashes_error_test() {
wisp.set_logger_level(wisp.CriticalLevel)
use <- exception.defer(fn() { wisp.set_logger_level(wisp.InfoLevel) })
{
use <- wisp.rescue_crashes
panic as "we need to crash to test the middleware"
}
|> should.equal(wisp.internal_server_error())
}
pub fn rescue_crashes_ok_test() {
{
use <- wisp.rescue_crashes
wisp.ok()
}
|> should.equal(wisp.ok())
}
pub fn serve_static_test() {
let handler = fn(request) {
use <- wisp.serve_static(request, under: "/stuff", from: "./")
wisp.ok()
}
// Get a text file
let response =
testing.get("/stuff/test/fixture.txt", [])
|> handler
response.status
|> should.equal(200)
response.headers
|> should.equal([#("content-type", "text/plain; charset=utf-8")])
response.body
|> should.equal(wisp.File("./test/fixture.txt"))
// Get a json file
let response =
testing.get("/stuff/test/fixture.json", [])
|> handler
response.status
|> should.equal(200)
response.headers
|> should.equal([#("content-type", "application/json; charset=utf-8")])
response.body
|> should.equal(wisp.File("./test/fixture.json"))
// Get some other file
let response =
testing.get("/stuff/test/fixture.dat", [])
|> handler
response.status
|> should.equal(200)
response.headers
|> should.equal([#("content-type", "application/octet-stream")])
response.body
|> should.equal(wisp.File("./test/fixture.dat"))
// Get something not handled by the static file server
let response =
testing.get("/stuff/this-does-not-exist", [])
|> handler
response.status
|> should.equal(200)
response.headers
|> should.equal([])
response.body
|> should.equal(wisp.Empty)
}
pub fn serve_static_under_has_no_trailing_slash_test() {
let request =
testing.get("/", [])
|> request.set_path("/stuff/test/fixture.txt")
let response = {
use <- wisp.serve_static(request, under: "stuff", from: "./")
wisp.ok()
}
response.status
|> should.equal(200)
response.headers
|> should.equal([#("content-type", "text/plain; charset=utf-8")])
response.body
|> should.equal(wisp.File("./test/fixture.txt"))
}
pub fn serve_static_from_has_no_trailing_slash_test() {
let request =
testing.get("/", [])
|> request.set_path("/stuff/test/fixture.txt")
let response = {
use <- wisp.serve_static(request, under: "stuff", from: ".")
wisp.ok()
}
response.status
|> should.equal(200)
response.headers
|> should.equal([#("content-type", "text/plain; charset=utf-8")])
response.body
|> should.equal(wisp.File("./test/fixture.txt"))
}
pub fn serve_static_not_found_test() {
let request =
testing.get("/", [])
|> request.set_path("/stuff/credit_card_details.txt")
{
use <- wisp.serve_static(request, under: "/stuff", from: "./")
wisp.ok()
}
|> should.equal(wisp.ok())
}
pub fn serve_static_go_up_test() {
let request =
testing.get("/", [])
|> request.set_path("/../test/fixture.txt")
{
use <- wisp.serve_static(request, under: "/stuff", from: "./src/")
wisp.ok()
}
|> should.equal(wisp.ok())
}
pub fn temporary_file_test() {
// Create tmp files for a first request
let request1 = testing.get("/", [])
let assert Ok(request1_file1) = wisp.new_temporary_file(request1)
let assert Ok(request1_file2) = wisp.new_temporary_file(request1)
// The files exist
request1_file1
|> should.not_equal(request1_file2)
let assert Ok(_) = simplifile.read(request1_file1)
let assert Ok(_) = simplifile.read(request1_file2)
// Create tmp files for a second request
let request2 = testing.get("/", [])
let assert Ok(request2_file1) = wisp.new_temporary_file(request2)
let assert Ok(request2_file2) = wisp.new_temporary_file(request2)
// The files exist
request2_file1
|> should.not_equal(request1_file2)
let assert Ok(_) = simplifile.read(request2_file1)
let assert Ok(_) = simplifile.read(request2_file2)
// Delete the files for the first request
let assert Ok(_) = wisp.delete_temporary_files(request1)
// They no longer exist
let assert Error(simplifile.Enoent) = simplifile.read(request1_file1)
let assert Error(simplifile.Enoent) = simplifile.read(request1_file2)
// The files for the second request still exist
let assert Ok(_) = simplifile.read(request2_file1)
let assert Ok(_) = simplifile.read(request2_file2)
// Delete the files for the first request
let assert Ok(_) = wisp.delete_temporary_files(request2)
// They no longer exist
let assert Error(simplifile.Enoent) = simplifile.read(request2_file1)
let assert Error(simplifile.Enoent) = simplifile.read(request2_file2)
}
pub fn require_content_type_test() {
{
let request = testing.get("/", [#("content-type", "text/plain")])
use <- wisp.require_content_type(request, "text/plain")
wisp.ok()
}
|> should.equal(wisp.ok())
}
pub fn require_content_type_charset_test() {
{
let request =
testing.get("/", [#("content-type", "text/plain; charset=utf-8")])
use <- wisp.require_content_type(request, "text/plain")
wisp.ok()
}
|> should.equal(wisp.ok())
}
pub fn require_content_type_missing_test() {
{
let request = testing.get("/", [])
use <- wisp.require_content_type(request, "text/plain")
wisp.ok()
}
|> should.equal(wisp.unsupported_media_type(["text/plain"]))
}
pub fn require_content_type_invalid_test() {
{
let request = testing.get("/", [#("content-type", "text/plain")])
use <- wisp.require_content_type(request, "text/html")
panic as "should be unreachable"
}
|> should.equal(wisp.unsupported_media_type(["text/html"]))
}
pub fn json_test() {
testing.post("/", [], "{\"one\":1,\"two\":2}")
|> request.set_header("content-type", "application/json")
|> json_handler(fn(json) {
json
|> should.equal(dynamic.from(dict.from_list([#("one", 1), #("two", 2)])))
})
|> should.equal(wisp.ok())
}
pub fn json_wrong_content_type_test() {
testing.post("/", [], "{\"one\":1,\"two\":2}")
|> request.set_header("content-type", "text/plain")
|> json_handler(fn(_) { panic as "should be unreachable" })
|> should.equal(wisp.unsupported_media_type(["application/json"]))
}
pub fn json_no_content_type_test() {
testing.post("/", [], "{\"one\":1,\"two\":2}")
|> json_handler(fn(_) { panic as "should be unreachable" })
|> should.equal(wisp.unsupported_media_type(["application/json"]))
}
pub fn json_too_big_test() {
testing.post("/", [], "{\"one\":1,\"two\":2}")
|> wisp.set_max_body_size(1)
|> request.set_header("content-type", "application/json")
|> json_handler(fn(_) { panic as "should be unreachable" })
|> should.equal(Response(413, [], wisp.Empty))
}
pub fn json_syntax_error_test() {
testing.post("/", [], "{\"one\":1,\"two\":2")
|> request.set_header("content-type", "application/json")
|> json_handler(fn(_) { panic as "should be unreachable" })
|> should.equal(Response(400, [], wisp.Empty))
}
pub fn urlencoded_form_test() {
testing.post("/", [], "one=1&two=2")
|> request.set_header("content-type", "application/x-www-form-urlencoded")
|> form_handler(fn(form) {
form
|> should.equal(wisp.FormData([#("one", "1"), #("two", "2")], []))
})
|> should.equal(wisp.ok())
}
pub fn urlencoded_form_with_charset_test() {
testing.post("/", [], "one=1&two=2")
|> request.set_header(
"content-type",
"application/x-www-form-urlencoded; charset=UTF-8",
)
|> form_handler(fn(form) {
form
|> should.equal(wisp.FormData([#("one", "1"), #("two", "2")], []))
})
|> should.equal(wisp.ok())
}
pub fn urlencoded_too_big_form_test() {
testing.post("/", [], "12")
|> request.set_header("content-type", "application/x-www-form-urlencoded")
|> wisp.set_max_body_size(1)
|> form_handler(fn(_) { panic as "should be unreachable" })
|> should.equal(Response(413, [], wisp.Empty))
}
pub fn multipart_form_test() {
"--theboundary\r
Content-Disposition: form-data; name=\"one\"\r
\r
1\r
--theboundary\r
Content-Disposition: form-data; name=\"two\"\r
\r
2\r
--theboundary--\r
"
|> testing.post("/", [], _)
|> request.set_header(
"content-type",
"multipart/form-data; boundary=theboundary",
)
|> form_handler(fn(form) {
form
|> should.equal(wisp.FormData([#("one", "1"), #("two", "2")], []))
})
|> should.equal(wisp.ok())
}
pub fn multipart_form_too_big_test() {
"--theboundary\r
Content-Disposition: form-data; name=\"one\"\r
\r
1\r
--theboundary--\r
"
|> testing.post("/", [], _)
|> wisp.set_max_body_size(1)
|> request.set_header(
"content-type",
"multipart/form-data; boundary=theboundary",
)
|> form_handler(fn(_) { panic as "should be unreachable" })
|> should.equal(Response(413, [], wisp.Empty))
}
pub fn multipart_form_no_boundary_test() {
"--theboundary\r
Content-Disposition: form-data; name=\"one\"\r
\r
1\r
--theboundary--\r
"
|> testing.post("/", [], _)
|> request.set_header("content-type", "multipart/form-data")
|> form_handler(fn(_) { panic as "should be unreachable" })
|> should.equal(Response(400, [], wisp.Empty))
}
pub fn multipart_form_invalid_format_test() {
"--theboundary\r\n--theboundary--\r\n"
|> testing.post("/", [], _)
|> request.set_header(
"content-type",
"multipart/form-data; boundary=theboundary",
)
|> form_handler(fn(_) { panic as "should be unreachable" })
|> should.equal(Response(400, [], wisp.Empty))
}
pub fn form_unknown_content_type_test() {
"one=1&two=2"
|> testing.post("/", [], _)
|> request.set_header("content-type", "text/form")
|> form_handler(fn(_) { panic as "should be unreachable" })
|> should.equal(Response(
415,
[#("accept", "application/x-www-form-urlencoded, multipart/form-data")],
wisp.Empty,
))
}
pub fn multipart_form_with_files_test() {
"--theboundary\r
Content-Disposition: form-data; name=\"one\"\r
\r
1\r
--theboundary\r
Content-Disposition: form-data; name=\"two\"; filename=\"file.txt\"\r
\r
file contents\r
--theboundary--\r
"
|> testing.post("/", [], _)
|> request.set_header(
"content-type",
"multipart/form-data; boundary=theboundary",
)
|> form_handler(fn(form) {
let assert [#("one", "1")] = form.values
let assert [#("two", wisp.UploadedFile("file.txt", path))] = form.files
let assert Ok("file contents") = simplifile.read(path)
})
|> should.equal(wisp.ok())
}
pub fn multipart_form_files_too_big_test() {
let testcase = fn(limit, callback) {
"--theboundary\r
Content-Disposition: form-data; name=\"two\"; filename=\"file.txt\"\r
\r
12\r
--theboundary\r
Content-Disposition: form-data; name=\"two\"\r
\r
this one isn't a file. If it was it would use the entire quota.\r
--theboundary\r
Content-Disposition: form-data; name=\"two\"; filename=\"another.txt\"\r
\r
34\r
--theboundary--\r
"
|> testing.post("/", [], _)
|> wisp.set_max_files_size(limit)
|> request.set_header(
"content-type",
"multipart/form-data; boundary=theboundary",
)
|> form_handler(callback)
}
testcase(1, fn(_) { panic as "should be unreachable for limit of 1" })
|> should.equal(Response(413, [], wisp.Empty))
testcase(2, fn(_) { panic as "should be unreachable for limit of 2" })
|> should.equal(Response(413, [], wisp.Empty))
testcase(3, fn(_) { panic as "should be unreachable for limit of 3" })
|> should.equal(Response(413, [], wisp.Empty))
testcase(4, fn(_) { Nil })
|> should.equal(Response(200, [], wisp.Empty))
}
pub fn handle_head_test() {
let handler = fn(request, header) {
use request <- wisp.handle_head(request)
use <- wisp.require_method(request, http.Get)
list.key_find(request.headers, "x-original-method")
|> should.equal(header)
string_tree.from_string("Hello!")
|> wisp.html_response(201)
}
testing.get("/", [])
|> request.set_method(http.Get)
|> handler(Error(Nil))
|> should.equal(Response(
201,
[#("content-type", "text/html; charset=utf-8")],
wisp.Text(string_tree.from_string("Hello!")),
))
testing.get("/", [])
|> request.set_method(http.Head)
|> handler(Ok("HEAD"))
|> should.equal(Response(
201,
[#("content-type", "text/html; charset=utf-8")],
wisp.Empty,
))
testing.get("/", [])
|> request.set_method(http.Post)
|> handler(Error(Nil))
|> should.equal(Response(405, [#("allow", "GET")], wisp.Empty))
}
pub fn multipart_form_fields_are_sorted_test() {
"--theboundary\r
Content-Disposition: form-data; name=\"xx\"\r
\r
XX\r
--theboundary\r
Content-Disposition: form-data; name=\"zz\"\r
\r
ZZ\r
--theboundary\r
Content-Disposition: form-data; name=\"yy\"\r
\r
YY\r
--theboundary\r
Content-Disposition: form-data; name=\"cc\"; filename=\"file.txt\"\r
\r
CC\r
--theboundary\r
Content-Disposition: form-data; name=\"aa\"; filename=\"file.txt\"\r
\r
AA\r
--theboundary\r
Content-Disposition: form-data; name=\"bb\"; filename=\"file.txt\"\r
\r
BB\r
--theboundary--\r
"
|> testing.post("/", [], _)
|> request.set_header(
"content-type",
"multipart/form-data; boundary=theboundary",
)
|> form_handler(fn(form) {
// Fields are sorted by name.
let assert [#("xx", "XX"), #("yy", "YY"), #("zz", "ZZ")] = form.values
let assert [
#("aa", wisp.UploadedFile("file.txt", path_a)),
#("bb", wisp.UploadedFile("file.txt", path_b)),
#("cc", wisp.UploadedFile("file.txt", path_c)),
] = form.files
let assert Ok("AA") = simplifile.read(path_a)
let assert Ok("BB") = simplifile.read(path_b)
let assert Ok("CC") = simplifile.read(path_c)
})
|> should.equal(wisp.ok())
}
pub fn urlencoded_form_fields_are_sorted_test() {
"xx=XX&zz=ZZ&yy=YY&cc=CC&aa=AA&bb=BB"
|> testing.post("/", [], _)
|> request.set_header("content-type", "application/x-www-form-urlencoded")
|> form_handler(fn(form) {
// Fields are sorted by name.
let assert [
#("aa", "AA"),
#("bb", "BB"),
#("cc", "CC"),
#("xx", "XX"),
#("yy", "YY"),
#("zz", "ZZ"),
] = form.values
})
|> should.equal(wisp.ok())
}
pub fn message_signing_test() {
let request = testing.get("/", [])
let request1 = wisp.set_secret_key_base(request, wisp.random_string(64))
let request2 = wisp.set_secret_key_base(request, wisp.random_string(64))
let signed1 = wisp.sign_message(request1, <<"a":utf8>>, crypto.Sha512)
let signed2 = wisp.sign_message(request2, <<"b":utf8>>, crypto.Sha512)
let assert Ok(<<"a":utf8>>) = wisp.verify_signed_message(request1, signed1)
let assert Ok(<<"b":utf8>>) = wisp.verify_signed_message(request2, signed2)
let assert Error(Nil) = wisp.verify_signed_message(request1, signed2)
let assert Error(Nil) = wisp.verify_signed_message(request2, signed1)
}
pub fn create_canned_connection_test() {
let secret = wisp.random_string(64)
let connection = wisp.create_canned_connection(<<"Hello!":utf8>>, secret)
let request = request.set_body(request.new(), connection)
request
|> wisp.get_secret_key_base
|> should.equal(secret)
request
|> wisp.read_body_to_bitstring
|> should.equal(Ok(<<"Hello!":utf8>>))
}
pub fn escape_html_test() {
""
|> wisp.escape_html
|> should.equal("<script>alert('&');</script>")
}
pub fn set_header_test() {
wisp.ok()
|> wisp.set_header("accept", "application/json")
|> wisp.set_header("accept", "text/plain")
|> wisp.set_header("content-type", "text/html")
|> should.equal(Response(
200,
[#("accept", "text/plain"), #("content-type", "text/html")],
wisp.Empty,
))
}
pub fn string_body_test() {
wisp.ok()
|> wisp.string_body("Hello, world!")
|> should.equal(Response(
200,
[],
wisp.Text(string_tree.from_string("Hello, world!")),
))
}
pub fn string_tree_body_test() {
wisp.ok()
|> wisp.string_tree_body(string_tree.from_string("Hello, world!"))
|> should.equal(Response(
200,
[],
wisp.Text(string_tree.from_string("Hello, world!")),
))
}
pub fn json_body_test() {
wisp.ok()
|> wisp.json_body(string_tree.from_string("{\"one\":1,\"two\":2}"))
|> should.equal(Response(
200,
[#("content-type", "application/json; charset=utf-8")],
wisp.Text(string_tree.from_string("{\"one\":1,\"two\":2}")),
))
}
pub fn priv_directory_test() {
let assert Error(Nil) = wisp.priv_directory("unknown_application")
let assert Ok(dir) = wisp.priv_directory("wisp")
let assert True = string.ends_with(dir, "/wisp/priv")
let assert Ok(dir) = wisp.priv_directory("gleam_erlang")
let assert True = string.ends_with(dir, "/gleam_erlang/priv")
let assert Ok(dir) = wisp.priv_directory("gleam_stdlib")
let assert True = string.ends_with(dir, "/gleam_stdlib/priv")
}
pub fn set_cookie_plain_test() {
let req = testing.get("/", [])
let response =
wisp.ok()
|> wisp.set_cookie(req, "id", "123", wisp.PlainText, 60 * 60 * 24 * 365)
|> wisp.set_cookie(req, "flash", "hi-there", wisp.PlainText, 60)
response.headers
|> should.equal([
#(
"set-cookie",
"flash=aGktdGhlcmU; Max-Age=60; Path=/; Secure; HttpOnly; SameSite=Lax",
),
#(
"set-cookie",
"id=MTIz; Max-Age=31536000; Path=/; Secure; HttpOnly; SameSite=Lax",
),
])
}
pub fn set_cookie_signed_test() {
let req = testing.get("/", [])
let response =
wisp.ok()
|> wisp.set_cookie(req, "id", "123", wisp.Signed, 60 * 60 * 24 * 365)
|> wisp.set_cookie(req, "flash", "hi-there", wisp.Signed, 60)
response.headers
|> should.equal([
#(
"set-cookie",
"flash=SFM1MTI.aGktdGhlcmU.uWUWvrAleKQ2jsWcU97HzGgPqtLjjUgl4oe40-RPJ5qRRcE_soXPacgmaHTLxK3xZbOJ5DOTIRMI0szD4Re7wA; Max-Age=60; Path=/; Secure; HttpOnly; SameSite=Lax",
),
#(
"set-cookie",
"id=SFM1MTI.MTIz.LT5VxVwopQ7VhZ3OzF6Pgy3sfIIQaiUH5anHXNRt6o3taBMfCNBQskZ-EIkodchsPGSu_AJrAHjMfYPV7D5ogg; Max-Age=31536000; Path=/; Secure; HttpOnly; SameSite=Lax",
),
])
}
pub fn get_cookie_test() {
let request =
testing.get("/", [
// Plain text
#("cookie", "plain=MTIz"),
// Signed
#(
"cookie",
"signed=SFM1MTI.aGktdGhlcmU.uWUWvrAleKQ2jsWcU97HzGgPqtLjjUgl4oe40-RPJ5qRRcE_soXPacgmaHTLxK3xZbOJ5DOTIRMI0szD4Re7wA",
),
// Signed but tampered with
#(
"cookie",
"signed-and-tampered-with=SFM1MTI.aGktdGhlcmU.uWUWvrAleKQ2jsWcU97HzGgPqtLjjUgl4oe40-RPJ5qRRcE_soXPacgmaHTLxK3xZbOJ5DOTIRMI0szD4Re7wAA",
),
])
request
|> wisp.get_cookie("plain", wisp.PlainText)
|> should.equal(Ok("123"))
request
|> wisp.get_cookie("plain", wisp.Signed)
|> should.equal(Error(Nil))
request
|> wisp.get_cookie("signed", wisp.PlainText)
|> should.equal(Error(Nil))
request
|> wisp.get_cookie("signed", wisp.Signed)
|> should.equal(Ok("hi-there"))
request
|> wisp.get_cookie("signed-and-tampered-with", wisp.PlainText)
|> should.equal(Error(Nil))
request
|> wisp.get_cookie("signed-and-tampered-with", wisp.Signed)
|> should.equal(Error(Nil))
request
|> wisp.get_cookie("unknown", wisp.PlainText)
|> should.equal(Error(Nil))
request
|> wisp.get_cookie("unknown", wisp.Signed)
|> should.equal(Error(Nil))
}
// Let's roundtrip signing and verification a bunch of times to have confidence
// it works, and that we detect any regressions.
pub fn cookie_sign_roundtrip_test() {
use _ <- list.each(list.repeat(1, 10_000))
let message =
<>
|> bit_array.base64_encode(True)
let req = testing.get("/", [])
let signed = wisp.sign_message(req, <>, crypto.Sha512)
let req = testing.get("/", [#("cookie", "message=" <> signed)])
let assert Ok(out) = wisp.get_cookie(req, "message", wisp.Signed)
out
|> should.equal(message)
}
pub fn get_query_test() {
testing.get("/wibble?wobble=1&wubble=2&wobble=3&wabble", [])
|> wisp.get_query
|> should.equal([
#("wobble", "1"),
#("wubble", "2"),
#("wobble", "3"),
#("wabble", ""),
])
}
pub fn get_query_no_query_test() {
testing.get("/wibble", [])
|> wisp.get_query
|> should.equal([])
}