#![allow(missing_docs)]
#![allow(clippy::unwrap_used, clippy::expect_used)]
use hsh::error::{DecodeError, Error, HashingErrorKind};
#[test]
fn unsupported_algorithm_display() {
let e = Error::UnsupportedAlgorithm("foo".into());
assert_eq!(format!("{e}"), "unsupported hash algorithm: foo");
}
#[test]
fn invalid_hash_string_display() {
let e = Error::InvalidHashString("bad PHC".into());
assert_eq!(format!("{e}"), "invalid hash string: bad PHC");
}
#[test]
fn invalid_parameter_display() {
let e = Error::InvalidParameter("cost out of range".into());
assert_eq!(format!("{e}"), "invalid parameter: cost out of range");
}
#[test]
fn invalid_password_display() {
let e = Error::InvalidPassword("too long".into());
assert_eq!(format!("{e}"), "password rejected: too long");
}
#[test]
fn invalid_salt_display() {
let e = Error::InvalidSalt("not base64".into());
assert_eq!(format!("{e}"), "invalid salt: not base64");
}
#[test]
fn verification_display() {
let e = Error::Verification("stored corrupt".into());
assert_eq!(format!("{e}"), "verification failed: stored corrupt");
}
#[test]
fn invalid_policy_display() {
let e = Error::InvalidPolicy("primary missing".into());
assert_eq!(format!("{e}"), "invalid policy: primary missing");
}
#[test]
fn hashing_error_kind_display_covers_every_variant() {
assert_eq!(format!("{}", HashingErrorKind::Argon2), "argon2");
assert_eq!(format!("{}", HashingErrorKind::Bcrypt), "bcrypt");
assert_eq!(format!("{}", HashingErrorKind::Scrypt), "scrypt");
assert_eq!(format!("{}", HashingErrorKind::Pbkdf2), "pbkdf2");
assert_eq!(
format!("{}", HashingErrorKind::PhcEncoder),
"phc encoder"
);
}
#[test]
fn hashing_error_constructor_threads_kind_and_detail() {
let e =
Error::hashing(HashingErrorKind::Argon2, "memory cost too low");
let rendered = format!("{e}");
assert!(rendered.contains("argon2"));
assert!(rendered.contains("memory cost too low"));
}
#[test]
fn hashing_error_accepts_owned_string() {
let detail = String::from("dynamic detail");
let e = Error::hashing(HashingErrorKind::Bcrypt, detail);
let rendered = format!("{e}");
assert!(rendered.contains("bcrypt"));
assert!(rendered.contains("dynamic detail"));
}
#[test]
fn hashing_error_display_format_is_kind_colon_detail() {
let outer =
Error::hashing(HashingErrorKind::Scrypt, "invalid log_n");
let Error::Hashing(inner) = outer else {
unreachable!("constructor returns Error::Hashing variant");
};
assert_eq!(format!("{inner}"), "scrypt: invalid log_n");
assert_eq!(inner.kind, HashingErrorKind::Scrypt);
assert_eq!(inner.detail, "invalid log_n");
}
#[test]
fn hashing_error_kind_equality_and_ord() {
assert_eq!(HashingErrorKind::Argon2, HashingErrorKind::Argon2);
assert_ne!(HashingErrorKind::Argon2, HashingErrorKind::Bcrypt);
let mut kinds = [
HashingErrorKind::PhcEncoder,
HashingErrorKind::Argon2,
HashingErrorKind::Bcrypt,
];
kinds.sort();
assert_eq!(kinds[0], HashingErrorKind::Argon2);
}
#[test]
fn decode_utf8_display() {
let e = DecodeError::Utf8("invalid byte 0xff".into());
let s = format!("{e}");
assert!(s.starts_with("utf-8 decode:"));
assert!(s.contains("invalid byte 0xff"));
}
#[test]
fn decode_base64_display() {
let e = DecodeError::Base64("invalid padding".into());
let s = format!("{e}");
assert!(s.starts_with("base64 decode:"));
}
#[test]
fn decode_json_display() {
let e = DecodeError::Json("expected `,`".into());
let s = format!("{e}");
assert!(s.starts_with("json decode:"));
}
#[test]
fn error_decode_is_transparent_to_inner() {
let inner = DecodeError::Base64("bad input".into());
let outer = Error::Decode(inner);
assert!(format!("{outer}").starts_with("base64 decode:"));
}
#[test]
fn from_utf8_error_wraps_into_decode_utf8() {
let bad: Vec<u8> = vec![0xff, 0xfe];
let err = std::str::from_utf8(&bad).unwrap_err();
let hsh_err: Error = err.into();
assert!(matches!(hsh_err, Error::Decode(DecodeError::Utf8(_))));
}
#[test]
fn from_base64_decode_error_wraps_into_decode_base64() {
use base64::{engine::general_purpose, Engine as _};
let err = general_purpose::STANDARD.decode("@@@").unwrap_err();
let hsh_err: Error = err.into();
assert!(matches!(hsh_err, Error::Decode(DecodeError::Base64(_))));
}
#[test]
fn from_serde_json_error_wraps_into_decode_json() {
let err = serde_json::from_str::<i32>("{").unwrap_err();
let hsh_err: Error = err.into();
assert!(matches!(hsh_err, Error::Decode(DecodeError::Json(_))));
}
#[cfg(feature = "pepper")]
#[test]
fn from_pepper_error_wraps_into_pepper_variant() {
let pe = hsh_kms::PepperError::UnknownVersion(
hsh_kms::KeyVersion::new(7),
);
let hsh_err: Error = pe.into();
assert!(matches!(hsh_err, Error::Pepper(_)));
assert!(format!("{hsh_err}").contains("pepper provider:"));
}
#[test]
fn error_implements_std_error() {
fn assert_std_error<
T: std::error::Error + Send + Sync + 'static,
>() {
}
assert_std_error::<Error>();
assert_std_error::<DecodeError>();
}
#[test]
fn hashing_error_source_chain() {
let outer =
Error::hashing(HashingErrorKind::Pbkdf2, "iterations < 1");
let Error::Hashing(inner) = outer else {
unreachable!();
};
let as_err: &dyn std::error::Error = &inner;
assert!(as_err.source().is_none());
}
#[test]
fn error_clone_preserves_variant_and_payload() {
let original = Error::InvalidParameter("oops".into());
let cloned = original.clone();
assert_eq!(format!("{original}"), format!("{cloned}"));
}
#[test]
fn hashing_error_clone_preserves_kind_and_detail() {
let outer = Error::hashing(HashingErrorKind::Bcrypt, "cost = 0");
let Error::Hashing(original) = outer else {
unreachable!();
};
let cloned = original.clone();
assert_eq!(cloned.kind, HashingErrorKind::Bcrypt);
assert_eq!(cloned.detail, "cost = 0");
}
#[test]
fn decode_error_clone_round_trip() {
let original = DecodeError::Json("expected `]`".into());
let cloned = original.clone();
assert_eq!(format!("{original}"), format!("{cloned}"));
}
#[test]
fn error_accepts_static_literal_payload() {
let e = Error::InvalidPassword("static literal".into());
let payload: &str = match &e {
Error::InvalidPassword(s) => s,
_ => unreachable!(),
};
assert_eq!(payload, "static literal");
}
#[test]
fn error_accepts_owned_string_payload() {
let dynamic = format!("computed at runtime: {}", 42);
let e = Error::InvalidPassword(dynamic.into());
let payload: &str = match &e {
Error::InvalidPassword(s) => s,
_ => unreachable!(),
};
assert!(payload.contains("42"));
}
#[test]
fn error_is_send_and_sync() {
fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<Error>();
assert_send_sync::<DecodeError>();
assert_send_sync::<HashingErrorKind>();
}