use crate::test_helpers::{PASSWORD, USERNAME, endpoint};
use proptest::prelude::*;
use proptest::test_runner::Config as ProptestConfig;
use rabbitmq_http_client::{api::Client, password_hashing, requests::UserParams};
use tokio::runtime::Runtime;
fn arb_username() -> impl Strategy<Value = String> {
prop::string::string_regex(r"rust\.tests\.proptest\.users\.[a-zA-Z0-9_-]{8,20}").unwrap()
}
fn arb_password() -> impl Strategy<Value = String> {
prop::string::string_regex(r"[a-zA-Z0-9!@#$%^&*()_+-=]{12,32}").unwrap()
}
fn arb_user_tags() -> impl Strategy<Value = String> {
prop_oneof![
Just("".to_string()),
Just("administrator".to_string()),
Just("monitoring".to_string()),
Just("management".to_string()),
Just("administrator,monitoring".to_string()),
Just("monitoring,management".to_string()),
Just("administrator,management".to_string()),
Just("administrator,monitoring,management".to_string()),
]
}
fn arb_user_params() -> impl Strategy<Value = (String, String, String)> {
(arb_username(), arb_password(), arb_user_tags())
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(10))]
#[test]
fn prop_async_user_create_list_delete(
(username, password, tags) in arb_user_params()
) {
let rt = Runtime::new().unwrap();
rt.block_on(async {
let endpoint = endpoint();
let client = Client::new(&endpoint, USERNAME, PASSWORD);
let _ = client.delete_user(&username, true).await;
let salt = password_hashing::salt();
let password_hash = password_hashing::base64_encoded_salted_password_hash_sha256(&salt, &password);
let params = UserParams {
name: &username,
password_hash: &password_hash,
tags: &tags,
};
let result1 = client.create_user(¶ms).await;
prop_assert!(result1.is_ok(), "Failed to create user: {result1:?}");
let result2 = client.list_users().await;
prop_assert!(result2.is_ok(), "Failed to list users: {result2:?}");
let users = result2.unwrap();
let found_user = users.iter().find(|u| u.name == username);
prop_assert!(found_user.is_some(), "list_users did not include the created user: {}", username);
let user = found_user.unwrap();
prop_assert_eq!(&user.name, &username);
prop_assert_eq!(&user.password_hash, &password_hash);
let result3 = client.get_user(&username).await;
prop_assert!(result3.is_ok(), "Failed to get user info: {result3:?}");
let user_info = result3.unwrap();
prop_assert_eq!(&user_info.name, &username);
prop_assert_eq!(&user_info.password_hash, &password_hash);
let result4 = client.delete_user(&username, false).await;
prop_assert!(result4.is_ok(), "Failed to delete user: {result4:?}");
Ok(())
})?;
}
#[test]
fn prop_async_user_without_permissions(
(username, password) in (arb_username(), arb_password())
) {
let rt = Runtime::new().unwrap();
rt.block_on(async {
let endpoint = endpoint();
let client = Client::new(&endpoint, USERNAME, PASSWORD);
let _ = client.delete_user(&username, true).await;
let salt = password_hashing::salt();
let password_hash = password_hashing::base64_encoded_salted_password_hash_sha256(&salt, &password);
let params = UserParams {
name: &username,
password_hash: &password_hash,
tags: "",
};
let result1 = client.create_user(¶ms).await;
prop_assert!(result1.is_ok(), "Failed to create user without permissions: {result1:?}");
let result2 = client.list_users_without_permissions().await;
prop_assert!(result2.is_ok(), "Failed to list users without permissions: {result2:?}");
let users_without_permissions = result2.unwrap();
let found_user = users_without_permissions.iter().find(|u| u.name == username);
prop_assert!(found_user.is_some(), "list_users_without_permissions did not include the user: {}", username);
let _ = client.delete_user(&username, true).await;
Ok(())
})?;
}
#[test]
fn prop_async_user_idempotent_delete(
username in arb_username()
) {
let rt = Runtime::new().unwrap();
rt.block_on(async {
let endpoint = endpoint();
let client = Client::new(&endpoint, USERNAME, PASSWORD);
let result1 = client.delete_user(&username, true).await;
prop_assert!(result1.is_ok(), "Idempotent delete of non-existent user should succeed: {result1:?}");
let salt = password_hashing::salt();
let password_hash = password_hashing::base64_encoded_salted_password_hash_sha256(&salt, "testpassword");
let params = UserParams {
name: &username,
password_hash: &password_hash,
tags: "monitoring",
};
let result2 = client.create_user(¶ms).await;
prop_assert!(result2.is_ok(), "Failed to create user: {result2:?}");
let result3 = client.delete_user(&username, true).await;
prop_assert!(result3.is_ok(), "Failed to delete existing user: {result3:?}");
let result4 = client.delete_user(&username, true).await;
prop_assert!(result4.is_ok(), "Second idempotent delete should succeed: {result4:?}");
Ok(())
})?;
}
#[test]
fn prop_async_user_bulk_delete(
usernames in prop::collection::vec(arb_username(), 2..4)
) {
let rt = Runtime::new().unwrap();
rt.block_on(async {
let endpoint = endpoint();
let client = Client::new(&endpoint, USERNAME, PASSWORD);
for username in &usernames {
let _ = client.delete_user(username, true).await;
}
for username in &usernames {
let salt = password_hashing::salt();
let password_hash = password_hashing::base64_encoded_salted_password_hash_sha256(&salt, "testpassword");
let params = UserParams {
name: username,
password_hash: &password_hash,
tags: "monitoring",
};
let result1 = client.create_user(¶ms).await;
prop_assert!(result1.is_ok(), "Failed to create user {}: {result1:?}", username);
}
let result2 = client.list_users().await;
prop_assert!(result2.is_ok(), "Failed to list users: {result2:?}");
let users = result2.unwrap();
for username in &usernames {
let found_user = users.iter().any(|u| u.name == *username);
prop_assert!(found_user, "list_users did not include the created user {}", username);
}
let usernames_ref: Vec<&str> = usernames.iter().map(|s| s.as_str()).collect();
let result3 = client.delete_users(usernames_ref).await;
prop_assert!(result3.is_ok(), "Failed to bulk delete users: {result3:?}");
let result4 = client.list_users().await;
prop_assert!(result4.is_ok(), "Failed to list users after bulk delete: {result4:?}");
let users_after_delete = result4.unwrap();
for username in &usernames {
let found_user = users_after_delete.iter().any(|u| u.name == *username);
prop_assert!(!found_user, "list_users still includes deleted user {}", username);
}
Ok(())
})?;
}
#[test]
fn prop_async_user_list_consistency(
usernames in prop::collection::vec(arb_username(), 1..3)
) {
let rt = Runtime::new().unwrap();
rt.block_on(async {
let endpoint = endpoint();
let client = Client::new(&endpoint, USERNAME, PASSWORD);
for username in &usernames {
let _ = client.delete_user(username, true).await;
}
for username in &usernames {
let salt = password_hashing::salt();
let password_hash = password_hashing::base64_encoded_salted_password_hash_sha256(&salt, "testpassword");
let params = UserParams {
name: username,
password_hash: &password_hash,
tags: "administrator",
};
let result1 = client.create_user(¶ms).await;
prop_assert!(result1.is_ok(), "Failed to create user {}: {result1:?}", username);
}
let result2 = client.list_users().await;
prop_assert!(result2.is_ok(), "Failed to list users: {result2:?}");
let users = result2.unwrap();
for username in &usernames {
let found_user = users.iter().any(|u| u.name == *username);
prop_assert!(found_user, "list_users did not include the created user {}", username);
}
for username in &usernames {
let _ = client.delete_user(username, true).await;
}
Ok(())
})?;
}
#[test]
fn prop_async_current_user_info(
_dummy_input in Just(1u8)
) {
let rt = Runtime::new().unwrap();
rt.block_on(async {
let endpoint = endpoint();
let client = Client::new(&endpoint, USERNAME, PASSWORD);
let result1 = client.current_user().await;
prop_assert!(result1.is_ok(), "Failed to get current user info: {result1:?}");
let current_user = result1.unwrap();
prop_assert_eq!(current_user.name, USERNAME.to_string());
Ok(())
})?;
}
}