(** Tests for {!Hito_persistence.Bcrypt_hash} — the hashing adapter behind the {!Hito_app.Password_hash.S} port. *) open Hito_persistence let hash_tests = [ ( "a hashed password verifies against itself", `Quick, fun () -> let hash = Bcrypt_hash.hash "correct horse battery staple" in Alcotest.(check bool) "verifies" true (Bcrypt_hash.verify ~hash "correct horse battery staple") ); ( "a wrong password does not verify", `Quick, fun () -> let hash = Bcrypt_hash.hash "correct horse battery staple" in Alcotest.(check bool) "rejects" false (Bcrypt_hash.verify ~hash "Tr0ub4dor&3") ); ( "hashing is salted: the same password hashes differently", `Quick, fun () -> let a = Bcrypt_hash.hash "same password" in let b = Bcrypt_hash.hash "same password" in Alcotest.(check bool) "distinct hashes" false (String.equal a b); Alcotest.(check bool) "both still verify" true (Bcrypt_hash.verify ~hash:a "same password" && Bcrypt_hash.verify ~hash:b "same password") ); ( "a malformed stored hash never verifies", `Quick, fun () -> Alcotest.(check bool) "rejects garbage" false (Bcrypt_hash.verify ~hash:"not a bcrypt hash" "anything") ); ( "the production work factor is pinned high", `Quick, fun () -> (* A bcrypt hash records its own cost in the prefix, $2y$NN$. safepass defaults to 6, far too cheap for a stored verifier; production pins 12. Reading it back from a fresh hash proves the pin took effect. *) let hash = Bcrypt_hash.hash "any password" in Alcotest.(check bool) "hash carries the pinned cost" true (String.length hash > 7 && String.sub hash 4 2 = "12") ); ] let suite = [ ("bcrypt_hash", hash_tests) ]