(*--------------------------------------------------------------------------- Copyright (c) 2025 Anil Madhavapeddy . All rights reserved. SPDX-License-Identifier: ISC ---------------------------------------------------------------------------*) (** Tests for Cache_control module - RFC 9111 compliance *) module Cache_control = Requests.Cache_control (** {1 Response Parsing Tests} *) let test_parse_max_age () = let cc = Cache_control.parse_response "max-age=3600" in Alcotest.(check (option int)) "max_age" (Some 3600) cc.max_age let test_no_cache_no_store () = let cc = Cache_control.parse_response "no-cache, no-store" in Alcotest.(check bool) "no_cache present" true (Option.is_some cc.no_cache); Alcotest.(check bool) "no_store" true cc.no_store let test_public_max_age_immutable () = let cc = Cache_control.parse_response "public, max-age=604800, immutable" in Alcotest.(check bool) "public" true cc.public; Alcotest.(check (option int)) "max_age" (Some 604800) cc.max_age; Alcotest.(check bool) "immutable" true cc.immutable let test_parse_private_must_revalidate () = let cc = Cache_control.parse_response "private, must-revalidate" in Alcotest.(check bool) "private_ present" true (Option.is_some cc.private_); Alcotest.(check bool) "must_revalidate" true cc.must_revalidate let test_parse_s_maxage () = let cc = Cache_control.parse_response "s-maxage=600" in Alcotest.(check (option int)) "s_maxage" (Some 600) cc.s_maxage let test_parse_no_transform () = let cc = Cache_control.parse_response "no-transform" in Alcotest.(check bool) "no_transform" true cc.no_transform let test_parse_proxy_revalidate () = let cc = Cache_control.parse_response "proxy-revalidate" in Alcotest.(check bool) "proxy_revalidate" true cc.proxy_revalidate let test_parse_stale_while_revalidate () = let cc = Cache_control.parse_response "stale-while-revalidate=60" in Alcotest.(check (option int)) "stale_while_revalidate" (Some 60) cc.stale_while_revalidate let test_parse_stale_if_error () = let cc = Cache_control.parse_response "stale-if-error=300" in Alcotest.(check (option int)) "stale_if_error" (Some 300) cc.stale_if_error let test_parse_must_understand () = let cc = Cache_control.parse_response "must-understand" in Alcotest.(check bool) "must_understand" true cc.must_understand (** {1 Request Parsing Tests} *) let test_request_max_age_zero () = let cc = Cache_control.parse_request "max-age=0" in Alcotest.(check (option int)) "req_max_age" (Some 0) cc.req_max_age let test_request_no_cache () = let cc = Cache_control.parse_request "no-cache" in Alcotest.(check bool) "req_no_cache" true cc.req_no_cache let test_request_no_store () = let cc = Cache_control.parse_request "no-store" in Alcotest.(check bool) "req_no_store" true cc.req_no_store let test_request_no_transform () = let cc = Cache_control.parse_request "no-transform" in Alcotest.(check bool) "req_no_transform" true cc.req_no_transform let test_request_only_if_cached () = let cc = Cache_control.parse_request "only-if-cached" in Alcotest.(check bool) "req_only_if_cached" true cc.req_only_if_cached let test_request_min_fresh () = let cc = Cache_control.parse_request "min-fresh=120" in Alcotest.(check (option int)) "req_min_fresh" (Some 120) cc.req_min_fresh (** {1 Cacheability Tests} *) let test_cacheable_200_max_age () = let cc = Cache_control.parse_response "max-age=3600" in Alcotest.(check bool) "cacheable 200 with max-age" true (Cache_control.is_cacheable ~response_cc:cc ~status:200) let test_is_cacheable_no_store () = let cc = Cache_control.parse_response "no-store" in Alcotest.(check bool) "not cacheable with no-store" false (Cache_control.is_cacheable ~response_cc:cc ~status:200) let test_is_cacheable_default_status () = let cc = Cache_control.empty_response in Alcotest.(check bool) "cacheable 200 default" true (Cache_control.is_cacheable ~response_cc:cc ~status:200) let test_cacheable_non_default_status () = let cc = Cache_control.empty_response in Alcotest.(check bool) "non-cacheable 500 without directives" false (Cache_control.is_cacheable ~response_cc:cc ~status:500) let test_is_cacheable_301 () = let cc = Cache_control.empty_response in Alcotest.(check bool) "cacheable 301 by default" true (Cache_control.is_cacheable ~response_cc:cc ~status:301) let test_is_cacheable_404 () = let cc = Cache_control.empty_response in Alcotest.(check bool) "cacheable 404 by default" true (Cache_control.is_cacheable ~response_cc:cc ~status:404) (** {1 Predicate Tests} *) let test_must_revalidate_flag () = let cc = Cache_control.parse_response "must-revalidate" in Alcotest.(check bool) "must_revalidate" true (Cache_control.must_revalidate ~response_cc:cc) let test_revalidate_with_no_cache () = let cc = Cache_control.parse_response "no-cache" in Alcotest.(check bool) "must_revalidate via no-cache" true (Cache_control.must_revalidate ~response_cc:cc) let test_revalidate_with_proxy () = let cc = Cache_control.parse_response "proxy-revalidate" in Alcotest.(check bool) "must_revalidate via proxy-revalidate" true (Cache_control.must_revalidate ~response_cc:cc) let test_must_revalidate_false () = let cc = Cache_control.parse_response "max-age=3600" in Alcotest.(check bool) "must_revalidate false" false (Cache_control.must_revalidate ~response_cc:cc) let test_is_public () = let cc = Cache_control.parse_response "public" in Alcotest.(check bool) "is_public" true (Cache_control.is_public ~response_cc:cc) let test_is_public_false () = let cc = Cache_control.parse_response "private" in Alcotest.(check bool) "is_public false" false (Cache_control.is_public ~response_cc:cc) let test_is_private () = let cc = Cache_control.parse_response "private" in Alcotest.(check bool) "is_private" true (Cache_control.is_private ~response_cc:cc) let test_is_private_false () = let cc = Cache_control.parse_response "public" in Alcotest.(check bool) "is_private false" false (Cache_control.is_private ~response_cc:cc) (** {1 Freshness Lifetime Tests} *) let test_freshness_lifetime_max_age () = let cc = Cache_control.parse_response "max-age=3600" in let fl = Cache_control.freshness_lifetime ~response_cc:cc () in Alcotest.(check (option int)) "freshness_lifetime from max-age" (Some 3600) fl let test_freshness_lifetime_no_directives () = let cc = Cache_control.empty_response in let fl = Cache_control.freshness_lifetime ~response_cc:cc () in Alcotest.(check (option int)) "freshness_lifetime none" None fl (** {1 Empty Values Tests} *) let test_empty_response () = let cc = Cache_control.empty_response in Alcotest.(check (option int)) "max_age" None cc.max_age; Alcotest.(check (option int)) "s_maxage" None cc.s_maxage; Alcotest.(check bool) "no_store" false cc.no_store; Alcotest.(check bool) "must_revalidate" false cc.must_revalidate; Alcotest.(check bool) "public" false cc.public; Alcotest.(check bool) "immutable" false cc.immutable let test_empty_request () = let cc = Cache_control.empty_request in Alcotest.(check (option int)) "req_max_age" None cc.req_max_age; Alcotest.(check bool) "req_no_cache" false cc.req_no_cache; Alcotest.(check bool) "req_no_store" false cc.req_no_store (** {1 Test Suite} *) let suite = ( "cache_control", [ Alcotest.test_case "max-age=3600" `Quick test_parse_max_age; Alcotest.test_case "no-cache, no-store" `Quick test_no_cache_no_store; Alcotest.test_case "public, max-age, immutable" `Quick test_public_max_age_immutable; Alcotest.test_case "private, must-revalidate" `Quick test_parse_private_must_revalidate; Alcotest.test_case "s-maxage=600" `Quick test_parse_s_maxage; Alcotest.test_case "no-transform" `Quick test_parse_no_transform; Alcotest.test_case "proxy-revalidate" `Quick test_parse_proxy_revalidate; Alcotest.test_case "stale-while-revalidate" `Quick test_parse_stale_while_revalidate; Alcotest.test_case "stale-if-error" `Quick test_parse_stale_if_error; Alcotest.test_case "must-understand" `Quick test_parse_must_understand; Alcotest.test_case "max-age=0" `Quick test_request_max_age_zero; Alcotest.test_case "no-cache" `Quick test_request_no_cache; Alcotest.test_case "no-store" `Quick test_request_no_store; Alcotest.test_case "no-transform" `Quick test_request_no_transform; Alcotest.test_case "only-if-cached" `Quick test_request_only_if_cached; Alcotest.test_case "min-fresh=120" `Quick test_request_min_fresh; Alcotest.test_case "200 with max-age" `Quick test_cacheable_200_max_age; Alcotest.test_case "no-store not cacheable" `Quick test_is_cacheable_no_store; Alcotest.test_case "200 default status" `Quick test_is_cacheable_default_status; Alcotest.test_case "500 non-default" `Quick test_cacheable_non_default_status; Alcotest.test_case "301 cacheable by default" `Quick test_is_cacheable_301; Alcotest.test_case "404 cacheable by default" `Quick test_is_cacheable_404; Alcotest.test_case "must-revalidate" `Quick test_must_revalidate_flag; Alcotest.test_case "must-revalidate via no-cache" `Quick test_revalidate_with_no_cache; Alcotest.test_case "must-revalidate via proxy-revalidate" `Quick test_revalidate_with_proxy; Alcotest.test_case "must-revalidate false" `Quick test_must_revalidate_false; Alcotest.test_case "is_public" `Quick test_is_public; Alcotest.test_case "is_public false" `Quick test_is_public_false; Alcotest.test_case "is_private" `Quick test_is_private; Alcotest.test_case "is_private false" `Quick test_is_private_false; Alcotest.test_case "from max-age" `Quick test_freshness_lifetime_max_age; Alcotest.test_case "no directives" `Quick test_freshness_lifetime_no_directives; Alcotest.test_case "empty_response" `Quick test_empty_response; Alcotest.test_case "empty_request" `Quick test_empty_request; ] )