This commit is contained in:
swrup 2025-11-11 02:07:51 +01:00
parent aa2ff7b2f0
commit 2f3113f55d
11742 changed files with 1223940 additions and 0 deletions

View file

@ -0,0 +1,15 @@
(tests
(libraries h1 alcotest)
(modules
helpers
test_client_connection
test_headers
test_h1
test_iovec
test_method
test_request
test_response
test_server_connection
test_version
test_websocket)
(names test_h1))

View file

@ -0,0 +1,76 @@
open H1
let maybe_serialize_body f body =
match body with
| None -> ()
| Some body -> Faraday.write_string f body
let request_to_string ?body r =
let f = Faraday.create 0x1000 in
H1_private.Serialize.write_request f r;
maybe_serialize_body f body;
Faraday.serialize_to_string f
let response_to_string ?body r =
let f = Faraday.create 0x1000 in
H1_private.Serialize.write_response f r;
maybe_serialize_body f body;
Faraday.serialize_to_string f
module Read_operation = struct
type t = [ `Read | `Yield | `Close | `Upgrade ]
let pp_hum fmt (t : t) =
let str =
match t with
| `Read -> "Read"
| `Yield -> "Yield"
| `Close -> "Close"
| `Upgrade -> "Upgrade"
in
Format.pp_print_string fmt str
;;
end
module Write_operation = struct
type t = [ `Write of Bstr.t IOVec.t list | `Yield | `Close of int | `Upgrade ]
let iovecs_to_string iovecs =
let len = IOVec.lengthv iovecs in
let bytes = Bytes.create len in
let dst_off = ref 0 in
List.iter (fun { IOVec.buffer; off = src_off; len } ->
Bstr.blit_to_bytes buffer ~src_off bytes ~dst_off:!dst_off ~len;
dst_off := !dst_off + len)
iovecs;
Bytes.to_string bytes
;;
let pp_hum fmt (t : t) =
match t with
| `Write iovecs -> Format.fprintf fmt "Write %S" (iovecs_to_string iovecs)
| `Yield -> Format.pp_print_string fmt "Yield"
| `Close len -> Format.fprintf fmt "Close %i" len
| `Upgrade -> Format.pp_print_string fmt "Upgrade"
;;
let to_write_as_string t =
match t with
| `Write iovecs -> Some (iovecs_to_string iovecs)
| `Close _ | `Yield | `Upgrade -> None
;;
end
let write_operation = Alcotest.of_pp Write_operation.pp_hum
let read_operation = Alcotest.of_pp Read_operation.pp_hum
module Headers = struct
include Headers
let (@) a b = Headers.add_list a (Headers.to_list b)
let connection_close = Headers.of_list ["connection", "close"]
let encoding_chunked = Headers.of_list ["transfer-encoding", "chunked"]
let encoding_fixed n = Headers.of_list ["content-length", string_of_int n]
let upgrade protocol = Headers.of_list ["connection", "upgrade" ; "upgrade", protocol]
end

View file

