init projectmycelium

This commit is contained in:
mik-tf
2025-09-01 21:37:01 -04:00
commit b41efb0e99
319 changed files with 128160 additions and 0 deletions

View File

@@ -0,0 +1,75 @@
//! UX Test Assertions
//!
//! Custom assertion functions for UX testing
use serde_json::Value;
/// UX-specific assertions
pub struct UXAssertions;
impl UXAssertions {
/// Assert ResponseBuilder envelope format
pub fn assert_response_envelope(response: &Value) -> Result<(), Box<dyn std::error::Error>> {
if !response.get("success").is_some() {
return Err("Missing 'success' field in response envelope".into());
}
let success = response["success"].as_bool().unwrap_or(false);
if success {
if !response.get("data").is_some() {
return Err("Missing 'data' field in successful response".into());
}
} else {
if !response.get("error").is_some() {
return Err("Missing 'error' field in failed response".into());
}
}
Ok(())
}
/// Assert currency format (TFC base currency)
pub fn assert_currency_format(amount: &Value) -> Result<(), Box<dyn std::error::Error>> {
if !amount.get("amount").is_some() {
return Err("Missing 'amount' field in currency object".into());
}
if !amount.get("currency").is_some() {
return Err("Missing 'currency' field in currency object".into());
}
if !amount.get("formatted").is_some() {
return Err("Missing 'formatted' field in currency object".into());
}
// Validate currency is one of the supported types
let currency = amount["currency"].as_str().unwrap_or("");
match currency {
"TFC" | "USD" | "EUR" | "CAD" => Ok(()),
_ => Err(format!("Unsupported currency: {}", currency).into()),
}
}
/// Assert cart consistency between API and UI
pub fn assert_cart_consistency(api_count: usize, api_total: f64, ui_count: usize, ui_total: f64) -> Result<(), Box<dyn std::error::Error>> {
if api_count != ui_count {
return Err(format!("Cart count mismatch: API shows {}, UI shows {}", api_count, ui_count).into());
}
let total_diff = (api_total - ui_total).abs();
if total_diff > 0.01 { // Allow for small floating point differences
return Err(format!("Cart total mismatch: API shows {}, UI shows {}", api_total, ui_total).into());
}
Ok(())
}
/// Assert page load performance
pub fn assert_page_load_time(load_time_ms: u64, max_allowed_ms: u64) -> Result<(), Box<dyn std::error::Error>> {
if load_time_ms > max_allowed_ms {
return Err(format!("Page load too slow: {}ms > {}ms", load_time_ms, max_allowed_ms).into());
}
Ok(())
}
}

View File

@@ -0,0 +1,11 @@
//! Test Utilities and Helpers
//!
//! Common utilities and helper functions for UX testing
pub mod ux_test_helper;
pub mod assertions;
pub mod test_fixtures;
pub use ux_test_helper::*;
pub use assertions::*;
pub use test_fixtures::*;

View File

@@ -0,0 +1,42 @@
//! Test Fixtures
//!
//! Common test data and setup helpers
use crate::environment::*;
/// Common test data
pub struct TestFixtures;
impl TestFixtures {
/// Sample SSH public key for testing
pub fn sample_ssh_public_key() -> &'static str {
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC7vbqajDhA+17ZAdaZcZSjJF7Dp+iSbq3 test@example.com"
}
/// Sample product data for testing
pub fn sample_product_data() -> serde_json::Value {
serde_json::json!({
"id": "test-product-1",
"name": "Test VM Instance",
"category": "compute",
"price": 15.0,
"currency": "TFC",
"description": "Test virtual machine instance"
})
}
/// Get test environment with pre-configured data
pub async fn setup_test_environment() -> Result<UXTestEnvironment, Box<dyn std::error::Error>> {
// Initialize logging
env_logger::builder()
.filter_level(log::LevelFilter::Info)
.try_init()
.ok();
// Create test environment
let environment = UXTestEnvironment::new().await?;
log::info!("Test environment setup complete");
Ok(environment)
}
}

