#![allow(missing_docs)]
use entropy_auth::{
ApiKey, HmacRequestSigner, HmacRequestVerifier, Timestamp, extract_bearer_token,
};
#[test]
fn api_key_generate_verify_returns_true() {
let (key, hash) = ApiKey::generate().unwrap();
assert!(
key.verify(&hash),
"generated key should verify against its own hash",
);
}
#[test]
fn api_key_generate_parse_verify_returns_true() {
let (key, hash) = ApiKey::generate().unwrap();
let key_str = key.as_str().to_owned();
let parsed = ApiKey::parse(&key_str).unwrap();
assert!(
parsed.verify(&hash),
"parsed key should verify against the original hash",
);
}
#[test]
fn api_key_wrong_key_verify_fails() {
let (_, hash_a) = ApiKey::generate().unwrap();
let (key_b, _) = ApiKey::generate().unwrap();
assert!(
!key_b.verify(&hash_a),
"a different key should not verify against another key's hash",
);
}
#[test]
fn api_key_format_is_correct() {
let (key, _) = ApiKey::generate().unwrap();
let full = key.as_str();
assert!(full.starts_with("esw_"));
let parts: Vec<&str> = full.splitn(3, '_').collect();
assert_eq!(parts.len(), 3);
assert_eq!(parts[0], "esw");
assert_eq!(parts[1].len(), 8, "prefix should be 8 hex chars");
assert_eq!(parts[2].len(), 64, "secret should be 64 hex chars");
}
#[test]
fn api_key_prefix_matches_hash_prefix() {
let (key, hash) = ApiKey::generate().unwrap();
assert_eq!(key.prefix(), hash.prefix());
}
#[test]
fn hmac_sign_verify_ok() {
let secret = b"shared-secret-key".to_vec();
let signer = HmacRequestSigner::new(secret.clone());
let verifier = HmacRequestVerifier::new(secret);
let method = "POST";
let path = "/api/v1/data";
let timestamp = "1700000000";
let body = b"request body";
let signature = signer.sign(method, path, timestamp, body);
verifier
.verify(method, path, timestamp, body, &signature)
.expect("valid signature should verify");
}
#[test]
fn hmac_sign_tamper_signature_verify_err() {
let secret = b"shared-secret-key".to_vec();
let signer = HmacRequestSigner::new(secret.clone());
let verifier = HmacRequestVerifier::new(secret);
let signature = signer.sign("POST", "/api", "1700000000", b"body");
let mut tampered_bytes = signature.clone().into_bytes();
tampered_bytes[0] ^= 0x01;
let tampered = String::from_utf8(tampered_bytes).unwrap();
let result = verifier.verify("POST", "/api", "1700000000", b"body", &tampered);
assert!(result.is_err());
}
#[test]
fn hmac_sign_tamper_body_verify_err() {
let secret = b"shared-secret-key".to_vec();
let signer = HmacRequestSigner::new(secret.clone());
let verifier = HmacRequestVerifier::new(secret);
let signature = signer.sign("POST", "/api", "1700000000", b"original body");
let result = verifier.verify("POST", "/api", "1700000000", b"tampered body", &signature);
assert!(result.is_err());
}
#[test]
fn hmac_verify_expired_timestamp_with_max_age() {
let secret = b"shared-secret-key".to_vec();
let signer = HmacRequestSigner::new(secret.clone());
let verifier = HmacRequestVerifier::new(secret).with_max_age(300);
let signature = signer.sign("GET", "/", "1000000000", b"");
let result = verifier.verify("GET", "/", "1000000000", b"", &signature);
assert!(result.is_err());
let err = result.unwrap_err();
assert!(
err.is_timestamp_expired(),
"stale timestamp should produce TimestampExpired",
);
}
#[test]
fn hmac_verify_recent_timestamp_with_max_age() {
let secret = b"shared-secret-key".to_vec();
let signer = HmacRequestSigner::new(secret.clone());
let verifier = HmacRequestVerifier::new(secret).with_max_age(300);
let now = Timestamp::now().unix_epoch_secs().to_string();
let signature = signer.sign("GET", "/", &now, b"");
verifier
.verify("GET", "/", &now, b"", &signature)
.expect("current timestamp should be accepted");
}
#[test]
fn hmac_verify_without_max_age_accepts_old_timestamp() {
let secret = b"shared-secret-key".to_vec();
let signer = HmacRequestSigner::new(secret.clone());
let verifier = HmacRequestVerifier::new(secret);
let signature = signer.sign("GET", "/", "1000000000", b"");
verifier
.verify("GET", "/", "1000000000", b"", &signature)
.expect("without max_age, any timestamp should be accepted");
}
#[test]
fn bearer_valid_extraction() {
let token = extract_bearer_token("Bearer my-secret-token").unwrap();
assert_eq!(token, "my-secret-token");
}
#[test]
fn bearer_missing_prefix_returns_err() {
let result = extract_bearer_token("Basic dXNlcjpwYXNz");
assert!(result.is_err());
let err = result.unwrap_err();
assert!(err.is_missing_prefix());
}
#[test]
fn bearer_empty_token_returns_err() {
let result = extract_bearer_token("Bearer ");
assert!(result.is_err());
let err = result.unwrap_err();
assert!(err.is_empty_token());
}
#[test]
fn bearer_empty_header_returns_err() {
let result = extract_bearer_token("");
assert!(result.is_err());
let err = result.unwrap_err();
assert!(err.is_missing_prefix());
}
#[test]
fn bearer_lowercase_rejected() {
let result = extract_bearer_token("bearer xyz");
assert!(result.is_err());
assert!(result.unwrap_err().is_missing_prefix());
}
#[test]
fn bearer_jwt_like_token_accepted() {
let token = extract_bearer_token("Bearer eyJhbGciOiJIUzI1NiJ9.payload.sig").unwrap();
assert_eq!(token, "eyJhbGciOiJIUzI1NiJ9.payload.sig");
}