#![allow(missing_docs)]
#![allow(clippy::unwrap_used, clippy::expect_used)]
use hsh::api;
use hsh::error::{Error, HashingErrorKind};
#[test]
fn map_argon2_err_wraps_into_hashing_argon2() {
let upstream = password_hash::Error::Password;
let e = api::map_argon2_err(upstream);
let Error::Hashing(inner) = e else {
panic!("expected Error::Hashing");
};
assert_eq!(inner.kind, HashingErrorKind::Argon2);
assert!(!inner.detail.is_empty());
}
#[test]
fn map_scrypt_err_wraps_into_hashing_scrypt() {
let upstream = password_hash::Error::Password;
let e = api::map_scrypt_err(upstream);
let Error::Hashing(inner) = e else {
panic!("expected Error::Hashing");
};
assert_eq!(inner.kind, HashingErrorKind::Scrypt);
}
#[test]
fn map_argon2_err_preserves_upstream_message() {
let upstream = password_hash::Error::Algorithm;
let e = api::map_argon2_err(upstream);
let Error::Hashing(inner) = e else {
unreachable!();
};
assert!(!inner.detail.is_empty());
}
#[test]
fn map_bcrypt_utf8_err_wraps_into_hashing_bcrypt() {
let bad: Vec<u8> = vec![0xff, 0xfe, 0xfd];
let upstream = String::from_utf8(bad).unwrap_err();
let e = api::map_bcrypt_utf8_err(upstream);
let Error::Hashing(inner) = e else {
panic!("expected Error::Hashing");
};
assert_eq!(inner.kind, HashingErrorKind::Bcrypt);
assert!(inner.detail.contains("non-UTF-8"));
}
#[test]
fn pbkdf2_missing_salt_returns_invalid_hash_string() {
let e = api::pbkdf2_missing_salt();
match e {
Error::InvalidHashString(s) => {
assert!(s.contains("salt"));
}
_ => panic!("expected InvalidHashString"),
}
}
#[test]
fn pbkdf2_missing_hash_returns_invalid_hash_string() {
let e = api::pbkdf2_missing_hash();
match e {
Error::InvalidHashString(s) => {
assert!(s.contains("hash"));
}
_ => panic!("expected InvalidHashString"),
}
}
#[test]
fn parse_pbkdf2_params_extracts_i_and_l() {
use hsh::algorithms::pbkdf2::{Pbkdf2Params, Prf};
use hsh::policy::{PolicyBuilder, PrimaryAlgorithm};
use password_hash::PasswordHash;
let policy =
PolicyBuilder::from_preset(&hsh::Policy::owasp_minimum_2025())
.primary(PrimaryAlgorithm::Pbkdf2)
.pbkdf2(Pbkdf2Params {
prf: Prf::Sha256,
iterations: 7,
dk_len: 32,
})
.build()
.unwrap();
let stored = api::hash(&policy, "pw").unwrap();
let parsed = PasswordHash::new(&stored).unwrap();
let (iters, dk) = api::parse_pbkdf2_params(&parsed, 16).unwrap();
assert_eq!(iters, 7);
assert_eq!(dk, 32);
}
#[test]
fn parse_pbkdf2_params_uses_default_dk_len_when_l_missing() {
use hsh::algorithms::pbkdf2::{Pbkdf2Params, Prf};
use hsh::policy::{PolicyBuilder, PrimaryAlgorithm};
use password_hash::PasswordHash;
let policy =
PolicyBuilder::from_preset(&hsh::Policy::owasp_minimum_2025())
.primary(PrimaryAlgorithm::Pbkdf2)
.pbkdf2(Pbkdf2Params {
prf: Prf::Sha256,
iterations: 1,
dk_len: 32,
})
.build()
.unwrap();
let stored = api::hash(&policy, "pw").unwrap();
let parsed = PasswordHash::new(&stored).unwrap();
let (_, dk) = api::parse_pbkdf2_params(&parsed, 99).unwrap();
assert_eq!(dk, 32);
}
fn pbkdf2_phc_with_overrides(replacements: &[(&str, &str)]) -> String {
use hsh::algorithms::pbkdf2::{Pbkdf2Params, Prf};
use hsh::policy::{PolicyBuilder, PrimaryAlgorithm};
let policy =
PolicyBuilder::from_preset(&hsh::Policy::owasp_minimum_2025())
.primary(PrimaryAlgorithm::Pbkdf2)
.pbkdf2(Pbkdf2Params {
prf: Prf::Sha256,
iterations: 1,
dk_len: 32,
})
.build()
.unwrap();
let mut stored = api::hash(&policy, "pw").unwrap();
for (needle, replacement) in replacements {
stored = stored.replacen(needle, replacement, 1);
}
stored
}
#[test]
fn parse_pbkdf2_params_rejects_bad_iteration_decimal() {
use password_hash::PasswordHash;
let phc = pbkdf2_phc_with_overrides(&[("i=1,", "i=notanumber,")]);
let parsed = PasswordHash::new(&phc).unwrap();
let err = api::parse_pbkdf2_params(&parsed, 4).unwrap_err();
match err {
Error::InvalidHashString(s) => {
assert!(s.contains("iteration"));
}
_ => panic!("expected InvalidHashString"),
}
}
#[test]
fn parse_pbkdf2_params_rejects_bad_dk_len_decimal() {
use password_hash::PasswordHash;
let phc = pbkdf2_phc_with_overrides(&[("l=32", "l=notanumber")]);
let parsed = PasswordHash::new(&phc).unwrap();
let err = api::parse_pbkdf2_params(&parsed, 4).unwrap_err();
match err {
Error::InvalidHashString(s) => {
assert!(s.contains("output length"));
}
_ => panic!("expected InvalidHashString"),
}
}
#[test]
fn parse_pbkdf2_params_ignores_unknown_keys() {
use password_hash::PasswordHash;
let phc = pbkdf2_phc_with_overrides(&[("$i=1,", "$foo=bar,i=1,")]);
let parsed = PasswordHash::new(&phc).unwrap();
let (iters, dk) = api::parse_pbkdf2_params(&parsed, 0).unwrap();
assert_eq!(iters, 1);
assert_eq!(dk, 32);
}
#[test]
fn pbkdf2_bad_iter_helper_returns_invalid_hash_string() {
let e = api::pbkdf2_bad_iter();
match e {
Error::InvalidHashString(s) => {
assert!(s.contains("iteration count"));
}
_ => panic!("expected InvalidHashString"),
}
}
#[test]
fn pbkdf2_bad_dk_len_helper_returns_invalid_hash_string() {
let e = api::pbkdf2_bad_dk_len();
match e {
Error::InvalidHashString(s) => {
assert!(s.contains("output length"));
}
_ => panic!("expected InvalidHashString"),
}
}
#[test]
fn bcrypt_requires_utf8_helper_returns_invalid_password() {
let e = api::bcrypt_requires_utf8();
match e {
Error::InvalidPassword(s) => {
assert!(s.contains("bcrypt"));
assert!(s.contains("UTF-8"));
}
_ => panic!("expected InvalidPassword"),
}
}
#[test]
fn bcrypt_verify_requires_utf8_helper_returns_invalid_password() {
let e = api::bcrypt_verify_requires_utf8();
match e {
Error::InvalidPassword(s) => {
assert!(s.contains("verification"));
}
_ => panic!("expected InvalidPassword"),
}
}
#[test]
fn pepper_malformed_prefix_helper_returns_invalid_hash_string() {
let e = api::pepper_malformed_prefix();
match e {
Error::InvalidHashString(s) => {
assert!(s.contains("pepper"));
}
_ => panic!("expected InvalidHashString"),
}
}
#[test]
fn pepper_keyver_not_int_helper_returns_invalid_hash_string() {
let e = api::pepper_keyver_not_int();
match e {
Error::InvalidHashString(s) => {
assert!(s.contains("keyver"));
}
_ => panic!("expected InvalidHashString"),
}
}
#[test]
fn phc_not_recognised_helper_returns_invalid_hash_string() {
let e = api::phc_not_recognised();
match e {
Error::InvalidHashString(s) => {
assert!(s.contains("PHC"));
}
_ => panic!("expected InvalidHashString"),
}
}
#[test]
fn fips_primary_must_be_pbkdf2_helper_includes_primary_name() {
use hsh::policy::PrimaryAlgorithm;
let e =
api::fips_primary_must_be_pbkdf2(PrimaryAlgorithm::Argon2id);
match e {
Error::InvalidParameter(s) => {
assert!(s.contains("Argon2id"));
assert!(s.contains("FIPS"));
assert!(s.contains("PBKDF2"));
}
_ => panic!("expected InvalidParameter"),
}
}
#[test]
fn fips_feature_not_built_helper_mentions_fips_feature_flag() {
let e = api::fips_feature_not_built();
match e {
Error::InvalidParameter(s) => {
assert!(s.contains("fips"));
assert!(s.contains("feature"));
}
_ => panic!("expected InvalidParameter"),
}
}