View File

@@ -0,0 +1,358 @@
//! UX Test Helper
//!
//! High-level interface for UX testing operations
use crate::environment::*;
use std::time::Duration;
use tokio::time::timeout;
/// Main UX test helper providing high-level testing operations
pub struct UXTestHelper<'a> {
pub browser: &'a BrowserManager,
pub api_client: &'a APITestClient,
pub data_manager: &'a TestDataManager,
current_user: Option<TestPersona>,
}
impl<'a> UXTestHelper<'a> {
/// Create a new UX test helper
pub fn new(environment: &'a UXTestEnvironment) -> Self {
Self {
browser: &environment.browser,
api_client: &environment.api_client,
data_manager: &environment.data_manager,
current_user: None,
}
}
// ===== AUTHENTICATION HELPERS =====
/// Register a new user
pub async fn register_user(&mut self, persona: &TestPersona) -> Result<(), Box<dyn std::error::Error>> {
log::info!("Registering user: {}", persona.email);
// Navigate to registration page
self.browser.navigate_to("/register").await?;
self.assert_page_loaded("Register").await?;
// Fill registration form
self.browser.type_text("input[name='email']", &persona.email).await?;
self.browser.type_text("input[name='password']", &persona.password).await?;
self.browser.type_text("input[name='name']", &persona.name).await?;
// Submit form
self.browser.click("button[type='submit']").await?;
// Wait for success or error
self.browser.wait_for_element(".alert, .notification, .success").await?;
// Take screenshot
self.browser.take_screenshot(&format!("register_{}", self.safe_filename(&persona.email))).await?;
Ok(())
}
/// Login as a test persona
pub async fn login_as(&mut self, persona: &TestPersona) -> Result<(), Box<dyn std::error::Error>> {
log::info!("Logging in as: {}", persona.email);
// Navigate to login page
self.browser.navigate_to("/login").await?;
self.assert_page_loaded("Login").await?;
// Fill login form
self.browser.type_text("input[name='email']", &persona.email).await?;
self.browser.type_text("input[name='password']", &persona.password).await?;
// Submit form
self.browser.click("button[type='submit']").await?;
// Wait for dashboard redirect or success
self.browser.wait_for_element(".dashboard, .user-menu, .navbar-user").await?;
// Verify authentication
self.assert_user_is_authenticated().await?;
// Store current user
self.current_user = Some(persona.clone());
// Take screenshot
self.browser.take_screenshot(&format!("login_{}", self.safe_filename(&persona.email))).await?;
Ok(())
}
/// Logout current user
pub async fn logout(&mut self) -> Result<(), Box<dyn std::error::Error>> {
log::info!("Logging out current user");
// Look for logout link/button
if self.browser.element_exists(".user-menu").await {
self.browser.click(".user-menu").await?;
self.browser.wait_for_element("a[href='/logout']").await?;
self.browser.click("a[href='/logout']").await?;
} else {
// Direct navigation to logout
self.browser.navigate_to("/logout").await?;
}
// Wait for redirect to home page
self.browser.wait_for_element(".home, .login-prompt").await?;
self.current_user = None;
// Take screenshot
self.browser.take_screenshot("logout").await?;
Ok(())
}
// ===== SHOPPING HELPERS =====
/// Add product to cart
pub async fn add_product_to_cart(&mut self, product_id: &str) -> Result<(), Box<dyn std::error::Error>> {
log::info!("Adding product to cart: {}", product_id);
// Navigate to product or find it in marketplace
self.browser.navigate_to("/marketplace").await?;
// Find the product
let product_selector = &format!("[data-product-id='{}']", product_id);
self.browser.wait_for_element(product_selector).await?;
// Click add to cart button
let add_to_cart_selector = &format!("{} .add-to-cart, {} button[data-action='add-to-cart']", product_selector, product_selector);
self.browser.click(add_to_cart_selector).await?;
// Wait for cart update notification
self.browser.wait_for_element(".cart-updated, .notification, .alert-success").await?;
// Take screenshot
self.browser.take_screenshot(&format!("add_to_cart_{}", product_id)).await?;
Ok(())
}
/// Buy product directly (Buy Now)
pub async fn buy_now(&mut self, product_id: &str) -> Result<String, Box<dyn std::error::Error>> {
log::info!("Buying product directly: {}", product_id);
// Navigate to product
self.browser.navigate_to("/marketplace").await?;
// Find the product
let product_selector = &format!("[data-product-id='{}']", product_id);
self.browser.wait_for_element(product_selector).await?;
// Click buy now button
let buy_now_selector = &format!("{} .buy-now, {} button[data-action='buy-now']", product_selector, product_selector);
self.browser.click(buy_now_selector).await?;
// Complete checkout flow
self.complete_checkout_flow().await
}
/// Complete checkout flow
pub async fn complete_checkout_flow(&mut self) -> Result<String, Box<dyn std::error::Error>> {
log::info!("Completing checkout flow");
// Wait for checkout page or modal
self.browser.wait_for_element(".checkout, .checkout-modal, .purchase-confirmation").await?;
// If there's a confirm purchase button, click it
if self.browser.element_exists("button[data-action='confirm-purchase']").await {
self.browser.click("button[data-action='confirm-purchase']").await?;
}
// Wait for order confirmation
self.browser.wait_for_element(".order-confirmation, .purchase-success").await?;
// Extract order ID if available
let order_id = if let Ok(order_element) = self.browser.find_element(".order-id, [data-order-id]").await {
order_element.text().await.unwrap_or_else(|_| "unknown".to_string())
} else {
"test-order-id".to_string()
};
// Take screenshot
self.browser.take_screenshot(&format!("order_confirmation_{}", order_id)).await?;
Ok(order_id)
}
/// Navigate to cart
pub async fn navigate_to_cart(&mut self) -> Result<(), Box<dyn std::error::Error>> {
let cart_url = if self.current_user.is_some() {
"/dashboard/cart"
} else {
"/cart"
};
self.browser.navigate_to(cart_url).await?;
self.assert_page_loaded("Cart").await?;
Ok(())
}
// ===== DASHBOARD HELPERS =====
/// Navigate to dashboard section
pub async fn navigate_to_dashboard_section(&mut self, section: &str) -> Result<(), Box<dyn std::error::Error>> {
let url = format!("/dashboard/{}", section);
self.browser.navigate_to(&url).await?;
// Wait for dashboard content
self.browser.wait_for_element(".dashboard-content, .dashboard-section").await?;
// Take screenshot
self.browser.take_screenshot(&format!("dashboard_{}", section)).await?;
Ok(())
}
/// Navigate to wallet
pub async fn navigate_to_wallet(&mut self) -> Result<(), Box<dyn std::error::Error>> {
self.browser.navigate_to("/dashboard/wallet").await?;
self.browser.wait_for_element(".wallet-dashboard, .wallet-content").await?;
// Take screenshot
self.browser.take_screenshot("wallet_dashboard").await?;
Ok(())
}
/// Navigate to settings
pub async fn navigate_to_settings(&mut self) -> Result<(), Box<dyn std::error::Error>> {
self.browser.navigate_to("/dashboard/settings").await?;
self.browser.wait_for_element(".settings-form, .settings-content").await?;
// Take screenshot
self.browser.take_screenshot("settings_page").await?;
Ok(())
}
// ===== SETTINGS HELPERS =====
/// Update profile information
pub async fn update_profile(&mut self, name: &str, country: &str, timezone: &str) -> Result<(), Box<dyn std::error::Error>> {
self.navigate_to_settings().await?;
// Fill profile form
self.browser.type_text("input[name='name']", name).await?;
self.browser.type_text("select[name='country']", country).await?;
self.browser.type_text("select[name='timezone']", timezone).await?;
// Submit form
self.browser.click("button[data-action='update-profile']").await?;
// Wait for success notification
self.browser.wait_for_element(".alert-success, .notification-success").await?;
self.browser.take_screenshot("profile_updated").await?;
Ok(())
}
/// Add SSH key
pub async fn add_ssh_key(&mut self, key_name: &str, public_key: &str) -> Result<(), Box<dyn std::error::Error>> {
self.navigate_to_settings().await?;
// Navigate to SSH keys section
if self.browser.element_exists("a[href='#ssh-keys']").await {
self.browser.click("a[href='#ssh-keys']").await?;
}
// Fill SSH key form
self.browser.type_text("input[name='ssh_key_name']", key_name).await?;
self.browser.type_text("textarea[name='public_key']", public_key).await?;
// Submit form
self.browser.click("button[data-action='add-ssh-key']").await?;
// Wait for success notification
self.browser.wait_for_element(".alert-success, .notification-success").await?;
self.browser.take_screenshot(&format!("ssh_key_added_{}", key_name)).await?;
Ok(())
}
// ===== ASSERTION HELPERS =====
/// Assert that page has loaded with expected title
pub async fn assert_page_loaded(&self, expected_title_part: &str) -> Result<(), Box<dyn std::error::Error>> {
let title = self.browser.get_title().await?;
if !title.to_lowercase().contains(&expected_title_part.to_lowercase()) {
return Err(format!("Expected page title to contain '{}', got '{}'", expected_title_part, title).into());
}
Ok(())
}
/// Assert user is authenticated
pub async fn assert_user_is_authenticated(&self) -> Result<(), Box<dyn std::error::Error>> {
// Check for authentication indicators in UI
if !self.browser.element_exists(".user-menu, .navbar-user, .dashboard-link").await {
return Err("User does not appear to be authenticated (no user menu found)".into());
}
// Validate with API
let auth_status = self.api_client.validate_auth_status().await?;
if !auth_status.authenticated {
return Err("API reports user is not authenticated".into());
}
Ok(())
}
/// Assert cart item count
pub async fn assert_cart_item_count(&self, expected_count: usize) -> Result<(), Box<dyn std::error::Error>> {
// Check UI cart badge
if let Ok(cart_badge) = self.browser.find_element(".cart-badge, .cart-count").await {
let displayed_count = cart_badge.text().await?.parse::<usize>().unwrap_or(0);
if displayed_count != expected_count {
return Err(format!("Expected cart count {}, but UI shows {}", expected_count, displayed_count).into());
}
}
// Validate with API
let cart_state = self.api_client.validate_cart_state().await?;
if cart_state.item_count != expected_count {
return Err(format!("Expected cart count {}, but API shows {}", expected_count, cart_state.item_count).into());
}
Ok(())
}
/// Assert element contains text
pub async fn assert_element_contains_text(&self, selector: &str, expected_text: &str) -> Result<(), Box<dyn std::error::Error>> {
let element_text = self.browser.get_text(selector).await?;
if !element_text.contains(expected_text) {
return Err(format!("Expected element '{}' to contain '{}', but got '{}'", selector, expected_text, element_text).into());
}
Ok(())
}
// ===== UTILITY HELPERS =====
/// Create safe filename from email
fn safe_filename(&self, email: &str) -> String {
email.replace("@", "_at_").replace(".", "_dot_")
}
/// Take screenshot with timestamp
pub async fn take_screenshot(&self, name: &str) -> Result<(), Box<dyn std::error::Error>> {
self.browser.take_screenshot(name).await?;
Ok(())
}
/// Wait for specific duration
pub async fn wait(&self, duration: Duration) {
tokio::time::sleep(duration).await;
}
/// Get current user
pub fn current_user(&self) -> Option<&TestPersona> {
self.current_user.as_ref()
}
}