use super::*;
use crate::storage::{CacheData, CacheKey, CachePrefix, GENERIC_CACHE_STORE};
use crate::test_utils::init_test_environment;
use std::time::{SystemTime, UNIX_EPOCH};
use crate::passkey::main::test_utils as passkey_test_utils;
fn create_valid_stored_options() -> StoredOptions {
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
StoredOptions {
challenge: "test_challenge".to_string(),
user: crate::passkey::types::PublicKeyCredentialUserEntity {
user_handle: "test_user_handle".to_string(),
name: "test_user".to_string(),
display_name: "Test User".to_string(),
},
timestamp: now,
ttl: 300, }
}
fn create_expired_stored_options() -> StoredOptions {
let old_timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs()
- 400;
StoredOptions {
challenge: "expired_challenge".to_string(),
user: crate::passkey::types::PublicKeyCredentialUserEntity {
user_handle: "expired_user_handle".to_string(),
name: "expired_user".to_string(),
display_name: "Expired User".to_string(),
},
timestamp: old_timestamp,
ttl: 300, }
}
#[tokio::test]
async fn test_get_and_validate_options_success() {
init_test_environment().await;
let challenge_type = "registration";
let id = "test_challenge_id_success";
let stored_options = create_valid_stored_options();
use crate::storage::{CacheKey, CachePrefix, store_cache_keyed};
let cache_prefix =
CachePrefix::new(challenge_type.to_string()).expect("Failed to create cache prefix");
let cache_key = CacheKey::new(id.to_string()).expect("Failed to create cache key");
store_cache_keyed::<_, PasskeyError>(
cache_prefix,
cache_key,
stored_options.clone(),
300, )
.await
.expect("Failed to store options");
let challenge_type_typed =
crate::passkey::types::ChallengeType::new(challenge_type.to_string()).unwrap();
let challenge_id_typed = crate::passkey::types::ChallengeId::new(id.to_string()).unwrap();
let result = get_and_validate_options(&challenge_type_typed, &challenge_id_typed).await;
assert!(result.is_ok());
let retrieved_options = result.unwrap();
assert_eq!(retrieved_options.challenge, stored_options.challenge);
assert_eq!(retrieved_options.user.name, stored_options.user.name);
}
#[tokio::test]
async fn test_get_and_validate_options_not_found() {
init_test_environment().await;
let challenge_type_typed = crate::passkey::types::ChallengeType::registration();
let challenge_id_typed =
crate::passkey::types::ChallengeId::new("nonexistent".to_string()).unwrap();
let result = get_and_validate_options(&challenge_type_typed, &challenge_id_typed).await;
assert!(result.is_err());
match result.unwrap_err() {
PasskeyError::NotFound(msg) => assert_eq!(msg, "Challenge not found"),
_ => panic!("Expected NotFound error"),
}
}
#[tokio::test]
async fn test_get_and_validate_options_expired() {
init_test_environment().await;
let challenge_type = "registration";
let id = "expired_challenge_id_test";
let expired_options = create_expired_stored_options();
use crate::storage::{CacheKey, CachePrefix, store_cache_keyed};
let cache_prefix =
CachePrefix::new(challenge_type.to_string()).expect("Failed to create cache prefix");
let cache_key = CacheKey::new(id.to_string()).expect("Failed to create cache key");
store_cache_keyed::<_, PasskeyError>(
cache_prefix,
cache_key,
expired_options,
300, )
.await
.expect("Failed to store expired options");
let challenge_type_typed =
crate::passkey::types::ChallengeType::new(challenge_type.to_string()).unwrap();
let challenge_id_typed = crate::passkey::types::ChallengeId::new(id.to_string()).unwrap();
let result = get_and_validate_options(&challenge_type_typed, &challenge_id_typed).await;
assert!(result.is_err());
match result.unwrap_err() {
PasskeyError::Authentication(msg) => {
assert!(msg.contains("Challenge has expired"));
}
_ => panic!("Expected Authentication error for expired challenge"),
}
}
#[tokio::test]
async fn test_remove_options_success() {
init_test_environment().await;
let challenge_type = "authentication";
let id = "remove_challenge_test_id";
let stored_options = create_valid_stored_options();
use crate::storage::{CacheKey, CachePrefix, store_cache_keyed};
let cache_prefix =
CachePrefix::new(challenge_type.to_string()).expect("Failed to create cache prefix");
let cache_key = CacheKey::new(id.to_string()).expect("Failed to create cache key");
store_cache_keyed::<_, PasskeyError>(
cache_prefix,
cache_key,
stored_options,
300, )
.await
.expect("Failed to store options");
let (cache_prefix_verify, cache_key_verify) = (
CachePrefix::new(challenge_type.to_string()).unwrap(),
CacheKey::new(id.to_string()).unwrap(),
);
use crate::storage::get_data;
let before_removal: Option<StoredOptions> =
get_data::<_, PasskeyError>(cache_prefix_verify, cache_key_verify)
.await
.expect("Failed to get from cache");
assert!(before_removal.is_some());
let (cache_prefix_remove, cache_key_remove) = (
CachePrefix::new(challenge_type.to_string()).unwrap(),
CacheKey::new(id.to_string()).unwrap(),
);
let result = remove_options(cache_prefix_remove, cache_key_remove).await;
assert!(result.is_ok());
let (cache_prefix_after, cache_key_after) = (
CachePrefix::new(challenge_type.to_string()).unwrap(),
CacheKey::new(id.to_string()).unwrap(),
);
let after_removal: Option<StoredOptions> =
get_data::<_, PasskeyError>(cache_prefix_after, cache_key_after)
.await
.expect("Failed to get from cache");
assert!(after_removal.is_none());
}
#[tokio::test]
async fn test_remove_options_nonexistent() {
init_test_environment().await;
let (cache_prefix, cache_key) = (
CachePrefix::new("authentication".to_string()).unwrap(),
CacheKey::new("nonexistent".to_string()).unwrap(),
);
let result = remove_options(cache_prefix, cache_key).await;
assert!(result.is_ok());
}
#[tokio::test]
async fn test_ttl_validation_with_passkey_timeout() {
init_test_environment().await;
let challenge_type = "registration";
let id = "ttl_validation_test_id";
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
let stored_options = StoredOptions {
challenge: "ttl_test_challenge".to_string(),
user: crate::passkey::types::PublicKeyCredentialUserEntity {
user_handle: "ttl_test_user_handle".to_string(),
name: "ttl_test_user".to_string(),
display_name: "TTL Test User".to_string(),
},
timestamp: now - (*PASSKEY_CHALLENGE_TIMEOUT as u64) - 1, ttl: 86400, };
use crate::storage::{CacheKey, CachePrefix, store_cache_keyed};
let cache_prefix =
CachePrefix::new(challenge_type.to_string()).expect("Failed to create cache prefix");
let cache_key = CacheKey::new(id.to_string()).expect("Failed to create cache key");
store_cache_keyed::<_, PasskeyError>(
cache_prefix,
cache_key,
stored_options,
300, )
.await
.expect("Failed to store options");
let challenge_type_typed =
crate::passkey::types::ChallengeType::new(challenge_type.to_string()).unwrap();
let challenge_id_typed = crate::passkey::types::ChallengeId::new(id.to_string()).unwrap();
let result = get_and_validate_options(&challenge_type_typed, &challenge_id_typed).await;
assert!(result.is_err());
match result.unwrap_err() {
PasskeyError::Authentication(msg) => {
assert!(msg.contains("Challenge has expired"));
}
_ => panic!("Expected Authentication error for timeout"),
}
}
#[tokio::test]
async fn test_options_cache_basics() {
init_test_environment().await;
let test_key = "test_cache_key";
let test_category = "test_category";
let test_value = "test_value_123".to_string();
let cache_data = CacheData {
value: test_value.clone(),
};
let cache_prefix = CachePrefix::new(test_category.to_string()).unwrap();
let cache_key = CacheKey::new(test_key.to_string()).unwrap();
let result = GENERIC_CACHE_STORE
.lock()
.await
.put_with_ttl(cache_prefix, cache_key, cache_data, 300)
.await;
assert!(result.is_ok(), "Failed to put test data in cache");
let cache_prefix = CachePrefix::new(test_category.to_string()).unwrap();
let cache_key = CacheKey::new(test_key.to_string()).unwrap();
let get_result = GENERIC_CACHE_STORE
.lock()
.await
.get(cache_prefix, cache_key)
.await;
assert!(get_result.is_ok(), "Failed to get test data from cache");
let retrieved_data = get_result.unwrap();
assert!(
retrieved_data.is_some(),
"Cache should contain our test data"
);
assert_eq!(retrieved_data.unwrap().value, test_value);
let (cache_prefix_remove, cache_key_remove) = (
CachePrefix::new(test_category.to_string()).unwrap(),
CacheKey::new(test_key.to_string()).unwrap(),
);
let remove_result =
passkey_test_utils::remove_from_cache(cache_prefix_remove, cache_key_remove).await;
assert!(
remove_result.is_ok(),
"Failed to remove test data from cache"
);
let cache_prefix = CachePrefix::new(test_category.to_string()).unwrap();
let cache_key = CacheKey::new(test_key.to_string()).unwrap();
let final_result = GENERIC_CACHE_STORE
.lock()
.await
.get(cache_prefix, cache_key)
.await
.unwrap();
assert!(
final_result.is_none(),
"Cache should be empty after removal"
);
}
#[tokio::test]
async fn test_challenge_lifecycle_integration() {
use crate::passkey::main::test_utils as passkey_test_utils;
use crate::test_utils::init_test_environment;
init_test_environment().await;
let challenge_type = "test_challenge";
let id = "test_challenge_lifecycle_id";
let challenge_str = "test_challenge_123";
let user_handle = "test_user_handle_challenge";
let name = "Test User Challenge";
let display_name = "Test User Display Name";
let ttl = 300;
let create_result = passkey_test_utils::create_test_challenge(
challenge_type,
id,
challenge_str,
user_handle,
name,
display_name,
ttl,
)
.await;
assert!(create_result.is_ok(), "Failed to create test challenge");
let (cache_prefix_exists, cache_key_exists) = (
CachePrefix::new(challenge_type.to_string()).unwrap(),
CacheKey::new(id.to_string()).unwrap(),
);
let exists =
passkey_test_utils::check_cache_exists(cache_prefix_exists, cache_key_exists).await;
assert!(exists, "Challenge should exist in cache");
let challenge_type_typed =
crate::passkey::types::ChallengeType::new(challenge_type.to_string()).unwrap();
let challenge_id_typed = crate::passkey::types::ChallengeId::new(id.to_string()).unwrap();
let validate_result =
super::get_and_validate_options(&challenge_type_typed, &challenge_id_typed).await;
assert!(validate_result.is_ok(), "Challenge validation failed");
let stored_options = validate_result.unwrap();
assert_eq!(stored_options.challenge, challenge_str);
assert_eq!(stored_options.user.user_handle, user_handle);
assert_eq!(stored_options.user.name, name);
assert_eq!(stored_options.user.display_name, display_name);
let (cache_prefix_remove, cache_key_remove) = (
CachePrefix::new(challenge_type.to_string()).unwrap(),
CacheKey::new(id.to_string()).unwrap(),
);
let remove_result = super::remove_options(cache_prefix_remove, cache_key_remove).await;
assert!(remove_result.is_ok(), "Failed to remove challenge");
let (cache_prefix_check, cache_key_check) = (
CachePrefix::new(challenge_type.to_string()).unwrap(),
CacheKey::new(id.to_string()).unwrap(),
);
let exists_after =
passkey_test_utils::check_cache_exists(cache_prefix_check, cache_key_check).await;
assert!(!exists_after, "Challenge should be removed from cache");
let challenge_type_typed =
crate::passkey::types::ChallengeType::new(challenge_type.to_string()).unwrap();
let challenge_id_typed = crate::passkey::types::ChallengeId::new(id.to_string()).unwrap();
let validate_again =
super::get_and_validate_options(&challenge_type_typed, &challenge_id_typed).await;
assert!(
validate_again.is_err(),
"Challenge should not exist anymore"
);
match validate_again.unwrap_err() {
crate::passkey::errors::PasskeyError::NotFound(_) => {
}
e => panic!("Expected NotFound error, got: {e:?}"),
}
}
#[tokio::test]
async fn test_challenge_expiration() {
use crate::passkey::main::test_utils as passkey_test_utils;
use crate::test_utils::init_test_environment;
init_test_environment().await;
let challenge_type = "test_challenge";
let id = "test_challenge_expiry_id";
let challenge_str = "test_challenge_expiry";
let user_handle = "test_user_handle_expiry";
let name = "Test User Expiry";
let display_name = "Test User Expiry";
let ttl = 1;
let create_result = passkey_test_utils::create_test_challenge(
challenge_type,
id,
challenge_str,
user_handle,
name,
display_name,
ttl,
)
.await;
assert!(create_result.is_ok(), "Failed to create test challenge");
tokio::time::sleep(std::time::Duration::from_secs(2)).await;
let challenge_type_typed =
crate::passkey::types::ChallengeType::new(challenge_type.to_string()).unwrap();
let challenge_id_typed = crate::passkey::types::ChallengeId::new(id.to_string()).unwrap();
let validate_result =
super::get_and_validate_options(&challenge_type_typed, &challenge_id_typed).await;
match validate_result {
Err(crate::passkey::errors::PasskeyError::Authentication(msg)) => {
assert!(msg.contains("expired"), "Error should indicate expiration");
}
Err(crate::passkey::errors::PasskeyError::NotFound(_)) => {
}
_ => panic!("Expected Authentication or NotFound error for expired challenge"),
}
}