@ -0,0 +1,339 @@
open H1
open Helpers
open Client_connection
let response_error_pp_hum fmt = function
| `Malformed_response str ->
Format.fprintf fmt "Malformed_response: %s" str
| `Invalid_response_body_length resp ->
Format.fprintf fmt "Invalid_response_body_length: %s" (response_to_string resp)
| `Exn exn ->
Format.fprintf fmt "Exn (%s)" (Printexc.to_string exn)
;;
module Response = struct
include Response
let pp = pp_hum
let equal x y = x = y
end
module Alcotest = struct
include Alcotest
let response_error = of_pp response_error_pp_hum
end
let feed_string t str =
let len = String.length str in
let input = Bstr.of_string str in
read t input ~off:0 ~len
let read_string t str =
let c = feed_string t str in
Alcotest.(check int) "read consumes all input" (String.length str) c;
;;
let read_response t r =
let response_string = response_to_string r in
read_string t response_string
;;
let reader_ready t =
Alcotest.check read_operation "Reader is ready"
`Read (next_read_operation t :> Read_operation.t);
;;
let reader_closed t =
Alcotest.check read_operation "Reader is closed"
`Close (next_read_operation t :> Read_operation.t);
;;
let write_string ?(msg="output written") t str =
let len = String.length str in
Alcotest.(check (option string)) msg
(Some str)
(next_write_operation t |> Write_operation.to_write_as_string);
report_write_result t (`Ok len);
;;
let write_request ?(msg="request written") t r =
let request_string = request_to_string r in
write_string ~msg t request_string
;;
let writer_yielded t =
Alcotest.check write_operation "Writer is in a yield state"
`Yield (next_write_operation t :> Write_operation.t);
;;
let writer_closed t =
Alcotest.check write_operation "Writer is closed"
(`Close 0) (next_write_operation t :> Write_operation.t);
;;
let connection_is_shutdown t =
Alcotest.check read_operation "Reader is closed"
`Close (next_read_operation t :> Read_operation.t);
writer_closed t;
;;
let default_response_handler expected_response response body =
Alcotest.check (module Response) "expected response" expected_response response;
let on_read _ ~off:_ ~len:_ = () in
let on_eof () = () in
Body.Reader.schedule_read body ~on_read ~on_eof;
;;
let no_error_handler _ = assert false
let test_get () =
let request' = Request.create `GET "/" in
let response = Response.create `OK in
(* Single GET *)
let body, t =
request
request'
~response_handler:(default_response_handler response)
~error_handler:no_error_handler
in
Body.Writer.close body;
write_request t request';
writer_closed t;
read_response t response;
(* Single GET, response closes connection *)
let response = Response.create `OK ~headers:Headers.connection_close in
let body, t =
request
request'
~response_handler:(default_response_handler response)
~error_handler:no_error_handler
in
Body.Writer.close body;
write_request t request';
read_response t response;
let c = read_eof t Bstr.empty ~off:0 ~len:0 in
Alcotest.(check int) "read_eof with no input returns 0" 0 c;
connection_is_shutdown t;
(* Single GET, streaming body *)
let response = Response.create `OK ~headers:Headers.encoding_chunked in
let body, t =
request
request'
~response_handler:(default_response_handler response)
~error_handler:no_error_handler
in
Body.Writer.close body;
write_request t request';
read_response t response;
read_string t "d\r\nHello, world!\r\n0\r\n\r\n"
;;
let test_send_streaming_body () =
let request' = Request.create `GET "/" ~headers:Headers.encoding_chunked in
let response = Response.create `OK ~headers:Headers.encoding_chunked in
let body, t =
request
request'
~response_handler:(default_response_handler response)
~error_handler:no_error_handler
in
write_request t request';
read_response t response;
Body.Writer.write_string body "hello";
write_string t "5\r\nhello\r\n";
Body.Writer.write_string body "world";
Body.Writer.close body;
write_string t "5\r\nworld\r\n";
write_string t "0\r\n\r\n";
writer_closed t
;;
let test_response_eof () =
let request' = Request.create `GET "/" in
let response = Response.create `OK in (* not actually writen to the channel *)
let error_message = ref None in
let body, t =
request
request'
~response_handler:(default_response_handler response)
~error_handler:(function
| `Malformed_response msg -> error_message := Some msg
| _ -> assert false)
in
Body.Writer.close body;
write_request t request';
writer_closed t;
reader_ready t;
let c = read_eof t Bstr.empty ~off:0 ~len:0 in
Alcotest.(check int) "read_eof with no input returns 0" 0 c;
connection_is_shutdown t;
Alcotest.(check (option string)) "unexpected eof"
(Some "unexpected eof")
!error_message
;;
let test_response_header_order () =
let request' = Request.create `GET "/" in
let headers =
[ "a", "1"
; "b", "2"
; "c", "3"
]
in
let response = Response.create `OK ~headers:(Headers.of_list headers) in
let received = ref None in
let body, t =
request
request'
~response_handler:(fun response _ -> received := Some response)
~error_handler:no_error_handler
in
Body.Writer.close body;
write_request t request';
writer_closed t;
read_response t response;
match !received with
| None -> assert false
| Some received ->
Alcotest.(check (list (pair string string))) "headers are equal"
headers (Headers.to_list received.headers);
;;
let test_report_exn () =
let request' = Request.create `GET "/" in
let response = Response.create `OK in (* not actually writen to the channel *)
let error_message = ref None in
let body, t =
request
request'
~response_handler:(default_response_handler response)
~error_handler:(function
| `Exn (Failure msg) -> error_message := Some msg
| _ -> assert false)
in
Body.Writer.close body;
write_request t request';
writer_closed t;
reader_ready t;
report_exn t (Failure "something went wrong");
connection_is_shutdown t;
Alcotest.(check (option string)) "something went wrong"
(Some "something went wrong")
!error_message
;;
let test_input_shrunk () =
let request' = Request.create `GET "/" in
let response = Response.create `OK in (* not actually writen to the channel *)
let error_message = ref None in
let body, t =
request
request'
~response_handler:(default_response_handler response)
~error_handler:(function
| `Exn (Failure msg) -> error_message := Some msg
| _ -> assert false)
in
Body.Writer.close body;
write_request t request';
writer_closed t;
reader_ready t;
let c = feed_string t "HTTP/1.1 200 OK\r\nDate" in
Alcotest.(check int) "read the status line" c 17;
report_exn t (Failure "something went wrong");
connection_is_shutdown t;
Alcotest.(check (option string)) "something went wrong"
(Some "something went wrong")
!error_message
;;
let test_failed_response_parse () =
let request' = Request.create `GET "/" in
let test response bytes_read expected_error =
let error = ref None in
let body, t =
request
request'
~response_handler:(fun _ _ -> assert false)
~error_handler:(fun e -> error := Some e)
in
Body.Writer.close body;
write_request t request';
writer_closed t;
reader_ready t;
let len = feed_string t response in
Alcotest.(check int) "bytes read" len bytes_read;
connection_is_shutdown t;
Alcotest.(check (option response_error)) "Response error"
(Some expected_error) !error;
in
test "HTTP/1.1 200\r\n\r\n" 12 (`Malformed_response ": char ' '");
let response = Response.create `OK ~headers:(Headers.encoding_fixed (-1)) in
test (response_to_string response) 39 (`Invalid_response_body_length response);
;;
let test_schedule_read_with_data_available () =
let request' = Request.create `GET "/" in
let response = Response.create `OK ~headers:(Headers.encoding_fixed 6) in
let body = ref None in
let response_handler response' body' =
body := Some body';
Alcotest.check (module Response) "expected response" response response';
in
let req_body, t =
request request' ~response_handler ~error_handler:no_error_handler
in
Body.Writer.close req_body;
write_request t request';
writer_closed t;
read_response t response;
let body = Option.get !body in
let schedule_read expected =
let did_read = ref false in
Body.Reader.schedule_read body
~on_read:(fun buf ~off ~len ->
let actual = Bstr.sub_string buf ~off ~len in
did_read := true;
Alcotest.(check string) "Body" expected actual)
~on_eof:(fun () -> assert false);
Alcotest.(check bool) "on_read called" true !did_read;
in
(* We get some data on the connection, but not the full response yet. *)
read_string t "Hello";
(* Schedule a read when there is already data available. on_read should be called
straight away, as who knows how long it'll be before more data arrives. *)
schedule_read "Hello";
read_string t "!";
schedule_read "!";
let did_eof = ref false in
Body.Reader.schedule_read body
~on_read:(fun _ ~off:_ ~len:_ -> Alcotest.fail "Expected eof")
~on_eof:(fun () -> did_eof := true);
Alcotest.(check bool) "on_eof called" true !did_eof;
reader_closed t;
;;
let tests =
[ "GET" , `Quick, test_get
; "send streaming body", `Quick, test_send_streaming_body
; "Response EOF", `Quick, test_response_eof
; "Response header order preserved", `Quick, test_response_header_order
; "report_exn" , `Quick, test_report_exn
; "input_shrunk", `Quick, test_input_shrunk
; "failed response parse", `Quick, test_failed_response_parse
; "schedule read with data available", `Quick, test_schedule_read_with_data_available
]

View file

@ -0,0 +1,12 @@
let () =
Alcotest.run "h1 unit tests"
[ "websocket" , Test_websocket.tests
; "version" , Test_version.tests
; "method" , Test_method.tests
; "iovec" , Test_iovec.tests
; "headers" , Test_headers.tests
; "request" , Test_request.tests
; "response" , Test_response.tests
; "client connection", Test_client_connection.tests
; "server connection", Test_server_connection.tests
]

View file

@ -0,0 +1,78 @@
open H1
module Array = ArrayLabels
module List = ListLabels
let check msg ~expect actual =
Alcotest.(check (list (pair string string))) msg expect (Headers.to_list actual)
;;
let test_replace () =
check "replace trailing element"
~expect:["c", "d"; "a", "d"]
(Headers.replace
(Headers.of_list ["c", "d"; "a", "b"])
"a"
"d");
check "replace middle element"
~expect:["e", "f"; "c", "z"; "a", "b"]
(Headers.replace
(Headers.of_list ["e", "f"; "c", "d"; "a", "b"])
"c"
"z");
check "remove multiple trailing elements"
~expect:["c", "d"; "a", "d"]
(Headers.replace
(Headers.of_list [ "c", "d"; "a", "b"; "a", "c"])
"a"
"d");
;;
let test_remove () =
check "remove leading element"
~expect:["c", "d"]
(Headers.remove
(Headers.of_list ["a", "b"; "c", "d"])
"a");
check "remove trailing element"
~expect:["c", "d"]
(Headers.remove
(Headers.of_list ["c", "d"; "a", "b"])
"a");
;;
let test_ci_equal () =
let string_of_char x = String.init 1 (fun _ -> x) in
let ascii =
Array.init (0xff + 1) ~f:Char.chr
|> Array.to_list
in
let ascii_pairs =
List.map ascii ~f:(fun x ->
List.map ascii ~f:(fun y -> x, y))
|> List.concat
in
(* Ensure that the branch free case-insensitive equality check is consistent
* with a naive implementation. *)
List.iter ascii_pairs ~f:(fun (x, y) ->
let char_ci_equal =
Char.compare (Char.lowercase_ascii x) (Char.lowercase_ascii y) = 0
in
let headers_equal =
let headers = Headers.of_list [ string_of_char y, "value" ] in
Headers.mem headers (string_of_char x)
in
Alcotest.(check bool)
(Printf.sprintf "CI: %C = %C" x y)
char_ci_equal
headers_equal)
;;
let tests =
[ "remove" , `Quick, test_remove
; "replace" , `Quick, test_replace
; "CI equal", `Quick, test_ci_equal
]

View file

@ -0,0 +1,43 @@
open H1
open IOVec
(* The length of the buffer is ignored by iovec operations *)
let buffer = Bstr.empty
let test_lengthv () =
Alcotest.(check int) "lengthv [] = 0" (lengthv []) 0;
Alcotest.(check int) "lengthv [iovec] = length iovec"
(lengthv [{ buffer; off = 0; len = 0 }]) (length {buffer; off = 0; len = 0 });
Alcotest.(check int) "lengthv [iovec] = length iovec"
(lengthv [{ buffer; off = 0; len = 10 }]) (length {buffer; off = 0; len = 10 });
;;
let test_shiftv_raises () =
Alcotest.check_raises
"IOVec.shiftv: -1 is a negative number"
(Failure "IOVec.shiftv: -1 is a negative number")
(fun () -> ignore (shiftv [] (-1)));
let test f =
Alcotest.check_raises
"shiftv iovecs n raises when n > lengthv iovecs"
(Failure "shiftv: n > lengthv iovecs")
(fun () -> ignore (f ()))
in
test (fun () -> shiftv [] 1);
test (fun () -> shiftv [{ buffer; off = 0; len = 1 }] 2);
test (fun () -> shiftv [{ buffer; off = 0; len = 1 }; { buffer; off = 0; len = 1 }] 3);
;;
let test_shiftv () =
Alcotest.(check (of_pp pp_hum |> list)) "shiftv [] 0 = []" (shiftv [] 0) [];
Alcotest.(check (of_pp pp_hum |> list)) "shiftv [{... len ... }] len = []"
(shiftv [{ buffer; off = 0; len = 1 }] 1) [];
Alcotest.(check (of_pp pp_hum |> list)) "shiftv [iovec] n when length iovec < n"
(shiftv [{ buffer; off = 0; len = 4 }] 2) [{ buffer; off = 2; len = 2 }];
;;
let tests =
[ "lengthv" , `Quick, test_lengthv
; "shiftv" , `Quick, test_shiftv
; "shiftv raises ", `Quick, test_shiftv_raises
]

View file

@ -0,0 +1,41 @@
open H1
open Method
let test_is_safe () =
Alcotest.(check bool) "GET is safe" (is_safe `GET ) true;
Alcotest.(check bool) "HEAD is safe" (is_safe `HEAD) true;
Alcotest.(check bool) "POST is safe" (is_safe `POST) false;
Alcotest.(check bool) "PUT is safe" (is_safe `PUT ) false;
Alcotest.(check bool) "DELETE is safe" (is_safe `DELETE ) false;
Alcotest.(check bool) "CONNECT is safe" (is_safe `CONNECT) false;
Alcotest.(check bool) "OPTIONS is safe" (is_safe `OPTIONS) true;
Alcotest.(check bool) "TRACE is safe" (is_safe `TRACE ) true;
;;
let test_is_cacheable () =
Alcotest.(check bool) "GET is cacheable" (is_cacheable `GET ) true;
Alcotest.(check bool) "HEAD is cacheable" (is_cacheable `HEAD) true;
Alcotest.(check bool) "POST is cacheable" (is_cacheable `POST) true;
Alcotest.(check bool) "PUT is cacheable" (is_cacheable `PUT ) false;
Alcotest.(check bool) "DELETE is cacheable" (is_cacheable `DELETE ) false;
Alcotest.(check bool) "CONNECT is cacheable" (is_cacheable `CONNECT) false;
Alcotest.(check bool) "OPTIONS is cacheable" (is_cacheable `OPTIONS) false;
Alcotest.(check bool) "TRACE is cacheable" (is_cacheable `TRACE ) false;
;;
let test_is_idempotent () =
Alcotest.(check bool) "GET is idempotent" (is_idempotent `GET ) true;
Alcotest.(check bool) "HEAD is idempotent" (is_idempotent `HEAD) true;
Alcotest.(check bool) "POST is idempotent" (is_idempotent `POST) false;
Alcotest.(check bool) "PUT is idempotent" (is_idempotent `PUT ) true;
Alcotest.(check bool) "DELETE is idempotent" (is_idempotent `DELETE ) true;
Alcotest.(check bool) "CONNECT is idempotent" (is_idempotent `CONNECT) false;
Alcotest.(check bool) "OPTIONS is idempotent" (is_idempotent `OPTIONS) true;
Alcotest.(check bool) "TRACE is idempotent" (is_idempotent `TRACE ) true;
;;
let tests =
[ "is_safe" , `Quick, test_is_safe
; "is_cacheable" , `Quick, test_is_cacheable
; "is_idempotent", `Quick, test_is_idempotent
]

View file

@ -0,0 +1,104 @@
open H1
open Request
open Helpers
let body_length = Alcotest.of_pp Request.Body_length.pp_hum
let check =
let alco =
Alcotest.result
(Alcotest.of_pp pp_hum)
Alcotest.string
in
fun message ~expect input ->
let actual =
Angstrom.parse_string ~consume:All H1_private.Parse.request input
in
Alcotest.check alco message expect actual
;;
let test_parse_valid () =
check
"valid GET without headers"
~expect:(Ok (Request.create `GET "/"))
"GET / HTTP/1.1\r\n\r\n";
check
"valid non-standard method without headers"
~expect:(Ok (Request.create (`Other "some-other-verb") "/"))
"some-other-verb / HTTP/1.1\r\n\r\n";
check
"valid GET with headers"
~expect:(Ok (Request.create ~headers:(Headers.of_list [ "Link", "/path/to/some/website"]) `GET "/"))
"GET / HTTP/1.1\r\nLink: /path/to/some/website\r\n\r\n";
;;
let test_parse_invalid_errors () =
check
"doesn't end"
~expect:(Error ": not enough input")
"GET / HTTP/1.1\r\n";
check
"invalid version"
~expect:(Error "eol: string")
"GET / HTTP/1.22\r\n\r\n";
check
"malformed header"
~expect:(Error "header: char ':'")
"GET / HTTP/1.1\r\nLink : /path/to/some/website\r\n\r\n";
;;
let test_body_length () =
let check message request ~expect =
let actual = Request.body_length request in
Alcotest.check body_length message expect actual
in
let req method_ headers = Request.create method_ ~headers "/" in
check
"no headers"
~expect:(`Fixed 0L)
(req `GET Headers.empty);
check
"single fixed"
~expect:(`Fixed 10L)
(req `GET Headers.(encoding_fixed 10));
check
"negative fixed"
~expect:(`Error `Bad_request)
(req `GET Headers.(encoding_fixed (-10)));
check
"multiple fixed"
~expect:(`Error `Bad_request)
(req `GET Headers.(encoding_fixed 10 @ encoding_fixed 20));
check
"chunked"
~expect:`Chunked
(req `GET Headers.encoding_chunked);
check
"chunked multiple times"
~expect:`Chunked
(req `GET Headers.(encoding_chunked @ encoding_chunked));
let encoding_gzip = Headers.of_list ["transfer-encoding", "gzip"] in
check
"non-chunked transfer-encoding"
~expect:(`Error `Bad_request)
(req `GET encoding_gzip);
check
"chunked after non-chunked"
~expect:`Chunked
(req `GET Headers.(encoding_gzip @ encoding_chunked));
check
"chunked before non-chunked"
~expect:(`Error `Bad_request)
(req `GET Headers.(encoding_chunked @ encoding_gzip));
check
"chunked case-insensitive"
~expect:`Chunked
(req `GET Headers.(of_list ["transfer-encoding", "CHUNKED"]));
;;
let tests =
[ "parse valid" , `Quick, test_parse_valid
; "parse invalid errors", `Quick, test_parse_invalid_errors
; "body length", `Quick, test_body_length
]

View file

@ -0,0 +1,115 @@
open H1
open Response
open Helpers
let body_length = Alcotest.of_pp Response.Body_length.pp_hum
let check =
let alco =
Alcotest.result
(Alcotest.of_pp pp_hum)
Alcotest.string
in
fun message ~expect input ->
let actual =
Angstrom.parse_string ~consume:All H1_private.Parse.response input
in
Alcotest.check alco message expect actual
;;
let test_parse_valid () =
check
"OK response without headers"
~expect:(Ok (Response.create `OK))
"HTTP/1.1 200 OK\r\n\r\n";
;;
let test_parse_invalid_error () =
check
"OK response without a status message"
~expect:(Error ": char ' '")
"HTTP/1.1 200\r\n\r\n";
check
"OK response without a status message"
~expect:(Error ": status-code empty")
"HTTP/1.1 OK\r\n\r\n";
check
"OK response without a status message"
~expect:(Error ": status-code too long: \"999999937377999999999200\"")
"HTTP/1.1 999999937377999999999200\r\n\r\n";
;;
let test_body_length () =
let check message request_method response ~expect =
let actual = Response.body_length response ~request_method in
Alcotest.check body_length message expect actual
in
let res status headers = Response.create status ~headers in
check
"requested HEAD"
~expect:(`Fixed 0L)
`HEAD (res `OK Headers.empty);
check
"requested CONNECT"
~expect:(`Close_delimited)
`CONNECT (res `OK Headers.empty);
check
"status: informational"
~expect:(`Fixed 0L)
`GET (res `Continue Headers.empty);
check
"status: no content"
~expect:(`Fixed 0L)
`GET (res `No_content Headers.empty);
check
"status: not modified"
~expect:(`Fixed 0L)
`GET (res `Not_modified Headers.empty);
check
"no header"
~expect:(`Close_delimited)
`GET (res `OK Headers.empty);
check
"single fixed"
~expect:(`Fixed 10L)
`GET (res `OK Headers.(encoding_fixed 10));
check
"negative fixed"
~expect:(`Error `Internal_server_error)
`GET (res `OK Headers.(encoding_fixed (-10)));
check
"multiple fixed"
~expect:(`Error `Internal_server_error)
`GET (res `OK Headers.(encoding_fixed 10 @ encoding_fixed 20));
check
"chunked"
~expect:`Chunked
`GET (res `OK Headers.encoding_chunked);
check
"chunked multiple times"
~expect:`Chunked
`GET (res `OK Headers.(encoding_chunked @ encoding_chunked));
let encoding_gzip = Headers.of_list ["transfer-encoding", "gzip"] in
check
"non-chunked transfer-encoding"
~expect:`Close_delimited
`GET (res `OK encoding_gzip);
check
"chunked after non-chunked"
~expect:`Chunked
`GET (res `OK Headers.(encoding_gzip @ encoding_chunked));
check
"chunked before non-chunked"
~expect:`Close_delimited
`GET (res `OK Headers.(encoding_chunked @ encoding_gzip));
check
"chunked case-insensitive"
~expect:`Chunked
`GET (res `OK Headers.(of_list ["transfer-encoding", "CHUNKED"]));
;;
let tests =
[ "parse valid" , `Quick, test_parse_valid
; "parse invalid error", `Quick, test_parse_invalid_error
; "body length" , `Quick, test_body_length
]

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,22 @@
open H1
open Version
let v1_0 = { major = 1; minor = 0 }
let v1_1 = { major = 1; minor = 1 }
let test_compare () =
Alcotest.(check int) "compare v1_1 v1_0" (compare v1_1 v1_0) 1;
Alcotest.(check int) "compare v1_1 v1_1" (compare v1_1 v1_1) 0;
Alcotest.(check int) "compare v1_0 v1_0" (compare v1_0 v1_0) 0;
Alcotest.(check int) "compare v1_0 v1_1" (compare v1_0 v1_1) (-1);
;;
let test_to_string () =
Alcotest.(check string) "to_string v1_1" (to_string v1_1) "HTTP/1.1";
Alcotest.(check string) "to_string v1_0" (to_string v1_0) "HTTP/1.0";
;;
let tests =
[ "compare" , `Quick, test_compare
; "to_string", `Quick, test_to_string
]

View file

@ -0,0 +1,46 @@
open H1.Websocket
module Testable = struct
let opcode = Alcotest.testable Opcode.pp_hum ( = )
end
let parse_frame serialized_frame =
match Angstrom.parse_string ~consume:All Frame.parse serialized_frame with
| Ok frame -> frame
| Error err -> Alcotest.fail err
let test_parsing_ping_frame () =
let frame = parse_frame "\137\128\000\000\046\216" in
Alcotest.check Testable.opcode "opcode" `Ping (Frame.opcode frame);
Alcotest.(check bool) "has mask" true (Frame.has_mask frame);
Alcotest.(check int32) "mask" 11992l (Frame.mask_exn frame);
Alcotest.(check int) "payload_length" (Frame.payload_length frame) 0;
Alcotest.(check int) "length" (Frame.length frame) 6
let test_parsing_close_frame () =
let frame = parse_frame "\136\000" in
Alcotest.check Testable.opcode "opcode" `Connection_close
(Frame.opcode frame);
Alcotest.(check int) "payload_length" (Frame.payload_length frame) 0;
Alcotest.(check int) "length" (Frame.length frame) 2
let test_parsing_text_frame () =
let frame =
parse_frame
"\129\139\086\057\046\216\103\011\029\236\099\015\025\224\111\009\036"
in
Alcotest.check Testable.opcode "opcode" `Text (Frame.opcode frame);
Alcotest.(check bool) "has mask" true (Frame.has_mask frame);
Alcotest.(check int32) "mask" 1446588120l (Frame.mask_exn frame);
Alcotest.(check int) "payload_length" (Frame.payload_length frame) 11;
Alcotest.(check int) "length" (Frame.length frame) 17;
Frame.unmask_inplace frame;
let payload = Bstr.to_string (Frame.payload_view frame) in
Alcotest.(check string) "payload" "1234567890\n" payload
let tests =
[
("parsing ping frame", `Quick, test_parsing_ping_frame);
("parsing close frame", `Quick, test_parsing_close_frame);
("parsing text frame", `Quick, test_parsing_text_frame);
]