(*--------------------------------------------------------------------------- Copyright (c) 2025 Anil Madhavapeddy . All rights reserved. SPDX-License-Identifier: ISC ---------------------------------------------------------------------------*) (** Tests for Error module *) module Error = Requests.Error (** {1 is_timeout Tests} *) let test_is_timeout_true () = let e = Error.Timeout { operation = "connect"; duration = Some 30.0 } in Alcotest.(check bool) "timeout is_timeout" true (Error.is_timeout e) let test_is_timeout_false () = let e = Error.Dns_resolution_failed { hostname = "example.com" } in Alcotest.(check bool) "dns is not timeout" false (Error.is_timeout e) (** {1 is_dns Tests} *) let test_is_dns_true () = let e = Error.Dns_resolution_failed { hostname = "example.com" } in Alcotest.(check bool) "dns is_dns" true (Error.is_dns e) let test_is_dns_false () = let e = Error.Timeout { operation = "read"; duration = None } in Alcotest.(check bool) "timeout is not dns" false (Error.is_dns e) (** {1 is_retryable Tests} *) let test_is_retryable_timeout () = let e = Error.Timeout { operation = "connect"; duration = Some 5.0 } in Alcotest.(check bool) "timeout is retryable" true (Error.is_retryable e) let test_is_retryable_dns () = let e = Error.Dns_resolution_failed { hostname = "example.com" } in Alcotest.(check bool) "dns is retryable" true (Error.is_retryable e) let test_is_retryable_503 () = let e = Error.Http_error { url = "https://example.com"; status = 503; reason = "Service Unavailable"; body_preview = None; headers = []; } in Alcotest.(check bool) "503 is retryable" true (Error.is_retryable e) let test_is_retryable_502 () = let e = Error.Http_error { url = "https://example.com"; status = 502; reason = "Bad Gateway"; body_preview = None; headers = []; } in Alcotest.(check bool) "502 is retryable" true (Error.is_retryable e) let test_is_retryable_429 () = let e = Error.Http_error { url = "https://example.com"; status = 429; reason = "Too Many Requests"; body_preview = None; headers = []; } in Alcotest.(check bool) "429 is retryable" true (Error.is_retryable e) let test_is_retryable_connection () = let e = Error.Tcp_connect_failed { host = "example.com"; port = 443; reason = "refused" } in Alcotest.(check bool) "tcp connect failed is retryable" true (Error.is_retryable e) let test_is_retryable_404_false () = let e = Error.Http_error { url = "https://example.com"; status = 404; reason = "Not Found"; body_preview = None; headers = []; } in Alcotest.(check bool) "404 is not retryable" false (Error.is_retryable e) (** {1 is_client_error Tests} *) let test_is_client_error_400 () = let e = Error.Http_error { url = "https://example.com"; status = 400; reason = "Bad Request"; body_preview = None; headers = []; } in Alcotest.(check bool) "400 is client error" true (Error.is_client_error e) let test_is_client_error_404 () = let e = Error.Http_error { url = "https://example.com"; status = 404; reason = "Not Found"; body_preview = None; headers = []; } in Alcotest.(check bool) "404 is client error" true (Error.is_client_error e) let test_is_client_error_499 () = let e = Error.Http_error { url = "https://example.com"; status = 499; reason = "Client Closed Request"; body_preview = None; headers = []; } in Alcotest.(check bool) "499 is client error" true (Error.is_client_error e) let test_client_error_500_false () = let e = Error.Http_error { url = "https://example.com"; status = 500; reason = "Internal Server Error"; body_preview = None; headers = []; } in Alcotest.(check bool) "500 is not client error" false (Error.is_client_error e) (** {1 is_server_error Tests} *) let test_is_server_error_500 () = let e = Error.Http_error { url = "https://example.com"; status = 500; reason = "Internal Server Error"; body_preview = None; headers = []; } in Alcotest.(check bool) "500 is server error" true (Error.is_server_error e) let test_is_server_error_503 () = let e = Error.Http_error { url = "https://example.com"; status = 503; reason = "Service Unavailable"; body_preview = None; headers = []; } in Alcotest.(check bool) "503 is server error" true (Error.is_server_error e) let test_server_error_400_false () = let e = Error.Http_error { url = "https://example.com"; status = 400; reason = "Bad Request"; body_preview = None; headers = []; } in Alcotest.(check bool) "400 is not server error" false (Error.is_server_error e) (** {1 is_security_error Tests} *) let test_is_security_error_tls () = let e = Error.Tls_handshake_failed { host = "example.com"; reason = "cert expired" } in Alcotest.(check bool) "tls is not security error" false (Error.is_security_error e) let test_security_error_body_large () = let e = Error.Body_too_large { limit = 1048576L; actual = Some 2097152L } in Alcotest.(check bool) "body_too_large is security error" true (Error.is_security_error e) let test_security_decompression_bomb () = let e = Error.Decompression_bomb { limit = 10485760L; ratio = 100.0 } in Alcotest.(check bool) "decompression_bomb is security error" true (Error.is_security_error e) let test_security_invalid_header () = let e = Error.Invalid_header { name = "Host"; reason = "contains newline" } in Alcotest.(check bool) "invalid_header is security error" true (Error.is_security_error e) let test_security_timeout_false () = let e = Error.Timeout { operation = "read"; duration = None } in Alcotest.(check bool) "timeout is not security error" false (Error.is_security_error e) (** {1 sanitize_url Tests} *) let test_sanitize_url_no_credentials () = let url = "https://example.com/path" in let sanitized = Error.sanitize_url url in Alcotest.(check string) "no change" "https://example.com/path" sanitized let has_substring s sub = let len_s = String.length s in let len_sub = String.length sub in if len_sub > len_s then false else let found = ref false in for i = 0 to len_s - len_sub do if String.sub s i len_sub = sub then found := true done; !found let test_sanitize_url_with_userinfo () = let url = "https://user:pass@example.com/path" in let sanitized = Error.sanitize_url url in Alcotest.(check bool) "no user:pass" false (has_substring sanitized "user:pass") let test_sanitize_url_user_only () = let url = "https://user@example.com/path" in let sanitized = Error.sanitize_url url in Alcotest.(check bool) "no user@" false (has_substring sanitized "user@") (** {1 is_sensitive_header Tests} *) let test_is_sensitive_authorization () = Alcotest.(check bool) "Authorization is sensitive" true (Error.is_sensitive_header "Authorization") let test_is_sensitive_cookie () = Alcotest.(check bool) "Cookie is sensitive" true (Error.is_sensitive_header "Cookie") let test_is_sensitive_set_cookie () = Alcotest.(check bool) "Set-Cookie is sensitive" true (Error.is_sensitive_header "Set-Cookie") let test_is_sensitive_case_insensitive () = Alcotest.(check bool) "authorization (lowercase) is sensitive" true (Error.is_sensitive_header "authorization") let test_sensitive_content_type_false () = Alcotest.(check bool) "Content-Type is not sensitive" false (Error.is_sensitive_header "Content-Type") (** {1 Error Constructor Tests} *) let test_err_creates_eio_exn () = let exn = Error.err (Error.Timeout { operation = "connect"; duration = Some 5.0 }) in match exn with | Eio.Io (Error.E (Timeout _), _) -> Alcotest.(check pass) "is Eio.Io" () () | _ -> Alcotest.fail "Expected Eio.Io with Timeout" let test_of_eio_exn () = let exn = Error.err (Error.Timeout { operation = "connect"; duration = Some 5.0 }) in match Error.of_eio_exn exn with | Some (Error.Timeout { operation; _ }) -> Alcotest.(check string) "operation" "connect" operation | _ -> Alcotest.fail "Expected Some Timeout" let test_to_string () = let e = Error.Timeout { operation = "connect"; duration = Some 5.0 } in let s = Error.to_string e in Alcotest.(check bool) "contains operation" true (String.length s > 0) (** {1 is_tls Tests} *) let test_is_tls_true () = let e = Error.Tls_handshake_failed { host = "example.com"; reason = "cert invalid" } in Alcotest.(check bool) "tls handshake is_tls" true (Error.is_tls e) let test_is_tls_false () = let e = Error.Dns_resolution_failed { hostname = "example.com" } in Alcotest.(check bool) "dns is not tls" false (Error.is_tls e) (** {1 is_connection Tests} *) let test_is_connection_dns () = let e = Error.Dns_resolution_failed { hostname = "example.com" } in Alcotest.(check bool) "dns is connection" true (Error.is_connection e) let test_is_connection_tcp () = let e = Error.Tcp_connect_failed { host = "example.com"; port = 443; reason = "refused" } in Alcotest.(check bool) "tcp is connection" true (Error.is_connection e) let test_is_connection_tls () = let e = Error.Tls_handshake_failed { host = "example.com"; reason = "cert invalid" } in Alcotest.(check bool) "tls is connection" true (Error.is_connection e) let test_is_connection_timeout_false () = let e = Error.Timeout { operation = "read"; duration = None } in Alcotest.(check bool) "timeout is not connection" false (Error.is_connection e) (** {1 HTTP Status Helpers} *) let test_get_http_status () = let e = Error.Http_error { url = "https://example.com"; status = 404; reason = "Not Found"; body_preview = None; headers = []; } in Alcotest.(check (option int)) "status" (Some 404) (Error.http_status e) let test_get_http_status_none () = let e = Error.Timeout { operation = "read"; duration = None } in Alcotest.(check (option int)) "no status" None (Error.http_status e) let test_get_url () = let e = Error.Http_error { url = "https://example.com/path"; status = 500; reason = "ISE"; body_preview = None; headers = []; } in Alcotest.(check (option string)) "url" (Some "https://example.com/path") (Error.url e) (** {1 Test Suite} *) let suite = ( "error", [ Alcotest.test_case "true for Timeout" `Quick test_is_timeout_true; Alcotest.test_case "false for DNS" `Quick test_is_timeout_false; Alcotest.test_case "true for DNS" `Quick test_is_dns_true; Alcotest.test_case "false for Timeout" `Quick test_is_dns_false; Alcotest.test_case "timeout" `Quick test_is_retryable_timeout; Alcotest.test_case "dns" `Quick test_is_retryable_dns; Alcotest.test_case "503" `Quick test_is_retryable_503; Alcotest.test_case "502" `Quick test_is_retryable_502; Alcotest.test_case "429" `Quick test_is_retryable_429; Alcotest.test_case "connection" `Quick test_is_retryable_connection; Alcotest.test_case "404 not retryable" `Quick test_is_retryable_404_false; Alcotest.test_case "400" `Quick test_is_client_error_400; Alcotest.test_case "404" `Quick test_is_client_error_404; Alcotest.test_case "499" `Quick test_is_client_error_499; Alcotest.test_case "500 not client" `Quick test_client_error_500_false; Alcotest.test_case "500" `Quick test_is_server_error_500; Alcotest.test_case "503" `Quick test_is_server_error_503; Alcotest.test_case "400 not server" `Quick test_server_error_400_false; Alcotest.test_case "TLS" `Quick test_is_security_error_tls; Alcotest.test_case "body too large" `Quick test_security_error_body_large; Alcotest.test_case "decompression bomb" `Quick test_security_decompression_bomb; Alcotest.test_case "invalid header" `Quick test_security_invalid_header; Alcotest.test_case "timeout not security" `Quick test_security_timeout_false; Alcotest.test_case "no credentials" `Quick test_sanitize_url_no_credentials; Alcotest.test_case "with userinfo" `Quick test_sanitize_url_with_userinfo; Alcotest.test_case "with user only" `Quick test_sanitize_url_user_only; Alcotest.test_case "Authorization" `Quick test_is_sensitive_authorization; Alcotest.test_case "Cookie" `Quick test_is_sensitive_cookie; Alcotest.test_case "Set-Cookie" `Quick test_is_sensitive_set_cookie; Alcotest.test_case "case insensitive" `Quick test_is_sensitive_case_insensitive; Alcotest.test_case "Content-Type not sensitive" `Quick test_sensitive_content_type_false; Alcotest.test_case "err creates Eio.Io" `Quick test_err_creates_eio_exn; Alcotest.test_case "of_eio_exn" `Quick test_of_eio_exn; Alcotest.test_case "to_string" `Quick test_to_string; Alcotest.test_case "true for TLS" `Quick test_is_tls_true; Alcotest.test_case "false for DNS" `Quick test_is_tls_false; Alcotest.test_case "DNS" `Quick test_is_connection_dns; Alcotest.test_case "TCP" `Quick test_is_connection_tcp; Alcotest.test_case "TLS" `Quick test_is_connection_tls; Alcotest.test_case "timeout not connection" `Quick test_is_connection_timeout_false; Alcotest.test_case "http_status" `Quick test_get_http_status; Alcotest.test_case "http_status none" `Quick test_get_http_status_none; Alcotest.test_case "url" `Quick test_get_url; ] )