···
1
1
import gleam/dynamic.{type Dynamic}
2
2
+
import gleam/dynamic/decode
2
3
import gleam/http.{Get, Head, Http, Options, Post, Put}
3
4
import gleam/http/request
4
5
import gleam/http/response
5
6
import gleam/javascript/promise.{type Promise}
7
7
+
import gleam/json
6
8
import gleam/list
7
9
import gleam/option
8
10
import gleam/result
···
62
64
promise.resolve(Nil)
63
65
}
64
66
67
67
+
pub fn read_json_decodes_body_test() -> Promise(Nil) {
68
68
+
let request = request_with_body([<<"{\"one\":1,\"two\":2}">>])
69
69
+
70
70
+
use response <- promise.await(
71
71
+
smol.read_json(
72
72
+
request,
73
73
+
using: two_ints_decoder(),
74
74
+
up_to: 32,
75
75
+
then: fn(pair) {
76
76
+
assert pair == #(1, 2)
77
77
+
smol.send_string("ok")
78
78
+
},
79
79
+
),
80
80
+
)
81
81
+
assert response.status == 200
82
82
+
83
83
+
promise.resolve(Nil)
84
84
+
}
85
85
+
86
86
+
pub fn read_json_rejects_too_large_body_test() -> Promise(Nil) {
87
87
+
let request = request_with_body([<<"{\"one\":1,\"two\":2}">>])
88
88
+
89
89
+
use response <- promise.await(
90
90
+
smol.read_json(request, using: two_ints_decoder(), up_to: 1, then: fn(_) {
91
91
+
smol.send_string("unexpected")
92
92
+
}),
93
93
+
)
94
94
+
assert response.status == 413
95
95
+
96
96
+
use text <- promise.await(stream_text(response.body))
97
97
+
assert text == "Payload Too Large"
98
98
+
99
99
+
promise.resolve(Nil)
100
100
+
}
101
101
+
102
102
+
pub fn read_json_rejects_invalid_json_test() -> Promise(Nil) {
103
103
+
let request = request_with_body([<<"{\"one\":">>])
104
104
+
105
105
+
use response <- promise.await(
106
106
+
smol.read_json(request, using: two_ints_decoder(), up_to: 32, then: fn(_) {
107
107
+
smol.send_string("unexpected")
108
108
+
}),
109
109
+
)
110
110
+
assert response.status == 422
111
111
+
112
112
+
use text <- promise.await(stream_text(response.body))
113
113
+
assert text == "Unprocessable Entity"
114
114
+
115
115
+
promise.resolve(Nil)
116
116
+
}
117
117
+
118
118
+
pub fn read_form_reads_urlencoded_fields_test() -> Promise(Nil) {
119
119
+
let request = request_with_body([<<"one=1&two=2">>])
120
120
+
121
121
+
use response <- promise.await(
122
122
+
smol.read_form(request, up_to: 16, then: fn(fields) {
123
123
+
assert fields == [#("one", "1"), #("two", "2")]
124
124
+
smol.send_string("ok")
125
125
+
}),
126
126
+
)
127
127
+
assert response.status == 200
128
128
+
129
129
+
promise.resolve(Nil)
130
130
+
}
131
131
+
132
132
+
pub fn read_form_rejects_too_large_body_test() -> Promise(Nil) {
133
133
+
let request = request_with_body([<<"one=1">>])
134
134
+
135
135
+
use response <- promise.await(
136
136
+
smol.read_form(request, up_to: 4, then: fn(_fields) {
137
137
+
smol.send_string("unexpected")
138
138
+
}),
139
139
+
)
140
140
+
assert response.status == 413
141
141
+
142
142
+
use text <- promise.await(stream_text(response.body))
143
143
+
assert text == "Payload Too Large"
144
144
+
145
145
+
promise.resolve(Nil)
146
146
+
}
147
147
+
65
148
pub fn read_multipart_reads_fields_and_uploads_test() -> Promise(Nil) {
66
149
let request = multipart_request([<<multipart_body():utf8>>])
67
150
···
188
271
189
272
use text <- promise.await(stream_text(response.body))
190
273
assert text == "hello"
274
274
+
275
275
+
promise.resolve(Nil)
276
276
+
}
277
277
+
278
278
+
pub fn handle_head_passes_through_get_test() -> Promise(Nil) {
279
279
+
let request = request_with_method_headers_body(Get, [], [])
280
280
+
281
281
+
use response <- promise.await(
282
282
+
smol.handle_head(request, then: fn(request) {
283
283
+
assert request.method == Get
284
284
+
assert request.get_header(request, "x-original-method") == Error(Nil)
285
285
+
smol.send_string("hello")
286
286
+
}),
287
287
+
)
288
288
+
assert response.status == 200
289
289
+
290
290
+
use text <- promise.await(stream_text(response.body))
291
291
+
assert text == "hello"
292
292
+
293
293
+
promise.resolve(Nil)
294
294
+
}
295
295
+
296
296
+
pub fn handle_head_passes_through_post_test() -> Promise(Nil) {
297
297
+
let request = request_with_method_headers_body(Post, [], [])
298
298
+
299
299
+
use response <- promise.await(
300
300
+
smol.handle_head(request, then: fn(request) {
301
301
+
assert request.method == Post
302
302
+
assert request.get_header(request, "x-original-method") == Error(Nil)
303
303
+
smol.require_method(request, require: Get, then: fn() {
304
304
+
smol.send_string("unexpected")
305
305
+
})
306
306
+
}),
307
307
+
)
308
308
+
assert response.status == 405
309
309
+
assert_header(response, "allow", "GET")
191
310
192
311
promise.resolve(Nil)
193
312
}
···
430
549
promise.resolve(Nil)
431
550
}
432
551
552
552
+
pub fn require_method_allows_matching_method_test() -> Promise(Nil) {
553
553
+
let request = request_with_method_headers_body(Get, [], [])
554
554
+
555
555
+
use response <- promise.await(
556
556
+
smol.require_method(request, require: Get, then: fn() {
557
557
+
smol.send_string("ok")
558
558
+
}),
559
559
+
)
560
560
+
assert response.status == 200
561
561
+
562
562
+
promise.resolve(Nil)
563
563
+
}
564
564
+
565
565
+
pub fn require_method_rejects_other_method_test() -> Promise(Nil) {
566
566
+
let request = request_with_method_headers_body(Post, [], [])
567
567
+
568
568
+
use response <- promise.await(
569
569
+
smol.require_method(request, require: Get, then: fn() {
570
570
+
smol.send_string("unexpected")
571
571
+
}),
572
572
+
)
573
573
+
assert response.status == 405
574
574
+
assert_header(response, "allow", "GET")
575
575
+
576
576
+
use text <- promise.await(stream_text(response.body))
577
577
+
assert text == "Method Not Allowed"
578
578
+
579
579
+
promise.resolve(Nil)
580
580
+
}
581
581
+
582
582
+
pub fn require_content_type_allows_exact_match_test() -> Promise(Nil) {
583
583
+
let request =
584
584
+
request_with_method_headers_body(
585
585
+
Post,
586
586
+
[#("content-type", "text/plain")],
587
587
+
[],
588
588
+
)
589
589
+
590
590
+
use response <- promise.await(
591
591
+
smol.require_content_type(request, accept: ["text/plain"], then: fn() {
592
592
+
smol.send_string("ok")
593
593
+
}),
594
594
+
)
595
595
+
assert response.status == 200
596
596
+
597
597
+
promise.resolve(Nil)
598
598
+
}
599
599
+
600
600
+
pub fn require_content_type_allows_charset_test() -> Promise(Nil) {
601
601
+
let request =
602
602
+
request_with_method_headers_body(
603
603
+
Post,
604
604
+
[#("content-type", "text/plain; charset=utf-8")],
605
605
+
[],
606
606
+
)
607
607
+
608
608
+
use response <- promise.await(
609
609
+
smol.require_content_type(request, accept: ["text/plain"], then: fn() {
610
610
+
smol.send_string("ok")
611
611
+
}),
612
612
+
)
613
613
+
assert response.status == 200
614
614
+
615
615
+
promise.resolve(Nil)
616
616
+
}
617
617
+
618
618
+
pub fn require_content_type_rejects_missing_header_test() -> Promise(Nil) {
619
619
+
let request = request_with_method_headers_body(Post, [], [])
620
620
+
621
621
+
use response <- promise.await(
622
622
+
smol.require_content_type(request, accept: ["text/plain"], then: fn() {
623
623
+
smol.send_string("unexpected")
624
624
+
}),
625
625
+
)
626
626
+
assert response.status == 415
627
627
+
assert_header(response, "accept", "text/plain")
628
628
+
629
629
+
use text <- promise.await(stream_text(response.body))
630
630
+
assert text == "Unsupported Media Type"
631
631
+
632
632
+
promise.resolve(Nil)
633
633
+
}
634
634
+
635
635
+
pub fn require_content_type_rejects_other_type_test() -> Promise(Nil) {
636
636
+
let request =
637
637
+
request_with_method_headers_body(
638
638
+
Post,
639
639
+
[#("content-type", "text/plain")],
640
640
+
[],
641
641
+
)
642
642
+
643
643
+
use response <- promise.await(
644
644
+
smol.require_content_type(request, accept: ["text/html"], then: fn() {
645
645
+
smol.send_string("unexpected")
646
646
+
}),
647
647
+
)
648
648
+
assert response.status == 415
649
649
+
assert_header(response, "accept", "text/html")
650
650
+
651
651
+
promise.resolve(Nil)
652
652
+
}
653
653
+
433
654
pub fn csrf_known_header_protection_allows_safe_methods_test() -> Promise(Nil) {
434
655
let request = request_with_method_headers_body(Get, [], [])
435
656
···
580
801
promise.resolve(Nil)
581
802
}
582
803
804
804
+
pub fn send_html_sets_content_type_and_body_test() -> Promise(Nil) {
805
805
+
use response <- promise.await(smol.send_html("<h1>Hello</h1>"))
806
806
+
807
807
+
assert response.status == 200
808
808
+
assert_header(response, "content-type", "text/html; charset=utf-8")
809
809
+
810
810
+
use text <- promise.await(stream_text(response.body))
811
811
+
assert text == "<h1>Hello</h1>"
812
812
+
813
813
+
promise.resolve(Nil)
814
814
+
}
815
815
+
816
816
+
pub fn send_json_sets_content_type_and_body_test() -> Promise(Nil) {
817
817
+
use response <- promise.await(
818
818
+
smol.send_json(
819
819
+
json.object([
820
820
+
#("one", json.int(1)),
821
821
+
#("two", json.int(2)),
822
822
+
]),
823
823
+
),
824
824
+
)
825
825
+
826
826
+
assert response.status == 200
827
827
+
assert_header(response, "content-type", "application/json; charset=utf-8")
828
828
+
829
829
+
use text <- promise.await(stream_text(response.body))
830
830
+
assert text == "{\"one\":1,\"two\":2}"
831
831
+
832
832
+
promise.resolve(Nil)
833
833
+
}
834
834
+
835
835
+
pub fn send_status_uses_standard_status_text_test() -> Promise(Nil) {
836
836
+
use response <- promise.await(smol.send_status(404))
837
837
+
838
838
+
assert response.status == 404
839
839
+
assert_header(response, "content-type", "text/plain; charset=utf-8")
840
840
+
841
841
+
use text <- promise.await(stream_text(response.body))
842
842
+
assert text == "Not Found"
843
843
+
844
844
+
promise.resolve(Nil)
845
845
+
}
846
846
+
847
847
+
pub fn redirect_sets_see_other_location_test() -> Promise(Nil) {
848
848
+
use response <- promise.await(smol.redirect(to: "https://example.com/wibble"))
849
849
+
850
850
+
assert response.status == 303
851
851
+
assert_header(response, "location", "https://example.com/wibble")
852
852
+
853
853
+
use text <- promise.await(stream_text(response.body))
854
854
+
assert text == "https://example.com/wibble"
855
855
+
856
856
+
promise.resolve(Nil)
857
857
+
}
858
858
+
859
859
+
pub fn moved_permanently_sets_location_test() -> Promise(Nil) {
860
860
+
use response <- promise.await(smol.moved_permanently(
861
861
+
to: "https://example.com/wobble",
862
862
+
))
863
863
+
864
864
+
assert response.status == 308
865
865
+
assert_header(response, "location", "https://example.com/wobble")
866
866
+
867
867
+
use text <- promise.await(stream_text(response.body))
868
868
+
assert text == "https://example.com/wobble"
869
869
+
870
870
+
promise.resolve(Nil)
871
871
+
}
872
872
+
873
873
+
pub fn with_status_replaces_status_test() -> Promise(Nil) {
874
874
+
use response <- promise.await(
875
875
+
smol.send_string("created")
876
876
+
|> smol.with_status(201),
877
877
+
)
878
878
+
879
879
+
assert response.status == 201
880
880
+
881
881
+
use text <- promise.await(stream_text(response.body))
882
882
+
assert text == "created"
883
883
+
884
884
+
promise.resolve(Nil)
885
885
+
}
886
886
+
887
887
+
pub fn with_extra_headers_replaces_normal_headers_test() -> Promise(Nil) {
888
888
+
use response <- promise.await(
889
889
+
smol.send_string("ok")
890
890
+
|> smol.with_extra_headers([
891
891
+
#("accept", "application/json"),
892
892
+
#("accept", "text/plain"),
893
893
+
#("content-type", "text/html"),
894
894
+
]),
895
895
+
)
896
896
+
897
897
+
assert_header(response, "accept", "text/plain")
898
898
+
assert_header(response, "content-type", "text/html")
899
899
+
900
900
+
promise.resolve(Nil)
901
901
+
}
902
902
+
583
903
pub fn as_file_download_sets_content_disposition_test() -> Promise(Nil) {
584
904
use response <- promise.await(
585
905
smol.send_string("{}")
···
695
1015
696
1016
fn send_status_handler(status) {
697
1017
smol.adapt(fn(_request) { smol.send_status(status) })
1018
1018
+
}
1019
1019
+
1020
1020
+
fn two_ints_decoder() {
1021
1021
+
use one <- decode.field("one", decode.int)
1022
1022
+
use two <- decode.field("two", decode.int)
1023
1023
+
decode.success(#(one, two))
698
1024
}
699
1025
700
1026
fn assert_null_body_response(response, expected_status) {